Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions docs/src/api/class-browsertype.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,17 @@ can see what is going on. Defaults to 0.
Maximum time in milliseconds to wait for the connection to be established. Defaults to
`30000` (30 seconds). Pass `0` to disable timeout.

### option: BrowserType.connectOverCDP.noDefaults
* since: v1.53
- `noDefaults` <[boolean]>

When true, Playwright will not send default overrides to the browser on the default context. This includes
`Browser.setDownloadBehavior`, `Emulation.setFocusEmulationEnabled`, and `Emulation.setEmulatedMedia`.
Useful when attaching to a user's daily-driver browser where these overrides would interfere with
existing browser state. New contexts created via
[browser.newContext([options])](https://playwright.dev/docs/api/class-browser#browser-new-context)
are not affected. Defaults to `false`.


## async method: BrowserType.connectToWorker
* since: v1.60
Expand Down
10 changes: 10 additions & 0 deletions packages/playwright-client/types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22586,6 +22586,16 @@ export interface ConnectOverCDPOptions {
*/
isLocal?: boolean;

/**
* When true, Playwright will not send default overrides to the browser on the default context. This includes
* `Browser.setDownloadBehavior`, `Emulation.setFocusEmulationEnabled`, and `Emulation.setEmulatedMedia`. Useful when
* attaching to a user's daily-driver browser where these overrides would interfere with existing browser state. New
* contexts created via
* [browser.newContext([options])](https://playwright.dev/docs/api/class-browser#browser-new-context) are not
* affected. Defaults to `false`.
*/
noDefaults?: boolean;

/**
* Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going
* on. Defaults to 0.
Expand Down
1 change: 1 addition & 0 deletions packages/playwright-core/src/client/browserType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
slowMo: params.slowMo,
timeout: new TimeoutSettings(this._platform).timeout(params),
isLocal: params.isLocal,
noDefaults: params.noDefaults,
});
const browser = Browser.from(result.browser);
browser._connectToBrowserType(this, {}, undefined);
Expand Down
1 change: 1 addition & 0 deletions packages/playwright-core/src/protocol/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,7 @@ scheme.BrowserTypeConnectOverCDPParams = tObject({
slowMo: tOptional(tFloat),
timeout: tFloat,
isLocal: tOptional(tBoolean),
noDefaults: tOptional(tBoolean),
});
scheme.BrowserTypeConnectOverCDPResult = tObject({
browser: tChannel(['Browser']),
Expand Down
1 change: 1 addition & 0 deletions packages/playwright-core/src/server/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export type BrowserOptions = {
sdkLanguage?: Language;
originalLaunchOptions: types.LaunchOptions;
userDataDir?: string;
noDefaults?: boolean;
};

export abstract class Browser extends SdkObject {
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright-core/src/server/browserType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ export abstract class BrowserType extends SdkObject {
}
}

async connectOverCDP(progress: Progress, endpointURL: string, options: { slowMo?: number, timeout?: number, headers?: types.HeadersArray, isLocal?: boolean }): Promise<Browser> {
async connectOverCDP(progress: Progress, endpointURL: string, options: { slowMo?: number, timeout?: number, headers?: types.HeadersArray, isLocal?: boolean, noDefaults?: boolean }): Promise<Browser> {
throw new Error('CDP connections are only supported by Chromium');
}

Expand Down
12 changes: 8 additions & 4 deletions packages/playwright-core/src/server/chromium/chromium.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,11 @@ export class Chromium extends BrowserType {
return super.launchPersistentContext(progress, userDataDir, options);
}

override async connectOverCDP(progress: Progress, endpointURL: string, options: { slowMo?: number, headers?: types.HeadersArray, isLocal?: boolean }) {
override async connectOverCDP(progress: Progress, endpointURL: string, options: { slowMo?: number, headers?: types.HeadersArray, isLocal?: boolean, noDefaults?: boolean }) {
return await this._connectOverCDPInternal(progress, endpointURL, options);
}

async _connectOverCDPInternal(progress: Progress, endpointURL: string, options: types.LaunchOptions & { headers?: types.HeadersArray, isLocal?: boolean }, onClose?: () => Promise<void>) {
async _connectOverCDPInternal(progress: Progress, endpointURL: string, options: types.LaunchOptions & { headers?: types.HeadersArray, isLocal?: boolean, noDefaults?: boolean }, onClose?: () => Promise<void>) {
let headersMap: { [key: string]: string; } | undefined;
if (options.headers)
headersMap = headersArrayToObject(options.headers, false);
Expand Down Expand Up @@ -112,7 +112,7 @@ export class Chromium extends BrowserType {
return this._connectOverCDPImpl(progress, chromeTransport, closeAndWait, options, onClose);
}

private async _connectOverCDPImpl(progress: Progress, transport: ConnectionTransport, closeAndWait: () => Promise<void>, options: types.LaunchOptions & { isLocal?: boolean }, onClose?: () => Promise<void>) {
private async _connectOverCDPImpl(progress: Progress, transport: ConnectionTransport, closeAndWait: () => Promise<void>, options: types.LaunchOptions & { isLocal?: boolean, noDefaults?: boolean }, onClose?: () => Promise<void>) {
const artifactsDir = await progress.race(fs.promises.mkdtemp(ARTIFACTS_FOLDER));
const doCleanup = async () => {
await removeFolders([artifactsDir]);
Expand All @@ -128,7 +128,10 @@ export class Chromium extends BrowserType {

try {
const browserProcess: BrowserProcess = { close: doClose, kill: doClose };
const persistent: types.BrowserContextOptions = { noDefaultViewport: true };
const persistent: types.BrowserContextOptions = {
noDefaultViewport: true,
...(options.noDefaults ? { acceptDownloads: 'internal-browser-default' as const } : {}),
};
const browserOptions: BrowserOptions = {
slowMo: options.slowMo,
name: 'chromium',
Expand All @@ -141,6 +144,7 @@ export class Chromium extends BrowserType {
downloadsPath: options.downloadsPath || artifactsDir,
tracesDir: options.tracesDir || artifactsDir,
originalLaunchOptions: {},
noDefaults: options.noDefaults,
};
validateBrowserContextOptions(persistent, browserOptions);
const browser = await progress.race(CRBrowser.connect(this.attribution.playwright, transport, browserOptions));
Expand Down
7 changes: 5 additions & 2 deletions packages/playwright-core/src/server/chromium/crPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -512,9 +512,11 @@ class FrameSession {
this._client.send('Target.setAutoAttach', { autoAttach: true, waitForDebuggerOnStart: true, flatten: true }),
];
if (!this._page.isStorageStatePage) {
const skipDefaultOverrides = this._crPage._browserContext._browser.options.noDefaults &&
this._crPage._browserContext === this._crPage._browserContext._browser._defaultContext;
if (this._crPage._browserContext.needsPlaywrightBinding())
promises.push(this.exposePlaywrightBinding());
if (this._isMainFrame())
if (this._isMainFrame() && !skipDefaultOverrides)
promises.push(this._client.send('Emulation.setFocusEmulationEnabled', { enabled: true }));
const options = this._crPage._browserContext._options;
if (options.bypassCSP)
Expand All @@ -536,7 +538,8 @@ class FrameSession {
if (!this._crPage._browserContext._browser.options.headful)
promises.push(this._setDefaultFontFamilies(this._client));
promises.push(this._updateGeolocation(true));
promises.push(this._updateEmulateMedia());
if (!skipDefaultOverrides)
promises.push(this._updateEmulateMedia());
promises.push(this._updateFileChooserInterception(true));
for (const initScript of this._crPage._page.allInitScripts())
promises.push(this._evaluateOnNewDocument(initScript, 'main', true /* runImmediately */));
Expand Down
10 changes: 10 additions & 0 deletions packages/playwright-core/types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22586,6 +22586,16 @@ export interface ConnectOverCDPOptions {
*/
isLocal?: boolean;

/**
* When true, Playwright will not send default overrides to the browser on the default context. This includes
* `Browser.setDownloadBehavior`, `Emulation.setFocusEmulationEnabled`, and `Emulation.setEmulatedMedia`. Useful when
* attaching to a user's daily-driver browser where these overrides would interfere with existing browser state. New
* contexts created via
* [browser.newContext([options])](https://playwright.dev/docs/api/class-browser#browser-new-context) are not
* affected. Defaults to `false`.
*/
noDefaults?: boolean;

/**
* Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going
* on. Defaults to 0.
Expand Down
2 changes: 2 additions & 0 deletions packages/protocol/src/channels.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1133,11 +1133,13 @@ export type BrowserTypeConnectOverCDPParams = {
slowMo?: number,
timeout: number,
isLocal?: boolean,
noDefaults?: boolean,
};
export type BrowserTypeConnectOverCDPOptions = {
headers?: NameValue[],
slowMo?: number,
isLocal?: boolean,
noDefaults?: boolean,
};
export type BrowserTypeConnectOverCDPResult = {
browser: BrowserChannel,
Expand Down
1 change: 1 addition & 0 deletions packages/protocol/src/protocol.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1030,6 +1030,7 @@ BrowserType:
slowMo: float?
timeout: float
isLocal: boolean?
noDefaults: boolean?
returns:
browser: Browser
defaultContext: BrowserContext?
Expand Down
58 changes: 58 additions & 0 deletions tests/library/chromium/connect-over-cdp.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -618,3 +618,61 @@ test('should get title and URL of existing page', async ({ browserType, mode, se
await browserServer.close();
}
});

test('should skip default overrides with noDefaults', async ({ browserType, mode, server }, testInfo) => {
test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/40158' });
const port = 9339 + testInfo.workerIndex;
const browserServer = await browserType.launch({
args: ['--remote-debugging-port=' + port]
});
try {
const browser = await browserType.connectOverCDP({
endpointURL: `http://127.0.0.1:${port}/`,
noDefaults: true,
});
const defaultContext = browser.contexts()[0];
const page = await defaultContext.newPage();

// Browser.setDownloadBehavior was not sent, so no Playwright download event fires.
server.setRoute('/download', (req, res) => {
res.setHeader('Content-Type', 'application/octet-stream');
res.setHeader('Content-Disposition', 'attachment; filename=file.txt');
res.end('Hello world');
});
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
let sawDownload = false;
page.on('download', () => { sawDownload = true; });
await page.click('a');
await page.waitForTimeout(500);
expect(sawDownload).toBe(false);

await browser.close();
} finally {
await browserServer.close();
}
});

test('noDefaults should not affect new contexts', async ({ browserType, mode, server }, testInfo) => {
test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/40158' });
const port = 9339 + testInfo.workerIndex;
const browserServer = await browserType.launch({
args: ['--remote-debugging-port=' + port]
});
try {
const browser = await browserType.connectOverCDP({
endpointURL: `http://127.0.0.1:${port}/`,
noDefaults: true,
});

// New contexts should still get normal Playwright defaults.
const newContext = await browser.newContext();
const page = await newContext.newPage();
const hasFocus = await page.evaluate(() => document.hasFocus());
expect(hasFocus).toBe(true);

await newContext.close();
await browser.close();
} finally {
await browserServer.close();
}
});
Loading