mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-14 21:53:35 +03:00
feat(pause): page._pause to wait for user to click resume (#5050)
This commit is contained in:
parent
a2422a40ec
commit
3e4e511d84
@ -639,6 +639,12 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
|
||||
return this;
|
||||
}
|
||||
|
||||
async _pause() {
|
||||
return this._wrapApiCall('page.pause', async () => {
|
||||
await this._channel.pause();
|
||||
});
|
||||
}
|
||||
|
||||
async _pdf(options: PDFOptions = {}): Promise<Buffer> {
|
||||
return this._wrapApiCall('page.pdf', async () => {
|
||||
const transportOptions: channels.PagePdfParams = { ...options } as channels.PagePdfParams;
|
||||
|
@ -237,6 +237,10 @@ export class PageDispatcher extends Dispatcher<Page, channels.PageInitializer> i
|
||||
return { entries: await coverage.stopCSSCoverage() };
|
||||
}
|
||||
|
||||
async pause() {
|
||||
await this._page.pause();
|
||||
}
|
||||
|
||||
_onFrameAttached(frame: Frame) {
|
||||
this._dispatchEvent('frameAttached', { frame: FrameDispatcher.from(this._scope, frame) });
|
||||
}
|
||||
|
@ -770,6 +770,7 @@ export interface PageChannel extends Channel {
|
||||
mouseClick(params: PageMouseClickParams, metadata?: Metadata): Promise<PageMouseClickResult>;
|
||||
touchscreenTap(params: PageTouchscreenTapParams, metadata?: Metadata): Promise<PageTouchscreenTapResult>;
|
||||
accessibilitySnapshot(params: PageAccessibilitySnapshotParams, metadata?: Metadata): Promise<PageAccessibilitySnapshotResult>;
|
||||
pause(params?: PagePauseParams, metadata?: Metadata): Promise<PagePauseResult>;
|
||||
pdf(params: PagePdfParams, metadata?: Metadata): Promise<PagePdfResult>;
|
||||
crStartJSCoverage(params: PageCrStartJSCoverageParams, metadata?: Metadata): Promise<PageCrStartJSCoverageResult>;
|
||||
crStopJSCoverage(params?: PageCrStopJSCoverageParams, metadata?: Metadata): Promise<PageCrStopJSCoverageResult>;
|
||||
@ -1066,6 +1067,9 @@ export type PageAccessibilitySnapshotOptions = {
|
||||
export type PageAccessibilitySnapshotResult = {
|
||||
rootAXNode?: AXNode,
|
||||
};
|
||||
export type PagePauseParams = {};
|
||||
export type PagePauseOptions = {};
|
||||
export type PagePauseResult = void;
|
||||
export type PagePdfParams = {
|
||||
scale?: number,
|
||||
displayHeaderFooter?: boolean,
|
||||
|
@ -842,6 +842,8 @@ Page:
|
||||
returns:
|
||||
rootAXNode: AXNode?
|
||||
|
||||
pause:
|
||||
|
||||
pdf:
|
||||
parameters:
|
||||
scale: number?
|
||||
|
@ -443,6 +443,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||
interestingOnly: tOptional(tBoolean),
|
||||
root: tOptional(tChannel('ElementHandle')),
|
||||
});
|
||||
scheme.PagePauseParams = tOptional(tObject({}));
|
||||
scheme.PagePdfParams = tObject({
|
||||
scale: tOptional(tNumber),
|
||||
displayHeaderFooter: tOptional(tBoolean),
|
||||
|
@ -32,7 +32,10 @@ type ContextData = {
|
||||
contextPromise: Promise<dom.FrameExecutionContext>;
|
||||
contextResolveCallback: (c: dom.FrameExecutionContext) => void;
|
||||
context: dom.FrameExecutionContext | null;
|
||||
rerunnableTasks: Set<RerunnableTask>;
|
||||
rerunnableTasks: Set<{
|
||||
rerun(context: dom.FrameExecutionContext): Promise<void>;
|
||||
terminate(error: Error): void;
|
||||
}>;
|
||||
};
|
||||
|
||||
type DocumentInfo = {
|
||||
@ -1046,6 +1049,24 @@ export class Frame extends EventEmitter {
|
||||
this._parentFrame = null;
|
||||
}
|
||||
|
||||
async evaluateSurvivingNavigations<T>(callback: (context: dom.FrameExecutionContext) => Promise<T>, world: types.World) {
|
||||
return new Promise<T>((resolve, terminate) => {
|
||||
const data = this._contextData.get(world)!;
|
||||
const task = {
|
||||
terminate,
|
||||
async rerun(context: dom.FrameExecutionContext) {
|
||||
try {
|
||||
resolve(await callback(context));
|
||||
data.rerunnableTasks.delete(task);
|
||||
} catch (e) {}
|
||||
}
|
||||
};
|
||||
data.rerunnableTasks.add(task);
|
||||
if (data.context)
|
||||
task.rerun(data.context);
|
||||
});
|
||||
}
|
||||
|
||||
private _scheduleRerunnableTask<T>(progress: Progress, world: types.World, task: dom.SchedulableTask<T>): Promise<T> {
|
||||
const data = this._contextData.get(world)!;
|
||||
const rerunnableTask = new RerunnableTask(data, progress, task, true /* returnByValue */);
|
||||
|
@ -492,6 +492,31 @@ export class Page extends EventEmitter {
|
||||
const identifier = PageBinding.identifier(name, world);
|
||||
return this._pageBindings.get(identifier) || this._browserContext._pageBindings.get(identifier);
|
||||
}
|
||||
|
||||
async pause() {
|
||||
if (!this._browserContext._browser._options.headful)
|
||||
throw new Error('Cannot pause in headless mode.');
|
||||
await this.mainFrame().evaluateSurvivingNavigations(async context => {
|
||||
await context.evaluateInternal(async () => {
|
||||
const element = document.createElement('playwright-resume');
|
||||
element.style.position = 'absolute';
|
||||
element.style.top = '10px';
|
||||
element.style.left = '10px';
|
||||
element.style.zIndex = '2147483646';
|
||||
element.style.opacity = '0.9';
|
||||
element.setAttribute('role', 'button');
|
||||
element.tabIndex = 0;
|
||||
element.style.fontSize = '50px';
|
||||
element.textContent = '▶️';
|
||||
element.title = 'Resume script';
|
||||
document.body.appendChild(element);
|
||||
await new Promise(x => {
|
||||
element.onclick = x;
|
||||
});
|
||||
element.remove();
|
||||
});
|
||||
}, 'utility');
|
||||
}
|
||||
}
|
||||
|
||||
export class Worker extends EventEmitter {
|
||||
|
56
test/pause.spec.ts
Normal file
56
test/pause.spec.ts
Normal file
@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { folio } from './fixtures';
|
||||
const extended = folio.extend();
|
||||
extended.browserOptions.override(({browserOptions}, runTest) => {
|
||||
return runTest({
|
||||
...browserOptions,
|
||||
headless: false,
|
||||
});
|
||||
});
|
||||
const {it, expect } = extended.build();
|
||||
it('should pause and resume the script', 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.click('playwright-resume');
|
||||
await resumePromise;
|
||||
expect(resolved).toBe(true);
|
||||
});
|
||||
|
||||
it('should pause through a navigation', async ({page, server}) => {
|
||||
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.goto(server.EMPTY_PAGE);
|
||||
await page.click('playwright-resume');
|
||||
await resumePromise;
|
||||
expect(resolved).toBe(true);
|
||||
});
|
||||
|
||||
it('should pause after a navigation', async ({page, server}) => {
|
||||
await page.goto(server.EMPTY_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.click('playwright-resume');
|
||||
await resumePromise;
|
||||
expect(resolved).toBe(true);
|
||||
});
|
Loading…
Reference in New Issue
Block a user