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.
@@ -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
+ });