chore: allow inspecting element from server (#16324)
This commit is contained in:
Родитель
850e14eccf
Коммит
8ed238843b
|
@ -570,7 +570,7 @@ async function codegen(options: Options, url: string | undefined, language: stri
|
||||||
contextOptions,
|
contextOptions,
|
||||||
device: options.device,
|
device: options.device,
|
||||||
saveStorage: options.saveStorage,
|
saveStorage: options.saveStorage,
|
||||||
startRecording: true,
|
mode: 'recording',
|
||||||
outputFile: outputFile ? path.resolve(outputFile) : undefined
|
outputFile: outputFile ? path.resolve(outputFile) : undefined
|
||||||
});
|
});
|
||||||
await openPage(context, url);
|
await openPage(context, url);
|
||||||
|
|
|
@ -21,9 +21,13 @@ import * as playwright from '../..';
|
||||||
import type { BrowserType } from '../client/browserType';
|
import type { BrowserType } from '../client/browserType';
|
||||||
import type { LaunchServerOptions } from '../client/types';
|
import type { LaunchServerOptions } from '../client/types';
|
||||||
import { createPlaywright, DispatcherConnection, Root, PlaywrightDispatcher } from '../server';
|
import { createPlaywright, DispatcherConnection, Root, PlaywrightDispatcher } from '../server';
|
||||||
|
import type { Playwright } from '../server';
|
||||||
import { IpcTransport, PipeTransport } from '../protocol/transport';
|
import { IpcTransport, PipeTransport } from '../protocol/transport';
|
||||||
import { PlaywrightServer } from '../remote/playwrightServer';
|
import { PlaywrightServer } from '../remote/playwrightServer';
|
||||||
import { gracefullyCloseAll } from '../utils/processLauncher';
|
import { gracefullyCloseAll } from '../utils/processLauncher';
|
||||||
|
import { Recorder } from '../server/recorder';
|
||||||
|
import { EmptyRecorderApp } from '../server/recorder/recorderApp';
|
||||||
|
import type { BrowserContext } from '../server/browserContext';
|
||||||
|
|
||||||
export function printApiJson() {
|
export function printApiJson() {
|
||||||
// Note: this file is generated by build-playwright-driver.sh
|
// Note: this file is generated by build-playwright-driver.sh
|
||||||
|
@ -54,10 +58,8 @@ export async function runServer(port: number | undefined, path = '/', maxClients
|
||||||
process.on('exit', () => server.close().catch(console.error));
|
process.on('exit', () => server.close().catch(console.error));
|
||||||
console.log('Listening on ' + wsEndpoint); // eslint-disable-line no-console
|
console.log('Listening on ' + wsEndpoint); // eslint-disable-line no-console
|
||||||
process.stdin.on('close', () => selfDestruct());
|
process.stdin.on('close', () => selfDestruct());
|
||||||
process.stdin.on('data', data => {
|
if (process.send && server.preLaunchedPlaywright())
|
||||||
if (data.toString() === '<EOL>')
|
wireController(server.preLaunchedPlaywright()!, wsEndpoint);
|
||||||
selfDestruct();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function launchBrowserServer(browserName: string, configFile?: string) {
|
export async function launchBrowserServer(browserName: string, configFile?: string) {
|
||||||
|
@ -77,3 +79,42 @@ function selfDestruct() {
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function wireController(playwright: Playwright, wsEndpoint: string) {
|
||||||
|
process.send!({ method: 'ready', params: { wsEndpoint } });
|
||||||
|
process.on('message', async message => {
|
||||||
|
try {
|
||||||
|
if (message.method === 'kill') {
|
||||||
|
selfDestruct();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.method === 'inspect') {
|
||||||
|
for (const recorder of await allRecorders(playwright))
|
||||||
|
recorder.setMode(message.params.enabled ? 'inspecting' : 'none');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.method === 'highlight') {
|
||||||
|
for (const recorder of await allRecorders(playwright))
|
||||||
|
recorder.setHighlightedSelector(message.params.selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
process.send!({ method: 'error', params: { error: e.toString() } });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function allRecorders(playwright: Playwright): Promise<Recorder[]> {
|
||||||
|
const contexts = new Set<BrowserContext>();
|
||||||
|
for (const page of playwright.allPages())
|
||||||
|
contexts.add(page.context());
|
||||||
|
const result = await Promise.all([...contexts].map(c => Recorder.show(c, {}, () => Promise.resolve(new InspectingRecorderApp()))));
|
||||||
|
return result.filter(Boolean) as Recorder[];
|
||||||
|
}
|
||||||
|
|
||||||
|
class InspectingRecorderApp extends EmptyRecorderApp {
|
||||||
|
override async setSelector(selector: string): Promise<void> {
|
||||||
|
process.send!({ method: 'inspectRequested', params: { selector } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -373,7 +373,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
|
||||||
contextOptions?: BrowserContextOptions,
|
contextOptions?: BrowserContextOptions,
|
||||||
device?: string,
|
device?: string,
|
||||||
saveStorage?: string,
|
saveStorage?: string,
|
||||||
startRecording?: boolean,
|
mode?: 'recording' | 'inspecting',
|
||||||
outputFile?: string
|
outputFile?: string
|
||||||
}) {
|
}) {
|
||||||
await this._channel.recorderSupplementEnable(params);
|
await this._channel.recorderSupplementEnable(params);
|
||||||
|
|
|
@ -1416,7 +1416,7 @@ export type BrowserContextPauseOptions = {};
|
||||||
export type BrowserContextPauseResult = void;
|
export type BrowserContextPauseResult = void;
|
||||||
export type BrowserContextRecorderSupplementEnableParams = {
|
export type BrowserContextRecorderSupplementEnableParams = {
|
||||||
language?: string,
|
language?: string,
|
||||||
startRecording?: boolean,
|
mode?: 'inspecting' | 'recording',
|
||||||
pauseOnNextStatement?: boolean,
|
pauseOnNextStatement?: boolean,
|
||||||
launchOptions?: any,
|
launchOptions?: any,
|
||||||
contextOptions?: any,
|
contextOptions?: any,
|
||||||
|
@ -1426,7 +1426,7 @@ export type BrowserContextRecorderSupplementEnableParams = {
|
||||||
};
|
};
|
||||||
export type BrowserContextRecorderSupplementEnableOptions = {
|
export type BrowserContextRecorderSupplementEnableOptions = {
|
||||||
language?: string,
|
language?: string,
|
||||||
startRecording?: boolean,
|
mode?: 'inspecting' | 'recording',
|
||||||
pauseOnNextStatement?: boolean,
|
pauseOnNextStatement?: boolean,
|
||||||
launchOptions?: any,
|
launchOptions?: any,
|
||||||
contextOptions?: any,
|
contextOptions?: any,
|
||||||
|
|
|
@ -937,7 +937,11 @@ BrowserContext:
|
||||||
experimental: True
|
experimental: True
|
||||||
parameters:
|
parameters:
|
||||||
language: string?
|
language: string?
|
||||||
startRecording: boolean?
|
mode:
|
||||||
|
type: enum?
|
||||||
|
literals:
|
||||||
|
- inspecting
|
||||||
|
- recording
|
||||||
pauseOnNextStatement: boolean?
|
pauseOnNextStatement: boolean?
|
||||||
launchOptions: json?
|
launchOptions: json?
|
||||||
contextOptions: json?
|
contextOptions: json?
|
||||||
|
|
|
@ -765,7 +765,7 @@ scheme.BrowserContextPauseParams = tOptional(tObject({}));
|
||||||
scheme.BrowserContextPauseResult = tOptional(tObject({}));
|
scheme.BrowserContextPauseResult = tOptional(tObject({}));
|
||||||
scheme.BrowserContextRecorderSupplementEnableParams = tObject({
|
scheme.BrowserContextRecorderSupplementEnableParams = tObject({
|
||||||
language: tOptional(tString),
|
language: tOptional(tString),
|
||||||
startRecording: tOptional(tBoolean),
|
mode: tOptional(tEnum(['inspecting', 'recording'])),
|
||||||
pauseOnNextStatement: tOptional(tBoolean),
|
pauseOnNextStatement: tOptional(tBoolean),
|
||||||
launchOptions: tOptional(tAny),
|
launchOptions: tOptional(tAny),
|
||||||
contextOptions: tOptional(tAny),
|
contextOptions: tOptional(tAny),
|
||||||
|
|
|
@ -62,6 +62,10 @@ export class PlaywrightServer {
|
||||||
this._preLaunchedPlaywright = createPlaywright('javascript');
|
this._preLaunchedPlaywright = createPlaywright('javascript');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
preLaunchedPlaywright(): Playwright | null {
|
||||||
|
return this._preLaunchedPlaywright;
|
||||||
|
}
|
||||||
|
|
||||||
async listen(port: number = 0): Promise<string> {
|
async listen(port: number = 0): Promise<string> {
|
||||||
const server = http.createServer((request, response) => {
|
const server = http.createServer((request, response) => {
|
||||||
response.end('Running');
|
response.end('Running');
|
||||||
|
|
|
@ -85,7 +85,7 @@ class Recorder {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _pollRecorderMode() {
|
private async _pollRecorderMode() {
|
||||||
const pollPeriod = 1000;
|
const pollPeriod = 500;
|
||||||
if (this._pollRecorderModeTimer)
|
if (this._pollRecorderModeTimer)
|
||||||
clearTimeout(this._pollRecorderModeTimer);
|
clearTimeout(this._pollRecorderModeTimer);
|
||||||
const state = await globalThis.__pw_recorderState().catch(e => null);
|
const state = await globalThis.__pw_recorderState().catch(e => null);
|
||||||
|
|
|
@ -69,6 +69,10 @@ export class Playwright extends SdkObject {
|
||||||
allBrowsers(): Browser[] {
|
allBrowsers(): Browser[] {
|
||||||
return [...this._allBrowsers];
|
return [...this._allBrowsers];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
allPages(): Page[] {
|
||||||
|
return [...this._allPages];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createPlaywright(sdkLanguage: string, isInternalPlaywright: boolean = false) {
|
export function createPlaywright(sdkLanguage: string, isInternalPlaywright: boolean = false) {
|
||||||
|
|
|
@ -29,6 +29,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 { EmptyRecorderApp } from './recorder/recorderApp';
|
||||||
import type { IRecorderApp } from './recorder/recorderApp';
|
import type { IRecorderApp } from './recorder/recorderApp';
|
||||||
import { RecorderApp } from './recorder/recorderApp';
|
import { RecorderApp } from './recorder/recorderApp';
|
||||||
import type { CallMetadata, InstrumentationListener, SdkObject } from './instrumentation';
|
import type { CallMetadata, InstrumentationListener, SdkObject } from './instrumentation';
|
||||||
|
@ -42,7 +43,7 @@ import { raceAgainstTimeout } from '../utils/timeoutRunner';
|
||||||
|
|
||||||
type BindingSource = { frame: Frame, page: Page };
|
type BindingSource = { frame: Frame, page: Page };
|
||||||
|
|
||||||
const symbol = Symbol('RecorderSupplement');
|
const recorderSymbol = Symbol('recorderSymbol');
|
||||||
|
|
||||||
export class Recorder implements InstrumentationListener {
|
export class Recorder implements InstrumentationListener {
|
||||||
private _context: BrowserContext;
|
private _context: BrowserContext;
|
||||||
|
@ -55,31 +56,39 @@ export class Recorder implements InstrumentationListener {
|
||||||
private _allMetadatas = new Map<string, CallMetadata>();
|
private _allMetadatas = new Map<string, CallMetadata>();
|
||||||
private _debugger: Debugger;
|
private _debugger: Debugger;
|
||||||
private _contextRecorder: ContextRecorder;
|
private _contextRecorder: ContextRecorder;
|
||||||
|
private _recorderAppFactory: (recorder: Recorder) => Promise<IRecorderApp>;
|
||||||
|
|
||||||
static showInspector(context: BrowserContext) {
|
static showInspector(context: BrowserContext) {
|
||||||
Recorder.show(context, {}).catch(() => {});
|
Recorder.show(context, {}).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
static show(context: BrowserContext, params: channels.BrowserContextRecorderSupplementEnableParams = {}): Promise<Recorder> {
|
static show(context: BrowserContext, params: channels.BrowserContextRecorderSupplementEnableParams = {}, recorderAppFactory = Recorder.defaultRecorderAppFactory): Promise<Recorder> {
|
||||||
let recorderPromise = (context as any)[symbol] as Promise<Recorder>;
|
let recorderPromise = (context as any)[recorderSymbol] as Promise<Recorder>;
|
||||||
if (!recorderPromise) {
|
if (!recorderPromise) {
|
||||||
const recorder = new Recorder(context, params);
|
const recorder = new Recorder(context, params, recorderAppFactory);
|
||||||
recorderPromise = recorder.install().then(() => recorder);
|
recorderPromise = recorder.install().then(() => recorder);
|
||||||
(context as any)[symbol] = recorderPromise;
|
(context as any)[recorderSymbol] = recorderPromise;
|
||||||
}
|
}
|
||||||
return recorderPromise;
|
return recorderPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(context: BrowserContext, params: channels.BrowserContextRecorderSupplementEnableParams) {
|
constructor(context: BrowserContext, params: channels.BrowserContextRecorderSupplementEnableParams, recorderAppFactory: (recorder: Recorder) => Promise<IRecorderApp>) {
|
||||||
this._mode = params.startRecording ? 'recording' : 'none';
|
this._mode = params.mode || 'none';
|
||||||
|
this._recorderAppFactory = recorderAppFactory;
|
||||||
this._contextRecorder = new ContextRecorder(context, params);
|
this._contextRecorder = new ContextRecorder(context, params);
|
||||||
this._context = context;
|
this._context = context;
|
||||||
this._debugger = Debugger.lookup(context)!;
|
this._debugger = Debugger.lookup(context)!;
|
||||||
context.instrumentation.addListener(this, context);
|
context.instrumentation.addListener(this, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async defaultRecorderAppFactory(recorder: Recorder) {
|
||||||
|
if (process.env.PW_CODEGEN_NO_INSPECTOR)
|
||||||
|
return new EmptyRecorderApp();
|
||||||
|
return await RecorderApp.open(recorder, recorder._context);
|
||||||
|
}
|
||||||
|
|
||||||
async install() {
|
async install() {
|
||||||
const recorderApp = await RecorderApp.open(this._context._browser.options.sdkLanguage, !!this._context._browser.options.headful);
|
const recorderApp = await this._recorderAppFactory(this);
|
||||||
this._recorderApp = recorderApp;
|
this._recorderApp = recorderApp;
|
||||||
recorderApp.once('close', () => {
|
recorderApp.once('close', () => {
|
||||||
this._debugger.resume(false);
|
this._debugger.resume(false);
|
||||||
|
@ -87,7 +96,7 @@ export class Recorder implements InstrumentationListener {
|
||||||
});
|
});
|
||||||
recorderApp.on('event', (data: EventData) => {
|
recorderApp.on('event', (data: EventData) => {
|
||||||
if (data.event === 'setMode') {
|
if (data.event === 'setMode') {
|
||||||
this._setMode(data.params.mode);
|
this.setMode(data.params.mode);
|
||||||
this._refreshOverlay();
|
this._refreshOverlay();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -149,9 +158,7 @@ export class Recorder implements InstrumentationListener {
|
||||||
});
|
});
|
||||||
|
|
||||||
await this._context.exposeBinding('__pw_recorderSetSelector', false, async (_, selector: string) => {
|
await this._context.exposeBinding('__pw_recorderSetSelector', false, async (_, selector: string) => {
|
||||||
this._setMode('none');
|
|
||||||
await this._recorderApp?.setSelector(selector, true);
|
await this._recorderApp?.setSelector(selector, true);
|
||||||
await this._recorderApp?.bringToFront();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await this._context.exposeBinding('__pw_resume', false, () => {
|
await this._context.exposeBinding('__pw_resume', false, () => {
|
||||||
|
@ -179,15 +186,22 @@ export class Recorder implements InstrumentationListener {
|
||||||
this.updateCallLog([...this._currentCallsMetadata.keys()]);
|
this.updateCallLog([...this._currentCallsMetadata.keys()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setMode(mode: Mode) {
|
setMode(mode: Mode) {
|
||||||
|
if (this._mode === mode)
|
||||||
|
return;
|
||||||
this._mode = mode;
|
this._mode = mode;
|
||||||
this._recorderApp?.setMode(this._mode);
|
this._recorderApp?.setMode(this._mode);
|
||||||
this._contextRecorder.setEnabled(this._mode === 'recording');
|
this._contextRecorder.setEnabled(this._mode === 'recording');
|
||||||
this._debugger.setMuted(this._mode === 'recording');
|
this._debugger.setMuted(this._mode === 'recording');
|
||||||
if (this._mode !== 'none')
|
if (this._mode !== 'none' && this._context.pages().length === 1)
|
||||||
this._context.pages()[0].bringToFront().catch(() => {});
|
this._context.pages()[0].bringToFront().catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setHighlightedSelector(selector: string) {
|
||||||
|
this._highlightedSelector = selector;
|
||||||
|
this._refreshOverlay();
|
||||||
|
}
|
||||||
|
|
||||||
private _refreshOverlay() {
|
private _refreshOverlay() {
|
||||||
for (const page of this._context.pages())
|
for (const page of this._context.pages())
|
||||||
page.mainFrame().evaluateExpression('window.__pw_refreshOverlay()', false, undefined, 'main').catch(() => {});
|
page.mainFrame().evaluateExpression('window.__pw_refreshOverlay()', false, undefined, 'main').catch(() => {});
|
||||||
|
@ -320,7 +334,7 @@ class ContextRecorder extends EventEmitter {
|
||||||
const orderedLanguages = [primaryLanguage, ...languages];
|
const orderedLanguages = [primaryLanguage, ...languages];
|
||||||
|
|
||||||
this._recorderSources = [];
|
this._recorderSources = [];
|
||||||
const generator = new CodeGenerator(context._browser.options.name, !!params.startRecording, params.launchOptions || {}, params.contextOptions || {}, params.device, params.saveStorage);
|
const generator = new CodeGenerator(context._browser.options.name, params.mode === 'recording', params.launchOptions || {}, params.contextOptions || {}, params.device, params.saveStorage);
|
||||||
const throttledOutputFile = params.outputFile ? new ThrottledFile(params.outputFile) : null;
|
const throttledOutputFile = params.outputFile ? new ThrottledFile(params.outputFile) : null;
|
||||||
generator.on('change', () => {
|
generator.on('change', () => {
|
||||||
this._recorderSources = [];
|
this._recorderSources = [];
|
||||||
|
@ -421,7 +435,7 @@ class ContextRecorder extends EventEmitter {
|
||||||
|
|
||||||
clearScript(): void {
|
clearScript(): void {
|
||||||
this._generator.restart();
|
this._generator.restart();
|
||||||
if (!!this._params.startRecording) {
|
if (this._params.mode === 'recording') {
|
||||||
for (const page of this._context.pages())
|
for (const page of this._context.pages())
|
||||||
this._onFrameNavigated(page.mainFrame(), page);
|
this._onFrameNavigated(page.mainFrame(), page);
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,8 @@ import { isUnderTest } from '../../utils';
|
||||||
import { mime } from '../../utilsBundle';
|
import { mime } from '../../utilsBundle';
|
||||||
import { installAppIcon } from '../chromium/crApp';
|
import { installAppIcon } from '../chromium/crApp';
|
||||||
import { findChromiumChannel } from '../registry';
|
import { findChromiumChannel } from '../registry';
|
||||||
|
import type { Recorder } from '../recorder';
|
||||||
|
import type { BrowserContext } from '../browserContext';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
|
@ -45,17 +47,28 @@ export interface IRecorderApp extends EventEmitter {
|
||||||
setFileIfNeeded(file: string): Promise<void>;
|
setFileIfNeeded(file: string): Promise<void>;
|
||||||
setSelector(selector: string, focus?: boolean): Promise<void>;
|
setSelector(selector: string, focus?: boolean): Promise<void>;
|
||||||
updateCallLogs(callLogs: CallLog[]): Promise<void>;
|
updateCallLogs(callLogs: CallLog[]): Promise<void>;
|
||||||
bringToFront(): void;
|
|
||||||
setSources(sources: Source[]): Promise<void>;
|
setSources(sources: Source[]): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class EmptyRecorderApp extends EventEmitter implements IRecorderApp {
|
||||||
|
async close(): Promise<void> {}
|
||||||
|
async setPaused(paused: boolean): Promise<void> {}
|
||||||
|
async setMode(mode: 'none' | 'recording' | 'inspecting'): Promise<void> {}
|
||||||
|
async setFileIfNeeded(file: string): Promise<void> {}
|
||||||
|
async setSelector(selector: string, focus?: boolean): Promise<void> {}
|
||||||
|
async updateCallLogs(callLogs: CallLog[]): Promise<void> {}
|
||||||
|
async setSources(sources: Source[]): Promise<void> {}
|
||||||
|
}
|
||||||
|
|
||||||
export class RecorderApp extends EventEmitter implements IRecorderApp {
|
export class RecorderApp extends EventEmitter implements IRecorderApp {
|
||||||
private _page: Page;
|
private _page: Page;
|
||||||
readonly wsEndpoint: string | undefined;
|
readonly wsEndpoint: string | undefined;
|
||||||
|
private _recorder: Recorder;
|
||||||
|
|
||||||
constructor(page: Page, wsEndpoint: string | undefined) {
|
constructor(recorder: Recorder, page: Page, wsEndpoint: string | undefined) {
|
||||||
super();
|
super();
|
||||||
this.setMaxListeners(0);
|
this.setMaxListeners(0);
|
||||||
|
this._recorder = recorder;
|
||||||
this._page = page;
|
this._page = page;
|
||||||
this.wsEndpoint = wsEndpoint;
|
this.wsEndpoint = wsEndpoint;
|
||||||
}
|
}
|
||||||
|
@ -96,9 +109,9 @@ export class RecorderApp extends EventEmitter implements IRecorderApp {
|
||||||
await mainFrame.goto(serverSideCallMetadata(), 'https://playwright/index.html');
|
await mainFrame.goto(serverSideCallMetadata(), 'https://playwright/index.html');
|
||||||
}
|
}
|
||||||
|
|
||||||
static async open(sdkLanguage: string, headed: boolean): Promise<IRecorderApp> {
|
static async open(recorder: Recorder, inspectedContext: BrowserContext): Promise<IRecorderApp> {
|
||||||
if (process.env.PW_CODEGEN_NO_INSPECTOR)
|
const sdkLanguage = inspectedContext._browser.options.sdkLanguage;
|
||||||
return new HeadlessRecorderApp();
|
const headed = !!inspectedContext._browser.options.headful;
|
||||||
const recorderPlaywright = (require('../playwright').createPlaywright as typeof import('../playwright').createPlaywright)('javascript', true);
|
const recorderPlaywright = (require('../playwright').createPlaywright as typeof import('../playwright').createPlaywright)('javascript', true);
|
||||||
const args = [
|
const args = [
|
||||||
'--app=data:text/html,',
|
'--app=data:text/html,',
|
||||||
|
@ -122,7 +135,7 @@ export class RecorderApp extends EventEmitter implements IRecorderApp {
|
||||||
});
|
});
|
||||||
|
|
||||||
const [page] = context.pages();
|
const [page] = context.pages();
|
||||||
const result = new RecorderApp(page, context._browser.options.wsEndpoint);
|
const result = new RecorderApp(recorder, page, context._browser.options.wsEndpoint);
|
||||||
await result._init();
|
await result._init();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -161,6 +174,10 @@ export class RecorderApp extends EventEmitter implements IRecorderApp {
|
||||||
}
|
}
|
||||||
|
|
||||||
async setSelector(selector: string, focus?: boolean): Promise<void> {
|
async setSelector(selector: string, focus?: boolean): Promise<void> {
|
||||||
|
if (focus) {
|
||||||
|
this._recorder.setMode('none');
|
||||||
|
this._page.bringToFront();
|
||||||
|
}
|
||||||
await this._page.mainFrame().evaluateExpression(((arg: any) => {
|
await this._page.mainFrame().evaluateExpression(((arg: any) => {
|
||||||
window.playwrightSetSelector(arg.selector, arg.focus);
|
window.playwrightSetSelector(arg.selector, arg.focus);
|
||||||
}).toString(), true, { selector, focus }, 'main').catch(() => {});
|
}).toString(), true, { selector, focus }, 'main').catch(() => {});
|
||||||
|
@ -171,19 +188,4 @@ export class RecorderApp extends EventEmitter implements IRecorderApp {
|
||||||
window.playwrightUpdateLogs(callLogs);
|
window.playwrightUpdateLogs(callLogs);
|
||||||
}).toString(), true, callLogs, 'main').catch(() => {});
|
}).toString(), true, callLogs, 'main').catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
async bringToFront() {
|
|
||||||
await this._page.bringToFront();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class HeadlessRecorderApp extends EventEmitter implements IRecorderApp {
|
|
||||||
async close(): Promise<void> {}
|
|
||||||
async setPaused(paused: boolean): Promise<void> {}
|
|
||||||
async setMode(mode: 'none' | 'recording' | 'inspecting'): Promise<void> {}
|
|
||||||
async setFileIfNeeded(file: string): Promise<void> {}
|
|
||||||
async setSelector(selector: string, focus?: boolean): Promise<void> {}
|
|
||||||
async updateCallLogs(callLogs: CallLog[]): Promise<void> {}
|
|
||||||
bringToFront(): void {}
|
|
||||||
async setSources(sources: Source[]): Promise<void> {}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,7 +65,7 @@ export const test = contextTest.extend<CLITestArgs>({
|
||||||
|
|
||||||
openRecorder: async ({ page, recorderPageGetter }, run) => {
|
openRecorder: async ({ page, recorderPageGetter }, run) => {
|
||||||
await run(async () => {
|
await run(async () => {
|
||||||
await (page.context() as any)._enableRecorder({ language: 'javascript', startRecording: true });
|
await (page.context() as any)._enableRecorder({ language: 'javascript', mode: 'recording' });
|
||||||
return new Recorder(page, await recorderPageGetter());
|
return new Recorder(page, await recorderPageGetter());
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
Загрузка…
Ссылка в новой задаче