From 14a1eaa4743aa2569c5882f2b687d696199678d8 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Thu, 1 Jun 2023 17:54:43 -0700 Subject: [PATCH] chore: add Playwright to attribution (#23447) This makes it easier to plumb all kinds of options around. --- .../playwright-core/src/androidServerImpl.ts | 2 +- .../playwright-core/src/browserServerImpl.ts | 2 +- packages/playwright-core/src/cli/driver.ts | 2 +- .../playwright-core/src/inProcessFactory.ts | 2 +- .../src/remote/playwrightConnection.ts | 2 +- .../src/remote/playwrightServer.ts | 6 ++-- .../src/server/android/android.ts | 11 +++--- .../playwright-core/src/server/browser.ts | 15 ++------ .../src/server/browserContext.ts | 4 +-- .../playwright-core/src/server/browserType.ts | 19 +++++----- .../src/server/chromium/chromium.ts | 17 +++++---- .../src/server/chromium/crBrowser.ts | 9 ++--- .../src/server/chromium/crPage.ts | 2 +- packages/playwright-core/src/server/dom.ts | 2 +- .../src/server/electron/electron.ts | 13 +++---- .../src/server/firefox/ffBrowser.ts | 9 ++--- .../src/server/firefox/firefox.ts | 9 ++--- packages/playwright-core/src/server/frames.ts | 2 +- .../src/server/instrumentation.ts | 3 +- .../playwright-core/src/server/playwright.ts | 36 ++++++++++--------- .../playwright-core/src/server/recorder.ts | 2 +- .../src/server/recorder/recorderApp.ts | 4 +-- .../src/server/trace/recorder/tracing.ts | 4 +-- .../src/server/trace/viewer/traceViewer.ts | 2 +- .../src/server/webkit/webkit.ts | 9 ++--- .../src/server/webkit/wkBrowser.ts | 9 ++--- tests/library/trace-viewer.spec.ts | 3 +- 27 files changed, 96 insertions(+), 104 deletions(-) diff --git a/packages/playwright-core/src/androidServerImpl.ts b/packages/playwright-core/src/androidServerImpl.ts index 82a02856e2..4c3685076e 100644 --- a/packages/playwright-core/src/androidServerImpl.ts +++ b/packages/playwright-core/src/androidServerImpl.ts @@ -24,7 +24,7 @@ import { PlaywrightServer } from './remote/playwrightServer'; export class AndroidServerLauncherImpl { async launchServer(options: LaunchAndroidServerOptions = {}): Promise { - const playwright = createPlaywright('javascript'); + const playwright = createPlaywright({ sdkLanguage: 'javascript', isServer: true }); // 1. Pre-connect to the device let devices = await playwright.android.devices({ host: options.adbHost, diff --git a/packages/playwright-core/src/browserServerImpl.ts b/packages/playwright-core/src/browserServerImpl.ts index 123d10b020..4220e0b49f 100644 --- a/packages/playwright-core/src/browserServerImpl.ts +++ b/packages/playwright-core/src/browserServerImpl.ts @@ -36,7 +36,7 @@ export class BrowserServerLauncherImpl implements BrowserServerLauncher { } async launchServer(options: LaunchServerOptions = {}): Promise { - const playwright = createPlaywright('javascript'); + const playwright = createPlaywright({ sdkLanguage: 'javascript', isServer: true }); // TODO: enable socks proxy once ipv6 is supported. const socksProxy = false ? new SocksProxy() : undefined; playwright.options.socksProxyPort = await socksProxy?.listen(0); diff --git a/packages/playwright-core/src/cli/driver.ts b/packages/playwright-core/src/cli/driver.ts index 3db878c0bc..547ed576e8 100644 --- a/packages/playwright-core/src/cli/driver.ts +++ b/packages/playwright-core/src/cli/driver.ts @@ -33,7 +33,7 @@ export function printApiJson() { export function runDriver() { const dispatcherConnection = new DispatcherConnection(); new RootDispatcher(dispatcherConnection, async (rootScope, { sdkLanguage }) => { - const playwright = createPlaywright(sdkLanguage); + const playwright = createPlaywright({ sdkLanguage }); return new PlaywrightDispatcher(rootScope, playwright); }); const transport = new PipeTransport(process.stdout, process.stdin); diff --git a/packages/playwright-core/src/inProcessFactory.ts b/packages/playwright-core/src/inProcessFactory.ts index 9ef4d7e6be..07959404ad 100644 --- a/packages/playwright-core/src/inProcessFactory.ts +++ b/packages/playwright-core/src/inProcessFactory.ts @@ -22,7 +22,7 @@ import { AndroidServerLauncherImpl } from './androidServerImpl'; import type { Language } from './utils/isomorphic/locatorGenerators'; export function createInProcessPlaywright(): PlaywrightAPI { - const playwright = createPlaywright((process.env.PW_LANG_NAME as Language | undefined) || 'javascript'); + const playwright = createPlaywright({ sdkLanguage: (process.env.PW_LANG_NAME as Language | undefined) || 'javascript' }); const clientConnection = new Connection(); const dispatcherConnection = new DispatcherConnection(true /* local */); diff --git a/packages/playwright-core/src/remote/playwrightConnection.ts b/packages/playwright-core/src/remote/playwrightConnection.ts index 2a3138eef5..447140da10 100644 --- a/packages/playwright-core/src/remote/playwrightConnection.ts +++ b/packages/playwright-core/src/remote/playwrightConnection.ts @@ -108,7 +108,7 @@ export class PlaywrightConnection { private async _initLaunchBrowserMode(scope: RootDispatcher) { debugLogger.log('server', `[${this._id}] engaged launch mode for "${this._options.browserName}"`); - const playwright = createPlaywright('javascript'); + const playwright = createPlaywright({ sdkLanguage: 'javascript', isServer: true }); const ownedSocksProxy = await this._createOwnedSocksProxy(playwright); const browser = await playwright[this._options.browserName as 'chromium'].launch(serverSideCallMetadata(), this._options.launchOptions); diff --git a/packages/playwright-core/src/remote/playwrightServer.ts b/packages/playwright-core/src/remote/playwrightServer.ts index b5ae0e4583..384b3cbda1 100644 --- a/packages/playwright-core/src/remote/playwrightServer.ts +++ b/packages/playwright-core/src/remote/playwrightServer.ts @@ -49,9 +49,9 @@ export class PlaywrightServer { constructor(options: ServerOptions) { this._options = options; if (options.preLaunchedBrowser) - this._preLaunchedPlaywright = options.preLaunchedBrowser.options.rootSdkObject as Playwright; + this._preLaunchedPlaywright = options.preLaunchedBrowser.attribution.playwright; if (options.preLaunchedAndroidDevice) - this._preLaunchedPlaywright = options.preLaunchedAndroidDevice._android._playwrightOptions.rootSdkObject as Playwright; + this._preLaunchedPlaywright = options.preLaunchedAndroidDevice._android.attribution.playwright; } async listen(port: number = 0): Promise { @@ -114,7 +114,7 @@ export class PlaywrightServer { const isExtension = this._options.mode === 'extension'; if (isExtension) { if (!this._preLaunchedPlaywright) - this._preLaunchedPlaywright = createPlaywright('javascript'); + this._preLaunchedPlaywright = createPlaywright({ sdkLanguage: 'javascript', isServer: true }); } let clientType: ClientType = 'launch-browser'; diff --git a/packages/playwright-core/src/server/android/android.ts b/packages/playwright-core/src/server/android/android.ts index 1263bfa58f..456ea92311 100644 --- a/packages/playwright-core/src/server/android/android.ts +++ b/packages/playwright-core/src/server/android/android.ts @@ -23,7 +23,7 @@ import type * as stream from 'stream'; import { wsReceiver, wsSender } from '../../utilsBundle'; import { createGuid, makeWaitForNextTask, isUnderTest } from '../../utils'; import { removeFolders } from '../../utils/fileUtils'; -import type { BrowserOptions, BrowserProcess, PlaywrightOptions } from '../browser'; +import type { BrowserOptions, BrowserProcess } from '../browser'; import type { BrowserContext } from '../browserContext'; import { validateBrowserContextOptions } from '../browserContext'; import { ProgressController } from '../progress'; @@ -63,12 +63,10 @@ export class Android extends SdkObject { private _backend: Backend; private _devices = new Map(); readonly _timeoutSettings: TimeoutSettings; - readonly _playwrightOptions: PlaywrightOptions; - constructor(backend: Backend, playwrightOptions: PlaywrightOptions) { - super(playwrightOptions.rootSdkObject, 'android'); + constructor(parent: SdkObject, backend: Backend) { + super(parent, 'android'); this._backend = backend; - this._playwrightOptions = playwrightOptions; this._timeoutSettings = new TimeoutSettings(); } @@ -326,7 +324,6 @@ export class AndroidDevice extends SdkObject { cleanupArtifactsDir().catch(e => debug('pw:android')(`could not cleanup artifacts dir: ${e}`)); }); const browserOptions: BrowserOptions = { - ...this._android._playwrightOptions, name: 'clank', isChromium: true, slowMo: 0, @@ -342,7 +339,7 @@ export class AndroidDevice extends SdkObject { }; validateBrowserContextOptions(options, browserOptions); - const browser = await CRBrowser.connect(androidBrowser, browserOptions); + const browser = await CRBrowser.connect(this.attribution.playwright, androidBrowser, browserOptions); const controller = new ProgressController(serverSideCallMetadata(), this); const defaultContext = browser._defaultContext!; await controller.run(async progress => { diff --git a/packages/playwright-core/src/server/browser.ts b/packages/playwright-core/src/server/browser.ts index 73933cee9b..c485c3d5cb 100644 --- a/packages/playwright-core/src/server/browser.ts +++ b/packages/playwright-core/src/server/browser.ts @@ -25,8 +25,6 @@ import type { RecentLogsCollector } from '../common/debugLogger'; import type { CallMetadata } from './instrumentation'; import { SdkObject } from './instrumentation'; import { Artifact } from './artifact'; -import type { Selectors } from './selectors'; -import type { Language } from '../utils/isomorphic/locatorGenerators'; export interface BrowserProcess { onclose?: ((exitCode: number | null, signal: string | null) => void); @@ -35,14 +33,7 @@ export interface BrowserProcess { close(): Promise; } -export type PlaywrightOptions = { - rootSdkObject: SdkObject; - selectors: Selectors; - socksProxyPort?: number; - sdkLanguage: Language, -}; - -export type BrowserOptions = PlaywrightOptions & { +export type BrowserOptions = { name: string, isChromium: boolean, channel?: string, @@ -74,8 +65,8 @@ export abstract class Browser extends SdkObject { readonly _idToVideo = new Map(); private _contextForReuse: { context: BrowserContext, hash: string } | undefined; - constructor(options: BrowserOptions) { - super(options.rootSdkObject, 'browser'); + constructor(parent: SdkObject, options: BrowserOptions) { + super(parent, 'browser'); this.attribution.browser = this; this.options = options; this.instrumentation.onBrowserOpen(this); diff --git a/packages/playwright-core/src/server/browserContext.ts b/packages/playwright-core/src/server/browserContext.ts index 3b352e7c9b..cd61469049 100644 --- a/packages/playwright-core/src/server/browserContext.ts +++ b/packages/playwright-core/src/server/browserContext.ts @@ -107,11 +107,11 @@ export abstract class BrowserContext extends SdkObject { } selectors(): Selectors { - return this._selectors || this._browser.options.selectors; + return this._selectors || this.attribution.playwright.selectors; } async _initialize() { - if (this.attribution.isInternalPlaywright) + if (this.attribution.playwright.options.isInternalPlaywright) return; // Debugger will pause execution upon page.pause in headed mode. this._debugger = new Debugger(this); diff --git a/packages/playwright-core/src/server/browserType.ts b/packages/playwright-core/src/server/browserType.ts index 1ae07171be..9ce4e3da5f 100644 --- a/packages/playwright-core/src/server/browserType.ts +++ b/packages/playwright-core/src/server/browserType.ts @@ -23,7 +23,7 @@ import type { BrowserName } from './registry'; import { registry } from './registry'; import type { ConnectionTransport } from './transport'; import { WebSocketTransport } from './transport'; -import type { BrowserOptions, Browser, BrowserProcess, PlaywrightOptions } from './browser'; +import type { BrowserOptions, Browser, BrowserProcess } from './browser'; import type { Env } from '../utils/processLauncher'; import { launchProcess, envArrayToObject } from '../utils/processLauncher'; import { PipeTransport } from './pipeTransport'; @@ -45,17 +45,15 @@ export const kNoXServerRunningError = 'Looks like you launched a headed browser export abstract class BrowserType extends SdkObject { private _name: BrowserName; - readonly _playwrightOptions: PlaywrightOptions; - constructor(browserName: BrowserName, playwrightOptions: PlaywrightOptions) { - super(playwrightOptions.rootSdkObject, 'browser-type'); + constructor(parent: SdkObject, browserName: BrowserName) { + super(parent, 'browser-type'); this.attribution.browserType = this; - this._playwrightOptions = playwrightOptions; this._name = browserName; } executablePath(): string { - return registry.findExecutable(this._name).executablePath(this._playwrightOptions.sdkLanguage) || ''; + return registry.findExecutable(this._name).executablePath(this.attribution.playwright.options.sdkLanguage) || ''; } name(): string { @@ -107,7 +105,6 @@ export abstract class BrowserType extends SdkObject { if ((options as any).__testHookBeforeCreateBrowser) await (options as any).__testHookBeforeCreateBrowser(); const browserOptions: BrowserOptions = { - ...this._playwrightOptions, name: this._name, isChromium: this._name === 'chromium', channel: options.channel, @@ -184,8 +181,8 @@ export abstract class BrowserType extends SdkObject { const registryExecutable = registry.findExecutable(options.channel || this._name); if (!registryExecutable || registryExecutable.browserName !== this._name) throw new Error(`Unsupported ${this._name} channel "${options.channel}"`); - executable = registryExecutable.executablePathOrDie(this._playwrightOptions.sdkLanguage); - await registryExecutable.validateHostRequirements(this._playwrightOptions.sdkLanguage); + executable = registryExecutable.executablePathOrDie(this.attribution.playwright.options.sdkLanguage); + await registryExecutable.validateHostRequirements(this.attribution.playwright.options.sdkLanguage); } const waitForWSEndpoint = (options.useWebSocket || options.args?.some(a => a.startsWith('--remote-debugging-port'))) ? new ManualPromise() : undefined; @@ -275,8 +272,8 @@ export abstract class BrowserType extends SdkObject { headless = false; if (downloadsPath && !path.isAbsolute(downloadsPath)) downloadsPath = path.join(process.cwd(), downloadsPath); - if (this._playwrightOptions.socksProxyPort) - proxy = { server: `socks5://127.0.0.1:${this._playwrightOptions.socksProxyPort}` }; + if (this.attribution.playwright.options.socksProxyPort) + proxy = { server: `socks5://127.0.0.1:${this.attribution.playwright.options.socksProxyPort}` }; return { ...options, devtools, headless, downloadsPath, proxy }; } diff --git a/packages/playwright-core/src/server/chromium/chromium.ts b/packages/playwright-core/src/server/chromium/chromium.ts index af72f730fe..5eb7497780 100644 --- a/packages/playwright-core/src/server/chromium/chromium.ts +++ b/packages/playwright-core/src/server/chromium/chromium.ts @@ -28,7 +28,7 @@ import { BrowserType, kNoXServerRunningError } from '../browserType'; import type { ConnectionTransport, ProtocolRequest } from '../transport'; import { WebSocketTransport } from '../transport'; import { CRDevTools } from './crDevTools'; -import type { BrowserOptions, BrowserProcess, PlaywrightOptions } from '../browser'; +import type { BrowserOptions, BrowserProcess } from '../browser'; import { Browser } from '../browser'; import type * as types from '../types'; import type * as channels from '@protocol/channels'; @@ -43,7 +43,7 @@ import type { Progress } from '../progress'; import { ProgressController } from '../progress'; import { TimeoutSettings } from '../../common/timeoutSettings'; import { helper } from '../helper'; -import type { CallMetadata } from '../instrumentation'; +import type { CallMetadata, SdkObject } from '../instrumentation'; import type http from 'http'; import { registry } from '../registry'; import { ManualPromise } from '../../utils/manualPromise'; @@ -55,8 +55,8 @@ const ARTIFACTS_FOLDER = path.join(os.tmpdir(), 'playwright-artifacts-'); export class Chromium extends BrowserType { private _devtools: CRDevTools | undefined; - constructor(playwrightOptions: PlaywrightOptions) { - super('chromium', playwrightOptions); + constructor(parent: SdkObject) { + super(parent, 'chromium'); if (debugMode()) this._devtools = this._createDevTools(); @@ -99,7 +99,6 @@ export class Chromium extends BrowserType { const browserProcess: BrowserProcess = { close: doClose, kill: doClose }; const persistent: channels.BrowserNewContextParams = { noDefaultViewport: true }; const browserOptions: BrowserOptions = { - ...this._playwrightOptions, slowMo: options.slowMo, name: 'chromium', isChromium: true, @@ -120,7 +119,7 @@ export class Chromium extends BrowserType { }; validateBrowserContextOptions(persistent, browserOptions); progress.throwIfAborted(); - const browser = await CRBrowser.connect(chromeTransport, browserOptions); + const browser = await CRBrowser.connect(this.attribution.playwright, chromeTransport, browserOptions); browser.on(Browser.Events.Disconnected, doCleanup); return browser; } @@ -137,7 +136,7 @@ export class Chromium extends BrowserType { devtools = this._createDevTools(); await (options as any).__testHookForDevTools(devtools); } - return CRBrowser.connect(transport, options, devtools); + return CRBrowser.connect(this.attribution.playwright, transport, options, devtools); } _rewriteStartupError(error: Error): Error { @@ -309,14 +308,14 @@ export class Chromium extends BrowserType { const proxyURL = new URL(proxy.server); const isSocks = proxyURL.protocol === 'socks5:'; // https://www.chromium.org/developers/design-documents/network-settings - if (isSocks && !this._playwrightOptions.socksProxyPort) { + if (isSocks && !this.attribution.playwright.options.socksProxyPort) { // https://www.chromium.org/developers/design-documents/network-stack/socks-proxy chromeArguments.push(`--host-resolver-rules="MAP * ~NOTFOUND , EXCLUDE ${proxyURL.hostname}"`); } chromeArguments.push(`--proxy-server=${proxy.server}`); const proxyBypassRules = []; // https://source.chromium.org/chromium/chromium/src/+/master:net/docs/proxy.md;l=548;drc=71698e610121078e0d1a811054dcf9fd89b49578 - if (this._playwrightOptions.socksProxyPort) + if (this.attribution.playwright.options.socksProxyPort) proxyBypassRules.push('<-loopback>'); if (proxy.bypass) proxyBypassRules.push(...proxy.bypass.split(',').map(t => t.trim()).map(t => t.startsWith('.') ? '*' + t : t)); diff --git a/packages/playwright-core/src/server/chromium/crBrowser.ts b/packages/playwright-core/src/server/chromium/crBrowser.ts index 8eb1e35928..35ede903de 100644 --- a/packages/playwright-core/src/server/chromium/crBrowser.ts +++ b/packages/playwright-core/src/server/chromium/crBrowser.ts @@ -34,6 +34,7 @@ import { readProtocolStream } from './crProtocolHelper'; import type { Protocol } from './protocol'; import type { CRDevTools } from './crDevTools'; import { CRServiceWorker } from './crServiceWorker'; +import type { SdkObject } from '../instrumentation'; export class CRBrowser extends Browser { readonly _connection: CRConnection; @@ -51,11 +52,11 @@ export class CRBrowser extends Browser { private _tracingClient: CRSession | undefined; private _userAgent: string = ''; - static async connect(transport: ConnectionTransport, options: BrowserOptions, devtools?: CRDevTools): Promise { + static async connect(parent: SdkObject, transport: ConnectionTransport, options: BrowserOptions, devtools?: CRDevTools): Promise { // Make a copy in case we need to update `headful` property below. options = { ...options }; const connection = new CRConnection(transport, options.protocolLogger, options.browserLogsCollector); - const browser = new CRBrowser(connection, options); + const browser = new CRBrowser(parent, connection, options); browser._devtools = devtools; const session = connection.rootSession; if ((options as any).__testHookOnConnectToBrowser) @@ -85,8 +86,8 @@ export class CRBrowser extends Browser { return browser; } - constructor(connection: CRConnection, options: BrowserOptions) { - super(options); + constructor(parent: SdkObject, connection: CRConnection, options: BrowserOptions) { + super(parent, options); this._connection = connection; this._session = this._connection.rootSession; this._connection.on(ConnectionEvents.Disconnected, () => this._didClose()); diff --git a/packages/playwright-core/src/server/chromium/crPage.ts b/packages/playwright-core/src/server/chromium/crPage.ts index 7e93d85343..4b93c1d410 100644 --- a/packages/playwright-core/src/server/chromium/crPage.ts +++ b/packages/playwright-core/src/server/chromium/crPage.ts @@ -922,7 +922,7 @@ class FrameSession { async _createVideoRecorder(screencastId: string, options: types.PageScreencastOptions): Promise { assert(!this._screencastId); - const ffmpegPath = registry.findExecutable('ffmpeg')!.executablePathOrDie(this._page._browserContext._browser.options.sdkLanguage); + const ffmpegPath = registry.findExecutable('ffmpeg')!.executablePathOrDie(this._page.attribution.playwright.options.sdkLanguage); this._videoRecorder = await VideoRecorder.launch(this._crPage._page, ffmpegPath, options); this._screencastId = screencastId; } diff --git a/packages/playwright-core/src/server/dom.ts b/packages/playwright-core/src/server/dom.ts index 2a1fb3c41f..52aa5caecf 100644 --- a/packages/playwright-core/src/server/dom.ts +++ b/packages/playwright-core/src/server/dom.ts @@ -86,7 +86,7 @@ export class FrameExecutionContext extends js.ExecutionContext { const selectorsRegistry = this.frame._page.context().selectors(); for (const [name, { source }] of selectorsRegistry._engines) custom.push(`{ name: '${name}', engine: (${source}) }`); - const sdkLanguage = this.frame._page.context()._browser.options.sdkLanguage; + const sdkLanguage = this.frame.attribution.playwright.options.sdkLanguage; const source = ` (() => { const module = {}; diff --git a/packages/playwright-core/src/server/electron/electron.ts b/packages/playwright-core/src/server/electron/electron.ts index ee9f9faec9..9bc6353b04 100644 --- a/packages/playwright-core/src/server/electron/electron.ts +++ b/packages/playwright-core/src/server/electron/electron.ts @@ -35,7 +35,8 @@ import type { Progress } from '../progress'; import { ProgressController } from '../progress'; import { helper } from '../helper'; import { eventsHelper } from '../../utils/eventsHelper'; -import type { BrowserOptions, BrowserProcess, PlaywrightOptions } from '../browser'; +import type { BrowserOptions, BrowserProcess } from '../browser'; +import type { Playwright } from '../playwright'; import type * as childProcess from 'child_process'; import * as readline from 'readline'; import { RecentLogsCollector } from '../../common/debugLogger'; @@ -116,11 +117,8 @@ export class ElectronApplication extends SdkObject { } export class Electron extends SdkObject { - private _playwrightOptions: PlaywrightOptions; - - constructor(playwrightOptions: PlaywrightOptions) { - super(playwrightOptions.rootSdkObject, 'electron'); - this._playwrightOptions = playwrightOptions; + constructor(playwright: Playwright) { + super(playwright, 'electron'); } async launch(options: channels.ElectronLaunchParams): Promise { @@ -224,7 +222,6 @@ export class Electron extends SdkObject { noDefaultViewport: true, }; const browserOptions: BrowserOptions = { - ...this._playwrightOptions, name: 'electron', isChromium: true, headful: true, @@ -238,7 +235,7 @@ export class Electron extends SdkObject { originalLaunchOptions: {}, }; validateBrowserContextOptions(contextOptions, browserOptions); - const browser = await CRBrowser.connect(chromeTransport, browserOptions); + const browser = await CRBrowser.connect(this.attribution.playwright, chromeTransport, browserOptions); app = new ElectronApplication(this, browser, nodeConnection, launchedProcess); await app.initialize(); return app; diff --git a/packages/playwright-core/src/server/firefox/ffBrowser.ts b/packages/playwright-core/src/server/firefox/ffBrowser.ts index 6cf00cc534..9cb79034f9 100644 --- a/packages/playwright-core/src/server/firefox/ffBrowser.ts +++ b/packages/playwright-core/src/server/firefox/ffBrowser.ts @@ -28,6 +28,7 @@ import type * as channels from '@protocol/channels'; import { ConnectionEvents, FFConnection } from './ffConnection'; import { FFPage } from './ffPage'; import type { Protocol } from './protocol'; +import type { SdkObject } from '../instrumentation'; export class FFBrowser extends Browser { _connection: FFConnection; @@ -36,9 +37,9 @@ export class FFBrowser extends Browser { private _version = ''; private _userAgent: string = ''; - static async connect(transport: ConnectionTransport, options: BrowserOptions): Promise { + static async connect(parent: SdkObject, transport: ConnectionTransport, options: BrowserOptions): Promise { const connection = new FFConnection(transport, options.protocolLogger, options.browserLogsCollector); - const browser = new FFBrowser(connection, options); + const browser = new FFBrowser(parent, connection, options); if ((options as any).__testHookOnConnectToBrowser) await (options as any).__testHookOnConnectToBrowser(); let firefoxUserPrefs = options.persistent ? {} : options.originalLaunchOptions.firefoxUserPrefs ?? {}; @@ -61,8 +62,8 @@ export class FFBrowser extends Browser { return browser; } - constructor(connection: FFConnection, options: BrowserOptions) { - super(options); + constructor(parent: SdkObject, connection: FFConnection, options: BrowserOptions) { + super(parent, options); this._connection = connection; this._ffPages = new Map(); this._contexts = new Map(); diff --git a/packages/playwright-core/src/server/firefox/firefox.ts b/packages/playwright-core/src/server/firefox/firefox.ts index 7ffbaebbf3..a694ebf904 100644 --- a/packages/playwright-core/src/server/firefox/firefox.ts +++ b/packages/playwright-core/src/server/firefox/firefox.ts @@ -22,18 +22,19 @@ import { kBrowserCloseMessageId } from './ffConnection'; import { BrowserType, kNoXServerRunningError } from '../browserType'; import type { Env } from '../../utils/processLauncher'; import type { ConnectionTransport } from '../transport'; -import type { BrowserOptions, PlaywrightOptions } from '../browser'; +import type { BrowserOptions } from '../browser'; import type * as types from '../types'; import { rewriteErrorMessage } from '../../utils/stackTrace'; import { wrapInASCIIBox } from '../../utils'; +import type { SdkObject } from '../instrumentation'; export class Firefox extends BrowserType { - constructor(playwrightOptions: PlaywrightOptions) { - super('firefox', playwrightOptions); + constructor(parent: SdkObject) { + super(parent, 'firefox'); } _connectToTransport(transport: ConnectionTransport, options: BrowserOptions): Promise { - return FFBrowser.connect(transport, options); + return FFBrowser.connect(this.attribution.playwright, transport, options); } _rewriteStartupError(error: Error): Error { diff --git a/packages/playwright-core/src/server/frames.ts b/packages/playwright-core/src/server/frames.ts index 9ac6c8658a..7b604649a2 100644 --- a/packages/playwright-core/src/server/frames.ts +++ b/packages/playwright-core/src/server/frames.ts @@ -1675,7 +1675,7 @@ export class Frame extends SdkObject { } private _asLocator(selector: string) { - return asLocator(this._page.context()._browser.options.sdkLanguage, selector); + return asLocator(this._page.attribution.playwright.options.sdkLanguage, selector); } } diff --git a/packages/playwright-core/src/server/instrumentation.ts b/packages/playwright-core/src/server/instrumentation.ts index 7fba75dee4..7ff19202d9 100644 --- a/packages/playwright-core/src/server/instrumentation.ts +++ b/packages/playwright-core/src/server/instrumentation.ts @@ -23,9 +23,10 @@ import type { BrowserType } from './browserType'; import type { ElementHandle } from './dom'; import type { Frame } from './frames'; import type { Page } from './page'; +import type { Playwright } from './playwright'; export type Attribution = { - isInternalPlaywright: boolean, + playwright: Playwright; browserType?: BrowserType; browser?: Browser; context?: BrowserContext | APIRequestContext; diff --git a/packages/playwright-core/src/server/playwright.ts b/packages/playwright-core/src/server/playwright.ts index 309d351cca..cfa3e699ac 100644 --- a/packages/playwright-core/src/server/playwright.ts +++ b/packages/playwright-core/src/server/playwright.ts @@ -16,7 +16,7 @@ import { Android } from './android/android'; import { AdbBackend } from './android/backendAdb'; -import type { Browser, PlaywrightOptions } from './browser'; +import type { Browser } from './browser'; import { Chromium } from './chromium/chromium'; import { Electron } from './electron/electron'; import { Firefox } from './firefox/firefox'; @@ -29,6 +29,13 @@ import type { Page } from './page'; import { DebugController } from './debugController'; import type { Language } from '../utils/isomorphic/locatorGenerators'; +type PlaywrightOptions = { + socksProxyPort?: number; + sdkLanguage: Language; + isInternalPlaywright?: boolean; + isServer?: boolean; +}; + export class Playwright extends SdkObject { readonly selectors: Selectors; readonly chromium: Chromium; @@ -41,8 +48,10 @@ export class Playwright extends SdkObject { private _allPages = new Set(); private _allBrowsers = new Set(); - constructor(sdkLanguage: Language, isInternalPlaywright: boolean) { - super({ attribution: { isInternalPlaywright }, instrumentation: createInstrumentation() } as any, undefined, 'Playwright'); + constructor(options: PlaywrightOptions) { + super({ attribution: {}, instrumentation: createInstrumentation() } as any, undefined, 'Playwright'); + this.options = options; + this.attribution.playwright = this; this.instrumentation.addListener({ onBrowserOpen: browser => this._allBrowsers.add(browser), onBrowserClose: browser => this._allBrowsers.delete(browser), @@ -52,17 +61,12 @@ export class Playwright extends SdkObject { debugLogger.log(logName as any, message); } }, null); - this.options = { - rootSdkObject: this, - selectors: new Selectors(), - sdkLanguage: sdkLanguage, - }; - this.chromium = new Chromium(this.options); - this.firefox = new Firefox(this.options); - this.webkit = new WebKit(this.options); - this.electron = new Electron(this.options); - this.android = new Android(new AdbBackend(), this.options); - this.selectors = this.options.selectors; + this.chromium = new Chromium(this); + this.firefox = new Firefox(this); + this.webkit = new WebKit(this); + this.electron = new Electron(this); + this.android = new Android(this, new AdbBackend()); + this.selectors = new Selectors(); this.debugController = new DebugController(this); } @@ -79,6 +83,6 @@ export class Playwright extends SdkObject { } } -export function createPlaywright(sdkLanguage: Language, isInternalPlaywright: boolean = false) { - return new Playwright(sdkLanguage, isInternalPlaywright); +export function createPlaywright(options: PlaywrightOptions) { + return new Playwright(options); } diff --git a/packages/playwright-core/src/server/recorder.ts b/packages/playwright-core/src/server/recorder.ts index edd1073d5d..6c0602bf38 100644 --- a/packages/playwright-core/src/server/recorder.ts +++ b/packages/playwright-core/src/server/recorder.ts @@ -358,7 +358,7 @@ class ContextRecorder extends EventEmitter { this._context = context; this._params = params; this._recorderSources = []; - const language = params.language || context._browser.options.sdkLanguage; + const language = params.language || context.attribution.playwright.options.sdkLanguage; this.setOutput(language, params.outputFile); const generator = new CodeGenerator(context._browser.options.name, params.mode === 'recording', params.launchOptions || {}, params.contextOptions || {}, params.device, params.saveStorage); generator.on('change', () => { diff --git a/packages/playwright-core/src/server/recorder/recorderApp.ts b/packages/playwright-core/src/server/recorder/recorderApp.ts index 9fdb884005..eb09c5c1c1 100644 --- a/packages/playwright-core/src/server/recorder/recorderApp.ts +++ b/packages/playwright-core/src/server/recorder/recorderApp.ts @@ -114,9 +114,9 @@ export class RecorderApp extends EventEmitter implements IRecorderApp { } static async open(recorder: Recorder, inspectedContext: BrowserContext, handleSIGINT: boolean | undefined): Promise { - const sdkLanguage = inspectedContext._browser.options.sdkLanguage; + const sdkLanguage = inspectedContext.attribution.playwright.options.sdkLanguage; const headed = !!inspectedContext._browser.options.headful; - const recorderPlaywright = (require('../playwright').createPlaywright as typeof import('../playwright').createPlaywright)('javascript', true); + const recorderPlaywright = (require('../playwright').createPlaywright as typeof import('../playwright').createPlaywright)({ sdkLanguage: 'javascript', isInternalPlaywright: true }); const args = [ '--app=data:text/html,', '--window-size=600,600', diff --git a/packages/playwright-core/src/server/trace/recorder/tracing.ts b/packages/playwright-core/src/server/trace/recorder/tracing.ts index 4ce912f79d..e50b8ea119 100644 --- a/packages/playwright-core/src/server/trace/recorder/tracing.ts +++ b/packages/playwright-core/src/server/trace/recorder/tracing.ts @@ -99,7 +99,7 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps options: {}, platform: process.platform, wallTime: 0, - sdkLanguage: (context as BrowserContext)?._browser?.options?.sdkLanguage, + sdkLanguage: context.attribution.playwright.options.sdkLanguage, testIdAttributeName }; if (context instanceof BrowserContext) { @@ -119,7 +119,7 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps throw new Error('Cannot start tracing while stopping'); // Re-write for testing. - this._contextCreatedEvent.sdkLanguage = (this._context as BrowserContext)?._browser?.options?.sdkLanguage; + this._contextCreatedEvent.sdkLanguage = this._context.attribution.playwright.options.sdkLanguage; if (this._state) { const o = this._state.options; diff --git a/packages/playwright-core/src/server/trace/viewer/traceViewer.ts b/packages/playwright-core/src/server/trace/viewer/traceViewer.ts index b1934cf633..e2d35d4a88 100644 --- a/packages/playwright-core/src/server/trace/viewer/traceViewer.ts +++ b/packages/playwright-core/src/server/trace/viewer/traceViewer.ts @@ -74,7 +74,7 @@ export async function showTraceViewer(traceUrls: string[], browserName: string, const urlPrefix = await server.start({ preferredPort: port, host }); - const traceViewerPlaywright = createPlaywright('javascript', true); + const traceViewerPlaywright = createPlaywright({ sdkLanguage: 'javascript', isInternalPlaywright: true }); const traceViewerBrowser = isUnderTest() ? 'chromium' : browserName; const args = traceViewerBrowser === 'chromium' ? [ '--app=data:text/html,', diff --git a/packages/playwright-core/src/server/webkit/webkit.ts b/packages/playwright-core/src/server/webkit/webkit.ts index 4f7f63dcea..1767fdcc59 100644 --- a/packages/playwright-core/src/server/webkit/webkit.ts +++ b/packages/playwright-core/src/server/webkit/webkit.ts @@ -21,18 +21,19 @@ import path from 'path'; import { kBrowserCloseMessageId } from './wkConnection'; import { BrowserType, kNoXServerRunningError } from '../browserType'; import type { ConnectionTransport } from '../transport'; -import type { BrowserOptions, PlaywrightOptions } from '../browser'; +import type { BrowserOptions } from '../browser'; import type * as types from '../types'; import { rewriteErrorMessage } from '../../utils/stackTrace'; import { wrapInASCIIBox } from '../../utils'; +import type { SdkObject } from '../instrumentation'; export class WebKit extends BrowserType { - constructor(playwrightOptions: PlaywrightOptions) { - super('webkit', playwrightOptions); + constructor(parent: SdkObject) { + super(parent, 'webkit'); } _connectToTransport(transport: ConnectionTransport, options: BrowserOptions): Promise { - return WKBrowser.connect(transport, options); + return WKBrowser.connect(this.attribution.playwright, transport, options); } _amendEnvironment(env: Env, userDataDir: string, executable: string, browserArguments: string[]): Env { diff --git a/packages/playwright-core/src/server/webkit/wkBrowser.ts b/packages/playwright-core/src/server/webkit/wkBrowser.ts index fd7b45ceb1..ffe0976a72 100644 --- a/packages/playwright-core/src/server/webkit/wkBrowser.ts +++ b/packages/playwright-core/src/server/webkit/wkBrowser.ts @@ -31,6 +31,7 @@ import type { PageProxyMessageReceivedPayload } from './wkConnection'; import { kPageProxyMessageReceived, WKConnection, WKSession } from './wkConnection'; import { WKPage } from './wkPage'; import { kBrowserClosedError } from '../../common/errors'; +import type { SdkObject } from '../instrumentation'; const DEFAULT_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Safari/605.1.15'; const BROWSER_VERSION = '16.4'; @@ -42,8 +43,8 @@ export class WKBrowser extends Browser { readonly _wkPages = new Map(); private readonly _eventListeners: RegisteredListener[]; - static async connect(transport: ConnectionTransport, options: BrowserOptions): Promise { - const browser = new WKBrowser(transport, options); + static async connect(parent: SdkObject, transport: ConnectionTransport, options: BrowserOptions): Promise { + const browser = new WKBrowser(parent, transport, options); if ((options as any).__testHookOnConnectToBrowser) await (options as any).__testHookOnConnectToBrowser(); const promises: Promise[] = [ @@ -58,8 +59,8 @@ export class WKBrowser extends Browser { return browser; } - constructor(transport: ConnectionTransport, options: BrowserOptions) { - super(options); + constructor(parent: SdkObject, transport: ConnectionTransport, options: BrowserOptions) { + super(parent, options); this._connection = new WKConnection(transport, this._onDisconnect.bind(this), options.protocolLogger, options.browserLogsCollector); this._browserSession = this._connection.browserSession; this._eventListeners = [ diff --git a/tests/library/trace-viewer.spec.ts b/tests/library/trace-viewer.spec.ts index 46635c63df..e5c3b8d40d 100644 --- a/tests/library/trace-viewer.spec.ts +++ b/tests/library/trace-viewer.spec.ts @@ -774,7 +774,7 @@ test('should display waitForLoadState even if did not wait for it', async ({ run }); test('should display language-specific locators', async ({ runAndTrace, server, page, toImpl }) => { - toImpl(page.context())._browser.options.sdkLanguage = 'python'; + toImpl(page).attribution.playwright.options.sdkLanguage = 'python'; const traceViewer = await runAndTrace(async () => { await page.setContent(''); await page.getByRole('button', { name: 'Submit' }).click(); @@ -783,6 +783,7 @@ test('should display language-specific locators', async ({ runAndTrace, server, /page.setContent/, /locator.clickget_by_role\("button", name="Submit"\)/, ]); + toImpl(page).attribution.playwright.options.sdkLanguage = 'javascript'; }); test('should pick locator', async ({ page, runAndTrace, server }) => {