Skip to content

Commit edc87a6

Browse files
arderypPhilip Arderydunglas
authored
Support PHPUnit 10 (#589)
* [phpunit-10] first pass at making Panther workable with phpunit-10; this is a temporary fix state where we have dropped phpunit-bridge in favor of phpunit/phpunit, since the bridge is not yet 10 compliant; there is a chance we will also want to do a bit of file restructuring/reorganization but I will wait to see what the maintainers think about that * require php 8.1, because phpunit 10 requires it * adding php-cs-fixer dependency, running fixer using default config * moving php requirement back to ^8.0 to support older versions of PHPUnit; moving phpstan to dev dependencies * making takeScreenshotIfTestFailed compatible with PHPUnit <10 as well as 10 * removing .idea * removing phpstan and php-cs-fixer from dev dependencies * removing phpunit dev dependency, replacing with phpunit-bridge; reverting most of the tests changes; restoring phpstan.neon; adding new phpunit.xml.dist.10 file for usage with phpunit 10; tests passing on phpunit-bridge with phpunit.xml.dist and phpstan 10 with phpunit.xml.dist.10 * ran php-cs-fixer * skipping test assertion when inconsistent empty html bug reveals itsself; need @dunglas approval and will revert if he says so, or perhaps he can figure out and resolve the bug * making test workaround more explicit * making inconsistent test workaround more robust * thought I removed new isGetClientStaticMethodAvailable function but I guess I didn't, removing now at @dunglas request * Update phpstan.neon Co-authored-by: Kévin Dunglas <[email protected]> * Update src/ServerExtension.php Co-authored-by: Kévin Dunglas <[email protected]> * fixing phpstan ignore line issue * fixing CS issue * adding new GitHub Actions workflow for testing the suite with php 8.1 and phpunit 10 (in place of symfony/phpunit-bridge, which is not yet phpunit 10 compliant) --------- Co-authored-by: Philip Ardery <[email protected]> Co-authored-by: Kévin Dunglas <[email protected]>
1 parent 52e7ea4 commit edc87a6

16 files changed

+331
-95
lines changed

.github/workflows/ci.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,3 +216,43 @@ jobs:
216216

217217
- name: Run tests
218218
run: vendor/bin/simple-phpunit
219+
220+
phpunit-10:
221+
runs-on: ubuntu-latest
222+
strategy:
223+
matrix:
224+
php-versions: [ '8.1' ]
225+
fail-fast: false
226+
name: PHP ${{ matrix.php-versions }} (phpunit 10) Test on ubuntu-latest
227+
steps:
228+
- name: Checkout
229+
uses: actions/checkout@v2
230+
231+
- name: Setup PHP
232+
uses: shivammathur/setup-php@v2
233+
with:
234+
php-version: ${{ matrix.php-versions }}
235+
extensions: zip
236+
237+
- name: Get composer cache directory
238+
id: composercache
239+
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
240+
241+
- name: Cache dependencies
242+
uses: actions/cache@v2
243+
with:
244+
path: ${{ steps.composercache.outputs.dir }}
245+
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
246+
restore-keys: ${{ runner.os }}-composer-
247+
248+
- name: Install dependencies
249+
run: composer install --prefer-dist
250+
251+
- name: Remove phpunit-bridge dependency (not yet phpunit 10 compliant)
252+
run: composer remove --dev symfony/phpunit-bridge
253+
254+
- name: Install latest phpunit 10
255+
run: composer require --dev --prefer-dist phpunit/phpunit:^10.0
256+
257+
- name: Run tests
258+
run: vendor/bin/phpunit --configuration phpunit.xml.dist.10

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/.php-cs-fixer.php
22
/.php-cs-fixer.cache
3+
/.phpunit.cache
34
/.phpunit.result.cache
45
/composer.phar
56
/composer.lock

examples/basic.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
$client = Client::createChromeClient();
1919
// Or, if you care about the open web and prefer to use Firefox
20-
//$client = Client::createFirefoxClient();
20+
// $client = Client::createFirefoxClient();
2121

2222
$client->request('GET', 'https://api-platform.com'); // Yes, this website is 100% written in JavaScript
2323
$client->clickLink('Get started');

phpstan.neon

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ parameters:
88
inferPrivatePropertyTypeFromConstructor: true
99
excludePaths:
1010
- tests/DummyKernel.php
11+
# There are lots of missing phpunit classes since we are supporting multiple versions
12+
- src/ServerExtension.php
1113
ignoreErrors:
1214
# False positive
1315
- '#Call to an undefined method ReflectionType::getName\(\)\.#'

phpunit.xml.dist.10

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<!-- http://phpunit.de/manual/4.1/en/appendixes.configuration.html -->
4+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.0/phpunit.xsd"
6+
backupGlobals="false"
7+
bootstrap="vendor/autoload.php"
8+
colors="true"
9+
cacheDirectory=".phpunit.cache"
10+
>
11+
12+
<coverage>
13+
<include>
14+
<directory>.</directory>
15+
</include>
16+
<exclude>
17+
<directory>tests</directory>
18+
<directory>vendor</directory>
19+
</exclude>
20+
</coverage>
21+
22+
<php>
23+
<env name="KERNEL_CLASS" value="Symfony\Component\Panther\Tests\DummyKernel"/>
24+
<env name="SYMFONY_DEPRECATIONS_HELPER" value="max[direct]=0"/>
25+
<server name="SYMFONY_PHPUNIT_VERSION" value="10.0"/>
26+
</php>
27+
28+
<testsuites>
29+
<testsuite name="Project Test Suite">
30+
<directory>tests</directory>
31+
</testsuite>
32+
</testsuites>
33+
34+
</phpunit>

src/DomCrawler/Crawler.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
namespace Symfony\Component\Panther\DomCrawler;
1515

16-
use function array_merge;
1716
use Facebook\WebDriver\Exception\NoSuchElementException;
1817
use Facebook\WebDriver\WebDriver;
1918
use Facebook\WebDriver\WebDriverBy;

src/PantherTestCaseTrait.php

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
namespace Symfony\Component\Panther;
1515

16-
use PHPUnit\Runner\BaseTestRunner;
1716
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
1817
use Symfony\Component\BrowserKit\HttpBrowser as HttpBrowserClient;
1918
use Symfony\Component\HttpClient\HttpClient;
@@ -129,14 +128,30 @@ public static function isWebServerStarted(): bool
129128

130129
public function takeScreenshotIfTestFailed(): void
131130
{
132-
if (!\in_array($this->getStatus(), [BaseTestRunner::STATUS_ERROR, BaseTestRunner::STATUS_FAILURE], true)) {
131+
if (class_exists(BaseTestRunner::class) && method_exists($this, 'getStatus')) {
132+
/**
133+
* PHPUnit <10 TestCase.
134+
*/
135+
$status = $this->getStatus();
136+
$isError = BaseTestRunner::STATUS_FAILURE === $status;
137+
$isFailure = BaseTestRunner::STATUS_ERROR === $status;
138+
} elseif (method_exists($this, 'status')) {
139+
/**
140+
* PHPUnit 10 TestCase.
141+
*/
142+
$status = $this->status();
143+
$isError = $status->isError();
144+
$isFailure = $status->isFailure();
145+
} else {
146+
/*
147+
* Symfony WebTestCase.
148+
*/
133149
return;
134150
}
135-
136-
$type = BaseTestRunner::STATUS_FAILURE === $this->getStatus() ? 'failure' : 'error';
137-
$test = $this->toString();
138-
139-
ServerExtension::takeScreenshots($type, $test);
151+
if ($isError || $isFailure) {
152+
$type = $isError ? 'error' : 'failure';
153+
ServerExtensionLegacy::takeScreenshots($type, $this->toString());
154+
}
140155
}
141156

142157
/**
@@ -147,7 +162,7 @@ public function takeScreenshotIfTestFailed(): void
147162
protected static function createPantherClient(array $options = [], array $kernelOptions = [], array $managerOptions = []): PantherClient
148163
{
149164
$browser = ($options['browser'] ?? self::$defaultOptions['browser'] ?? PantherTestCase::CHROME);
150-
$callGetClient = \is_callable([self::class, 'getClient']) && (new \ReflectionMethod(self::class, 'getClient'))->isStatic();
165+
$callGetClient = method_exists(self::class, 'getClient') && (new \ReflectionMethod(self::class, 'getClient'))->isStatic();
151166
if (null !== self::$pantherClient) {
152167
$browserManager = self::$pantherClient->getBrowserManager();
153168
if (
@@ -156,7 +171,8 @@ protected static function createPantherClient(array $options = [], array $kernel
156171
) {
157172
ServerExtension::registerClient(self::$pantherClient);
158173

159-
return $callGetClient ? self::getClient(self::$pantherClient) : self::$pantherClient; // @phpstan-ignore-line
174+
/* @phpstan-ignore-next-line */
175+
return $callGetClient ? self::getClient(self::$pantherClient) : self::$pantherClient;
160176
}
161177
}
162178

