browser-commander 0.23.0 → 0.25.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/.jscpd-baseline.json +48 -30
- package/CHANGELOG.md +32 -0
- package/README.md +19 -7
- package/eslint.config.js +1 -1
- package/examples/react-test-app/package-lock.json +890 -0
- package/examples/react-test-app/package.json +4 -4
- package/package.json +20 -17
- package/src/browser/attach/snapshot.js +4 -28
- package/src/browser/browser-cookie-credentials.js +15 -8
- package/src/browser/browser-cookies.js +36 -30
- package/src/browser/browser-profile-files.js +31 -0
- package/src/browser/browser-profiles.js +11 -13
- package/src/browser/browser-sources.js +39 -9
- package/src/browser/browser-sources.json +600 -23
- package/src/browser/connector.js +46 -4
- package/src/browser/launcher.js +26 -1
- package/src/browser/migration/capabilities.json +117 -0
- package/src/browser/migration/chromium-history.sql +22 -0
- package/src/browser/migration/chromium-writers.js +85 -0
- package/src/browser/migration/cookies.js +4 -1
- package/src/browser/migration/data-classes.js +68 -0
- package/src/browser/migration/domains.js +32 -0
- package/src/browser/migration/firefox-history.js +106 -0
- package/src/browser/migration/firefox.js +39 -25
- package/src/browser/migration/history.js +161 -2
- package/src/browser/migration/index.js +229 -83
- package/src/browser/migration/os-crypt-keys.js +1 -5
- package/src/browser/migration/password-metadata.js +95 -0
- package/src/browser/migration/passwords.js +78 -41
- package/src/browser/migration/safari-import.js +100 -0
- package/src/browser/migration/safari.js +110 -0
- package/src/browser/migration/sqlite-snapshot.js +31 -65
- package/src/browser/migration/validation.js +48 -0
- package/src/browser/real-browser.js +21 -3
- package/src/browser/safari-access.js +45 -0
- package/src/browser/safari-cookies.js +39 -36
- package/src/browser/safari-profiles.js +83 -0
- package/src/browser/storage-state.js +27 -0
- package/src/browser/system-browser.js +31 -143
- package/src/browser/webdriver.js +18 -2
- package/src/cli/args.js +7 -0
- package/src/cli/commands.js +12 -6
- package/src/cli/handle-methods.js +19 -2
- package/src/cli/high-level-methods.js +22 -3
- package/src/cli/modules.js +5 -1
- package/src/cli/sessions.js +14 -2
- package/src/tests/index.js +1 -0
- package/tests/e2e/engine-matrix.e2e.test.js +107 -0
- package/tests/e2e/playwright.e2e.test.js +11 -17
- package/tests/e2e/puppeteer.e2e.test.js +13 -17
- package/tests/unit/browser/attach/snapshot.test.js +30 -0
- package/tests/unit/browser/browser-cookies.test.js +35 -0
- package/tests/unit/browser/catalogue-launch.test.js +93 -0
- package/tests/unit/browser/cookie-sources.test.js +92 -17
- package/tests/unit/browser/credential-catalogue.test.js +50 -0
- package/tests/unit/browser/firefox-cookie-expiry.test.js +79 -0
- package/tests/unit/browser/migration/data-classes.test.js +89 -0
- package/tests/unit/browser/migration/domain-isolation.test.js +212 -0
- package/tests/unit/browser/migration/firefox-history.test.js +130 -0
- package/tests/unit/browser/migration/history-metadata.test.js +74 -0
- package/tests/unit/browser/migration/index.test.js +16 -1
- package/tests/unit/browser/migration/opera-keys.test.js +22 -0
- package/tests/unit/browser/migration/safari-data.test.js +143 -0
- package/tests/unit/browser/migration/sqlite-snapshot.test.js +56 -1
- package/tests/unit/browser/migration/validation.test.js +63 -0
- package/tests/unit/browser/migration/yandex.test.js +84 -0
- package/tests/unit/browser/real-browser.test.js +19 -0
- package/tests/unit/browser/safari-cookies.test.js +26 -1
- package/tests/unit/browser/safari-profiles.test.js +111 -0
- package/tests/unit/cli/dispatcher.test.js +6 -2
- package/tests/unit/cli/main.test.js +5 -0
- package/tests/unit/cli/selenium-parity.test.js +186 -0
- package/tests/unit/scripts/browser-support.test.js +14 -7
- package/tests/unit/scripts/check-pipeline-status.test.js +33 -1
- package/tests/unit/scripts/migration-support.test.js +31 -0
- package/tests/unit/tests/index.test.js +2 -1
- package/tests/unit/traces/links.test.js +2 -2
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { describe, it } from 'node:test';
|
|
3
|
+
import { createDispatcher } from '../../../src/cli/dispatcher.js';
|
|
4
|
+
import { buildLaunchOptions } from '../../../src/cli/sessions.js';
|
|
5
|
+
import { launchBrowserWithDependencies } from '../../../src/browser/launcher.js';
|
|
6
|
+
import { connectBrowserWithDependencies } from '../../../src/browser/connector.js';
|
|
7
|
+
|
|
8
|
+
// feature-parity: engines.webdriver@native-typed engines.cli-matrix@native-typed
|
|
9
|
+
|
|
10
|
+
describe('Selenium uses the common engine entry points', () => {
|
|
11
|
+
it('restores portable cookies and origin state through the common launcher', async () => {
|
|
12
|
+
const visited = [];
|
|
13
|
+
const seeded = [];
|
|
14
|
+
const page = {
|
|
15
|
+
url: () => 'about:blank',
|
|
16
|
+
goto: async (url) => {
|
|
17
|
+
visited.push(url);
|
|
18
|
+
},
|
|
19
|
+
setCookie: async (...cookies) => {
|
|
20
|
+
seeded.push(...cookies);
|
|
21
|
+
},
|
|
22
|
+
evaluateOnNewDocument: async () => {},
|
|
23
|
+
evaluate: async () => {},
|
|
24
|
+
bringToFront: async () => {},
|
|
25
|
+
};
|
|
26
|
+
const cookie = {
|
|
27
|
+
name: 'session',
|
|
28
|
+
value: 'value',
|
|
29
|
+
domain: 'example.com',
|
|
30
|
+
path: '/',
|
|
31
|
+
secure: true,
|
|
32
|
+
};
|
|
33
|
+
const result = await launchBrowserWithDependencies(
|
|
34
|
+
{
|
|
35
|
+
engine: 'selenium',
|
|
36
|
+
launch: 'engine',
|
|
37
|
+
storageState: {
|
|
38
|
+
cookies: [cookie],
|
|
39
|
+
origins: [
|
|
40
|
+
{
|
|
41
|
+
origin: 'https://example.com',
|
|
42
|
+
localStorage: [{ name: 'theme', value: 'dark' }],
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
launchWebDriver: async () => ({
|
|
49
|
+
driver: {},
|
|
50
|
+
page,
|
|
51
|
+
close: async () => {},
|
|
52
|
+
}),
|
|
53
|
+
settleMs: 0,
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
assert.deepEqual(seeded, [cookie]);
|
|
57
|
+
assert.deepEqual(visited, ['https://example.com', 'about:blank']);
|
|
58
|
+
await result.close();
|
|
59
|
+
});
|
|
60
|
+
it('preserves WebDriver browser and driver options in the CLI', () => {
|
|
61
|
+
assert.deepEqual(
|
|
62
|
+
buildLaunchOptions({
|
|
63
|
+
engine: 'selenium',
|
|
64
|
+
browser: 'firefox',
|
|
65
|
+
driverPath: '/driver',
|
|
66
|
+
bidi: true,
|
|
67
|
+
}),
|
|
68
|
+
{
|
|
69
|
+
engine: 'selenium',
|
|
70
|
+
headless: false,
|
|
71
|
+
browser: 'firefox',
|
|
72
|
+
driverPath: '/driver',
|
|
73
|
+
bidi: true,
|
|
74
|
+
}
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('launches through the native driver without loading a CDP engine', async () => {
|
|
79
|
+
const page = { bringToFront: async () => {} };
|
|
80
|
+
const driver = {};
|
|
81
|
+
let options;
|
|
82
|
+
let closed = false;
|
|
83
|
+
const result = await launchBrowserWithDependencies(
|
|
84
|
+
{ engine: 'selenium', launch: 'engine', extraArgs: ['--no-sandbox'] },
|
|
85
|
+
{
|
|
86
|
+
launchWebDriver: async (value) => {
|
|
87
|
+
options = value;
|
|
88
|
+
return {
|
|
89
|
+
driver,
|
|
90
|
+
page,
|
|
91
|
+
close: async () => {
|
|
92
|
+
closed = true;
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
},
|
|
96
|
+
settleMs: 0,
|
|
97
|
+
}
|
|
98
|
+
);
|
|
99
|
+
assert.equal(result.browser, driver);
|
|
100
|
+
assert.equal(result.page, page);
|
|
101
|
+
assert.deepEqual(options.args, ['--no-sandbox']);
|
|
102
|
+
await result.close();
|
|
103
|
+
assert.equal(closed, true);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('connects to a Selenium Grid through the common connector', async () => {
|
|
107
|
+
const driver = {};
|
|
108
|
+
const page = {};
|
|
109
|
+
let options;
|
|
110
|
+
const result = await connectBrowserWithDependencies(
|
|
111
|
+
{
|
|
112
|
+
engine: 'selenium',
|
|
113
|
+
serverUrl: 'http://grid:4444',
|
|
114
|
+
capabilities: { browserName: 'firefox' },
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
connectWebDriver: async (value) => {
|
|
118
|
+
options = value;
|
|
119
|
+
return { driver, page, close: async () => {} };
|
|
120
|
+
},
|
|
121
|
+
}
|
|
122
|
+
);
|
|
123
|
+
assert.equal(result.browser, driver);
|
|
124
|
+
assert.equal(options.capabilities.browserName, 'firefox');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('exposes the raw Selenium module and driver over generic handles', async () => {
|
|
128
|
+
const driver = { getTitle: async () => 'native driver' };
|
|
129
|
+
const page = {
|
|
130
|
+
waitForSelector: async () => ({
|
|
131
|
+
clear: async () => {},
|
|
132
|
+
sendKeys: async () => {},
|
|
133
|
+
}),
|
|
134
|
+
url: () => 'about:blank',
|
|
135
|
+
};
|
|
136
|
+
const dispatcher = createDispatcher({
|
|
137
|
+
dependencies: {
|
|
138
|
+
loadEngine: async (name) => {
|
|
139
|
+
assert.equal(name, 'selenium');
|
|
140
|
+
return { Builder: class Builder {} };
|
|
141
|
+
},
|
|
142
|
+
launchBrowser: async () => ({
|
|
143
|
+
browser: driver,
|
|
144
|
+
driver,
|
|
145
|
+
page,
|
|
146
|
+
close: async () => {},
|
|
147
|
+
}),
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
try {
|
|
151
|
+
const root = await dispatcher.dispatch('handle.root', {
|
|
152
|
+
name: 'selenium',
|
|
153
|
+
});
|
|
154
|
+
assert.ok(root.$handle);
|
|
155
|
+
const builder = await dispatcher.dispatch('handle.get', {
|
|
156
|
+
handle: root,
|
|
157
|
+
property: 'Builder',
|
|
158
|
+
});
|
|
159
|
+
const instance = await dispatcher.dispatch('handle.construct', {
|
|
160
|
+
handle: builder,
|
|
161
|
+
});
|
|
162
|
+
assert.equal(
|
|
163
|
+
(await dispatcher.dispatch('handle.describe', { handle: instance }))
|
|
164
|
+
.type,
|
|
165
|
+
'Builder'
|
|
166
|
+
);
|
|
167
|
+
await dispatcher.dispatch('session.launch', { engine: 'selenium' });
|
|
168
|
+
await dispatcher.dispatch('page.fill', {
|
|
169
|
+
selector: '#name',
|
|
170
|
+
value: 'Ada',
|
|
171
|
+
});
|
|
172
|
+
const handles = await dispatcher.dispatch('handle.root', {
|
|
173
|
+
name: 'session:s1',
|
|
174
|
+
});
|
|
175
|
+
assert.equal(
|
|
176
|
+
await dispatcher.dispatch('handle.call', {
|
|
177
|
+
handle: handles.browser,
|
|
178
|
+
method: 'getTitle',
|
|
179
|
+
}),
|
|
180
|
+
'native driver'
|
|
181
|
+
);
|
|
182
|
+
} finally {
|
|
183
|
+
await dispatcher.close();
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
});
|
|
@@ -16,12 +16,13 @@ import { pathToFileURL } from 'node:url';
|
|
|
16
16
|
|
|
17
17
|
import { repoPath } from '../../helpers/repo.js';
|
|
18
18
|
|
|
19
|
-
const
|
|
20
|
-
|
|
19
|
+
const generator = await import(
|
|
20
|
+
pathToFileURL(repoPath('scripts/generate-browser-support.mjs'))
|
|
21
|
+
);
|
|
21
22
|
|
|
22
23
|
describe('generate-browser-support', () => {
|
|
23
24
|
it('renders a row per browser with per-platform availability', () => {
|
|
24
|
-
const table = renderBrowserSupport([
|
|
25
|
+
const table = generator.renderBrowserSupport([
|
|
25
26
|
{
|
|
26
27
|
id: 'chrome',
|
|
27
28
|
family: 'chromium',
|
|
@@ -47,10 +48,16 @@ describe('generate-browser-support', () => {
|
|
|
47
48
|
const readme = fs
|
|
48
49
|
.readFileSync(repoPath('README.md'), 'utf8')
|
|
49
50
|
.replaceAll('\r\n', '\n');
|
|
50
|
-
const begin = readme.indexOf(BEGIN_MARKER);
|
|
51
|
-
const end = readme.indexOf(END_MARKER);
|
|
51
|
+
const begin = readme.indexOf(generator.BEGIN_MARKER);
|
|
52
|
+
const end = readme.indexOf(generator.END_MARKER);
|
|
52
53
|
assert.ok(begin !== -1 && end > begin, 'README is missing the markers');
|
|
53
|
-
const between = readme
|
|
54
|
-
|
|
54
|
+
const between = readme
|
|
55
|
+
.slice(begin + generator.BEGIN_MARKER.length, end)
|
|
56
|
+
.trim();
|
|
57
|
+
const browsers = generator.readCatalogue(repoPath('.'));
|
|
58
|
+
assert.equal(
|
|
59
|
+
between,
|
|
60
|
+
`${generator.renderBrowserSupport(browsers)}\n\n${generator.renderBrowserControls(browsers)}`
|
|
61
|
+
);
|
|
55
62
|
});
|
|
56
63
|
});
|
|
@@ -5,10 +5,12 @@
|
|
|
5
5
|
* *failed*, and a run whose only casualty is a cancelled job is filed under
|
|
6
6
|
* `cancelled` as well - run 24045269874 of this repository is one such run on
|
|
7
7
|
* `main`. scripts/check-pipeline-status.sh is the only thing that looks at
|
|
8
|
-
* that, so these tests pin the
|
|
8
|
+
* that, so these tests pin the readings it has to get right: a failure is
|
|
9
9
|
* always an error, a cancellation off the default branch is a warning, a
|
|
10
10
|
* cancellation on the default branch is an error unless a newer run has already
|
|
11
11
|
* taken over the branch head.
|
|
12
|
+
* Hosted-runner failures can report an undocumented abandoned result; it and
|
|
13
|
+
* other unexpected/missing results must fail on every branch.
|
|
12
14
|
*
|
|
13
15
|
* See docs/CI-TIMEOUT-BUDGETS.md.
|
|
14
16
|
*/
|
|
@@ -66,6 +68,36 @@ describe('check-pipeline-status.sh', { skip: !BASH_AVAILABLE }, () => {
|
|
|
66
68
|
assert.match(output, /::error::Pipeline failed\. Failing jobs: lint/);
|
|
67
69
|
});
|
|
68
70
|
|
|
71
|
+
for (const env of [
|
|
72
|
+
{ IS_MAIN: 'false' },
|
|
73
|
+
{ IS_MAIN: 'true', RUN_SHA: 'abc', BRANCH_HEAD_SHA: 'def' },
|
|
74
|
+
]) {
|
|
75
|
+
it(`fails on an abandoned runner job with IS_MAIN=${env.IS_MAIN}`, () => {
|
|
76
|
+
// Hosted-runner acquisition failure in run 37362314527 left the macOS
|
|
77
|
+
// check cancelled, but GitHub sent "abandoned" to the needs context.
|
|
78
|
+
const { status, output } = runGate(
|
|
79
|
+
results({ lint: 'success', test: 'abandoned', release: 'skipped' }),
|
|
80
|
+
env
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
assert.equal(status, 1);
|
|
84
|
+
assert.match(output, /::error::Pipeline has unexpected job results/);
|
|
85
|
+
assert.match(output, /test \(abandoned\)/);
|
|
86
|
+
assert.doesNotMatch(output, /All required jobs succeeded/);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
for (const result of ['timed_out', null, undefined]) {
|
|
91
|
+
it(`rejects an unexpected result of ${result}`, () => {
|
|
92
|
+
const { status, output } = runGate(results({ test: result }));
|
|
93
|
+
|
|
94
|
+
assert.equal(status, 1);
|
|
95
|
+
assert.match(output, /::error::Pipeline has unexpected job results/);
|
|
96
|
+
assert.match(output, /test \(/);
|
|
97
|
+
assert.doesNotMatch(output, /All required jobs succeeded/);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
69
101
|
it('only warns about a cancellation off the default branch', () => {
|
|
70
102
|
const { status, output } = runGate(results({ test: 'cancelled' }), {
|
|
71
103
|
IS_MAIN: 'false',
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { it } from 'node:test';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
5
|
+
import { repoPath } from '../../helpers/repo.js';
|
|
6
|
+
import { ALL_DATA_CLASSES } from '../../../src/browser/migration/index.js';
|
|
7
|
+
|
|
8
|
+
const { BEGIN_MARKER, END_MARKER, readCapabilities, renderMigrationSupport } =
|
|
9
|
+
await import(
|
|
10
|
+
pathToFileURL(repoPath('scripts/generate-migration-support.mjs'))
|
|
11
|
+
);
|
|
12
|
+
it('generates each source/target/class cell and declares unsupported target writers', () => {
|
|
13
|
+
const capabilities = readCapabilities(repoPath('.'));
|
|
14
|
+
assert.deepEqual(ALL_DATA_CLASSES, capabilities.classes);
|
|
15
|
+
const table = renderMigrationSupport(capabilities);
|
|
16
|
+
assert.equal(table.split('\n').length, 11);
|
|
17
|
+
assert.match(table, /safari\s+\| chromium\s+\| seed\s+\| translate/u);
|
|
18
|
+
const doc = readFileSync(
|
|
19
|
+
repoPath('docs/profile-migration.md'),
|
|
20
|
+
'utf8'
|
|
21
|
+
).replaceAll('\r\n', '\n');
|
|
22
|
+
assert.equal(
|
|
23
|
+
doc
|
|
24
|
+
.slice(
|
|
25
|
+
doc.indexOf(BEGIN_MARKER) + BEGIN_MARKER.length,
|
|
26
|
+
doc.indexOf(END_MARKER)
|
|
27
|
+
)
|
|
28
|
+
.trim(),
|
|
29
|
+
table
|
|
30
|
+
);
|
|
31
|
+
});
|
|
@@ -26,12 +26,13 @@ describe('browser-commander/tests', () => {
|
|
|
26
26
|
assert.deepStrictEqual(normalizeBrowserTestEngines(), [
|
|
27
27
|
'playwright',
|
|
28
28
|
'puppeteer',
|
|
29
|
+
'selenium',
|
|
29
30
|
]);
|
|
30
31
|
assert.deepStrictEqual(normalizeBrowserTestEngines('Playwright'), [
|
|
31
32
|
'playwright',
|
|
32
33
|
]);
|
|
33
34
|
assert.throws(
|
|
34
|
-
() => normalizeBrowserTestEngines('
|
|
35
|
+
() => normalizeBrowserTestEngines('unknown'),
|
|
35
36
|
/Unsupported browser test engine/
|
|
36
37
|
);
|
|
37
38
|
});
|
|
@@ -544,8 +544,8 @@ describe('trace links export (issue #94)', () => {
|
|
|
544
544
|
// The notation's formatter and parser decide whether an export can be read
|
|
545
545
|
// back, so the range this package depends on and the version the tests
|
|
546
546
|
// proved are the same range.
|
|
547
|
-
assert.strictEqual(manifest.dependencies['links-notation'], '^0.
|
|
548
|
-
assert.match(installed.version, /^0\.
|
|
547
|
+
assert.strictEqual(manifest.dependencies['links-notation'], '^0.22.0');
|
|
548
|
+
assert.match(installed.version, /^0\.22\./);
|
|
549
549
|
});
|
|
550
550
|
|
|
551
551
|
it('should export a bundle named by path as readily as an open one', async () => {
|