api: add waitForElementState('disabled') (#3537)
Allows waiting for the element to be disabled.
This commit is contained in:
parent
0a22e2758a
commit
18292325b6
|
|
@ -3113,7 +3113,7 @@ 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])
|
#### elementHandle.waitForElementState(state[, options])
|
||||||
- `state` <"visible"|"hidden"|"stable"|"enabled"> A state to wait for, see below for more details.
|
- `state` <"visible"|"hidden"|"stable"|"enabled"|"disabled"> A state to wait for, see below for more details.
|
||||||
- `options` <[Object]>
|
- `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.
|
- `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`.
|
- returns: <[Promise]> Promise that resolves when the element satisfies the `state`.
|
||||||
|
|
@ -3123,6 +3123,7 @@ Depending on the `state` parameter, this method waits for one of the [actionabil
|
||||||
- `"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.
|
- `"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).
|
- `"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).
|
- `"enabled"` Wait until the element is [enabled](./actionability.md#enabled).
|
||||||
|
- `"disabled"` Wait until the element is [not enabled](./actionability.md#enabled).
|
||||||
|
|
||||||
If the element does not satisfy the condition for the `timeout` milliseconds, this method will throw.
|
If the element does not satisfy the condition for the `timeout` milliseconds, this method will throw.
|
||||||
|
|
||||||
|
|
|
||||||
11
src/dom.ts
11
src/dom.ts
|
|
@ -602,8 +602,9 @@ 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> {
|
async waitForElementState(state: 'visible' | 'hidden' | 'stable' | 'enabled' | 'disabled', options: types.TimeoutOptions = {}): Promise<void> {
|
||||||
return this._page._runAbortableTask(async progress => {
|
return this._page._runAbortableTask(async progress => {
|
||||||
|
progress.log(` waiting for element to be ${state}`);
|
||||||
if (state === 'visible') {
|
if (state === 'visible') {
|
||||||
const poll = await this._evaluateHandleInUtility(([injected, node]) => {
|
const poll = await this._evaluateHandleInUtility(([injected, node]) => {
|
||||||
return injected.waitForNodeVisible(node);
|
return injected.waitForNodeVisible(node);
|
||||||
|
|
@ -628,6 +629,14 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||||
assertDone(throwRetargetableDOMError(await pollHandler.finish()));
|
assertDone(throwRetargetableDOMError(await pollHandler.finish()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (state === 'disabled') {
|
||||||
|
const poll = await this._evaluateHandleInUtility(([injected, node]) => {
|
||||||
|
return injected.waitForNodeDisabled(node);
|
||||||
|
}, {});
|
||||||
|
const pollHandler = new InjectedScriptPollHandler(progress, poll);
|
||||||
|
assertDone(throwRetargetableDOMError(await pollHandler.finish()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (state === 'stable') {
|
if (state === 'stable') {
|
||||||
const rafCount = this._page._delegate.rafCountForStablePosition();
|
const rafCount = this._page._delegate.rafCountForStablePosition();
|
||||||
const poll = await this._evaluateHandleInUtility(([injected, node, rafCount]) => {
|
const poll = await this._evaluateHandleInUtility(([injected, node, rafCount]) => {
|
||||||
|
|
|
||||||
|
|
@ -383,6 +383,19 @@ export default class InjectedScript {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
waitForNodeDisabled(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 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';
|
||||||
|
|
|
||||||
|
|
@ -1914,7 +1914,7 @@ export type ElementHandleUncheckOptions = {
|
||||||
};
|
};
|
||||||
export type ElementHandleUncheckResult = void;
|
export type ElementHandleUncheckResult = void;
|
||||||
export type ElementHandleWaitForElementStateParams = {
|
export type ElementHandleWaitForElementStateParams = {
|
||||||
state: 'visible' | 'hidden' | 'stable' | 'enabled',
|
state: 'visible' | 'hidden' | 'stable' | 'enabled' | 'disabled',
|
||||||
timeout?: number,
|
timeout?: number,
|
||||||
};
|
};
|
||||||
export type ElementHandleWaitForElementStateOptions = {
|
export type ElementHandleWaitForElementStateOptions = {
|
||||||
|
|
|
||||||
|
|
@ -221,7 +221,7 @@ export class ElementHandle<T extends Node = Node> extends JSHandle<T> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForElementState(state: 'visible' | 'hidden' | 'stable' | 'enabled', options: ElementHandleWaitForElementStateOptions = {}): Promise<void> {
|
async waitForElementState(state: 'visible' | 'hidden' | 'stable' | 'enabled' | 'disabled', options: ElementHandleWaitForElementStateOptions = {}): Promise<void> {
|
||||||
return this._wrapApiCall('elementHandle.waitForElementState', async () => {
|
return this._wrapApiCall('elementHandle.waitForElementState', async () => {
|
||||||
return await this._elementChannel.waitForElementState({ state, ...options });
|
return await this._elementChannel.waitForElementState({ state, ...options });
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1585,6 +1585,7 @@ ElementHandle:
|
||||||
- hidden
|
- hidden
|
||||||
- stable
|
- stable
|
||||||
- enabled
|
- enabled
|
||||||
|
- disabled
|
||||||
timeout: number?
|
timeout: number?
|
||||||
|
|
||||||
waitForSelector:
|
waitForSelector:
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,7 @@ 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> {
|
async waitForElementState(params: { state: 'visible' | 'hidden' | 'stable' | 'enabled' | 'disabled' } & types.TimeoutOptions): Promise<void> {
|
||||||
await this._elementHandle.waitForElementState(params.state, params);
|
await this._elementHandle.waitForElementState(params.state, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -759,7 +759,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||||
timeout: tOptional(tNumber),
|
timeout: tOptional(tNumber),
|
||||||
});
|
});
|
||||||
scheme.ElementHandleWaitForElementStateParams = tObject({
|
scheme.ElementHandleWaitForElementStateParams = tObject({
|
||||||
state: tEnum(['visible', 'hidden', 'stable', 'enabled']),
|
state: tEnum(['visible', 'hidden', 'stable', 'enabled', 'disabled']),
|
||||||
timeout: tOptional(tNumber),
|
timeout: tOptional(tNumber),
|
||||||
});
|
});
|
||||||
scheme.ElementHandleWaitForSelectorParams = tObject({
|
scheme.ElementHandleWaitForSelectorParams = tObject({
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,17 @@ it('should throw waiting for enabled when detached', async ({ page }) => {
|
||||||
expect(error.message).toContain('Element is not attached to the DOM');
|
expect(error.message).toContain('Element is not attached to the DOM');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should wait for disabled button', async({page}) => {
|
||||||
|
await page.setContent('<button><span>Target</span></button>');
|
||||||
|
const span = await page.$('text=Target');
|
||||||
|
let done = false;
|
||||||
|
const promise = span.waitForElementState('disabled').then(() => done = true);
|
||||||
|
await giveItAChanceToResolve(page);
|
||||||
|
expect(done).toBe(false);
|
||||||
|
await span.evaluate(span => (span.parentElement as HTMLButtonElement).disabled = true);
|
||||||
|
await promise;
|
||||||
|
});
|
||||||
|
|
||||||
it('should wait for stable position', async({page, server}) => {
|
it('should wait for stable position', async({page, server}) => {
|
||||||
await page.goto(server.PREFIX + '/input/button.html');
|
await page.goto(server.PREFIX + '/input/button.html');
|
||||||
const button = await page.$('button');
|
const button = await page.$('button');
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue