Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(vscode): allow running codegen with selected browser/options #29454

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/playwright-core/src/protocol/validator.ts
Expand Up @@ -400,6 +400,9 @@ scheme.DebugControllerNavigateResult = tOptional(tObject({}));
scheme.DebugControllerSetRecorderModeParams = tObject({
mode: tEnum(['inspecting', 'recording', 'none']),
testIdAttributeName: tOptional(tString),
browserName: tOptional(tEnum(['chromium', 'firefox', 'webkit'])),
contextOptions: tOptional(tType('BrowserNewContextForReuseParams')),
launchOptions: tOptional(tType('BrowserTypeLaunchParams')),
});
scheme.DebugControllerSetRecorderModeResult = tOptional(tObject({}));
scheme.DebugControllerHighlightParams = tObject({
Expand Down
34 changes: 1 addition & 33 deletions packages/playwright-core/src/remote/playwrightConnection.ts
Expand Up @@ -181,13 +181,7 @@ export class PlaywrightConnection {
debugLogger.log('server', `[${this._id}] engaged reuse browsers mode for ${this._options.browserName}`);
const playwright = this._preLaunched.playwright!;

const requestedOptions = launchOptionsHash(this._options.launchOptions);
let browser = playwright.allBrowsers().find(b => {
if (b.options.name !== this._options.browserName)
return false;
const existingOptions = launchOptionsHash(b.options.originalLaunchOptions);
return existingOptions === requestedOptions;
});
let browser = playwright.findBrowserWithMatchingOptions(this._options.browserName, this._options.launchOptions);

// Close remaining browsers of this type+channel. Keep different browser types for the speed.
for (const b of playwright.allBrowsers()) {
Expand Down Expand Up @@ -272,18 +266,6 @@ export class PlaywrightConnection {
}
}

function launchOptionsHash(options: LaunchOptions) {
const copy = { ...options };
for (const k of Object.keys(copy)) {
const key = k as keyof LaunchOptions;
if (copy[key] === defaultLaunchOptions[key])
delete copy[key];
}
for (const key of optionsThatAllowBrowserReuse)
delete copy[key];
return JSON.stringify(copy);
}

function filterLaunchOptions(options: LaunchOptions): LaunchOptions {
return {
channel: options.channel,
Expand All @@ -299,17 +281,3 @@ function filterLaunchOptions(options: LaunchOptions): LaunchOptions {
executablePath: isUnderTest() ? options.executablePath : undefined,
};
}

const defaultLaunchOptions: LaunchOptions = {
ignoreAllDefaultArgs: false,
handleSIGINT: false,
handleSIGTERM: false,
handleSIGHUP: false,
headless: true,
devtools: false,
};

const optionsThatAllowBrowserReuse: (keyof LaunchOptions)[] = [
'headless',
'tracesDir',
];
23 changes: 23 additions & 0 deletions packages/playwright-core/src/server/browserContext.ts
Expand Up @@ -228,6 +228,29 @@ export abstract class BrowserContext extends SdkObject {
await page?.resetForReuse(metadata);
}

async reapplyContextOptionsIfNeeded(options: channels.BrowserNewContextForReuseParams = {}) {
const promises: Promise<any>[] = [];
const hash = (obj: any) => JSON.stringify(obj);
if (options.viewport && hash(options.viewport) !== hash(this._options.viewport))
promises.push(...this.pages().map(page => page.setViewportSize(options.viewport!)));
if (options.extraHTTPHeaders && hash(options.extraHTTPHeaders) !== hash(this._options.extraHTTPHeaders))
promises.push(this.setExtraHTTPHeaders(options.extraHTTPHeaders));
if (options.geolocation && hash(options.geolocation) !== hash(this._options.geolocation))
promises.push(this.setGeolocation(options.geolocation));
if (options.offline !== undefined && options.offline !== this._options.offline)
promises.push(this.setOffline(!!options.offline));
if (options.userAgent && options.userAgent !== this._options.userAgent)
promises.push(this.setUserAgent(options.userAgent));
if (options.storageState && hash(options.storageState) !== hash(this._options.storageState))
promises.push(this.setStorageState(serverSideCallMetadata(), options.storageState));
if (options.permissions && hash(options.permissions) !== hash(this._options.permissions))
promises.push(this.grantPermissions(options.permissions));
const hashMedia = (colorScheme?: types.ColorScheme, reducedMotion?: types.ReducedMotion, forcedColors?: types.ForcedColors) => hash({ colorScheme, reducedMotion, forcedColors });
if (hashMedia(options.colorScheme, options.reducedMotion, options.forcedColors) !== hashMedia(this._options.colorScheme, this._options.reducedMotion, this._options.forcedColors))
promises.push(...this.pages().map(page => page.emulateMedia({ colorScheme: options.colorScheme, reducedMotion: options.reducedMotion, forcedColors: options.forcedColors })));
await Promise.all(promises);
}

_browserClosed() {
for (const page of this.pages())
page._didClose();
Expand Down
35 changes: 25 additions & 10 deletions packages/playwright-core/src/server/debugController.ts
Expand Up @@ -25,6 +25,7 @@ import { Recorder } from './recorder';
import { EmptyRecorderApp } from './recorder/recorderApp';
import { asLocator } from '../utils/isomorphic/locatorGenerators';
import type { Language } from '../utils/isomorphic/locatorGenerators';
import type { BrowserNewContextForReuseParams, BrowserTypeLaunchParams } from '@protocol/channels';

const internalMetadata = serverSideCallMetadata();

Expand Down Expand Up @@ -79,20 +80,27 @@ export class DebugController extends SdkObject {
}
}

async resetForReuse() {
async resetForReuse(params: BrowserNewContextForReuseParams | null = null) {
const contexts = new Set<BrowserContext>();
for (const page of this._playwright.allPages())
contexts.add(page.context());
for (const context of contexts)
await context.resetForReuse(internalMetadata, null);
await context.resetForReuse(internalMetadata, params);
}

async navigate(url: string) {
for (const p of this._playwright.allPages())
await p.mainFrame().goto(internalMetadata, url);
}

async setRecorderMode(params: { mode: Mode, file?: string, testIdAttributeName?: string }) {
async setRecorderMode(params: {
mode: Mode,
file?: string,
testIdAttributeName?: string,
browserName?: 'chromium' | 'firefox' | 'webkit',
contextOptions?: BrowserNewContextForReuseParams,
launchOptions?: BrowserTypeLaunchParams,
}) {
// TODO: |file| is only used in the legacy mode.
await this._closeBrowsersWithoutPages();

Expand All @@ -105,15 +113,22 @@ export class DebugController extends SdkObject {
return;
}

if (!this._playwright.allBrowsers().length)
await this._playwright.chromium.launch(internalMetadata, { headless: !!process.env.PW_DEBUG_CONTROLLER_HEADLESS });
// Previous browser + launchOptions did not match the previous one.
const launchOptions = {
...params.launchOptions,
headless: !!process.env.PW_DEBUG_CONTROLLER_HEADLESS,
};
const browserName = params.browserName ?? 'chromium';
let browser = this._playwright.findBrowserWithMatchingOptions(browserName, launchOptions);
if (!browser) {
await this.closeAllBrowsers();
browser = await this._playwright[browserName].launch(internalMetadata, launchOptions);
}
// Create page if none.
const pages = this._playwright.allPages();
if (!pages.length) {
const [browser] = this._playwright.allBrowsers();
const { context } = await browser.newContextForReuse({}, internalMetadata);
const { context } = await browser.newContextForReuse(params.contextOptions || {}, internalMetadata);
await context.reapplyContextOptionsIfNeeded(params.contextOptions);
if (!context.pages().length)
await context.newPage(internalMetadata);
}
// Update test id attribute.
if (params.testIdAttributeName) {
for (const page of this._playwright.allPages())
Expand Down
35 changes: 35 additions & 0 deletions packages/playwright-core/src/server/playwright.ts
Expand Up @@ -28,6 +28,7 @@ import { debugLogger } from '../utils/debugLogger';
import type { Page } from './page';
import { DebugController } from './debugController';
import type { Language } from '../utils/isomorphic/locatorGenerators';
import type { LaunchOptions } from './types';

type PlaywrightOptions = {
socksProxyPort?: number;
Expand Down Expand Up @@ -81,6 +82,40 @@ export class Playwright extends SdkObject {
allPages(): Page[] {
return [...this._allPages];
}

findBrowserWithMatchingOptions(requestedBrowserName: string | null, requestedOptions: LaunchOptions): Browser | undefined {
return this.allBrowsers().find(b => {
if (b.options.name !== (requestedBrowserName ?? 'chromium'))
return false;
return launchOptionsHash(b.options.originalLaunchOptions) === launchOptionsHash(requestedOptions);
});
}
}

const defaultLaunchOptions: LaunchOptions = {
ignoreAllDefaultArgs: false,
handleSIGINT: false,
handleSIGTERM: false,
handleSIGHUP: false,
headless: true,
devtools: false,
};

const optionsThatAllowBrowserReuse: (keyof LaunchOptions)[] = [
'headless',
'tracesDir',
];

function launchOptionsHash(options: LaunchOptions) {
const copy = { ...options };
for (const k of Object.keys(copy)) {
const key = k as keyof LaunchOptions;
if (copy[key] === defaultLaunchOptions[key])
delete copy[key];
}
for (const key of optionsThatAllowBrowserReuse)
delete copy[key];
return JSON.stringify(copy);
}

export function createPlaywright(options: PlaywrightOptions) {
Expand Down
6 changes: 6 additions & 0 deletions packages/protocol/src/channels.ts
Expand Up @@ -701,9 +701,15 @@ export type DebugControllerNavigateResult = void;
export type DebugControllerSetRecorderModeParams = {
mode: 'inspecting' | 'recording' | 'none',
testIdAttributeName?: string,
browserName?: 'chromium' | 'firefox' | 'webkit',
contextOptions?: BrowserNewContextForReuseParams,
launchOptions?: BrowserTypeLaunchParams,
};
export type DebugControllerSetRecorderModeOptions = {
testIdAttributeName?: string,
browserName?: 'chromium' | 'firefox' | 'webkit',
contextOptions?: BrowserNewContextForReuseParams,
launchOptions?: BrowserTypeLaunchParams,
};
export type DebugControllerSetRecorderModeResult = void;
export type DebugControllerHighlightParams = {
Expand Down
8 changes: 8 additions & 0 deletions packages/protocol/src/protocol.yml
Expand Up @@ -745,6 +745,14 @@ DebugController:
- recording
- none
testIdAttributeName: string?
browserName:
type: enum?
literals:
- chromium
- firefox
- webkit
contextOptions: BrowserNewContextForReuseParams?
launchOptions: BrowserTypeLaunchParams?

highlight:
parameters:
Expand Down