Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(pause): make page.pause public #5288

Merged
merged 1 commit into from
Feb 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 13 additions & 0 deletions docs/src/api/class-page.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

## 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
- returns: <[Buffer]>

Expand Down
25 changes: 19 additions & 6 deletions src/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ program
.command('open [url]')
.description('open page in browser specified via -b, --browser')
.action(function(url, command) {
open(command.parent, url);
open(command.parent, url, language());
}).on('--help', function() {
console.log('');
console.log('Examples:');
Expand All @@ -70,8 +70,9 @@ for (const {alias, name, type} of browsers) {
program
.command(`${alias} [url]`)
.description(`open page in ${name}`)
.option('--target <language>', `language to use, one of javascript, python, python-async, csharp`, language())
.action(function(url, command) {
open({ ...command.parent, browser: type }, url);
open({ ...command.parent, browser: type }, url, command.target);
}).on('--help', function() {
console.log('');
console.log('Examples:');
Expand All @@ -84,7 +85,7 @@ program
.command('codegen [url]')
.description('open page and generate code for user actions')
.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) {
codegen(command.parent, url, command.target, command.output);
}).on('--help', function() {
Expand Down Expand Up @@ -316,9 +317,16 @@ async function openPage(context: BrowserContext, url: string | undefined): Promi
return page;
}

async function open(options: Options, url: string | undefined) {
const { context } = await launchContext(options, false);
await context._enableConsoleApi();
async function open(options: Options, url: string | undefined, language: string) {
const { context, launchOptions, contextOptions } = await launchContext(options, false);
await context._enableRecorder({
language,
launchOptions,
contextOptions,
device: options.device,
saveStorage: options.saveStorage,
terminal: !!process.stdout.columns,
});
await openPage(context, url);
if (process.env.PWCLI_EXIT_FOR_TEST)
await Promise.all(context.pages().map(p => p.close()));
Expand All @@ -334,6 +342,7 @@ async function codegen(options: Options, url: string | undefined, language: stri
contextOptions,
device: options.device,
saveStorage: options.saveStorage,
startRecording: true,
terminal: !!process.stdout.columns,
outputFile: outputFile ? path.resolve(outputFile) : undefined
});
Expand Down Expand Up @@ -409,3 +418,7 @@ function validateOptions(options: Options) {
process.exit(0);
}
}

function language(): string {
return process.env.PW_CLI_TARGET_LANG || 'javascript';
}
5 changes: 1 addition & 4 deletions src/client/browserContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,16 +276,13 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
});
}

async _enableConsoleApi() {
await this._channel.consoleSupplementExpose();
}

async _enableRecorder(params: {
language: string,
launchOptions?: LaunchOptions,
contextOptions?: BrowserContextOptions,
device?: string,
saveStorage?: string,
startRecording?: boolean,
terminal?: boolean,
outputFile?: string
}) {
Expand Down
2 changes: 1 addition & 1 deletion src/client/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -642,7 +642,7 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
return this;
}

async _pause() {
async pause() {
await this.context()._pause();
}

Expand Down
10 changes: 2 additions & 8 deletions src/dispatchers/browserContextDispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import { RouteDispatcher, RequestDispatcher } from './networkDispatchers';
import { CRBrowserContext } from '../server/chromium/crBrowser';
import { CDPSessionDispatcher } from './cdpSessionDispatcher';
import { RecorderSupplement } from '../server/supplements/recorderSupplement';
import { ConsoleApiSupplement } from '../server/supplements/consoleApiSupplement';

export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channels.BrowserContextInitializer> implements channels.BrowserContextChannel {
private _context: BrowserContext;
Expand Down Expand Up @@ -129,19 +128,14 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
await this._context.close();
}

async consoleSupplementExpose(): Promise<void> {
const consoleApi = new ConsoleApiSupplement(this._context);
await consoleApi.install();
}

async recorderSupplementEnable(params: channels.BrowserContextRecorderSupplementEnableParams): Promise<void> {
await RecorderSupplement.getOrCreate(this._context, 'codegen', params);
await RecorderSupplement.getOrCreate(this._context, params);
}

async pause() {
if (!this._context._browser.options.headful)
return;
const recorder = await RecorderSupplement.getOrCreate(this._context, 'pause', {
const recorder = await RecorderSupplement.getOrCreate(this._context, {
language: 'javascript',
terminal: true
});
Expand Down
6 changes: 2 additions & 4 deletions src/protocol/channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -558,7 +558,6 @@ export interface BrowserContextChannel extends Channel {
setNetworkInterceptionEnabled(params: BrowserContextSetNetworkInterceptionEnabledParams, metadata?: Metadata): Promise<BrowserContextSetNetworkInterceptionEnabledResult>;
setOffline(params: BrowserContextSetOfflineParams, metadata?: Metadata): Promise<BrowserContextSetOfflineResult>;
storageState(params?: BrowserContextStorageStateParams, metadata?: Metadata): Promise<BrowserContextStorageStateResult>;
consoleSupplementExpose(params?: BrowserContextConsoleSupplementExposeParams, metadata?: Metadata): Promise<BrowserContextConsoleSupplementExposeResult>;
pause(params?: BrowserContextPauseParams, metadata?: Metadata): Promise<BrowserContextPauseResult>;
recorderSupplementEnable(params: BrowserContextRecorderSupplementEnableParams, metadata?: Metadata): Promise<BrowserContextRecorderSupplementEnableResult>;
crNewCDPSession(params: BrowserContextCrNewCDPSessionParams, metadata?: Metadata): Promise<BrowserContextCrNewCDPSessionResult>;
Expand Down Expand Up @@ -709,14 +708,12 @@ export type BrowserContextStorageStateResult = {
cookies: NetworkCookie[],
origins: OriginStorage[],
};
export type BrowserContextConsoleSupplementExposeParams = {};
export type BrowserContextConsoleSupplementExposeOptions = {};
export type BrowserContextConsoleSupplementExposeResult = void;
export type BrowserContextPauseParams = {};
export type BrowserContextPauseOptions = {};
export type BrowserContextPauseResult = void;
export type BrowserContextRecorderSupplementEnableParams = {
language: string,
startRecording?: boolean,
launchOptions?: any,
contextOptions?: any,
device?: string,
Expand All @@ -725,6 +722,7 @@ export type BrowserContextRecorderSupplementEnableParams = {
outputFile?: string,
};
export type BrowserContextRecorderSupplementEnableOptions = {
startRecording?: boolean,
launchOptions?: any,
contextOptions?: any,
device?: string,
Expand Down
4 changes: 1 addition & 3 deletions src/protocol/protocol.yml
Original file line number Diff line number Diff line change
Expand Up @@ -601,16 +601,14 @@ BrowserContext:
type: array
items: OriginStorage

consoleSupplementExpose:
experimental: True

pause:
experimental: True

recorderSupplementEnable:
experimental: True
parameters:
language: string
startRecording: boolean?
launchOptions: json?
contextOptions: json?
device: string?
Expand Down
2 changes: 1 addition & 1 deletion src/protocol/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,10 +337,10 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
offline: tBoolean,
});
scheme.BrowserContextStorageStateParams = tOptional(tObject({}));
scheme.BrowserContextConsoleSupplementExposeParams = tOptional(tObject({}));
scheme.BrowserContextPauseParams = tOptional(tObject({}));
scheme.BrowserContextRecorderSupplementEnableParams = tObject({
language: tString,
startRecording: tOptional(tBoolean),
launchOptions: tOptional(tAny),
contextOptions: tOptional(tAny),
device: tOptional(tString),
Expand Down
30 changes: 0 additions & 30 deletions src/server/supplements/consoleApiSupplement.ts

This file was deleted.

29 changes: 24 additions & 5 deletions src/server/supplements/injected/consoleApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,35 @@
import type InjectedScript from '../../injected/injectedScript';
import { generateSelector } from './selectorGenerator';

type ConsoleAPIInterface = {
pavelfeldman marked this conversation as resolved.
Show resolved Hide resolved
$: (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 {
private _injectedScript: InjectedScript;

constructor(injectedScript: InjectedScript) {
this._injectedScript = injectedScript;
if ((window as any).playwright)
if (window.playwright)
return;
(window as any).playwright = {
window.playwright = {
$: (selector: string) => this._querySelector(selector),
$$: (selector: string) => this._querySelectorAll(selector),
inspect: (selector: string) => this._inspect(selector),
selector: (element: Element) => this._selector(element),
resume: () => this._resume(),
};
}

Expand All @@ -47,18 +64,20 @@ export class ConsoleAPI {
}

private _inspect(selector: string) {
if (typeof (window as any).inspect !== 'function')
return;
if (typeof selector !== 'string')
throw new Error(`Usage: playwright.inspect('Playwright >> selector').`);
(window as any).inspect(this._querySelector(selector));
window.inspect(this._querySelector(selector));
}

private _selector(element: Element) {
if (!(element instanceof Element))
throw new Error(`Usage: playwright.selector(element).`);
return generateSelector(this._injectedScript, element).selector;
}

private _resume() {
window._playwrightResume().catch(() => {});
}
}

export default ConsoleAPI;