browser-commander 0.19.0 → 0.21.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 +156 -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 +25 -6
- package/scripts/run-tests.mjs +4 -2
- 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 +12 -17
- 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 +102 -0
- package/src/browser/real-browser.js +513 -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 +253 -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 +195 -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/downloads/manager.js +9 -0
- package/src/downloads/sources.js +71 -0
- package/src/downloads/staging.js +21 -9
- 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 +10 -2
- package/src/traces/recorder.js +16 -3
- 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/downloads.e2e.test.js +79 -2
- 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 +25 -17
- package/tests/e2e/puppeteer.e2e.test.js +17 -23
- package/tests/e2e/webdriver.e2e.test.js +239 -0
- package/tests/fixtures/firefox-nss-fixtures.mjs +211 -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/trace-fixtures.js +18 -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 +312 -0
- package/tests/unit/browser/attach/snapshot.test.js +390 -0
- package/tests/unit/browser/attach/websocket.test.js +240 -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 +238 -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/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 +409 -0
- package/tests/unit/cli/serve.test.js +167 -0
- package/tests/unit/core/webdriver-conversions.test.js +235 -0
- package/tests/unit/core/webdriver-page.test.js +534 -0
- package/tests/unit/downloads/manager.test.js +82 -9
- package/tests/unit/downloads/staging.test.js +24 -2
- 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/scripts/ci-timeout-budgets.test.js +4 -0
- package/tests/unit/scripts/ci-workflow-policy.test.js +8 -0
- package/tests/unit/scripts/feature-parity.test.js +221 -0
- package/tests/unit/scripts/npm-release-helpers.test.js +3 -2
- package/tests/unit/scripts/pre-commit-config.test.js +2 -1
- package/tests/unit/traces/bundle.test.js +24 -3
- package/tests/unit/traces/links.test.js +7 -0
- package/tests/unit/traces/reader.test.js +11 -4
- package/tests/unit/traces/recorder.test.js +4 -0
- package/tests/unit/traces/viewer.test.js +4 -0
- package/tests/unit/utilities/command-stream-only.test.js +28 -0
- package/tests/unit/utilities/subprocess.test.js +80 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import {
|
|
4
|
+
WEBDRIVER_KEYS,
|
|
5
|
+
buildEvaluateScript,
|
|
6
|
+
fromWebDriverCookie,
|
|
7
|
+
toCentimetres,
|
|
8
|
+
toConsoleMessage,
|
|
9
|
+
toNetworkRequest,
|
|
10
|
+
toPrintOptions,
|
|
11
|
+
toScriptLiteral,
|
|
12
|
+
toWebDriverChord,
|
|
13
|
+
toWebDriverCookie,
|
|
14
|
+
toWebDriverKey,
|
|
15
|
+
} from '../../../src/core/webdriver-conversions.js';
|
|
16
|
+
|
|
17
|
+
const closeTo = (actual, expected) =>
|
|
18
|
+
assert.ok(
|
|
19
|
+
Math.abs(actual - expected) < 1e-3,
|
|
20
|
+
`${actual} is not close to ${expected}`
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
describe('WebDriver key conversion', () => {
|
|
24
|
+
it('passes single characters through and maps key names', () => {
|
|
25
|
+
assert.equal(toWebDriverKey('a'), 'a');
|
|
26
|
+
assert.equal(toWebDriverKey('Enter'), WEBDRIVER_KEYS.Enter);
|
|
27
|
+
assert.equal(WEBDRIVER_KEYS.Enter, String.fromCharCode(0xe007));
|
|
28
|
+
assert.equal(WEBDRIVER_KEYS.Control, String.fromCharCode(0xe009));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('rejects unknown key names and empty keys', () => {
|
|
32
|
+
assert.throws(() => toWebDriverKey('NoSuchKey'), /Unknown key "NoSuchKey"/);
|
|
33
|
+
assert.throws(() => toWebDriverKey(''), TypeError);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('splits chords, keeping a trailing plus as the plus key', () => {
|
|
37
|
+
assert.deepEqual(toWebDriverChord('Control+A'), [
|
|
38
|
+
WEBDRIVER_KEYS.Control,
|
|
39
|
+
'A',
|
|
40
|
+
]);
|
|
41
|
+
assert.deepEqual(toWebDriverChord('Control++'), [
|
|
42
|
+
WEBDRIVER_KEYS.Control,
|
|
43
|
+
'+',
|
|
44
|
+
]);
|
|
45
|
+
assert.deepEqual(toWebDriverChord('+'), ['+']);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('WebDriver print options', () => {
|
|
50
|
+
it('converts lengths to centimetres', () => {
|
|
51
|
+
closeTo(toCentimetres(96), 2.54);
|
|
52
|
+
closeTo(toCentimetres('1in'), 2.54);
|
|
53
|
+
closeTo(toCentimetres('10mm'), 1);
|
|
54
|
+
closeTo(toCentimetres('2cm'), 2);
|
|
55
|
+
closeTo(toCentimetres('48px'), 1.27);
|
|
56
|
+
assert.throws(() => toCentimetres('1em'), /Cannot convert/);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('accepts surrounding and inner whitespace, and stays linear on long runs of it', () => {
|
|
60
|
+
closeTo(toCentimetres(' 10 mm '), 1);
|
|
61
|
+
const started = Date.now();
|
|
62
|
+
assert.throws(
|
|
63
|
+
() => toCentimetres(`9${' '.repeat(100000)}x`),
|
|
64
|
+
/Cannot convert/
|
|
65
|
+
);
|
|
66
|
+
assert.ok(Date.now() - started < 1000);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('maps Puppeteer pdf options onto Print Page parameters', () => {
|
|
70
|
+
const print = toPrintOptions({
|
|
71
|
+
format: 'A4',
|
|
72
|
+
landscape: true,
|
|
73
|
+
printBackground: true,
|
|
74
|
+
scale: 0.5,
|
|
75
|
+
pageRanges: '1-2, 4',
|
|
76
|
+
margin: { top: '1cm', left: 96 },
|
|
77
|
+
});
|
|
78
|
+
assert.equal(print.orientation, 'landscape');
|
|
79
|
+
assert.equal(print.background, true);
|
|
80
|
+
assert.equal(print.scale, 0.5);
|
|
81
|
+
assert.deepEqual(print.pageRanges, ['1-2', '4']);
|
|
82
|
+
closeTo(print.width, 21);
|
|
83
|
+
closeTo(print.height, 29.7);
|
|
84
|
+
closeTo(print.top, 1);
|
|
85
|
+
closeTo(print.left, 2.54);
|
|
86
|
+
assert.equal(print.right, undefined);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('lets explicit width and height override the format', () => {
|
|
90
|
+
const print = toPrintOptions({ format: 'letter', width: '10cm' });
|
|
91
|
+
closeTo(print.width, 10);
|
|
92
|
+
closeTo(print.height, 27.94);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('refuses options WebDriver cannot honour, naming the limitation', () => {
|
|
96
|
+
assert.throws(
|
|
97
|
+
() => toPrintOptions({ displayHeaderFooter: true, headerTemplate: 'x' }),
|
|
98
|
+
/does not support displayHeaderFooter, headerTemplate.*webdriver-print-options/
|
|
99
|
+
);
|
|
100
|
+
assert.throws(
|
|
101
|
+
() => toPrintOptions({ format: 'B5' }),
|
|
102
|
+
/Unknown paper format/
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('returns no parameters for no options', () => {
|
|
107
|
+
assert.deepEqual(toPrintOptions(), {});
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('WebDriver cookie conversion', () => {
|
|
112
|
+
it('reports session cookies with expires -1', () => {
|
|
113
|
+
const cookie = fromWebDriverCookie({
|
|
114
|
+
name: 'a',
|
|
115
|
+
value: 'bc',
|
|
116
|
+
domain: 'example.com',
|
|
117
|
+
httpOnly: true,
|
|
118
|
+
});
|
|
119
|
+
assert.deepEqual(cookie, {
|
|
120
|
+
name: 'a',
|
|
121
|
+
value: 'bc',
|
|
122
|
+
domain: 'example.com',
|
|
123
|
+
path: '/',
|
|
124
|
+
expires: -1,
|
|
125
|
+
size: 3,
|
|
126
|
+
httpOnly: true,
|
|
127
|
+
secure: false,
|
|
128
|
+
session: true,
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('keeps expiry and sameSite of persistent cookies', () => {
|
|
133
|
+
const cookie = fromWebDriverCookie({
|
|
134
|
+
name: 'a',
|
|
135
|
+
value: 'b',
|
|
136
|
+
path: '/x',
|
|
137
|
+
expiry: 1900000000,
|
|
138
|
+
sameSite: 'Lax',
|
|
139
|
+
});
|
|
140
|
+
assert.equal(cookie.expires, 1900000000);
|
|
141
|
+
assert.equal(cookie.session, false);
|
|
142
|
+
assert.equal(cookie.sameSite, 'Lax');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('turns expires into expiry and drops fields WebDriver does not take', () => {
|
|
146
|
+
assert.deepEqual(
|
|
147
|
+
toWebDriverCookie({
|
|
148
|
+
name: 'a',
|
|
149
|
+
value: 'b',
|
|
150
|
+
url: 'https://example.com',
|
|
151
|
+
expires: 1900000000.7,
|
|
152
|
+
secure: true,
|
|
153
|
+
}),
|
|
154
|
+
{ name: 'a', value: 'b', secure: true, expiry: 1900000000 }
|
|
155
|
+
);
|
|
156
|
+
assert.deepEqual(
|
|
157
|
+
toWebDriverCookie({ name: 'a', value: 'b', expires: -1 }),
|
|
158
|
+
{
|
|
159
|
+
name: 'a',
|
|
160
|
+
value: 'b',
|
|
161
|
+
}
|
|
162
|
+
);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe('WebDriver evaluate script', () => {
|
|
167
|
+
it('embeds the function source and reports location.href', async () => {
|
|
168
|
+
const source = buildEvaluateScript((a, b) => a + b);
|
|
169
|
+
assert.match(source, /\(\(a, b\) => a \+ b\)\.apply\(null, args\)/);
|
|
170
|
+
assert.match(source, /location\.href/);
|
|
171
|
+
// The script body runs as a function whose arguments are the call args.
|
|
172
|
+
const location = { href: 'https://example.com/' };
|
|
173
|
+
const run = new Function('location', `return (function () { ${source} })`)(
|
|
174
|
+
location
|
|
175
|
+
);
|
|
176
|
+
assert.deepEqual(await run(2, 3), [5, 'https://example.com/']);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('evaluates expression strings', async () => {
|
|
180
|
+
const source = buildEvaluateScript('1 + 1');
|
|
181
|
+
const run = new Function('location', `return (function () { ${source} })`)({
|
|
182
|
+
href: 'about:blank',
|
|
183
|
+
});
|
|
184
|
+
assert.deepEqual(await run(), [2, 'about:blank']);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
describe('WebDriver BiDi event conversion', () => {
|
|
189
|
+
it('turns log entries into console messages', () => {
|
|
190
|
+
const message = toConsoleMessage({
|
|
191
|
+
level: 'warn',
|
|
192
|
+
method: 'warn',
|
|
193
|
+
text: 'careful',
|
|
194
|
+
args: [{ type: 'string', value: 'careful' }],
|
|
195
|
+
source: { context: 'c', url: 'https://example.com/' },
|
|
196
|
+
});
|
|
197
|
+
assert.equal(message.type(), 'warning');
|
|
198
|
+
assert.equal(message.text(), 'careful');
|
|
199
|
+
assert.equal(message.args().length, 1);
|
|
200
|
+
assert.equal(message.location().url, 'https://example.com/');
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('turns network events into requests with lower-case headers', () => {
|
|
204
|
+
const request = toNetworkRequest({
|
|
205
|
+
navigation: 'nav-1',
|
|
206
|
+
request: {
|
|
207
|
+
request: 'r1',
|
|
208
|
+
url: 'https://example.com/',
|
|
209
|
+
method: 'GET',
|
|
210
|
+
headers: [{ name: 'Accept', value: { type: 'string', value: '*/*' } }],
|
|
211
|
+
},
|
|
212
|
+
response: { status: 204 },
|
|
213
|
+
});
|
|
214
|
+
assert.equal(request.id, 'r1');
|
|
215
|
+
assert.equal(request.url(), 'https://example.com/');
|
|
216
|
+
assert.equal(request.method(), 'GET');
|
|
217
|
+
assert.deepEqual(request.headers(), { accept: '*/*' });
|
|
218
|
+
assert.equal(request.isNavigationRequest(), true);
|
|
219
|
+
assert.equal(request.response().status(), 204);
|
|
220
|
+
assert.equal(request.failure(), undefined);
|
|
221
|
+
assert.deepEqual(
|
|
222
|
+
toNetworkRequest({ request: {}, errorText: 'net::ERR_FAILED' }).failure(),
|
|
223
|
+
{ errorText: 'net::ERR_FAILED' }
|
|
224
|
+
);
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
describe('script literals', () => {
|
|
229
|
+
it('round-trips the value and cannot close a script element or a line', () => {
|
|
230
|
+
const value = ['</script>', 'a\\b', '\u2028\u2029', { path: '/x' }];
|
|
231
|
+
const literal = toScriptLiteral(value);
|
|
232
|
+
assert.doesNotMatch(literal, /[<>/\u2028\u2029]/u);
|
|
233
|
+
assert.deepEqual(new Function(`return ${literal};`)(), value);
|
|
234
|
+
});
|
|
235
|
+
});
|