browser-commander 0.24.0 → 0.26.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 +1 -1
- package/CHANGELOG.md +32 -0
- package/README.md +7 -4
- package/package.json +4 -1
- 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 +610 -25
- package/src/browser/launcher.js +14 -0
- 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 +18 -1
- package/src/browser/safari-access.js +45 -0
- package/src/browser/safari-cookies.js +39 -36
- package/src/browser/safari-launch.js +42 -0
- package/src/browser/safari-profiles.js +83 -0
- package/src/browser/safari-support.js +98 -0
- package/src/browser/storage-state.js +28 -8
- package/src/browser/system-browser.js +31 -143
- package/src/browser/webdriver.js +64 -19
- package/src/cli/args.js +2 -0
- package/src/cli/commands.js +2 -0
- package/src/cli/high-level-methods.js +2 -0
- package/src/cli/sessions.js +5 -0
- package/src/core/webdriver-page.js +62 -0
- package/src/exports.js +6 -0
- package/src/traces/recorder.js +5 -0
- package/tests/e2e/safari.e2e.test.js +104 -0
- 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/browser/safari.test.js +192 -0
- package/tests/unit/browser/webdriver.test.js +48 -3
- package/tests/unit/cli/dispatcher.test.js +5 -1
- package/tests/unit/cli/main.test.js +5 -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
|
@@ -49,6 +49,22 @@ async function installed(browser = 'safari', legacy = false) {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
describe('Safari cookies', { timeout: 10000 }, () => {
|
|
52
|
+
it('uses whole domains for discovery while retaining reader substring filters', async () => {
|
|
53
|
+
const options = await installed();
|
|
54
|
+
const sources = await listCookieSources({
|
|
55
|
+
...options,
|
|
56
|
+
domains: ['hub.com', 'github.co'],
|
|
57
|
+
});
|
|
58
|
+
assert.deepEqual(sources, []);
|
|
59
|
+
assert.deepEqual(
|
|
60
|
+
await readBrowserCookiesWithDependencies(
|
|
61
|
+
{ browser: 'safari', domainFilter: 'hub.com', cache: false },
|
|
62
|
+
options
|
|
63
|
+
),
|
|
64
|
+
expected.slice(0, 2)
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
52
68
|
it('decodes multiple pages, flags, UTF-8 and Apple epoch without guessing session cookies', () => {
|
|
53
69
|
assert.deepEqual(parseSafariCookies(fixture), expected);
|
|
54
70
|
});
|
|
@@ -133,7 +149,16 @@ describe('Safari cookies', { timeout: 10000 }, () => {
|
|
|
133
149
|
});
|
|
134
150
|
assert.deepEqual(report.cookies, expected.slice(0, 2));
|
|
135
151
|
assert.equal(report.migrated.cookies, 2);
|
|
136
|
-
|
|
152
|
+
const classes = JSON.parse(
|
|
153
|
+
await readFile(
|
|
154
|
+
repoPath('tests/fixtures/migration-data-classes.json'),
|
|
155
|
+
'utf8'
|
|
156
|
+
)
|
|
157
|
+
);
|
|
158
|
+
assert.deepEqual(
|
|
159
|
+
new Set(report.skipped.map((entry) => entry.type)),
|
|
160
|
+
new Set(classes.filter((type) => type !== 'cookies'))
|
|
161
|
+
);
|
|
137
162
|
assert.equal(
|
|
138
163
|
report.skipped.find((entry) => entry.type === 'passwords').reason,
|
|
139
164
|
'safari-password-export-required'
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { copyFile, mkdir, readFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { it } from 'node:test';
|
|
5
|
+
import Database from 'better-sqlite3';
|
|
6
|
+
import { listBrowserProfiles } from '../../../src/browser/browser-profiles.js';
|
|
7
|
+
import { listCookieSources } from '../../../src/browser/browser-cookies.js';
|
|
8
|
+
import { findSafariCookieFile } from '../../../src/browser/safari-cookies.js';
|
|
9
|
+
import { repoPath } from '../../helpers/repo.js';
|
|
10
|
+
import { useTempDirectories } from '../../helpers/temp-directory.js';
|
|
11
|
+
|
|
12
|
+
const temporary = useTempDirectories('bc-safari-stores-');
|
|
13
|
+
const uuid = '11111111-2222-3333-4444-555555555555';
|
|
14
|
+
|
|
15
|
+
it('discovers Safari named profiles and prefers WebKit stores to stale legacy cookies', async () => {
|
|
16
|
+
const homeDir = await temporary();
|
|
17
|
+
const root = path.join(
|
|
18
|
+
homeDir,
|
|
19
|
+
'Library/Containers/com.apple.Safari/Data/Library'
|
|
20
|
+
);
|
|
21
|
+
const named = path.join(root, 'Safari/Profiles', uuid);
|
|
22
|
+
await mkdir(named, { recursive: true });
|
|
23
|
+
const db = new Database(path.join(root, 'Safari/SafariTabs.db'));
|
|
24
|
+
db.exec(
|
|
25
|
+
await readFile(
|
|
26
|
+
repoPath('tests/fixtures/safari-data/SafariTabs.sql'),
|
|
27
|
+
'utf8'
|
|
28
|
+
)
|
|
29
|
+
);
|
|
30
|
+
db.close();
|
|
31
|
+
const locations = [
|
|
32
|
+
'Cookies',
|
|
33
|
+
'WebKit/WebsiteData/Default/Cookies',
|
|
34
|
+
`WebKit/WebsiteDataStore/${uuid}/Cookies`,
|
|
35
|
+
];
|
|
36
|
+
for (const location of locations) {
|
|
37
|
+
await mkdir(path.join(root, location), { recursive: true });
|
|
38
|
+
await copyFile(
|
|
39
|
+
repoPath('tests/fixtures/safari/Cookies.binarycookies'),
|
|
40
|
+
path.join(root, location, 'Cookies.binarycookies')
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
const profiles = await listBrowserProfiles({
|
|
44
|
+
browser: 'safari',
|
|
45
|
+
platform: 'darwin',
|
|
46
|
+
homeDir,
|
|
47
|
+
environment: {},
|
|
48
|
+
});
|
|
49
|
+
assert.deepEqual(
|
|
50
|
+
profiles.map(({ name }) => name),
|
|
51
|
+
['Default', uuid]
|
|
52
|
+
);
|
|
53
|
+
assert.equal(profiles[1].displayName, 'Work');
|
|
54
|
+
assert.equal(profiles[1].path, named);
|
|
55
|
+
assert.equal(
|
|
56
|
+
await findSafariCookieFile(root),
|
|
57
|
+
path.join(root, locations[1], 'Cookies.binarycookies')
|
|
58
|
+
);
|
|
59
|
+
assert.equal(
|
|
60
|
+
await findSafariCookieFile(named),
|
|
61
|
+
path.join(root, locations[2], 'Cookies.binarycookies')
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('preserves a Safari catalogue error while listing its readable cookies and other browsers', async () => {
|
|
66
|
+
const homeDir = await temporary();
|
|
67
|
+
const root = path.join(
|
|
68
|
+
homeDir,
|
|
69
|
+
'Library/Containers/com.apple.Safari/Data/Library'
|
|
70
|
+
);
|
|
71
|
+
await mkdir(path.join(root, 'Safari'), { recursive: true });
|
|
72
|
+
await mkdir(path.join(root, 'Cookies'));
|
|
73
|
+
await copyFile(
|
|
74
|
+
repoPath('tests/fixtures/safari/Cookies.binarycookies'),
|
|
75
|
+
path.join(root, 'Cookies/Cookies.binarycookies')
|
|
76
|
+
);
|
|
77
|
+
const tabs = path.join(root, 'Safari/SafariTabs.db');
|
|
78
|
+
new Database(tabs).close();
|
|
79
|
+
const chromium = path.join(
|
|
80
|
+
homeDir,
|
|
81
|
+
'Library/Application Support/Google/Chrome/Default'
|
|
82
|
+
);
|
|
83
|
+
await mkdir(chromium, { recursive: true });
|
|
84
|
+
const db = new Database(path.join(chromium, 'Cookies'));
|
|
85
|
+
db.exec(
|
|
86
|
+
"CREATE TABLE cookies(host_key TEXT); INSERT INTO cookies VALUES ('.github.com')"
|
|
87
|
+
);
|
|
88
|
+
db.close();
|
|
89
|
+
const before = await readFile(tabs);
|
|
90
|
+
const options = { platform: 'darwin', homeDir, environment: {} };
|
|
91
|
+
const profiles = await listBrowserProfiles(options);
|
|
92
|
+
assert.equal(
|
|
93
|
+
profiles.filter(({ browser }) => browser === 'safari').length,
|
|
94
|
+
2
|
|
95
|
+
);
|
|
96
|
+
const sources = await listCookieSources({
|
|
97
|
+
...options,
|
|
98
|
+
domains: ['github.com'],
|
|
99
|
+
});
|
|
100
|
+
assert.equal(sources.find(({ browser }) => browser === 'chrome').cookies, 1);
|
|
101
|
+
assert.equal(
|
|
102
|
+
sources.find(({ browser, error }) => browser === 'safari' && !error)
|
|
103
|
+
.cookies,
|
|
104
|
+
4
|
|
105
|
+
);
|
|
106
|
+
assert.match(
|
|
107
|
+
sources.find(({ browser, error }) => browser === 'safari' && error).error,
|
|
108
|
+
/no such table/u
|
|
109
|
+
);
|
|
110
|
+
assert.deepEqual(await readFile(tabs), before);
|
|
111
|
+
});
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// feature-parity: safari.control@native-typed safari.setup@native-typed safari.seed@native-typed safari.unsupported@native-typed
|
|
2
|
+
import { it } from 'node:test';
|
|
3
|
+
import assert from 'node:assert/strict';
|
|
4
|
+
import vm from 'node:vm';
|
|
5
|
+
import { findBrowserSource } from '../../../src/browser/browser-sources.js';
|
|
6
|
+
import {
|
|
7
|
+
buildDriverServerArgs,
|
|
8
|
+
resolveWebDriverExecutable,
|
|
9
|
+
} from '../../../src/browser/webdriver.js';
|
|
10
|
+
import {
|
|
11
|
+
WebDriverPage,
|
|
12
|
+
createWebDriverPage,
|
|
13
|
+
} from '../../../src/core/webdriver-page.js';
|
|
14
|
+
import { createMockDriver } from '../../helpers/webdriver-mocks.js';
|
|
15
|
+
import { launchBrowserWithDependencies } from '../../../src/browser/launcher.js';
|
|
16
|
+
import { launchAndConnectRealBrowserWithDependencies } from '../../../src/browser/real-browser.js';
|
|
17
|
+
import {
|
|
18
|
+
SafariSetupError,
|
|
19
|
+
SafariUnsupportedError,
|
|
20
|
+
safariLaunchError,
|
|
21
|
+
openSafariSettings,
|
|
22
|
+
} from '../../../src/browser/safari-support.js';
|
|
23
|
+
import { startTrace } from '../../../src/traces/recorder.js';
|
|
24
|
+
import {
|
|
25
|
+
buildLaunchOptions,
|
|
26
|
+
sessionFromLaunch,
|
|
27
|
+
} from '../../../src/cli/sessions.js';
|
|
28
|
+
|
|
29
|
+
it('catalogue exposes both Safari WebDriver executables', () => {
|
|
30
|
+
for (const channel of ['safari', 'safari-tp']) {
|
|
31
|
+
const source = findBrowserSource(channel);
|
|
32
|
+
assert.equal(source.controlProtocol, 'webdriver');
|
|
33
|
+
assert.ok(source.executables.darwin[0].endsWith('/safaridriver'));
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('Safari channels route real/common launch and CLI to Selenium and seed cookies', async () => {
|
|
38
|
+
for (const launch of [
|
|
39
|
+
launchBrowserWithDependencies,
|
|
40
|
+
launchAndConnectRealBrowserWithDependencies,
|
|
41
|
+
]) {
|
|
42
|
+
const driver = createMockDriver({
|
|
43
|
+
capabilities: { browserName: 'safari' },
|
|
44
|
+
});
|
|
45
|
+
const page = await createWebDriverPage(driver);
|
|
46
|
+
let calls = 0;
|
|
47
|
+
const launched = await launch(
|
|
48
|
+
{
|
|
49
|
+
channel: 'safari-tp',
|
|
50
|
+
seedCookies: [
|
|
51
|
+
{
|
|
52
|
+
name: 'auth',
|
|
53
|
+
value: 'yes',
|
|
54
|
+
domain: '.example.test',
|
|
55
|
+
secure: true,
|
|
56
|
+
httpOnly: true,
|
|
57
|
+
expires: -1,
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
launchWebDriver: async (options) => {
|
|
63
|
+
calls++;
|
|
64
|
+
assert.equal(options.browser, 'safari-technology-preview');
|
|
65
|
+
return {
|
|
66
|
+
driver,
|
|
67
|
+
page,
|
|
68
|
+
close: () => driver.quit(),
|
|
69
|
+
driverPath: '/Safari TP/safaridriver',
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
resolveExecutable: () => assert.fail('Safari must bypass CDP'),
|
|
73
|
+
resolveSystem: () =>
|
|
74
|
+
assert.fail('Safari must bypass Chromium resolution'),
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
assert.equal(calls, 1);
|
|
78
|
+
assert.equal(launched.engine, 'selenium');
|
|
79
|
+
assert.equal(launched.browser, driver);
|
|
80
|
+
assert.equal(driver.state.url, 'about:blank');
|
|
81
|
+
assert.ok(
|
|
82
|
+
driver.calls.some(
|
|
83
|
+
([method, url]) => method === 'get' && url === 'https://example.test'
|
|
84
|
+
)
|
|
85
|
+
);
|
|
86
|
+
assert.ok(
|
|
87
|
+
driver.calls.some(
|
|
88
|
+
([method, cookie]) => method === 'addCookie' && cookie.httpOnly
|
|
89
|
+
)
|
|
90
|
+
);
|
|
91
|
+
assert.equal(sessionFromLaunch('playwright', launched).engine, 'selenium');
|
|
92
|
+
await launched.close();
|
|
93
|
+
}
|
|
94
|
+
assert.equal(buildLaunchOptions({ browser: 'safari' }).channel, 'safari');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('Safari fails unsupported launch options before starting and page features before writes', async () => {
|
|
98
|
+
for (const options of [
|
|
99
|
+
{ headless: true },
|
|
100
|
+
{ userDataDir: '/tmp/profile' },
|
|
101
|
+
{ args: ['--flag'] },
|
|
102
|
+
{ fingerprint: {} },
|
|
103
|
+
{ downloads: true },
|
|
104
|
+
]) {
|
|
105
|
+
await assert.rejects(
|
|
106
|
+
() =>
|
|
107
|
+
launchBrowserWithDependencies(
|
|
108
|
+
{ channel: 'safari', ...options },
|
|
109
|
+
{ launchWebDriver: () => assert.fail('must reject before starting') }
|
|
110
|
+
),
|
|
111
|
+
SafariUnsupportedError
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
const page = new WebDriverPage(createMockDriver(), { browserName: 'safari' });
|
|
115
|
+
await assert.rejects(
|
|
116
|
+
() => page.pdf(),
|
|
117
|
+
(error) =>
|
|
118
|
+
error instanceof SafariUnsupportedError && error.feature === 'PDF'
|
|
119
|
+
);
|
|
120
|
+
await assert.rejects(
|
|
121
|
+
() => page.setRequestInterception(true),
|
|
122
|
+
SafariUnsupportedError
|
|
123
|
+
);
|
|
124
|
+
await assert.rejects(
|
|
125
|
+
() => startTrace({ page, output: '/must-not-create' }),
|
|
126
|
+
SafariUnsupportedError
|
|
127
|
+
);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('Safari async evaluation uses execute/async and returns rejected promises as errors', async () => {
|
|
131
|
+
const driver = createMockDriver();
|
|
132
|
+
let mode = 'success';
|
|
133
|
+
driver.executeAsyncScript = (source, ...args) =>
|
|
134
|
+
new Promise((resolve) => {
|
|
135
|
+
vm.runInNewContext(`(function(){${source}}).apply(null, args)`, {
|
|
136
|
+
args: [...args, resolve],
|
|
137
|
+
location: { href: 'https://example.test' },
|
|
138
|
+
setTimeout,
|
|
139
|
+
mode,
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
const page = new WebDriverPage(driver, { browserName: 'safari' });
|
|
143
|
+
assert.equal(await page.evaluateAsync(async (value) => value + 1, 4), 5);
|
|
144
|
+
mode = 'error';
|
|
145
|
+
await assert.rejects(
|
|
146
|
+
() =>
|
|
147
|
+
page.evaluateAsync(async () => {
|
|
148
|
+
throw new Error('rejected');
|
|
149
|
+
}),
|
|
150
|
+
/rejected/
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('setup errors identify both authorization steps and offer an explicit settings opener', async () => {
|
|
155
|
+
for (const message of [
|
|
156
|
+
"You must enable the 'Allow Remote Automation' option",
|
|
157
|
+
'safaridriver requires --enable authorization',
|
|
158
|
+
]) {
|
|
159
|
+
const error = safariLaunchError(
|
|
160
|
+
new Error(message),
|
|
161
|
+
'safari-technology-preview'
|
|
162
|
+
);
|
|
163
|
+
assert.ok(error instanceof SafariSetupError);
|
|
164
|
+
assert.match(error.message, /Show features for web developers/);
|
|
165
|
+
assert.match(error.message, /admin password/);
|
|
166
|
+
assert.match(error.message, /Safari Technology Preview.app/);
|
|
167
|
+
assert.equal(typeof error.openSettings, 'function');
|
|
168
|
+
}
|
|
169
|
+
const other = new Error('port already in use');
|
|
170
|
+
assert.equal(safariLaunchError(other, 'safari'), other);
|
|
171
|
+
let argv;
|
|
172
|
+
await openSafariSettings({
|
|
173
|
+
channel: 'safari-tp',
|
|
174
|
+
platform: 'darwin',
|
|
175
|
+
run: async (file, args) => {
|
|
176
|
+
assert.equal(file, 'osascript');
|
|
177
|
+
argv = args;
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
assert.ok(argv.some((arg) => arg.includes('Safari Technology Preview')));
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('Safari uses its bundled driver and the native --port argument', async () => {
|
|
184
|
+
assert.deepEqual(buildDriverServerArgs('safari', 12345), ['--port', '12345']);
|
|
185
|
+
const result = await resolveWebDriverExecutable({
|
|
186
|
+
browser: 'safari',
|
|
187
|
+
platform: 'darwin',
|
|
188
|
+
checkAccess: async () => {},
|
|
189
|
+
runCommand: () => assert.fail('Safari needs no Selenium Manager'),
|
|
190
|
+
});
|
|
191
|
+
assert.equal(result.driverPath, '/usr/bin/safaridriver');
|
|
192
|
+
});
|
|
@@ -92,7 +92,7 @@ function createMockSelenium(driver = createMockDriver()) {
|
|
|
92
92
|
record,
|
|
93
93
|
driver,
|
|
94
94
|
selenium: {
|
|
95
|
-
webdriver: { Builder },
|
|
95
|
+
webdriver: { Builder, Capabilities: Options },
|
|
96
96
|
chrome: {
|
|
97
97
|
Options: class extends Options {
|
|
98
98
|
constructor() {
|
|
@@ -265,8 +265,8 @@ describe('resolveWebDriverExecutable', () => {
|
|
|
265
265
|
|
|
266
266
|
it('rejects browsers it has no driver for', async () => {
|
|
267
267
|
await assert.rejects(
|
|
268
|
-
() => resolveWebDriverExecutable({ browser: '
|
|
269
|
-
/Unsupported WebDriver browser:
|
|
268
|
+
() => resolveWebDriverExecutable({ browser: 'unknown-browser' }),
|
|
269
|
+
/Unsupported WebDriver browser: unknown-browser/
|
|
270
270
|
);
|
|
271
271
|
});
|
|
272
272
|
});
|
|
@@ -446,6 +446,51 @@ describe('launchWebDriver', () => {
|
|
|
446
446
|
});
|
|
447
447
|
});
|
|
448
448
|
|
|
449
|
+
function safariDependencies() {
|
|
450
|
+
return launchDependencies({
|
|
451
|
+
platform: 'darwin',
|
|
452
|
+
checkAccess: async () => {},
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
describe('Safari driver lifecycle', () => {
|
|
457
|
+
it('uses no disk profile or Chromium/BiDi capabilities and closes idempotently', async () => {
|
|
458
|
+
const { dependencies, started, mock } = safariDependencies();
|
|
459
|
+
mock.driver.state.url = '';
|
|
460
|
+
const result = await launchWebDriver(
|
|
461
|
+
{ browser: 'safari-technology-preview' },
|
|
462
|
+
dependencies
|
|
463
|
+
);
|
|
464
|
+
assert.equal(result.page.url(), 'about:blank');
|
|
465
|
+
assert.equal(result.userDataDir, undefined);
|
|
466
|
+
assert.equal(result.temporaryProfile, false);
|
|
467
|
+
assert.equal(
|
|
468
|
+
mock.record.builders[0].capabilities.capabilities.browserName,
|
|
469
|
+
'Safari Technology Preview'
|
|
470
|
+
);
|
|
471
|
+
assert.deepEqual(started[0].args, ['--port', '41001']);
|
|
472
|
+
assert.match(started[0].file, /Safari Technology Preview.app/);
|
|
473
|
+
await Promise.all([result.close(), result.close()]);
|
|
474
|
+
assert.equal(
|
|
475
|
+
mock.driver.calls.filter(([name]) => name === 'quit').length,
|
|
476
|
+
1
|
|
477
|
+
);
|
|
478
|
+
assert.ok(started[0].child.killed);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
it('cleans up the driver if Safari authorization fails', async () => {
|
|
482
|
+
const { dependencies, started, mock } = safariDependencies();
|
|
483
|
+
mock.selenium.webdriver.Builder.prototype.build = () => {
|
|
484
|
+
throw new Error("You must enable the 'Allow Remote Automation' option");
|
|
485
|
+
};
|
|
486
|
+
await assert.rejects(
|
|
487
|
+
() => launchWebDriver({ browser: 'safari' }, dependencies),
|
|
488
|
+
(error) => error.code === 'SAFARI_SETUP_REQUIRED'
|
|
489
|
+
);
|
|
490
|
+
assert.ok(started[0].child.killed);
|
|
491
|
+
});
|
|
492
|
+
});
|
|
493
|
+
|
|
449
494
|
describe('connectWebDriver', () => {
|
|
450
495
|
it('opens a session on a running server with the given capabilities', async () => {
|
|
451
496
|
const mock = createMockSelenium();
|
|
@@ -182,8 +182,12 @@ describe('dispatcher: high-level methods', () => {
|
|
|
182
182
|
const migrated = await dispatch('profile.migrate', {
|
|
183
183
|
from: 'chrome',
|
|
184
184
|
include: ['cookies'],
|
|
185
|
+
passwordCsv: '/tmp/export.csv',
|
|
186
|
+
includePaymentCards: true,
|
|
185
187
|
});
|
|
186
188
|
assert.deepEqual(migrated.options.include, ['cookies']);
|
|
189
|
+
assert.equal(migrated.options.passwordCsv, '/tmp/export.csv');
|
|
190
|
+
assert.equal(migrated.options.includePaymentCards, true);
|
|
187
191
|
assert.deepEqual(await dispatch('doctor', {}), { ok: true, unlisted: [] });
|
|
188
192
|
assert.equal((await dispatch('version')).language, 'js');
|
|
189
193
|
});
|
|
@@ -276,7 +280,7 @@ describe('dispatcher: high-level methods', () => {
|
|
|
276
280
|
{ code: -32602 }
|
|
277
281
|
);
|
|
278
282
|
await assert.rejects(
|
|
279
|
-
dispatcher.dispatch('session.launch', { browser: '
|
|
283
|
+
dispatcher.dispatch('session.launch', { browser: 'unknown-browser' }),
|
|
280
284
|
{ code: -32602 }
|
|
281
285
|
);
|
|
282
286
|
await assert.rejects(dispatcher.dispatch('page.goto', { url: 'x' }), {
|
|
@@ -247,6 +247,9 @@ describe('runCli', () => {
|
|
|
247
247
|
'cookies, bookmarks',
|
|
248
248
|
'--domain',
|
|
249
249
|
'a.test',
|
|
250
|
+
'--password-csv',
|
|
251
|
+
'/tmp/export.csv',
|
|
252
|
+
'--include-payment-cards',
|
|
250
253
|
]);
|
|
251
254
|
|
|
252
255
|
// The source browser is grouped under `from` so migrateProfile receives
|
|
@@ -254,6 +257,8 @@ describe('runCli', () => {
|
|
|
254
257
|
assert.equal(document.options.from.browser, 'chrome');
|
|
255
258
|
assert.deepEqual(document.options.include, ['cookies', 'bookmarks']);
|
|
256
259
|
assert.deepEqual(document.options.domains, ['a.test']);
|
|
260
|
+
assert.equal(document.options.passwordCsv, '/tmp/export.csv');
|
|
261
|
+
assert.equal(document.options.includePaymentCards, true);
|
|
257
262
|
});
|
|
258
263
|
|
|
259
264
|
it('lists cookie sources with per-domain counts', async () => {
|
|
@@ -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
|
+
});
|