pw-repl 0.12.7 → 0.13.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
@@ -22,36 +22,52 @@ function wrap(text, width = WIDTH) {
22
22
  }).join('\n\n');
23
23
  }
24
24
 
25
+ // The common tasks, as the README's table lists them too (a unit test keeps the two the same).
26
+ const TASKS = [
27
+ ['see where I am', 'tab, info'],
28
+ ['see what is on the page', 'snapshot, screenshot'],
29
+ ['do something on it', 'click, fill, type, press, drag'],
30
+ ['click what has no selector (a canvas)', 'mouseclick <x> <y>, mousedblclick <x> <y>'],
31
+ ['run some JavaScript in the page', 'eval document.title; eval --file=check.js for a long one'],
32
+ ['wait for a page or element', 'wait load, wait <selector> [--gone], wait text <text>'],
33
+ ['choose a file in a file input', 'upload <selector> <file>'],
34
+ ['see it as a phone, or in dark mode', 'emulate mobile, emulate dark'],
35
+ ['see what the page requested', 'requests, then body <#> for what one got back'],
36
+ ['see console messages and errors', 'console'],
37
+ ['show an agent what I do', 'watch on, click around, then watch'],
38
+ ['see each step as I click', 'watch on --live'],
39
+ ['watch a tab someone will open', 'watch on --next-tab [url-part]'],
40
+ ['replay what I clicked through', 'watch save flow.txt, then pw-repl send --file flow.txt'],
41
+ ['record requests and console together', 'capture on, then capture off'],
42
+ ['find what makes the page slow', 'profile on, then profile off; profile reload for a load'],
43
+ ['fake a backend response', 'route <glob> <status> <json>, route <glob> abort'],
44
+ ['change or slow an API response', 'route <glob> patch <json>, route <glob> delay <time>'],
45
+ ['cut or slow the whole network', 'network off, network slow, then network on'],
46
+ ['record a video of the page', 'record on, then record off (help video for a take)'],
47
+ ['show a pointer where the agent acts', 'cursor on, then cursor off'],
48
+ ['say what is happening on screen', 'toast <text>, then toast off'],
49
+ ['point something out on the page', 'highlight <ref>, then highlight off'],
50
+ ['clean up', 'modes off'],
51
+ ];
52
+
53
+ // The commands that turn on a mode, as the README lists them too.
54
+ const MODES = 'watch, capture, profile, record, route, network off or slow, emulate, viewport, highlight, cursor and toast';
55
+
25
56
  const OVERVIEW = wrap(`pw-repl drives Chromium over CDP. Commands act on the selected tab (\`tab\` shows which).
26
57
  The browser may be a shared session, with other users' tabs open in it.
27
58
 
28
59
  Common tasks:
29
- where am I? tab, info
30
- what is on the page? snapshot, screenshot
31
- do something on it click, fill, press
32
- wait for a page or element wait load, wait <selector>, wait <selector> --gone
33
- choose a file in a file input upload <selector> <file>
34
- see it as a phone, or dark emulate mobile, emulate dark
35
- what did the page request? requests, then body <#> for what one got back
36
- console messages and errors console
37
- show an agent what I do watch on, click around, then watch
38
- see each step as I click watch on --live
39
- watch a tab someone will open watch on --next-tab [url-part]
40
- requests and console together capture on, then capture off
41
- a video of what I do help video
42
- break the backend on purpose route <glob> <status> <json>, route <glob> abort, network off
43
- change or slow an API response route <glob> patch <json>, route <glob> delay <secs>
44
- slow the whole network network slow
45
- clean up modes off
46
-
47
- Modes (watch, capture, record, route, network off or slow, emulate, viewport) stay on until turned off;
48
- the prompt shows the selected tab's: (watch routes:1) pw>. tab, watch, capture, record, route, network,
49
- emulate and modes on their own show their state and what you can run next.
60
+ ${TASKS.map(([task, commands]) => ` ${task.padEnd(38)} ${commands}`).join('\n')}
61
+
62
+ Modes (${MODES}) stay on until turned off; the prompt shows the selected tab's: (watch routes:1) pw>.
63
+ Each of them on its own, and modes, shows its state and what you can run next.
64
+
65
+ Times are in milliseconds, as in Playwright, or in seconds with an s: wait .done 30000, or wait .done 30s.
50
66
 
51
67
  Topics (help <topic>):
52
68
  tabs open, select, close, navigate network requests, bodies, console, fakes, network off
53
69
  interact click, fill, type, press, select devtools eval, CDP commands, cookie and storage names
54
- inspect snapshot, watch, text, screenshots session modes, markers, server, quitting
70
+ inspect snapshot, watch, wait, screenshots session modes, server, clients, quitting
55
71
  video record a take, with a pointer
56
72
 
57
73
  help <command> for usage and caveats (e.g. help route), help <command> --all for all of it; help --all
@@ -71,7 +87,7 @@ tab on its own lists them all.`,
71
87
  Commands use the first match (click, dblclick and hover the first they can use: help click); see help
72
88
  fill for selectors with spaces. mousemove and the other mouse commands act at a point instead. An action
73
89
  scrolls its element into view first, as Playwright does; profile counts that scroll in the action's step.`,
74
- commands: ['click', 'dblclick', 'hover', 'fill', 'type', 'press', 'select', 'check', 'uncheck', 'upload', 'mousemove', 'mouseclick', 'mousedown', 'mouseup', 'mousewheel'],
90
+ commands: ['click', 'dblclick', 'hover', 'drag', 'fill', 'type', 'press', 'select', 'check', 'uncheck', 'upload', 'mousemove', 'mouseclick', 'mousedblclick', 'mousedown', 'mouseup', 'mousewheel'],
75
91
  },
76
92
  inspect: {
77
93
  intro: `Output is capped; --all right after the command, or at the end, shows everything (text --all body).`,
@@ -106,7 +122,7 @@ box to point at something, and toast <text> a line of text saying what is happen
106
122
  the viewer needs to read it. After a step that loads a page, wait for text only the result shows (wait
107
123
  text ...), not a heading the page always has, and not wait load: a single-page app loads nothing.
108
124
 
109
- record on --steps also saves when and where each step ran, in the video's seconds and pixels, for
125
+ record on --steps also saves when and where each step ran, in seconds into the video and its pixels, for
110
126
  editing it afterwards.
111
127
 
112
128
  The user's own screen, with their real pointer, takes a screen recorder they run (OBS, ffmpeg -f
@@ -121,15 +137,10 @@ clean browser of its own (here, its video-start is record on).`,
121
137
  devtools: {
122
138
  intro: `Cookie and storage listings omit values. eval, cdp, html, screenshots, recordings and URLs can
123
139
  still show sensitive data (cdp Network.getCookies shows cookie values).`,
124
- commands: ['eval', 'cdp', 'cookies', 'storage'],
140
+ commands: ['eval', 'cdp', 'clipboard', 'cookies', 'storage'],
125
141
  },
126
142
  session: {
127
- intro: `Completion: @<id> <command> makes the REPL print [[pw-done:<id>:ok|error]] when it finishes.
128
-
129
- Unknown outcome: a command that timed out after it began acting on the page may or may not have done
130
- it. It says so (pw-repl send exits 2), and the REPL carries on.
131
-
132
- Server: pw-repl serve also takes commands on /tmp/pw-repl.sock; they show here as [server] lines.
143
+ intro: `Server: pw-repl serve also takes commands on /tmp/pw-repl.sock; they show here as [server] lines.
133
144
 
134
145
  Clients: pw-repl send -c <name> (or $PW_CLIENT) sends as a client of its own. It has a selected tab of
135
146
  its own, so clients do not move each other's; its commands show as [server:<name>] lines. modes and tab
@@ -143,7 +154,12 @@ selected tab, if any: (watch network:off routes:2 capture) pw>. modes lists them
143
154
 
144
155
  Times (requests, console, watch) are local, with their UTC offset: 18:16:15.721-06:00.
145
156
 
146
- Dialogs are reported and never answered on their own; dialog accept or dismiss answers one, from any client, whoever opened it.`,
157
+ Dialogs are reported and never answered on their own; dialog accept or dismiss answers one, from any client, whoever opened it.
158
+
159
+ Unknown outcome: a command that timed out after it began acting on the page may or may not have done
160
+ it. It says so (pw-repl send exits 2), and the REPL carries on.
161
+
162
+ Completion: @<id> <command> makes the REPL print [[pw-done:<id>:ok|error]] when it finishes.`,
147
163
  commands: ['modes', 'dialog', 'help', 'quit'],
148
164
  },
149
165
  };
@@ -197,6 +213,8 @@ afresh.`,
197
213
  ['[button]', 'left, right or middle, after a ref or a quoted selector: click e5 right. Default: left.'],
198
214
  ],
