diff --git a/docs/actionability.md b/docs/actionability.md
index bff54316e1..720f6f7242 100644
--- a/docs/actionability.md
+++ b/docs/actionability.md
@@ -8,7 +8,8 @@ Some actions like `page.click()` support `{force: true}` option that disable non
| Actions | Performed checks |
| ------ | ------- |
-| `check()`
`click()`
`dblclick()`
`hover()`
`uncheck()` | [Visible]
[Stable]
[Enabled]
[Receiving Events]
[Attached] |
+| `check()`
`click()`
`dblclick()`
`uncheck()` | [Visible]
[Stable]
[Enabled]
[Receiving Events]
[Attached] |
+| `hover()` | [Visible]
[Stable]
[Receiving Events]
[Attached] |
| `fill()` | [Visible]
[Enabled]
[Editable]
[Attached] |
| `dispatchEvent()`
`focus()`
`press()`
`setInputFiles()`
`selectOption()`
`type()` | [Attached] |
| `scrollIntoViewIfNeeded()`
`screenshot()` | [Visible]
[Stable]
[Attached] |
diff --git a/src/dom.ts b/src/dom.ts
index f715061061..82528a92af 100644
--- a/src/dom.ts
+++ b/src/dom.ts
@@ -287,11 +287,12 @@ export class ElementHandle extends js.JSHandle {
};
}
- async _retryPointerAction(progress: Progress, actionName: string, action: (point: types.Point) => Promise, options: types.PointerActionOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<'error:notconnected' | 'done'> {
+ async _retryPointerAction(progress: Progress, actionName: string, waitForEnabled: boolean, action: (point: types.Point) => Promise,
+ options: types.PointerActionOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<'error:notconnected' | 'done'> {
let first = true;
while (progress.isRunning()) {
progress.logger.info(`${first ? 'attempting' : 'retrying'} ${actionName} action`);
- const result = await this._performPointerAction(progress, actionName, action, options);
+ const result = await this._performPointerAction(progress, actionName, waitForEnabled, action, options);
first = false;
if (result === 'error:notvisible') {
if (options.force)
@@ -316,12 +317,12 @@ export class ElementHandle extends js.JSHandle {
return 'done';
}
- async _performPointerAction(progress: Progress, actionName: string, action: (point: types.Point) => Promise, options: types.PointerActionOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<'error:notvisible' | 'error:notconnected' | 'error:notinviewport' | 'error:nothittarget' | 'done'> {
+ async _performPointerAction(progress: Progress, actionName: string, waitForEnabled: boolean, action: (point: types.Point) => Promise, options: types.PointerActionOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<'error:notvisible' | 'error:notconnected' | 'error:notinviewport' | 'error:nothittarget' | 'done'> {
const { force = false, position } = options;
if ((options as any).__testHookBeforeStable)
await (options as any).__testHookBeforeStable();
if (!force) {
- const result = await this._waitForDisplayedAtStablePosition(progress, true /* waitForEnabled */);
+ const result = await this._waitForDisplayedAtStablePosition(progress, waitForEnabled);
if (result !== 'done')
return result;
}
@@ -379,7 +380,7 @@ export class ElementHandle extends js.JSHandle {
}
_hover(progress: Progress, options: types.PointerActionOptions & types.PointerActionWaitOptions): Promise<'error:notconnected' | 'done'> {
- return this._retryPointerAction(progress, 'hover', point => this._page.mouse.move(point.x, point.y), options);
+ return this._retryPointerAction(progress, 'hover', false /* waitForEnabled */, point => this._page.mouse.move(point.x, point.y), options);
}
click(options: types.MouseClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}): Promise {
@@ -390,7 +391,7 @@ export class ElementHandle extends js.JSHandle {
}
_click(progress: Progress, options: types.MouseClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<'error:notconnected' | 'done'> {
- return this._retryPointerAction(progress, 'click', point => this._page.mouse.click(point.x, point.y, options), options);
+ return this._retryPointerAction(progress, 'click', true /* waitForEnabled */, point => this._page.mouse.click(point.x, point.y, options), options);
}
dblclick(options: types.MouseMultiClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}): Promise {
@@ -401,7 +402,7 @@ export class ElementHandle extends js.JSHandle {
}
_dblclick(progress: Progress, options: types.MouseMultiClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<'error:notconnected' | 'done'> {
- return this._retryPointerAction(progress, 'dblclick', point => this._page.mouse.dblclick(point.x, point.y, options), options);
+ return this._retryPointerAction(progress, 'dblclick', true /* waitForEnabled */, point => this._page.mouse.dblclick(point.x, point.y, options), options);
}
async selectOption(values: string | ElementHandle | types.SelectOption | string[] | ElementHandle[] | types.SelectOption[] | null, options: types.NavigatingActionWaitOptions = {}): Promise {
diff --git a/test/mouse.spec.ts b/test/mouse.spec.ts
index 0d39149229..f4a7506fc4 100644
--- a/test/mouse.spec.ts
+++ b/test/mouse.spec.ts
@@ -107,6 +107,13 @@ it('should trigger hover state', async({page, server}) => {
expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-91');
});
+it('should trigger hover state on disabled button', async({page, server}) => {
+ await page.goto(server.PREFIX + '/input/scrollable.html');
+ await page.$eval('#button-6', (button: HTMLButtonElement) => button.disabled = true);
+ await page.hover('#button-6', { timeout: 5000 });
+ expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-6');
+});
+
it('should trigger hover state with removed window.Node', async({page, server}) => {
await page.goto(server.PREFIX + '/input/scrollable.html');
await page.evaluate(() => delete window.Node);