browser-commander 0.7.0 → 0.8.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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.0
4
+
5
+ ### Minor Changes
6
+
7
+ - a681a87: Add unified `page.pdf(options)` method to `EngineAdapter`, `PlaywrightAdapter`, and `PuppeteerAdapter`, eliminating the need for users to access raw page objects via the `page._page || page` workaround. The `pdf()` method is also exposed on the `BrowserCommander` facade via `commander.pdf({ pdfOptions })`.
8
+
3
9
  ## 0.7.0
4
10
 
5
11
  ### Minor Changes
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Example: PDF generation via browser-commander
3
+ *
4
+ * This example shows how to use the unified page.pdf() method
5
+ * instead of the old workaround: `const rawPage = page._page || page`.
6
+ *
7
+ * Supports both Playwright and Puppeteer engines.
8
+ *
9
+ * Usage (Playwright):
10
+ * node examples/pdf-generation.js playwright
11
+ *
12
+ * Usage (Puppeteer):
13
+ * node examples/pdf-generation.js puppeteer
14
+ */
15
+
16
+ import fs from 'node:fs';
17
+ import path from 'node:path';
18
+ import { fileURLToPath } from 'node:url';
19
+
20
+ import { makeBrowserCommander } from '../src/factory.js';
21
+
22
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
23
+ const engine = process.argv[2] || 'playwright';
24
+
25
+ async function generatePdf() {
26
+ let browser;
27
+ let page;
28
+
29
+ if (engine === 'playwright') {
30
+ const { chromium } = await import('playwright');
31
+ browser = await chromium.launch({ headless: true });
32
+ const context = await browser.newContext();
33
+ page = await context.newPage();
34
+ } else if (engine === 'puppeteer') {
35
+ const puppeteer = await import('puppeteer');
36
+ browser = await puppeteer.launch({ headless: true });
37
+ page = await browser.newPage();
38
+ } else {
39
+ console.error(
40
+ `Unknown engine: ${engine}. Use 'playwright' or 'puppeteer'.`
41
+ );
42
+ process.exit(1);
43
+ }
44
+
45
+ try {
46
+ const commander = makeBrowserCommander({ page, verbose: false });
47
+
48
+ // Navigate to a page
49
+ await page.goto('https://example.com');
50
+
51
+ // Generate PDF using the unified API (no workarounds needed!)
52
+ const pdfBuffer = await commander.pdf({
53
+ pdfOptions: {
54
+ format: 'A4',
55
+ printBackground: true,
56
+ margin: { top: '1cm', right: '1cm', bottom: '1cm', left: '1cm' },
57
+ },
58
+ });
59
+
60
+ const outputPath = path.join(__dirname, 'output.pdf');
61
+ fs.writeFileSync(outputPath, pdfBuffer);
62
+
63
+ console.log(
64
+ `PDF generated successfully: ${outputPath} (${pdfBuffer.length} bytes)`
65
+ );
66
+
67
+ await commander.destroy();
68
+ } finally {
69
+ await browser.close();
70
+ }
71
+ }
72
+
73
+ generatePdf().catch((error) => {
74
+ console.error('Error:', error.message);
75
+ process.exit(1);
76
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "browser-commander",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Universal browser automation library that supports both Playwright and Puppeteer with a unified API",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/bindings.js CHANGED
@@ -53,6 +53,7 @@ import {
53
53
  checkAndClearFlag,
54
54
  findToggleButton,
55
55
  } from './high-level/universal-logic.js';
56
+ import { pdf } from './browser/pdf.js';
56
57
  import { pressKey, typeText, keyDown, keyUp } from './interactions/keyboard.js';
57
58
 
58
59
  /**
@@ -215,6 +216,9 @@ export function createBoundFunctions(options = {}) {
215
216
  findByText: findByTextBound,
216
217
  });
217
218
 
219
+ // Bound pdf generation
220
+ const pdfBound = (opts = {}) => pdf({ ...opts, page, engine });
221
+
218
222
  // Bound keyboard
219
223
  const pressKeyBound = (opts) => pressKey({ ...opts, page, engine });
220
224
  const typeTextBound = (opts) => typeText({ ...opts, page, engine });
@@ -307,6 +311,9 @@ export function createBoundFunctions(options = {}) {
307
311
  checkAndClearFlag: checkAndClearFlagBound,
308
312
  findToggleButton: findToggleButtonBound,
309
313
 
314
+ // PDF generation
315
+ pdf: pdfBound,
316
+
310
317
  // Page-level keyboard interaction
311
318
  // Usage: await commander.keyboard.press('Escape')
312
319
  keyboard: {
@@ -0,0 +1,31 @@
1
+ /**
2
+ * PDF generation support
3
+ *
4
+ * Wraps Playwright/Puppeteer's page.pdf() method behind a unified interface.
5
+ * Both engines expose identical options: format, printBackground, margin, etc.
6
+ *
7
+ * Note: PDF generation only works in Chromium headless mode.
8
+ * Playwright Firefox and WebKit do not support page.pdf().
9
+ */
10
+
11
+ import { createEngineAdapter } from '../core/engine-adapter.js';
12
+
13
+ /**
14
+ * Generate a PDF of the current page
15
+ *
16
+ * @param {Object} options - Configuration options
17
+ * @param {Object} options.page - Playwright or Puppeteer page object
18
+ * @param {string} options.engine - Engine type ('playwright' or 'puppeteer')
19
+ * @param {Object} options.pdfOptions - PDF generation options passed to the engine
20
+ * @param {string} [options.pdfOptions.format] - Paper format (e.g. 'A4', 'Letter')
21
+ * @param {boolean} [options.pdfOptions.printBackground] - Print background graphics
22
+ * @param {Object} [options.pdfOptions.margin] - Page margins { top, right, bottom, left }
23
+ * @param {string} [options.pdfOptions.path] - File path to save the PDF (optional)
24
+ * @param {string} [options.pdfOptions.scale] - Scale of the webpage rendering (0.1–2)
25
+ * @returns {Promise<Buffer>} - PDF as a Buffer
26
+ */
27
+ export async function pdf(options = {}) {
28
+ const { page, engine, pdfOptions = {} } = options;
29
+ const adapter = createEngineAdapter(page, engine);
30
+ return await adapter.pdf(pdfOptions);
31
+ }
@@ -237,6 +237,15 @@ export class EngineAdapter {
237
237
  getMainFrame() {
238
238
  throw new Error('getMainFrame() must be implemented by subclass');
239
239
  }
240
+
241
+ /**
242
+ * Generate a PDF of the current page
243
+ * @param {Object} options - PDF options (format, printBackground, margin, etc.)
244
+ * @returns {Promise<Buffer>} - PDF buffer
245
+ */
246
+ async pdf(options = {}) {
247
+ throw new Error('pdf() must be implemented by subclass');
248
+ }
240
249
  }
241
250
 
242
251
  /**
@@ -389,6 +398,10 @@ export class PlaywrightAdapter extends EngineAdapter {
389
398
  getMainFrame() {
390
399
  return this.page.mainFrame();
391
400
  }
401
+
402
+ async pdf(options = {}) {
403
+ return await this.page.pdf(options);
404
+ }
392
405
  }
393
406
 
394
407
  /**
@@ -525,6 +538,10 @@ export class PuppeteerAdapter extends EngineAdapter {
525
538
  getMainFrame() {
526
539
  return this.page.mainFrame();
527
540
  }
541
+
542
+ async pdf(options = {}) {
543
+ return await this.page.pdf(options);
544
+ }
528
545
  }
529
546
 
530
547
  /**
package/src/exports.js CHANGED
@@ -55,6 +55,8 @@ export {
55
55
  verifyNavigation,
56
56
  } from './browser/navigation.js';
57
57
 
58
+ export { pdf } from './browser/pdf.js';
59
+
58
60
  // Re-export element operations
59
61
  export {
60
62
  createPlaywrightLocator,
@@ -152,6 +152,7 @@ export function createMockPlaywrightPage(options = {}) {
152
152
  down: async (key) => {},
153
153
  up: async (key) => {},
154
154
  },
155
+ pdf: async (opts = {}) => Buffer.from('%PDF-1.4 mock playwright'),
155
156
  };
156
157
  }
157
158
 
@@ -312,6 +313,7 @@ export function createMockPuppeteerPage(options = {}) {
312
313
  down: async (key) => {},
313
314
  up: async (key) => {},
314
315
  },
316
+ pdf: async (opts = {}) => Buffer.from('%PDF-1.4 mock puppeteer'),
315
317
  };
316
318
 
317
319
  return page;
@@ -0,0 +1,80 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import { pdf } from '../../../src/browser/pdf.js';
4
+ import {
5
+ createMockPlaywrightPage,
6
+ createMockPuppeteerPage,
7
+ } from '../../helpers/mocks.js';
8
+
9
+ describe('browser/pdf', () => {
10
+ describe('pdf() with Playwright engine', () => {
11
+ it('should return a Buffer', async () => {
12
+ const page = createMockPlaywrightPage();
13
+ const result = await pdf({ page, engine: 'playwright' });
14
+ assert.ok(result instanceof Buffer || result instanceof Uint8Array);
15
+ });
16
+
17
+ it('should pass pdf options to the underlying page', async () => {
18
+ const capturedOptions = {};
19
+ const page = createMockPlaywrightPage();
20
+ page.pdf = async (opts = {}) => {
21
+ Object.assign(capturedOptions, opts);
22
+ return Buffer.from('%PDF-1.4');
23
+ };
24
+
25
+ await pdf({
26
+ page,
27
+ engine: 'playwright',
28
+ pdfOptions: {
29
+ format: 'A4',
30
+ printBackground: true,
31
+ margin: { top: '1cm', right: '1cm', bottom: '1cm', left: '1cm' },
32
+ },
33
+ });
34
+
35
+ assert.strictEqual(capturedOptions.format, 'A4');
36
+ assert.strictEqual(capturedOptions.printBackground, true);
37
+ assert.deepStrictEqual(capturedOptions.margin, {
38
+ top: '1cm',
39
+ right: '1cm',
40
+ bottom: '1cm',
41
+ left: '1cm',
42
+ });
43
+ });
44
+
45
+ it('should work with empty options', async () => {
46
+ const page = createMockPlaywrightPage();
47
+ const result = await pdf({ page, engine: 'playwright', pdfOptions: {} });
48
+ assert.ok(result);
49
+ });
50
+ });
51
+
52
+ describe('pdf() with Puppeteer engine', () => {
53
+ it('should return a Buffer', async () => {
54
+ const page = createMockPuppeteerPage();
55
+ const result = await pdf({ page, engine: 'puppeteer' });
56
+ assert.ok(result instanceof Buffer || result instanceof Uint8Array);
57
+ });
58
+
59
+ it('should pass pdf options to the underlying page', async () => {
60
+ const capturedOptions = {};
61
+ const page = createMockPuppeteerPage();
62
+ page.pdf = async (opts = {}) => {
63
+ Object.assign(capturedOptions, opts);
64
+ return Buffer.from('%PDF-1.4');
65
+ };
66
+
67
+ await pdf({
68
+ page,
69
+ engine: 'puppeteer',
70
+ pdfOptions: {
71
+ format: 'Letter',
72
+ printBackground: false,
73
+ },
74
+ });
75
+
76
+ assert.strictEqual(capturedOptions.format, 'Letter');
77
+ assert.strictEqual(capturedOptions.printBackground, false);
78
+ });
79
+ });
80
+ });
@@ -215,6 +215,27 @@ describe('engine-adapter', () => {
215
215
  });
216
216
  });
217
217
 
218
+ describe('pdf()', () => {
219
+ it('PlaywrightAdapter.pdf() should return a Buffer', async () => {
220
+ const page = createMockPlaywrightPage();
221
+ const adapter = new PlaywrightAdapter(page);
222
+ const result = await adapter.pdf({ format: 'A4', printBackground: true });
223
+ assert.ok(result instanceof Buffer || result instanceof Uint8Array);
224
+ });
225
+
226
+ it('PuppeteerAdapter.pdf() should return a Buffer', async () => {
227
+ const page = createMockPuppeteerPage();
228
+ const adapter = new PuppeteerAdapter(page);
229
+ const result = await adapter.pdf({ format: 'A4', printBackground: true });
230
+ assert.ok(result instanceof Buffer || result instanceof Uint8Array);
231
+ });
232
+
233
+ it('EngineAdapter base class pdf() should throw', async () => {
234
+ const adapter = new EngineAdapter({});
235
+ await assert.rejects(async () => adapter.pdf({}), /must be implemented/);
236
+ });
237
+ });
238
+
218
239
  describe('createEngineAdapter', () => {
219
240
  it('should create PlaywrightAdapter for playwright engine', () => {
220
241
  const page = createMockPlaywrightPage();