browser-commander 0.24.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 +1 -1
- package/CHANGELOG.md +24 -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 +600 -23
- 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 +13 -1
- 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/system-browser.js +31 -143
- package/src/cli/args.js +2 -0
- package/src/cli/commands.js +2 -0
- package/src/cli/high-level-methods.js +2 -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/cli/dispatcher.test.js +4 -0
- 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
|
+
});
|
|
@@ -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
|
});
|
|
@@ -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
|
+
});
|