mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore: run most actions through page._runAbortableTask (#2721)
This introduces a single place for future snapshots.
This commit is contained in:
parent
bab6833232
commit
ab6a6c9b82
63
src/dom.ts
63
src/dom.ts
@ -27,7 +27,7 @@ import * as js from './javascript';
|
|||||||
import { Page } from './page';
|
import { Page } from './page';
|
||||||
import { selectors } from './selectors';
|
import { selectors } from './selectors';
|
||||||
import * as types from './types';
|
import * as types from './types';
|
||||||
import { Progress, ProgressController } from './progress';
|
import { Progress } from './progress';
|
||||||
import DebugScript from './debug/injected/debugScript';
|
import DebugScript from './debug/injected/debugScript';
|
||||||
import { FatalDOMError, RetargetableDOMError } from './common/domErrors';
|
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 => {});
|
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() {
|
async _initializePreview() {
|
||||||
const utility = await this._context.injectedScript();
|
const utility = await this._context.injectedScript();
|
||||||
this._preview = await utility.evaluate((injected, e) => 'JSHandle@' + injected.previewNode(e), this);
|
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 = {}) {
|
async scrollIntoViewIfNeeded(options: types.TimeoutOptions = {}) {
|
||||||
return this._runAbortableTask(
|
return this._page._runAbortableTask(
|
||||||
progress => this._waitAndScrollIntoViewIfNeeded(progress),
|
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'> {
|
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> {
|
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);
|
const result = await this._hover(progress, options);
|
||||||
return assertDone(throwRetargetableDOMError(result));
|
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'> {
|
_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> {
|
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);
|
const result = await this._click(progress, options);
|
||||||
return assertDone(throwRetargetableDOMError(result));
|
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'> {
|
_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> {
|
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);
|
const result = await this._dblclick(progress, options);
|
||||||
return assertDone(throwRetargetableDOMError(result));
|
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'> {
|
_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[]> {
|
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);
|
const result = await this._selectOption(progress, values, options);
|
||||||
return throwRetargetableDOMError(result);
|
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'> {
|
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> {
|
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);
|
const result = await this._fill(progress, value, options);
|
||||||
assertDone(throwRetargetableDOMError(result));
|
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'> {
|
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> {
|
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.
|
progress.throwIfAborted(); // Avoid action that has side-effects.
|
||||||
const poll = await this._evaluateHandleInUtility(([injected, node]) => {
|
const poll = await this._evaluateHandleInUtility(([injected, node]) => {
|
||||||
return injected.waitForVisibleAndSelectText(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 pollHandler = new InjectedScriptPollHandler(progress, poll);
|
||||||
const result = throwFatalDOMError(await pollHandler.finish());
|
const result = throwFatalDOMError(await pollHandler.finish());
|
||||||
assertDone(throwRetargetableDOMError(result));
|
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 = {}) {
|
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);
|
const result = await this._setInputFiles(progress, files, options);
|
||||||
return assertDone(throwRetargetableDOMError(result));
|
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'> {
|
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> {
|
async focus(): Promise<void> {
|
||||||
return this._runAbortableTask(async progress => {
|
return this._page._runAbortableTask(async progress => {
|
||||||
const result = await this._focus(progress);
|
const result = await this._focus(progress);
|
||||||
return assertDone(throwRetargetableDOMError(result));
|
return assertDone(throwRetargetableDOMError(result));
|
||||||
}, 0, 'focus');
|
}, 0, 'elementHandle.focus');
|
||||||
}
|
}
|
||||||
|
|
||||||
async _focus(progress: Progress): Promise<'error:notconnected' | 'done'> {
|
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> {
|
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);
|
const result = await this._type(progress, text, options);
|
||||||
return assertDone(throwRetargetableDOMError(result));
|
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'> {
|
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> {
|
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);
|
const result = await this._press(progress, key, options);
|
||||||
return assertDone(throwRetargetableDOMError(result));
|
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'> {
|
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 = {}) {
|
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);
|
const result = await this._setChecked(progress, true, options);
|
||||||
return assertDone(throwRetargetableDOMError(result));
|
return assertDone(throwRetargetableDOMError(result));
|
||||||
}, this._page._timeoutSettings.timeout(options), 'check');
|
}, this._page._timeoutSettings.timeout(options), 'elementHandle.check');
|
||||||
}
|
}
|
||||||
|
|
||||||
async uncheck(options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) {
|
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);
|
const result = await this._setChecked(progress, false, options);
|
||||||
return assertDone(throwRetargetableDOMError(result));
|
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'> {
|
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> {
|
async screenshot(options: types.ElementScreenshotOptions = {}): Promise<Buffer> {
|
||||||
return this._runAbortableTask(
|
return this._page._runAbortableTask(
|
||||||
progress => this._page._screenshotter.screenshotElement(progress, this, options),
|
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> {
|
async $(selector: string): Promise<ElementHandle | null> {
|
||||||
|
|||||||
@ -334,20 +334,13 @@ export class Frame {
|
|||||||
this._parentFrame._childFrames.add(this);
|
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) {
|
private _apiName(method: string) {
|
||||||
const subject = this._page._callingPageAPI ? 'page' : 'frame';
|
const subject = this._page._callingPageAPI ? 'page' : 'frame';
|
||||||
return `${subject}.${method}`;
|
return `${subject}.${method}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async goto(url: string, options: types.GotoOptions = {}): Promise<network.Response | null> {
|
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'));
|
return runNavigationTask(this, options, this._apiName('goto'), async progress => {
|
||||||
abortProgressOnFrameDetach(progressController, this);
|
|
||||||
return progressController.run(async progress => {
|
|
||||||
progress.logger.info(`navigating to "${url}", waiting until "${options.waitUntil || 'load'}"`);
|
progress.logger.info(`navigating to "${url}", waiting until "${options.waitUntil || 'load'}"`);
|
||||||
const headers = (this._page._state.extraHTTPHeaders || {});
|
const headers = (this._page._state.extraHTTPHeaders || {});
|
||||||
let referer = headers['referer'] || headers['Referer'];
|
let referer = headers['referer'] || headers['Referer'];
|
||||||
@ -380,9 +373,7 @@ export class Frame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async waitForNavigation(options: types.WaitForNavigationOptions = {}): Promise<network.Response | null> {
|
async waitForNavigation(options: types.WaitForNavigationOptions = {}): Promise<network.Response | null> {
|
||||||
const progressController = new ProgressController(this._page._logger, this._page._timeoutSettings.navigationTimeout(options), this._apiName('waitForNavigation'));
|
return runNavigationTask(this, options, this._apiName('waitForNavigation'), async progress => {
|
||||||
abortProgressOnFrameDetach(progressController, this);
|
|
||||||
return progressController.run(async progress => {
|
|
||||||
const toUrl = typeof options.url === 'string' ? ` to "${options.url}"` : '';
|
const toUrl = typeof options.url === 'string' ? ` to "${options.url}"` : '';
|
||||||
progress.logger.info(`waiting for navigation${toUrl} until "${options.waitUntil || 'load'}"`);
|
progress.logger.info(`waiting for navigation${toUrl} until "${options.waitUntil || 'load'}"`);
|
||||||
const frameTask = new FrameTask(this, progress);
|
const frameTask = new FrameTask(this, progress);
|
||||||
@ -399,9 +390,7 @@ export class Frame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async waitForLoadState(state: types.LifecycleEvent = 'load', options: types.TimeoutOptions = {}): Promise<void> {
|
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'));
|
return runNavigationTask(this, options, this._apiName('waitForLoadState'), progress => this._waitForLoadState(progress, state));
|
||||||
abortProgressOnFrameDetach(progressController, this);
|
|
||||||
return progressController.run(progress => this._waitForLoadState(progress, state));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async _waitForLoadState(progress: Progress, state: types.LifecycleEvent): Promise<void> {
|
async _waitForLoadState(progress: Progress, state: types.LifecycleEvent): Promise<void> {
|
||||||
@ -468,7 +457,7 @@ export class Frame {
|
|||||||
throw new Error(`Unsupported state option "${state}"`);
|
throw new Error(`Unsupported state option "${state}"`);
|
||||||
const info = selectors._parseSelector(selector);
|
const info = selectors._parseSelector(selector);
|
||||||
const task = selectors._waitForSelectorTask(info, state);
|
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}`);
|
progress.logger.info(`waiting for selector "${selector}"${state === 'attached' ? '' : ' to be ' + state}`);
|
||||||
const result = await this._scheduleRerunnableTask(progress, info.world, task);
|
const result = await this._scheduleRerunnableTask(progress, info.world, task);
|
||||||
if (!result.asElement()) {
|
if (!result.asElement()) {
|
||||||
@ -483,17 +472,17 @@ export class Frame {
|
|||||||
return adopted;
|
return adopted;
|
||||||
}
|
}
|
||||||
return handle;
|
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> {
|
async dispatchEvent(selector: string, type: string, eventInit?: Object, options: types.TimeoutOptions = {}): Promise<void> {
|
||||||
const info = selectors._parseSelector(selector);
|
const info = selectors._parseSelector(selector);
|
||||||
const task = selectors._dispatchEventTask(info, type, eventInit || {});
|
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}"...`);
|
progress.logger.info(`Dispatching "${type}" event on selector "${selector}"...`);
|
||||||
const result = await this._scheduleRerunnableTask(progress, 'main', task);
|
const result = await this._scheduleRerunnableTask(progress, 'main', task);
|
||||||
result.dispose();
|
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>;
|
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> {
|
async setContent(html: string, options: types.NavigateOptions = {}): Promise<void> {
|
||||||
const progressController = new ProgressController(this._page._logger, this._page._timeoutSettings.navigationTimeout(options), this._apiName('setContent'));
|
return runNavigationTask(this, options, this._apiName('setContent'), async progress => {
|
||||||
abortProgressOnFrameDetach(progressController, this);
|
|
||||||
return progressController.run(async progress => {
|
|
||||||
const waitUntil = options.waitUntil === undefined ? 'load' : options.waitUntil;
|
const waitUntil = options.waitUntil === undefined ? 'load' : options.waitUntil;
|
||||||
progress.logger.info(`setting frame content, waiting until "${waitUntil}"`);
|
progress.logger.info(`setting frame content, waiting until "${waitUntil}"`);
|
||||||
const tag = `--playwright--set--content--${this._id}--${++this._setContentCounter}--`;
|
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'>,
|
action: (progress: Progress, handle: dom.ElementHandle<Element>) => Promise<R | 'error:notconnected'>,
|
||||||
apiName: string): Promise<R> {
|
apiName: string): Promise<R> {
|
||||||
const info = selectors._parseSelector(selector);
|
const info = selectors._parseSelector(selector);
|
||||||
return this._runAbortableTask(async progress => {
|
return this._page._runAbortableTask(async progress => {
|
||||||
while (progress.isRunning()) {
|
while (progress.isRunning()) {
|
||||||
progress.logger.info(`waiting for selector "${selector}"`);
|
progress.logger.info(`waiting for selector "${selector}"`);
|
||||||
const task = selectors._waitForSelectorTask(info, 'attached');
|
const task = selectors._waitForSelectorTask(info, 'attached');
|
||||||
@ -753,63 +740,63 @@ export class Frame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async click(selector: string, options: types.MouseClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) {
|
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 = {}) {
|
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 = {}) {
|
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 = {}) {
|
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> {
|
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> {
|
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> {
|
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> {
|
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 = {}) {
|
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[]> {
|
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> {
|
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 = {}) {
|
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 = {}) {
|
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 = {}) {
|
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 = {}) {
|
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) {
|
async waitForTimeout(timeout: number) {
|
||||||
@ -841,9 +828,9 @@ export class Frame {
|
|||||||
return injectedScript.pollInterval(polling, (progress, continuePolling) => innerPredicate(arg) || continuePolling);
|
return injectedScript.pollInterval(polling, (progress, continuePolling) => innerPredicate(arg) || continuePolling);
|
||||||
}, { injectedScript, predicateBody, polling, arg });
|
}, { injectedScript, predicateBody, polling, arg });
|
||||||
};
|
};
|
||||||
return this._runAbortableTask(
|
return this._page._runAbortableTask(
|
||||||
progress => this._scheduleRerunnableTask(progress, 'main', task),
|
progress => this._scheduleRerunnableTask(progress, 'main', task),
|
||||||
this._page._timeoutSettings.timeout(options), 'waitForFunction');
|
this._page._timeoutSettings.timeout(options), this._apiName('waitForFunction'));
|
||||||
}
|
}
|
||||||
|
|
||||||
async title(): Promise<string> {
|
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>> {
|
private _scheduleRerunnableTask<T>(progress: Progress, world: types.World, task: SchedulableTask<T>): Promise<js.SmartHandle<T>> {
|
||||||
const data = this._contextData.get(world)!;
|
const data = this._contextData.get(world)!;
|
||||||
const rerunnableTask = new RerunnableTask(data, progress, task);
|
const rerunnableTask = new RerunnableTask(data, progress, task);
|
||||||
|
if (this._detached)
|
||||||
|
rerunnableTask.terminate(new Error('waitForFunction failed: frame got detached.'));
|
||||||
if (data.context)
|
if (data.context)
|
||||||
rerunnableTask.rerun(data.context);
|
rerunnableTask.rerun(data.context);
|
||||||
return rerunnableTask.promise;
|
return rerunnableTask.promise;
|
||||||
@ -1114,8 +1103,11 @@ class FrameTask {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function abortProgressOnFrameDetach(controller: ProgressController, frame: Frame) {
|
async function runNavigationTask<T>(frame: Frame, options: types.TimeoutOptions, apiName: string, task: (progress: Progress) => Promise<T>): Promise<T> {
|
||||||
frame._page._disconnectedPromise.then(() => controller.abort(new Error('Navigation failed because page was closed!')));
|
const page = frame._page;
|
||||||
frame._page._crashedPromise.then(() => controller.abort(new Error('Navigation failed because page crashed!')));
|
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!')));
|
frame._detachedPromise.then(() => controller.abort(new Error('Navigating frame was detached!')));
|
||||||
|
return controller.run(task);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -285,7 +285,7 @@ export class Response {
|
|||||||
return this._statusText;
|
return this._statusText;
|
||||||
}
|
}
|
||||||
|
|
||||||
headers(): object {
|
headers(): types.Headers {
|
||||||
return { ...this._headers };
|
return { ...this._headers };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
21
src/page.ts
21
src/page.ts
@ -31,7 +31,7 @@ import * as accessibility from './accessibility';
|
|||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import { FileChooser } from './fileChooser';
|
import { FileChooser } from './fileChooser';
|
||||||
import { logError, Logger } from './logger';
|
import { logError, Logger } from './logger';
|
||||||
import { ProgressController, Progress } from './progress';
|
import { ProgressController, Progress, runAbortableTask } from './progress';
|
||||||
|
|
||||||
export interface PageDelegate {
|
export interface PageDelegate {
|
||||||
readonly rawMouse: input.RawMouse;
|
readonly rawMouse: input.RawMouse;
|
||||||
@ -68,12 +68,13 @@ export interface PageDelegate {
|
|||||||
getBoundingBox(handle: dom.ElementHandle): Promise<types.Rect | null>;
|
getBoundingBox(handle: dom.ElementHandle): Promise<types.Rect | null>;
|
||||||
getFrameElement(frame: frames.Frame): Promise<dom.ElementHandle>;
|
getFrameElement(frame: frames.Frame): Promise<dom.ElementHandle>;
|
||||||
scrollRectIntoViewIfNeeded(handle: dom.ElementHandle, rect?: types.Rect): Promise<'error:notvisible' | 'error:notconnected' | 'done'>;
|
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}>;
|
getAccessibilityTree(needle?: dom.ElementHandle): Promise<{tree: accessibility.AXNode, needle: accessibility.AXNode | null}>;
|
||||||
pdf?: (options?: types.PDFOptions) => Promise<Buffer>;
|
pdf?: (options?: types.PDFOptions) => Promise<Buffer>;
|
||||||
coverage?: () => any;
|
coverage?: () => any;
|
||||||
|
|
||||||
|
// Work around WebKit's raf issues on Windows.
|
||||||
|
rafCountForStablePosition(): number;
|
||||||
// Work around Chrome's non-associated input and protocol.
|
// Work around Chrome's non-associated input and protocol.
|
||||||
inputActionEpilogue(): Promise<void>;
|
inputActionEpilogue(): Promise<void>;
|
||||||
// Work around for asynchronously dispatched CSP errors in Firefox.
|
// 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;
|
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() {
|
_didClose() {
|
||||||
assert(!this._closed, 'Page closed twice');
|
assert(!this._closed, 'Page closed twice');
|
||||||
this._closed = true;
|
this._closed = true;
|
||||||
@ -166,6 +162,12 @@ export class Page extends EventEmitter {
|
|||||||
this._disconnectedCallback(new Error('Page closed'));
|
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) {
|
async _onFileChooserOpened(handle: dom.ElementHandle) {
|
||||||
const multiple = await handle.evaluate(element => !!(element as HTMLInputElement).multiple);
|
const multiple = await handle.evaluate(element => !!(element as HTMLInputElement).multiple);
|
||||||
if (!this.listenerCount(Events.Page.FileChooser)) {
|
if (!this.listenerCount(Events.Page.FileChooser)) {
|
||||||
@ -448,8 +450,9 @@ export class Page extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async screenshot(options: types.ScreenshotOptions = {}): Promise<Buffer> {
|
async screenshot(options: types.ScreenshotOptions = {}): Promise<Buffer> {
|
||||||
const controller = new ProgressController(this._logger, this._timeoutSettings.timeout(options), 'page.screenshot');
|
return this._runAbortableTask(
|
||||||
return controller.run(progress => this._screenshotter.screenshotPage(progress, options));
|
progress => this._screenshotter.screenshotPage(progress, options),
|
||||||
|
this._timeoutSettings.timeout(options), 'page.screenshot');
|
||||||
}
|
}
|
||||||
|
|
||||||
async title(): Promise<string> {
|
async title(): Promise<string> {
|
||||||
|
|||||||
@ -431,9 +431,9 @@ describe('Frame.waitForSelector', function() {
|
|||||||
});
|
});
|
||||||
it('should respect timeout', async({page, server}) => {
|
it('should respect timeout', async({page, server}) => {
|
||||||
let error = null;
|
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).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.message).toContain('waiting for selector "div"');
|
||||||
expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
|
expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
|
||||||
});
|
});
|
||||||
@ -521,9 +521,9 @@ describe('Frame.waitForSelector xpath', function() {
|
|||||||
});
|
});
|
||||||
it('should respect timeout', async({page}) => {
|
it('should respect timeout', async({page}) => {
|
||||||
let error = null;
|
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).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.message).toContain('waiting for selector "//div"');
|
||||||
expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
|
expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user