chore(recorder): move recording output into the gui app (#5425)
This commit is contained in:
parent
a42c46b986
commit
449adfd3ae
1
index.js
1
index.js
|
|
@ -15,5 +15,4 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { setUnderTest } = require('./lib/utils/utils');
|
const { setUnderTest } = require('./lib/utils/utils');
|
||||||
setUnderTest(); // Note: we must call setUnderTest before initializing.
|
|
||||||
module.exports = require('./lib/inprocess');
|
module.exports = require('./lib/inprocess');
|
||||||
|
|
|
||||||
|
|
@ -332,7 +332,6 @@ async function open(options: Options, url: string | undefined, language: string)
|
||||||
contextOptions,
|
contextOptions,
|
||||||
device: options.device,
|
device: options.device,
|
||||||
saveStorage: options.saveStorage,
|
saveStorage: options.saveStorage,
|
||||||
terminal: !!process.stdout.columns,
|
|
||||||
});
|
});
|
||||||
await openPage(context, url);
|
await openPage(context, url);
|
||||||
if (process.env.PWCLI_EXIT_FOR_TEST)
|
if (process.env.PWCLI_EXIT_FOR_TEST)
|
||||||
|
|
@ -350,7 +349,6 @@ async function codegen(options: Options, url: string | undefined, language: stri
|
||||||
device: options.device,
|
device: options.device,
|
||||||
saveStorage: options.saveStorage,
|
saveStorage: options.saveStorage,
|
||||||
startRecording: true,
|
startRecording: true,
|
||||||
terminal: !!process.stdout.columns,
|
|
||||||
outputFile: outputFile ? path.resolve(outputFile) : undefined
|
outputFile: outputFile ? path.resolve(outputFile) : undefined
|
||||||
});
|
});
|
||||||
await openPage(context, url);
|
await openPage(context, url);
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import * as util from 'util';
|
||||||
import { isString } from '../utils/utils';
|
import { isString } from '../utils/utils';
|
||||||
import * as channels from '../protocol/channels';
|
import * as channels from '../protocol/channels';
|
||||||
import { Events } from './events';
|
import { Events } from './events';
|
||||||
import { BrowserContext, prepareBrowserContextOptions } from './browserContext';
|
import { BrowserContext, prepareBrowserContextParams } from './browserContext';
|
||||||
import { ChannelOwner } from './channelOwner';
|
import { ChannelOwner } from './channelOwner';
|
||||||
import * as api from '../../types/types';
|
import * as api from '../../types/types';
|
||||||
import * as types from './types';
|
import * as types from './types';
|
||||||
|
|
@ -230,7 +230,7 @@ export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel, c
|
||||||
|
|
||||||
async launchBrowser(options: types.BrowserContextOptions & { pkg?: string } = {}): Promise<ChromiumBrowserContext> {
|
async launchBrowser(options: types.BrowserContextOptions & { pkg?: string } = {}): Promise<ChromiumBrowserContext> {
|
||||||
return this._wrapApiCall('androidDevice.launchBrowser', async () => {
|
return this._wrapApiCall('androidDevice.launchBrowser', async () => {
|
||||||
const contextOptions = await prepareBrowserContextOptions(options);
|
const contextOptions = await prepareBrowserContextParams(options);
|
||||||
const { context } = await this._channel.launchBrowser(contextOptions);
|
const { context } = await this._channel.launchBrowser(contextOptions);
|
||||||
return BrowserContext.from(context) as ChromiumBrowserContext;
|
return BrowserContext.from(context) as ChromiumBrowserContext;
|
||||||
});
|
});
|
||||||
|
|
@ -394,7 +394,7 @@ export class AndroidWebView extends EventEmitter implements api.AndroidWebView {
|
||||||
|
|
||||||
private async _fetchPage(): Promise<Page> {
|
private async _fetchPage(): Promise<Page> {
|
||||||
return this._device._wrapApiCall('androidWebView.page', async () => {
|
return this._device._wrapApiCall('androidWebView.page', async () => {
|
||||||
const { context } = await this._device._channel.connectToWebView({ pid: this._data.pid });
|
const { context } = await this._device._channel.connectToWebView({ pid: this._data.pid, sdkLanguage: 'javascript' });
|
||||||
return BrowserContext.from(context).pages()[0];
|
return BrowserContext.from(context).pages()[0];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as channels from '../protocol/channels';
|
import * as channels from '../protocol/channels';
|
||||||
import { BrowserContext, prepareBrowserContextOptions } from './browserContext';
|
import { BrowserContext, prepareBrowserContextParams } from './browserContext';
|
||||||
import { Page } from './page';
|
import { Page } from './page';
|
||||||
import { ChannelOwner } from './channelOwner';
|
import { ChannelOwner } from './channelOwner';
|
||||||
import { Events } from './events';
|
import { Events } from './events';
|
||||||
|
|
@ -47,7 +47,7 @@ export class Browser extends ChannelOwner<channels.BrowserChannel, channels.Brow
|
||||||
return this._wrapApiCall('browser.newContext', async () => {
|
return this._wrapApiCall('browser.newContext', async () => {
|
||||||
if (this._isRemote && options._traceDir)
|
if (this._isRemote && options._traceDir)
|
||||||
throw new Error(`"_traceDir" is not supported in connected browser`);
|
throw new Error(`"_traceDir" is not supported in connected browser`);
|
||||||
const contextOptions = await prepareBrowserContextOptions(options);
|
const contextOptions = await prepareBrowserContextParams(options);
|
||||||
const context = BrowserContext.from((await this._channel.newContext(contextOptions)).context);
|
const context = BrowserContext.from((await this._channel.newContext(contextOptions)).context);
|
||||||
context._options = contextOptions;
|
context._options = contextOptions;
|
||||||
this._contexts.add(context);
|
this._contexts.add(context);
|
||||||
|
|
|
||||||
|
|
@ -43,9 +43,9 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
|
||||||
_timeoutSettings = new TimeoutSettings();
|
_timeoutSettings = new TimeoutSettings();
|
||||||
_ownerPage: Page | undefined;
|
_ownerPage: Page | undefined;
|
||||||
private _closedPromise: Promise<void>;
|
private _closedPromise: Promise<void>;
|
||||||
_options: channels.BrowserNewContextParams = {};
|
_options: channels.BrowserNewContextParams = {
|
||||||
private _stdout: NodeJS.WriteStream;
|
sdkLanguage: 'javascript'
|
||||||
private _stderr: NodeJS.WriteStream;
|
};
|
||||||
|
|
||||||
static from(context: channels.BrowserContextChannel): BrowserContext {
|
static from(context: channels.BrowserContextChannel): BrowserContext {
|
||||||
return (context as any)._object;
|
return (context as any)._object;
|
||||||
|
|
@ -64,23 +64,9 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
|
||||||
this._channel.on('close', () => this._onClose());
|
this._channel.on('close', () => this._onClose());
|
||||||
this._channel.on('page', ({page}) => this._onPage(Page.from(page)));
|
this._channel.on('page', ({page}) => this._onPage(Page.from(page)));
|
||||||
this._channel.on('route', ({ route, request }) => this._onRoute(network.Route.from(route), network.Request.from(request)));
|
this._channel.on('route', ({ route, request }) => this._onRoute(network.Route.from(route), network.Request.from(request)));
|
||||||
this._stdout = process.stdout;
|
|
||||||
this._stderr = process.stderr;
|
|
||||||
this._channel.on('stdout', ({ data }) => {
|
|
||||||
this._stdout.write(Buffer.from(data, 'base64'));
|
|
||||||
this._pushTerminalSize();
|
|
||||||
});
|
|
||||||
this._channel.on('stderr', ({ data }) => {
|
|
||||||
this._stderr.write(Buffer.from(data, 'base64'));
|
|
||||||
this._pushTerminalSize();
|
|
||||||
});
|
|
||||||
this._closedPromise = new Promise(f => this.once(Events.BrowserContext.Close, f));
|
this._closedPromise = new Promise(f => this.once(Events.BrowserContext.Close, f));
|
||||||
}
|
}
|
||||||
|
|
||||||
private _pushTerminalSize() {
|
|
||||||
this._channel.setTerminalSizeNoReply({ rows: process.stdout.rows, columns: process.stdout.columns }).catch(() => {});
|
|
||||||
}
|
|
||||||
|
|
||||||
private _onPage(page: Page): void {
|
private _onPage(page: Page): void {
|
||||||
this._pages.add(page);
|
this._pages.add(page);
|
||||||
this.emit(Events.BrowserContext.Page, page);
|
this.emit(Events.BrowserContext.Page, page);
|
||||||
|
|
@ -283,31 +269,30 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
|
||||||
device?: string,
|
device?: string,
|
||||||
saveStorage?: string,
|
saveStorage?: string,
|
||||||
startRecording?: boolean,
|
startRecording?: boolean,
|
||||||
terminal?: boolean,
|
|
||||||
outputFile?: string
|
outputFile?: string
|
||||||
}) {
|
}) {
|
||||||
this._pushTerminalSize();
|
|
||||||
await this._channel.recorderSupplementEnable(params);
|
await this._channel.recorderSupplementEnable(params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function prepareBrowserContextOptions(options: BrowserContextOptions): Promise<channels.BrowserNewContextOptions> {
|
export async function prepareBrowserContextParams(options: BrowserContextOptions): Promise<channels.BrowserNewContextParams> {
|
||||||
if (options.videoSize && !options.videosPath)
|
if (options.videoSize && !options.videosPath)
|
||||||
throw new Error(`"videoSize" option requires "videosPath" to be specified`);
|
throw new Error(`"videoSize" option requires "videosPath" to be specified`);
|
||||||
if (options.extraHTTPHeaders)
|
if (options.extraHTTPHeaders)
|
||||||
network.validateHeaders(options.extraHTTPHeaders);
|
network.validateHeaders(options.extraHTTPHeaders);
|
||||||
const contextOptions: channels.BrowserNewContextParams = {
|
const contextParams: channels.BrowserNewContextParams = {
|
||||||
|
sdkLanguage: 'javascript',
|
||||||
...options,
|
...options,
|
||||||
viewport: options.viewport === null ? undefined : options.viewport,
|
viewport: options.viewport === null ? undefined : options.viewport,
|
||||||
noDefaultViewport: options.viewport === null,
|
noDefaultViewport: options.viewport === null,
|
||||||
extraHTTPHeaders: options.extraHTTPHeaders ? headersObjectToArray(options.extraHTTPHeaders) : undefined,
|
extraHTTPHeaders: options.extraHTTPHeaders ? headersObjectToArray(options.extraHTTPHeaders) : undefined,
|
||||||
storageState: typeof options.storageState === 'string' ? JSON.parse(await fsReadFileAsync(options.storageState, 'utf8')) : options.storageState,
|
storageState: typeof options.storageState === 'string' ? JSON.parse(await fsReadFileAsync(options.storageState, 'utf8')) : options.storageState,
|
||||||
};
|
};
|
||||||
if (!contextOptions.recordVideo && options.videosPath) {
|
if (!contextParams.recordVideo && options.videosPath) {
|
||||||
contextOptions.recordVideo = {
|
contextParams.recordVideo = {
|
||||||
dir: options.videosPath,
|
dir: options.videosPath,
|
||||||
size: options.videoSize
|
size: options.videoSize
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return contextOptions;
|
return contextParams;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
import * as channels from '../protocol/channels';
|
import * as channels from '../protocol/channels';
|
||||||
import { Browser } from './browser';
|
import { Browser } from './browser';
|
||||||
import { BrowserContext, prepareBrowserContextOptions } from './browserContext';
|
import { BrowserContext, prepareBrowserContextParams } from './browserContext';
|
||||||
import { ChannelOwner } from './channelOwner';
|
import { ChannelOwner } from './channelOwner';
|
||||||
import { LaunchOptions, LaunchServerOptions, ConnectOptions, LaunchPersistentContextOptions } from './types';
|
import { LaunchOptions, LaunchServerOptions, ConnectOptions, LaunchPersistentContextOptions } from './types';
|
||||||
import WebSocket from 'ws';
|
import WebSocket from 'ws';
|
||||||
|
|
@ -94,17 +94,17 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel, chann
|
||||||
async launchPersistentContext(userDataDir: string, options: LaunchPersistentContextOptions = {}): Promise<BrowserContext> {
|
async launchPersistentContext(userDataDir: string, options: LaunchPersistentContextOptions = {}): Promise<BrowserContext> {
|
||||||
return this._wrapApiCall('browserType.launchPersistentContext', async () => {
|
return this._wrapApiCall('browserType.launchPersistentContext', async () => {
|
||||||
assert(!(options as any).port, 'Cannot specify a port without launching as a server.');
|
assert(!(options as any).port, 'Cannot specify a port without launching as a server.');
|
||||||
const contextOptions = await prepareBrowserContextOptions(options);
|
const contextParams = await prepareBrowserContextParams(options);
|
||||||
const persistentOptions: channels.BrowserTypeLaunchPersistentContextParams = {
|
const persistentParams: channels.BrowserTypeLaunchPersistentContextParams = {
|
||||||
...contextOptions,
|
...contextParams,
|
||||||
ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : undefined,
|
ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : undefined,
|
||||||
ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs),
|
ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs),
|
||||||
env: options.env ? envObjectToArray(options.env) : undefined,
|
env: options.env ? envObjectToArray(options.env) : undefined,
|
||||||
userDataDir,
|
userDataDir,
|
||||||
};
|
};
|
||||||
const result = await this._channel.launchPersistentContext(persistentOptions);
|
const result = await this._channel.launchPersistentContext(persistentParams);
|
||||||
const context = BrowserContext.from(result.context);
|
const context = BrowserContext.from(result.context);
|
||||||
context._options = contextOptions;
|
context._options = contextParams;
|
||||||
context._logger = options.logger;
|
context._logger = options.logger;
|
||||||
return context;
|
return context;
|
||||||
}, options.logger);
|
}, options.logger);
|
||||||
|
|
@ -192,6 +192,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel, chann
|
||||||
const logger = params.logger;
|
const logger = params.logger;
|
||||||
return this._wrapApiCall('browserType.connectOverCDP', async () => {
|
return this._wrapApiCall('browserType.connectOverCDP', async () => {
|
||||||
const result = await this._channel.connectOverCDP({
|
const result = await this._channel.connectOverCDP({
|
||||||
|
sdkLanguage: 'javascript',
|
||||||
wsEndpoint: params.wsEndpoint,
|
wsEndpoint: params.wsEndpoint,
|
||||||
slowMo: params.slowMo,
|
slowMo: params.slowMo,
|
||||||
timeout: params.timeout
|
timeout: params.timeout
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ export class Electron extends ChannelOwner<channels.ElectronChannel, channels.El
|
||||||
async launch(options: ElectronOptions = {}): Promise<ElectronApplication> {
|
async launch(options: ElectronOptions = {}): Promise<ElectronApplication> {
|
||||||
return this._wrapApiCall('electron.launch', async () => {
|
return this._wrapApiCall('electron.launch', async () => {
|
||||||
const params: channels.ElectronLaunchParams = {
|
const params: channels.ElectronLaunchParams = {
|
||||||
|
sdkLanguage: 'javascript',
|
||||||
...options,
|
...options,
|
||||||
env: envObjectToArray(options.env ? options.env : process.env),
|
env: envObjectToArray(options.env ? options.env : process.env),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -165,7 +165,7 @@ export class AndroidDeviceDispatcher extends Dispatcher<AndroidDevice, channels.
|
||||||
}
|
}
|
||||||
|
|
||||||
async connectToWebView(params: channels.AndroidDeviceConnectToWebViewParams): Promise<channels.AndroidDeviceConnectToWebViewResult> {
|
async connectToWebView(params: channels.AndroidDeviceConnectToWebViewParams): Promise<channels.AndroidDeviceConnectToWebViewResult> {
|
||||||
return { context: new BrowserContextDispatcher(this._scope, await this._object.connectToWebView(params.pid)) };
|
return { context: new BrowserContextDispatcher(this._scope, await this._object.connectToWebView(params.pid, params.sdkLanguage)) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import { CRBrowserContext } from '../server/chromium/crBrowser';
|
||||||
import { CDPSessionDispatcher } from './cdpSessionDispatcher';
|
import { CDPSessionDispatcher } from './cdpSessionDispatcher';
|
||||||
import { RecorderSupplement } from '../server/supplements/recorderSupplement';
|
import { RecorderSupplement } from '../server/supplements/recorderSupplement';
|
||||||
import { CallMetadata } from '../server/instrumentation';
|
import { CallMetadata } from '../server/instrumentation';
|
||||||
|
import { isUnderTest } from '../utils/utils';
|
||||||
|
|
||||||
export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channels.BrowserContextInitializer> implements channels.BrowserContextChannel {
|
export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channels.BrowserContextInitializer> implements channels.BrowserContextChannel {
|
||||||
private _context: BrowserContext;
|
private _context: BrowserContext;
|
||||||
|
|
@ -38,8 +39,6 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
|
||||||
this._dispatchEvent('close');
|
this._dispatchEvent('close');
|
||||||
this._dispose();
|
this._dispose();
|
||||||
});
|
});
|
||||||
context.on(BrowserContext.Events.StdOut, data => this._dispatchEvent('stdout', { data: Buffer.from(data, 'utf8').toString('base64') }));
|
|
||||||
context.on(BrowserContext.Events.StdErr, data => this._dispatchEvent('stderr', { data: Buffer.from(data, 'utf8').toString('base64') }));
|
|
||||||
|
|
||||||
if (context._browser.options.name === 'chromium') {
|
if (context._browser.options.name === 'chromium') {
|
||||||
for (const page of (context as CRBrowserContext).backgroundPages())
|
for (const page of (context as CRBrowserContext).backgroundPages())
|
||||||
|
|
@ -134,12 +133,9 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
|
||||||
}
|
}
|
||||||
|
|
||||||
async pause() {
|
async pause() {
|
||||||
if (!this._context._browser.options.headful)
|
if (!this._context._browser.options.headful && !isUnderTest())
|
||||||
return;
|
return;
|
||||||
const recorder = await RecorderSupplement.getOrCreate(this._context, {
|
const recorder = await RecorderSupplement.getOrCreate(this._context);
|
||||||
language: 'javascript',
|
|
||||||
terminal: true
|
|
||||||
});
|
|
||||||
await recorder.pause();
|
await recorder.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -149,8 +145,4 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
|
||||||
const crBrowserContext = this._object as CRBrowserContext;
|
const crBrowserContext = this._object as CRBrowserContext;
|
||||||
return { session: new CDPSessionDispatcher(this._scope, await crBrowserContext.newCDPSession((params.page as PageDispatcher)._object)) };
|
return { session: new CDPSessionDispatcher(this._scope, await crBrowserContext.newCDPSession((params.page as PageDispatcher)._object)) };
|
||||||
}
|
}
|
||||||
|
|
||||||
async setTerminalSizeNoReply(params: channels.BrowserContextSetTerminalSizeNoReplyParams): Promise<void> {
|
|
||||||
this._context.terminalSize = params;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -255,6 +255,7 @@ export type BrowserTypeLaunchResult = {
|
||||||
};
|
};
|
||||||
export type BrowserTypeLaunchPersistentContextParams = {
|
export type BrowserTypeLaunchPersistentContextParams = {
|
||||||
userDataDir: string,
|
userDataDir: string,
|
||||||
|
sdkLanguage: string,
|
||||||
executablePath?: string,
|
executablePath?: string,
|
||||||
args?: string[],
|
args?: string[],
|
||||||
ignoreAllDefaultArgs?: boolean,
|
ignoreAllDefaultArgs?: boolean,
|
||||||
|
|
@ -384,6 +385,7 @@ export type BrowserTypeLaunchPersistentContextResult = {
|
||||||
context: BrowserContextChannel,
|
context: BrowserContextChannel,
|
||||||
};
|
};
|
||||||
export type BrowserTypeConnectOverCDPParams = {
|
export type BrowserTypeConnectOverCDPParams = {
|
||||||
|
sdkLanguage: string,
|
||||||
wsEndpoint: string,
|
wsEndpoint: string,
|
||||||
slowMo?: number,
|
slowMo?: number,
|
||||||
timeout?: number,
|
timeout?: number,
|
||||||
|
|
@ -415,6 +417,7 @@ export type BrowserCloseParams = {};
|
||||||
export type BrowserCloseOptions = {};
|
export type BrowserCloseOptions = {};
|
||||||
export type BrowserCloseResult = void;
|
export type BrowserCloseResult = void;
|
||||||
export type BrowserNewContextParams = {
|
export type BrowserNewContextParams = {
|
||||||
|
sdkLanguage: string,
|
||||||
noDefaultViewport?: boolean,
|
noDefaultViewport?: boolean,
|
||||||
viewport?: {
|
viewport?: {
|
||||||
width: number,
|
width: number,
|
||||||
|
|
@ -556,8 +559,6 @@ export interface BrowserContextChannel extends Channel {
|
||||||
on(event: 'close', callback: (params: BrowserContextCloseEvent) => void): this;
|
on(event: 'close', callback: (params: BrowserContextCloseEvent) => void): this;
|
||||||
on(event: 'page', callback: (params: BrowserContextPageEvent) => void): this;
|
on(event: 'page', callback: (params: BrowserContextPageEvent) => void): this;
|
||||||
on(event: 'route', callback: (params: BrowserContextRouteEvent) => void): this;
|
on(event: 'route', callback: (params: BrowserContextRouteEvent) => void): this;
|
||||||
on(event: 'stdout', callback: (params: BrowserContextStdoutEvent) => void): this;
|
|
||||||
on(event: 'stderr', callback: (params: BrowserContextStderrEvent) => void): this;
|
|
||||||
on(event: 'crBackgroundPage', callback: (params: BrowserContextCrBackgroundPageEvent) => void): this;
|
on(event: 'crBackgroundPage', callback: (params: BrowserContextCrBackgroundPageEvent) => void): this;
|
||||||
on(event: 'crServiceWorker', callback: (params: BrowserContextCrServiceWorkerEvent) => void): this;
|
on(event: 'crServiceWorker', callback: (params: BrowserContextCrServiceWorkerEvent) => void): this;
|
||||||
addCookies(params: BrowserContextAddCookiesParams, metadata?: Metadata): Promise<BrowserContextAddCookiesResult>;
|
addCookies(params: BrowserContextAddCookiesParams, metadata?: Metadata): Promise<BrowserContextAddCookiesResult>;
|
||||||
|
|
@ -580,7 +581,6 @@ export interface BrowserContextChannel extends Channel {
|
||||||
pause(params?: BrowserContextPauseParams, metadata?: Metadata): Promise<BrowserContextPauseResult>;
|
pause(params?: BrowserContextPauseParams, metadata?: Metadata): Promise<BrowserContextPauseResult>;
|
||||||
recorderSupplementEnable(params: BrowserContextRecorderSupplementEnableParams, metadata?: Metadata): Promise<BrowserContextRecorderSupplementEnableResult>;
|
recorderSupplementEnable(params: BrowserContextRecorderSupplementEnableParams, metadata?: Metadata): Promise<BrowserContextRecorderSupplementEnableResult>;
|
||||||
crNewCDPSession(params: BrowserContextCrNewCDPSessionParams, metadata?: Metadata): Promise<BrowserContextCrNewCDPSessionResult>;
|
crNewCDPSession(params: BrowserContextCrNewCDPSessionParams, metadata?: Metadata): Promise<BrowserContextCrNewCDPSessionResult>;
|
||||||
setTerminalSizeNoReply(params: BrowserContextSetTerminalSizeNoReplyParams, metadata?: Metadata): Promise<BrowserContextSetTerminalSizeNoReplyResult>;
|
|
||||||
}
|
}
|
||||||
export type BrowserContextBindingCallEvent = {
|
export type BrowserContextBindingCallEvent = {
|
||||||
binding: BindingCallChannel,
|
binding: BindingCallChannel,
|
||||||
|
|
@ -593,12 +593,6 @@ export type BrowserContextRouteEvent = {
|
||||||
route: RouteChannel,
|
route: RouteChannel,
|
||||||
request: RequestChannel,
|
request: RequestChannel,
|
||||||
};
|
};
|
||||||
export type BrowserContextStdoutEvent = {
|
|
||||||
data: Binary,
|
|
||||||
};
|
|
||||||
export type BrowserContextStderrEvent = {
|
|
||||||
data: Binary,
|
|
||||||
};
|
|
||||||
export type BrowserContextCrBackgroundPageEvent = {
|
export type BrowserContextCrBackgroundPageEvent = {
|
||||||
page: PageChannel,
|
page: PageChannel,
|
||||||
};
|
};
|
||||||
|
|
@ -731,22 +725,21 @@ export type BrowserContextPauseParams = {};
|
||||||
export type BrowserContextPauseOptions = {};
|
export type BrowserContextPauseOptions = {};
|
||||||
export type BrowserContextPauseResult = void;
|
export type BrowserContextPauseResult = void;
|
||||||
export type BrowserContextRecorderSupplementEnableParams = {
|
export type BrowserContextRecorderSupplementEnableParams = {
|
||||||
language: string,
|
language?: string,
|
||||||
startRecording?: boolean,
|
startRecording?: boolean,
|
||||||
launchOptions?: any,
|
launchOptions?: any,
|
||||||
contextOptions?: any,
|
contextOptions?: any,
|
||||||
device?: string,
|
device?: string,
|
||||||
saveStorage?: string,
|
saveStorage?: string,
|
||||||
terminal?: boolean,
|
|
||||||
outputFile?: string,
|
outputFile?: string,
|
||||||
};
|
};
|
||||||
export type BrowserContextRecorderSupplementEnableOptions = {
|
export type BrowserContextRecorderSupplementEnableOptions = {
|
||||||
|
language?: string,
|
||||||
startRecording?: boolean,
|
startRecording?: boolean,
|
||||||
launchOptions?: any,
|
launchOptions?: any,
|
||||||
contextOptions?: any,
|
contextOptions?: any,
|
||||||
device?: string,
|
device?: string,
|
||||||
saveStorage?: string,
|
saveStorage?: string,
|
||||||
terminal?: boolean,
|
|
||||||
outputFile?: string,
|
outputFile?: string,
|
||||||
};
|
};
|
||||||
export type BrowserContextRecorderSupplementEnableResult = void;
|
export type BrowserContextRecorderSupplementEnableResult = void;
|
||||||
|
|
@ -759,15 +752,6 @@ export type BrowserContextCrNewCDPSessionOptions = {
|
||||||
export type BrowserContextCrNewCDPSessionResult = {
|
export type BrowserContextCrNewCDPSessionResult = {
|
||||||
session: CDPSessionChannel,
|
session: CDPSessionChannel,
|
||||||
};
|
};
|
||||||
export type BrowserContextSetTerminalSizeNoReplyParams = {
|
|
||||||
rows?: number,
|
|
||||||
columns?: number,
|
|
||||||
};
|
|
||||||
export type BrowserContextSetTerminalSizeNoReplyOptions = {
|
|
||||||
rows?: number,
|
|
||||||
columns?: number,
|
|
||||||
};
|
|
||||||
export type BrowserContextSetTerminalSizeNoReplyResult = void;
|
|
||||||
|
|
||||||
// ----------- Page -----------
|
// ----------- Page -----------
|
||||||
export type PageInitializer = {
|
export type PageInitializer = {
|
||||||
|
|
@ -2482,6 +2466,7 @@ export interface ElectronChannel extends Channel {
|
||||||
launch(params: ElectronLaunchParams, metadata?: Metadata): Promise<ElectronLaunchResult>;
|
launch(params: ElectronLaunchParams, metadata?: Metadata): Promise<ElectronLaunchResult>;
|
||||||
}
|
}
|
||||||
export type ElectronLaunchParams = {
|
export type ElectronLaunchParams = {
|
||||||
|
sdkLanguage: string,
|
||||||
executablePath?: string,
|
executablePath?: string,
|
||||||
args?: string[],
|
args?: string[],
|
||||||
cwd?: string,
|
cwd?: string,
|
||||||
|
|
@ -2783,6 +2768,7 @@ export type AndroidDeviceInputDragOptions = {
|
||||||
};
|
};
|
||||||
export type AndroidDeviceInputDragResult = void;
|
export type AndroidDeviceInputDragResult = void;
|
||||||
export type AndroidDeviceLaunchBrowserParams = {
|
export type AndroidDeviceLaunchBrowserParams = {
|
||||||
|
sdkLanguage: string,
|
||||||
pkg?: string,
|
pkg?: string,
|
||||||
ignoreHTTPSErrors?: boolean,
|
ignoreHTTPSErrors?: boolean,
|
||||||
javaScriptEnabled?: boolean,
|
javaScriptEnabled?: boolean,
|
||||||
|
|
@ -2918,6 +2904,7 @@ export type AndroidDeviceSetDefaultTimeoutNoReplyOptions = {
|
||||||
};
|
};
|
||||||
export type AndroidDeviceSetDefaultTimeoutNoReplyResult = void;
|
export type AndroidDeviceSetDefaultTimeoutNoReplyResult = void;
|
||||||
export type AndroidDeviceConnectToWebViewParams = {
|
export type AndroidDeviceConnectToWebViewParams = {
|
||||||
|
sdkLanguage: string,
|
||||||
pid: number,
|
pid: number,
|
||||||
};
|
};
|
||||||
export type AndroidDeviceConnectToWebViewOptions = {
|
export type AndroidDeviceConnectToWebViewOptions = {
|
||||||
|
|
|
||||||
|
|
@ -313,6 +313,7 @@ BrowserType:
|
||||||
launchPersistentContext:
|
launchPersistentContext:
|
||||||
parameters:
|
parameters:
|
||||||
userDataDir: string
|
userDataDir: string
|
||||||
|
sdkLanguage: string
|
||||||
executablePath: string?
|
executablePath: string?
|
||||||
args:
|
args:
|
||||||
type: array?
|
type: array?
|
||||||
|
|
@ -401,6 +402,7 @@ BrowserType:
|
||||||
|
|
||||||
connectOverCDP:
|
connectOverCDP:
|
||||||
parameters:
|
parameters:
|
||||||
|
sdkLanguage: string
|
||||||
wsEndpoint: string
|
wsEndpoint: string
|
||||||
slowMo: number?
|
slowMo: number?
|
||||||
timeout: number?
|
timeout: number?
|
||||||
|
|
@ -421,6 +423,7 @@ Browser:
|
||||||
|
|
||||||
newContext:
|
newContext:
|
||||||
parameters:
|
parameters:
|
||||||
|
sdkLanguage: string
|
||||||
noDefaultViewport: boolean?
|
noDefaultViewport: boolean?
|
||||||
viewport:
|
viewport:
|
||||||
type: object?
|
type: object?
|
||||||
|
|
@ -623,13 +626,12 @@ BrowserContext:
|
||||||
recorderSupplementEnable:
|
recorderSupplementEnable:
|
||||||
experimental: True
|
experimental: True
|
||||||
parameters:
|
parameters:
|
||||||
language: string
|
language: string?
|
||||||
startRecording: boolean?
|
startRecording: boolean?
|
||||||
launchOptions: json?
|
launchOptions: json?
|
||||||
contextOptions: json?
|
contextOptions: json?
|
||||||
device: string?
|
device: string?
|
||||||
saveStorage: string?
|
saveStorage: string?
|
||||||
terminal: boolean?
|
|
||||||
outputFile: string?
|
outputFile: string?
|
||||||
|
|
||||||
crNewCDPSession:
|
crNewCDPSession:
|
||||||
|
|
@ -638,11 +640,6 @@ BrowserContext:
|
||||||
returns:
|
returns:
|
||||||
session: CDPSession
|
session: CDPSession
|
||||||
|
|
||||||
setTerminalSizeNoReply:
|
|
||||||
parameters:
|
|
||||||
rows: number?
|
|
||||||
columns: number?
|
|
||||||
|
|
||||||
events:
|
events:
|
||||||
|
|
||||||
bindingCall:
|
bindingCall:
|
||||||
|
|
@ -660,14 +657,6 @@ BrowserContext:
|
||||||
route: Route
|
route: Route
|
||||||
request: Request
|
request: Request
|
||||||
|
|
||||||
stdout:
|
|
||||||
parameters:
|
|
||||||
data: binary
|
|
||||||
|
|
||||||
stderr:
|
|
||||||
parameters:
|
|
||||||
data: binary
|
|
||||||
|
|
||||||
crBackgroundPage:
|
crBackgroundPage:
|
||||||
parameters:
|
parameters:
|
||||||
page: Page
|
page: Page
|
||||||
|
|
@ -2106,6 +2095,7 @@ Electron:
|
||||||
|
|
||||||
launch:
|
launch:
|
||||||
parameters:
|
parameters:
|
||||||
|
sdkLanguage: string
|
||||||
executablePath: string?
|
executablePath: string?
|
||||||
args:
|
args:
|
||||||
type: array?
|
type: array?
|
||||||
|
|
@ -2322,6 +2312,7 @@ AndroidDevice:
|
||||||
|
|
||||||
launchBrowser:
|
launchBrowser:
|
||||||
parameters:
|
parameters:
|
||||||
|
sdkLanguage: string
|
||||||
pkg: string?
|
pkg: string?
|
||||||
ignoreHTTPSErrors: boolean?
|
ignoreHTTPSErrors: boolean?
|
||||||
javaScriptEnabled: boolean?
|
javaScriptEnabled: boolean?
|
||||||
|
|
@ -2415,6 +2406,7 @@ AndroidDevice:
|
||||||
|
|
||||||
connectToWebView:
|
connectToWebView:
|
||||||
parameters:
|
parameters:
|
||||||
|
sdkLanguage: string
|
||||||
pid: number
|
pid: number
|
||||||
returns:
|
returns:
|
||||||
context: BrowserContext
|
context: BrowserContext
|
||||||
|
|
|
||||||
|
|
@ -168,6 +168,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||||
});
|
});
|
||||||
scheme.BrowserTypeLaunchPersistentContextParams = tObject({
|
scheme.BrowserTypeLaunchPersistentContextParams = tObject({
|
||||||
userDataDir: tString,
|
userDataDir: tString,
|
||||||
|
sdkLanguage: tString,
|
||||||
executablePath: tOptional(tString),
|
executablePath: tOptional(tString),
|
||||||
args: tOptional(tArray(tString)),
|
args: tOptional(tArray(tString)),
|
||||||
ignoreAllDefaultArgs: tOptional(tBoolean),
|
ignoreAllDefaultArgs: tOptional(tBoolean),
|
||||||
|
|
@ -231,12 +232,14 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
scheme.BrowserTypeConnectOverCDPParams = tObject({
|
scheme.BrowserTypeConnectOverCDPParams = tObject({
|
||||||
|
sdkLanguage: tString,
|
||||||
wsEndpoint: tString,
|
wsEndpoint: tString,
|
||||||
slowMo: tOptional(tNumber),
|
slowMo: tOptional(tNumber),
|
||||||
timeout: tOptional(tNumber),
|
timeout: tOptional(tNumber),
|
||||||
});
|
});
|
||||||
scheme.BrowserCloseParams = tOptional(tObject({}));
|
scheme.BrowserCloseParams = tOptional(tObject({}));
|
||||||
scheme.BrowserNewContextParams = tObject({
|
scheme.BrowserNewContextParams = tObject({
|
||||||
|
sdkLanguage: tString,
|
||||||
noDefaultViewport: tOptional(tBoolean),
|
noDefaultViewport: tOptional(tBoolean),
|
||||||
viewport: tOptional(tObject({
|
viewport: tOptional(tObject({
|
||||||
width: tNumber,
|
width: tNumber,
|
||||||
|
|
@ -349,22 +352,17 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||||
scheme.BrowserContextStorageStateParams = tOptional(tObject({}));
|
scheme.BrowserContextStorageStateParams = tOptional(tObject({}));
|
||||||
scheme.BrowserContextPauseParams = tOptional(tObject({}));
|
scheme.BrowserContextPauseParams = tOptional(tObject({}));
|
||||||
scheme.BrowserContextRecorderSupplementEnableParams = tObject({
|
scheme.BrowserContextRecorderSupplementEnableParams = tObject({
|
||||||
language: tString,
|
language: tOptional(tString),
|
||||||
startRecording: tOptional(tBoolean),
|
startRecording: tOptional(tBoolean),
|
||||||
launchOptions: tOptional(tAny),
|
launchOptions: tOptional(tAny),
|
||||||
contextOptions: tOptional(tAny),
|
contextOptions: tOptional(tAny),
|
||||||
device: tOptional(tString),
|
device: tOptional(tString),
|
||||||
saveStorage: tOptional(tString),
|
saveStorage: tOptional(tString),
|
||||||
terminal: tOptional(tBoolean),
|
|
||||||
outputFile: tOptional(tString),
|
outputFile: tOptional(tString),
|
||||||
});
|
});
|
||||||
scheme.BrowserContextCrNewCDPSessionParams = tObject({
|
scheme.BrowserContextCrNewCDPSessionParams = tObject({
|
||||||
page: tChannel('Page'),
|
page: tChannel('Page'),
|
||||||
});
|
});
|
||||||
scheme.BrowserContextSetTerminalSizeNoReplyParams = tObject({
|
|
||||||
rows: tOptional(tNumber),
|
|
||||||
columns: tOptional(tNumber),
|
|
||||||
});
|
|
||||||
scheme.PageSetDefaultNavigationTimeoutNoReplyParams = tObject({
|
scheme.PageSetDefaultNavigationTimeoutNoReplyParams = tObject({
|
||||||
timeout: tNumber,
|
timeout: tNumber,
|
||||||
});
|
});
|
||||||
|
|
@ -925,6 +923,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||||
});
|
});
|
||||||
scheme.CDPSessionDetachParams = tOptional(tObject({}));
|
scheme.CDPSessionDetachParams = tOptional(tObject({}));
|
||||||
scheme.ElectronLaunchParams = tObject({
|
scheme.ElectronLaunchParams = tObject({
|
||||||
|
sdkLanguage: tString,
|
||||||
executablePath: tOptional(tString),
|
executablePath: tOptional(tString),
|
||||||
args: tOptional(tArray(tString)),
|
args: tOptional(tArray(tString)),
|
||||||
cwd: tOptional(tString),
|
cwd: tOptional(tString),
|
||||||
|
|
@ -1030,6 +1029,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||||
steps: tNumber,
|
steps: tNumber,
|
||||||
});
|
});
|
||||||
scheme.AndroidDeviceLaunchBrowserParams = tObject({
|
scheme.AndroidDeviceLaunchBrowserParams = tObject({
|
||||||
|
sdkLanguage: tString,
|
||||||
pkg: tOptional(tString),
|
pkg: tOptional(tString),
|
||||||
ignoreHTTPSErrors: tOptional(tBoolean),
|
ignoreHTTPSErrors: tOptional(tBoolean),
|
||||||
javaScriptEnabled: tOptional(tBoolean),
|
javaScriptEnabled: tOptional(tBoolean),
|
||||||
|
|
@ -1093,6 +1093,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||||
timeout: tNumber,
|
timeout: tNumber,
|
||||||
});
|
});
|
||||||
scheme.AndroidDeviceConnectToWebViewParams = tObject({
|
scheme.AndroidDeviceConnectToWebViewParams = tObject({
|
||||||
|
sdkLanguage: tString,
|
||||||
pid: tNumber,
|
pid: tNumber,
|
||||||
});
|
});
|
||||||
scheme.AndroidDeviceCloseParams = tOptional(tObject({}));
|
scheme.AndroidDeviceCloseParams = tOptional(tObject({}));
|
||||||
|
|
|
||||||
|
|
@ -233,7 +233,7 @@ export class AndroidDevice extends SdkObject {
|
||||||
this.emit(AndroidDevice.Events.Closed);
|
this.emit(AndroidDevice.Events.Closed);
|
||||||
}
|
}
|
||||||
|
|
||||||
async launchBrowser(pkg: string = 'com.android.chrome', options: types.BrowserContextOptions = {}): Promise<BrowserContext> {
|
async launchBrowser(pkg: string = 'com.android.chrome', options: types.BrowserContextOptions): Promise<BrowserContext> {
|
||||||
debug('pw:android')('Force-stopping', pkg);
|
debug('pw:android')('Force-stopping', pkg);
|
||||||
await this._backend.runCommand(`shell:am force-stop ${pkg}`);
|
await this._backend.runCommand(`shell:am force-stop ${pkg}`);
|
||||||
|
|
||||||
|
|
@ -245,14 +245,14 @@ export class AndroidDevice extends SdkObject {
|
||||||
return await this._connectToBrowser(socketName, options);
|
return await this._connectToBrowser(socketName, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async connectToWebView(pid: number): Promise<BrowserContext> {
|
async connectToWebView(pid: number, sdkLanguage: string): Promise<BrowserContext> {
|
||||||
const webView = this._webViews.get(pid);
|
const webView = this._webViews.get(pid);
|
||||||
if (!webView)
|
if (!webView)
|
||||||
throw new Error('WebView has been closed');
|
throw new Error('WebView has been closed');
|
||||||
return await this._connectToBrowser(`webview_devtools_remote_${pid}`);
|
return await this._connectToBrowser(`webview_devtools_remote_${pid}`, { sdkLanguage });
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _connectToBrowser(socketName: string, options: types.BrowserContextOptions = {}): Promise<BrowserContext> {
|
private async _connectToBrowser(socketName: string, options: types.BrowserContextOptions): Promise<BrowserContext> {
|
||||||
const socket = await this._waitForLocalAbstract(socketName);
|
const socket = await this._waitForLocalAbstract(socketName);
|
||||||
const androidBrowser = new AndroidBrowser(this, socket);
|
const androidBrowser = new AndroidBrowser(this, socket);
|
||||||
await androidBrowser._init();
|
await androidBrowser._init();
|
||||||
|
|
|
||||||
|
|
@ -33,11 +33,10 @@ export interface BrowserProcess {
|
||||||
|
|
||||||
export type PlaywrightOptions = {
|
export type PlaywrightOptions = {
|
||||||
registry: registry.Registry,
|
registry: registry.Registry,
|
||||||
isInternal: boolean,
|
|
||||||
rootSdkObject: SdkObject,
|
rootSdkObject: SdkObject,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BrowserOptions = PlaywrightOptions & types.UIOptions & {
|
export type BrowserOptions = PlaywrightOptions & {
|
||||||
name: string,
|
name: string,
|
||||||
isChromium: boolean,
|
isChromium: boolean,
|
||||||
downloadsPath?: string,
|
downloadsPath?: string,
|
||||||
|
|
@ -47,6 +46,7 @@ export type BrowserOptions = PlaywrightOptions & types.UIOptions & {
|
||||||
proxy?: ProxySettings,
|
proxy?: ProxySettings,
|
||||||
protocolLogger: types.ProtocolLogger,
|
protocolLogger: types.ProtocolLogger,
|
||||||
browserLogsCollector: RecentLogsCollector,
|
browserLogsCollector: RecentLogsCollector,
|
||||||
|
slowMo?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export abstract class Browser extends SdkObject {
|
export abstract class Browser extends SdkObject {
|
||||||
|
|
@ -66,12 +66,12 @@ export abstract class Browser extends SdkObject {
|
||||||
this.options = options;
|
this.options = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract newContext(options?: types.BrowserContextOptions): Promise<BrowserContext>;
|
abstract newContext(options: types.BrowserContextOptions): Promise<BrowserContext>;
|
||||||
abstract contexts(): BrowserContext[];
|
abstract contexts(): BrowserContext[];
|
||||||
abstract isConnected(): boolean;
|
abstract isConnected(): boolean;
|
||||||
abstract version(): string;
|
abstract version(): string;
|
||||||
|
|
||||||
async newPage(options?: types.BrowserContextOptions): Promise<Page> {
|
async newPage(options: types.BrowserContextOptions): Promise<Page> {
|
||||||
const context = await this.newContext(options);
|
const context = await this.newContext(options);
|
||||||
const page = await context.newPage();
|
const page = await context.newPage();
|
||||||
page._ownedContext = context;
|
page._ownedContext = context;
|
||||||
|
|
@ -131,4 +131,3 @@ export abstract class Browser extends SdkObject {
|
||||||
await new Promise(x => this.once(Browser.Events.Disconnected, x));
|
await new Promise(x => this.once(Browser.Events.Disconnected, x));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,8 +63,6 @@ export abstract class BrowserContext extends SdkObject {
|
||||||
Page: 'page',
|
Page: 'page',
|
||||||
VideoStarted: 'videostarted',
|
VideoStarted: 'videostarted',
|
||||||
BeforeClose: 'beforeclose',
|
BeforeClose: 'beforeclose',
|
||||||
StdOut: 'stdout',
|
|
||||||
StdErr: 'stderr',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
readonly _timeoutSettings = new TimeoutSettings();
|
readonly _timeoutSettings = new TimeoutSettings();
|
||||||
|
|
@ -81,7 +79,6 @@ export abstract class BrowserContext extends SdkObject {
|
||||||
readonly _browserContextId: string | undefined;
|
readonly _browserContextId: string | undefined;
|
||||||
private _selectors?: Selectors;
|
private _selectors?: Selectors;
|
||||||
private _origins = new Set<string>();
|
private _origins = new Set<string>();
|
||||||
terminalSize: { rows?: number, columns?: number } = {};
|
|
||||||
|
|
||||||
constructor(browser: Browser, options: types.BrowserContextOptions, browserContextId: string | undefined) {
|
constructor(browser: Browser, options: types.BrowserContextOptions, browserContextId: string | undefined) {
|
||||||
super(browser);
|
super(browser);
|
||||||
|
|
@ -281,7 +278,7 @@ export abstract class BrowserContext extends SdkObject {
|
||||||
await this._browser.close();
|
await this._browser.close();
|
||||||
|
|
||||||
// Bookkeeping.
|
// Bookkeeping.
|
||||||
await this.instrumentation.onContextWillDestroy(this);
|
await this.instrumentation.onContextDidDestroy(this);
|
||||||
this._didCloseInternal();
|
this._didCloseInternal();
|
||||||
}
|
}
|
||||||
await this._closePromise;
|
await this._closePromise;
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ export abstract class BrowserType extends SdkObject {
|
||||||
return browser;
|
return browser;
|
||||||
}
|
}
|
||||||
|
|
||||||
async launchPersistentContext(metadata: CallMetadata, userDataDir?: string, options: types.LaunchPersistentOptions = {}): Promise<BrowserContext> {
|
async launchPersistentContext(metadata: CallMetadata, userDataDir: string, options: types.LaunchPersistentOptions): Promise<BrowserContext> {
|
||||||
options = validateLaunchOptions(options);
|
options = validateLaunchOptions(options);
|
||||||
const controller = new ProgressController(metadata, this);
|
const controller = new ProgressController(metadata, this);
|
||||||
const persistent: types.BrowserContextOptions = options;
|
const persistent: types.BrowserContextOptions = options;
|
||||||
|
|
@ -223,7 +223,7 @@ export abstract class BrowserType extends SdkObject {
|
||||||
return { browserProcess, downloadsPath, transport };
|
return { browserProcess, downloadsPath, transport };
|
||||||
}
|
}
|
||||||
|
|
||||||
async connectOverCDP(metadata: CallMetadata, wsEndpoint: string, uiOptions: types.UIOptions, timeout?: number): Promise<Browser> {
|
async connectOverCDP(metadata: CallMetadata, wsEndpoint: string, options: { slowMo?: number, sdkLanguage: string }, timeout?: number): Promise<Browser> {
|
||||||
throw new Error('CDP connections are only supported by Chromium');
|
throw new Error('CDP connections are only supported by Chromium');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ export class Chromium extends BrowserType {
|
||||||
this._devtools = this._createDevTools();
|
this._devtools = this._createDevTools();
|
||||||
}
|
}
|
||||||
|
|
||||||
async connectOverCDP(metadata: CallMetadata, wsEndpoint: string, uiOptions: types.UIOptions, timeout?: number) {
|
async connectOverCDP(metadata: CallMetadata, wsEndpoint: string, options: { slowMo?: number, sdkLanguage: string }, timeout?: number) {
|
||||||
const controller = new ProgressController(metadata, this);
|
const controller = new ProgressController(metadata, this);
|
||||||
controller.setLogName('browser');
|
controller.setLogName('browser');
|
||||||
const browserLogsCollector = new RecentLogsCollector();
|
const browserLogsCollector = new RecentLogsCollector();
|
||||||
|
|
@ -58,10 +58,10 @@ export class Chromium extends BrowserType {
|
||||||
};
|
};
|
||||||
const browserOptions: BrowserOptions = {
|
const browserOptions: BrowserOptions = {
|
||||||
...this._playwrightOptions,
|
...this._playwrightOptions,
|
||||||
...uiOptions,
|
slowMo: options.slowMo,
|
||||||
name: 'chromium',
|
name: 'chromium',
|
||||||
isChromium: true,
|
isChromium: true,
|
||||||
persistent: { noDefaultViewport: true },
|
persistent: { sdkLanguage: options.sdkLanguage, noDefaultViewport: true },
|
||||||
browserProcess,
|
browserProcess,
|
||||||
protocolLogger: helper.debugProtocolLogger(),
|
protocolLogger: helper.debugProtocolLogger(),
|
||||||
browserLogsCollector,
|
browserLogsCollector,
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ export class CRBrowser extends Browser {
|
||||||
this._session.on('Target.detachedFromTarget', this._onDetachedFromTarget.bind(this));
|
this._session.on('Target.detachedFromTarget', this._onDetachedFromTarget.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
async newContext(options: types.BrowserContextOptions = {}): Promise<BrowserContext> {
|
async newContext(options: types.BrowserContextOptions): Promise<BrowserContext> {
|
||||||
validateBrowserContextOptions(options, this.options);
|
validateBrowserContextOptions(options, this.options);
|
||||||
const { browserContextId } = await this._session.send('Target.createBrowserContext', {
|
const { browserContextId } = await this._session.send('Target.createBrowserContext', {
|
||||||
disposeOnDetach: true,
|
disposeOnDetach: true,
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ import { RecentLogsCollector } from '../../utils/debugLogger';
|
||||||
import { internalCallMetadata, SdkObject } from '../instrumentation';
|
import { internalCallMetadata, SdkObject } from '../instrumentation';
|
||||||
|
|
||||||
export type ElectronLaunchOptionsBase = {
|
export type ElectronLaunchOptionsBase = {
|
||||||
|
sdkLanguage: string,
|
||||||
executablePath?: string,
|
executablePath?: string,
|
||||||
args?: string[],
|
args?: string[],
|
||||||
cwd?: string,
|
cwd?: string,
|
||||||
|
|
@ -130,7 +131,7 @@ export class Electron extends SdkObject {
|
||||||
this._playwrightOptions = playwrightOptions;
|
this._playwrightOptions = playwrightOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
async launch(options: ElectronLaunchOptionsBase = {}): Promise<ElectronApplication> {
|
async launch(options: ElectronLaunchOptionsBase): Promise<ElectronApplication> {
|
||||||
const {
|
const {
|
||||||
args = [],
|
args = [],
|
||||||
} = options;
|
} = options;
|
||||||
|
|
@ -182,7 +183,7 @@ export class Electron extends SdkObject {
|
||||||
name: 'electron',
|
name: 'electron',
|
||||||
isChromium: true,
|
isChromium: true,
|
||||||
headful: true,
|
headful: true,
|
||||||
persistent: { noDefaultViewport: true },
|
persistent: { sdkLanguage: options.sdkLanguage, noDefaultViewport: true },
|
||||||
browserProcess,
|
browserProcess,
|
||||||
protocolLogger: helper.debugProtocolLogger(),
|
protocolLogger: helper.debugProtocolLogger(),
|
||||||
browserLogsCollector,
|
browserLogsCollector,
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ export class FFBrowser extends Browser {
|
||||||
return !this._connection._closed;
|
return !this._connection._closed;
|
||||||
}
|
}
|
||||||
|
|
||||||
async newContext(options: types.BrowserContextOptions = {}): Promise<BrowserContext> {
|
async newContext(options: types.BrowserContextOptions): Promise<BrowserContext> {
|
||||||
validateBrowserContextOptions(options, this.options);
|
validateBrowserContextOptions(options, this.options);
|
||||||
if (options.isMobile)
|
if (options.isMobile)
|
||||||
throw new Error('options.isMobile is not supported in Firefox');
|
throw new Error('options.isMobile is not supported in Firefox');
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,6 @@ export class Playwright extends SdkObject {
|
||||||
const instrumentation = multiplexInstrumentation(listeners);
|
const instrumentation = multiplexInstrumentation(listeners);
|
||||||
super({ attribution: {}, instrumentation } as any);
|
super({ attribution: {}, instrumentation } as any);
|
||||||
this.options = {
|
this.options = {
|
||||||
isInternal,
|
|
||||||
registry: new Registry(path.join(__dirname, '..', '..')),
|
registry: new Registry(path.join(__dirname, '..', '..')),
|
||||||
rootSdkObject: this,
|
rootSdkObject: this,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -22,12 +22,8 @@ import { debugLogger } from '../../utils/debugLogger';
|
||||||
|
|
||||||
export class InspectorController implements InstrumentationListener {
|
export class InspectorController implements InstrumentationListener {
|
||||||
async onContextCreated(context: BrowserContext): Promise<void> {
|
async onContextCreated(context: BrowserContext): Promise<void> {
|
||||||
if (isDebugMode()) {
|
if (isDebugMode())
|
||||||
RecorderSupplement.getOrCreate(context, {
|
RecorderSupplement.getOrCreate(context);
|
||||||
language: process.env.PW_CLI_TARGET_LANG || 'javascript',
|
|
||||||
terminal: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onCallLog(logName: string, message: string): void {
|
onCallLog(logName: string, message: string): void {
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import * as querystring from 'querystring';
|
|
||||||
import * as hljs from '../../../third_party/highlightjs/highlightjs';
|
|
||||||
|
|
||||||
export interface RecorderOutput {
|
export interface RecorderOutput {
|
||||||
printLn(text: string): void;
|
printLn(text: string): void;
|
||||||
|
|
@ -111,79 +109,3 @@ export class FileOutput extends BufferedOutput implements RecorderOutput {
|
||||||
fs.writeFileSync(this._fileName, this.buffer());
|
fs.writeFileSync(this._fileName, this.buffer());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TerminalOutput implements RecorderOutput {
|
|
||||||
private _output: Writable;
|
|
||||||
private _language: string;
|
|
||||||
|
|
||||||
static create(output: Writable, language: string) {
|
|
||||||
if (process.stdout.columns)
|
|
||||||
return new TerminalOutput(output, language);
|
|
||||||
return new FlushingTerminalOutput(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(output: Writable, language: string) {
|
|
||||||
this._output = output;
|
|
||||||
this._language = language;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _highlight(text: string) {
|
|
||||||
let highlightedCode = hljs.highlight(this._language, text).value;
|
|
||||||
highlightedCode = querystring.unescape(highlightedCode);
|
|
||||||
highlightedCode = highlightedCode.replace(/<span class="hljs-keyword">/g, '\x1b[38;5;205m');
|
|
||||||
highlightedCode = highlightedCode.replace(/<span class="hljs-built_in">/g, '\x1b[38;5;220m');
|
|
||||||
highlightedCode = highlightedCode.replace(/<span class="hljs-literal">/g, '\x1b[38;5;159m');
|
|
||||||
highlightedCode = highlightedCode.replace(/<span class="hljs-title">/g, '');
|
|
||||||
highlightedCode = highlightedCode.replace(/<span class="hljs-number">/g, '\x1b[38;5;78m');
|
|
||||||
highlightedCode = highlightedCode.replace(/<span class="hljs-string">/g, '\x1b[38;5;130m');
|
|
||||||
highlightedCode = highlightedCode.replace(/<span class="hljs-comment">/g, '\x1b[38;5;23m');
|
|
||||||
highlightedCode = highlightedCode.replace(/<span class="hljs-subst">/g, '\x1b[38;5;242m');
|
|
||||||
highlightedCode = highlightedCode.replace(/<span class="hljs-function">/g, '');
|
|
||||||
highlightedCode = highlightedCode.replace(/<span class="hljs-params">/g, '');
|
|
||||||
highlightedCode = highlightedCode.replace(/<span class="hljs-attr">/g, '');
|
|
||||||
highlightedCode = highlightedCode.replace(/<\/span>/g, '\x1b[0m');
|
|
||||||
highlightedCode = highlightedCode.replace(/'/g, "'");
|
|
||||||
highlightedCode = highlightedCode.replace(/"/g, '"');
|
|
||||||
highlightedCode = highlightedCode.replace(/>/g, '>');
|
|
||||||
highlightedCode = highlightedCode.replace(/</g, '<');
|
|
||||||
highlightedCode = highlightedCode.replace(/&/g, '&');
|
|
||||||
return highlightedCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
printLn(text: string) {
|
|
||||||
// Split into lines for highlighter to not fail.
|
|
||||||
for (const line of text.split('\n'))
|
|
||||||
this._output.write(this._highlight(line) + '\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
popLn(text: string) {
|
|
||||||
const terminalWidth = this._output.columns();
|
|
||||||
for (const line of text.split('\n')) {
|
|
||||||
const terminalLines = ((line.length - 1) / terminalWidth | 0) + 1;
|
|
||||||
for (let i = 0; i < terminalLines; ++i)
|
|
||||||
this._output.write('\u001B[1A\u001B[2K');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
flush() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class FlushingTerminalOutput extends BufferedOutput implements RecorderOutput {
|
|
||||||
private _output: Writable
|
|
||||||
|
|
||||||
constructor(output: Writable) {
|
|
||||||
super();
|
|
||||||
this._output = output;
|
|
||||||
}
|
|
||||||
|
|
||||||
printLn(text: string) {
|
|
||||||
super.printLn(text);
|
|
||||||
this._output.write('-------------8<-------------\n');
|
|
||||||
this._output.write(this.buffer() + '\n');
|
|
||||||
this._output.write('-------------8<-------------\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
flush() {
|
|
||||||
this._output.write(this.buffer() + '\n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ import { ProgressController } from '../../progress';
|
||||||
import { createPlaywright } from '../../playwright';
|
import { createPlaywright } from '../../playwright';
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import { internalCallMetadata } from '../../instrumentation';
|
import { internalCallMetadata } from '../../instrumentation';
|
||||||
|
import { isUnderTest } from '../../../utils/utils';
|
||||||
|
import { BrowserContext } from '../../browserContext';
|
||||||
|
|
||||||
const readFileAsync = util.promisify(fs.readFile);
|
const readFileAsync = util.promisify(fs.readFile);
|
||||||
|
|
||||||
|
|
@ -90,14 +92,17 @@ export class RecorderApp extends EventEmitter {
|
||||||
await mainFrame.goto(internalCallMetadata(), 'https://playwright/index.html');
|
await mainFrame.goto(internalCallMetadata(), 'https://playwright/index.html');
|
||||||
}
|
}
|
||||||
|
|
||||||
static async open(): Promise<RecorderApp> {
|
static async open(inspectedContext: BrowserContext): Promise<RecorderApp> {
|
||||||
const recorderPlaywright = createPlaywright(true);
|
const recorderPlaywright = createPlaywright(true);
|
||||||
const context = await recorderPlaywright.chromium.launchPersistentContext(internalCallMetadata(), undefined, {
|
const context = await recorderPlaywright.chromium.launchPersistentContext(internalCallMetadata(), '', {
|
||||||
|
sdkLanguage: inspectedContext._options.sdkLanguage,
|
||||||
args: [
|
args: [
|
||||||
'--app=data:text/html,',
|
'--app=data:text/html,',
|
||||||
'--window-size=300,800',
|
'--window-size=600,600',
|
||||||
|
'--window-position=1280,10',
|
||||||
],
|
],
|
||||||
noDefaultViewport: true
|
noDefaultViewport: true,
|
||||||
|
headless: isUnderTest()
|
||||||
});
|
});
|
||||||
|
|
||||||
const controller = new ProgressController(internalCallMetadata(), context._browser);
|
const controller = new ProgressController(internalCallMetadata(), context._browser);
|
||||||
|
|
@ -127,6 +132,15 @@ export class RecorderApp extends EventEmitter {
|
||||||
await this._page.mainFrame()._evaluateExpression(((param: { text: string, language: string }) => {
|
await this._page.mainFrame()._evaluateExpression(((param: { text: string, language: string }) => {
|
||||||
window.playwrightSetSource(param);
|
window.playwrightSetSource(param);
|
||||||
}).toString(), true, { text, language }, 'main').catch(() => {});
|
}).toString(), true, { text, language }, 'main').catch(() => {});
|
||||||
|
|
||||||
|
// Testing harness for runCLI mode.
|
||||||
|
{
|
||||||
|
if (process.env.PWCLI_EXIT_FOR_TEST) {
|
||||||
|
process.stdout.write('\n-------------8<-------------\n');
|
||||||
|
process.stdout.write(text);
|
||||||
|
process.stdout.write('\n-------------8<-------------\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async bringToFront() {
|
async bringToFront() {
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ import { CSharpLanguageGenerator } from './recorder/csharp';
|
||||||
import { PythonLanguageGenerator } from './recorder/python';
|
import { PythonLanguageGenerator } from './recorder/python';
|
||||||
import * as recorderSource from '../../generated/recorderSource';
|
import * as recorderSource from '../../generated/recorderSource';
|
||||||
import * as consoleApiSource from '../../generated/consoleApiSource';
|
import * as consoleApiSource from '../../generated/consoleApiSource';
|
||||||
import { BufferedOutput, FileOutput, FlushingTerminalOutput, OutputMultiplexer, RecorderOutput, TerminalOutput, Writable } from './recorder/outputs';
|
import { BufferedOutput, FileOutput, OutputMultiplexer, RecorderOutput } from './recorder/outputs';
|
||||||
import { EventData, Mode, RecorderApp } from './recorder/recorderApp';
|
import { EventData, Mode, RecorderApp } from './recorder/recorderApp';
|
||||||
import { internalCallMetadata } from '../instrumentation';
|
import { internalCallMetadata } from '../instrumentation';
|
||||||
|
|
||||||
|
|
@ -51,7 +51,7 @@ export class RecorderSupplement {
|
||||||
private _highlighterType: string;
|
private _highlighterType: string;
|
||||||
private _params: channels.BrowserContextRecorderSupplementEnableParams;
|
private _params: channels.BrowserContextRecorderSupplementEnableParams;
|
||||||
|
|
||||||
static getOrCreate(context: BrowserContext, params: channels.BrowserContextRecorderSupplementEnableParams): Promise<RecorderSupplement> {
|
static getOrCreate(context: BrowserContext, params: channels.BrowserContextRecorderSupplementEnableParams = {}): Promise<RecorderSupplement> {
|
||||||
let recorderPromise = (context as any)[symbol] as Promise<RecorderSupplement>;
|
let recorderPromise = (context as any)[symbol] as Promise<RecorderSupplement>;
|
||||||
if (!recorderPromise) {
|
if (!recorderPromise) {
|
||||||
const recorder = new RecorderSupplement(context, params);
|
const recorder = new RecorderSupplement(context, params);
|
||||||
|
|
@ -66,22 +66,19 @@ export class RecorderSupplement {
|
||||||
this._params = params;
|
this._params = params;
|
||||||
this._mode = params.startRecording ? 'recording' : 'none';
|
this._mode = params.startRecording ? 'recording' : 'none';
|
||||||
let languageGenerator: LanguageGenerator;
|
let languageGenerator: LanguageGenerator;
|
||||||
switch (params.language) {
|
const language = params.language || context._options.sdkLanguage;
|
||||||
|
switch (language) {
|
||||||
case 'javascript': languageGenerator = new JavaScriptLanguageGenerator(); break;
|
case 'javascript': languageGenerator = new JavaScriptLanguageGenerator(); break;
|
||||||
case 'csharp': languageGenerator = new CSharpLanguageGenerator(); break;
|
case 'csharp': languageGenerator = new CSharpLanguageGenerator(); break;
|
||||||
case 'python':
|
case 'python':
|
||||||
case 'python-async': languageGenerator = new PythonLanguageGenerator(params.language === 'python-async'); break;
|
case 'python-async': languageGenerator = new PythonLanguageGenerator(params.language === 'python-async'); break;
|
||||||
default: throw new Error(`Invalid target: '${params.language}'`);
|
default: throw new Error(`Invalid target: '${params.language}'`);
|
||||||
}
|
}
|
||||||
let highlighterType = params.language;
|
let highlighterType = language;
|
||||||
if (highlighterType === 'python-async')
|
if (highlighterType === 'python-async')
|
||||||
highlighterType = 'python';
|
highlighterType = 'python';
|
||||||
|
|
||||||
const writable: Writable = {
|
const outputs: RecorderOutput[] = [];
|
||||||
write: (text: string) => context.emit(BrowserContext.Events.StdOut, text),
|
|
||||||
columns: () => context.terminalSize.columns || 80
|
|
||||||
};
|
|
||||||
const outputs: RecorderOutput[] = [params.terminal ? new TerminalOutput(writable, highlighterType) : new FlushingTerminalOutput(writable)];
|
|
||||||
this._highlighterType = highlighterType;
|
this._highlighterType = highlighterType;
|
||||||
this._bufferedOutput = new BufferedOutput(async text => {
|
this._bufferedOutput = new BufferedOutput(async text => {
|
||||||
if (this._recorderApp)
|
if (this._recorderApp)
|
||||||
|
|
@ -99,7 +96,7 @@ export class RecorderSupplement {
|
||||||
}
|
}
|
||||||
|
|
||||||
async install() {
|
async install() {
|
||||||
const recorderApp = await RecorderApp.open();
|
const recorderApp = await RecorderApp.open(this._context);
|
||||||
this._recorderApp = recorderApp;
|
this._recorderApp = recorderApp;
|
||||||
recorderApp.once('close', () => {
|
recorderApp.once('close', () => {
|
||||||
this._recorderApp = null;
|
this._recorderApp = null;
|
||||||
|
|
@ -153,10 +150,6 @@ export class RecorderSupplement {
|
||||||
await this._context.exposeBinding('_playwrightRecorderCommitAction', false,
|
await this._context.exposeBinding('_playwrightRecorderCommitAction', false,
|
||||||
(source: BindingSource, action: actions.Action) => this._generator.commitLastAction());
|
(source: BindingSource, action: actions.Action) => this._generator.commitLastAction());
|
||||||
|
|
||||||
await this._context.exposeBinding('_playwrightRecorderPrintSelector', false, (_, text) => {
|
|
||||||
this._context.emit(BrowserContext.Events.StdOut, `Selector: \x1b[38;5;130m${text}\x1b[0m\n`);
|
|
||||||
});
|
|
||||||
|
|
||||||
await this._context.exposeBinding('_playwrightRecorderState', false, () => {
|
await this._context.exposeBinding('_playwrightRecorderState', false, () => {
|
||||||
return { mode: this._mode };
|
return { mode: this._mode };
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -215,6 +215,7 @@ export type SetNetworkCookieParam = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BrowserContextOptions = {
|
export type BrowserContextOptions = {
|
||||||
|
sdkLanguage: string,
|
||||||
viewport?: Size,
|
viewport?: Size,
|
||||||
noDefaultViewport?: boolean,
|
noDefaultViewport?: boolean,
|
||||||
ignoreHTTPSErrors?: boolean,
|
ignoreHTTPSErrors?: boolean,
|
||||||
|
|
@ -248,7 +249,7 @@ export type BrowserContextOptions = {
|
||||||
|
|
||||||
export type EnvArray = { name: string, value: string }[];
|
export type EnvArray = { name: string, value: string }[];
|
||||||
|
|
||||||
type LaunchOptionsBase = UIOptions & {
|
type LaunchOptionsBase = {
|
||||||
executablePath?: string,
|
executablePath?: string,
|
||||||
args?: string[],
|
args?: string[],
|
||||||
ignoreDefaultArgs?: string[],
|
ignoreDefaultArgs?: string[],
|
||||||
|
|
@ -263,6 +264,7 @@ type LaunchOptionsBase = UIOptions & {
|
||||||
proxy?: ProxySettings,
|
proxy?: ProxySettings,
|
||||||
downloadsPath?: string,
|
downloadsPath?: string,
|
||||||
chromiumSandbox?: boolean,
|
chromiumSandbox?: boolean,
|
||||||
|
slowMo?: number;
|
||||||
};
|
};
|
||||||
export type LaunchOptions = LaunchOptionsBase & {
|
export type LaunchOptions = LaunchOptionsBase & {
|
||||||
firefoxUserPrefs?: { [key: string]: string | number | boolean },
|
firefoxUserPrefs?: { [key: string]: string | number | boolean },
|
||||||
|
|
@ -338,7 +340,3 @@ export type SetStorageState = {
|
||||||
cookies?: SetNetworkCookieParam[],
|
cookies?: SetNetworkCookieParam[],
|
||||||
origins?: OriginStorage[]
|
origins?: OriginStorage[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UIOptions = {
|
|
||||||
slowMo?: number;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ export class WKBrowser extends Browser {
|
||||||
this._didClose();
|
this._didClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
async newContext(options: types.BrowserContextOptions = {}): Promise<BrowserContext> {
|
async newContext(options: types.BrowserContextOptions): Promise<BrowserContext> {
|
||||||
validateBrowserContextOptions(options, this.options);
|
validateBrowserContextOptions(options, this.options);
|
||||||
const createOptions = options.proxy ? {
|
const createOptions = options.proxy ? {
|
||||||
proxyServer: options.proxy.server,
|
proxyServer: options.proxy.server,
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ declare global {
|
||||||
playwrightSetPaused: (paused: boolean) => void;
|
playwrightSetPaused: (paused: boolean) => void;
|
||||||
playwrightSetSource: (params: { text: string, language: string }) => void;
|
playwrightSetSource: (params: { text: string, language: string }) => void;
|
||||||
dispatch(data: any): Promise<void>;
|
dispatch(data: any): Promise<void>;
|
||||||
|
playwrightSourceEchoForTest?: (text: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -43,6 +44,8 @@ export const Recorder: React.FC<RecorderProps> = ({
|
||||||
window.playwrightSetMode = setMode;
|
window.playwrightSetMode = setMode;
|
||||||
window.playwrightSetSource = setSource;
|
window.playwrightSetSource = setSource;
|
||||||
window.playwrightSetPaused = setPaused;
|
window.playwrightSetPaused = setPaused;
|
||||||
|
if (window.playwrightSourceEchoForTest)
|
||||||
|
window.playwrightSourceEchoForTest(source.text).catch(e => {});
|
||||||
|
|
||||||
return <div className="recorder">
|
return <div className="recorder">
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,12 @@
|
||||||
|
|
||||||
import { folio } from './cli.fixtures';
|
import { folio } from './cli.fixtures';
|
||||||
import * as http from 'http';
|
import * as http from 'http';
|
||||||
import * as url from 'url';
|
|
||||||
|
|
||||||
const { it, describe, expect } = folio;
|
const { it, describe, expect } = folio;
|
||||||
|
|
||||||
describe('cli codegen', (test, { browserName, headful }) => {
|
describe('cli codegen', (suite, { mode, browserName, headful }) => {
|
||||||
test.fixme(browserName === 'firefox' && headful, 'Focus is off');
|
suite.fixme(browserName === 'firefox' && headful, 'Focus is off');
|
||||||
|
suite.skip(mode !== 'default');
|
||||||
}, () => {
|
}, () => {
|
||||||
it('should click', async ({ page, recorder }) => {
|
it('should click', async ({ page, recorder }) => {
|
||||||
await recorder.setContentAndWait(`<button onclick="console.log('click')">Submit</button>`);
|
await recorder.setContentAndWait(`<button onclick="console.log('click')">Submit</button>`);
|
||||||
|
|
@ -351,309 +351,4 @@ describe('cli codegen', (test, { browserName, headful }) => {
|
||||||
]);`);
|
]);`);
|
||||||
expect(page.url()).toContain('about:blank#foo');
|
expect(page.url()).toContain('about:blank#foo');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should contain open page', async ({ recorder }) => {
|
|
||||||
await recorder.setContentAndWait(``);
|
|
||||||
expect(recorder.output()).toContain(`const page = await context.newPage();`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should contain second page', async ({ contextWrapper, recorder }) => {
|
|
||||||
await recorder.setContentAndWait(``);
|
|
||||||
await contextWrapper.context.newPage();
|
|
||||||
await recorder.waitForOutput('page1');
|
|
||||||
expect(recorder.output()).toContain('const page1 = await context.newPage();');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should contain close page', async ({ contextWrapper, recorder }) => {
|
|
||||||
await recorder.setContentAndWait(``);
|
|
||||||
await contextWrapper.context.newPage();
|
|
||||||
await recorder.page.close();
|
|
||||||
await recorder.waitForOutput('page.close();');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not lead to an error if html gets clicked', async ({ contextWrapper, recorder }) => {
|
|
||||||
await recorder.setContentAndWait('');
|
|
||||||
await contextWrapper.context.newPage();
|
|
||||||
const errors: any[] = [];
|
|
||||||
recorder.page.on('pageerror', e => errors.push(e));
|
|
||||||
await recorder.page.evaluate(() => document.querySelector('body').remove());
|
|
||||||
const selector = await recorder.hoverOverElement('html');
|
|
||||||
expect(selector).toBe('html');
|
|
||||||
await recorder.page.close();
|
|
||||||
await recorder.waitForOutput('page.close();');
|
|
||||||
expect(errors.length).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should upload a single file', async ({ page, recorder }) => {
|
|
||||||
await recorder.setContentAndWait(`
|
|
||||||
<form>
|
|
||||||
<input type="file">
|
|
||||||
</form>
|
|
||||||
`);
|
|
||||||
|
|
||||||
await page.focus('input[type=file]');
|
|
||||||
await page.setInputFiles('input[type=file]', 'test/assets/file-to-upload.txt');
|
|
||||||
await page.click('input[type=file]');
|
|
||||||
|
|
||||||
await recorder.waitForOutput(`
|
|
||||||
// Upload file-to-upload.txt
|
|
||||||
await page.setInputFiles('input[type="file"]', 'file-to-upload.txt');`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should upload multiple files', async ({ page, recorder }) => {
|
|
||||||
await recorder.setContentAndWait(`
|
|
||||||
<form>
|
|
||||||
<input type="file" multiple>
|
|
||||||
</form>
|
|
||||||
`);
|
|
||||||
|
|
||||||
await page.focus('input[type=file]');
|
|
||||||
await page.setInputFiles('input[type=file]', ['test/assets/file-to-upload.txt', 'test/assets/file-to-upload-2.txt']);
|
|
||||||
await page.click('input[type=file]');
|
|
||||||
|
|
||||||
await recorder.waitForOutput(`
|
|
||||||
// Upload file-to-upload.txt, file-to-upload-2.txt
|
|
||||||
await page.setInputFiles('input[type="file"]', ['file-to-upload.txt', 'file-to-upload-2.txt']);`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should clear files', async ({ page, recorder }) => {
|
|
||||||
await recorder.setContentAndWait(`
|
|
||||||
<form>
|
|
||||||
<input type="file" multiple>
|
|
||||||
</form>
|
|
||||||
`);
|
|
||||||
await page.focus('input[type=file]');
|
|
||||||
await page.setInputFiles('input[type=file]', 'test/assets/file-to-upload.txt');
|
|
||||||
await page.setInputFiles('input[type=file]', []);
|
|
||||||
await page.click('input[type=file]');
|
|
||||||
|
|
||||||
await recorder.waitForOutput(`
|
|
||||||
// Clear selected files
|
|
||||||
await page.setInputFiles('input[type="file"]', []);`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should download files', (test, {browserName}) => {
|
|
||||||
test.fixme(browserName === 'webkit', 'Generated page.waitForNavigation next to page.waitForEvent(download)');
|
|
||||||
}, async ({ page, recorder, httpServer }) => {
|
|
||||||
httpServer.setHandler((req: http.IncomingMessage, res: http.ServerResponse) => {
|
|
||||||
const pathName = url.parse(req.url!).path;
|
|
||||||
if (pathName === '/download') {
|
|
||||||
res.setHeader('Content-Type', 'application/octet-stream');
|
|
||||||
res.setHeader('Content-Disposition', 'attachment; filename=file.txt');
|
|
||||||
res.end(`Hello world`);
|
|
||||||
} else {
|
|
||||||
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
||||||
res.end('');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
await recorder.setContentAndWait(`
|
|
||||||
<a href="${httpServer.PREFIX}/download" download>Download</a>
|
|
||||||
`, httpServer.PREFIX);
|
|
||||||
await recorder.hoverOverElement('text=Download');
|
|
||||||
await Promise.all([
|
|
||||||
page.waitForEvent('download'),
|
|
||||||
page.click('text=Download')
|
|
||||||
]);
|
|
||||||
await recorder.waitForOutput(`
|
|
||||||
// Click text=Download
|
|
||||||
const [download] = await Promise.all([
|
|
||||||
page.waitForEvent('download'),
|
|
||||||
page.click('text=Download')
|
|
||||||
]);`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle dialogs', async ({ page, recorder }) => {
|
|
||||||
await recorder.setContentAndWait(`
|
|
||||||
<button onclick="alert()">click me</button>
|
|
||||||
`);
|
|
||||||
await recorder.hoverOverElement('button');
|
|
||||||
page.once('dialog', async dialog => {
|
|
||||||
await dialog.dismiss();
|
|
||||||
});
|
|
||||||
await page.click('text=click me');
|
|
||||||
await recorder.waitForOutput(`
|
|
||||||
// Click text=click me
|
|
||||||
page.once('dialog', dialog => {
|
|
||||||
console.log(\`Dialog message: $\{dialog.message()}\`);
|
|
||||||
dialog.dismiss().catch(() => {});
|
|
||||||
});
|
|
||||||
await page.click('text=click me')`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle history.postData', async ({ page, recorder, httpServer }) => {
|
|
||||||
httpServer.setHandler((req: http.IncomingMessage, res: http.ServerResponse) => {
|
|
||||||
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
||||||
res.end('Hello world');
|
|
||||||
});
|
|
||||||
await recorder.setContentAndWait(`
|
|
||||||
<script>
|
|
||||||
let seqNum = 0;
|
|
||||||
function pushState() {
|
|
||||||
history.pushState({}, 'title', '${httpServer.PREFIX}/#seqNum=' + (++seqNum));
|
|
||||||
}
|
|
||||||
</script>`, httpServer.PREFIX);
|
|
||||||
for (let i = 1; i < 3; ++i) {
|
|
||||||
await page.evaluate('pushState()');
|
|
||||||
await recorder.waitForOutput(`await page.goto('${httpServer.PREFIX}/#seqNum=${i}');`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should record open in a new tab with url', (test, { browserName }) => {
|
|
||||||
test.fixme(browserName === 'webkit', 'Ctrl+click does not open in new tab on WebKit');
|
|
||||||
}, async ({ page, recorder, browserName, platform }) => {
|
|
||||||
await recorder.setContentAndWait(`<a href="about:blank?foo">link</a>`);
|
|
||||||
|
|
||||||
const selector = await recorder.hoverOverElement('a');
|
|
||||||
expect(selector).toBe('text=link');
|
|
||||||
|
|
||||||
await page.click('a', { modifiers: [ platform === 'darwin' ? 'Meta' : 'Control'] });
|
|
||||||
await recorder.waitForOutput('page1');
|
|
||||||
if (browserName === 'chromium') {
|
|
||||||
expect(recorder.output()).toContain(`
|
|
||||||
// Open new page
|
|
||||||
const page1 = await context.newPage();
|
|
||||||
page1.goto('about:blank?foo');`);
|
|
||||||
} else if (browserName === 'firefox') {
|
|
||||||
expect(recorder.output()).toContain(`
|
|
||||||
// Click text=link
|
|
||||||
const [page1] = await Promise.all([
|
|
||||||
page.waitForEvent('popup'),
|
|
||||||
page.click('text=link', {
|
|
||||||
modifiers: ['${platform === 'darwin' ? 'Meta' : 'Control'}']
|
|
||||||
})
|
|
||||||
]);`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not clash pages', (test, { browserName }) => {
|
|
||||||
test.fixme(browserName === 'firefox', 'Times out on Firefox, maybe the focus issue');
|
|
||||||
}, async ({ page, recorder }) => {
|
|
||||||
const [popup1] = await Promise.all([
|
|
||||||
page.context().waitForEvent('page'),
|
|
||||||
page.evaluate(`window.open('about:blank')`)
|
|
||||||
]);
|
|
||||||
await recorder.setPageContentAndWait(popup1, '<input id=name>');
|
|
||||||
|
|
||||||
const [popup2] = await Promise.all([
|
|
||||||
page.context().waitForEvent('page'),
|
|
||||||
page.evaluate(`window.open('about:blank')`)
|
|
||||||
]);
|
|
||||||
await recorder.setPageContentAndWait(popup2, '<input id=name>');
|
|
||||||
|
|
||||||
await popup1.type('input', 'TextA');
|
|
||||||
await recorder.waitForOutput('TextA');
|
|
||||||
|
|
||||||
await popup2.type('input', 'TextB');
|
|
||||||
await recorder.waitForOutput('TextB');
|
|
||||||
|
|
||||||
expect(recorder.output()).toContain(`await page1.fill('input', 'TextA');`);
|
|
||||||
expect(recorder.output()).toContain(`await page2.fill('input', 'TextB');`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('click should emit events in order', async ({ page, recorder }) => {
|
|
||||||
await recorder.setContentAndWait(`
|
|
||||||
<button id=button>
|
|
||||||
<script>
|
|
||||||
button.addEventListener('mousedown', e => console.log(e.type));
|
|
||||||
button.addEventListener('mouseup', e => console.log(e.type));
|
|
||||||
button.addEventListener('click', e => console.log(e.type));
|
|
||||||
</script>
|
|
||||||
`);
|
|
||||||
|
|
||||||
const messages: any[] = [];
|
|
||||||
page.on('console', message => messages.push(message.text()));
|
|
||||||
await Promise.all([
|
|
||||||
page.click('button'),
|
|
||||||
recorder.waitForOutput('page.click')
|
|
||||||
]);
|
|
||||||
expect(messages).toEqual(['mousedown', 'mouseup', 'click']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update hover model on action', async ({ page, recorder }) => {
|
|
||||||
await recorder.setContentAndWait(`<input id="checkbox" type="checkbox" name="accept" onchange="checkbox.name='updated'"></input>`);
|
|
||||||
const [ models ] = await Promise.all([
|
|
||||||
recorder.waitForActionPerformed(),
|
|
||||||
page.click('input')
|
|
||||||
]);
|
|
||||||
expect(models.hovered).toBe('input[name="updated"]');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update active model on action', (test, { browserName, headful }) => {
|
|
||||||
test.fixme(browserName === 'webkit' && !headful);
|
|
||||||
test.fixme(browserName === 'firefox' && !headful);
|
|
||||||
}, async ({ page, recorder }) => {
|
|
||||||
await recorder.setContentAndWait(`<input id="checkbox" type="checkbox" name="accept" onchange="checkbox.name='updated'"></input>`);
|
|
||||||
const [ models ] = await Promise.all([
|
|
||||||
recorder.waitForActionPerformed(),
|
|
||||||
page.click('input')
|
|
||||||
]);
|
|
||||||
expect(models.active).toBe('input[name="updated"]');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should check input with chaning id', async ({ page, recorder }) => {
|
|
||||||
await recorder.setContentAndWait(`<input id="checkbox" type="checkbox" name="accept" onchange="checkbox.name = 'updated'"></input>`);
|
|
||||||
await Promise.all([
|
|
||||||
recorder.waitForActionPerformed(),
|
|
||||||
page.click('input[id=checkbox]')
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should prefer frame name', async ({ page, recorder, server }) => {
|
|
||||||
await recorder.setContentAndWait(`
|
|
||||||
<iframe src='./frames/frame.html' name='one'></iframe>
|
|
||||||
<iframe src='./frames/frame.html' name='two'></iframe>
|
|
||||||
<iframe src='./frames/frame.html'></iframe>
|
|
||||||
`, server.EMPTY_PAGE, 4);
|
|
||||||
const frameOne = page.frame({ name: 'one' });
|
|
||||||
const frameTwo = page.frame({ name: 'two' });
|
|
||||||
const otherFrame = page.frames().find(f => f !== page.mainFrame() && !f.name());
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
recorder.waitForOutput('one'),
|
|
||||||
frameOne.click('div'),
|
|
||||||
]);
|
|
||||||
expect(recorder.output()).toContain(`
|
|
||||||
// Click text=Hi, I'm frame
|
|
||||||
await page.frame({
|
|
||||||
name: 'one'
|
|
||||||
}).click('text=Hi, I\\'m frame');`);
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
recorder.waitForOutput('two'),
|
|
||||||
frameTwo.click('div'),
|
|
||||||
]);
|
|
||||||
expect(recorder.output()).toContain(`
|
|
||||||
// Click text=Hi, I'm frame
|
|
||||||
await page.frame({
|
|
||||||
name: 'two'
|
|
||||||
}).click('text=Hi, I\\'m frame');`);
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
recorder.waitForOutput('url: \''),
|
|
||||||
otherFrame.click('div'),
|
|
||||||
]);
|
|
||||||
expect(recorder.output()).toContain(`
|
|
||||||
// Click text=Hi, I'm frame
|
|
||||||
await page.frame({
|
|
||||||
url: '${otherFrame.url()}'
|
|
||||||
}).click('text=Hi, I\\'m frame');`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should record navigations after identical pushState', async ({ page, recorder, httpServer }) => {
|
|
||||||
httpServer.setHandler((req: http.IncomingMessage, res: http.ServerResponse) => {
|
|
||||||
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
||||||
res.end('Hello world');
|
|
||||||
});
|
|
||||||
await recorder.setContentAndWait(`
|
|
||||||
<script>
|
|
||||||
function pushState() {
|
|
||||||
history.pushState({}, 'title', '${httpServer.PREFIX}');
|
|
||||||
}
|
|
||||||
</script>`, httpServer.PREFIX);
|
|
||||||
for (let i = 1; i < 3; ++i)
|
|
||||||
await page.evaluate('pushState()');
|
|
||||||
|
|
||||||
await page.goto(httpServer.PREFIX + '/page2.html');
|
|
||||||
await recorder.waitForOutput(`await page.goto('${httpServer.PREFIX}/page2.html');`);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
337
test/cli/cli-codegen-2.spec.ts
Normal file
337
test/cli/cli-codegen-2.spec.ts
Normal file
|
|
@ -0,0 +1,337 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Microsoft Corporation.
|
||||||
|
*
|
||||||
|
* 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 { folio } from './cli.fixtures';
|
||||||
|
import * as http from 'http';
|
||||||
|
import * as url from 'url';
|
||||||
|
|
||||||
|
const { it, describe, expect } = folio;
|
||||||
|
|
||||||
|
describe('cli codegen', (suite, { mode, browserName, headful }) => {
|
||||||
|
suite.fixme(browserName === 'firefox' && headful, 'Focus is off');
|
||||||
|
suite.skip(mode !== 'default');
|
||||||
|
}, () => {
|
||||||
|
it('should contain open page', async ({ recorder }) => {
|
||||||
|
await recorder.setContentAndWait(``);
|
||||||
|
expect(recorder.output()).toContain(`const page = await context.newPage();`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain second page', async ({ context, recorder }) => {
|
||||||
|
await recorder.setContentAndWait(``);
|
||||||
|
await context.newPage();
|
||||||
|
await recorder.waitForOutput('page1');
|
||||||
|
expect(recorder.output()).toContain('const page1 = await context.newPage();');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain close page', async ({ context, recorder }) => {
|
||||||
|
await recorder.setContentAndWait(``);
|
||||||
|
await context.newPage();
|
||||||
|
await recorder.page.close();
|
||||||
|
await recorder.waitForOutput('page.close();');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not lead to an error if html gets clicked', async ({ context, recorder }) => {
|
||||||
|
await recorder.setContentAndWait('');
|
||||||
|
await context.newPage();
|
||||||
|
const errors: any[] = [];
|
||||||
|
recorder.page.on('pageerror', e => errors.push(e));
|
||||||
|
await recorder.page.evaluate(() => document.querySelector('body').remove());
|
||||||
|
const selector = await recorder.hoverOverElement('html');
|
||||||
|
expect(selector).toBe('html');
|
||||||
|
await recorder.page.close();
|
||||||
|
await recorder.waitForOutput('page.close();');
|
||||||
|
expect(errors.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should upload a single file', (test, { browserName }) => {
|
||||||
|
test.fixme(browserName === 'firefox', 'Hangs');
|
||||||
|
}, async ({ page, recorder }) => {
|
||||||
|
await recorder.setContentAndWait(`
|
||||||
|
<form>
|
||||||
|
<input type="file">
|
||||||
|
</form>
|
||||||
|
`);
|
||||||
|
|
||||||
|
await page.focus('input[type=file]');
|
||||||
|
await page.setInputFiles('input[type=file]', 'test/assets/file-to-upload.txt');
|
||||||
|
await page.click('input[type=file]');
|
||||||
|
|
||||||
|
await recorder.waitForOutput(`
|
||||||
|
// Upload file-to-upload.txt
|
||||||
|
await page.setInputFiles('input[type="file"]', 'file-to-upload.txt');`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should upload multiple files', (test, { browserName }) => {
|
||||||
|
test.fixme(browserName === 'firefox', 'Hangs');
|
||||||
|
}, async ({ page, recorder }) => {
|
||||||
|
await recorder.setContentAndWait(`
|
||||||
|
<form>
|
||||||
|
<input type="file" multiple>
|
||||||
|
</form>
|
||||||
|
`);
|
||||||
|
|
||||||
|
await page.focus('input[type=file]');
|
||||||
|
await page.setInputFiles('input[type=file]', ['test/assets/file-to-upload.txt', 'test/assets/file-to-upload-2.txt']);
|
||||||
|
await page.click('input[type=file]');
|
||||||
|
|
||||||
|
await recorder.waitForOutput(`
|
||||||
|
// Upload file-to-upload.txt, file-to-upload-2.txt
|
||||||
|
await page.setInputFiles('input[type="file"]', ['file-to-upload.txt', 'file-to-upload-2.txt']);`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should clear files', (test, { browserName }) => {
|
||||||
|
test.fixme(browserName === 'firefox', 'Hangs');
|
||||||
|
}, async ({ page, recorder }) => {
|
||||||
|
await recorder.setContentAndWait(`
|
||||||
|
<form>
|
||||||
|
<input type="file" multiple>
|
||||||
|
</form>
|
||||||
|
`);
|
||||||
|
await page.focus('input[type=file]');
|
||||||
|
await page.setInputFiles('input[type=file]', 'test/assets/file-to-upload.txt');
|
||||||
|
await page.setInputFiles('input[type=file]', []);
|
||||||
|
await page.click('input[type=file]');
|
||||||
|
|
||||||
|
await recorder.waitForOutput(`
|
||||||
|
// Clear selected files
|
||||||
|
await page.setInputFiles('input[type="file"]', []);`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should download files', (test, {browserName, headful}) => {
|
||||||
|
test.fixme(browserName === 'webkit', 'Generated page.waitForNavigation next to page.waitForEvent(download)');
|
||||||
|
}, async ({ page, recorder, httpServer }) => {
|
||||||
|
httpServer.setHandler((req: http.IncomingMessage, res: http.ServerResponse) => {
|
||||||
|
const pathName = url.parse(req.url!).path;
|
||||||
|
if (pathName === '/download') {
|
||||||
|
res.setHeader('Content-Type', 'application/octet-stream');
|
||||||
|
res.setHeader('Content-Disposition', 'attachment; filename=file.txt');
|
||||||
|
res.end(`Hello world`);
|
||||||
|
} else {
|
||||||
|
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
||||||
|
res.end('');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await recorder.setContentAndWait(`
|
||||||
|
<a href="${httpServer.PREFIX}/download" download>Download</a>
|
||||||
|
`, httpServer.PREFIX);
|
||||||
|
await recorder.hoverOverElement('text=Download');
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForEvent('download'),
|
||||||
|
page.click('text=Download')
|
||||||
|
]);
|
||||||
|
await recorder.waitForOutput(`
|
||||||
|
// Click text=Download
|
||||||
|
const [download] = await Promise.all([
|
||||||
|
page.waitForEvent('download'),
|
||||||
|
page.click('text=Download')
|
||||||
|
]);`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle dialogs', async ({ page, recorder }) => {
|
||||||
|
await recorder.setContentAndWait(`
|
||||||
|
<button onclick="alert()">click me</button>
|
||||||
|
`);
|
||||||
|
await recorder.hoverOverElement('button');
|
||||||
|
page.once('dialog', async dialog => {
|
||||||
|
await dialog.dismiss();
|
||||||
|
});
|
||||||
|
await page.click('text=click me');
|
||||||
|
await recorder.waitForOutput(`
|
||||||
|
// Click text=click me
|
||||||
|
page.once('dialog', dialog => {
|
||||||
|
console.log(\`Dialog message: $\{dialog.message()}\`);
|
||||||
|
dialog.dismiss().catch(() => {});
|
||||||
|
});
|
||||||
|
await page.click('text=click me')`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle history.postData', async ({ page, recorder, httpServer }) => {
|
||||||
|
httpServer.setHandler((req: http.IncomingMessage, res: http.ServerResponse) => {
|
||||||
|
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
||||||
|
res.end('Hello world');
|
||||||
|
});
|
||||||
|
await recorder.setContentAndWait(`
|
||||||
|
<script>
|
||||||
|
let seqNum = 0;
|
||||||
|
function pushState() {
|
||||||
|
history.pushState({}, 'title', '${httpServer.PREFIX}/#seqNum=' + (++seqNum));
|
||||||
|
}
|
||||||
|
</script>`, httpServer.PREFIX);
|
||||||
|
for (let i = 1; i < 3; ++i) {
|
||||||
|
await page.evaluate('pushState()');
|
||||||
|
await recorder.waitForOutput(`await page.goto('${httpServer.PREFIX}/#seqNum=${i}');`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should record open in a new tab with url', (test, { browserName }) => {
|
||||||
|
test.fixme(browserName === 'webkit', 'Ctrl+click does not open in new tab on WebKit');
|
||||||
|
}, async ({ page, recorder, browserName, platform }) => {
|
||||||
|
await recorder.setContentAndWait(`<a href="about:blank?foo">link</a>`);
|
||||||
|
|
||||||
|
const selector = await recorder.hoverOverElement('a');
|
||||||
|
expect(selector).toBe('text=link');
|
||||||
|
|
||||||
|
await page.click('a', { modifiers: [ platform === 'darwin' ? 'Meta' : 'Control'] });
|
||||||
|
await recorder.waitForOutput('page1');
|
||||||
|
if (browserName === 'chromium') {
|
||||||
|
expect(recorder.output()).toContain(`
|
||||||
|
// Open new page
|
||||||
|
const page1 = await context.newPage();
|
||||||
|
page1.goto('about:blank?foo');`);
|
||||||
|
} else if (browserName === 'firefox') {
|
||||||
|
expect(recorder.output()).toContain(`
|
||||||
|
// Click text=link
|
||||||
|
const [page1] = await Promise.all([
|
||||||
|
page.waitForEvent('popup'),
|
||||||
|
page.click('text=link', {
|
||||||
|
modifiers: ['${platform === 'darwin' ? 'Meta' : 'Control'}']
|
||||||
|
})
|
||||||
|
]);`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not clash pages', (test, { browserName }) => {
|
||||||
|
test.fixme(browserName === 'firefox', 'Times out on Firefox, maybe the focus issue');
|
||||||
|
}, async ({ page, recorder }) => {
|
||||||
|
const [popup1] = await Promise.all([
|
||||||
|
page.context().waitForEvent('page'),
|
||||||
|
page.evaluate(`window.open('about:blank')`)
|
||||||
|
]);
|
||||||
|
await recorder.setPageContentAndWait(popup1, '<input id=name>');
|
||||||
|
|
||||||
|
const [popup2] = await Promise.all([
|
||||||
|
page.context().waitForEvent('page'),
|
||||||
|
page.evaluate(`window.open('about:blank')`)
|
||||||
|
]);
|
||||||
|
await recorder.setPageContentAndWait(popup2, '<input id=name>');
|
||||||
|
|
||||||
|
await popup1.type('input', 'TextA');
|
||||||
|
await recorder.waitForOutput('TextA');
|
||||||
|
|
||||||
|
await popup2.type('input', 'TextB');
|
||||||
|
await recorder.waitForOutput('TextB');
|
||||||
|
|
||||||
|
expect(recorder.output()).toContain(`await page1.fill('input', 'TextA');`);
|
||||||
|
expect(recorder.output()).toContain(`await page2.fill('input', 'TextB');`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('click should emit events in order', async ({ page, recorder }) => {
|
||||||
|
await recorder.setContentAndWait(`
|
||||||
|
<button id=button>
|
||||||
|
<script>
|
||||||
|
button.addEventListener('mousedown', e => console.log(e.type));
|
||||||
|
button.addEventListener('mouseup', e => console.log(e.type));
|
||||||
|
button.addEventListener('click', e => console.log(e.type));
|
||||||
|
</script>
|
||||||
|
`);
|
||||||
|
|
||||||
|
const messages: any[] = [];
|
||||||
|
page.on('console', message => messages.push(message.text()));
|
||||||
|
await Promise.all([
|
||||||
|
page.click('button'),
|
||||||
|
recorder.waitForOutput('page.click')
|
||||||
|
]);
|
||||||
|
expect(messages).toEqual(['mousedown', 'mouseup', 'click']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update hover model on action', async ({ page, recorder }) => {
|
||||||
|
await recorder.setContentAndWait(`<input id="checkbox" type="checkbox" name="accept" onchange="checkbox.name='updated'"></input>`);
|
||||||
|
const [ models ] = await Promise.all([
|
||||||
|
recorder.waitForActionPerformed(),
|
||||||
|
page.click('input')
|
||||||
|
]);
|
||||||
|
expect(models.hovered).toBe('input[name="updated"]');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update active model on action', (test, { browserName, headful }) => {
|
||||||
|
test.fixme(browserName === 'webkit' && !headful);
|
||||||
|
test.fixme(browserName === 'firefox' && !headful);
|
||||||
|
}, async ({ page, recorder }) => {
|
||||||
|
await recorder.setContentAndWait(`<input id="checkbox" type="checkbox" name="accept" onchange="checkbox.name='updated'"></input>`);
|
||||||
|
const [ models ] = await Promise.all([
|
||||||
|
recorder.waitForActionPerformed(),
|
||||||
|
page.click('input')
|
||||||
|
]);
|
||||||
|
expect(models.active).toBe('input[name="updated"]');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should check input with chaning id', async ({ page, recorder }) => {
|
||||||
|
await recorder.setContentAndWait(`<input id="checkbox" type="checkbox" name="accept" onchange="checkbox.name = 'updated'"></input>`);
|
||||||
|
await Promise.all([
|
||||||
|
recorder.waitForActionPerformed(),
|
||||||
|
page.click('input[id=checkbox]')
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should prefer frame name', async ({ page, recorder, server }) => {
|
||||||
|
await recorder.setContentAndWait(`
|
||||||
|
<iframe src='./frames/frame.html' name='one'></iframe>
|
||||||
|
<iframe src='./frames/frame.html' name='two'></iframe>
|
||||||
|
<iframe src='./frames/frame.html'></iframe>
|
||||||
|
`, server.EMPTY_PAGE, 4);
|
||||||
|
const frameOne = page.frame({ name: 'one' });
|
||||||
|
const frameTwo = page.frame({ name: 'two' });
|
||||||
|
const otherFrame = page.frames().find(f => f !== page.mainFrame() && !f.name());
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
recorder.waitForOutput('one'),
|
||||||
|
frameOne.click('div'),
|
||||||
|
]);
|
||||||
|
expect(recorder.output()).toContain(`
|
||||||
|
// Click text=Hi, I'm frame
|
||||||
|
await page.frame({
|
||||||
|
name: 'one'
|
||||||
|
}).click('text=Hi, I\\'m frame');`);
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
recorder.waitForOutput('two'),
|
||||||
|
frameTwo.click('div'),
|
||||||
|
]);
|
||||||
|
expect(recorder.output()).toContain(`
|
||||||
|
// Click text=Hi, I'm frame
|
||||||
|
await page.frame({
|
||||||
|
name: 'two'
|
||||||
|
}).click('text=Hi, I\\'m frame');`);
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
recorder.waitForOutput('url: \''),
|
||||||
|
otherFrame.click('div'),
|
||||||
|
]);
|
||||||
|
expect(recorder.output()).toContain(`
|
||||||
|
// Click text=Hi, I'm frame
|
||||||
|
await page.frame({
|
||||||
|
url: '${otherFrame.url()}'
|
||||||
|
}).click('text=Hi, I\\'m frame');`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should record navigations after identical pushState', async ({ page, recorder, httpServer }) => {
|
||||||
|
httpServer.setHandler((req: http.IncomingMessage, res: http.ServerResponse) => {
|
||||||
|
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
||||||
|
res.end('Hello world');
|
||||||
|
});
|
||||||
|
await recorder.setContentAndWait(`
|
||||||
|
<script>
|
||||||
|
function pushState() {
|
||||||
|
history.pushState({}, 'title', '${httpServer.PREFIX}');
|
||||||
|
}
|
||||||
|
</script>`, httpServer.PREFIX);
|
||||||
|
for (let i = 1; i < 3; ++i)
|
||||||
|
await page.evaluate('pushState()');
|
||||||
|
|
||||||
|
await page.goto(httpServer.PREFIX + '/page2.html');
|
||||||
|
await recorder.waitForOutput(`await page.goto('${httpServer.PREFIX}/page2.html');`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -22,17 +22,21 @@ const { it, expect } = folio;
|
||||||
|
|
||||||
const emptyHTML = new URL('file://' + path.join(__dirname, '..', 'assets', 'empty.html')).toString();
|
const emptyHTML = new URL('file://' + path.join(__dirname, '..', 'assets', 'empty.html')).toString();
|
||||||
|
|
||||||
it('should print the correct imports and context options', async ({ runCLI }) => {
|
function capitalize(browserName: string): string {
|
||||||
|
return browserName[0].toUpperCase() + browserName.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should print the correct imports and context options', async ({ browserName, runCLI }) => {
|
||||||
const cli = runCLI(['codegen', '--target=csharp', emptyHTML]);
|
const cli = runCLI(['codegen', '--target=csharp', emptyHTML]);
|
||||||
const expectedResult = `await Playwright.InstallAsync();
|
const expectedResult = `await Playwright.InstallAsync();
|
||||||
using var playwright = await Playwright.CreateAsync();
|
using var playwright = await Playwright.CreateAsync();
|
||||||
await using var browser = await playwright.Chromium.LaunchAsync(headless: false);
|
await using var browser = await playwright.${capitalize(browserName)}.LaunchAsync(headless: false);
|
||||||
var context = await browser.NewContextAsync();`;
|
var context = await browser.NewContextAsync();`;
|
||||||
await cli.waitFor(expectedResult).catch(e => e);
|
await cli.waitFor(expectedResult).catch(e => e);
|
||||||
expect(cli.text()).toContain(expectedResult);
|
expect(cli.text()).toContain(expectedResult);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should print the correct context options for custom settings', async ({ runCLI }) => {
|
it('should print the correct context options for custom settings', async ({ browserName, runCLI }) => {
|
||||||
const cli = runCLI([
|
const cli = runCLI([
|
||||||
'--color-scheme=dark',
|
'--color-scheme=dark',
|
||||||
'--geolocation=37.819722,-122.478611',
|
'--geolocation=37.819722,-122.478611',
|
||||||
|
|
@ -47,7 +51,7 @@ it('should print the correct context options for custom settings', async ({ runC
|
||||||
emptyHTML]);
|
emptyHTML]);
|
||||||
const expectedResult = `await Playwright.InstallAsync();
|
const expectedResult = `await Playwright.InstallAsync();
|
||||||
using var playwright = await Playwright.CreateAsync();
|
using var playwright = await Playwright.CreateAsync();
|
||||||
await using var browser = await playwright.Chromium.LaunchAsync(
|
await using var browser = await playwright.${capitalize(browserName)}.LaunchAsync(
|
||||||
headless: false,
|
headless: false,
|
||||||
proxy: new ProxySettings
|
proxy: new ProxySettings
|
||||||
{
|
{
|
||||||
|
|
@ -79,14 +83,13 @@ it('should print the correct context options when using a device', async ({ runC
|
||||||
using var playwright = await Playwright.CreateAsync();
|
using var playwright = await Playwright.CreateAsync();
|
||||||
await using var browser = await playwright.Chromium.LaunchAsync(headless: false);
|
await using var browser = await playwright.Chromium.LaunchAsync(headless: false);
|
||||||
var context = await browser.NewContextAsync(playwright.Devices["Pixel 2"]);`;
|
var context = await browser.NewContextAsync(playwright.Devices["Pixel 2"]);`;
|
||||||
|
|
||||||
await cli.waitFor(expectedResult);
|
await cli.waitFor(expectedResult);
|
||||||
expect(cli.text()).toContain(expectedResult);
|
expect(cli.text()).toContain(expectedResult);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should print the correct context options when using a device and additional options', async ({ runCLI }) => {
|
it('should print the correct context options when using a device and additional options', async ({ runCLI }) => {
|
||||||
const cli = runCLI([
|
const cli = runCLI([
|
||||||
'--device=Pixel 2',
|
'--device=iPhone 11',
|
||||||
'--color-scheme=dark',
|
'--color-scheme=dark',
|
||||||
'--geolocation=37.819722,-122.478611',
|
'--geolocation=37.819722,-122.478611',
|
||||||
'--lang=es',
|
'--lang=es',
|
||||||
|
|
@ -100,13 +103,13 @@ it('should print the correct context options when using a device and additional
|
||||||
emptyHTML]);
|
emptyHTML]);
|
||||||
const expectedResult = `await Playwright.InstallAsync();
|
const expectedResult = `await Playwright.InstallAsync();
|
||||||
using var playwright = await Playwright.CreateAsync();
|
using var playwright = await Playwright.CreateAsync();
|
||||||
await using var browser = await playwright.Chromium.LaunchAsync(
|
await using var browser = await playwright.Webkit.LaunchAsync(
|
||||||
headless: false,
|
headless: false,
|
||||||
proxy: new ProxySettings
|
proxy: new ProxySettings
|
||||||
{
|
{
|
||||||
Server = "http://myproxy:3128",
|
Server = "http://myproxy:3128",
|
||||||
});
|
});
|
||||||
var context = await browser.NewContextAsync(new BrowserContextOptions(playwright.Devices["Pixel 2"])
|
var context = await browser.NewContextAsync(new BrowserContextOptions(playwright.Devices["iPhone 11"])
|
||||||
{
|
{
|
||||||
UserAgent = "hardkodemium",
|
UserAgent = "hardkodemium",
|
||||||
Viewport = new ViewportSize
|
Viewport = new ViewportSize
|
||||||
|
|
@ -129,21 +132,20 @@ var context = await browser.NewContextAsync(new BrowserContextOptions(playwright
|
||||||
expect(cli.text()).toContain(expectedResult);
|
expect(cli.text()).toContain(expectedResult);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should print load/save storageState', async ({ runCLI, testInfo }) => {
|
it('should print load/save storageState', async ({ browserName, runCLI, testInfo }) => {
|
||||||
const loadFileName = testInfo.outputPath('load.json');
|
const loadFileName = testInfo.outputPath('load.json');
|
||||||
const saveFileName = testInfo.outputPath('save.json');
|
const saveFileName = testInfo.outputPath('save.json');
|
||||||
await fs.promises.writeFile(loadFileName, JSON.stringify({ cookies: [], origins: [] }), 'utf8');
|
await fs.promises.writeFile(loadFileName, JSON.stringify({ cookies: [], origins: [] }), 'utf8');
|
||||||
const cli = runCLI([`--load-storage=${loadFileName}`, `--save-storage=${saveFileName}`, 'codegen', '--target=csharp', emptyHTML]);
|
const cli = runCLI([`--load-storage=${loadFileName}`, `--save-storage=${saveFileName}`, 'codegen', '--target=csharp', emptyHTML]);
|
||||||
const expectedResult = `await Playwright.InstallAsync();
|
const expectedResult1 = `await Playwright.InstallAsync();
|
||||||
using var playwright = await Playwright.CreateAsync();
|
using var playwright = await Playwright.CreateAsync();
|
||||||
await using var browser = await playwright.Chromium.LaunchAsync(headless: false);
|
await using var browser = await playwright.${capitalize(browserName)}.LaunchAsync(headless: false);
|
||||||
var context = await browser.NewContextAsync(storageState: "${loadFileName}");
|
var context = await browser.NewContextAsync(storageState: "${loadFileName}");`;
|
||||||
|
await cli.waitFor(expectedResult1);
|
||||||
// Open new page
|
|
||||||
var page = await context.NewPageAsync();
|
|
||||||
|
|
||||||
|
const expectedResult2 = `
|
||||||
// ---------------------
|
// ---------------------
|
||||||
await context.StorageStateAsync(path: "${saveFileName}");
|
await context.StorageStateAsync(path: "${saveFileName}");
|
||||||
`;
|
`;
|
||||||
await cli.waitFor(expectedResult);
|
await cli.waitFor(expectedResult2);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -22,12 +22,12 @@ const { it, expect } = folio;
|
||||||
|
|
||||||
const emptyHTML = new URL('file://' + path.join(__dirname, '..', 'assets', 'empty.html')).toString();
|
const emptyHTML = new URL('file://' + path.join(__dirname, '..', 'assets', 'empty.html')).toString();
|
||||||
|
|
||||||
it('should print the correct imports and context options', async ({ runCLI }) => {
|
it('should print the correct imports and context options', async ({ browserName, runCLI }) => {
|
||||||
const cli = runCLI(['codegen', emptyHTML]);
|
const cli = runCLI(['codegen', emptyHTML]);
|
||||||
const expectedResult = `const { chromium } = require('playwright');
|
const expectedResult = `const { ${browserName} } = require('playwright');
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
const browser = await chromium.launch({
|
const browser = await ${browserName}.launch({
|
||||||
headless: false
|
headless: false
|
||||||
});
|
});
|
||||||
const context = await browser.newContext();`;
|
const context = await browser.newContext();`;
|
||||||
|
|
@ -35,12 +35,12 @@ it('should print the correct imports and context options', async ({ runCLI }) =>
|
||||||
expect(cli.text()).toContain(expectedResult);
|
expect(cli.text()).toContain(expectedResult);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should print the correct context options for custom settings', async ({ runCLI }) => {
|
it('should print the correct context options for custom settings', async ({ browserName, runCLI }) => {
|
||||||
const cli = runCLI(['--color-scheme=light', 'codegen', emptyHTML]);
|
const cli = runCLI(['--color-scheme=light', 'codegen', emptyHTML]);
|
||||||
const expectedResult = `const { chromium } = require('playwright');
|
const expectedResult = `const { ${browserName} } = require('playwright');
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
const browser = await chromium.launch({
|
const browser = await ${browserName}.launch({
|
||||||
headless: false
|
headless: false
|
||||||
});
|
});
|
||||||
const context = await browser.newContext({
|
const context = await browser.newContext({
|
||||||
|
|
@ -67,30 +67,30 @@ it('should print the correct context options when using a device', async ({ runC
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should print the correct context options when using a device and additional options', async ({ runCLI }) => {
|
it('should print the correct context options when using a device and additional options', async ({ runCLI }) => {
|
||||||
const cli = runCLI(['--color-scheme=light', '--device=Pixel 2', 'codegen', emptyHTML]);
|
const cli = runCLI(['--color-scheme=light', '--device=iPhone 11', 'codegen', emptyHTML]);
|
||||||
const expectedResult = `const { chromium, devices } = require('playwright');
|
const expectedResult = `const { webkit, devices } = require('playwright');
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
const browser = await chromium.launch({
|
const browser = await webkit.launch({
|
||||||
headless: false
|
headless: false
|
||||||
});
|
});
|
||||||
const context = await browser.newContext({
|
const context = await browser.newContext({
|
||||||
...devices['Pixel 2'],
|
...devices['iPhone 11'],
|
||||||
colorScheme: 'light'
|
colorScheme: 'light'
|
||||||
});`;
|
});`;
|
||||||
await cli.waitFor(expectedResult);
|
await cli.waitFor(expectedResult);
|
||||||
expect(cli.text()).toContain(expectedResult);
|
expect(cli.text()).toContain(expectedResult);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should save the codegen output to a file if specified', async ({ runCLI, testInfo }) => {
|
it('should save the codegen output to a file if specified', async ({ browserName, runCLI, testInfo }) => {
|
||||||
const tmpFile = testInfo.outputPath('script.js');
|
const tmpFile = testInfo.outputPath('script.js');
|
||||||
const cli = runCLI(['codegen', '--output', tmpFile, emptyHTML]);
|
const cli = runCLI(['codegen', '--output', tmpFile, emptyHTML]);
|
||||||
await cli.exited;
|
await cli.exited;
|
||||||
const content = fs.readFileSync(tmpFile);
|
const content = fs.readFileSync(tmpFile);
|
||||||
expect(content.toString()).toBe(`const { chromium } = require('playwright');
|
expect(content.toString()).toBe(`const { ${browserName} } = require('playwright');
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
const browser = await chromium.launch({
|
const browser = await ${browserName}.launch({
|
||||||
headless: false
|
headless: false
|
||||||
});
|
});
|
||||||
const context = await browser.newContext();
|
const context = await browser.newContext();
|
||||||
|
|
@ -110,25 +110,27 @@ it('should save the codegen output to a file if specified', async ({ runCLI, tes
|
||||||
})();`);
|
})();`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should print load/save storageState', async ({ runCLI, testInfo }) => {
|
it('should print load/save storageState', async ({ browserName, runCLI, testInfo }) => {
|
||||||
const loadFileName = testInfo.outputPath('load.json');
|
const loadFileName = testInfo.outputPath('load.json');
|
||||||
const saveFileName = testInfo.outputPath('save.json');
|
const saveFileName = testInfo.outputPath('save.json');
|
||||||
await fs.promises.writeFile(loadFileName, JSON.stringify({ cookies: [], origins: [] }), 'utf8');
|
await fs.promises.writeFile(loadFileName, JSON.stringify({ cookies: [], origins: [] }), 'utf8');
|
||||||
const cli = runCLI([`--load-storage=${loadFileName}`, `--save-storage=${saveFileName}`, 'codegen', emptyHTML]);
|
const cli = runCLI([`--load-storage=${loadFileName}`, `--save-storage=${saveFileName}`, 'codegen', emptyHTML]);
|
||||||
const expectedResult = `const { chromium } = require('playwright');
|
const expectedResult1 = `const { ${browserName} } = require('playwright');
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
const browser = await chromium.launch({
|
const browser = await ${browserName}.launch({
|
||||||
headless: false
|
headless: false
|
||||||
});
|
});
|
||||||
const context = await browser.newContext({
|
const context = await browser.newContext({
|
||||||
storageState: '${loadFileName}'
|
storageState: '${loadFileName}'
|
||||||
});
|
});`;
|
||||||
|
await cli.waitFor(expectedResult1);
|
||||||
|
|
||||||
|
const expectedResult2 = `
|
||||||
// ---------------------
|
// ---------------------
|
||||||
await context.storageState({ path: '${saveFileName}' });
|
await context.storageState({ path: '${saveFileName}' });
|
||||||
await context.close();
|
await context.close();
|
||||||
await browser.close();
|
await browser.close();
|
||||||
})();`;
|
})();`;
|
||||||
await cli.waitFor(expectedResult);
|
await cli.waitFor(expectedResult2);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -22,25 +22,25 @@ const { it, expect } = folio;
|
||||||
|
|
||||||
const emptyHTML = new URL('file://' + path.join(__dirname, '..', 'assets', 'empty.html')).toString();
|
const emptyHTML = new URL('file://' + path.join(__dirname, '..', 'assets', 'empty.html')).toString();
|
||||||
|
|
||||||
it('should print the correct imports and context options', async ({ runCLI }) => {
|
it('should print the correct imports and context options', async ({ browserName, runCLI }) => {
|
||||||
const cli = runCLI(['codegen', '--target=python-async', emptyHTML]);
|
const cli = runCLI(['codegen', '--target=python-async', emptyHTML]);
|
||||||
const expectedResult = `import asyncio
|
const expectedResult = `import asyncio
|
||||||
from playwright.async_api import async_playwright
|
from playwright.async_api import async_playwright
|
||||||
|
|
||||||
async def run(playwright):
|
async def run(playwright):
|
||||||
browser = await playwright.chromium.launch(headless=False)
|
browser = await playwright.${browserName}.launch(headless=False)
|
||||||
context = await browser.new_context()`;
|
context = await browser.new_context()`;
|
||||||
await cli.waitFor(expectedResult);
|
await cli.waitFor(expectedResult);
|
||||||
expect(cli.text()).toContain(expectedResult);
|
expect(cli.text()).toContain(expectedResult);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should print the correct context options for custom settings', async ({ runCLI }) => {
|
it('should print the correct context options for custom settings', async ({ browserName, runCLI }) => {
|
||||||
const cli = runCLI(['--color-scheme=light', 'codegen', '--target=python-async', emptyHTML]);
|
const cli = runCLI(['--color-scheme=light', 'codegen', '--target=python-async', emptyHTML]);
|
||||||
const expectedResult = `import asyncio
|
const expectedResult = `import asyncio
|
||||||
from playwright.async_api import async_playwright
|
from playwright.async_api import async_playwright
|
||||||
|
|
||||||
async def run(playwright):
|
async def run(playwright):
|
||||||
browser = await playwright.chromium.launch(headless=False)
|
browser = await playwright.${browserName}.launch(headless=False)
|
||||||
context = await browser.new_context(color_scheme="light")`;
|
context = await browser.new_context(color_scheme="light")`;
|
||||||
await cli.waitFor(expectedResult);
|
await cli.waitFor(expectedResult);
|
||||||
expect(cli.text()).toContain(expectedResult);
|
expect(cli.text()).toContain(expectedResult);
|
||||||
|
|
@ -59,18 +59,18 @@ async def run(playwright):
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should print the correct context options when using a device and additional options', async ({ runCLI }) => {
|
it('should print the correct context options when using a device and additional options', async ({ runCLI }) => {
|
||||||
const cli = runCLI(['--color-scheme=light', '--device=Pixel 2', 'codegen', '--target=python-async', emptyHTML]);
|
const cli = runCLI(['--color-scheme=light', '--device=iPhone 11', 'codegen', '--target=python-async', emptyHTML]);
|
||||||
const expectedResult = `import asyncio
|
const expectedResult = `import asyncio
|
||||||
from playwright.async_api import async_playwright
|
from playwright.async_api import async_playwright
|
||||||
|
|
||||||
async def run(playwright):
|
async def run(playwright):
|
||||||
browser = await playwright.chromium.launch(headless=False)
|
browser = await playwright.webkit.launch(headless=False)
|
||||||
context = await browser.new_context(**playwright.devices["Pixel 2"], color_scheme="light")`;
|
context = await browser.new_context(**playwright.devices["iPhone 11"], color_scheme="light")`;
|
||||||
await cli.waitFor(expectedResult);
|
await cli.waitFor(expectedResult);
|
||||||
expect(cli.text()).toContain(expectedResult);
|
expect(cli.text()).toContain(expectedResult);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should save the codegen output to a file if specified', async ({ runCLI, testInfo }) => {
|
it('should save the codegen output to a file if specified', async ({ browserName, runCLI, testInfo }) => {
|
||||||
const tmpFile = testInfo.outputPath('script.js');
|
const tmpFile = testInfo.outputPath('script.js');
|
||||||
const cli = runCLI(['codegen', '--target=python-async', '--output', tmpFile, emptyHTML]);
|
const cli = runCLI(['codegen', '--target=python-async', '--output', tmpFile, emptyHTML]);
|
||||||
await cli.exited;
|
await cli.exited;
|
||||||
|
|
@ -79,7 +79,7 @@ it('should save the codegen output to a file if specified', async ({ runCLI, tes
|
||||||
from playwright.async_api import async_playwright
|
from playwright.async_api import async_playwright
|
||||||
|
|
||||||
async def run(playwright):
|
async def run(playwright):
|
||||||
browser = await playwright.chromium.launch(headless=False)
|
browser = await playwright.${browserName}.launch(headless=False)
|
||||||
context = await browser.new_context()
|
context = await browser.new_context()
|
||||||
|
|
||||||
# Open new page
|
# Open new page
|
||||||
|
|
@ -101,21 +101,20 @@ async def main():
|
||||||
asyncio.run(main())`);
|
asyncio.run(main())`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should print load/save storage_state', async ({ runCLI, testInfo }) => {
|
it('should print load/save storage_state', async ({ browserName, runCLI, testInfo }) => {
|
||||||
const loadFileName = testInfo.outputPath('load.json');
|
const loadFileName = testInfo.outputPath('load.json');
|
||||||
const saveFileName = testInfo.outputPath('save.json');
|
const saveFileName = testInfo.outputPath('save.json');
|
||||||
await fs.promises.writeFile(loadFileName, JSON.stringify({ cookies: [], origins: [] }), 'utf8');
|
await fs.promises.writeFile(loadFileName, JSON.stringify({ cookies: [], origins: [] }), 'utf8');
|
||||||
const cli = runCLI([`--load-storage=${loadFileName}`, `--save-storage=${saveFileName}`, 'codegen', '--target=python-async', emptyHTML]);
|
const cli = runCLI([`--load-storage=${loadFileName}`, `--save-storage=${saveFileName}`, 'codegen', '--target=python-async', emptyHTML]);
|
||||||
const expectedResult = `import asyncio
|
const expectedResult1 = `import asyncio
|
||||||
from playwright.async_api import async_playwright
|
from playwright.async_api import async_playwright
|
||||||
|
|
||||||
async def run(playwright):
|
async def run(playwright):
|
||||||
browser = await playwright.chromium.launch(headless=False)
|
browser = await playwright.${browserName}.launch(headless=False)
|
||||||
context = await browser.new_context(storage_state="${loadFileName}")
|
context = await browser.new_context(storage_state="${loadFileName}")`;
|
||||||
|
await cli.waitFor(expectedResult1);
|
||||||
# Open new page
|
|
||||||
page = await context.new_page()
|
|
||||||
|
|
||||||
|
const expectedResult2 = `
|
||||||
# ---------------------
|
# ---------------------
|
||||||
await context.storage_state(path="${saveFileName}")
|
await context.storage_state(path="${saveFileName}")
|
||||||
await context.close()
|
await context.close()
|
||||||
|
|
@ -125,5 +124,5 @@ async def main():
|
||||||
async with async_playwright() as playwright:
|
async with async_playwright() as playwright:
|
||||||
await run(playwright)
|
await run(playwright)
|
||||||
asyncio.run(main())`;
|
asyncio.run(main())`;
|
||||||
await cli.waitFor(expectedResult);
|
await cli.waitFor(expectedResult2);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -22,23 +22,23 @@ const { it, expect } = folio;
|
||||||
|
|
||||||
const emptyHTML = new URL('file://' + path.join(__dirname, '..', 'assets', 'empty.html')).toString();
|
const emptyHTML = new URL('file://' + path.join(__dirname, '..', 'assets', 'empty.html')).toString();
|
||||||
|
|
||||||
it('should print the correct imports and context options', async ({ runCLI }) => {
|
it('should print the correct imports and context options', async ({ runCLI, browserName }) => {
|
||||||
const cli = runCLI(['codegen', '--target=python', emptyHTML]);
|
const cli = runCLI(['codegen', '--target=python', emptyHTML]);
|
||||||
const expectedResult = `from playwright.sync_api import sync_playwright
|
const expectedResult = `from playwright.sync_api import sync_playwright
|
||||||
|
|
||||||
def run(playwright):
|
def run(playwright):
|
||||||
browser = playwright.chromium.launch(headless=False)
|
browser = playwright.${browserName}.launch(headless=False)
|
||||||
context = browser.new_context()`;
|
context = browser.new_context()`;
|
||||||
await cli.waitFor(expectedResult);
|
await cli.waitFor(expectedResult);
|
||||||
expect(cli.text()).toContain(expectedResult);
|
expect(cli.text()).toContain(expectedResult);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should print the correct context options for custom settings', async ({ runCLI }) => {
|
it('should print the correct context options for custom settings', async ({ runCLI, browserName }) => {
|
||||||
const cli = runCLI(['--color-scheme=light', 'codegen', '--target=python', emptyHTML]);
|
const cli = runCLI(['--color-scheme=light', 'codegen', '--target=python', emptyHTML]);
|
||||||
const expectedResult = `from playwright.sync_api import sync_playwright
|
const expectedResult = `from playwright.sync_api import sync_playwright
|
||||||
|
|
||||||
def run(playwright):
|
def run(playwright):
|
||||||
browser = playwright.chromium.launch(headless=False)
|
browser = playwright.${browserName}.launch(headless=False)
|
||||||
context = browser.new_context(color_scheme="light")`;
|
context = browser.new_context(color_scheme="light")`;
|
||||||
await cli.waitFor(expectedResult);
|
await cli.waitFor(expectedResult);
|
||||||
expect(cli.text()).toContain(expectedResult);
|
expect(cli.text()).toContain(expectedResult);
|
||||||
|
|
@ -56,17 +56,17 @@ def run(playwright):
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should print the correct context options when using a device and additional options', async ({ runCLI }) => {
|
it('should print the correct context options when using a device and additional options', async ({ runCLI }) => {
|
||||||
const cli = runCLI(['--color-scheme=light', '--device=Pixel 2', 'codegen', '--target=python', emptyHTML]);
|
const cli = runCLI(['--color-scheme=light', '--device=iPhone 11', 'codegen', '--target=python', emptyHTML]);
|
||||||
const expectedResult = `from playwright.sync_api import sync_playwright
|
const expectedResult = `from playwright.sync_api import sync_playwright
|
||||||
|
|
||||||
def run(playwright):
|
def run(playwright):
|
||||||
browser = playwright.chromium.launch(headless=False)
|
browser = playwright.webkit.launch(headless=False)
|
||||||
context = browser.new_context(**playwright.devices["Pixel 2"], color_scheme="light")`;
|
context = browser.new_context(**playwright.devices["iPhone 11"], color_scheme="light")`;
|
||||||
await cli.waitFor(expectedResult);
|
await cli.waitFor(expectedResult);
|
||||||
expect(cli.text()).toContain(expectedResult);
|
expect(cli.text()).toContain(expectedResult);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should save the codegen output to a file if specified', async ({ runCLI, testInfo }) => {
|
it('should save the codegen output to a file if specified', async ({ runCLI, browserName, testInfo }) => {
|
||||||
const tmpFile = testInfo.outputPath('script.js');
|
const tmpFile = testInfo.outputPath('script.js');
|
||||||
const cli = runCLI(['codegen', '--target=python', '--output', tmpFile, emptyHTML]);
|
const cli = runCLI(['codegen', '--target=python', '--output', tmpFile, emptyHTML]);
|
||||||
await cli.exited;
|
await cli.exited;
|
||||||
|
|
@ -74,7 +74,7 @@ it('should save the codegen output to a file if specified', async ({ runCLI, tes
|
||||||
expect(content.toString()).toBe(`from playwright.sync_api import sync_playwright
|
expect(content.toString()).toBe(`from playwright.sync_api import sync_playwright
|
||||||
|
|
||||||
def run(playwright):
|
def run(playwright):
|
||||||
browser = playwright.chromium.launch(headless=False)
|
browser = playwright.${browserName}.launch(headless=False)
|
||||||
context = browser.new_context()
|
context = browser.new_context()
|
||||||
|
|
||||||
# Open new page
|
# Open new page
|
||||||
|
|
@ -94,20 +94,19 @@ with sync_playwright() as playwright:
|
||||||
run(playwright)`);
|
run(playwright)`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should print load/save storage_state', async ({ runCLI, testInfo }) => {
|
it('should print load/save storage_state', async ({ runCLI, browserName, testInfo }) => {
|
||||||
const loadFileName = testInfo.outputPath('load.json');
|
const loadFileName = testInfo.outputPath('load.json');
|
||||||
const saveFileName = testInfo.outputPath('save.json');
|
const saveFileName = testInfo.outputPath('save.json');
|
||||||
await fs.promises.writeFile(loadFileName, JSON.stringify({ cookies: [], origins: [] }), 'utf8');
|
await fs.promises.writeFile(loadFileName, JSON.stringify({ cookies: [], origins: [] }), 'utf8');
|
||||||
const cli = runCLI([`--load-storage=${loadFileName}`, `--save-storage=${saveFileName}`, 'codegen', '--target=python', emptyHTML]);
|
const cli = runCLI([`--load-storage=${loadFileName}`, `--save-storage=${saveFileName}`, 'codegen', '--target=python', emptyHTML]);
|
||||||
const expectedResult = `from playwright.sync_api import sync_playwright
|
const expectedResult1 = `from playwright.sync_api import sync_playwright
|
||||||
|
|
||||||
def run(playwright):
|
def run(playwright):
|
||||||
browser = playwright.chromium.launch(headless=False)
|
browser = playwright.${browserName}.launch(headless=False)
|
||||||
context = browser.new_context(storage_state="${loadFileName}")
|
context = browser.new_context(storage_state="${loadFileName}")`;
|
||||||
|
await cli.waitFor(expectedResult1);
|
||||||
# Open new page
|
|
||||||
page = context.new_page()
|
|
||||||
|
|
||||||
|
const expectedResult2 = `
|
||||||
# ---------------------
|
# ---------------------
|
||||||
context.storage_state(path="${saveFileName}")
|
context.storage_state(path="${saveFileName}")
|
||||||
context.close()
|
context.close()
|
||||||
|
|
@ -115,5 +114,5 @@ def run(playwright):
|
||||||
|
|
||||||
with sync_playwright() as playwright:
|
with sync_playwright() as playwright:
|
||||||
run(playwright)`;
|
run(playwright)`;
|
||||||
await cli.waitFor(expectedResult);
|
await cli.waitFor(expectedResult2);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,8 @@
|
||||||
import * as http from 'http';
|
import * as http from 'http';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { ChildProcess, spawn } from 'child_process';
|
import { ChildProcess, spawn } from 'child_process';
|
||||||
import { folio as baseFolio } from '../fixtures';
|
import { folio as baseFolio } from '../recorder.fixtures';
|
||||||
import type { Page, BrowserType, Browser, BrowserContext } from '../..';
|
import type { BrowserType, Browser, Page } from '../..';
|
||||||
export { config } from 'folio';
|
export { config } from 'folio';
|
||||||
|
|
||||||
type WorkerFixtures = {
|
type WorkerFixtures = {
|
||||||
|
|
@ -28,28 +28,19 @@ type WorkerFixtures = {
|
||||||
};
|
};
|
||||||
|
|
||||||
type TestFixtures = {
|
type TestFixtures = {
|
||||||
contextWrapper: { context: BrowserContext, output: WritableBuffer };
|
|
||||||
recorder: Recorder;
|
recorder: Recorder;
|
||||||
runCLI: (args: string[]) => CLIMock;
|
runCLI: (args: string[]) => CLIMock;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fixtures = baseFolio.extend<TestFixtures, WorkerFixtures>();
|
export const fixtures = baseFolio.extend<TestFixtures, WorkerFixtures>();
|
||||||
|
|
||||||
fixtures.contextWrapper.init(async ({ browser }, runTest) => {
|
fixtures.recorder.init(async ({ page, recorderFrame }, runTest) => {
|
||||||
const context = await browser.newContext() as BrowserContext;
|
await (page.context() as any)._enableRecorder({ language: 'javascript', startRecording: true });
|
||||||
const outputBuffer = new WritableBuffer();
|
const recorderFrameInstance = await recorderFrame();
|
||||||
(context as any)._stdout = outputBuffer;
|
const recorder = new Recorder(page, recorderFrameInstance);
|
||||||
await (context as any)._enableRecorder({ language: 'javascript', startRecording: true });
|
await recorderFrameInstance._page.context().exposeBinding('playwrightSourceEchoForTest', false,
|
||||||
await runTest({ context, output: outputBuffer });
|
(_: any, text: string) => recorder.setText(text));
|
||||||
await context.close();
|
await runTest(recorder);
|
||||||
});
|
|
||||||
|
|
||||||
fixtures.recorder.init(async ({ contextWrapper }, runTest) => {
|
|
||||||
const page = await contextWrapper.context.newPage();
|
|
||||||
if (process.env.PWCONSOLE)
|
|
||||||
page.on('console', console.log);
|
|
||||||
await runTest(new Recorder(page, contextWrapper.output));
|
|
||||||
await page.close();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
fixtures.httpServer.init(async ({testWorkerIndex}, runTest) => {
|
fixtures.httpServer.init(async ({testWorkerIndex}, runTest) => {
|
||||||
|
|
@ -63,11 +54,6 @@ fixtures.httpServer.init(async ({testWorkerIndex}, runTest) => {
|
||||||
server.close();
|
server.close();
|
||||||
}, { scope: 'worker' });
|
}, { scope: 'worker' });
|
||||||
|
|
||||||
|
|
||||||
fixtures.page.override(async ({ recorder }, runTest) => {
|
|
||||||
await runTest(recorder.page);
|
|
||||||
});
|
|
||||||
|
|
||||||
function removeAnsiColors(input: string): string {
|
function removeAnsiColors(input: string): string {
|
||||||
const pattern = [
|
const pattern = [
|
||||||
'[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)',
|
'[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)',
|
||||||
|
|
@ -76,51 +62,19 @@ function removeAnsiColors(input: string): string {
|
||||||
return input.replace(new RegExp(pattern, 'g'), '');
|
return input.replace(new RegExp(pattern, 'g'), '');
|
||||||
}
|
}
|
||||||
|
|
||||||
class WritableBuffer {
|
|
||||||
_data: string;
|
|
||||||
private _callback: () => void;
|
|
||||||
_text: string;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this._data = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
write(data: Buffer) {
|
|
||||||
if (!data)
|
|
||||||
return;
|
|
||||||
const chunk = data.toString('utf8');
|
|
||||||
this._data += chunk;
|
|
||||||
if (this._callback && chunk.includes(this._text))
|
|
||||||
this._callback();
|
|
||||||
}
|
|
||||||
|
|
||||||
_waitFor(text: string): Promise<void> {
|
|
||||||
if (this._data.includes(text))
|
|
||||||
return Promise.resolve();
|
|
||||||
this._text = text;
|
|
||||||
return new Promise(f => this._callback = f);
|
|
||||||
}
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return this._data;
|
|
||||||
}
|
|
||||||
|
|
||||||
text() {
|
|
||||||
return removeAnsiColors(this.data());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Recorder {
|
class Recorder {
|
||||||
page: Page;
|
page: Page;
|
||||||
_output: WritableBuffer;
|
|
||||||
_highlightCallback: Function
|
_highlightCallback: Function
|
||||||
_highlightInstalled: boolean
|
_highlightInstalled: boolean
|
||||||
_actionReporterInstalled: boolean
|
_actionReporterInstalled: boolean
|
||||||
_actionPerformedCallback: Function
|
_actionPerformedCallback: Function
|
||||||
|
recorderFrame: any;
|
||||||
|
private _text: string;
|
||||||
|
private _waiters = [];
|
||||||
|
|
||||||
constructor(page: Page, output: WritableBuffer) {
|
constructor(page: Page, recorderFrame: any) {
|
||||||
this.page = page;
|
this.page = page;
|
||||||
this._output = output;
|
this.recorderFrame = recorderFrame;
|
||||||
this._highlightCallback = () => { };
|
this._highlightCallback = () => { };
|
||||||
this._highlightInstalled = false;
|
this._highlightInstalled = false;
|
||||||
this._actionReporterInstalled = false;
|
this._actionReporterInstalled = false;
|
||||||
|
|
@ -147,12 +101,20 @@ class Recorder {
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setText(text: string) {
|
||||||
|
this._text = text;
|
||||||
|
for (const waiter of this._waiters) {
|
||||||
|
if (text.includes(waiter.text))
|
||||||
|
waiter.fulfill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async waitForOutput(text: string): Promise<void> {
|
async waitForOutput(text: string): Promise<void> {
|
||||||
await this._output._waitFor(text);
|
return new Promise(fulfill => this._waiters.push({ text, fulfill }));
|
||||||
}
|
}
|
||||||
|
|
||||||
output(): string {
|
output(): string {
|
||||||
return this._output.text();
|
return this._text;
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForHighlight(action: () => Promise<void>): Promise<string> {
|
async waitForHighlight(action: () => Promise<void>): Promise<string> {
|
||||||
|
|
@ -184,10 +146,10 @@ class Recorder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fixtures.runCLI.init(async ({ }, runTest) => {
|
fixtures.runCLI.init(async ({ browserName }, runTest) => {
|
||||||
let cli: CLIMock;
|
let cli: CLIMock;
|
||||||
const cliFactory = (args: string[]) => {
|
const cliFactory = (args: string[]) => {
|
||||||
cli = new CLIMock(args);
|
cli = new CLIMock(browserName, args);
|
||||||
return cli;
|
return cli;
|
||||||
};
|
};
|
||||||
await runTest(cliFactory);
|
await runTest(cliFactory);
|
||||||
|
|
@ -201,10 +163,11 @@ class CLIMock {
|
||||||
private waitForCallback: () => void;
|
private waitForCallback: () => void;
|
||||||
exited: Promise<void>;
|
exited: Promise<void>;
|
||||||
|
|
||||||
constructor(args: string[]) {
|
constructor(browserName, args: string[]) {
|
||||||
this.data = '';
|
this.data = '';
|
||||||
this.process = spawn('node', [
|
this.process = spawn('node', [
|
||||||
path.join(__dirname, '..', '..', 'lib', 'cli', 'cli.js'),
|
path.join(__dirname, '..', '..', 'lib', 'cli', 'cli.js'),
|
||||||
|
`--browser=${browserName}`,
|
||||||
...args
|
...args
|
||||||
], {
|
], {
|
||||||
env: {
|
env: {
|
||||||
|
|
|
||||||
|
|
@ -103,8 +103,8 @@ fixtures.browserOptions.override(async ({ browserName, headful, slowMo }, run) =
|
||||||
fixtures.playwright.override(async ({ browserName, testWorkerIndex, platform, mode }, run) => {
|
fixtures.playwright.override(async ({ browserName, testWorkerIndex, platform, mode }, run) => {
|
||||||
assert(platform); // Depend on platform to generate all tests.
|
assert(platform); // Depend on platform to generate all tests.
|
||||||
const { coverage, uninstall } = installCoverageHooks(browserName);
|
const { coverage, uninstall } = installCoverageHooks(browserName);
|
||||||
|
require('../lib/utils/utils').setUnderTest();
|
||||||
if (mode === 'driver') {
|
if (mode === 'driver') {
|
||||||
require('../lib/utils/utils').setUnderTest();
|
|
||||||
const connection = new Connection();
|
const connection = new Connection();
|
||||||
const spawnedProcess = childProcess.fork(path.join(__dirname, '..', 'lib', 'cli', 'cli.js'), ['run-driver'], {
|
const spawnedProcess = childProcess.fork(path.join(__dirname, '..', 'lib', 'cli', 'cli.js'), ['run-driver'], {
|
||||||
stdio: 'pipe',
|
stdio: 'pipe',
|
||||||
|
|
@ -126,7 +126,6 @@ fixtures.playwright.override(async ({ browserName, testWorkerIndex, platform, mo
|
||||||
spawnedProcess.stderr.destroy();
|
spawnedProcess.stderr.destroy();
|
||||||
await teardownCoverage();
|
await teardownCoverage();
|
||||||
} else if (mode === 'service') {
|
} else if (mode === 'service') {
|
||||||
require('../lib/utils/utils').setUnderTest();
|
|
||||||
const port = 9407 + testWorkerIndex * 2;
|
const port = 9407 + testWorkerIndex * 2;
|
||||||
const spawnedProcess = childProcess.fork(path.join(__dirname, '..', 'lib', 'service.js'), [String(port)], {
|
const spawnedProcess = childProcess.fork(path.join(__dirname, '..', 'lib', 'service.js'), [String(port)], {
|
||||||
stdio: 'pipe'
|
stdio: 'pipe'
|
||||||
|
|
|
||||||
|
|
@ -14,37 +14,8 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { folio } from './fixtures';
|
import { folio } from './recorder.fixtures';
|
||||||
import { internalCallMetadata } from '../lib/server/instrumentation';
|
const { it, expect, describe} = folio;
|
||||||
|
|
||||||
const extended = folio.extend<{
|
|
||||||
recorderFrame: () => Promise<any>,
|
|
||||||
recorderClick: (selector: string) => Promise<void>
|
|
||||||
}>();
|
|
||||||
|
|
||||||
extended.browserOptions.override(async ({browserOptions}, runTest) => {
|
|
||||||
await runTest({
|
|
||||||
...browserOptions,
|
|
||||||
headless: false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
extended.recorderFrame.init(async ({context, toImpl}, runTest) => {
|
|
||||||
await runTest(async () => {
|
|
||||||
while (!toImpl(context).recorderAppForTest)
|
|
||||||
await new Promise(f => setTimeout(f, 100));
|
|
||||||
return toImpl(context).recorderAppForTest._page.mainFrame();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
extended.recorderClick.init(async ({ recorderFrame }, runTest) => {
|
|
||||||
await runTest(async (selector: string) => {
|
|
||||||
const frame = await recorderFrame();
|
|
||||||
frame.click(internalCallMetadata(), selector, {});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const {it, expect, describe} = extended.build();
|
|
||||||
|
|
||||||
describe('pause', (suite, { mode }) => {
|
describe('pause', (suite, { mode }) => {
|
||||||
suite.skip(mode !== 'default');
|
suite.skip(mode !== 'default');
|
||||||
|
|
|
||||||
40
test/recorder.fixtures.ts
Normal file
40
test/recorder.fixtures.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
/**
|
||||||
|
* Copyright 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 { folio as baseFolio } from './fixtures';
|
||||||
|
import { internalCallMetadata } from '../lib/server/instrumentation';
|
||||||
|
|
||||||
|
const fixtures = baseFolio.extend<{
|
||||||
|
recorderFrame: () => Promise<any>,
|
||||||
|
recorderClick: (selector: string) => Promise<void>
|
||||||
|
}>();
|
||||||
|
|
||||||
|
fixtures.recorderFrame.init(async ({context, toImpl}, runTest) => {
|
||||||
|
await runTest(async () => {
|
||||||
|
while (!toImpl(context).recorderAppForTest)
|
||||||
|
await new Promise(f => setTimeout(f, 100));
|
||||||
|
return toImpl(context).recorderAppForTest._page.mainFrame();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
fixtures.recorderClick.init(async ({ recorderFrame }, runTest) => {
|
||||||
|
await runTest(async (selector: string) => {
|
||||||
|
const frame = await recorderFrame();
|
||||||
|
await frame.click(internalCallMetadata(), selector, {});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export const folio = fixtures.build();
|
||||||
|
|
@ -154,7 +154,7 @@ DEPS['src/service.ts'] = ['src/remote/'];
|
||||||
// CLI should only use client-side features.
|
// CLI should only use client-side features.
|
||||||
DEPS['src/cli/'] = ['src/cli/**', 'src/client/**', 'src/install/**', 'src/generated/', 'src/server/injected/', 'src/debug/injected/', 'src/trace/**', 'src/utils/**'];
|
DEPS['src/cli/'] = ['src/cli/**', 'src/client/**', 'src/install/**', 'src/generated/', 'src/server/injected/', 'src/debug/injected/', 'src/trace/**', 'src/utils/**'];
|
||||||
|
|
||||||
DEPS['src/server/supplements/recorder/recorderApp.ts'] = ['src/server/', 'src/server/chromium/'];
|
DEPS['src/server/supplements/recorder/recorderApp.ts'] = ['src/utils/', 'src/server/', 'src/server/chromium/'];
|
||||||
DEPS['src/utils/'] = ['src/common/'];
|
DEPS['src/utils/'] = ['src/common/'];
|
||||||
|
|
||||||
checkDeps().catch(e => {
|
checkDeps().catch(e => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue