browser-commander 0.32.1 → 0.33.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 +8 -0
- package/README.md +48 -5
- package/package.json +1 -1
- package/src/browser/chrome-for-testing-cache.js +198 -0
- package/src/browser/launch-executable.js +203 -0
- package/src/browser/launch-restrictions.json +7 -1
- package/src/browser/launcher.js +78 -65
- package/src/browser/parity.js +6 -1
- package/src/browser/real-browser.js +29 -9
- package/src/browser/webdriver.js +22 -3
- package/src/capture/screenshot.js +16 -1
- package/src/capture/viewport.js +2 -1
- package/src/exports.js +4 -0
- package/src/traces/recorder.js +18 -2
- package/tests/e2e/startup-readiness.e2e.test.js +2 -0
- package/tests/unit/browser/chrome-for-testing-cache.test.js +139 -0
- package/tests/unit/browser/launch-executable.test.js +93 -0
- package/tests/unit/browser/launcher.test.js +235 -31
- package/tests/unit/browser/parity.test.js +19 -0
- package/tests/unit/browser/real-browser.test.js +52 -0
- package/tests/unit/browser/webdriver.test.js +39 -0
- package/tests/unit/traces/issue-162-defaults.test.js +1 -1
- package/tests/unit/traces/issue-166-policy.test.js +1 -1
- package/tests/unit/traces/issue-180-screenshots.test.js +173 -0
|
@@ -381,6 +381,45 @@ describe('launchWebDriver', () => {
|
|
|
381
381
|
await session.close(); // idempotent
|
|
382
382
|
});
|
|
383
383
|
|
|
384
|
+
it('adds the Chrome for Testing defaults for the browser it resolves (issue #179)', async () => {
|
|
385
|
+
const launchWith = async (browserPath, options = {}) => {
|
|
386
|
+
const { mock, dependencies } = launchDependencies({
|
|
387
|
+
runCommand: async () =>
|
|
388
|
+
managerOutput({
|
|
389
|
+
driver_path: '/cache/chromedriver',
|
|
390
|
+
browser_path: browserPath,
|
|
391
|
+
}),
|
|
392
|
+
});
|
|
393
|
+
const session = await launchWebDriver(options, dependencies);
|
|
394
|
+
await session.close();
|
|
395
|
+
const { args } = mock.record.builders[0].options;
|
|
396
|
+
return {
|
|
397
|
+
defaults: session.defaultRestrictions,
|
|
398
|
+
switches: args.filter((arg) =>
|
|
399
|
+
['--test-type=gpu', '--use-mock-keychain'].includes(arg)
|
|
400
|
+
),
|
|
401
|
+
};
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
// Selenium Manager downloads Chrome for Testing when no Chrome is installed.
|
|
405
|
+
const cft = await launchWith(
|
|
406
|
+
'/home/u/.cache/selenium/chrome/linux64/131.0.6778.85/chrome'
|
|
407
|
+
);
|
|
408
|
+
assert.deepEqual(cft.defaults, [
|
|
409
|
+
'no-chrome-for-testing-infobar',
|
|
410
|
+
'mock-keychain',
|
|
411
|
+
]);
|
|
412
|
+
assert.deepEqual(cft.switches, ['--test-type=gpu', '--use-mock-keychain']);
|
|
413
|
+
|
|
414
|
+
const chrome = await launchWith('/opt/google/chrome/chrome');
|
|
415
|
+
assert.deepEqual(chrome, { defaults: [], switches: [] });
|
|
416
|
+
const off = await launchWith(
|
|
417
|
+
'/home/u/.cache/selenium/chrome/linux64/131.0.6778.85/chrome',
|
|
418
|
+
{ defaultRestrictions: [] }
|
|
419
|
+
);
|
|
420
|
+
assert.deepEqual(off, { defaults: [], switches: [] });
|
|
421
|
+
});
|
|
422
|
+
|
|
384
423
|
it('starts geckodriver for Firefox with a -profile argument', async () => {
|
|
385
424
|
const { mock, started, dependencies } = launchDependencies({
|
|
386
425
|
environment: { PATH: '/usr/bin' },
|
|
@@ -181,7 +181,7 @@ describe('complete recording defaults (issue #162)', { timeout: 15000 }, () => {
|
|
|
181
181
|
},
|
|
182
182
|
};
|
|
183
183
|
const launched = await launchBrowserWithDependencies(
|
|
184
|
-
{ debug: { output } },
|
|
184
|
+
{ channel: 'chrome', debug: { output } },
|
|
185
185
|
{
|
|
186
186
|
resolveSystem: async () => '/usr/bin/google-chrome',
|
|
187
187
|
settleMs: 0,
|
|
@@ -244,7 +244,7 @@ describe('opt-in recording policy (issue #166)', { timeout: 15000 }, () => {
|
|
|
244
244
|
await fixture(async ({ root }) => {
|
|
245
245
|
await assert.rejects(
|
|
246
246
|
launchBrowserWithDependencies(
|
|
247
|
-
{ debug: { directory: root } },
|
|
247
|
+
{ channel: 'chrome', debug: { directory: root } },
|
|
248
248
|
{
|
|
249
249
|
resolveSystem: async () => '/usr/bin/google-chrome',
|
|
250
250
|
launchRealBrowser: async () => {
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { startTrace } from '../../../src/traces/recorder.js';
|
|
6
|
+
import { TRACE_MODE } from '../../../src/traces/schema.js';
|
|
7
|
+
import { screenshot } from '../../../src/capture/index.js';
|
|
8
|
+
import {
|
|
9
|
+
createFakeCommander,
|
|
10
|
+
createFakePage,
|
|
11
|
+
useTempTraceDirectory,
|
|
12
|
+
} from '../../helpers/trace-fixtures.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A clipped engine screenshot of a headful window can leave Chrome's emulated
|
|
16
|
+
* size stuck after a resize (issue #180). Without a fixed viewport, captures
|
|
17
|
+
* go through CDP `Page.captureScreenshot` with no `clip`.
|
|
18
|
+
*/
|
|
19
|
+
function cdpFake({ viewport = null, failWith, engine = 'playwright' } = {}) {
|
|
20
|
+
const calls = { cdp: [], engine: [], detached: 0 };
|
|
21
|
+
const session = {
|
|
22
|
+
send: async (method, params) => {
|
|
23
|
+
calls.cdp.push({ method, params });
|
|
24
|
+
if (failWith) {
|
|
25
|
+
throw new Error(failWith);
|
|
26
|
+
}
|
|
27
|
+
return { data: Buffer.from('cdp-png').toString('base64') };
|
|
28
|
+
},
|
|
29
|
+
detach: async () => {
|
|
30
|
+
calls.detached += 1;
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
const page = createFakePage();
|
|
34
|
+
page.screenshot = async (options) => {
|
|
35
|
+
calls.engine.push(options);
|
|
36
|
+
return Buffer.from('engine-png');
|
|
37
|
+
};
|
|
38
|
+
if (engine === 'playwright') {
|
|
39
|
+
page.context = () => ({ newCDPSession: async () => session });
|
|
40
|
+
page.viewportSize = () => viewport;
|
|
41
|
+
} else {
|
|
42
|
+
page.createCDPSession = async () => session;
|
|
43
|
+
page.viewport = () => viewport;
|
|
44
|
+
}
|
|
45
|
+
return { page, calls };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
describe('trace checkpoint screenshots (issue #180)', () => {
|
|
49
|
+
const directory = useTempTraceDirectory();
|
|
50
|
+
|
|
51
|
+
async function checkpointShot(page, { withCommander = true, engine } = {}) {
|
|
52
|
+
const commander = createFakeCommander(page, engine ? { engine } : {});
|
|
53
|
+
const trace = await startTrace({
|
|
54
|
+
page,
|
|
55
|
+
...(withCommander ? { commander } : {}),
|
|
56
|
+
output: path.join(directory.path, 'run'),
|
|
57
|
+
mode: TRACE_MODE.CHECKPOINTS,
|
|
58
|
+
network: false,
|
|
59
|
+
links: false,
|
|
60
|
+
screenshots: true,
|
|
61
|
+
});
|
|
62
|
+
await trace.checkpoint('resized');
|
|
63
|
+
const stopped = await trace.stop();
|
|
64
|
+
return (
|
|
65
|
+
await fs.readFile(path.join(stopped.path, 'checkpoints/0001.png'))
|
|
66
|
+
).toString();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
for (const engine of ['playwright', 'puppeteer']) {
|
|
70
|
+
for (const withCommander of [true, false]) {
|
|
71
|
+
it(`${engine} ${withCommander ? 'commander' : 'bare page'} without a fixed viewport sends no clip`, async () => {
|
|
72
|
+
const { page, calls } = cdpFake({ engine });
|
|
73
|
+
assert.equal(
|
|
74
|
+
await checkpointShot(page, { withCommander, engine }),
|
|
75
|
+
'cdp-png'
|
|
76
|
+
);
|
|
77
|
+
assert.deepEqual(calls.cdp, [
|
|
78
|
+
{
|
|
79
|
+
method: 'Page.captureScreenshot',
|
|
80
|
+
params: {
|
|
81
|
+
format: 'png',
|
|
82
|
+
fromSurface: false,
|
|
83
|
+
captureBeyondViewport: false,
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
]);
|
|
87
|
+
assert.equal(calls.engine.length, 0);
|
|
88
|
+
assert.equal(calls.detached, 1);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
it('a fixed viewport keeps the engine screenshot', async () => {
|
|
94
|
+
const { page, calls } = cdpFake({
|
|
95
|
+
viewport: { width: 800, height: 600 },
|
|
96
|
+
});
|
|
97
|
+
assert.equal(await checkpointShot(page), 'engine-png');
|
|
98
|
+
assert.equal(calls.cdp.length, 0);
|
|
99
|
+
assert.deepEqual(calls.engine, [{ type: 'png' }]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('falls back to the engine screenshot when the view cannot be captured', async () => {
|
|
103
|
+
const { page, calls } = cdpFake({
|
|
104
|
+
failWith: 'Unable to capture screenshot',
|
|
105
|
+
});
|
|
106
|
+
assert.equal(await checkpointShot(page), 'engine-png');
|
|
107
|
+
assert.equal(calls.cdp.length, 1);
|
|
108
|
+
assert.equal(calls.detached, 1);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('a page without CDP keeps the engine screenshot', async () => {
|
|
112
|
+
const page = createFakePage();
|
|
113
|
+
assert.equal(
|
|
114
|
+
await checkpointShot(page, { withCommander: false }),
|
|
115
|
+
'fake-png'
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe('default commander screenshots (issue #180)', () => {
|
|
121
|
+
for (const engine of ['playwright', 'puppeteer']) {
|
|
122
|
+
it(`${engine} without a fixed viewport sends no clip`, async () => {
|
|
123
|
+
const { page, calls } = cdpFake({ engine });
|
|
124
|
+
assert.equal((await screenshot({ page, engine })).toString(), 'cdp-png');
|
|
125
|
+
assert.equal(calls.cdp.length, 1);
|
|
126
|
+
assert.equal('clip' in calls.cdp[0].params, false);
|
|
127
|
+
assert.equal(calls.engine.length, 0);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it(`${engine} with a fixed viewport keeps the engine screenshot`, async () => {
|
|
131
|
+
const { page, calls } = cdpFake({
|
|
132
|
+
engine,
|
|
133
|
+
viewport: { width: 800, height: 600 },
|
|
134
|
+
});
|
|
135
|
+
assert.equal(
|
|
136
|
+
(await screenshot({ page, engine })).toString(),
|
|
137
|
+
'engine-png'
|
|
138
|
+
);
|
|
139
|
+
assert.equal(calls.cdp.length, 0);
|
|
140
|
+
assert.equal(calls.engine[0].fullPage, false);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
it('JPEG quality reaches the view capture', async () => {
|
|
145
|
+
const { page, calls } = cdpFake();
|
|
146
|
+
await screenshot({ page, format: 'jpeg', quality: 60 });
|
|
147
|
+
assert.deepEqual(calls.cdp[0].params, {
|
|
148
|
+
format: 'jpeg',
|
|
149
|
+
fromSurface: false,
|
|
150
|
+
captureBeyondViewport: false,
|
|
151
|
+
quality: 60,
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
for (const options of [
|
|
156
|
+
{ fullPage: true },
|
|
157
|
+
{ clip: { x: 0, y: 0, width: 1, height: 1 } },
|
|
158
|
+
{ selector: '#pager' },
|
|
159
|
+
{ omitBackground: true },
|
|
160
|
+
{ hideScrollbars: true },
|
|
161
|
+
{ animations: 'disabled' },
|
|
162
|
+
{ scale: 'css' },
|
|
163
|
+
{ stableViewport: false },
|
|
164
|
+
]) {
|
|
165
|
+
it(`${JSON.stringify(options)} keeps the engine screenshot`, async () => {
|
|
166
|
+
const { page, calls } = cdpFake();
|
|
167
|
+
page.locator = () => ({ first: () => page });
|
|
168
|
+
await screenshot({ page, ...options });
|
|
169
|
+
assert.equal(calls.cdp.length, 0);
|
|
170
|
+
assert.equal(calls.engine.length, 1);
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
});
|