Version
1.59.1
Steps to reproduce
-
Create a new project: npm init playwright@latest repro
-
Add the following test:
import { test, expect } from '@playwright/test';
test('toBeVisible misses element that appears before timeout', async ({ page }) => {
// Page starts empty; after 1900ms, inject a visible element
await page.setContent('
');
await page.evaluate(() => {
setTimeout(() => {
document.getElementById('target')!.style.display = 'block';
}, 1900);
});
// Timeout of 2500ms should be enough — element appears at 1900ms.
// But the retry schedule [0, 100, 250, 500, 1000, 1000, ...] means:
// check at 0, 100, 350, 850, 1850, (next: 2850)
// The check at 1850ms misses it (element appears at 1900ms).
// The deadline at 2500ms fires during the 1000ms sleep before the next check.
// The assertion times out without ever seeing the element.
await expect(page.locator('#target')).toBeVisible({ timeout: 2500 });
});
-
Run:
npx playwright test
-
The test fails with a timeout, even though the element became visible 600ms before the deadline.
Expected behavior
The assertion should pass. The element is visible at 1900ms and the timeout is 2500ms — there are 600ms of remaining budget. The assertion should perform at least one final check before giving up.
Actual behavior
The assertion times out at 2500ms without checking the DOM again after its last retry at 1850ms. The retryWithProgressAndTimeouts loop in packages/playwright-core/src/server/frames.ts sleeps for the full 1000ms interval while racing against the deadline via progress.race(...). When the deadline fires mid-sleep, it aborts immediately without a final check.
The effective retry schedule is [0, 100, 250, 500, 1000, 1000, ...] (cumulative: 0, 100, 350, 850, 1850, 2850, ...). Any timeout between 1851ms and 2849ms wastes up to 999ms of budget.
Additional context
The root cause is in retryWithProgressAndTimeouts:
const timeout = timeouts[Math.min(timeoutIndex++, timeouts.length - 1)];
if (timeout) {
const actionPromise = new Promise((f) => setTimeout(f, timeout));
await progress.race(/* ... */ actionPromise); // deadline aborts here mid-sleep
}
Possible fix:
- Perform one final
action() call before throwing TimeoutError
This affects all locator assertions that use [retryWithProgressAndTimeouts] with [100, 250, 500, 1000] intervals: toBeVisible, toBeHidden, toContainText, toHaveText, etc.
Environment
System:
OS: macOS 26.3.1
CPU: (10) arm64 Apple M2 Pro
Memory: 184.58 MB / 32.00 GB
Binaries:
Node: 23.11.0 - /opt/homebrew/bin/node
npm: 11.12.1 - /Users/daniel.rost/Develop/Projects/Angular/dialog_pitch_pwa/online-diagnosis-pwa/node_modules/.bin/npm
IDEs:
VSCode: 1.116.0 - /usr/local/bin/code
Languages:
Bash: 3.2.57 - /bin/bash
npmPackages:
@playwright/test: 1.59.1 => 1.59.1
Version
1.59.1
Steps to reproduce
Create a new project: npm init playwright@latest repro
Add the following test:
Run:
npx playwright test
The test fails with a timeout, even though the element became visible 600ms before the deadline.
Expected behavior
The assertion should pass. The element is visible at 1900ms and the timeout is 2500ms — there are 600ms of remaining budget. The assertion should perform at least one final check before giving up.
Actual behavior
The assertion times out at 2500ms without checking the DOM again after its last retry at 1850ms. The
retryWithProgressAndTimeoutsloop inpackages/playwright-core/src/server/frames.tssleeps for the full 1000ms interval while racing against the deadline viaprogress.race(...). When the deadline fires mid-sleep, it aborts immediately without a final check.The effective retry schedule is [0, 100, 250, 500, 1000, 1000, ...] (cumulative: 0, 100, 350, 850, 1850, 2850, ...). Any timeout between 1851ms and 2849ms wastes up to 999ms of budget.
Additional context
The root cause is in
retryWithProgressAndTimeouts:Possible fix:
action()call before throwingTimeoutErrorThis affects all locator assertions that use [retryWithProgressAndTimeouts] with
[100, 250, 500, 1000]intervals:toBeVisible,toBeHidden,toContainText,toHaveText, etc.Environment
System: OS: macOS 26.3.1 CPU: (10) arm64 Apple M2 Pro Memory: 184.58 MB / 32.00 GB Binaries: Node: 23.11.0 - /opt/homebrew/bin/node npm: 11.12.1 - /Users/daniel.rost/Develop/Projects/Angular/dialog_pitch_pwa/online-diagnosis-pwa/node_modules/.bin/npm IDEs: VSCode: 1.116.0 - /usr/local/bin/code Languages: Bash: 3.2.57 - /bin/bash npmPackages: @playwright/test: 1.59.1 => 1.59.1