mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
fix(input): climb dom for pointer-events:none targets (#1666)
This commit is contained in:
parent
3dc14eddaf
commit
889cf8f7b6
@ -61,8 +61,7 @@ const iPhone11 = devices['iPhone 11 Pro'];
|
|||||||
(async () => {
|
(async () => {
|
||||||
const browser = await webkit.launch();
|
const browser = await webkit.launch();
|
||||||
const context = await browser.newContext({
|
const context = await browser.newContext({
|
||||||
viewport: iPhone11.viewport,
|
...iPhone11,
|
||||||
userAgent: iPhone11.userAgent,
|
|
||||||
geolocation: { longitude: 12.492507, latitude: 41.889938 },
|
geolocation: { longitude: 12.492507, latitude: 41.889938 },
|
||||||
permissions: ['geolocation']
|
permissions: ['geolocation']
|
||||||
});
|
});
|
||||||
|
16
src/dom.ts
16
src/dom.ts
@ -14,6 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import * as debug from 'debug';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as mime from 'mime';
|
import * as mime from 'mime';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
@ -36,6 +37,8 @@ export type ClickOptions = PointerActionOptions & input.MouseClickOptions;
|
|||||||
|
|
||||||
export type MultiClickOptions = PointerActionOptions & input.MouseMultiClickOptions;
|
export type MultiClickOptions = PointerActionOptions & input.MouseMultiClickOptions;
|
||||||
|
|
||||||
|
const debugInput = debug('pw:input');
|
||||||
|
|
||||||
export class FrameExecutionContext extends js.ExecutionContext {
|
export class FrameExecutionContext extends js.ExecutionContext {
|
||||||
readonly frame: frames.Frame;
|
readonly frame: frames.Frame;
|
||||||
private _injectedPromise?: Promise<js.JSHandle>;
|
private _injectedPromise?: Promise<js.JSHandle>;
|
||||||
@ -112,7 +115,9 @@ 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> {
|
||||||
|
debugInput('scrolling into veiw if needed...');
|
||||||
await this._page._delegate.scrollRectIntoViewIfNeeded(this, rect);
|
await this._page._delegate.scrollRectIntoViewIfNeeded(this, rect);
|
||||||
|
debugInput('...done');
|
||||||
}
|
}
|
||||||
|
|
||||||
async scrollIntoViewIfNeeded() {
|
async scrollIntoViewIfNeeded() {
|
||||||
@ -186,11 +191,16 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||||||
if (!force)
|
if (!force)
|
||||||
await this._waitForHitTargetAt(point, options);
|
await this._waitForHitTargetAt(point, options);
|
||||||
|
|
||||||
|
point.x = (point.x * 100 | 0) / 100;
|
||||||
|
point.y = (point.y * 100 | 0) / 100;
|
||||||
|
|
||||||
await this._page._frameManager.waitForNavigationsCreatedBy(async () => {
|
await this._page._frameManager.waitForNavigationsCreatedBy(async () => {
|
||||||
let restoreModifiers: input.Modifier[] | undefined;
|
let restoreModifiers: input.Modifier[] | undefined;
|
||||||
if (options && options.modifiers)
|
if (options && options.modifiers)
|
||||||
restoreModifiers = await this._page.keyboard._ensureModifiers(options.modifiers);
|
restoreModifiers = await this._page.keyboard._ensureModifiers(options.modifiers);
|
||||||
|
debugInput('performing input action...');
|
||||||
await action(point);
|
await action(point);
|
||||||
|
debugInput('...done');
|
||||||
if (restoreModifiers)
|
if (restoreModifiers)
|
||||||
await this._page.keyboard._ensureModifiers(restoreModifiers);
|
await this._page.keyboard._ensureModifiers(restoreModifiers);
|
||||||
}, options, true);
|
}, options, true);
|
||||||
@ -356,13 +366,16 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _waitForDisplayedAtStablePosition(options: types.TimeoutOptions = {}): Promise<void> {
|
async _waitForDisplayedAtStablePosition(options: types.TimeoutOptions = {}): Promise<void> {
|
||||||
|
debugInput('waiting for element to be displayed and not moving...');
|
||||||
const stablePromise = this._evaluateInUtility(({ injected, node }, timeout) => {
|
const stablePromise = this._evaluateInUtility(({ injected, node }, timeout) => {
|
||||||
return injected.waitForDisplayedAtStablePosition(node, timeout);
|
return injected.waitForDisplayedAtStablePosition(node, timeout);
|
||||||
}, options.timeout || 0);
|
}, options.timeout || 0);
|
||||||
await helper.waitWithTimeout(stablePromise, 'element to be displayed and not moving', options.timeout || 0);
|
await helper.waitWithTimeout(stablePromise, 'element to be displayed and not moving', options.timeout || 0);
|
||||||
|
debugInput('...done');
|
||||||
}
|
}
|
||||||
|
|
||||||
async _waitForHitTargetAt(point: types.Point, options: types.TimeoutOptions = {}): Promise<void> {
|
async _waitForHitTargetAt(point: types.Point, options: types.TimeoutOptions = {}): Promise<void> {
|
||||||
|
debugInput(`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();
|
||||||
@ -375,7 +388,8 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||||||
const hitTargetPromise = this._evaluateInUtility(({ injected, node }, { timeout, point }) => {
|
const hitTargetPromise = this._evaluateInUtility(({ injected, node }, { timeout, point }) => {
|
||||||
return injected.waitForHitTargetAt(node, timeout, point);
|
return injected.waitForHitTargetAt(node, timeout, point);
|
||||||
}, { timeout: options.timeout || 0, point });
|
}, { timeout: options.timeout || 0, point });
|
||||||
await helper.waitWithTimeout(hitTargetPromise, 'element to receive mouse events', options.timeout || 0);
|
await helper.waitWithTimeout(hitTargetPromise, 'element to receive pointer events', options.timeout || 0);
|
||||||
|
debugInput('...done');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,7 +247,9 @@ class Injected {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async waitForHitTargetAt(node: Node, timeout: number, point: types.Point) {
|
async waitForHitTargetAt(node: Node, timeout: number, point: types.Point) {
|
||||||
const element = node.nodeType === Node.ELEMENT_NODE ? (node as Element) : node.parentElement;
|
let element = node.nodeType === Node.ELEMENT_NODE ? (node as Element) : node.parentElement;
|
||||||
|
while (element && window.getComputedStyle(element).pointerEvents === 'none')
|
||||||
|
element = element.parentElement;
|
||||||
if (!element)
|
if (!element)
|
||||||
throw new Error('Element is not attached to the DOM');
|
throw new Error('Element is not attached to the DOM');
|
||||||
const result = await this.poll('raf', timeout, () => {
|
const result = await this.poll('raf', timeout, () => {
|
||||||
|
@ -483,6 +483,10 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROMI
|
|||||||
expect(await page.evaluate(() => window.result)).toBe('Was not clicked');
|
expect(await page.evaluate(() => window.result)).toBe('Was not clicked');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should climb dom for pointer-events:none targets', async({page, server}) => {
|
||||||
|
await page.setContent('<button><label style="pointer-events:none">Click target</label></button>')
|
||||||
|
await page.click('text=Click target');
|
||||||
|
});
|
||||||
it('should update modifiers correctly', async({page, server}) => {
|
it('should update modifiers correctly', async({page, server}) => {
|
||||||
await page.goto(server.PREFIX + '/input/button.html');
|
await page.goto(server.PREFIX + '/input/button.html');
|
||||||
await page.click('button', { modifiers: ['Shift'] });
|
await page.click('button', { modifiers: ['Shift'] });
|
||||||
|
Loading…
x
Reference in New Issue
Block a user