You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug]: The Python version of Playwright is not compatible with PEP-749/PEP-649 annotation semantics #42857
NameError is emitted for Unresolved despite not being needed by playwright in order to function correctly.
Additional context
The Python version makes use of inspect.signature to inspect the parameters of callbacks passed to methods like Page.on, but does not make use of the newly added format parameter in Python 3.14+, despite not needing to inspect the annotations.
It's best practice to use Format.FORWARDREF or Format.STRING if you don't need to inspect annotations, so the deferred execution of the annotations does not result in a NameError for any symbols only defined inside an if TYPE_CHECKING block.
I recommend writing a little compat helper like the following:
Hi, I'm the Playwright bot and I took a first look at this.
Reproduced — this is a real bug on Python 3.14. Both inspect.signature() call sites in playwright-python 1.63.0 evaluate handler annotations under PEP 649/749 deferred semantics, so a handler annotated with a name that only exists under if TYPE_CHECKING: blows up with NameError when the event actually fires:
FAILED test_real.py::test_console_handler[chromium] - NameError: Page.wait_for_timeout: name 'ConsoleMessage' is not defined
Worth noting this is a 3.14 regression, not a pre-existing wart: the exact same file passes on Python 3.12. And your diagnosis is right on both counts — the annotations are never needed (only parameter.kind is read), and annotation_format=Format.FORWARDREF fixes it. I patched both call sites in an installed 1.63.0 and the repro passes.
The second call site is worse than the first, because the NameError escapes into add_locator_handler's internals and gets swallowed: instead of a clean failure you get Locator.click: Timeout 30000ms exceeded with locator handler has finished, waiting for locator("#o") to be hidden looping 64 times, and the NameError only surfaces later attached to an unrelated call (Tracing.stop).
Full matrix — what I ran
Environment: Linux x64, CPython 3.14.7 and 3.12.3 (via uv), playwright==1.63.0 + pytest-playwright, Chromium headless shell.
The two call sites, both confirmed present in the installed 1.63.0:
File
Line
Caller
playwright/_impl/_impl_to_api_mapping.py
122
ImplToApiMapping.wrap_handler → wrapper_func
playwright/_impl/_page.py
128
LocatorHandler.__call__
Note both evaluate the signature lazily, at dispatch time, not at registration — page.on(...) and page.add_locator_handler(...) return fine; the NameError only lands once the event fires. That's why this can hide behind a timeout.
Results:
Case
3.12.3
3.14.7
bare def cb(msg: Unresolvable), page.on("console", ...)
NameError at def time (annotations eager — not a valid repro pre-3.14)
NameError at dispatch ✅ repro
bare def h(loc: Unresolvable), add_locator_handler
NameError at def time
30s Locator.click timeout, NameError leaks to Tracing.stop ✅ repro
if TYPE_CHECKING: import, no __future__
n/a (name undefined at runtime pre-3.14)
NameError ✅ repro — the realistic case
if TYPE_CHECKING: import +from __future__ import annotations
passes
passes
That last row is the useful boundary: with from __future__ import annotations the annotations are plain strings and inspect.signature leaves them alone (eval_str=False), so it works on both. It's specifically PEP 649 native deferred annotations — no __future__ import, which is the 3.14 default — that break.
Isolated check of the mechanism:
inspect.signature (default) -> NameError: name 'Unresolvable' is not defined
signature(FORWARDREF) -> OK (msg: Unresolvable) -> None
Patching both call sites to annotation_format=Format.FORWARDREF on the installed 1.63.0 clears the NameError on all of the above; the only remaining failure in my scratch suite was my own throwaway locator-handler test, whose handler never hides the overlay, so the click times out for an unrelated reason.
Not verified: 3.13 with PYTHON_FUTURE_ANNOTATIONS-style opt-in, free-threaded builds, or the async API surface — I only exercised sync_api. The async path goes through the same wrap_handler, so I'd expect it to behave identically.
One caveat on status: microsoft/playwright-python#3204 is linked here and proposes exactly this fix, but at the time I looked it's still open and unmerged, so nothing has shipped — 1.63.0 is affected and so is current main.
Triaged on Linux by the Playwright bot - agent run
Activity
github-actions commentedon Sep 23, 2026
Hi, I'm the Playwright bot and I took a first look at this.
Reproduced — this is a real bug on Python 3.14. Both
inspect.signature()call sites inplaywright-python1.63.0 evaluate handler annotations under PEP 649/749 deferred semantics, so a handler annotated with a name that only exists underif TYPE_CHECKING:blows up withNameErrorwhen the event actually fires:Worth noting this is a 3.14 regression, not a pre-existing wart: the exact same file passes on Python 3.12. And your diagnosis is right on both counts — the annotations are never needed (only
parameter.kindis read), andannotation_format=Format.FORWARDREFfixes it. I patched both call sites in an installed 1.63.0 and the repro passes.The second call site is worse than the first, because the
NameErrorescapes intoadd_locator_handler's internals and gets swallowed: instead of a clean failure you getLocator.click: Timeout 30000ms exceededwithlocator handler has finished, waiting for locator("#o") to be hiddenlooping 64 times, and theNameErroronly surfaces later attached to an unrelated call (Tracing.stop).Full matrix — what I ran
Environment: Linux x64, CPython 3.14.7 and 3.12.3 (via uv),
playwright==1.63.0+pytest-playwright, Chromium headless shell.The two call sites, both confirmed present in the installed 1.63.0:
playwright/_impl/_impl_to_api_mapping.pyImplToApiMapping.wrap_handler→wrapper_funcplaywright/_impl/_page.pyLocatorHandler.__call__Note both evaluate the signature lazily, at dispatch time, not at registration —
page.on(...)andpage.add_locator_handler(...)return fine; theNameErroronly lands once the event fires. That's why this can hide behind a timeout.Results:
def cb(msg: Unresolvable),page.on("console", ...)NameErrorat def time (annotations eager — not a valid repro pre-3.14)NameErrorat dispatch ✅ reprodef h(loc: Unresolvable),add_locator_handlerNameErrorat def timeLocator.clicktimeout,NameErrorleaks toTracing.stop✅ reproif TYPE_CHECKING:import, no__future__NameError✅ repro — the realistic caseif TYPE_CHECKING:import +from __future__ import annotationsThat last row is the useful boundary: with
from __future__ import annotationsthe annotations are plain strings andinspect.signatureleaves them alone (eval_str=False), so it works on both. It's specifically PEP 649 native deferred annotations — no__future__import, which is the 3.14 default — that break.Isolated check of the mechanism:
Patching both call sites to
annotation_format=Format.FORWARDREFon the installed 1.63.0 clears theNameErroron all of the above; the only remaining failure in my scratch suite was my own throwaway locator-handler test, whose handler never hides the overlay, so the click times out for an unrelated reason.Not verified: 3.13 with
PYTHON_FUTURE_ANNOTATIONS-style opt-in, free-threaded builds, or the async API surface — I only exercisedsync_api. The async path goes through the samewrap_handler, so I'd expect it to behave identically.One caveat on status: microsoft/playwright-python#3204 is linked here and proposes exactly this fix, but at the time I looked it's still open and unmerged, so nothing has shipped — 1.63.0 is affected and so is current
main.Triaged on Linux by the Playwright bot - agent run