mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
test(inspector): add some tests (#5461)
This commit is contained in:
parent
1f3449c7da
commit
0c7da44465
@ -325,7 +325,7 @@ async function openPage(context: BrowserContext, url: string | undefined): Promi
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function open(options: Options, url: string | undefined, language: string) {
|
async function open(options: Options, url: string | undefined, language: string) {
|
||||||
const { context, launchOptions, contextOptions } = await launchContext(options, false);
|
const { context, launchOptions, contextOptions } = await launchContext(options, !!process.env.PWCLI_HEADLESS_FOR_TEST);
|
||||||
await context._enableRecorder({
|
await context._enableRecorder({
|
||||||
language,
|
language,
|
||||||
launchOptions,
|
launchOptions,
|
||||||
@ -339,7 +339,7 @@ async function open(options: Options, url: string | undefined, language: string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function codegen(options: Options, url: string | undefined, language: string, outputFile?: string) {
|
async function codegen(options: Options, url: string | undefined, language: string, outputFile?: string) {
|
||||||
const { context, launchOptions, contextOptions } = await launchContext(options, false);
|
const { context, launchOptions, contextOptions } = await launchContext(options, !!process.env.PWCLI_HEADLESS_FOR_TEST);
|
||||||
if (process.env.PWTRACE)
|
if (process.env.PWTRACE)
|
||||||
contextOptions._traceDir = path.join(process.cwd(), '.trace');
|
contextOptions._traceDir = path.join(process.cwd(), '.trace');
|
||||||
await context._enableRecorder({
|
await context._enableRecorder({
|
||||||
|
|||||||
@ -47,6 +47,7 @@ export type BrowserOptions = PlaywrightOptions & {
|
|||||||
protocolLogger: types.ProtocolLogger,
|
protocolLogger: types.ProtocolLogger,
|
||||||
browserLogsCollector: RecentLogsCollector,
|
browserLogsCollector: RecentLogsCollector,
|
||||||
slowMo?: number;
|
slowMo?: number;
|
||||||
|
wsEndpoint?: string; // Only there when connected over web socket.
|
||||||
};
|
};
|
||||||
|
|
||||||
export abstract class Browser extends SdkObject {
|
export abstract class Browser extends SdkObject {
|
||||||
|
|||||||
@ -20,7 +20,7 @@ import path from 'path';
|
|||||||
import * as util from 'util';
|
import * as util from 'util';
|
||||||
import { BrowserContext, normalizeProxySettings, validateBrowserContextOptions } from './browserContext';
|
import { BrowserContext, normalizeProxySettings, validateBrowserContextOptions } from './browserContext';
|
||||||
import * as registry from '../utils/registry';
|
import * as registry from '../utils/registry';
|
||||||
import { ConnectionTransport } from './transport';
|
import { ConnectionTransport, WebSocketTransport } from './transport';
|
||||||
import { BrowserOptions, Browser, BrowserProcess, PlaywrightOptions } from './browser';
|
import { BrowserOptions, Browser, BrowserProcess, PlaywrightOptions } from './browser';
|
||||||
import { launchProcess, Env, envArrayToObject } from './processLauncher';
|
import { launchProcess, Env, envArrayToObject } from './processLauncher';
|
||||||
import { PipeTransport } from './pipeTransport';
|
import { PipeTransport } from './pipeTransport';
|
||||||
@ -112,6 +112,7 @@ export abstract class BrowserType extends SdkObject {
|
|||||||
proxy: options.proxy,
|
proxy: options.proxy,
|
||||||
protocolLogger,
|
protocolLogger,
|
||||||
browserLogsCollector,
|
browserLogsCollector,
|
||||||
|
wsEndpoint: options.useWebSocket ? (transport as WebSocketTransport).wsEndpoint : undefined,
|
||||||
};
|
};
|
||||||
if (persistent)
|
if (persistent)
|
||||||
validateBrowserContextOptions(persistent, browserOptions);
|
validateBrowserContextOptions(persistent, browserOptions);
|
||||||
@ -180,6 +181,8 @@ export abstract class BrowserType extends SdkObject {
|
|||||||
await validateHostRequirements(this._registry, this._name);
|
await validateHostRequirements(this._registry, this._name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let wsEndpointCallback: ((wsEndpoint: string) => void) | undefined;
|
||||||
|
const wsEndpoint = options.useWebSocket ? new Promise<string>(f => wsEndpointCallback = f) : undefined;
|
||||||
// Note: it is important to define these variables before launchProcess, so that we don't get
|
// Note: it is important to define these variables before launchProcess, so that we don't get
|
||||||
// "Cannot access 'browserServer' before initialization" if something went wrong.
|
// "Cannot access 'browserServer' before initialization" if something went wrong.
|
||||||
let transport: ConnectionTransport | undefined = undefined;
|
let transport: ConnectionTransport | undefined = undefined;
|
||||||
@ -192,6 +195,11 @@ export abstract class BrowserType extends SdkObject {
|
|||||||
handleSIGTERM,
|
handleSIGTERM,
|
||||||
handleSIGHUP,
|
handleSIGHUP,
|
||||||
log: (message: string) => {
|
log: (message: string) => {
|
||||||
|
if (wsEndpointCallback) {
|
||||||
|
const match = message.match(/DevTools listening on (.*)/);
|
||||||
|
if (match)
|
||||||
|
wsEndpointCallback(match[1]);
|
||||||
|
}
|
||||||
progress.log(message);
|
progress.log(message);
|
||||||
browserLogsCollector.log(message);
|
browserLogsCollector.log(message);
|
||||||
},
|
},
|
||||||
@ -217,9 +225,12 @@ export abstract class BrowserType extends SdkObject {
|
|||||||
kill
|
kill
|
||||||
};
|
};
|
||||||
progress.cleanupWhenAborted(() => browserProcess && closeOrKill(browserProcess, progress.timeUntilDeadline()));
|
progress.cleanupWhenAborted(() => browserProcess && closeOrKill(browserProcess, progress.timeUntilDeadline()));
|
||||||
|
if (options.useWebSocket) {
|
||||||
const stdio = launchedProcess.stdio as unknown as [NodeJS.ReadableStream, NodeJS.WritableStream, NodeJS.WritableStream, NodeJS.WritableStream, NodeJS.ReadableStream];
|
transport = await WebSocketTransport.connect(progress, await wsEndpoint!);
|
||||||
transport = new PipeTransport(stdio[3], stdio[4]);
|
} else {
|
||||||
|
const stdio = launchedProcess.stdio as unknown as [NodeJS.ReadableStream, NodeJS.WritableStream, NodeJS.WritableStream, NodeJS.WritableStream, NodeJS.ReadableStream];
|
||||||
|
transport = new PipeTransport(stdio[3], stdio[4]);
|
||||||
|
}
|
||||||
return { browserProcess, downloadsPath, transport };
|
return { browserProcess, downloadsPath, transport };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -242,7 +253,10 @@ function copyTestHooks(from: object, to: object) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function validateLaunchOptions<Options extends types.LaunchOptions>(options: Options): Options {
|
function validateLaunchOptions<Options extends types.LaunchOptions>(options: Options): Options {
|
||||||
const { devtools = false, headless = !isDebugMode() && !devtools } = options;
|
const { devtools = false } = options;
|
||||||
|
let { headless = !devtools } = options;
|
||||||
|
if (isDebugMode())
|
||||||
|
headless = false;
|
||||||
return { ...options, devtools, headless };
|
return { ...options, devtools, headless };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -119,7 +119,10 @@ export class Chromium extends BrowserType {
|
|||||||
throw new Error('Arguments can not specify page to be opened');
|
throw new Error('Arguments can not specify page to be opened');
|
||||||
const chromeArguments = [...DEFAULT_ARGS];
|
const chromeArguments = [...DEFAULT_ARGS];
|
||||||
chromeArguments.push(`--user-data-dir=${userDataDir}`);
|
chromeArguments.push(`--user-data-dir=${userDataDir}`);
|
||||||
chromeArguments.push('--remote-debugging-pipe');
|
if (options.useWebSocket)
|
||||||
|
chromeArguments.push('--remote-debugging-port=0');
|
||||||
|
else
|
||||||
|
chromeArguments.push('--remote-debugging-pipe');
|
||||||
if (options.devtools)
|
if (options.devtools)
|
||||||
chromeArguments.push('--auto-open-devtools-for-tabs');
|
chromeArguments.push('--auto-open-devtools-for-tabs');
|
||||||
if (options.headless) {
|
if (options.headless) {
|
||||||
|
|||||||
@ -21,16 +21,11 @@ import { CallMetadata, InstrumentationListener, SdkObject } from '../instrumenta
|
|||||||
import { isDebugMode, isUnderTest } from '../../utils/utils';
|
import { isDebugMode, isUnderTest } from '../../utils/utils';
|
||||||
|
|
||||||
export class InspectorController implements InstrumentationListener {
|
export class InspectorController implements InstrumentationListener {
|
||||||
private _recorders = new Map<BrowserContext, Promise<RecorderSupplement>>();
|
|
||||||
private _waitOperations = new Map<string, CallMetadata>();
|
private _waitOperations = new Map<string, CallMetadata>();
|
||||||
|
|
||||||
async onContextCreated(context: BrowserContext): Promise<void> {
|
async onContextCreated(context: BrowserContext): Promise<void> {
|
||||||
if (isDebugMode())
|
if (isDebugMode())
|
||||||
this._recorders.set(context, RecorderSupplement.getOrCreate(context));
|
RecorderSupplement.getOrCreate(context);
|
||||||
}
|
|
||||||
|
|
||||||
async onContextDidDestroy(context: BrowserContext): Promise<void> {
|
|
||||||
this._recorders.delete(context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async onBeforeCall(sdkObject: SdkObject, metadata: CallMetadata): Promise<void> {
|
async onBeforeCall(sdkObject: SdkObject, metadata: CallMetadata): Promise<void> {
|
||||||
@ -61,10 +56,10 @@ export class InspectorController implements InstrumentationListener {
|
|||||||
// Force create recorder on pause.
|
// Force create recorder on pause.
|
||||||
if (!context._browser.options.headful && !isUnderTest())
|
if (!context._browser.options.headful && !isUnderTest())
|
||||||
return;
|
return;
|
||||||
this._recorders.set(context, RecorderSupplement.getOrCreate(context));
|
RecorderSupplement.getOrCreate(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
const recorder = await this._recorders.get(context);
|
const recorder = await RecorderSupplement.getNoCreate(context);
|
||||||
await recorder?.onBeforeCall(sdkObject, metadata);
|
await recorder?.onBeforeCall(sdkObject, metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,14 +82,14 @@ export class InspectorController implements InstrumentationListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const recorder = await this._recorders.get(sdkObject.attribution.context);
|
const recorder = await RecorderSupplement.getNoCreate(sdkObject.attribution.context);
|
||||||
await recorder?.onAfterCall(metadata);
|
await recorder?.onAfterCall(metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
async onBeforeInputAction(sdkObject: SdkObject, metadata: CallMetadata): Promise<void> {
|
async onBeforeInputAction(sdkObject: SdkObject, metadata: CallMetadata): Promise<void> {
|
||||||
if (!sdkObject.attribution.context)
|
if (!sdkObject.attribution.context)
|
||||||
return;
|
return;
|
||||||
const recorder = await this._recorders.get(sdkObject.attribution.context);
|
const recorder = await RecorderSupplement.getNoCreate(sdkObject.attribution.context);
|
||||||
await recorder?.onBeforeInputAction(metadata);
|
await recorder?.onBeforeInputAction(metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,7 +97,7 @@ export class InspectorController implements InstrumentationListener {
|
|||||||
debugLogger.log(logName as any, message);
|
debugLogger.log(logName as any, message);
|
||||||
if (!sdkObject.attribution.context)
|
if (!sdkObject.attribution.context)
|
||||||
return;
|
return;
|
||||||
const recorder = await this._recorders.get(sdkObject.attribution.context);
|
const recorder = await RecorderSupplement.getNoCreate(sdkObject.attribution.context);
|
||||||
await recorder?.updateCallLog([metadata]);
|
await recorder?.updateCallLog([metadata]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,7 +26,6 @@ import { internalCallMetadata } from '../../instrumentation';
|
|||||||
import type { CallLog, EventData, Mode, Source } from './recorderTypes';
|
import type { CallLog, EventData, Mode, Source } from './recorderTypes';
|
||||||
import { BrowserContext } from '../../browserContext';
|
import { BrowserContext } from '../../browserContext';
|
||||||
import { isUnderTest } from '../../../utils/utils';
|
import { isUnderTest } from '../../../utils/utils';
|
||||||
import { RecentLogsCollector } from '../../../utils/debugLogger';
|
|
||||||
|
|
||||||
const readFileAsync = util.promisify(fs.readFile);
|
const readFileAsync = util.promisify(fs.readFile);
|
||||||
|
|
||||||
@ -104,34 +103,20 @@ export class RecorderApp extends EventEmitter {
|
|||||||
sdkLanguage: inspectedContext._options.sdkLanguage,
|
sdkLanguage: inspectedContext._options.sdkLanguage,
|
||||||
args,
|
args,
|
||||||
noDefaultViewport: true,
|
noDefaultViewport: true,
|
||||||
headless: isUnderTest() && !inspectedContext._browser.options.headful
|
headless: !!process.env.PWCLI_HEADLESS_FOR_TEST || (isUnderTest() && !inspectedContext._browser.options.headful),
|
||||||
|
useWebSocket: isUnderTest()
|
||||||
});
|
});
|
||||||
const wsEndpoint = isUnderTest() ? await this._parseWsEndpoint(context._browser.options.browserLogsCollector) : undefined;
|
|
||||||
const controller = new ProgressController(internalCallMetadata(), context._browser);
|
const controller = new ProgressController(internalCallMetadata(), context._browser);
|
||||||
await controller.run(async progress => {
|
await controller.run(async progress => {
|
||||||
await context._browser._defaultContext!._loadDefaultContextAsIs(progress);
|
await context._browser._defaultContext!._loadDefaultContextAsIs(progress);
|
||||||
});
|
});
|
||||||
|
|
||||||
const [page] = context.pages();
|
const [page] = context.pages();
|
||||||
const result = new RecorderApp(page, wsEndpoint);
|
const result = new RecorderApp(page, context._browser.options.wsEndpoint);
|
||||||
await result._init();
|
await result._init();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async _parseWsEndpoint(recentLogs: RecentLogsCollector): Promise<string> {
|
|
||||||
let callback: ((log: string) => void) | undefined;
|
|
||||||
const result = new Promise<string>(f => callback = f);
|
|
||||||
const check = (log: string) => {
|
|
||||||
const match = log.match(/DevTools listening on (.*)/);
|
|
||||||
if (match)
|
|
||||||
callback!(match[1]);
|
|
||||||
};
|
|
||||||
for (const log of recentLogs.recentLogs())
|
|
||||||
check(log);
|
|
||||||
recentLogs.on('log', check);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
async setMode(mode: 'none' | 'recording' | 'inspecting'): Promise<void> {
|
async setMode(mode: 'none' | 'recording' | 'inspecting'): Promise<void> {
|
||||||
await this._page.mainFrame()._evaluateExpression(((mode: Mode) => {
|
await this._page.mainFrame()._evaluateExpression(((mode: Mode) => {
|
||||||
window.playwrightSetMode(mode);
|
window.playwrightSetMode(mode);
|
||||||
|
|||||||
@ -67,6 +67,10 @@ export class RecorderSupplement {
|
|||||||
return recorderPromise;
|
return recorderPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getNoCreate(context: BrowserContext): Promise<RecorderSupplement> | undefined {
|
||||||
|
return (context as any)[symbol] as Promise<RecorderSupplement> | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(context: BrowserContext, params: channels.BrowserContextRecorderSupplementEnableParams) {
|
constructor(context: BrowserContext, params: channels.BrowserContextRecorderSupplementEnableParams) {
|
||||||
this._context = context;
|
this._context = context;
|
||||||
this._params = params;
|
this._params = params;
|
||||||
@ -325,6 +329,8 @@ export class RecorderSupplement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async onBeforeCall(sdkObject: SdkObject, metadata: CallMetadata): Promise<void> {
|
async onBeforeCall(sdkObject: SdkObject, metadata: CallMetadata): Promise<void> {
|
||||||
|
if (this._mode === 'recording')
|
||||||
|
return;
|
||||||
this._currentCallsMetadata.set(metadata, sdkObject);
|
this._currentCallsMetadata.set(metadata, sdkObject);
|
||||||
this._updateUserSources();
|
this._updateUserSources();
|
||||||
this.updateCallLog([metadata]);
|
this.updateCallLog([metadata]);
|
||||||
@ -333,6 +339,8 @@ export class RecorderSupplement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async onAfterCall(metadata: CallMetadata): Promise<void> {
|
async onAfterCall(metadata: CallMetadata): Promise<void> {
|
||||||
|
if (this._mode === 'recording')
|
||||||
|
return;
|
||||||
if (!metadata.error)
|
if (!metadata.error)
|
||||||
this._currentCallsMetadata.delete(metadata);
|
this._currentCallsMetadata.delete(metadata);
|
||||||
this._pausedCallsMetadata.delete(metadata);
|
this._pausedCallsMetadata.delete(metadata);
|
||||||
@ -372,16 +380,20 @@ export class RecorderSupplement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async onBeforeInputAction(metadata: CallMetadata): Promise<void> {
|
async onBeforeInputAction(metadata: CallMetadata): Promise<void> {
|
||||||
|
if (this._mode === 'recording')
|
||||||
|
return;
|
||||||
if (this._pauseOnNextStatement)
|
if (this._pauseOnNextStatement)
|
||||||
await this.pause(metadata);
|
await this.pause(metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateCallLog(metadatas: CallMetadata[]): Promise<void> {
|
async updateCallLog(metadatas: CallMetadata[]): Promise<void> {
|
||||||
|
if (this._mode === 'recording')
|
||||||
|
return;
|
||||||
const logs: CallLog[] = [];
|
const logs: CallLog[] = [];
|
||||||
for (const metadata of metadatas) {
|
for (const metadata of metadatas) {
|
||||||
if (!metadata.method)
|
if (!metadata.method)
|
||||||
continue;
|
continue;
|
||||||
const title = metadata.stack?.[0]?.function || metadata.method;
|
const title = metadata.method;
|
||||||
let status: 'done' | 'in-progress' | 'paused' | 'error' = 'done';
|
let status: 'done' | 'in-progress' | 'paused' | 'error' = 'done';
|
||||||
if (this._currentCallsMetadata.has(metadata))
|
if (this._currentCallsMetadata.has(metadata))
|
||||||
status = 'in-progress';
|
status = 'in-progress';
|
||||||
|
|||||||
@ -50,6 +50,7 @@ export class WebSocketTransport implements ConnectionTransport {
|
|||||||
|
|
||||||
onmessage?: (message: ProtocolResponse) => void;
|
onmessage?: (message: ProtocolResponse) => void;
|
||||||
onclose?: () => void;
|
onclose?: () => void;
|
||||||
|
readonly wsEndpoint: string;
|
||||||
|
|
||||||
static async connect(progress: Progress, url: string): Promise<WebSocketTransport> {
|
static async connect(progress: Progress, url: string): Promise<WebSocketTransport> {
|
||||||
progress.log(`<ws connecting> ${url}`);
|
progress.log(`<ws connecting> ${url}`);
|
||||||
@ -75,6 +76,7 @@ export class WebSocketTransport implements ConnectionTransport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
constructor(progress: Progress, url: string) {
|
constructor(progress: Progress, url: string) {
|
||||||
|
this.wsEndpoint = url;
|
||||||
this._ws = new WebSocket(url, [], {
|
this._ws = new WebSocket(url, [], {
|
||||||
perMessageDeflate: false,
|
perMessageDeflate: false,
|
||||||
maxPayload: 256 * 1024 * 1024, // 256Mb,
|
maxPayload: 256 * 1024 * 1024, // 256Mb,
|
||||||
|
|||||||
@ -264,7 +264,8 @@ type LaunchOptionsBase = {
|
|||||||
proxy?: ProxySettings,
|
proxy?: ProxySettings,
|
||||||
downloadsPath?: string,
|
downloadsPath?: string,
|
||||||
chromiumSandbox?: boolean,
|
chromiumSandbox?: boolean,
|
||||||
slowMo?: number;
|
slowMo?: number,
|
||||||
|
useWebSocket?: boolean,
|
||||||
};
|
};
|
||||||
export type LaunchOptions = LaunchOptionsBase & {
|
export type LaunchOptions = LaunchOptionsBase & {
|
||||||
firefoxUserPrefs?: { [key: string]: string | number | boolean },
|
firefoxUserPrefs?: { [key: string]: string | number | boolean },
|
||||||
|
|||||||
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { EventEmitter } from 'events';
|
|
||||||
|
|
||||||
const debugLoggerColorMap = {
|
const debugLoggerColorMap = {
|
||||||
'api': 45, // cyan
|
'api': 45, // cyan
|
||||||
@ -64,11 +63,10 @@ class DebugLogger {
|
|||||||
export const debugLogger = new DebugLogger();
|
export const debugLogger = new DebugLogger();
|
||||||
|
|
||||||
const kLogCount = 50;
|
const kLogCount = 50;
|
||||||
export class RecentLogsCollector extends EventEmitter {
|
export class RecentLogsCollector {
|
||||||
private _logs: string[] = [];
|
private _logs: string[] = [];
|
||||||
|
|
||||||
log(message: string) {
|
log(message: string) {
|
||||||
this.emit('log', message);
|
|
||||||
this._logs.push(message);
|
this._logs.push(message);
|
||||||
if (this._logs.length === kLogCount * 2)
|
if (this._logs.length === kLogCount * 2)
|
||||||
this._logs.splice(0, kLogCount);
|
this._logs.splice(0, kLogCount);
|
||||||
|
|||||||
@ -18,7 +18,7 @@
|
|||||||
import { folio } from './remoteServer.fixture';
|
import { folio } from './remoteServer.fixture';
|
||||||
const { it, expect, describe } = folio;
|
const { it, expect, describe } = folio;
|
||||||
|
|
||||||
describe('lauch server', (suite, { mode }) => {
|
describe('launch server', (suite, { mode }) => {
|
||||||
suite.skip(mode !== 'default');
|
suite.skip(mode !== 'default');
|
||||||
}, () => {
|
}, () => {
|
||||||
it('should work', async ({browserType, browserOptions}) => {
|
it('should work', async ({browserType, browserOptions}) => {
|
||||||
|
|||||||
@ -19,9 +19,9 @@ import * as http from 'http';
|
|||||||
|
|
||||||
const { it, describe, expect } = folio;
|
const { it, describe, expect } = folio;
|
||||||
|
|
||||||
describe('cli codegen', (suite, { mode, browserName, headful }) => {
|
describe('cli codegen', (suite, { browserName, headful, mode }) => {
|
||||||
suite.fixme(browserName === 'firefox' && headful, 'Focus is off');
|
|
||||||
suite.skip(mode !== 'default');
|
suite.skip(mode !== 'default');
|
||||||
|
suite.fixme(browserName === 'firefox' && headful, 'Focus is off');
|
||||||
}, () => {
|
}, () => {
|
||||||
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>`);
|
||||||
|
|||||||
@ -20,8 +20,7 @@ import * as url from 'url';
|
|||||||
|
|
||||||
const { it, describe, expect } = folio;
|
const { it, describe, expect } = folio;
|
||||||
|
|
||||||
describe('cli codegen', (suite, { mode, browserName, headful }) => {
|
describe('cli codegen', (suite, { mode }) => {
|
||||||
// suite.fixme(browserName === 'firefox' && headful, 'Focus is off');
|
|
||||||
suite.skip(mode !== 'default');
|
suite.skip(mode !== 'default');
|
||||||
}, () => {
|
}, () => {
|
||||||
it('should contain open page', async ({ recorder }) => {
|
it('should contain open page', async ({ recorder }) => {
|
||||||
|
|||||||
@ -146,10 +146,10 @@ class Recorder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fixtures.runCLI.init(async ({ browserName }, runTest) => {
|
fixtures.runCLI.init(async ({ browserName, headful }, runTest) => {
|
||||||
let cli: CLIMock;
|
let cli: CLIMock;
|
||||||
const cliFactory = (args: string[]) => {
|
const cliFactory = (args: string[]) => {
|
||||||
cli = new CLIMock(browserName, args);
|
cli = new CLIMock(browserName, !headful, args);
|
||||||
return cli;
|
return cli;
|
||||||
};
|
};
|
||||||
await runTest(cliFactory);
|
await runTest(cliFactory);
|
||||||
@ -163,7 +163,7 @@ class CLIMock {
|
|||||||
private waitForCallback: () => void;
|
private waitForCallback: () => void;
|
||||||
exited: Promise<void>;
|
exited: Promise<void>;
|
||||||
|
|
||||||
constructor(browserName, args: string[]) {
|
constructor(browserName: string, headless: boolean, 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'),
|
||||||
@ -172,7 +172,8 @@ class CLIMock {
|
|||||||
], {
|
], {
|
||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
PWCLI_EXIT_FOR_TEST: '1'
|
PWCLI_EXIT_FOR_TEST: '1',
|
||||||
|
PWCLI_HEADLESS_FOR_TEST: headless ? '1' : undefined,
|
||||||
},
|
},
|
||||||
stdio: 'pipe'
|
stdio: 'pipe'
|
||||||
});
|
});
|
||||||
|
|||||||
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { expect } from 'folio';
|
import { expect } from 'folio';
|
||||||
|
import { Page } from '..';
|
||||||
import { folio } from './recorder.fixtures';
|
import { folio } from './recorder.fixtures';
|
||||||
const { it, describe} = folio;
|
const { it, describe} = folio;
|
||||||
|
|
||||||
@ -119,4 +120,116 @@ describe('pause', (suite, { mode }) => {
|
|||||||
await recorderPage.click('[title="Step over"]');
|
await recorderPage.click('[title="Step over"]');
|
||||||
await scriptPromise;
|
await scriptPromise;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should skip input when resuming', async ({page, recorderPageGetter}) => {
|
||||||
|
await page.setContent('<button>Submit</button>');
|
||||||
|
const scriptPromise = (async () => {
|
||||||
|
await page.pause();
|
||||||
|
await page.click('button');
|
||||||
|
await page.pause(); // 2
|
||||||
|
})();
|
||||||
|
const recorderPage = await recorderPageGetter();
|
||||||
|
await recorderPage.click('[title="Resume"]');
|
||||||
|
await recorderPage.waitForSelector('.source-line-paused:has-text("page.pause(); // 2")');
|
||||||
|
await recorderPage.click('[title=Resume]');
|
||||||
|
await scriptPromise;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should populate log', async ({page, recorderPageGetter}) => {
|
||||||
|
await page.setContent('<button>Submit</button>');
|
||||||
|
const scriptPromise = (async () => {
|
||||||
|
await page.pause();
|
||||||
|
await page.click('button');
|
||||||
|
await page.pause(); // 2
|
||||||
|
})();
|
||||||
|
const recorderPage = await recorderPageGetter();
|
||||||
|
await recorderPage.click('[title="Resume"]');
|
||||||
|
await recorderPage.waitForSelector('.source-line-paused:has-text("page.pause(); // 2")');
|
||||||
|
expect(await sanitizeLog(recorderPage)).toEqual([
|
||||||
|
'pause',
|
||||||
|
'click',
|
||||||
|
'waiting for selector "button"',
|
||||||
|
'selector resolved to visible <button>Submit</button>',
|
||||||
|
'attempting click action',
|
||||||
|
'waiting for element to be visible, enabled and stable',
|
||||||
|
'element is visible, enabled and stable',
|
||||||
|
'scrolling into view if needed',
|
||||||
|
'done scrolling',
|
||||||
|
'checking that element receives pointer events at ()',
|
||||||
|
'element does receive pointer events',
|
||||||
|
'performing click action',
|
||||||
|
'click action done',
|
||||||
|
'waiting for scheduled navigations to finish',
|
||||||
|
'navigations have finished',
|
||||||
|
'pause',
|
||||||
|
]);
|
||||||
|
await recorderPage.click('[title="Resume"]');
|
||||||
|
await scriptPromise;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should populate log with waitForEvent', async ({page, recorderPageGetter}) => {
|
||||||
|
await page.setContent('<button onclick="console.log(1)">Submit</button>');
|
||||||
|
const scriptPromise = (async () => {
|
||||||
|
await page.pause();
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForEvent('console'),
|
||||||
|
page.click('button'),
|
||||||
|
]);
|
||||||
|
await page.pause(); // 2
|
||||||
|
})();
|
||||||
|
const recorderPage = await recorderPageGetter();
|
||||||
|
await recorderPage.click('[title="Resume"]');
|
||||||
|
await recorderPage.waitForSelector('.source-line-paused:has-text("page.pause(); // 2")');
|
||||||
|
expect(await sanitizeLog(recorderPage)).toEqual([
|
||||||
|
'pause',
|
||||||
|
'waitForEvent()',
|
||||||
|
'waiting for event \"console\"',
|
||||||
|
'click',
|
||||||
|
'waiting for selector "button"',
|
||||||
|
'selector resolved to visible <button onclick=\"console.log()\">Submit</button>',
|
||||||
|
'attempting click action',
|
||||||
|
'waiting for element to be visible, enabled and stable',
|
||||||
|
'element is visible, enabled and stable',
|
||||||
|
'scrolling into view if needed',
|
||||||
|
'done scrolling',
|
||||||
|
'checking that element receives pointer events at ()',
|
||||||
|
'element does receive pointer events',
|
||||||
|
'performing click action',
|
||||||
|
'click action done',
|
||||||
|
'waiting for scheduled navigations to finish',
|
||||||
|
'navigations have finished',
|
||||||
|
'pause',
|
||||||
|
]);
|
||||||
|
await recorderPage.click('[title="Resume"]');
|
||||||
|
await scriptPromise;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should populate log with error', async ({page, recorderPageGetter}) => {
|
||||||
|
await page.setContent('<button onclick="console.log(1)">Submit</button>');
|
||||||
|
const scriptPromise = (async () => {
|
||||||
|
await page.pause();
|
||||||
|
await page.isChecked('button');
|
||||||
|
})().catch(e => e);
|
||||||
|
const recorderPage = await recorderPageGetter();
|
||||||
|
await recorderPage.click('[title="Resume"]');
|
||||||
|
await recorderPage.waitForSelector('.source-line-error');
|
||||||
|
expect(await sanitizeLog(recorderPage)).toEqual([
|
||||||
|
'pause',
|
||||||
|
'isChecked',
|
||||||
|
'checking \"checked\" state of \"button\"',
|
||||||
|
'selector resolved to <button onclick=\"console.log()\">Submit</button>',
|
||||||
|
'Not a checkbox or radio button',
|
||||||
|
]);
|
||||||
|
const error = await scriptPromise;
|
||||||
|
expect(error.message).toContain('Not a checkbox or radio button');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function sanitizeLog(recorderPage: Page): Promise<string[]> {
|
||||||
|
const text = await recorderPage.innerText('.recorder-log');
|
||||||
|
return text.split('\n').filter(l => {
|
||||||
|
return l !== 'element is not stable - waiting...';
|
||||||
|
}).map(l => {
|
||||||
|
return l.replace(/\(.*\)/, '()');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user