mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-14 21:53:35 +03:00
chore: introduce instrumentation api (#5385)
This commit is contained in:
parent
1240dd48cb
commit
2e01fbdbec
@ -31,9 +31,9 @@ import { createGuid } from './utils/utils';
|
||||
import { SelectorsDispatcher } from './dispatchers/selectorsDispatcher';
|
||||
import { Selectors } from './server/selectors';
|
||||
import { BrowserContext, Video } from './server/browserContext';
|
||||
import { StreamDispatcher, StreamWrapper } from './dispatchers/streamDispatcher';
|
||||
import { StreamDispatcher } from './dispatchers/streamDispatcher';
|
||||
import { ProtocolLogger } from './server/types';
|
||||
import { SdkObject } from './server/sdkObject';
|
||||
import { CallMetadata, internalCallMetadata, SdkObject } from './server/instrumentation';
|
||||
|
||||
export class BrowserServerLauncherImpl implements BrowserServerLauncher {
|
||||
private _browserType: BrowserType;
|
||||
@ -43,7 +43,7 @@ export class BrowserServerLauncherImpl implements BrowserServerLauncher {
|
||||
}
|
||||
|
||||
async launchServer(options: LaunchServerOptions = {}): Promise<BrowserServerImpl> {
|
||||
const browser = await this._browserType.launch({
|
||||
const browser = await this._browserType.launch(internalCallMetadata(), {
|
||||
...options,
|
||||
ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : undefined,
|
||||
ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs),
|
||||
@ -119,7 +119,7 @@ export class BrowserServerImpl extends EventEmitter implements BrowserServer {
|
||||
connection.dispatch(JSON.parse(Buffer.from(message).toString()));
|
||||
});
|
||||
socket.on('error', () => {});
|
||||
const selectors = new Selectors(this._browser.options.rootSdkObject);
|
||||
const selectors = new Selectors();
|
||||
const scope = connection.rootDispatcher();
|
||||
const remoteBrowser = new RemoteBrowserDispatcher(scope, this._browser, selectors);
|
||||
socket.on('close', () => {
|
||||
@ -156,12 +156,12 @@ class ConnectedBrowser extends BrowserDispatcher {
|
||||
this._selectors = selectors;
|
||||
}
|
||||
|
||||
async newContext(params: channels.BrowserNewContextParams): Promise<{ context: channels.BrowserContextChannel }> {
|
||||
async newContext(params: channels.BrowserNewContextParams, metadata: CallMetadata): Promise<{ context: channels.BrowserContextChannel }> {
|
||||
if (params.recordVideo) {
|
||||
// TODO: we should create a separate temp directory or accept a launchServer parameter.
|
||||
params.recordVideo.dir = this._object.options.downloadsPath!;
|
||||
}
|
||||
const result = await super.newContext(params);
|
||||
const result = await super.newContext(params, metadata);
|
||||
const dispatcher = result.context as BrowserContextDispatcher;
|
||||
dispatcher._object.on(BrowserContext.Events.VideoStarted, (video: Video) => this._sendVideo(dispatcher, video));
|
||||
dispatcher._object._setSelectors(this._selectors);
|
||||
@ -189,7 +189,7 @@ class ConnectedBrowser extends BrowserDispatcher {
|
||||
video._waitForCallbackOnFinish(async () => {
|
||||
const readable = fs.createReadStream(video._path);
|
||||
await new Promise(f => readable.on('readable', f));
|
||||
const stream = new StreamDispatcher(this._remoteBrowser!._scope, new StreamWrapper(this._object, readable));
|
||||
const stream = new StreamDispatcher(this._remoteBrowser!._scope, readable);
|
||||
this._remoteBrowser!._dispatchEvent('video', {
|
||||
stream,
|
||||
context: contextDispatcher,
|
||||
|
@ -74,11 +74,11 @@ export class ScreenshotGenerator {
|
||||
const snapshots = action.snapshots || [];
|
||||
const snapshotId = snapshots.length ? snapshots[0].snapshotId : undefined;
|
||||
const snapshotUrl = this._snapshotServer.snapshotUrl(action.pageId!, snapshotId, action.endTime);
|
||||
console.log('Generating screenshot for ' + action.action); // eslint-disable-line no-console
|
||||
console.log('Generating screenshot for ' + action.method); // eslint-disable-line no-console
|
||||
await page.evaluate(snapshotUrl => (window as any).showSnapshot(snapshotUrl), snapshotUrl);
|
||||
|
||||
try {
|
||||
const element = await page.$(action.selector || '*[__playwright_target__]');
|
||||
const element = await page.$(action.params.selector || '*[__playwright_target__]');
|
||||
if (element) {
|
||||
await element.evaluate(e => {
|
||||
e.style.backgroundColor = '#ff69b460';
|
||||
|
@ -18,6 +18,7 @@ import { Dispatcher, DispatcherScope, existingDispatcher } from './dispatcher';
|
||||
import { Android, AndroidDevice, SocketBackend } from '../server/android/android';
|
||||
import * as channels from '../protocol/channels';
|
||||
import { BrowserContextDispatcher } from './browserContextDispatcher';
|
||||
import { CallMetadata } from '../server/instrumentation';
|
||||
|
||||
export class AndroidDispatcher extends Dispatcher<Android, channels.AndroidInitializer> implements channels.AndroidChannel {
|
||||
constructor(scope: DispatcherScope, android: Android) {
|
||||
@ -141,7 +142,7 @@ export class AndroidDeviceDispatcher extends Dispatcher<AndroidDevice, channels.
|
||||
return { result: (await this._object.shell(params.command)).toString('base64') };
|
||||
}
|
||||
|
||||
async open(params: channels.AndroidDeviceOpenParams, metadata?: channels.Metadata): Promise<channels.AndroidDeviceOpenResult> {
|
||||
async open(params: channels.AndroidDeviceOpenParams, metadata: CallMetadata): Promise<channels.AndroidDeviceOpenResult> {
|
||||
const socket = await this._object.open(params.command);
|
||||
return { socket: new AndroidSocketDispatcher(this._scope, socket) };
|
||||
}
|
||||
@ -182,11 +183,11 @@ export class AndroidSocketDispatcher extends Dispatcher<SocketBackend, channels.
|
||||
});
|
||||
}
|
||||
|
||||
async write(params: channels.AndroidSocketWriteParams, metadata?: channels.Metadata): Promise<void> {
|
||||
async write(params: channels.AndroidSocketWriteParams, metadata: CallMetadata): Promise<void> {
|
||||
await this._object.write(Buffer.from(params.data, 'base64'));
|
||||
}
|
||||
|
||||
async close(params: channels.AndroidSocketCloseParams, metadata?: channels.Metadata): Promise<void> {
|
||||
async close(params: channels.AndroidSocketCloseParams, metadata: CallMetadata): Promise<void> {
|
||||
await this._object.close();
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import { RouteDispatcher, RequestDispatcher } from './networkDispatchers';
|
||||
import { CRBrowserContext } from '../server/chromium/crBrowser';
|
||||
import { CDPSessionDispatcher } from './cdpSessionDispatcher';
|
||||
import { RecorderSupplement } from '../server/supplements/recorderSupplement';
|
||||
import { CallMetadata } from '../server/instrumentation';
|
||||
|
||||
export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channels.BrowserContextInitializer> implements channels.BrowserContextChannel {
|
||||
private _context: BrowserContext;
|
||||
@ -120,8 +121,8 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
|
||||
});
|
||||
}
|
||||
|
||||
async storageState(): Promise<channels.BrowserContextStorageStateResult> {
|
||||
return await this._context.storageState();
|
||||
async storageState(params: channels.BrowserContextStorageStateParams, metadata: CallMetadata): Promise<channels.BrowserContextStorageStateResult> {
|
||||
return await this._context.storageState(metadata);
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
|
@ -21,6 +21,7 @@ import { CDPSessionDispatcher } from './cdpSessionDispatcher';
|
||||
import { Dispatcher, DispatcherScope } from './dispatcher';
|
||||
import { CRBrowser } from '../server/chromium/crBrowser';
|
||||
import { PageDispatcher } from './pageDispatcher';
|
||||
import { CallMetadata } from '../server/instrumentation';
|
||||
|
||||
export class BrowserDispatcher extends Dispatcher<Browser, channels.BrowserInitializer> implements channels.BrowserChannel {
|
||||
constructor(scope: DispatcherScope, browser: Browser) {
|
||||
@ -33,10 +34,10 @@ export class BrowserDispatcher extends Dispatcher<Browser, channels.BrowserIniti
|
||||
this._dispose();
|
||||
}
|
||||
|
||||
async newContext(params: channels.BrowserNewContextParams): Promise<channels.BrowserNewContextResult> {
|
||||
async newContext(params: channels.BrowserNewContextParams, metadata: CallMetadata): Promise<channels.BrowserNewContextResult> {
|
||||
const context = await this._object.newContext(params);
|
||||
if (params.storageState)
|
||||
await context.setStorageState(params.storageState);
|
||||
await context.setStorageState(metadata, params.storageState);
|
||||
return { context: new BrowserContextDispatcher(this._scope, context) };
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,7 @@ import { BrowserDispatcher } from './browserDispatcher';
|
||||
import * as channels from '../protocol/channels';
|
||||
import { Dispatcher, DispatcherScope } from './dispatcher';
|
||||
import { BrowserContextDispatcher } from './browserContextDispatcher';
|
||||
import { CallMetadata } from '../server/instrumentation';
|
||||
|
||||
export class BrowserTypeDispatcher extends Dispatcher<BrowserType, channels.BrowserTypeInitializer> implements channels.BrowserTypeChannel {
|
||||
constructor(scope: DispatcherScope, browserType: BrowserType) {
|
||||
@ -28,13 +29,13 @@ export class BrowserTypeDispatcher extends Dispatcher<BrowserType, channels.Brow
|
||||
}, true);
|
||||
}
|
||||
|
||||
async launch(params: channels.BrowserTypeLaunchParams): Promise<channels.BrowserTypeLaunchResult> {
|
||||
const browser = await this._object.launch(params);
|
||||
async launch(params: channels.BrowserTypeLaunchParams, metadata: CallMetadata): Promise<channels.BrowserTypeLaunchResult> {
|
||||
const browser = await this._object.launch(metadata, params);
|
||||
return { browser: new BrowserDispatcher(this._scope, browser) };
|
||||
}
|
||||
|
||||
async launchPersistentContext(params: channels.BrowserTypeLaunchPersistentContextParams): Promise<channels.BrowserTypeLaunchPersistentContextResult> {
|
||||
const browserContext = await this._object.launchPersistentContext(params.userDataDir, params);
|
||||
async launchPersistentContext(params: channels.BrowserTypeLaunchPersistentContextParams, metadata: CallMetadata): Promise<channels.BrowserTypeLaunchPersistentContextResult> {
|
||||
const browserContext = await this._object.launchPersistentContext(metadata, params.userDataDir, params);
|
||||
return { context: new BrowserContextDispatcher(this._scope, browserContext) };
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ import { createScheme, Validator, ValidationError } from '../protocol/validator'
|
||||
import { assert, createGuid, debugAssert, isUnderTest } from '../utils/utils';
|
||||
import { tOptional } from '../protocol/validatorPrimitives';
|
||||
import { kBrowserOrContextClosedError } from '../utils/errors';
|
||||
import { SdkObject } from '../server/sdkObject';
|
||||
import { CallMetadata } from '../server/instrumentation';
|
||||
|
||||
export const dispatcherSymbol = Symbol('dispatcher');
|
||||
|
||||
@ -39,14 +39,7 @@ export function lookupNullableDispatcher<DispatcherType>(object: any | null): Di
|
||||
return object ? lookupDispatcher(object) : undefined;
|
||||
}
|
||||
|
||||
export type CallMetadata = channels.Metadata & {
|
||||
object: SdkObject;
|
||||
type: string;
|
||||
method: string;
|
||||
params: any;
|
||||
};
|
||||
|
||||
export class Dispatcher<Type extends SdkObject, Initializer> extends EventEmitter implements channels.Channel {
|
||||
export class Dispatcher<Type, Initializer> extends EventEmitter implements channels.Channel {
|
||||
private _connection: DispatcherConnection;
|
||||
private _isScope: boolean;
|
||||
// Parent is always "isScope".
|
||||
@ -120,9 +113,9 @@ export class Dispatcher<Type extends SdkObject, Initializer> extends EventEmitte
|
||||
}
|
||||
|
||||
export type DispatcherScope = Dispatcher<any, any>;
|
||||
class Root extends Dispatcher<SdkObject, {}> {
|
||||
class Root extends Dispatcher<{}, {}> {
|
||||
constructor(connection: DispatcherConnection) {
|
||||
super(connection, new SdkObject(null), '', {}, true, '');
|
||||
super(connection, {}, '', {}, true, '');
|
||||
}
|
||||
}
|
||||
|
||||
@ -186,8 +179,7 @@ export class DispatcherConnection {
|
||||
if (typeof (dispatcher as any)[method] !== 'function')
|
||||
throw new Error(`Mismatching dispatcher: "${dispatcher._type}" does not implement "${method}"`);
|
||||
const callMetadata: CallMetadata = {
|
||||
...this._validateMetadata(metadata).stack,
|
||||
object: dispatcher._object,
|
||||
...this._validateMetadata(metadata),
|
||||
type: dispatcher._type,
|
||||
method,
|
||||
params,
|
||||
|
@ -17,7 +17,7 @@
|
||||
import { Download } from '../server/download';
|
||||
import * as channels from '../protocol/channels';
|
||||
import { Dispatcher, DispatcherScope } from './dispatcher';
|
||||
import { StreamDispatcher, StreamWrapper } from './streamDispatcher';
|
||||
import { StreamDispatcher } from './streamDispatcher';
|
||||
import * as fs from 'fs';
|
||||
import * as util from 'util';
|
||||
import { mkdirIfNeeded } from '../utils/utils';
|
||||
@ -65,7 +65,7 @@ export class DownloadDispatcher extends Dispatcher<Download, channels.DownloadIn
|
||||
try {
|
||||
const readable = fs.createReadStream(localPath);
|
||||
await new Promise(f => readable.on('readable', f));
|
||||
const stream = new StreamDispatcher(this._scope, new StreamWrapper(this._object, readable));
|
||||
const stream = new StreamDispatcher(this._scope, readable);
|
||||
// Resolve with a stream, so that client starts saving the data.
|
||||
resolve({ stream });
|
||||
// Block the download until the stream is consumed.
|
||||
@ -87,7 +87,7 @@ export class DownloadDispatcher extends Dispatcher<Download, channels.DownloadIn
|
||||
return {};
|
||||
const readable = fs.createReadStream(fileName);
|
||||
await new Promise(f => readable.on('readable', f));
|
||||
return { stream: new StreamDispatcher(this._scope, new StreamWrapper(this._object, readable)) };
|
||||
return { stream: new StreamDispatcher(this._scope, readable) };
|
||||
}
|
||||
|
||||
async failure(): Promise<channels.DownloadFailureResult> {
|
||||
|
@ -20,7 +20,7 @@ import * as channels from '../protocol/channels';
|
||||
import { DispatcherScope, lookupNullableDispatcher } from './dispatcher';
|
||||
import { JSHandleDispatcher, serializeResult, parseArgument } from './jsHandleDispatcher';
|
||||
import { FrameDispatcher } from './frameDispatcher';
|
||||
import { runAction } from '../server/browserContext';
|
||||
import { CallMetadata } from '../server/instrumentation';
|
||||
|
||||
export function createHandle(scope: DispatcherScope, handle: js.JSHandle): JSHandleDispatcher {
|
||||
return handle.asElement() ? new ElementHandleDispatcher(scope, handle.asElement()!) : new JSHandleDispatcher(scope, handle);
|
||||
@ -40,171 +40,149 @@ export class ElementHandleDispatcher extends JSHandleDispatcher implements chann
|
||||
this._elementHandle = elementHandle;
|
||||
}
|
||||
|
||||
async ownerFrame(): Promise<channels.ElementHandleOwnerFrameResult> {
|
||||
async ownerFrame(params: channels.ElementHandleOwnerFrameParams, metadata: CallMetadata): Promise<channels.ElementHandleOwnerFrameResult> {
|
||||
return { frame: lookupNullableDispatcher<FrameDispatcher>(await this._elementHandle.ownerFrame()) };
|
||||
}
|
||||
|
||||
async contentFrame(): Promise<channels.ElementHandleContentFrameResult> {
|
||||
async contentFrame(params: channels.ElementHandleContentFrameParams, metadata: CallMetadata): Promise<channels.ElementHandleContentFrameResult> {
|
||||
return { frame: lookupNullableDispatcher<FrameDispatcher>(await this._elementHandle.contentFrame()) };
|
||||
}
|
||||
|
||||
async getAttribute(params: channels.ElementHandleGetAttributeParams): Promise<channels.ElementHandleGetAttributeResult> {
|
||||
async getAttribute(params: channels.ElementHandleGetAttributeParams, metadata: CallMetadata): Promise<channels.ElementHandleGetAttributeResult> {
|
||||
const value = await this._elementHandle.getAttribute(params.name);
|
||||
return { value: value === null ? undefined : value };
|
||||
}
|
||||
|
||||
async textContent(): Promise<channels.ElementHandleTextContentResult> {
|
||||
async textContent(params: channels.ElementHandleTextContentParams, metadata: CallMetadata): Promise<channels.ElementHandleTextContentResult> {
|
||||
const value = await this._elementHandle.textContent();
|
||||
return { value: value === null ? undefined : value };
|
||||
}
|
||||
|
||||
async innerText(): Promise<channels.ElementHandleInnerTextResult> {
|
||||
async innerText(params: channels.ElementHandleInnerTextParams, metadata: CallMetadata): Promise<channels.ElementHandleInnerTextResult> {
|
||||
return { value: await this._elementHandle.innerText() };
|
||||
}
|
||||
|
||||
async innerHTML(): Promise<channels.ElementHandleInnerHTMLResult> {
|
||||
async innerHTML(params: channels.ElementHandleInnerHTMLParams, metadata: CallMetadata): Promise<channels.ElementHandleInnerHTMLResult> {
|
||||
return { value: await this._elementHandle.innerHTML() };
|
||||
}
|
||||
|
||||
async isChecked(): Promise<channels.ElementHandleIsCheckedResult> {
|
||||
async isChecked(params: channels.ElementHandleIsCheckedParams, metadata: CallMetadata): Promise<channels.ElementHandleIsCheckedResult> {
|
||||
return { value: await this._elementHandle.isChecked() };
|
||||
}
|
||||
|
||||
async isDisabled(): Promise<channels.ElementHandleIsDisabledResult> {
|
||||
async isDisabled(params: channels.ElementHandleIsDisabledParams, metadata: CallMetadata): Promise<channels.ElementHandleIsDisabledResult> {
|
||||
return { value: await this._elementHandle.isDisabled() };
|
||||
}
|
||||
|
||||
async isEditable(): Promise<channels.ElementHandleIsEditableResult> {
|
||||
async isEditable(params: channels.ElementHandleIsEditableParams, metadata: CallMetadata): Promise<channels.ElementHandleIsEditableResult> {
|
||||
return { value: await this._elementHandle.isEditable() };
|
||||
}
|
||||
|
||||
async isEnabled(): Promise<channels.ElementHandleIsEnabledResult> {
|
||||
async isEnabled(params: channels.ElementHandleIsEnabledParams, metadata: CallMetadata): Promise<channels.ElementHandleIsEnabledResult> {
|
||||
return { value: await this._elementHandle.isEnabled() };
|
||||
}
|
||||
|
||||
async isHidden(): Promise<channels.ElementHandleIsHiddenResult> {
|
||||
async isHidden(params: channels.ElementHandleIsHiddenParams, metadata: CallMetadata): Promise<channels.ElementHandleIsHiddenResult> {
|
||||
return { value: await this._elementHandle.isHidden() };
|
||||
}
|
||||
|
||||
async isVisible(): Promise<channels.ElementHandleIsVisibleResult> {
|
||||
async isVisible(params: channels.ElementHandleIsVisibleParams, metadata: CallMetadata): Promise<channels.ElementHandleIsVisibleResult> {
|
||||
return { value: await this._elementHandle.isVisible() };
|
||||
}
|
||||
|
||||
async dispatchEvent(params: channels.ElementHandleDispatchEventParams): Promise<void> {
|
||||
async dispatchEvent(params: channels.ElementHandleDispatchEventParams, metadata: CallMetadata): Promise<void> {
|
||||
await this._elementHandle.dispatchEvent(params.type, parseArgument(params.eventInit));
|
||||
}
|
||||
|
||||
async scrollIntoViewIfNeeded(params: channels.ElementHandleScrollIntoViewIfNeededParams): Promise<void> {
|
||||
await this._elementHandle.scrollIntoViewIfNeeded(params);
|
||||
async scrollIntoViewIfNeeded(params: channels.ElementHandleScrollIntoViewIfNeededParams, metadata: CallMetadata): Promise<void> {
|
||||
await this._elementHandle.scrollIntoViewIfNeeded(metadata, params);
|
||||
}
|
||||
|
||||
async hover(params: channels.ElementHandleHoverParams, metadata?: channels.Metadata): Promise<void> {
|
||||
return runAction(async controller => {
|
||||
return await this._elementHandle.hover(controller, params);
|
||||
}, { ...metadata, type: 'hover', target: this._elementHandle, page: this._elementHandle._page });
|
||||
async hover(params: channels.ElementHandleHoverParams, metadata: CallMetadata): Promise<void> {
|
||||
return await this._elementHandle.hover(metadata, params);
|
||||
}
|
||||
|
||||
async click(params: channels.ElementHandleClickParams, metadata?: channels.Metadata): Promise<void> {
|
||||
return runAction(async controller => {
|
||||
return await this._elementHandle.click(controller, params);
|
||||
}, { ...metadata, type: 'click', target: this._elementHandle, page: this._elementHandle._page });
|
||||
async click(params: channels.ElementHandleClickParams, metadata: CallMetadata): Promise<void> {
|
||||
return await this._elementHandle.click(metadata, params);
|
||||
}
|
||||
|
||||
async dblclick(params: channels.ElementHandleDblclickParams, metadata?: channels.Metadata): Promise<void> {
|
||||
return runAction(async controller => {
|
||||
return await this._elementHandle.dblclick(controller, params);
|
||||
}, { ...metadata, type: 'dblclick', target: this._elementHandle, page: this._elementHandle._page });
|
||||
async dblclick(params: channels.ElementHandleDblclickParams, metadata: CallMetadata): Promise<void> {
|
||||
return await this._elementHandle.dblclick(metadata, params);
|
||||
}
|
||||
|
||||
async tap(params: channels.ElementHandleTapParams, metadata?: channels.Metadata): Promise<void> {
|
||||
return runAction(async controller => {
|
||||
return await this._elementHandle.tap(controller, params);
|
||||
}, { ...metadata, type: 'tap', target: this._elementHandle, page: this._elementHandle._page });
|
||||
async tap(params: channels.ElementHandleTapParams, metadata: CallMetadata): Promise<void> {
|
||||
return await this._elementHandle.tap(metadata, params);
|
||||
}
|
||||
|
||||
async selectOption(params: channels.ElementHandleSelectOptionParams, metadata?: channels.Metadata): Promise<channels.ElementHandleSelectOptionResult> {
|
||||
return runAction(async controller => {
|
||||
const elements = (params.elements || []).map(e => (e as ElementHandleDispatcher)._elementHandle);
|
||||
return { values: await this._elementHandle.selectOption(controller, elements, params.options || [], params) };
|
||||
}, { ...metadata, type: 'selectOption', target: this._elementHandle, page: this._elementHandle._page });
|
||||
async selectOption(params: channels.ElementHandleSelectOptionParams, metadata: CallMetadata): Promise<channels.ElementHandleSelectOptionResult> {
|
||||
const elements = (params.elements || []).map(e => (e as ElementHandleDispatcher)._elementHandle);
|
||||
return { values: await this._elementHandle.selectOption(metadata, elements, params.options || [], params) };
|
||||
}
|
||||
|
||||
async fill(params: channels.ElementHandleFillParams, metadata?: channels.Metadata): Promise<void> {
|
||||
return runAction(async controller => {
|
||||
return await this._elementHandle.fill(controller, params.value, params);
|
||||
}, { ...metadata, type: 'fill', value: params.value, target: this._elementHandle, page: this._elementHandle._page });
|
||||
async fill(params: channels.ElementHandleFillParams, metadata: CallMetadata): Promise<void> {
|
||||
return await this._elementHandle.fill(metadata, params.value, params);
|
||||
}
|
||||
|
||||
async selectText(params: channels.ElementHandleSelectTextParams): Promise<void> {
|
||||
await this._elementHandle.selectText(params);
|
||||
async selectText(params: channels.ElementHandleSelectTextParams, metadata: CallMetadata): Promise<void> {
|
||||
await this._elementHandle.selectText(metadata, params);
|
||||
}
|
||||
|
||||
async setInputFiles(params: channels.ElementHandleSetInputFilesParams, metadata?: channels.Metadata): Promise<void> {
|
||||
return runAction(async controller => {
|
||||
return await this._elementHandle.setInputFiles(controller, params.files, params);
|
||||
}, { ...metadata, type: 'setInputFiles', target: this._elementHandle, page: this._elementHandle._page });
|
||||
async setInputFiles(params: channels.ElementHandleSetInputFilesParams, metadata: CallMetadata): Promise<void> {
|
||||
return await this._elementHandle.setInputFiles(metadata, params.files, params);
|
||||
}
|
||||
|
||||
async focus(): Promise<void> {
|
||||
await this._elementHandle.focus();
|
||||
async focus(params: channels.ElementHandleFocusParams, metadata: CallMetadata): Promise<void> {
|
||||
await this._elementHandle.focus(metadata);
|
||||
}
|
||||
|
||||
async type(params: channels.ElementHandleTypeParams, metadata?: channels.Metadata): Promise<void> {
|
||||
return runAction(async controller => {
|
||||
return await this._elementHandle.type(controller, params.text, params);
|
||||
}, { ...metadata, type: 'type', value: params.text, target: this._elementHandle, page: this._elementHandle._page });
|
||||
async type(params: channels.ElementHandleTypeParams, metadata: CallMetadata): Promise<void> {
|
||||
return await this._elementHandle.type(metadata, params.text, params);
|
||||
}
|
||||
|
||||
async press(params: channels.ElementHandlePressParams, metadata?: channels.Metadata): Promise<void> {
|
||||
return runAction(async controller => {
|
||||
return await this._elementHandle.press(controller, params.key, params);
|
||||
}, { ...metadata, type: 'press', value: params.key, target: this._elementHandle, page: this._elementHandle._page });
|
||||
async press(params: channels.ElementHandlePressParams, metadata: CallMetadata): Promise<void> {
|
||||
return await this._elementHandle.press(metadata, params.key, params);
|
||||
}
|
||||
|
||||
async check(params: channels.ElementHandleCheckParams, metadata?: channels.Metadata): Promise<void> {
|
||||
return runAction(async controller => {
|
||||
return await this._elementHandle.check(controller, params);
|
||||
}, { ...metadata, type: 'check', target: this._elementHandle, page: this._elementHandle._page });
|
||||
async check(params: channels.ElementHandleCheckParams, metadata: CallMetadata): Promise<void> {
|
||||
return await this._elementHandle.check(metadata, params);
|
||||
}
|
||||
|
||||
async uncheck(params: channels.ElementHandleUncheckParams, metadata?: channels.Metadata): Promise<void> {
|
||||
return runAction(async controller => {
|
||||
return await this._elementHandle.uncheck(controller, params);
|
||||
}, { ...metadata, type: 'uncheck', target: this._elementHandle, page: this._elementHandle._page });
|
||||
async uncheck(params: channels.ElementHandleUncheckParams, metadata: CallMetadata): Promise<void> {
|
||||
return await this._elementHandle.uncheck(metadata, params);
|
||||
}
|
||||
|
||||
async boundingBox(): Promise<channels.ElementHandleBoundingBoxResult> {
|
||||
async boundingBox(params: channels.ElementHandleBoundingBoxParams, metadata: CallMetadata): Promise<channels.ElementHandleBoundingBoxResult> {
|
||||
const value = await this._elementHandle.boundingBox();
|
||||
return { value: value || undefined };
|
||||
}
|
||||
|
||||
async screenshot(params: channels.ElementHandleScreenshotParams): Promise<channels.ElementHandleScreenshotResult> {
|
||||
return { binary: (await this._elementHandle.screenshot(params)).toString('base64') };
|
||||
async screenshot(params: channels.ElementHandleScreenshotParams, metadata: CallMetadata): Promise<channels.ElementHandleScreenshotResult> {
|
||||
return { binary: (await this._elementHandle.screenshot(metadata, params)).toString('base64') };
|
||||
}
|
||||
|
||||
async querySelector(params: channels.ElementHandleQuerySelectorParams): Promise<channels.ElementHandleQuerySelectorResult> {
|
||||
async querySelector(params: channels.ElementHandleQuerySelectorParams, metadata: CallMetadata): Promise<channels.ElementHandleQuerySelectorResult> {
|
||||
const handle = await this._elementHandle.$(params.selector);
|
||||
return { element: handle ? new ElementHandleDispatcher(this._scope, handle) : undefined };
|
||||
}
|
||||
|
||||
async querySelectorAll(params: channels.ElementHandleQuerySelectorAllParams): Promise<channels.ElementHandleQuerySelectorAllResult> {
|
||||
async querySelectorAll(params: channels.ElementHandleQuerySelectorAllParams, metadata: CallMetadata): Promise<channels.ElementHandleQuerySelectorAllResult> {
|
||||
const elements = await this._elementHandle.$$(params.selector);
|
||||
return { elements: elements.map(e => new ElementHandleDispatcher(this._scope, e)) };
|
||||
}
|
||||
|
||||
async evalOnSelector(params: channels.ElementHandleEvalOnSelectorParams): Promise<channels.ElementHandleEvalOnSelectorResult> {
|
||||
async evalOnSelector(params: channels.ElementHandleEvalOnSelectorParams, metadata: CallMetadata): Promise<channels.ElementHandleEvalOnSelectorResult> {
|
||||
return { value: serializeResult(await this._elementHandle._$evalExpression(params.selector, params.expression, params.isFunction, parseArgument(params.arg))) };
|
||||
}
|
||||
|
||||
async evalOnSelectorAll(params: channels.ElementHandleEvalOnSelectorAllParams): Promise<channels.ElementHandleEvalOnSelectorAllResult> {
|
||||
async evalOnSelectorAll(params: channels.ElementHandleEvalOnSelectorAllParams, metadata: CallMetadata): Promise<channels.ElementHandleEvalOnSelectorAllResult> {
|
||||
return { value: serializeResult(await this._elementHandle._$$evalExpression(params.selector, params.expression, params.isFunction, parseArgument(params.arg))) };
|
||||
}
|
||||
|
||||
async waitForElementState(params: channels.ElementHandleWaitForElementStateParams): Promise<void> {
|
||||
await this._elementHandle.waitForElementState(params.state, params);
|
||||
async waitForElementState(params: channels.ElementHandleWaitForElementStateParams, metadata: CallMetadata): Promise<void> {
|
||||
await this._elementHandle.waitForElementState(metadata, params.state, params);
|
||||
}
|
||||
|
||||
async waitForSelector(params: channels.ElementHandleWaitForSelectorParams): Promise<channels.ElementHandleWaitForSelectorResult> {
|
||||
return { element: ElementHandleDispatcher.createNullable(this._scope, await this._elementHandle.waitForSelector(params.selector, params)) };
|
||||
async waitForSelector(params: channels.ElementHandleWaitForSelectorParams, metadata: CallMetadata): Promise<channels.ElementHandleWaitForSelectorResult> {
|
||||
return { element: ElementHandleDispatcher.createNullable(this._scope, await this._elementHandle.waitForSelector(metadata, params.selector, params)) };
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ import { Dispatcher, DispatcherScope, lookupNullableDispatcher, existingDispatch
|
||||
import { ElementHandleDispatcher, createHandle } from './elementHandlerDispatcher';
|
||||
import { parseArgument, serializeResult } from './jsHandleDispatcher';
|
||||
import { ResponseDispatcher, RequestDispatcher } from './networkDispatchers';
|
||||
import { runAction } from '../server/browserContext';
|
||||
import { CallMetadata } from '../server/instrumentation';
|
||||
|
||||
export class FrameDispatcher extends Dispatcher<Frame, channels.FrameInitializer> implements channels.FrameChannel {
|
||||
private _frame: Frame;
|
||||
@ -52,45 +52,43 @@ export class FrameDispatcher extends Dispatcher<Frame, channels.FrameInitializer
|
||||
});
|
||||
}
|
||||
|
||||
async goto(params: channels.FrameGotoParams, metadata?: channels.Metadata): Promise<channels.FrameGotoResult> {
|
||||
return await runAction(async controller => {
|
||||
return { response: lookupNullableDispatcher<ResponseDispatcher>(await this._frame.goto(controller, params.url, params)) };
|
||||
}, { ...metadata, type: 'goto', value: params.url, page: this._frame._page });
|
||||
async goto(params: channels.FrameGotoParams, metadata: CallMetadata): Promise<channels.FrameGotoResult> {
|
||||
return { response: lookupNullableDispatcher<ResponseDispatcher>(await this._frame.goto(metadata, params.url, params)) };
|
||||
}
|
||||
|
||||
async frameElement(): Promise<channels.FrameFrameElementResult> {
|
||||
return { element: new ElementHandleDispatcher(this._scope, await this._frame.frameElement()) };
|
||||
}
|
||||
|
||||
async evaluateExpression(params: channels.FrameEvaluateExpressionParams): Promise<channels.FrameEvaluateExpressionResult> {
|
||||
async evaluateExpression(params: channels.FrameEvaluateExpressionParams, metadata: CallMetadata): Promise<channels.FrameEvaluateExpressionResult> {
|
||||
return { value: serializeResult(await this._frame._evaluateExpression(params.expression, params.isFunction, parseArgument(params.arg), params.world)) };
|
||||
}
|
||||
|
||||
async evaluateExpressionHandle(params: channels.FrameEvaluateExpressionHandleParams): Promise<channels.FrameEvaluateExpressionHandleResult> {
|
||||
async evaluateExpressionHandle(params: channels.FrameEvaluateExpressionHandleParams, metadata: CallMetadata): Promise<channels.FrameEvaluateExpressionHandleResult> {
|
||||
return { handle: createHandle(this._scope, await this._frame._evaluateExpressionHandle(params.expression, params.isFunction, parseArgument(params.arg), params.world)) };
|
||||
}
|
||||
|
||||
async waitForSelector(params: channels.FrameWaitForSelectorParams): Promise<channels.FrameWaitForSelectorResult> {
|
||||
return { element: ElementHandleDispatcher.createNullable(this._scope, await this._frame.waitForSelector(params.selector, params)) };
|
||||
async waitForSelector(params: channels.FrameWaitForSelectorParams, metadata: CallMetadata): Promise<channels.FrameWaitForSelectorResult> {
|
||||
return { element: ElementHandleDispatcher.createNullable(this._scope, await this._frame.waitForSelector(metadata, params.selector, params)) };
|
||||
}
|
||||
|
||||
async dispatchEvent(params: channels.FrameDispatchEventParams): Promise<void> {
|
||||
return this._frame.dispatchEvent(params.selector, params.type, parseArgument(params.eventInit), params);
|
||||
async dispatchEvent(params: channels.FrameDispatchEventParams, metadata: CallMetadata): Promise<void> {
|
||||
return this._frame.dispatchEvent(metadata, params.selector, params.type, parseArgument(params.eventInit), params);
|
||||
}
|
||||
|
||||
async evalOnSelector(params: channels.FrameEvalOnSelectorParams): Promise<channels.FrameEvalOnSelectorResult> {
|
||||
async evalOnSelector(params: channels.FrameEvalOnSelectorParams, metadata: CallMetadata): Promise<channels.FrameEvalOnSelectorResult> {
|
||||
return { value: serializeResult(await this._frame._$evalExpression(params.selector, params.expression, params.isFunction, parseArgument(params.arg))) };
|
||||
}
|
||||
|
||||
async evalOnSelectorAll(params: channels.FrameEvalOnSelectorAllParams): Promise<channels.FrameEvalOnSelectorAllResult> {
|
||||
async evalOnSelectorAll(params: channels.FrameEvalOnSelectorAllParams, metadata: CallMetadata): Promise<channels.FrameEvalOnSelectorAllResult> {
|
||||
return { value: serializeResult(await this._frame._$$evalExpression(params.selector, params.expression, params.isFunction, parseArgument(params.arg))) };
|
||||
}
|
||||
|
||||
async querySelector(params: channels.FrameQuerySelectorParams): Promise<channels.FrameQuerySelectorResult> {
|
||||
async querySelector(params: channels.FrameQuerySelectorParams, metadata: CallMetadata): Promise<channels.FrameQuerySelectorResult> {
|
||||
return { element: ElementHandleDispatcher.createNullable(this._scope, await this._frame.$(params.selector)) };
|
||||
}
|
||||
|
||||
async querySelectorAll(params: channels.FrameQuerySelectorAllParams): Promise<channels.FrameQuerySelectorAllResult> {
|
||||
async querySelectorAll(params: channels.FrameQuerySelectorAllParams, metadata: CallMetadata): Promise<channels.FrameQuerySelectorAllResult> {
|
||||
const elements = await this._frame.$$(params.selector);
|
||||
return { elements: elements.map(e => new ElementHandleDispatcher(this._scope, e)) };
|
||||
}
|
||||
@ -99,138 +97,114 @@ export class FrameDispatcher extends Dispatcher<Frame, channels.FrameInitializer
|
||||
return { value: await this._frame.content() };
|
||||
}
|
||||
|
||||
async setContent(params: channels.FrameSetContentParams, metadata?: channels.Metadata): Promise<void> {
|
||||
return await runAction(async controller => {
|
||||
return await this._frame.setContent(controller, params.html, params);
|
||||
}, { ...metadata, type: 'setContent', value: params.html, page: this._frame._page });
|
||||
async setContent(params: channels.FrameSetContentParams, metadata: CallMetadata): Promise<void> {
|
||||
return await this._frame.setContent(metadata, params.html, params);
|
||||
}
|
||||
|
||||
async addScriptTag(params: channels.FrameAddScriptTagParams): Promise<channels.FrameAddScriptTagResult> {
|
||||
async addScriptTag(params: channels.FrameAddScriptTagParams, metadata: CallMetadata): Promise<channels.FrameAddScriptTagResult> {
|
||||
return { element: new ElementHandleDispatcher(this._scope, await this._frame.addScriptTag(params)) };
|
||||
}
|
||||
|
||||
async addStyleTag(params: channels.FrameAddStyleTagParams): Promise<channels.FrameAddStyleTagResult> {
|
||||
async addStyleTag(params: channels.FrameAddStyleTagParams, metadata: CallMetadata): Promise<channels.FrameAddStyleTagResult> {
|
||||
return { element: new ElementHandleDispatcher(this._scope, await this._frame.addStyleTag(params)) };
|
||||
}
|
||||
|
||||
async click(params: channels.FrameClickParams, metadata?: channels.Metadata): Promise<void> {
|
||||
return runAction(async controller => {
|
||||
return await this._frame.click(controller, params.selector, params);
|
||||
}, { ...metadata, type: 'click', target: params.selector, page: this._frame._page });
|
||||
async click(params: channels.FrameClickParams, metadata: CallMetadata): Promise<void> {
|
||||
return await this._frame.click(metadata, params.selector, params);
|
||||
}
|
||||
|
||||
async dblclick(params: channels.FrameDblclickParams, metadata?: channels.Metadata): Promise<void> {
|
||||
return runAction(async controller => {
|
||||
return await this._frame.dblclick(controller, params.selector, params);
|
||||
}, { ...metadata, type: 'dblclick', target: params.selector, page: this._frame._page });
|
||||
async dblclick(params: channels.FrameDblclickParams, metadata: CallMetadata): Promise<void> {
|
||||
return await this._frame.dblclick(metadata, params.selector, params);
|
||||
}
|
||||
|
||||
async tap(params: channels.FrameTapParams, metadata?: channels.Metadata): Promise<void> {
|
||||
return runAction(async controller => {
|
||||
return await this._frame.tap(controller, params.selector, params);
|
||||
}, { ...metadata, type: 'tap', target: params.selector, page: this._frame._page });
|
||||
async tap(params: channels.FrameTapParams, metadata: CallMetadata): Promise<void> {
|
||||
return await this._frame.tap(metadata, params.selector, params);
|
||||
}
|
||||
|
||||
async fill(params: channels.FrameFillParams, metadata?: channels.Metadata): Promise<void> {
|
||||
return runAction(async controller => {
|
||||
return await this._frame.fill(controller, params.selector, params.value, params);
|
||||
}, { ...metadata, type: 'fill', value: params.value, target: params.selector, page: this._frame._page });
|
||||
async fill(params: channels.FrameFillParams, metadata: CallMetadata): Promise<void> {
|
||||
return await this._frame.fill(metadata, params.selector, params.value, params);
|
||||
}
|
||||
|
||||
async focus(params: channels.FrameFocusParams): Promise<void> {
|
||||
await this._frame.focus(params.selector, params);
|
||||
async focus(params: channels.FrameFocusParams, metadata: CallMetadata): Promise<void> {
|
||||
await this._frame.focus(metadata, params.selector, params);
|
||||
}
|
||||
|
||||
async textContent(params: channels.FrameTextContentParams): Promise<channels.FrameTextContentResult> {
|
||||
const value = await this._frame.textContent(params.selector, params);
|
||||
async textContent(params: channels.FrameTextContentParams, metadata: CallMetadata): Promise<channels.FrameTextContentResult> {
|
||||
const value = await this._frame.textContent(metadata, params.selector, params);
|
||||
return { value: value === null ? undefined : value };
|
||||
}
|
||||
|
||||
async innerText(params: channels.FrameInnerTextParams): Promise<channels.FrameInnerTextResult> {
|
||||
return { value: await this._frame.innerText(params.selector, params) };
|
||||
async innerText(params: channels.FrameInnerTextParams, metadata: CallMetadata): Promise<channels.FrameInnerTextResult> {
|
||||
return { value: await this._frame.innerText(metadata, params.selector, params) };
|
||||
}
|
||||
|
||||
async innerHTML(params: channels.FrameInnerHTMLParams): Promise<channels.FrameInnerHTMLResult> {
|
||||
return { value: await this._frame.innerHTML(params.selector, params) };
|
||||
async innerHTML(params: channels.FrameInnerHTMLParams, metadata: CallMetadata): Promise<channels.FrameInnerHTMLResult> {
|
||||
return { value: await this._frame.innerHTML(metadata, params.selector, params) };
|
||||
}
|
||||
|
||||
async getAttribute(params: channels.FrameGetAttributeParams): Promise<channels.FrameGetAttributeResult> {
|
||||
const value = await this._frame.getAttribute(params.selector, params.name, params);
|
||||
async getAttribute(params: channels.FrameGetAttributeParams, metadata: CallMetadata): Promise<channels.FrameGetAttributeResult> {
|
||||
const value = await this._frame.getAttribute(metadata, params.selector, params.name, params);
|
||||
return { value: value === null ? undefined : value };
|
||||
}
|
||||
|
||||
async isChecked(params: channels.FrameIsCheckedParams): Promise<channels.FrameIsCheckedResult> {
|
||||
return { value: await this._frame.isChecked(params.selector, params) };
|
||||
async isChecked(params: channels.FrameIsCheckedParams, metadata: CallMetadata): Promise<channels.FrameIsCheckedResult> {
|
||||
return { value: await this._frame.isChecked(metadata, params.selector, params) };
|
||||
}
|
||||
|
||||
async isDisabled(params: channels.FrameIsDisabledParams): Promise<channels.FrameIsDisabledResult> {
|
||||
return { value: await this._frame.isDisabled(params.selector, params) };
|
||||
async isDisabled(params: channels.FrameIsDisabledParams, metadata: CallMetadata): Promise<channels.FrameIsDisabledResult> {
|
||||
return { value: await this._frame.isDisabled(metadata, params.selector, params) };
|
||||
}
|
||||
|
||||
async isEditable(params: channels.FrameIsEditableParams): Promise<channels.FrameIsEditableResult> {
|
||||
return { value: await this._frame.isEditable(params.selector, params) };
|
||||
async isEditable(params: channels.FrameIsEditableParams, metadata: CallMetadata): Promise<channels.FrameIsEditableResult> {
|
||||
return { value: await this._frame.isEditable(metadata, params.selector, params) };
|
||||
}
|
||||
|
||||
async isEnabled(params: channels.FrameIsEnabledParams): Promise<channels.FrameIsEnabledResult> {
|
||||
return { value: await this._frame.isEnabled(params.selector, params) };
|
||||
async isEnabled(params: channels.FrameIsEnabledParams, metadata: CallMetadata): Promise<channels.FrameIsEnabledResult> {
|
||||
return { value: await this._frame.isEnabled(metadata, params.selector, params) };
|
||||
}
|
||||
|
||||
async isHidden(params: channels.FrameIsHiddenParams): Promise<channels.FrameIsHiddenResult> {
|
||||
return { value: await this._frame.isHidden(params.selector, params) };
|
||||
async isHidden(params: channels.FrameIsHiddenParams, metadata: CallMetadata): Promise<channels.FrameIsHiddenResult> {
|
||||
return { value: await this._frame.isHidden(metadata, params.selector, params) };
|
||||
}
|
||||
|
||||
async isVisible(params: channels.FrameIsVisibleParams): Promise<channels.FrameIsVisibleResult> {
|
||||
return { value: await this._frame.isVisible(params.selector, params) };
|
||||
async isVisible(params: channels.FrameIsVisibleParams, metadata: CallMetadata): Promise<channels.FrameIsVisibleResult> {
|
||||
return { value: await this._frame.isVisible(metadata, params.selector, params) };
|
||||
}
|
||||
|
||||
async hover(params: channels.FrameHoverParams, metadata?: channels.Metadata): Promise<void> {
|
||||
return runAction(async controller => {
|
||||
return await this._frame.hover(controller, params.selector, params);
|
||||
}, { ...metadata, type: 'hover', target: params.selector, page: this._frame._page });
|
||||
async hover(params: channels.FrameHoverParams, metadata: CallMetadata): Promise<void> {
|
||||
return await this._frame.hover(metadata, params.selector, params);
|
||||
}
|
||||
|
||||
async selectOption(params: channels.FrameSelectOptionParams, metadata?: channels.Metadata): Promise<channels.FrameSelectOptionResult> {
|
||||
return runAction(async controller => {
|
||||
const elements = (params.elements || []).map(e => (e as ElementHandleDispatcher)._elementHandle);
|
||||
return { values: await this._frame.selectOption(controller, params.selector, elements, params.options || [], params) };
|
||||
}, { ...metadata, type: 'selectOption', target: params.selector, page: this._frame._page });
|
||||
async selectOption(params: channels.FrameSelectOptionParams, metadata: CallMetadata): Promise<channels.FrameSelectOptionResult> {
|
||||
const elements = (params.elements || []).map(e => (e as ElementHandleDispatcher)._elementHandle);
|
||||
return { values: await this._frame.selectOption(metadata, params.selector, elements, params.options || [], params) };
|
||||
}
|
||||
|
||||
async setInputFiles(params: channels.FrameSetInputFilesParams, metadata?: channels.Metadata): Promise<void> {
|
||||
return runAction(async controller => {
|
||||
return await this._frame.setInputFiles(controller, params.selector, params.files, params);
|
||||
}, { ...metadata, type: 'setInputFiles', target: params.selector, page: this._frame._page });
|
||||
async setInputFiles(params: channels.FrameSetInputFilesParams, metadata: CallMetadata): Promise<void> {
|
||||
return await this._frame.setInputFiles(metadata, params.selector, params.files, params);
|
||||
}
|
||||
|
||||
async type(params: channels.FrameTypeParams, metadata?: channels.Metadata): Promise<void> {
|
||||
return runAction(async controller => {
|
||||
return await this._frame.type(controller, params.selector, params.text, params);
|
||||
}, { ...metadata, type: 'type', value: params.text, target: params.selector, page: this._frame._page });
|
||||
async type(params: channels.FrameTypeParams, metadata: CallMetadata): Promise<void> {
|
||||
return await this._frame.type(metadata, params.selector, params.text, params);
|
||||
}
|
||||
|
||||
async press(params: channels.FramePressParams, metadata?: channels.Metadata): Promise<void> {
|
||||
return runAction(async controller => {
|
||||
return await this._frame.press(controller, params.selector, params.key, params);
|
||||
}, { ...metadata, type: 'press', value: params.key, target: params.selector, page: this._frame._page });
|
||||
async press(params: channels.FramePressParams, metadata: CallMetadata): Promise<void> {
|
||||
return await this._frame.press(metadata, params.selector, params.key, params);
|
||||
}
|
||||
|
||||
async check(params: channels.FrameCheckParams, metadata?: channels.Metadata): Promise<void> {
|
||||
return runAction(async controller => {
|
||||
return await this._frame.check(controller, params.selector, params);
|
||||
}, { ...metadata, type: 'check', target: params.selector, page: this._frame._page });
|
||||
async check(params: channels.FrameCheckParams, metadata: CallMetadata): Promise<void> {
|
||||
return await this._frame.check(metadata, params.selector, params);
|
||||
}
|
||||
|
||||
async uncheck(params: channels.FrameUncheckParams, metadata?: channels.Metadata): Promise<void> {
|
||||
return runAction(async controller => {
|
||||
return await this._frame.uncheck(controller, params.selector, params);
|
||||
}, { ...metadata, type: 'uncheck', target: params.selector, page: this._frame._page });
|
||||
async uncheck(params: channels.FrameUncheckParams, metadata: CallMetadata): Promise<void> {
|
||||
return await this._frame.uncheck(metadata, params.selector, params);
|
||||
}
|
||||
|
||||
async waitForFunction(params: channels.FrameWaitForFunctionParams): Promise<channels.FrameWaitForFunctionResult> {
|
||||
return { handle: createHandle(this._scope, await this._frame._waitForFunctionExpression(params.expression, params.isFunction, parseArgument(params.arg), params)) };
|
||||
async waitForFunction(params: channels.FrameWaitForFunctionParams, metadata: CallMetadata): Promise<channels.FrameWaitForFunctionResult> {
|
||||
return { handle: createHandle(this._scope, await this._frame._waitForFunctionExpression(metadata, params.expression, params.isFunction, parseArgument(params.arg), params)) };
|
||||
}
|
||||
|
||||
async title(): Promise<channels.FrameTitleResult> {
|
||||
async title(params: channels.FrameTitleParams, metadata: CallMetadata): Promise<channels.FrameTitleResult> {
|
||||
return { value: await this._frame.title() };
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { BrowserContext, runAction, Video } from '../server/browserContext';
|
||||
import { BrowserContext, Video } from '../server/browserContext';
|
||||
import { Frame } from '../server/frames';
|
||||
import { Request } from '../server/network';
|
||||
import { Page, Worker } from '../server/page';
|
||||
@ -31,7 +31,7 @@ import { ElementHandleDispatcher, createHandle } from './elementHandlerDispatche
|
||||
import { FileChooser } from '../server/fileChooser';
|
||||
import { CRCoverage } from '../server/chromium/crCoverage';
|
||||
import { JSHandle } from '../server/javascript';
|
||||
import { SdkObject } from '../server/sdkObject';
|
||||
import { CallMetadata } from '../server/instrumentation';
|
||||
|
||||
export class PageDispatcher extends Dispatcher<Page, channels.PageInitializer> implements channels.PageChannel {
|
||||
private _page: Page;
|
||||
@ -80,19 +80,19 @@ export class PageDispatcher extends Dispatcher<Page, channels.PageInitializer> i
|
||||
page.on(Page.Events.Worker, worker => this._dispatchEvent('worker', { worker: new WorkerDispatcher(this._scope, worker) }));
|
||||
}
|
||||
|
||||
async setDefaultNavigationTimeoutNoReply(params: channels.PageSetDefaultNavigationTimeoutNoReplyParams): Promise<void> {
|
||||
async setDefaultNavigationTimeoutNoReply(params: channels.PageSetDefaultNavigationTimeoutNoReplyParams, metadata: CallMetadata): Promise<void> {
|
||||
this._page.setDefaultNavigationTimeout(params.timeout);
|
||||
}
|
||||
|
||||
async setDefaultTimeoutNoReply(params: channels.PageSetDefaultTimeoutNoReplyParams): Promise<void> {
|
||||
async setDefaultTimeoutNoReply(params: channels.PageSetDefaultTimeoutNoReplyParams, metadata: CallMetadata): Promise<void> {
|
||||
this._page.setDefaultTimeout(params.timeout);
|
||||
}
|
||||
|
||||
async opener(): Promise<channels.PageOpenerResult> {
|
||||
async opener(params: channels.PageOpenerParams, metadata: CallMetadata): Promise<channels.PageOpenerResult> {
|
||||
return { page: lookupNullableDispatcher<PageDispatcher>(await this._page.opener()) };
|
||||
}
|
||||
|
||||
async exposeBinding(params: channels.PageExposeBindingParams): Promise<void> {
|
||||
async exposeBinding(params: channels.PageExposeBindingParams, metadata: CallMetadata): Promise<void> {
|
||||
await this._page.exposeBinding(params.name, !!params.needsHandle, (source, ...args) => {
|
||||
const binding = new BindingCallDispatcher(this._scope, params.name, !!params.needsHandle, source, args);
|
||||
this._dispatchEvent('bindingCall', { binding });
|
||||
@ -100,44 +100,38 @@ export class PageDispatcher extends Dispatcher<Page, channels.PageInitializer> i
|
||||
});
|
||||
}
|
||||
|
||||
async setExtraHTTPHeaders(params: channels.PageSetExtraHTTPHeadersParams): Promise<void> {
|
||||
async setExtraHTTPHeaders(params: channels.PageSetExtraHTTPHeadersParams, metadata: CallMetadata): Promise<void> {
|
||||
await this._page.setExtraHTTPHeaders(params.headers);
|
||||
}
|
||||
|
||||
async reload(params: channels.PageReloadParams, metadata?: channels.Metadata): Promise<channels.PageReloadResult> {
|
||||
return await runAction(async controller => {
|
||||
return { response: lookupNullableDispatcher<ResponseDispatcher>(await this._page.reload(controller, params)) };
|
||||
}, { ...metadata, type: 'reload', page: this._page });
|
||||
async reload(params: channels.PageReloadParams, metadata: CallMetadata): Promise<channels.PageReloadResult> {
|
||||
return { response: lookupNullableDispatcher<ResponseDispatcher>(await this._page.reload(metadata, params)) };
|
||||
}
|
||||
|
||||
async goBack(params: channels.PageGoBackParams, metadata?: channels.Metadata): Promise<channels.PageGoBackResult> {
|
||||
return await runAction(async controller => {
|
||||
return { response: lookupNullableDispatcher<ResponseDispatcher>(await this._page.goBack(controller, params)) };
|
||||
}, { ...metadata, type: 'goBack', page: this._page });
|
||||
async goBack(params: channels.PageGoBackParams, metadata: CallMetadata): Promise<channels.PageGoBackResult> {
|
||||
return { response: lookupNullableDispatcher<ResponseDispatcher>(await this._page.goBack(metadata, params)) };
|
||||
}
|
||||
|
||||
async goForward(params: channels.PageGoForwardParams, metadata?: channels.Metadata): Promise<channels.PageGoForwardResult> {
|
||||
return await runAction(async controller => {
|
||||
return { response: lookupNullableDispatcher<ResponseDispatcher>(await this._page.goForward(controller, params)) };
|
||||
}, { ...metadata, type: 'goForward', page: this._page });
|
||||
async goForward(params: channels.PageGoForwardParams, metadata: CallMetadata): Promise<channels.PageGoForwardResult> {
|
||||
return { response: lookupNullableDispatcher<ResponseDispatcher>(await this._page.goForward(metadata, params)) };
|
||||
}
|
||||
|
||||
async emulateMedia(params: channels.PageEmulateMediaParams): Promise<void> {
|
||||
async emulateMedia(params: channels.PageEmulateMediaParams, metadata: CallMetadata): Promise<void> {
|
||||
await this._page.emulateMedia({
|
||||
media: params.media === 'null' ? null : params.media,
|
||||
colorScheme: params.colorScheme === 'null' ? null : params.colorScheme,
|
||||
});
|
||||
}
|
||||
|
||||
async setViewportSize(params: channels.PageSetViewportSizeParams): Promise<void> {
|
||||
async setViewportSize(params: channels.PageSetViewportSizeParams, metadata: CallMetadata): Promise<void> {
|
||||
await this._page.setViewportSize(params.viewportSize);
|
||||
}
|
||||
|
||||
async addInitScript(params: channels.PageAddInitScriptParams): Promise<void> {
|
||||
async addInitScript(params: channels.PageAddInitScriptParams, metadata: CallMetadata): Promise<void> {
|
||||
await this._page._addInitScriptExpression(params.source);
|
||||
}
|
||||
|
||||
async setNetworkInterceptionEnabled(params: channels.PageSetNetworkInterceptionEnabledParams): Promise<void> {
|
||||
async setNetworkInterceptionEnabled(params: channels.PageSetNetworkInterceptionEnabledParams, metadata: CallMetadata): Promise<void> {
|
||||
if (!params.enabled) {
|
||||
await this._page._setClientRequestInterceptor(undefined);
|
||||
return;
|
||||
@ -147,59 +141,59 @@ export class PageDispatcher extends Dispatcher<Page, channels.PageInitializer> i
|
||||
});
|
||||
}
|
||||
|
||||
async screenshot(params: channels.PageScreenshotParams): Promise<channels.PageScreenshotResult> {
|
||||
return { binary: (await this._page.screenshot(params)).toString('base64') };
|
||||
async screenshot(params: channels.PageScreenshotParams, metadata: CallMetadata): Promise<channels.PageScreenshotResult> {
|
||||
return { binary: (await this._page.screenshot(metadata, params)).toString('base64') };
|
||||
}
|
||||
|
||||
async close(params: channels.PageCloseParams): Promise<void> {
|
||||
async close(params: channels.PageCloseParams, metadata: CallMetadata): Promise<void> {
|
||||
await this._page.close(params);
|
||||
}
|
||||
|
||||
async setFileChooserInterceptedNoReply(params: channels.PageSetFileChooserInterceptedNoReplyParams): Promise<void> {
|
||||
async setFileChooserInterceptedNoReply(params: channels.PageSetFileChooserInterceptedNoReplyParams, metadata: CallMetadata): Promise<void> {
|
||||
await this._page._setFileChooserIntercepted(params.intercepted);
|
||||
}
|
||||
|
||||
async keyboardDown(params: channels.PageKeyboardDownParams): Promise<void> {
|
||||
async keyboardDown(params: channels.PageKeyboardDownParams, metadata: CallMetadata): Promise<void> {
|
||||
await this._page.keyboard.down(params.key);
|
||||
}
|
||||
|
||||
async keyboardUp(params: channels.PageKeyboardUpParams): Promise<void> {
|
||||
async keyboardUp(params: channels.PageKeyboardUpParams, metadata: CallMetadata): Promise<void> {
|
||||
await this._page.keyboard.up(params.key);
|
||||
}
|
||||
|
||||
async keyboardInsertText(params: channels.PageKeyboardInsertTextParams): Promise<void> {
|
||||
async keyboardInsertText(params: channels.PageKeyboardInsertTextParams, metadata: CallMetadata): Promise<void> {
|
||||
await this._page.keyboard.insertText(params.text);
|
||||
}
|
||||
|
||||
async keyboardType(params: channels.PageKeyboardTypeParams): Promise<void> {
|
||||
async keyboardType(params: channels.PageKeyboardTypeParams, metadata: CallMetadata): Promise<void> {
|
||||
await this._page.keyboard.type(params.text, params);
|
||||
}
|
||||
|
||||
async keyboardPress(params: channels.PageKeyboardPressParams): Promise<void> {
|
||||
async keyboardPress(params: channels.PageKeyboardPressParams, metadata: CallMetadata): Promise<void> {
|
||||
await this._page.keyboard.press(params.key, params);
|
||||
}
|
||||
|
||||
async mouseMove(params: channels.PageMouseMoveParams): Promise<void> {
|
||||
async mouseMove(params: channels.PageMouseMoveParams, metadata: CallMetadata): Promise<void> {
|
||||
await this._page.mouse.move(params.x, params.y, params);
|
||||
}
|
||||
|
||||
async mouseDown(params: channels.PageMouseDownParams): Promise<void> {
|
||||
async mouseDown(params: channels.PageMouseDownParams, metadata: CallMetadata): Promise<void> {
|
||||
await this._page.mouse.down(params);
|
||||
}
|
||||
|
||||
async mouseUp(params: channels.PageMouseUpParams): Promise<void> {
|
||||
async mouseUp(params: channels.PageMouseUpParams, metadata: CallMetadata): Promise<void> {
|
||||
await this._page.mouse.up(params);
|
||||
}
|
||||
|
||||
async mouseClick(params: channels.PageMouseClickParams): Promise<void> {
|
||||
async mouseClick(params: channels.PageMouseClickParams, metadata: CallMetadata): Promise<void> {
|
||||
await this._page.mouse.click(params.x, params.y, params);
|
||||
}
|
||||
|
||||
async touchscreenTap(params: channels.PageTouchscreenTapParams): Promise<void> {
|
||||
async touchscreenTap(params: channels.PageTouchscreenTapParams, metadata: CallMetadata): Promise<void> {
|
||||
await this._page.touchscreen.tap(params.x, params.y);
|
||||
}
|
||||
|
||||
async accessibilitySnapshot(params: channels.PageAccessibilitySnapshotParams): Promise<channels.PageAccessibilitySnapshotResult> {
|
||||
async accessibilitySnapshot(params: channels.PageAccessibilitySnapshotParams, metadata: CallMetadata): Promise<channels.PageAccessibilitySnapshotResult> {
|
||||
const rootAXNode = await this._page.accessibility.snapshot({
|
||||
interestingOnly: params.interestingOnly,
|
||||
root: params.root ? (params.root as ElementHandleDispatcher)._elementHandle : undefined
|
||||
@ -207,33 +201,33 @@ export class PageDispatcher extends Dispatcher<Page, channels.PageInitializer> i
|
||||
return { rootAXNode: rootAXNode || undefined };
|
||||
}
|
||||
|
||||
async pdf(params: channels.PagePdfParams): Promise<channels.PagePdfResult> {
|
||||
async pdf(params: channels.PagePdfParams, metadata: CallMetadata): Promise<channels.PagePdfResult> {
|
||||
if (!this._page.pdf)
|
||||
throw new Error('PDF generation is only supported for Headless Chromium');
|
||||
const buffer = await this._page.pdf(params);
|
||||
return { pdf: buffer.toString('base64') };
|
||||
}
|
||||
|
||||
async bringToFront(): Promise<void> {
|
||||
async bringToFront(params: channels.PageBringToFrontParams, metadata: CallMetadata): Promise<void> {
|
||||
await this._page.bringToFront();
|
||||
}
|
||||
|
||||
async crStartJSCoverage(params: channels.PageCrStartJSCoverageParams): Promise<void> {
|
||||
async crStartJSCoverage(params: channels.PageCrStartJSCoverageParams, metadata: CallMetadata): Promise<void> {
|
||||
const coverage = this._page.coverage as CRCoverage;
|
||||
await coverage.startJSCoverage(params);
|
||||
}
|
||||
|
||||
async crStopJSCoverage(): Promise<channels.PageCrStopJSCoverageResult> {
|
||||
async crStopJSCoverage(params: channels.PageCrStopJSCoverageParams, metadata: CallMetadata): Promise<channels.PageCrStopJSCoverageResult> {
|
||||
const coverage = this._page.coverage as CRCoverage;
|
||||
return { entries: await coverage.stopJSCoverage() };
|
||||
}
|
||||
|
||||
async crStartCSSCoverage(params: channels.PageCrStartCSSCoverageParams): Promise<void> {
|
||||
async crStartCSSCoverage(params: channels.PageCrStartCSSCoverageParams, metadata: CallMetadata): Promise<void> {
|
||||
const coverage = this._page.coverage as CRCoverage;
|
||||
await coverage.startCSSCoverage(params);
|
||||
}
|
||||
|
||||
async crStopCSSCoverage(): Promise<channels.PageCrStopCSSCoverageResult> {
|
||||
async crStopCSSCoverage(params: channels.PageCrStopCSSCoverageParams, metadata: CallMetadata): Promise<channels.PageCrStopCSSCoverageResult> {
|
||||
const coverage = this._page.coverage as CRCoverage;
|
||||
return { entries: await coverage.stopCSSCoverage() };
|
||||
}
|
||||
@ -256,22 +250,22 @@ export class WorkerDispatcher extends Dispatcher<Worker, channels.WorkerInitiali
|
||||
worker.on(Worker.Events.Close, () => this._dispatchEvent('close'));
|
||||
}
|
||||
|
||||
async evaluateExpression(params: channels.WorkerEvaluateExpressionParams): Promise<channels.WorkerEvaluateExpressionResult> {
|
||||
async evaluateExpression(params: channels.WorkerEvaluateExpressionParams, metadata: CallMetadata): Promise<channels.WorkerEvaluateExpressionResult> {
|
||||
return { value: serializeResult(await this._object._evaluateExpression(params.expression, params.isFunction, parseArgument(params.arg))) };
|
||||
}
|
||||
|
||||
async evaluateExpressionHandle(params: channels.WorkerEvaluateExpressionHandleParams): Promise<channels.WorkerEvaluateExpressionHandleResult> {
|
||||
async evaluateExpressionHandle(params: channels.WorkerEvaluateExpressionHandleParams, metadata: CallMetadata): Promise<channels.WorkerEvaluateExpressionHandleResult> {
|
||||
return { handle: createHandle(this._scope, await this._object._evaluateExpressionHandle(params.expression, params.isFunction, parseArgument(params.arg))) };
|
||||
}
|
||||
}
|
||||
|
||||
export class BindingCallDispatcher extends Dispatcher<SdkObject, channels.BindingCallInitializer> implements channels.BindingCallChannel {
|
||||
export class BindingCallDispatcher extends Dispatcher<{}, channels.BindingCallInitializer> implements channels.BindingCallChannel {
|
||||
private _resolve: ((arg: any) => void) | undefined;
|
||||
private _reject: ((error: any) => void) | undefined;
|
||||
private _promise: Promise<any>;
|
||||
|
||||
constructor(scope: DispatcherScope, name: string, needsHandle: boolean, source: { context: BrowserContext, page: Page, frame: Frame }, args: any[]) {
|
||||
super(scope, new SdkObject(null), 'BindingCall', {
|
||||
super(scope, {}, 'BindingCall', {
|
||||
frame: lookupDispatcher<FrameDispatcher>(source.frame),
|
||||
name,
|
||||
args: needsHandle ? undefined : args.map(serializeResult),
|
||||
@ -287,11 +281,11 @@ export class BindingCallDispatcher extends Dispatcher<SdkObject, channels.Bindin
|
||||
return this._promise;
|
||||
}
|
||||
|
||||
async resolve(params: channels.BindingCallResolveParams): Promise<void> {
|
||||
async resolve(params: channels.BindingCallResolveParams, metadata: CallMetadata): Promise<void> {
|
||||
this._resolve!(parseArgument(params.result));
|
||||
}
|
||||
|
||||
async reject(params: channels.BindingCallRejectParams): Promise<void> {
|
||||
async reject(params: channels.BindingCallRejectParams, metadata: CallMetadata): Promise<void> {
|
||||
this._reject!(parseError(params.error));
|
||||
}
|
||||
}
|
||||
|
@ -17,27 +17,18 @@
|
||||
import * as channels from '../protocol/channels';
|
||||
import { Dispatcher, DispatcherScope } from './dispatcher';
|
||||
import * as stream from 'stream';
|
||||
import { SdkObject } from '../server/sdkObject';
|
||||
|
||||
export class StreamWrapper extends SdkObject {
|
||||
readonly stream: stream.Readable;
|
||||
constructor(parentObject: SdkObject, stream: stream.Readable) {
|
||||
super(parentObject);
|
||||
this.stream = stream;
|
||||
}
|
||||
}
|
||||
|
||||
export class StreamDispatcher extends Dispatcher<StreamWrapper, channels.StreamInitializer> implements channels.StreamChannel {
|
||||
constructor(scope: DispatcherScope, stream: StreamWrapper) {
|
||||
export class StreamDispatcher extends Dispatcher<stream.Readable, channels.StreamInitializer> implements channels.StreamChannel {
|
||||
constructor(scope: DispatcherScope, stream: stream.Readable) {
|
||||
super(scope, stream, 'Stream', {});
|
||||
}
|
||||
|
||||
async read(params: channels.StreamReadParams): Promise<channels.StreamReadResult> {
|
||||
const buffer = this._object.stream.read(Math.min(this._object.stream.readableLength, params.size || this._object.stream.readableLength));
|
||||
const buffer = this._object.read(Math.min(this._object.readableLength, params.size || this._object.readableLength));
|
||||
return { binary: buffer ? buffer.toString('base64') : '' };
|
||||
}
|
||||
|
||||
async close() {
|
||||
this._object.stream.destroy();
|
||||
this._object.destroy();
|
||||
}
|
||||
}
|
||||
|
@ -32,12 +32,12 @@ import { RecentLogsCollector } from '../../utils/debugLogger';
|
||||
import { TimeoutSettings } from '../../utils/timeoutSettings';
|
||||
import { AndroidWebView } from '../../protocol/channels';
|
||||
import { CRPage } from '../chromium/crPage';
|
||||
import { SdkObject } from '../sdkObject';
|
||||
import { SdkObject, internalCallMetadata } from '../instrumentation';
|
||||
|
||||
const readFileAsync = util.promisify(fs.readFile);
|
||||
|
||||
export interface Backend {
|
||||
devices(owner: SdkObject): Promise<DeviceBackend[]>;
|
||||
devices(): Promise<DeviceBackend[]>;
|
||||
}
|
||||
|
||||
export interface DeviceBackend {
|
||||
@ -45,11 +45,11 @@ export interface DeviceBackend {
|
||||
status: string;
|
||||
close(): Promise<void>;
|
||||
init(): Promise<void>;
|
||||
runCommand(owner: SdkObject, command: string): Promise<Buffer>;
|
||||
open(owner: SdkObject, command: string): Promise<SocketBackend>;
|
||||
runCommand(command: string): Promise<Buffer>;
|
||||
open(command: string): Promise<SocketBackend>;
|
||||
}
|
||||
|
||||
export interface SocketBackend extends SdkObject {
|
||||
export interface SocketBackend extends EventEmitter {
|
||||
write(data: Buffer): Promise<void>;
|
||||
close(): Promise<void>;
|
||||
}
|
||||
@ -72,7 +72,7 @@ export class Android extends SdkObject {
|
||||
}
|
||||
|
||||
async devices(): Promise<AndroidDevice[]> {
|
||||
const devices = (await this._backend.devices(this)).filter(d => d.status === 'device');
|
||||
const devices = (await this._backend.devices()).filter(d => d.status === 'device');
|
||||
const newSerials = new Set<string>();
|
||||
for (const d of devices) {
|
||||
newSerials.add(d.serial);
|
||||
@ -125,7 +125,7 @@ export class AndroidDevice extends SdkObject {
|
||||
|
||||
static async create(android: Android, backend: DeviceBackend): Promise<AndroidDevice> {
|
||||
await backend.init();
|
||||
const model = await backend.runCommand(android, 'shell:getprop ro.product.model');
|
||||
const model = await backend.runCommand('shell:getprop ro.product.model');
|
||||
const device = new AndroidDevice(android, backend, model.toString().trim());
|
||||
await device._init();
|
||||
return device;
|
||||
@ -144,17 +144,17 @@ export class AndroidDevice extends SdkObject {
|
||||
}
|
||||
|
||||
async shell(command: string): Promise<Buffer> {
|
||||
const result = await this._backend.runCommand(this, `shell:${command}`);
|
||||
const result = await this._backend.runCommand(`shell:${command}`);
|
||||
await this._refreshWebViews();
|
||||
return result;
|
||||
}
|
||||
|
||||
async open(command: string): Promise<SocketBackend> {
|
||||
return await this._backend.open(this, `${command}`);
|
||||
return await this._backend.open(`${command}`);
|
||||
}
|
||||
|
||||
async screenshot(): Promise<Buffer> {
|
||||
return await this._backend.runCommand(this, `shell:screencap -p`);
|
||||
return await this._backend.runCommand(`shell:screencap -p`);
|
||||
}
|
||||
|
||||
private async _driver(): Promise<Transport> {
|
||||
@ -199,7 +199,7 @@ export class AndroidDevice extends SdkObject {
|
||||
debug('pw:android')(`Polling the socket localabstract:${socketName}`);
|
||||
while (!socket) {
|
||||
try {
|
||||
socket = await this._backend.open(this, `localabstract:${socketName}`);
|
||||
socket = await this._backend.open(`localabstract:${socketName}`);
|
||||
} catch (e) {
|
||||
await new Promise(f => setTimeout(f, 250));
|
||||
}
|
||||
@ -235,13 +235,13 @@ export class AndroidDevice extends SdkObject {
|
||||
|
||||
async launchBrowser(pkg: string = 'com.android.chrome', options: types.BrowserContextOptions = {}): Promise<BrowserContext> {
|
||||
debug('pw:android')('Force-stopping', pkg);
|
||||
await this._backend.runCommand(this, `shell:am force-stop ${pkg}`);
|
||||
await this._backend.runCommand(`shell:am force-stop ${pkg}`);
|
||||
|
||||
const socketName = 'playwright-' + createGuid();
|
||||
const commandLine = `_ --disable-fre --no-default-browser-check --no-first-run --remote-debugging-socket-name=${socketName}`;
|
||||
debug('pw:android')('Starting', pkg, commandLine);
|
||||
await this._backend.runCommand(this, `shell:echo "${commandLine}" > /data/local/tmp/chrome-command-line`);
|
||||
await this._backend.runCommand(this, `shell:am start -n ${pkg}/com.google.android.apps.chrome.Main about:blank`);
|
||||
await this._backend.runCommand(`shell:echo "${commandLine}" > /data/local/tmp/chrome-command-line`);
|
||||
await this._backend.runCommand(`shell:am start -n ${pkg}/com.google.android.apps.chrome.Main about:blank`);
|
||||
return await this._connectToBrowser(socketName, options);
|
||||
}
|
||||
|
||||
@ -273,7 +273,7 @@ export class AndroidDevice extends SdkObject {
|
||||
validateBrowserContextOptions(options, browserOptions);
|
||||
|
||||
const browser = await CRBrowser.connect(androidBrowser, browserOptions);
|
||||
const controller = new ProgressController();
|
||||
const controller = new ProgressController(internalCallMetadata(), this);
|
||||
const defaultContext = browser._defaultContext!;
|
||||
await controller.run(async progress => {
|
||||
await defaultContext._loadDefaultContextAsIs(progress);
|
||||
@ -296,7 +296,7 @@ export class AndroidDevice extends SdkObject {
|
||||
async installApk(content: Buffer, options?: { args?: string[] }): Promise<void> {
|
||||
const args = options && options.args ? options.args : ['-r', '-t', '-S'];
|
||||
debug('pw:android')('Opening install socket');
|
||||
const installSocket = await this._backend.open(this, `shell:cmd package install ${args.join(' ')} ${content.length}`);
|
||||
const installSocket = await this._backend.open(`shell:cmd package install ${args.join(' ')} ${content.length}`);
|
||||
debug('pw:android')('Writing driver bytes: ' + content.length);
|
||||
await installSocket.write(content);
|
||||
const success = await new Promise(f => installSocket.on('data', f));
|
||||
@ -305,7 +305,7 @@ export class AndroidDevice extends SdkObject {
|
||||
}
|
||||
|
||||
async push(content: Buffer, path: string, mode = 0o644): Promise<void> {
|
||||
const socket = await this._backend.open(this, `sync:`);
|
||||
const socket = await this._backend.open(`sync:`);
|
||||
const sendHeader = async (command: string, length: number) => {
|
||||
const buffer = Buffer.alloc(command.length + 4);
|
||||
buffer.write(command, 0);
|
||||
@ -329,7 +329,7 @@ export class AndroidDevice extends SdkObject {
|
||||
}
|
||||
|
||||
private async _refreshWebViews() {
|
||||
const sockets = (await this._backend.runCommand(this, `shell:cat /proc/net/unix | grep webview_devtools_remote`)).toString().split('\n');
|
||||
const sockets = (await this._backend.runCommand(`shell:cat /proc/net/unix | grep webview_devtools_remote`)).toString().split('\n');
|
||||
if (this._isClosed)
|
||||
return;
|
||||
|
||||
@ -345,7 +345,7 @@ export class AndroidDevice extends SdkObject {
|
||||
if (this._webViews.has(pid))
|
||||
continue;
|
||||
|
||||
const procs = (await this._backend.runCommand(this, `shell:ps -A | grep ${pid}`)).toString().split('\n');
|
||||
const procs = (await this._backend.runCommand(`shell:ps -A | grep ${pid}`)).toString().split('\n');
|
||||
if (this._isClosed)
|
||||
return;
|
||||
let pkg = '';
|
||||
|
@ -17,12 +17,12 @@
|
||||
import * as assert from 'assert';
|
||||
import * as debug from 'debug';
|
||||
import * as net from 'net';
|
||||
import { SdkObject } from '../sdkObject';
|
||||
import { EventEmitter } from 'events';
|
||||
import { Backend, DeviceBackend, SocketBackend } from './android';
|
||||
|
||||
export class AdbBackend implements Backend {
|
||||
async devices(sdkObject: SdkObject): Promise<DeviceBackend[]> {
|
||||
const result = await runCommand(sdkObject, 'host:devices');
|
||||
async devices(): Promise<DeviceBackend[]> {
|
||||
const result = await runCommand('host:devices');
|
||||
const lines = result.toString().trim().split('\n');
|
||||
return lines.map(line => {
|
||||
const [serial, status] = line.trim().split('\t');
|
||||
@ -46,20 +46,20 @@ class AdbDevice implements DeviceBackend {
|
||||
async close() {
|
||||
}
|
||||
|
||||
runCommand(sdkObject: SdkObject, command: string): Promise<Buffer> {
|
||||
return runCommand(sdkObject, command, this.serial);
|
||||
runCommand(command: string): Promise<Buffer> {
|
||||
return runCommand(command, this.serial);
|
||||
}
|
||||
|
||||
async open(sdkObject: SdkObject, command: string): Promise<SocketBackend> {
|
||||
const result = await open(sdkObject, command, this.serial);
|
||||
async open(command: string): Promise<SocketBackend> {
|
||||
const result = await open(command, this.serial);
|
||||
result.becomeSocket();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
async function runCommand(sdkObject: SdkObject, command: string, serial?: string): Promise<Buffer> {
|
||||
async function runCommand(command: string, serial?: string): Promise<Buffer> {
|
||||
debug('pw:adb:runCommand')(command, serial);
|
||||
const socket = new BufferedSocketWrapper(sdkObject, command, net.createConnection({ port: 5037 }));
|
||||
const socket = new BufferedSocketWrapper(command, net.createConnection({ port: 5037 }));
|
||||
if (serial) {
|
||||
await socket.write(encodeMessage(`host:transport:${serial}`));
|
||||
const status = await socket.read(4);
|
||||
@ -79,8 +79,8 @@ async function runCommand(sdkObject: SdkObject, command: string, serial?: string
|
||||
return commandOutput;
|
||||
}
|
||||
|
||||
async function open(sdkObject: SdkObject, command: string, serial?: string): Promise<BufferedSocketWrapper> {
|
||||
const socket = new BufferedSocketWrapper(sdkObject, command, net.createConnection({ port: 5037 }));
|
||||
async function open(command: string, serial?: string): Promise<BufferedSocketWrapper> {
|
||||
const socket = new BufferedSocketWrapper(command, net.createConnection({ port: 5037 }));
|
||||
if (serial) {
|
||||
await socket.write(encodeMessage(`host:transport:${serial}`));
|
||||
const status = await socket.read(4);
|
||||
@ -98,7 +98,7 @@ function encodeMessage(message: string): Buffer {
|
||||
return Buffer.from(lenHex + message);
|
||||
}
|
||||
|
||||
class BufferedSocketWrapper extends SdkObject implements SocketBackend {
|
||||
class BufferedSocketWrapper extends EventEmitter implements SocketBackend {
|
||||
private _socket: net.Socket;
|
||||
private _buffer = Buffer.from([]);
|
||||
private _isSocket = false;
|
||||
@ -107,8 +107,8 @@ class BufferedSocketWrapper extends SdkObject implements SocketBackend {
|
||||
private _isClosed = false;
|
||||
private _command: string;
|
||||
|
||||
constructor(parent: SdkObject, command: string, socket: net.Socket) {
|
||||
super(parent);
|
||||
constructor(command: string, socket: net.Socket) {
|
||||
super();
|
||||
this._command = command;
|
||||
this._socket = socket;
|
||||
this._connectPromise = new Promise(f => this._socket.on('connect', f));
|
||||
|
@ -15,15 +15,14 @@
|
||||
*/
|
||||
|
||||
import * as types from './types';
|
||||
import { BrowserContext, ContextListener, Video } from './browserContext';
|
||||
import { BrowserContext, Video } from './browserContext';
|
||||
import { Page } from './page';
|
||||
import { Download } from './download';
|
||||
import { ProxySettings } from './types';
|
||||
import { ChildProcess } from 'child_process';
|
||||
import { RecentLogsCollector } from '../utils/debugLogger';
|
||||
import * as registry from '../utils/registry';
|
||||
import { SdkObject } from './sdkObject';
|
||||
import { Selectors } from './selectors';
|
||||
import { SdkObject } from './instrumentation';
|
||||
|
||||
export interface BrowserProcess {
|
||||
onclose: ((exitCode: number | null, signal: string | null) => void) | undefined;
|
||||
@ -33,12 +32,9 @@ export interface BrowserProcess {
|
||||
}
|
||||
|
||||
export type PlaywrightOptions = {
|
||||
contextListeners: ContextListener[],
|
||||
registry: registry.Registry,
|
||||
isInternal: boolean,
|
||||
rootSdkObject: SdkObject,
|
||||
// FIXME, this is suspicious
|
||||
selectors: Selectors
|
||||
};
|
||||
|
||||
export type BrowserOptions = PlaywrightOptions & {
|
||||
|
@ -18,17 +18,16 @@
|
||||
import { TimeoutSettings } from '../utils/timeoutSettings';
|
||||
import { mkdirIfNeeded } from '../utils/utils';
|
||||
import { Browser, BrowserOptions } from './browser';
|
||||
import * as dom from './dom';
|
||||
import { Download } from './download';
|
||||
import * as frames from './frames';
|
||||
import { helper } from './helper';
|
||||
import * as network from './network';
|
||||
import { Page, PageBinding, PageDelegate } from './page';
|
||||
import { Progress, ProgressController, ProgressResult } from './progress';
|
||||
import { Selectors } from './selectors';
|
||||
import { Progress } from './progress';
|
||||
import { Selectors, serverSelectors } from './selectors';
|
||||
import * as types from './types';
|
||||
import * as path from 'path';
|
||||
import { SdkObject } from './sdkObject';
|
||||
import { CallMetadata, SdkObject } from './instrumentation';
|
||||
|
||||
export class Video {
|
||||
readonly _videoId: string;
|
||||
@ -58,42 +57,6 @@ export class Video {
|
||||
}
|
||||
}
|
||||
|
||||
export type ActionMetadata = {
|
||||
type: 'click' | 'fill' | 'dblclick' | 'hover' | 'selectOption' | 'setInputFiles' | 'type' | 'press' | 'check' | 'uncheck' | 'goto' | 'setContent' | 'goBack' | 'goForward' | 'reload' | 'tap',
|
||||
page: Page,
|
||||
target?: dom.ElementHandle | string,
|
||||
value?: string,
|
||||
stack?: string,
|
||||
};
|
||||
|
||||
export interface ActionListener {
|
||||
onActionCheckpoint(name: string, metadata: ActionMetadata): Promise<void>;
|
||||
onAfterAction(result: ProgressResult, metadata: ActionMetadata): Promise<void>;
|
||||
}
|
||||
|
||||
export async function runAction<T>(task: (controller: ProgressController) => Promise<T>, metadata: ActionMetadata): Promise<T> {
|
||||
const controller = new ProgressController();
|
||||
controller.setListener({
|
||||
onProgressCheckpoint: async (name: string): Promise<void> => {
|
||||
for (const listener of metadata.page._browserContext._actionListeners)
|
||||
await listener.onActionCheckpoint(name, metadata);
|
||||
},
|
||||
|
||||
onProgressDone: async (result: ProgressResult): Promise<void> => {
|
||||
for (const listener of metadata.page._browserContext._actionListeners)
|
||||
await listener.onAfterAction(result, metadata);
|
||||
},
|
||||
});
|
||||
const result = await task(controller);
|
||||
return result;
|
||||
}
|
||||
|
||||
export interface ContextListener {
|
||||
onContextCreated(context: BrowserContext): Promise<void>;
|
||||
onContextWillDestroy(context: BrowserContext): Promise<void>;
|
||||
onContextDidDestroy(context: BrowserContext): Promise<void>;
|
||||
}
|
||||
|
||||
export abstract class BrowserContext extends SdkObject {
|
||||
static Events = {
|
||||
Close: 'close',
|
||||
@ -117,7 +80,6 @@ export abstract class BrowserContext extends SdkObject {
|
||||
readonly _browser: Browser;
|
||||
readonly _browserContextId: string | undefined;
|
||||
private _selectors?: Selectors;
|
||||
readonly _actionListeners = new Set<ActionListener>();
|
||||
private _origins = new Set<string>();
|
||||
terminalSize: { rows?: number, columns?: number } = {};
|
||||
|
||||
@ -136,12 +98,11 @@ export abstract class BrowserContext extends SdkObject {
|
||||
}
|
||||
|
||||
selectors(): Selectors {
|
||||
return this._selectors || this._browser.options.selectors;
|
||||
return this._selectors || serverSelectors;
|
||||
}
|
||||
|
||||
async _initialize() {
|
||||
for (const listener of this._browser.options.contextListeners)
|
||||
await listener.onContextCreated(this);
|
||||
await this.instrumentation.onContextCreated(this);
|
||||
}
|
||||
|
||||
async _ensureVideosPath() {
|
||||
@ -292,8 +253,7 @@ export abstract class BrowserContext extends SdkObject {
|
||||
this.emit(BrowserContext.Events.BeforeClose);
|
||||
this._closedStatus = 'closing';
|
||||
|
||||
for (const listener of this._browser.options.contextListeners)
|
||||
await listener.onContextWillDestroy(this);
|
||||
await this.instrumentation.onContextWillDestroy(this);
|
||||
|
||||
// Collect videos/downloads that we will await.
|
||||
const promises: Promise<any>[] = [];
|
||||
@ -321,8 +281,7 @@ export abstract class BrowserContext extends SdkObject {
|
||||
await this._browser.close();
|
||||
|
||||
// Bookkeeping.
|
||||
for (const listener of this._browser.options.contextListeners)
|
||||
await listener.onContextDidDestroy(this);
|
||||
await this.instrumentation.onContextWillDestroy(this);
|
||||
this._didCloseInternal();
|
||||
}
|
||||
await this._closePromise;
|
||||
@ -343,7 +302,7 @@ export abstract class BrowserContext extends SdkObject {
|
||||
this._origins.add(origin);
|
||||
}
|
||||
|
||||
async storageState(): Promise<types.StorageState> {
|
||||
async storageState(metadata: CallMetadata): Promise<types.StorageState> {
|
||||
const result: types.StorageState = {
|
||||
cookies: (await this.cookies()).filter(c => c.value !== ''),
|
||||
origins: []
|
||||
@ -357,7 +316,7 @@ export abstract class BrowserContext extends SdkObject {
|
||||
const originStorage: types.OriginStorage = { origin, localStorage: [] };
|
||||
result.origins.push(originStorage);
|
||||
const frame = page.mainFrame();
|
||||
await frame.goto(new ProgressController(), origin);
|
||||
await frame.goto(metadata, origin);
|
||||
const storage = await frame._evaluateExpression(`({
|
||||
localStorage: Object.keys(localStorage).map(name => ({ name, value: localStorage.getItem(name) })),
|
||||
})`, false, undefined, 'utility');
|
||||
@ -368,7 +327,7 @@ export abstract class BrowserContext extends SdkObject {
|
||||
return result;
|
||||
}
|
||||
|
||||
async setStorageState(state: types.SetStorageState) {
|
||||
async setStorageState(metadata: CallMetadata, state: types.SetStorageState) {
|
||||
if (state.cookies)
|
||||
await this.addCookies(state.cookies);
|
||||
if (state.origins && state.origins.length) {
|
||||
@ -378,7 +337,7 @@ export abstract class BrowserContext extends SdkObject {
|
||||
});
|
||||
for (const originState of state.origins) {
|
||||
const frame = page.mainFrame();
|
||||
await frame.goto(new ProgressController(), originState.origin);
|
||||
await frame.goto(metadata, originState.origin);
|
||||
await frame._evaluateExpression(`
|
||||
originState => {
|
||||
for (const { name, value } of (originState.localStorage || []))
|
||||
|
@ -31,7 +31,7 @@ import { validateHostRequirements } from './validateDependencies';
|
||||
import { isDebugMode } from '../utils/utils';
|
||||
import { helper } from './helper';
|
||||
import { RecentLogsCollector } from '../utils/debugLogger';
|
||||
import { SdkObject } from './sdkObject';
|
||||
import { CallMetadata, SdkObject } from './instrumentation';
|
||||
|
||||
const mkdirAsync = util.promisify(fs.mkdir);
|
||||
const mkdtempAsync = util.promisify(fs.mkdtemp);
|
||||
@ -59,9 +59,9 @@ export abstract class BrowserType extends SdkObject {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
async launch(options: types.LaunchOptions, protocolLogger?: types.ProtocolLogger): Promise<Browser> {
|
||||
async launch(metadata: CallMetadata, options: types.LaunchOptions, protocolLogger?: types.ProtocolLogger): Promise<Browser> {
|
||||
options = validateLaunchOptions(options);
|
||||
const controller = new ProgressController();
|
||||
const controller = new ProgressController(metadata, this);
|
||||
controller.setLogName('browser');
|
||||
const browser = await controller.run(progress => {
|
||||
return this._innerLaunchWithRetries(progress, options, undefined, helper.debugProtocolLogger(protocolLogger)).catch(e => { throw this._rewriteStartupError(e); });
|
||||
@ -69,10 +69,10 @@ export abstract class BrowserType extends SdkObject {
|
||||
return browser;
|
||||
}
|
||||
|
||||
async launchPersistentContext(userDataDir?: string, options: types.LaunchPersistentOptions = {}): Promise<BrowserContext> {
|
||||
async launchPersistentContext(metadata: CallMetadata, userDataDir?: string, options: types.LaunchPersistentOptions = {}): Promise<BrowserContext> {
|
||||
options = validateLaunchOptions(options);
|
||||
const controller = new ProgressController(metadata, this);
|
||||
const persistent: types.BrowserContextOptions = options;
|
||||
const controller = new ProgressController();
|
||||
controller.setLogName('browser');
|
||||
const browser = await controller.run(progress => {
|
||||
return this._innerLaunchWithRetries(progress, options, persistent, helper.debugProtocolLogger(), userDataDir).catch(e => { throw this._rewriteStartupError(e); });
|
||||
|
@ -23,7 +23,6 @@ import { rewriteErrorMessage } from '../../utils/stackTrace';
|
||||
import { debugLogger, RecentLogsCollector } from '../../utils/debugLogger';
|
||||
import { ProtocolLogger } from '../types';
|
||||
import { helper } from '../helper';
|
||||
import { SdkObject } from '../sdkObject';
|
||||
|
||||
export const ConnectionEvents = {
|
||||
Disconnected: Symbol('ConnectionEvents.Disconnected')
|
||||
@ -124,7 +123,7 @@ export const CRSessionEvents = {
|
||||
Disconnected: Symbol('Events.CDPSession.Disconnected')
|
||||
};
|
||||
|
||||
export class CRSession extends SdkObject {
|
||||
export class CRSession extends EventEmitter {
|
||||
_connection: CRConnection | null;
|
||||
_eventListener?: (method: string, params?: Object) => void;
|
||||
private readonly _callbacks = new Map<number, {resolve: (o: any) => void, reject: (e: Error) => void, error: Error, method: string}>();
|
||||
@ -140,7 +139,8 @@ export class CRSession extends SdkObject {
|
||||
once: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
|
||||
|
||||
constructor(connection: CRConnection, rootSessionId: string, targetType: string, sessionId: string) {
|
||||
super(null);
|
||||
super();
|
||||
this.setMaxListeners(0);
|
||||
this._connection = connection;
|
||||
this._rootSessionId = rootSessionId;
|
||||
this._targetType = targetType;
|
||||
|
@ -759,7 +759,7 @@ class FrameSession {
|
||||
lineNumber: lineNumber || 0,
|
||||
columnNumber: 0,
|
||||
};
|
||||
this._page.emit(Page.Events.Console, new ConsoleMessage(this._page, level, text, [], location));
|
||||
this._page.emit(Page.Events.Console, new ConsoleMessage(level, text, [], location));
|
||||
}
|
||||
}
|
||||
|
||||
@ -814,7 +814,7 @@ class FrameSession {
|
||||
const ffmpegPath = this._crPage._browserContext._browser.options.registry.executablePath('ffmpeg');
|
||||
if (!ffmpegPath)
|
||||
throw new Error('ffmpeg executable was not found');
|
||||
this._videoRecorder = await VideoRecorder.launch(ffmpegPath, options);
|
||||
this._videoRecorder = await VideoRecorder.launch(this._crPage._page, ffmpegPath, options);
|
||||
this._screencastId = screencastId;
|
||||
const gotFirstFrame = new Promise(f => this._client.once('Page.screencastFrame', f));
|
||||
await this._client.send('Page.startScreencast', {
|
||||
|
@ -16,8 +16,10 @@
|
||||
|
||||
import { ChildProcess } from 'child_process';
|
||||
import { assert, monotonicTime } from '../../utils/utils';
|
||||
import { Page } from '../page';
|
||||
import { launchProcess } from '../processLauncher';
|
||||
import { Progress, ProgressController } from '../progress';
|
||||
import { internalCallMetadata } from '../instrumentation';
|
||||
import * as types from '../types';
|
||||
|
||||
const fps = 25;
|
||||
@ -34,11 +36,11 @@ export class VideoRecorder {
|
||||
private _isStopped = false;
|
||||
private _ffmpegPath: string;
|
||||
|
||||
static async launch(ffmpegPath: string, options: types.PageScreencastOptions): Promise<VideoRecorder> {
|
||||
static async launch(page: Page, ffmpegPath: string, options: types.PageScreencastOptions): Promise<VideoRecorder> {
|
||||
if (!options.outputFile.endsWith('.webm'))
|
||||
throw new Error('File must have .webm extension');
|
||||
|
||||
const controller = new ProgressController();
|
||||
const controller = new ProgressController(internalCallMetadata(), page);
|
||||
controller.setLogName('browser');
|
||||
return await controller.run(async progress => {
|
||||
const recorder = new VideoRecorder(ffmpegPath, progress);
|
||||
|
@ -15,17 +15,15 @@
|
||||
*/
|
||||
|
||||
import * as js from './javascript';
|
||||
import { SdkObject } from './sdkObject';
|
||||
import { ConsoleMessageLocation } from './types';
|
||||
|
||||
export class ConsoleMessage extends SdkObject {
|
||||
export class ConsoleMessage {
|
||||
private _type: string;
|
||||
private _text?: string;
|
||||
private _args: js.JSHandle[];
|
||||
private _location: ConsoleMessageLocation;
|
||||
|
||||
constructor(parent: SdkObject, type: string, text: string | undefined, args: js.JSHandle[], location?: ConsoleMessageLocation) {
|
||||
super(parent);
|
||||
constructor(type: string, text: string | undefined, args: js.JSHandle[], location?: ConsoleMessageLocation) {
|
||||
this._type = type;
|
||||
this._text = text;
|
||||
this._args = args;
|
||||
|
@ -17,7 +17,7 @@
|
||||
|
||||
import { assert } from '../utils/utils';
|
||||
import { Page } from './page';
|
||||
import { SdkObject } from './sdkObject';
|
||||
import { SdkObject } from './instrumentation';
|
||||
|
||||
type OnHandle = (accept: boolean, promptText?: string) => Promise<void>;
|
||||
|
||||
|
@ -22,8 +22,9 @@ import * as js from './javascript';
|
||||
import { Page } from './page';
|
||||
import { SelectorInfo } from './selectors';
|
||||
import * as types from './types';
|
||||
import { Progress, ProgressController, runAbortableTask } from './progress';
|
||||
import { Progress, ProgressController } from './progress';
|
||||
import { FatalDOMError, RetargetableDOMError } from './common/domErrors';
|
||||
import { CallMetadata } from './instrumentation';
|
||||
|
||||
export class FrameExecutionContext extends js.ExecutionContext {
|
||||
readonly frame: frames.Frame;
|
||||
@ -214,8 +215,9 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
}
|
||||
}
|
||||
|
||||
async scrollIntoViewIfNeeded(options: types.TimeoutOptions = {}) {
|
||||
return runAbortableTask(
|
||||
async scrollIntoViewIfNeeded(metadata: CallMetadata, options: types.TimeoutOptions = {}) {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(
|
||||
progress => this._waitAndScrollIntoViewIfNeeded(progress),
|
||||
this._page._timeoutSettings.timeout(options));
|
||||
}
|
||||
@ -392,7 +394,8 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
return 'done';
|
||||
}
|
||||
|
||||
async hover(controller: ProgressController, options: types.PointerActionOptions & types.PointerActionWaitOptions): Promise<void> {
|
||||
async hover(metadata: CallMetadata, options: types.PointerActionOptions & types.PointerActionWaitOptions): Promise<void> {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(async progress => {
|
||||
const result = await this._hover(progress, options);
|
||||
return assertDone(throwRetargetableDOMError(result));
|
||||
@ -403,7 +406,8 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
return this._retryPointerAction(progress, 'hover', false /* waitForEnabled */, point => this._page.mouse.move(point.x, point.y), options);
|
||||
}
|
||||
|
||||
async click(controller: ProgressController, options: types.MouseClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}): Promise<void> {
|
||||
async click(metadata: CallMetadata, options: types.MouseClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}): Promise<void> {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(async progress => {
|
||||
const result = await this._click(progress, options);
|
||||
return assertDone(throwRetargetableDOMError(result));
|
||||
@ -414,7 +418,8 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
return this._retryPointerAction(progress, 'click', true /* waitForEnabled */, point => this._page.mouse.click(point.x, point.y, options), options);
|
||||
}
|
||||
|
||||
async dblclick(controller: ProgressController, options: types.MouseMultiClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<void> {
|
||||
async dblclick(metadata: CallMetadata, options: types.MouseMultiClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<void> {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(async progress => {
|
||||
const result = await this._dblclick(progress, options);
|
||||
return assertDone(throwRetargetableDOMError(result));
|
||||
@ -425,7 +430,8 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
return this._retryPointerAction(progress, 'dblclick', true /* waitForEnabled */, point => this._page.mouse.dblclick(point.x, point.y, options), options);
|
||||
}
|
||||
|
||||
async tap(controller: ProgressController, options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}): Promise<void> {
|
||||
async tap(metadata: CallMetadata, options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}): Promise<void> {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(async progress => {
|
||||
const result = await this._tap(progress, options);
|
||||
return assertDone(throwRetargetableDOMError(result));
|
||||
@ -436,7 +442,8 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
return this._retryPointerAction(progress, 'tap', true /* waitForEnabled */, point => this._page.touchscreen.tap(point.x, point.y), options);
|
||||
}
|
||||
|
||||
async selectOption(controller: ProgressController, elements: ElementHandle[], values: types.SelectOption[], options: types.NavigatingActionWaitOptions): Promise<string[]> {
|
||||
async selectOption(metadata: CallMetadata, elements: ElementHandle[], values: types.SelectOption[], options: types.NavigatingActionWaitOptions): Promise<string[]> {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(async progress => {
|
||||
const result = await this._selectOption(progress, elements, values, options);
|
||||
return throwRetargetableDOMError(result);
|
||||
@ -457,7 +464,8 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
});
|
||||
}
|
||||
|
||||
async fill(controller: ProgressController, value: string, options: types.NavigatingActionWaitOptions = {}): Promise<void> {
|
||||
async fill(metadata: CallMetadata, value: string, options: types.NavigatingActionWaitOptions = {}): Promise<void> {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(async progress => {
|
||||
const result = await this._fill(progress, value, options);
|
||||
assertDone(throwRetargetableDOMError(result));
|
||||
@ -491,8 +499,9 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
}, 'input');
|
||||
}
|
||||
|
||||
async selectText(options: types.TimeoutOptions = {}): Promise<void> {
|
||||
return runAbortableTask(async progress => {
|
||||
async selectText(metadata: CallMetadata, options: types.TimeoutOptions = {}): Promise<void> {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(async progress => {
|
||||
progress.throwIfAborted(); // Avoid action that has side-effects.
|
||||
const poll = await this._evaluateHandleInUtility(([injected, node]) => {
|
||||
return injected.waitForVisibleAndSelectText(node);
|
||||
@ -503,7 +512,8 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
}, this._page._timeoutSettings.timeout(options));
|
||||
}
|
||||
|
||||
async setInputFiles(controller: ProgressController, files: types.FilePayload[], options: types.NavigatingActionWaitOptions) {
|
||||
async setInputFiles(metadata: CallMetadata, files: types.FilePayload[], options: types.NavigatingActionWaitOptions) {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(async progress => {
|
||||
const result = await this._setInputFiles(progress, files, options);
|
||||
return assertDone(throwRetargetableDOMError(result));
|
||||
@ -531,8 +541,9 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
return 'done';
|
||||
}
|
||||
|
||||
async focus(): Promise<void> {
|
||||
await runAbortableTask(async progress => {
|
||||
async focus(metadata: CallMetadata): Promise<void> {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
await controller.run(async progress => {
|
||||
const result = await this._focus(progress);
|
||||
await this._page._doSlowMo();
|
||||
return assertDone(throwRetargetableDOMError(result));
|
||||
@ -545,7 +556,8 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
return throwFatalDOMError(result);
|
||||
}
|
||||
|
||||
async type(controller: ProgressController, text: string, options: { delay?: number } & types.NavigatingActionWaitOptions): Promise<void> {
|
||||
async type(metadata: CallMetadata, text: string, options: { delay?: number } & types.NavigatingActionWaitOptions): Promise<void> {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(async progress => {
|
||||
const result = await this._type(progress, text, options);
|
||||
return assertDone(throwRetargetableDOMError(result));
|
||||
@ -565,7 +577,8 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
}, 'input');
|
||||
}
|
||||
|
||||
async press(controller: ProgressController, key: string, options: { delay?: number } & types.NavigatingActionWaitOptions): Promise<void> {
|
||||
async press(metadata: CallMetadata, key: string, options: { delay?: number } & types.NavigatingActionWaitOptions): Promise<void> {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(async progress => {
|
||||
const result = await this._press(progress, key, options);
|
||||
return assertDone(throwRetargetableDOMError(result));
|
||||
@ -585,14 +598,16 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
}, 'input');
|
||||
}
|
||||
|
||||
async check(controller: ProgressController, options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions) {
|
||||
async check(metadata: CallMetadata, options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions) {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(async progress => {
|
||||
const result = await this._setChecked(progress, true, options);
|
||||
return assertDone(throwRetargetableDOMError(result));
|
||||
}, this._page._timeoutSettings.timeout(options));
|
||||
}
|
||||
|
||||
async uncheck(controller: ProgressController, options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions) {
|
||||
async uncheck(metadata: CallMetadata, options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions) {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(async progress => {
|
||||
const result = await this._setChecked(progress, false, options);
|
||||
return assertDone(throwRetargetableDOMError(result));
|
||||
@ -614,8 +629,9 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
return this._page._delegate.getBoundingBox(this);
|
||||
}
|
||||
|
||||
async screenshot(options: types.ElementScreenshotOptions = {}): Promise<Buffer> {
|
||||
return runAbortableTask(
|
||||
async screenshot(metadata: CallMetadata, options: types.ElementScreenshotOptions = {}): Promise<Buffer> {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(
|
||||
progress => this._page._screenshotter.screenshotElement(progress, this, options),
|
||||
this._page._timeoutSettings.timeout(options));
|
||||
}
|
||||
@ -679,8 +695,9 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
}, {});
|
||||
}
|
||||
|
||||
async waitForElementState(state: 'visible' | 'hidden' | 'stable' | 'enabled' | 'disabled' | 'editable', options: types.TimeoutOptions = {}): Promise<void> {
|
||||
return runAbortableTask(async progress => {
|
||||
async waitForElementState(metadata: CallMetadata, state: 'visible' | 'hidden' | 'stable' | 'enabled' | 'disabled' | 'editable', options: types.TimeoutOptions = {}): Promise<void> {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(async progress => {
|
||||
progress.log(` waiting for element to be ${state}`);
|
||||
if (state === 'visible') {
|
||||
const poll = await this._evaluateHandleInUtility(([injected, node]) => {
|
||||
@ -735,13 +752,14 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
}, this._page._timeoutSettings.timeout(options));
|
||||
}
|
||||
|
||||
async waitForSelector(selector: string, options: types.WaitForElementOptions = {}): Promise<ElementHandle<Element> | null> {
|
||||
async waitForSelector(metadata: CallMetadata, selector: string, options: types.WaitForElementOptions = {}): Promise<ElementHandle<Element> | null> {
|
||||
const { state = 'visible' } = options;
|
||||
if (!['attached', 'detached', 'visible', 'hidden'].includes(state))
|
||||
throw new Error(`state: expected one of (attached|detached|visible|hidden)`);
|
||||
const info = this._page.selectors._parseSelector(selector);
|
||||
const task = waitForSelectorTask(info, state, this);
|
||||
return runAbortableTask(async progress => {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(async progress => {
|
||||
progress.log(`waiting for selector "${selector}"${state === 'attached' ? '' : ' to be ' + state}`);
|
||||
const context = await this._context.frame._context(info.world);
|
||||
const injected = await context.injectedScript();
|
||||
|
@ -19,11 +19,10 @@ import * as fs from 'fs';
|
||||
import * as util from 'util';
|
||||
import { Page } from './page';
|
||||
import { assert } from '../utils/utils';
|
||||
import { SdkObject } from './sdkObject';
|
||||
|
||||
type SaveCallback = (localPath: string, error?: string) => Promise<void>;
|
||||
|
||||
export class Download extends SdkObject {
|
||||
export class Download {
|
||||
private _downloadsPath: string;
|
||||
private _uuid: string;
|
||||
private _finishedCallback: () => void;
|
||||
@ -38,7 +37,6 @@ export class Download extends SdkObject {
|
||||
private _suggestedFilename: string | undefined;
|
||||
|
||||
constructor(page: Page, downloadsPath: string, uuid: string, url: string, suggestedFilename?: string) {
|
||||
super(page);
|
||||
this._page = page;
|
||||
this._downloadsPath = downloadsPath;
|
||||
this._uuid = uuid;
|
||||
|
@ -26,13 +26,13 @@ import * as types from '../types';
|
||||
import { launchProcess, envArrayToObject } from '../processLauncher';
|
||||
import { BrowserContext } from '../browserContext';
|
||||
import type {BrowserWindow} from 'electron';
|
||||
import { Progress, ProgressController, runAbortableTask } from '../progress';
|
||||
import { Progress, ProgressController } from '../progress';
|
||||
import { helper } from '../helper';
|
||||
import { BrowserOptions, BrowserProcess, PlaywrightOptions } from '../browser';
|
||||
import * as childProcess from 'child_process';
|
||||
import * as readline from 'readline';
|
||||
import { RecentLogsCollector } from '../../utils/debugLogger';
|
||||
import { SdkObject } from '../sdkObject';
|
||||
import { internalCallMetadata, SdkObject } from '../instrumentation';
|
||||
|
||||
export type ElectronLaunchOptionsBase = {
|
||||
executablePath?: string,
|
||||
@ -89,7 +89,8 @@ export class ElectronApplication extends SdkObject {
|
||||
if (!handle)
|
||||
return;
|
||||
page.browserWindow = handle;
|
||||
await runAbortableTask(progress => page.mainFrame()._waitForLoadState(progress, 'domcontentloaded'), page._timeoutSettings.navigationTimeout({})).catch(e => {}); // can happen after detach
|
||||
const controller = new ProgressController(internalCallMetadata(), this);
|
||||
await controller.run(progress => page.mainFrame()._waitForLoadState(progress, 'domcontentloaded'), page._timeoutSettings.navigationTimeout({})).catch(e => {}); // can happen after detach
|
||||
this.emit(ElectronApplication.Events.Window, page);
|
||||
}
|
||||
|
||||
@ -105,7 +106,7 @@ export class ElectronApplication extends SdkObject {
|
||||
}
|
||||
|
||||
private async _waitForEvent(event: string, predicate?: Function): Promise<any> {
|
||||
const progressController = new ProgressController();
|
||||
const progressController = new ProgressController(internalCallMetadata(), this);
|
||||
if (event !== ElectronApplication.Events.Close)
|
||||
this._browserContext._closePromise.then(error => progressController.abort(error));
|
||||
return progressController.run(progress => helper.waitForEvent(progress, this, event, predicate).promise, this._timeoutSettings.timeout({}));
|
||||
@ -133,7 +134,7 @@ export class Electron extends SdkObject {
|
||||
const {
|
||||
args = [],
|
||||
} = options;
|
||||
const controller = new ProgressController();
|
||||
const controller = new ProgressController(internalCallMetadata(), this);
|
||||
controller.setLogName('browser');
|
||||
return controller.run(async progress => {
|
||||
let app: ElectronApplication | undefined = undefined;
|
||||
|
@ -23,10 +23,10 @@ import * as network from './network';
|
||||
import { Page } from './page';
|
||||
import * as types from './types';
|
||||
import { BrowserContext } from './browserContext';
|
||||
import { Progress, ProgressController, runAbortableTask } from './progress';
|
||||
import { Progress, ProgressController } from './progress';
|
||||
import { assert, makeWaitForNextTask } from '../utils/utils';
|
||||
import { debugLogger } from '../utils/debugLogger';
|
||||
import { SdkObject } from './sdkObject';
|
||||
import { CallMetadata, SdkObject } from './instrumentation';
|
||||
|
||||
type ContextData = {
|
||||
contextPromise: Promise<dom.FrameExecutionContext>;
|
||||
@ -480,14 +480,16 @@ export class Frame extends SdkObject {
|
||||
this._subtreeLifecycleEvents = events;
|
||||
}
|
||||
|
||||
setupNavigationProgressController(controller: ProgressController) {
|
||||
setupNavigationProgressController(metadata: CallMetadata): ProgressController {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
this._page._disconnectedPromise.then(() => controller.abort(new Error('Navigation failed because page was closed!')));
|
||||
this._page._crashedPromise.then(() => controller.abort(new Error('Navigation failed because page crashed!')));
|
||||
this._detachedPromise.then(() => controller.abort(new Error('Navigating frame was detached!')));
|
||||
return controller;
|
||||
}
|
||||
|
||||
async goto(controller: ProgressController, url: string, options: types.GotoOptions = {}): Promise<network.Response | null> {
|
||||
this.setupNavigationProgressController(controller);
|
||||
async goto(metadata: CallMetadata, url: string, options: types.GotoOptions = {}): Promise<network.Response | null> {
|
||||
const controller = this.setupNavigationProgressController(metadata);
|
||||
return controller.run(async progress => {
|
||||
const waitUntil = verifyLifecycle('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
|
||||
progress.log(`navigating to "${url}", waiting until "${waitUntil}"`);
|
||||
@ -599,7 +601,8 @@ export class Frame extends SdkObject {
|
||||
return this._page.selectors._query(this, selector);
|
||||
}
|
||||
|
||||
async waitForSelector(selector: string, options: types.WaitForElementOptions = {}): Promise<dom.ElementHandle<Element> | null> {
|
||||
async waitForSelector(metadata: CallMetadata, selector: string, options: types.WaitForElementOptions = {}): Promise<dom.ElementHandle<Element> | null> {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
if ((options as any).visibility)
|
||||
throw new Error('options.visibility is not supported, did you mean options.state?');
|
||||
if ((options as any).waitFor && (options as any).waitFor !== 'visible')
|
||||
@ -609,7 +612,7 @@ export class Frame extends SdkObject {
|
||||
throw new Error(`state: expected one of (attached|detached|visible|hidden)`);
|
||||
const info = this._page.selectors._parseSelector(selector);
|
||||
const task = dom.waitForSelectorTask(info, state);
|
||||
return runAbortableTask(async progress => {
|
||||
return controller.run(async progress => {
|
||||
progress.log(`waiting for selector "${selector}"${state === 'attached' ? '' : ' to be ' + state}`);
|
||||
const result = await this._scheduleRerunnableHandleTask(progress, info.world, task);
|
||||
if (!result.asElement()) {
|
||||
@ -621,10 +624,11 @@ export class Frame extends SdkObject {
|
||||
}, this._page._timeoutSettings.timeout(options));
|
||||
}
|
||||
|
||||
async dispatchEvent(selector: string, type: string, eventInit?: Object, options: types.TimeoutOptions = {}): Promise<void> {
|
||||
async dispatchEvent(metadata: CallMetadata, selector: string, type: string, eventInit?: Object, options: types.TimeoutOptions = {}): Promise<void> {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
const info = this._page.selectors._parseSelector(selector);
|
||||
const task = dom.dispatchEventTask(info, type, eventInit || {});
|
||||
await runAbortableTask(async progress => {
|
||||
await controller.run(async progress => {
|
||||
progress.log(`Dispatching "${type}" event on selector "${selector}"...`);
|
||||
// Note: we always dispatch events in the main world.
|
||||
await this._scheduleRerunnableTask(progress, 'main', task);
|
||||
@ -664,8 +668,8 @@ export class Frame extends SdkObject {
|
||||
});
|
||||
}
|
||||
|
||||
async setContent(controller: ProgressController, html: string, options: types.NavigateOptions = {}): Promise<void> {
|
||||
this.setupNavigationProgressController(controller);
|
||||
async setContent(metadata: CallMetadata, html: string, options: types.NavigateOptions = {}): Promise<void> {
|
||||
const controller = this.setupNavigationProgressController(metadata);
|
||||
return controller.run(async progress => {
|
||||
const waitUntil = options.waitUntil === undefined ? 'load' : options.waitUntil;
|
||||
progress.log(`setting frame content, waiting until "${waitUntil}"`);
|
||||
@ -857,166 +861,188 @@ export class Frame extends SdkObject {
|
||||
}
|
||||
|
||||
private async _retryWithSelectorIfNotConnected<R>(
|
||||
controller: ProgressController,
|
||||
selector: string, options: types.TimeoutOptions,
|
||||
action: (progress: Progress, handle: dom.ElementHandle<Element>) => Promise<R | 'error:notconnected'>): Promise<R> {
|
||||
return runAbortableTask(async progress => {
|
||||
return controller.run(async progress => {
|
||||
return this._retryWithProgressIfNotConnected(progress, selector, handle => action(progress, handle));
|
||||
}, this._page._timeoutSettings.timeout(options));
|
||||
}
|
||||
|
||||
async click(controller: ProgressController, selector: string, options: types.MouseClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions) {
|
||||
async click(metadata: CallMetadata, selector: string, options: types.MouseClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions) {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(async progress => {
|
||||
return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, handle => handle._click(progress, options)));
|
||||
}, this._page._timeoutSettings.timeout(options));
|
||||
}
|
||||
|
||||
async dblclick(controller: ProgressController, selector: string, options: types.MouseMultiClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) {
|
||||
async dblclick(metadata: CallMetadata, selector: string, options: types.MouseMultiClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(async progress => {
|
||||
return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, handle => handle._dblclick(progress, options)));
|
||||
}, this._page._timeoutSettings.timeout(options));
|
||||
}
|
||||
|
||||
async tap(controller: ProgressController, selector: string, options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions) {
|
||||
async tap(metadata: CallMetadata, selector: string, options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions) {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(async progress => {
|
||||
return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, handle => handle._tap(progress, options)));
|
||||
}, this._page._timeoutSettings.timeout(options));
|
||||
}
|
||||
|
||||
async fill(controller: ProgressController, selector: string, value: string, options: types.NavigatingActionWaitOptions) {
|
||||
async fill(metadata: CallMetadata, selector: string, value: string, options: types.NavigatingActionWaitOptions) {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(async progress => {
|
||||
return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, handle => handle._fill(progress, value, options)));
|
||||
}, this._page._timeoutSettings.timeout(options));
|
||||
}
|
||||
|
||||
async focus(selector: string, options: types.TimeoutOptions = {}) {
|
||||
await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle._focus(progress));
|
||||
async focus(metadata: CallMetadata, selector: string, options: types.TimeoutOptions = {}) {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
await this._retryWithSelectorIfNotConnected(controller, selector, options, (progress, handle) => handle._focus(progress));
|
||||
await this._page._doSlowMo();
|
||||
}
|
||||
|
||||
async textContent(selector: string, options: types.TimeoutOptions = {}): Promise<string | null> {
|
||||
async textContent(metadata: CallMetadata, selector: string, options: types.TimeoutOptions = {}): Promise<string | null> {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
const info = this._page.selectors._parseSelector(selector);
|
||||
const task = dom.textContentTask(info);
|
||||
return runAbortableTask(async progress => {
|
||||
return controller.run(async progress => {
|
||||
progress.log(` retrieving textContent from "${selector}"`);
|
||||
return this._scheduleRerunnableTask(progress, info.world, task);
|
||||
}, this._page._timeoutSettings.timeout(options));
|
||||
}
|
||||
|
||||
async innerText(selector: string, options: types.TimeoutOptions = {}): Promise<string> {
|
||||
async innerText(metadata: CallMetadata, selector: string, options: types.TimeoutOptions = {}): Promise<string> {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
const info = this._page.selectors._parseSelector(selector);
|
||||
const task = dom.innerTextTask(info);
|
||||
return runAbortableTask(async progress => {
|
||||
return controller.run(async progress => {
|
||||
progress.log(` retrieving innerText from "${selector}"`);
|
||||
const result = dom.throwFatalDOMError(await this._scheduleRerunnableTask(progress, info.world, task));
|
||||
return result.innerText;
|
||||
}, this._page._timeoutSettings.timeout(options));
|
||||
}
|
||||
|
||||
async innerHTML(selector: string, options: types.TimeoutOptions = {}): Promise<string> {
|
||||
async innerHTML(metadata: CallMetadata, selector: string, options: types.TimeoutOptions = {}): Promise<string> {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
const info = this._page.selectors._parseSelector(selector);
|
||||
const task = dom.innerHTMLTask(info);
|
||||
return runAbortableTask(async progress => {
|
||||
return controller.run(async progress => {
|
||||
progress.log(` retrieving innerHTML from "${selector}"`);
|
||||
return this._scheduleRerunnableTask(progress, info.world, task);
|
||||
}, this._page._timeoutSettings.timeout(options));
|
||||
}
|
||||
|
||||
async getAttribute(selector: string, name: string, options: types.TimeoutOptions = {}): Promise<string | null> {
|
||||
async getAttribute(metadata: CallMetadata, selector: string, name: string, options: types.TimeoutOptions = {}): Promise<string | null> {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
const info = this._page.selectors._parseSelector(selector);
|
||||
const task = dom.getAttributeTask(info, name);
|
||||
return runAbortableTask(async progress => {
|
||||
return controller.run(async progress => {
|
||||
progress.log(` retrieving attribute "${name}" from "${selector}"`);
|
||||
return this._scheduleRerunnableTask(progress, info.world, task);
|
||||
}, this._page._timeoutSettings.timeout(options));
|
||||
}
|
||||
|
||||
async isVisible(selector: string, options: types.TimeoutOptions = {}): Promise<boolean> {
|
||||
async isVisible(metadata: CallMetadata, selector: string, options: types.TimeoutOptions = {}): Promise<boolean> {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
const info = this._page.selectors._parseSelector(selector);
|
||||
const task = dom.visibleTask(info);
|
||||
return runAbortableTask(async progress => {
|
||||
return controller.run(async progress => {
|
||||
progress.log(` checking visibility of "${selector}"`);
|
||||
return this._scheduleRerunnableTask(progress, info.world, task);
|
||||
}, this._page._timeoutSettings.timeout(options));
|
||||
}
|
||||
|
||||
async isHidden(selector: string, options: types.TimeoutOptions = {}): Promise<boolean> {
|
||||
return !(await this.isVisible(selector, options));
|
||||
async isHidden(metadata: CallMetadata, selector: string, options: types.TimeoutOptions = {}): Promise<boolean> {
|
||||
return !(await this.isVisible(metadata, selector, options));
|
||||
}
|
||||
|
||||
async isDisabled(selector: string, options: types.TimeoutOptions = {}): Promise<boolean> {
|
||||
async isDisabled(metadata: CallMetadata, selector: string, options: types.TimeoutOptions = {}): Promise<boolean> {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
const info = this._page.selectors._parseSelector(selector);
|
||||
const task = dom.disabledTask(info);
|
||||
return runAbortableTask(async progress => {
|
||||
return controller.run(async progress => {
|
||||
progress.log(` checking disabled state of "${selector}"`);
|
||||
return this._scheduleRerunnableTask(progress, info.world, task);
|
||||
}, this._page._timeoutSettings.timeout(options));
|
||||
}
|
||||
|
||||
async isEnabled(selector: string, options: types.TimeoutOptions = {}): Promise<boolean> {
|
||||
return !(await this.isDisabled(selector, options));
|
||||
async isEnabled(metadata: CallMetadata, selector: string, options: types.TimeoutOptions = {}): Promise<boolean> {
|
||||
return !(await this.isDisabled(metadata, selector, options));
|
||||
}
|
||||
|
||||
async isEditable(selector: string, options: types.TimeoutOptions = {}): Promise<boolean> {
|
||||
async isEditable(metadata: CallMetadata, selector: string, options: types.TimeoutOptions = {}): Promise<boolean> {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
const info = this._page.selectors._parseSelector(selector);
|
||||
const task = dom.editableTask(info);
|
||||
return runAbortableTask(async progress => {
|
||||
return controller.run(async progress => {
|
||||
progress.log(` checking editable state of "${selector}"`);
|
||||
return this._scheduleRerunnableTask(progress, info.world, task);
|
||||
}, this._page._timeoutSettings.timeout(options));
|
||||
}
|
||||
|
||||
async isChecked(selector: string, options: types.TimeoutOptions = {}): Promise<boolean> {
|
||||
async isChecked(metadata: CallMetadata, selector: string, options: types.TimeoutOptions = {}): Promise<boolean> {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
const info = this._page.selectors._parseSelector(selector);
|
||||
const task = dom.checkedTask(info);
|
||||
return runAbortableTask(async progress => {
|
||||
return controller.run(async progress => {
|
||||
progress.log(` checking checked state of "${selector}"`);
|
||||
return this._scheduleRerunnableTask(progress, info.world, task);
|
||||
}, this._page._timeoutSettings.timeout(options));
|
||||
}
|
||||
|
||||
async hover(controller: ProgressController, selector: string, options: types.PointerActionOptions & types.PointerActionWaitOptions = {}) {
|
||||
async hover(metadata: CallMetadata, selector: string, options: types.PointerActionOptions & types.PointerActionWaitOptions = {}) {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(async progress => {
|
||||
return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, handle => handle._hover(progress, options)));
|
||||
}, this._page._timeoutSettings.timeout(options));
|
||||
}
|
||||
|
||||
async selectOption(controller: ProgressController, selector: string, elements: dom.ElementHandle[], values: types.SelectOption[], options: types.NavigatingActionWaitOptions = {}): Promise<string[]> {
|
||||
async selectOption(metadata: CallMetadata, selector: string, elements: dom.ElementHandle[], values: types.SelectOption[], options: types.NavigatingActionWaitOptions = {}): Promise<string[]> {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(async progress => {
|
||||
return await this._retryWithProgressIfNotConnected(progress, selector, handle => handle._selectOption(progress, elements, values, options));
|
||||
}, this._page._timeoutSettings.timeout(options));
|
||||
}
|
||||
|
||||
async setInputFiles(controller: ProgressController, selector: string, files: types.FilePayload[], options: types.NavigatingActionWaitOptions = {}): Promise<void> {
|
||||
async setInputFiles(metadata: CallMetadata, selector: string, files: types.FilePayload[], options: types.NavigatingActionWaitOptions = {}): Promise<void> {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(async progress => {
|
||||
return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, handle => handle._setInputFiles(progress, files, options)));
|
||||
}, this._page._timeoutSettings.timeout(options));
|
||||
}
|
||||
|
||||
async type(controller: ProgressController, selector: string, text: string, options: { delay?: number } & types.NavigatingActionWaitOptions = {}) {
|
||||
async type(metadata: CallMetadata, selector: string, text: string, options: { delay?: number } & types.NavigatingActionWaitOptions = {}) {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(async progress => {
|
||||
return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, handle => handle._type(progress, text, options)));
|
||||
}, this._page._timeoutSettings.timeout(options));
|
||||
}
|
||||
|
||||
async press(controller: ProgressController, selector: string, key: string, options: { delay?: number } & types.NavigatingActionWaitOptions = {}) {
|
||||
async press(metadata: CallMetadata, selector: string, key: string, options: { delay?: number } & types.NavigatingActionWaitOptions = {}) {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(async progress => {
|
||||
return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, handle => handle._press(progress, key, options)));
|
||||
}, this._page._timeoutSettings.timeout(options));
|
||||
}
|
||||
|
||||
async check(controller: ProgressController, selector: string, options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) {
|
||||
async check(metadata: CallMetadata, selector: string, options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(async progress => {
|
||||
return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, handle => handle._setChecked(progress, true, options)));
|
||||
}, this._page._timeoutSettings.timeout(options));
|
||||
}
|
||||
|
||||
async uncheck(controller: ProgressController, selector: string, options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) {
|
||||
async uncheck(metadata: CallMetadata, selector: string, options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(async progress => {
|
||||
return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, handle => handle._setChecked(progress, false, options)));
|
||||
}, this._page._timeoutSettings.timeout(options));
|
||||
}
|
||||
|
||||
async _waitForFunctionExpression<R>(expression: string, isFunction: boolean | undefined, arg: any, options: types.WaitForFunctionOptions = {}): Promise<js.SmartHandle<R>> {
|
||||
async _waitForFunctionExpression<R>(metadata: CallMetadata, expression: string, isFunction: boolean | undefined, arg: any, options: types.WaitForFunctionOptions = {}): Promise<js.SmartHandle<R>> {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
if (typeof options.pollingInterval === 'number')
|
||||
assert(options.pollingInterval > 0, 'Cannot poll with non-positive interval: ' + options.pollingInterval);
|
||||
expression = js.normalizeEvaluationExpression(expression, isFunction);
|
||||
@ -1038,7 +1064,7 @@ export class Frame extends SdkObject {
|
||||
return injectedScript.pollRaf((progress, continuePolling) => predicate(arg) || continuePolling);
|
||||
return injectedScript.pollInterval(polling, (progress, continuePolling) => predicate(arg) || continuePolling);
|
||||
}, { expression, isFunction, polling: options.pollingInterval, arg });
|
||||
return runAbortableTask(
|
||||
return controller.run(
|
||||
progress => this._scheduleRerunnableHandleTask(progress, 'main', task),
|
||||
this._page._timeoutSettings.timeout(options));
|
||||
}
|
||||
|
114
src/server/instrumentation.ts
Normal file
114
src/server/instrumentation.ts
Normal file
@ -0,0 +1,114 @@
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
import type { Browser } from './browser';
|
||||
import type { BrowserContext } from './browserContext';
|
||||
import type { BrowserType } from './browserType';
|
||||
import type { Frame } from './frames';
|
||||
import type { Page } from './page';
|
||||
|
||||
export type Attribution = {
|
||||
browserType?: BrowserType;
|
||||
browser?: Browser;
|
||||
context?: BrowserContext;
|
||||
page?: Page;
|
||||
frame?: Frame;
|
||||
};
|
||||
|
||||
export type CallMetadata = {
|
||||
type: string;
|
||||
method: string;
|
||||
params: any;
|
||||
stack: string;
|
||||
};
|
||||
|
||||
export class SdkObject extends EventEmitter {
|
||||
attribution: Attribution;
|
||||
instrumentation: Instrumentation;
|
||||
|
||||
protected constructor(parent: SdkObject) {
|
||||
super();
|
||||
this.setMaxListeners(0);
|
||||
this.attribution = { ...parent.attribution };
|
||||
this.instrumentation = parent.instrumentation;
|
||||
}
|
||||
}
|
||||
|
||||
export type ActionResult = {
|
||||
logs: string[],
|
||||
startTime: number,
|
||||
endTime: number,
|
||||
error?: Error,
|
||||
};
|
||||
|
||||
export interface Instrumentation {
|
||||
onContextCreated(context: BrowserContext): Promise<void>;
|
||||
onContextWillDestroy(context: BrowserContext): Promise<void>;
|
||||
onContextDidDestroy(context: BrowserContext): Promise<void>;
|
||||
onActionCheckpoint(name: string, sdkObject: SdkObject, metadata: CallMetadata): Promise<void>;
|
||||
onAfterAction(result: ActionResult, sdkObject: SdkObject, metadata: CallMetadata): Promise<void>;
|
||||
}
|
||||
|
||||
export interface InstrumentationListener {
|
||||
onContextCreated?(context: BrowserContext): Promise<void>;
|
||||
onContextWillDestroy?(context: BrowserContext): Promise<void>;
|
||||
onContextDidDestroy?(context: BrowserContext): Promise<void>;
|
||||
onActionCheckpoint?(name: string, sdkObject: SdkObject, metadata: CallMetadata): Promise<void>;
|
||||
onAfterAction?(result: ActionResult, sdkObject: SdkObject, metadata: CallMetadata): Promise<void>;
|
||||
}
|
||||
|
||||
export class InstrumentationMultiplexer implements Instrumentation {
|
||||
private _listeners: InstrumentationListener[];
|
||||
|
||||
constructor(listeners: InstrumentationListener[]) {
|
||||
this._listeners = listeners;
|
||||
}
|
||||
|
||||
async onContextCreated(context: BrowserContext): Promise<void> {
|
||||
for (const listener of this._listeners)
|
||||
await listener.onContextCreated?.(context);
|
||||
}
|
||||
|
||||
async onContextWillDestroy(context: BrowserContext): Promise<void> {
|
||||
for (const listener of this._listeners)
|
||||
await listener.onContextWillDestroy?.(context);
|
||||
}
|
||||
|
||||
async onContextDidDestroy(context: BrowserContext): Promise<void> {
|
||||
for (const listener of this._listeners)
|
||||
await listener.onContextDidDestroy?.(context);
|
||||
}
|
||||
|
||||
async onActionCheckpoint(name: string, sdkObject: SdkObject, metadata: CallMetadata): Promise<void> {
|
||||
for (const listener of this._listeners)
|
||||
await listener.onActionCheckpoint?.(name, sdkObject, metadata);
|
||||
}
|
||||
|
||||
async onAfterAction(result: ActionResult, sdkObject: SdkObject, metadata: CallMetadata): Promise<void> {
|
||||
for (const listener of this._listeners)
|
||||
await listener.onAfterAction?.(result, sdkObject, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
export function internalCallMetadata(): CallMetadata {
|
||||
return {
|
||||
type: 'Internal',
|
||||
method: '',
|
||||
params: {},
|
||||
stack: ''
|
||||
};
|
||||
}
|
@ -18,7 +18,7 @@ import * as dom from './dom';
|
||||
import * as utilityScriptSource from '../generated/utilityScriptSource';
|
||||
import { serializeAsCallArgument } from './common/utilityScriptSerializers';
|
||||
import type UtilityScript from './injected/utilityScript';
|
||||
import { SdkObject } from './sdkObject';
|
||||
import { SdkObject } from './instrumentation';
|
||||
|
||||
type ObjectId = string;
|
||||
export type RemoteObject = {
|
||||
|
@ -17,7 +17,7 @@
|
||||
import * as frames from './frames';
|
||||
import * as types from './types';
|
||||
import { assert } from '../utils/utils';
|
||||
import { SdkObject } from './sdkObject';
|
||||
import { SdkObject } from './instrumentation';
|
||||
|
||||
export function filterCookies(cookies: types.NetworkCookie[], urls: string[]): types.NetworkCookie[] {
|
||||
const parsedURLs = urls.map(s => new URL(s));
|
||||
|
@ -27,11 +27,11 @@ import { BrowserContext, Video } from './browserContext';
|
||||
import { ConsoleMessage } from './console';
|
||||
import * as accessibility from './accessibility';
|
||||
import { FileChooser } from './fileChooser';
|
||||
import { ProgressController, runAbortableTask } from './progress';
|
||||
import { ProgressController } from './progress';
|
||||
import { assert, isError } from '../utils/utils';
|
||||
import { debugLogger } from '../utils/debugLogger';
|
||||
import { Selectors } from './selectors';
|
||||
import { SdkObject } from './sdkObject';
|
||||
import { CallMetadata, SdkObject } from './instrumentation';
|
||||
|
||||
export interface PageDelegate {
|
||||
readonly rawMouse: input.RawMouse;
|
||||
@ -288,7 +288,7 @@ export class Page extends SdkObject {
|
||||
}
|
||||
|
||||
_addConsoleMessage(type: string, args: js.JSHandle[], location: types.ConsoleMessageLocation, text?: string) {
|
||||
const message = new ConsoleMessage(this, type, text, args, location);
|
||||
const message = new ConsoleMessage(type, text, args, location);
|
||||
const intercepted = this._frameManager.interceptConsoleMessage(message);
|
||||
if (intercepted || !this.listenerCount(Page.Events.Console))
|
||||
args.forEach(arg => arg.dispose());
|
||||
@ -296,8 +296,8 @@ export class Page extends SdkObject {
|
||||
this.emit(Page.Events.Console, message);
|
||||
}
|
||||
|
||||
async reload(controller: ProgressController, options: types.NavigateOptions): Promise<network.Response | null> {
|
||||
this.mainFrame().setupNavigationProgressController(controller);
|
||||
async reload(metadata: CallMetadata, options: types.NavigateOptions): Promise<network.Response | null> {
|
||||
const controller = this.mainFrame().setupNavigationProgressController(metadata);
|
||||
const response = await controller.run(async progress => {
|
||||
// Note: waitForNavigation may fail before we get response to reload(),
|
||||
// so we should await it immediately.
|
||||
@ -311,8 +311,8 @@ export class Page extends SdkObject {
|
||||
return response;
|
||||
}
|
||||
|
||||
async goBack(controller: ProgressController, options: types.NavigateOptions): Promise<network.Response | null> {
|
||||
this.mainFrame().setupNavigationProgressController(controller);
|
||||
async goBack(metadata: CallMetadata, options: types.NavigateOptions): Promise<network.Response | null> {
|
||||
const controller = this.mainFrame().setupNavigationProgressController(metadata);
|
||||
const response = await controller.run(async progress => {
|
||||
// Note: waitForNavigation may fail before we get response to goBack,
|
||||
// so we should catch it immediately.
|
||||
@ -333,8 +333,8 @@ export class Page extends SdkObject {
|
||||
return response;
|
||||
}
|
||||
|
||||
async goForward(controller: ProgressController, options: types.NavigateOptions): Promise<network.Response | null> {
|
||||
this.mainFrame().setupNavigationProgressController(controller);
|
||||
async goForward(metadata: CallMetadata, options: types.NavigateOptions): Promise<network.Response | null> {
|
||||
const controller = this.mainFrame().setupNavigationProgressController(metadata);
|
||||
const response = await controller.run(async progress => {
|
||||
// Note: waitForNavigation may fail before we get response to goForward,
|
||||
// so we should catch it immediately.
|
||||
@ -421,8 +421,9 @@ export class Page extends SdkObject {
|
||||
route.continue();
|
||||
}
|
||||
|
||||
async screenshot(options: types.ScreenshotOptions = {}): Promise<Buffer> {
|
||||
return runAbortableTask(
|
||||
async screenshot(metadata: CallMetadata, options: types.ScreenshotOptions = {}): Promise<Buffer> {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(
|
||||
progress => this._screenshotter.screenshotPage(progress, options),
|
||||
this._timeoutSettings.timeout(options));
|
||||
}
|
||||
|
@ -22,12 +22,12 @@ import { PlaywrightOptions } from './browser';
|
||||
import { Chromium } from './chromium/chromium';
|
||||
import { Electron } from './electron/electron';
|
||||
import { Firefox } from './firefox/firefox';
|
||||
import { Selectors } from './selectors';
|
||||
import { Selectors, serverSelectors } from './selectors';
|
||||
import { HarTracer } from './supplements/har/harTracer';
|
||||
import { InspectorController } from './supplements/inspectorController';
|
||||
import { WebKit } from './webkit/webkit';
|
||||
import { Registry } from '../utils/registry';
|
||||
import { SdkObject } from './sdkObject';
|
||||
import { InstrumentationMultiplexer, SdkObject } from './instrumentation';
|
||||
|
||||
export class Playwright extends SdkObject {
|
||||
readonly selectors: Selectors;
|
||||
@ -39,25 +39,23 @@ export class Playwright extends SdkObject {
|
||||
readonly options: PlaywrightOptions;
|
||||
|
||||
constructor(isInternal: boolean) {
|
||||
super(null);
|
||||
this.selectors = new Selectors(this);
|
||||
const instrumentation = new InstrumentationMultiplexer(isInternal ? [] : [
|
||||
new InspectorController(),
|
||||
new Tracer(),
|
||||
new HarTracer()
|
||||
]);
|
||||
super({ attribution: {}, instrumentation } as any);
|
||||
this.options = {
|
||||
isInternal,
|
||||
registry: new Registry(path.join(__dirname, '..', '..')),
|
||||
|
||||
contextListeners: isInternal ? [] : [
|
||||
new InspectorController(),
|
||||
new Tracer(),
|
||||
new HarTracer()
|
||||
],
|
||||
rootSdkObject: this,
|
||||
selectors: this.selectors
|
||||
};
|
||||
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 = serverSelectors;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,13 +18,7 @@ import { TimeoutError } from '../utils/errors';
|
||||
import { assert, monotonicTime } from '../utils/utils';
|
||||
import { rewriteErrorMessage } from '../utils/stackTrace';
|
||||
import { debugLogger, LogName } from '../utils/debugLogger';
|
||||
|
||||
export type ProgressResult = {
|
||||
logs: string[],
|
||||
startTime: number,
|
||||
endTime: number,
|
||||
error?: Error,
|
||||
};
|
||||
import { CallMetadata, Instrumentation, SdkObject } from './instrumentation';
|
||||
|
||||
export interface Progress {
|
||||
log(message: string): void;
|
||||
@ -35,16 +29,6 @@ export interface Progress {
|
||||
checkpoint(name: string): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ProgressListener {
|
||||
onProgressCheckpoint(name: string): Promise<void>;
|
||||
onProgressDone(result: ProgressResult): Promise<void>;
|
||||
}
|
||||
|
||||
export async function runAbortableTask<T>(task: (progress: Progress) => Promise<T>, timeout: number): Promise<T> {
|
||||
const controller = new ProgressController();
|
||||
return controller.run(task, timeout);
|
||||
}
|
||||
|
||||
export class ProgressController {
|
||||
// Promise and callback that forcefully abort the progress.
|
||||
// This promise always rejects.
|
||||
@ -59,21 +43,22 @@ export class ProgressController {
|
||||
private _deadline: number = 0;
|
||||
private _timeout: number = 0;
|
||||
private _logRecording: string[] = [];
|
||||
private _listener?: ProgressListener;
|
||||
readonly metadata: CallMetadata;
|
||||
readonly instrumentation: Instrumentation;
|
||||
readonly sdkObject: SdkObject;
|
||||
|
||||
constructor() {
|
||||
constructor(metadata: CallMetadata, sdkObject: SdkObject) {
|
||||
this.metadata = metadata;
|
||||
this.sdkObject = sdkObject;
|
||||
this.instrumentation = sdkObject.instrumentation;
|
||||
this._forceAbortPromise = new Promise((resolve, reject) => this._forceAbort = reject);
|
||||
this._forceAbortPromise.catch(e => null); // Prevent unhandle promsie rejection.
|
||||
this._forceAbortPromise.catch(e => null); // Prevent unhandled promise rejection.
|
||||
}
|
||||
|
||||
setLogName(logName: LogName) {
|
||||
this._logName = logName;
|
||||
}
|
||||
|
||||
setListener(listener: ProgressListener) {
|
||||
this._listener = listener;
|
||||
}
|
||||
|
||||
async run<T>(task: (progress: Progress) => Promise<T>, timeout?: number): Promise<T> {
|
||||
if (timeout) {
|
||||
this._timeout = timeout;
|
||||
@ -102,8 +87,7 @@ export class ProgressController {
|
||||
throw new AbortedError();
|
||||
},
|
||||
checkpoint: async (name: string) => {
|
||||
if (this._listener)
|
||||
await this._listener.onProgressCheckpoint(name);
|
||||
await this.instrumentation.onActionCheckpoint(name, this.sdkObject, this.metadata);
|
||||
},
|
||||
};
|
||||
|
||||
@ -115,27 +99,23 @@ export class ProgressController {
|
||||
const result = await Promise.race([promise, this._forceAbortPromise]);
|
||||
clearTimeout(timer);
|
||||
this._state = 'finished';
|
||||
if (this._listener) {
|
||||
await this._listener.onProgressDone({
|
||||
startTime,
|
||||
endTime: monotonicTime(),
|
||||
logs: this._logRecording,
|
||||
});
|
||||
}
|
||||
await this.instrumentation.onAfterAction({
|
||||
startTime,
|
||||
endTime: monotonicTime(),
|
||||
logs: this._logRecording,
|
||||
}, this.sdkObject, this.metadata);
|
||||
this._logRecording = [];
|
||||
return result;
|
||||
} catch (e) {
|
||||
clearTimeout(timer);
|
||||
this._state = 'aborted';
|
||||
await Promise.all(this._cleanups.splice(0).map(cleanup => runCleanup(cleanup)));
|
||||
if (this._listener) {
|
||||
await this._listener.onProgressDone({
|
||||
startTime,
|
||||
endTime: monotonicTime(),
|
||||
logs: this._logRecording,
|
||||
error: e,
|
||||
});
|
||||
}
|
||||
await this.instrumentation.onAfterAction({
|
||||
startTime,
|
||||
endTime: monotonicTime(),
|
||||
logs: this._logRecording,
|
||||
error: e,
|
||||
}, this.sdkObject, this.metadata);
|
||||
rewriteErrorMessage(e,
|
||||
e.message +
|
||||
formatLogRecording(this._logRecording) +
|
||||
|
@ -1,39 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
import type { Browser } from './browser';
|
||||
import type { BrowserContext } from './browserContext';
|
||||
import type { BrowserType } from './browserType';
|
||||
import type { Frame } from './frames';
|
||||
import type { Page } from './page';
|
||||
|
||||
export type Attribution = {
|
||||
browserType?: BrowserType;
|
||||
browser?: Browser;
|
||||
context?: BrowserContext;
|
||||
page?: Page;
|
||||
frame?: Frame;
|
||||
};
|
||||
|
||||
export class SdkObject extends EventEmitter {
|
||||
attribution: Attribution;
|
||||
constructor(parent: SdkObject | null) {
|
||||
super();
|
||||
this.setMaxListeners(0);
|
||||
this.attribution = { ...parent?.attribution };
|
||||
}
|
||||
}
|
@ -19,7 +19,6 @@ import * as frames from './frames';
|
||||
import * as js from './javascript';
|
||||
import * as types from './types';
|
||||
import { ParsedSelector, parseSelector } from './common/selectorParser';
|
||||
import { SdkObject } from './sdkObject';
|
||||
|
||||
export type SelectorInfo = {
|
||||
parsed: ParsedSelector,
|
||||
@ -27,12 +26,11 @@ export type SelectorInfo = {
|
||||
selector: string,
|
||||
};
|
||||
|
||||
export class Selectors extends SdkObject {
|
||||
export class Selectors {
|
||||
readonly _builtinEngines: Set<string>;
|
||||
readonly _engines: Map<string, { source: string, contentScript: boolean }>;
|
||||
|
||||
constructor(parent: SdkObject) {
|
||||
super(parent);
|
||||
constructor() {
|
||||
// Note: keep in sync with InjectedScript class.
|
||||
this._builtinEngines = new Set([
|
||||
'css', 'css:light',
|
||||
@ -136,6 +134,4 @@ export class Selectors extends SdkObject {
|
||||
}
|
||||
}
|
||||
|
||||
export function serverSelectors(parent: SdkObject) {
|
||||
return new Selectors(parent);
|
||||
}
|
||||
export const serverSelectors = new Selectors();
|
||||
|
@ -16,15 +16,16 @@
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as util from 'util';
|
||||
import { BrowserContext, ContextListener } from '../../browserContext';
|
||||
import { BrowserContext } from '../../browserContext';
|
||||
import { helper } from '../../helper';
|
||||
import * as network from '../../network';
|
||||
import { Page } from '../../page';
|
||||
import { InstrumentationListener } from '../../instrumentation';
|
||||
import * as har from './har';
|
||||
|
||||
const fsWriteFileAsync = util.promisify(fs.writeFile.bind(fs));
|
||||
|
||||
export class HarTracer implements ContextListener {
|
||||
export class HarTracer implements InstrumentationListener {
|
||||
private _contextTracers = new Map<BrowserContext, HarContextTracer>();
|
||||
|
||||
async onContextCreated(context: BrowserContext): Promise<void> {
|
||||
|
@ -14,11 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { BrowserContext, ContextListener } from '../browserContext';
|
||||
import { BrowserContext } from '../browserContext';
|
||||
import { isDebugMode } from '../../utils/utils';
|
||||
import { RecorderSupplement } from './recorderSupplement';
|
||||
import { InstrumentationListener } from '../instrumentation';
|
||||
|
||||
export class InspectorController implements ContextListener {
|
||||
export class InspectorController implements InstrumentationListener {
|
||||
async onContextCreated(context: BrowserContext): Promise<void> {
|
||||
if (isDebugMode()) {
|
||||
RecorderSupplement.getOrCreate(context, {
|
||||
|
@ -22,6 +22,7 @@ import { Page } from '../../page';
|
||||
import { ProgressController } from '../../progress';
|
||||
import { createPlaywright } from '../../playwright';
|
||||
import { EventEmitter } from 'events';
|
||||
import { internalCallMetadata } from '../../instrumentation';
|
||||
|
||||
const readFileAsync = util.promisify(fs.readFile);
|
||||
|
||||
@ -85,12 +86,13 @@ export class RecorderApp extends EventEmitter {
|
||||
this._page.context().close().catch(e => console.error(e));
|
||||
});
|
||||
|
||||
await this._page.mainFrame().goto(new ProgressController(), 'https://playwright/index.html');
|
||||
const mainFrame = this._page.mainFrame();
|
||||
await mainFrame.goto(internalCallMetadata(), 'https://playwright/index.html');
|
||||
}
|
||||
|
||||
static async open(): Promise<RecorderApp> {
|
||||
const recorderPlaywright = createPlaywright(true);
|
||||
const context = await recorderPlaywright.chromium.launchPersistentContext(undefined, {
|
||||
const context = await recorderPlaywright.chromium.launchPersistentContext(internalCallMetadata(), undefined, {
|
||||
args: [
|
||||
'--app=data:text/html,',
|
||||
'--window-size=300,800',
|
||||
@ -98,7 +100,7 @@ export class RecorderApp extends EventEmitter {
|
||||
noDefaultViewport: true
|
||||
});
|
||||
|
||||
const controller = new ProgressController();
|
||||
const controller = new ProgressController(internalCallMetadata(), context._browser);
|
||||
await controller.run(async progress => {
|
||||
await context._browser._defaultContext!._loadDefaultContextAsIs(progress);
|
||||
});
|
||||
|
@ -25,11 +25,11 @@ import { LanguageGenerator } from './recorder/language';
|
||||
import { JavaScriptLanguageGenerator } from './recorder/javascript';
|
||||
import { CSharpLanguageGenerator } from './recorder/csharp';
|
||||
import { PythonLanguageGenerator } from './recorder/python';
|
||||
import { ProgressController } from '../progress';
|
||||
import * as recorderSource from '../../generated/recorderSource';
|
||||
import * as consoleApiSource from '../../generated/consoleApiSource';
|
||||
import { BufferedOutput, FileOutput, FlushingTerminalOutput, OutputMultiplexer, RecorderOutput, TerminalOutput, Writable } from './recorder/outputs';
|
||||
import { EventData, Mode, RecorderApp } from './recorder/recorderApp';
|
||||
import { internalCallMetadata } from '../instrumentation';
|
||||
|
||||
type BindingSource = { frame: Frame, page: Page };
|
||||
|
||||
@ -238,30 +238,30 @@ export class RecorderSupplement {
|
||||
|
||||
private async _performAction(frame: Frame, action: actions.Action) {
|
||||
const page = frame._page;
|
||||
const controller = new ProgressController();
|
||||
const actionInContext: ActionInContext = {
|
||||
pageAlias: this._pageAliases.get(page)!,
|
||||
...describeFrame(frame),
|
||||
action
|
||||
};
|
||||
this._generator.willPerformAction(actionInContext);
|
||||
const noCallMetadata = internalCallMetadata();
|
||||
try {
|
||||
const kActionTimeout = 5000;
|
||||
if (action.name === 'click') {
|
||||
const { options } = toClickOptions(action);
|
||||
await frame.click(controller, action.selector, { ...options, timeout: kActionTimeout });
|
||||
await frame.click(noCallMetadata, action.selector, { ...options, timeout: kActionTimeout });
|
||||
}
|
||||
if (action.name === 'press') {
|
||||
const modifiers = toModifiers(action.modifiers);
|
||||
const shortcut = [...modifiers, action.key].join('+');
|
||||
await frame.press(controller, action.selector, shortcut, { timeout: kActionTimeout });
|
||||
await frame.press(noCallMetadata, action.selector, shortcut, { timeout: kActionTimeout });
|
||||
}
|
||||
if (action.name === 'check')
|
||||
await frame.check(controller, action.selector, { timeout: kActionTimeout });
|
||||
await frame.check(noCallMetadata, action.selector, { timeout: kActionTimeout });
|
||||
if (action.name === 'uncheck')
|
||||
await frame.uncheck(controller, action.selector, { timeout: kActionTimeout });
|
||||
await frame.uncheck(noCallMetadata, action.selector, { timeout: kActionTimeout });
|
||||
if (action.name === 'select')
|
||||
await frame.selectOption(controller, action.selector, [], action.options.map(value => ({ value })), { timeout: kActionTimeout });
|
||||
await frame.selectOption(noCallMetadata, action.selector, [], action.options.map(value => ({ value })), { timeout: kActionTimeout });
|
||||
} catch (e) {
|
||||
this._generator.performedActionFailed(actionInContext);
|
||||
return;
|
||||
|
@ -78,15 +78,14 @@ export type ActionTraceEvent = {
|
||||
timestamp: number,
|
||||
type: 'action',
|
||||
contextId: string,
|
||||
action: string,
|
||||
objectType: string,
|
||||
method: string,
|
||||
params: any,
|
||||
stack?: string,
|
||||
pageId?: string,
|
||||
selector?: string,
|
||||
label?: string,
|
||||
value?: string,
|
||||
startTime: number,
|
||||
endTime: number,
|
||||
logs?: string[],
|
||||
stack?: string,
|
||||
error?: string,
|
||||
snapshots?: { name: string, snapshotId: string }[],
|
||||
};
|
||||
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ActionListener, ActionMetadata, BrowserContext, ContextListener, Video } from '../server/browserContext';
|
||||
import { BrowserContext, Video } from '../server/browserContext';
|
||||
import type { SnapshotterResource as SnapshotterResource, SnapshotterBlob, SnapshotterDelegate } from './snapshotter';
|
||||
import * as trace from './traceTypes';
|
||||
import * as path from 'path';
|
||||
@ -24,17 +24,17 @@ import { createGuid, getFromENV, mkdirIfNeeded, monotonicTime } from '../utils/u
|
||||
import { Page } from '../server/page';
|
||||
import { Snapshotter } from './snapshotter';
|
||||
import { helper, RegisteredListener } from '../server/helper';
|
||||
import { ProgressResult } from '../server/progress';
|
||||
import { Dialog } from '../server/dialog';
|
||||
import { Frame, NavigationEvent } from '../server/frames';
|
||||
import { snapshotScript } from './snapshotterInjected';
|
||||
import { ActionResult, CallMetadata, InstrumentationListener, SdkObject } from '../server/instrumentation';
|
||||
|
||||
const fsWriteFileAsync = util.promisify(fs.writeFile.bind(fs));
|
||||
const fsAppendFileAsync = util.promisify(fs.appendFile.bind(fs));
|
||||
const fsAccessAsync = util.promisify(fs.access.bind(fs));
|
||||
const envTrace = getFromENV('PW_TRACE_DIR');
|
||||
|
||||
export class Tracer implements ContextListener {
|
||||
export class Tracer implements InstrumentationListener {
|
||||
private _contextTracers = new Map<BrowserContext, ContextTracer>();
|
||||
|
||||
async onContextCreated(context: BrowserContext): Promise<void> {
|
||||
@ -56,19 +56,27 @@ export class Tracer implements ContextListener {
|
||||
this._contextTracers.delete(context);
|
||||
}
|
||||
}
|
||||
|
||||
async onActionCheckpoint(name: string, sdkObject: SdkObject, metadata: CallMetadata): Promise<void> {
|
||||
this._contextTracers.get(sdkObject.attribution.context!)?.onActionCheckpoint(name, sdkObject, metadata);
|
||||
}
|
||||
|
||||
async onAfterAction(result: ActionResult, sdkObject: SdkObject, metadata: CallMetadata): Promise<void> {
|
||||
this._contextTracers.get(sdkObject.attribution.context!)?.onAfterAction(result, sdkObject, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
const pageIdSymbol = Symbol('pageId');
|
||||
const snapshotsSymbol = Symbol('snapshots');
|
||||
|
||||
// TODO: this is a hacky way to pass snapshots between onActionCheckpoint and onAfterAction.
|
||||
function snapshotsForMetadata(metadata: ActionMetadata): { name: string, snapshotId: string }[] {
|
||||
function snapshotsForMetadata(metadata: CallMetadata): { name: string, snapshotId: string }[] {
|
||||
if (!(metadata as any)[snapshotsSymbol])
|
||||
(metadata as any)[snapshotsSymbol] = [];
|
||||
return (metadata as any)[snapshotsSymbol];
|
||||
}
|
||||
|
||||
class ContextTracer implements SnapshotterDelegate, ActionListener {
|
||||
class ContextTracer implements SnapshotterDelegate {
|
||||
private _context: BrowserContext;
|
||||
private _contextId: string;
|
||||
private _traceStoragePromise: Promise<string>;
|
||||
@ -102,7 +110,6 @@ class ContextTracer implements SnapshotterDelegate, ActionListener {
|
||||
this._eventListeners = [
|
||||
helper.addEventListener(context, BrowserContext.Events.Page, this._onPage.bind(this)),
|
||||
];
|
||||
this._context._actionListeners.add(this);
|
||||
}
|
||||
|
||||
onBlob(blob: SnapshotterBlob): void {
|
||||
@ -147,24 +154,29 @@ class ContextTracer implements SnapshotterDelegate, ActionListener {
|
||||
return (page as any)[pageIdSymbol];
|
||||
}
|
||||
|
||||
async onActionCheckpoint(name: string, metadata: ActionMetadata): Promise<void> {
|
||||
async onActionCheckpoint(name: string, sdkObject: SdkObject, metadata: CallMetadata): Promise<void> {
|
||||
if (!sdkObject.attribution.page)
|
||||
return;
|
||||
const snapshotId = createGuid();
|
||||
snapshotsForMetadata(metadata).push({ name, snapshotId });
|
||||
await this._snapshotter.forceSnapshot(metadata.page, snapshotId);
|
||||
await this._snapshotter.forceSnapshot(sdkObject.attribution.page, snapshotId);
|
||||
}
|
||||
|
||||
async onAfterAction(result: ProgressResult, metadata: ActionMetadata): Promise<void> {
|
||||
async onAfterAction(result: ActionResult, sdkObject: SdkObject, metadata: CallMetadata): Promise<void> {
|
||||
if (!sdkObject.attribution.page)
|
||||
return;
|
||||
const event: trace.ActionTraceEvent = {
|
||||
timestamp: monotonicTime(),
|
||||
type: 'action',
|
||||
contextId: this._contextId,
|
||||
pageId: this.pageId(metadata.page),
|
||||
action: metadata.type,
|
||||
selector: typeof metadata.target === 'string' ? metadata.target : undefined,
|
||||
value: metadata.value,
|
||||
pageId: this.pageId(sdkObject.attribution.page),
|
||||
objectType: metadata.type,
|
||||
method: metadata.method,
|
||||
// FIXME: filter out evaluation snippets, binary
|
||||
params: metadata.params,
|
||||
stack: metadata.stack,
|
||||
startTime: result.startTime,
|
||||
endTime: result.endTime,
|
||||
stack: metadata.stack,
|
||||
logs: result.logs.slice(),
|
||||
error: result.error ? result.error.stack : undefined,
|
||||
snapshots: snapshotsForMetadata(metadata),
|
||||
@ -265,7 +277,6 @@ class ContextTracer implements SnapshotterDelegate, ActionListener {
|
||||
|
||||
async dispose() {
|
||||
this._disposed = true;
|
||||
this._context._actionListeners.delete(this);
|
||||
helper.removeEventListeners(this._eventListeners);
|
||||
this._snapshotter.dispose();
|
||||
const event: trace.ContextDestroyedTraceEvent = {
|
||||
|
@ -36,6 +36,7 @@ export const ActionList: React.FC<ActionListProps> = ({
|
||||
const targetAction = highlightedAction || selectedAction;
|
||||
return <div className='action-list'>{actions.map(actionEntry => {
|
||||
const { action, actionId, thumbnailUrl } = actionEntry;
|
||||
const selector = action.params.selector;
|
||||
return <div
|
||||
className={'action-entry' + (actionEntry === targetAction ? ' selected' : '')}
|
||||
key={actionId}
|
||||
@ -45,9 +46,9 @@ export const ActionList: React.FC<ActionListProps> = ({
|
||||
>
|
||||
<div className='action-header'>
|
||||
<div className={'action-error codicon codicon-issues'} hidden={!actionEntry.action.error} />
|
||||
<div className='action-title'>{action.action}</div>
|
||||
{action.selector && <div className='action-selector' title={action.selector}>{action.selector}</div>}
|
||||
{action.action === 'goto' && action.value && <div className='action-url' title={action.value}>{action.value}</div>}
|
||||
<div className='action-title'>{action.method}</div>
|
||||
{action.params.selector && <div className='action-selector' title={action.params.selector}>{action.params.selector}</div>}
|
||||
{action.method === 'goto' && action.params.url && <div className='action-url' title={action.params.url}>{action.params.url}</div>}
|
||||
</div>
|
||||
<div className='action-thumbnail'>
|
||||
<img src={thumbnailUrl} />
|
||||
|
@ -54,17 +54,17 @@ export const Timeline: React.FunctionComponent<{
|
||||
const bars: TimelineBar[] = [];
|
||||
for (const page of context.pages) {
|
||||
for (const entry of page.actions) {
|
||||
let detail = entry.action.selector || '';
|
||||
if (entry.action.action === 'goto')
|
||||
detail = entry.action.value || '';
|
||||
let detail = entry.action.params.selector || '';
|
||||
if (entry.action.method === 'goto')
|
||||
detail = entry.action.params.url || '';
|
||||
bars.push({
|
||||
entry,
|
||||
leftTime: entry.action.startTime,
|
||||
rightTime: entry.action.endTime,
|
||||
leftPosition: timeToPosition(measure.width, boundaries, entry.action.startTime),
|
||||
rightPosition: timeToPosition(measure.width, boundaries, entry.action.endTime),
|
||||
label: entry.action.action + ' ' + detail,
|
||||
type: entry.action.action,
|
||||
label: entry.action.method + ' ' + detail,
|
||||
type: entry.action.method,
|
||||
priority: 0,
|
||||
});
|
||||
if (entry === (highlightedAction || selectedAction))
|
||||
|
@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import { folio } from './fixtures';
|
||||
import { ProgressController } from '../lib/server/progress';
|
||||
import { internalCallMetadata } from '../lib/server/instrumentation';
|
||||
|
||||
const extended = folio.extend<{
|
||||
recorderFrame: () => Promise<any>,
|
||||
@ -40,7 +40,7 @@ extended.recorderFrame.init(async ({context, toImpl}, runTest) => {
|
||||
extended.recorderClick.init(async ({ recorderFrame }, runTest) => {
|
||||
await runTest(async (selector: string) => {
|
||||
const frame = await recorderFrame();
|
||||
frame.click(new ProgressController(), selector, {});
|
||||
frame.click(internalCallMetadata(), selector, {});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -43,11 +43,11 @@ it('should record trace', (test, { browserName, platform }) => {
|
||||
expect(pageEvent.contextId).toBe(contextId);
|
||||
const pageId = pageEvent.pageId;
|
||||
|
||||
const gotoEvent = traceEvents.find(event => event.type === 'action' && event.action === 'goto') as trace.ActionTraceEvent;
|
||||
const gotoEvent = traceEvents.find(event => event.type === 'action' && event.method === 'goto') as trace.ActionTraceEvent;
|
||||
expect(gotoEvent).toBeTruthy();
|
||||
expect(gotoEvent.contextId).toBe(contextId);
|
||||
expect(gotoEvent.pageId).toBe(pageId);
|
||||
expect(gotoEvent.value).toBe(url);
|
||||
expect(gotoEvent.params.url).toBe(url);
|
||||
|
||||
const resourceEvent = traceEvents.find(event => event.type === 'resource' && event.url.endsWith('/frames/style.css')) as trace.NetworkResourceTraceEvent;
|
||||
expect(resourceEvent).toBeTruthy();
|
||||
@ -59,7 +59,7 @@ it('should record trace', (test, { browserName, platform }) => {
|
||||
expect(resourceEvent.requestHeaders.length).toBeGreaterThan(0);
|
||||
expect(resourceEvent.requestSha1).toBe('none');
|
||||
|
||||
const clickEvent = traceEvents.find(event => event.type === 'action' && event.action === 'click') as trace.ActionTraceEvent;
|
||||
const clickEvent = traceEvents.find(event => event.type === 'action' && event.method === 'click') as trace.ActionTraceEvent;
|
||||
expect(clickEvent).toBeTruthy();
|
||||
expect(clickEvent.snapshots.length).toBe(2);
|
||||
const snapshotId = clickEvent.snapshots[0].snapshotId;
|
||||
@ -93,11 +93,11 @@ it('should record trace with POST', (test, { browserName, platform }) => {
|
||||
expect(pageEvent.contextId).toBe(contextId);
|
||||
const pageId = pageEvent.pageId;
|
||||
|
||||
const gotoEvent = traceEvents.find(event => event.type === 'action' && event.action === 'goto') as trace.ActionTraceEvent;
|
||||
const gotoEvent = traceEvents.find(event => event.type === 'action' && event.method === 'goto') as trace.ActionTraceEvent;
|
||||
expect(gotoEvent).toBeTruthy();
|
||||
expect(gotoEvent.contextId).toBe(contextId);
|
||||
expect(gotoEvent.pageId).toBe(pageId);
|
||||
expect(gotoEvent.value).toBe(url);
|
||||
expect(gotoEvent.params.url).toBe(url);
|
||||
|
||||
const resourceEvent = traceEvents.find(event => event.type === 'resource' && event.url.endsWith('/file.json')) as trace.NetworkResourceTraceEvent;
|
||||
expect(resourceEvent).toBeTruthy();
|
||||
|
Loading…
Reference in New Issue
Block a user