chore: disambiguate internal flags (#12866)

This commit is contained in:
Pavel Feldman 2022-03-17 17:27:33 -08:00 committed by GitHub
parent 25483452c0
commit c7d6f96328
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 103 additions and 50 deletions

View file

@ -20,7 +20,7 @@ import { BrowserServerLauncher, BrowserServer } from './client/browserType';
import { envObjectToArray } from './client/clientHelper';
import { createGuid } from './utils/utils';
import { ProtocolLogger } from './server/types';
import { internalCallMetadata } from './server/instrumentation';
import { serverSideCallMetadata } from './server/instrumentation';
import { createPlaywright } from './server/playwright';
import { PlaywrightServer } from './remote/playwrightServer';
import { helper } from './server/helper';
@ -36,7 +36,7 @@ export class BrowserServerLauncherImpl implements BrowserServerLauncher {
async launchServer(options: LaunchServerOptions = {}): Promise<BrowserServer> {
const playwright = createPlaywright('javascript');
// 1. Pre-launch the browser
const metadata = internalCallMetadata();
const metadata = serverSideCallMetadata();
const browser = await playwright[this._browserName].launch(metadata, {
...options,
ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : undefined,

View file

@ -21,7 +21,7 @@ import { CDPSessionDispatcher } from './cdpSessionDispatcher';
import { Dispatcher, DispatcherScope } from './dispatcher';
import { CRBrowser } from '../server/chromium/crBrowser';
import { PageDispatcher } from './pageDispatcher';
import { CallMetadata, internalCallMetadata } from '../server/instrumentation';
import { CallMetadata, serverSideCallMetadata } from '../server/instrumentation';
import { BrowserContext } from '../server/browserContext';
import { Selectors } from '../server/selectors';
@ -131,6 +131,6 @@ export class ConnectedBrowserDispatcher extends Dispatcher<Browser, channels.Bro
}
async cleanupContexts() {
await Promise.all(Array.from(this._contexts).map(context => context.close(internalCallMetadata())));
await Promise.all(Array.from(this._contexts).map(context => context.close(serverSideCallMetadata())));
}
}

View file

@ -27,7 +27,12 @@ export type CallMetadata = {
method: string;
params: any;
apiName?: string;
// Client is making an internal call that should not show up in
// the inspector or trace.
internal?: boolean;
// Service-side is making a call to itself, this metadata does not go
// through the dispatcher, so is always excluded from inspector / tracing.
isServerSide?: boolean;
stack?: StackFrame[];
log: string[];
afterSnapshot?: string;

View file

@ -18,7 +18,7 @@ import debug from 'debug';
import * as http from 'http';
import WebSocket from 'ws';
import { DispatcherConnection, DispatcherScope, Root } from '../dispatchers/dispatcher';
import { internalCallMetadata } from '../server/instrumentation';
import { serverSideCallMetadata } from '../server/instrumentation';
import { createPlaywright, Playwright } from '../server/playwright';
import { Browser } from '../server/browser';
import { gracefullyCloseAll } from '../utils/processLauncher';
@ -169,7 +169,7 @@ class Connection {
const playwright = createPlaywright('javascript');
const socksProxy = enableSocksProxy ? await this._enableSocksProxy(playwright) : undefined;
const browser = await playwright[executable.browserName].launch(internalCallMetadata(), {
const browser = await playwright[executable.browserName].launch(serverSideCallMetadata(), {
channel: executable.type === 'browser' ? undefined : executable.name,
});

View file

@ -33,7 +33,7 @@ import { RecentLogsCollector } from '../../utils/debugLogger';
import { gracefullyCloseSet } from '../../utils/processLauncher';
import { TimeoutSettings } from '../../utils/timeoutSettings';
import { AndroidWebView } from '../../protocol/channels';
import { SdkObject, internalCallMetadata } from '../instrumentation';
import { SdkObject, serverSideCallMetadata } from '../instrumentation';
const ARTIFACTS_FOLDER = path.join(os.tmpdir(), 'playwright-artifacts-');
@ -288,7 +288,7 @@ export class AndroidDevice extends SdkObject {
validateBrowserContextOptions(options, browserOptions);
const browser = await CRBrowser.connect(androidBrowser, browserOptions);
const controller = new ProgressController(internalCallMetadata(), this);
const controller = new ProgressController(serverSideCallMetadata(), this);
const defaultContext = browser._defaultContext!;
await controller.run(async progress => {
await defaultContext._loadDefaultContextAsIs(progress);

View file

@ -103,10 +103,10 @@ export abstract class Browser extends SdkObject {
_videoStarted(context: BrowserContext, videoId: string, path: string, pageOrError: Promise<Page | Error>) {
const artifact = new Artifact(context, path);
this._idToVideo.set(videoId, { context, artifact });
context.emit(BrowserContext.Events.VideoStarted, artifact);
pageOrError.then(page => {
if (page instanceof Page) {
page._video = artifact;
page.emitOnContext(BrowserContext.Events.VideoStarted, artifact);
page.emit(Page.Events.Video, artifact);
}
});

View file

@ -28,7 +28,7 @@ import { Progress } from './progress';
import { Selectors } from './selectors';
import * as types from './types';
import path from 'path';
import { CallMetadata, internalCallMetadata, SdkObject } from './instrumentation';
import { CallMetadata, serverSideCallMetadata, SdkObject } from './instrumentation';
import { Debugger } from './supplements/debugger';
import { Tracing } from './trace/recorder/tracing';
import { HarRecorder } from './supplements/har/harRecorder';
@ -97,7 +97,7 @@ export abstract class BrowserContext extends SdkObject {
}
async _initialize() {
if (this.attribution.isInternal)
if (this.attribution.isInternalPlaywright)
return;
// Debugger will pause execution upon page.pause in headed mode.
const contextDebugger = new Debugger(this);
@ -326,6 +326,8 @@ export abstract class BrowserContext extends SdkObject {
async newPage(metadata: CallMetadata): Promise<Page> {
const pageDelegate = await this.newPageDelegate();
if (metadata.isServerSide)
pageDelegate.potentiallyUninitializedPage().markAsServerSideOnly();
const pageOrError = await pageDelegate.pageOrError();
if (pageOrError instanceof Page) {
if (pageOrError.isClosed())
@ -345,7 +347,7 @@ export abstract class BrowserContext extends SdkObject {
origins: []
};
if (this._origins.size) {
const internalMetadata = internalCallMetadata();
const internalMetadata = serverSideCallMetadata();
const page = await this.newPage(internalMetadata);
await page._setServerRequestInterceptor(handler => {
handler.fulfill({ body: '<html></html>' }).catch(() => {});
@ -370,7 +372,7 @@ export abstract class BrowserContext extends SdkObject {
if (state.cookies)
await this.addCookies(state.cookies);
if (state.origins && state.origins.length) {
const internalMetadata = internalCallMetadata();
const internalMetadata = serverSideCallMetadata();
const page = await this.newPage(internalMetadata);
await page._setServerRequestInterceptor(handler => {
handler.fulfill({ body: '<html></html>' }).catch(() => {});

View file

@ -174,14 +174,14 @@ export class CRBrowser extends Browser {
assert(!this._serviceWorkers.has(targetInfo.targetId), 'Duplicate target ' + targetInfo.targetId);
if (targetInfo.type === 'background_page') {
const backgroundPage = new CRPage(session, targetInfo.targetId, context, null, false, true);
const backgroundPage = new CRPage(session, targetInfo.targetId, context, null, { hasUIWindow: false, isBackgroundPage: true });
this._backgroundPages.set(targetInfo.targetId, backgroundPage);
return;
}
if (targetInfo.type === 'page') {
const opener = targetInfo.openerId ? this._crPages.get(targetInfo.openerId) || null : null;
const crPage = new CRPage(session, targetInfo.targetId, context, opener, true, false);
const crPage = new CRPage(session, targetInfo.targetId, context, opener, { hasUIWindow: true, isBackgroundPage: false });
this._crPages.set(targetInfo.targetId, crPage);
return;
}

View file

@ -74,10 +74,10 @@ export class CRPage implements PageDelegate {
return crPage._mainFrameSession;
}
constructor(client: CRSession, targetId: string, browserContext: CRBrowserContext, opener: CRPage | null, hasUIWindow: boolean, isBackgroundPage: boolean) {
constructor(client: CRSession, targetId: string, browserContext: CRBrowserContext, opener: CRPage | null, bits: { hasUIWindow: boolean, isBackgroundPage: boolean }) {
this._targetId = targetId;
this._opener = opener;
this._isBackgroundPage = isBackgroundPage;
this._isBackgroundPage = bits.isBackgroundPage;
const dragManager = new DragManager(this);
this.rawKeyboard = new RawKeyboardImpl(client, browserContext._browser._platform() === 'mac', dragManager);
this.rawMouse = new RawMouseImpl(this, client, dragManager);
@ -97,7 +97,7 @@ export class CRPage implements PageDelegate {
}
// Note: it is important to call |reportAsNew| before resolving pageOrError promise,
// so that anyone who awaits pageOrError got a ready and reported page.
this._pagePromise = this._mainFrameSession._initialize(hasUIWindow).then(async r => {
this._pagePromise = this._mainFrameSession._initialize(bits.hasUIWindow).then(async r => {
await this._page.initOpener(this._opener);
return r;
}).catch(async e => {
@ -113,6 +113,10 @@ export class CRPage implements PageDelegate {
});
}
potentiallyUninitializedPage(): Page {
return this._page;
}
private _reportAsNew(error?: Error) {
if (this._isBackgroundPage) {
if (!error)

View file

@ -19,7 +19,7 @@ import { assert, monotonicTime } from '../../utils/utils';
import { Page } from '../page';
import { launchProcess } from '../../utils/processLauncher';
import { Progress, ProgressController } from '../progress';
import { internalCallMetadata } from '../instrumentation';
import { serverSideCallMetadata } from '../instrumentation';
import * as types from '../types';
const fps = 25;
@ -40,7 +40,7 @@ export class VideoRecorder {
if (!options.outputFile.endsWith('.webm'))
throw new Error('File must have .webm extension');
const controller = new ProgressController(internalCallMetadata(), page);
const controller = new ProgressController(serverSideCallMetadata(), page);
controller.setLogName('browser');
return await controller.run(async progress => {
const recorder = new VideoRecorder(page, ffmpegPath, progress);

View file

@ -35,7 +35,7 @@ import { BrowserOptions, BrowserProcess, PlaywrightOptions } from '../browser';
import * as childProcess from 'child_process';
import * as readline from 'readline';
import { RecentLogsCollector } from '../../utils/debugLogger';
import { internalCallMetadata, SdkObject } from '../instrumentation';
import { serverSideCallMetadata, SdkObject } from '../instrumentation';
import * as channels from '../../protocol/channels';
import { BrowserContextOptions } from '../types';
@ -82,9 +82,9 @@ export class ElectronApplication extends SdkObject {
}
async close() {
const progressController = new ProgressController(internalCallMetadata(), this);
const progressController = new ProgressController(serverSideCallMetadata(), this);
const closed = progressController.run(progress => helper.waitForEvent(progress, this, ElectronApplication.Events.Close).promise);
await this._browserContext.close(internalCallMetadata());
await this._browserContext.close(serverSideCallMetadata());
this._nodeConnection.close();
await closed;
}
@ -112,7 +112,7 @@ export class Electron extends SdkObject {
const {
args = [],
} = options;
const controller = new ProgressController(internalCallMetadata(), this);
const controller = new ProgressController(serverSideCallMetadata(), this);
controller.setLogName('browser');
return controller.run(async progress => {
let app: ElectronApplication | undefined = undefined;

View file

@ -116,6 +116,10 @@ export class FFPage implements PageDelegate {
this.evaluateOnNewDocument('', UTILITY_WORLD_NAME).catch(e => this._markAsError(e));
}
potentiallyUninitializedPage(): Page {
return this._page;
}
async _markAsError(error: Error) {
// Same error may be report twice: channer disconnected and session.send fails.
if (this._initializationFailed)

View file

@ -30,7 +30,7 @@ import { Progress, ProgressController } from './progress';
import { assert, constructURLBasedOnBaseURL, makeWaitForNextTask } from '../utils/utils';
import { ManualPromise } from '../utils/async';
import { debugLogger } from '../utils/debugLogger';
import { CallMetadata, internalCallMetadata, SdkObject } from './instrumentation';
import { CallMetadata, serverSideCallMetadata, SdkObject } from './instrumentation';
import type InjectedScript from './injected/injectedScript';
import type { ElementStateWithoutStable, FrameExpectParams, InjectedScriptPoll, InjectedScriptProgress } from './injected/injectedScript';
import { isSessionClosedError } from './protocolError';
@ -277,7 +277,7 @@ export class FrameManager {
route.continue(request, {});
return;
}
this._page._browserContext.emit(BrowserContext.Events.Request, request);
this._page.emitOnContext(BrowserContext.Events.Request, request);
if (route)
this._page._requestStarted(request, route);
}
@ -285,14 +285,14 @@ export class FrameManager {
requestReceivedResponse(response: network.Response) {
if (response.request()._isFavicon)
return;
this._page._browserContext.emit(BrowserContext.Events.Response, response);
this._page.emitOnContext(BrowserContext.Events.Response, response);
}
reportRequestFinished(request: network.Request, response: network.Response | null) {
this._inflightRequestFinished(request);
if (request._isFavicon)
return;
this._page._browserContext.emit(BrowserContext.Events.RequestFinished, { request, response });
this._page.emitOnContext(BrowserContext.Events.RequestFinished, { request, response });
}
requestFailed(request: network.Request, canceled: boolean) {
@ -306,7 +306,7 @@ export class FrameManager {
}
if (request._isFavicon)
return;
this._page._browserContext.emit(BrowserContext.Events.RequestFailed, request);
this._page.emitOnContext(BrowserContext.Events.RequestFailed, request);
}
dialogDidOpen(dialog: Dialog) {
@ -1366,7 +1366,7 @@ export class Frame extends SdkObject {
return result;
return JSON.stringify(result);
}`;
const handle = await this._waitForFunctionExpression(internalCallMetadata(), expression, true, undefined, { timeout: progress.timeUntilDeadline() }, 'utility');
const handle = await this._waitForFunctionExpression(serverSideCallMetadata(), expression, true, undefined, { timeout: progress.timeUntilDeadline() }, 'utility');
return JSON.parse(handle.rawValue()) as R;
}

View file

@ -25,7 +25,7 @@ import type { Frame } from './frames';
import type { Page } from './page';
export type Attribution = {
isInternal: boolean,
isInternalPlaywright: boolean,
browserType?: BrowserType;
browser?: Browser;
context?: BrowserContext | APIRequestContext;
@ -92,7 +92,7 @@ export function createInstrumentation(): Instrumentation {
});
}
export function internalCallMetadata(): CallMetadata {
export function serverSideCallMetadata(): CallMetadata {
return {
id: '',
wallTime: 0,
@ -102,6 +102,7 @@ export function internalCallMetadata(): CallMetadata {
method: '',
params: {},
log: [],
snapshots: []
snapshots: [],
isServerSide: true,
};
}

View file

@ -49,6 +49,7 @@ export interface PageDelegate {
exposeBinding(binding: PageBinding): Promise<void>;
evaluateOnNewDocument(source: string): Promise<void>;
closePage(runBeforeUnload: boolean): Promise<void>;
potentiallyUninitializedPage(): Page;
pageOrError(): Promise<Page | Error>;
navigateFrame(frame: frames.Frame, url: string, referrer: string | undefined): Promise<frames.GotoResult>;
@ -158,6 +159,7 @@ export class Page extends SdkObject {
_video: Artifact | null = null;
_opener: Page | undefined;
private _frameThrottler = new FrameThrottler(10, 200);
private _isServerSideOnly = false;
constructor(delegate: PageDelegate, browserContext: BrowserContext) {
super(browserContext, 'page');
@ -203,8 +205,8 @@ export class Page extends SdkObject {
this._setIsError(error);
}
this._initialized = true;
this._browserContext.emit(BrowserContext.Events.Page, this);
// I may happen that page iniatialization finishes after Close event has already been sent,
this.emitOnContext(BrowserContext.Events.Page, this);
// I may happen that page initialization finishes after Close event has already been sent,
// in that case we fire another Close event to ensure that each reported Page will have
// corresponding Close event after it is reported on the context.
if (this.isClosed())
@ -215,6 +217,12 @@ export class Page extends SdkObject {
return this._initialized ? this : undefined;
}
emitOnContext(event: string | symbol, ...args: any[]) {
if (this._isServerSideOnly)
return;
this._browserContext.emit(event, ...args);
}
async _doSlowMo() {
const slowMo = this._browserContext._browser.options.slowMo;
if (!slowMo)
@ -611,6 +619,10 @@ export class Page extends SdkObject {
async hideHighlight() {
await Promise.all(this.frames().map(frame => frame.hideHighlight().catch(() => {})));
}
markAsServerSideOnly() {
this._isServerSideOnly = true;
}
}
export class Worker extends SdkObject {

View file

@ -36,8 +36,8 @@ export class Playwright extends SdkObject {
readonly options: PlaywrightOptions;
private _allPages = new Set<Page>();
constructor(sdkLanguage: string, isInternal: boolean) {
super({ attribution: { isInternal }, instrumentation: createInstrumentation() } as any, undefined, 'Playwright');
constructor(sdkLanguage: string, isInternalPlaywright: boolean) {
super({ attribution: { isInternalPlaywright }, instrumentation: createInstrumentation() } as any, undefined, 'Playwright');
this.instrumentation.addListener({
onPageOpen: page => this._allPages.add(page),
onPageClose: page => this._allPages.delete(page),
@ -63,6 +63,6 @@ export class Playwright extends SdkObject {
}
}
export function createPlaywright(sdkLanguage: string, isInternal: boolean = false) {
return new Playwright(sdkLanguage, isInternal);
export function createPlaywright(sdkLanguage: string, isInternalPlaywright: boolean = false) {
return new Playwright(sdkLanguage, isInternalPlaywright);
}

View file

@ -19,7 +19,7 @@ import path from 'path';
import { Page } from '../../page';
import { ProgressController } from '../../progress';
import { EventEmitter } from 'events';
import { internalCallMetadata } from '../../instrumentation';
import { serverSideCallMetadata } from '../../instrumentation';
import type { CallLog, EventData, Mode, Source } from './recorderTypes';
import { isUnderTest } from '../../../utils/utils';
import * as mime from 'mime';
@ -61,7 +61,7 @@ export class RecorderApp extends EventEmitter implements IRecorderApp {
}
async close() {
await this._page.context().close(internalCallMetadata());
await this._page.context().close(serverSideCallMetadata());
}
private async _init() {
@ -89,11 +89,11 @@ export class RecorderApp extends EventEmitter implements IRecorderApp {
this._page.once('close', () => {
this.emit('close');
this._page.context().close(internalCallMetadata()).catch(() => {});
this._page.context().close(serverSideCallMetadata()).catch(() => {});
});
const mainFrame = this._page.mainFrame();
await mainFrame.goto(internalCallMetadata(), 'https://playwright/index.html');
await mainFrame.goto(serverSideCallMetadata(), 'https://playwright/index.html');
}
static async open(sdkLanguage: string, headed: boolean): Promise<IRecorderApp> {
@ -108,7 +108,7 @@ export class RecorderApp extends EventEmitter implements IRecorderApp {
];
if (process.env.PWTEST_RECORDER_PORT)
args.push(`--remote-debugging-port=${process.env.PWTEST_RECORDER_PORT}`);
const context = await recorderPlaywright.chromium.launchPersistentContext(internalCallMetadata(), '', {
const context = await recorderPlaywright.chromium.launchPersistentContext(serverSideCallMetadata(), '', {
channel: findChromiumChannel(sdkLanguage),
args,
noDefaultViewport: true,
@ -116,7 +116,7 @@ export class RecorderApp extends EventEmitter implements IRecorderApp {
headless: !!process.env.PWTEST_CLI_HEADLESS || (isUnderTest() && !headed),
useWebSocket: !!process.env.PWTEST_RECORDER_PORT
});
const controller = new ProgressController(internalCallMetadata(), context._browser);
const controller = new ProgressController(serverSideCallMetadata(), context._browser);
await controller.run(async progress => {
await context._browser._defaultContext!._loadDefaultContextAsIs(progress);
});

View file

@ -22,7 +22,7 @@ import { findChromiumChannel } from '../../../utils/registry';
import { isUnderTest } from '../../../utils/utils';
import { BrowserContext } from '../../browserContext';
import { installAppIcon } from '../../chromium/crApp';
import { internalCallMetadata } from '../../instrumentation';
import { serverSideCallMetadata } from '../../instrumentation';
import { createPlaywright } from '../../playwright';
import { ProgressController } from '../../progress';
@ -61,7 +61,7 @@ export async function showTraceViewer(traceUrls: string[], browserName: string,
if (isUnderTest())
args.push(`--remote-debugging-port=0`);
const context = await traceViewerPlaywright[traceViewerBrowser as 'chromium'].launchPersistentContext(internalCallMetadata(), '', {
const context = await traceViewerPlaywright[traceViewerBrowser as 'chromium'].launchPersistentContext(serverSideCallMetadata(), '', {
// TODO: store language in the trace.
channel: findChromiumChannel(traceViewerPlaywright.options.sdkLanguage),
args,
@ -71,7 +71,7 @@ export async function showTraceViewer(traceUrls: string[], browserName: string,
useWebSocket: isUnderTest()
});
const controller = new ProgressController(internalCallMetadata(), context._browser);
const controller = new ProgressController(serverSideCallMetadata(), context._browser);
await controller.run(async progress => {
await context._browser._defaultContext!._loadDefaultContextAsIs(progress);
});
@ -82,11 +82,11 @@ export async function showTraceViewer(traceUrls: string[], browserName: string,
await installAppIcon(page);
if (isUnderTest())
page.on('close', () => context.close(internalCallMetadata()).catch(() => {}));
page.on('close', () => context.close(serverSideCallMetadata()).catch(() => {}));
else
page.on('close', () => process.exit());
const searchQuery = traceUrls.length ? '?' + traceUrls.map(t => `trace=${t}`).join('&') : '';
await page.mainFrame().goto(internalCallMetadata(), urlPrefix + `/trace/index.html${searchQuery}`);
await page.mainFrame().goto(serverSideCallMetadata(), urlPrefix + `/trace/index.html${searchQuery}`);
return context;
}

View file

@ -109,6 +109,10 @@ export class WKPage implements PageDelegate {
}
}
potentiallyUninitializedPage(): Page {
return this._page;
}
private async _initializePageProxySession() {
const promises: Promise<any>[] = [
this._pageProxySession.send('Dialog.enable'),

View file

@ -141,3 +141,24 @@ it('should capture cookies', async ({ server, context, page, contextFactory }) =
'empty=',
]);
});
it('should not emit events about internal page', async ({ contextFactory }) => {
const context = await contextFactory();
const page = await context.newPage();
await page.route('**/*', route => {
route.fulfill({ body: '<html></html>' });
});
await page.goto('https://www.example.com');
await page.evaluate(() => localStorage['name1'] = 'value1');
await page.goto('https://www.domain.com');
await page.evaluate(() => localStorage['name2'] = 'value2');
const events = [];
context.on('page', e => events.push(e));
context.on('request', e => events.push(e));
context.on('requestfailed', e => events.push(e));
context.on('requestfinished', e => events.push(e));
context.on('response', e => events.push(e));
await context.storageState();
expect(events).toHaveLength(0);
});