diff --git a/docs/actionability.md b/docs/actionability.md
index 4c51023ad4a38..bff54316e17a3 100644
--- a/docs/actionability.md
+++ b/docs/actionability.md
@@ -11,7 +11,8 @@ Some actions like `page.click()` support `{force: true}` option that disable non
| `check()`
`click()`
`dblclick()`
`hover()`
`uncheck()` | [Visible]
[Stable]
[Enabled]
[Receiving Events]
[Attached] |
| `fill()` | [Visible]
[Enabled]
[Editable]
[Attached] |
| `dispatchEvent()`
`focus()`
`press()`
`setInputFiles()`
`selectOption()`
`type()` | [Attached] |
-| `selectText()`
`scrollIntoViewIfNeeded()`
`screenshot()` | [Visible]
[Attached] |
+| `scrollIntoViewIfNeeded()`
`screenshot()` | [Visible]
[Stable]
[Attached] |
+| `selectText()` | [Visible]
[Attached] |
| `getAttribute()`
`innerText()`
`innerHTML()`
`textContent()` | [Attached] |
### Visible
diff --git a/src/dom.ts b/src/dom.ts
index c3aa18701ef8a..f715061061f18 100644
--- a/src/dom.ts
+++ b/src/dom.ts
@@ -209,7 +209,7 @@ export class ElementHandle extends js.JSHandle {
async _waitAndScrollIntoViewIfNeeded(progress: Progress): Promise {
while (progress.isRunning()) {
- assertDone(throwRetargetableDOMError(await this._waitForVisible(progress)));
+ assertDone(throwRetargetableDOMError(await this._waitForDisplayedAtStablePosition(progress, false /* waitForEnabled */)));
progress.throwIfAborted(); // Avoid action that has side-effects.
const result = throwRetargetableDOMError(await this._scrollRectIntoViewIfNeeded());
@@ -321,7 +321,7 @@ export class ElementHandle extends js.JSHandle {
if ((options as any).__testHookBeforeStable)
await (options as any).__testHookBeforeStable();
if (!force) {
- const result = await this._waitForDisplayedAtStablePositionAndEnabled(progress);
+ const result = await this._waitForDisplayedAtStablePosition(progress, true /* waitForEnabled */);
if (result !== 'done')
return result;
}
@@ -636,15 +636,21 @@ export class ElementHandle extends js.JSHandle {
return result;
}
- async _waitForDisplayedAtStablePositionAndEnabled(progress: Progress): Promise<'error:notconnected' | 'done'> {
- progress.logger.info(' waiting for element to be visible, enabled and not moving');
+ async _waitForDisplayedAtStablePosition(progress: Progress, waitForEnabled: boolean): Promise<'error:notconnected' | 'done'> {
+ if (waitForEnabled)
+ progress.logger.info(` waiting for element to be visible, enabled and not moving`);
+ else
+ progress.logger.info(` waiting for element to be visible and not moving`);
const rafCount = this._page._delegate.rafCountForStablePosition();
- const poll = this._evaluateHandleInUtility(([injected, node, rafCount]) => {
- return injected.waitForDisplayedAtStablePositionAndEnabled(node, rafCount);
- }, rafCount);
+ const poll = this._evaluateHandleInUtility(([injected, node, { rafCount, waitForEnabled }]) => {
+ return injected.waitForDisplayedAtStablePosition(node, rafCount, waitForEnabled);
+ }, { rafCount, waitForEnabled });
const pollHandler = new InjectedScriptPollHandler(progress, await poll);
const result = await pollHandler.finish();
- progress.logger.info(' element is visible, enabled and does not move');
+ if (waitForEnabled)
+ progress.logger.info(' element is visible, enabled and does not move');
+ else
+ progress.logger.info(' element is visible and does not move');
return result;
}
diff --git a/src/injected/injectedScript.ts b/src/injected/injectedScript.ts
index e6756eef68cbb..6ed217b1cda9b 100644
--- a/src/injected/injectedScript.ts
+++ b/src/injected/injectedScript.ts
@@ -422,7 +422,7 @@ export default class InjectedScript {
input.dispatchEvent(new Event('change', { 'bubbles': true }));
}
- waitForDisplayedAtStablePositionAndEnabled(node: Node, rafCount: number): types.InjectedScriptPoll<'error:notconnected' | 'done'> {
+ waitForDisplayedAtStablePosition(node: Node, rafCount: number, waitForEnabled: boolean): types.InjectedScriptPoll<'error:notconnected' | 'done'> {
let lastRect: types.Rect | undefined;
let counter = 0;
let samePositionCounter = 0;
@@ -464,7 +464,7 @@ export default class InjectedScript {
const isVisible = !!style && style.visibility !== 'hidden';
const elementOrButton = element.closest('button, [role=button]') || element;
- const isDisabled = ['BUTTON', 'INPUT', 'SELECT'].includes(elementOrButton.nodeName) && elementOrButton.hasAttribute('disabled');
+ const isDisabled = waitForEnabled && ['BUTTON', 'INPUT', 'SELECT'].includes(elementOrButton.nodeName) && elementOrButton.hasAttribute('disabled');
if (isDisplayed && isStable && isVisible && !isDisabled)
return 'done';
diff --git a/test/assets/grid.html b/test/assets/grid.html
index 0bdbb1220e5a1..571d5c797efee 100644
--- a/test/assets/grid.html
+++ b/test/assets/grid.html
@@ -49,4 +49,14 @@
::-webkit-scrollbar {
display: none;
}
+
+@keyframes move {
+ from { left: -500px; background-color: cyan; }
+ to { left: 0; background-color: rgb(255, 210, 204); }
+}
+.box.animation {
+ position: relative;
+ animation: 2s linear 0s move forwards;
+}
+
diff --git a/test/elementhandle.jest.js b/test/elementhandle.jest.js
index f58482df54e74..6db643dd17cf3 100644
--- a/test/elementhandle.jest.js
+++ b/test/elementhandle.jest.js
@@ -361,6 +361,21 @@ describe('ElementHandle.scrollIntoViewIfNeeded', function() {
await page.setContent('Hello
');
await testWaiting(page, div => div.parentElement.style.display = 'block');
});
+ it('should wait for element to stop moving', async({page, server}) => {
+ await page.setContent(`
+
+ moving
+ `);
+ await testWaiting(page, div => div.classList.remove('animated'));
+ });
it('should timeout waiting for visible', async({page, server}) => {
await page.setContent('Hello
');
diff --git a/test/screenshot.jest.js b/test/screenshot.jest.js
index ca8c6c1f47d08..17c3a9809d06a 100644
--- a/test/screenshot.jest.js
+++ b/test/screenshot.jest.js
@@ -537,4 +537,19 @@ describe.skip(ffheadful)('ElementHandle.screenshot', function() {
await utils.verifyViewport(page, 350, 360);
await context.close();
});
+ it('should wait for element to stop moving', async({page, server}) => {
+ await page.setViewportSize({ width: 500, height: 500 });
+ await page.goto(server.PREFIX + '/grid.html');
+ const elementHandle = await page.$('.box:nth-of-type(3)');
+ await elementHandle.evaluate(e => e.classList.add('animation'));
+ const screenshot = await elementHandle.screenshot();
+ expect(screenshot).toBeGolden('screenshot-element-bounding-box.png');
+ });
+ it('should take screenshot of disabled button', async({page}) => {
+ await page.setViewportSize({ width: 500, height: 500 });
+ await page.setContent(``);
+ const button = await page.$('button');
+ const screenshot = await button.screenshot();
+ expect(screenshot).toBeInstanceOf(Buffer);
+ });
});