chore: run most actions through page._runAbortableTask (#2721)

This introduces a single place for future snapshots.
This commit is contained in:
Dmitry Gozman 2020-06-25 16:57:21 -07:00 committed by GitHub
parent bab6833232
commit ab6a6c9b82
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 80 additions and 90 deletions

View File

@ -27,7 +27,7 @@ import * as js from './javascript';
import { Page } from './page';
import { selectors } from './selectors';
import * as types from './types';
import { Progress, ProgressController } from './progress';
import { Progress } from './progress';
import DebugScript from './debug/injected/debugScript';
import { FatalDOMError, RetargetableDOMError } from './common/domErrors';
@ -122,11 +122,6 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
this._initializePreview().catch(e => {});
}
private _runAbortableTask<T>(task: (progress: Progress) => Promise<T>, timeout: number, apiName: string): Promise<T> {
const controller = new ProgressController(this._page._logger, timeout, `elementHandle.${apiName}`);
return controller.run(task);
}
async _initializePreview() {
const utility = await this._context.injectedScript();
this._preview = await utility.evaluate((injected, e) => 'JSHandle@' + injected.previewNode(e), this);
@ -229,9 +224,9 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
}
async scrollIntoViewIfNeeded(options: types.TimeoutOptions = {}) {
return this._runAbortableTask(
return this._page._runAbortableTask(
progress => this._waitAndScrollIntoViewIfNeeded(progress),
this._page._timeoutSettings.timeout(options), 'scrollIntoViewIfNeeded');
this._page._timeoutSettings.timeout(options), 'elementHandle.scrollIntoViewIfNeeded');
}
private async _waitForVisible(progress: Progress): Promise<'error:notconnected' | 'done'> {
@ -378,10 +373,10 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
}
hover(options: types.PointerActionOptions & types.PointerActionWaitOptions = {}): Promise<void> {
return this._runAbortableTask(async progress => {
return this._page._runAbortableTask(async progress => {
const result = await this._hover(progress, options);
return assertDone(throwRetargetableDOMError(result));
}, this._page._timeoutSettings.timeout(options), 'hover');
}, this._page._timeoutSettings.timeout(options), 'elementHandle.hover');
}
_hover(progress: Progress, options: types.PointerActionOptions & types.PointerActionWaitOptions): Promise<'error:notconnected' | 'done'> {
@ -389,10 +384,10 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
}
click(options: types.MouseClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}): Promise<void> {
return this._runAbortableTask(async progress => {
return this._page._runAbortableTask(async progress => {
const result = await this._click(progress, options);
return assertDone(throwRetargetableDOMError(result));
}, this._page._timeoutSettings.timeout(options), 'click');
}, this._page._timeoutSettings.timeout(options), 'elementHandle.click');
}
_click(progress: Progress, options: types.MouseClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<'error:notconnected' | 'done'> {
@ -400,10 +395,10 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
}
dblclick(options: types.MouseMultiClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}): Promise<void> {
return this._runAbortableTask(async progress => {
return this._page._runAbortableTask(async progress => {
const result = await this._dblclick(progress, options);
return assertDone(throwRetargetableDOMError(result));
}, this._page._timeoutSettings.timeout(options), 'dblclick');
}, this._page._timeoutSettings.timeout(options), 'elementHandle.dblclick');
}
_dblclick(progress: Progress, options: types.MouseMultiClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<'error:notconnected' | 'done'> {
@ -411,10 +406,10 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
}
async selectOption(values: string | ElementHandle | types.SelectOption | string[] | ElementHandle[] | types.SelectOption[] | null, options: types.NavigatingActionWaitOptions = {}): Promise<string[]> {
return this._runAbortableTask(async progress => {
return this._page._runAbortableTask(async progress => {
const result = await this._selectOption(progress, values, options);
return throwRetargetableDOMError(result);
}, this._page._timeoutSettings.timeout(options), 'selectOption');
}, this._page._timeoutSettings.timeout(options), 'elementHandle.selectOption');
}
async _selectOption(progress: Progress, values: string | ElementHandle | types.SelectOption | string[] | ElementHandle[] | types.SelectOption[] | null, options: types.NavigatingActionWaitOptions): Promise<string[] | 'error:notconnected'> {
@ -444,10 +439,10 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
}
async fill(value: string, options: types.NavigatingActionWaitOptions = {}): Promise<void> {
return this._runAbortableTask(async progress => {
return this._page._runAbortableTask(async progress => {
const result = await this._fill(progress, value, options);
assertDone(throwRetargetableDOMError(result));
}, this._page._timeoutSettings.timeout(options), 'fill');
}, this._page._timeoutSettings.timeout(options), 'elementHandle.fill');
}
async _fill(progress: Progress, value: string, options: types.NavigatingActionWaitOptions): Promise<'error:notconnected' | 'done'> {
@ -478,7 +473,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
}
async selectText(options: types.TimeoutOptions = {}): Promise<void> {
return this._runAbortableTask(async progress => {
return this._page._runAbortableTask(async progress => {
progress.throwIfAborted(); // Avoid action that has side-effects.
const poll = await this._evaluateHandleInUtility(([injected, node]) => {
return injected.waitForVisibleAndSelectText(node);
@ -486,14 +481,14 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
const pollHandler = new InjectedScriptPollHandler(progress, poll);
const result = throwFatalDOMError(await pollHandler.finish());
assertDone(throwRetargetableDOMError(result));
}, this._page._timeoutSettings.timeout(options), 'selectText');
}, this._page._timeoutSettings.timeout(options), 'elementHandle.selectText');
}
async setInputFiles(files: string | types.FilePayload | string[] | types.FilePayload[], options: types.NavigatingActionWaitOptions = {}) {
return this._runAbortableTask(async progress => {
return this._page._runAbortableTask(async progress => {
const result = await this._setInputFiles(progress, files, options);
return assertDone(throwRetargetableDOMError(result));
}, this._page._timeoutSettings.timeout(options), 'setInputFiles');
}, this._page._timeoutSettings.timeout(options), 'elementHandle.setInputFiles');
}
async _setInputFiles(progress: Progress, files: string | types.FilePayload | string[] | types.FilePayload[], options: types.NavigatingActionWaitOptions): Promise<'error:notconnected' | 'done'> {
@ -534,10 +529,10 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
}
async focus(): Promise<void> {
return this._runAbortableTask(async progress => {
return this._page._runAbortableTask(async progress => {
const result = await this._focus(progress);
return assertDone(throwRetargetableDOMError(result));
}, 0, 'focus');
}, 0, 'elementHandle.focus');
}
async _focus(progress: Progress): Promise<'error:notconnected' | 'done'> {
@ -547,10 +542,10 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
}
async type(text: string, options: { delay?: number } & types.NavigatingActionWaitOptions = {}): Promise<void> {
return this._runAbortableTask(async progress => {
return this._page._runAbortableTask(async progress => {
const result = await this._type(progress, text, options);
return assertDone(throwRetargetableDOMError(result));
}, this._page._timeoutSettings.timeout(options), 'type');
}, this._page._timeoutSettings.timeout(options), 'elementHandle.type');
}
async _type(progress: Progress, text: string, options: { delay?: number } & types.NavigatingActionWaitOptions): Promise<'error:notconnected' | 'done'> {
@ -566,10 +561,10 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
}
async press(key: string, options: { delay?: number } & types.NavigatingActionWaitOptions = {}): Promise<void> {
return this._runAbortableTask(async progress => {
return this._page._runAbortableTask(async progress => {
const result = await this._press(progress, key, options);
return assertDone(throwRetargetableDOMError(result));
}, this._page._timeoutSettings.timeout(options), 'press');
}, this._page._timeoutSettings.timeout(options), 'elementHandle.press');
}
async _press(progress: Progress, key: string, options: { delay?: number } & types.NavigatingActionWaitOptions): Promise<'error:notconnected' | 'done'> {
@ -585,17 +580,17 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
}
async check(options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) {
return this._runAbortableTask(async progress => {
return this._page._runAbortableTask(async progress => {
const result = await this._setChecked(progress, true, options);
return assertDone(throwRetargetableDOMError(result));
}, this._page._timeoutSettings.timeout(options), 'check');
}, this._page._timeoutSettings.timeout(options), 'elementHandle.check');
}
async uncheck(options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) {
return this._runAbortableTask(async progress => {
return this._page._runAbortableTask(async progress => {
const result = await this._setChecked(progress, false, options);
return assertDone(throwRetargetableDOMError(result));
}, this._page._timeoutSettings.timeout(options), 'uncheck');
}, this._page._timeoutSettings.timeout(options), 'elementHandle.uncheck');
}
async _setChecked(progress: Progress, state: boolean, options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<'error:notconnected' | 'done'> {
@ -614,9 +609,9 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
}
async screenshot(options: types.ElementScreenshotOptions = {}): Promise<Buffer> {
return this._runAbortableTask(
return this._page._runAbortableTask(
progress => this._page._screenshotter.screenshotElement(progress, this, options),
this._page._timeoutSettings.timeout(options), 'screenshot');
this._page._timeoutSettings.timeout(options), 'elementHandle.screenshot');
}
async $(selector: string): Promise<ElementHandle | null> {

View File

@ -334,20 +334,13 @@ export class Frame {
this._parentFrame._childFrames.add(this);
}
private _runAbortableTask<T>(task: (progress: Progress) => Promise<T>, timeout: number, apiName: string): Promise<T> {
const controller = new ProgressController(this._page._logger, timeout, this._apiName(apiName));
return controller.run(task);
}
private _apiName(method: string) {
const subject = this._page._callingPageAPI ? 'page' : 'frame';
return `${subject}.${method}`;
}
async goto(url: string, options: types.GotoOptions = {}): Promise<network.Response | null> {
const progressController = new ProgressController(this._page._logger, this._page._timeoutSettings.navigationTimeout(options), this._apiName('goto'));
abortProgressOnFrameDetach(progressController, this);
return progressController.run(async progress => {
return runNavigationTask(this, options, this._apiName('goto'), async progress => {
progress.logger.info(`navigating to "${url}", waiting until "${options.waitUntil || 'load'}"`);
const headers = (this._page._state.extraHTTPHeaders || {});
let referer = headers['referer'] || headers['Referer'];
@ -380,9 +373,7 @@ export class Frame {
}
async waitForNavigation(options: types.WaitForNavigationOptions = {}): Promise<network.Response | null> {
const progressController = new ProgressController(this._page._logger, this._page._timeoutSettings.navigationTimeout(options), this._apiName('waitForNavigation'));
abortProgressOnFrameDetach(progressController, this);
return progressController.run(async progress => {
return runNavigationTask(this, options, this._apiName('waitForNavigation'), async progress => {
const toUrl = typeof options.url === 'string' ? ` to "${options.url}"` : '';
progress.logger.info(`waiting for navigation${toUrl} until "${options.waitUntil || 'load'}"`);
const frameTask = new FrameTask(this, progress);
@ -399,9 +390,7 @@ export class Frame {
}
async waitForLoadState(state: types.LifecycleEvent = 'load', options: types.TimeoutOptions = {}): Promise<void> {
const progressController = new ProgressController(this._page._logger, this._page._timeoutSettings.navigationTimeout(options), this._apiName('waitForLoadState'));
abortProgressOnFrameDetach(progressController, this);
return progressController.run(progress => this._waitForLoadState(progress, state));
return runNavigationTask(this, options, this._apiName('waitForLoadState'), progress => this._waitForLoadState(progress, state));
}
async _waitForLoadState(progress: Progress, state: types.LifecycleEvent): Promise<void> {
@ -468,7 +457,7 @@ export class Frame {
throw new Error(`Unsupported state option "${state}"`);
const info = selectors._parseSelector(selector);
const task = selectors._waitForSelectorTask(info, state);
return this._runAbortableTask(async progress => {
return this._page._runAbortableTask(async progress => {
progress.logger.info(`waiting for selector "${selector}"${state === 'attached' ? '' : ' to be ' + state}`);
const result = await this._scheduleRerunnableTask(progress, info.world, task);
if (!result.asElement()) {
@ -483,17 +472,17 @@ export class Frame {
return adopted;
}
return handle;
}, this._page._timeoutSettings.timeout(options), 'waitForSelector');
}, this._page._timeoutSettings.timeout(options), this._apiName('waitForSelector'));
}
async dispatchEvent(selector: string, type: string, eventInit?: Object, options: types.TimeoutOptions = {}): Promise<void> {
const info = selectors._parseSelector(selector);
const task = selectors._dispatchEventTask(info, type, eventInit || {});
return this._runAbortableTask(async progress => {
return this._page._runAbortableTask(async progress => {
progress.logger.info(`Dispatching "${type}" event on selector "${selector}"...`);
const result = await this._scheduleRerunnableTask(progress, 'main', task);
result.dispose();
}, this._page._timeoutSettings.timeout(options), 'dispatchEvent');
}, this._page._timeoutSettings.timeout(options), this._apiName('dispatchEvent'));
}
async $eval<R, Arg>(selector: string, pageFunction: js.FuncOn<Element, Arg, R>, arg: Arg): Promise<R>;
@ -543,9 +532,7 @@ export class Frame {
}
async setContent(html: string, options: types.NavigateOptions = {}): Promise<void> {
const progressController = new ProgressController(this._page._logger, this._page._timeoutSettings.navigationTimeout(options), this._apiName('setContent'));
abortProgressOnFrameDetach(progressController, this);
return progressController.run(async progress => {
return runNavigationTask(this, options, this._apiName('setContent'), async progress => {
const waitUntil = options.waitUntil === undefined ? 'load' : options.waitUntil;
progress.logger.info(`setting frame content, waiting until "${waitUntil}"`);
const tag = `--playwright--set--content--${this._id}--${++this._setContentCounter}--`;
@ -733,7 +720,7 @@ export class Frame {
action: (progress: Progress, handle: dom.ElementHandle<Element>) => Promise<R | 'error:notconnected'>,
apiName: string): Promise<R> {
const info = selectors._parseSelector(selector);
return this._runAbortableTask(async progress => {
return this._page._runAbortableTask(async progress => {
while (progress.isRunning()) {
progress.logger.info(`waiting for selector "${selector}"`);
const task = selectors._waitForSelectorTask(info, 'attached');
@ -753,63 +740,63 @@ export class Frame {
}
async click(selector: string, options: types.MouseClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) {
await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle._click(progress, options), 'click');
await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle._click(progress, options), this._apiName('click'));
}
async dblclick(selector: string, options: types.MouseMultiClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) {
await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle._dblclick(progress, options), 'dblclick');
await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle._dblclick(progress, options), this._apiName('dblclick'));
}
async fill(selector: string, value: string, options: types.NavigatingActionWaitOptions = {}) {
await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle._fill(progress, value, options), 'fill');
await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle._fill(progress, value, options), this._apiName('fill'));
}
async focus(selector: string, options: types.TimeoutOptions = {}) {
await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle._focus(progress), 'focus');
await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle._focus(progress), this._apiName('focus'));
}
async textContent(selector: string, options: types.TimeoutOptions = {}): Promise<null|string> {
return await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle.textContent(), 'textContent');
return await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle.textContent(), this._apiName('textContent'));
}
async innerText(selector: string, options: types.TimeoutOptions = {}): Promise<string> {
return await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle.innerText(), 'innerText');
return await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle.innerText(), this._apiName('innerText'));
}
async innerHTML(selector: string, options: types.TimeoutOptions = {}): Promise<string> {
return await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle.innerHTML(), 'innerHTML');
return await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle.innerHTML(), this._apiName('innerHTML'));
}
async getAttribute(selector: string, name: string, options: types.TimeoutOptions = {}): Promise<string | null> {
return await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle.getAttribute(name), 'getAttribute');
return await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle.getAttribute(name), this._apiName('getAttribute'));
}
async hover(selector: string, options: types.PointerActionOptions & types.PointerActionWaitOptions = {}) {
await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle._hover(progress, options), 'hover');
await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle._hover(progress, options), this._apiName('hover'));
}
async selectOption(selector: string, values: string | dom.ElementHandle | types.SelectOption | string[] | dom.ElementHandle[] | types.SelectOption[] | null, options: types.NavigatingActionWaitOptions = {}): Promise<string[]> {
return this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle._selectOption(progress, values, options), 'selectOption');
return this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle._selectOption(progress, values, options), this._apiName('selectOption'));
}
async setInputFiles(selector: string, files: string | types.FilePayload | string[] | types.FilePayload[], options: types.NavigatingActionWaitOptions = {}): Promise<void> {
await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle._setInputFiles(progress, files, options), 'setInputFiles');
await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle._setInputFiles(progress, files, options), this._apiName('setInputFiles'));
}
async type(selector: string, text: string, options: { delay?: number } & types.NavigatingActionWaitOptions = {}) {
await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle._type(progress, text, options), 'type');
await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle._type(progress, text, options), this._apiName('type'));
}
async press(selector: string, key: string, options: { delay?: number } & types.NavigatingActionWaitOptions = {}) {
await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle._press(progress, key, options), 'press');
await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle._press(progress, key, options), this._apiName('press'));
}
async check(selector: string, options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) {
await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle._setChecked(progress, true, options), 'check');
await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle._setChecked(progress, true, options), this._apiName('check'));
}
async uncheck(selector: string, options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) {
await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle._setChecked(progress, false, options), 'uncheck');
await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle._setChecked(progress, false, options), this._apiName('uncheck'));
}
async waitForTimeout(timeout: number) {
@ -841,9 +828,9 @@ export class Frame {
return injectedScript.pollInterval(polling, (progress, continuePolling) => innerPredicate(arg) || continuePolling);
}, { injectedScript, predicateBody, polling, arg });
};
return this._runAbortableTask(
return this._page._runAbortableTask(
progress => this._scheduleRerunnableTask(progress, 'main', task),
this._page._timeoutSettings.timeout(options), 'waitForFunction');
this._page._timeoutSettings.timeout(options), this._apiName('waitForFunction'));
}
async title(): Promise<string> {
@ -866,6 +853,8 @@ export class Frame {
private _scheduleRerunnableTask<T>(progress: Progress, world: types.World, task: SchedulableTask<T>): Promise<js.SmartHandle<T>> {
const data = this._contextData.get(world)!;
const rerunnableTask = new RerunnableTask(data, progress, task);
if (this._detached)
rerunnableTask.terminate(new Error('waitForFunction failed: frame got detached.'));
if (data.context)
rerunnableTask.rerun(data.context);
return rerunnableTask.promise;
@ -1114,8 +1103,11 @@ class FrameTask {
}
}
function abortProgressOnFrameDetach(controller: ProgressController, frame: Frame) {
frame._page._disconnectedPromise.then(() => controller.abort(new Error('Navigation failed because page was closed!')));
frame._page._crashedPromise.then(() => controller.abort(new Error('Navigation failed because page crashed!')));
async function runNavigationTask<T>(frame: Frame, options: types.TimeoutOptions, apiName: string, task: (progress: Progress) => Promise<T>): Promise<T> {
const page = frame._page;
const controller = new ProgressController(page._logger, page._timeoutSettings.navigationTimeout(options), apiName);
page._disconnectedPromise.then(() => controller.abort(new Error('Navigation failed because page was closed!')));
page._crashedPromise.then(() => controller.abort(new Error('Navigation failed because page crashed!')));
frame._detachedPromise.then(() => controller.abort(new Error('Navigating frame was detached!')));
return controller.run(task);
}

View File

@ -285,7 +285,7 @@ export class Response {
return this._statusText;
}
headers(): object {
headers(): types.Headers {
return { ...this._headers };
}

View File

@ -31,7 +31,7 @@ import * as accessibility from './accessibility';
import { EventEmitter } from 'events';
import { FileChooser } from './fileChooser';
import { logError, Logger } from './logger';
import { ProgressController, Progress } from './progress';
import { ProgressController, Progress, runAbortableTask } from './progress';
export interface PageDelegate {
readonly rawMouse: input.RawMouse;
@ -68,12 +68,13 @@ export interface PageDelegate {
getBoundingBox(handle: dom.ElementHandle): Promise<types.Rect | null>;
getFrameElement(frame: frames.Frame): Promise<dom.ElementHandle>;
scrollRectIntoViewIfNeeded(handle: dom.ElementHandle, rect?: types.Rect): Promise<'error:notvisible' | 'error:notconnected' | 'done'>;
rafCountForStablePosition(): number;
getAccessibilityTree(needle?: dom.ElementHandle): Promise<{tree: accessibility.AXNode, needle: accessibility.AXNode | null}>;
pdf?: (options?: types.PDFOptions) => Promise<Buffer>;
coverage?: () => any;
// Work around WebKit's raf issues on Windows.
rafCountForStablePosition(): number;
// Work around Chrome's non-associated input and protocol.
inputActionEpilogue(): Promise<void>;
// Work around for asynchronously dispatched CSP errors in Firefox.
@ -143,11 +144,6 @@ export class Page extends EventEmitter {
this.coverage = delegate.coverage ? delegate.coverage() : null;
}
private _runAbortableTask<T>(task: (progress: Progress) => Promise<T>, timeout: number, apiName: string): Promise<T> {
const controller = new ProgressController(this._logger, timeout, `page.${apiName}`);
return controller.run(task);
}
_didClose() {
assert(!this._closed, 'Page closed twice');
this._closed = true;
@ -166,6 +162,12 @@ export class Page extends EventEmitter {
this._disconnectedCallback(new Error('Page closed'));
}
async _runAbortableTask<T>(task: (progress: Progress) => Promise<T>, timeout: number, apiName: string): Promise<T> {
return runAbortableTask(async progress => {
return task(progress);
}, this._logger, timeout, apiName);
}
async _onFileChooserOpened(handle: dom.ElementHandle) {
const multiple = await handle.evaluate(element => !!(element as HTMLInputElement).multiple);
if (!this.listenerCount(Events.Page.FileChooser)) {
@ -448,8 +450,9 @@ export class Page extends EventEmitter {
}
async screenshot(options: types.ScreenshotOptions = {}): Promise<Buffer> {
const controller = new ProgressController(this._logger, this._timeoutSettings.timeout(options), 'page.screenshot');
return controller.run(progress => this._screenshotter.screenshotPage(progress, options));
return this._runAbortableTask(
progress => this._screenshotter.screenshotPage(progress, options),
this._timeoutSettings.timeout(options), 'page.screenshot');
}
async title(): Promise<string> {

View File

@ -431,9 +431,9 @@ describe('Frame.waitForSelector', function() {
});
it('should respect timeout', async({page, server}) => {
let error = null;
await page.waitForSelector('div', { timeout: 10, state: 'attached' }).catch(e => error = e);
await page.waitForSelector('div', { timeout: 3000, state: 'attached' }).catch(e => error = e);
expect(error).toBeTruthy();
expect(error.message).toContain('Timeout 10ms exceeded during page.waitForSelector');
expect(error.message).toContain('Timeout 3000ms exceeded during page.waitForSelector');
expect(error.message).toContain('waiting for selector "div"');
expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
});
@ -521,9 +521,9 @@ describe('Frame.waitForSelector xpath', function() {
});
it('should respect timeout', async({page}) => {
let error = null;
await page.waitForSelector('//div', { state: 'attached', timeout: 10 }).catch(e => error = e);
await page.waitForSelector('//div', { state: 'attached', timeout: 3000 }).catch(e => error = e);
expect(error).toBeTruthy();
expect(error.message).toContain('Timeout 10ms exceeded during page.waitForSelector');
expect(error.message).toContain('Timeout 3000ms exceeded during page.waitForSelector');
expect(error.message).toContain('waiting for selector "//div"');
expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
});