From 19dd2d0c0b4bc0fd167f6ff28ee2786330f95e8b Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 17 Feb 2026 20:27:38 -0600 Subject: [PATCH] fix(#491): auto-disable viewport for --start-maximized and --window-size args (#492) Fixes #491 When `--start-maximized` or `--window-size` is passed as a browser arg, Playwright's default viewport (1280x720) overrides the browser's own window sizing, making those flags have no effect on the page content. This change auto-detects those args and sets `viewport: null` so Playwright defers to the browser's window size. Explicit viewport values still take priority. Also allows `viewport: null` in the launch protocol for agents that want to disable viewport emulation directly. --- src/browser.test.ts | 36 ++++++++++++++++++++++++++++++++++++ src/browser.ts | 23 +++++++++++++++++------ src/protocol.test.ts | 25 +++++++++++++++++++++++++ src/protocol.ts | 2 ++ src/types.ts | 4 ++-- 5 files changed, 82 insertions(+), 8 deletions(-) diff --git a/src/browser.test.ts b/src/browser.test.ts index 5488930..fa7462d 100644 --- a/src/browser.test.ts +++ b/src/browser.test.ts @@ -484,6 +484,42 @@ describe('BrowserManager', () => { expect(size?.width).toBe(1920); expect(size?.height).toBe(1080); }); + + it('should disable viewport when --start-maximized is in args', async () => { + const testBrowser = new BrowserManager(); + await testBrowser.launch({ headless: true, args: ['--start-maximized'] }); + const page = testBrowser.getPage(); + expect(page.viewportSize()).toBeNull(); + await testBrowser.close(); + }); + + it('should disable viewport when --window-size is in args', async () => { + const testBrowser = new BrowserManager(); + await testBrowser.launch({ headless: true, args: ['--window-size=800,600'] }); + const page = testBrowser.getPage(); + expect(page.viewportSize()).toBeNull(); + await testBrowser.close(); + }); + + it('should use default viewport when no window size args', async () => { + const testBrowser = new BrowserManager(); + await testBrowser.launch({ headless: true }); + const page = testBrowser.getPage(); + expect(page.viewportSize()).toEqual({ width: 1280, height: 720 }); + await testBrowser.close(); + }); + + it('should use explicit viewport even with --start-maximized', async () => { + const testBrowser = new BrowserManager(); + await testBrowser.launch({ + headless: true, + args: ['--start-maximized'], + viewport: { width: 800, height: 600 }, + }); + const page = testBrowser.getPage(); + expect(page.viewportSize()).toEqual({ width: 800, height: 600 }); + await testBrowser.close(); + }); }); describe('snapshot', () => { diff --git a/src/browser.ts b/src/browser.ts index 365ae8e..5205355 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -1156,7 +1156,6 @@ export class BrowserManager { const launcher = browserType === 'firefox' ? firefox : browserType === 'webkit' ? webkit : chromium; - const viewport = options.viewport ?? { width: 1280, height: 720 }; // Build base args array with file access flags if enabled // --allow-file-access-from-files: allows file:// URLs to read other file:// URLs via XHR/fetch @@ -1170,6 +1169,18 @@ export class BrowserManager { ? fileAccessArgs : undefined; + // Auto-detect args that control window size and disable viewport emulation + // so Playwright doesn't override the browser's own sizing behavior + const hasWindowSizeArgs = baseArgs?.some( + (arg) => arg === '--start-maximized' || arg.startsWith('--window-size=') + ); + const viewport = + options.viewport !== undefined + ? options.viewport + : hasWindowSizeArgs + ? null + : { width: 1280, height: 720 }; + let context: BrowserContext; if (hasExtensions) { // Extensions require persistent context in a temp directory @@ -1597,16 +1608,16 @@ export class BrowserManager { /** * Create a new window (new context) */ - async newWindow(viewport?: { - width: number; - height: number; - }): Promise<{ index: number; total: number }> { + async newWindow(viewport?: { width: number; height: number } | null): Promise<{ + index: number; + total: number; + }> { if (!this.browser) { throw new Error('Browser not launched'); } const context = await this.browser.newContext({ - viewport: viewport ?? { width: 1280, height: 720 }, + viewport: viewport === undefined ? { width: 1280, height: 720 } : viewport, }); context.setDefaultTimeout(60000); this.contexts.push(context); diff --git a/src/protocol.test.ts b/src/protocol.test.ts index 0e14263..92ef74b 100644 --- a/src/protocol.test.ts +++ b/src/protocol.test.ts @@ -598,6 +598,31 @@ describe('parseCommand', () => { expect(result.command.allowFileAccess).toBe(false); } }); + + it('should parse launch with viewport dimensions', () => { + const result = parseCommand( + cmd({ id: '1', action: 'launch', viewport: { width: 1920, height: 1080 } }) + ); + expect(result.success).toBe(true); + if (result.success) { + expect(result.command.viewport).toEqual({ width: 1920, height: 1080 }); + } + }); + + it('should parse launch with viewport null', () => { + const result = parseCommand(cmd({ id: '1', action: 'launch', viewport: null })); + expect(result.success).toBe(true); + if (result.success) { + expect(result.command.viewport).toBeNull(); + } + }); + + it('should reject launch with invalid viewport', () => { + const result = parseCommand( + cmd({ id: '1', action: 'launch', viewport: { width: -1, height: 720 } }) + ); + expect(result.success).toBe(false); + }); }); describe('mouse actions', () => { diff --git a/src/protocol.ts b/src/protocol.ts index 72e00b4..ce5e693 100644 --- a/src/protocol.ts +++ b/src/protocol.ts @@ -16,6 +16,7 @@ const launchSchema = baseCommandSchema.extend({ width: z.number().positive(), height: z.number().positive(), }) + .nullable() .optional(), browser: z.enum(['chromium', 'firefox', 'webkit']).optional(), cdpPort: z.number().positive().optional(), @@ -822,6 +823,7 @@ const windowNewSchema = baseCommandSchema.extend({ width: z.number().positive(), height: z.number().positive(), }) + .nullable() .optional(), }); diff --git a/src/types.ts b/src/types.ts index 004f9e6..9f672d8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -10,7 +10,7 @@ export interface BaseCommand { export interface LaunchCommand extends BaseCommand { action: 'launch'; headless?: boolean; - viewport?: { width: number; height: number }; + viewport?: { width: number; height: number } | null; browser?: 'chromium' | 'firefox' | 'webkit'; headers?: Record; executablePath?: string; @@ -850,7 +850,7 @@ export interface TabCloseCommand extends BaseCommand { export interface WindowNewCommand extends BaseCommand { action: 'window_new'; - viewport?: { width: number; height: number }; + viewport?: { width: number; height: number } | null; } // Union of all command types