pw-repl 0.3.1 → 0.3.2
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 +23 -0
- package/lib/commands.js +44 -13
- package/lib/help.js +13 -9
- package/lib/send.js +2 -1
- package/package.json +1 -1
- package/skill/SKILL.md +7 -5
package/AGENTS.md
CHANGED
|
@@ -27,6 +27,29 @@ If you hit a limitation or write a workaround, consider adding the capability to
|
|
|
27
27
|
- `skill/SKILL.md` is how agents learn to use the REPL: keep it in step with a change to how it is
|
|
28
28
|
used, and leave its Custom rules section empty.
|
|
29
29
|
|
|
30
|
+
## Waves
|
|
31
|
+
|
|
32
|
+
Work goes in waves, and each one ends with a release that is ready to push.
|
|
33
|
+
|
|
34
|
+
1. Build what was asked, with tests, and try it in a running REPL.
|
|
35
|
+
2. Try it on a fresh agent that has not seen the code or the change (a guinea pig), on something real: a
|
|
36
|
+
small local page whose planted bugs need the change to find.
|
|
37
|
+
- Brief it with goals only: what to find out or do in the browser, never the commands.
|
|
38
|
+
- First it reads back its plan, learned from `pw-repl skill` and help alone: the commands, where it
|
|
39
|
+
found them, and what is unclear. It runs nothing yet.
|
|
40
|
+
- A wrong or unsure plan means the docs or the brief were unclear: find out which, fix that, then
|
|
41
|
+
ask again.
|
|
42
|
+
- Then it carries the plan out, in a browser and on a socket of its own, and reports as it goes:
|
|
43
|
+
above all, where the tool did something other than what the docs led it to expect.
|
|
44
|
+
3. Triage what it reports. Fix what matters, and what is small and useful; don't put off something
|
|
45
|
+
useful for later. Leave trivia alone, and say why.
|
|
46
|
+
4. Repeat 2 and 3, with a fresh agent each time, until a pass comes back clean: no real bugs, and
|
|
47
|
+
nothing unclear in the docs or the brief that changed the plan. A pass after fixes tries the fixes,
|
|
48
|
+
and something not tried yet.
|
|
49
|
+
5. Only then cut the release (Releasing, steps 1-3), and say it is ready. The maintainer pushes and
|
|
50
|
+
publishes it, and the next wave starts from what they find. A release cut too early and not pushed
|
|
51
|
+
yet is undone (its commit and tag) and cut again after the fixes.
|
|
52
|
+
|
|
30
53
|
## Releasing
|
|
31
54
|
|
|
32
55
|
Commits follow [Conventional Commits](https://www.conventionalcommits.org/): `feat:`, `fix:`, `docs:`,
|
package/lib/commands.js
CHANGED
|
@@ -154,12 +154,12 @@ async function viewportText() {
|
|
|
154
154
|
|
|
155
155
|
// A page restored from the back/forward cache never fires its load events
|
|
156
156
|
// again, so back and forward wait only for the navigation, then briefly for
|
|
157
|
-
// the
|
|
157
|
+
// the document to be parsed, which a restored one already is.
|
|
158
158
|
async function historyStep(go, direction) {
|
|
159
159
|
const before = state.page.url();
|
|
160
160
|
const response = await go();
|
|
161
161
|
if (response === null && state.page.url() === before) throw new Error(`No page to go ${direction} to`);
|
|
162
|
-
await state.page.
|
|
162
|
+
await state.page.waitForFunction(() => document.readyState !== 'loading', null, { timeout: 3000 }).catch(() => {});
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
const SCREENSHOT_DELAY_MAX = 60;
|
|
@@ -212,10 +212,18 @@ function emulatedKinds(e) {
|
|
|
212
212
|
return [e?.device && 'mobile', e?.scheme, e?.locale && 'locale', e?.timezone && 'timezone'].filter(Boolean);
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
+
const DEVICES_SHOWN = 20;
|
|
216
|
+
|
|
215
217
|
function deviceNamed(name) {
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
return found;
|
|
218
|
+
const names = Object.keys(devices);
|
|
219
|
+
const found = names.find(d => d.toLowerCase() === name.toLowerCase());
|
|
220
|
+
if (found) return found;
|
|
221
|
+
// The names that have every word typed, so a near miss finds the exact name.
|
|
222
|
+
const words = name.toLowerCase().split(/\s+/);
|
|
223
|
+
const close = names.filter(d => !/ landscape$/.test(d) && words.every(w => d.toLowerCase().includes(w)));
|
|
224
|
+
if (!close.length) throw new Error(`No device ${JSON.stringify(name)}; names are Playwright's, e.g. Pixel 7, iPhone 13, iPad Mini, Galaxy S9+`);
|
|
225
|
+
const more = close.length > DEVICES_SHOWN ? `, and ${close.length - DEVICES_SHOWN} more` : '';
|
|
226
|
+
throw new Error(`No device ${JSON.stringify(name)}; matching: ${close.slice(0, DEVICES_SHOWN).join(', ')}${more} (each also as "<name> landscape")`);
|
|
219
227
|
}
|
|
220
228
|
|
|
221
229
|
function deviceText(name) {
|
|
@@ -225,6 +233,14 @@ function deviceText(name) {
|
|
|
225
233
|
|
|
226
234
|
// Sends only what changed. Chrome takes the user agent and the languages
|
|
227
235
|
// together, so a change to either sends both.
|
|
236
|
+
function deviceMetrics(session, name) {
|
|
237
|
+
const d = devices[name];
|
|
238
|
+
return session.send('Emulation.setDeviceMetricsOverride', {
|
|
239
|
+
width: d.viewport.width, height: d.viewport.height, deviceScaleFactor: d.deviceScaleFactor, mobile: d.isMobile,
|
|
240
|
+
screenWidth: d.screen?.width || d.viewport.width, screenHeight: d.screen?.height || d.viewport.height,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
228
244
|
async function setEmulation(p, next) {
|
|
229
245
|
const session = await keptSession(p);
|
|
230
246
|
const previous = emulations.get(p) || {};
|
|
@@ -232,10 +248,7 @@ async function setEmulation(p, next) {
|
|
|
232
248
|
if (next.device !== previous.device) {
|
|
233
249
|
const d = devices[next.device];
|
|
234
250
|
if (d) {
|
|
235
|
-
await
|
|
236
|
-
width: d.viewport.width, height: d.viewport.height, deviceScaleFactor: d.deviceScaleFactor, mobile: d.isMobile,
|
|
237
|
-
screenWidth: d.screen?.width || d.viewport.width, screenHeight: d.screen?.height || d.viewport.height,
|
|
238
|
-
});
|
|
251
|
+
await deviceMetrics(session, next.device);
|
|
239
252
|
} else {
|
|
240
253
|
await send('Emulation.clearDeviceMetricsOverride');
|
|
241
254
|
}
|
|
@@ -1042,7 +1055,11 @@ function grepSnapshot(text, needle) {
|
|
|
1042
1055
|
const indent = line.length - line.trimStart().length;
|
|
1043
1056
|
while (stack.length && stack[stack.length - 1].indent >= indent) stack.pop();
|
|
1044
1057
|
if (line.toLowerCase().includes(lower)) {
|
|
1045
|
-
const
|
|
1058
|
+
const around = stack.filter(a => !/^generic(?: \[|$)/.test(a.label));
|
|
1059
|
+
// A hit with no ref of its own (text) keeps the ref of what holds it, so
|
|
1060
|
+
// snapshot <ref> can show what sits beside it: the value next to a label.
|
|
1061
|
+
const own = /\[ref=/.test(line);
|
|
1062
|
+
const path = around.map((a, i) => (!own && i === around.length - 1 ? a.label : a.label.replace(/ \[ref=[^\]]+\]/g, '')));
|
|
1046
1063
|
hits.push([...path, label(line)].join(' › '));
|
|
1047
1064
|
}
|
|
1048
1065
|
stack.push({ indent, label: label(line) });
|
|
@@ -1473,7 +1490,19 @@ const commands = {
|
|
|
1473
1490
|
// Chrome does not draw a tab that is not in front, and tabs open in the
|
|
1474
1491
|
// background, so the tab is brought to the front for the shot.
|
|
1475
1492
|
await state.page.bringToFront();
|
|
1476
|
-
|
|
1493
|
+
let image;
|
|
1494
|
+
try {
|
|
1495
|
+
image = await state.page.screenshot({ fullPage: full });
|
|
1496
|
+
} finally {
|
|
1497
|
+
// Playwright's screenshot resets the screen size and pixel ratio it
|
|
1498
|
+
// finds; an emulated phone's are set again.
|
|
1499
|
+
const device = emulations.get(state.page)?.device;
|
|
1500
|
+
if (device) {
|
|
1501
|
+
const session = await keptSession(state.page);
|
|
1502
|
+
await session.send('Emulation.clearDeviceMetricsOverride');
|
|
1503
|
+
await deviceMetrics(session, device);
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1477
1506
|
try {
|
|
1478
1507
|
fs.writeFileSync(filepath, image, { flag: 'wx', mode: 0o600 });
|
|
1479
1508
|
} catch (error) {
|
|
@@ -1570,6 +1599,8 @@ const commands = {
|
|
|
1570
1599
|
if (tokens.length > 1 && /^\d+$/.test(tokens[tokens.length - 1])) seconds = Number(tokens.pop());
|
|
1571
1600
|
if (!tokens.length || seconds < 1 || seconds > WAIT_MAX) throw new Error(`${usage} (1-${WAIT_MAX})`);
|
|
1572
1601
|
const timeout = seconds * 1000;
|
|
1602
|
+
// Said plainly, not as Playwright's call log.
|
|
1603
|
+
const plainly = message => error => { throw /Timeout \d+ms exceeded/.test(error.message) ? new Error(message) : error; };
|
|
1573
1604
|
const kind = tokens[0];
|
|
1574
1605
|
if (kind === 'load' && tokens.length === 1) {
|
|
1575
1606
|
if (gone) throw new Error(usage);
|
|
@@ -1582,13 +1613,13 @@ const commands = {
|
|
|
1582
1613
|
return waitForRequest(what, timeout);
|
|
1583
1614
|
}
|
|
1584
1615
|
if (gone) return waitGone(state.page.getByText(what), what, timeout);
|
|
1585
|
-
await state.page.getByText(what).first().waitFor({ state: 'visible', timeout });
|
|
1616
|
+
await state.page.getByText(what).first().waitFor({ state: 'visible', timeout }).catch(plainly(`No visible text matches ${what} within ${seconds}s`));
|
|
1586
1617
|
out.log(`Visible: ${what}`);
|
|
1587
1618
|
return;
|
|
1588
1619
|
}
|
|
1589
1620
|
const selector = toSelector(unquote(tokens.join(' ')));
|
|
1590
1621
|
if (gone) return waitGone(state.page.locator(selector), tokens.join(' '), timeout);
|
|
1591
|
-
await state.page.waitForSelector(selector, { state: 'attached', timeout });
|
|
1622
|
+
await state.page.waitForSelector(selector, { state: 'attached', timeout }).catch(plainly(`No element matches ${tokens.join(' ')} within ${seconds}s`));
|
|
1592
1623
|
out.log(`Found: ${tokens.join(' ')}`);
|
|
1593
1624
|
},
|
|
1594
1625
|
|
package/lib/help.js
CHANGED
|
@@ -52,7 +52,7 @@ const TOPICS = {
|
|
|
52
52
|
commands: ['requests', 'body', 'console', 'capture', 'route', 'network'],
|
|
53
53
|
},
|
|
54
54
|
devtools: {
|
|
55
|
-
intro: 'Cookie and storage listings omit values. eval, html, screenshots and URLs can still show
|
|
55
|
+
intro: 'Cookie and storage listings omit values. eval, cdp, html, screenshots and URLs can still show\nsensitive data (cdp Network.getCookies shows cookie values).',
|
|
56
56
|
commands: ['eval', 'cdp', 'cookies', 'storage'],
|
|
57
57
|
},
|
|
58
58
|
session: {
|
|
@@ -84,8 +84,12 @@ const COMMANDS = {
|
|
|
84
84
|
summary: 'navigate the selected tab',
|
|
85
85
|
detail: 'Without a scheme, http:// is assumed for localhost, 127.x and [::1], and https:// otherwise, as in\nthe address bar. tab new <url> reads its URL the same way.',
|
|
86
86
|
},
|
|
87
|
-
back: {
|
|
88
|
-
|
|
87
|
+
back: {
|
|
88
|
+
usage: 'back',
|
|
89
|
+
summary: 'go back one history entry',
|
|
90
|
+
detail: 'The browser may bring the page back from its back/forward cache, as it was: nothing loads or runs\nagain, so a loading message or a request the page makes on load does not come back. reload loads it\nafresh.',
|
|
91
|
+
},
|
|
92
|
+
forward: { usage: 'forward', summary: 'go forward one history entry', detail: 'Like back, it may bring the page back from the back/forward cache without loading it again.' },
|
|
89
93
|
reload: { usage: 'reload', summary: 'reload the selected tab' },
|
|
90
94
|
info: { usage: 'info', summary: 'selected tab URL, title and viewport' },
|
|
91
95
|
click: { usage: 'click <selector>', summary: 'click the first match' },
|
|
@@ -109,7 +113,7 @@ const COMMANDS = {
|
|
|
109
113
|
snapshot: {
|
|
110
114
|
usage: 'snapshot [--full] [--grep <text> | <eN> | selector]',
|
|
111
115
|
summary: 'outline by role and name, with [ref=eN] labels',
|
|
112
|
-
detail: 'Playwright\'s accessibility snapshot, with unnamed layout wrappers (generic) and cursor hints left\nout; --full shows it unchanged.\n\n--grep <text> prints only the lines containing text (role, name or flag such as [disabled], any\ncase), each with the named elements around it. A hit with no named element around it prints without\na path.\n\nsnapshot e3 outlines one element. A ref (e3, or f1e3 in newer Playwright) works as a selector in any\ncommand: click e3. Refs can change when the page changes (e102 may become f4e98), so take a new\nsnapshot after it does.\n\nOutput over 60 lines ends with a line count.',
|
|
116
|
+
detail: 'Playwright\'s accessibility snapshot, with unnamed layout wrappers (generic) and cursor hints left\nout; --full shows it unchanged.\n\n--grep <text> prints only the lines containing text (role, name or flag such as [disabled], any\ncase), each with the named elements around it. A hit with no named element around it prints without\na path. A hit on text (a label) keeps the ref of the element holding it, so snapshot <that ref>\nshows what sits beside it, such as the value next to the label.\n\nsnapshot e3 outlines one element. A ref (e3, or f1e3 in newer Playwright) works as a selector in any\ncommand: click e3. Refs can change when the page changes (e102 may become f4e98), so take a new\nsnapshot after it does.\n\nOutput over 60 lines ends with a line count.',
|
|
113
117
|
},
|
|
114
118
|
watch: {
|
|
115
119
|
usage: 'watch on [--changes] [--live] | off | [n|new]',
|
|
@@ -131,18 +135,18 @@ const COMMANDS = {
|
|
|
131
135
|
screenshot: {
|
|
132
136
|
usage: 'screenshot [--full] [--delay|-d <seconds>] [name]',
|
|
133
137
|
summary: 'save a PNG of the viewport (or --full page)',
|
|
134
|
-
detail: 'Saved as screenshot-<name or timestamp>.png in $PW_SCREENSHOT_DIR (default /tmp). It brings the tab to\nthe front of its window first: Chrome draws only the tab in front.\n\n--delay counts down out loud first (maximum 60s), so someone can hold a hover or open a menu.',
|
|
138
|
+
detail: 'Saved as screenshot-<name or timestamp>.png in $PW_SCREENSHOT_DIR (default /tmp). It brings the tab to\nthe front of its window first: Chrome draws only the tab in front. The image is at CSS pixel size,\none image pixel per CSS pixel, also while emulate mobile is on.\n\n--delay counts down out loud first (maximum 60s), so someone can hold a hover or open a menu.',
|
|
135
139
|
},
|
|
136
140
|
viewport: { usage: 'viewport [WxH]', summary: 'show or set the viewport size' },
|
|
137
141
|
emulate: {
|
|
138
142
|
usage: 'emulate [<what> [off] | off]',
|
|
139
143
|
summary: 'emulate a phone, dark mode, a locale or a timezone',
|
|
140
|
-
detail: 'emulate mobile [device] a phone\'s screen, touch and user agent: a Pixel 7, or a device named as in\n Playwright\'s list, e.g. emulate mobile iPhone 13\nemulate dark | light the color scheme the page\'s CSS and matchMedia see\nemulate locale <tag> the language (Accept-Language, navigator.language) and date and number\n formats, e.g. fr-FR\nemulate timezone <zone> an IANA timezone, e.g. Asia/Tokyo\nemulate <what> off stop one (dark or light off stops the color scheme); emulate off stops all\n\nPer tab, until turned off or the REPL exits. emulate on its own shows what is on.\n\nThe page sees the user agent, touch and navigator.languages from its next load: reload after\
|
|
144
|
+
detail: 'emulate mobile [device] a phone\'s screen, touch and user agent: a Pixel 7, or a device named as in\n Playwright\'s list, e.g. emulate mobile iPhone 13; a name that is not\n exact lists the devices it matches (emulate mobile galaxy)\nemulate dark | light the color scheme the page\'s CSS and matchMedia see\nemulate locale <tag> the language (Accept-Language, navigator.language) and date and number\n formats, e.g. fr-FR\nemulate timezone <zone> an IANA timezone, e.g. Asia/Tokyo\nemulate <what> off stop one (dark or light off stops the color scheme); emulate off stops all\n\nPer tab, until turned off or the REPL exits. emulate on its own shows what is on.\n\nThe page sees the user agent, touch and navigator.languages from its next load: reload after\nturning mobile or locale on or off. While mobile is on, viewport refuses: the device sets the size.',
|
|
141
145
|
},
|
|
142
146
|
wait: {
|
|
143
147
|
usage: 'wait [text|request] <what> [--gone] [secs]',
|
|
144
148
|
summary: 'wait for an element, text, a response or a load (10s)',
|
|
145
|
-
detail: 'wait <selector> waits for a matching element; wait text <text> for text to be visible; wait request\n<url-part|glob> for a matching response, counting one that finished since the previous command began\n(so click, then wait request, does not miss it).\n\nwait load waits for the page to finish loading (its load event). A navigation that began since the\nprevious command began counts, so click a link, then wait load, waits for the new page.\n\n--gone waits instead until nothing matching is visible (removed or hidden): wait .spinner --gone.\n\nUp to 120s. A wait that times out is an error; the REPL carries on.',
|
|
149
|
+
detail: 'wait <selector> waits for a matching element; wait text <text> for text to be visible; wait request\n<url-part|glob> for a matching response, counting one that finished since the previous command began\n(so click, then wait request, does not miss it).\n\nwait load waits for the page to finish loading (its load event). A navigation that began since the\nprevious command began counts, so click a link, then wait load, waits for the new page. A page\nthat changes its own URL without loading (an app\'s own routing) is not a load: wait for something on\nthe new view instead.\n\nwait text matches any part of an element\'s text, in any case.\n\n--gone waits instead until nothing matching is visible (removed or hidden): wait .spinner --gone.\nIt is done at once if nothing matches yet, so wait for it to appear first if it may not have.\n\nUp to 120s. A wait that times out is an error; the REPL carries on.',
|
|
146
150
|
},
|
|
147
151
|
sleep: { usage: 'sleep <ms>', summary: 'wait a fixed time (maximum 3600000)' },
|
|
148
152
|
requests: {
|
|
@@ -158,7 +162,7 @@ const COMMANDS = {
|
|
|
158
162
|
console: {
|
|
159
163
|
usage: 'console [--all] [n] [filter]',
|
|
160
164
|
summary: 'the last n console messages and page errors',
|
|
161
|
-
detail: 'Recording starts when the REPL connects; the last 200 per tab are kept. Each line: time, [type],\ntext. Types are console levels (log, warning, error, ...) and pageerror for uncaught exceptions.\n\nfilter matches the type or the text, e.g. console error.',
|
|
165
|
+
detail: 'Recording starts when the REPL connects; the last 200 per tab are kept. Each line: time, [type],\ntext. Types are console levels (log, warning, error, ...) and pageerror for uncaught exceptions.\n\nfilter matches the type or the text, e.g. console error.\n\nA page brought back from the back/forward cache (back, forward) reports its earlier messages again,\nat the time it comes back, though nothing loaded again.',
|
|
162
166
|
},
|
|
163
167
|
capture: {
|
|
164
168
|
usage: 'capture on [requests|console] [secs] | off',
|
|
@@ -173,7 +177,7 @@ const COMMANDS = {
|
|
|
173
177
|
network: {
|
|
174
178
|
usage: 'network [on|off|slow [<ms> [<kbps>]]]',
|
|
175
179
|
summary: 'cut, slow or restore the tab\'s network',
|
|
176
|
-
detail: 'network off cuts it, like dropped wifi. Stopping a service is not the same: a dev proxy in front of\nit usually holds the request open, so the page spins instead of failing.\n\nnetwork slow
|
|
180
|
+
detail: 'network off cuts it, like dropped wifi. Stopping a service is not the same: a dev proxy in front of\nit usually holds the request open, so the page spins instead of failing.\n\nnetwork slow makes each request take at least <ms> (Chrome\'s latency is a minimum, not added to a\nslow server\'s own time) and limits its speed: by default as DevTools\' Slow 4G (563ms, 1440 kbps\ndown, 675 up); network slow <ms> [<kbps>] sets them, kbps both ways (left out, it stays Slow\n4G\'s). To slow one API, use route <glob> delay <secs>.\n\nPer tab; lasts until network on or the REPL exits. Routes still answer while it is off or slow.',
|
|
177
181
|
},
|
|
178
182
|
eval: {
|
|
179
183
|
usage: 'eval [--all] <JavaScript>',
|
package/lib/send.js
CHANGED
|
@@ -117,7 +117,8 @@ async function where(options) {
|
|
|
117
117
|
// use, so a REPL that will not start can be told apart from one that is not running.
|
|
118
118
|
const cdp = info?.browser || process.env.PW_CDP_URL || 'http://localhost:9222';
|
|
119
119
|
const version = await fetch(`${cdp}/json/version`, { signal: AbortSignal.timeout(2000) }).then(r => r.json()).catch(() => null);
|
|
120
|
-
|
|
120
|
+
// With no REPL running, it is only where one would connect, not a browser of a REPL that stopped.
|
|
121
|
+
const whose = info?.launched ? ', launched by this REPL' : info ? '' : '; run and serve connect here without --launch';
|
|
121
122
|
console.log(version ? `browser: ${cdp} answers (${version.Browser}${whose})` : `browser: nothing answers at ${cdp}; pw-repl run says how to start one`);
|
|
122
123
|
if (way.kind === 'server') {
|
|
123
124
|
const name = client.describe(way.endpoint);
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -59,9 +59,10 @@ pw-repl send -t 90 'screenshot -d 60' # wait longer than the 20s default
|
|
|
59
59
|
```
|
|
60
60
|
|
|
61
61
|
Always send commands with `pw-repl send`; don't type into the pane yourself. The words after `send`
|
|
62
|
-
are the command. For `fill`, `type`, `select` and `
|
|
63
|
-
(`pw-repl send fill "text=Your name" Ada`); other commands get the words as they are
|
|
64
|
-
(`pw-repl send eval "document.title + ' x'"`).
|
|
62
|
+
are the command. For `fill`, `type`, `select`, `press` and `upload`, a word quoted in your shell stays
|
|
63
|
+
one word (`pw-repl send fill "text=Your name" Ada`); other commands get the words as they are, joined by
|
|
64
|
+
spaces (`pw-repl send eval "document.title + ' x'"`). A command that takes only a selector takes the whole
|
|
65
|
+
line, spaces and all: `pw-repl send click "text=Your name"`. It works however the REPL was started:
|
|
65
66
|
|
|
66
67
|
- `run` (in tmux): `send` types the command into the tmux pane and reads the result back off the screen.
|
|
67
68
|
It types only when the pane's last line is a bare prompt, so nothing lands in a shell or in the middle
|
|
@@ -114,8 +115,9 @@ help is the command reference; this file does not repeat it.
|
|
|
114
115
|
one works too (`--headless=new`); nobody answers its dialogs but `dialog`.
|
|
115
116
|
- `PW_CHROME` — the Chromium `--launch` starts (default: Playwright's own, then one on the `PATH`).
|
|
116
117
|
- `PW_CDP_URL` — CDP endpoint (default `http://localhost:9222`).
|
|
117
|
-
- `PW_SCREENSHOT_DIR` — where screenshots
|
|
118
|
-
|
|
118
|
+
- `PW_SCREENSHOT_DIR` — where the REPL saves screenshots (default `/tmp`), read when it starts, not by
|
|
119
|
+
`send`. `screenshot` prints each file's path; other REPLs may save theirs there too. Remove only your
|
|
120
|
+
own, and keep those too if they are needed, e.g. as evidence or as something to hand over.
|
|
119
121
|
- `PW_ENDPOINT` / `PW_TMUX_SESSION` — defaults for `send -e` / `-s`. `PW_SOCKET` — the socket `send`
|
|
120
122
|
looks for when neither is given (default `/tmp/playwright-repl.sock`).
|
|
121
123
|
|