playwright-python / _impl
AGENTS.md15k starsChanged 10 days agoApache-2.0View on GitHub
These rules say how to write playwright/impl from packages/playwright-core/src/client in microsoft/playwright. Use them at each roll, for every new or changed line of the client. The upstream code is in .upstream/, the checkout at the commit in DRIVERVERSION. The steps of a roll are in the playwright-roll skill. The port is line by line. The structure of the Python follows the JavaScript. The timing of the Python follows asyncio. A reviewer reads the .ts file and the .py file side…
- Reads credentials
We split this AGENTS.md into the rules it gives an agent
13 rules. About 0 are common, 3 are distinctive, and 10 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
10
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 71
Use asyncio.ensure_future().
- Distinctive: line 133
Do not split a function when no caller depends on the prefix.
- Very specific: line 152
Every parameter of the protocol is passed, by its protocol name.
- Very specific: line 219
When several have settled, the first in the list wins.
- Very specific: line 234
Do not write then() and catch() helpers.
- Very specific: line 294
When the task that awaits an API call is cancelled, the call is aborted.
- Very specific: line 303
Write # Not ported: the signal option at the member.
- Very specific: line 340
Use it only where upstream relies on the task boundary: the transport (1.13) and setTimeout(…, 0) (1.11).
- Distinctive: line 349
Keep the members in the JavaScript order.
- Distinctive: line 415
Keep the text of a message.
- Very specific: line 501
Do not mark a plain rename or a plain await.
- Very specific: line 523
Every object that the generated API wraps carries _loop and _dispatcher_fiber, next to each other.
- Very specific: line 537
Test with _dispatcher_fiber, not with a flag: if route._dispatcher_fiber:.
≈ “Common” is an estimate: at least 1 in 5 of the 36,368 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
How to port the JavaScript client to Python
These rules say how to write playwright/_impl from packages/playwright-core/src/client
in microsoft/playwright. Use them at each roll, for every new or changed line of the
client. The upstream code is in .upstream/, the checkout at the commit in
DRIVER_VERSION. The steps of a roll are in the playwright-roll skill.
The port is line by line. The structure of the Python follows the JavaScript. The timing
of the Python follows asyncio. A reviewer reads the .ts file and the .py file side by
side. Each JavaScript statement maps to one Python statement, or to one of the fixed
patterns in this document.
Each rule shows the JavaScript form, then the Python form. When a rule changes the behavior,
the rule says why. "Mark it" means: add a # Python: comment at that line (see section 11).
The low-level helpers that these rules use are in playwright/_impl/utils/tasks.py:
fire_and_forget(), race(), completed(), failed(), wait_for_next_task() and
call_in_next_task().
1. Promises and asyncio
1.1 The two timing rules of the JavaScript client
The client code relies on two rules of the JavaScript runtime:
- An
asyncfunction runs its body at the call, up to the firstawait. The caller continues after that. - The driver's messages are dispatched one per macrotask. All promise reactions run before the next message.
asyncio does not have these rules:
- A coroutine does not run until a task runs it.
asyncio.ensure_future(coro)runs the first step at the next turn of the event loop, not at the call. - A task resumes one step per turn of the event loop. Several callbacks can run between two steps.
Rule 1 is not imitated. The rules below make the start of a function explicit where it matters (1.4) and leave it to asyncio where it does not.
Rule 2 is imitated by one helper, wait_for_next_task() (see 1.17). JavaScript calls a
macrotask a "task"; the helper waits until the next task would start: until every
reaction to the current task has run. The transport awaits it before it dispatches the
next message (1.13), and setTimeout(callback, 0) is call_in_next_task(callback)
(1.11). Use them where upstream relies on a task boundary; nowhere else.
1.2 await is await
const response = await this._channel.goto(params);response = await self._channel.goto(url=url, waitUntil=wait_until, referer=referer)Both suspend here. This is most of the lines. No mark.
1.3 A promise that is awaited later is a task
const promise = this._doSomething();
await this._somethingElse();
await promise;task = asyncio.ensure_future(self._do_something())
await self._something_else()
await taskUse asyncio.ensure_future(). It takes a coroutine or a future. self._do_something()
starts one turn later than in JavaScript. See 1.4 when that matters. No mark.
An async IIFE, (async () => { ... })(), becomes a named async def method and
asyncio.ensure_future(self._name()). Mark it.
1.4 Split a function at its first await when the caller needs the start now
Some callers depend on timing rule 1: the body of the called function must run before the caller continues. The known cases in the client are:
waitForEvent(),waitForNavigation()and the Pythonexpect_*()context managers: the listener must be registered before the action that fires the event.- A channel call: the message must go out before the caller continues (see 1.5).
_wrapApiCall(): the stack and the zone are captured at the call.- A function that sets a flag before its first
await, when the caller starts several of them and awaits them together (RouteHandler.stop()sets_ignoreException). - An event listener that changes state before its first
await(see 1.12).
For these cases, write the function as a plain def that runs the prefix and returns
the awaitable of the rest. The @api_call() decorator calls the method at once, so the
prefix runs at the call, inside the API zone, and the decorator returns the awaitable.
@apiCall()
async waitForEvent(event, predicate) {
const waiter = Waiter.createForEvent(this, event); // runs at the call
waiter.rejectOnTimeout(...);
const result = await waiter.waitForEvent(this, event, predicate);
waiter.dispose();
return result;
}@api_call()
def wait_for_event(self, event, predicate, timeout) -> Awaitable[Any]:
# Python: a plain def. The waiter and its listeners are set up at the call, as the
# JS async function sets them up before its first await; the result is the wait.
waiter = Waiter.create_for_event(self, event)
waiter.reject_on_timeout(...)
wait = waiter.wait_for_event(self, event, predicate)
async def result() -> Any:
r = await wait
waiter.dispose()
return r
return result()
def expect_event(self, event, predicate, timeout) -> EventContextManagerImpl:
# Python only: waitForEvent() as a context manager. wait_for_event() is a plain
# def, so its listeners are registered here, before the with block runs; the
# wait runs in a task.
return EventContextManagerImpl(
asyncio.ensure_future(self.wait_for_event(event=event, predicate=predicate, timeout=timeout))
)await page.wait_for_event(...) works as before: the decorator returns an awaitable.
Waiter.wait_for_event() is a plain def for the same reason: it registers the listener
and returns the wait.
Do not split a function when no caller depends on the prefix. Mark each split.
1.5 A channel call sends now and returns an awaitable
The generated channel methods are plain def. The message goes out at the call. The
method returns an awaitable of the result. Channel._call() never raises at the call; a
failure is raised when the awaitable is awaited, as a rejected promise is.
await this._channel.click(params);
const { value } = await this._channel.getAttribute(params);
this._channel.screencastFrameAck(params).catch(() => {});await self._channel.click(selector=selector, ...)
value = (await self._channel.getAttribute(selector=selector, name=name))["value"]
fire_and_forget(self._channel.screencastFrameAck(frameId=frame_id))Every parameter of the protocol is passed, by its protocol name. An absent option is
None. timeout is self._timeout(timeout), or NO_TIMEOUT where the JS has
kNoTimeout. A result has every top-level field; a field the driver left out is None.
1.6 Fire and forget: .catch(() => {})
this._disableInterception().catch(() => {});
void this._channel.updateSubscription(params);fire_and_forget(self._disable_interception())
fire_and_forget(self._channel.updateSubscription(event=event, enabled=enabled))fire_and_forget() runs a coroutine as a task and keeps a reference to it. It marks the
exception of the awaitable as handled. It never raises. Use it for every awaitable that
nobody awaits. Never drop an awaitable; never call asyncio.ensure_future() and discard
the task.
1.7 .catch(handler) that is awaited is try / except
const error = await this._doStopChunk(path).catch(e => e);
await route._innerContinue(true).catch(() => {});try:
await self._do_stop_chunk(path)
error = None
except Exception as e:
error = e
try:
await route._inner_continue(is_fallback=True)
except Exception:
passCatch Exception, not BaseException. asyncio.CancelledError must pass through (see
1.14). Keep the JS comment that says why the error is ignored.
1.8 Promise.all() is asyncio.gather()
await Promise.all(promises);
const [handled] = await Promise.all([route._startHandling(), handler(route, request)]);await asyncio.gather(*promises)
handled, _ = await asyncio.gather(route._start_handling(), handler(route, route.request))Both raise the first error and let the other awaitables run on. gather() accepts
coroutines and futures. A JavaScript value in the list is not awaitable in Python; keep it
out of the list and use it directly.
1.9 Promise.race() is race()
return await Promise.race([promise, ...this._failures]);return await race([promise, *self._failures])race() returns the result, or raises the error, of the first awaitable that settles.
When several have settled, the first in the list wins. The coroutines that race()
started for the call are cancelled when the race ends; a future or a task that was given
to race() runs on, as in JavaScript. The errors of the losers are marked as handled, as
Promise.race() handles them.
1.10 Promises that are created by hand
| JavaScript | Python |
new ManualPromise<T>() | ManualPromise[T]() (a future that may be awaited by several tasks) |
new Promise<T>(resolve => { ... }) | future = loop.create_future(), then future.set_result(value) |
Promise.resolve(value) | completed(value) |
Promise.reject(error) | failed(error) |
promise.then(f) | async def that awaits the promise and calls f |
Do not write then() and catch() helpers. Write the async def.
One exception. promise.then(() => { throw e }) is one microtask in JavaScript. A task
is one turn later than a done callback. Where a reaction is only a throw (the failures of
a Waiter), write it as a done callback (future.add_done_callback(...)), or raise in
the listener itself, so that the failure settles at the event. Mark it.
1.11 Timers
const timer = setTimeout(callback, timeout);
clearTimeout(timer);timer = asyncio.get_running_loop().call_later(timeout / 1000, callback)
timer.cancel()JavaScript timeouts are in milliseconds; asyncio timers are in seconds. The callback runs at the next turn after the delay. It does not wait for other tasks.
setTimeout(callback, 0) is different. Upstream uses it to run the callback in the next
task, after the reactions to the events that the current code emits (a timer is a
macrotask; the reactions are microtasks). That is call_in_next_task(callback), which
awaits wait_for_next_task() (1.17) and then calls the callback. Do not use
call_soon() for it: one turn is not the end of the reactions.
setTimeout(() => browser._didClose(), 0);call_in_next_task(lambda: browser._did_close())1.12 Async event listeners
this._channel.on('route', async ({ route }) => { ... });self._channel.onRoute(lambda event: self._on_route(network.Route.from_(event["route"])))emit() calls the listener. When the listener returns an awaitable, EventEmitter
runs it as a task and keeps it in _pending_handlers. An error of the task goes to the
rejection handler of the emitter, or is reported as an unhandled error. The body of an
async def listener runs one turn after the event (timing rule 1 does not hold). When the
state that the listener changes before its first await matters, apply 1.4: register a
plain def that changes the state and returns the awaitable of the rest.
1.13 The transport dispatches each message in its own task
Upstream pipeTransport.ts hands each message to setImmediate(): each message is a
task of its own, so all the reactions to a message run before the next one. The Python
transport does await wait_for_next_task() after each message (1.17).
1.14 Cancellation is Python only
JavaScript cannot cancel a promise. Python can cancel a task. The rules of the port:
- When the task that awaits an API call is cancelled, the call is aborted. The connection
sends
__abort__for its message. - A future that several tasks may await is never cancelled by one of them.
ManualPromiseshields itself in__await__. A shared future that is not aManualPromiseis awaited withasyncio.shield(). Mark it. - Catch
asyncio.CancelledErroronly to clean up, and raise it again. race()andgather()follow these rules. See 1.9.
AbortSignal and the signal options are not ported. Cancellation is their counterpart.
Write # Not ported: the signal option at the member.
1.15 Every task has an owner
A task that nobody holds can be collected while it runs, and its error is lost. Each task is one of:
- awaited by the code that created it (
await task); - kept by
fire_and_forget(); - kept by
EventEmitterin_pending_handlers; - kept by an object that cancels it on dispose (for example the failure tasks of a
Waiter).
1.16 Errors of a task that nobody awaits
In Node.js an unhandled rejection crashes the process. In Python the port reports the
error of an event listener, and the next API call raises it. The error of a
fire_and_forget() awaitable is dropped, as the .catch(() => {}) dropped it.
1.17 wait_for_next_task(): the task boundary
In JavaScript, a task (macrotask) is a message from the driver, a timer, or an I/O
callback. The reactions of promises (microtasks) all run before the next task starts.
asyncio has no such boundary: the callback of a future, and then each task that the
callback wakes up, is a turn of the event loop of its own, in one queue with everything
else. So the reactions to a message are a chain of turns of unknown depth: a wait is two
turns, an expect_*() is three, a gather() adds one more.
wait_for_next_task() waits until that chain is done: it yields until the event loop
has no callback ready to run. Then the next message, or the callback of a 0 ms timer,
runs as the next task would in JavaScript. Without it, a listener that user code
registers after one event misses the next message when both messages were read from the
pipe together.
The ready queue is not public API (loop._ready); on a loop that does not have it, the
helper yields twice, the depth of a wait. It stops after 100 turns.
Use it only where upstream relies on the task boundary: the transport (1.13) and
setTimeout(…, 0) (1.11). Do not use it to order the client's own code; make that
order explicit at the site (1.4, 1.10).
2. Files, classes and members
- One Python module per JavaScript file, in snake_case:
browserContext.tsisbrowser_context.py. The parts ofpackages/isomorphic,packages/protocolandpackages/utilsthat the client uses are underisomorphic/,protocol/andutils/. - Keep the members in the JavaScript order. Keep the JavaScript comments.
constructor()is__init__(). Initialize the fields in the JavaScript order. A field declared with!and set later is annotated and set toNonewithcast().static from(channel)isfrom_(channel), a@staticmethod.static fromNullable()isfrom_nullable().- A getter, or a method without parameters that the generated API reads as a property,
is a
@property. The generated API decides; check_generated.py. Mark it in the module docstring. toJSON()is__repr__().[util.inspect.custom]()is__str__().[Symbol.asyncDispose]()is not ported; the Python API hasclose()or the generated context manager.privateand_namemembers keep the underscore. Public members have no underscore.- A JavaScript module function is a Python module function, at the same position.
3. Names and options
- camelCase is snake_case:
waitForLoadStateiswait_for_load_state. - An options object is keyword arguments. The implementation method takes the arguments
that the generated API passes, in that order, with the Python names. An option that can
be absent is
Optional[X]with no default. So mypy reports a call that does not pass a new option. - The channel call takes the protocol names, in camelCase:
waitUntil=wait_until. options?.xisx.{ ...options, y }is{**options, "y": y}.- A JavaScript parameter that the Python API does not have (for example
noWaitAfteronhover()) is accepted by the implementation method and not sent. Say so in the module docstring. - A user-facing message that names an option or a method names the Python one:
'Pass { acceptDownloads: true }'is"Pass 'accept_downloads=True'". Mark it.
4. undefined, null and None
Noneisundefined. ANoneoption is not sent; the validator drops the key. ANonevalue in a result from the driver is a field that the driver left out.- A JSON
nullinside the data of an array (for example an IndexedDB record in a storage state) isNonetoo, and the validator keeps it, as it is data and not an option. x !== undefinedisx is not None.x ?? yisx if x is not None else y.if (x)on an object or an array isif x is not None:{}and[]are true in JavaScript and false in Python. Mark it.if (x)on a string or a number isif x.x || yon an object isx if x is not None else y. On a string or a number it isx or y.- A value that goes to the page (an evaluate argument) and is
Noneisundefined, as the Python client has always sent it.
5. Errors
throw new Error(message)israise Error(message). The classes are the public ones ofplaywright/_impl/_errors.py:Error,TimeoutError,TargetClosedError. Import them fromplaywright/_impl/errors.py.assert(condition, message)from@isomorphic/assertisif not condition: raise Error(message). Never use theassertstatement: it is removed bypython -O, and it raises the wrong class.e.messageise.message.e.nameise.name.e.stackise._stack.rewriteErrorMessage(e, message)isrewrite_error_message(e, message). The Python version returns a copy, because one error object can fail several calls. Mark the call.isTargetClosedError(e)isis_target_closed_error(e):try { ... } catch (e) { if (isTargetClosedError(e)) return; throw e; }try: ... except Exception as e: if is_target_closed_error(e): return raise- Keep the text of a message. Change only the names of options and methods (see 3).
- An error that comes from the driver is parsed by
parse_error(); a client error is serialized byserialize_error(). Both keepname,messageandstack.
6. Events
this._channel.on('x', handler)isself._channel.onX(handler). The generatedonX()passes the event payload as a dict with every top-level field; an absent field isNone.this.emit(Events.Page.Close, this)isself.emit(Events.Page.Close, self).emit()returnsTruewhen a listener ran.on(),once(),addListener(),removeListener(),off()keep their Node.js semantics inevent_emitter.py. A listener is a plain function; see 1.12 forasync.- The constants of
events.tsare inevents.py, with the same strings.
7. Types
| JavaScript | Python |
interface X { a: string; b?: number } | class X(TypedDict) with b: NotRequired[...], or a total=False TypedDict |
T | undefined, T? | Optional[T] |
Array<T> as input | Sequence[T] |
Array<T> as output or a field | List[T] |
Record<string, T> | Dict[str, T] |
Map<K, V> | Dict[K, V]; utils/map.py when the keys are objects compared by identity |
Set<T> | Dict[T, None] when the order matters, else Set[T] |
Buffer | bytes; base64 at the channel boundary |
number | float, or int where the protocol says integer |
string | RegExp | Union[str, Pattern[str]] |
Function, (a: A) => B | Callable[[A], B] |
Promise<T> | Awaitable[T] as a return type; asyncio.Future[T] for a future |
The protocol types are generated into protocol_types.py; never write them by hand. The
public TypedDicts of the API are generated into playwright/_impl/_api_structures.py.
8. Strings, numbers, regular expressions and JSON
String(n)isjs_string(n):30000.0prints as30000. Use it in messages.- A template literal is an f-string.
JSON.stringify(x)isjson.dumps(x, separators=(",", ":"), ensure_ascii=False).JSON.parse(s)isjson.loads(s).json.loads()acceptsNaN; mark it where it matters.- A
RegExpis are.Pattern. The flags go to the driver as JavaScript letters throughescape_regex_flags(); a flag that JavaScript does not have raises. s.startsWith(x)iss.startswith(x).xs.includes(x)isx in xs.encodeURIComponent(s)isurllib.parse.quote(s, safe="").Buffer.from(s, 'base64')isbase64.b64decode(s).buffer.toString('base64')isbase64.b64encode(buffer).decode().Number.MAX_SAFE_INTEGERis the constantMAX_SAFE_INTEGERin the module that uses it.
9. Files, paths and the process
await fs.promises.readFile(p)isawait asyncio.to_thread(Path(p).read_bytes). Never read or write a file on the event loop.fs.promises.mkdir(p, { recursive: true })isos.makedirs(p, exist_ok=True), in a thread.path.resolve(p)isos.path.abspath(p).path.basename(p)isos.path.basename(p).path.join()isos.path.join(). APathargument is accepted everywhere a string is.process.env.Xisos.environ.get("X").process.platformissys.platform.process.cwd()isos.getcwd().
10. Loops and collections
for (const x of xs)isfor x in xs.xs.map(f)is a list comprehension.xs.filter(f)is a list comprehension withif.xs.find(f)isnext((x for x in xs if f(x)), None).xs.some(f)isany(f(x) for x in xs).xs.every(f)isall(...).xs.indexOf(x)compares by identity:next((i for i, y in enumerate(xs) if y is x), -1). Mark it.xs.splice(i, 1)isdel xs[i].xs.push(x)isxs.append(x).Object.entries(o)iso.items().Object.keys(o)iso.keys().delete o.xiso.pop("x", None).- JavaScript iterates a
Mapor aSetthat changes during the loop. Python does not. Iterate a copy:for x in list(xs). Mark it.
11. Marks
Three marks, each a comment on the line before the code:
# Python: <reason>— the Python does something different from the JavaScript. Say what and why, in one or two lines. Use it for every rule above that says "mark it".# Python only: <what>— code with no JavaScript counterpart: a helper, theexpect_*()methods,__repr__().# Not ported: <member> <reason>— a JavaScript member that is left out. Give the reason:langs: jsin the docs, a test hook, a Node.js internal.
Do not mark a plain rename or a plain await. Do not repeat the code in the comment. When
a whole module deviates in the same way, say it once in the module docstring.
12. What is not ported
- A member with
* langs: jsindocs/src/api/*.md. Check the docs, not the name. - The
signaloptions andAbortSignal(see 1.14). - The
exposeFunctionsoptions andaddInitScript(script, arg). - The
__testHook*options: a JavaScript function that the server calls. android.ts,electron.ts,coverage.ts:langs: js.channels.d.ts: the generator writesprotocol_channels.pyandprotocol_types.py.
13. The sync API
The sync API is not a port. It runs on greenlets, in
playwright/sync_api/_context_manager.py, playwright/_impl/_sync_base.py and
playwright/_impl/_greenlets.py.
- The dispatcher fiber is a greenlet that runs the event loop. The user code runs in the
main greenlet. A sync call creates a task for the coroutine and switches to the
dispatcher fiber until the task is done (
wait_for_future()); between sync calls the loop is paused inside the callback that resumed the user code. - Every object that the generated API wraps carries
_loopand_dispatcher_fiber, next to each other. The fiber isNoneunder the async API. AChannelOwnertakes them from the connection; a plain class takes them from the object that creates it, which is a parameter where upstream has none (mark it). - A user callback that may block in a sync call runs in a greenlet of its own, so that
it can switch to the dispatcher fiber: the connection runs each channel listener in an
EventGreenlet(Connection.dispatch()); a callback that a task calls and awaits runs throughrun_in_greenlet(): a route handler (RouteGreenlet), a WebSocket route handler (WebSocketRouteGreenlet), a binding (BindingGreenlet) and a locator handler (LocatorHandlerGreenlet). A callback that runs in the dispatcher fiber itself would spin inwait_for_future(): a switch to the current greenlet returns at once. - A predicate is called in the listener of the event, which runs in the greenlet of the
event. A wrapper around a user predicate is a plain def that returns the user's result
(
wait_for_response()): anasync defwrapper would call the predicate in a task. - Test with
_dispatcher_fiber, not with a flag:if route._dispatcher_fiber:. - The async API is the port; the sync branch is always the
# Python:marked one.
Trending
More talk lately.about the whole repository
Who's talking about microsoft/playwright-python
- Playwright“Playwright at PYCon US with Andrew Knight”YouTube · May 12, 2023 · 6.1k views
- Bee Solutions“Monitoramento de Serviços com Downdetector Atualizado com Zabbix e Grafana - (BEE 2026)”YouTube · Jan 7, 2026 · 2.9k views
- QA Mania“Playwright python - how to run browsers concurrently”YouTube · Dec 11, 2020 · 2.5k views
- 開発ライブラリ紹介“取得したら中身が空っぽ?|playwrightでJSサイトも丸ごと取得 #shorts”YouTube · Sep 5, 2026 · 1.1k views
- coursegurukul“What is Playwright? | The Future of Web Testing | Playwright Tutorial for Beginners | Course Gurukul”YouTube · Apr 26, 2025 · 258 views
The biggest 5 of 72 mentions linking to the repository that our crawl found. Each line is their own headline.
See all 72
Talked about on GitHub, Stack Overflow, YouTube, the web and Hacker News (72 times).
On GitHub · 50
- Feb 9, 2022[Feature] Directly set Proxy-Authorization in Chrome without configuring any other proxy settings — microsoft/playwright65 reactions · 12 comments
- Jan 12, 2021[Feature] Support for Raspberry PI — microsoft/playwright34 reactions · 8 comments
- Sep 13, 2020[Feature] Driver - allow to install only selected browsers — microsoft/playwright14 reactions · 3 comments
- Nov 10, 2023[BUG] Opening page with large number of IFrames crashes/hangs playwright — microsoft/playwright9 reactions · 7 comments
- Nov 9, 2023[BUG] Unable to access iframe in firefox (works with chromium and webkit) — microsoft/playwright9 reactions · 3 comments
45 more on GitHub
- Jan 30, 2026python3Packages.playwright: 1.57.0 -> 1.58.0 — NixOS/nixpkgs5 reactions · 1 comment
- Sep 17, 2023[Feature] Open Telemetry Tracing — microsoft/playwright5 reactions · 2 comments
- Jun 9, 2026Bump the uv-all group across 1 directory with 3 updates — Ven0m0/dot-termux3 reactions · 1 comment
- May 20, 2026Bump the pip-all group across 1 directory with 3 updates — Ven0m0/dot-termux3 reactions · 1 comment
- May 3, 2024[Feature]: Proper support for scrolling to the bottom of a page — microsoft/playwright3 reactions · 7 comments
- Dec 30, 2022Update playwright driver to 1.29.1 — YusukeIwaki/playwright-ruby-client3 reactions · 3 comments
- Jan 22, 2021[Feature](playwright-cli) Option to install system dependencies — microsoft/playwright3 reactions · 4 comments
- Jul 7, 2026Bump the uv-all group across 1 directory with 2 updates — Ven0m0/dot-termux2 reactions · 2 comments
- Jul 7, 2026Bump the pip-all group across 1 directory with 2 updates — Ven0m0/dot-termux2 reactions · 2 comments
- Jun 30, 2026Bump playwright from 1.60.0 to 1.61.0 in the uv-all group — Ven0m0/dot-termux2 reactions · 2 comments
- Jun 30, 2026Bump playwright from 1.60.0 to 1.61.0 in the pip-all group — Ven0m0/dot-termux2 reactions · 2 comments
- May 20, 2026chore(deps): update playwright requirement from >=1.40.0 to >=1.60.0 — EvezArt/evez-claw-extensions2 reactions · 2 comments
- May 12, 2026chore(deps): update playwright requirement from >=1.40.0 to >=1.59.0 — EvezArt/evez-claw2 reactions · 2 comments
- Mar 26, 2026new rule - suggest changing function parameter type to readonly alternative if parameter is not mutated inside the function — astral-sh/ruff2 reactions · 1 comment
- Jul 31, 2024Please update Playwright-python to 1.40 for Python 3.12 compatibility — daijro/hrequests2 reactions · 4 comments
- Aug 13, 2022Flaky test: CypressError: `cy.request()` timed out waiting `30000ms` for a response from your server — cypress-io/cypress2 reactions · 7 comments
- Feb 3, 2022Installation fails using PDM — pytorch/vision2 reactions · 3 comments
- Oct 25, 2021[Regression] Page.screenshot does not work inside Docker with BrowserServer — microsoft/playwright2 reactions · 6 comments
- Oct 16, 2020setting asyncclick to use asyncio.WindowsProactorEventLoopPolicy — python-trio/asyncclick2 reactions · 2 comments
- Oct 29, 2026build(deps-dev): bump playwright from 1.63.0 to 1.64.1 — paulkakell/timeboardapp1 reaction · 1 comment
- Oct 26, 2026chore(deps-pip): bump the pip-jade-sprint-minor-patch group in /mission-control/jade_temple/sprint with 2 updates — GBOGEB/pipeline-automation-hub1 reaction · 2 comments
- Oct 26, 2026chore(deps-dev): bump playwright from 1.64.0 to 1.64.1 — MoonLadderStudios/MoonMind1 reaction · 1 comment
- Oct 26, 2026deps(deps): bump the pip-minor-patch group with 5 updates — cyxc1124/cyxcbot1 reaction · 1 comment
- Oct 26, 2026[Docs]: python - release notes haven't been updated since 1.63 — microsoft/playwright1 reaction
- Oct 26, 2026Update playwright requirement from >=1.62.0 to >=1.64.1 — WhiteDevil-93/Hermes-pro1 reaction · 2 comments
- Oct 25, 2026chore(deps): bump the uv-minor-patch group across 1 directory with 6 updates — mzworthington/RoMini1 reaction · 1 comment
- Oct 23, 2026chore(deps): update playwright requirement from >=1.40 to >=1.64.0 — escoffier-labs/brigade1 reaction · 3 comments
- Oct 21, 2026Build(deps): update playwright requirement from >=1.63.0 to >=1.64.0 in /tests/e2e — leynos/axinite1 reaction · 3 comments
- Oct 20, 2026fix(deps-dev): bump playwright from 1.50.0 to 1.63.0 — openai/openai-agents-python1 reaction · 4 comments
- Oct 18, 2026deps(deps): bump the pip-minor-patch group across 1 directory with 7 updates — cyxc1124/cyxcbot1 reaction · 1 comment
- Oct 17, 2026build(deps-dev): bump playwright from 1.63.0 to 1.64.0 — paulkakell/timeboardapp1 reaction · 2 comments
- Oct 17, 2026deps: bump playwright from 1.63.0 to 1.64.0 — scope-impact/rdm1 reaction · 1 comment
- Oct 17, 2026chore(deps-dev): bump playwright from 1.63.0 to 1.64.0 — MoonLadderStudios/MoonMind1 reaction · 1 comment
- Oct 8, 2026build(deps): bump playwright from 1.57.0 to 1.63.0 — Kotodama-Project/Kotodama-project1 reaction · 3 comments
- Sep 23, 2026Build(deps): update playwright requirement from >=1.61.0 to >=1.63.0 in /tests/e2e — leynos/axinite1 reaction · 1 comment
- Sep 22, 2026Bump playwright from 1.62.0 to 1.63.0 — gvatsal60/resume-studio1 reaction · 2 comments
- Sep 21, 2026chore(deps): Bump the python-deps group across 1 directory with 3 updates — julianocosta89/sig-meeting-notes1 reaction · 1 comment
- Sep 21, 2026Bump playwright from 1.62.0 to 1.63.0 — MoonLadderStudios/MoonMind1 reaction
- Aug 18, 2026chore(deps): bump the python-deps group across 1 directory with 2 updates — jimc1682000/film-brain1 reaction · 2 comments
- Aug 10, 2026Build(deps): update playwright requirement from >=1.61.0 to >=1.62.0 in /tests/e2e — leynos/axinite1 reaction · 1 comment
- Aug 10, 2026Chore(deps-dev): bump playwright from 1.61.0 to 1.62.0 — MoonLadderStudios/MoonMind1 reaction
- Aug 4, 2026chore(deps): update playwright requirement from >=1.61.0 to >=1.62.0 in the python-deps group across 1 directory — jimc1682000/film-brain1 reaction · 5 comments
- Aug 4, 2026Bump playwright from 1.61.0 to 1.62.0 — gvatsal60/resume-studio1 reaction · 1 comment
- Jul 26, 2026deps: update playwright requirement from <2,>=1.45 to >=1.61.0,<2 — Jnapier2/image-downloader1 reaction · 2 comments
- Jul 16, 2026Build(deps): update playwright requirement from >=1.40 to >=1.61.0 in /tests/e2e — leynos/axinite1 reaction
On Stack Overflow · 11
- Aug 25, 2018python - webdriver and asyncio25 votes
- Apr 1, 2022How to use the Playwright library in a Jupyter notebook instead of using a regular .py script (on Windows)14 votes
- Oct 30, 2022Installing playwright in Docker image fails3 votes
- Jun 1, 2022Can I test Electron application using Playwright Python library?3 votes
- Jun 21, 2024How to save django db data in the middle of a playwright test?2 votes
6 more on Stack Overflow
- May 17, 2023Right mouse click and selecting the appropriate item using Playwright2 votes
- Feb 26, 2024PyInstaller with Playwright and Firefox0 votes
- Dec 27, 2022Playwright and PM2 Issue - hang while creating PlaywrightContextManager0 votes · 1 answer
- Nov 5, 2022Install playwright from a local directory0 votes
- Oct 16, 2024Encountering NotImplementedError in Asynchronous Code with Playwright: Understanding the Cause and Seeking Solutions-1 votes
- Feb 24, 2022Making sure my headless browser is in the right place in my Playwright + Python + Aws lambda setup-1 votes
On YouTube · 5
- May 12, 2023Playwright at PYCon US with Andrew Knight — Playwright6.1k views · 7 comments
- Jan 7, 2026Monitoramento de Serviços com Downdetector Atualizado com Zabbix e Grafana - (BEE 2026) — Bee Solutions2.9k views · 17 comments
- Dec 11, 2020Playwright python - how to run browsers concurrently — QA Mania2.5k views · 8 comments
- Sep 5, 2026取得したら中身が空っぽ?|playwrightでJSサイトも丸ごと取得 #shorts — 開発ライブラリ紹介1.1k views
- Apr 26, 2025What is Playwright? | The Future of Web Testing | Playwright Tutorial for Beginners | Course Gurukul — coursegurukulsays it tested it258 views
On the web · 4
- Mar 19, 2024Python Playwright doesn't work in my computer · Issue #2364 — google.com
- Aug 23, 2022Python Playwright docker issue #1529 — google.com
- Oct 17, 2021Microsoft has released a Playwright-Python — google.com
- May 12, 2021[FEATURE] Playwright on PyPy · Issue #687 — google.com
On Hacker News · 2
- Sep 30, 2020Python version of the Playwright testing and automation librarysays it tested it2 points
- Oct 5, 2020Playwright: Microsoft's end-to-end testing framework for Pythonsays it tested it1 point · 1 comment
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 28,693 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repository5 other files it gives its agents.
- AGENTS.mdAGENTS.md
- tests/AGENTS.mdAGENTS.md
- playwright-releaseSkill
- playwright-rollSkill
- playwright-triageSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

