feat(pause): make page.pause public (#5288)
This commit is contained in:
parent
509c3e91b4
commit
34adc28ed3
|
|
@ -1479,6 +1479,19 @@ The page's main frame. Page is guaranteed to have a main frame which persists du
|
||||||
|
|
||||||
Returns the opener for popup pages and `null` for others. If the opener has been closed already the returns `null`.
|
Returns the opener for popup pages and `null` for others. If the opener has been closed already the returns `null`.
|
||||||
|
|
||||||
|
## async method: Page.pause
|
||||||
|
|
||||||
|
Pauses script execution. Playwright will stop executing the script and wait for the user to either press 'Resume'
|
||||||
|
button in the page overlay or to call `playwright.resume()` in the DevTools console.
|
||||||
|
|
||||||
|
User can inspect selectors or perform manual steps while paused. Resume will continue running the original script from
|
||||||
|
the place it was paused.
|
||||||
|
|
||||||
|
:::note
|
||||||
|
This method requires Playwright to be started in a headed mode, with a falsy [`options: headless`] value in
|
||||||
|
the [`method: BrowserType.launch`].
|
||||||
|
:::
|
||||||
|
|
||||||
## async method: Page.pdf
|
## async method: Page.pdf
|
||||||
- returns: <[Buffer]>
|
- returns: <[Buffer]>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ program
|
||||||
.command('open [url]')
|
.command('open [url]')
|
||||||
.description('open page in browser specified via -b, --browser')
|
.description('open page in browser specified via -b, --browser')
|
||||||
.action(function(url, command) {
|
.action(function(url, command) {
|
||||||
open(command.parent, url);
|
open(command.parent, url, language());
|
||||||
}).on('--help', function() {
|
}).on('--help', function() {
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log('Examples:');
|
console.log('Examples:');
|
||||||
|
|
@ -70,8 +70,9 @@ for (const {alias, name, type} of browsers) {
|
||||||
program
|
program
|
||||||
.command(`${alias} [url]`)
|
.command(`${alias} [url]`)
|
||||||
.description(`open page in ${name}`)
|
.description(`open page in ${name}`)
|
||||||
|
.option('--target <language>', `language to use, one of javascript, python, python-async, csharp`, language())
|
||||||
.action(function(url, command) {
|
.action(function(url, command) {
|
||||||
open({ ...command.parent, browser: type }, url);
|
open({ ...command.parent, browser: type }, url, command.target);
|
||||||
}).on('--help', function() {
|
}).on('--help', function() {
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log('Examples:');
|
console.log('Examples:');
|
||||||
|
|
@ -84,7 +85,7 @@ program
|
||||||
.command('codegen [url]')
|
.command('codegen [url]')
|
||||||
.description('open page and generate code for user actions')
|
.description('open page and generate code for user actions')
|
||||||
.option('-o, --output <file name>', 'saves the generated script to a file')
|
.option('-o, --output <file name>', 'saves the generated script to a file')
|
||||||
.option('--target <language>', `language to use, one of javascript, python, python-async, csharp`, process.env.PW_CLI_TARGET_LANG || 'javascript')
|
.option('--target <language>', `language to use, one of javascript, python, python-async, csharp`, language())
|
||||||
.action(function(url, command) {
|
.action(function(url, command) {
|
||||||
codegen(command.parent, url, command.target, command.output);
|
codegen(command.parent, url, command.target, command.output);
|
||||||
}).on('--help', function() {
|
}).on('--help', function() {
|
||||||
|
|
@ -316,9 +317,16 @@ async function openPage(context: BrowserContext, url: string | undefined): Promi
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function open(options: Options, url: string | undefined) {
|
async function open(options: Options, url: string | undefined, language: string) {
|
||||||
const { context } = await launchContext(options, false);
|
const { context, launchOptions, contextOptions } = await launchContext(options, false);
|
||||||
await context._enableConsoleApi();
|
await context._enableRecorder({
|
||||||
|
language,
|
||||||
|
launchOptions,
|
||||||
|
contextOptions,
|
||||||
|
device: options.device,
|
||||||
|
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)
|
||||||
await Promise.all(context.pages().map(p => p.close()));
|
await Promise.all(context.pages().map(p => p.close()));
|
||||||
|
|
@ -334,6 +342,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,
|
||||||
terminal: !!process.stdout.columns,
|
terminal: !!process.stdout.columns,
|
||||||
outputFile: outputFile ? path.resolve(outputFile) : undefined
|
outputFile: outputFile ? path.resolve(outputFile) : undefined
|
||||||
});
|
});
|
||||||
|
|
@ -409,3 +418,7 @@ function validateOptions(options: Options) {
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function language(): string {
|
||||||
|
return process.env.PW_CLI_TARGET_LANG || 'javascript';
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -276,16 +276,13 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async _enableConsoleApi() {
|
|
||||||
await this._channel.consoleSupplementExpose();
|
|
||||||
}
|
|
||||||
|
|
||||||
async _enableRecorder(params: {
|
async _enableRecorder(params: {
|
||||||
language: string,
|
language: string,
|
||||||
launchOptions?: LaunchOptions,
|
launchOptions?: LaunchOptions,
|
||||||
contextOptions?: BrowserContextOptions,
|
contextOptions?: BrowserContextOptions,
|
||||||
device?: string,
|
device?: string,
|
||||||
saveStorage?: string,
|
saveStorage?: string,
|
||||||
|
startRecording?: boolean,
|
||||||
terminal?: boolean,
|
terminal?: boolean,
|
||||||
outputFile?: string
|
outputFile?: string
|
||||||
}) {
|
}) {
|
||||||
|
|
|
||||||
|
|
@ -642,7 +642,7 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _pause() {
|
async pause() {
|
||||||
await this.context()._pause();
|
await this.context()._pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ import { RouteDispatcher, RequestDispatcher } from './networkDispatchers';
|
||||||
import { CRBrowserContext } from '../server/chromium/crBrowser';
|
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 { ConsoleApiSupplement } from '../server/supplements/consoleApiSupplement';
|
|
||||||
|
|
||||||
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;
|
||||||
|
|
@ -129,19 +128,14 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
|
||||||
await this._context.close();
|
await this._context.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
async consoleSupplementExpose(): Promise<void> {
|
|
||||||
const consoleApi = new ConsoleApiSupplement(this._context);
|
|
||||||
await consoleApi.install();
|
|
||||||
}
|
|
||||||
|
|
||||||
async recorderSupplementEnable(params: channels.BrowserContextRecorderSupplementEnableParams): Promise<void> {
|
async recorderSupplementEnable(params: channels.BrowserContextRecorderSupplementEnableParams): Promise<void> {
|
||||||
await RecorderSupplement.getOrCreate(this._context, 'codegen', params);
|
await RecorderSupplement.getOrCreate(this._context, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
async pause() {
|
async pause() {
|
||||||
if (!this._context._browser.options.headful)
|
if (!this._context._browser.options.headful)
|
||||||
return;
|
return;
|
||||||
const recorder = await RecorderSupplement.getOrCreate(this._context, 'pause', {
|
const recorder = await RecorderSupplement.getOrCreate(this._context, {
|
||||||
language: 'javascript',
|
language: 'javascript',
|
||||||
terminal: true
|
terminal: true
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -558,7 +558,6 @@ export interface BrowserContextChannel extends Channel {
|
||||||
setNetworkInterceptionEnabled(params: BrowserContextSetNetworkInterceptionEnabledParams, metadata?: Metadata): Promise<BrowserContextSetNetworkInterceptionEnabledResult>;
|
setNetworkInterceptionEnabled(params: BrowserContextSetNetworkInterceptionEnabledParams, metadata?: Metadata): Promise<BrowserContextSetNetworkInterceptionEnabledResult>;
|
||||||
setOffline(params: BrowserContextSetOfflineParams, metadata?: Metadata): Promise<BrowserContextSetOfflineResult>;
|
setOffline(params: BrowserContextSetOfflineParams, metadata?: Metadata): Promise<BrowserContextSetOfflineResult>;
|
||||||
storageState(params?: BrowserContextStorageStateParams, metadata?: Metadata): Promise<BrowserContextStorageStateResult>;
|
storageState(params?: BrowserContextStorageStateParams, metadata?: Metadata): Promise<BrowserContextStorageStateResult>;
|
||||||
consoleSupplementExpose(params?: BrowserContextConsoleSupplementExposeParams, metadata?: Metadata): Promise<BrowserContextConsoleSupplementExposeResult>;
|
|
||||||
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>;
|
||||||
|
|
@ -709,14 +708,12 @@ export type BrowserContextStorageStateResult = {
|
||||||
cookies: NetworkCookie[],
|
cookies: NetworkCookie[],
|
||||||
origins: OriginStorage[],
|
origins: OriginStorage[],
|
||||||
};
|
};
|
||||||
export type BrowserContextConsoleSupplementExposeParams = {};
|
|
||||||
export type BrowserContextConsoleSupplementExposeOptions = {};
|
|
||||||
export type BrowserContextConsoleSupplementExposeResult = void;
|
|
||||||
export type BrowserContextPauseParams = {};
|
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,
|
||||||
launchOptions?: any,
|
launchOptions?: any,
|
||||||
contextOptions?: any,
|
contextOptions?: any,
|
||||||
device?: string,
|
device?: string,
|
||||||
|
|
@ -725,6 +722,7 @@ export type BrowserContextRecorderSupplementEnableParams = {
|
||||||
outputFile?: string,
|
outputFile?: string,
|
||||||
};
|
};
|
||||||
export type BrowserContextRecorderSupplementEnableOptions = {
|
export type BrowserContextRecorderSupplementEnableOptions = {
|
||||||
|
startRecording?: boolean,
|
||||||
launchOptions?: any,
|
launchOptions?: any,
|
||||||
contextOptions?: any,
|
contextOptions?: any,
|
||||||
device?: string,
|
device?: string,
|
||||||
|
|
|
||||||
|
|
@ -601,9 +601,6 @@ BrowserContext:
|
||||||
type: array
|
type: array
|
||||||
items: OriginStorage
|
items: OriginStorage
|
||||||
|
|
||||||
consoleSupplementExpose:
|
|
||||||
experimental: True
|
|
||||||
|
|
||||||
pause:
|
pause:
|
||||||
experimental: True
|
experimental: True
|
||||||
|
|
||||||
|
|
@ -611,6 +608,7 @@ BrowserContext:
|
||||||
experimental: True
|
experimental: True
|
||||||
parameters:
|
parameters:
|
||||||
language: string
|
language: string
|
||||||
|
startRecording: boolean?
|
||||||
launchOptions: json?
|
launchOptions: json?
|
||||||
contextOptions: json?
|
contextOptions: json?
|
||||||
device: string?
|
device: string?
|
||||||
|
|
|
||||||
|
|
@ -337,10 +337,10 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||||
offline: tBoolean,
|
offline: tBoolean,
|
||||||
});
|
});
|
||||||
scheme.BrowserContextStorageStateParams = tOptional(tObject({}));
|
scheme.BrowserContextStorageStateParams = tOptional(tObject({}));
|
||||||
scheme.BrowserContextConsoleSupplementExposeParams = tOptional(tObject({}));
|
|
||||||
scheme.BrowserContextPauseParams = tOptional(tObject({}));
|
scheme.BrowserContextPauseParams = tOptional(tObject({}));
|
||||||
scheme.BrowserContextRecorderSupplementEnableParams = tObject({
|
scheme.BrowserContextRecorderSupplementEnableParams = tObject({
|
||||||
language: tString,
|
language: tString,
|
||||||
|
startRecording: tOptional(tBoolean),
|
||||||
launchOptions: tOptional(tAny),
|
launchOptions: tOptional(tAny),
|
||||||
contextOptions: tOptional(tAny),
|
contextOptions: tOptional(tAny),
|
||||||
device: tOptional(tString),
|
device: tOptional(tString),
|
||||||
|
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
/**
|
|
||||||
* 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 * as consoleApiSource from '../../generated/consoleApiSource';
|
|
||||||
import { BrowserContext } from '../browserContext';
|
|
||||||
|
|
||||||
export class ConsoleApiSupplement {
|
|
||||||
private _context: BrowserContext;
|
|
||||||
|
|
||||||
constructor(context: BrowserContext) {
|
|
||||||
this._context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
async install() {
|
|
||||||
await this._context.extendInjectedScript(consoleApiSource.source);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -17,18 +17,35 @@
|
||||||
import type InjectedScript from '../../injected/injectedScript';
|
import type InjectedScript from '../../injected/injectedScript';
|
||||||
import { generateSelector } from './selectorGenerator';
|
import { generateSelector } from './selectorGenerator';
|
||||||
|
|
||||||
|
type ConsoleAPIInterface = {
|
||||||
|
$: (selector: string) => void;
|
||||||
|
$$: (selector: string) => void;
|
||||||
|
inspect: (selector: string) => void;
|
||||||
|
selector: (element: Element) => void;
|
||||||
|
resume: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
playwright?: ConsoleAPIInterface;
|
||||||
|
inspect: (element: Element | undefined) => void;
|
||||||
|
_playwrightResume: () => Promise<void>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class ConsoleAPI {
|
export class ConsoleAPI {
|
||||||
private _injectedScript: InjectedScript;
|
private _injectedScript: InjectedScript;
|
||||||
|
|
||||||
constructor(injectedScript: InjectedScript) {
|
constructor(injectedScript: InjectedScript) {
|
||||||
this._injectedScript = injectedScript;
|
this._injectedScript = injectedScript;
|
||||||
if ((window as any).playwright)
|
if (window.playwright)
|
||||||
return;
|
return;
|
||||||
(window as any).playwright = {
|
window.playwright = {
|
||||||
$: (selector: string) => this._querySelector(selector),
|
$: (selector: string) => this._querySelector(selector),
|
||||||
$$: (selector: string) => this._querySelectorAll(selector),
|
$$: (selector: string) => this._querySelectorAll(selector),
|
||||||
inspect: (selector: string) => this._inspect(selector),
|
inspect: (selector: string) => this._inspect(selector),
|
||||||
selector: (element: Element) => this._selector(element),
|
selector: (element: Element) => this._selector(element),
|
||||||
|
resume: () => this._resume(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -47,11 +64,9 @@ export class ConsoleAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _inspect(selector: string) {
|
private _inspect(selector: string) {
|
||||||
if (typeof (window as any).inspect !== 'function')
|
|
||||||
return;
|
|
||||||
if (typeof selector !== 'string')
|
if (typeof selector !== 'string')
|
||||||
throw new Error(`Usage: playwright.inspect('Playwright >> selector').`);
|
throw new Error(`Usage: playwright.inspect('Playwright >> selector').`);
|
||||||
(window as any).inspect(this._querySelector(selector));
|
window.inspect(this._querySelector(selector));
|
||||||
}
|
}
|
||||||
|
|
||||||
private _selector(element: Element) {
|
private _selector(element: Element) {
|
||||||
|
|
@ -59,6 +74,10 @@ export class ConsoleAPI {
|
||||||
throw new Error(`Usage: playwright.selector(element).`);
|
throw new Error(`Usage: playwright.selector(element).`);
|
||||||
return generateSelector(this._injectedScript, element).selector;
|
return generateSelector(this._injectedScript, element).selector;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _resume() {
|
||||||
|
window._playwrightResume().catch(() => {});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ConsoleAPI;
|
export default ConsoleAPI;
|
||||||
|
|
|
||||||
|
|
@ -22,14 +22,14 @@ import type { State, SetUIState } from '../recorder/state';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
playwrightRecorderPerformAction: (action: actions.Action) => Promise<void>;
|
_playwrightRecorderPerformAction: (action: actions.Action) => Promise<void>;
|
||||||
playwrightRecorderRecordAction: (action: actions.Action) => Promise<void>;
|
_playwrightRecorderRecordAction: (action: actions.Action) => Promise<void>;
|
||||||
playwrightRecorderCommitAction: () => Promise<void>;
|
_playwrightRecorderCommitAction: () => Promise<void>;
|
||||||
playwrightRecorderState: () => Promise<State>;
|
_playwrightRecorderState: () => Promise<State>;
|
||||||
playwrightRecorderSetUIState: (state: SetUIState) => Promise<void>;
|
_playwrightRecorderSetUIState: (state: SetUIState) => Promise<void>;
|
||||||
playwrightRecorderResume: () => Promise<boolean>;
|
_playwrightRecorderShowRecorderPage: () => Promise<void>;
|
||||||
playwrightRecorderShowRecorderPage: () => Promise<void>;
|
_playwrightRecorderPrintSelector: (text: string) => Promise<void>;
|
||||||
playwrightRecorderPrintSelector: (text: string) => Promise<void>;
|
_playwrightResume: () => Promise<void>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,7 +52,6 @@ export class Recorder {
|
||||||
private _outerToolbarElement: HTMLElement;
|
private _outerToolbarElement: HTMLElement;
|
||||||
private _toolbar: Element$;
|
private _toolbar: Element$;
|
||||||
private _state: State = {
|
private _state: State = {
|
||||||
canResume: false,
|
|
||||||
uiState: {
|
uiState: {
|
||||||
mode: 'none',
|
mode: 'none',
|
||||||
},
|
},
|
||||||
|
|
@ -167,13 +166,13 @@ export class Recorder {
|
||||||
if (this._toolbar.$('#pw-button-resume').classList.contains('disabled'))
|
if (this._toolbar.$('#pw-button-resume').classList.contains('disabled'))
|
||||||
return;
|
return;
|
||||||
this._updateUIState({ mode: 'none' });
|
this._updateUIState({ mode: 'none' });
|
||||||
window.playwrightRecorderResume().catch(() => {});
|
window._playwrightResume().catch(() => {});
|
||||||
});
|
});
|
||||||
this._toolbar.$('#pw-button-playwright').addEventListener('click', () => {
|
this._toolbar.$('#pw-button-playwright').addEventListener('click', () => {
|
||||||
if (this._toolbar.$('#pw-button-playwright').classList.contains('disabled'))
|
if (this._toolbar.$('#pw-button-playwright').classList.contains('disabled'))
|
||||||
return;
|
return;
|
||||||
this._toolbar.$('#pw-button-playwright').classList.toggle('toggled');
|
this._toolbar.$('#pw-button-playwright').classList.toggle('toggled');
|
||||||
window.playwrightRecorderShowRecorderPage().catch(() => {});
|
window._playwrightRecorderShowRecorderPage().catch(() => {});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -211,19 +210,19 @@ export class Recorder {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _updateUIState(uiState: SetUIState) {
|
private async _updateUIState(uiState: SetUIState) {
|
||||||
window.playwrightRecorderSetUIState(uiState).then(() => this._pollRecorderMode());
|
window._playwrightRecorderSetUIState(uiState).then(() => this._pollRecorderMode());
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _pollRecorderMode(skipAnimations: boolean = false) {
|
private async _pollRecorderMode(skipAnimations: boolean = false) {
|
||||||
if (this._pollRecorderModeTimer)
|
if (this._pollRecorderModeTimer)
|
||||||
clearTimeout(this._pollRecorderModeTimer);
|
clearTimeout(this._pollRecorderModeTimer);
|
||||||
const state = await window.playwrightRecorderState().catch(e => null);
|
const state = await window._playwrightRecorderState().catch(e => null);
|
||||||
if (!state) {
|
if (!state) {
|
||||||
this._pollRecorderModeTimer = setTimeout(() => this._pollRecorderMode(), 250);
|
this._pollRecorderModeTimer = setTimeout(() => this._pollRecorderMode(), 250);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { canResume, isPaused, uiState } = state;
|
const { isPaused, uiState } = state;
|
||||||
if (uiState.mode !== this._state.uiState.mode) {
|
if (uiState.mode !== this._state.uiState.mode) {
|
||||||
this._state.uiState.mode = uiState.mode;
|
this._state.uiState.mode = uiState.mode;
|
||||||
this._toolbar.$('#pw-button-inspect').classList.toggle('toggled', uiState.mode === 'inspecting');
|
this._toolbar.$('#pw-button-inspect').classList.toggle('toggled', uiState.mode === 'inspecting');
|
||||||
|
|
@ -234,13 +233,7 @@ export class Recorder {
|
||||||
|
|
||||||
if (isPaused !== this._state.isPaused) {
|
if (isPaused !== this._state.isPaused) {
|
||||||
this._state.isPaused = isPaused;
|
this._state.isPaused = isPaused;
|
||||||
this._toolbar.$('#pw-button-resume-group').classList.toggle('hidden', false);
|
this._toolbar.$('#pw-button-resume-group').classList.toggle('hidden', !isPaused);
|
||||||
this._toolbar.$('#pw-button-resume').classList.toggle('disabled', !isPaused);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (canResume !== this._state.canResume) {
|
|
||||||
this._state.canResume = canResume;
|
|
||||||
this._toolbar.$('#pw-button-resume-group').classList.toggle('hidden', !canResume);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this._state = state;
|
this._state = state;
|
||||||
|
|
@ -280,7 +273,7 @@ export class Recorder {
|
||||||
if (this._state.uiState.mode === 'inspecting' && !this._isInToolbar(event.target as HTMLElement)) {
|
if (this._state.uiState.mode === 'inspecting' && !this._isInToolbar(event.target as HTMLElement)) {
|
||||||
if (this._hoveredModel) {
|
if (this._hoveredModel) {
|
||||||
copy(this._hoveredModel.selector);
|
copy(this._hoveredModel.selector);
|
||||||
window.playwrightRecorderPrintSelector(this._hoveredModel.selector);
|
window._playwrightRecorderPrintSelector(this._hoveredModel.selector);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this._shouldIgnoreMouseEvent(event))
|
if (this._shouldIgnoreMouseEvent(event))
|
||||||
|
|
@ -389,7 +382,7 @@ export class Recorder {
|
||||||
const { selector, elements } = generateSelector(this._injectedScript, hoveredElement);
|
const { selector, elements } = generateSelector(this._injectedScript, hoveredElement);
|
||||||
if ((this._hoveredModel && this._hoveredModel.selector === selector) || this._hoveredElement !== hoveredElement)
|
if ((this._hoveredModel && this._hoveredModel.selector === selector) || this._hoveredElement !== hoveredElement)
|
||||||
return;
|
return;
|
||||||
window.playwrightRecorderCommitAction();
|
window._playwrightRecorderCommitAction();
|
||||||
this._hoveredModel = selector ? { selector, elements } : null;
|
this._hoveredModel = selector ? { selector, elements } : null;
|
||||||
this._updateHighlight();
|
this._updateHighlight();
|
||||||
if ((window as any)._highlightUpdatedForTest)
|
if ((window as any)._highlightUpdatedForTest)
|
||||||
|
|
@ -483,7 +476,7 @@ export class Recorder {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (elementType === 'file') {
|
if (elementType === 'file') {
|
||||||
window.playwrightRecorderRecordAction({
|
window._playwrightRecorderRecordAction({
|
||||||
name: 'setInputFiles',
|
name: 'setInputFiles',
|
||||||
selector: this._activeModel!.selector,
|
selector: this._activeModel!.selector,
|
||||||
signals: [],
|
signals: [],
|
||||||
|
|
@ -495,7 +488,7 @@ export class Recorder {
|
||||||
// Non-navigating actions are simply recorded by Playwright.
|
// Non-navigating actions are simply recorded by Playwright.
|
||||||
if (this._consumedDueWrongTarget(event))
|
if (this._consumedDueWrongTarget(event))
|
||||||
return;
|
return;
|
||||||
window.playwrightRecorderRecordAction({
|
window._playwrightRecorderRecordAction({
|
||||||
name: 'fill',
|
name: 'fill',
|
||||||
selector: this._activeModel!.selector,
|
selector: this._activeModel!.selector,
|
||||||
signals: [],
|
signals: [],
|
||||||
|
|
@ -592,7 +585,7 @@ export class Recorder {
|
||||||
|
|
||||||
private async _performAction(action: actions.Action) {
|
private async _performAction(action: actions.Action) {
|
||||||
this._performingAction = true;
|
this._performingAction = true;
|
||||||
await window.playwrightRecorderPerformAction(action).catch(() => {});
|
await window._playwrightRecorderPerformAction(action).catch(() => {});
|
||||||
this._performingAction = false;
|
this._performingAction = false;
|
||||||
|
|
||||||
// Action could have changed DOM, update hovered model selectors.
|
// Action could have changed DOM, update hovered model selectors.
|
||||||
|
|
|
||||||
|
|
@ -16,16 +16,13 @@
|
||||||
|
|
||||||
import { BrowserContext, ContextListener } from '../browserContext';
|
import { BrowserContext, ContextListener } from '../browserContext';
|
||||||
import { isDebugMode } from '../../utils/utils';
|
import { isDebugMode } from '../../utils/utils';
|
||||||
import { ConsoleApiSupplement } from './consoleApiSupplement';
|
|
||||||
import { RecorderSupplement } from './recorderSupplement';
|
import { RecorderSupplement } from './recorderSupplement';
|
||||||
|
|
||||||
export class InspectorController implements ContextListener {
|
export class InspectorController implements ContextListener {
|
||||||
async onContextCreated(context: BrowserContext): Promise<void> {
|
async onContextCreated(context: BrowserContext): Promise<void> {
|
||||||
if (isDebugMode()) {
|
if (isDebugMode()) {
|
||||||
const consoleApi = new ConsoleApiSupplement(context);
|
RecorderSupplement.getOrCreate(context, {
|
||||||
await consoleApi.install();
|
language: process.env.PW_CLI_TARGET_LANG || 'javascript',
|
||||||
RecorderSupplement.getOrCreate(context, 'debug', {
|
|
||||||
language: 'javascript',
|
|
||||||
terminal: true,
|
terminal: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ export class RecorderApp extends EventEmitter {
|
||||||
await route.continue();
|
await route.continue();
|
||||||
});
|
});
|
||||||
|
|
||||||
await this._page.exposeBinding('playwrightClear', false, (_, text: string) => {
|
await this._page.exposeBinding('_playwrightClear', false, (_, text: string) => {
|
||||||
this.emit('clear');
|
this.emit('clear');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -103,7 +103,7 @@ export class RecorderApp extends EventEmitter {
|
||||||
|
|
||||||
async setScript(text: string, language: string): Promise<void> {
|
async setScript(text: string, language: string): Promise<void> {
|
||||||
await this._page.mainFrame()._evaluateExpression(((param: { text: string, language: string }) => {
|
await this._page.mainFrame()._evaluateExpression(((param: { text: string, language: string }) => {
|
||||||
(window as any).playwrightSetSource(param);
|
(window as any)._playwrightSetSource(param);
|
||||||
}).toString(), true, { text, language }, 'main');
|
}).toString(), true, { text, language }, 'main');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ export type SetUIState = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
canResume: boolean,
|
|
||||||
isPaused: boolean,
|
isPaused: boolean,
|
||||||
uiState: UIState,
|
uiState: UIState,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,27 +47,27 @@ export class RecorderSupplement {
|
||||||
private _resumeCallback: (() => void) | null = null;
|
private _resumeCallback: (() => void) | null = null;
|
||||||
private _recorderUIState: UIState;
|
private _recorderUIState: UIState;
|
||||||
private _paused = false;
|
private _paused = false;
|
||||||
private _app: App;
|
|
||||||
private _output: OutputMultiplexer;
|
private _output: OutputMultiplexer;
|
||||||
private _bufferedOutput: BufferedOutput;
|
private _bufferedOutput: BufferedOutput;
|
||||||
private _recorderApp: Promise<RecorderApp> | null = null;
|
private _recorderApp: Promise<RecorderApp> | null = null;
|
||||||
private _highlighterType: string;
|
private _highlighterType: string;
|
||||||
|
private _params: channels.BrowserContextRecorderSupplementEnableParams;
|
||||||
|
|
||||||
static getOrCreate(context: BrowserContext, app: App, 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, app, params);
|
const recorder = new RecorderSupplement(context, params);
|
||||||
recorderPromise = recorder.install().then(() => recorder);
|
recorderPromise = recorder.install().then(() => recorder);
|
||||||
(context as any)[symbol] = recorderPromise;
|
(context as any)[symbol] = recorderPromise;
|
||||||
}
|
}
|
||||||
return recorderPromise;
|
return recorderPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(context: BrowserContext, app: App, params: channels.BrowserContextRecorderSupplementEnableParams) {
|
constructor(context: BrowserContext, params: channels.BrowserContextRecorderSupplementEnableParams) {
|
||||||
this._context = context;
|
this._context = context;
|
||||||
this._app = app;
|
this._params = params;
|
||||||
this._recorderUIState = {
|
this._recorderUIState = {
|
||||||
mode: app === 'codegen' ? 'recording' : 'none',
|
mode: params.startRecording ? 'recording' : 'none',
|
||||||
};
|
};
|
||||||
let languageGenerator: LanguageGenerator;
|
let languageGenerator: LanguageGenerator;
|
||||||
switch (params.language) {
|
switch (params.language) {
|
||||||
|
|
@ -97,10 +97,10 @@ export class RecorderSupplement {
|
||||||
if (params.outputFile)
|
if (params.outputFile)
|
||||||
outputs.push(new FileOutput(params.outputFile));
|
outputs.push(new FileOutput(params.outputFile));
|
||||||
this._output = new OutputMultiplexer(outputs);
|
this._output = new OutputMultiplexer(outputs);
|
||||||
this._output.setEnabled(app === 'codegen');
|
this._output.setEnabled(!!params.startRecording);
|
||||||
context.on(BrowserContext.Events.BeforeClose, () => this._output.flush());
|
context.on(BrowserContext.Events.BeforeClose, () => this._output.flush());
|
||||||
|
|
||||||
const generator = new CodeGenerator(context._browser.options.name, app === 'codegen', params.launchOptions || {}, params.contextOptions || {}, this._output, languageGenerator, params.device, params.saveStorage);
|
const generator = new CodeGenerator(context._browser.options.name, !!params.startRecording, params.launchOptions || {}, params.contextOptions || {}, this._output, languageGenerator, params.device, params.saveStorage);
|
||||||
this._generator = generator;
|
this._generator = generator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -117,18 +117,18 @@ export class RecorderSupplement {
|
||||||
|
|
||||||
// Input actions that potentially lead to navigation are intercepted on the page and are
|
// Input actions that potentially lead to navigation are intercepted on the page and are
|
||||||
// performed by the Playwright.
|
// performed by the Playwright.
|
||||||
await this._context.exposeBinding('playwrightRecorderPerformAction', false,
|
await this._context.exposeBinding('_playwrightRecorderPerformAction', false,
|
||||||
(source: BindingSource, action: actions.Action) => this._performAction(source.frame, action));
|
(source: BindingSource, action: actions.Action) => this._performAction(source.frame, action));
|
||||||
|
|
||||||
// Other non-essential actions are simply being recorded.
|
// Other non-essential actions are simply being recorded.
|
||||||
await this._context.exposeBinding('playwrightRecorderRecordAction', false,
|
await this._context.exposeBinding('_playwrightRecorderRecordAction', false,
|
||||||
(source: BindingSource, action: actions.Action) => this._recordAction(source.frame, action));
|
(source: BindingSource, action: actions.Action) => this._recordAction(source.frame, action));
|
||||||
|
|
||||||
// Commits last action so that no further signals are added to it.
|
// Commits last action so that no further signals are added to it.
|
||||||
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('playwrightRecorderShowRecorderPage', false, ({ page }) => {
|
await this._context.exposeBinding('_playwrightRecorderShowRecorderPage', false, ({ page }) => {
|
||||||
if (this._recorderApp) {
|
if (this._recorderApp) {
|
||||||
this._recorderApp.then(p => p.bringToFront()).catch(() => {});
|
this._recorderApp.then(p => p.bringToFront()).catch(() => {});
|
||||||
return;
|
return;
|
||||||
|
|
@ -143,25 +143,24 @@ export class RecorderSupplement {
|
||||||
}).catch(e => console.error(e));
|
}).catch(e => console.error(e));
|
||||||
});
|
});
|
||||||
|
|
||||||
await this._context.exposeBinding('playwrightRecorderPrintSelector', false, (_, text) => {
|
await this._context.exposeBinding('_playwrightRecorderPrintSelector', false, (_, text) => {
|
||||||
this._context.emit(BrowserContext.Events.StdOut, `Selector: \x1b[38;5;130m${text}\x1b[0m\n`);
|
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, () => {
|
||||||
const state: State = {
|
const state: State = {
|
||||||
uiState: this._recorderUIState,
|
uiState: this._recorderUIState,
|
||||||
canResume: this._app === 'pause',
|
|
||||||
isPaused: this._paused,
|
isPaused: this._paused,
|
||||||
};
|
};
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
|
|
||||||
await this._context.exposeBinding('playwrightRecorderSetUIState', false, (source, state: UIState) => {
|
await this._context.exposeBinding('_playwrightRecorderSetUIState', false, (source, state: UIState) => {
|
||||||
this._recorderUIState = { ...this._recorderUIState, ...state };
|
this._recorderUIState = { ...this._recorderUIState, ...state };
|
||||||
this._output.setEnabled(state.mode === 'recording');
|
this._output.setEnabled(state.mode === 'recording');
|
||||||
});
|
});
|
||||||
|
|
||||||
await this._context.exposeBinding('playwrightRecorderResume', false, () => {
|
await this._context.exposeBinding('_playwrightResume', false, () => {
|
||||||
if (this._resumeCallback) {
|
if (this._resumeCallback) {
|
||||||
this._resumeCallback();
|
this._resumeCallback();
|
||||||
this._resumeCallback = null;
|
this._resumeCallback = null;
|
||||||
|
|
@ -222,7 +221,7 @@ export class RecorderSupplement {
|
||||||
private _clearScript(): void {
|
private _clearScript(): void {
|
||||||
this._bufferedOutput.clear();
|
this._bufferedOutput.clear();
|
||||||
this._generator.restart();
|
this._generator.restart();
|
||||||
if (this._app === 'codegen') {
|
if (!!this._params.startRecording) {
|
||||||
for (const page of this._context.pages())
|
for (const page of this._context.pages())
|
||||||
this._onFrameNavigated(page.mainFrame(), page);
|
this._onFrameNavigated(page.mainFrame(), page);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,8 @@ import { Source } from '../components/source';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
playwrightClear(): Promise<void>
|
_playwrightClear(): Promise<void>
|
||||||
playwrightSetSource: (params: { text: string, language: string }) => void
|
_playwrightSetSource: (params: { text: string, language: string }) => void
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -33,7 +33,7 @@ export interface RecorderProps {
|
||||||
export const Recorder: React.FC<RecorderProps> = ({
|
export const Recorder: React.FC<RecorderProps> = ({
|
||||||
}) => {
|
}) => {
|
||||||
const [source, setSource] = React.useState({ language: 'javascript', text: '' });
|
const [source, setSource] = React.useState({ language: 'javascript', text: '' });
|
||||||
window.playwrightSetSource = setSource;
|
window._playwrightSetSource = setSource;
|
||||||
|
|
||||||
return <div className="recorder">
|
return <div className="recorder">
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
|
|
@ -41,7 +41,7 @@ export const Recorder: React.FC<RecorderProps> = ({
|
||||||
copy(source.text);
|
copy(source.text);
|
||||||
}}></ToolbarButton>
|
}}></ToolbarButton>
|
||||||
<ToolbarButton icon="trashcan" title="Clear" onClick={() => {
|
<ToolbarButton icon="trashcan" title="Clear" onClick={() => {
|
||||||
window.playwrightClear().catch(e => console.error(e));
|
window._playwrightClear().catch(e => console.error(e));
|
||||||
}}></ToolbarButton>
|
}}></ToolbarButton>
|
||||||
<div style={{flex: "auto"}}></div>
|
<div style={{flex: "auto"}}></div>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ fixtures.contextWrapper.init(async ({ browser }, runTest) => {
|
||||||
const context = await browser.newContext() as BrowserContext;
|
const context = await browser.newContext() as BrowserContext;
|
||||||
const outputBuffer = new WritableBuffer();
|
const outputBuffer = new WritableBuffer();
|
||||||
(context as any)._stdout = outputBuffer;
|
(context as any)._stdout = outputBuffer;
|
||||||
await (context as any)._enableRecorder({ language: 'javascript' });
|
await (context as any)._enableRecorder({ language: 'javascript', startRecording: true });
|
||||||
await runTest({ context, output: outputBuffer });
|
await runTest({ context, output: outputBuffer });
|
||||||
await context.close();
|
await context.close();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,10 @@ extended.browserOptions.override(({browserOptions}, runTest) => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
const {it, expect } = extended.build();
|
const {it, expect } = extended.build();
|
||||||
|
|
||||||
it('should pause and resume the script', async ({page}) => {
|
it('should pause and resume the script', async ({page}) => {
|
||||||
let resolved = false;
|
let resolved = false;
|
||||||
const resumePromise = (page as any)._pause().then(() => resolved = true);
|
const resumePromise = (page as any).pause().then(() => resolved = true);
|
||||||
await new Promise(x => setTimeout(x, 0));
|
await new Promise(x => setTimeout(x, 0));
|
||||||
expect(resolved).toBe(false);
|
expect(resolved).toBe(false);
|
||||||
await page.click('#pw-button-resume');
|
await page.click('#pw-button-resume');
|
||||||
|
|
@ -32,9 +33,20 @@ it('should pause and resume the script', async ({page}) => {
|
||||||
expect(resolved).toBe(true);
|
expect(resolved).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should resume from console', async ({page}) => {
|
||||||
|
let resolved = false;
|
||||||
|
const resumePromise = (page as any).pause().then(() => resolved = true);
|
||||||
|
await new Promise(x => setTimeout(x, 0));
|
||||||
|
expect(resolved).toBe(false);
|
||||||
|
await page.waitForFunction(() => !!(window as any).playwright.resume);
|
||||||
|
await page.evaluate('window.playwright.resume()');
|
||||||
|
await resumePromise;
|
||||||
|
expect(resolved).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it('should pause through a navigation', async ({page, server}) => {
|
it('should pause through a navigation', async ({page, server}) => {
|
||||||
let resolved = false;
|
let resolved = false;
|
||||||
const resumePromise = (page as any)._pause().then(() => resolved = true);
|
const resumePromise = (page as any).pause().then(() => resolved = true);
|
||||||
await new Promise(x => setTimeout(x, 0));
|
await new Promise(x => setTimeout(x, 0));
|
||||||
expect(resolved).toBe(false);
|
expect(resolved).toBe(false);
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
|
|
@ -47,7 +59,7 @@ it('should pause after a navigation', async ({page, server}) => {
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
|
|
||||||
let resolved = false;
|
let resolved = false;
|
||||||
const resumePromise = (page as any)._pause().then(() => resolved = true);
|
const resumePromise = (page as any).pause().then(() => resolved = true);
|
||||||
await new Promise(x => setTimeout(x, 0));
|
await new Promise(x => setTimeout(x, 0));
|
||||||
expect(resolved).toBe(false);
|
expect(resolved).toBe(false);
|
||||||
await page.click('#pw-button-resume');
|
await page.click('#pw-button-resume');
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import type { Page, Frame } from '..';
|
||||||
|
|
||||||
const fixtures = folio.extend();
|
const fixtures = folio.extend();
|
||||||
fixtures.context.override(async ({ context }, run) => {
|
fixtures.context.override(async ({ context }, run) => {
|
||||||
await (context as any)._enableConsoleApi();
|
await (context as any)._enableRecorder({ language: 'javascript' });
|
||||||
await run(context);
|
await run(context);
|
||||||
});
|
});
|
||||||
const { describe, it, expect } = fixtures.build();
|
const { describe, it, expect } = fixtures.build();
|
||||||
|
|
|
||||||
12
types/types.d.ts
vendored
12
types/types.d.ts
vendored
|
|
@ -2080,6 +2080,18 @@ export interface Page {
|
||||||
*/
|
*/
|
||||||
opener(): Promise<null|Page>;
|
opener(): Promise<null|Page>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pauses script execution. Playwright will stop executing the script and wait for the user to either press 'Resume' button
|
||||||
|
* in the page overlay or to call `playwright.resume()` in the DevTools console.
|
||||||
|
*
|
||||||
|
* User can inspect selectors or perform manual steps while paused. Resume will continue running the original script from
|
||||||
|
* the place it was paused.
|
||||||
|
*
|
||||||
|
* > NOTE: This method requires Playwright to be started in a headed mode, with a falsy [`options: headless`] value in the
|
||||||
|
* [browserType.launch([options])](https://playwright.dev/docs/api/class-browsertype#browsertypelaunchoptions).
|
||||||
|
*/
|
||||||
|
pause(): Promise<void>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the PDF buffer.
|
* Returns the PDF buffer.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue