Skip to content

Commit

Permalink
Support PHPUnit 10 (#589)
Browse files Browse the repository at this point in the history
* [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]>
  • Loading branch information
3 people authored May 30, 2023
1 parent 52e7ea4 commit edc87a6
Show file tree
Hide file tree
Showing 16 changed files with 331 additions and 95 deletions.
40 changes: 40 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -216,3 +216,43 @@ jobs:

- name: Run tests
run: vendor/bin/simple-phpunit

phpunit-10:
runs-on: ubuntu-latest
strategy:
matrix:
php-versions: [ '8.1' ]
fail-fast: false
name: PHP ${{ matrix.php-versions }} (phpunit 10) Test on ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
extensions: zip

- name: Get composer cache directory
id: composercache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"

- name: Cache dependencies
uses: actions/cache@v2
with:
path: ${{ steps.composercache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
restore-keys: ${{ runner.os }}-composer-

- name: Install dependencies
run: composer install --prefer-dist

- name: Remove phpunit-bridge dependency (not yet phpunit 10 compliant)
run: composer remove --dev symfony/phpunit-bridge

- name: Install latest phpunit 10
run: composer require --dev --prefer-dist phpunit/phpunit:^10.0

- name: Run tests
run: vendor/bin/phpunit --configuration phpunit.xml.dist.10
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/.php-cs-fixer.php
/.php-cs-fixer.cache
/.phpunit.cache
/.phpunit.result.cache
/composer.phar
/composer.lock
Expand Down
2 changes: 1 addition & 1 deletion examples/basic.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

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

$client->request('GET', 'https://api-platform.com'); // Yes, this website is 100% written in JavaScript
$client->clickLink('Get started');
Expand Down
2 changes: 2 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ parameters:
inferPrivatePropertyTypeFromConstructor: true
excludePaths:
- tests/DummyKernel.php
# There are lots of missing phpunit classes since we are supporting multiple versions
- src/ServerExtension.php
ignoreErrors:
# False positive
- '#Call to an undefined method ReflectionType::getName\(\)\.#'
Expand Down
34 changes: 34 additions & 0 deletions phpunit.xml.dist.10
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>

<!-- http://phpunit.de/manual/4.1/en/appendixes.configuration.html -->
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.0/phpunit.xsd"
backupGlobals="false"
bootstrap="vendor/autoload.php"
colors="true"
cacheDirectory=".phpunit.cache"
>

<coverage>
<include>
<directory>.</directory>
</include>
<exclude>
<directory>tests</directory>
<directory>vendor</directory>
</exclude>
</coverage>

<php>
<env name="KERNEL_CLASS" value="Symfony\Component\Panther\Tests\DummyKernel"/>
<env name="SYMFONY_DEPRECATIONS_HELPER" value="max[direct]=0"/>
<server name="SYMFONY_PHPUNIT_VERSION" value="10.0"/>
</php>

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

</phpunit>
1 change: 0 additions & 1 deletion src/DomCrawler/Crawler.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@

namespace Symfony\Component\Panther\DomCrawler;

use function array_merge;
use Facebook\WebDriver\Exception\NoSuchElementException;
use Facebook\WebDriver\WebDriver;
use Facebook\WebDriver\WebDriverBy;
Expand Down
41 changes: 30 additions & 11 deletions src/PantherTestCaseTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@

namespace Symfony\Component\Panther;

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

public function takeScreenshotIfTestFailed(): void
{
if (!\in_array($this->getStatus(), [BaseTestRunner::STATUS_ERROR, BaseTestRunner::STATUS_FAILURE], true)) {
if (class_exists(BaseTestRunner::class) && method_exists($this, 'getStatus')) {
/**
* PHPUnit <10 TestCase.
*/
$status = $this->getStatus();
$isError = BaseTestRunner::STATUS_FAILURE === $status;
$isFailure = BaseTestRunner::STATUS_ERROR === $status;
} elseif (method_exists($this, 'status')) {
/**
* PHPUnit 10 TestCase.
*/
$status = $this->status();
$isError = $status->isError();
$isFailure = $status->isFailure();
} else {
/*
* Symfony WebTestCase.
*/
return;
}

$type = BaseTestRunner::STATUS_FAILURE === $this->getStatus() ? 'failure' : 'error';
$test = $this->toString();

ServerExtension::takeScreenshots($type, $test);
if ($isError || $isFailure) {
$type = $isError ? 'error' : 'failure';
ServerExtensionLegacy::takeScreenshots($type, $this->toString());
}
}

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

return $callGetClient ? self::getClient(self::$pantherClient) : self::$pantherClient; // @phpstan-ignore-line
/* @phpstan-ignore-next-line */
return $callGetClient ? self::getClient(self::$pantherClient) : self::$pantherClient;
}
}

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

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

return $callGetClient ? self::getClient(self::$pantherClient) : self::$pantherClient; // @phpstan-ignore-line
/* @phpstan-ignore-next-line */
return $callGetClient ? self::getClient(self::$pantherClient) : self::$pantherClient;
}

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

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

private static function getWebServerDir(array $options): string
Expand Down
169 changes: 102 additions & 67 deletions src/ServerExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,86 +13,121 @@

namespace Symfony\Component\Panther;

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\AfterLastTestHook;
use PHPUnit\Runner\AfterTestErrorHook;
use PHPUnit\Runner\AfterTestFailureHook;
use PHPUnit\Runner\AfterTestHook;
use PHPUnit\Runner\BeforeFirstTestHook;
use PHPUnit\Runner\BeforeTestHook;
use PHPUnit\Runner\Extension\Extension;
use PHPUnit\Runner\Extension\Facade;
use PHPUnit\Runner\Extension\ParameterCollection;
use PHPUnit\TextUI\Configuration\Configuration;

/**
/*
* @author Dany Maillard <[email protected]>
*/
final class ServerExtension implements BeforeFirstTestHook, AfterLastTestHook, BeforeTestHook, AfterTestHook, AfterTestErrorHook, AfterTestFailureHook
{
use ServerTrait;

private static bool $enabled = false;

/** @var Client[] */
private static array $registeredClients = [];

public static function registerClient(Client $client): void
if (interface_exists(Extension::class)) {
/**
* PHPUnit >= 10.
*/
final class ServerExtension implements Extension
{
if (self::$enabled && !\in_array($client, self::$registeredClients, true)) {
self::$registeredClients[] = $client;
public function bootstrap(Configuration $configuration, Facade $facade, ParameterCollection $parameters): void
{
$extension = new ServerExtensionLegacy();

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

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

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

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

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

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

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

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

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

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

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

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

public function executeBeforeFirstTest(): void
{
self::$enabled = true;
$this->keepServerOnTeardown();
}

public function executeAfterLastTest(): void
{
$this->stopWebServer();
}

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

public function executeAfterTest(string $test, float $time): void
{
self::reset();
}

public function executeAfterTestError(string $test, string $message, float $time): void
{
$this->pause(sprintf('Error: %s', $message));
}

public function executeAfterTestFailure(string $test, string $message, float $time): void
{
$this->pause(sprintf('Failure: %s', $message));
}

private static function reset(): void
{
self::$registeredClients = [];
public static function registerClient(Client $client): void
{
ServerExtensionLegacy::registerClient($client);
}
}

public static function takeScreenshots(string $type, string $test): void
} elseif (interface_exists(BeforeFirstTestHook::class)) {
/**
* PHPUnit < 10.
*/
final class ServerExtension extends ServerExtensionLegacy implements BeforeFirstTestHook, BeforeTestHook, AfterTestHook, AfterLastTestHook, AfterTestErrorHook, AfterTestFailureHook
{
if (!self::$enabled || !($_SERVER['PANTHER_ERROR_SCREENSHOT_DIR'] ?? false)) {
return;
}

foreach (self::$registeredClients as $i => $client) {
$screenshotPath = sprintf('%s/%s_%s_%s-%d.png',
$_SERVER['PANTHER_ERROR_SCREENSHOT_DIR'],
date('Y-m-d_H-i-s'),
$type,
strtr($test, ['\\' => '-', ':' => '_']),
$i
);
$client->takeScreenshot($screenshotPath);
if ($_SERVER['PANTHER_ERROR_SCREENSHOT_ATTACH'] ?? false) {
printf('[[ATTACHMENT|%s]]', $screenshotPath);
}
}
}
} else {
exit("Failed to initialize Symfony\Component\Panther\ServerExtension, undetectable or unsupported phpunit version.");
}
Loading

0 comments on commit edc87a6

Please sign in to comment.