From 9917f08a0108125f63615c94d49836713bfcc405 Mon Sep 17 00:00:00 2001 From: Jordi Sala Morales Date: Fri, 19 Nov 2021 19:50:05 +0100 Subject: [PATCH] Add support for Symfony 6 (#509) --- .github/workflows/ci.yml | 41 +++++++++++++++++++- composer.json | 18 ++++----- phpstan.neon | 2 - src/Client.php | 6 +-- src/DomCrawler/Crawler.php | 32 +++++++-------- src/DomCrawler/Field/ChoiceFormField.php | 4 +- src/DomCrawler/Field/FileFormField.php | 4 +- src/DomCrawler/Field/FormFieldTrait.php | 7 +--- src/DomCrawler/Form.php | 15 +++++-- src/DomCrawler/Link.php | 2 +- src/ServerTrait.php | 2 +- src/WebTestAssertionsTrait.php | 10 +++-- tests/DomCrawler/CrawlerTest.php | 4 ++ tests/DomCrawler/Field/FileFormFieldTest.php | 2 +- tests/DummyKernel.php | 25 +++++++++++- tests/fixtures/env.php | 2 +- 16 files changed, 122 insertions(+), 54 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5a24093a..f90b6442 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '7.4' # PHP CS Fixer isn't compatible with PHP 8 yet /~https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues/4702 + php-version: '8.0' tools: php-cs-fixer, cs2pr - name: PHP Coding Standards Fixer @@ -58,7 +58,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php-versions: ['7.1', '7.2', '7.3', '7.4', '8.0'] + php-versions: ['8.0'] fail-fast: false name: PHP ${{ matrix.php-versions }} Test on ubuntu-latest steps: @@ -88,6 +88,43 @@ jobs: - name: Run tests run: vendor/bin/simple-phpunit + phpunit-dev: + runs-on: ubuntu-latest + strategy: + matrix: + php-versions: ['8.0'] + fail-fast: false + name: PHP ${{ matrix.php-versions }} Test dev dependencies 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: Allow dev dependencies + run: composer config minimum-stability dev + + - name: Install dependencies + run: composer install --prefer-dist + + - name: Run tests + run: vendor/bin/simple-phpunit + phpunit-lowest: runs-on: ubuntu-latest name: PHP 8.0 (lowest) Test on ubuntu-latest diff --git a/composer.json b/composer.json index fb74e77c..a660ea83 100644 --- a/composer.json +++ b/composer.json @@ -17,16 +17,16 @@ } ], "require": { - "php": ">=7.1", + "php": ">=8.0", "ext-dom": "*", "ext-libxml": "*", "php-webdriver/webdriver": "^1.8.2", - "symfony/browser-kit": "^4.4 || ^5.0", + "symfony/browser-kit": "^4.4 || ^5.0 || ^6.0", "symfony/deprecation-contracts": "^2.4", - "symfony/dom-crawler": "^4.4 || ^5.0", - "symfony/http-client": "^4.4.11 || ^5.2", + "symfony/dom-crawler": "^4.4 || ^5.0 || ^6.0", + "symfony/http-client": "^4.4.11 || ^5.2 || ^6.0", "symfony/polyfill-php72": "^1.9", - "symfony/process": "^4.4 || ^5.0" + "symfony/process": "^4.4 || ^5.0 || ^6.0" }, "autoload": { "psr-4": { "Symfony\\Component\\Panther\\": "src/" } @@ -43,9 +43,9 @@ "sort-packages": true }, "require-dev": { - "symfony/css-selector": "^4.4 || ^5.0", - "symfony/framework-bundle": "^4.4 || ^5.0", - "symfony/mime": "^4.4 || ^5.0", - "symfony/phpunit-bridge": "^5.2" + "symfony/css-selector": "^4.4 || ^5.0 || ^6.0", + "symfony/framework-bundle": "^4.4 || ^5.0 || ^6.0", + "symfony/mime": "^4.4 || ^5.0 || ^6.0", + "symfony/phpunit-bridge": "^5.2 || ^6.0" } } diff --git a/phpstan.neon b/phpstan.neon index 3afa253a..c9b930d9 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -7,8 +7,6 @@ parameters: - vendor/bin/.phpunit/phpunit/vendor/autoload.php inferPrivatePropertyTypeFromConstructor: true ignoreErrors: - - message: '#.+#' - path: tests/DummyKernel.php # False positive - '#Call to an undefined method ReflectionType::getName\(\)\.#' # To fix in PHP WebDriver diff --git a/src/Client.php b/src/Client.php index 32c6a023..67c00c3b 100644 --- a/src/Client.php +++ b/src/Client.php @@ -139,12 +139,12 @@ public function start(): void $this->isFirefox = false; } - public function getRequest() + public function getRequest(): object { throw new \LogicException('HttpFoundation Request object is not available when using WebDriver.'); } - public function getResponse() + public function getResponse(): object { throw new \LogicException('HttpFoundation Response object is not available when using WebDriver.'); } @@ -190,7 +190,7 @@ public function setServerParameter($key, $value): void throw new \InvalidArgumentException('Server parameters cannot be set when using WebDriver.'); } - public function getServerParameter($key, $default = '') + public function getServerParameter($key, $default = ''): mixed { throw new \InvalidArgumentException('Server parameters cannot be retrieved when using WebDriver.'); } diff --git a/src/DomCrawler/Crawler.php b/src/DomCrawler/Crawler.php index 16fd3e2b..32a6d4da 100644 --- a/src/DomCrawler/Crawler.php +++ b/src/DomCrawler/Crawler.php @@ -38,7 +38,7 @@ public function __construct(array $elements = [], ?WebDriver $webDriver = null, { $this->uri = $uri; $this->webDriver = $webDriver; - $this->elements = $elements ?? []; + $this->elements = $elements; } public function clear(): void @@ -86,7 +86,7 @@ public function addNode(\DOMNode $node): void throw $this->createNotSupportedException(__METHOD__); } - public function eq($position): self + public function eq($position): static { if (isset($this->elements[$position])) { return $this->createSubCrawler([$this->elements[$position]]); @@ -105,12 +105,12 @@ public function each(\Closure $closure): array return $data; } - public function slice($offset = 0, $length = null): self + public function slice($offset = 0, $length = null): static { return $this->createSubCrawler(\array_slice($this->elements, $offset, $length)); } - public function reduce(\Closure $closure): self + public function reduce(\Closure $closure): static { $elements = []; foreach ($this->elements as $i => $element) { @@ -122,22 +122,22 @@ public function reduce(\Closure $closure): self return $this->createSubCrawler($elements); } - public function last(): self + public function last(): static { return $this->eq(\count($this->elements) - 1); } - public function siblings(): self + public function siblings(): static { return $this->createSubCrawlerFromXpath('(preceding-sibling::* | following-sibling::*)'); } - public function nextAll(): self + public function nextAll(): static { return $this->createSubCrawlerFromXpath('following-sibling::*'); } - public function previousAll(): self + public function previousAll(): static { return $this->createSubCrawlerFromXpath('preceding-sibling::*'); } @@ -149,7 +149,7 @@ public function parents(): self return $this->ancestors(); } - public function ancestors(): self + public function ancestors(): static { return $this->createSubCrawlerFromXpath('ancestor::*', true); } @@ -157,7 +157,7 @@ public function ancestors(): self /** * @see /~https://github.com/symfony/symfony/issues/26432 */ - public function children(string $selector = null): self + public function children(string $selector = null): static { $xpath = 'child::*'; if (null !== $selector) { @@ -219,7 +219,7 @@ public function html(string $default = null): string } } - public function evaluate($xpath): self + public function evaluate($xpath): static { throw $this->createNotSupportedException(__METHOD__); } @@ -241,29 +241,29 @@ public function extract($attributes): array return $data; } - public function filterXPath($xpath): self + public function filterXPath($xpath): static { return $this->filterWebDriverBy(WebDriverBy::xpath($xpath)); } - public function filter($selector): self + public function filter($selector): static { return $this->filterWebDriverBy(WebDriverBy::cssSelector($selector)); } - public function selectLink($value): self + public function selectLink($value): static { return $this->selectFromXpath( sprintf('descendant-or-self::a[contains(concat(\' \', normalize-space(string(.)), \' \'), %1$s) or ./img[contains(concat(\' \', normalize-space(string(@alt)), \' \'), %1$s)]]', self::xpathLiteral(' '.$value.' ')) ); } - public function selectImage($value): self + public function selectImage($value): static { return $this->selectFromXpath(sprintf('descendant-or-self::img[contains(normalize-space(string(@alt)), %s)]', self::xpathLiteral($value))); } - public function selectButton($value): self + public function selectButton($value): static { return $this->selectFromXpath( sprintf( diff --git a/src/DomCrawler/Field/ChoiceFormField.php b/src/DomCrawler/Field/ChoiceFormField.php index fe3769f0..e2f2994b 100644 --- a/src/DomCrawler/Field/ChoiceFormField.php +++ b/src/DomCrawler/Field/ChoiceFormField.php @@ -79,7 +79,7 @@ public function untick(): void $this->setValue(false); } - public function getValue() + public function getValue(): array|string|null { $type = $this->element->getAttribute('type'); @@ -182,7 +182,7 @@ public function availableOptionValues(): array /** * Disables the internal validation of the field. */ - public function disableValidation(): self + public function disableValidation(): static { throw $this->createNotSupportedException(__METHOD__); } diff --git a/src/DomCrawler/Field/FileFormField.php b/src/DomCrawler/Field/FileFormField.php index 9339d8be..e967a676 100644 --- a/src/DomCrawler/Field/FileFormField.php +++ b/src/DomCrawler/Field/FileFormField.php @@ -24,10 +24,12 @@ final class FileFormField extends BaseFileFormField /** * @var array + * + * @phpstan-ignore-next-line */ protected $value; - public function getValue() + public function getValue(): array|string|null { return $this->value; } diff --git a/src/DomCrawler/Field/FormFieldTrait.php b/src/DomCrawler/Field/FormFieldTrait.php index 3b3507cb..fb5ed608 100644 --- a/src/DomCrawler/Field/FormFieldTrait.php +++ b/src/DomCrawler/Field/FormFieldTrait.php @@ -34,7 +34,7 @@ public function __construct(WebDriverElement $element) $this->initialize(); } - public function getLabel(): void + public function getLabel(): ?\DOMElement { throw $this->createNotSupportedException(__METHOD__); } @@ -44,10 +44,7 @@ public function getName(): string return $this->element->getAttribute('name') ?? ''; } - /** - * @return string|array|null - */ - public function getValue() + public function getValue(): array|string|null { return $this->element->getAttribute('value'); } diff --git a/src/DomCrawler/Form.php b/src/DomCrawler/Form.php index 23e28736..21082ae9 100644 --- a/src/DomCrawler/Form.php +++ b/src/DomCrawler/Form.php @@ -103,7 +103,10 @@ public function getFormNode(): \DOMElement throw $this->createNotSupportedException(__METHOD__); } - public function setValues(array $values): self + /** + * Disables the internal validation of the field. + */ + public function setValues(array $values): static { foreach ($values as $name => $value) { $this->setValue($name, $value); @@ -212,7 +215,12 @@ public function set(FormField $field): void $this->setValue($field->getName(), $field->getValue()); } - public function get($name) + /** + * @param mixed $name + * + * @return FormField|FormField[]|FormField[][] + */ + public function get($name): FormField|array { return $this->getFormField($this->getFormElement($name)); } @@ -239,8 +247,7 @@ public function offsetExists($name): bool * * @return FormField|FormField[]|FormField[][] */ - #[\ReturnTypeWillChange] - public function offsetGet($name) + public function offsetGet($name): FormField|array { return $this->get($name); } diff --git a/src/DomCrawler/Link.php b/src/DomCrawler/Link.php index 94f072e4..b88f2de7 100644 --- a/src/DomCrawler/Link.php +++ b/src/DomCrawler/Link.php @@ -43,7 +43,7 @@ public function getElement(): WebDriverElement return $this->element; } - public function getNode() + public function getNode(): \DOMElement { throw $this->createNotSupportedException(__METHOD__); } diff --git a/src/ServerTrait.php b/src/ServerTrait.php index 45450488..2c082ec5 100644 --- a/src/ServerTrait.php +++ b/src/ServerTrait.php @@ -35,7 +35,7 @@ private function stopWebServer(): void private function pause($message): void { if (\in_array('--debug', $_SERVER['argv'], true) - && $_SERVER['PANTHER_NO_HEADLESS'] ?? false + && ($_SERVER['PANTHER_NO_HEADLESS'] ?? false) ) { echo "$message\n\nPress enter to continue..."; if (!$this->testing) { diff --git a/src/WebTestAssertionsTrait.php b/src/WebTestAssertionsTrait.php index ac876849..b399639c 100644 --- a/src/WebTestAssertionsTrait.php +++ b/src/WebTestAssertionsTrait.php @@ -16,7 +16,6 @@ use Facebook\WebDriver\WebDriverElement; use Symfony\Bundle\FrameworkBundle\KernelBrowser; use Symfony\Bundle\FrameworkBundle\Test\WebTestAssertionsTrait as BaseWebTestAssertionsTrait; -use Symfony\Component\BrowserKit\AbstractBrowser; use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use Symfony\Component\Panther\Client as PantherClient; @@ -275,9 +274,9 @@ private static function findElement(string $locator): WebDriverElement * @param array $options An array of options to pass to the createKernel method * @param array $server An array of server parameters * - * @return AbstractBrowser A browser instance + * @return KernelBrowser A browser instance */ - protected static function createClient(array $options = [], array $server = []): AbstractBrowser + protected static function createClient(array $options = [], array $server = []): KernelBrowser { $kernel = static::bootKernel($options); @@ -293,6 +292,9 @@ protected static function createClient(array $options = [], array $server = []): $client->setServerParameters($server); - return self::getClient($client); + /** @var KernelBrowser $wrapperClient */ + $wrapperClient = self::getClient($client); + + return $wrapperClient; } } diff --git a/tests/DomCrawler/CrawlerTest.php b/tests/DomCrawler/CrawlerTest.php index c371e407..075320c7 100644 --- a/tests/DomCrawler/CrawlerTest.php +++ b/tests/DomCrawler/CrawlerTest.php @@ -226,6 +226,10 @@ public function testParents(callable $clientFactory): void { $crawler = $this->request($clientFactory, '/basic.html'); + if (!method_exists($crawler, 'parents')) { + $this->markTestSkipped('Dom Crawler on Symfony 6.0 does not have `parents()` method'); + } + $names = []; $crawler->filter('main > h1')->parents()->each(function (Crawler $c, int $i) use (&$names) { $names[$i] = $c->nodeName(); diff --git a/tests/DomCrawler/Field/FileFormFieldTest.php b/tests/DomCrawler/Field/FileFormFieldTest.php index f1d05f85..10172d66 100644 --- a/tests/DomCrawler/Field/FileFormFieldTest.php +++ b/tests/DomCrawler/Field/FileFormFieldTest.php @@ -31,7 +31,7 @@ private function assertValueContains($needle, $haystack): void return; } - if (4 === $haystack['error'] ?? 0) { + if (4 === ($haystack['error'] ?? 0)) { $this->markTestSkipped('File upload is currently buggy with Firefox'); // FIXME } diff --git a/tests/DummyKernel.php b/tests/DummyKernel.php index 4f45f0e6..87141281 100644 --- a/tests/DummyKernel.php +++ b/tests/DummyKernel.php @@ -13,6 +13,7 @@ namespace Symfony\Component\Panther\Tests; +use Symfony\Bundle\FrameworkBundle\FrameworkBundle; use Symfony\Component\Config\Loader\LoaderInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; @@ -27,10 +28,12 @@ class DummyKernel implements KernelInterface { public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true): Response { + return new Response(); } public function registerBundles(): iterable { + return []; } public function registerContainerConfiguration(LoaderInterface $loader): void @@ -47,37 +50,45 @@ public function shutdown(): void public function getBundles(): array { + return []; } public function getBundle($name, $first = true): BundleInterface { + return new FrameworkBundle(); } - public function locateResource($name, $dir = null, $first = true) + public function locateResource($name, $dir = null, $first = true): string { + return ''; } public function getName(): string { + return ''; } public function getEnvironment(): string { + return ''; } public function isDebug(): bool { + return false; } public function getRootDir(): string { + return ''; } public function getContainer(): ContainerInterface { return new class() implements ContainerInterface { - public function get($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) + public function get($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE): ?object { + return new \stdClass(); } public function has($id): bool @@ -112,21 +123,31 @@ public function setParameter($name, $value): void public function getStartTime(): float { + return 0; } public function getCacheDir(): string { + return ''; } public function getLogDir(): string { + return ''; } public function getCharset(): string { + return ''; } public function getProjectDir(): string { + return ''; + } + + public function getBuildDir(): string + { + return ''; } } diff --git a/tests/fixtures/env.php b/tests/fixtures/env.php index b5ebd46a..fc7f06a0 100644 --- a/tests/fixtures/env.php +++ b/tests/fixtures/env.php @@ -13,7 +13,7 @@ require __DIR__.'/security-check.php'; -if ('APP_ENV' === $_GET['name'] ?? null) { ?> +if ('APP_ENV' === ($_GET['name'] ?? null)) { ?>