pw-repl 0.12.4 → 0.12.6
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 +2 -1
- package/README.md +8 -5
- package/bin/pw-repl.js +3 -1
- package/lib/commands.js +4 -0
- package/lib/elements.js +7 -2
- package/lib/help.js +80 -9
- package/lib/inspect.js +12 -5
- package/lib/modes.js +6 -0
- package/lib/output.js +13 -4
- package/lib/profile.js +319 -0
- package/lib/runner.js +2 -2
- package/lib/send.js +3 -2
- package/lib/tabs.js +1 -1
- package/lib/trace.js +530 -0
- package/lib/util.js +4 -1
- package/package.json +1 -1
- package/skill/SKILL.md +6 -1
package/AGENTS.md
CHANGED
|
@@ -20,7 +20,8 @@ If you hit a limitation or write a workaround, consider adding the capability to
|
|
|
20
20
|
drawn over the page, which the element and mouse commands glide first), `lib/toast.js` (a line of text
|
|
21
21
|
over the page), `lib/overlay.js` (the element the cursor and toast are drawn in, one layer each), `lib/inspect.js` (reading the
|
|
22
22
|
page, screenshots, eval, CDP, waits), `lib/watch.js`, `lib/requestlog.js` (requests, bodies, console),
|
|
23
|
-
`lib/routes.js`, `lib/network.js`, `lib/emulation.js` (and viewport), `lib/capture.js`, `lib/
|
|
23
|
+
`lib/routes.js`, `lib/network.js`, `lib/emulation.js` (and viewport), `lib/capture.js`, `lib/profile.js` (performance traces;
|
|
24
|
+
`lib/trace.js` reads and sums them up), `lib/record.js`
|
|
24
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
|
|
25
26
|
session kept per tab, and `lib/util.js` small shared helpers. No facility requires `lib/modes.js` or
|
|
26
27
|
`lib/commands.js`, and a unit test fails on a require cycle, which CommonJS would not report.
|
package/README.md
CHANGED
|
@@ -54,6 +54,8 @@ pw> console error # and see how the page copes
|
|
|
54
54
|
- **Record:** what someone does in a tab (`watch`), a timeline of requests and console (`capture`), or a
|
|
55
55
|
video of the tab (`record`), with a virtual pointer (`cursor`), captions (`toast`) and highlights
|
|
56
56
|
(`highlight`).
|
|
57
|
+
- **Profile:** what keeps a page's main thread busy (`profile`): long tasks and the function behind each,
|
|
58
|
+
forced style and layout, each click's or key's time to the next paint, and dropped frames.
|
|
57
59
|
- **Share:** many clients on one REPL, each with its own tab; a command server on a Unix socket or a local
|
|
58
60
|
port, with an HTTP+JSON protocol.
|
|
59
61
|
|
|
@@ -146,6 +148,7 @@ example the Docker bridge gateway, `ip route | awk '/default/ {print $3}'`).
|
|
|
146
148
|
| see each step as I click | `watch on --live` |
|
|
147
149
|
| replay what I clicked through | `watch on`, click around, `watch save flow.txt`, then `pw-repl send --file flow.txt` |
|
|
148
150
|
| record requests and console together | `capture on`, then `capture off` |
|
|
151
|
+
| find what makes the page slow | `profile on`, then `profile off`; `profile reload` for a page load |
|
|
149
152
|
| fake a backend response | `route <glob> <status> <json>`, `route <glob> abort` |
|
|
150
153
|
| change or slow an API response | `route <glob> patch <json>`, `route <glob> delay <secs>` |
|
|
151
154
|
| cut or slow the whole network | `network off`, `network slow`, then `network on` |
|
|
@@ -159,11 +162,11 @@ example the Docker bridge gateway, `ip route | awk '/default/ {print $3}'`).
|
|
|
159
162
|
|
|
160
163
|
### Modes
|
|
161
164
|
|
|
162
|
-
Some commands turn on a mode that stays on until you turn it off: `watch`, `capture`, `
|
|
163
|
-
off` or `slow`, `emulate`, `viewport`, `record`, `highlight`, `cursor` and `toast`. While any
|
|
164
|
-
the selected tab, the prompt shows them: `(watch network:off routes:2) pw>`. `modes` lists them
|
|
165
|
-
tab, and `modes off` turns them all off. Each of these commands on its own shows its state and
|
|
166
|
-
can run next.
|
|
165
|
+
Some commands turn on a mode that stays on until you turn it off: `watch`, `capture`, `profile`, `route`,
|
|
166
|
+
`network off` or `slow`, `emulate`, `viewport`, `record`, `highlight`, `cursor` and `toast`. While any
|
|
167
|
+
are on in the selected tab, the prompt shows them: `(watch network:off routes:2) pw>`. `modes` lists them
|
|
168
|
+
for every tab, and `modes off` turns them all off. Each of these commands on its own shows its state and
|
|
169
|
+
what you can run next.
|
|
167
170
|
|
|
168
171
|
## For agents and scripts
|
|
169
172
|
|
package/bin/pw-repl.js
CHANGED
|
@@ -38,7 +38,9 @@ $PW_ENDPOINT, then $PW_SOCKET, then the default.
|
|
|
38
38
|
send -c <client> (or $PW_CLIENT) sends as a client of the REPL's with a selected tab of its own, so
|
|
39
39
|
several agents can share one REPL; pw-repl help session has the rest.
|
|
40
40
|
|
|
41
|
-
send exit status: 0 ok, 1 command error, 2 completion not confirmed, 64 usage or unreachable.
|
|
41
|
+
send exit status: 0 ok, 1 command error, 2 completion not confirmed, 64 usage or unreachable. send -t is
|
|
42
|
+
how long send waits for the answer (default 20s); it does not lengthen a command's own limits, such as
|
|
43
|
+
the 5s an action waits for its element.
|
|
42
44
|
|
|
43
45
|
send --file <file> runs each line of a file as a command, in order, as if typed at the REPL's prompt:
|
|
44
46
|
quoted as the prompt reads it (no shell), with a file it records, uploads or saves relative to this
|
package/lib/commands.js
CHANGED
|
@@ -14,6 +14,7 @@ const { commands: networkCommands } = require('./network');
|
|
|
14
14
|
const { commands: emulationCommands, EMULATE_KINDS } = require('./emulation');
|
|
15
15
|
const { commands: captureCommands } = require('./capture');
|
|
16
16
|
const { commands: recordCommands } = require('./record');
|
|
17
|
+
const { commands: profileCommands } = require('./profile');
|
|
17
18
|
const { commands: inspectCommands } = require('./inspect');
|
|
18
19
|
const { commands: watchCommands, nextTabWanted, waitingForMe, watchNewTab } = require('./watch');
|
|
19
20
|
const { commands: tabsCommands, isOpening } = require('./tabs');
|
|
@@ -33,6 +34,7 @@ const commands = {
|
|
|
33
34
|
...emulationCommands,
|
|
34
35
|
...captureCommands,
|
|
35
36
|
...recordCommands,
|
|
37
|
+
...profileCommands,
|
|
36
38
|
...dialogsCommands,
|
|
37
39
|
...modesCommands,
|
|
38
40
|
|
|
@@ -117,6 +119,7 @@ function complete(line) {
|
|
|
117
119
|
if (command === 'watch') return match(['on', 'off', 'new', '--all']);
|
|
118
120
|
if (command === 'capture') return match(['on', 'off']);
|
|
119
121
|
if (command === 'record') return match(['on', 'off']);
|
|
122
|
+
if (command === 'profile') return match(['on', 'off', 'save', 'reload']);
|
|
120
123
|
if (command === 'cursor') return match(['on', 'off']);
|
|
121
124
|
if (command === 'toast') return match(['off']);
|
|
122
125
|
if (command === 'viewport') return match(['off']);
|
|
@@ -124,6 +127,7 @@ function complete(line) {
|
|
|
124
127
|
}
|
|
125
128
|
if (words.length >= 3 && command === 'watch' && words[1] === 'on') return match(['--changes', '--live', '--next-tab'].filter(f => !words.slice(2, -1).includes(f)));
|
|
126
129
|
if (words.length === 3 && command === 'capture' && words[1] === 'on') return match(['requests', 'console']);
|
|
130
|
+
if (words.length >= 3 && command === 'profile' && words[1] === 'on') return match(['--cpu']);
|
|
127
131
|
if (words.length === 3 && command === 'emulate' && EMULATE_KINDS.includes(words[1])) return match(['off']);
|
|
128
132
|
if (words.length === 3 && command === 'route' && words[1] === 'off') return match(['--all', ...(state.page ? routesFor(state.page).keys() : [])]);
|
|
129
133
|
return [[], current];
|
package/lib/elements.js
CHANGED
|
@@ -5,7 +5,7 @@ const { state, onShutdown } = require('./state');
|
|
|
5
5
|
const out = require('./output');
|
|
6
6
|
const { takeOptions } = require('./cli-names');
|
|
7
7
|
const { unquote, toSelector, keyName, splitSelector, REF } = require('./syntax');
|
|
8
|
-
const { numbers, hints } = require('./util');
|
|
8
|
+
const { numbers, hints, BUSY_PAGE } = require('./util');
|
|
9
9
|
const { tabState } = require('./tabstate');
|
|
10
10
|
const { cursorOf, aiming, aimAt, glideTo, showClick, showSelect } = require('./cursor');
|
|
11
11
|
const { recordingOf, stepTarget, pauseToShow } = require('./record');
|
|
@@ -95,8 +95,13 @@ async function onElement(selector, action, budget = 5000) {
|
|
|
95
95
|
// or the check the element kept failing.
|
|
96
96
|
function lastBlocker(log) {
|
|
97
97
|
const lines = log.split('\n').map(line => line.trim().replace(/^-\s*/, ''));
|
|
98
|
+
// Its last step was the action itself: it was sent, and the page stopped answering, as when the click
|
|
99
|
+
// sets off a long task. A profile started now would miss that task: Chrome traces only what begins
|
|
100
|
+
// after it starts.
|
|
101
|
+
const sent = /^performing (\w+) action$/.exec(lines.filter(Boolean).pop() || '');
|
|
102
|
+
if (sent) return `the ${sent[1]} was sent, and may have landed, but the page stopped answering after it, likely busy with what it set off. A profile shows that only if it is on before the action: profile on, then the action again, if repeating it is safe`;
|
|
98
103
|
const line = lines.reverse().find(l => /intercepts pointer events|element is not |element is outside|not attached|detached/.test(l));
|
|
99
|
-
if (!line) return
|
|
104
|
+
if (!line) return `it never became ready to act on; ${BUSY_PAGE}`;
|
|
100
105
|
return line.length > 240 ? `${line.slice(0, 240)}…` : line;
|
|
101
106
|
}
|
|
102
107
|
|
package/lib/help.js
CHANGED
|
@@ -69,12 +69,13 @@ tab on its own lists them all.`,
|
|
|
69
69
|
interact: {
|
|
70
70
|
intro: `Selectors are Playwright selectors (CSS, text=..., role=...) or snapshot refs such as e5.
|
|
71
71
|
Commands use the first match (click, dblclick and hover the first they can use: help click); see help
|
|
72
|
-
fill for selectors with spaces. mousemove and the other mouse commands act at a point instead
|
|
72
|
+
fill for selectors with spaces. mousemove and the other mouse commands act at a point instead. An action
|
|
73
|
+
scrolls its element into view first, as Playwright does; profile counts that scroll in the action's step.`,
|
|
73
74
|
commands: ['click', 'dblclick', 'hover', 'fill', 'type', 'press', 'select', 'check', 'uncheck', 'upload', 'mousemove', 'mouseclick', 'mousedown', 'mouseup', 'mousewheel'],
|
|
74
75
|
},
|
|
75
76
|
inspect: {
|
|
76
77
|
intro: `Output is capped; --all right after the command, or at the end, shows everything (text --all body).`,
|
|
77
|
-
commands: ['snapshot', 'watch', 'text', 'html', 'attrs', 'listeners', 'count', 'visible', 'links', 'inputs', 'screenshot', 'highlight', 'viewport', 'emulate', 'wait', 'sleep'],
|
|
78
|
+
commands: ['snapshot', 'watch', 'profile', 'text', 'html', 'attrs', 'listeners', 'count', 'visible', 'links', 'inputs', 'screenshot', 'highlight', 'viewport', 'emulate', 'wait', 'sleep'],
|
|
78
79
|
},
|
|
79
80
|
video: {
|
|
80
81
|
intro: `A video of what you do in a tab, for someone to watch. Record it from a file of commands sent with
|
|
@@ -235,7 +236,7 @@ A click at a point: mouseclick <x> <y>. A drag: mousedown, then mousemove to ano
|
|
|
235
236
|
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
|
|
236
237
|
it. The cursor (help cursor) moves there first and shows a ring at the press.`,
|
|
237
238
|
},
|
|
238
|
-
mousedown: { usage: 'mousedown [button]', summary: 'press a mouse button
|
|
239
|
+
mousedown: { usage: 'mousedown [button]', summary: 'press a mouse button at the mouse (left, right, middle)' },
|
|
239
240
|
mouseup: { usage: 'mouseup [button]', summary: 'release a mouse button where the mouse is' },
|
|
240
241
|
mousewheel: {
|
|
241
242
|
usage: 'mousewheel <dx> <dy>',
|
|
@@ -252,7 +253,7 @@ scrolled or redrawn. While recording, it scrolls in eased steps over up to 1s (h
|
|
|
252
253
|
summary: 'clear an input and fill it',
|
|
253
254
|
args: [
|
|
254
255
|
['<selector>', 'the first word; quote it if it has spaces: fill "text=Your name" Ada.'],
|
|
255
|
-
['<value>', 'the rest of the line; quotes around all of it are removed, so fill #name "" clears it.'],
|
|
256
|
+
['<value>', 'the rest of the line; quotes around all of it are removed, so fill #name "" clears it. Over send, the shell\'s quotes do it: pw-repl send fill #name "" (\'""\' types two quote marks).'],
|
|
256
257
|
],
|
|
257
258
|
options: [
|
|
258
259
|
['--submit', 'at the end: presses Enter in it afterwards.'],
|
|
@@ -261,8 +262,8 @@ scrolled or redrawn. While recording, it scrolls in eased steps over up to 1s (h
|
|
|
261
262
|
once, even while recording: type shows it being typed.`,
|
|
262
263
|
},
|
|
263
264
|
type: {
|
|
264
|
-
usage: 'type [<selector>] <text> [--submit]',
|
|
265
|
-
summary: 'type key by key after what the field holds
|
|
265
|
+
usage: 'type [--delay=<ms>] [<selector>] <text> [--submit]',
|
|
266
|
+
summary: 'type key by key after what the field holds',
|
|
266
267
|
args: [
|
|
267
268
|
['[<selector>]', 'as for fill. Without it (one word, or one quoted), into what has focus: type "garden hose".'],
|
|
268
269
|
['<text>', 'the rest of the line, as fill\'s value.'],
|
|
@@ -281,10 +282,10 @@ Date and time inputs: they take keys part by part, in the order they show them (
|
|
|
281
282
|
from the first part with a selector, or from the part a click landed on. type says what the input then
|
|
282
283
|
holds. Their ISO form (2026-10-02) is refused, since typed it comes out mangled: fill sets it.`,
|
|
283
284
|
},
|
|
284
|
-
press: { usage: 'press <key> | press <selector> <key>', summary: 'press a key
|
|
285
|
+
press: { usage: 'press <key> | press <selector> <key>', summary: 'press a key: Enter, Escape, Control+A (any case)' },
|
|
285
286
|
select: {
|
|
286
287
|
usage: 'select <selector> <value>',
|
|
287
|
-
summary: 'choose an option
|
|
288
|
+
summary: 'choose an option; the value is the rest of the line',
|
|
288
289
|
args: [
|
|
289
290
|
['<value>', 'an option\'s value or its label.'],
|
|
290
291
|
],
|
|
@@ -314,6 +315,7 @@ all.`,
|
|
|
314
315
|
['<selector>', 'outlines the first match, with new refs: those from before stop working.'],
|
|
315
316
|
],
|
|
316
317
|
options: [
|
|
318
|
+
['--all', 'the whole outline, not capped.'],
|
|
317
319
|
['--full', 'Playwright\'s snapshot unchanged.'],
|
|
318
320
|
['--grep <text>', 'only the lines containing text (role, name or a flag such as [disabled], any case), each with the named elements around it.'],
|
|
319
321
|
['--regex <pattern>', '--grep by a regular expression: snapshot --regex "Total: \\d+".'],
|
|
@@ -332,7 +334,8 @@ value attribute so too. A field a Show password button made plain text, with no
|
|
|
332
334
|
shows its value. Every other field shows what it holds (textbox "Name": Ada), and a select its chosen
|
|
333
335
|
option: inputs lists the fields without what they hold.
|
|
334
336
|
|
|
335
|
-
Output over 60 lines ends with a line count
|
|
337
|
+
Output over 60 lines ends with a line count. Past 12000 characters it is cut at the end of a line, saying
|
|
338
|
+
where: --all shows all.`,
|
|
336
339
|
},
|
|
337
340
|
watch: {
|
|
338
341
|
usage: 'watch on [options] | off | [n|new] | save <file>',
|
|
@@ -703,6 +706,74 @@ Kept from when the REPL connects; the last 200 per tab.
|
|
|
703
706
|
|
|
704
707
|
A page brought back from the back/forward cache (back, forward) reports its earlier messages again, at
|
|
705
708
|
the time it comes back, though nothing loaded again.`,
|
|
709
|
+
},
|
|
710
|
+
profile: {
|
|
711
|
+
usage: 'profile on [secs] | off | reload | save <file>',
|
|
712
|
+
summary: 'what slows the tab: long tasks, layouts, input, frames',
|
|
713
|
+
args: [
|
|
714
|
+
['on', 'starts a performance trace of the selected tab, until profile off (at most 120 seconds).'],
|
|
715
|
+
['[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.'],
|
|
716
|
+
['off', 'stops it and prints the summary.'],
|
|
717
|
+
['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.'],
|
|
718
|
+
['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.'],
|
|
719
|
+
['(none)', 'says whether one is running, or shows the last one.'],
|
|
720
|
+
],
|
|
721
|
+
optionsFor: 'profile on',
|
|
722
|
+
options: [
|
|
723
|
+
['--cpu', 'samples JS more often, and adds the functions that ran longest themselves. The trace gets much bigger.'],
|
|
724
|
+
],
|
|
725
|
+
detail: `For "the page feels slow": what the tab's main thread was busy with, and when. It is a Chrome
|
|
726
|
+
performance trace, as DevTools' Performance panel records (Playwright's browser.startTracing), summed up.
|
|
727
|
+
Use it around something you do (profile on, click e5, profile off), or while the user uses the page: steps
|
|
728
|
+
they take in the browser are in it.
|
|
729
|
+
|
|
730
|
+
The summary: main-thread busy time by kind (scripting, style/layout, paint, GC, other); the page's
|
|
731
|
+
load marks, when it loaded; each long task (over 50ms), with what set it off, the script it ran, the
|
|
732
|
+
function it spent most time in ("most in"), each as file:line function, and a big response (100 KB or more) that finished just before
|
|
733
|
+
it; forced style and layout (work a script waited for, reading a size or a style), by where in the script; and the steps:
|
|
734
|
+
each click or key press with its time to the next paint (input delay, handlers, presentation), and each
|
|
735
|
+
burst of wheel or scroll events and each drag, with the main thread's busy time and the frames drawn and
|
|
736
|
+
dropped. Times are from when it started. --all lists every long task and step, not the first ones.
|
|
737
|
+
|
|
738
|
+
Long tasks read kind, what set it off, script: scripting, click event, app.js:120 handleClick. The kind
|
|
739
|
+
is what most of its time went to (GC, style/layout); what set it off is the event or callback that began
|
|
740
|
+
it (timer, promise callbacks); promise callbacks a click handler queued run in the click's task, so
|
|
741
|
+
show as click event. No script is named when none of its code came from a page URL.
|
|
742
|
+
|
|
743
|
+
A page that freezes: profile on, then do what freezes it. Your commands wait for the page meanwhile, and
|
|
744
|
+
may time out, but the profile records it all; profile off once the page answers again.
|
|
745
|
+
|
|
746
|
+
Size: a busy page fills Chrome's trace buffer in under a minute (one that restyles many elements as it
|
|
747
|
+
draws, sooner), which stops the profile there, and the summary says so. Stopping a long one takes a
|
|
748
|
+
while, as Chrome writes out its trace: give send time, pw-repl send -t 60 profile off. If send gives up
|
|
749
|
+
first, the profile still ends, and profile shows it.
|
|
750
|
+
|
|
751
|
+
One profile runs at a time, in a browser: Chrome traces one at a time, the whole browser's, so another
|
|
752
|
+
REPL's profile, or DevTools' Performance panel recording, stops profile on. It belongs to the tab, ends
|
|
753
|
+
if the tab closes (profile shows it), and modes lists it.`,
|
|
754
|
+
moreAbout: 'what is left out, inlined functions, frames in a background tab, and the mark it leaves in the page',
|
|
755
|
+
more: `The tab's own renderer only: a cross-site iframe running in a process of its own is left out, and
|
|
756
|
+
so is anything another tab of the same site, sharing the tab's process, does at the same time.
|
|
757
|
+
|
|
758
|
+
Frames: a tab pw-repl uses is kept drawing as if it were in front (help tab), so its frame counts are a
|
|
759
|
+
tab in front's; one behind another tab draws far less, as the user would see it. The summary says so.
|
|
760
|
+
|
|
761
|
+
A function the JS engine inlined into its caller counts as the caller, in "most in" and --cpu alike: a
|
|
762
|
+
small hot function called in a loop can show as the loop's function.
|
|
763
|
+
|
|
764
|
+
A wheel-zoom or drag the page handles in its own script (a map) shows as a step from its events; one the
|
|
765
|
+
browser handles alone, with no listener, may show only as frames.
|
|
766
|
+
|
|
767
|
+
A page gets scroll events for any scroll, input or not. Those next to a click or key press, with no wheel
|
|
768
|
+
event (an action's scroll into view, or a handler's scrollTo), are that step's: it says the page
|
|
769
|
+
scrolled. A burst of scroll events alone may also be the page's own script scrolling.
|
|
770
|
+
|
|
771
|
+
A step under way when the profile began (an action still running at profile on) reads before, without
|
|
772
|
+
its busy time: the trace has nothing from before it began.
|
|
773
|
+
|
|
774
|
+
profile on and off each run console.timeStamp in the page, which prints nothing, so the tab is found in
|
|
775
|
+
the trace even when its page did nothing. The trace is kept in a temporary folder until the next
|
|
776
|
+
profile, or until the REPL stops.`,
|
|
706
777
|
},
|
|
707
778
|
capture: {
|
|
708
779
|
usage: 'capture on [requests|console] [secs] | off',
|
package/lib/inspect.js
CHANGED
|
@@ -6,7 +6,7 @@ const out = require('./output');
|
|
|
6
6
|
const { printOutput } = out;
|
|
7
7
|
const { devices } = require('playwright-core');
|
|
8
8
|
const { toSelector, REF, unquote } = require('./syntax');
|
|
9
|
-
const { regexOf, COMMAND_TIMEOUT } = require('./util');
|
|
9
|
+
const { regexOf, COMMAND_TIMEOUT, BUSY_PAGE } = require('./util');
|
|
10
10
|
const { tabState } = require('./tabstate');
|
|
11
11
|
const { keptSession } = require('./cdp');
|
|
12
12
|
const { CDP_REFUSED } = require('./dialogs');
|
|
@@ -127,6 +127,13 @@ async function waitGone(locator, what, timeout) {
|
|
|
127
127
|
|
|
128
128
|
const SNAPSHOT_HINT_LINES = 60;
|
|
129
129
|
|
|
130
|
+
function evaluated(promise) {
|
|
131
|
+
return withTimeout(promise, 'Page evaluation', COMMAND_TIMEOUT).catch(error => {
|
|
132
|
+
if (error.message === 'Page evaluation timed out') error.message += ` after ${COMMAND_TIMEOUT / 1000}s; ${BUSY_PAGE}`;
|
|
133
|
+
throw error;
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
130
137
|
// Each matching line (any part of it: role, name, flags such as [disabled]),
|
|
131
138
|
// with the named elements it sits in, so a match can be placed without the
|
|
132
139
|
// rest of the tree.
|
|
@@ -422,7 +429,7 @@ const commands = {
|
|
|
422
429
|
return;
|
|
423
430
|
}
|
|
424
431
|
const lines = text.split('\n').length;
|
|
425
|
-
printOutput(lines > SNAPSHOT_HINT_LINES ?
|
|
432
|
+
printOutput(text, all, lines > SNAPSHOT_HINT_LINES ? `[${lines} lines; snapshot --grep <text> or snapshot <eN> shows less]` : '');
|
|
426
433
|
},
|
|
427
434
|
|
|
428
435
|
async wait(args) {
|
|
@@ -480,7 +487,7 @@ const commands = {
|
|
|
480
487
|
try {
|
|
481
488
|
// Made from its text by the debugger, as eval is, so a page's CSP cannot
|
|
482
489
|
// refuse it, and in the element's own frame, which may be an iframe.
|
|
483
|
-
const fn = await
|
|
490
|
+
const fn = await evaluated((await el.ownerFrame()).evaluateHandle(unquote(onRef[1])));
|
|
484
491
|
try {
|
|
485
492
|
printOutput(await el.evaluate((e, f) => f(e), fn), all);
|
|
486
493
|
} finally {
|
|
@@ -502,13 +509,13 @@ const commands = {
|
|
|
502
509
|
const call = `const value = (${args}\n); return typeof value === 'function' ? value() : value;`;
|
|
503
510
|
if (awaits && !single && compiles(AsyncFunction, args)) {
|
|
504
511
|
const session = await keptSession(state.page);
|
|
505
|
-
const { result, exceptionDetails } = await
|
|
512
|
+
const { result, exceptionDetails } = await evaluated(session.send('Runtime.evaluate', { expression: args, replMode: true, awaitPromise: true, returnByValue: true }));
|
|
506
513
|
if (exceptionDetails) throw new Error(exceptionDetails.exception?.description || exceptionDetails.text);
|
|
507
514
|
printOutput(result.value !== undefined ? result.value : result.description ?? 'undefined', all);
|
|
508
515
|
return;
|
|
509
516
|
}
|
|
510
517
|
const expression = single ? `(${awaits ? 'async ' : ''}() => { ${call} })()` : args;
|
|
511
|
-
const value = await
|
|
518
|
+
const value = await evaluated(state.page.evaluate(expression));
|
|
512
519
|
// An empty string is shown as one, not as no output at all.
|
|
513
520
|
printOutput(value === '' ? '""' : value, all);
|
|
514
521
|
// Quotes around the whole of it, as a shell's quoting sent on, make it a string, not code to run:
|
package/lib/modes.js
CHANGED
|
@@ -8,6 +8,7 @@ const { setNetwork } = require('./network');
|
|
|
8
8
|
const { emulatedKinds, setEmulation, setViewport } = require('./emulation');
|
|
9
9
|
const { recordingOf, finishRecording } = require('./record');
|
|
10
10
|
const { currentCapture, endCapture } = require('./capture');
|
|
11
|
+
const { currentProfile, finishProfile } = require('./profile');
|
|
11
12
|
const { nextTabWanted, waitingText, stopWaitingForNextTab, stopWatching } = require('./watch');
|
|
12
13
|
const { hideHighlights, setLabels } = require('./elements');
|
|
13
14
|
const { cursorOf, cursorOff } = require('./cursor');
|
|
@@ -31,6 +32,7 @@ function activeModes(p) {
|
|
|
31
32
|
if (cursorOf(p)) modes.push('cursor');
|
|
32
33
|
if (toastOf(p)) modes.push('toast');
|
|
33
34
|
if (currentCapture()?.page === p) modes.push('capture');
|
|
35
|
+
if (currentProfile()?.page === p) modes.push('profile');
|
|
34
36
|
if (recordingOf(p)) modes.push('record');
|
|
35
37
|
return modes;
|
|
36
38
|
}
|
|
@@ -87,6 +89,8 @@ function modeOwners(p) {
|
|
|
87
89
|
if (toast) owners.push([toast.owner ?? null, 'toast']);
|
|
88
90
|
const capture = currentCapture();
|
|
89
91
|
if (capture?.page === p) owners.push([capture.owner ?? null, 'capture']);
|
|
92
|
+
const profile = currentProfile();
|
|
93
|
+
if (profile?.page === p) owners.push([profile.owner ?? null, 'profile']);
|
|
90
94
|
const recording = recordingOf(p);
|
|
91
95
|
if (recording) owners.push([recording.owner ?? null, 'record']);
|
|
92
96
|
return owners;
|
|
@@ -116,6 +120,8 @@ async function allModesOff(mine = false) {
|
|
|
116
120
|
if (watch?.on && ours(watch.owner)) { stopWatching(p, watch); done.push('watch off'); }
|
|
117
121
|
const capture = currentCapture();
|
|
118
122
|
if (capture?.page === p && ours(capture.owner)) { endCapture(); done.push('capture off (capture shows it)'); }
|
|
123
|
+
const profile = currentProfile();
|
|
124
|
+
if (profile?.page === p && ours(profile.owner)) { await finishProfile(); done.push('profile off (profile shows it)'); }
|
|
119
125
|
const globs = [...(tabState(p).routes || [])].filter(([, route]) => ours(route.owner)).map(([glob]) => glob);
|
|
120
126
|
if (globs.length) { await unroute(p, globs); done.push(`${globs.length} route${globs.length === 1 ? '' : 's'} removed`); }
|
|
121
127
|
if ('network' in tabState(p) && ours(tabState(p).network.owner)) { await setNetwork(p, null); done.push('network on'); }
|
package/lib/output.js
CHANGED
|
@@ -65,16 +65,25 @@ function aside(text) {
|
|
|
65
65
|
else console.log(text);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
// A footer is printed after the cut, so it is never cut off itself.
|
|
69
|
+
function printOutput(value, all = false, footer = '') {
|
|
69
70
|
let text;
|
|
70
71
|
if (typeof value === 'string') text = value;
|
|
71
72
|
else text = JSON.stringify(value, null, 2);
|
|
72
73
|
if (text === undefined) text = 'undefined';
|
|
73
74
|
if (!all && text.length > OUTPUT_LIMIT) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
// Cut at a line's end, unless the last one within the limit comes so early (a short line before a
|
|
76
|
+
// long minified one) that little would be shown.
|
|
77
|
+
const end = text.lastIndexOf('\n', OUTPUT_LIMIT);
|
|
78
|
+
if (end > OUTPUT_LIMIT / 2) {
|
|
79
|
+
const shown = text.slice(0, end);
|
|
80
|
+
const at = shown.split('\n').length;
|
|
81
|
+
text = `${shown}\n[truncated at line ${at} of ${text.split('\n').length}; add --all to the command to show all]`;
|
|
82
|
+
} else {
|
|
83
|
+
text = `${text.slice(0, OUTPUT_LIMIT)}\n[truncated; add --all to the command to show all]`;
|
|
84
|
+
}
|
|
77
85
|
}
|
|
86
|
+
log(footer ? `${text}\n${footer}` : text);
|
|
78
87
|
}
|
|
79
88
|
|
|
80
89
|
module.exports = { OUTPUT_LIMIT, log, error, collect, take, notice, aside, onIdlePrint, onSelectedTab, printOutput };
|