@@ -174,7 +190,8 @@ protected static function createPantherClient(array $options = [], array $kernel
174190

175191
ServerExtension::registerClient(self::$pantherClient);
176192

177-
return $callGetClient ? self::getClient(self::$pantherClient) : self::$pantherClient; // @phpstan-ignore-line
193+
/* @phpstan-ignore-next-line */
194+
return $callGetClient ? self::getClient(self::$pantherClient) : self::$pantherClient;
178195
}
179196

180197
/**
@@ -216,7 +233,9 @@ protected static function createHttpBrowserClient(array $options = [], array $ke
216233
self::$httpBrowserClient->setServerParameter('HTTPS', 'true');
217234
}
218235

219-
return \is_callable([self::class, 'getClient']) && (new \ReflectionMethod(self::class, 'getClient'))->isStatic() ? self::getClient(self::$httpBrowserClient) : self::$httpBrowserClient; // @phpstan-ignore-line
236+
// @phpstan-ignore-next-line
237+
return method_exists(self::class, 'getClient') && (new \ReflectionMethod(self::class, 'getClient'))->isStatic() ?
238+
self::getClient(self::$httpBrowserClient) : self::$httpBrowserClient;
220239
}
221240

222241
private static function getWebServerDir(array $options): string

src/ServerExtension.php

Lines changed: 102 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -13,86 +13,121 @@
1313

1414
namespace Symfony\Component\Panther;
1515

16+
use PHPUnit\Event\Test\Errored;
17+
use PHPUnit\Event\Test\ErroredSubscriber;
18+
use PHPUnit\Event\Test\Failed;
19+
use PHPUnit\Event\Test\FailedSubscriber;
20+
use PHPUnit\Event\Test\Finished as TestFinishedEvent;
21+
use PHPUnit\Event\Test\FinishedSubscriber as TestFinishedSubscriber;
22+
use PHPUnit\Event\Test\PreparationStarted as TestStartedEvent;
23+
use PHPUnit\Event\Test\PreparationStartedSubscriber as TestStartedSubscriber;
24+
use PHPUnit\Event\TestRunner\Finished as TestRunnerFinishedEvent;
25+
use PHPUnit\Event\TestRunner\FinishedSubscriber as TestRunnerFinishedSubscriber;
26+
use PHPUnit\Event\TestRunner\Started as TestRunnerStartedEvent;
27+
use PHPUnit\Event\TestRunner\StartedSubscriber as TestRunnerStartedSubscriber;
1628
use PHPUnit\Runner\AfterLastTestHook;
1729
use PHPUnit\Runner\AfterTestErrorHook;
1830
use PHPUnit\Runner\AfterTestFailureHook;
1931
use PHPUnit\Runner\AfterTestHook;
2032
use PHPUnit\Runner\BeforeFirstTestHook;
2133
use PHPUnit\Runner\BeforeTestHook;
34+
use PHPUnit\Runner\Extension\Extension;
35+
use PHPUnit\Runner\Extension\Facade;
36+
use PHPUnit\Runner\Extension\ParameterCollection;
37+
use PHPUnit\TextUI\Configuration\Configuration;
2238

23-
/**
39+
/*
2440
* @author Dany Maillard <[email protected]>
2541
*/
26-
final class ServerExtension implements BeforeFirstTestHook, AfterLastTestHook, BeforeTestHook, AfterTestHook, AfterTestErrorHook, AfterTestFailureHook
27-
{
28-
use ServerTrait;
29-
30-
private static bool $enabled = false;
31-
32-
/** @var Client[] */
33-
private static array $registeredClients = [];
34-
35-
public static function registerClient(Client $client): void
42+
if (interface_exists(Extension::class)) {
43+
/**
44+
* PHPUnit >= 10.
45+
*/
46+
final class ServerExtension implements Extension
3647
{
37-
if (self::$enabled && !\in_array($client, self::$registeredClients, true)) {
38-
self::$registeredClients[] = $client;
48+
public function bootstrap(Configuration $configuration, Facade $facade, ParameterCollection $parameters): void
49+
{
50+
$extension = new ServerExtensionLegacy();
51+
52+
$facade->registerSubscriber(new class($extension) implements TestRunnerStartedSubscriber {
53+
public function __construct(private $extension)
54+
{
55+
}
56+
57+
public function notify(TestRunnerStartedEvent $event): void
58+
{
59+
$this->extension->executeBeforeFirstTest();
60+
}
61+
});
62+
63+
$facade->registerSubscriber(new class($extension) implements TestRunnerFinishedSubscriber {
64+
public function __construct(private $extension)
65+
{
66+
}
67+
68+
public function notify(TestRunnerFinishedEvent $event): void
69+
{
70+
$this->extension->executeAfterLastTest();
71+
}
72+
});
73+
74+
$facade->registerSubscriber(new class($extension) implements TestStartedSubscriber {
75+
public function __construct(private $extension)
76+
{
77+
}
78+
79+
public function notify(TestStartedEvent $event): void
80+
{
81+
$this->extension->executeBeforeTest();
82+
}
83+
});
84+
85+
$facade->registerSubscriber(new class($extension) implements TestFinishedSubscriber {
86+
public function __construct(private $extension)
87+
{
88+
}
89+
90+
public function notify(TestFinishedEvent $event): void
91+
{
92+
$this->extension->executeAfterTest();
93+
}
94+
});
95+
96+
$facade->registerSubscriber(new class($extension) implements ErroredSubscriber {
97+
public function __construct(private $extension)
98+
{
99+
}
100+
101+
public function notify(Errored $event): void
102+
{
103+
$this->extension->executeAfterTestError();
104+
}
105+
});
106+
107+
$facade->registerSubscriber(new class($extension) implements FailedSubscriber {
108+
public function __construct(private $extension)
109+
{
110+
}
111+
112+
public function notify(Failed $event): void
113+
{
114+
$this->extension->executeAfterTestFailure();
115+
}
116+
});
39117
}
40-
}
41-
42-
public function executeBeforeFirstTest(): void
43-
{
44-
self::$enabled = true;
45-
$this->keepServerOnTeardown();
46-
}
47118

48-
public function executeAfterLastTest(): void
49-
{
50-
$this->stopWebServer();
51-
}
52-
53-
public function executeBeforeTest(string $test): void
54-
{
55-
self::reset();
56-
}
57-
58-
public function executeAfterTest(string $test, float $time): void
59-
{
60-
self::reset();
61-
}
62-
63-
public function executeAfterTestError(string $test, string $message, float $time): void
64-
{
65-
$this->pause(sprintf('Error: %s', $message));
66-
}
67-
68-
public function executeAfterTestFailure(string $test, string $message, float $time): void
69-
{
70-
$this->pause(sprintf('Failure: %s', $message));
71-
}
72-
73-
private static function reset(): void
74-
{
75-
self::$registeredClients = [];
119+
public static function registerClient(Client $client): void
120+
{
121+
ServerExtensionLegacy::registerClient($client);
122+
}
76123
}
77-
78-
public static function takeScreenshots(string $type, string $test): void
124+
} elseif (interface_exists(BeforeFirstTestHook::class)) {
125+
/**
126+
* PHPUnit < 10.
127+
*/
128+
final class ServerExtension extends ServerExtensionLegacy implements BeforeFirstTestHook, BeforeTestHook, AfterTestHook, AfterLastTestHook, AfterTestErrorHook, AfterTestFailureHook
79129
{
80-
if (!self::$enabled || !($_SERVER['PANTHER_ERROR_SCREENSHOT_DIR'] ?? false)) {
81-
return;
82-
}
83-
84-
foreach (self::$registeredClients as $i => $client) {
85-
$screenshotPath = sprintf('%s/%s_%s_%s-%d.png',
86-
$_SERVER['PANTHER_ERROR_SCREENSHOT_DIR'],
87-
date('Y-m-d_H-i-s'),
88-
$type,
89-
strtr($test, ['\\' => '-', ':' => '_']),
90-
$i
91-
);
92-
$client->takeScreenshot($screenshotPath);
93-
if ($_SERVER['PANTHER_ERROR_SCREENSHOT_ATTACH'] ?? false) {
94-
printf('[[ATTACHMENT|%s]]', $screenshotPath);
95-
}
96-
}
97130
}
131+
} else {
132+
exit("Failed to initialize Symfony\Component\Panther\ServerExtension, undetectable or unsupported phpunit version.");
98133
}

0 commit comments

Comments
 (0)