@guanzhu.me/pw-cli 0.0.1 → 0.0.3

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
@@ -21,6 +21,7 @@ Raw Playwright is excellent for test suites and scripted automation, but ad hoc
21
21
  - Named profile support
22
22
  - `run-code` for inline JavaScript or piped stdin
23
23
  - `run-script` for executing local JavaScript files
24
+ - `run-script` supports CommonJS-style scripts that use `require`, `module`, `exports`, `__filename`, and `__dirname`
24
25
  - Queue management for multi-step flows
25
26
  - Automatic browser launch when needed
26
27
  - XPath command conversion for common actions
@@ -73,6 +74,13 @@ Run a local script:
73
74
  pw-cli run-script ./scrape.js --url https://example.com
74
75
  ```
75
76
 
77
+ Reuse a page that was opened through `pw-cli open`:
78
+
79
+ ```bash
80
+ pw-cli open https://www.amazon.com
81
+ pw-cli run-script ./collect-rank-node.js "wireless earbuds" --pages 3
82
+ ```
83
+
76
84
  Use XPath with common commands:
77
85
 
78
86
  ```bash
package/bin/pw-cli.js CHANGED
@@ -760,12 +760,19 @@ async function main() {
760
760
  const openIdx = argv.indexOf('open');
761
761
  const afterOpen = argv.slice(openIdx + 1);
762
762
 
763
- // If a URL is provided with open, split into two steps to avoid goto timeout:
764
- // 1) spawn open (no URL) to start the browser daemon
765
- // 2) navigate via run-code with waitUntil:'domcontentloaded' + timeout:0
766
- // so redirects (login flows, SPA routing) never time out
763
+ // If a URL is provided with open:
764
+ // - If a session is already running: open a new tab directly via our CDP executor
765
+ // - Otherwise: spawn open (no URL) to start the browser, then navigate via playwright-cli run-code
766
+ // (lenient wait strategy so redirects/login flows/SPA routing never time out)
767
767
  const urlArg = afterOpen.find(a => !a.startsWith('-') && /^https?:\/\//.test(a));
768
768
  if (urlArg) {
769
+ const navCode = `async page => { const newPage = await page.context().newPage(); await newPage.goto(${JSON.stringify(urlArg)}, { waitUntil: 'domcontentloaded', timeout: 0 }); return newPage.url(); }`;
770
+ const alive = await isSessionAlive(session);
771
+ if (alive) {
772
+ // Browser already running — create a new tab directly, skip playwright-cli open
773
+ await handleRunCode(['run-code', navCode]);
774
+ return;
775
+ }
769
776
  const { spawnSync } = require('child_process');
770
777
  const openOnlyArgs = injectOpenDefaults(afterOpen.filter(a => a !== urlArg));
771
778
  const res = spawnSync(process.execPath, [cliPath, 'open', ...openOnlyArgs], {
@@ -776,8 +783,6 @@ async function main() {
776
783
  process.stderr.write('pw-cli: failed to open browser\n');
777
784
  process.exit(res.status || 1);
778
785
  }
779
- // Replace open+url with a run-code navigation (lenient wait strategy)
780
- const navCode = `async page => { await page.goto(${JSON.stringify(urlArg)}, { waitUntil: 'domcontentloaded', timeout: 0 }); return page.url(); }`;
781
786
  argv = ['run-code', navCode];
782
787
  } else {
783
788
  const enhanced = injectOpenDefaults(afterOpen);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guanzhu.me/pw-cli",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
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"
package/src/executor.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
+ const Module = require('module');
5
6
 
6
7
  function isFunctionExpression(code) {
7
8
  const text = code.trim();
@@ -32,11 +33,39 @@ async function runCode(code, globals) {
32
33
  return runFunctionExpression(code, globals);
33
34
  }
34
35
 
36
+ return runProgram(code, globals);
37
+ }
38
+
39
+ async function runProgram(code, globals) {
35
40
  const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
36
41
  const fn = new AsyncFunction(...Object.keys(globals), code);
37
42
  return fn.call(globals, ...Object.values(globals));
38
43
  }
39
44
 
45
+ async function withTemporaryGlobals(globals, fn) {
46
+ const previous = new Map();
47
+ const keys = Object.keys(globals);
48
+
49
+ for (const key of keys) {
50
+ const existed = Object.prototype.hasOwnProperty.call(globalThis, key);
51
+ previous.set(key, { existed, value: globalThis[key] });
52
+ globalThis[key] = globals[key];
53
+ }
54
+
55
+ try {
56
+ return await fn();
57
+ } finally {
58
+ for (const key of keys) {
59
+ const entry = previous.get(key);
60
+ if (!entry.existed) {
61
+ delete globalThis[key];
62
+ } else {
63
+ globalThis[key] = entry.value;
64
+ }
65
+ }
66
+ }
67
+ }
68
+
40
69
  async function execCode(code, { browser, context, page, playwright }) {
41
70
  const globals = {
42
71
  browser,
@@ -47,7 +76,7 @@ async function execCode(code, { browser, context, page, playwright }) {
47
76
  console,
48
77
  process,
49
78
  };
50
- return runCode(code, globals);
79
+ return withTemporaryGlobals(globals, () => runCode(code, globals));
51
80
  }
52
81
 
53
82
  async function execScript(scriptPath, scriptArgs, { browser, context, page, playwright }) {
@@ -59,19 +88,58 @@ async function execScript(scriptPath, scriptArgs, { browser, context, page, play
59
88
  }
60
89
 
61
90
  const code = fs.readFileSync(absPath, 'utf8');
91
+ const moduleDir = path.dirname(absPath);
92
+ const scriptModule = {
93
+ id: absPath,
94
+ filename: absPath,
95
+ path: moduleDir,
96
+ exports: {},
97
+ loaded: false,
98
+ children: [],
99
+ parent: require.main || module,
100
+ };
101
+ const localRequire = Module.createRequire(absPath);
102
+ const scriptRequire = function scriptRequire(id) {
103
+ try {
104
+ return localRequire(id);
105
+ } catch (error) {
106
+ if (
107
+ error &&
108
+ error.code === 'MODULE_NOT_FOUND' &&
109
+ typeof id === 'string' &&
110
+ !id.startsWith('.') &&
111
+ !path.isAbsolute(id)
112
+ ) {
113
+ return require(id);
114
+ }
115
+ throw error;
116
+ }
117
+ };
118
+
119
+ scriptRequire.resolve = localRequire.resolve.bind(localRequire);
120
+ scriptRequire.cache = require.cache;
121
+ scriptRequire.extensions = require.extensions;
122
+ scriptRequire.main = scriptModule;
123
+
124
+ scriptModule.require = scriptRequire;
125
+
62
126
  const globals = {
63
127
  browser,
64
128
  context,
65
129
  page,
66
130
  playwright,
67
131
  args: scriptArgs,
68
- require,
132
+ require: scriptRequire,
133
+ module: scriptModule,
134
+ exports: scriptModule.exports,
69
135
  console,
70
136
  process,
71
137
  __filename: absPath,
72
- __dirname: path.dirname(absPath),
138
+ __dirname: moduleDir,
73
139
  };
74
- return runCode(code, globals);
140
+ const result = await withTemporaryGlobals(globals, () => runProgram(code, globals));
141
+ scriptModule.loaded = true;
142
+ return result === undefined ? scriptModule.exports : result;
75
143
  }
76
144
 
77
145
  module.exports = { execCode, execScript };