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/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
- const OVERVIEW = `playwright-repl drives Chromium over CDP. Commands act on the selected tab (\`tab\` shows which).
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: 'No tab is selected when the REPL starts: tab new opens one, tab <index|url-part> selects one.\ntab on its own lists them all.',
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: 'Selectors are Playwright selectors (CSS, text=..., role=...) or snapshot refs such as e5.\nCommands use the first match; see help fill for selectors with spaces.',
48
- commands: ['click', 'dblclick', 'hover', 'fill', 'type', 'press', 'select', 'check', 'uncheck', 'upload'],
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: 'Output is capped; --all right after the command, or at the end, shows everything (text --all body).',
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: 'requests and console record all the time; a capture records only while it runs.',
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: 'Cookie and storage listings omit values. eval, cdp, html, screenshots and URLs can still show\nsensitive data (cdp Network.getCookies shows cookie values).',
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: 'tab <index> uses the numbers from your latest tab listing (each client has its own); they change\nwhen tabs open or close.\n\ntab <url-part> selects the one tab whose URL contains it, and refuses if none or several do.\n\ntab new opens a tab behind the one in front, so it does not take the window from whoever is using it,\nand selects it; tab shows who opened each tab opened this way. tab close closes the selected tab, or\nthe one matching url-part.\n\nClosing the selected tab goes back to the previous tab if tab new opened it; otherwise no tab is\nselected, and commands that need one refuse until one is.',
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: 'Without a scheme, http:// is assumed for localhost, 127.x and [::1], and https:// otherwise, as in\nthe address bar. tab new <url> reads its URL the same way.',
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: 'The browser may bring the page back from its back/forward cache, as it was: nothing loads or runs\nagain, so a loading message or a request the page makes on load does not come back. reload loads it\nafresh.',
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: 'Like back, it may bring the page back from the back/forward cache without loading it again.' },
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: 'After a ref or a quoted selector, a button (left, right, middle) may follow: click e5 right.\n--modifiers=<key>[,<key>] holds keys down meanwhile (Alt, Control, ControlOrMeta, Meta, Shift).',
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: 'The selector is the first word; quote it if it has spaces. The rest of the line is the value, and\nquotes around all of it are removed, so fill #name "" clears the field. --submit at the end presses\nEnter in it afterwards.\n\nExamples: fill #name Ada Lovelace, fill "text=Your name" Ada, fill e7 Ada',
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: 'With one word, or one quoted, it is typed into the element that has focus: type "garden hose".\n--submit at the end presses Enter afterwards.',
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: 'The selector is a file input (even a hidden one), its label, or a button that opens the file\npicker. The page then does what it does with a chosen file; often that is the upload itself.\n\nThe REPL reads the files and hands the page their contents, so they need not be where the browser\nruns; up to 50MB in all. Relative paths are from the folder pw-repl send runs in, or the REPL\'s own\nfor a command typed at its prompt. Quote a path with spaces.',
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: 'Playwright\'s accessibility snapshot, with unnamed layout wrappers (generic) and cursor hints left\nout; --full shows it unchanged.\n\n--grep <text> prints only the lines containing text (role, name or flag such as [disabled], any\ncase), each with the named elements around it. A hit with no named element around it prints without\na path. A hit on text (a label) keeps the ref of the element holding it, so snapshot <that ref>\nshows what sits beside it, such as the value next to the label. --regex <pattern> is --grep by a\nregular expression: snapshot --regex "Total: \\d+".\n\nsnapshot e3 outlines one element. A ref (e3, or f1e3 in newer Playwright) works as a selector in any\ncommand: click e3. Refs can change when the page changes (e102 may become f4e98), so take a new\nsnapshot after it does.\n\nOutput over 60 lines ends with a line count.',
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: 'Off until watch on; a watch on after watch off starts a new recording. Records clicks, typing (once\nit pauses), Enter and Escape, form changes, submits and navigations, each described by role and name\nlike snapshot, with up to 5 of the requests it caused underneath, each with its status as in requests\n(body <#> for one), leaving out scripts and what requests hides. The REPL\'s own fill is recorded\nas type.\n\nFrom its first watch on, a tab keeps response bodies of up to 100 KB, so body <#> still shows one\nafter the tab navigates, even when the page navigates as soon as the request answers.\n\nwatch on --next-tab [url-part] returns at once, then waits for the next tab someone opens (tab new,\na client\'s own, does not count), or the next whose URL contains url-part, and watches it from its\nfirst page and selects it for the client that asked: for a tab someone is about to open. It takes\n--changes and --live too; watch off stops waiting, and modes lists it.\n\nwatch on its own says whether it is on and shows the last 20 steps; watch <n> shows the last n.\nwatch new shows only the steps it has not shown yet, and requests that have since arrived for the\nlast one.\n\n--live also prints each step in the REPL window (never in the server\'s answer to a command) once it\nsettles, with its requests and changes, marked [watch], or [watch <url>] for a tab that is not the\nselected one. A step cut short by the next one, or by watch off, prints at once; changes it made\nthen show under the next step.\n\n--changes also shows what each step changed on the page once it settles, in up to 5 lines: + added,\n- removed, ~ changed, each element once with the first names inside it. Not for navigations: what a\npage changes on its own after a step settles (data arriving later) shows under the next step.\n\nA watch belongs to the tab, not to a client: every client sees the same steps, watch new\'s place is\nshared, and anyone\'s watch off stops it.\n\nTyped values are not recorded, and password fields not at all; --changes leaves out field values,\nbut what the page itself shows (e.g. "Hello <name>") is shown. Nothing is replayed.',
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: 'One line each: the event, how it was added (capture, once, passive), the start of the handler and\nits line in its script. Listeners added on a parent (e.g. document, for delegation) are not the\nelement\'s own: check listeners document too.',
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: 'Saved as screenshot-<name or timestamp>.png in $PW_SCREENSHOT_DIR; without it, next to the REPL\'s\nsocket when it serves on one of its own, or else in /tmp. --filename=<file> saves it there instead (a\nJPEG if it ends in .jpg), relative to the folder pw-repl send runs in; it never replaces a file.\n\nscreenshot e5 saves only that element, from a snapshot ref. --full-page is --full.\n\nIt brings the tab to the front of its window first: Chrome draws only the tab in front. The image\nis at CSS pixel size, one image pixel per CSS pixel, also while emulate mobile is on.\n\n--delay counts down out loud first (maximum 60s), so someone can hold a hover or open a menu.',
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: 'emulate mobile [device] a phone\'s screen, touch and user agent: a Pixel 7, or a device named as in\n Playwright\'s list, e.g. emulate mobile iPhone 13; a name that is not\n exact lists the devices it matches (emulate mobile galaxy)\nemulate dark | light the color scheme the page\'s CSS and matchMedia see\nemulate locale <tag> the language (Accept-Language, navigator.language) and date and number\n formats, e.g. fr-FR\nemulate timezone <zone> an IANA timezone, e.g. Asia/Tokyo\nemulate <what> off stop one (dark or light off stops the color scheme); emulate off stops all\n\nPer tab, until turned off or the REPL exits. emulate on its own shows what is on.\n\nThe page sees the user agent, touch and navigator.languages from its next load: reload after\nturning mobile or locale on or off. Dark, light and timezone apply at once, but what the page\nalready drew with them (times, colors set by its script) changes only when it reloads.\n\nWhile mobile is on, viewport refuses: the device sets the size.',
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: 'wait <selector> waits for a matching element to be visible, and wait text <text> for text to be;\nwait request <url-part|glob> waits for a matching response, counting one that finished since the\nprevious command began (so click, then wait request, does not miss it).\n\nwait load waits for the page to finish loading (its load event). A navigation that began since the\nprevious command began counts, so click a link, then wait load, waits for the new page. A page\nthat changes its own URL without loading (an app\'s own routing) is not a load: wait for something on\nthe new view instead.\n\nwait text matches any part of an element\'s text, in any case.\n\n--gone waits instead until nothing matching is visible (removed or hidden): wait .spinner --gone.\nIt is done at once if nothing matches yet, so wait for it to appear first if it may not have.\n\nUp to 120s. A wait that times out is an error; the REPL carries on.',
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: 'Recording starts when the REPL connects; the last 200 per tab are kept. Each line: #number, time,\nmethod, status (HTTP code, pending, no response, failed: <reason>, or <code> faked), duration, URL.\nA request that finished a moment ago can still show pending.\n\nbody <#> shows what a request got back.\n\nImages, fonts, stylesheets, media and extension requests are hidden unless --all is given.\n\nurl-filter is a substring: requests 20 /api/ shows API calls only, e.g. when dev-server scripts\ncrowd the list. --regex <pattern>, at the end, matches URLs by a regular expression instead.',
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: 'body <url-part> shows the latest finished request whose URL contains url-part (body slots.json).\n\nRead from the browser on demand: JSON is pretty-printed, binary is not shown, output is capped. The\nbrowser may drop a body (e.g. after the tab navigates); then body reports not available, unless a\nwatch kept it (help watch).\n\nBodies can contain sensitive data.',
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: 'Recording starts when the REPL connects; the last 200 per tab are kept. Each line: time, [type],\ntext. Types are console levels (log, warning, error, ...) and pageerror for uncaught exceptions. A\n"--- the page loaded <url>" line marks each load after the first message shown.\n\nA level (error, warning, info, debug) shows that level and those above it: console warning shows\nwarnings and errors, page errors among them. info, which leaves out debug, is what playwright-cli\nshows by default. Anything else is a filter, matching part of the type or the text.\n\nA page brought back from the back/forward cache (back, forward) reports its earlier messages again,\nat the time it comes back, though nothing loaded again.',
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: 'capture on records both until capture off, which prints them; requests or console records only one.\nWith secs (1-3600 seconds) it records that long, then prints. Other commands wait until a timed\ncapture ends, so it suits recording what someone does in the browser; around your own commands, use\ncapture on and capture off.\n\nOne capture runs at a time, on the tab selected when it started. Unlike requests and console it\nkeeps more than the last 200 and lists both together.\n\ncapture on its own says whether one is running, or shows the last one.',
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: 'route <glob> <status> <json> answer with this status (200-599) and JSON, or no body; it never\n reaches the network, so it still answers while the network is off.\n The body is the rest of the line. Another kind needs its type:\n route <glob> 200 --content-type=text/plain hello\nroute <glob> patch <json> let it through, then change its JSON response: a JSON Merge Patch, where\n objects merge, null removes a key and anything else replaces\nroute <glob> delay <secs> hold it for up to 120 seconds, then let it through\nroute <glob> abort fail it as if the connection broke\n\nEach matching request prints a line (Faked:, Patched:, Delayed:, Aborted:) in the REPL window, and in\nthe answer to a command running then, with its number as in requests; requests shows a fake as\n<status> faked and a patch as <status> patched. If a route fails it prints "Route failed" and aborts\nthe request.\n\nRoutes belong to the tab and last until route off <glob> (or route off --all) or the REPL exits.\nRouting the same glob again replaces it. route on its own lists the selected tab\'s routes. A glob may\nbe quoted.\n\nExample: route **/api/cart patch {"total": 0}',
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: 'network off cuts it, like dropped wifi. Stopping a service is not the same: a dev proxy in front of\nit usually holds the request open, so the page spins instead of failing.\n\nnetwork slow makes each request take at least <ms> (Chrome\'s latency is a minimum, not added to a\nslow server\'s own time) and limits its speed: by default as DevTools\' Slow 4G (563ms, 1440 kbps\ndown, 675 up); network slow <ms> [<kbps>] sets them, kbps both ways (left out, it stays Slow\n4G\'s). To slow one API, use route <glob> delay <secs>.\n\nPer tab; lasts until network on or the REPL exits. Routes still answer while it is off or slow.',
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: 'Promises are awaited, await works at the top level, and a function (() => document.title) is\ncalled. eval <function> <ref> calls it with that element: eval "el => el.textContent" e5.\n\neval fetch(...) sends a new request, which can change state on the server; it cannot read one that\nalready happened (requests lists those).',
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: 'The session is detached afterwards, so subscriptions and settings do not persist.\n\nBrowser.close and Target.closeTarget are refused.',
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: 'The modes are watch, network off or slow, route, capture and emulate. Each is turned on and off with\nits own command: watch on|off, network off|slow|on, route <glob> ... | route off <glob>|--all,\ncapture on|off, emulate ... | emulate off.\n\nmodes off turns off every one in every tab; a capture it stops is kept for capture to show. With\nclients (help session), modes says which client turned each on, and modes off --mine turns off only\nthe sender\'s own.',
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: 'While a dialog is open, its page and the commands that read it wait. dialog runs at once, ahead\nof them; it answers the selected tab\'s dialog, or the only one open. accept text answers a prompt.\n\nWith nobody at the browser (e.g. headless), it is the only way to answer one.',
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: 'Runs immediately, even while another command is waiting. Only available at the prompt.',
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)) {