chore: migrate most actions to Progress (#2439)
This commit is contained in:
parent
abfd278461
commit
f188b0a174
133
src/dom.ts
133
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 { NotConnectedError, TimeoutError } from './errors';
|
||||
import { NotConnectedError } from './errors';
|
||||
import { Log, logError } from './logger';
|
||||
import { Progress } from './progress';
|
||||
|
||||
|
|
@ -240,43 +240,41 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||
};
|
||||
}
|
||||
|
||||
async _retryPointerAction(actionName: string, action: (point: types.Point) => Promise<void>, options: PointerActionOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}): Promise<void> {
|
||||
this._page._log(inputLog, `elementHandle.${actionName}()`);
|
||||
const deadline = this._page._timeoutSettings.computeDeadline(options);
|
||||
while (!helper.isPastDeadline(deadline)) {
|
||||
const result = await this._performPointerAction(actionName, action, deadline, options);
|
||||
async _retryPointerAction(progress: Progress, action: (point: types.Point) => Promise<void>, options: PointerActionOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<void> {
|
||||
progress.log(inputLog, progress.apiName);
|
||||
while (!progress.isCanceled()) {
|
||||
const result = await this._performPointerAction(progress, action, options);
|
||||
if (result === 'done')
|
||||
return;
|
||||
}
|
||||
throw new TimeoutError(`waiting for element to receive pointer events failed: timeout exceeded. Re-run with the DEBUG=pw:input env variable to see the debug log.`);
|
||||
}
|
||||
|
||||
async _performPointerAction(actionName: string, action: (point: types.Point) => Promise<void>, deadline: number, options: PointerActionOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}): Promise<'done' | 'retry'> {
|
||||
async _performPointerAction(progress: Progress, action: (point: types.Point) => Promise<void>, options: PointerActionOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<'done' | 'retry'> {
|
||||
const { force = false, position } = options;
|
||||
if (!force)
|
||||
await this._waitForDisplayedAtStablePositionAndEnabled(deadline);
|
||||
await this._waitForDisplayedAtStablePositionAndEnabled(progress);
|
||||
|
||||
this._page._log(inputLog, 'scrolling into view if needed...');
|
||||
progress.log(inputLog, 'scrolling into view if needed...');
|
||||
const scrolled = await this._scrollRectIntoViewIfNeeded(position ? { x: position.x, y: position.y, width: 0, height: 0 } : undefined);
|
||||
if (scrolled === 'invisible') {
|
||||
if (force)
|
||||
throw new Error('Element is not visible');
|
||||
this._page._log(inputLog, '...element is not visible, retrying input action');
|
||||
progress.log(inputLog, '...element is not visible, retrying input action');
|
||||
return 'retry';
|
||||
}
|
||||
this._page._log(inputLog, '...done scrolling');
|
||||
progress.log(inputLog, '...done scrolling');
|
||||
|
||||
const maybePoint = position ? await this._offsetPoint(position) : await this._clickablePoint();
|
||||
if (maybePoint === 'invisible') {
|
||||
if (force)
|
||||
throw new Error('Element is not visible');
|
||||
this._page._log(inputLog, 'element is not visibile, retrying input action');
|
||||
progress.log(inputLog, 'element is not visibile, retrying input action');
|
||||
return 'retry';
|
||||
}
|
||||
if (maybePoint === 'outsideviewport') {
|
||||
if (force)
|
||||
throw new Error('Element is outside of the viewport');
|
||||
this._page._log(inputLog, 'element is outside of the viewport, retrying input action');
|
||||
progress.log(inputLog, 'element is outside of the viewport, retrying input action');
|
||||
return 'retry';
|
||||
}
|
||||
const point = roundPoint(maybePoint);
|
||||
|
|
@ -284,41 +282,53 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||
if (!force) {
|
||||
if ((options as any).__testHookBeforeHitTarget)
|
||||
await (options as any).__testHookBeforeHitTarget();
|
||||
this._page._log(inputLog, `checking that element receives pointer events at (${point.x},${point.y})...`);
|
||||
progress.log(inputLog, `checking that element receives pointer events at (${point.x},${point.y})...`);
|
||||
const matchesHitTarget = await this._checkHitTargetAt(point);
|
||||
if (!matchesHitTarget) {
|
||||
this._page._log(inputLog, '...element does not receive pointer events, retrying input action');
|
||||
progress.log(inputLog, '...element does not receive pointer events, retrying input action');
|
||||
return 'retry';
|
||||
}
|
||||
this._page._log(inputLog, `...element does receive pointer events, continuing input action`);
|
||||
progress.log(inputLog, `...element does receive pointer events, continuing input action`);
|
||||
}
|
||||
|
||||
await this._page._frameManager.waitForSignalsCreatedBy(async () => {
|
||||
let restoreModifiers: input.Modifier[] | undefined;
|
||||
if (options && options.modifiers)
|
||||
restoreModifiers = await this._page.keyboard._ensureModifiers(options.modifiers);
|
||||
this._page._log(inputLog, `performing "${actionName}" action...`);
|
||||
progress.log(inputLog, `performing ${progress.apiName} action...`);
|
||||
await action(point);
|
||||
this._page._log(inputLog, `... "${actionName}" action done`);
|
||||
this._page._log(inputLog, 'waiting for scheduled navigations to finish...');
|
||||
progress.log(inputLog, `...${progress.apiName} action done`);
|
||||
progress.log(inputLog, 'waiting for scheduled navigations to finish...');
|
||||
if (restoreModifiers)
|
||||
await this._page.keyboard._ensureModifiers(restoreModifiers);
|
||||
}, deadline, options, true);
|
||||
this._page._log(inputLog, '...navigations have finished');
|
||||
}, progress.deadline, options, true);
|
||||
progress.log(inputLog, '...navigations have finished');
|
||||
|
||||
return 'done';
|
||||
}
|
||||
|
||||
hover(options?: PointerActionOptions & types.PointerActionWaitOptions): Promise<void> {
|
||||
return this._retryPointerAction('hover', point => this._page.mouse.move(point.x, point.y), options);
|
||||
hover(options: PointerActionOptions & types.PointerActionWaitOptions = {}): Promise<void> {
|
||||
return Progress.runCancelableTask(progress => this._hover(progress, options), options, this._page, this._page._timeoutSettings);
|
||||
}
|
||||
|
||||
click(options?: ClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<void> {
|
||||
return this._retryPointerAction('click', point => this._page.mouse.click(point.x, point.y, options), options);
|
||||
_hover(progress: Progress, options: PointerActionOptions & types.PointerActionWaitOptions): Promise<void> {
|
||||
return this._retryPointerAction(progress, point => this._page.mouse.move(point.x, point.y), options);
|
||||
}
|
||||
|
||||
dblclick(options?: MultiClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<void> {
|
||||
return this._retryPointerAction('dblclick', point => this._page.mouse.dblclick(point.x, point.y, options), options);
|
||||
click(options: ClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}): Promise<void> {
|
||||
return Progress.runCancelableTask(progress => this._click(progress, options), options, this._page, this._page._timeoutSettings);
|
||||
}
|
||||
|
||||
_click(progress: Progress, options: ClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<void> {
|
||||
return this._retryPointerAction(progress, point => this._page.mouse.click(point.x, point.y, options), options);
|
||||
}
|
||||
|
||||
dblclick(options: MultiClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}): Promise<void> {
|
||||
return Progress.runCancelableTask(progress => this._dblclick(progress, options), options, this._page, this._page._timeoutSettings);
|
||||
}
|
||||
|
||||
_dblclick(progress: Progress, options: MultiClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<void> {
|
||||
return this._retryPointerAction(progress, point => this._page.mouse.dblclick(point.x, point.y, options), options);
|
||||
}
|
||||
|
||||
async selectOption(values: string | ElementHandle | types.SelectOption | string[] | ElementHandle[] | types.SelectOption[], options?: types.NavigatingActionWaitOptions): Promise<string[]> {
|
||||
|
|
@ -346,28 +356,27 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||
}, deadline, options);
|
||||
}
|
||||
|
||||
async fill(value: string, options?: types.NavigatingActionWaitOptions): Promise<void> {
|
||||
this._page._log(inputLog, `elementHandle.fill(${value})`);
|
||||
async fill(value: string, options: types.NavigatingActionWaitOptions = {}): Promise<void> {
|
||||
return Progress.runCancelableTask(progress => this._fill(progress, value, options), options, this._page, this._page._timeoutSettings);
|
||||
}
|
||||
|
||||
async _fill(progress: Progress, value: string, options: types.NavigatingActionWaitOptions): Promise<void> {
|
||||
progress.log(inputLog, `elementHandle.fill("${value}")`);
|
||||
assert(helper.isString(value), 'Value must be string. Found value "' + value + '" of type "' + (typeof value) + '"');
|
||||
const deadline = this._page._timeoutSettings.computeDeadline(options);
|
||||
await this._page._frameManager.waitForSignalsCreatedBy(async () => {
|
||||
const poll = await this._evaluateHandleInUtility(({ injected, node }, { value }) => {
|
||||
return injected.waitForEnabledAndFill(node, value);
|
||||
}, { value });
|
||||
try {
|
||||
const filledPromise = poll.evaluate(poll => poll.result);
|
||||
const injectedResult = await helper.waitWithDeadline(filledPromise, 'element to be visible and enabled', deadline, 'pw:input');
|
||||
const needsInput = handleInjectedResult(injectedResult);
|
||||
if (needsInput) {
|
||||
if (value)
|
||||
await this._page.keyboard.insertText(value);
|
||||
else
|
||||
await this._page.keyboard.press('Delete');
|
||||
}
|
||||
} finally {
|
||||
poll.evaluate(poll => poll.cancel()).catch(e => {}).then(() => poll.dispose());
|
||||
new InjectedScriptPollHandler(progress, poll);
|
||||
const injectedResult = await poll.evaluate(poll => poll.result);
|
||||
const needsInput = handleInjectedResult(injectedResult);
|
||||
if (needsInput) {
|
||||
if (value)
|
||||
await this._page.keyboard.insertText(value);
|
||||
else
|
||||
await this._page.keyboard.press('Delete');
|
||||
}
|
||||
}, deadline, options, true);
|
||||
}, progress.deadline, options, true);
|
||||
}
|
||||
|
||||
async selectText(): Promise<void> {
|
||||
|
|
@ -436,20 +445,24 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||
}, deadline, options, true);
|
||||
}
|
||||
|
||||
async check(options?: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions) {
|
||||
this._page._log(inputLog, `elementHandle.check()`);
|
||||
await this._setChecked(true, options);
|
||||
async check(options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) {
|
||||
return Progress.runCancelableTask(async progress => {
|
||||
progress.log(inputLog, `elementHandle.check()`);
|
||||
await this._setChecked(progress, true, options);
|
||||
}, options, this._page, this._page._timeoutSettings);
|
||||
}
|
||||
|
||||
async uncheck(options?: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions) {
|
||||
this._page._log(inputLog, `elementHandle.uncheck()`);
|
||||
await this._setChecked(false, options);
|
||||
async uncheck(options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) {
|
||||
return Progress.runCancelableTask(async progress => {
|
||||
progress.log(inputLog, `elementHandle.uncheck()`);
|
||||
await this._setChecked(progress, false, options);
|
||||
}, options, this._page, this._page._timeoutSettings);
|
||||
}
|
||||
|
||||
private async _setChecked(state: boolean, options?: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions) {
|
||||
async _setChecked(progress: Progress, state: boolean, options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions) {
|
||||
if (await this._evaluateInUtility(({ injected, node }) => injected.isCheckboxChecked(node), {}) === state)
|
||||
return;
|
||||
await this.click(options);
|
||||
await this._click(progress, options);
|
||||
if (await this._evaluateInUtility(({ injected, node }) => injected.isCheckboxChecked(node), {}) !== state)
|
||||
throw new Error('Unable to click checkbox');
|
||||
}
|
||||
|
|
@ -490,20 +503,16 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||
return result;
|
||||
}
|
||||
|
||||
async _waitForDisplayedAtStablePositionAndEnabled(deadline: number): Promise<void> {
|
||||
this._page._log(inputLog, 'waiting for element to be displayed, enabled and not moving...');
|
||||
async _waitForDisplayedAtStablePositionAndEnabled(progress: Progress): Promise<void> {
|
||||
progress.log(inputLog, 'waiting for element to be displayed, enabled and not moving...');
|
||||
const rafCount = this._page._delegate.rafCountForStablePosition();
|
||||
const poll = await this._evaluateHandleInUtility(({ injected, node }, { rafCount }) => {
|
||||
return injected.waitForDisplayedAtStablePositionAndEnabled(node, rafCount);
|
||||
}, { rafCount });
|
||||
try {
|
||||
const stablePromise = poll.evaluate(poll => poll.result);
|
||||
const injectedResult = await helper.waitWithDeadline(stablePromise, 'element to be displayed and not moving', deadline, 'pw:input');
|
||||
handleInjectedResult(injectedResult);
|
||||
} finally {
|
||||
poll.evaluate(poll => poll.cancel()).catch(e => {}).then(() => poll.dispose());
|
||||
}
|
||||
this._page._log(inputLog, '...element is displayed and does not move');
|
||||
new InjectedScriptPollHandler(progress, poll);
|
||||
const injectedResult = await poll.evaluate(poll => poll.result);
|
||||
handleInjectedResult(injectedResult);
|
||||
progress.log(inputLog, '...element is displayed and does not move');
|
||||
}
|
||||
|
||||
async _checkHitTargetAt(point: types.Point): Promise<boolean> {
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@ export class FrameManager {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: take progress parameter.
|
||||
async waitForSignalsCreatedBy<T>(action: () => Promise<T>, deadline: number, options: types.NavigatingActionWaitOptions = {}, input?: boolean): Promise<T> {
|
||||
if (options.noWaitAfter)
|
||||
return action();
|
||||
|
|
@ -697,107 +698,94 @@ export class Frame {
|
|||
}
|
||||
|
||||
private async _retryWithSelectorIfNotConnected<R>(
|
||||
actionName: string,
|
||||
selector: string, options: types.TimeoutOptions,
|
||||
action: (handle: dom.ElementHandle<Element>, deadline: number) => Promise<R>): Promise<R> {
|
||||
const deadline = this._page._timeoutSettings.computeDeadline(options);
|
||||
this._page._log(dom.inputLog, `(page|frame).${actionName}("${selector}")`);
|
||||
while (!helper.isPastDeadline(deadline)) {
|
||||
try {
|
||||
const { world, task } = selectors._waitForSelectorTask(selector, 'attached');
|
||||
this._page._log(dom.inputLog, `waiting for the selector "${selector}"`);
|
||||
const handle = await Progress.runCancelableTask(
|
||||
progress => this._scheduleRerunnableTask(progress, world, task),
|
||||
options, this._page, this._page._timeoutSettings);
|
||||
this._page._log(dom.inputLog, `...got element for the selector`);
|
||||
const element = handle.asElement() as dom.ElementHandle<Element>;
|
||||
action: (progress: Progress, handle: dom.ElementHandle<Element>) => Promise<R>): Promise<R> {
|
||||
return Progress.runCancelableTask(async progress => {
|
||||
progress.log(dom.inputLog, `${progress.apiName}("${selector}")`);
|
||||
while (!progress.isCanceled()) {
|
||||
try {
|
||||
return await action(element, deadline);
|
||||
} finally {
|
||||
const { world, task } = selectors._waitForSelectorTask(selector, 'attached');
|
||||
progress.log(dom.inputLog, `waiting for the selector "${selector}"`);
|
||||
const handle = await this._scheduleRerunnableTask(progress, world, task);
|
||||
progress.log(dom.inputLog, `...got element for the selector`);
|
||||
const element = handle.asElement() as dom.ElementHandle<Element>;
|
||||
progress.cleanupWhenCanceled(() => element.dispose());
|
||||
const result = await action(progress, element);
|
||||
element.dispose();
|
||||
return result;
|
||||
} catch (e) {
|
||||
if (!(e instanceof NotConnectedError))
|
||||
throw e;
|
||||
progress.log(dom.inputLog, 'element was detached from the DOM, retrying');
|
||||
}
|
||||
} catch (e) {
|
||||
if (!(e instanceof NotConnectedError))
|
||||
throw e;
|
||||
this._page._log(dom.inputLog, 'Element was detached from the DOM, retrying');
|
||||
}
|
||||
}
|
||||
throw new TimeoutError(`waiting for selector "${selector}" failed: timeout exceeded. Re-run with the DEBUG=pw:input env variable to see the debug log.`);
|
||||
return undefined as any;
|
||||
}, options, this._page, this._page._timeoutSettings);
|
||||
}
|
||||
|
||||
async click(selector: string, options: dom.ClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) {
|
||||
await this._retryWithSelectorIfNotConnected('click', selector, options,
|
||||
(handle, deadline) => handle.click(helper.optionsWithUpdatedTimeout(options, deadline)));
|
||||
await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle._click(progress, options));
|
||||
}
|
||||
|
||||
async dblclick(selector: string, options: dom.MultiClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) {
|
||||
await this._retryWithSelectorIfNotConnected('dblclick', selector, options,
|
||||
(handle, deadline) => handle.dblclick(helper.optionsWithUpdatedTimeout(options, deadline)));
|
||||
await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle._dblclick(progress, options));
|
||||
}
|
||||
|
||||
async fill(selector: string, value: string, options: types.NavigatingActionWaitOptions = {}) {
|
||||
await this._retryWithSelectorIfNotConnected('fill', selector, options,
|
||||
(handle, deadline) => handle.fill(value, helper.optionsWithUpdatedTimeout(options, deadline)));
|
||||
await this._retryWithSelectorIfNotConnected(selector, options,
|
||||
(progress, handle) => handle._fill(progress, value, options));
|
||||
}
|
||||
|
||||
async focus(selector: string, options: types.TimeoutOptions = {}) {
|
||||
await this._retryWithSelectorIfNotConnected('focus', selector, options,
|
||||
(handle, deadline) => handle.focus());
|
||||
await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle.focus());
|
||||
}
|
||||
|
||||
async textContent(selector: string, options: types.TimeoutOptions = {}): Promise<null|string> {
|
||||
return await this._retryWithSelectorIfNotConnected('textContent', selector, options,
|
||||
(handle, deadline) => handle.textContent());
|
||||
return await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle.textContent());
|
||||
}
|
||||
|
||||
async innerText(selector: string, options: types.TimeoutOptions = {}): Promise<string> {
|
||||
return await this._retryWithSelectorIfNotConnected('innerText', selector, options,
|
||||
(handle, deadline) => handle.innerText());
|
||||
return await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle.innerText());
|
||||
}
|
||||
|
||||
async innerHTML(selector: string, options: types.TimeoutOptions = {}): Promise<string> {
|
||||
return await this._retryWithSelectorIfNotConnected('innerHTML', selector, options,
|
||||
(handle, deadline) => handle.innerHTML());
|
||||
return await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle.innerHTML());
|
||||
}
|
||||
|
||||
async getAttribute(selector: string, name: string, options: types.TimeoutOptions = {}): Promise<string | null> {
|
||||
return await this._retryWithSelectorIfNotConnected('getAttribute', selector, options,
|
||||
(handle, deadline) => handle.getAttribute(name) as Promise<string>);
|
||||
return await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle.getAttribute(name));
|
||||
}
|
||||
|
||||
async hover(selector: string, options: dom.PointerActionOptions & types.PointerActionWaitOptions = {}) {
|
||||
await this._retryWithSelectorIfNotConnected('hover', selector, options,
|
||||
(handle, deadline) => handle.hover(helper.optionsWithUpdatedTimeout(options, deadline)));
|
||||
await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle._hover(progress, options));
|
||||
}
|
||||
|
||||
async selectOption(selector: string, values: string | dom.ElementHandle | types.SelectOption | string[] | dom.ElementHandle[] | types.SelectOption[], options: types.NavigatingActionWaitOptions = {}): Promise<string[]> {
|
||||
return await this._retryWithSelectorIfNotConnected('selectOption', selector, options,
|
||||
(handle, deadline) => handle.selectOption(values, helper.optionsWithUpdatedTimeout(options, deadline)));
|
||||
return await this._retryWithSelectorIfNotConnected(selector, options,
|
||||
(progress, handle) => handle.selectOption(values, helper.optionsWithUpdatedTimeout(options, progress.deadline)));
|
||||
}
|
||||
|
||||
async setInputFiles(selector: string, files: string | types.FilePayload | string[] | types.FilePayload[], options: types.NavigatingActionWaitOptions = {}): Promise<void> {
|
||||
await this._retryWithSelectorIfNotConnected('setInputFiles', selector, options,
|
||||
(handle, deadline) => handle.setInputFiles(files, helper.optionsWithUpdatedTimeout(options, deadline)));
|
||||
await this._retryWithSelectorIfNotConnected(selector, options,
|
||||
(progress, handle) => handle.setInputFiles(files, helper.optionsWithUpdatedTimeout(options, progress.deadline)));
|
||||
}
|
||||
|
||||
async type(selector: string, text: string, options: { delay?: number } & types.NavigatingActionWaitOptions = {}) {
|
||||
await this._retryWithSelectorIfNotConnected('type', selector, options,
|
||||
(handle, deadline) => handle.type(text, helper.optionsWithUpdatedTimeout(options, deadline)));
|
||||
await this._retryWithSelectorIfNotConnected(selector, options,
|
||||
(progress, handle) => handle.type(text, helper.optionsWithUpdatedTimeout(options, progress.deadline)));
|
||||
}
|
||||
|
||||
async press(selector: string, key: string, options: { delay?: number } & types.NavigatingActionWaitOptions = {}) {
|
||||
await this._retryWithSelectorIfNotConnected('press', selector, options,
|
||||
(handle, deadline) => handle.press(key, helper.optionsWithUpdatedTimeout(options, deadline)));
|
||||
await this._retryWithSelectorIfNotConnected(selector, options,
|
||||
(progress, handle) => handle.press(key, helper.optionsWithUpdatedTimeout(options, progress.deadline)));
|
||||
}
|
||||
|
||||
async check(selector: string, options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) {
|
||||
await this._retryWithSelectorIfNotConnected('check', selector, options,
|
||||
(handle, deadline) => handle.check(helper.optionsWithUpdatedTimeout(options, deadline)));
|
||||
await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle._setChecked(progress, true, options));
|
||||
}
|
||||
|
||||
async uncheck(selector: string, options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) {
|
||||
await this._retryWithSelectorIfNotConnected('uncheck', selector, options,
|
||||
(handle, deadline) => handle.uncheck(helper.optionsWithUpdatedTimeout(options, deadline)));
|
||||
await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle._setChecked(progress, false, options));
|
||||
}
|
||||
|
||||
async waitForTimeout(timeout: number) {
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ describe('Page.click', function() {
|
|||
await page.goto(server.PREFIX + '/input/button.html');
|
||||
await page.$eval('button', b => b.style.display = 'none');
|
||||
await page.click('button', { force: true }).catch(e => error = e);
|
||||
expect(error.message).toBe('Element is not visible');
|
||||
expect(error.message).toContain('Element is not visible');
|
||||
expect(await page.evaluate(() => result)).toBe('Was not clicked');
|
||||
});
|
||||
it('should waitFor display:none to be gone', async({page, server}) => {
|
||||
|
|
@ -180,16 +180,16 @@ describe('Page.click', function() {
|
|||
it('should timeout waiting for display:none to be gone', async({page, server}) => {
|
||||
await page.goto(server.PREFIX + '/input/button.html');
|
||||
await page.$eval('button', b => b.style.display = 'none');
|
||||
const error = await page.click('button', { timeout: 100 }).catch(e => e);
|
||||
expect(error.message).toContain('timeout exceeded');
|
||||
expect(error.message).toContain('DEBUG=pw:input');
|
||||
const error = await page.click('button', { timeout: 5000 }).catch(e => e);
|
||||
expect(error.message).toContain('Timeout 5000ms exceeded during page.click.');
|
||||
expect(error.message).toContain('waiting for element to be displayed, enabled and not moving...');
|
||||
});
|
||||
it('should timeout waiting for visbility:hidden to be gone', async({page, server}) => {
|
||||
await page.goto(server.PREFIX + '/input/button.html');
|
||||
await page.$eval('button', b => b.style.visibility = 'hidden');
|
||||
const error = await page.click('button', { timeout: 100 }).catch(e => e);
|
||||
expect(error.message).toContain('timeout exceeded');
|
||||
expect(error.message).toContain('DEBUG=pw:input');
|
||||
const error = await page.click('button', { timeout: 5000 }).catch(e => e);
|
||||
expect(error.message).toContain('Timeout 5000ms exceeded during page.click.');
|
||||
expect(error.message).toContain('waiting for element to be displayed, enabled and not moving...');
|
||||
});
|
||||
it('should waitFor visible when parent is hidden', async({page, server}) => {
|
||||
let done = false;
|
||||
|
|
@ -429,9 +429,9 @@ describe('Page.click', function() {
|
|||
button.style.transition = 'margin 5s linear 0s';
|
||||
button.style.marginLeft = '200px';
|
||||
});
|
||||
const error = await button.click({ timeout: 100 }).catch(e => e);
|
||||
expect(error.message).toContain('timeout exceeded');
|
||||
expect(error.message).toContain('DEBUG=pw:input');
|
||||
const error = await button.click({ timeout: 5000 }).catch(e => e);
|
||||
expect(error.message).toContain('Timeout 5000ms exceeded during elementHandle.click.');
|
||||
expect(error.message).toContain('waiting for element to be displayed, enabled and not moving...');
|
||||
});
|
||||
it('should wait for becoming hit target', async({page, server}) => {
|
||||
await page.goto(server.PREFIX + '/input/button.html');
|
||||
|
|
@ -477,9 +477,9 @@ describe('Page.click', function() {
|
|||
blocker.style.top = '0';
|
||||
document.body.appendChild(blocker);
|
||||
});
|
||||
const error = await button.click({ timeout: 100 }).catch(e => e);
|
||||
expect(error.message).toContain('timeout exceeded');
|
||||
expect(error.message).toContain('DEBUG=pw:input');
|
||||
const error = await button.click({ timeout: 5000 }).catch(e => e);
|
||||
expect(error.message).toContain('Timeout 5000ms exceeded during elementHandle.click.');
|
||||
expect(error.message).toContain('...element does not receive pointer events, retrying input action');
|
||||
});
|
||||
it('should fail when obscured and not waiting for hit target', async({page, server}) => {
|
||||
await page.goto(server.PREFIX + '/input/button.html');
|
||||
|
|
@ -689,7 +689,7 @@ describe('Page.click', function() {
|
|||
await handle.evaluate(button => button.className = 'animated');
|
||||
const error = await promise;
|
||||
expect(await page.evaluate(() => window.clicked)).toBe(undefined);
|
||||
expect(error.message).toBe('Element is outside of the viewport');
|
||||
expect(error.message).toContain('Element is outside of the viewport');
|
||||
});
|
||||
it('should fail when element jumps during hit testing', async({page, server}) => {
|
||||
await page.setContent('<button>Click me</button>');
|
||||
|
|
@ -699,12 +699,12 @@ describe('Page.click', function() {
|
|||
const margin = parseInt(document.querySelector('button').style.marginLeft || 0) + 100;
|
||||
document.querySelector('button').style.marginLeft = margin + 'px';
|
||||
});
|
||||
const promise = handle.click({ timeout: 1000, __testHookBeforeHitTarget }).then(() => clicked = true).catch(e => e);
|
||||
const promise = handle.click({ timeout: 5000, __testHookBeforeHitTarget }).then(() => clicked = true).catch(e => e);
|
||||
const error = await promise;
|
||||
expect(clicked).toBe(false);
|
||||
expect(await page.evaluate(() => window.clicked)).toBe(undefined);
|
||||
expect(error.message).toContain('timeout exceeded');
|
||||
expect(error.message).toContain('DEBUG=pw:input');
|
||||
expect(error.message).toContain('Timeout 5000ms exceeded during elementHandle.click.');
|
||||
expect(error.message).toContain('...element does not receive pointer events, retrying input action');
|
||||
});
|
||||
it('should dispatch microtasks in order', async({page, server}) => {
|
||||
await page.setContent(`
|
||||
|
|
|
|||
|
|
@ -259,20 +259,34 @@ describe('ElementHandle.click', function() {
|
|||
const button = await page.$('button');
|
||||
await page.evaluate(button => button.style.display = 'none', button);
|
||||
const error = await button.click({ force: true }).catch(err => err);
|
||||
expect(error.message).toBe('Element is not visible');
|
||||
expect(error.message).toContain('Element is not visible');
|
||||
});
|
||||
it('should throw for recursively hidden nodes with force', async({page, server}) => {
|
||||
await page.goto(server.PREFIX + '/input/button.html');
|
||||
const button = await page.$('button');
|
||||
await page.evaluate(button => button.parentElement.style.display = 'none', button);
|
||||
const error = await button.click({ force: true }).catch(err => err);
|
||||
expect(error.message).toBe('Element is not visible');
|
||||
expect(error.message).toContain('Element is not visible');
|
||||
});
|
||||
it('should throw for <br> elements with force', async({page, server}) => {
|
||||
await page.setContent('hello<br>goodbye');
|
||||
const br = await page.$('br');
|
||||
const error = await br.click({ force: true }).catch(err => err);
|
||||
expect(error.message).toBe('Element is outside of the viewport');
|
||||
expect(error.message).toContain('Element is outside of the viewport');
|
||||
});
|
||||
it('should double click the button', async({page, server}) => {
|
||||
await page.goto(server.PREFIX + '/input/button.html');
|
||||
await page.evaluate(() => {
|
||||
window.double = false;
|
||||
const button = document.querySelector('button');
|
||||
button.addEventListener('dblclick', event => {
|
||||
window.double = true;
|
||||
});
|
||||
});
|
||||
const button = await page.$('button');
|
||||
await button.dblclick();
|
||||
expect(await page.evaluate('double')).toBe(true);
|
||||
expect(await page.evaluate('result')).toBe('Clicked');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -387,3 +401,18 @@ describe('ElementHandle convenience API', function() {
|
|||
expect(await page.textContent('#inner')).toBe('Text,\nmore text');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ElementHandle.check', () => {
|
||||
it('should check the box', async({page}) => {
|
||||
await page.setContent(`<input id='checkbox' type='checkbox'></input>`);
|
||||
const input = await page.$('input');
|
||||
await input.check();
|
||||
expect(await page.evaluate(() => checkbox.checked)).toBe(true);
|
||||
});
|
||||
it('should uncheck the box', async({page}) => {
|
||||
await page.setContent(`<input id='checkbox' type='checkbox' checked></input>`);
|
||||
const input = await page.$('input');
|
||||
await input.uncheck();
|
||||
expect(await page.evaluate(() => checkbox.checked)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1054,7 +1054,7 @@ describe('Page.fill', function() {
|
|||
it.skip(WEBKIT)('should throw on incorrect date', async({page, server}) => {
|
||||
await page.setContent('<input type=date>');
|
||||
const error = await page.fill('input', '2020-13-05').catch(e => e);
|
||||
expect(error.message).toBe('Malformed date "2020-13-05"');
|
||||
expect(error.message).toContain('Malformed date "2020-13-05"');
|
||||
});
|
||||
it('should fill time input', async({page, server}) => {
|
||||
await page.setContent('<input type=time>');
|
||||
|
|
@ -1064,7 +1064,7 @@ describe('Page.fill', function() {
|
|||
it.skip(WEBKIT)('should throw on incorrect time', async({page, server}) => {
|
||||
await page.setContent('<input type=time>');
|
||||
const error = await page.fill('input', '25:05').catch(e => e);
|
||||
expect(error.message).toBe('Malformed time "25:05"');
|
||||
expect(error.message).toContain('Malformed time "25:05"');
|
||||
});
|
||||
it('should fill datetime-local input', async({page, server}) => {
|
||||
await page.setContent('<input type=datetime-local>');
|
||||
|
|
@ -1074,7 +1074,7 @@ describe('Page.fill', function() {
|
|||
it.skip(WEBKIT || FFOX)('should throw on incorrect datetime-local', async({page, server}) => {
|
||||
await page.setContent('<input type=datetime-local>');
|
||||
const error = await page.fill('input', 'abc').catch(e => e);
|
||||
expect(error.message).toBe('Malformed datetime-local "abc"');
|
||||
expect(error.message).toContain('Malformed datetime-local "abc"');
|
||||
});
|
||||
it('should fill contenteditable', async({page, server}) => {
|
||||
await page.goto(server.PREFIX + '/input/textarea.html');
|
||||
|
|
|
|||
Loading…
Reference in a new issue