199
215
  options: [
216
+ ['--button=<button>', 'the button, after any selector: click #menu --button=right. Over send, the shell takes the quotes, so pw-repl send click \'#menu\' right clicks a selector "#menu right": use --button, or quote it twice (\'"#menu"\').'],
217
+ ['--click-count=<n>', 'clicks this many times at once, as Playwright\'s clickCount: 2 is a double-click (as dblclick), 3 a triple-click, which selects a line of text or all of a field\'s. 1 to 3. Default: 1.'],
200
218
  ['--modifiers=<keys>', 'keys held down meanwhile, comma-separated: Alt, Control, ControlOrMeta, Meta, Shift.'],
201
219
  ],
202
220
  detail: `Several matches: hidden ones and ones covered by something else (a dialog's overlay) are passed over,
@@ -208,8 +226,23 @@ it to be ready.
208
226
  Timed out after it began acting, it says what stopped it (e.g. <div class="overlay"> intercepts pointer
209
227
  events) and that the outcome is unknown; Playwright's full call log goes to the REPL's pane or log.`,
210
228
  },
211
- dblclick: { usage: 'dblclick <selector> [button]', summary: 'double-click, choosing the match as click does' },
229
+ dblclick: {
230
+ usage: 'dblclick <selector> [button]',
231
+ summary: 'double-click, as click does, with its options',
232
+ detail: `click --click-count=2, as Playwright's dblclick is. For a triple-click: click --click-count=3.`,
233
+ },
212
234
  hover: { usage: 'hover <selector>', summary: 'hover, choosing the match as click does' },
235
+ drag: {
236
+ usage: 'drag <from> <to>',
237
+ summary: 'drag one element onto another',
238
+ args: [
239
+ ['<from> <to>', 'selectors or snapshot refs, each quoted if it has spaces: drag e5 e9, drag "text=Item 1" "#done ul".'],
240
+ ],
241
+ detail: `As Playwright's dragTo: it hovers the first match of <from>, presses the left button, hovers the
242
+ first match of <to>, and releases, waiting up to 5s in all for each to be ready. An HTML5 drag (a
243
+ draggable element) gets dragstart, dragover and drop; any other gets pointer and mouse events. A drag
244
+ through points of its own: mousemove, mousedown, mousemove, mouseup.`,
245
+ },
213
246
  mousemove: {
214
247
  usage: 'mousemove <x> <y>',
215
248
  summary: 'move the mouse to a point in the viewport',
@@ -220,7 +253,7 @@ events) and that the outcome is unknown; Playwright's full call log goes to the
220
253
  eval (r => [r.x + r.width / 2, r.y + r.height / 2])(document.querySelector("#map")
221
254
  .getBoundingClientRect())
222
255
 
223
- A click at a point: mouseclick <x> <y>. A drag: mousedown, then mousemove to another point, then mouseup.`,
256
+ A click at a point: mouseclick <x> <y>; a double-click: mousedblclick <x> <y>. A drag: mousedown, then mousemove to another point, then mouseup.`,
224
257
  },
225
258
  mouseclick: {
226
259
  usage: 'mouseclick <x> <y> [button]',
@@ -230,14 +263,30 @@ A click at a point: mouseclick <x> <y>. A drag: mousedown, then mousemove to ano
230
263
  ['[button]', 'left, right or middle. Default: left.'],
231
264
  ],
232
265
  options: [
266
+ ['--button=<button>', 'the button, as click\'s: mouseclick 400 300 --button=right.'],
267
+ ['--click-count=<n>', 'as click\'s: 2 is a double-click (as mousedblclick), 3 a triple-click. 1 to 3. Default: 1.'],
233
268
  ['--delay=<ms>', 'holds the button down this long between press and release, as Playwright\'s mouse.click delay. Default: 0.'],
234
269
  ],
235
270
  detail: `For what has no selector, such as a marker drawn on a canvas; anything a selector finds is better
236
271
  clicked with click, which waits for it. help mousemove shows how to find a point. The mouse stays at the point, for a mousewheel or mousedown after
237
272
  it. The cursor (help cursor) moves there first and shows a ring at the press.`,
238
273
  },
239
- mousedown: { usage: 'mousedown [button]', summary: 'press a mouse button at the mouse (left, right, middle)' },
240
- mouseup: { usage: 'mouseup [button]', summary: 'release a mouse button where the mouse is' },
274
+ mousedblclick: {
275
+ usage: 'mousedblclick <x> <y> [button]',
276
+ summary: 'double-click at a point in the viewport',
277
+ args: [
278
+ ['<x> <y>', 'CSS pixels from the top left of the tab\'s viewport, as for mousemove.'],
279
+ ['[button]', 'left, right or middle. Default: left.'],
280
+ ],
281
+ options: [
282
+ ['--button=<button>', 'the button, as click\'s.'],
283
+ ],
284
+ detail: `mouseclick --click-count=2, as Playwright's mouse.dblclick is: the page gets two clicks and a
285
+ dblclick. Two mouseclicks are two single clicks, which fire no dblclick. For a triple-click: mouseclick
286
+ <x> <y> --click-count=3.`,
287
+ },
288
+ mousedown: { usage: 'mousedown [button]', summary: 'press a mouse button at the mouse (left, right, middle)', detail: 'The button is a word or --button=<button>, as for mouseclick.' },
289
+ mouseup: { usage: 'mouseup [button]', summary: 'release a mouse button where the mouse is', detail: 'The button is a word or --button=<button>, as for mouseclick.' },
241
290
  mousewheel: {
242
291
  usage: 'mousewheel <dx> <dy>',
243
292
  summary: 'turn the wheel where the mouse is',
@@ -259,14 +308,19 @@ scrolled or redrawn. While recording, it scrolls in eased steps over up to 1s (h
259
308
  ['--submit', 'at the end: presses Enter in it afterwards.'],
260
309
  ],
261
310
  detail: `Examples: fill #name Ada Lovelace, fill "text=Your name" Ada, fill e7 Ada. It sets the whole value at
262
- once, even while recording: type shows it being typed.`,
311
+ once, even while recording: type shows it being typed.
312
+
313
+ At the prompt and in a send --file line, \\n, \\t and \\r in double quotes are a newline, a tab and a
314
+ carriage return (fill #notes "line 1\\nline 2"), and \\\\ is a backslash. Over send, the shell's quoting comes
315
+ first, and its "a\\nb" is a literal \\n, typed as a backslash and an n: give it a real newline, as
316
+ $'line 1\\nline 2'.`,
263
317
  },
264
318
  type: {
265
319
  usage: 'type [--delay=<ms>] [<selector>] <text> [--submit]',
266
320
  summary: 'type key by key after what the field holds',
267
321
  args: [
268
322
  ['[<selector>]', 'as for fill. Without it (one word, or one quoted), into what has focus: type "garden hose".'],
269
- ['<text>', 'the rest of the line, as fill\'s value.'],
323
+ ['<text>', 'the rest of the line, as fill\'s value: a newline in it presses Enter, which in a textarea starts a new line (help fill says how to give one).'],
270
324
  ],
271
325
  options: [
272
326
  ['--delay=<ms>', 'right after type: the gap between keys. Default: none, or 60 while the tab records.'],
@@ -320,10 +374,13 @@ all.`,
320
374
  ['--grep <text>', 'only the lines containing text (role, name or a flag such as [disabled], any case), each with the named elements around it.'],
321
375
  ['--regex <pattern>', '--grep by a regular expression: snapshot --regex "Total: \\d+".'],
322
376
  ],
323
- detail: `Refs: a ref (e3, or f1e3 in newer Playwright) works as a selector in any command: click e3. It belongs
377
+ detail: `Refs: a ref (e3; f1e3, f2e3 once the page has loaded again, when the old refs stop working) works as a selector in any command: click e3. It belongs
324
378
  to the tab whose snapshot gave it, and can change when the page changes (e102 may become f4e98): take a
325
379
  new snapshot after it does.
326
380
 
381
+ Quotes: some names and text are quoted, YAML-style, as one with a quote mark in it is, and their quotes
382
+ are escaped: paragraph: "Say \\"hi\\"" is the text Say "hi" on the page, with no backslashes.
383
+
327
384
  --grep: a hit with no named element around it prints without a path. A hit on text (a label) keeps the
328
385
  ref of the element holding it, so snapshot <that ref> shows what sits beside it, such as the value next
329
386
  to the label.
@@ -381,7 +438,14 @@ one no selector finds alone) stays a comment saying why, and the save says how m
381
438
  ends with the last step, not with what it brought: to end on a result that comes later (search results
382
439
  loading), add a wait for it at the end (wait text ...).
383
440
 
384
- Shared: every client sees the same steps, and watch new's place is shared.`,
441
+ Watching someone: read a response body over 100 KB that matters as soon as it shows in requests, since
442
+ the browser drops it once the tab navigates. watch leaves out images and other static files, failed ones
443
+ too; console error and requests --all show what failed. Without a url-part, --next-tab takes the first
444
+ tab anyone opens: check tab and info once it fires, since another tab opening later does not show in
445
+ watch.
446
+
447
+ Shared: every client sees the same steps, and watch new's place is shared: when another client watches
448
+ the tab too, read it with watch <n>, since each one's watch new would hide steps from the other.`,
385
449
  moreAbout: 'bodies kept, --live timing, what --changes misses, one --next-tab at a time, and what watch save leaves out',
386
450
  more: `From its first watch on, a tab keeps response bodies of up to 100 KB, so body <#> still shows one
387
451
  after the tab navigates, even when the page navigates as soon as the request answers.
@@ -465,7 +529,7 @@ circle). That changes the page, so put it back afterwards, as it was if it had a
465
529
  eval document.querySelector("#rate-3").style.outline = ""`,
466
530
  },
467
531
  screenshot: {
468
- usage: 'screenshot [<ref>] [--full] [-d <secs>] [name]',
532
+ usage: 'screenshot [<ref>] [--full] [-d <time>] [name]',
469
533
  summary: 'save a PNG of the viewport, --full page or an element',
470
534
  args: [
471
535
  ['[<ref>]', 'only that element, from a snapshot ref: screenshot e5.'],
@@ -473,7 +537,7 @@ circle). That changes the page, so put it back afterwards, as it was if it had a
473
537
  ],
474
538
  options: [
475
539
  ['--full', 'the whole page, not only the viewport; --full-page too.'],
476
- ['-d, --delay <secs>', 'counts down out loud first, up to 60, so someone can hold a hover or open a menu.'],
540
+ ['-d, --delay <time>', 'counts down out loud first, in seconds, up to 60s, so someone can hold a hover or open a menu.'],
477
541
  ['--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.'],
478
542
  ],
479
543
  detail: `Where: $PW_SCREENSHOT_DIR (a folder that exists); without it, next to the REPL's socket when it serves on one of its own, or
@@ -526,9 +590,9 @@ shows in screenshots and recordings. Selectors, snapshot and text don't see it,
526
590
  ],
527
591
  detail: `The REPL's clicks and typing don't move a pointer on screen, so a recording shows things happening
528
592
  with nothing pointing at them. With the cursor on, these commands move it smoothly to their element or
529
- point before they act: click, dblclick, hover, fill, type <selector>, press <selector>, select, check,
530
- uncheck, upload, mousemove and mouseclick. Each move takes 150 to 600ms by distance (300 to 1000ms while the tab
531
- records), and the command waits for it. Clicks (click, dblclick, check, uncheck, mouseclick, mousedown, and upload
593
+ point before they act: click, dblclick, hover, drag (to each end), fill, type <selector>, press <selector>, select, check,
594
+ uncheck, upload, mousemove, mouseclick and mousedblclick. Each move takes 150 to 600ms by distance (300 to 1000ms while the tab
595
+ records), and the command waits for it. Clicks (click, dblclick, check, uncheck, mouseclick, mousedblclick, mousedown, and upload
532
596
  when it clicks a button) show a ring where they land.
533
597
 
534
598
  Only the drawing moves: no mouse events are sent along the way, so the page gets the same input as with
@@ -541,19 +605,19 @@ the same spot, and shows in screenshots and recordings. Selectors, snapshot and
541
605
  takes no clicks, and it points at elements inside frames too.`,
542
606
  },
543
607
  record: {
544
- usage: 'record [on [file] [seconds] [options] | off [file]]',
608
+ usage: 'record [on [file] [time] [options] | off [file]]',
545
609
  summary: 'record the selected tab\'s page to a video',
546
610
  args: [
547
611
  ['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.'],
548
612
  ['[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.'],
549
- ['[seconds]', 'stops by itself after them, 1 to 3600. Default: 3600.'],
613
+ ['[time]', 'stops by itself after it, 1s or more. Default: 1 hour (3600s).'],
550
614
  ],
551
615
  optionsFor: 'record on',
552
616
  options: [
553
617
  ['--steps', 'also save clip.steps.txt next to clip.webm: when and where each command ran (Steps, below). Default: off.'],
554
618
  ['--pause=<ms>', 'a pause after each action, and each highlight drawn, so the viewer sees it. Default: 750; 0 none.'],
555
- ['--lead=<ms>', 'still page recorded before record on returns. Default: 1000; counts toward the seconds.'],
556
- ['--tail=<ms>', 'still page recorded after record off, before it stops. Default: 1000; none when it stops at its seconds.'],
619
+ ['--lead=<ms>', 'still page recorded before record on returns. Default: 1000; counts toward its time.'],
620
+ ['--tail=<ms>', 'still page recorded after record off, before it stops. Default: 1000; none when it stops at its time.'],
557
621
  ['--filename=<file>', 'the file, as playwright-cli writes it.'],
558
622
  ],
559
623
  detail: `What it records: the page only, from inside the browser, at the viewport's size in CSS pixels (set
@@ -594,7 +658,7 @@ height loses a pixel), scrollbars included; a phone's under emulate mobile is it
594
658
  is 412 wide, less sharp than the phone draws it.
