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.
This commit is contained in:
Chris Tate
2026-02-17 20:27:38 -06:00
committed by GitHub
parent f9b33ac23d
commit 19dd2d0c0b
5 changed files with 82 additions and 8 deletions
+36
View File
@@ -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', () => {
+17 -6
View File
@@ -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);
+25
View File
@@ -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', () => {
+2
View File
@@ -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(),
});
+2 -2
View File
@@ -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<string, string>;
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