browser-commander 0.11.0 → 0.13.0
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/CHANGELOG.md +16 -0
- package/README.md +104 -0
- package/examples/pdf-generation.js +7 -4
- package/package.json +1 -1
- package/scripts/run-tests.mjs +19 -1
- package/src/README.md +13 -0
- package/src/bindings.js +8 -0
- package/src/browser/connector.js +163 -0
- package/src/browser/launcher.js +17 -0
- package/src/browser/navigation.js +58 -0
- package/src/browser/real-browser.js +439 -0
- package/src/browser/storage-state.js +133 -0
- package/src/core/navigation-manager.js +57 -24
- package/src/exports.js +4 -0
- package/tests/e2e/playwright.e2e.test.js +19 -0
- package/tests/e2e/puppeteer.e2e.test.js +19 -0
- package/tests/helpers/mocks.js +3 -0
- package/tests/unit/bindings.test.js +4 -0
- package/tests/unit/browser/connector.test.js +129 -0
- package/tests/unit/browser/navigation.test.js +61 -0
- package/tests/unit/browser/real-browser.test.js +165 -0
- package/tests/unit/browser/storage-state.test.js +148 -0
- package/tests/unit/core/navigation-manager.test.js +62 -0
- package/tests/unit/scripts/ci-workflow-policy.test.js +73 -0
- package/tests/unit/scripts/npm-release-helpers.test.js +20 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.13.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 46d0049: Add CDP attachment through `connectBrowser()` for Playwright and Puppeteer, plus `launchAndConnectRealBrowser()` for starting an installed Chrome-family browser with a dedicated automation profile.
|
|
8
|
+
|
|
9
|
+
## 0.12.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- Add `commander.setContent()` for safely loading in-memory HTML through the managed navigation lifecycle.
|
|
14
|
+
|
|
15
|
+
Add portable cookie and localStorage restoration through `launchBrowser({ storageState })` and the `saveStorageState()` helper for Playwright and Puppeteer.
|
|
16
|
+
|
|
17
|
+
Avoid intermittent Node 24 test-runner transport failures on macOS by running test files without child-process isolation.
|
|
18
|
+
|
|
3
19
|
## 0.11.0
|
|
4
20
|
|
|
5
21
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -82,6 +82,12 @@ commander.pageTrigger({
|
|
|
82
82
|
// 4. Navigate - action auto-starts when page is ready
|
|
83
83
|
await commander.goto({ url: 'https://example.com' });
|
|
84
84
|
|
|
85
|
+
// Or safely replace the document with in-memory HTML
|
|
86
|
+
await commander.setContent({
|
|
87
|
+
html: '<h1>Hi</h1>',
|
|
88
|
+
waitUntil: 'networkidle',
|
|
89
|
+
});
|
|
90
|
+
|
|
85
91
|
// 5. Cleanup
|
|
86
92
|
await commander.destroy();
|
|
87
93
|
await browser.close();
|
|
@@ -100,6 +106,57 @@ const { browser, page } = await launchBrowser({
|
|
|
100
106
|
});
|
|
101
107
|
```
|
|
102
108
|
|
|
109
|
+
Attach to a Chrome-family browser that is already listening for CDP connections:
|
|
110
|
+
|
|
111
|
+
```javascript
|
|
112
|
+
import { connectBrowser, makeBrowserCommander } from 'browser-commander';
|
|
113
|
+
|
|
114
|
+
const { browser, page } = await connectBrowser({
|
|
115
|
+
engine: 'playwright', // or 'puppeteer'
|
|
116
|
+
cdpEndpoint: 'http://127.0.0.1:9222',
|
|
117
|
+
});
|
|
118
|
+
const commander = makeBrowserCommander({ page });
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`launchAndConnectRealBrowser()` can find and start a genuine installed Chrome,
|
|
122
|
+
Edge, Brave, or Chromium with a loopback CDP endpoint and then attach to it. It
|
|
123
|
+
always uses a dedicated profile; Chrome 136 and newer do not honor remote
|
|
124
|
+
debugging switches for the default profile. See the
|
|
125
|
+
[Chrome remote-debugging security change](https://developer.chrome.com/blog/remote-debugging-port).
|
|
126
|
+
|
|
127
|
+
```javascript
|
|
128
|
+
import { launchAndConnectRealBrowser } from 'browser-commander';
|
|
129
|
+
|
|
130
|
+
const connection = await launchAndConnectRealBrowser({
|
|
131
|
+
engine: 'puppeteer',
|
|
132
|
+
channel: 'chrome',
|
|
133
|
+
userDataDir: '/tmp/my-automation-profile',
|
|
134
|
+
seedCookies: [
|
|
135
|
+
{ name: 'session', value: 'saved', url: 'https://example.com' },
|
|
136
|
+
],
|
|
137
|
+
});
|
|
138
|
+
await connection.browser.close();
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Cookie seeding copies only cookies you explicitly provide; the helper does not
|
|
142
|
+
read, decrypt, or expose cookies from a browser's default profile.
|
|
143
|
+
|
|
144
|
+
Reuse a saved authenticated session by passing Playwright-compatible storage
|
|
145
|
+
state as a JSON file path or object. Cookies and localStorage are restored for
|
|
146
|
+
both engines:
|
|
147
|
+
|
|
148
|
+
```javascript
|
|
149
|
+
import { launchBrowser, saveStorageState } from 'browser-commander';
|
|
150
|
+
|
|
151
|
+
const { browser, page } = await launchBrowser({
|
|
152
|
+
engine: 'playwright',
|
|
153
|
+
storageState: './gmail-state.json',
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// Save the current session for a later launch.
|
|
157
|
+
await saveStorageState(page, './gmail-state.json');
|
|
158
|
+
```
|
|
159
|
+
|
|
103
160
|
## Browser Commander Tests
|
|
104
161
|
|
|
105
162
|
`browser-commander/tests` adds browser fixtures and scheduling helpers on top of
|
|
@@ -271,11 +328,58 @@ const { browser, page } = await launchBrowser({
|
|
|
271
328
|
slowMo: 150, // Slow down operations (ms)
|
|
272
329
|
verbose: false, // Enable debug logging
|
|
273
330
|
args: ['--no-sandbox', '--disable-setuid-sandbox'], // Custom Chrome args to append
|
|
331
|
+
storageState: './session-state.json', // Saved cookies and localStorage, as a path or object
|
|
274
332
|
});
|
|
275
333
|
```
|
|
276
334
|
|
|
277
335
|
The `args` option allows passing custom Chrome arguments, which is useful for headless server environments (Docker, CI/CD) that require flags like `--no-sandbox`.
|
|
278
336
|
|
|
337
|
+
The `storageState` option accepts a Playwright-compatible JSON path or object.
|
|
338
|
+
Each engine restores its cookies and origin-specific localStorage before
|
|
339
|
+
navigation, including when Playwright uses a persistent context.
|
|
340
|
+
|
|
341
|
+
### connectBrowser(options)
|
|
342
|
+
|
|
343
|
+
Connect to an existing Chrome-family browser over an HTTP or WebSocket CDP
|
|
344
|
+
endpoint. Exactly one of `cdpEndpoint` and `wsEndpoint` is required. The raw
|
|
345
|
+
`browser` and `page` work with both the underlying engine API and
|
|
346
|
+
`makeBrowserCommander({ page })`.
|
|
347
|
+
|
|
348
|
+
```javascript
|
|
349
|
+
const { browser, page } = await connectBrowser({
|
|
350
|
+
engine: 'playwright',
|
|
351
|
+
wsEndpoint: 'ws://127.0.0.1:9222/devtools/browser/<id>',
|
|
352
|
+
timeout: 30_000,
|
|
353
|
+
seedCookies: [
|
|
354
|
+
{ name: 'session', value: 'saved', url: 'https://example.com' },
|
|
355
|
+
],
|
|
356
|
+
});
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
Playwright accepts `timeout` and Puppeteer accepts `protocolTimeout`.
|
|
360
|
+
`storageState` can also seed Playwright-compatible cookies and localStorage.
|
|
361
|
+
|
|
362
|
+
### launchAndConnectRealBrowser(options)
|
|
363
|
+
|
|
364
|
+
Start an installed browser and connect through `connectBrowser()`. Use
|
|
365
|
+
`channel` (`chrome`, `chrome-beta`, `chrome-dev`, `chrome-canary`, `msedge`,
|
|
366
|
+
`msedge-beta`, `msedge-dev`, `msedge-canary`, `brave`, or `chromium`) or an
|
|
367
|
+
explicit `executablePath`. The helper defaults to
|
|
368
|
+
a managed directory under `~/.browser-commander/real-browser/`, rejects known
|
|
369
|
+
default browser-profile paths, protects its remote-debugging arguments, and
|
|
370
|
+
returns the spawned `browserProcess`, resolved `cdpEndpoint`, executable path,
|
|
371
|
+
and profile path alongside `{ browser, page }`.
|
|
372
|
+
|
|
373
|
+
### saveStorageState(page, filePath)
|
|
374
|
+
|
|
375
|
+
Save the current cookies and localStorage in Playwright's portable storage
|
|
376
|
+
state format. The helper supports pages from either engine and also returns the
|
|
377
|
+
saved object:
|
|
378
|
+
|
|
379
|
+
```javascript
|
|
380
|
+
const state = await saveStorageState(page, './session-state.json');
|
|
381
|
+
```
|
|
382
|
+
|
|
279
383
|
The `colorScheme` option allows setting the initial color scheme (`'light'`, `'dark'`, or `'no-preference'`) at launch time for screenshot services and testing tools:
|
|
280
384
|
|
|
281
385
|
```javascript
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Example: PDF generation via browser-commander
|
|
3
3
|
*
|
|
4
|
-
* This example shows how to
|
|
5
|
-
*
|
|
4
|
+
* This example shows how to load in-memory HTML and generate a PDF through the
|
|
5
|
+
* unified commander API.
|
|
6
6
|
*
|
|
7
7
|
* Supports both Playwright and Puppeteer engines.
|
|
8
8
|
*
|
|
@@ -45,8 +45,11 @@ async function generatePdf() {
|
|
|
45
45
|
try {
|
|
46
46
|
const commander = makeBrowserCommander({ page, verbose: false });
|
|
47
47
|
|
|
48
|
-
//
|
|
49
|
-
await
|
|
48
|
+
// Load HTML assembled in memory using the managed navigation lifecycle.
|
|
49
|
+
await commander.setContent({
|
|
50
|
+
html: '<main><h1>Browser Commander</h1><p>Generated in memory.</p></main>',
|
|
51
|
+
waitUntil: 'load',
|
|
52
|
+
});
|
|
50
53
|
|
|
51
54
|
// Generate PDF using the unified API (no workarounds needed!)
|
|
52
55
|
const pdfBuffer = await commander.pdf({
|
package/package.json
CHANGED
package/scripts/run-tests.mjs
CHANGED
|
@@ -137,9 +137,27 @@ export function resolveTestFiles(targets, cwd = process.cwd()) {
|
|
|
137
137
|
return uniqueFiles.map((file) => relative(cwd, file));
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
|
|
140
|
+
function supportsTestIsolation(nodeVersion) {
|
|
141
|
+
const majorVersion = Number.parseInt(nodeVersion.split('.')[0], 10);
|
|
142
|
+
return Number.isInteger(majorVersion) && majorVersion >= 24;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function buildNodeTestArgs(
|
|
146
|
+
options,
|
|
147
|
+
testFiles,
|
|
148
|
+
nodeVersion = process.versions.node
|
|
149
|
+
) {
|
|
141
150
|
const args = ['--test'];
|
|
142
151
|
|
|
152
|
+
// Node's default process isolation serializes each test file's results over
|
|
153
|
+
// a child-process channel. Node 24 on macOS can intermittently corrupt that
|
|
154
|
+
// stream (ERR_TEST_FAILURE: "Unable to deserialize cloned data"). Running
|
|
155
|
+
// supported runtimes in-process removes that transport while keeping the
|
|
156
|
+
// Node 20-compatible fallback for consumers on the minimum engine version.
|
|
157
|
+
if (supportsTestIsolation(nodeVersion)) {
|
|
158
|
+
args.push('--test-isolation=none');
|
|
159
|
+
}
|
|
160
|
+
|
|
143
161
|
if (options.coverage) {
|
|
144
162
|
args.push('--experimental-test-coverage');
|
|
145
163
|
}
|
package/src/README.md
CHANGED
|
@@ -417,6 +417,19 @@ await commander.goto({
|
|
|
417
417
|
});
|
|
418
418
|
```
|
|
419
419
|
|
|
420
|
+
### commander.setContent(options)
|
|
421
|
+
|
|
422
|
+
Load an in-memory HTML document while preserving the same page-trigger cleanup
|
|
423
|
+
and readiness guarantees as `goto()`:
|
|
424
|
+
|
|
425
|
+
```javascript
|
|
426
|
+
await commander.setContent({
|
|
427
|
+
html: '<h1>Hi</h1>',
|
|
428
|
+
waitUntil: 'networkidle', // Playwright/Puppeteer option
|
|
429
|
+
timeout: 60000,
|
|
430
|
+
});
|
|
431
|
+
```
|
|
432
|
+
|
|
420
433
|
### commander.clickButton(options)
|
|
421
434
|
|
|
422
435
|
```javascript
|
package/src/bindings.js
CHANGED
|
@@ -8,6 +8,7 @@ import { getUrl, unfocusAddressBar } from './utilities/url.js';
|
|
|
8
8
|
import {
|
|
9
9
|
waitForUrlStabilization,
|
|
10
10
|
goto,
|
|
11
|
+
setContent,
|
|
11
12
|
waitForNavigation,
|
|
12
13
|
waitForPageReady,
|
|
13
14
|
waitAfterAction,
|
|
@@ -115,6 +116,12 @@ export function createBoundFunctions(options = {}) {
|
|
|
115
116
|
waitForUrlStabilization: waitForUrlStabilizationBound,
|
|
116
117
|
navigationManager,
|
|
117
118
|
});
|
|
119
|
+
const setContentBound = (opts) =>
|
|
120
|
+
setContent({
|
|
121
|
+
...opts,
|
|
122
|
+
page,
|
|
123
|
+
navigationManager,
|
|
124
|
+
});
|
|
118
125
|
const waitForNavigationBound = (opts) =>
|
|
119
126
|
waitForNavigation({
|
|
120
127
|
...opts,
|
|
@@ -312,6 +319,7 @@ export function createBoundFunctions(options = {}) {
|
|
|
312
319
|
querySelectorAll: querySelectorAllBound,
|
|
313
320
|
waitForUrlStabilization: waitForUrlStabilizationBound,
|
|
314
321
|
goto: gotoBound,
|
|
322
|
+
setContent: setContentBound,
|
|
315
323
|
getUrl: getUrlBound,
|
|
316
324
|
waitForNavigation: waitForNavigationBound,
|
|
317
325
|
waitForPageReady: waitForPageReadyBound,
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import {
|
|
2
|
+
loadStorageState,
|
|
3
|
+
restorePlaywrightStorageState,
|
|
4
|
+
restorePuppeteerStorageState,
|
|
5
|
+
} from './storage-state.js';
|
|
6
|
+
|
|
7
|
+
function validateConnectionOptions({ engine, cdpEndpoint, wsEndpoint }) {
|
|
8
|
+
if (!['playwright', 'puppeteer'].includes(engine)) {
|
|
9
|
+
throw new Error(
|
|
10
|
+
`Invalid engine: ${engine}. Expected 'playwright' or 'puppeteer'`
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (Boolean(cdpEndpoint) === Boolean(wsEndpoint)) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
'connectBrowser requires exactly one of cdpEndpoint or wsEndpoint'
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function addDefinedOptions(options, values) {
|
|
22
|
+
for (const [key, value] of Object.entries(values)) {
|
|
23
|
+
if (value !== undefined) {
|
|
24
|
+
options[key] = value;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return options;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function buildPlaywrightConnectOptions({ slowMo, timeout, headers }) {
|
|
31
|
+
return addDefinedOptions({}, { slowMo, timeout, headers });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function buildPuppeteerConnectOptions({
|
|
35
|
+
cdpEndpoint,
|
|
36
|
+
wsEndpoint,
|
|
37
|
+
slowMo,
|
|
38
|
+
protocolTimeout,
|
|
39
|
+
headers,
|
|
40
|
+
}) {
|
|
41
|
+
return addDefinedOptions(
|
|
42
|
+
{
|
|
43
|
+
...(cdpEndpoint
|
|
44
|
+
? { browserURL: cdpEndpoint }
|
|
45
|
+
: { browserWSEndpoint: wsEndpoint }),
|
|
46
|
+
defaultViewport: null,
|
|
47
|
+
},
|
|
48
|
+
{ slowMo, protocolTimeout, headers }
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function prepareStorageState({ storageState, seedCookies }) {
|
|
53
|
+
if (seedCookies !== undefined && !Array.isArray(seedCookies)) {
|
|
54
|
+
throw new TypeError('seedCookies must be an array');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const resolvedStorageState = await loadStorageState(storageState);
|
|
58
|
+
if (!resolvedStorageState && seedCookies === undefined) {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
...(resolvedStorageState ?? {}),
|
|
64
|
+
cookies: [...(resolvedStorageState?.cookies ?? []), ...(seedCookies ?? [])],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function connectPlaywright({ options, loadPlaywright, storageState }) {
|
|
69
|
+
const { chromium } = await loadPlaywright();
|
|
70
|
+
const endpoint = options.cdpEndpoint ?? options.wsEndpoint;
|
|
71
|
+
const browser = await chromium.connectOverCDP(
|
|
72
|
+
endpoint,
|
|
73
|
+
buildPlaywrightConnectOptions(options)
|
|
74
|
+
);
|
|
75
|
+
const context = browser.contexts()[0];
|
|
76
|
+
if (!context) {
|
|
77
|
+
throw new Error('Connected Playwright browser has no default context');
|
|
78
|
+
}
|
|
79
|
+
const page = context.pages()[0] ?? (await context.newPage());
|
|
80
|
+
|
|
81
|
+
await restorePlaywrightStorageState({ context, storageState });
|
|
82
|
+
return { browser, page };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function connectPuppeteer({ options, loadPuppeteer, storageState }) {
|
|
86
|
+
const puppeteerModule = await loadPuppeteer();
|
|
87
|
+
const puppeteer = puppeteerModule.default ?? puppeteerModule;
|
|
88
|
+
const browser = await puppeteer.connect(
|
|
89
|
+
buildPuppeteerConnectOptions(options)
|
|
90
|
+
);
|
|
91
|
+
const pages = await browser.pages();
|
|
92
|
+
const page = pages[0] ?? (await browser.newPage());
|
|
93
|
+
|
|
94
|
+
await restorePuppeteerStorageState({ page, storageState });
|
|
95
|
+
return { browser, page };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Attach to an already-running Chromium-family browser over CDP.
|
|
100
|
+
*
|
|
101
|
+
* @param {Object} options - Connection options
|
|
102
|
+
* @param {'playwright'|'puppeteer'} [options.engine='playwright'] - Automation engine
|
|
103
|
+
* @param {string} [options.cdpEndpoint] - HTTP DevTools endpoint, such as http://127.0.0.1:9222
|
|
104
|
+
* @param {string} [options.wsEndpoint] - DevTools browser WebSocket endpoint
|
|
105
|
+
* @param {number} [options.slowMo] - Delay engine operations by this many milliseconds
|
|
106
|
+
* @param {number} [options.timeout] - Playwright connection timeout in milliseconds
|
|
107
|
+
* @param {number} [options.protocolTimeout] - Puppeteer CDP call timeout in milliseconds
|
|
108
|
+
* @param {Object<string,string>} [options.headers] - Additional connection headers
|
|
109
|
+
* @param {Object[]|string|Object} [options.storageState] - Playwright-compatible state path or object
|
|
110
|
+
* @param {Object[]} [options.seedCookies] - Cookies to seed after connecting
|
|
111
|
+
* @param {boolean} [options.verbose=false] - Enable connection logging
|
|
112
|
+
* @returns {Promise<{browser: Object, page: Object}>} Raw browser and page handles
|
|
113
|
+
*/
|
|
114
|
+
export async function connectBrowser(options = {}) {
|
|
115
|
+
return await connectBrowserWithDependencies(options);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Dependency-injected implementation used by the public connector and tests.
|
|
120
|
+
*
|
|
121
|
+
* @param {Object} options - See {@link connectBrowser}
|
|
122
|
+
* @param {Object} dependencies - Optional engine module loaders
|
|
123
|
+
* @returns {Promise<{browser: Object, page: Object}>} Raw browser and page handles
|
|
124
|
+
*/
|
|
125
|
+
export async function connectBrowserWithDependencies(
|
|
126
|
+
options = {},
|
|
127
|
+
dependencies = {}
|
|
128
|
+
) {
|
|
129
|
+
const normalizedOptions = {
|
|
130
|
+
engine: 'playwright',
|
|
131
|
+
verbose: false,
|
|
132
|
+
...options,
|
|
133
|
+
};
|
|
134
|
+
validateConnectionOptions(normalizedOptions);
|
|
135
|
+
|
|
136
|
+
const storageState = await prepareStorageState(normalizedOptions);
|
|
137
|
+
const { engine, verbose } = normalizedOptions;
|
|
138
|
+
if (verbose) {
|
|
139
|
+
console.log(`Connecting to browser with ${engine} engine...`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const result =
|
|
143
|
+
engine === 'playwright'
|
|
144
|
+
? await connectPlaywright({
|
|
145
|
+
options: normalizedOptions,
|
|
146
|
+
loadPlaywright:
|
|
147
|
+
dependencies.loadPlaywright ?? (() => import('playwright')),
|
|
148
|
+
storageState,
|
|
149
|
+
})
|
|
150
|
+
: await connectPuppeteer({
|
|
151
|
+
options: normalizedOptions,
|
|
152
|
+
loadPuppeteer:
|
|
153
|
+
dependencies.loadPuppeteer ?? (() => import('puppeteer')),
|
|
154
|
+
storageState,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
if (verbose) {
|
|
158
|
+
console.log(`Connected to browser with ${engine} engine`);
|
|
159
|
+
}
|
|
160
|
+
return result;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export { buildPlaywrightConnectOptions, buildPuppeteerConnectOptions };
|
package/src/browser/launcher.js
CHANGED
|
@@ -7,6 +7,11 @@ import {
|
|
|
7
7
|
buildPlaywrightLaunchOptions,
|
|
8
8
|
buildPuppeteerLaunchOptions,
|
|
9
9
|
} from './launch-options.js';
|
|
10
|
+
import {
|
|
11
|
+
loadStorageState,
|
|
12
|
+
restorePlaywrightStorageState,
|
|
13
|
+
restorePuppeteerStorageState,
|
|
14
|
+
} from './storage-state.js';
|
|
10
15
|
|
|
11
16
|
/**
|
|
12
17
|
* Launch browser with default configuration
|
|
@@ -20,6 +25,7 @@ import {
|
|
|
20
25
|
* @param {string|null} [options.colorScheme] - Emulate color scheme: 'light', 'dark', 'no-preference', or null to reset
|
|
21
26
|
* @param {string} [options.channel] - Installed browser channel, such as 'chrome', 'chrome-beta', or 'msedge'
|
|
22
27
|
* @param {string} [options.executablePath] - Explicit path to a Chrome or Chromium executable
|
|
28
|
+
* @param {string|Object} [options.storageState] - Playwright-compatible storage state path or object
|
|
23
29
|
* @returns {Promise<Object>} - Object with browser and page
|
|
24
30
|
*/
|
|
25
31
|
export async function launchBrowser(options = {}) {
|
|
@@ -33,6 +39,7 @@ export async function launchBrowser(options = {}) {
|
|
|
33
39
|
colorScheme,
|
|
34
40
|
channel,
|
|
35
41
|
executablePath,
|
|
42
|
+
storageState,
|
|
36
43
|
} = options;
|
|
37
44
|
|
|
38
45
|
// Combine default CHROME_ARGS with custom args
|
|
@@ -44,6 +51,8 @@ export async function launchBrowser(options = {}) {
|
|
|
44
51
|
);
|
|
45
52
|
}
|
|
46
53
|
|
|
54
|
+
const resolvedStorageState = await loadStorageState(storageState);
|
|
55
|
+
|
|
47
56
|
// Set environment variables to suppress warnings
|
|
48
57
|
process.env.GOOGLE_API_KEY = 'no';
|
|
49
58
|
process.env.GOOGLE_DEFAULT_CLIENT_ID = 'no';
|
|
@@ -74,6 +83,10 @@ export async function launchBrowser(options = {}) {
|
|
|
74
83
|
contextOptions
|
|
75
84
|
);
|
|
76
85
|
page = browser.pages()[0];
|
|
86
|
+
await restorePlaywrightStorageState({
|
|
87
|
+
context: browser,
|
|
88
|
+
storageState: resolvedStorageState,
|
|
89
|
+
});
|
|
77
90
|
} else {
|
|
78
91
|
const puppeteer = await import('puppeteer');
|
|
79
92
|
browser = await puppeteer.default.launch(
|
|
@@ -87,6 +100,10 @@ export async function launchBrowser(options = {}) {
|
|
|
87
100
|
);
|
|
88
101
|
const pages = await browser.pages();
|
|
89
102
|
page = pages[0];
|
|
103
|
+
await restorePuppeteerStorageState({
|
|
104
|
+
page,
|
|
105
|
+
storageState: resolvedStorageState,
|
|
106
|
+
});
|
|
90
107
|
}
|
|
91
108
|
|
|
92
109
|
if (verbose) {
|
|
@@ -348,6 +348,64 @@ export async function goto(options = {}) {
|
|
|
348
348
|
}
|
|
349
349
|
}
|
|
350
350
|
|
|
351
|
+
/**
|
|
352
|
+
* Replace the current document with an in-memory HTML string.
|
|
353
|
+
* When NavigationManager is available, this uses the same managed lifecycle as
|
|
354
|
+
* goto so active page triggers are stopped before the document is replaced.
|
|
355
|
+
*
|
|
356
|
+
* @param {Object} options - Configuration options
|
|
357
|
+
* @param {Object} options.page - Browser page object
|
|
358
|
+
* @param {Object} options.navigationManager - NavigationManager instance (preferred)
|
|
359
|
+
* @param {string} options.html - HTML to load
|
|
360
|
+
* @param {string} options.waitUntil - Wait until condition (default: 'load')
|
|
361
|
+
* @param {number} options.timeout - Content loading timeout in ms (default: 60000)
|
|
362
|
+
* @returns {Promise<Object>} Content loading result
|
|
363
|
+
*/
|
|
364
|
+
export async function setContent(options = {}) {
|
|
365
|
+
const {
|
|
366
|
+
page,
|
|
367
|
+
navigationManager,
|
|
368
|
+
html,
|
|
369
|
+
waitUntil = 'load',
|
|
370
|
+
timeout = 60000,
|
|
371
|
+
} = options;
|
|
372
|
+
|
|
373
|
+
if (typeof html !== 'string') {
|
|
374
|
+
throw new Error('html is required in options');
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
try {
|
|
378
|
+
let loaded;
|
|
379
|
+
if (navigationManager) {
|
|
380
|
+
loaded = await navigationManager.setContent({
|
|
381
|
+
html,
|
|
382
|
+
waitUntil,
|
|
383
|
+
timeout,
|
|
384
|
+
});
|
|
385
|
+
} else {
|
|
386
|
+
await page.setContent(html, { waitUntil, timeout });
|
|
387
|
+
loaded = true;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return loaded
|
|
391
|
+
? { loaded: true, actualUrl: page.url() }
|
|
392
|
+
: {
|
|
393
|
+
loaded: false,
|
|
394
|
+
actualUrl: page.url(),
|
|
395
|
+
reason: 'content loading stopped/interrupted',
|
|
396
|
+
};
|
|
397
|
+
} catch (error) {
|
|
398
|
+
if (isNavigationError(error) || isActionStoppedError(error)) {
|
|
399
|
+
return {
|
|
400
|
+
loaded: false,
|
|
401
|
+
actualUrl: page.url(),
|
|
402
|
+
reason: 'content loading stopped/interrupted',
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
throw error;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
351
409
|
/**
|
|
352
410
|
* Wait for navigation
|
|
353
411
|
* @param {Object} options - Configuration options
|