pw-repl 0.8.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +8 -3
- package/README.md +3 -3
- package/bin/pw-repl.js +33 -41
- package/lib/cli-names.js +4 -3
- package/lib/client.js +3 -0
- package/lib/cursor.js +50 -9
- package/lib/elements.js +5 -4
- package/lib/help.js +496 -351
- package/lib/inspect.js +1 -1
- package/lib/record.js +66 -22
- package/lib/runner.js +2 -1
- package/lib/send.js +68 -1
- package/package.json +1 -1
- package/skill/SKILL.md +19 -10
package/lib/help.js
CHANGED
|
@@ -38,7 +38,7 @@ Common tasks:
|
|
|
38
38
|
see each step as I click watch on --live
|
|
39
39
|
watch a tab someone will open watch on --next-tab [url-part]
|
|
40
40
|
requests and console together capture on, then capture off
|
|
41
|
-
a video of
|
|
41
|
+
a video of what I do help video
|
|
42
42
|
break the backend on purpose route <glob> <status> <json>, route <glob> abort, network off
|
|
43
43
|
change or slow an API response route <glob> patch <json>, route <glob> delay <secs>
|
|
44
44
|
slow the whole network network slow
|
|
@@ -52,13 +52,13 @@ Topics (help <topic>):
|
|
|
52
52
|
tabs open, select, close, navigate network requests, bodies, console, fakes, network off
|
|
53
53
|
interact click, fill, type, press, select devtools eval, CDP commands, cookie and storage names
|
|
54
54
|
inspect snapshot, watch, text, screenshots session modes, markers, server, quitting
|
|
55
|
+
video record a take, with a pointer
|
|
55
56
|
|
|
56
|
-
help <command> for usage and caveats (e.g. help route)
|
|
57
|
+
help <command> for usage and caveats (e.g. help route), help <command> --all for all of it; help --all
|
|
58
|
+
for everything.
|
|
57
59
|
|
|
58
60
|
Know playwright-cli? Its command names work here too (tab-list, go-back, set-color-scheme, route
|
|
59
|
-
--status=...); help playwright-cli lists them
|
|
60
|
-
|
|
61
|
-
Recording video? help video says which way suits what you want.`);
|
|
61
|
+
--status=...); help playwright-cli lists them.`);
|
|
62
62
|
|
|
63
63
|
const TOPICS = {
|
|
64
64
|
tabs: {
|
|
@@ -74,7 +74,42 @@ fill for selectors with spaces. mousemove and the other mouse commands act at a
|
|
|
74
74
|
},
|
|
75
75
|
inspect: {
|
|
76
76
|
intro: `Output is capped; --all right after the command, or at the end, shows everything (text --all body).`,
|
|
77
|
-
commands: ['snapshot', 'watch', 'text', 'html', 'attrs', 'listeners', 'count', 'visible', 'links', 'inputs', 'screenshot', 'highlight', '
|
|
77
|
+
commands: ['snapshot', 'watch', 'text', 'html', 'attrs', 'listeners', 'count', 'visible', 'links', 'inputs', 'screenshot', 'highlight', 'viewport', 'emulate', 'wait', 'sleep'],
|
|
78
|
+
},
|
|
79
|
+
video: {
|
|
80
|
+
intro: `A video of what you do in a tab, for someone to watch. Record it from a file of commands sent with
|
|
81
|
+
pw-repl send --file, not command by command: the time between commands sent one by one is in the video.
|
|
82
|
+
|
|
83
|
+
1. Set its size first: viewport 1280x720 (help viewport). Then try the flow without recording, and note
|
|
84
|
+
the selectors it needs (#id, text=..., role=...), not snapshot refs: the take starts again from a fresh
|
|
85
|
+
page, where the refs are stale.
|
|
86
|
+
|
|
87
|
+
2. Write the take, one command per line, then run it: pw-repl send --file take.txt (pw-repl help, outside
|
|
88
|
+
the REPL, has the rest). Its first line puts the page back as it was before the rehearsal (goto <url>),
|
|
89
|
+
so the take does not start from the rehearsal's end.
|
|
90
|
+
|
|
91
|
+
goto https://shop.example/support/new
|
|
92
|
+
record on clip.webm
|
|
93
|
+
cursor on #subject
|
|
94
|
+
type #subject Package arrived damaged
|
|
95
|
+
select #area Shipping
|
|
96
|
+
click #send
|
|
97
|
+
wait text Ticket filed
|
|
98
|
+
record off
|
|
99
|
+
|
|
100
|
+
3. While it records, pw-repl paces it for a viewer (help record): type types at a readable pace (use it,
|
|
101
|
+
not fill, for text the viewer should see typed), each action is followed by a pause, an element out of
|
|
102
|
+
view is scrolled to smoothly, and the video starts and ends on a still page. highlight <selector> draws a
|
|
103
|
+
box to point at something. After a step that loads a page, wait for text only the result shows (wait
|
|
104
|
+
text ...), not a heading the page always has, and not wait load: a single-page app loads nothing.
|
|
105
|
+
|
|
106
|
+
record on --steps also saves when and where each step ran, in the video's seconds and pixels, for
|
|
107
|
+
editing it afterwards.
|
|
108
|
+
|
|
109
|
+
The user's own screen, with their real pointer, takes a screen recorder they run (OBS, ffmpeg -f
|
|
110
|
+
x11grab, macOS screencapture -v): ask first. playwright-cli itself is a separate tool that records a
|
|
111
|
+
clean browser of its own (here, its video-start is record on).`,
|
|
112
|
+
commands: ['record', 'cursor'],
|
|
78
113
|
},
|
|
79
114
|
network: {
|
|
80
115
|
intro: `requests and console record all the time; a capture records only while it runs.`,
|
|
@@ -114,27 +149,25 @@ const COMMANDS = {
|
|
|
114
149
|
tab: {
|
|
115
150
|
usage: 'tab [<index>|<url-part>|new [url]|close [url-part]]',
|
|
116
151
|
summary: 'list tabs (* is selected); select, open or close one',
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
the
|
|
125
|
-
|
|
126
|
-
Closing the selected tab goes back to the previous tab if tab new opened it; otherwise no tab is
|
|
152
|
+
args: [
|
|
153
|
+
['(none)', 'lists the tabs: * marks the selected one, and each says who opened it with tab new.'],
|
|
154
|
+
['<index>', 'selects by the number in your latest listing (each client has its own); numbers change when tabs open or close.'],
|
|
155
|
+
['<url-part>', 'selects the one tab whose URL contains it; refused if none or several do.'],
|
|
156
|
+
['new [url]', 'opens a tab behind the one in front, so it does not take the window from whoever is using it, and selects it. The URL is read as goto reads it.'],
|
|
157
|
+
['close [url-part]', 'closes the selected tab, or the one matching url-part.'],
|
|
158
|
+
],
|
|
159
|
+
detail: `Closing the selected tab goes back to the previous tab if tab new opened it; otherwise no tab is
|
|
127
160
|
selected, and commands that need one refuse until one is.`,
|
|
128
161
|
},
|
|
129
162
|
goto: {
|
|
130
163
|
usage: 'goto <url>',
|
|
131
164
|
summary: 'navigate the selected tab',
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
A page that does not load says why, as wait load does: a failed request, after which the tab shows
|
|
136
|
-
Chrome's error page (info names the URL that failed), or an answer with no page (a 204), which
|
|
137
|
-
|
|
165
|
+
args: [
|
|
166
|
+
['<url>', 'without a scheme, http:// for localhost, 127.x and [::1], and https:// otherwise, as in the address bar.'],
|
|
167
|
+
],
|
|
168
|
+
detail: `A page that does not load says why, as wait load does: a failed request, after which the tab shows
|
|
169
|
+
Chrome's error page (info names the URL that failed), or an answer with no page (a 204), which leaves
|
|
170
|
+
the tab where it was. tab new, reload, back and forward say it the same way.`,
|
|
138
171
|
},
|
|
139
172
|
back: {
|
|
140
173
|
usage: 'back',
|
|
@@ -149,139 +182,163 @@ afresh.`,
|
|
|
149
182
|
click: {
|
|
150
183
|
usage: 'click <selector> [button]',
|
|
151
184
|
summary: 'click the first match that can be clicked',
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
185
|
+
args: [
|
|
186
|
+
['<selector>', 'a Playwright selector or a snapshot ref (e5); quote one with spaces.'],
|
|
187
|
+
['[button]', 'left, right or middle, after a ref or a quoted selector: click e5 right. Default: left.'],
|
|
188
|
+
],
|
|
189
|
+
options: [
|
|
190
|
+
['--modifiers=<keys>', 'keys held down meanwhile, comma-separated: Alt, Control, ControlOrMeta, Meta, Shift.'],
|
|
191
|
+
],
|
|
192
|
+
detail: `Several matches: hidden ones and ones covered by something else (a dialog's overlay) are passed over,
|
|
193
|
+
and it says which it used: Clicked: text=Buy (match 3 of 4; ...). If none can be clicked within 5s, it
|
|
194
|
+
says why for each and does nothing. A ref, or a single match, is clicked as it is, waiting up to 5s for
|
|
195
|
+
it to be ready.
|
|
196
|
+
|
|
197
|
+
Timed out after it began acting, it says what stopped it (e.g. <div class="overlay"> intercepts pointer
|
|
198
|
+
events) and that the outcome is unknown; Playwright's full call log goes to the REPL's pane or log.`,
|
|
163
199
|
},
|
|
164
200
|
dblclick: { usage: 'dblclick <selector> [button]', summary: 'double-click, choosing the match as click does' },
|
|
165
201
|
hover: { usage: 'hover <selector>', summary: 'hover, choosing the match as click does' },
|
|
166
202
|
mousemove: {
|
|
167
203
|
usage: 'mousemove <x> <y>',
|
|
168
204
|
summary: 'move the mouse to a point in the viewport',
|
|
169
|
-
|
|
170
|
-
|
|
205
|
+
args: [
|
|
206
|
+
['<x> <y>', 'CSS pixels from the top left of the tab\'s viewport (not the page), as mouse events have them.'],
|
|
207
|
+
],
|
|
208
|
+
detail: `A snapshot has no coordinates; eval of getBoundingClientRect gives an element's centre:
|
|
171
209
|
eval (r => [r.x + r.width / 2, r.y + r.height / 2])(document.querySelector("#map")
|
|
172
210
|
.getBoundingClientRect())
|
|
173
211
|
|
|
174
|
-
A click at a point: mousemove <x> <y>, mousedown, mouseup. A drag: mousedown, then mousemove to
|
|
175
|
-
|
|
212
|
+
A click at a point: mousemove <x> <y>, mousedown, mouseup. A drag: mousedown, then mousemove to another
|
|
213
|
+
point, then mouseup.`,
|
|
176
214
|
},
|
|
177
215
|
mousedown: { usage: 'mousedown [button]', summary: 'press a mouse button where the mouse is (left, right or middle)' },
|
|
178
216
|
mouseup: { usage: 'mouseup [button]', summary: 'release a mouse button where the mouse is' },
|
|
179
217
|
mousewheel: {
|
|
180
218
|
usage: 'mousewheel <dx> <dy>',
|
|
181
219
|
summary: 'turn the wheel where the mouse is',
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
220
|
+
args: [
|
|
221
|
+
['<dx> <dy>', 'CSS pixels; positive dy scrolls down.'],
|
|
222
|
+
],
|
|
223
|
+
detail: `The events go to the mouse's point (mousemove puts it there): a map zooms around it, so mousemove 400
|
|
224
|
+
300, then mousewheel 0 -300 zooms in. It returns once the events are sent, not once the page has
|
|
225
|
+
scrolled or redrawn. While recording, it scrolls in eased steps over up to 1s (help record).`,
|
|
185
226
|
},
|
|
186
227
|
fill: {
|
|
187
228
|
usage: 'fill <selector> <value> [--submit]',
|
|
188
229
|
summary: 'clear an input and fill it',
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
230
|
+
args: [
|
|
231
|
+
['<selector>', 'the first word; quote it if it has spaces: fill "text=Your name" Ada.'],
|
|
232
|
+
['<value>', 'the rest of the line; quotes around all of it are removed, so fill #name "" clears it.'],
|
|
233
|
+
],
|
|
234
|
+
options: [
|
|
235
|
+
['--submit', 'at the end: presses Enter in it afterwards.'],
|
|
236
|
+
],
|
|
237
|
+
detail: `Examples: fill #name Ada Lovelace, fill "text=Your name" Ada, fill e7 Ada. It sets the whole value at
|
|
238
|
+
once, even while recording: type shows it being typed.`,
|
|
194
239
|
},
|
|
195
240
|
type: {
|
|
196
241
|
usage: 'type [<selector>] <text> [--submit]',
|
|
197
242
|
summary: 'type key by key after what the field holds; text as for fill',
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
243
|
+
args: [
|
|
244
|
+
['[<selector>]', 'as for fill. Without it (one word, or one quoted), into what has focus: type "garden hose".'],
|
|
245
|
+
['<text>', 'the rest of the line, as fill\'s value.'],
|
|
246
|
+
],
|
|
247
|
+
options: [
|
|
248
|
+
['--delay=<ms>', 'right after type: the gap between keys. Default: none, or 60 while the tab records.'],
|
|
249
|
+
['--submit', 'at the end: presses Enter afterwards.'],
|
|
250
|
+
],
|
|
251
|
+
detail: `While the tab is recording (help record), keys go 60ms apart, so the video shows the text being typed.
|
|
252
|
+
The whole text takes its length times the gap, and send waits 20s by default: about 300 characters at
|
|
253
|
+
60ms need a longer send -t, or a shorter --delay. --delay is a gap between keys, not a wait before
|
|
254
|
+
starting like screenshot's. A page that reacts to each key, such as search as you type, may react to
|
|
255
|
+
each one.`,
|
|
206
256
|
},
|
|
207
257
|
press: { usage: 'press <key> | press <selector> <key>', summary: 'press a key (Enter, Escape, Control+A; any case), maybe on an element' },
|
|
208
258
|
select: {
|
|
209
259
|
usage: 'select <selector> <value>',
|
|
210
260
|
summary: 'choose an option in a select (the value is the rest of the line)',
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
261
|
+
args: [
|
|
262
|
+
['<value>', 'an option\'s value or its label.'],
|
|
263
|
+
],
|
|
264
|
+
detail: `With the cursor on (help cursor), it also draws the select's list opening and the cursor choosing the
|
|
265
|
+
option, since a native select's list opens outside the page, where no screenshot or recording sees it;
|
|
266
|
+
the page gets the same input either way.`,
|
|
214
267
|
},
|
|
215
268
|
check: { usage: 'check <selector>', summary: 'check a checkbox' },
|
|
216
269
|
uncheck: { usage: 'uncheck <selector>', summary: 'uncheck a checkbox' },
|
|
217
270
|
upload: {
|
|
218
271
|
usage: 'upload <selector> <file>...',
|
|
219
272
|
summary: 'choose files in a file input, as the file picker would',
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
273
|
+
args: [
|
|
274
|
+
['<selector>', 'a file input (even a hidden one), its label, or a button that opens the file picker.'],
|
|
275
|
+
['<file>...', 'one or more; quote one with spaces. Relative to the REPL\'s folder; through pw-repl send, the sender\'s.'],
|
|
276
|
+
],
|
|
277
|
+
detail: `The page then does what it does with a chosen file; often that is the upload itself. The REPL reads
|
|
278
|
+
the files and hands the page their contents, so they need not be where the browser runs; up to 50MB in
|
|
279
|
+
all.`,
|
|
226
280
|
},
|
|
227
281
|
snapshot: {
|
|
228
282
|
usage: 'snapshot [--full] [--grep <text> | <eN> | selector]',
|
|
229
283
|
summary: 'outline by role and name, with [ref=eN] labels',
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
snapshot
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
ref
|
|
284
|
+
args: [
|
|
285
|
+
['(none)', 'the whole page: Playwright\'s accessibility snapshot, with unnamed layout wrappers (generic), cursor hints and elements with no role or name of their own (a card that is only a div) left out.'],
|
|
286
|
+
['<eN>', 'outlines one element; the refs of the last snapshot keep working.'],
|
|
287
|
+
['<selector>', 'outlines the first match, with new refs: those from before stop working.'],
|
|
288
|
+
],
|
|
289
|
+
options: [
|
|
290
|
+
['--full', 'Playwright\'s snapshot unchanged.'],
|
|
291
|
+
['--grep <text>', 'only the lines containing text (role, name or a flag such as [disabled], any case), each with the named elements around it.'],
|
|
292
|
+
['--regex <pattern>', '--grep by a regular expression: snapshot --regex "Total: \\d+".'],
|
|
293
|
+
],
|
|
294
|
+
detail: `Refs: a ref (e3, or f1e3 in newer Playwright) works as a selector in any command: click e3. It belongs
|
|
295
|
+
to the tab whose snapshot gave it, and can change when the page changes (e102 may become f4e98): take a
|
|
296
|
+
new snapshot after it does.
|
|
297
|
+
|
|
298
|
+
--grep: a hit with no named element around it prints without a path. A hit on text (a label) keeps the
|
|
299
|
+
ref of the element holding it, so snapshot <that ref> shows what sits beside it, such as the value next
|
|
300
|
+
to the label.
|
|
246
301
|
|
|
247
302
|
Output over 60 lines ends with a line count.`,
|
|
248
303
|
},
|
|
249
304
|
watch: {
|
|
250
305
|
usage: 'watch on [--changes] [--live] | off | [n|new]',
|
|
251
306
|
summary: 'record what happens in the tab; show the last n steps',
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
(
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
307
|
+
args: [
|
|
308
|
+
['on', 'starts recording the tab\'s steps; after watch off, a new recording.'],
|
|
309
|
+
['off', 'stops it; anyone\'s watch off stops it, since a watch belongs to the tab.'],
|
|
310
|
+
['(none) | <n>', 'says whether it is on, and shows the last 20 steps, or the last n.'],
|
|
311
|
+
['new', 'only the steps not shown yet, and requests that have since arrived for the last one.'],
|
|
312
|
+
],
|
|
313
|
+
optionsFor: 'watch on',
|
|
314
|
+
options: [
|
|
315
|
+
['--changes', 'also what each step changed on the page once it settled, in up to 5 lines: + added, - removed, ~ changed.'],
|
|
316
|
+
['--live', 'also prints each step in the REPL window once it settles (never in a command\'s answer), marked [watch], or [watch <url>] for a tab that is not the selected one.'],
|
|
317
|
+
['--next-tab [part]', 'returns at once, then watches the next tab someone opens, or the next whose URL contains part, from its first page, and selects it for you. tab new, a client\'s own, does not count.'],
|
|
318
|
+
],
|
|
319
|
+
detail: `What a step is: a click, typing (once it pauses), Enter or Escape, a form change, a submit or a
|
|
320
|
+
navigation, described by role and name as in snapshot, with up to 5 of the requests it caused under it,
|
|
321
|
+
each with its status as in requests (body <#> for one), leaving out scripts and what requests hides. The
|
|
322
|
+
REPL's own fill shows as type. A double-click shows as two clicks; hovering and moving the mouse are not
|
|
323
|
+
recorded.
|
|
324
|
+
|
|
325
|
+
Privacy: typed values are not recorded, and password fields not at all; --changes leaves out field
|
|
326
|
+
values, but what the page itself shows (e.g. "Hello <name>") is shown. Nothing is replayed.
|
|
327
|
+
|
|
328
|
+
Shared: every client sees the same steps, and watch new's place is shared.`,
|
|
329
|
+
moreAbout: 'bodies kept, --live timing, what --changes misses, and one --next-tab at a time',
|
|
330
|
+
more: `From its first watch on, a tab keeps response bodies of up to 100 KB, so body <#> still shows one
|
|
259
331
|
after the tab navigates, even when the page navigates as soon as the request answers.
|
|
260
332
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
first page and selects it for the client that asked: for a tab someone is about to open. It takes
|
|
264
|
-
--changes and --live too; watch off stops waiting, and modes lists it. One tab is waited for at a time:
|
|
265
|
-
while one client waits, another's watch on --next-tab is refused, and its watch off leaves the wait on.
|
|
333
|
+
--live: a step cut short by the next one, or by watch off, prints at once; changes it made then show
|
|
334
|
+
under the next step.
|
|
266
335
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
last one.
|
|
336
|
+
--changes: each element once, with the first names inside it. Not for navigations: what a page changes
|
|
337
|
+
on its own after a step settles (data arriving later) shows under the next step.
|
|
270
338
|
|
|
271
|
-
--
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
then show under the next step.
|
|
275
|
-
|
|
276
|
-
--changes also shows what each step changed on the page once it settles, in up to 5 lines: + added,
|
|
277
|
-
- removed, ~ changed, each element once with the first names inside it. Not for navigations: what a
|
|
278
|
-
page changes on its own after a step settles (data arriving later) shows under the next step.
|
|
279
|
-
|
|
280
|
-
A watch belongs to the tab, not to a client: every client sees the same steps, watch new's place is
|
|
281
|
-
shared, and anyone's watch off stops it.
|
|
282
|
-
|
|
283
|
-
Typed values are not recorded, and password fields not at all; --changes leaves out field values,
|
|
284
|
-
but what the page itself shows (e.g. "Hello <name>") is shown. Nothing is replayed.`,
|
|
339
|
+
--next-tab: it takes --changes and --live too; watch off stops waiting, and modes lists it. One tab is
|
|
340
|
+
waited for at a time: while one client waits, another's watch on --next-tab is refused, and its watch
|
|
341
|
+
off leaves the wait on.`,
|
|
285
342
|
},
|
|
286
343
|
text: { usage: 'text [--all] <selector>', summary: 'visible text of the first match' },
|
|
287
344
|
html: { usage: 'html [--all] <selector>', summary: 'outer HTML of the first match' },
|
|
@@ -289,11 +346,13 @@ but what the page itself shows (e.g. "Hello <name>") is shown. Nothing is replay
|
|
|
289
346
|
listeners: {
|
|
290
347
|
usage: 'listeners <selector>|document|window',
|
|
291
348
|
summary: 'the page\'s event listeners on an element',
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
349
|
+
options: [
|
|
350
|
+
['--all', 'each handler whole, as written.'],
|
|
351
|
+
],
|
|
352
|
+
detail: `For the first match, one line each: the event, how it was added (capture, once, passive), the start
|
|
353
|
+
of the handler, where ↵ marks a line break, and its line in its script. Listeners added on a parent
|
|
354
|
+
(e.g. document, for delegation) are not the element's own: check listeners document too. Those
|
|
355
|
+
Playwright adds to window as it acts are left out, and it says how many.`,
|
|
297
356
|
},
|
|
298
357
|
count: { usage: 'count <selector>', summary: 'number of matches' },
|
|
299
358
|
visible: { usage: 'visible <selector>', summary: 'whether the first match is visible' },
|
|
@@ -301,290 +360,364 @@ says how many.`,
|
|
|
301
360
|
inputs: { usage: 'inputs [--all]', summary: 'form controls on the page; values are omitted' },
|
|
302
361
|
highlight: {
|
|
303
362
|
usage: 'highlight [<selector> [--style=<css>] | off]',
|
|
304
|
-
summary: 'draw a box over elements, for
|
|
305
|
-
|
|
306
|
-
tinted box
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
363
|
+
summary: 'draw a box over elements, for the user or a screenshot',
|
|
364
|
+
args: [
|
|
365
|
+
['<selector>', 'draws Playwright\'s highlight over every match, as playwright-cli\'s does: a tinted box labelled with its locator, that follows the element as the page scrolls or changes, and is in screenshots.'],
|
|
366
|
+
['(none)', 'lists the selected tab\'s highlights.'],
|
|
367
|
+
['off', 'hides them all, as highlight --hide does.'],
|
|
368
|
+
],
|
|
369
|
+
options: [
|
|
370
|
+
['--style=<css>', 'CSS added to the box, quoted if it has spaces: highlight e5 --style="outline: 3px solid blue; border-radius: 50%" circles e5 in blue.'],
|
|
371
|
+
['--hide <selector>', 'hides one.'],
|
|
372
|
+
],
|
|
373
|
+
detail: `A highlight is a mode of its tab: modes shows it, modes off hides it, and the REPL hides every one as
|
|
374
|
+
it stops. Loading a new page drops them; a page that changes its route without loading keeps them.
|
|
314
375
|
|
|
315
376
|
The box is drawn over the page, in an element of Playwright's own added to it (x-pw-glass); the page's
|
|
316
377
|
own elements are not changed, and clicks and typing reach them as before. Playwright always labels the
|
|
317
|
-
box, and the label can cover what is just below it
|
|
318
|
-
|
|
319
|
-
|
|
378
|
+
box, and the label can cover what is just below it.`,
|
|
379
|
+
moreAbout: 'marking a screenshot without the label',
|
|
380
|
+
more: `For a screenshot without the label, eval can outline the element itself (with border-radius: 50% for
|
|
381
|
+
a circle). That changes the page, so put it back afterwards, as it was if it had a style of its own:
|
|
320
382
|
eval document.querySelector("#rate-3").style.outline = "3px solid blue"
|
|
321
383
|
eval document.querySelector("#rate-3").style.outline = ""`,
|
|
322
384
|
},
|
|
323
385
|
screenshot: {
|
|
324
386
|
usage: 'screenshot [<ref>] [--full] [-d <secs>] [name]',
|
|
325
387
|
summary: 'save a PNG of the viewport, --full page or an element',
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
388
|
+
args: [
|
|
389
|
+
['[<ref>]', 'only that element, from a snapshot ref: screenshot e5.'],
|
|
390
|
+
['[name]', 'saved as screenshot-<name>.png; letters, digits, underscores and hyphens. Default: a timestamp.'],
|
|
391
|
+
],
|
|
392
|
+
options: [
|
|
393
|
+
['--full', 'the whole page, not only the viewport; --full-page too.'],
|
|
394
|
+
['-d, --delay <secs>', 'counts down out loud first, up to 60, so someone can hold a hover or open a menu.'],
|
|
395
|
+
['--filename=<file>', 'saves it there instead (a JPEG if it ends in .jpg). Relative to the REPL\'s folder; through pw-repl send, the sender\'s. It never replaces a file.'],
|
|
396
|
+
],
|
|
397
|
+
detail: `Where: $PW_SCREENSHOT_DIR; without it, next to the REPL's socket when it serves on one of its own, or
|
|
398
|
+
else in /tmp.
|
|
399
|
+
|
|
400
|
+
It brings the tab to the front of its window first: Chrome draws only the tab in front. The image is
|
|
401
|
+
at CSS pixel size, one image pixel per CSS pixel. While emulate mobile is on, it is what the phone
|
|
402
|
+
shows: a page laid out wider than the phone (no viewport meta tag) comes out shrunk to fit.`,
|
|
337
403
|
},
|
|
338
404
|
cursor: {
|
|
339
405
|
usage: 'cursor [on [<selector> | <x> <y>] | off]',
|
|
340
406
|
summary: 'draw a pointer where the REPL acts, for videos',
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
<
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
and
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
407
|
+
args: [
|
|
408
|
+
['on', 'fades it in where the mouse last went, or the middle.'],
|
|
409
|
+
['on <selector>', 'fades it in on the element (a video\'s first, say), scrolled into view; when it is on already, glides it there.'],
|
|
410
|
+
['on <x> <y>', 'the same, at a point in the viewport.'],
|
|
411
|
+
['off', 'fades it out.'],
|
|
412
|
+
['(none)', 'says whether it is on, and where.'],
|
|
413
|
+
],
|
|
414
|
+
detail: `The REPL's clicks and typing don't move a pointer on screen, so a recording shows things happening
|
|
415
|
+
with nothing pointing at them. With the cursor on, these commands move it smoothly to their element or
|
|
416
|
+
point before they act: click, dblclick, hover, fill, type <selector>, press <selector>, select, check,
|
|
417
|
+
uncheck, upload and mousemove. Each move takes 150 to 600ms by distance (300 to 1000ms while the tab
|
|
418
|
+
records), and the command waits for it. Clicks (click, dblclick, check, uncheck, mousedown, and upload
|
|
419
|
+
when it clicks a button) show a ring where they land.
|
|
420
|
+
|
|
421
|
+
Only the drawing moves: no mouse events are sent along the way, so the page gets the same input as with
|
|
422
|
+
the cursor off. It shows only the REPL's input, from every client, never the user's own mouse. fill,
|
|
423
|
+
select and the like move the drawing but leave the real mouse where it was; mousedown, mouseup and
|
|
424
|
+
mousewheel act at the real mouse, so the pointer moves back there first.
|
|
425
|
+
|
|
426
|
+
It is a mode of its tab: modes lists it and modes off turns it off. It stays on across page loads, at
|
|
427
|
+
the same spot, and shows in screenshots and recordings. Selectors, snapshot and text don't see it, it
|
|
428
|
+
takes no clicks, and it points at elements inside frames too.`,
|
|
359
429
|
},
|
|
360
430
|
record: {
|
|
361
|
-
usage: 'record [on [file] [seconds] | off [file]]',
|
|
431
|
+
usage: 'record [on [file] [seconds] [options] | off [file]]',
|
|
362
432
|
summary: 'record the selected tab\'s page to a video',
|
|
363
|
-
|
|
364
|
-
path, length, size and bytes.
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
from inside the browser, at the viewport's size in CSS pixels
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
the
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
433
|
+
args: [
|
|
434
|
+
['on | off', 'on starts recording the selected tab; off stops it, saves the file and says its path, length, size and bytes. Neither: says whether it is recording, and how your last recording ended.'],
|
|
435
|
+
['[file]', 'clip.webm or clip.mp4; its ending picks the format. Relative to the REPL\'s folder; through pw-repl send, the sender\'s. Default: recording-<timestamp>.webm where screenshot saves. On record off: saves it under that name instead.'],
|
|
436
|
+
['[seconds]', 'stops by itself after them, 1 to 3600. Default: 3600.'],
|
|
437
|
+
],
|
|
438
|
+
optionsFor: 'record on',
|
|
439
|
+
options: [
|
|
440
|
+
['--steps', 'also save clip.steps.txt next to clip.webm: when and where each command ran (Steps, below). Default: off.'],
|
|
441
|
+
['--pause=<ms>', 'a pause after each action, so the viewer sees what it did. Default: 750; 0 none.'],
|
|
442
|
+
['--lead=<ms>', 'still page recorded before record on returns. Default: 1000; counts toward the seconds.'],
|
|
443
|
+
['--tail=<ms>', 'still page recorded after record off, before it stops. Default: 1000; none when it stops at its seconds.'],
|
|
444
|
+
['--filename=<file>', 'the file, as playwright-cli writes it.'],
|
|
445
|
+
],
|
|
446
|
+
detail: `What it records: the page only, from inside the browser, at the viewport's size in CSS pixels (set
|
|
447
|
+
it first: viewport 1280x720 fills a 16:9 player), with no pointer (cursor on draws one) and no toolbars. A hidden, background
|
|
448
|
+
or headless tab keeps recording. It brings the tab to the front first, as screenshot does. The file is
|
|
449
|
+
owner-only, as a screenshot is, and never replaces one. One recording per tab; another client with the
|
|
450
|
+
tab selected can stop it, and both are told whose it was.
|
|
451
|
+
|
|
452
|
+
While recording: type types at a pace the video can show (help type), mousewheel scrolls in eased steps,
|
|
453
|
+
an element out of view is scrolled to smoothly, each action is followed by --pause, and with the cursor
|
|
454
|
+
on it rests 300ms on the element before acting. fill still sets a value at once: use type for text the
|
|
455
|
+
viewer should see typed. Send the take as a file of commands (pw-repl send --file take.txt), or the time
|
|
456
|
+
between commands is in the video. help video has the whole way through.
|
|
457
|
+
|
|
458
|
+
Steps (--steps): one line per command run on the tab while it recorded: when it began and ended, in
|
|
459
|
+
seconds into the video, and the element or point it acted on, in the video's pixels. Use it to find a
|
|
460
|
+
moment in the video ("at 0:12 it clicked Save"), to add captions or cuts in an editor, or to check what
|
|
461
|
+
ran. It names the command and what it acted on, never what it typed or chose, the code it ran or a URL.
|
|
462
|
+
A (page) line marks each new page, loaded or routed to by a single-page app. It is renamed with the
|
|
463
|
+
video.
|
|
464
|
+
|
|
465
|
+
ffmpeg: $PW_FFMPEG, and only it when it is set (the REPL's, not send's); otherwise Playwright's own (npx
|
|
466
|
+
playwright-core install ffmpeg, in $PLAYWRIGHT_BROWSERS_PATH or ~/.cache/ms-playwright), then one on the
|
|
467
|
+
PATH. Playwright's writes only WebM; an MP4 needs a system ffmpeg with H.264. Without one that can write
|
|
468
|
+
the format, record on fails at once, recording nothing. WebM plays in browsers, GitHub, GitLab and Slack;
|
|
469
|
+
MP4 everywhere. About 15 MB a minute.`,
|
|
470
|
+
moreAbout: 'an ffmpeg that exits mid-recording, a page that changes size, phones under emulate, and how a recording ended',
|
|
471
|
+
more: `An ffmpeg that exits mid-recording ends the recording then; the pane says what was saved, and the
|
|
472
|
+
next record off fails with it. ffmpeg writes in blocks, so what it has not written yet is lost: the last
|
|
473
|
+
seconds, or at first all of it.
|
|
474
|
+
|
|
475
|
+
A page that changes size while recording is fitted into the first size, and the steps after it leave
|
|
476
|
+
out where they acted. Under viewport or emulate, a page the tab loads while recording holds the last
|
|
477
|
+
frame until the new page's HTML is in, and is then recorded at the viewport's size. Chrome sends a
|
|
478
|
+
frame as the page draws, and a still page keeps its real length. The size is made even (an odd width or
|
|
479
|
+
height loses a pixel), scrollbars included; a phone's under emulate mobile is its CSS size: a Pixel 7's
|
|
480
|
+
is 412 wide, less sharp than the phone draws it.
|
|
481
|
+
|
|
482
|
+
How it ended: record on its own, or record off when nothing is recording, says how your last recording
|
|
483
|
+
ended (record off, its seconds, its tab closing or the REPL stopping) with what record off would have
|
|
484
|
+
said. tab close says what became of a recording it ended. A REPL stopped with pw-repl stop cannot be
|
|
485
|
+
asked: stop prints what became of each recording it ended, and of one that ended by itself that record
|
|
486
|
+
has not shown since.
|
|
487
|
+
|
|
488
|
+
A name given to record off that does not end as its file does, or cannot be used, is refused, and it
|
|
489
|
+
goes on recording. There is no --mp4 or --webm: the file's ending picks the format.`,
|
|
398
490
|
},
|
|
399
491
|
viewport: {
|
|
400
492
|
usage: 'viewport [WxH | off]',
|
|
401
493
|
summary: 'show, set or unset the viewport size',
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
494
|
+
args: [
|
|
495
|
+
['<W>x<H>', 'sets the selected tab\'s page to that size, until viewport off, modes off or the REPL exits. The window is not resized.'],
|
|
496
|
+
['off', 'back to the window\'s size.'],
|
|
497
|
+
['(none)', 'shows the size, and whether it was set or is the window\'s.'],
|
|
498
|
+
],
|
|
499
|
+
detail: `emulate mobile sets a phone's size instead, and each refuses while the other is on.`,
|
|
405
500
|
},
|
|
406
501
|
emulate: {
|
|
407
502
|
usage: 'emulate [<what> [off] | off]',
|
|
408
503
|
summary: 'emulate a phone, dark mode, a locale or a timezone',
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
Per tab, until turned off or the REPL exits; emulate mobile <device> while another is on switches to
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
The page sees the user agent, touch and navigator.languages from its next load: reload after
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
While mobile is on, viewport refuses: the device sets the size.`,
|
|
504
|
+
args: [
|
|
505
|
+
['mobile [device]', 'a phone\'s screen, touch and user agent: a Pixel 7, or a device named as in Playwright\'s list, e.g. emulate mobile iPhone 13. A name that is not exact lists the devices it matches (emulate mobile galaxy).'],
|
|
506
|
+
['dark | light', 'the color scheme the page\'s CSS and matchMedia see.'],
|
|
507
|
+
['locale <tag>', 'the language (Accept-Language, navigator.language) and date and number formats, e.g. fr-FR.'],
|
|
508
|
+
['timezone <zone>', 'an IANA timezone, e.g. Asia/Tokyo.'],
|
|
509
|
+
['<what> off', 'stops one (dark or light off stops the color scheme).'],
|
|
510
|
+
['off', 'stops all.'],
|
|
511
|
+
['(none)', 'shows what is on.'],
|
|
512
|
+
],
|
|
513
|
+
detail: `Per tab, until turned off or the REPL exits; emulate mobile <device> while another is on switches to
|
|
514
|
+
it. While mobile is on, viewport refuses: the device sets the size.
|
|
515
|
+
|
|
516
|
+
The page sees the user agent, touch and navigator.languages from its next load: reload after turning
|
|
517
|
+
mobile or locale on or off. Touch means the page sees a touch screen; click still clicks with the
|
|
518
|
+
mouse, and sends no touch events. Dark, light and timezone apply at once, but what the page already
|
|
519
|
+
drew with them (times, colors set by its script) changes only when it reloads.`,
|
|
427
520
|
},
|
|
428
521
|
wait: {
|
|
429
522
|
usage: 'wait [text|request] <what> [--gone] [secs]',
|
|
430
523
|
summary: 'wait for an element, text, a response or a load (10s)',
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
524
|
+
args: [
|
|
525
|
+
['<selector>', 'for a matching element to be visible.'],
|
|
526
|
+
['text <text>', 'for text to be visible: any part of an element\'s text, any case.'],
|
|
527
|
+
['request <part>', 'for a matching response (a URL part or a glob), counting one that finished since the previous command began, or is still under way: click, then wait request, does not miss it.'],
|
|
528
|
+
['load', 'for the page to finish loading (its load event), counting a navigation that began since the previous command began: click a link, then wait load.'],
|
|
529
|
+
['[secs]', 'how long, up to 120. Default: 10.'],
|
|
530
|
+
],
|
|
531
|
+
options: [
|
|
532
|
+
['--gone', 'until nothing matching is visible (removed or hidden): wait .spinner --gone. Done at once if nothing matches yet, so wait for it to appear first if it may not have.'],
|
|
533
|
+
],
|
|
534
|
+
detail: `A page that changes its own URL without loading (an app's own routing) is not a load: wait for
|
|
535
|
+
something on the new view instead. A wait that times out is an error; the REPL carries on.`,
|
|
536
|
+
},
|
|
537
|
+
sleep: {
|
|
538
|
+
usage: 'sleep <ms>',
|
|
539
|
+
summary: 'wait a fixed time, in milliseconds (maximum 3600000)',
|
|
540
|
+
detail: `While recording, each action is already followed by a pause (help record): a sleep adds to it.`,
|
|
446
541
|
},
|
|
447
|
-
sleep: { usage: 'sleep <ms>', summary: 'wait a fixed time, in milliseconds (maximum 3600000)' },
|
|
448
542
|
requests: {
|
|
449
543
|
usage: 'requests [--all] [n] [url-filter]',
|
|
450
544
|
summary: 'the selected tab\'s last n requests (default 20)',
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
body <#> shows what a request got back.
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
545
|
+
args: [
|
|
546
|
+
['[n]', 'how many of the latest to show. Default: 20. When more match, a first line says so: (last 20 of 112 kept; requests 112 shows them all).'],
|
|
547
|
+
['[url-filter]', 'a part of the URL: requests 20 /api/ shows API calls only.'],
|
|
548
|
+
],
|
|
549
|
+
options: [
|
|
550
|
+
['--all', 'also images, fonts, stylesheets, media and extension requests, hidden otherwise.'],
|
|
551
|
+
['--regex <pattern>', 'at the end: matches URLs by a regular expression instead.'],
|
|
552
|
+
],
|
|
553
|
+
detail: `Each line: #number, time, method, status (HTTP code, pending, no response, failed: <reason>, <code>
|
|
554
|
+
then failed: <reason> when its body was cut off, or <code> faked), duration, kind (document, fetch, xhr,
|
|
555
|
+
script, image, ...), URL. A line "--- the page loads <url>" marks where each page load starts, even when
|
|
556
|
+
a filter hides that request. body <#> shows what a request got back.
|
|
557
|
+
|
|
558
|
+
Kept: from when the REPL connects, and a tab opened since from its first page (one a link opens
|
|
559
|
+
included); the last 200 per tab, of every kind the page requests (not the browser's own, such as the
|
|
560
|
+
favicon). A page often makes more than 20 in one load. One that finished a moment ago can still show
|
|
561
|
+
pending.`,
|
|
468
562
|
},
|
|
469
563
|
body: {
|
|
470
564
|
usage: 'body [--all] <#>|<url-part>',
|
|
471
565
|
summary: 'the response body of request <#>, or of the latest one matching',
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
566
|
+
args: [
|
|
567
|
+
['<#>', 'a request\'s number, from requests.'],
|
|
568
|
+
['<url-part>', 'the latest finished request whose URL contains it: body slots.json.'],
|
|
569
|
+
],
|
|
570
|
+
options: [
|
|
571
|
+
['--all', 'the whole body, not capped.'],
|
|
572
|
+
],
|
|
573
|
+
detail: `Read from the browser on demand: JSON is pretty-printed, binary is not shown. The browser may drop a
|
|
574
|
+
body (e.g. after the tab navigates); then body reports not available, unless a watch kept it (help
|
|
575
|
+
watch). Bodies can contain sensitive data.`,
|
|
479
576
|
},
|
|
480
577
|
console: {
|
|
481
578
|
usage: 'console [--all] [n] [level|filter]',
|
|
482
579
|
summary: 'the last n console messages and page errors',
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
580
|
+
args: [
|
|
581
|
+
['[n]', 'how many of the latest to show. Default: 20; when more match, a first line says so.'],
|
|
582
|
+
['[level]', 'error, warning, info or debug: that level and those above it. console warning shows warnings and errors, page errors among them. info, which leaves out debug, is playwright-cli\'s default.'],
|
|
583
|
+
['[filter]', 'anything else: matches part of the type or the text.'],
|
|
584
|
+
],
|
|
585
|
+
options: [
|
|
586
|
+
['--all', 'each message whole, not capped.'],
|
|
587
|
+
],
|
|
588
|
+
detail: `Each line: time, [type], text. Types are console levels (log, warning, error, ...) and pageerror for
|
|
589
|
+
uncaught exceptions. A "--- the page loaded <url>" line marks each load after the first message shown.
|
|
590
|
+
Kept from when the REPL connects; the last 200 per tab.
|
|
591
|
+
|
|
592
|
+
A page brought back from the back/forward cache (back, forward) reports its earlier messages again, at
|
|
593
|
+
the time it comes back, though nothing loaded again.`,
|
|
495
594
|
},
|
|
496
595
|
capture: {
|
|
497
596
|
usage: 'capture on [requests|console] [secs] | off',
|
|
498
597
|
summary: 'record requests and console together, in time order',
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
598
|
+
args: [
|
|
599
|
+
['on', 'records both until capture off, which prints them.'],
|
|
600
|
+
['requests | console', 'records only one.'],
|
|
601
|
+
['[secs]', 'records that long (1 to 3600), then prints. Other commands wait until it ends.'],
|
|
602
|
+
['(none)', 'says whether one is running, or shows the last one.'],
|
|
603
|
+
],
|
|
604
|
+
detail: `A timed capture suits recording what someone does in the browser; around your own commands, use
|
|
605
|
+
capture on and capture off. One runs at a time, on the tab selected when it started. Unlike requests
|
|
606
|
+
and console it keeps more than the last 200, and lists both together.`,
|
|
508
607
|
},
|
|
509
608
|
route: {
|
|
510
609
|
usage: 'route <glob> <how> | off <glob>|--all',
|
|
511
610
|
summary: 'fake, patch, delay or fail the selected tab\'s matching requests',
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
be quoted.
|
|
531
|
-
|
|
532
|
-
Example: route **/api/cart patch {"total": 0}`,
|
|
611
|
+
args: [
|
|
612
|
+
['<glob> <status> <json>', 'answers with this status (200-599) and JSON (the rest of the line), or no body. It never reaches the network, so it still answers while the network is off.'],
|
|
613
|
+
['<glob> patch <json>', 'lets it through, then changes its JSON response: a JSON Merge Patch, where objects merge, null removes a key and anything else replaces; one that is not an object (an array) replaces the whole body.'],
|
|
614
|
+
['<glob> delay <secs>', 'holds it for up to 120 seconds, then lets it through.'],
|
|
615
|
+
['<glob> abort', 'fails it as if the connection broke.'],
|
|
616
|
+
['off <glob> | --all', 'removes one route, or all.'],
|
|
617
|
+
['(none)', 'lists the selected tab\'s routes.'],
|
|
618
|
+
],
|
|
619
|
+
options: [
|
|
620
|
+
['--content-type=<t>', 'the type of a body that is not JSON: route <glob> 200 --content-type=text/plain hello.'],
|
|
621
|
+
],
|
|
622
|
+
detail: `Each matching request prints a line (Faked:, Patched:, Delayed:, Aborted:) with its number as in
|
|
623
|
+
requests: in the REPL window, and in the answer to a command running then if that command's client has
|
|
624
|
+
the tab selected. One the page makes after reload or a click has answered shows only in the window
|
|
625
|
+
(wait request <glob> waits for it). requests shows a fake as <status> faked and a patch as <status>
|
|
626
|
+
patched. A route that fails prints "Route failed" and aborts the request.
|
|
627
|
+
|
|
628
|
+
Routes belong to the tab and last until route off or the REPL exits. Routing the same glob again
|
|
629
|
+
replaces it. A glob may be quoted. Example: route **/api/cart patch {"total": 0}`,
|
|
533
630
|
},
|
|
534
631
|
network: {
|
|
535
632
|
usage: 'network [on|off|slow [<ms> [<kbps>]]]',
|
|
536
633
|
summary: 'cut, slow or restore the tab\'s network',
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
Per tab; lasts until network on or the REPL exits. Routes still answer while it is off or slow.`,
|
|
634
|
+
args: [
|
|
635
|
+
['off', 'cuts it, like dropped wifi.'],
|
|
636
|
+
['slow [<ms> [<kbps>]]', 'each request takes at least <ms> (a minimum, not added to a slow server\'s own time), at <kbps> both ways. Default: DevTools\' Slow 4G: 563ms, 1440 kbps down, 675 up.'],
|
|
637
|
+
['on', 'restores it.'],
|
|
638
|
+
],
|
|
639
|
+
detail: `Per tab; lasts until network on or the REPL exits. Routes still answer while it is off or slow. To slow
|
|
640
|
+
one API, use route <glob> delay <secs>. Stopping a service is not the same as network off: a dev proxy
|
|
641
|
+
in front of it usually holds the request open, so the page spins instead of failing.`,
|
|
546
642
|
},
|
|
547
643
|
eval: {
|
|
548
644
|
usage: 'eval [--all] <JavaScript>',
|
|
549
645
|
summary: 'evaluate JavaScript in the selected tab and print the result',
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
646
|
+
args: [
|
|
647
|
+
['<JavaScript>', 'the rest of the line. Promises are awaited, await works at the top level, and a function (() => document.title) is called.'],
|
|
648
|
+
['<function> <ref>', 'calls it with that element: eval "el => el.textContent" e5.'],
|
|
649
|
+
],
|
|
650
|
+
options: [
|
|
651
|
+
['--all', 'the whole result, not capped.'],
|
|
652
|
+
],
|
|
653
|
+
detail: `eval fetch(...) sends a new request, which can change state on the server; it cannot read one that
|
|
554
654
|
already happened (requests lists those).`,
|
|
555
655
|
},
|
|
556
656
|
cdp: {
|
|
557
657
|
usage: 'cdp [--all] <method> <JSON object>',
|
|
558
658
|
summary: 'send one CDP command through a temporary session',
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
659
|
+
args: [
|
|
660
|
+
['<method>', 'a CDP method: Network.getCookies.'],
|
|
661
|
+
['<JSON object>', 'its parameters: {} for none.'],
|
|
662
|
+
],
|
|
663
|
+
options: [
|
|
664
|
+
['--all', 'the whole result, not capped.'],
|
|
665
|
+
],
|
|
666
|
+
detail: `The session is detached afterwards, so subscriptions and settings do not persist. Browser.close and
|
|
667
|
+
Target.closeTarget are refused, and so is Page.handleJavaScriptDialog: dialog answers one.`,
|
|
668
|
+
},
|
|
669
|
+
cookies: {
|
|
670
|
+
usage: 'cookies [--all]',
|
|
671
|
+
summary: 'cookie names, domains and flags; values are omitted',
|
|
672
|
+
detail: `--all lists every cookie, not capped. cdp Network.getCookies {} shows the values.`,
|
|
673
|
+
},
|
|
674
|
+
storage: {
|
|
675
|
+
usage: 'storage [--all]',
|
|
676
|
+
summary: 'localStorage keys; values are omitted',
|
|
677
|
+
detail: `--all lists every key, not capped. eval localStorage.getItem("<key>") shows a value.`,
|
|
562
678
|
},
|
|
563
|
-
cookies: { usage: 'cookies [--all]', summary: 'cookie names, domains and flags; values are omitted' },
|
|
564
|
-
storage: { usage: 'storage [--all]', summary: 'localStorage keys; values are omitted' },
|
|
565
679
|
modes: {
|
|
566
680
|
usage: 'modes [off [--mine]]',
|
|
567
681
|
summary: 'the modes on in every tab; modes off turns them all off',
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
682
|
+
args: [
|
|
683
|
+
['(none)', 'lists the modes on in every tab, and with clients (help session), which client turned each on.'],
|
|
684
|
+
['off', 'turns off every one in every tab; a capture it stops is kept for capture to show, and a recording it stops is saved, as record off would.'],
|
|
685
|
+
],
|
|
686
|
+
options: [
|
|
687
|
+
['--mine', 'after off: only the sender\'s own.'],
|
|
688
|
+
],
|
|
689
|
+
detail: `The modes, each turned on and off with its own command: watch on|off, network off|slow|on, route <glob>
|
|
690
|
+
... | route off <glob>|--all, capture on|off, record on|off, emulate ... | emulate off, viewport <WxH> |
|
|
691
|
+
viewport off, highlight <selector> | highlight off, cursor on|off.`,
|
|
577
692
|
},
|
|
578
693
|
dialog: {
|
|
579
694
|
usage: 'dialog [accept [text] | dismiss]',
|
|
580
695
|
summary: 'show, accept or dismiss an open alert, confirm or prompt',
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
696
|
+
args: [
|
|
697
|
+
['(none)', 'lists the open dialogs; * marks the selected tab\'s.'],
|
|
698
|
+
['accept [text]', 'answers OK: text answers a prompt, and accept alone keeps its default, as OK in the browser does.'],
|
|
699
|
+
['dismiss', 'answers Cancel.'],
|
|
700
|
+
],
|
|
701
|
+
detail: `It answers the selected tab's dialog, or the only one open, and runs at once, ahead of other commands.
|
|
702
|
+
While a dialog is open, its page and the commands that read it wait, a click that opened it included,
|
|
703
|
+
until it is answered. Commands run one at a time, so every client's commands behind that one wait too,
|
|
704
|
+
in any tab: in a shared REPL, answer a dialog you open.
|
|
705
|
+
|
|
706
|
+
An agent can answer any dialog, one it opened included; one the user answers in the browser is gone
|
|
707
|
+
from dialog then. cdp cannot answer one: Chrome lets only a session that saw it open do so. With nobody
|
|
708
|
+
at the browser (e.g. headless), it is the only way to answer one.`,
|
|
709
|
+
},
|
|
710
|
+
help: {
|
|
711
|
+
usage: 'help [topic | command | --all]',
|
|
712
|
+
summary: 'this help; --all prints every topic and command in full',
|
|
713
|
+
args: [
|
|
714
|
+
['(none)', 'what this is, and the topics.'],
|
|
715
|
+
['<topic>', 'the topic, and one line per command in it.'],
|
|
716
|
+
['<command>', 'its usage, arguments, options and what most need to know.'],
|
|
717
|
+
['<command> --all', 'all of it, with what only goes wrong now and then.'],
|
|
718
|
+
['--all', 'every topic and command in full.'],
|
|
719
|
+
],
|
|
586
720
|
},
|
|
587
|
-
help: { usage: 'help [topic | command | --all]', summary: 'this help; --all prints every topic and command in full' },
|
|
588
721
|
quit: {
|
|
589
722
|
usage: 'quit',
|
|
590
723
|
summary: 'disconnect, leaving Chromium running (one --launch started stops too)',
|
|
@@ -593,24 +726,52 @@ from dialog then. cdp cannot answer one: Chrome lets only a session that saw it
|
|
|
593
726
|
};
|
|
594
727
|
|
|
595
728
|
for (const topic of Object.values(TOPICS)) topic.intro = wrap(topic.intro);
|
|
596
|
-
for (const entry of Object.values(COMMANDS))
|
|
729
|
+
for (const entry of Object.values(COMMANDS)) {
|
|
730
|
+
if (entry.detail) entry.detail = wrap(entry.detail);
|
|
731
|
+
if (entry.more) entry.more = wrap(entry.more);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// An Arguments or Options list: each name in a column of its own, its text wrapped beside it.
|
|
735
|
+
const NAME_WIDTH = 18;
|
|
736
|
+
function list(items, width = WIDTH) {
|
|
737
|
+
return items.flatMap(([name, text]) => {
|
|
738
|
+
const lines = wrap(text, width - NAME_WIDTH - 4).split('\n');
|
|
739
|
+
const first = name.length <= NAME_WIDTH ? ` ${name.padEnd(NAME_WIDTH)} ${lines.shift()}` : ` ${name}`;
|
|
740
|
+
return [first, ...lines.map(l => `${' '.repeat(NAME_WIDTH + 4)}${l}`)];
|
|
741
|
+
}).join('\n');
|
|
742
|
+
}
|
|
597
743
|
|
|
598
744
|
function commandLines(names) {
|
|
599
745
|
const width = Math.max(...names.map(n => COMMANDS[n].usage.length));
|
|
600
746
|
return names.map(n => ` ${COMMANDS[n].usage.padEnd(width)} ${COMMANDS[n].summary}`);
|
|
601
747
|
}
|
|
602
748
|
|
|
603
|
-
|
|
749
|
+
// A command's help: usage and summary, its arguments and options, what most need to know, and, with
|
|
750
|
+
// full, the rest (what goes wrong rarely); without it, a line says where the rest is.
|
|
751
|
+
function commandBody(name, full) {
|
|
604
752
|
const entry = COMMANDS[name];
|
|
605
|
-
|
|
753
|
+
const parts = [];
|
|
754
|
+
const lists = [];
|
|
755
|
+
if (entry.args) lists.push(`Arguments:\n${list(entry.args)}`);
|
|
756
|
+
if (entry.options) lists.push(`Options${entry.optionsFor ? ` (${entry.optionsFor})` : ''}:\n${list(entry.options)}`);
|
|
757
|
+
if (lists.length) parts.push(lists.join('\n'));
|
|
758
|
+
if (entry.detail) parts.push(entry.detail);
|
|
759
|
+
if (entry.more) parts.push(full ? entry.more : wrap(`More, on ${entry.moreAbout}: help ${name} --all.`));
|
|
760
|
+
return parts.join('\n\n');
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
function renderCommand(name, full = false) {
|
|
764
|
+
const entry = COMMANDS[name];
|
|
765
|
+
const body = commandBody(name, full);
|
|
766
|
+
return [`${entry.usage} — ${entry.summary}`, ...(body ? ['', body] : [])].join('\n');
|
|
606
767
|
}
|
|
607
768
|
|
|
608
769
|
// Every topic's command list, with each command's details indented under it.
|
|
609
770
|
function renderAll() {
|
|
610
771
|
const sections = Object.entries(TOPICS).map(([name, topic]) => {
|
|
611
772
|
const lines = commandLines(topic.commands).flatMap((line, i) => {
|
|
612
|
-
const
|
|
613
|
-
return
|
|
773
|
+
const body = commandBody(topic.commands[i], true);
|
|
774
|
+
return body ? [line, ...body.split('\n').map(d => (d ? ` ${d}` : '')), ''] : [line];
|
|
614
775
|
});
|
|
615
776
|
if (lines[lines.length - 1] === '') lines.pop();
|
|
616
777
|
return [`== ${name} ==`, topic.intro, '', ...lines].join('\n');
|
|
@@ -618,23 +779,6 @@ function renderAll() {
|
|
|
618
779
|
return [OVERVIEW, ...sections].join('\n\n');
|
|
619
780
|
}
|
|
620
781
|
|
|
621
|
-
// Not a command: the ways to record a video, since none is right for every use.
|
|
622
|
-
const VIDEO = `There are three ways to record a video of a page, and each gives something up.
|
|
623
|
-
|
|
624
|
-
record on (help record) records the selected tab's page from inside the browser: headless, hidden and
|
|
625
|
-
background tabs too, with nothing to set up but ffmpeg. It has no pointer and no browser toolbars:
|
|
626
|
-
cursor on draws a pointer that shows where the REPL clicks. For quick clips, and for an agent recording
|
|
627
|
-
its own work.
|
|
628
|
-
|
|
629
|
-
A screen recorder on the person's machine (OBS, ffmpeg -f x11grab, macOS screencapture -v) records their
|
|
630
|
-
screen as they see it, pointer included, smooth and in real time. It records the screen or a window, not
|
|
631
|
-
a tab: size the window, and crop off the toolbars, for just the page. Ask before starting one, or have
|
|
632
|
-
them start it. It cannot see a headless browser: for a browser of the REPL's own, --launch --headed on
|
|
633
|
-
a screen. For a polished video of a person's browser.
|
|
634
|
-
|
|
635
|
-
Microsoft's playwright-cli (the npm package @playwright/cli; video-start and video-stop) records a
|
|
636
|
-
browser of its own, not this one, which starts logged out. For a scripted flow in a clean browser.`;
|
|
637
|
-
|
|
638
782
|
function render(topicOrCommand) {
|
|
639
783
|
if (!topicOrCommand) return OVERVIEW;
|
|
640
784
|
if (topicOrCommand === 'playwright-cli') {
|
|
@@ -651,11 +795,10 @@ Its other options are refused, never read as part of a selector or a value.
|
|
|
651
795
|
Different on purpose, since the browser may be someone else's too:
|
|
652
796
|
routes, network and emulation per tab, not the whole browser, so other people's tabs are untouched
|
|
653
797
|
close turns off your modes; tabs stay open (tab close <url-part> closes one)
|
|
654
|
-
upload <selector> <file> names the input: file pickers are not caught, as
|
|
655
|
-
no snapshot after each command
|
|
798
|
+
upload <selector> <file> names the input: file pickers are not caught, as the user's would be
|
|
799
|
+
no snapshot after each command the user reads the pane too; snapshot when you need one
|
|
656
800
|
-s=<session> -c <name> is the nearest: a client with a selected tab of its own`);
|
|
657
801
|
}
|
|
658
|
-
if (topicOrCommand === 'video') return wrap(VIDEO);
|
|
659
802
|
if (topicOrCommand === '--all') return renderAll();
|
|
660
803
|
if (Object.hasOwn(TOPICS, topicOrCommand)) {
|
|
661
804
|
const topic = TOPICS[topicOrCommand];
|
|
@@ -664,6 +807,8 @@ Different on purpose, since the browser may be someone else's too:
|
|
|
664
807
|
return Object.hasOwn(COMMANDS, topicOrCommand) ? `${text}\n\n${renderCommand(topicOrCommand)}` : text;
|
|
665
808
|
}
|
|
666
809
|
if (Object.hasOwn(COMMANDS, topicOrCommand)) return renderCommand(topicOrCommand);
|
|
810
|
+
const full = /^(\S+)\s+--all$/.exec(topicOrCommand);
|
|
811
|
+
if (full && Object.hasOwn(COMMANDS, full[1])) return renderCommand(full[1], true);
|
|
667
812
|
// A playwright-cli name: what it runs here, with that command's help.
|
|
668
813
|
const cli = require('./cli-names').describe(topicOrCommand);
|
|
669
814
|
if (cli?.nearest) return wrap(`${topicOrCommand} is playwright-cli's; ${cli.nearest}`);
|