browser-commander 0.20.0 → 0.21.1
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/.jscpd.json +2 -1
- package/.prettierignore +1 -0
- package/CHANGELOG.md +159 -0
- package/README.md +216 -32
- package/bin/browser-commander.js +16 -0
- package/eslint.config.js +10 -0
- package/examples/react-test-app/src/App.jsx +24 -23
- package/extension/background.js +14 -0
- package/extension/manifest.json +12 -0
- package/extension/relay-handler.js +353 -0
- package/package.json +21 -4
- package/src/browser/attach/differences.js +131 -0
- package/src/browser/attach/extension-relay.js +402 -0
- package/src/browser/attach/index.js +81 -0
- package/src/browser/attach/snapshot-launch.js +146 -0
- package/src/browser/attach/snapshot.js +374 -0
- package/src/browser/attach/websocket.js +615 -0
- package/src/browser/browser-cookie-cache.js +26 -19
- package/src/browser/browser-cookie-credentials.js +6 -9
- package/src/browser/browser-cookies.js +5 -2
- package/src/browser/cdp-session.js +94 -0
- package/src/browser/connector.js +42 -4
- package/src/browser/debugging-port.js +155 -0
- package/src/browser/launch-options.js +63 -52
- package/src/browser/launch-restrictions.json +147 -0
- package/src/browser/launcher.js +329 -133
- package/src/browser/media.js +5 -0
- package/src/browser/migration/bookmarks.js +73 -0
- package/src/browser/migration/chromium-crypto.js +89 -0
- package/src/browser/migration/cookies.js +155 -0
- package/src/browser/migration/extensions.js +187 -0
- package/src/browser/migration/firefox-bookmarks.js +144 -0
- package/src/browser/migration/firefox-der.js +84 -0
- package/src/browser/migration/firefox-nss.js +281 -0
- package/src/browser/migration/firefox.js +334 -0
- package/src/browser/migration/fs-utils.js +51 -0
- package/src/browser/migration/history.js +103 -0
- package/src/browser/migration/index.js +307 -0
- package/src/browser/migration/os-crypt-keys.js +106 -0
- package/src/browser/migration/passwords.js +171 -0
- package/src/browser/migration/preferences.js +132 -0
- package/src/browser/migration/sqlite-snapshot.js +138 -0
- package/src/browser/open-in-user-browser.js +112 -0
- package/src/browser/parity.js +439 -0
- package/src/browser/profile-directory.js +205 -0
- package/src/browser/real-browser.js +545 -306
- package/src/browser/restrictions.js +148 -0
- package/src/browser/system-browser.js +232 -0
- package/src/browser/webdriver.js +572 -0
- package/src/cli/args.js +300 -0
- package/src/cli/commands.js +332 -0
- package/src/cli/dispatcher.js +99 -0
- package/src/cli/handle-methods.js +136 -0
- package/src/cli/handles.js +273 -0
- package/src/cli/high-level-methods.js +310 -0
- package/src/cli/main.js +67 -0
- package/src/cli/modules.js +100 -0
- package/src/cli/rpc-error.js +77 -0
- package/src/cli/script.js +77 -0
- package/src/cli/serve.js +144 -0
- package/src/cli/sessions.js +199 -0
- package/src/core/constants.js +7 -1
- package/src/core/engine-adapter.js +107 -1
- package/src/core/engine-detection.js +18 -3
- package/src/core/navigation-safety.js +4 -0
- package/src/core/webdriver-conversions.js +347 -0
- package/src/core/webdriver-events.js +229 -0
- package/src/core/webdriver-page.js +716 -0
- package/src/downloads/attach.js +6 -0
- package/src/elements/selectors.js +3 -2
- package/src/elements/visibility.js +4 -0
- package/src/exports.js +61 -2
- package/src/factory.js +19 -4
- package/src/fingerprint/apply.js +6 -0
- package/src/fingerprint/automation-parity.js +7 -8
- package/src/fingerprint/limitations.js +1 -1
- package/src/fingerprint/limitations.json +45 -2
- package/src/parity/harness.js +303 -0
- package/src/parity/probe.js +838 -0
- package/src/parity/version-page.js +144 -0
- package/src/traces/bundle.js +3 -2
- package/src/traces/observers.js +14 -5
- package/src/traces/recorder.js +3 -0
- package/src/traces/viewer.js +16 -8
- package/src/utilities/subprocess.js +199 -0
- package/tests/e2e/api-coverage.e2e.test.js +174 -0
- package/tests/e2e/cli.e2e.test.js +160 -0
- package/tests/e2e/fingerprint-parity.e2e.test.js +104 -46
- package/tests/e2e/google-signin-probe.e2e.test.js +112 -0
- package/tests/e2e/measure-parity.e2e.test.js +118 -0
- package/tests/e2e/playwright.e2e.test.js +26 -17
- package/tests/e2e/puppeteer.e2e.test.js +18 -23
- package/tests/e2e/webdriver.e2e.test.js +239 -0
- package/tests/fixtures/firefox-nss-fixtures.mjs +211 -0
- package/tests/fixtures/traces/conformance/expected/bundle/checkpoints/0001.html +2 -0
- package/tests/fixtures/traces/conformance/expected/bundle/checkpoints/0001.png +1 -0
- package/tests/fixtures/traces/conformance/expected/bundle/checkpoints/0001.state.json +54 -0
- package/tests/fixtures/traces/conformance/expected/bundle/checkpoints/0002.html +2 -0
- package/tests/fixtures/traces/conformance/expected/bundle/checkpoints/0002.png +1 -0
- package/tests/fixtures/traces/conformance/expected/bundle/checkpoints/0002.state.json +60 -0
- package/tests/fixtures/traces/conformance/expected/bundle/checkpoints/0003.html +2 -0
- package/tests/fixtures/traces/conformance/expected/bundle/checkpoints/0003.png +1 -0
- package/tests/fixtures/traces/conformance/expected/bundle/checkpoints/0003.state.json +60 -0
- package/tests/fixtures/traces/conformance/expected/bundle/events.ndjson +18 -0
- package/tests/fixtures/traces/conformance/expected/bundle/manifest.json +77 -0
- package/tests/fixtures/traces/conformance/expected/bundle/mutations/0001.ndjson +2 -0
- package/tests/fixtures/traces/conformance/expected/bundle/mutations/0002.ndjson +1 -0
- package/tests/fixtures/traces/conformance/expected/bundle/viewer.html +309 -0
- package/tests/fixtures/traces/conformance/expected/redaction.json +86 -0
- package/tests/fixtures/traces/conformance/expected/trace.lino +27 -0
- package/tests/fixtures/traces/conformance/scenario.json +249 -0
- package/tests/helpers/cli-fakes.js +214 -0
- package/tests/helpers/declared-api.js +80 -0
- package/tests/helpers/e2e-browser.js +40 -14
- package/tests/helpers/fake-chrome.js +124 -0
- package/tests/helpers/migration-fixtures.js +242 -0
- package/tests/helpers/temp-directory.js +24 -0
- package/tests/helpers/webdriver-mocks.js +183 -0
- package/tests/unit/browser/attach/extension-relay.test.js +360 -0
- package/tests/unit/browser/attach/snapshot-launch.test.js +321 -0
- package/tests/unit/browser/attach/snapshot.test.js +409 -0
- package/tests/unit/browser/attach/websocket.test.js +240 -0
- package/tests/unit/browser/browser-cookie-lock.test.js +102 -0
- package/tests/unit/browser/browser-cookies.test.js +2 -32
- package/tests/unit/browser/cdp-session.test.js +162 -0
- package/tests/unit/browser/connector.test.js +23 -0
- package/tests/unit/browser/launch-building-blocks.test.js +361 -0
- package/tests/unit/browser/launch-options.test.js +41 -22
- package/tests/unit/browser/launcher.test.js +281 -102
- package/tests/unit/browser/media.test.js +13 -1
- package/tests/unit/browser/migration/bookmarks.test.js +69 -0
- package/tests/unit/browser/migration/chromium-crypto.test.js +94 -0
- package/tests/unit/browser/migration/cookies.test.js +94 -0
- package/tests/unit/browser/migration/extensions.test.js +104 -0
- package/tests/unit/browser/migration/firefox-nss.test.js +113 -0
- package/tests/unit/browser/migration/firefox.test.js +139 -0
- package/tests/unit/browser/migration/history.test.js +48 -0
- package/tests/unit/browser/migration/index.test.js +117 -0
- package/tests/unit/browser/migration/os-crypt-keys.test.js +95 -0
- package/tests/unit/browser/migration/passwords.test.js +144 -0
- package/tests/unit/browser/migration/preferences.test.js +77 -0
- package/tests/unit/browser/migration/sqlite-snapshot.test.js +75 -0
- package/tests/unit/browser/open-in-user-browser.test.js +123 -0
- package/tests/unit/browser/parity.test.js +281 -0
- package/tests/unit/browser/real-browser.test.js +458 -33
- package/tests/unit/browser/storage-state.test.js +2 -0
- package/tests/unit/browser/webdriver.test.js +473 -0
- package/tests/unit/cli/bin.test.js +69 -0
- package/tests/unit/cli/declared-api.test.js +49 -0
- package/tests/unit/cli/dispatcher.test.js +429 -0
- package/tests/unit/cli/handles.test.js +170 -0
- package/tests/unit/cli/main.test.js +437 -0
- package/tests/unit/cli/serve.test.js +167 -0
- package/tests/unit/core/navigation-manager.test.js +11 -1
- package/tests/unit/core/webdriver-conversions.test.js +235 -0
- package/tests/unit/core/webdriver-page.test.js +534 -0
- package/tests/unit/fingerprint/automation-parity.test.js +17 -4
- package/tests/unit/fingerprint/limitations.test.js +1 -1
- package/tests/unit/parity/harness.test.js +138 -0
- package/tests/unit/playwright-protocol-coverage.test.js +231 -0
- package/tests/unit/puppeteer-bindings-coverage.test.js +214 -0
- package/tests/unit/scripts/ci-timeout-budgets.test.js +27 -0
- package/tests/unit/scripts/feature-parity.test.js +327 -0
- package/tests/unit/traces/conformance.test.js +92 -0
- package/tests/unit/traces/reader.test.js +1 -0
- package/tests/unit/traces/recorder.test.js +1 -0
- package/tests/unit/traces/viewer.test.js +78 -0
- package/tests/unit/utilities/command-stream-only.test.js +28 -0
- package/tests/unit/utilities/subprocess.test.js +80 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import { syncBuiltinESMExports } from 'node:module';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { test } from 'node:test';
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
clearBrowserCookieMemoryCache,
|
|
10
|
+
getCachedCredential,
|
|
11
|
+
} from '../../../src/browser/browser-cookie-cache.js';
|
|
12
|
+
|
|
13
|
+
async function cleanup(t, directory) {
|
|
14
|
+
t.mock.restoreAll();
|
|
15
|
+
syncBuiltinESMExports();
|
|
16
|
+
clearBrowserCookieMemoryCache();
|
|
17
|
+
await fs.rm(directory, { recursive: true, force: true });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
test('retries a Windows delete-pending credential lock without rereading the credential', async (t) => {
|
|
21
|
+
const directory = await fs.mkdtemp(path.join(os.tmpdir(), 'bc-cookie-lock-'));
|
|
22
|
+
const realOpen = fs.open;
|
|
23
|
+
let attempted = false;
|
|
24
|
+
let reads = 0;
|
|
25
|
+
// Model the observed Windows exclusive-open EPERM while the previous
|
|
26
|
+
// reader's lock is pending deletion. Other file operations stay real.
|
|
27
|
+
t.mock.method(fs, 'open', async (filename, ...args) => {
|
|
28
|
+
if (filename.endsWith('.lock') && !attempted) {
|
|
29
|
+
attempted = true;
|
|
30
|
+
throw Object.assign(new Error('lock pending deletion'), {
|
|
31
|
+
code: 'EPERM',
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return realOpen(filename, ...args);
|
|
35
|
+
});
|
|
36
|
+
syncBuiltinESMExports();
|
|
37
|
+
try {
|
|
38
|
+
const options = {
|
|
39
|
+
cache: { enabled: true, dir: directory, ttlSeconds: 60 },
|
|
40
|
+
identity: 'windows-lock-contention',
|
|
41
|
+
refresh: false,
|
|
42
|
+
metadata: {},
|
|
43
|
+
platform: 'win32',
|
|
44
|
+
create: async () => {
|
|
45
|
+
reads += 1;
|
|
46
|
+
return Buffer.from('credential');
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
assert.deepEqual(
|
|
50
|
+
await getCachedCredential(options),
|
|
51
|
+
Buffer.from('credential')
|
|
52
|
+
);
|
|
53
|
+
clearBrowserCookieMemoryCache();
|
|
54
|
+
assert.deepEqual(
|
|
55
|
+
await getCachedCredential(options),
|
|
56
|
+
Buffer.from('credential')
|
|
57
|
+
);
|
|
58
|
+
assert.equal(reads, 1);
|
|
59
|
+
assert.ok(attempted);
|
|
60
|
+
} finally {
|
|
61
|
+
await cleanup(t, directory);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('does not retry permanent permission errors or Unix EPERM', async (t) => {
|
|
66
|
+
const directory = await fs.mkdtemp(
|
|
67
|
+
path.join(os.tmpdir(), 'bc-cookie-denied-')
|
|
68
|
+
);
|
|
69
|
+
const realOpen = fs.open;
|
|
70
|
+
try {
|
|
71
|
+
for (const [platform, code] of [
|
|
72
|
+
['win32', 'EACCES'],
|
|
73
|
+
['linux', 'EPERM'],
|
|
74
|
+
]) {
|
|
75
|
+
const error = Object.assign(new Error('access denied'), { code });
|
|
76
|
+
t.mock.method(fs, 'open', async (filename, ...args) => {
|
|
77
|
+
if (filename.endsWith('.lock')) {
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
return realOpen(filename, ...args);
|
|
81
|
+
});
|
|
82
|
+
syncBuiltinESMExports();
|
|
83
|
+
await assert.rejects(
|
|
84
|
+
getCachedCredential({
|
|
85
|
+
cache: { enabled: true, dir: directory, ttlSeconds: 60 },
|
|
86
|
+
identity: platform,
|
|
87
|
+
refresh: false,
|
|
88
|
+
metadata: {},
|
|
89
|
+
platform,
|
|
90
|
+
create: async () => {
|
|
91
|
+
throw new Error('must not read credentials');
|
|
92
|
+
},
|
|
93
|
+
}),
|
|
94
|
+
(received) => received === error
|
|
95
|
+
);
|
|
96
|
+
t.mock.restoreAll();
|
|
97
|
+
syncBuiltinESMExports();
|
|
98
|
+
}
|
|
99
|
+
} finally {
|
|
100
|
+
await cleanup(t, directory);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
listBrowserProfiles as publicListBrowserProfiles,
|
|
38
38
|
readBrowserCookies as publicReadBrowserCookies,
|
|
39
39
|
} from '../../../src/index.js';
|
|
40
|
+
import { writeFirefoxCookies } from '../../helpers/migration-fixtures.js';
|
|
40
41
|
|
|
41
42
|
const CHROME_EPOCH_OFFSET_SECONDS = 11_644_473_600;
|
|
42
43
|
const execFile = promisify(execFileCallback);
|
|
@@ -136,43 +137,12 @@ async function createFirefoxProfile({ homeDir, rows }) {
|
|
|
136
137
|
const root = path.join(homeDir, '.mozilla', 'firefox');
|
|
137
138
|
const profileName = 'fixture.default-release';
|
|
138
139
|
const profilePath = path.join(root, profileName);
|
|
139
|
-
const cookiePath = path.join(profilePath, 'cookies.sqlite');
|
|
140
140
|
await mkdir(profilePath, { recursive: true });
|
|
141
141
|
await writeFile(
|
|
142
142
|
path.join(root, 'profiles.ini'),
|
|
143
143
|
`[Profile0]\nName=default-release\nIsRelative=1\nPath=${profileName}\nDefault=1\n`
|
|
144
144
|
);
|
|
145
|
-
const
|
|
146
|
-
database.exec(`
|
|
147
|
-
CREATE TABLE moz_cookies (
|
|
148
|
-
name TEXT,
|
|
149
|
-
value TEXT,
|
|
150
|
-
host TEXT,
|
|
151
|
-
path TEXT,
|
|
152
|
-
expiry INTEGER,
|
|
153
|
-
isSecure INTEGER,
|
|
154
|
-
isHttpOnly INTEGER,
|
|
155
|
-
sameSite INTEGER
|
|
156
|
-
);
|
|
157
|
-
`);
|
|
158
|
-
const insert = database.prepare(`
|
|
159
|
-
INSERT INTO moz_cookies
|
|
160
|
-
(name, value, host, path, expiry, isSecure, isHttpOnly, sameSite)
|
|
161
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
162
|
-
`);
|
|
163
|
-
for (const row of rows) {
|
|
164
|
-
insert.run(
|
|
165
|
-
row.name,
|
|
166
|
-
row.value,
|
|
167
|
-
row.host,
|
|
168
|
-
row.path ?? '/',
|
|
169
|
-
row.expiry ?? 0,
|
|
170
|
-
row.secure ? 1 : 0,
|
|
171
|
-
row.httpOnly ? 1 : 0,
|
|
172
|
-
row.sameSite ?? 0
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
database.close();
|
|
145
|
+
const cookiePath = await writeFirefoxCookies(profilePath, rows);
|
|
176
146
|
return { cookiePath, profilePath, root };
|
|
177
147
|
}
|
|
178
148
|
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
createCdpSession,
|
|
6
|
+
wrapCdpSession,
|
|
7
|
+
} from '../../../src/browser/cdp-session.js';
|
|
8
|
+
import { makeBrowserCommander } from '../../../src/factory.js';
|
|
9
|
+
import * as publicApi from '../../../src/index.js';
|
|
10
|
+
|
|
11
|
+
function createEngineSession() {
|
|
12
|
+
const calls = [];
|
|
13
|
+
const listeners = [];
|
|
14
|
+
return {
|
|
15
|
+
calls,
|
|
16
|
+
listeners,
|
|
17
|
+
detachCount: 0,
|
|
18
|
+
send(method, params) {
|
|
19
|
+
calls.push({ method, params });
|
|
20
|
+
return Promise.resolve({ echoed: method });
|
|
21
|
+
},
|
|
22
|
+
on(event, listener) {
|
|
23
|
+
listeners.push(['on', event, listener]);
|
|
24
|
+
},
|
|
25
|
+
once(event, listener) {
|
|
26
|
+
listeners.push(['once', event, listener]);
|
|
27
|
+
},
|
|
28
|
+
off(event, listener) {
|
|
29
|
+
listeners.push(['off', event, listener]);
|
|
30
|
+
},
|
|
31
|
+
detach() {
|
|
32
|
+
this.detachCount += 1;
|
|
33
|
+
return Promise.resolve();
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function createPlaywrightPage(session) {
|
|
39
|
+
const context = {
|
|
40
|
+
targets: [],
|
|
41
|
+
newCDPSession(target) {
|
|
42
|
+
context.targets.push(target);
|
|
43
|
+
return Promise.resolve(session);
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
const page = { context: () => context, locator: () => ({}) };
|
|
47
|
+
return { page, context };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
describe('createCdpSession', () => {
|
|
51
|
+
it('opens a Playwright session through page.context().newCDPSession(page)', async () => {
|
|
52
|
+
const session = createEngineSession();
|
|
53
|
+
const { page, context } = createPlaywrightPage(session);
|
|
54
|
+
|
|
55
|
+
const cdp = await createCdpSession(page, { engine: 'playwright' });
|
|
56
|
+
|
|
57
|
+
assert.equal(cdp.engine, 'playwright');
|
|
58
|
+
assert.equal(cdp.session, session);
|
|
59
|
+
assert.deepEqual(context.targets, [page]);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('opens a Puppeteer session through page.createCDPSession()', async () => {
|
|
63
|
+
const session = createEngineSession();
|
|
64
|
+
const page = { createCDPSession: () => Promise.resolve(session) };
|
|
65
|
+
|
|
66
|
+
const cdp = await createCdpSession(page, { engine: 'puppeteer' });
|
|
67
|
+
|
|
68
|
+
assert.equal(cdp.engine, 'puppeteer');
|
|
69
|
+
assert.equal(cdp.session, session);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('detects the engine from the page when it is not given', async () => {
|
|
73
|
+
const session = createEngineSession();
|
|
74
|
+
const { page } = createPlaywrightPage(session);
|
|
75
|
+
|
|
76
|
+
assert.equal((await createCdpSession(page)).engine, 'playwright');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('accepts the older {browser, page, engine} form', async () => {
|
|
80
|
+
const session = createEngineSession();
|
|
81
|
+
const page = { createCDPSession: () => Promise.resolve(session) };
|
|
82
|
+
|
|
83
|
+
const cdp = await createCdpSession({
|
|
84
|
+
browser: {},
|
|
85
|
+
page,
|
|
86
|
+
engine: 'puppeteer',
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
assert.equal(cdp.session, session);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('requires a page', async () => {
|
|
93
|
+
await assert.rejects(() => createCdpSession(undefined), /requires a page/u);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe('the uniform CDP session surface', () => {
|
|
98
|
+
it('sends commands with empty params by default', async () => {
|
|
99
|
+
const session = createEngineSession();
|
|
100
|
+
const cdp = wrapCdpSession(session, 'puppeteer');
|
|
101
|
+
|
|
102
|
+
assert.deepEqual(await cdp.send('Browser.getVersion'), {
|
|
103
|
+
echoed: 'Browser.getVersion',
|
|
104
|
+
});
|
|
105
|
+
await cdp.send('Runtime.evaluate', { expression: '1' });
|
|
106
|
+
assert.deepEqual(session.calls, [
|
|
107
|
+
{ method: 'Browser.getVersion', params: {} },
|
|
108
|
+
{ method: 'Runtime.evaluate', params: { expression: '1' } },
|
|
109
|
+
]);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('forwards listeners and chains', () => {
|
|
113
|
+
const session = createEngineSession();
|
|
114
|
+
const cdp = wrapCdpSession(session, 'playwright');
|
|
115
|
+
const listener = () => {};
|
|
116
|
+
|
|
117
|
+
assert.equal(cdp.on('Network.requestWillBeSent', listener), cdp);
|
|
118
|
+
assert.equal(cdp.once('Page.loadEventFired', listener), cdp);
|
|
119
|
+
assert.equal(cdp.off('Network.requestWillBeSent', listener), cdp);
|
|
120
|
+
assert.deepEqual(
|
|
121
|
+
session.listeners.map(([kind, event]) => `${kind}:${event}`),
|
|
122
|
+
[
|
|
123
|
+
'on:Network.requestWillBeSent',
|
|
124
|
+
'once:Page.loadEventFired',
|
|
125
|
+
'off:Network.requestWillBeSent',
|
|
126
|
+
]
|
|
127
|
+
);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('detaches once', async () => {
|
|
131
|
+
const session = createEngineSession();
|
|
132
|
+
const cdp = wrapCdpSession(session, 'playwright');
|
|
133
|
+
|
|
134
|
+
await cdp.detach();
|
|
135
|
+
await cdp.detach();
|
|
136
|
+
|
|
137
|
+
assert.equal(session.detachCount, 1);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe('public cdpSession entry points', () => {
|
|
142
|
+
it('exports createCdpSession and wrapCdpSession', () => {
|
|
143
|
+
assert.equal(publicApi.createCdpSession, createCdpSession);
|
|
144
|
+
assert.equal(publicApi.wrapCdpSession, wrapCdpSession);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('opens a session from a commander', async () => {
|
|
148
|
+
const session = createEngineSession();
|
|
149
|
+
const { page } = createPlaywrightPage(session);
|
|
150
|
+
const commander = makeBrowserCommander({
|
|
151
|
+
page,
|
|
152
|
+
enableNetworkTracking: false,
|
|
153
|
+
enableNavigationManager: false,
|
|
154
|
+
enableDialogManager: false,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const cdp = await commander.createCdpSession();
|
|
158
|
+
|
|
159
|
+
assert.equal(cdp.engine, 'playwright');
|
|
160
|
+
assert.equal(cdp.session, session);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
@@ -3,6 +3,7 @@ import { describe, it } from 'node:test';
|
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
connectBrowser,
|
|
6
|
+
pickForegroundPage,
|
|
6
7
|
connectBrowserWithDependencies,
|
|
7
8
|
} from '../../../src/browser/connector.js';
|
|
8
9
|
import { connectBrowser as publicConnectBrowser } from '../../../src/index.js';
|
|
@@ -127,3 +128,25 @@ describe('connectBrowser', () => {
|
|
|
127
128
|
);
|
|
128
129
|
});
|
|
129
130
|
});
|
|
131
|
+
|
|
132
|
+
describe('pickForegroundPage', () => {
|
|
133
|
+
const page = (id, state) => ({ id, evaluate: async () => state });
|
|
134
|
+
|
|
135
|
+
it('prefers the visible tab over the first one', async () => {
|
|
136
|
+
const picked = await pickForegroundPage([
|
|
137
|
+
page('ntp', 'hidden'),
|
|
138
|
+
page('whats-new', 'visible'),
|
|
139
|
+
]);
|
|
140
|
+
assert.equal(picked.id, 'whats-new');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('falls back to the first tab when none reports visible', async () => {
|
|
144
|
+
const failing = {
|
|
145
|
+
id: 'crashed',
|
|
146
|
+
evaluate: async () => Promise.reject(new Error('gone')),
|
|
147
|
+
};
|
|
148
|
+
const picked = await pickForegroundPage([failing, page('b', 'hidden')]);
|
|
149
|
+
assert.equal(picked.id, 'crashed');
|
|
150
|
+
assert.equal(await pickForegroundPage([]), undefined);
|
|
151
|
+
});
|
|
152
|
+
});
|