Version
1.59.0 (playwright-python), Chromium 147.0.7727.15, macOS (also reproduces with the default headless shell)
Steps to reproduce
importasynciofromplaywright.async_apiimportasync_playwrightPAGE="<script>function tick(){requestAnimationFrame(tick)}requestAnimationFrame(tick)</script>"asyncdefmain():
asyncwithasync_playwright() aspw:
browser=awaitpw.chromium.launch(headless=True)
page=awaitbrowser.new_page()
awaitpage.clock.install()
awaitpage.set_content(PAGE)
awaitasyncio.sleep(0.3)
forcyclein (1, 2):
fake_now=int(awaitpage.evaluate("Date.now()"))
awaitasyncio.wait_for(page.clock.pause_at(fake_now), timeout=10)
print(f"cycle {cycle}: pause_at OK")
awaitpage.clock.resume()
awaitasyncio.sleep(0.3)
awaitbrowser.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.