diff --git a/composer.json b/composer.json index 41801fec..1099a0ae 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ "php": ">=8.0", "ext-dom": "*", "ext-libxml": "*", - "php-webdriver/webdriver": "^1.8.2", + "php-webdriver/webdriver": "^1.11.0", "symfony/browser-kit": "^5.4 || ^6.4 || ^7.0", "symfony/dependency-injection": "^5.4 || ^6.4 || ^7.0", "symfony/deprecation-contracts": "^2.4 || ^3", diff --git a/src/ProcessManager/FirefoxManager.php b/src/ProcessManager/FirefoxManager.php index 370460cf..9b39bff8 100644 --- a/src/ProcessManager/FirefoxManager.php +++ b/src/ProcessManager/FirefoxManager.php @@ -54,33 +54,7 @@ public function start(): WebDriver $this->waitUntilReady($this->process, $url.$this->options['path'], 'firefox'); } - $firefoxOptions = []; - if (isset($_SERVER['PANTHER_FIREFOX_BINARY'])) { - $firefoxOptions['binary'] = $_SERVER['PANTHER_FIREFOX_BINARY']; - } - if ($this->arguments) { - $firefoxOptions['args'] = $this->arguments; - } - - $capabilities = DesiredCapabilities::firefox(); - $capabilities->setCapability('moz:firefoxOptions', $firefoxOptions); - - // Prefer reduced motion, see https://developer.mozilla.org/fr/docs/Web/CSS/@media/prefers-reduced-motion - /** @var FirefoxOptions|array $firefoxOptions */ - $firefoxOptions = $capabilities->getCapability('moz:firefoxOptions') ?? []; - $firefoxOptions = $firefoxOptions instanceof FirefoxOptions ? $firefoxOptions->toArray() : $firefoxOptions; - if (!filter_var($_SERVER['PANTHER_NO_REDUCED_MOTION'] ?? false, \FILTER_VALIDATE_BOOLEAN)) { - $firefoxOptions['prefs']['ui.prefersReducedMotion'] = 1; - } else { - $firefoxOptions['prefs']['ui.prefersReducedMotion'] = 0; - } - $capabilities->setCapability('moz:firefoxOptions', $firefoxOptions); - - foreach ($this->options['capabilities'] as $capability => $value) { - $capabilities->setCapability($capability, $value); - } - - return RemoteWebDriver::create($url, $capabilities, $this->options['connection_timeout_in_ms'] ?? null, $this->options['request_timeout_in_ms'] ?? null); + return RemoteWebDriver::create($url, $this->buildCapabilities(), $this->options['connection_timeout_in_ms'] ?? null, $this->options['request_timeout_in_ms'] ?? null); } public function quit(): void @@ -123,6 +97,44 @@ private function getDefaultArguments(): array return $args; } + private function buildCapabilities(): DesiredCapabilities + { + $capabilities = DesiredCapabilities::firefox(); + $firefoxOptions = $capabilities->getCapability(FirefoxOptions::CAPABILITY); + + if (isset($_SERVER['PANTHER_FIREFOX_BINARY'])) { + $firefoxOptions->setOption('binary', $_SERVER['PANTHER_FIREFOX_BINARY']); + } + if ($this->arguments) { + $firefoxOptions->addArguments($this->arguments); + } + + // Prefer reduced motion, see https://developer.mozilla.org/fr/docs/Web/CSS/@media/prefers-reduced-motion + if (!filter_var($_SERVER['PANTHER_NO_REDUCED_MOTION'] ?? false, \FILTER_VALIDATE_BOOLEAN)) { + $firefoxOptions->setPreference('ui.prefersReducedMotion', 1); + } else { + $firefoxOptions->setPreference('ui.prefersReducedMotion', 0); + } + + foreach ($this->options['capabilities'] as $capability => $value) { + if (FirefoxOptions::CAPABILITY !== $capability) { + $capabilities->setCapability($capability, $value); + continue; + } + + if (isset($value[FirefoxOptions::OPTION_ARGS])) { + $firefoxOptions->addArguments($value[FirefoxOptions::OPTION_ARGS]); + } + if (isset($value[FirefoxOptions::OPTION_PREFS])) { + foreach ($value[FirefoxOptions::OPTION_PREFS] as $preference => $preferenceValue) { + $firefoxOptions->setPreference($preference, $preferenceValue); + } + } + } + + return $capabilities; + } + private function getDefaultOptions(): array { return [ diff --git a/tests/ClientTest.php b/tests/ClientTest.php index edda41dd..f6662cd1 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -615,11 +615,18 @@ public function testPrefersReducedMotionDisabled(string $browser): void { $this->expectException(ElementClickInterceptedException::class); + $previous = $_SERVER['PANTHER_NO_REDUCED_MOTION'] ?? null; $_SERVER['PANTHER_NO_REDUCED_MOTION'] = true; $client = self::createPantherClient(['browser' => $browser]); $client->request('GET', '/prefers-reduced-motion.html'); $client->clickLink('Click me!'); + + if (null === $previous) { + unset($_SERVER['PANTHER_NO_REDUCED_MOTION']); + } else { + $_SERVER['PANTHER_NO_REDUCED_MOTION'] = $previous; + } } public static function providePrefersReducedMotion(): iterable diff --git a/tests/ProcessManager/FirefoxManagerTest.php b/tests/ProcessManager/FirefoxManagerTest.php new file mode 100644 index 00000000..5a5049bd --- /dev/null +++ b/tests/ProcessManager/FirefoxManagerTest.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Symfony\Component\Panther\Tests\ProcessManager; + +use Facebook\WebDriver\Firefox\FirefoxOptions; +use Facebook\WebDriver\Remote\DesiredCapabilities; +use PHPUnit\Framework\TestCase; +use Symfony\Component\Panther\Exception\RuntimeException; +use Symfony\Component\Panther\ProcessManager\FirefoxManager; + +/** + * @author Tugdual Saunier + */ +class FirefoxManagerTest extends TestCase +{ + public function testRun(): void + { + $manager = new FirefoxManager(); + $client = $manager->start(); + $this->assertNotEmpty($client->getCurrentURL()); + $manager->quit(); + } + + public function testAlreadyRunning(): void + { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('The port 4444 is already in use.'); + + $driver1 = new FirefoxManager(); + $driver1->start(); + + $driver2 = new FirefoxManager(); + try { + $driver2->start(); + } finally { + $driver1->quit(); + } + } + + public function testNonDefaultPort(): void + { + $manager = new FirefoxManager(null, null, ['port' => 4445]); + $client = $manager->start(); + $this->assertNotEmpty($client->getCurrentURL()); + $manager->quit(); + } + + public function testMultipleInstances(): void + { + $driver1 = new FirefoxManager(); + $client1 = $driver1->start(); + + $driver2 = new FirefoxManager(null, null, ['port' => 4445]); + $client2 = $driver2->start(); + + $this->assertNotEmpty($client1->getCurrentURL()); + $this->assertNotEmpty($client2->getCurrentURL()); + + $driver1->quit(); + $driver2->quit(); + } + + public function testCanOverrideOptions(): void + { + $manager = new FirefoxManager(null, null, [ + 'capabilities' => [ + 'platform' => 'LINUX', + 'browserName' => 'firefox-esr', + 'moz:firefoxOptions' => [ + 'prefs' => [ + 'devtools.console.stdout.content' => true, + 'reader.parse-on-load.enabled' => true, + ], + 'args' => [ + '--new-instance', + ], + ], + ], + ]); + $refl = new \ReflectionMethod($manager, 'buildCapabilities'); + $refl->setAccessible(true); + $capabilities = $refl->invoke($manager); + + $this->assertInstanceOf(DesiredCapabilities::class, $capabilities); + $this->assertEquals('LINUX', $capabilities->getCapability('platform')); + $this->assertEquals('firefox-esr', $capabilities->getCapability('browserName')); + + $this->assertInstanceOf(FirefoxOptions::class, $capabilities->getCapability('moz:firefoxOptions')); + $mozFirefoxOptions = $capabilities->getCapability('moz:firefoxOptions')->toArray(); + $this->assertArrayHasKey('prefs', $mozFirefoxOptions); + + // // our preferences should be set + $this->assertArrayHasKey('devtools.console.stdout.content', $mozFirefoxOptions['prefs']); + $this->assertTrue($mozFirefoxOptions['prefs']['devtools.console.stdout.content']); + + // but the default one should still be there + $this->assertArrayHasKey('ui.prefersReducedMotion', $mozFirefoxOptions['prefs']); + $this->assertEquals('1', $mozFirefoxOptions['prefs']['ui.prefersReducedMotion']); + $this->assertArrayHasKey('devtools.jsonview.enabled', $mozFirefoxOptions['prefs']); + $this->assertFalse($mozFirefoxOptions['prefs']['devtools.jsonview.enabled']); + + // except if we override then + $this->assertArrayHasKey('reader.parse-on-load.enabled', $mozFirefoxOptions['prefs']); + $this->assertTrue($mozFirefoxOptions['prefs']['reader.parse-on-load.enabled']); + + // default arguments should still be there + $this->assertContains('--headless', $mozFirefoxOptions['args']); + + // but our custom one should be there too + $this->assertContains('--new-instance', $mozFirefoxOptions['args']); + } +}