Skip to content

Commit

Permalink
feat: phpunit 10 support (#137)
Browse files Browse the repository at this point in the history
Co-authored-by: marek <[email protected]>
  • Loading branch information
raneomik and marek authored Dec 13, 2023
1 parent ded4e2d commit 89c1112
Show file tree
Hide file tree
Showing 9 changed files with 407 additions and 144 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ composer require zenstruck/browser --dev

Optionally, enable the provided extension in your `phpunit.xml`:

- PHPUnit 8 or 9 :
```xml
<!-- phpunit.xml -->

Expand All @@ -63,6 +64,17 @@ Optionally, enable the provided extension in your `phpunit.xml`:
</extensions>
```

- PHPUnit 10+ :

```xml
<phpunit>
...
<extensions>
<bootstrap class="Zenstruck\Browser\Test\BrowserExtension" />
</extensions>
</phpunit>
```

This extension provides the following features:

1. Intercepts test errors/failures and saves the browser's source (and screenshot/js console log if
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"justinrainbow/json-schema": "^5.2",
"mtdowling/jmespath.php": "^2.6",
"phpstan/phpstan": "^1.4",
"phpunit/phpunit": "^9.5",
"phpunit/phpunit": "^9.5|^10.4",
"symfony/mime": "^5.4|^6.0|^7.0",
"symfony/panther": "^1.1|^2.0.1",
"symfony/phpunit-bridge": "^6.0|^7.0",
Expand Down
4 changes: 4 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@ parameters:
level: 8
paths:
- src

excludePaths:
# skip phpunit 10 missing classes
- src/Browser/Test/BrowserExtension.php
40 changes: 20 additions & 20 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- https://phpunit.de/manual/current/en/appendixes.configuration.html -->
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
bootstrap="vendor/autoload.php"
failOnRisky="true"
failOnWarning="true"
colors="true"
>
<coverage>
<include>
<directory>./src/</directory>
</include>
</coverage>

<php>
<ini name="error_reporting" value="-1"/>
<server name="KERNEL_CLASS" value="Zenstruck\Browser\Tests\Fixture\Kernel"/>
<server name="SHELL_VERBOSITY" value="-1"/>
<server name="SYMFONY_DEPRECATIONS_HELPER" value="max[self]=0&amp;max[direct]=0"/>
<server name="PANTHER_WEB_SERVER_DIR" value="./tests/Fixture/public"/>
</php>
<coverage/>
<php>
<ini name="error_reporting" value="-1"/>
<server name="KERNEL_CLASS" value="Zenstruck\Browser\Tests\Fixture\Kernel"/>
<server name="SHELL_VERBOSITY" value="-1"/>
<server name="SYMFONY_DEPRECATIONS_HELPER" value="max[self]=0&amp;max[direct]=0"/>
<server name="PANTHER_WEB_SERVER_DIR" value="./tests/Fixture/public"/>
</php>

<testsuites>
<testsuite name="Project Test Suite">
<directory>./tests/</directory>
</testsuite>
</testsuites>
<testsuites>
<testsuite name="Project Test Suite">
<directory>./tests/</directory>
</testsuite>
</testsuites>

<source>
<include>
<directory>./src/</directory>
</include>
</source>
</phpunit>
140 changes: 140 additions & 0 deletions src/Browser/Test/BootstrappedExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
<?php

/*
* This file is part of the zenstruck/browser package.
*
* (c) Kevin Bond <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Zenstruck\Browser\Test;

use PHPUnit\Event\Code\Test;
use PHPUnit\Event\Test\Errored;
use PHPUnit\Event\Test\ErroredSubscriber;
use PHPUnit\Event\Test\Failed;
use PHPUnit\Event\Test\FailedSubscriber;
use PHPUnit\Event\Test\Finished as TestFinishedEvent;
use PHPUnit\Event\Test\FinishedSubscriber as TestFinishedSubscriber;
use PHPUnit\Event\Test\PreparationStarted as TestStartedEvent;
use PHPUnit\Event\Test\PreparationStartedSubscriber as TestStartedSubscriber;
use PHPUnit\Event\TestRunner\Finished as TestRunnerFinishedEvent;
use PHPUnit\Event\TestRunner\FinishedSubscriber as TestRunnerFinishedSubscriber;
use PHPUnit\Event\TestRunner\Started as TestRunnerStartedEvent;
use PHPUnit\Event\TestRunner\StartedSubscriber as TestRunnerStartedSubscriber;
use PHPUnit\Runner\Extension\Facade;
use PHPUnit\Runner\Extension\ParameterCollection;
use PHPUnit\TextUI\Configuration\Configuration;
use Zenstruck\Browser;

class BootstrappedExtension
{
public function bootstrap(Configuration $configuration, Facade $facade, ParameterCollection $parameters): void
{
$extension = new LegacyExtension();

$facade->registerSubscriber(new class($extension) implements TestRunnerStartedSubscriber {
public function __construct(
private LegacyExtension $extension,
) {
}

public function notify(TestRunnerStartedEvent $event): void
{
$this->extension->executeBeforeFirstTest();
}
});

$facade->registerSubscriber(new class($extension) implements TestRunnerFinishedSubscriber {
public function __construct(
private LegacyExtension $extension,
) {
}

public function notify(TestRunnerFinishedEvent $event): void
{
$this->extension->executeAfterLastTest();
}
});

$facade->registerSubscriber(new class($extension) implements TestStartedSubscriber {
public function __construct(
private LegacyExtension $extension,
) {
}
public function notify(TestStartedEvent $event): void
{
$this->extension->executeBeforeTest($event->test()->name());
}
});

$facade->registerSubscriber(new class($extension) implements TestFinishedSubscriber {
public function __construct(
private LegacyExtension $extension,
) {
}

public function notify(TestFinishedEvent $event): void
{
$this->extension->executeAfterTest(
$event->test()->name(),
(float) $event->telemetryInfo()->time()->seconds()
);
}
});

$facade->registerSubscriber(new class($extension) implements ErroredSubscriber {
public function __construct(
private LegacyExtension $extension,
) {
}

public function notify(Errored $event): void
{
$this->extension->executeAfterTestError(
BootstrappedExtension::testName($event->test()),
$event->throwable()->message(),
(float) $event->telemetryInfo()->time()->seconds()
);
}
});

$facade->registerSubscriber(new class($extension) implements FailedSubscriber {
public function __construct(
private LegacyExtension $extension,
) {
}

public function notify(Failed $event): void
{
$this->extension->executeAfterTestFailure(
BootstrappedExtension::testName($event->test()),
$event->throwable()->message(),
(float) $event->telemetryInfo()->time()->seconds())
;
}
});
}

/**
* @internal
*/
public static function testName(Test $test): string
{
if ($test->isTestMethod()) {
return $test->nameWithClass();
}

return $test->name();
}

/**
* @internal
*/
public static function registerBrowser(Browser $browser): void
{
LegacyExtension::registerBrowser($browser);
}
}
134 changes: 17 additions & 117 deletions src/Browser/Test/BrowserExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,124 +17,24 @@
use PHPUnit\Runner\AfterTestHook;
use PHPUnit\Runner\BeforeFirstTestHook;
use PHPUnit\Runner\BeforeTestHook;
use Zenstruck\Browser;

