diff --git a/src/dom.ts b/src/dom.ts index a2ab34a551e9e..7943b2fedce3e 100644 --- a/src/dom.ts +++ b/src/dom.ts @@ -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 extends js.JSHandle { this._initializePreview().catch(e => {}); } - private _runAbortableTask(task: (progress: Progress) => Promise, timeout: number, apiName: string): Promise { - 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 extends js.JSHandle { } 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 extends js.JSHandle { } hover(options: types.PointerActionOptions & types.PointerActionWaitOptions = {}): Promise { - 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 extends js.JSHandle { } click(options: types.MouseClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}): Promise { - 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 extends js.JSHandle { } dblclick(options: types.MouseMultiClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}): Promise { - 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 extends js.JSHandle { } async selectOption(values: string | ElementHandle | types.SelectOption | string[] | ElementHandle[] | types.SelectOption[] | null, options: types.NavigatingActionWaitOptions = {}): Promise { - 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 { @@ -444,10 +439,10 @@ export class ElementHandle extends js.JSHandle { } async fill(value: string, options: types.NavigatingActionWaitOptions = {}): Promise { - 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 extends js.JSHandle { } async selectText(options: types.TimeoutOptions = {}): Promise { - 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 extends js.JSHandle { 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 extends js.JSHandle { } async focus(): Promise { - 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 extends js.JSHandle { } async type(text: string, options: { delay?: number } & types.NavigatingActionWaitOptions = {}): Promise { - 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 extends js.JSHandle { } async press(key: string, options: { delay?: number } & types.NavigatingActionWaitOptions = {}): Promise { - 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 extends js.JSHandle { } 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 extends js.JSHandle { } async screenshot(options: types.ElementScreenshotOptions = {}): Promise { - 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 { diff --git a/src/frames.ts b/src/frames.ts index 126b4a27e1dcf..87120d8b655ba 100644 --- a/src/frames.ts +++ b/src/frames.ts @@ -334,20 +334,13 @@ export class Frame { this._parentFrame._childFrames.add(this); } - private _runAbortableTask(task: (progress: Progress) => Promise, timeout: number, apiName: string): Promise { - 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 { - 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 { - 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 { - 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 { @@ -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 { 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(selector: string, pageFunction: js.FuncOn, arg: Arg): Promise; @@ -543,9 +532,7 @@ export class Frame { } async setContent(html: string, options: types.NavigateOptions = {}): Promise { - 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) => Promise, apiName: string): Promise { 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { @@ -866,6 +853,8 @@ export class Frame { private _scheduleRerunnableTask(progress: Progress, world: types.World, task: SchedulableTask): Promise> { 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(frame: Frame, options: types.TimeoutOptions, apiName: string, task: (progress: Progress) => Promise): Promise { + 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); } diff --git a/src/network.ts b/src/network.ts index f764986a472e0..5db62a475025b 100644 --- a/src/network.ts +++ b/src/network.ts @@ -285,7 +285,7 @@ export class Response { return this._statusText; } - headers(): object { + headers(): types.Headers { return { ...this._headers }; } diff --git a/src/page.ts b/src/page.ts index 31ba7e5370a5f..a0ce65a7774b5 100644 --- a/src/page.ts +++ b/src/page.ts @@ -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; getFrameElement(frame: frames.Frame): Promise; 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; coverage?: () => any; + // Work around WebKit's raf issues on Windows. + rafCountForStablePosition(): number; // Work around Chrome's non-associated input and protocol. inputActionEpilogue(): Promise; // 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(task: (progress: Progress) => Promise, timeout: number, apiName: string): Promise { - 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(task: (progress: Progress) => Promise, timeout: number, apiName: string): Promise { + 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 { - 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 { diff --git a/test/waittask.spec.js b/test/waittask.spec.js index 5ef6d85f5a43f..7e511638df0b5 100644 --- a/test/waittask.spec.js +++ b/test/waittask.spec.js @@ -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); });