pw-repl 0.4.2 → 0.6.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 +34 -8
- package/README.md +90 -101
- package/bin/pw-repl.js +12 -2
- package/lib/background.js +4 -3
- package/lib/capture.js +139 -0
- package/lib/cdp.js +31 -0
- package/lib/cli-names.js +1 -3
- package/lib/client.js +6 -1
- package/lib/commands.js +34 -2285
- package/lib/dialogs.js +58 -0
- package/lib/elements.js +360 -0
- package/lib/emulation.js +180 -0
- package/lib/help.js +298 -39
- package/lib/inspect.js +460 -0
- package/lib/modes.js +115 -0
- package/lib/network.js +59 -0
- package/lib/output.js +12 -2
- package/lib/requestlog.js +367 -0
- package/lib/routes.js +179 -0
- package/lib/runner.js +7 -3
- package/lib/send.js +9 -0
- package/lib/server.js +9 -2
- package/lib/skill.js +29 -0
- package/lib/start.js +10 -5
- package/lib/state.js +5 -0
- package/lib/tabs.js +237 -0
- package/lib/tabstate.js +35 -0
- package/lib/util.js +49 -0
- package/lib/watch.js +530 -0
- package/package.json +1 -1
- package/skill/SKILL.md +35 -12
package/lib/help.js
CHANGED
|
@@ -3,7 +3,26 @@
|
|
|
3
3
|
// that a reader only goes deeper when they need to. Help describes what the
|
|
4
4
|
// tool does; instructions for agents belong in skill/SKILL.md.
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
// Help is written as plain paragraphs, and laid out here: each is re-flowed to WIDTH columns (help
|
|
7
|
+
// --all indents details by 6, within the 110 a pane shows). A paragraph with any line that is not
|
|
8
|
+
// plain prose (starting with "- " or whitespace, or with two spaces in a row: a bullet, a table, an
|
|
9
|
+
// indented example) is kept as it is written.
|
|
10
|
+
const WIDTH = 104;
|
|
11
|
+
|
|
12
|
+
function wrap(text, width = WIDTH) {
|
|
13
|
+
return text.split('\n\n').map(paragraph => {
|
|
14
|
+
const lines = paragraph.split('\n');
|
|
15
|
+
if (lines.some(line => /^- |^\s| /.test(line))) return paragraph;
|
|
16
|
+
const flowed = [];
|
|
17
|
+
let line = null;
|
|
18
|
+
for (const word of lines.join(' ').split(' ')) {
|
|
19
|
+
if (line !== null && `${line} ${word}`.length > width) { flowed.push(line); line = word; } else line = line === null ? word : `${line} ${word}`;
|
|
20
|
+
}
|
|
21
|
+
return [...flowed, line].join('\n');
|
|
22
|
+
}).join('\n\n');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const OVERVIEW = wrap(`playwright-repl drives Chromium over CDP. Commands act on the selected tab (\`tab\` shows which).
|
|
7
26
|
The browser may be a shared session, with other people's tabs open in it.
|
|
8
27
|
|
|
9
28
|
Common tasks:
|
|
@@ -36,27 +55,31 @@ Topics (help <topic>):
|
|
|
36
55
|
help <command> for usage and caveats (e.g. help route); help --all for everything.
|
|
37
56
|
|
|
38
57
|
Know playwright-cli? Its command names work here too (tab-list, go-back, set-color-scheme, route
|
|
39
|
-
--status=...); help playwright-cli lists them
|
|
58
|
+
--status=...); help playwright-cli lists them.`);
|
|
40
59
|
|
|
41
60
|
const TOPICS = {
|
|
42
61
|
tabs: {
|
|
43
|
-
intro:
|
|
62
|
+
intro: `No tab is selected when the REPL starts: tab new opens one, tab <index|url-part> selects one.
|
|
63
|
+
tab on its own lists them all.`,
|
|
44
64
|
commands: ['tab', 'goto', 'back', 'forward', 'reload', 'info'],
|
|
45
65
|
},
|
|
46
66
|
interact: {
|
|
47
|
-
intro:
|
|
48
|
-
|
|
67
|
+
intro: `Selectors are Playwright selectors (CSS, text=..., role=...) or snapshot refs such as e5.
|
|
68
|
+
Commands use the first match (click, dblclick and hover the first they can use: help click); see help
|
|
69
|
+
fill for selectors with spaces. mousemove and the other mouse commands act at a point instead.`,
|
|
70
|
+
commands: ['click', 'dblclick', 'hover', 'fill', 'type', 'press', 'select', 'check', 'uncheck', 'upload', 'mousemove', 'mousedown', 'mouseup', 'mousewheel'],
|
|
49
71
|
},
|
|
50
72
|
inspect: {
|
|
51
|
-
intro:
|
|
73
|
+
intro: `Output is capped; --all right after the command, or at the end, shows everything (text --all body).`,
|
|
52
74
|
commands: ['snapshot', 'watch', 'text', 'html', 'attrs', 'listeners', 'count', 'visible', 'links', 'inputs', 'screenshot', 'viewport', 'emulate', 'wait', 'sleep'],
|
|
53
75
|
},
|
|
54
76
|
network: {
|
|
55
|
-
intro:
|
|
77
|
+
intro: `requests and console record all the time; a capture records only while it runs.`,
|
|
56
78
|
commands: ['requests', 'body', 'console', 'capture', 'route', 'network'],
|
|
57
79
|
},
|
|
58
80
|
devtools: {
|
|
59
|
-
intro:
|
|
81
|
+
intro: `Cookie and storage listings omit values. eval, cdp, html, screenshots and URLs can still show
|
|
82
|
+
sensitive data (cdp Network.getCookies shows cookie values).`,
|
|
60
83
|
commands: ['eval', 'cdp', 'cookies', 'storage'],
|
|
61
84
|
},
|
|
62
85
|
session: {
|
|
@@ -86,37 +109,89 @@ const COMMANDS = {
|
|
|
86
109
|
tab: {
|
|
87
110
|
usage: 'tab [<index>|<url-part>|new [url]|close [url-part]]',
|
|
88
111
|
summary: 'list tabs (* is selected); select, open or close one',
|
|
89
|
-
detail:
|
|
112
|
+
detail: `tab <index> uses the numbers from your latest tab listing (each client has its own); they change
|
|
113
|
+
when tabs open or close.
|
|
114
|
+
|
|
115
|
+
tab <url-part> selects the one tab whose URL contains it, and refuses if none or several do.
|
|
116
|
+
|
|
117
|
+
tab new opens a tab behind the one in front, so it does not take the window from whoever is using it,
|
|
118
|
+
and selects it; tab shows who opened each tab opened this way. tab close closes the selected tab, or
|
|
119
|
+
the one matching url-part.
|
|
120
|
+
|
|
121
|
+
Closing the selected tab goes back to the previous tab if tab new opened it; otherwise no tab is
|
|
122
|
+
selected, and commands that need one refuse until one is.`,
|
|
90
123
|
},
|
|
91
124
|
goto: {
|
|
92
125
|
usage: 'goto <url>',
|
|
93
126
|
summary: 'navigate the selected tab',
|
|
94
|
-
detail:
|
|
127
|
+
detail: `Without a scheme, http:// is assumed for localhost, 127.x and [::1], and https:// otherwise, as in
|
|
128
|
+
the address bar. tab new <url> reads its URL the same way.
|
|
129
|
+
|
|
130
|
+
A page that does not load says why, as wait load does: a failed request, after which the tab shows
|
|
131
|
+
Chrome's error page (info names the URL that failed), or an answer with no page (a 204), which
|
|
132
|
+
leaves the tab where it was. tab new, reload, back and forward say it the same way.`,
|
|
95
133
|
},
|
|
96
134
|
back: {
|
|
97
135
|
usage: 'back',
|
|
98
136
|
summary: 'go back one history entry',
|
|
99
|
-
detail:
|
|
137
|
+
detail: `The browser may bring the page back from its back/forward cache, as it was: nothing loads or runs
|
|
138
|
+
again, so a loading message or a request the page makes on load does not come back. reload loads it
|
|
139
|
+
afresh.`,
|
|
100
140
|
},
|
|
101
|
-
forward: { usage: 'forward', summary: 'go forward one history entry', detail:
|
|
141
|
+
forward: { usage: 'forward', summary: 'go forward one history entry', detail: `Like back, it may bring the page back from the back/forward cache without loading it again.` },
|
|
102
142
|
reload: { usage: 'reload', summary: 'reload the selected tab' },
|
|
103
143
|
info: { usage: 'info', summary: 'selected tab URL, title and viewport' },
|
|
104
144
|
click: {
|
|
105
145
|
usage: 'click <selector> [button]',
|
|
106
|
-
summary: 'click the first match',
|
|
107
|
-
detail:
|
|
146
|
+
summary: 'click the first match that can be clicked',
|
|
147
|
+
detail: `After a ref or a quoted selector, a button (left, right, middle) may follow: click e5 right.
|
|
148
|
+
--modifiers=<key>[,<key>] holds keys down meanwhile (Alt, Control, ControlOrMeta, Meta, Shift).
|
|
149
|
+
|
|
150
|
+
When a selector matches several elements, hidden ones and ones covered by something else (a dialog's
|
|
151
|
+
overlay) are passed over, and it says which match it used: Clicked: text=Buy (match 3 of 4; ...). If
|
|
152
|
+
none can be clicked within 5s, it says why for each and does nothing. A ref, or a single match, is
|
|
153
|
+
clicked as it is, waiting up to 5s for it to be ready.
|
|
154
|
+
|
|
155
|
+
Timed out after it began acting, it says what stopped it (e.g. <div class="overlay"> intercepts
|
|
156
|
+
pointer events) and that the outcome is unknown; Playwright's full call log goes to the REPL's pane
|
|
157
|
+
or log.`,
|
|
158
|
+
},
|
|
159
|
+
dblclick: { usage: 'dblclick <selector> [button]', summary: 'double-click, choosing the match as click does' },
|
|
160
|
+
hover: { usage: 'hover <selector>', summary: 'hover, choosing the match as click does' },
|
|
161
|
+
mousemove: {
|
|
162
|
+
usage: 'mousemove <x> <y>',
|
|
163
|
+
summary: 'move the mouse to a point in the viewport',
|
|
164
|
+
detail: `x and y are CSS pixels from the top left of the tab's viewport (not the page), as mouse events
|
|
165
|
+
have them. A snapshot has no coordinates; eval of getBoundingClientRect gives an element's centre:
|
|
166
|
+
eval (r => [r.x + r.width / 2, r.y + r.height / 2])(document.querySelector("#map")
|
|
167
|
+
.getBoundingClientRect())
|
|
168
|
+
|
|
169
|
+
A click at a point: mousemove <x> <y>, mousedown, mouseup. A drag: mousedown, then mousemove to
|
|
170
|
+
another point, then mouseup.`,
|
|
171
|
+
},
|
|
172
|
+
mousedown: { usage: 'mousedown [button]', summary: 'press a mouse button where the mouse is (left, right or middle)' },
|
|
173
|
+
mouseup: { usage: 'mouseup [button]', summary: 'release a mouse button where the mouse is' },
|
|
174
|
+
mousewheel: {
|
|
175
|
+
usage: 'mousewheel <dx> <dy>',
|
|
176
|
+
summary: 'turn the wheel where the mouse is',
|
|
177
|
+
detail: `Wheel events at the mouse's point (mousemove puts it there), in CSS pixels: positive dy scrolls
|
|
178
|
+
down. A map zooms around that point: mousemove 400 300, then mousewheel 0 -300 zooms in. It returns
|
|
179
|
+
once the events are sent, not once the page has scrolled or redrawn.`,
|
|
108
180
|
},
|
|
109
|
-
dblclick: { usage: 'dblclick <selector> [button]', summary: 'double-click the first match; button and --modifiers as for click' },
|
|
110
|
-
hover: { usage: 'hover <selector>', summary: 'hover the first match' },
|
|
111
181
|
fill: {
|
|
112
182
|
usage: 'fill <selector> <value> [--submit]',
|
|
113
183
|
summary: 'clear an input and fill it',
|
|
114
|
-
detail:
|
|
184
|
+
detail: `The selector is the first word; quote it if it has spaces. The rest of the line is the value, and
|
|
185
|
+
quotes around all of it are removed, so fill #name "" clears the field. --submit at the end presses
|
|
186
|
+
Enter in it afterwards.
|
|
187
|
+
|
|
188
|
+
Examples: fill #name Ada Lovelace, fill "text=Your name" Ada, fill e7 Ada`,
|
|
115
189
|
},
|
|
116
190
|
type: {
|
|
117
191
|
usage: 'type [<selector>] <text> [--submit]',
|
|
118
192
|
summary: 'type key by key after what the field holds; text as for fill',
|
|
119
|
-
detail:
|
|
193
|
+
detail: `With one word, or one quoted, it is typed into the element that has focus: type "garden hose".
|
|
194
|
+
--submit at the end presses Enter afterwards.`,
|
|
120
195
|
},
|
|
121
196
|
press: { usage: 'press <key> | press <selector> <key>', summary: 'press a key (Enter, Escape, Control+A; any case), maybe on an element' },
|
|
122
197
|
select: { usage: 'select <selector> <value>', summary: 'choose an option in a select (the value is the rest of the line)' },
|
|
@@ -125,17 +200,71 @@ const COMMANDS = {
|
|
|
125
200
|
upload: {
|
|
126
201
|
usage: 'upload <selector> <file>...',
|
|
127
202
|
summary: 'choose files in a file input, as the file picker would',
|
|
128
|
-
detail:
|
|
203
|
+
detail: `The selector is a file input (even a hidden one), its label, or a button that opens the file
|
|
204
|
+
picker. The page then does what it does with a chosen file; often that is the upload itself.
|
|
205
|
+
|
|
206
|
+
The REPL reads the files and hands the page their contents, so they need not be where the browser
|
|
207
|
+
runs; up to 50MB in all. Relative paths are from the folder pw-repl send runs in, or the REPL's own
|
|
208
|
+
for a command typed at its prompt. Quote a path with spaces.`,
|
|
129
209
|
},
|
|
130
210
|
snapshot: {
|
|
131
211
|
usage: 'snapshot [--full] [--grep <text> | <eN> | selector]',
|
|
132
212
|
summary: 'outline by role and name, with [ref=eN] labels',
|
|
133
|
-
detail:
|
|
213
|
+
detail: `Playwright's accessibility snapshot, with unnamed layout wrappers (generic) and cursor hints left
|
|
214
|
+
out; --full shows it unchanged.
|
|
215
|
+
|
|
216
|
+
--grep <text> prints only the lines containing text (role, name or flag such as [disabled], any
|
|
217
|
+
case), each with the named elements around it. A hit with no named element around it prints without
|
|
218
|
+
a path. A hit on text (a label) keeps the ref of the element holding it, so snapshot <that ref>
|
|
219
|
+
shows what sits beside it, such as the value next to the label. --regex <pattern> is --grep by a
|
|
220
|
+
regular expression: snapshot --regex "Total: \\d+".
|
|
221
|
+
|
|
222
|
+
An element with no role or name of its own (a card that is only a div) is left out, with its ref;
|
|
223
|
+
snapshot --full keeps it.
|
|
224
|
+
|
|
225
|
+
snapshot e3 outlines one element, and the refs of the last snapshot keep working. A ref (e3, or f1e3
|
|
226
|
+
in newer Playwright) works as a selector in any command: click e3. Refs can change when the page
|
|
227
|
+
changes (e102 may become f4e98), so take a new snapshot after it does. snapshot <selector> (not a
|
|
228
|
+
ref) gives new refs, and those from before stop working.
|
|
229
|
+
|
|
230
|
+
Output over 60 lines ends with a line count.`,
|
|
134
231
|
},
|
|
135
232
|
watch: {
|
|
136
233
|
usage: 'watch on [--changes] [--live] | off | [n|new]',
|
|
137
234
|
summary: 'record what happens in the tab; show the last n steps',
|
|
138
|
-
detail:
|
|
235
|
+
detail: `Off until watch on; a watch on after watch off starts a new recording. Records clicks, typing (once
|
|
236
|
+
it pauses), Enter and Escape, form changes, submits and navigations, each described by role and name
|
|
237
|
+
like snapshot, with up to 5 of the requests it caused underneath, each with its status as in requests
|
|
238
|
+
(body <#> for one), leaving out scripts and what requests hides. The REPL's own fill is recorded
|
|
239
|
+
as type. A double-click shows as two clicks; hovering and moving the mouse are not recorded.
|
|
240
|
+
|
|
241
|
+
From its first watch on, a tab keeps response bodies of up to 100 KB, so body <#> still shows one
|
|
242
|
+
after the tab navigates, even when the page navigates as soon as the request answers.
|
|
243
|
+
|
|
244
|
+
watch on --next-tab [url-part] returns at once, then waits for the next tab someone opens (tab new,
|
|
245
|
+
a client's own, does not count), or the next whose URL contains url-part, and watches it from its
|
|
246
|
+
first page and selects it for the client that asked: for a tab someone is about to open. It takes
|
|
247
|
+
--changes and --live too; watch off stops waiting, and modes lists it. One tab is waited for at a time:
|
|
248
|
+
while one client waits, another's watch on --next-tab is refused, and its watch off leaves the wait on.
|
|
249
|
+
|
|
250
|
+
watch on its own says whether it is on and shows the last 20 steps; watch <n> shows the last n.
|
|
251
|
+
watch new shows only the steps it has not shown yet, and requests that have since arrived for the
|
|
252
|
+
last one.
|
|
253
|
+
|
|
254
|
+
--live also prints each step in the REPL window (never in the server's answer to a command) once it
|
|
255
|
+
settles, with its requests and changes, marked [watch], or [watch <url>] for a tab that is not the
|
|
256
|
+
selected one. A step cut short by the next one, or by watch off, prints at once; changes it made
|
|
257
|
+
then show under the next step.
|
|
258
|
+
|
|
259
|
+
--changes also shows what each step changed on the page once it settles, in up to 5 lines: + added,
|
|
260
|
+
- removed, ~ changed, each element once with the first names inside it. Not for navigations: what a
|
|
261
|
+
page changes on its own after a step settles (data arriving later) shows under the next step.
|
|
262
|
+
|
|
263
|
+
A watch belongs to the tab, not to a client: every client sees the same steps, watch new's place is
|
|
264
|
+
shared, and anyone's watch off stops it.
|
|
265
|
+
|
|
266
|
+
Typed values are not recorded, and password fields not at all; --changes leaves out field values,
|
|
267
|
+
but what the page itself shows (e.g. "Hello <name>") is shown. Nothing is replayed.`,
|
|
139
268
|
},
|
|
140
269
|
text: { usage: 'text [--all] <selector>', summary: 'visible text of the first match' },
|
|
141
270
|
html: { usage: 'html [--all] <selector>', summary: 'outer HTML of the first match' },
|
|
@@ -143,7 +272,11 @@ const COMMANDS = {
|
|
|
143
272
|
listeners: {
|
|
144
273
|
usage: 'listeners <selector>|document|window',
|
|
145
274
|
summary: 'the page\'s event listeners on an element',
|
|
146
|
-
detail:
|
|
275
|
+
detail: `For the first match, one line each: the event, how it was added (capture, once, passive), the
|
|
276
|
+
start of the handler, where ↵ marks a line break, and its line in its script. --all shows each handler
|
|
277
|
+
whole, as written. Listeners added on a parent (e.g. document, for delegation) are not the element's
|
|
278
|
+
own: check listeners document too. Those Playwright adds to window as it acts are left out, and it
|
|
279
|
+
says how many.`,
|
|
147
280
|
},
|
|
148
281
|
count: { usage: 'count <selector>', summary: 'number of matches' },
|
|
149
282
|
visible: { usage: 'visible <selector>', summary: 'whether the first match is visible' },
|
|
@@ -152,80 +285,206 @@ const COMMANDS = {
|
|
|
152
285
|
screenshot: {
|
|
153
286
|
usage: 'screenshot [<ref>] [--full] [-d <secs>] [name]',
|
|
154
287
|
summary: 'save a PNG of the viewport, --full page or an element',
|
|
155
|
-
detail:
|
|
288
|
+
detail: `Saved as screenshot-<name or timestamp>.png in $PW_SCREENSHOT_DIR; without it, next to the REPL's
|
|
289
|
+
socket when it serves on one of its own, or else in /tmp. --filename=<file> saves it there instead (a
|
|
290
|
+
JPEG if it ends in .jpg), relative to the folder pw-repl send runs in; it never replaces a file.
|
|
291
|
+
|
|
292
|
+
screenshot e5 saves only that element, from a snapshot ref. --full-page is --full.
|
|
293
|
+
|
|
294
|
+
It brings the tab to the front of its window first: Chrome draws only the tab in front. The image
|
|
295
|
+
is at CSS pixel size, one image pixel per CSS pixel. While emulate mobile is on, it is what the
|
|
296
|
+
phone shows: a page laid out wider than the phone (no viewport meta tag) comes out shrunk to fit.
|
|
297
|
+
|
|
298
|
+
--delay counts down out loud first (maximum 60s), so someone can hold a hover or open a menu.`,
|
|
156
299
|
},
|
|
157
300
|
viewport: { usage: 'viewport [WxH]', summary: 'show or set the viewport size' },
|
|
158
301
|
emulate: {
|
|
159
302
|
usage: 'emulate [<what> [off] | off]',
|
|
160
303
|
summary: 'emulate a phone, dark mode, a locale or a timezone',
|
|
161
|
-
detail:
|
|
304
|
+
detail: `emulate mobile [device] a phone's screen, touch and user agent: a Pixel 7, or a device named as in
|
|
305
|
+
Playwright's list, e.g. emulate mobile iPhone 13; a name that is not
|
|
306
|
+
exact lists the devices it matches (emulate mobile galaxy)
|
|
307
|
+
emulate dark | light the color scheme the page's CSS and matchMedia see
|
|
308
|
+
emulate locale <tag> the language (Accept-Language, navigator.language) and date and number
|
|
309
|
+
formats, e.g. fr-FR
|
|
310
|
+
emulate timezone <zone> an IANA timezone, e.g. Asia/Tokyo
|
|
311
|
+
emulate <what> off stop one (dark or light off stops the color scheme); emulate off stops all
|
|
312
|
+
|
|
313
|
+
Per tab, until turned off or the REPL exits. emulate on its own shows what is on.
|
|
314
|
+
|
|
315
|
+
The page sees the user agent, touch and navigator.languages from its next load: reload after
|
|
316
|
+
turning mobile or locale on or off. Dark, light and timezone apply at once, but what the page
|
|
317
|
+
already drew with them (times, colors set by its script) changes only when it reloads.
|
|
318
|
+
|
|
319
|
+
While mobile is on, viewport refuses: the device sets the size.`,
|
|
162
320
|
},
|
|
163
321
|
wait: {
|
|
164
322
|
usage: 'wait [text|request] <what> [--gone] [secs]',
|
|
165
323
|
summary: 'wait for an element, text, a response or a load (10s)',
|
|
166
|
-
detail:
|
|
324
|
+
detail: `wait <selector> waits for a matching element to be visible, and wait text <text> for text to be;
|
|
325
|
+
wait request <url-part|glob> waits for a matching response, counting one that finished since the
|
|
326
|
+
previous command began, or is still under way (so click, then wait request, does not miss it).
|
|
327
|
+
|
|
328
|
+
wait load waits for the page to finish loading (its load event). A navigation that began since the
|
|
329
|
+
previous command began counts, so click a link, then wait load, waits for the new page. A page
|
|
330
|
+
that changes its own URL without loading (an app's own routing) is not a load: wait for something on
|
|
331
|
+
the new view instead.
|
|
332
|
+
|
|
333
|
+
wait text matches any part of an element's text, in any case.
|
|
334
|
+
|
|
335
|
+
--gone waits instead until nothing matching is visible (removed or hidden): wait .spinner --gone.
|
|
336
|
+
It is done at once if nothing matches yet, so wait for it to appear first if it may not have.
|
|
337
|
+
|
|
338
|
+
Up to 120s. A wait that times out is an error; the REPL carries on.`,
|
|
167
339
|
},
|
|
168
340
|
sleep: { usage: 'sleep <ms>', summary: 'wait a fixed time, in milliseconds (maximum 3600000)' },
|
|
169
341
|
requests: {
|
|
170
342
|
usage: 'requests [--all] [n] [url-filter]',
|
|
171
343
|
summary: 'the selected tab\'s last n requests (default 20)',
|
|
172
|
-
detail:
|
|
344
|
+
detail: `Recording starts when the REPL connects, and a tab opened since is recorded from its first page (one a
|
|
345
|
+
link opens included); the last 200 per tab are kept, of every kind the page
|
|
346
|
+
requests (not the browser's own, such as the favicon). Each line: #number, time, method, status (HTTP
|
|
347
|
+
code, pending, no response, failed: <reason>, <code>, then failed: <reason> when its body was cut
|
|
348
|
+
off, or <code> faked), duration, kind (document, fetch, xhr, script, image, ...), URL. A request that
|
|
349
|
+
finished a moment ago can still show pending.
|
|
350
|
+
|
|
351
|
+
n is how many of the latest to show (default 20). When more match, a first line says so: (last 20
|
|
352
|
+
of 112 kept; requests 112 shows them all). A page often makes more than 20 in one load. A line
|
|
353
|
+
"--- the page loads <url>" marks where each page load starts, even when a filter hides that request.
|
|
354
|
+
|
|
355
|
+
body <#> shows what a request got back.
|
|
356
|
+
|
|
357
|
+
Images, fonts, stylesheets, media and extension requests are hidden unless --all is given.
|
|
358
|
+
|
|
359
|
+
url-filter is a substring: requests 20 /api/ shows API calls only, e.g. when dev-server scripts
|
|
360
|
+
crowd the list. --regex <pattern>, at the end, matches URLs by a regular expression instead.`,
|
|
173
361
|
},
|
|
174
362
|
body: {
|
|
175
363
|
usage: 'body [--all] <#>|<url-part>',
|
|
176
364
|
summary: 'the response body of request <#>, or of the latest one matching',
|
|
177
|
-
detail:
|
|
365
|
+
detail: `body <url-part> shows the latest finished request whose URL contains url-part (body slots.json).
|
|
366
|
+
|
|
367
|
+
Read from the browser on demand: JSON is pretty-printed, binary is not shown, output is capped. The
|
|
368
|
+
browser may drop a body (e.g. after the tab navigates); then body reports not available, unless a
|
|
369
|
+
watch kept it (help watch).
|
|
370
|
+
|
|
371
|
+
Bodies can contain sensitive data.`,
|
|
178
372
|
},
|
|
179
373
|
console: {
|
|
180
374
|
usage: 'console [--all] [n] [level|filter]',
|
|
181
375
|
summary: 'the last n console messages and page errors',
|
|
182
|
-
detail:
|
|
376
|
+
detail: `Recording starts when the REPL connects; the last 200 per tab are kept. Each line: time, [type],
|
|
377
|
+
text. Types are console levels (log, warning, error, ...) and pageerror for uncaught exceptions. A
|
|
378
|
+
"--- the page loaded <url>" line marks each load after the first message shown.
|
|
379
|
+
|
|
380
|
+
A level (error, warning, info, debug) shows that level and those above it: console warning shows
|
|
381
|
+
warnings and errors, page errors among them. info, which leaves out debug, is what playwright-cli
|
|
382
|
+
shows by default. Anything else is a filter, matching part of the type or the text.
|
|
383
|
+
|
|
384
|
+
n is how many of the latest to show (default 20); when more match, a first line says so.
|
|
385
|
+
|
|
386
|
+
A page brought back from the back/forward cache (back, forward) reports its earlier messages again,
|
|
387
|
+
at the time it comes back, though nothing loaded again.`,
|
|
183
388
|
},
|
|
184
389
|
capture: {
|
|
185
390
|
usage: 'capture on [requests|console] [secs] | off',
|
|
186
391
|
summary: 'record requests and console together, in time order',
|
|
187
|
-
detail:
|
|
392
|
+
detail: `capture on records both until capture off, which prints them; requests or console records only one.
|
|
393
|
+
With secs (1-3600 seconds) it records that long, then prints. Other commands wait until a timed
|
|
394
|
+
capture ends, so it suits recording what someone does in the browser; around your own commands, use
|
|
395
|
+
capture on and capture off.
|
|
396
|
+
|
|
397
|
+
One capture runs at a time, on the tab selected when it started. Unlike requests and console it
|
|
398
|
+
keeps more than the last 200 and lists both together.
|
|
399
|
+
|
|
400
|
+
capture on its own says whether one is running, or shows the last one.`,
|
|
188
401
|
},
|
|
189
402
|
route: {
|
|
190
403
|
usage: 'route <glob> <how> | off <glob>|--all',
|
|
191
404
|
summary: 'fake, patch, delay or fail the selected tab\'s matching requests',
|
|
192
|
-
detail:
|
|
405
|
+
detail: `route <glob> <status> <json> answer with this status (200-599) and JSON, or no body; it never
|
|
406
|
+
reaches the network, so it still answers while the network is off.
|
|
407
|
+
The body is the rest of the line. Another kind needs its type:
|
|
408
|
+
route <glob> 200 --content-type=text/plain hello
|
|
409
|
+
route <glob> patch <json> let it through, then change its JSON response: a JSON Merge Patch, where
|
|
410
|
+
objects merge, null removes a key and anything else replaces; a
|
|
411
|
+
patch that is not an object (an array) replaces the whole body
|
|
412
|
+
route <glob> delay <secs> hold it for up to 120 seconds, then let it through
|
|
413
|
+
route <glob> abort fail it as if the connection broke
|
|
414
|
+
|
|
415
|
+
Each matching request prints a line (Faked:, Patched:, Delayed:, Aborted:) in the REPL window, and in
|
|
416
|
+
the answer to a command running then if that command's client has the tab selected, with its number as in requests (one the page makes after reload or
|
|
417
|
+
a click has answered shows only in the window: wait request <glob> waits for it); requests shows a fake
|
|
418
|
+
as <status> faked and a patch as <status> patched. If a route fails it prints "Route failed" and aborts
|
|
419
|
+
the request.
|
|
420
|
+
|
|
421
|
+
Routes belong to the tab and last until route off <glob> (or route off --all) or the REPL exits.
|
|
422
|
+
Routing the same glob again replaces it. route on its own lists the selected tab's routes. A glob may
|
|
423
|
+
be quoted.
|
|
424
|
+
|
|
425
|
+
Example: route **/api/cart patch {"total": 0}`,
|
|
193
426
|
},
|
|
194
427
|
network: {
|
|
195
428
|
usage: 'network [on|off|slow [<ms> [<kbps>]]]',
|
|
196
429
|
summary: 'cut, slow or restore the tab\'s network',
|
|
197
|
-
detail:
|
|
430
|
+
detail: `network off cuts it, like dropped wifi. Stopping a service is not the same: a dev proxy in front of
|
|
431
|
+
it usually holds the request open, so the page spins instead of failing.
|
|
432
|
+
|
|
433
|
+
network slow makes each request take at least <ms> (Chrome's latency is a minimum, not added to a
|
|
434
|
+
slow server's own time) and limits its speed: by default as DevTools' Slow 4G (563ms, 1440 kbps
|
|
435
|
+
down, 675 up); network slow <ms> [<kbps>] sets them, kbps both ways (left out, it stays Slow
|
|
436
|
+
4G's). To slow one API, use route <glob> delay <secs>.
|
|
437
|
+
|
|
438
|
+
Per tab; lasts until network on or the REPL exits. Routes still answer while it is off or slow.`,
|
|
198
439
|
},
|
|
199
440
|
eval: {
|
|
200
441
|
usage: 'eval [--all] <JavaScript>',
|
|
201
442
|
summary: 'evaluate JavaScript in the selected tab and print the result',
|
|
202
|
-
detail:
|
|
443
|
+
detail: `Promises are awaited, await works at the top level, and a function (() => document.title) is
|
|
444
|
+
called. eval <function> <ref> calls it with that element: eval "el => el.textContent" e5.
|
|
445
|
+
|
|
446
|
+
eval fetch(...) sends a new request, which can change state on the server; it cannot read one that
|
|
447
|
+
already happened (requests lists those).`,
|
|
203
448
|
},
|
|
204
449
|
cdp: {
|
|
205
450
|
usage: 'cdp [--all] <method> <JSON object>',
|
|
206
451
|
summary: 'send one CDP command through a temporary session',
|
|
207
|
-
detail:
|
|
452
|
+
detail: `The session is detached afterwards, so subscriptions and settings do not persist.
|
|
453
|
+
|
|
454
|
+
Browser.close and Target.closeTarget are refused.`,
|
|
208
455
|
},
|
|
209
456
|
cookies: { usage: 'cookies [--all]', summary: 'cookie names, domains and flags; values are omitted' },
|
|
210
457
|
storage: { usage: 'storage [--all]', summary: 'localStorage keys; values are omitted' },
|
|
211
458
|
modes: {
|
|
212
459
|
usage: 'modes [off [--mine]]',
|
|
213
460
|
summary: 'the modes on in every tab; modes off turns them all off',
|
|
214
|
-
detail:
|
|
461
|
+
detail: `The modes are watch, network off or slow, route, capture and emulate. Each is turned on and off with
|
|
462
|
+
its own command: watch on|off, network off|slow|on, route <glob> ... | route off <glob>|--all,
|
|
463
|
+
capture on|off, emulate ... | emulate off.
|
|
464
|
+
|
|
465
|
+
modes off turns off every one in every tab; a capture it stops is kept for capture to show. With
|
|
466
|
+
clients (help session), modes says which client turned each on, and modes off --mine turns off only
|
|
467
|
+
the sender's own.`,
|
|
215
468
|
},
|
|
216
469
|
dialog: {
|
|
217
470
|
usage: 'dialog [accept [text] | dismiss]',
|
|
218
471
|
summary: 'show, accept or dismiss an open alert, confirm or prompt',
|
|
219
|
-
detail:
|
|
472
|
+
detail: `While a dialog is open, its page and the commands that read it wait. dialog runs at once, ahead
|
|
473
|
+
of them; it answers the selected tab's dialog, or the only one open. accept text answers a prompt.
|
|
474
|
+
|
|
475
|
+
With nobody at the browser (e.g. headless), it is the only way to answer one.`,
|
|
220
476
|
},
|
|
221
477
|
help: { usage: 'help [topic | command | --all]', summary: 'this help; --all prints every topic and command in full' },
|
|
222
478
|
quit: {
|
|
223
479
|
usage: 'quit',
|
|
224
|
-
summary: 'disconnect, leaving Chromium running',
|
|
225
|
-
detail:
|
|
480
|
+
summary: 'disconnect, leaving Chromium running (one --launch started stops too)',
|
|
481
|
+
detail: `Runs immediately, even while another command is waiting. Only available at the prompt.`,
|
|
226
482
|
},
|
|
227
483
|
};
|
|
228
484
|
|
|
485
|
+
for (const topic of Object.values(TOPICS)) topic.intro = wrap(topic.intro);
|
|
486
|
+
for (const entry of Object.values(COMMANDS)) if (entry.detail) entry.detail = wrap(entry.detail);
|
|
487
|
+
|
|
229
488
|
function commandLines(names) {
|
|
230
489
|
const width = Math.max(...names.map(n => COMMANDS[n].usage.length));
|
|
231
490
|
return names.map(n => ` ${COMMANDS[n].usage.padEnd(width)} ${COMMANDS[n].summary}`);
|
|
@@ -252,7 +511,7 @@ function renderAll() {
|
|
|
252
511
|
function render(topicOrCommand) {
|
|
253
512
|
if (!topicOrCommand) return OVERVIEW;
|
|
254
513
|
if (topicOrCommand === 'playwright-cli') {
|
|
255
|
-
return `playwright-cli's command names, and what they run here (each says so the first time):
|
|
514
|
+
return wrap(`playwright-cli's command names, and what they run here (each says so the first time):
|
|
256
515
|
|
|
257
516
|
${require('./cli-names').table()}
|
|
258
517
|
|
|
@@ -267,7 +526,7 @@ Different on purpose, since the browser may be someone else's too:
|
|
|
267
526
|
close turns off your modes; tabs stay open (tab close <url-part> closes one)
|
|
268
527
|
upload <selector> <file> names the input: file pickers are not caught, as a person's would be
|
|
269
528
|
no snapshot after each command a person reads the pane too; snapshot when you need one
|
|
270
|
-
-s=<session> -c <name> is the nearest: a client with a selected tab of its own
|
|
529
|
+
-s=<session> -c <name> is the nearest: a client with a selected tab of its own`);
|
|
271
530
|
}
|
|
272
531
|
if (topicOrCommand === '--all') return renderAll();
|
|
273
532
|
if (Object.hasOwn(TOPICS, topicOrCommand)) {
|