Version
1.59.0 (playwright-python), Chromium 147.0.7727.15, macOS (also reproduces with the default headless shell)
Steps to reproduce
import asyncio
from playwright.async_api import async_playwright
PAGE = "<script>function tick(){requestAnimationFrame(tick)}requestAnimationFrame(tick)</script>"
async def main():
async with async_playwright() as pw:
browser = await pw.chromium.launch(headless=True)
page = await browser.new_page()
await page.clock.install()
await page.set_content(PAGE)
await asyncio.sleep(0.3)
for cycle in (1, 2):
fake_now = int(await page.evaluate("Date.now()"))
await asyncio.wait_for(page.clock.pause_at(fake_now), timeout=10)
print(f"cycle {cycle}: pause_at OK")
await page.clock.resume()
await asyncio.sleep(0.3)
await browser.close()
asyncio.run(main())
Expected behavior
Both pause_at calls return promptly — each pauses at the page's current fake time.
Actual behavior
The first install -> pause_at -> resume cycle works; the second pause_at never resolves (hangs indefinitely; the wait_for above times out):
cycle 1: pause_at OK
cycle 2: pause_at HANGS (>10s)
Reproduces identically on the default headless shell and channel="chromium". Removing the requestAnimationFrame loop from the page makes both cycles pass.
Additional context
Version
1.59.0 (playwright-python), Chromium 147.0.7727.15, macOS (also reproduces with the default headless shell)
Steps to reproduce
Expected behavior
Both
pause_atcalls return promptly — each pauses at the page's current fake time.Actual behavior
The first
install -> pause_at -> resumecycle works; the secondpause_atnever resolves (hangs indefinitely; thewait_forabove times out):Reproduces identically on the default headless shell and
channel="chromium". Removing therequestAnimationFrameloop from the page makes both cycles pass.Additional context
pauseAtas the way to pause time. For any rAF-driven page (games, canvas/WebGL apps), repeated freeze/resume viapause_atdeadlocks on the second freeze, so that recommendation currently has no working path for such pages.fastForward):pause_atfast-forwards to the target time internally (_innerFastForwardTo), and a rAF callback re-queues itself on every fired frame.