595
659
 
596
660
  How it ended: record on its own, or record off when nothing is recording, says how your last recording
597
- ended (record off, its seconds, its tab closing or the REPL stopping) with what record off would have
661
+ ended (record off, its time, its tab closing or the REPL stopping) with what record off would have
598
662
  said. tab close says what became of a recording it ended. A REPL stopped with pw-repl stop cannot be
599
663
  asked: stop prints what became of each recording it ended, and of one that ended by itself that record
600
664
  has not shown since.
@@ -607,13 +671,15 @@ goes on recording. There is no --mp4 or --webm: the file's ending picks the form
607
671
  summary: 'show, set or unset the viewport size',
608
672
  args: [
609
673
  ['<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.'],
610
- ['off', 'back to the window\'s size.'],
611
- ['(none)', 'shows the size, and whether it was set or is the window\'s.'],
674
+ ['off', 'back to the window\'s page area.'],
675
+ ['(none)', 'shows the size, and whether it was set or is the window\'s page area.'],
612
676
  ],
613
677
  detail: `emulate mobile sets a phone's size instead, and each refuses while the other is on.
614
678
 
615
- The window's size here is the size of the page in it. Chromium's --window-size flag sizes the whole
679
+ The window's page area is the page's size without a viewport set. Chromium's --window-size flag sizes the whole
616
680
  window, frame included, so the page is smaller: --window-size=1280,900 gives 1280x757 in a headless one.
681
+ --launch starts its Chromium at --window-size=1280,800 (a page of 1280x657 headless), unless the flags
682
+ after -- give one; a profile it was given and has used before may keep its own last size.
617
683
  For a page of an exact size, use viewport <W>x<H>.`,
618
684
  },
619
685
  emulate: {
@@ -638,14 +704,14 @@ mouse, and sends no touch events. Dark, light and timezone apply at once, but wh
638
704
  drew with them (times, colors set by its script) changes only when it reloads.`,
639
705
  },
640
706
  wait: {
641
- usage: 'wait [text|request] <what> [--gone] [secs]',
707
+ usage: 'wait [text|request] <what> [--gone] [time]',
642
708
  summary: 'wait for an element, text, a response or a load (10s)',
643
709
  args: [
644
710
  ['<selector>', 'for a matching element to be visible.'],
645
- ['text <text>', 'for text to be visible: any part of an element\'s text, any case. Quote text that ends in a number, or the number is taken as the seconds: wait text "Showing 1".'],
711
+ ['text <text>', 'for text to be visible: any part of an element\'s text, any case. Quote text that ends in a number, or the number is taken as the time: wait text "Showing 1".'],
646
712
  ['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.'],
647
713
  ['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.'],
648
- ['[secs]', 'how long, up to 120. Default: 10.'],
714
+ ['[time]', 'how long, with no limit (give send time too: send -t). Default: 10s.'],
649
715
  ],
650
716
  options: [
651
717
  ['--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.'],
@@ -654,8 +720,8 @@ drew with them (times, colors set by its script) changes only when it reloads.`,
654
720
  something on the new view instead. A wait that times out is an error; the REPL carries on.`,
655
721
  },
656
722
  sleep: {
657
- usage: 'sleep <ms>',
658
- summary: 'wait a fixed time, in milliseconds (maximum 3600000)',
723
+ usage: 'sleep <time>',
724
+ summary: 'wait a fixed time: sleep 1500, or sleep 1.5s',
659
725
  detail: `While recording, each action is already followed by a pause (help record): a sleep adds to it.`,
660
726
  },
661
727
  requests: {
@@ -712,11 +778,11 @@ A page brought back from the back/forward cache (back, forward) reports its earl
712
778
  the time it comes back, though nothing loaded again.`,
713
779
  },
