pw-repl 0.12.8 → 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/AGENTS.md +10 -6
- package/README.md +2 -2
- package/bin/pw-repl.js +12 -10
- package/lib/background.js +2 -2
- package/lib/capture.js +11 -12
- package/lib/cli-names.js +1 -1
- package/lib/commands.js +3 -0
- package/lib/help.js +53 -27
- package/lib/inspect.js +82 -37
- package/lib/output.js +10 -3
- package/lib/profile.js +16 -14
- package/lib/record.js +17 -13
- package/lib/routes.js +8 -9
- package/lib/runner.js +28 -9
- package/lib/send.js +16 -6
- package/lib/server.js +4 -1
- package/lib/state.js +26 -9
- package/lib/toast.js +1 -6
- package/lib/util.js +25 -1
- package/lib/watch.js +4 -2
- package/lib/written.js +67 -0
- package/package.json +1 -1
- package/skill/SKILL.md +12 -4
package/AGENTS.md
CHANGED
|
@@ -23,7 +23,7 @@ If you hit a limitation or write a workaround, consider adding the capability to
|
|
|
23
23
|
`lib/routes.js`, `lib/network.js`, `lib/emulation.js` (and viewport), `lib/capture.js`, `lib/profile.js` (performance traces;
|
|
24
24
|
`lib/trace.js` reads and sums them up), `lib/record.js`
|
|
25
25
|
(video, through ffmpeg, and the steps file saved with it), `lib/dialogs.js` and `lib/modes.js`. Beneath them, `lib/tabstate.js` is the one record kept per tab, `lib/cdp.js` the CDP
|
|
26
|
-
session kept per tab,
|
|
26
|
+
session kept per tab, `lib/util.js` small shared helpers, and `lib/written.js` the files written and for whom. No facility requires `lib/modes.js` or
|
|
27
27
|
`lib/commands.js`, and a unit test fails on a require cycle, which CommonJS would not report.
|
|
28
28
|
- `bin/pw-repl.js` is the command line; `lib/start.js` connects and runs the prompt; `lib/runner.js`
|
|
29
29
|
runs commands one at a time (`quit` and `dialog` skip its queue); `lib/server.js` is the command
|
|
@@ -39,7 +39,7 @@ If you hit a limitation or write a workaround, consider adding the capability to
|
|
|
39
39
|
accepts theirs too (`lib/cli-names.js`, `help playwright-cli`). Deviate only where it is necessary, as
|
|
40
40
|
where sharing a browser with a user needs it, and say why in the help.
|
|
41
41
|
- Comment the *why* when it isn't obvious from the code.
|
|
42
|
-
- `npm test` runs the suite (
|
|
42
|
+
- `npm test` runs the suite (about two and a half minutes): a private headless Chromium, a local test site
|
|
43
43
|
(`test/harness.js`), and the real REPL with its server. Nothing is mocked, and the shared browser is
|
|
44
44
|
never touched. It finds Chromium through `PW_TEST_CHROME`, or where `--launch` looks (Playwright's
|
|
45
45
|
browsers, then the `PATH`), and skips the browser tests if there is none. Add a test with each new
|
|
@@ -105,10 +105,14 @@ Commits follow [Conventional Commits](https://www.conventionalcommits.org/): `fe
|
|
|
105
105
|
1.0, a change that breaks how the REPL is used is a minor bump (0.2.0 to 0.3.0), anything else a patch.
|
|
106
106
|
|
|
107
107
|
1. `npm test` passes and the working tree is clean.
|
|
108
|
-
2. `npm outdated` and `npm audit` show nothing that needs doing first.
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
108
|
+
2. `npm outdated` and `npm audit` show nothing that needs doing first. Dependencies are always the newest
|
|
109
|
+
stable release, never pinned back (user ruling). When the newest breaks a test, fix it in our code;
|
|
110
|
+
only a regression of the dependency's own, with the fix already in its next release, keeps the version
|
|
111
|
+
before it, until that release ships. playwright-core stays at 1.63 for that reason: on 1.64.1 a click
|
|
112
|
+
on a link to a 204 hangs at "waiting for scheduled navigations to finish" until its timeout, in plain
|
|
113
|
+
Playwright too (chromium.launch, a page with a link to a 204, page.click on it); 1.65's alphas fix it.
|
|
114
|
+
The test "waits for the page to load, counting a navigation that just began" shows it: move up once it
|
|
115
|
+
passes on the newest release.
|
|
112
116
|
3. `npm version <patch|minor|major> -m "chore: release %s"` sets the version in `package.json` and
|
|
113
117
|
`package-lock.json`, commits it as `chore: release X.Y.Z`, and tags that commit `vX.Y.Z`.
|
|
114
118
|
4. `git push --follow-tags` pushes the commits and the tag.
|
package/README.md
CHANGED
|
@@ -83,7 +83,7 @@ With `npx`, use `npx pw-repl@latest` the same way every time, so every command r
|
|
|
83
83
|
whole tab's network.
|
|
84
84
|
- **Emulate:** a phone, dark mode, a locale, a timezone, a viewport size.
|
|
85
85
|
- **Run code:** `eval` any JavaScript in the page, a line or a file of it, or send a raw CDP command with
|
|
86
|
-
`cdp
|
|
86
|
+
`cdp`; read what a Copy button put on the clipboard (`clipboard`).
|
|
87
87
|
- **Wait:** for an element, some text, a response or a page load.
|
|
88
88
|
- **Record:** what someone does in a tab (`watch`), a timeline of requests and console (`capture`), or a
|
|
89
89
|
video of the tab (`record`), with a virtual pointer (`cursor`), captions (`toast`) and highlights
|
|
@@ -154,7 +154,7 @@ example the Docker bridge gateway, `ip route | awk '/default/ {print $3}'`).
|
|
|
154
154
|
| record requests and console together | `capture on`, then `capture off` |
|
|
155
155
|
| find what makes the page slow | `profile on`, then `profile off`; `profile reload` for a load |
|
|
156
156
|
| fake a backend response | `route <glob> <status> <json>`, `route <glob> abort` |
|
|
157
|
-
| change or slow an API response | `route <glob> patch <json>`, `route <glob> delay <
|
|
157
|
+
| change or slow an API response | `route <glob> patch <json>`, `route <glob> delay <time>` |
|
|
158
158
|
| cut or slow the whole network | `network off`, `network slow`, then `network on` |
|
|
159
159
|
| record a video of the page | `record on`, then `record off` (`help video` for a take) |
|
|
160
160
|
| show a pointer where the agent acts | `cursor on`, then `cursor off` |
|
package/bin/pw-repl.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// The one command: pw-repl run | serve | attach | stop | send | where | help | skill.
|
|
3
3
|
|
|
4
4
|
const { looksLikeEndpoint } = require('../lib/client');
|
|
5
|
+
const { longDuration } = require('../lib/util');
|
|
5
6
|
|
|
6
7
|
const USAGE = `Usage:
|
|
7
8
|
pw-repl run [--launch [--headed]] [start-url] [-- <chromium flags>]
|
|
@@ -19,7 +20,7 @@ const USAGE = `Usage:
|
|
|
19
20
|
stop a background REPL, and say what became of each recording it ended; its socket is removed, its
|
|
20
21
|
log stays
|
|
21
22
|
|
|
22
|
-
pw-repl send [-e endpoint | -s session] [-c client] [-t
|
|
23
|
+
pw-repl send [-e endpoint | -s session] [-c client] [-t time] [--no-vars]
|
|
23
24
|
<command...> | --file <file>
|
|
24
25
|
run one command in a running REPL and print its output; --file runs a file of them, one per line
|
|
25
26
|
|
|
@@ -56,12 +57,14 @@ serves on that same one: $PW_ENDPOINT, then $PW_SOCKET, then the default.
|
|
|
56
57
|
send -c <client> (or $PW_CLIENT) sends as a client of the REPL's with a selected tab of its own, so
|
|
57
58
|
several agents can share one REPL; pw-repl help session has the rest.
|
|
58
59
|
|
|
59
|
-
send's options can also follow the command, as in pw-repl send open <url> -t
|
|
60
|
-
command, every word is the command's, -t
|
|
60
|
+
send's options can also follow the command, as in pw-repl send open <url> -t 30s; after a -- before the
|
|
61
|
+
command, every word is the command's, -t 30s included.
|
|
61
62
|
|
|
62
63
|
Exit status: 0 ok, 1 command error, 2 completion not confirmed, 64 usage or unreachable. -t is how long
|
|
63
|
-
send waits for the answer (default 20s); it does not
|
|
64
|
-
action waits for its element.
|
|
64
|
+
send waits for the answer, in milliseconds or with s for seconds (default 20s: 20000); it does not
|
|
65
|
+
lengthen a command's own limits, such as the 5s an action waits for its element. Over the server, giving
|
|
66
|
+
up cancels the command: one still queued is never run, and one running is no longer waited for, though
|
|
67
|
+
what it began in the page may go on.
|
|
65
68
|
|
|
66
69
|
--file <file> runs each line of a file as a command, in order, as if typed at the REPL's prompt: quoted
|
|
67
70
|
as the prompt reads it (no shell), with a file it records, uploads or saves relative to this folder, as
|
|
@@ -105,10 +108,10 @@ function shellLine(words) {
|
|
|
105
108
|
}
|
|
106
109
|
|
|
107
110
|
// -e, -s, -c and -t, then the command words (unquoted words are one command), and send's options again
|
|
108
|
-
// at the end, as an agent types them (send open <url> -t
|
|
111
|
+
// at the end, as an agent types them (send open <url> -t 30s); after --, every word is the command.
|
|
109
112
|
function parseSendArgs(args, subcommand) {
|
|
110
113
|
const allowCommand = subcommand === 'send';
|
|
111
|
-
const options = { endpoint: null, session: null, client: null, timeout:
|
|
114
|
+
const options = { endpoint: null, session: null, client: null, timeout: 20000, command: '', file: null };
|
|
112
115
|
const given = new Set();
|
|
113
116
|
const take = (arg, value, after) => {
|
|
114
117
|
if (value === undefined) usage(`${arg} needs a value`, subcommand);
|
|
@@ -120,8 +123,7 @@ function parseSendArgs(args, subcommand) {
|
|
|
120
123
|
if (arg === '-s') options.session = value;
|
|
121
124
|
if (arg === '-c') options.client = value;
|
|
122
125
|
if (arg === '-t') {
|
|
123
|
-
|
|
124
|
-
options.timeout = Number(value);
|
|
126
|
+
try { options.timeout = longDuration(value, '-t'); } catch (error) { usage(error.message, subcommand); }
|
|
125
127
|
}
|
|
126
128
|
};
|
|
127
129
|
let i = 0;
|
|
@@ -296,7 +298,7 @@ async function main() {
|
|
|
296
298
|
case '--help':
|
|
297
299
|
return console.log(`${USAGE}\n\n${REPL_HELP}`);
|
|
298
300
|
default: {
|
|
299
|
-
// An option before the subcommand (pw-repl -t
|
|
301
|
+
// An option before the subcommand (pw-repl -t 30s send ...): say where it goes, with the line fixed.
|
|
300
302
|
if (subcommand.startsWith('-')) {
|
|
301
303
|
const all = [subcommand, ...args];
|
|
302
304
|
const named = all.find(word => SUBCOMMANDS.includes(word));
|
package/lib/background.js
CHANGED
|
@@ -5,7 +5,7 @@ const path = require('path');
|
|
|
5
5
|
const readline = require('readline');
|
|
6
6
|
const { spawn } = require('child_process');
|
|
7
7
|
const client = require('./client');
|
|
8
|
-
const { STOPPED_RECORDING, ENDED_UNSEEN } = require('./util');
|
|
8
|
+
const { STOPPED_RECORDING, ENDED_UNSEEN, FILES_LEFT } = require('./util');
|
|
9
9
|
|
|
10
10
|
const BIN = path.join(__dirname, '..', 'bin', 'pw-repl.js');
|
|
11
11
|
const START_TIMEOUT = 20000;
|
|
@@ -162,7 +162,7 @@ async function stop(options) {
|
|
|
162
162
|
fs.closeSync(fd);
|
|
163
163
|
logged = buffer.toString('utf8');
|
|
164
164
|
} catch {}
|
|
165
|
-
for (const line of logged.split('\n')) if (line.startsWith(STOPPED_RECORDING) || line.startsWith(ENDED_UNSEEN)) console.log(line);
|
|
165
|
+
for (const line of logged.split('\n')) if (line.startsWith(STOPPED_RECORDING) || line.startsWith(ENDED_UNSEEN) || line.startsWith(FILES_LEFT)) console.log(line);
|
|
166
166
|
return 0;
|
|
167
167
|
}
|
|
168
168
|
|
package/lib/capture.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
const { state, onShutdown } = require('./state');
|
|
3
3
|
const out = require('./output');
|
|
4
4
|
const { printOutput } = out;
|
|
5
|
-
const { numbers, MAX_EVENT_TEXT, NO_TAB, clock, hints } = require('./util');
|
|
5
|
+
const { numbers, MAX_EVENT_TEXT, NO_TAB, clock, hints, TIME, longDuration, durationText } = require('./util');
|
|
6
6
|
const { consoleText, requestEntries } = require('./requestlog');
|
|
7
7
|
|
|
8
8
|
const MAX_CAPTURE_EVENTS = 10000;
|
|
@@ -17,16 +17,15 @@ function currentCapture() {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
// Records the selected tab's requests and console messages together, in time
|
|
20
|
-
// order, until capture off, or for a set
|
|
20
|
+
// order, until capture off, or for a set time.
|
|
21
21
|
async function startCapture(tokens) {
|
|
22
22
|
if (cap) { out.log('Already capturing; capture off stops it.'); return; }
|
|
23
23
|
if (!state.page || state.page.isClosed()) throw new Error(NO_TAB);
|
|
24
|
-
const usage = 'Usage: capture on [requests|console] [
|
|
24
|
+
const usage = 'Usage: capture on [requests|console] [time] (ms, or 30s)';
|
|
25
25
|
const kinds = tokens.filter(t => t === 'requests' || t === 'console');
|
|
26
|
-
const
|
|
27
|
-
if (kinds.length > 1 ||
|
|
28
|
-
const
|
|
29
|
-
if (seconds !== null && (seconds < 1 || seconds > 3600)) throw new Error(usage);
|
|
26
|
+
const times = tokens.filter(t => TIME.test(t));
|
|
27
|
+
if (kinds.length > 1 || times.length > 1 || kinds.length + times.length !== tokens.length) throw new Error(usage);
|
|
28
|
+
const ms = times.length ? longDuration(times[0], 'capture on\'s time') : null;
|
|
30
29
|
const label = kinds[0] || 'requests and console';
|
|
31
30
|
const startedAt = Date.now();
|
|
32
31
|
const events = [];
|
|
@@ -61,11 +60,11 @@ async function startCapture(tokens) {
|
|
|
61
60
|
get shortened() { return shortened; },
|
|
62
61
|
};
|
|
63
62
|
cap = capture;
|
|
64
|
-
if (
|
|
65
|
-
out.log(`Capturing ${label} for ${
|
|
63
|
+
if (ms) {
|
|
64
|
+
out.log(`Capturing ${label} for ${durationText(ms)}...`);
|
|
66
65
|
// Also ends early if the capture does, e.g. because its tab closed.
|
|
67
66
|
await new Promise(resolve => {
|
|
68
|
-
const timer = setTimeout(resolve,
|
|
67
|
+
const timer = setTimeout(resolve, ms);
|
|
69
68
|
capture.wake = () => { clearTimeout(timer); resolve(); };
|
|
70
69
|
});
|
|
71
70
|
if (cap === capture) stopCapture();
|
|
@@ -120,7 +119,7 @@ const commands = {
|
|
|
120
119
|
const [sub, ...rest] = (args || '').trim().split(/\s+/).filter(Boolean);
|
|
121
120
|
if (sub === 'on') return startCapture(rest);
|
|
122
121
|
if (sub === 'off' && !rest.length) return stopCapture(all);
|
|
123
|
-
if (sub) throw new Error('Usage: capture on [requests|console] [
|
|
122
|
+
if (sub) throw new Error('Usage: capture on [requests|console] [time] | capture off');
|
|
124
123
|
if (cap) {
|
|
125
124
|
out.log(`Capturing ${cap.label} on ${cap.page.url()} since ${clock(cap.startedAt)} (${cap.events.length} so far)`);
|
|
126
125
|
out.log(hints([['capture off', 'stop and print what it recorded']]));
|
|
@@ -132,7 +131,7 @@ const commands = {
|
|
|
132
131
|
} else {
|
|
133
132
|
out.log('Not capturing.');
|
|
134
133
|
}
|
|
135
|
-
out.log(hints([['capture on [requests|console] [
|
|
134
|
+
out.log(hints([['capture on [requests|console] [time]', 'record the selected tab\'s requests and console messages in time order']]));
|
|
136
135
|
},
|
|
137
136
|
};
|
|
138
137
|
|
package/lib/cli-names.js
CHANGED
|
@@ -15,7 +15,7 @@ const NAMES = {
|
|
|
15
15
|
'tab-close': [a => (a ? null : 'tab close'), 'tab close [url-part], or tab <index> then tab close'],
|
|
16
16
|
open: [a => `tab new ${a}`.trim(), 'tab new [url] (goto <url> navigates); --mobile, --device=<name>: emulate mobile'],
|
|
17
17
|
// It ends playwright-cli's own browser; this one is shared, so tabs stay open.
|
|
18
|
-
close: [() => 'close', 'modes off --mine
|
|
18
|
+
close: [() => 'close', 'modes off --mine; tabs stay open; lists the files you wrote'],
|
|
19
19
|
'go-back': [() => 'back', 'back'],
|
|
20
20
|
'go-forward': [() => 'forward', 'forward'],
|
|
21
21
|
find: [a => `snapshot --grep ${a}`, 'snapshot --grep <text>'],
|
package/lib/commands.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// The REPL's commands, gathered from each facility's module, and what the prompt and the runner
|
|
2
2
|
// need besides: playwright-cli's own commands, the hooks every page gets, and tab completion.
|
|
3
3
|
const { state, shutdown } = require('./state');
|
|
4
|
+
const { ownFilesText } = require('./written');
|
|
4
5
|
const out = require('./output');
|
|
5
6
|
const HELP = require('./help');
|
|
6
7
|
const { tabState } = require('./tabstate');
|
|
@@ -80,6 +81,8 @@ const cliCommands = {
|
|
|
80
81
|
await allModesOff(true);
|
|
81
82
|
const own = state.browser.contexts().flatMap(c => c.pages()).filter(p => tabState(p).owner === state.client);
|
|
82
83
|
if (own.length) out.log(`Tabs you opened are still open: ${own.map(p => p.url()).join(', ')}; tab close <url-part> closes one, or select it and tab close.`);
|
|
84
|
+
const files = ownFilesText(state.client);
|
|
85
|
+
if (files) out.log(files);
|
|
83
86
|
},
|
|
84
87
|
};
|
|
85
88
|
|
package/lib/help.js
CHANGED
|
@@ -41,7 +41,7 @@ const TASKS = [
|
|
|
41
41
|
['record requests and console together', 'capture on, then capture off'],
|
|
42
42
|
['find what makes the page slow', 'profile on, then profile off; profile reload for a load'],
|
|
43
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 <
|
|
44
|
+
['change or slow an API response', 'route <glob> patch <json>, route <glob> delay <time>'],
|
|
45
45
|
['cut or slow the whole network', 'network off, network slow, then network on'],
|
|
46
46
|
['record a video of the page', 'record on, then record off (help video for a take)'],
|
|
47
47
|
['show a pointer where the agent acts', 'cursor on, then cursor off'],
|
|
@@ -62,6 +62,8 @@ ${TASKS.map(([task, commands]) => ` ${task.padEnd(38)} ${commands}`).join('\n'
|
|
|
62
62
|
Modes (${MODES}) stay on until turned off; the prompt shows the selected tab's: (watch routes:1) pw>.
|
|
63
63
|
Each of them on its own, and modes, shows its state and what you can run next.
|
|
64
64
|
|
|
65
|
+
Times are in milliseconds, as in Playwright, or in seconds with an s: wait .done 30000, or wait .done 30s.
|
|
66
|
+
|
|
65
67
|
Topics (help <topic>):
|
|
66
68
|
tabs open, select, close, navigate network requests, bodies, console, fakes, network off
|
|
67
69
|
interact click, fill, type, press, select devtools eval, CDP commands, cookie and storage names
|
|
@@ -120,7 +122,7 @@ box to point at something, and toast <text> a line of text saying what is happen
|
|
|
120
122
|
the viewer needs to read it. After a step that loads a page, wait for text only the result shows (wait
|
|
121
123
|
text ...), not a heading the page always has, and not wait load: a single-page app loads nothing.
|
|
122
124
|
|
|
123
|
-
record on --steps also saves when and where each step ran, in the video
|
|
125
|
+
record on --steps also saves when and where each step ran, in seconds into the video and its pixels, for
|
|
124
126
|
editing it afterwards.
|
|
125
127
|
|
|
126
128
|
The user's own screen, with their real pointer, takes a screen recorder they run (OBS, ffmpeg -f
|
|
@@ -135,7 +137,7 @@ clean browser of its own (here, its video-start is record on).`,
|
|
|
135
137
|
devtools: {
|
|
136
138
|
intro: `Cookie and storage listings omit values. eval, cdp, html, screenshots, recordings and URLs can
|
|
137
139
|
still show sensitive data (cdp Network.getCookies shows cookie values).`,
|
|
138
|
-
commands: ['eval', 'cdp', 'cookies', 'storage'],
|
|
140
|
+
commands: ['eval', 'cdp', 'clipboard', 'cookies', 'storage'],
|
|
139
141
|
},
|
|
140
142
|
session: {
|
|
141
143
|
intro: `Server: pw-repl serve also takes commands on /tmp/pw-repl.sock; they show here as [server] lines.
|
|
@@ -372,7 +374,7 @@ all.`,
|
|
|
372
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.'],
|
|
373
375
|
['--regex <pattern>', '--grep by a regular expression: snapshot --regex "Total: \\d+".'],
|
|
374
376
|
],
|
|
375
|
-
detail: `Refs: a ref (e3,
|
|
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
|
|
376
378
|
to the tab whose snapshot gave it, and can change when the page changes (e102 may become f4e98): take a
|
|
377
379
|
new snapshot after it does.
|
|
378
380
|
|
|
@@ -527,7 +529,7 @@ circle). That changes the page, so put it back afterwards, as it was if it had a
|
|
|
527
529
|
eval document.querySelector("#rate-3").style.outline = ""`,
|
|
528
530
|
},
|
|
529
531
|
screenshot: {
|
|
530
|
-
usage: 'screenshot [<ref>] [--full] [-d <
|
|
532
|
+
usage: 'screenshot [<ref>] [--full] [-d <time>] [name]',
|
|
531
533
|
summary: 'save a PNG of the viewport, --full page or an element',
|
|
532
534
|
args: [
|
|
533
535
|
['[<ref>]', 'only that element, from a snapshot ref: screenshot e5.'],
|
|
@@ -535,7 +537,7 @@ circle). That changes the page, so put it back afterwards, as it was if it had a
|
|
|
535
537
|
],
|
|
536
538
|
options: [
|
|
537
539
|
['--full', 'the whole page, not only the viewport; --full-page too.'],
|
|
538
|
-
['-d, --delay <
|
|
540
|
+
['-d, --delay <time>', 'counts down out loud first, in seconds, up to 60s, so someone can hold a hover or open a menu.'],
|
|
539
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.'],
|
|
540
542
|
],
|
|
541
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
|
|
@@ -603,19 +605,19 @@ the same spot, and shows in screenshots and recordings. Selectors, snapshot and
|
|
|
603
605
|
takes no clicks, and it points at elements inside frames too.`,
|
|
604
606
|
},
|
|
605
607
|
record: {
|
|
606
|
-
usage: 'record [on [file] [
|
|
608
|
+
usage: 'record [on [file] [time] [options] | off [file]]',
|
|
607
609
|
summary: 'record the selected tab\'s page to a video',
|
|
608
610
|
args: [
|
|
609
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.'],
|
|
610
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.'],
|
|
611
|
-
['[
|
|
613
|
+
['[time]', 'stops by itself after it, 1s or more. Default: 1 hour (3600s).'],
|
|
612
614
|
],
|
|
613
615
|
optionsFor: 'record on',
|
|
614
616
|
options: [
|
|
615
617
|
['--steps', 'also save clip.steps.txt next to clip.webm: when and where each command ran (Steps, below). Default: off.'],
|
|
616
618
|
['--pause=<ms>', 'a pause after each action, and each highlight drawn, so the viewer sees it. Default: 750; 0 none.'],
|
|
617
|
-
['--lead=<ms>', 'still page recorded before record on returns. Default: 1000; counts toward
|
|
618
|
-
['--tail=<ms>', 'still page recorded after record off, before it stops. Default: 1000; none when it stops at its
|
|
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.'],
|
|
619
621
|
['--filename=<file>', 'the file, as playwright-cli writes it.'],
|
|
620
622
|
],
|
|
621
623
|
detail: `What it records: the page only, from inside the browser, at the viewport's size in CSS pixels (set
|
|
@@ -656,7 +658,7 @@ height loses a pixel), scrollbars included; a phone's under emulate mobile is it
|
|
|
656
658
|
is 412 wide, less sharp than the phone draws it.
|
|
657
659
|
|
|
658
660
|
How it ended: record on its own, or record off when nothing is recording, says how your last recording
|
|
659
|
-
ended (record off, its
|
|
661
|
+
ended (record off, its time, its tab closing or the REPL stopping) with what record off would have
|
|
660
662
|
said. tab close says what became of a recording it ended. A REPL stopped with pw-repl stop cannot be
|
|
661
663
|
asked: stop prints what became of each recording it ended, and of one that ended by itself that record
|
|
662
664
|
has not shown since.
|
|
@@ -702,14 +704,14 @@ mouse, and sends no touch events. Dark, light and timezone apply at once, but wh
|
|
|
702
704
|
drew with them (times, colors set by its script) changes only when it reloads.`,
|
|
703
705
|
},
|
|
704
706
|
wait: {
|
|
705
|
-
usage: 'wait [text|request] <what> [--gone] [
|
|
707
|
+
usage: 'wait [text|request] <what> [--gone] [time]',
|
|
706
708
|
summary: 'wait for an element, text, a response or a load (10s)',
|
|
707
709
|
args: [
|
|
708
710
|
['<selector>', 'for a matching element to be visible.'],
|
|
709
|
-
['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
|
|
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".'],
|
|
710
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.'],
|
|
711
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.'],
|
|
712
|
-
['[
|
|
714
|
+
['[time]', 'how long, with no limit (give send time too: send -t). Default: 10s.'],
|
|
713
715
|
],
|
|
714
716
|
options: [
|
|
715
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.'],
|
|
@@ -718,8 +720,8 @@ drew with them (times, colors set by its script) changes only when it reloads.`,
|
|
|
718
720
|
something on the new view instead. A wait that times out is an error; the REPL carries on.`,
|
|
719
721
|
},
|
|
720
722
|
sleep: {
|
|
721
|
-
usage: 'sleep <
|
|
722
|
-
summary: 'wait a fixed time,
|
|
723
|
+
usage: 'sleep <time>',
|
|
724
|
+
summary: 'wait a fixed time: sleep 1500, or sleep 1.5s',
|
|
723
725
|
detail: `While recording, each action is already followed by a pause (help record): a sleep adds to it.`,
|
|
724
726
|
},
|
|
725
727
|
requests: {
|
|
@@ -776,11 +778,11 @@ A page brought back from the back/forward cache (back, forward) reports its earl
|
|
|
776
778
|
the time it comes back, though nothing loaded again.`,
|
|
777
779
|
},
|
|
778
780
|
profile: {
|
|
779
|
-
usage: 'profile on [
|
|
781
|
+
usage: 'profile on [time] | off | reload | save <file>',
|
|
780
782
|
summary: 'what slows the tab: long tasks, layouts, input, frames',
|
|
781
783
|
args: [
|
|
782
|
-
['on', 'starts a performance trace of the selected tab, until profile off (at most
|
|
783
|
-
['[
|
|
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.'],
|
|
784
786
|
['off', 'stops it and prints the summary.'],
|
|
785
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.'],
|
|
786
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.'],
|
|
@@ -813,7 +815,7 @@ may time out, but the profile records it all; profile off once the page answers
|
|
|
813
815
|
|
|
814
816
|
Size: a busy page fills Chrome's trace buffer in under a minute (one that restyles many elements as it
|
|
815
817
|
draws, sooner), which stops the profile there, and the summary says so. Stopping a long one takes a
|
|
816
|
-
while, as Chrome writes out its trace: give send time, pw-repl send -t
|
|
818
|
+
while, as Chrome writes out its trace: give send time, pw-repl send -t 60s profile off. If send gives up
|
|
817
819
|
first, the profile still ends, and profile shows it.
|
|
818
820
|
|
|
819
821
|
One profile runs at a time, in a browser: Chrome traces one at a time, the whole browser's, so another
|
|
@@ -844,12 +846,12 @@ the trace even when its page did nothing. The trace is kept in a temporary folde
|
|
|
844
846
|
profile, or until the REPL stops.`,
|
|
845
847
|
},
|
|
846
848
|
capture: {
|
|
847
|
-
usage: 'capture on [requests|console] [
|
|
849
|
+
usage: 'capture on [requests|console] [time] | off',
|
|
848
850
|
summary: 'record requests and console together, in time order',
|
|
849
851
|
args: [
|
|
850
852
|
['on', 'records both until capture off, which prints them.'],
|
|
851
853
|
['requests | console', 'records only one.'],
|
|
852
|
-
['[
|
|
854
|
+
['[time]', 'records that long (1s or more), then prints. Other commands wait until it ends.'],
|
|
853
855
|
['(none)', 'says whether one is running, or shows the last one.'],
|
|
854
856
|
],
|
|
855
857
|
detail: `A timed capture suits recording what someone does in the browser; around your own commands, use
|
|
@@ -862,7 +864,7 @@ and console it keeps more than the last 200, and lists both together.`,
|
|
|
862
864
|
args: [
|
|
863
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.'],
|
|
864
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.'],
|
|
865
|
-
['<glob> delay <
|
|
867
|
+
['<glob> delay <time>', 'holds it that long, then lets it through.'],
|
|
866
868
|
['<glob> abort', 'fails it as if the connection broke.'],
|
|
867
869
|
['off <glob> | --all', 'removes one route, or all.'],
|
|
868
870
|
['(none)', 'lists the selected tab\'s routes.'],
|
|
@@ -888,11 +890,11 @@ replaces it. A glob may be quoted. Example: route **/api/cart patch {"total": 0}
|
|
|
888
890
|
['on', 'restores it.'],
|
|
889
891
|
],
|
|
890
892
|
detail: `Per tab; lasts until network on or the REPL exits. Routes still answer while it is off or slow. To slow
|
|
891
|
-
one API, use route <glob> delay <
|
|
893
|
+
one API, use route <glob> delay <time>. Stopping a service is not the same as network off: a dev proxy
|
|
892
894
|
in front of it usually holds the request open, so the page spins instead of failing.`,
|
|
893
895
|
},
|
|
894
896
|
eval: {
|
|
895
|
-
usage: 'eval [--all] <JavaScript>',
|
|
897
|
+
usage: 'eval [--all] [--timeout=<time>] <JavaScript>',
|
|
896
898
|
summary: 'evaluate JavaScript in the selected tab and print the result',
|
|
897
899
|
args: [
|
|
898
900
|
['<JavaScript>', 'the rest of the line. Promises are awaited, await works at the top level, and a function (() => document.title) is called.'],
|
|
@@ -900,10 +902,16 @@ in front of it usually holds the request open, so the page spins instead of fail
|
|
|
900
902
|
],
|
|
901
903
|
options: [
|
|
902
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 ...'],
|
|
903
906
|
['--all', 'the whole result, not capped.'],
|
|
904
907
|
],
|
|
905
908
|
detail: `eval fetch(...) sends a new request, which can change state on the server; it cannot read one that
|
|
906
|
-
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.`,
|
|
907
915
|
},
|
|
908
916
|
cdp: {
|
|
909
917
|
usage: 'cdp [--all] <method> <JSON object>',
|
|
@@ -917,6 +925,20 @@ already happened (requests lists those).`,
|
|
|
917
925
|
],
|
|
918
926
|
detail: `The session is detached afterwards, so subscriptions and settings do not persist. Browser.close and
|
|
919
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.`,
|
|
920
942
|
},
|
|
921
943
|
cookies: {
|
|
922
944
|
usage: 'cookies [--all]',
|
|
@@ -973,7 +995,11 @@ at the browser (e.g. headless), it is the only way to answer one.`,
|
|
|
973
995
|
quit: {
|
|
974
996
|
usage: 'quit',
|
|
975
997
|
summary: 'disconnect, leaving Chromium running (one --launch started stops too)',
|
|
976
|
-
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.`,
|
|
977
1003
|
},
|
|
978
1004
|
};
|
|
979
1005
|
|