@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 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 page/context/browser globals
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 page/context/browser globals
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('pw-cli: run-script requires a script path\n\nUsage: pw-cli run-script <file.js> [args...]\n');
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guanzhu.me/pw-cli",
3
- "version": "0.0.11",
3
+ "version": "0.0.13",
4
4
  "description": "Persistent Playwright browser CLI with headed defaults, profile support, queueing, and script execution",
5
5
  "bin": {
6
6
  "pw-cli": "./bin/pw-cli.js"
@@ -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
- let playwright;
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('No script path provided.\n\nUsage: pw-cli run-script <file.js> [args...]');
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 script with optional args
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, require, __filename, __dirname
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':
@@ -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.message}\n`);
90
+ process.stdout.write(`ERROR:${formatLaunchError(e)}\n`);
63
91
  process.exit(1);
64
92
  }
65
93
  })();