diff --git a/CHANGELOG.md b/CHANGELOG.md index 358f8f40..29e730c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +0.7.1 +----- + +* Fix some inconsistencies between Chrome and Firefox + 0.7.0 ----- diff --git a/src/Client.php b/src/Client.php index a691ba73..75fe8d7c 100644 --- a/src/Client.php +++ b/src/Client.php @@ -52,6 +52,7 @@ final class Client extends AbstractBrowser implements WebDriver, JavaScriptExecu private $webDriver; private $browserManager; private $baseUri; + private $isFirefox = false; /** * @param string[]|null $arguments @@ -89,9 +90,30 @@ public function __destruct() public function start() { - if (null === $this->webDriver) { - $this->webDriver = $this->browserManager->start(); + if (null !== $this->webDriver) { + return; + } + + $this->webDriver = $this->browserManager->start(); + if ($this->browserManager instanceof FirefoxManager) { + $this->isFirefox = true; + + return; + } + + if ($this->browserManager instanceof ChromeManager) { + $this->isFirefox = false; + + return; } + + if (method_exists($this->webDriver, 'getCapabilities')) { + $this->isFirefox = 'firefox' === $this->webDriver->getCapabilities()->getBrowserName(); + + return; + } + + $this->isFirefox = false; } public function getRequest() @@ -169,7 +191,30 @@ public function submit(Form $form, array $values = [], array $serverParameters = } $button = $form->getButton(); - null === $button ? $form->getElement()->submit() : $button->click(); + + if ($this->isFirefox) { + // For Firefox, we have to wait for the page to reload + // https://github.com/SeleniumHQ/selenium/issues/4570#issuecomment-327473270 + $selector = WebDriverBy::cssSelector('html'); + $previousId = $this->webDriver->findElement($selector)->getID(); + + null === $button ? $form->getElement()->submit() : $button->click(); + + try { + $this->webDriver->wait(5)->until(static function (WebDriver $driver) use ($previousId, $selector) { + try { + return $previousId !== $driver->findElement($selector)->getID(); + } catch (NoSuchElementException $e) { + // The html element isn't already available + return false; + } + }); + } catch (TimeoutException $e) { + // Probably a form using AJAX, do nothing + } + } else { + null === $button ? $form->getElement()->submit() : $button->click(); + } return $this->crawler = $this->createCrawler(); } diff --git a/src/DomCrawler/Crawler.php b/src/DomCrawler/Crawler.php index 2a0b612d..6bcb31bf 100644 --- a/src/DomCrawler/Crawler.php +++ b/src/DomCrawler/Crawler.php @@ -201,7 +201,11 @@ public function text(string $default = null, bool $normalizeWhitespace = true): public function html($default = null): string { try { - $this->getElementOrThrow(); + $element = $this->getElementOrThrow(); + + if ('html' === $element->getTagName()) { + return $this->webDriver->getPageSource(); + } return $this->attr('outerHTML'); } catch (\InvalidArgumentException $e) { @@ -227,7 +231,7 @@ public function extract($attributes) foreach ($this->elements as $element) { $elements = []; foreach ($attributes as $attribute) { - $elements[] = '_text' === $attribute ? $element->getText() : $element->getAttribute($attribute); + $elements[] = '_text' === $attribute ? $element->getText() : (string) $element->getAttribute($attribute); } $data[] = 1 === $count ? $elements[0] : $elements; @@ -385,7 +389,10 @@ private function filterWebDriverBy(WebDriverBy $selector): self { $subElements = []; foreach ($this->elements as $element) { - $subElements = \array_merge($subElements, $element->findElements($selector)); + $subElements = \array_merge( + $subElements, + $element->findElements($selector) + ); } return $this->createSubCrawler($subElements); diff --git a/src/PantherTestCaseTrait.php b/src/PantherTestCaseTrait.php index c3f8f926..6ce9266a 100644 --- a/src/PantherTestCaseTrait.php +++ b/src/PantherTestCaseTrait.php @@ -19,6 +19,8 @@ use Symfony\Component\BrowserKit\HttpBrowser as HttpBrowserClient; use Symfony\Component\HttpClient\HttpClient; use Symfony\Component\Panther\Client as PantherClient; +use Symfony\Component\Panther\ProcessManager\ChromeManager; +use Symfony\Component\Panther\ProcessManager\FirefoxManager; use Symfony\Component\Panther\ProcessManager\WebServerManager; /** @@ -160,14 +162,21 @@ public static function isWebServerStarted() */ protected static function createPantherClient(array $options = [], array $kernelOptions = []): PantherClient { + $browser = ($options['browser'] ?? self::$defaultOptions['browser'] ?? self::CHROME); $callGetClient = \is_callable([self::class, 'getClient']) && (new \ReflectionMethod(self::class, 'getClient'))->isStatic(); if (null !== self::$pantherClient) { - return $callGetClient ? self::getClient(self::$pantherClient) : self::$pantherClient; + $browserManager = self::$pantherClient->getBrowserManager(); + if ( + (self::CHROME === $browser && $browserManager instanceof ChromeManager) || + (self::FIREFOX === $browser && $browserManager instanceof FirefoxManager) + ) { + return $callGetClient ? self::getClient(self::$pantherClient) : self::$pantherClient; + } } self::startWebServer($options); - if (PantherTestCase::CHROME === ($options['browser'] ?? self::$defaultOptions['browser'] ?? PantherTestCase::CHROME)) { + if (self::CHROME === $browser) { self::$pantherClients[0] = self::$pantherClient = Client::createChromeClient(null, null, [], self::$baseUri); } else { self::$pantherClients[0] = self::$pantherClient = Client::createFirefoxClient(null, null, [], self::$baseUri); diff --git a/src/WebDriver/WebDriverCheckbox.php b/src/WebDriver/WebDriverCheckbox.php index 999f9377..91f44354 100644 --- a/src/WebDriver/WebDriverCheckbox.php +++ b/src/WebDriver/WebDriverCheckbox.php @@ -229,12 +229,12 @@ private function byVisibleText($text, $partial = false, $select = true) } } - private function getRelatedElements($value = null) + private function getRelatedElements($value = null): array { $valueSelector = $value ? \sprintf(' and @value = %s', XPathEscaper::escapeQuotes($value)) : ''; if (null === $formId = $this->element->getAttribute('form')) { $form = $this->element->findElement(WebDriverBy::xpath('ancestor::form')); - if ('' === $formId = $form->getAttribute('id')) { + if ('' === $formId = (string) $form->getAttribute('id')) { return $form->findElements(WebDriverBy::xpath(\sprintf('.//input[@name = %s%s]', XPathEscaper::escapeQuotes($this->name), $valueSelector))); } } diff --git a/tests/ClientTest.php b/tests/ClientTest.php index fe7dd122..7d06b252 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -25,6 +25,7 @@ use Symfony\Component\Panther\Client; use Symfony\Component\Panther\Cookie\CookieJar; use Symfony\Component\Panther\DomCrawler\Crawler; +use Symfony\Component\Panther\ProcessManager\ChromeManager; /** * @author Kévin Dunglas @@ -61,16 +62,10 @@ public function testWaitFor(string $locator) $this->assertSame('Hello', $crawler->filter('#hello')->text()); } - public function waitForDataProvider(): array + public function waitForDataProvider(): iterable { - return [ - 'css selector' => [ - 'locator' => '#hello', - ], - 'xpath expression' => [ - 'locator' => '//*[@id="hello"]', - ], - ]; + yield 'css selector' => ['locator' => '#hello']; + yield 'xpath expression' => ['locator' => '//*[@id="hello"]']; } public function testWaitForInvisibleElement(): void @@ -158,7 +153,7 @@ public function testFollowLink(callable $clientFactory, string $type): void /** * @dataProvider clientFactoryProvider */ - public function testSubmitForm(callable $clientFactory, string $type): void + public function testSubmitForm(callable $clientFactory): void { /** @var AbstractBrowser $client */ $client = $clientFactory(); @@ -169,7 +164,7 @@ public function testSubmitForm(callable $clientFactory, string $type): void $crawler = $client->submit($form); $this->assertInstanceOf(DomCrawlerCrawler::class, $crawler); - if (Client::class === $type) { + if ($client instanceof Client) { $this->assertInstanceOf(Crawler::class, $crawler); } $this->assertSame(self::$baseUri.'/form-handle.php', $crawler->getUri()); @@ -294,4 +289,18 @@ public function testServerPort(callable $clientFactory): void $clientFactory(); $this->assertEquals($expectedPort, \mb_substr(self::$baseUri, -4)); } + + /** + * @dataProvider clientFactoryProvider + */ + public function testBrowserProvider(callable $clientFactory): void + { + $client = $clientFactory(); + if (!$client instanceof Client) { + $this->markTestSkipped(); + } + + $client->request('GET', self::$baseUri.'/ua.php'); + $this->assertStringContainsString($client->getBrowserManager() instanceof ChromeManager ? 'Chrome' : 'Firefox', $client->getPageSource()); + } } diff --git a/tests/DomCrawler/CrawlerTest.php b/tests/DomCrawler/CrawlerTest.php index 5e63ad71..a44b8769 100644 --- a/tests/DomCrawler/CrawlerTest.php +++ b/tests/DomCrawler/CrawlerTest.php @@ -240,7 +240,7 @@ public function testExtract(callable $clientFactory): void $this->assertSame([['', 'Sibling'], ['foo', 'Sibling 2'], ['', 'Sibling 3']], $crawler->filter('main > p')->extract(['class', '_text'])); // Uncomment when https://github.com/symfony/symfony/pull/26433 will be merged - //$this->assertSame([[], [], []], $crawler->filter('main > p')->extract([])); + $this->assertSame([[], [], []], $crawler->filter('main > p')->extract([])); } /** diff --git a/tests/fixtures/ua.php b/tests/fixtures/ua.php new file mode 100644 index 00000000..6d49fa83 --- /dev/null +++ b/tests/fixtures/ua.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +require __DIR__.'/security-check.php'; + +echo $_SERVER['HTTP_USER_AGENT'] ?? 'unknown';