feat(api): add clear() (#18296)

Add `clear()` method as a convenience shortcut for `fill('')`.
Implemented for AndroidDevice, ElementHandle, Frame, Locator and Page.

Fixes https://github.com/microsoft/playwright/issues/14041
This commit is contained in:
Jean-François Greffier 2022-10-25 21:56:11 +02:00 committed by GitHub
parent caa9c6a597
commit 0fe1998c72
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 305 additions and 1 deletions

View File

@ -10,6 +10,20 @@
Emitted when a new WebView instance is detected.
## async method: AndroidDevice.clear
* since: v1.28
Clears the specific [`param: selector`] input box.
### param: AndroidDevice.clear.selector
* since: v1.28
- `selector` <[AndroidSelector]>
Selector to clear.
### option: AndroidDevice.clear.timeout = %%-android-timeout-%%
* since: v1.28
## async method: AndroidDevice.close
* since: v1.9

View File

@ -184,6 +184,20 @@ When all steps combined have not finished during the specified [`option: timeout
### option: ElementHandle.check.trial = %%-input-trial-%%
* since: v1.11
## async method: ElementHandle.clear
* since: v1.28
This method waits for [actionability](../actionability.md) checks, focuses the element, clears it and triggers an `input` event after clearing.
If the target element is not an `<input>`, `<textarea>` or `[contenteditable]` element, this method throws an error. However, if the element is inside the `<label>` element that has an associated [control](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control), the control will be cleared instead.
### option: ElementHandle.clear.force = %%-input-force-%%
* since: v1.28
### option: ElementHandle.clear.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.28
### option: ElementHandle.clear.timeout = %%-input-timeout-%%
* since: v1.28
## async method: ElementHandle.click
* since: v1.8

View File

@ -224,6 +224,25 @@ When all steps combined have not finished during the specified [`option: timeout
* since: v1.8
- returns: <[Array]<[Frame]>>
## async method: Frame.clear
* since: v1.28
This method waits for an element matching [`param: selector`], waits for [actionability](../actionability.md) checks, focuses the element, clears it and triggers an `input` event after clearing.
If the target element is not an `<input>`, `<textarea>` or `[contenteditable]` element, this method throws an error. However, if the element is inside the `<label>` element that has an associated [control](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control), the control will be cleared instead.
### param: Frame.clear.selector = %%-input-selector-%%
* since: v1.28
### option: Frame.clear.force = %%-input-force-%%
* since: v1.28
### option: Frame.clear.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.28
### option: Frame.clear.strict = %%-input-strict-%%
* since: v1.28
### option: Frame.clear.timeout = %%-input-timeout-%%
* since: v1.28
## async method: Frame.click
* since: v1.8

View File

@ -103,6 +103,20 @@ When all steps combined have not finished during the specified [`option: timeout
### option: Locator.check.trial = %%-input-trial-%%
* since: v1.14
## async method: Locator.clear
* since: v1.28
This method waits for [actionability](../actionability.md) checks, focuses the element, clears it and triggers an `input` event after clearing.
If the target element is not an `<input>`, `<textarea>` or `[contenteditable]` element, this method throws an error. However, if the element is inside the `<label>` element that has an associated [control](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control), the control will be cleared instead.
### option: Locator.clear.force = %%-input-force-%%
* since: v1.28
### option: Locator.clear.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.28
### option: Locator.clear.timeout = %%-input-timeout-%%
* since: v1.28
## async method: Locator.click
* since: v1.14

View File

@ -737,6 +737,25 @@ Shortcut for main frame's [`method: Frame.check`].
### option: Page.check.trial = %%-input-trial-%%
* since: v1.11
## async method: Page.clear
* since: v1.28
This method waits for an element matching [`param: selector`], waits for [actionability](../actionability.md) checks, focuses the element, clears it and triggers an `input` event after clearing. Note that you can pass an empty string to clear the input field.
If the target element is not an `<input>`, `<textarea>` or `[contenteditable]` element, this method throws an error. However, if the element is inside the `<label>` element that has an associated [control](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control), the control will be cleared instead.
### param: Page.clear.selector = %%-input-selector-%%
* since: v1.28
### option: Page.clear.force = %%-input-force-%%
* since: v1.28
### option: Page.clear.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.28
### option: Page.clear.strict = %%-input-strict-%%
* since: v1.28
### option: Page.clear.timeout = %%-input-timeout-%%
* since: v1.28
## async method: Page.click
* since: v1.8

View File

@ -185,6 +185,10 @@ export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel> i
await this._channel.fill({ selector: toSelectorChannel(selector), text, ...options });
}
async clear(selector: api.AndroidSelector, options?: types.TimeoutOptions) {
await this.fill(selector, '', options);
}
async press(selector: api.AndroidSelector, key: api.AndroidKey, options?: types.TimeoutOptions) {
await this.tap(selector, options);
await this.input.press(key);

View File

@ -142,6 +142,10 @@ export class ElementHandle<T extends Node = Node> extends JSHandle<T> implements
return await this._elementChannel.fill({ value, ...options });
}
async clear(options: channels.ElementHandleFillOptions = {}): Promise<void> {
return this.fill('', options);
}
async selectText(options: channels.ElementHandleSelectTextOptions = {}): Promise<void> {
await this._elementChannel.selectText(options);
}

View File

@ -293,6 +293,10 @@ export class Frame extends ChannelOwner<channels.FrameChannel> implements api.Fr
return await this._channel.fill({ selector, value, ...options });
}
async clear(selector: string, options: channels.FrameFillOptions = {}) {
return this.fill(selector, '', options);
}
async _highlight(selector: string) {
return await this._channel.highlight({ selector });
}

View File

@ -125,6 +125,10 @@ export class Locator implements api.Locator {
return this._frame.fill(this._selector, value, { strict: true, ...options });
}
async clear(options: channels.ElementHandleFillOptions = {}): Promise<void> {
return this.fill('', options);
}
async _highlight() {
// VS Code extension uses this one, keep it for now.
return this._frame._highlight(this._selector);

View File

@ -560,6 +560,10 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
return this._mainFrame.fill(selector, value, options);
}
async clear(selector: string, options?: channels.FrameFillOptions) {
return this.fill(selector, '', options);
}
locator(selector: string, options?: LocatorOptions): Locator {
return this.mainFrame().locator(selector, options);
}

View File

@ -1886,6 +1886,46 @@ export interface Page {
trial?: boolean;
}): Promise<void>;
/**
* This method waits for an element matching `selector`, waits for [actionability](https://playwright.dev/docs/actionability) checks, focuses the
* element, clears it and triggers an `input` event after clearing. Note that you can pass an empty string to clear the
* input field.
*
* If the target element is not an `<input>`, `<textarea>` or `[contenteditable]` element, this method throws an error.
* However, if the element is inside the `<label>` element that has an associated
* [control](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control), the control will be cleared
* instead.
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. See [working with selectors](https://playwright.dev/docs/selectors) for more details.
* @param options
*/
clear(selector: string, options?: {
/**
* Whether to bypass the [actionability](https://playwright.dev/docs/actionability) checks. Defaults to `false`.
*/
force?: boolean;
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to `false`.
*/
noWaitAfter?: boolean;
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
* element, the call throws an exception.
*/
strict?: boolean;
/**
* 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)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout)
* or [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-timeout) methods.
*/
timeout?: number;
}): Promise<void>;
/**
* This method clicks an element matching `selector` by performing the following steps:
* 1. Find an element matching `selector`. If there is none, wait until a matching element is attached to the DOM.
@ -5161,6 +5201,45 @@ export interface Frame {
childFrames(): Array<Frame>;
/**
* This method waits for an element matching `selector`, waits for [actionability](https://playwright.dev/docs/actionability) checks, focuses the
* element, clears it and triggers an `input` event after clearing.
*
* If the target element is not an `<input>`, `<textarea>` or `[contenteditable]` element, this method throws an error.
* However, if the element is inside the `<label>` element that has an associated
* [control](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control), the control will be cleared
* instead.
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. See [working with selectors](https://playwright.dev/docs/selectors) for more details.
* @param options
*/
clear(selector: string, options?: {
/**
* Whether to bypass the [actionability](https://playwright.dev/docs/actionability) checks. Defaults to `false`.
*/
force?: boolean;
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to `false`.
*/
noWaitAfter?: boolean;
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
* element, the call throws an exception.
*/
strict?: boolean;
/**
* 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)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout)
* or [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-timeout) methods.
*/
timeout?: number;
}): Promise<void>;
/**
* This method clicks an element matching `selector` by performing the following steps:
* 1. Find an element matching `selector`. If there is none, wait until a matching element is attached to the DOM.
@ -8484,6 +8563,38 @@ export interface ElementHandle<T=Node> extends JSHandle<T> {
trial?: boolean;
}): Promise<void>;
/**
* This method waits for [actionability](https://playwright.dev/docs/actionability) checks, focuses the element, clears it and triggers an
* `input` event after clearing.
*
* If the target element is not an `<input>`, `<textarea>` or `[contenteditable]` element, this method throws an error.
* However, if the element is inside the `<label>` element that has an associated
* [control](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control), the control will be cleared
* instead.
* @param options
*/
clear(options?: {
/**
* Whether to bypass the [actionability](https://playwright.dev/docs/actionability) checks. Defaults to `false`.
*/
force?: boolean;
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to `false`.
*/
noWaitAfter?: boolean;
/**
* 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)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout)
* or [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-timeout) methods.
*/
timeout?: number;
}): Promise<void>;
/**
* This method clicks the element by performing the following steps:
* 1. Wait for [actionability](https://playwright.dev/docs/actionability) checks on the element, unless `force` option is set.
@ -9606,6 +9717,38 @@ export interface Locator {
trial?: boolean;
}): Promise<void>;
/**
* This method waits for [actionability](https://playwright.dev/docs/actionability) checks, focuses the element, clears it and triggers an
* `input` event after clearing.
*
* If the target element is not an `<input>`, `<textarea>` or `[contenteditable]` element, this method throws an error.
* However, if the element is inside the `<label>` element that has an associated
* [control](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control), the control will be cleared
* instead.
* @param options
*/
clear(options?: {
/**
* Whether to bypass the [actionability](https://playwright.dev/docs/actionability) checks. Defaults to `false`.
*/
force?: boolean;
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to `false`.
*/
noWaitAfter?: boolean;
/**
* 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)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout)
* or [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-timeout) methods.
*/
timeout?: number;
}): Promise<void>;
/**
* This method clicks the element by performing the following steps:
* 1. Wait for [actionability](https://playwright.dev/docs/actionability) checks on the element, unless `force` option is set.
@ -12362,6 +12505,21 @@ export interface AndroidDevice {
*/
prependListener(event: 'webview', listener: (androidWebView: AndroidWebView) => void): this;
/**
* Clears the specific `selector` input box.
* @param selector Selector to clear.
* @param options
*/
clear(selector: AndroidSelector, options?: {
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by
* using the
* [androidDevice.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-androiddevice#android-device-set-default-timeout)
* method.
*/
timeout?: number;
}): Promise<void>;
/**
* Disconnects from the device.
*/

View File

@ -138,6 +138,9 @@ it.describe('slowMo', () => {
it('Frame SlowMo fill', async ({ page, server, toImpl }) => {
await checkFrameSlowMo(toImpl, page, server, frame => frame.fill('.fill', 'foo'));
});
it('Frame SlowMo clear', async ({ page, server, toImpl }) => {
await checkFrameSlowMo(toImpl, page, server, frame => frame.clear('.fill'));
});
it('Frame SlowMo focus', async ({ page, server, toImpl }) => {
await checkFrameSlowMo(toImpl, page, server, frame => frame.focus('button'));
});

View File

@ -47,6 +47,15 @@ it('should fill input when Node is removed', async ({ page, server }) => {
expect(await page.evaluate(() => window['result'])).toBe('some value');
});
it('should clear input', async ({ page, server }) => {
await page.goto(server.PREFIX + '/input/textarea.html');
const handle = await page.$('input');
await handle.fill('some value');
expect(await page.evaluate(() => window['result'])).toBe('some value');
await handle.clear();
expect(await page.evaluate(() => window['result'])).toBe('');
});
it('should check the box', async ({ page }) => {
await page.setContent(`<input id='checkbox' type='checkbox'></input>`);
const input = await page.$('input');

View File

@ -58,6 +58,15 @@ it('should fill input when Node is removed', async ({ page, server }) => {
expect(await page.evaluate(() => window['result'])).toBe('some value');
});
it('should clear input', async ({ page, server }) => {
await page.goto(server.PREFIX + '/input/textarea.html');
const handle = page.locator('input');
await handle.fill('some value');
expect(await page.evaluate(() => window['result'])).toBe('some value');
await handle.clear();
expect(await page.evaluate(() => window['result'])).toBe('');
});
it('should check the box', async ({ page }) => {
await page.setContent(`<input id='checkbox' type='checkbox'></input>`);
const input = page.locator('input');

View File

@ -44,6 +44,16 @@ it('should throw on unsupported inputs', async ({ page, server }) => {
}
});
it('should throw on unsupported inputs when clear()', async ({ page, server }) => {
await page.goto(server.PREFIX + '/input/textarea.html');
for (const type of ['button', 'checkbox', 'file', 'image', 'radio', 'reset', 'submit']) {
await page.$eval('input', (input, type) => input.setAttribute('type', type), type);
let error = null;
await page.clear('input').catch(e => error = e);
expect(error.message).toContain(`input of type "${type}" cannot be filled`);
}
});
it('should fill different input types', async ({ page, server }) => {
await page.goto(server.PREFIX + '/input/textarea.html');
for (const type of ['password', 'search', 'tel', 'text', 'url', 'invalid-type']) {
@ -192,6 +202,13 @@ it('should throw nice error without injected script stack when element is not an
expect(error.message).toContain('page.fill: Error: Element is not an <input>, <textarea> or [contenteditable] element\n=========================== logs');
});
it('should throw nice error without injected script stack when element is not an <input> when clear()', async ({ page, server }) => {
let error = null;
await page.goto(server.PREFIX + '/input/textarea.html');
await page.clear('body').catch(e => error = e);
expect(error.message).toContain('page.clear: Error: Element is not an <input>, <textarea> or [contenteditable] element\n=========================== logs');
});
it('should throw if passed a non-string value', async ({ page, server }) => {
let error = null;
await page.goto(server.PREFIX + '/input/textarea.html');
@ -292,7 +309,7 @@ it('should not be able to fill text into the input[type=number]', async ({ page
expect(error.message).toContain('Cannot type text into input[type=number]');
});
it('should be able to clear', async ({ page, server }) => {
it('should be able to clear using fill()', async ({ page, server }) => {
await page.goto(server.PREFIX + '/input/textarea.html');
await page.fill('input', 'some value');
expect(await page.evaluate(() => window['result'])).toBe('some value');
@ -300,6 +317,14 @@ it('should be able to clear', async ({ page, server }) => {
expect(await page.evaluate(() => window['result'])).toBe('');
});
it('should be able to clear using clear()', async ({ page, server }) => {
await page.goto(server.PREFIX + '/input/textarea.html');
await page.fill('input', 'some value');
expect(await page.evaluate(() => window['result'])).toBe('some value');
await page.clear('input');
expect(await page.evaluate(() => window['result'])).toBe('');
});
it('should not throw when fill causes navigation', async ({ page, server }) => {
await page.goto(server.PREFIX + '/input/textarea.html');
await page.setContent('<input type=date>');