714
780
  profile: {
715
- usage: 'profile on [secs] | off | reload | save <file>',
781
+ usage: 'profile on [time] | off | reload | save <file>',
716
782
  summary: 'what slows the tab: long tasks, layouts, input, frames',
717
783
  args: [
718
- ['on', 'starts a performance trace of the selected tab, until profile off (at most 120 seconds).'],
719
- ['[secs]', 'records that long (1 to 120), then prints. Other commands wait until it ends. Over 20, give send time too: pw-repl send -t 70 profile on 60.'],
784
+ ['on', 'starts a performance trace of the selected tab, until profile off (at most 120s; give it a time for longer).'],
785
+ ['[time]', 'records that long (1s or more), then prints. Other commands wait until it ends. Over 20s, give send time too: pw-repl send -t 70s profile on 60s.'],
720
786
  ['off', 'stops it and prints the summary.'],
721
787
  ['reload', 'reloads the tab and profiles its load, until its network is idle (at most 10s after load) and half a second more. For work that starts later (a timer): profile on, reload, sleep, profile off.'],
722
788
  ['save <file>', 'writes the last profile\'s trace, the tab\'s events only, as Chrome trace JSON for DevTools\' Performance panel (Load profile) or ui.perfetto.dev. It refuses a file that exists.'],
@@ -749,7 +815,7 @@ may time out, but the profile records it all; profile off once the page answers
749
815
 
750
816
  Size: a busy page fills Chrome's trace buffer in under a minute (one that restyles many elements as it
751
817
  draws, sooner), which stops the profile there, and the summary says so. Stopping a long one takes a
752
- while, as Chrome writes out its trace: give send time, pw-repl send -t 60 profile off. If send gives up
818
+ while, as Chrome writes out its trace: give send time, pw-repl send -t 60s profile off. If send gives up
753
819
  first, the profile still ends, and profile shows it.
754
820
 
755
821
  One profile runs at a time, in a browser: Chrome traces one at a time, the whole browser's, so another
@@ -780,12 +846,12 @@ the trace even when its page did nothing. The trace is kept in a temporary folde
780
846
  profile, or until the REPL stops.`,
781
847
  },
782
848
  capture: {
783
- usage: 'capture on [requests|console] [secs] | off',
849
+ usage: 'capture on [requests|console] [time] | off',
784
850
  summary: 'record requests and console together, in time order',
785
851
  args: [
786
852
  ['on', 'records both until capture off, which prints them.'],
787
853
  ['requests | console', 'records only one.'],
788
- ['[secs]', 'records that long (1 to 3600), then prints. Other commands wait until it ends.'],
854
+ ['[time]', 'records that long (1s or more), then prints. Other commands wait until it ends.'],
789
855
  ['(none)', 'says whether one is running, or shows the last one.'],
790
856
  ],
791
857
  detail: `A timed capture suits recording what someone does in the browser; around your own commands, use
@@ -798,7 +864,7 @@ and console it keeps more than the last 200, and lists both together.`,
798
864
  args: [
799
865
  ['<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.'],
800
866
  ['<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.'],
801
- ['<glob> delay <secs>', 'holds it for up to 120 seconds, then lets it through.'],
867
+ ['<glob> delay <time>', 'holds it that long, then lets it through.'],
802
868
  ['<glob> abort', 'fails it as if the connection broke.'],
803
869
  ['off <glob> | --all', 'removes one route, or all.'],
804
870
  ['(none)', 'lists the selected tab\'s routes.'],
@@ -824,21 +890,28 @@ replaces it. A glob may be quoted. Example: route **/api/cart patch {"total": 0}
824
890
  ['on', 'restores it.'],
825
891
  ],
826
892
  detail: `Per tab; lasts until network on or the REPL exits. Routes still answer while it is off or slow. To slow
827
- one API, use route <glob> delay <secs>. Stopping a service is not the same as network off: a dev proxy
893
+ one API, use route <glob> delay <time>. Stopping a service is not the same as network off: a dev proxy
828
894
  in front of it usually holds the request open, so the page spins instead of failing.`,
829
895
  },
830
896
  eval: {
831
- usage: 'eval [--all] <JavaScript>',
897
+ usage: 'eval [--all] [--timeout=<time>] <JavaScript>',
832
898
  summary: 'evaluate JavaScript in the selected tab and print the result',
833
899
  args: [
834
900
  ['<JavaScript>', 'the rest of the line. Promises are awaited, await works at the top level, and a function (() => document.title) is called.'],
835
- ['<function> <ref>', 'calls it with that element: eval "el => el.textContent" e5.'],
901
+ ['<function> <ref>', 'calls it with that element: eval "el => el.textContent" e5. Quoted, its \\n is a newline and its \\" a quote: write \\\\n for a JS string\'s \\n.'],
836
902
  ],
837
903
  options: [
904
+ ['--file=<path>', 'the JavaScript is the file\'s text: anything the line takes, over many lines and with // comments. With a ref or a selector after the path, it is a function called with that element (the first match). Over send, a relative path is from your shell\'s current folder.'],
905
+ ['--timeout=<time>', 'how long the page has to answer, before --file. Default: 15s. Past 20s, give send time too, or it gives up first and cancels the eval: pw-repl send -t 70s eval --timeout=60s ...'],
838
906
  ['--all', 'the whole result, not capped.'],
839
907
  ],
840
908
  detail: `eval fetch(...) sends a new request, which can change state on the server; it cannot read one that
841
- already happened (requests lists those).`,
909
+ already happened (requests lists those).
910
+
911
+ Long work (--timeout): every other command waits while an eval runs, the other clients' too. For work of
912
+ minutes, or in a browser others are using, start it without awaiting it, keep its result on window, and
913
+ read it back: eval "window.__done = null; run().then(r => window.__done = r); 'started'", then eval
914
+ window.__done until it is set.`,
842
915
  },
843
916
  cdp: {
844
917
  usage: 'cdp [--all] <method> <JSON object>',
@@ -852,6 +925,20 @@ already happened (requests lists those).`,
852
925
  ],
853
926
  detail: `The session is detached afterwards, so subscriptions and settings do not persist. Browser.close and
854
927
  Target.closeTarget are refused, and so is Page.handleJavaScriptDialog: dialog answers one.`,
928
+ },
929
+ clipboard: {
930
+ usage: 'clipboard [--all] [--shared-clipboard]',
931
+ summary: 'print the text on the clipboard, as a Copy button left it',
932
+ options: [
933
+ ['--shared-clipboard', 'reads it in a browser with a window too, whose clipboard is the computer\'s (below).'],
934
+ ['--all', 'the whole text, not capped.'],
935
+ ],
936
+ detail: `It reads through the selected tab, which must be a secure page (https, or http on localhost), granting
937
+ it clipboard-read only while it reads. Check a Copy button: click it, then clipboard.
938
+
939
+ A headless browser, as --launch starts, has a clipboard of its own, holding only what its pages copied.
940
+ One with a window reads the computer's: whatever its user last copied, a password too. There it refuses
941
+ without --shared-clipboard; ask the user before giving it.`,
855
942
  },
856
943
  cookies: {
857
944
  usage: 'cookies [--all]',
@@ -908,7 +995,11 @@ at the browser (e.g. headless), it is the only way to answer one.`,
908
995
  quit: {
909
996
  usage: 'quit',
910
997
  summary: 'disconnect, leaving Chromium running (one --launch started stops too)',
911
- detail: `Runs immediately, even while another command is waiting. Only available at the prompt.`,
998
+ detail: `Runs immediately, even while another command is waiting. Only available at the prompt.
999
+
1000
+ As it stops (quit, or pw-repl stop), it says, for each client, how many of the files it wrote are still
1001
+ there, how large, and in which folders: Files left by cart-bug: 3 files, 48 MB (2 screenshots, 1 video)
1002
+ in /tmp/. pw-repl send -c <name> close lists a client's own in full.`,
912
1003
  },
913
1004
  };
914
1005
 
@@ -990,9 +1081,11 @@ Different on purpose, since the browser may be someone else's too:
990
1081
  if (Object.hasOwn(TOPICS, topicOrCommand)) {
991
1082
  const topic = TOPICS[topicOrCommand];
992
1083
  const text = [topic.intro, '', ...commandLines(topic.commands)].join('\n');
993
- // A topic can share its name with a command (network); both are shown.
994
- return Object.hasOwn(COMMANDS, topicOrCommand) ? `${text}\n\n${renderCommand(topicOrCommand)}` : text;
1084
+ // A topic can share its name with a command (network): the topic, and where the command's help is.
1085
+ return Object.hasOwn(COMMANDS, topicOrCommand) ? `${text}\n\n${topicOrCommand} is also a command: help ${topicOrCommand} --command` : text;
995
1086
  }
1087
+ const asCommand = /^(\S+)\s+--command$/.exec(topicOrCommand);
1088
+ if (asCommand && Object.hasOwn(COMMANDS, asCommand[1])) return renderCommand(asCommand[1]);
996
1089
  if (Object.hasOwn(COMMANDS, topicOrCommand)) return renderCommand(topicOrCommand);
997
1090
  // Not a topic: the mouse commands are in interact, with the others that act on the page.
998
1091
  if (topicOrCommand === 'mouse') {
@@ -1020,4 +1113,4 @@ function notFound(topic) {
1020
1113
  return `No help for ${topic}. Topics: ${Object.keys(TOPICS).join(', ')}`;
1021
1114
  }
1022
1115
 
1023
- module.exports = { render, notFound, TOPICS, COMMANDS };
1116
+ module.exports = { render, notFound, TOPICS, COMMANDS, TASKS, MODES };