Skip to content

Commit 89c1112

Browse files
raneomikmarek
andauthored
feat: phpunit 10 support (#137)
Co-authored-by: marek <[email protected]>
1 parent ded4e2d commit 89c1112

File tree

9 files changed

+407
-144
lines changed

9 files changed

+407
-144
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ composer require zenstruck/browser --dev
5555

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

58+
- PHPUnit 8 or 9 :
5859
```xml
5960
<!-- phpunit.xml -->
6061

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

67+
- PHPUnit 10+ :
68+
69+
```xml
70+
<phpunit>
71+
...
72+
<extensions>
73+
<bootstrap class="Zenstruck\Browser\Test\BrowserExtension" />
74+
</extensions>
75+
</phpunit>
76+
```
77+
6678
This extension provides the following features:
6779

6880
1. Intercepts test errors/failures and saves the browser's source (and screenshot/js console log if

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
"justinrainbow/json-schema": "^5.2",
2727
"mtdowling/jmespath.php": "^2.6",
2828
"phpstan/phpstan": "^1.4",
29-
"phpunit/phpunit": "^9.5",
29+
"phpunit/phpunit": "^9.5|^10.4",
3030
"symfony/mime": "^5.4|^6.0|^7.0",
3131
"symfony/panther": "^1.1|^2.0.1",
3232
"symfony/phpunit-bridge": "^6.0|^7.0",

phpstan.neon

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,7 @@ parameters:
55
level: 8
66
paths:
77
- src
8+
9+
excludePaths:
10+
# skip phpunit 10 missing classes
11+
- src/Browser/Test/BrowserExtension.php

phpunit.xml.dist

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,30 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<!-- https://phpunit.de/manual/current/en/appendixes.configuration.html -->
33
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4-
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
5-
colors="true"
4+
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
65
bootstrap="vendor/autoload.php"
76
failOnRisky="true"
87
failOnWarning="true"
8+
colors="true"
99
>
10-
<coverage>
11-
<include>
12-
<directory>./src/</directory>
13-
</include>
14-
</coverage>
15-
16-
<php>
17-
<ini name="error_reporting" value="-1"/>
18-
<server name="KERNEL_CLASS" value="Zenstruck\Browser\Tests\Fixture\Kernel"/>
19-
<server name="SHELL_VERBOSITY" value="-1"/>
20-
<server name="SYMFONY_DEPRECATIONS_HELPER" value="max[self]=0&amp;max[direct]=0"/>
21-
<server name="PANTHER_WEB_SERVER_DIR" value="./tests/Fixture/public"/>
22-
</php>
10+
<coverage/>
11+
<php>
12+
<ini name="error_reporting" value="-1"/>
13+
<server name="KERNEL_CLASS" value="Zenstruck\Browser\Tests\Fixture\Kernel"/>
14+
<server name="SHELL_VERBOSITY" value="-1"/>
15+
<server name="SYMFONY_DEPRECATIONS_HELPER" value="max[self]=0&amp;max[direct]=0"/>
16+
<server name="PANTHER_WEB_SERVER_DIR" value="./tests/Fixture/public"/>
17+
</php>
2318

24-
<testsuites>
25-
<testsuite name="Project Test Suite">
26-
<directory>./tests/</directory>
27-
</testsuite>
28-
</testsuites>
19+
<testsuites>
20+
<testsuite name="Project Test Suite">
21+
<directory>./tests/</directory>
22+
</testsuite>
23+
</testsuites>
2924

25+
<source>
26+
<include>
27+
<directory>./src/</directory>
28+
</include>
29+
</source>
3030
</phpunit>
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the zenstruck/browser package.
5+
*
6+
* (c) Kevin Bond <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Zenstruck\Browser\Test;
13+
14+
use PHPUnit\Event\Code\Test;
15+
use PHPUnit\Event\Test\Errored;
16+
use PHPUnit\Event\Test\ErroredSubscriber;
17+
use PHPUnit\Event\Test\Failed;
18+
use PHPUnit\Event\Test\FailedSubscriber;
19+
use PHPUnit\Event\Test\Finished as TestFinishedEvent;
20+
use PHPUnit\Event\Test\FinishedSubscriber as TestFinishedSubscriber;
21+
use PHPUnit\Event\Test\PreparationStarted as TestStartedEvent;
22+
use PHPUnit\Event\Test\PreparationStartedSubscriber as TestStartedSubscriber;
23+
use PHPUnit\Event\TestRunner\Finished as TestRunnerFinishedEvent;
24+
use PHPUnit\Event\TestRunner\FinishedSubscriber as TestRunnerFinishedSubscriber;
25+
use PHPUnit\Event\TestRunner\Started as TestRunnerStartedEvent;
26+
use PHPUnit\Event\TestRunner\StartedSubscriber as TestRunnerStartedSubscriber;
27+
use PHPUnit\Runner\Extension\Facade;
28+
use PHPUnit\Runner\Extension\ParameterCollection;
29+
use PHPUnit\TextUI\Configuration\Configuration;
30+
use Zenstruck\Browser;
31+
32+
class BootstrappedExtension
33+
{
34+
public function bootstrap(Configuration $configuration, Facade $facade, ParameterCollection $parameters): void
35+
{
36+
$extension = new LegacyExtension();
37+
38+
$facade->registerSubscriber(new class($extension) implements TestRunnerStartedSubscriber {
39+
public function __construct(
40+
private LegacyExtension $extension,
41+
) {
42+
}
43+
44+
public function notify(TestRunnerStartedEvent $event): void
45+
{
46+
$this->extension->executeBeforeFirstTest();
47+
}
48+
});
49+
50+
$facade->registerSubscriber(new class($extension) implements TestRunnerFinishedSubscriber {
51+
public function __construct(
52+
private LegacyExtension $extension,
53+
) {
54+
}
55+
56+
public function notify(TestRunnerFinishedEvent $event): void
57+
{
58+
$this->extension->executeAfterLastTest();
59+
}
60+
});
61+
62+
$facade->registerSubscriber(new class($extension) implements TestStartedSubscriber {
63+
public function __construct(
64+
private LegacyExtension $extension,
65+
) {
66+
}
67+
public function notify(TestStartedEvent $event): void
68+
{
69+
$this->extension->executeBeforeTest($event->test()->name());
70+
}
71+
});
72+
73+
$facade->registerSubscriber(new class($extension) implements TestFinishedSubscriber {
74+
public function __construct(
75+
private LegacyExtension $extension,
76+
) {
77+
}
78+
79+
public function notify(TestFinishedEvent $event): void
80+
{
81+
$this->extension->executeAfterTest(
82+
$event->test()->name(),
83+
(float) $event->telemetryInfo()->time()->seconds()
84+
);
85+
}
86+
});
87+
88+
$facade->registerSubscriber(new class($extension) implements ErroredSubscriber {
89+
public function __construct(
90+
private LegacyExtension $extension,
91+
) {
92+
}
93+
94+
public function notify(Errored $event): void
95+
{
96+
$this->extension->executeAfterTestError(
97+
BootstrappedExtension::testName($event->test()),
98+
$event->throwable()->message(),
99+
(float) $event->telemetryInfo()->time()->seconds()
100+
);
101+
}
102+
});
103+
104+
$facade->registerSubscriber(new class($extension) implements FailedSubscriber {
105+
public function __construct(
106+
private LegacyExtension $extension,
107+
) {
108+
}
109+
110+
public function notify(Failed $event): void
111+
{
112+
$this->extension->executeAfterTestFailure(
113+
BootstrappedExtension::testName($event->test()),
114+
$event->throwable()->message(),
115+
(float) $event->telemetryInfo()->time()->seconds())
116+
;
117+
}
118+
});
119+
}
120+
121+
/**
122+
* @internal
123+
*/
124+
public static function testName(Test $test): string
125+
{
126+
if ($test->isTestMethod()) {
127+
return $test->nameWithClass();
128+
}
129+
130+
return $test->name();
131+
}
132+
133+
/**
134+
* @internal
135+
*/
136+
public static function registerBrowser(Browser $browser): void
137+
{
138+
LegacyExtension::registerBrowser($browser);
139+
}
140+
}

src/Browser/Test/BrowserExtension.php

Lines changed: 17 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -17,124 +17,24 @@
1717
use PHPUnit\Runner\AfterTestHook;
1818
use PHPUnit\Runner\BeforeFirstTestHook;
1919
use PHPUnit\Runner\BeforeTestHook;
20-
use Zenstruck\Browser;
21-
22-
/**
23-
* @author Kevin Bond <[email protected]>
24-
*/
25-
final class BrowserExtension implements BeforeFirstTestHook, BeforeTestHook, AfterTestHook, AfterTestErrorHook, AfterTestFailureHook, AfterLastTestHook
26-
{
27-
/** @var Browser[] */
28-
private static array $registeredBrowsers = [];
29-
private static bool $enabled = false;
30-
31-
/** @var array<string,array<string,string[]>> */
32-
private array $savedArtifacts = [];
20+
use PHPUnit\Runner\Extension\Extension;
3321

22+
if (interface_exists(Extension::class)) {
3423
/**
35-
* @internal
24+
* PHPUnit >= 10.
3625
*/
37-
public static function registerBrowser(Browser $browser): void
38-
{
39-
if (!self::$enabled) {
40-
return;
41-
}
42-
43-
self::$registeredBrowsers[] = $browser;
44-
}
45-
46-
public function executeBeforeFirstTest(): void
47-
{
48-
self::$enabled = true;
49-
}
50-
51-
public function executeBeforeTest(string $test): void
52-
{
53-
self::reset();
54-
}
55-
56-
public function executeAfterTest(string $test, float $time): void
57-
{
58-
foreach (self::$registeredBrowsers as $browser) {
59-
foreach ($browser->savedArtifacts() as $category => $artifacts) {
60-
if (!\count($artifacts)) {
61-
continue;
62-
}
63-
64-
$this->savedArtifacts[$test][$category] = $artifacts;
65-
}
66-
}
67-
68-
self::reset();
69-
}
70-
71-
public function executeAfterLastTest(): void
72-
{
73-
if (empty($this->savedArtifacts)) {
74-
return;
75-
}
76-
77-
echo "\n\nSaved Browser Artifacts:";
78-
79-
foreach ($this->savedArtifacts as $test => $categories) {
80-
echo "\n\n {$test}";
81-
82-
foreach ($categories as $category => $artifacts) {
83-
echo "\n {$category}:";
84-
85-
foreach ($artifacts as $artifact) {
86-
echo "\n * {$artifact}:";
87-
}
88-
}
89-
}
90-
}
91-
92-
public function executeAfterTestError(string $test, string $message, float $time): void
93-
{
94-
self::saveBrowserStates($test, 'error');
95-
}
96-
97-
public function executeAfterTestFailure(string $test, string $message, float $time): void
98-
{
99-
self::saveBrowserStates($test, 'failure');
100-
}
101-
102-
private static function saveBrowserStates(string $test, string $type): void
103-
{
104-
if (empty(self::$registeredBrowsers)) {
105-
return;
106-
}
107-
108-
$filename = \sprintf('%s_%s', $type, self::normalizeTestName($test));
109-
110-
foreach (self::$registeredBrowsers as $i => $browser) {
111-
try {
112-
$browser->saveCurrentState("{$filename}__{$i}");
113-
} catch (\Throwable $e) {
114-
// noop - swallow exceptions related to dumping the current state so as to not
115-
// lose the actual error/failure.
116-
}
117-
}
118-
}
119-
120-
private static function normalizeTestName(string $name): string
121-
{
122-
// Try to match for a numeric data set index. If it didn't, match for a string one.
123-
if (!\preg_match('#^([\w:\\\]+)(.+\#(\d+).+)?$#', $name, $matches)) {
124-
\preg_match('#^([\w:\\\]+)(.+"([\w ]+)".+)?$#', $name, $matches);
125-
}
126-
127-
$normalized = \strtr($matches[1], '\\:', '-_');
128-
129-
if (isset($matches[3])) {
130-
$normalized .= '__data-set-'.\strtr($matches[3], '\\: ', '-_-');
131-
}
132-
133-
return $normalized;
134-
}
135-
136-
private static function reset(): void
137-
{
138-
self::$registeredBrowsers = [];
139-
}
26+
final class BrowserExtension extends BootstrappedExtension implements Extension
27+
{}
28+
} else {
29+
/**
30+
* PHPUnit < 10.
31+
*/
32+
final class BrowserExtension extends LegacyExtension implements
33+
BeforeFirstTestHook,
34+
BeforeTestHook,
35+
AfterTestHook,
36+
AfterLastTestHook,
37+
AfterTestErrorHook,
38+
AfterTestFailureHook
39+
{}
14040
}

0 commit comments

Comments
 (0)