/**
* @author Kevin Bond <[email protected]>
*/
final class BrowserExtension implements BeforeFirstTestHook, BeforeTestHook, AfterTestHook, AfterTestErrorHook, AfterTestFailureHook, AfterLastTestHook
{
/** @var Browser[] */
private static array $registeredBrowsers = [];
private static bool $enabled = false;

/** @var array<string,array<string,string[]>> */
private array $savedArtifacts = [];
use PHPUnit\Runner\Extension\Extension;

if (interface_exists(Extension::class)) {
/**
* @internal
* PHPUnit >= 10.
*/
public static function registerBrowser(Browser $browser): void
{
if (!self::$enabled) {
return;
}

self::$registeredBrowsers[] = $browser;
}

public function executeBeforeFirstTest(): void
{
self::$enabled = true;
}

public function executeBeforeTest(string $test): void
{
self::reset();
}

public function executeAfterTest(string $test, float $time): void
{
foreach (self::$registeredBrowsers as $browser) {
foreach ($browser->savedArtifacts() as $category => $artifacts) {
if (!\count($artifacts)) {
continue;
}

$this->savedArtifacts[$test][$category] = $artifacts;
}
}

self::reset();
}

public function executeAfterLastTest(): void
{
if (empty($this->savedArtifacts)) {
return;
}

echo "\n\nSaved Browser Artifacts:";

foreach ($this->savedArtifacts as $test => $categories) {
echo "\n\n {$test}";

foreach ($categories as $category => $artifacts) {
echo "\n {$category}:";

foreach ($artifacts as $artifact) {
echo "\n * {$artifact}:";
}
}
}
}

public function executeAfterTestError(string $test, string $message, float $time): void
{
self::saveBrowserStates($test, 'error');
}

public function executeAfterTestFailure(string $test, string $message, float $time): void
{
self::saveBrowserStates($test, 'failure');
}

private static function saveBrowserStates(string $test, string $type): void
{
if (empty(self::$registeredBrowsers)) {
return;
}

$filename = \sprintf('%s_%s', $type, self::normalizeTestName($test));

foreach (self::$registeredBrowsers as $i => $browser) {
try {
$browser->saveCurrentState("{$filename}__{$i}");
} catch (\Throwable $e) {
// noop - swallow exceptions related to dumping the current state so as to not
// lose the actual error/failure.
}
}
}

private static function normalizeTestName(string $name): string
{
// Try to match for a numeric data set index. If it didn't, match for a string one.
if (!\preg_match('#^([\w:\\\]+)(.+\#(\d+).+)?$#', $name, $matches)) {
\preg_match('#^([\w:\\\]+)(.+"([\w ]+)".+)?$#', $name, $matches);
}

$normalized = \strtr($matches[1], '\\:', '-_');

if (isset($matches[3])) {
$normalized .= '__data-set-'.\strtr($matches[3], '\\: ', '-_-');
}

return $normalized;
}

private static function reset(): void
{
self::$registeredBrowsers = [];
}
final class BrowserExtension extends BootstrappedExtension implements Extension
{}
} else {
/**
* PHPUnit < 10.
*/
final class BrowserExtension extends LegacyExtension implements
BeforeFirstTestHook,
BeforeTestHook,
AfterTestHook,
AfterLastTestHook,
AfterTestErrorHook,
AfterTestFailureHook
{}
}
Loading

0 comments on commit 89c1112

Please sign in to comment.