@guanzhu.me/pw-cli 0.0.14 → 0.0.15
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 +35 -39
- package/package.json +1 -1
- package/src/cli.js +8 -4
- package/src/executor.js +88 -26
- package/src/launch-daemon.js +1 -1
package/README.md
CHANGED
|
@@ -20,8 +20,8 @@ Raw Playwright is excellent for test suites and scripted automation, but ad hoc
|
|
|
20
20
|
- Headed mode by default
|
|
21
21
|
- Named profile support
|
|
22
22
|
- `run-code` for inline JavaScript or piped stdin
|
|
23
|
-
- `run-script` for executing local JavaScript files
|
|
24
|
-
- `run-script` supports CommonJS
|
|
23
|
+
- `run-script` for executing local JavaScript files with `main` function convention
|
|
24
|
+
- `run-script` supports standard CommonJS modules (`require`, `__filename`, `__dirname`) and also bare-code scripts
|
|
25
25
|
- Queue management for multi-step flows
|
|
26
26
|
- Automatic browser launch when needed
|
|
27
27
|
- XPath command conversion for common actions
|
|
@@ -74,46 +74,42 @@ 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.
|
|
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:
|
|
77
|
+
`run-script` is intended for multi-step automation. Define an `async function main` that receives Playwright globals as a single object:
|
|
84
78
|
|
|
85
79
|
```javascript
|
|
86
80
|
// scripts/extract-links.js
|
|
87
81
|
const fs = require('fs');
|
|
88
82
|
|
|
89
|
-
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
nodes
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
|
|
83
|
+
async function main({ page, args }) {
|
|
84
|
+
const url = args[args.indexOf('--url') + 1] || 'https://example.com';
|
|
85
|
+
const output = args[args.indexOf('--output') + 1] || 'links.json';
|
|
86
|
+
|
|
87
|
+
await page.goto(url, { waitUntil: 'domcontentloaded' });
|
|
88
|
+
|
|
89
|
+
const links = await page.locator('a').evaluateAll(nodes =>
|
|
90
|
+
nodes
|
|
91
|
+
.map(a => ({
|
|
92
|
+
text: a.textContent.trim(),
|
|
93
|
+
href: a.href,
|
|
94
|
+
}))
|
|
95
|
+
.filter(item => item.href)
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
fs.writeFileSync(
|
|
99
|
+
output,
|
|
100
|
+
JSON.stringify(
|
|
101
|
+
{
|
|
102
|
+
url,
|
|
103
|
+
count: links.length,
|
|
104
|
+
links,
|
|
105
|
+
},
|
|
106
|
+
null,
|
|
107
|
+
2
|
|
108
|
+
)
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
return `saved ${links.length} links to ${output}`;
|
|
112
|
+
}
|
|
117
113
|
```
|
|
118
114
|
|
|
119
115
|
```bash
|
|
@@ -154,7 +150,7 @@ pw-cli list
|
|
|
154
150
|
- `open` injects headed and persistent defaults
|
|
155
151
|
- Browser-backed commands can auto-open a browser session if needed
|
|
156
152
|
- `run-code` accepts stdin and plain inline statements
|
|
157
|
-
- `run-script` executes a local `.js` file
|
|
153
|
+
- `run-script` executes a local `.js` file — auto-detects `main` function, `module.exports`, or bare code
|
|
158
154
|
- Common element commands accept XPath refs
|
|
159
155
|
- `queue` lets you batch multiple commands and run them in order
|
|
160
156
|
|
|
@@ -281,7 +277,7 @@ console [min-level] list console messages
|
|
|
281
277
|
run-code <code> run playwright code snippet
|
|
282
278
|
pw-cli: reads code from stdin when <code> is omitted
|
|
283
279
|
pw-cli: wraps plain statements in an async function
|
|
284
|
-
run-script <file> [...] run a local JavaScript file
|
|
280
|
+
run-script <file> [...] run a local JavaScript file (main function or module.exports)
|
|
285
281
|
network list all network requests since loading the page
|
|
286
282
|
tracing-start start trace recording
|
|
287
283
|
tracing-stop stop trace recording
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -55,10 +55,14 @@ function getRunScriptHelp() {
|
|
|
55
55
|
return `Usage:
|
|
56
56
|
pw-cli run-script <file.js> [args...]
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
Script format (standard module):
|
|
59
|
+
module.exports = async function ({ page, context, browser, playwright, args }) {
|
|
60
|
+
// your code here
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
The exported function receives { page, context, browser, playwright, args }.
|
|
64
|
+
CommonJS globals (require, __filename, __dirname) are available as usual.
|
|
65
|
+
Legacy bare-code scripts (without module.exports) are still supported.
|
|
62
66
|
|
|
63
67
|
Example:
|
|
64
68
|
pw-cli run-script ./scripts/extract-links.js --url https://example.com --output links.json`;
|
package/src/executor.js
CHANGED
|
@@ -79,25 +79,7 @@ async function execCode(code, { browser, context, page, playwright }) {
|
|
|
79
79
|
return withTemporaryGlobals(globals, () => runCode(code, globals));
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
const absPath = path.resolve(scriptPath);
|
|
84
|
-
if (!fs.existsSync(absPath)) {
|
|
85
|
-
const err = new Error(`Script not found: ${absPath}`);
|
|
86
|
-
err.code = 'ENOENT';
|
|
87
|
-
throw err;
|
|
88
|
-
}
|
|
89
|
-
|
|
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
|
-
};
|
|
82
|
+
function buildScriptRequire(absPath) {
|
|
101
83
|
const localRequire = Module.createRequire(absPath);
|
|
102
84
|
const scriptRequire = function scriptRequire(id) {
|
|
103
85
|
try {
|
|
@@ -119,16 +101,92 @@ async function execScript(scriptPath, scriptArgs, { browser, context, page, play
|
|
|
119
101
|
scriptRequire.resolve = localRequire.resolve.bind(localRequire);
|
|
120
102
|
scriptRequire.cache = require.cache;
|
|
121
103
|
scriptRequire.extensions = require.extensions;
|
|
104
|
+
return scriptRequire;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function isModuleExport(code) {
|
|
108
|
+
return /\bmodule\.exports\b/.test(code) || /\bexports\./.test(code);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function hasMainFunction(code) {
|
|
112
|
+
return /\b(async\s+)?function\s+main\s*\(/.test(code);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function execScript(scriptPath, scriptArgs, { browser, context, page, playwright }) {
|
|
116
|
+
const absPath = path.resolve(scriptPath);
|
|
117
|
+
if (!fs.existsSync(absPath)) {
|
|
118
|
+
const err = new Error(`Script not found: ${absPath}`);
|
|
119
|
+
err.code = 'ENOENT';
|
|
120
|
+
throw err;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const code = fs.readFileSync(absPath, 'utf8');
|
|
124
|
+
const pwGlobals = { page, context, browser, playwright, args: scriptArgs };
|
|
125
|
+
|
|
126
|
+
// Standard module pattern: script uses module.exports = function(...)
|
|
127
|
+
// The exported function receives Playwright globals as a single object argument.
|
|
128
|
+
if (isModuleExport(code)) {
|
|
129
|
+
return execModuleScript(absPath, code, pwGlobals);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Legacy bare-code pattern: script body is executed directly with globals as local variables.
|
|
133
|
+
return execBareScript(absPath, code, pwGlobals);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function execModuleScript(absPath, code, pwGlobals) {
|
|
137
|
+
const moduleDir = path.dirname(absPath);
|
|
138
|
+
const scriptRequire = buildScriptRequire(absPath);
|
|
139
|
+
const scriptModule = {
|
|
140
|
+
id: absPath,
|
|
141
|
+
filename: absPath,
|
|
142
|
+
path: moduleDir,
|
|
143
|
+
exports: {},
|
|
144
|
+
loaded: false,
|
|
145
|
+
children: [],
|
|
146
|
+
parent: require.main || module,
|
|
147
|
+
require: scriptRequire,
|
|
148
|
+
};
|
|
122
149
|
scriptRequire.main = scriptModule;
|
|
123
150
|
|
|
124
|
-
|
|
151
|
+
// Evaluate the module body to populate module.exports.
|
|
152
|
+
// Playwright globals are available during evaluation for backward compat.
|
|
153
|
+
const wrapGlobals = {
|
|
154
|
+
...pwGlobals,
|
|
155
|
+
require: scriptRequire,
|
|
156
|
+
module: scriptModule,
|
|
157
|
+
exports: scriptModule.exports,
|
|
158
|
+
console,
|
|
159
|
+
process,
|
|
160
|
+
__filename: absPath,
|
|
161
|
+
__dirname: moduleDir,
|
|
162
|
+
};
|
|
163
|
+
await withTemporaryGlobals(wrapGlobals, () => runProgram(code, wrapGlobals));
|
|
164
|
+
scriptModule.loaded = true;
|
|
165
|
+
|
|
166
|
+
const exported = scriptModule.exports;
|
|
167
|
+
if (typeof exported === 'function') {
|
|
168
|
+
return exported(pwGlobals);
|
|
169
|
+
}
|
|
170
|
+
return exported;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function execBareScript(absPath, code, pwGlobals) {
|
|
174
|
+
const moduleDir = path.dirname(absPath);
|
|
175
|
+
const scriptRequire = buildScriptRequire(absPath);
|
|
176
|
+
const scriptModule = {
|
|
177
|
+
id: absPath,
|
|
178
|
+
filename: absPath,
|
|
179
|
+
path: moduleDir,
|
|
180
|
+
exports: {},
|
|
181
|
+
loaded: false,
|
|
182
|
+
children: [],
|
|
183
|
+
parent: require.main || module,
|
|
184
|
+
require: scriptRequire,
|
|
185
|
+
};
|
|
186
|
+
scriptRequire.main = scriptModule;
|
|
125
187
|
|
|
126
188
|
const globals = {
|
|
127
|
-
|
|
128
|
-
context,
|
|
129
|
-
page,
|
|
130
|
-
playwright,
|
|
131
|
-
args: scriptArgs,
|
|
189
|
+
...pwGlobals,
|
|
132
190
|
require: scriptRequire,
|
|
133
191
|
module: scriptModule,
|
|
134
192
|
exports: scriptModule.exports,
|
|
@@ -137,7 +195,11 @@ async function execScript(scriptPath, scriptArgs, { browser, context, page, play
|
|
|
137
195
|
__filename: absPath,
|
|
138
196
|
__dirname: moduleDir,
|
|
139
197
|
};
|
|
140
|
-
|
|
198
|
+
// If the script defines a main function, append a call to it.
|
|
199
|
+
const finalCode = hasMainFunction(code)
|
|
200
|
+
? code + '\nreturn main({ page, context, browser, playwright, args });'
|
|
201
|
+
: code;
|
|
202
|
+
const result = await withTemporaryGlobals(globals, () => runProgram(finalCode, globals));
|
|
141
203
|
scriptModule.loaded = true;
|
|
142
204
|
return result === undefined ? scriptModule.exports : result;
|
|
143
205
|
}
|
package/src/launch-daemon.js
CHANGED
|
@@ -62,7 +62,7 @@ if (!profileDir) {
|
|
|
62
62
|
channel: 'chrome',
|
|
63
63
|
headless,
|
|
64
64
|
args: [`--remote-debugging-port=${port}`],
|
|
65
|
-
ignoreDefaultArgs: ['--enable-automation'],
|
|
65
|
+
ignoreDefaultArgs: ['--enable-automation', '--no-sandbox'],
|
|
66
66
|
});
|
|
67
67
|
|
|
68
68
|
// Wait briefly for CDP to be ready, then signal
|