mirror of
https://github.com/microsoft/playwright.git
synced 2024-10-27 13:50:25 +03:00
feat(input): retry when hit target check fails, prepare for page pause (#2020)
This commit is contained in:
parent
6c94f604d8
commit
b11d7f15bb
@ -258,6 +258,10 @@ export class CRPage implements PageDelegate {
|
|||||||
return this._sessionForHandle(handle)._scrollRectIntoViewIfNeeded(handle, rect);
|
return this._sessionForHandle(handle)._scrollRectIntoViewIfNeeded(handle, rect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async setActivityPaused(paused: boolean): Promise<void> {
|
||||||
|
await this._forAllFrameSessions(frame => frame._setActivityPaused(paused));
|
||||||
|
}
|
||||||
|
|
||||||
async getContentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null> {
|
async getContentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null> {
|
||||||
return this._sessionForHandle(handle)._getContentQuads(handle);
|
return this._sessionForHandle(handle)._getContentQuads(handle);
|
||||||
}
|
}
|
||||||
@ -795,6 +799,9 @@ class FrameSession {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _setActivityPaused(paused: boolean): Promise<void> {
|
||||||
|
}
|
||||||
|
|
||||||
async _getContentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null> {
|
async _getContentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null> {
|
||||||
const result = await this._client.send('DOM.getContentQuads', {
|
const result = await this._client.send('DOM.getContentQuads', {
|
||||||
objectId: toRemoteObject(handle).objectId
|
objectId: toRemoteObject(handle).objectId
|
||||||
|
113
src/dom.ts
113
src/dom.ts
@ -163,9 +163,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _scrollRectIntoViewIfNeeded(rect?: types.Rect): Promise<void> {
|
async _scrollRectIntoViewIfNeeded(rect?: types.Rect): Promise<void> {
|
||||||
this._page._log(inputLog, 'scrolling into view if needed...');
|
|
||||||
await this._page._delegate.scrollRectIntoViewIfNeeded(this, rect);
|
await this._page._delegate.scrollRectIntoViewIfNeeded(this, rect);
|
||||||
this._page._log(inputLog, '...done');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async scrollIntoViewIfNeeded() {
|
async scrollIntoViewIfNeeded() {
|
||||||
@ -229,44 +227,78 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||||||
return point;
|
return point;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _performPointerAction(action: (point: types.Point) => Promise<void>, options: PointerActionOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}): Promise<void> {
|
async _retryPointerAction(action: (point: types.Point) => Promise<void>, options: PointerActionOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}): Promise<void> {
|
||||||
const deadline = this._page._timeoutSettings.computeDeadline(options);
|
const deadline = this._page._timeoutSettings.computeDeadline(options);
|
||||||
const { force = false } = (options || {});
|
while (!helper.isPastDeadline(deadline)) {
|
||||||
if (!force)
|
const result = await this._performPointerAction(action, deadline, options);
|
||||||
await this._waitForDisplayedAtStablePosition(deadline);
|
if (result === 'done')
|
||||||
const position = options ? options.position : undefined;
|
return;
|
||||||
await this._scrollRectIntoViewIfNeeded(position ? { x: position.x, y: position.y, width: 0, height: 0 } : undefined);
|
}
|
||||||
const point = position ? await this._offsetPoint(position) : await this._clickablePoint();
|
throw new TimeoutError(`waiting for element to receive pointer events failed: timeout exceeded`);
|
||||||
point.x = (point.x * 100 | 0) / 100;
|
}
|
||||||
point.y = (point.y * 100 | 0) / 100;
|
|
||||||
await this._page.mouse.move(point.x, point.y); // Force any hover effects before waiting for hit target.
|
|
||||||
if (options && (options as any).__testHookBeforeWaitForHitTarget)
|
|
||||||
await (options as any).__testHookBeforeWaitForHitTarget();
|
|
||||||
if (!force)
|
|
||||||
await this._waitForHitTargetAt(point, deadline);
|
|
||||||
|
|
||||||
await this._page._frameManager.waitForSignalsCreatedBy(async () => {
|
async _performPointerAction(action: (point: types.Point) => Promise<void>, deadline: number, options: PointerActionOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}): Promise<'done' | 'retry'> {
|
||||||
let restoreModifiers: input.Modifier[] | undefined;
|
const { force = false, position } = options;
|
||||||
if (options && options.modifiers)
|
if (!force && !(options as any).__testHookSkipStablePosition)
|
||||||
restoreModifiers = await this._page.keyboard._ensureModifiers(options.modifiers);
|
await this._waitForDisplayedAtStablePosition(deadline);
|
||||||
this._page._log(inputLog, 'performing input action...');
|
|
||||||
await action(point);
|
let paused = false;
|
||||||
this._page._log(inputLog, '...done');
|
try {
|
||||||
if (restoreModifiers)
|
await this._page._delegate.setActivityPaused(true);
|
||||||
await this._page.keyboard._ensureModifiers(restoreModifiers);
|
paused = true;
|
||||||
}, deadline, options, true);
|
|
||||||
|
// Scroll into view and calculate the point again while paused just in case something has moved.
|
||||||
|
this._page._log(inputLog, 'scrolling into view if needed...');
|
||||||
|
await this._scrollRectIntoViewIfNeeded(position ? { x: position.x, y: position.y, width: 0, height: 0 } : undefined);
|
||||||
|
this._page._log(inputLog, '...done scrolling');
|
||||||
|
const point = roundPoint(position ? await this._offsetPoint(position) : await this._clickablePoint());
|
||||||
|
|
||||||
|
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})...`);
|
||||||
|
const matchesHitTarget = await this._checkHitTargetAt(point);
|
||||||
|
if (!matchesHitTarget) {
|
||||||
|
this._page._log(inputLog, '...element does not receive pointer events, retrying input action');
|
||||||
|
await this._page._delegate.setActivityPaused(false);
|
||||||
|
paused = false;
|
||||||
|
return 'retry';
|
||||||
|
}
|
||||||
|
this._page._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 input action...');
|
||||||
|
await action(point);
|
||||||
|
this._page._log(inputLog, '...input action done');
|
||||||
|
this._page._log(inputLog, 'waiting for navigations to finish...');
|
||||||
|
await this._page._delegate.setActivityPaused(false);
|
||||||
|
paused = false;
|
||||||
|
if (restoreModifiers)
|
||||||
|
await this._page.keyboard._ensureModifiers(restoreModifiers);
|
||||||
|
}, deadline, options, true);
|
||||||
|
this._page._log(inputLog, '...navigations have finished');
|
||||||
|
|
||||||
|
return 'done';
|
||||||
|
} finally {
|
||||||
|
if (paused)
|
||||||
|
await this._page._delegate.setActivityPaused(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hover(options?: PointerActionOptions & types.PointerActionWaitOptions): Promise<void> {
|
hover(options?: PointerActionOptions & types.PointerActionWaitOptions): Promise<void> {
|
||||||
return this._performPointerAction(point => this._page.mouse.move(point.x, point.y), options);
|
return this._retryPointerAction(point => this._page.mouse.move(point.x, point.y), options);
|
||||||
}
|
}
|
||||||
|
|
||||||
click(options?: ClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<void> {
|
click(options?: ClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<void> {
|
||||||
return this._performPointerAction(point => this._page.mouse.click(point.x, point.y, options), options);
|
return this._retryPointerAction(point => this._page.mouse.click(point.x, point.y, options), options);
|
||||||
}
|
}
|
||||||
|
|
||||||
dblclick(options?: MultiClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<void> {
|
dblclick(options?: MultiClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<void> {
|
||||||
return this._performPointerAction(point => this._page.mouse.dblclick(point.x, point.y, options), options);
|
return this._retryPointerAction(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[]> {
|
async selectOption(values: string | ElementHandle | types.SelectOption | string[] | ElementHandle[] | types.SelectOption[], options?: types.NavigatingActionWaitOptions): Promise<string[]> {
|
||||||
@ -429,11 +461,10 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||||||
const timeoutMessage = 'element to be displayed and not moving';
|
const timeoutMessage = 'element to be displayed and not moving';
|
||||||
const injectedResult = await helper.waitWithDeadline(stablePromise, timeoutMessage, deadline);
|
const injectedResult = await helper.waitWithDeadline(stablePromise, timeoutMessage, deadline);
|
||||||
handleInjectedResult(injectedResult, timeoutMessage);
|
handleInjectedResult(injectedResult, timeoutMessage);
|
||||||
this._page._log(inputLog, '...done');
|
this._page._log(inputLog, '...element is displayed and does not move');
|
||||||
}
|
}
|
||||||
|
|
||||||
async _waitForHitTargetAt(point: types.Point, deadline: number): Promise<void> {
|
async _checkHitTargetAt(point: types.Point): Promise<boolean> {
|
||||||
this._page._log(inputLog, `waiting for element to receive pointer events at (${point.x},${point.y}) ...`);
|
|
||||||
const frame = await this.ownerFrame();
|
const frame = await this.ownerFrame();
|
||||||
if (frame && frame.parentFrame()) {
|
if (frame && frame.parentFrame()) {
|
||||||
const element = await frame.frameElement();
|
const element = await frame.frameElement();
|
||||||
@ -443,13 +474,10 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||||||
// Translate from viewport coordinates to frame coordinates.
|
// Translate from viewport coordinates to frame coordinates.
|
||||||
point = { x: point.x - box.x, y: point.y - box.y };
|
point = { x: point.x - box.x, y: point.y - box.y };
|
||||||
}
|
}
|
||||||
const hitTargetPromise = this._evaluateInUtility(({ injected, node }, { timeout, point }) => {
|
const injectedResult = await this._evaluateInUtility(({ injected, node }, { point }) => {
|
||||||
return injected.waitForHitTargetAt(node, timeout, point);
|
return injected.checkHitTargetAt(node, point);
|
||||||
}, { timeout: helper.timeUntilDeadline(deadline), point });
|
}, { point });
|
||||||
const timeoutMessage = 'element to receive pointer events';
|
return handleInjectedResult(injectedResult, '');
|
||||||
const injectedResult = await helper.waitWithDeadline(hitTargetPromise, timeoutMessage, deadline);
|
|
||||||
handleInjectedResult(injectedResult, timeoutMessage);
|
|
||||||
this._page._log(inputLog, '...done');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -470,3 +498,10 @@ function handleInjectedResult<T = undefined>(injectedResult: InjectedResult<T>,
|
|||||||
throw new Error(injectedResult.error);
|
throw new Error(injectedResult.error);
|
||||||
return injectedResult.value as T;
|
return injectedResult.value as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function roundPoint(point: types.Point): types.Point {
|
||||||
|
return {
|
||||||
|
x: (point.x * 100 | 0) / 100,
|
||||||
|
y: (point.y * 100 | 0) / 100,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -428,6 +428,9 @@ export class FFPage implements PageDelegate {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async setActivityPaused(paused: boolean): Promise<void> {
|
||||||
|
}
|
||||||
|
|
||||||
async getContentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null> {
|
async getContentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null> {
|
||||||
const result = await this._session.send('Page.getContentQuads', {
|
const result = await this._session.send('Page.getContentQuads', {
|
||||||
frameId: handle._context.frame._id,
|
frameId: handle._context.frame._id,
|
||||||
|
@ -324,30 +324,16 @@ export class Injected {
|
|||||||
return { status: result === 'notconnected' ? 'notconnected' : (result ? 'success' : 'timeout') };
|
return { status: result === 'notconnected' ? 'notconnected' : (result ? 'success' : 'timeout') };
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForHitTargetAt(node: Node, timeout: number, point: types.Point): Promise<InjectedResult> {
|
checkHitTargetAt(node: Node, point: types.Point): InjectedResult<boolean> {
|
||||||
const targetElement = node.nodeType === Node.ELEMENT_NODE ? (node as Element) : node.parentElement;
|
let element = node.nodeType === Node.ELEMENT_NODE ? (node as Element) : node.parentElement;
|
||||||
let element = targetElement;
|
|
||||||
while (element && window.getComputedStyle(element).pointerEvents === 'none')
|
while (element && window.getComputedStyle(element).pointerEvents === 'none')
|
||||||
element = element.parentElement;
|
element = element.parentElement;
|
||||||
if (!element)
|
if (!element || !element.isConnected)
|
||||||
return { status: 'notconnected' };
|
return { status: 'notconnected' };
|
||||||
const result = await this.poll('raf', timeout, (): 'notconnected' | 'moved' | boolean => {
|
let hitElement = this._deepElementFromPoint(document, point.x, point.y);
|
||||||
if (!element!.isConnected)
|
while (hitElement && hitElement !== element)
|
||||||
return 'notconnected';
|
hitElement = this._parentElementOrShadowHost(hitElement);
|
||||||
const clientRect = targetElement!.getBoundingClientRect();
|
return { status: 'success', value: hitElement === element };
|
||||||
if (clientRect.left > point.x || clientRect.left + clientRect.width < point.x ||
|
|
||||||
clientRect.top > point.y || clientRect.top + clientRect.height < point.y)
|
|
||||||
return 'moved';
|
|
||||||
let hitElement = this._deepElementFromPoint(document, point.x, point.y);
|
|
||||||
while (hitElement && hitElement !== element)
|
|
||||||
hitElement = this._parentElementOrShadowHost(hitElement);
|
|
||||||
return hitElement === element;
|
|
||||||
});
|
|
||||||
if (result === 'notconnected')
|
|
||||||
return { status: 'notconnected' };
|
|
||||||
if (result === 'moved')
|
|
||||||
return { status: 'error', error: 'Element has moved during the action' };
|
|
||||||
return { status: result ? 'success' : 'timeout' };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatchEvent(node: Node, type: string, eventInit: Object) {
|
dispatchEvent(node: Node, type: string, eventInit: Object) {
|
||||||
|
@ -69,6 +69,7 @@ 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<void>;
|
scrollRectIntoViewIfNeeded(handle: dom.ElementHandle, rect?: types.Rect): Promise<void>;
|
||||||
|
setActivityPaused(paused: boolean): Promise<void>;
|
||||||
|
|
||||||
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>;
|
||||||
|
@ -731,6 +731,9 @@ export class WKPage implements PageDelegate {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async setActivityPaused(paused: boolean): Promise<void> {
|
||||||
|
}
|
||||||
|
|
||||||
async getContentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null> {
|
async getContentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null> {
|
||||||
const result = await this._session.send('DOM.getContentQuads', {
|
const result = await this._session.send('DOM.getContentQuads', {
|
||||||
objectId: toRemoteObject(handle).objectId!
|
objectId: toRemoteObject(handle).objectId!
|
||||||
|
@ -14,6 +14,7 @@ function addButton() {
|
|||||||
button.addEventListener('click', () => window.clicked = true);
|
button.addEventListener('click', () => window.clicked = true);
|
||||||
document.body.appendChild(button);
|
document.body.appendChild(button);
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopButton(remove) {
|
function stopButton(remove) {
|
||||||
const button = document.querySelector('button');
|
const button = document.querySelector('button');
|
||||||
button.style.marginLeft = button.getBoundingClientRect().left + 'px';
|
button.style.marginLeft = button.getBoundingClientRect().left + 'px';
|
||||||
@ -21,14 +22,21 @@ function stopButton(remove) {
|
|||||||
if (remove)
|
if (remove)
|
||||||
button.remove();
|
button.remove();
|
||||||
}
|
}
|
||||||
function startJumping() {
|
|
||||||
|
let x = 0;
|
||||||
|
function jump() {
|
||||||
|
x += 300;
|
||||||
const button = document.querySelector('button');
|
const button = document.querySelector('button');
|
||||||
let x = 0;
|
button.style.marginLeft = x + 'px';
|
||||||
|
}
|
||||||
|
|
||||||
|
function startJumping() {
|
||||||
|
x = 0;
|
||||||
const moveIt = () => {
|
const moveIt = () => {
|
||||||
x += 300;
|
jump();
|
||||||
button.style.marginLeft = x + 'px';
|
|
||||||
requestAnimationFrame(moveIt);
|
requestAnimationFrame(moveIt);
|
||||||
};
|
};
|
||||||
|
setInterval(jump, 0);
|
||||||
moveIt();
|
moveIt();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -221,7 +221,6 @@ describe('Page.click', function() {
|
|||||||
'mouseover',
|
'mouseover',
|
||||||
'mouseenter',
|
'mouseenter',
|
||||||
'mousemove',
|
'mousemove',
|
||||||
'mousemove',
|
|
||||||
'mousedown',
|
'mousedown',
|
||||||
'mouseup',
|
'mouseup',
|
||||||
'click',
|
'click',
|
||||||
@ -571,37 +570,50 @@ describe('Page.click', function() {
|
|||||||
expect(clicked).toBe(true);
|
expect(clicked).toBe(true);
|
||||||
expect(await page.evaluate(() => window.clicked)).toBe(true);
|
expect(await page.evaluate(() => window.clicked)).toBe(true);
|
||||||
});
|
});
|
||||||
it('should fail when element moves during hit testing', async({page, server}) => {
|
it('should retry when element jumps during hit testing', async({page, server}) => {
|
||||||
await page.goto(server.PREFIX + '/input/animating-button.html');
|
await page.goto(server.PREFIX + '/input/animating-button.html');
|
||||||
await page.evaluate(() => addButton());
|
await page.evaluate(() => addButton());
|
||||||
let clicked = false;
|
let clicked = false;
|
||||||
const handle = await page.$('button');
|
const handle = await page.$('button');
|
||||||
const __testHookBeforeWaitForHitTarget = () => page.evaluate(() => startJumping());
|
const __testHookBeforeHitTarget = () => page.evaluate(() => { if (window.x === 0) jump(); });
|
||||||
const promise = handle.click({ timeout: 0, __testHookBeforeWaitForHitTarget }).then(() => clicked = true).catch(e => e);
|
const promise = handle.click({ timeout: 0, __testHookBeforeHitTarget }).then(() => clicked = true);
|
||||||
expect(clicked).toBe(false);
|
expect(clicked).toBe(false);
|
||||||
expect(await page.evaluate(() => window.clicked)).toBe(undefined);
|
expect(await page.evaluate(() => window.clicked)).toBe(undefined);
|
||||||
await page.evaluate(() => stopButton());
|
await page.evaluate(() => stopButton());
|
||||||
|
await promise;
|
||||||
|
expect(clicked).toBe(true);
|
||||||
|
expect(await page.evaluate(() => window.clicked)).toBe(true);
|
||||||
|
});
|
||||||
|
it('should fail when element jumps during hit testing', async({page, server}) => {
|
||||||
|
await page.goto(server.PREFIX + '/input/animating-button.html');
|
||||||
|
await page.evaluate(() => addButton());
|
||||||
|
await page.evaluate(() => stopButton());
|
||||||
|
let clicked = false;
|
||||||
|
const handle = await page.$('button');
|
||||||
|
const __testHookBeforeHitTarget = () => page.evaluate(() => jump());
|
||||||
|
const promise = handle.click({ timeout: 1000, __testHookBeforeHitTarget, __testHookSkipStablePosition: true }).then(() => clicked = true).catch(e => e);
|
||||||
const error = await promise;
|
const error = await promise;
|
||||||
expect(clicked).toBe(false);
|
expect(clicked).toBe(false);
|
||||||
expect(error.message).toBe('Element has moved during the action');
|
|
||||||
expect(await page.evaluate(() => window.clicked)).toBe(undefined);
|
expect(await page.evaluate(() => window.clicked)).toBe(undefined);
|
||||||
});
|
|
||||||
it('should fail when element is blocked on hover', async({page, server}) => {
|
|
||||||
await page.setContent(`<style>
|
|
||||||
container { display: block; position: relative; width: 200px; height: 50px; }
|
|
||||||
div, button { position: absolute; left: 0; top: 0; bottom: 0; right: 0; }
|
|
||||||
div { pointer-events: none; }
|
|
||||||
container:hover div { pointer-events: auto; background: red; }
|
|
||||||
</style>
|
|
||||||
<container>
|
|
||||||
<button onclick="window.clicked=true">Click me</button>
|
|
||||||
<div></div>
|
|
||||||
</container>`);
|
|
||||||
const error = await page.click('button', { timeout: 3000 }).catch(e => e);
|
|
||||||
expect(error.message).toBe('waiting for element to receive pointer events failed: timeout exceeded');
|
expect(error.message).toBe('waiting for element to receive pointer events failed: timeout exceeded');
|
||||||
expect(await page.evaluate(() => window.clicked)).toBe(undefined);
|
|
||||||
});
|
});
|
||||||
it('should wait while element is blocked on hover', async({page, server}) => {
|
it.fail(CHROMIUM || WEBKIT || FFOX)('should work when element jumps uncontrollably', async({page, server}) => {
|
||||||
|
// This test requires pausing the page.
|
||||||
|
await page.goto(server.PREFIX + '/input/animating-button.html');
|
||||||
|
await page.evaluate(() => addButton());
|
||||||
|
await page.evaluate(() => stopButton());
|
||||||
|
const handle = await page.$('button');
|
||||||
|
await page.evaluate(() => startJumping());
|
||||||
|
let clicked = false;
|
||||||
|
const promise = handle.click({ timeout: 1000, __testHookSkipStablePosition: true }).then(() => clicked = true);
|
||||||
|
expect(clicked).toBe(false);
|
||||||
|
expect(await page.evaluate(() => window.clicked)).toBe(undefined);
|
||||||
|
await promise;
|
||||||
|
expect(clicked).toBe(true);
|
||||||
|
expect(await page.evaluate(() => window.clicked)).toBe(true);
|
||||||
|
});
|
||||||
|
it.fail(CHROMIUM || WEBKIT || FFOX)('should wait while element is blocked on hover', async({page, server}) => {
|
||||||
|
// This test requires pausing the page.
|
||||||
await page.setContent(`<style>
|
await page.setContent(`<style>
|
||||||
@keyframes move-out { from { marign-left: 0; } to { margin-left: 150px; } }
|
@keyframes move-out { from { marign-left: 0; } to { margin-left: 150px; } }
|
||||||
container { display: block; position: relative; width: 200px; height: 50px; }
|
container { display: block; position: relative; width: 200px; height: 50px; }
|
||||||
|
Loading…
Reference in New Issue
Block a user