diff --git a/src/Browser/PlaywrightBrowser.php b/src/Browser/PlaywrightBrowser.php index 1526c5a..b337f1d 100644 --- a/src/Browser/PlaywrightBrowser.php +++ b/src/Browser/PlaywrightBrowser.php @@ -11,9 +11,11 @@ namespace Zenstruck\Browser; +use Playwright\Assertions\Failure\AssertionException; use Playwright\Console\ConsoleMessage; use Playwright\Page\PageInterface; use Playwright\Symfony\Client\PlaywrightKernelClient; +use Playwright\Testing\Expect; use Symfony\Component\BrowserKit\CookieJar; use Symfony\Component\Filesystem\Filesystem; use Zenstruck\Assert; @@ -60,6 +62,10 @@ final public function __construct(PlaywrightKernelClient $client, array $options $this->screenshotDir = $options['screenshot_dir'] ?? null; $this->consoleLogDir = $options['console_log_dir'] ?? null; + if (null !== ($timeout = $options['default_timeout'] ?? null)) { + $this->page()->setDefaultTimeout((int) $timeout); + } + // subscribe before anything is navigated to, or the messages are already gone // @todo also collect uncaught errors once playwright-php exposes the "pageerror" event $this->page()->events()->onConsole(function(ConsoleMessage $message): void { @@ -76,9 +82,15 @@ final public function __construct(PlaywrightKernelClient $client, array $options */ final public function assertVisible(string $selector): self { - $element = $this->session()->assert()->elementExists('css', $selector); + $this->session()->assert(); - Assert::true($element->isVisible(), 'Expected element "%s" to be visible but it isn\'t.', [$selector]); + try { + (new Expect($this->page()->locator($selector)))->toBeVisible(); + + Assert::pass(); + } catch (AssertionException) { + Assert::fail('Expected element "%s" to be visible but it isn\'t.', [$selector]); + } return $this; } @@ -88,16 +100,16 @@ final public function assertVisible(string $selector): self */ final public function assertNotVisible(string $selector): self { - $element = $this->session()->page()->find('css', $selector); + $this->session()->assert(); - if (!$element) { - Assert::pass(); + try { + (new Expect($this->page()->locator($selector)))->toBeHidden(); - return $this; + Assert::pass(); + } catch (AssertionException) { + Assert::fail('Expected element "%s" to not be visible but it is.', [$selector]); } - Assert::false($element->isVisible(), 'Expected element "%s" to not be visible but it is.', [$selector]); - return $this; } diff --git a/src/Browser/Test/HasBrowser.php b/src/Browser/Test/HasBrowser.php index b89fb33..fa5ee93 100644 --- a/src/Browser/Test/HasBrowser.php +++ b/src/Browser/Test/HasBrowser.php @@ -192,6 +192,7 @@ protected function playwrightBrowser(): PlaywrightBrowser 'console_log_dir' => $_SERVER['BROWSER_CONSOLE_LOG_DIR'] ?? './var/browser/console-logs', 'follow_redirects' => (bool) ($_SERVER['BROWSER_FOLLOW_REDIRECTS'] ?? true), 'catch_exceptions' => (bool) ($_SERVER['BROWSER_CATCH_EXCEPTIONS'] ?? true), + 'default_timeout' => $_SERVER['BROWSER_DEFAULT_TIMEOUT'] ?? null, ]); BrowserExtension::registerBrowser($browser); diff --git a/tests/PlaywrightBrowserTest.php b/tests/PlaywrightBrowserTest.php index 0847b52..e71e821 100644 --- a/tests/PlaywrightBrowserTest.php +++ b/tests/PlaywrightBrowserTest.php @@ -14,6 +14,7 @@ use PHPUnit\Framework\AssertionFailedError; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\Test; +use Playwright\Exception\PlaywrightException; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Zenstruck\Browser\PlaywrightBrowser; use Zenstruck\Browser\Test\HasBrowser; @@ -163,6 +164,51 @@ public function can_check_if_element_is_visible_and_not_visible(): void ; } + /** + * @test + */ + #[Test] + public function visibility_assertions_retry_until_the_element_settles(): void + { + // #timeout-box only becomes visible 500ms after the page loads, #hide-box + // is hidden on click without a delay + $this->browser() + ->visit('/javascript') + ->assertVisible('#timeout-box') + ->click('hide') + ->assertNotVisible('#hide-box') + ; + } + + /** + * @test + */ + #[Test] + public function can_configure_the_default_timeout(): void + { + $_SERVER['BROWSER_DEFAULT_TIMEOUT'] = '250'; + + try { + $start = \hrtime(true); + + try { + $this->browser() + ->visit('/javascript') + ->waitUntilVisible('#invalid-element') + ; + + $this->fail('The wait should have timed out.'); + } catch (PlaywrightException) { + } + + // Playwright's own default is 30 seconds: timing out well before that + // proves the configured value was applied + $this->assertLessThan(10.0, (\hrtime(true) - $start) / 1e9); + } finally { + unset($_SERVER['BROWSER_DEFAULT_TIMEOUT']); + } + } + /** * @test */