@guanzhu.me/pw-cli 0.0.11 → 0.0.13
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/README.md +49 -2
- package/bin/pw-cli.js +36 -2
- package/package.json +1 -1
- package/src/browser-manager.js +19 -6
- package/src/cli.js +37 -3
- package/src/launch-daemon.js +37 -9
package/README.md
CHANGED
|
@@ -74,6 +74,52 @@ Run a local script:
|
|
|
74
74
|
pw-cli run-script ./scrape.js --url https://example.com
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
+
`run-script` is intended for multi-step automation. Your script can use:
|
|
78
|
+
|
|
79
|
+
- Playwright globals: `page`, `context`, `browser`, `playwright`
|
|
80
|
+
- Script args: `args`
|
|
81
|
+
- CommonJS globals: `require`, `module`, `exports`, `__filename`, `__dirname`
|
|
82
|
+
|
|
83
|
+
More complete example:
|
|
84
|
+
|
|
85
|
+
```javascript
|
|
86
|
+
// scripts/extract-links.js
|
|
87
|
+
const fs = require('fs');
|
|
88
|
+
|
|
89
|
+
const url = args[args.indexOf('--url') + 1] || 'https://example.com';
|
|
90
|
+
const output = args[args.indexOf('--output') + 1] || 'links.json';
|
|
91
|
+
|
|
92
|
+
await page.goto(url, { waitUntil: 'networkidle' });
|
|
93
|
+
|
|
94
|
+
const links = await page.locator('a').evaluateAll(nodes =>
|
|
95
|
+
nodes
|
|
96
|
+
.map(a => ({
|
|
97
|
+
text: a.textContent.trim(),
|
|
98
|
+
href: a.href,
|
|
99
|
+
}))
|
|
100
|
+
.filter(item => item.href)
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
fs.writeFileSync(
|
|
104
|
+
output,
|
|
105
|
+
JSON.stringify(
|
|
106
|
+
{
|
|
107
|
+
url,
|
|
108
|
+
count: links.length,
|
|
109
|
+
links,
|
|
110
|
+
},
|
|
111
|
+
null,
|
|
112
|
+
2
|
|
113
|
+
)
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
return `saved ${links.length} links to ${output}`;
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
pw-cli run-script ./scripts/extract-links.js --url https://example.com --output links.json
|
|
121
|
+
```
|
|
122
|
+
|
|
77
123
|
Reuse a page that was opened through `pw-cli open`:
|
|
78
124
|
|
|
79
125
|
```bash
|
|
@@ -108,7 +154,7 @@ pw-cli list
|
|
|
108
154
|
- `open` injects headed and persistent defaults
|
|
109
155
|
- Browser-backed commands can auto-open a browser session if needed
|
|
110
156
|
- `run-code` accepts stdin and plain inline statements
|
|
111
|
-
- `run-script` executes a local `.js` file with Playwright globals
|
|
157
|
+
- `run-script` executes a local `.js` file with Playwright globals, CommonJS globals, and `args`
|
|
112
158
|
- Common element commands accept XPath refs
|
|
113
159
|
- `queue` lets you batch multiple commands and run them in order
|
|
114
160
|
|
|
@@ -235,7 +281,7 @@ console [min-level] list console messages
|
|
|
235
281
|
run-code <code> run playwright code snippet
|
|
236
282
|
pw-cli: reads code from stdin when <code> is omitted
|
|
237
283
|
pw-cli: wraps plain statements in an async function
|
|
238
|
-
run-script <file> [...] run a local JavaScript file with
|
|
284
|
+
run-script <file> [...] run a local JavaScript file with Playwright globals and script args
|
|
239
285
|
network list all network requests since loading the page
|
|
240
286
|
tracing-start start trace recording
|
|
241
287
|
tracing-stop stop trace recording
|
|
@@ -289,6 +335,7 @@ pw-cli open https://example.com
|
|
|
289
335
|
pw-cli run-code "await page.goto('https://example.com'); return await page.title()"
|
|
290
336
|
echo "return await page.url()" | pw-cli run-code
|
|
291
337
|
pw-cli run-script ./scripts/smoke.js --env prod
|
|
338
|
+
pw-cli run-script ./scripts/extract-links.js --url https://example.com --output links.json
|
|
292
339
|
pw-cli click "//button[contains(., 'Submit')]"
|
|
293
340
|
pw-cli queue add goto https://example.com
|
|
294
341
|
pw-cli queue add snapshot
|
package/bin/pw-cli.js
CHANGED
|
@@ -229,7 +229,7 @@ DevTools:
|
|
|
229
229
|
run-code <code> run playwright code snippet
|
|
230
230
|
pw-cli: reads code from stdin when <code> is omitted
|
|
231
231
|
pw-cli: wraps plain statements in an async function
|
|
232
|
-
run-script <file> [...] run a local JavaScript file with
|
|
232
|
+
run-script <file> [...] run a local JavaScript file with Playwright globals and script args
|
|
233
233
|
network list all network requests since loading the page
|
|
234
234
|
tracing-start start trace recording
|
|
235
235
|
tracing-stop stop trace recording
|
|
@@ -271,13 +271,40 @@ Examples:
|
|
|
271
271
|
pw-cli run-code "await page.goto('https://example.com'); return await page.title()"
|
|
272
272
|
echo "return await page.url()" | pw-cli run-code
|
|
273
273
|
pw-cli run-script .\\scripts\\smoke.js --env prod
|
|
274
|
+
pw-cli run-script .\\scripts\\extract-links.js --url https://example.com --output links.json
|
|
274
275
|
pw-cli click "//button[contains(., 'Submit')]"
|
|
275
276
|
pw-cli queue add goto https://example.com
|
|
276
277
|
pw-cli queue add snapshot
|
|
277
278
|
pw-cli queue run
|
|
279
|
+
|
|
280
|
+
run-script example:
|
|
281
|
+
// scripts/extract-links.js
|
|
282
|
+
const fs = require('fs');
|
|
283
|
+
const url = args[args.indexOf('--url') + 1] || 'https://example.com';
|
|
284
|
+
const output = args[args.indexOf('--output') + 1] || 'links.json';
|
|
285
|
+
await page.goto(url, { waitUntil: 'networkidle' });
|
|
286
|
+
const links = await page.locator('a').evaluateAll(nodes =>
|
|
287
|
+
nodes.map(a => ({ text: a.textContent.trim(), href: a.href })).filter(x => x.href)
|
|
288
|
+
);
|
|
289
|
+
fs.writeFileSync(output, JSON.stringify({ url, count: links.length, links }, null, 2));
|
|
290
|
+
return \`saved \${links.length} links to \${output}\`;
|
|
278
291
|
`.trim() + '\n');
|
|
279
292
|
}
|
|
280
293
|
|
|
294
|
+
function getRunScriptHelpText() {
|
|
295
|
+
return `Usage:
|
|
296
|
+
pw-cli run-script <file.js> [args...]
|
|
297
|
+
|
|
298
|
+
What the script receives:
|
|
299
|
+
- Playwright globals: page, context, browser, playwright
|
|
300
|
+
- Script args array: args
|
|
301
|
+
- CommonJS globals: require, module, exports, __filename, __dirname
|
|
302
|
+
|
|
303
|
+
Example:
|
|
304
|
+
pw-cli run-script ./scripts/extract-links.js --url https://example.com --output links.json
|
|
305
|
+
`;
|
|
306
|
+
}
|
|
307
|
+
|
|
281
308
|
// Management commands that don't need a running browser
|
|
282
309
|
const MGMT_COMMANDS = new Set([
|
|
283
310
|
'open', 'close', 'list', 'kill-all', 'close-all', 'delete-data',
|
|
@@ -631,7 +658,7 @@ async function handleRunScript(rawArgv) {
|
|
|
631
658
|
const [scriptPath, ...scriptArgs] = positionals;
|
|
632
659
|
|
|
633
660
|
if (!scriptPath) {
|
|
634
|
-
process.stderr.write(
|
|
661
|
+
process.stderr.write(`pw-cli: run-script requires a script path\n\n${getRunScriptHelpText()}`);
|
|
635
662
|
process.exit(1);
|
|
636
663
|
}
|
|
637
664
|
if (!fs.existsSync(path.resolve(scriptPath))) {
|
|
@@ -719,6 +746,13 @@ async function main() {
|
|
|
719
746
|
return;
|
|
720
747
|
}
|
|
721
748
|
|
|
749
|
+
if (command === 'run-scirpt') {
|
|
750
|
+
process.stderr.write('pw-cli: unknown command: run-scirpt\n');
|
|
751
|
+
process.stderr.write('Did you mean: run-script?\n\n');
|
|
752
|
+
process.stdout.write(getRunScriptHelpText());
|
|
753
|
+
process.exit(1);
|
|
754
|
+
}
|
|
755
|
+
|
|
722
756
|
// ── queue: batch actions and run them together ────────────────────────────
|
|
723
757
|
if (command === 'queue') {
|
|
724
758
|
await handleQueue(rawArgv);
|
package/package.json
CHANGED
package/src/browser-manager.js
CHANGED
|
@@ -5,6 +5,7 @@ const path = require('path');
|
|
|
5
5
|
const os = require('os');
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const crypto = require('crypto');
|
|
8
|
+
const { execSync } = require('child_process');
|
|
8
9
|
const { readState, writeState, clearState, getProfileDir } = require('./state');
|
|
9
10
|
const { probeCDP, findFreePort, sleep } = require('./utils');
|
|
10
11
|
|
|
@@ -43,6 +44,23 @@ function getPlaywrightCliCdpPort(sessionName = 'default') {
|
|
|
43
44
|
return session?.resolvedConfig?.browser?.launchOptions?.cdpPort || null;
|
|
44
45
|
}
|
|
45
46
|
|
|
47
|
+
function loadPlaywright() {
|
|
48
|
+
try {
|
|
49
|
+
return require('playwright');
|
|
50
|
+
} catch {}
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const globalRoot = execSync('npm root -g', {
|
|
54
|
+
encoding: 'utf8',
|
|
55
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
56
|
+
}).trim();
|
|
57
|
+
const cliPlaywrightPath = path.join(globalRoot, '@playwright', 'cli', 'node_modules', 'playwright');
|
|
58
|
+
return require(cliPlaywrightPath);
|
|
59
|
+
} catch {}
|
|
60
|
+
|
|
61
|
+
throw new Error('playwright is not installed. Run: npm install -g playwright');
|
|
62
|
+
}
|
|
63
|
+
|
|
46
64
|
// ---------------------------------------------------------------------------
|
|
47
65
|
// Our own CDP-based browser launcher (fallback when playwright-cli not running)
|
|
48
66
|
// ---------------------------------------------------------------------------
|
|
@@ -104,12 +122,7 @@ async function launchBrowser({ headless = false, profile = 'default', port: pref
|
|
|
104
122
|
// getConnection — tries playwright-cli browser first, then our own
|
|
105
123
|
// ---------------------------------------------------------------------------
|
|
106
124
|
async function getConnection({ headless = false, profile = 'default', port: preferredPort = 9223 } = {}) {
|
|
107
|
-
|
|
108
|
-
try {
|
|
109
|
-
playwright = require('playwright');
|
|
110
|
-
} catch {
|
|
111
|
-
throw new Error('playwright is not installed. Run: npm install -g playwright');
|
|
112
|
-
}
|
|
125
|
+
const playwright = loadPlaywright();
|
|
113
126
|
|
|
114
127
|
// 1. Try to reuse playwright-cli's browser via its CDP port
|
|
115
128
|
const cliCdpPort = getPlaywrightCliCdpPort();
|
package/src/cli.js
CHANGED
|
@@ -51,10 +51,23 @@ async function cmdRunCode(rest, opts) {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
function getRunScriptHelp() {
|
|
55
|
+
return `Usage:
|
|
56
|
+
pw-cli run-script <file.js> [args...]
|
|
57
|
+
|
|
58
|
+
What the script receives:
|
|
59
|
+
- Playwright globals: page, context, browser, playwright
|
|
60
|
+
- Script args array: args
|
|
61
|
+
- CommonJS globals: require, module, exports, __filename, __dirname
|
|
62
|
+
|
|
63
|
+
Example:
|
|
64
|
+
pw-cli run-script ./scripts/extract-links.js --url https://example.com --output links.json`;
|
|
65
|
+
}
|
|
66
|
+
|
|
54
67
|
async function cmdRunScript(rest, opts) {
|
|
55
68
|
const [scriptPath, ...scriptArgs] = rest;
|
|
56
69
|
if (!scriptPath) {
|
|
57
|
-
die(
|
|
70
|
+
die(`No script path provided.\n\n${getRunScriptHelp()}`);
|
|
58
71
|
}
|
|
59
72
|
|
|
60
73
|
const conn = await getConnection(opts);
|
|
@@ -105,20 +118,34 @@ GLOBAL OPTIONS
|
|
|
105
118
|
|
|
106
119
|
COMMANDS
|
|
107
120
|
run-code [code] Execute inline JS (reads stdin if omitted)
|
|
108
|
-
run-script <file> [...] Execute a .js
|
|
121
|
+
run-script <file> [...] Execute a local .js file with Playwright globals and script args
|
|
109
122
|
kill Stop the running browser
|
|
110
123
|
status Show browser status
|
|
111
124
|
|
|
112
125
|
SCRIPT GLOBALS
|
|
113
|
-
page, context, browser, playwright, args
|
|
126
|
+
page, context, browser, playwright, args
|
|
127
|
+
require, module, exports, __filename, __dirname
|
|
114
128
|
|
|
115
129
|
EXAMPLES
|
|
116
130
|
pw-cli run-code "await page.goto('https://example.com'); console.log(await page.title())"
|
|
117
131
|
echo "await page.screenshot({ path: 'out.png' })" | pw-cli run-code
|
|
118
132
|
pw-cli run-script ./scrape.js --url https://example.com
|
|
133
|
+
pw-cli run-script ./scripts/extract-links.js --url https://example.com --output links.json
|
|
119
134
|
pw-cli --headless run-code "await page.goto('https://example.com')"
|
|
120
135
|
pw-cli --profile work status
|
|
121
136
|
pw-cli kill
|
|
137
|
+
|
|
138
|
+
RUN-SCRIPT EXAMPLE
|
|
139
|
+
// scripts/extract-links.js
|
|
140
|
+
const fs = require('fs');
|
|
141
|
+
const url = args[args.indexOf('--url') + 1] || 'https://example.com';
|
|
142
|
+
const output = args[args.indexOf('--output') + 1] || 'links.json';
|
|
143
|
+
await page.goto(url, { waitUntil: 'networkidle' });
|
|
144
|
+
const links = await page.locator('a').evaluateAll(nodes =>
|
|
145
|
+
nodes.map(a => ({ text: a.textContent.trim(), href: a.href })).filter(x => x.href)
|
|
146
|
+
);
|
|
147
|
+
fs.writeFileSync(output, JSON.stringify({ url, count: links.length, links }, null, 2));
|
|
148
|
+
return \`saved \${links.length} links to \${output}\`;
|
|
122
149
|
`.trim());
|
|
123
150
|
}
|
|
124
151
|
|
|
@@ -131,6 +158,13 @@ async function run(argv) {
|
|
|
131
158
|
return;
|
|
132
159
|
}
|
|
133
160
|
|
|
161
|
+
if (command === 'run-scirpt') {
|
|
162
|
+
console.error('Unknown command: run-scirpt');
|
|
163
|
+
console.error('Did you mean: run-script?\n');
|
|
164
|
+
console.log(getRunScriptHelp());
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
|
|
134
168
|
try {
|
|
135
169
|
switch (command) {
|
|
136
170
|
case 'run-code':
|
package/src/launch-daemon.js
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
// Args: --profile-dir <dir> --port <port> [--headless]
|
|
7
7
|
|
|
8
8
|
const args = process.argv.slice(2);
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const { execSync } = require('child_process');
|
|
9
11
|
|
|
10
12
|
function getArg(name) {
|
|
11
13
|
const i = args.indexOf(name);
|
|
@@ -16,22 +18,48 @@ const profileDir = getArg('--profile-dir');
|
|
|
16
18
|
const port = parseInt(getArg('--port') || '9222', 10);
|
|
17
19
|
const headless = args.includes('--headless');
|
|
18
20
|
|
|
21
|
+
function loadPlaywright() {
|
|
22
|
+
try {
|
|
23
|
+
return require('playwright');
|
|
24
|
+
} catch {}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const globalRoot = execSync('npm root -g', {
|
|
28
|
+
encoding: 'utf8',
|
|
29
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
30
|
+
}).trim();
|
|
31
|
+
const cliPlaywrightPath = path.join(globalRoot, '@playwright', 'cli', 'node_modules', 'playwright');
|
|
32
|
+
return require(cliPlaywrightPath);
|
|
33
|
+
} catch {}
|
|
34
|
+
|
|
35
|
+
throw new Error('playwright not found - run: npm install -g playwright');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function formatLaunchError(error) {
|
|
39
|
+
const message = error && error.message ? error.message : String(error);
|
|
40
|
+
if (message.includes("Executable doesn't exist")) {
|
|
41
|
+
return [
|
|
42
|
+
'Playwright browser executable is not installed for the selected engine.',
|
|
43
|
+
'pw-cli now prefers your local Chrome install for fallback launches.',
|
|
44
|
+
'Run one of the following commands first:',
|
|
45
|
+
' pw-cli open',
|
|
46
|
+
' pw-cli install-browser',
|
|
47
|
+
' npx playwright install chromium',
|
|
48
|
+
].join('\n');
|
|
49
|
+
}
|
|
50
|
+
return message;
|
|
51
|
+
}
|
|
52
|
+
|
|
19
53
|
if (!profileDir) {
|
|
20
54
|
process.stderr.write('ERROR:missing --profile-dir\n');
|
|
21
55
|
process.exit(1);
|
|
22
56
|
}
|
|
23
57
|
|
|
24
58
|
(async () => {
|
|
25
|
-
let playwright;
|
|
26
|
-
try {
|
|
27
|
-
playwright = require('playwright');
|
|
28
|
-
} catch (e) {
|
|
29
|
-
process.stdout.write(`ERROR:playwright not found - run: npm install -g playwright\n`);
|
|
30
|
-
process.exit(1);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
59
|
try {
|
|
60
|
+
const playwright = loadPlaywright();
|
|
34
61
|
const context = await playwright.chromium.launchPersistentContext(profileDir, {
|
|
62
|
+
channel: 'chrome',
|
|
35
63
|
headless,
|
|
36
64
|
args: [`--remote-debugging-port=${port}`],
|
|
37
65
|
ignoreDefaultArgs: ['--enable-automation'],
|
|
@@ -59,7 +87,7 @@ if (!profileDir) {
|
|
|
59
87
|
// Prevent premature exit
|
|
60
88
|
process.stdin.resume();
|
|
61
89
|
} catch (e) {
|
|
62
|
-
process.stdout.write(`ERROR:${e
|
|
90
|
+
process.stdout.write(`ERROR:${formatLaunchError(e)}\n`);
|
|
63
91
|
process.exit(1);
|
|
64
92
|
}
|
|
65
93
|
})();
|