browser-commander 0.33.1 → 0.33.2

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.33.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 0013b28: The failure screenshot that `browser-commander/tests` writes for a failed scenario captures the view without a clip for Playwright and Puppeteer pages that have no fixed viewport (issue #180), as trace checkpoints and default screenshots already do. A clipped engine capture could leave a resized headful window drawn at its old size. Pages with a fixed viewport and other engines keep the engine's screenshot.
8
+
3
9
  ## 0.33.1
4
10
 
5
11
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "browser-commander",
3
- "version": "0.33.1",
3
+ "version": "0.33.2",
4
4
  "description": "Universal browser automation library that supports Playwright, Puppeteer, and Selenium with a unified API",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -11,6 +11,7 @@ import path from 'node:path';
11
11
  import { test as baseTest } from 'test-anywhere';
12
12
  import { launchBrowser } from '../browser/launcher.js';
13
13
  import { makeBrowserCommander } from '../factory.js';
14
+ import { captureStableViewport } from '../capture/viewport.js';
14
15
  import {
15
16
  finishScenarioTrace,
16
17
  resolveTestDownloads,
@@ -571,7 +572,19 @@ export async function writeFailureArtifacts(options = {}) {
571
572
  if (page && typeof page.screenshot === 'function') {
572
573
  const screenshotPath = path.join(artifactsDir, `${safeName}.png`);
573
574
  try {
574
- await page.screenshot({ path: screenshotPath });
575
+ // A clipped engine capture can leave a resized headful window drawn at
576
+ // its old size (issue #180); without a fixed viewport, capture the view.
577
+ if (['playwright', 'puppeteer'].includes(engine)) {
578
+ const shot = await captureStableViewport(page, engine, {
579
+ type: 'png',
580
+ path: screenshotPath,
581
+ });
582
+ if (shot.engine === 'cdp') {
583
+ writeFileSync(screenshotPath, shot.bytes);
584
+ }
585
+ } else {
586
+ await page.screenshot({ path: screenshotPath });
587
+ }
575
588
  artifacts.push(screenshotPath);
576
589
  } catch (screenshotError) {
577
590
  const screenshotErrorPath = path.join(
@@ -232,6 +232,7 @@ describe('browser-commander/tests', () => {
232
232
  page: {
233
233
  screenshot: async ({ path: screenshotPath }) => {
234
234
  assert.ok(screenshotPath.endsWith('.png'));
235
+ return Buffer.from('png');
235
236
  },
236
237
  },
237
238
  error: new Error('broken'),
@@ -241,6 +242,7 @@ describe('browser-commander/tests', () => {
241
242
  });
242
243
 
243
244
  assert.strictEqual(artifacts.length, 2);
245
+ assert.ok(artifacts[1].endsWith('.png'), artifacts[1]);
244
246
  const errorJson = JSON.parse(readFileSync(artifacts[0], 'utf8'));
245
247
  assert.strictEqual(errorJson.message, 'broken');
246
248
  assert.strictEqual(errorJson.engine, 'playwright');
@@ -5,6 +5,7 @@ import path from 'node:path';
5
5
  import { startTrace } from '../../../src/traces/recorder.js';
6
6
  import { TRACE_MODE } from '../../../src/traces/schema.js';
7
7
  import { screenshot } from '../../../src/capture/index.js';
8
+ import { writeFailureArtifacts } from '../../../src/tests/index.js';
8
9
  import {
9
10
  createFakeCommander,
10
11
  createFakePage,
@@ -45,6 +46,22 @@ function cdpFake({ viewport = null, failWith, engine = 'playwright' } = {}) {
45
46
  return { page, calls };
46
47
  }
47
48
 
49
+ /** One clip-less CDP capture of the view, and no engine screenshot. */
50
+ function assertViewCapture(calls) {
51
+ assert.deepEqual(calls.cdp, [
52
+ {
53
+ method: 'Page.captureScreenshot',
54
+ params: {
55
+ format: 'png',
56
+ fromSurface: false,
57
+ captureBeyondViewport: false,
58
+ },
59
+ },
60
+ ]);
61
+ assert.equal(calls.engine.length, 0);
62
+ assert.equal(calls.detached, 1);
63
+ }
64
+
48
65
  describe('trace checkpoint screenshots (issue #180)', () => {
49
66
  const directory = useTempTraceDirectory();
50
67
 
@@ -74,18 +91,7 @@ describe('trace checkpoint screenshots (issue #180)', () => {
74
91
  await checkpointShot(page, { withCommander, engine }),
75
92
  'cdp-png'
76
93
  );
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);
94
+ assertViewCapture(calls);
89
95
  });
90
96
  }
91
97
  }
@@ -171,3 +177,48 @@ describe('default commander screenshots (issue #180)', () => {
171
177
  });
172
178
  }
173
179
  });
180
+
181
+ describe('failure artifact screenshots (issue #180)', () => {
182
+ const directory = useTempTraceDirectory();
183
+
184
+ /** The PNG artifact a failed scenario writes, read back as text. */
185
+ async function failureShot(page, engine) {
186
+ page.screenshot = async (options) => {
187
+ page.engineCalls.push(options);
188
+ await fs.writeFile(options.path, 'engine-png');
189
+ return Buffer.from('engine-png');
190
+ };
191
+ const [, shot] = await writeFailureArtifacts({
192
+ page,
193
+ error: new Error('broken'),
194
+ testId: 'resized',
195
+ engine,
196
+ artifactsDir: directory.path,
197
+ });
198
+ assert.ok(shot.endsWith('.png'), shot);
199
+ return (await fs.readFile(shot)).toString();
200
+ }
201
+
202
+ for (const engine of ['playwright', 'puppeteer']) {
203
+ it(`${engine} without a fixed viewport sends no clip`, async () => {
204
+ const { page, calls } = cdpFake({ engine });
205
+ page.engineCalls = calls.engine;
206
+ assert.equal(await failureShot(page, engine), 'cdp-png');
207
+ assertViewCapture(calls);
208
+ });
209
+ }
210
+
211
+ for (const [name, engine, viewport] of [
212
+ ['playwright with a fixed viewport', 'playwright', { width: 8, height: 6 }],
213
+ ['puppeteer with a fixed viewport', 'puppeteer', { width: 8, height: 6 }],
214
+ ['selenium', 'selenium', null],
215
+ ]) {
216
+ it(`${name} keeps the engine screenshot`, async () => {
217
+ const page =
218
+ engine === 'selenium' ? {} : cdpFake({ engine, viewport }).page;
219
+ page.engineCalls = [];
220
+ assert.equal(await failureShot(page, engine), 'engine-png');
221
+ assert.equal(page.engineCalls.length, 1);
222
+ });
223
+ }
224
+ });