@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 +8 -0
- package/bin/pw-cli.js +11 -6
- package/package.json +1 -1
- package/src/executor.js +72 -4
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
|
|
764
|
-
//
|
|
765
|
-
//
|
|
766
|
-
//
|
|
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
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:
|
|
138
|
+
__dirname: moduleDir,
|
|
73
139
|
};
|
|
74
|
-
|
|
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 };
|