api: ElementHandle.waitForElementState (#3501)
This method waits for visible, hidden, stable or enabled state, similar to the actionability checks performed before actions. This gives a bit more control to the user. Some examples: - Allows to wait for something to be stable before taking a screenshot. - Allows to wait for the element to be hidden/detached after a specific action.
This commit is contained in:
parent
58fc6b4003
commit
0e9793c452
18
docs/api.md
18
docs/api.md
|
|
@ -2748,6 +2748,7 @@ ElementHandle instances can be used as an argument in [`page.$eval()`](#pageeval
|
||||||
- [elementHandle.toString()](#elementhandletostring)
|
- [elementHandle.toString()](#elementhandletostring)
|
||||||
- [elementHandle.type(text[, options])](#elementhandletypetext-options)
|
- [elementHandle.type(text[, options])](#elementhandletypetext-options)
|
||||||
- [elementHandle.uncheck([options])](#elementhandleuncheckoptions)
|
- [elementHandle.uncheck([options])](#elementhandleuncheckoptions)
|
||||||
|
- [elementHandle.waitForElementState(state[, options])](#elementhandlewaitforelementstatestate-options)
|
||||||
- [elementHandle.waitForSelector(selector[, options])](#elementhandlewaitforselectorselector-options)
|
- [elementHandle.waitForSelector(selector[, options])](#elementhandlewaitforselectorselector-options)
|
||||||
<!-- GEN:stop -->
|
<!-- GEN:stop -->
|
||||||
<!-- GEN:toc-extends-JSHandle -->
|
<!-- GEN:toc-extends-JSHandle -->
|
||||||
|
|
@ -3111,6 +3112,21 @@ If the element is detached from the DOM at any moment during the action, this me
|
||||||
|
|
||||||
When all steps combined have not finished during the specified `timeout`, this method rejects with a [TimeoutError]. Passing zero timeout disables this.
|
When all steps combined have not finished during the specified `timeout`, this method rejects with a [TimeoutError]. Passing zero timeout disables this.
|
||||||
|
|
||||||
|
#### elementHandle.waitForElementState(state[, options])
|
||||||
|
- `state` <"visible"|"hidden"|"stable"|"enabled"> A state to wait for, see below for more details.
|
||||||
|
- `options` <[Object]>
|
||||||
|
- `timeout` <[number]> Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [browserContext.setDefaultTimeout(timeout)](#browsercontextsetdefaulttimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods.
|
||||||
|
- returns: <[Promise]> Promise that resolves when the element satisfies the `state`.
|
||||||
|
|
||||||
|
Depending on the `state` parameter, this method waits for one of the [actionability](./actionability.md) checks to pass. This method throws when the element is detached while waiting, unless waiting for the `"hidden"` state.
|
||||||
|
- `"visible"` Wait until the element is [visible](./actionability.md#visible).
|
||||||
|
- `"hidden"` Wait until the element is [not visible](./actionability.md#visible) or [not attached](./actionability.md#attached). Note that waiting for hidden does not throw when the element detaches.
|
||||||
|
- `"stable"` Wait until the element is both [visible](./actionability.md#visible) and [stable](./actionability.md#stable).
|
||||||
|
- `"enabled"` Wait until the element is [enabled](./actionability.md#enabled).
|
||||||
|
|
||||||
|
If the element does not satisfy the condition for the `timeout` milliseconds, this method will throw.
|
||||||
|
|
||||||
|
|
||||||
#### elementHandle.waitForSelector(selector[, options])
|
#### elementHandle.waitForSelector(selector[, options])
|
||||||
- `selector` <[string]> A selector of an element to wait for, relative to the element handle. See [working with selectors](#working-with-selectors) for more details.
|
- `selector` <[string]> A selector of an element to wait for, relative to the element handle. See [working with selectors](#working-with-selectors) for more details.
|
||||||
- `options` <[Object]>
|
- `options` <[Object]>
|
||||||
|
|
@ -3131,7 +3147,7 @@ const div = await page.$('div');
|
||||||
const span = await div.waitForSelector('span', { state: 'attached' });
|
const span = await div.waitForSelector('span', { state: 'attached' });
|
||||||
```
|
```
|
||||||
|
|
||||||
> **NOTE** This method works does not work across navigations, use [page.waitForSelector(selector[, options])](#pagewaitforselectorselector-options) instead.
|
> **NOTE** This method does not work across navigations, use [page.waitForSelector(selector[, options])](#pagewaitforselectorselector-options) instead.
|
||||||
|
|
||||||
### class: JSHandle
|
### class: JSHandle
|
||||||
|
|
||||||
|
|
|
||||||
47
src/dom.ts
47
src/dom.ts
|
|
@ -226,14 +226,6 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||||
this._page._timeoutSettings.timeout(options));
|
this._page._timeoutSettings.timeout(options));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _waitForVisible(progress: Progress): Promise<'error:notconnected' | 'done'> {
|
|
||||||
const poll = await this._evaluateHandleInUtility(([injected, node]) => {
|
|
||||||
return injected.waitForNodeVisible(node);
|
|
||||||
}, {});
|
|
||||||
const pollHandler = new InjectedScriptPollHandler(progress, poll);
|
|
||||||
return throwFatalDOMError(await pollHandler.finish());
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _clickablePoint(): Promise<types.Point | 'error:notvisible' | 'error:notinviewport'> {
|
private async _clickablePoint(): Promise<types.Point | 'error:notvisible' | 'error:notinviewport'> {
|
||||||
const intersectQuadWithViewport = (quad: types.Quad): types.Quad => {
|
const intersectQuadWithViewport = (quad: types.Quad): types.Quad => {
|
||||||
return quad.map(point => ({
|
return quad.map(point => ({
|
||||||
|
|
@ -623,6 +615,45 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async waitForElementState(state: 'visible' | 'hidden' | 'stable' | 'enabled', options: types.TimeoutOptions = {}): Promise<void> {
|
||||||
|
return this._page._runAbortableTask(async progress => {
|
||||||
|
if (state === 'visible') {
|
||||||
|
const poll = await this._evaluateHandleInUtility(([injected, node]) => {
|
||||||
|
return injected.waitForNodeVisible(node);
|
||||||
|
}, {});
|
||||||
|
const pollHandler = new InjectedScriptPollHandler(progress, poll);
|
||||||
|
assertDone(throwRetargetableDOMError(await pollHandler.finish()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (state === 'hidden') {
|
||||||
|
const poll = await this._evaluateHandleInUtility(([injected, node]) => {
|
||||||
|
return injected.waitForNodeHidden(node);
|
||||||
|
}, {});
|
||||||
|
const pollHandler = new InjectedScriptPollHandler(progress, poll);
|
||||||
|
assertDone(await pollHandler.finish());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (state === 'enabled') {
|
||||||
|
const poll = await this._evaluateHandleInUtility(([injected, node]) => {
|
||||||
|
return injected.waitForNodeEnabled(node);
|
||||||
|
}, {});
|
||||||
|
const pollHandler = new InjectedScriptPollHandler(progress, poll);
|
||||||
|
assertDone(throwRetargetableDOMError(await pollHandler.finish()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (state === 'stable') {
|
||||||
|
const rafCount = this._page._delegate.rafCountForStablePosition();
|
||||||
|
const poll = await this._evaluateHandleInUtility(([injected, node, rafCount]) => {
|
||||||
|
return injected.waitForDisplayedAtStablePosition(node, rafCount, false /* waitForEnabled */);
|
||||||
|
}, rafCount);
|
||||||
|
const pollHandler = new InjectedScriptPollHandler(progress, poll);
|
||||||
|
assertDone(throwRetargetableDOMError(await pollHandler.finish()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new Error(`state: expected one of (visible|hidden|stable|enabled)`);
|
||||||
|
}, this._page._timeoutSettings.timeout(options));
|
||||||
|
}
|
||||||
|
|
||||||
async waitForSelector(selector: string, options: types.WaitForElementOptions = {}): Promise<ElementHandle<Element> | null> {
|
async waitForSelector(selector: string, options: types.WaitForElementOptions = {}): Promise<ElementHandle<Element> | null> {
|
||||||
const { state = 'visible' } = options;
|
const { state = 'visible' } = options;
|
||||||
if (!['attached', 'detached', 'visible', 'hidden'].includes(state))
|
if (!['attached', 'detached', 'visible', 'hidden'].includes(state))
|
||||||
|
|
|
||||||
|
|
@ -357,6 +357,32 @@ export default class InjectedScript {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
waitForNodeHidden(node: Node): types.InjectedScriptPoll<'done'> {
|
||||||
|
return this.pollRaf((progress, continuePolling) => {
|
||||||
|
const element = node.nodeType === Node.ELEMENT_NODE ? node as Element : node.parentElement;
|
||||||
|
if (!node.isConnected || !element)
|
||||||
|
return 'done';
|
||||||
|
if (this.isVisible(element)) {
|
||||||
|
progress.logRepeating(' element is visible - waiting...');
|
||||||
|
return continuePolling;
|
||||||
|
}
|
||||||
|
return 'done';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
waitForNodeEnabled(node: Node): types.InjectedScriptPoll<'error:notconnected' | 'done'> {
|
||||||
|
return this.pollRaf((progress, continuePolling) => {
|
||||||
|
const element = node.nodeType === Node.ELEMENT_NODE ? node as Element : node.parentElement;
|
||||||
|
if (!node.isConnected || !element)
|
||||||
|
return 'error:notconnected';
|
||||||
|
if (this._isElementDisabled(element)) {
|
||||||
|
progress.logRepeating(' element is not enabled - waiting...');
|
||||||
|
return continuePolling;
|
||||||
|
}
|
||||||
|
return 'done';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
focusNode(node: Node, resetSelectionIfNotFocused?: boolean): FatalDOMError | 'error:notconnected' | 'done' {
|
focusNode(node: Node, resetSelectionIfNotFocused?: boolean): FatalDOMError | 'error:notconnected' | 'done' {
|
||||||
if (!node.isConnected)
|
if (!node.isConnected)
|
||||||
return 'error:notconnected';
|
return 'error:notconnected';
|
||||||
|
|
@ -463,8 +489,7 @@ export default class InjectedScript {
|
||||||
const style = element.ownerDocument && element.ownerDocument.defaultView ? element.ownerDocument.defaultView.getComputedStyle(element) : undefined;
|
const style = element.ownerDocument && element.ownerDocument.defaultView ? element.ownerDocument.defaultView.getComputedStyle(element) : undefined;
|
||||||
const isVisible = !!style && style.visibility !== 'hidden';
|
const isVisible = !!style && style.visibility !== 'hidden';
|
||||||
|
|
||||||
const elementOrButton = element.closest('button, [role=button]') || element;
|
const isDisabled = waitForEnabled && this._isElementDisabled(element);
|
||||||
const isDisabled = waitForEnabled && ['BUTTON', 'INPUT', 'SELECT'].includes(elementOrButton.nodeName) && elementOrButton.hasAttribute('disabled');
|
|
||||||
|
|
||||||
if (isDisplayed && isStable && isVisible && !isDisabled)
|
if (isDisplayed && isStable && isVisible && !isDisabled)
|
||||||
return 'done';
|
return 'done';
|
||||||
|
|
@ -526,6 +551,11 @@ export default class InjectedScript {
|
||||||
node.dispatchEvent(event);
|
node.dispatchEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _isElementDisabled(element: Element): boolean {
|
||||||
|
const elementOrButton = element.closest('button, [role=button]') || element;
|
||||||
|
return ['BUTTON', 'INPUT', 'SELECT'].includes(elementOrButton.nodeName) && elementOrButton.hasAttribute('disabled');
|
||||||
|
}
|
||||||
|
|
||||||
private _parentElementOrShadowHost(element: Element): Element | undefined {
|
private _parentElementOrShadowHost(element: Element): Element | undefined {
|
||||||
if (element.parentElement)
|
if (element.parentElement)
|
||||||
return element.parentElement;
|
return element.parentElement;
|
||||||
|
|
|
||||||
|
|
@ -1620,6 +1620,7 @@ export interface ElementHandleChannel extends JSHandleChannel {
|
||||||
textContent(params?: ElementHandleTextContentParams): Promise<ElementHandleTextContentResult>;
|
textContent(params?: ElementHandleTextContentParams): Promise<ElementHandleTextContentResult>;
|
||||||
type(params: ElementHandleTypeParams): Promise<ElementHandleTypeResult>;
|
type(params: ElementHandleTypeParams): Promise<ElementHandleTypeResult>;
|
||||||
uncheck(params: ElementHandleUncheckParams): Promise<ElementHandleUncheckResult>;
|
uncheck(params: ElementHandleUncheckParams): Promise<ElementHandleUncheckResult>;
|
||||||
|
waitForElementState(params: ElementHandleWaitForElementStateParams): Promise<ElementHandleWaitForElementStateResult>;
|
||||||
waitForSelector(params: ElementHandleWaitForSelectorParams): Promise<ElementHandleWaitForSelectorResult>;
|
waitForSelector(params: ElementHandleWaitForSelectorParams): Promise<ElementHandleWaitForSelectorResult>;
|
||||||
}
|
}
|
||||||
export type ElementHandleEvalOnSelectorParams = {
|
export type ElementHandleEvalOnSelectorParams = {
|
||||||
|
|
@ -1914,6 +1915,14 @@ export type ElementHandleUncheckOptions = {
|
||||||
timeout?: number,
|
timeout?: number,
|
||||||
};
|
};
|
||||||
export type ElementHandleUncheckResult = void;
|
export type ElementHandleUncheckResult = void;
|
||||||
|
export type ElementHandleWaitForElementStateParams = {
|
||||||
|
state: 'visible' | 'hidden' | 'stable' | 'enabled',
|
||||||
|
timeout?: number,
|
||||||
|
};
|
||||||
|
export type ElementHandleWaitForElementStateOptions = {
|
||||||
|
timeout?: number,
|
||||||
|
};
|
||||||
|
export type ElementHandleWaitForElementStateResult = void;
|
||||||
export type ElementHandleWaitForSelectorParams = {
|
export type ElementHandleWaitForSelectorParams = {
|
||||||
selector: string,
|
selector: string,
|
||||||
timeout?: number,
|
timeout?: number,
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ElementHandleChannel, JSHandleInitializer, ElementHandleScrollIntoViewIfNeededOptions, ElementHandleHoverOptions, ElementHandleClickOptions, ElementHandleDblclickOptions, ElementHandleFillOptions, ElementHandleSetInputFilesOptions, ElementHandlePressOptions, ElementHandleCheckOptions, ElementHandleUncheckOptions, ElementHandleScreenshotOptions, ElementHandleTypeOptions, ElementHandleSelectTextOptions, ElementHandleWaitForSelectorOptions } from '../channels';
|
import { ElementHandleChannel, JSHandleInitializer, ElementHandleScrollIntoViewIfNeededOptions, ElementHandleHoverOptions, ElementHandleClickOptions, ElementHandleDblclickOptions, ElementHandleFillOptions, ElementHandleSetInputFilesOptions, ElementHandlePressOptions, ElementHandleCheckOptions, ElementHandleUncheckOptions, ElementHandleScreenshotOptions, ElementHandleTypeOptions, ElementHandleSelectTextOptions, ElementHandleWaitForSelectorOptions, ElementHandleWaitForElementStateOptions } from '../channels';
|
||||||
import { Frame } from './frame';
|
import { Frame } from './frame';
|
||||||
import { FuncOn, JSHandle, serializeArgument, parseResult } from './jsHandle';
|
import { FuncOn, JSHandle, serializeArgument, parseResult } from './jsHandle';
|
||||||
import { ChannelOwner } from './channelOwner';
|
import { ChannelOwner } from './channelOwner';
|
||||||
|
|
@ -209,6 +209,12 @@ export class ElementHandle<T extends Node = Node> extends JSHandle<T> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async waitForElementState(state: 'visible' | 'hidden' | 'stable' | 'enabled', options: ElementHandleWaitForElementStateOptions = {}): Promise<void> {
|
||||||
|
return this._wrapApiCall('elementHandle.waitForElementState', async () => {
|
||||||
|
return await this._elementChannel.waitForElementState({ state, ...options });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async waitForSelector(selector: string, options: ElementHandleWaitForSelectorOptions = {}): Promise<ElementHandle<Element> | null> {
|
async waitForSelector(selector: string, options: ElementHandleWaitForSelectorOptions = {}): Promise<ElementHandle<Element> | null> {
|
||||||
return this._wrapApiCall('elementHandle.waitForSelector', async () => {
|
return this._wrapApiCall('elementHandle.waitForSelector', async () => {
|
||||||
const result = await this._elementChannel.waitForSelector({ selector, ...options });
|
const result = await this._elementChannel.waitForSelector({ selector, ...options });
|
||||||
|
|
|
||||||
|
|
@ -1577,6 +1577,17 @@ ElementHandle:
|
||||||
noWaitAfter: boolean?
|
noWaitAfter: boolean?
|
||||||
timeout: number?
|
timeout: number?
|
||||||
|
|
||||||
|
waitForElementState:
|
||||||
|
parameters:
|
||||||
|
state:
|
||||||
|
type: enum
|
||||||
|
literals:
|
||||||
|
- visible
|
||||||
|
- hidden
|
||||||
|
- stable
|
||||||
|
- enabled
|
||||||
|
timeout: number?
|
||||||
|
|
||||||
waitForSelector:
|
waitForSelector:
|
||||||
parameters:
|
parameters:
|
||||||
selector: string
|
selector: string
|
||||||
|
|
|
||||||
|
|
@ -149,6 +149,10 @@ export class ElementHandleDispatcher extends JSHandleDispatcher implements Eleme
|
||||||
return { value: serializeResult(await this._elementHandle._$$evalExpression(params.selector, params.expression, params.isFunction, parseArgument(params.arg))) };
|
return { value: serializeResult(await this._elementHandle._$$evalExpression(params.selector, params.expression, params.isFunction, parseArgument(params.arg))) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async waitForElementState(params: { state: 'visible' | 'hidden' | 'stable' | 'enabled' } & types.TimeoutOptions): Promise<void> {
|
||||||
|
await this._elementHandle.waitForElementState(params.state, params);
|
||||||
|
}
|
||||||
|
|
||||||
async waitForSelector(params: { selector: string } & types.WaitForElementOptions): Promise<{ element?: ElementHandleChannel }> {
|
async waitForSelector(params: { selector: string } & types.WaitForElementOptions): Promise<{ element?: ElementHandleChannel }> {
|
||||||
return { element: ElementHandleDispatcher.createNullable(this._scope, await this._elementHandle.waitForSelector(params.selector, params)) };
|
return { element: ElementHandleDispatcher.createNullable(this._scope, await this._elementHandle.waitForSelector(params.selector, params)) };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -759,6 +759,10 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||||
noWaitAfter: tOptional(tBoolean),
|
noWaitAfter: tOptional(tBoolean),
|
||||||
timeout: tOptional(tNumber),
|
timeout: tOptional(tNumber),
|
||||||
});
|
});
|
||||||
|
scheme.ElementHandleWaitForElementStateParams = tObject({
|
||||||
|
state: tEnum(['visible', 'hidden', 'stable', 'enabled']),
|
||||||
|
timeout: tOptional(tNumber),
|
||||||
|
});
|
||||||
scheme.ElementHandleWaitForSelectorParams = tObject({
|
scheme.ElementHandleWaitForSelectorParams = tObject({
|
||||||
selector: tString,
|
selector: tString,
|
||||||
timeout: tOptional(tNumber),
|
timeout: tOptional(tNumber),
|
||||||
|
|
|
||||||
118
test/elementhandle-wait-for-element-state.spec.ts
Normal file
118
test/elementhandle-wait-for-element-state.spec.ts
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2018 Google Inc. All rights reserved.
|
||||||
|
* Modifications copyright (c) Microsoft Corporation.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
import './base.fixture';
|
||||||
|
|
||||||
|
async function giveItAChanceToResolve(page) {
|
||||||
|
for (let i = 0; i < 5; i++)
|
||||||
|
await page.evaluate(() => new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f))));
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should wait for visible', async ({ page }) => {
|
||||||
|
await page.setContent(`<div style='display:none'>content</div>`);
|
||||||
|
const div = await page.$('div');
|
||||||
|
let done = false;
|
||||||
|
const promise = div.waitForElementState('visible').then(() => done = true);
|
||||||
|
await giveItAChanceToResolve(page);
|
||||||
|
expect(done).toBe(false);
|
||||||
|
await div.evaluate(div => div.style.display = 'block');
|
||||||
|
await promise;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should wait for already visible', async ({ page }) => {
|
||||||
|
await page.setContent(`<div>content</div>`);
|
||||||
|
const div = await page.$('div');
|
||||||
|
await div.waitForElementState('visible');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should timeout waiting for visible', async ({ page }) => {
|
||||||
|
await page.setContent(`<div style='display:none'>content</div>`);
|
||||||
|
const div = await page.$('div');
|
||||||
|
const error = await div.waitForElementState('visible', { timeout: 1000 }).catch(e => e);
|
||||||
|
expect(error.message).toContain('Timeout 1000ms exceeded');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw waiting for visible when detached', async ({ page }) => {
|
||||||
|
await page.setContent(`<div style='display:none'>content</div>`);
|
||||||
|
const div = await page.$('div');
|
||||||
|
const promise = div.waitForElementState('visible').catch(e => e);
|
||||||
|
await div.evaluate(div => div.remove());
|
||||||
|
const error = await promise;
|
||||||
|
expect(error.message).toContain('Element is not attached to the DOM');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should wait for hidden', async ({ page }) => {
|
||||||
|
await page.setContent(`<div>content</div>`);
|
||||||
|
const div = await page.$('div');
|
||||||
|
let done = false;
|
||||||
|
const promise = div.waitForElementState('hidden').then(() => done = true);
|
||||||
|
await giveItAChanceToResolve(page);
|
||||||
|
expect(done).toBe(false);
|
||||||
|
await div.evaluate(div => div.style.display = 'none');
|
||||||
|
await promise;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should wait for already hidden', async ({ page }) => {
|
||||||
|
await page.setContent(`<div></div>`);
|
||||||
|
const div = await page.$('div');
|
||||||
|
await div.waitForElementState('hidden');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should wait for hidden when detached', async ({ page }) => {
|
||||||
|
await page.setContent(`<div>content</div>`);
|
||||||
|
const div = await page.$('div');
|
||||||
|
let done = false;
|
||||||
|
const promise = div.waitForElementState('hidden').then(() => done = true);
|
||||||
|
await giveItAChanceToResolve(page);
|
||||||
|
expect(done).toBe(false);
|
||||||
|
await div.evaluate(div => div.remove());
|
||||||
|
await promise;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should wait for enabled button', async({page, server}) => {
|
||||||
|
await page.setContent('<button disabled><span>Target</span></button>');
|
||||||
|
const span = await page.$('text=Target');
|
||||||
|
let done = false;
|
||||||
|
const promise = span.waitForElementState('enabled').then(() => done = true);
|
||||||
|
await giveItAChanceToResolve(page);
|
||||||
|
expect(done).toBe(false);
|
||||||
|
await span.evaluate(span => (span.parentElement as HTMLButtonElement).disabled = false);
|
||||||
|
await promise;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw waiting for enabled when detached', async ({ page }) => {
|
||||||
|
await page.setContent(`<button disabled>Target</button>`);
|
||||||
|
const button = await page.$('button');
|
||||||
|
const promise = button.waitForElementState('enabled').catch(e => e);
|
||||||
|
await button.evaluate(button => button.remove());
|
||||||
|
const error = await promise;
|
||||||
|
expect(error.message).toContain('Element is not attached to the DOM');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should wait for stable position', async({page, server}) => {
|
||||||
|
await page.goto(server.PREFIX + '/input/button.html');
|
||||||
|
const button = await page.$('button');
|
||||||
|
await page.$eval('button', button => {
|
||||||
|
button.style.transition = 'margin 10000ms linear 0s';
|
||||||
|
button.style.marginLeft = '20000px';
|
||||||
|
});
|
||||||
|
let done = false;
|
||||||
|
const promise = button.waitForElementState('stable').then(() => done = true);
|
||||||
|
await giveItAChanceToResolve(page);
|
||||||
|
expect(done).toBe(false);
|
||||||
|
await button.evaluate(button => button.style.transition = '');
|
||||||
|
await promise;
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue