api: remove {page,frame,handle}.type() in favor of .fill()

This commit is contained in:
Dmitry Gozman 2020-02-19 14:09:40 -08:00
parent 33824aa1d7
commit bd7e3f170d
6 changed files with 97 additions and 109 deletions

View file

@ -492,7 +492,7 @@ page.removeListener('request', logRequest);
- [page.evaluateHandle(pageFunction[, ...args])](#pageevaluatehandlepagefunction-args)
- [page.evaluateOnNewDocument(pageFunction[, ...args])](#pageevaluateonnewdocumentpagefunction-args)
- [page.exposeFunction(name, playwrightFunction)](#pageexposefunctionname-playwrightfunction)
- [page.fill(selector, value, options)](#pagefillselector-value-options)
- [page.fill(selector, value[, options])](#pagefillselector-value-options)
- [page.focus(selector, options)](#pagefocusselector-options)
- [page.frames()](#pageframes)
- [page.goBack([options])](#pagegobackoptions)
@ -518,7 +518,6 @@ page.removeListener('request', logRequest);
- [page.setViewportSize(viewportSize)](#pagesetviewportsizeviewportsize)
- [page.title()](#pagetitle)
- [page.tripleclick(selector[, options])](#pagetripleclickselector-options)
- [page.type(selector, text[, options])](#pagetypeselector-text-options)
- [page.uncheck(selector, [options])](#pageuncheckselector-options)
- [page.url()](#pageurl)
- [page.viewportSize()](#pageviewportsize)
@ -1031,20 +1030,33 @@ const fs = require('fs');
})();
```
#### page.fill(selector, value, options)
#### page.fill(selector, value[, options])
- `selector` <[string]> A selector to query page for.
- `value` <[string]> Value to fill for the `<input>`, `<textarea>` or `[contenteditable]` element.
- `options` <[Object]>
- `waitFor` <"visible"|"hidden"|"any"|"nowait"> Wait for element to become visible (`visible`), hidden (`hidden`), present in dom (`any`) or do not wait at all (`nowait`). Defaults to `visible`.
- `clear` <[boolean]> Whether to clear existing value before typing the new one. Defaults to `true`.
- `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 which resolves when the element matching `selector` is successfully filled. The promise will be rejected if there is no element matching `selector`.
This method focuses the element and triggers an `input` event after filling.
```js
// Fill with 'Hello'.
await page.fill('#mytextarea', 'Hello');
// Leave 'Hello' in the text area and add 'World', resulting in 'HelloWorld'.
await page.fill('#mytextarea', 'World', { clear: false });
```
If there's no text `<input>`, `<textarea>` or `[contenteditable]` element matching `selector`, the method throws an error.
> **NOTE** Pass empty string as a value to clear the input field.
> **NOTE** Pass empty string as a value to clear the input field (unless `clear` option is set to false).
Shortcut for [page.mainFrame().fill()](#framefillselector-value)
> **NOTE** To trigger `keydown`, `keypress` or `keyup` events, use [keyboard.type](#keyboardtypetext-options).
> **NOTE** To press a special key, like `Control` or `ArrowDown`, use [keyboard.press](#keyboardpresskey-options).
Shortcut for [page.mainFrame().fill(selector, value[, options])](#framefillselector-value-options)
#### page.focus(selector, options)
- `selector` <[string]> A selector of an element to focus. If there are multiple elements satisfying the selector, the first will be focused.
@ -1406,26 +1418,6 @@ Bear in mind that if the first or second click of the `tripleclick()` triggers a
Shortcut for [page.mainFrame().tripleclick(selector[, options])](#frametripleclickselector-options).
#### page.type(selector, text[, options])
- `selector` <[string]> A selector of an element to type into. If there are multiple elements satisfying the selector, the first will be used.
- `text` <[string]> A text to type into a focused element.
- `options` <[Object]>
- `delay` <[number]> Time to wait between key presses in milliseconds. Defaults to 0.
- `waitFor` <"visible"|"hidden"|"any"|"nowait"> Wait for element to become visible (`visible`), hidden (`hidden`), present in dom (`any`) or do not wait at all (`nowait`). Defaults to `visible`.
- `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]>
Sends a `keydown`, `keypress`/`input`, and `keyup` event for each character in the text.
To press a special key, like `Control` or `ArrowDown`, use [`keyboard.press`](#keyboardpresskey-options).
```js
await page.type('#mytextarea', 'Hello'); // Types instantly
await page.type('#mytextarea', 'World', {delay: 100}); // Types slower, like a user
```
Shortcut for [page.mainFrame().type(selector, text[, options])](#frametypeselector-text-options).
#### page.uncheck(selector, [options])
- `selector` <[string]> A selector to search for uncheckbox to check. If there are multiple elements satisfying the selector, the first will be checked.
- `options` <[Object]>
@ -1693,7 +1685,7 @@ An example of getting text from an iframe element:
- [frame.dblclick(selector[, options])](#framedblclickselector-options)
- [frame.evaluate(pageFunction[, ...args])](#frameevaluatepagefunction-args)
- [frame.evaluateHandle(pageFunction[, ...args])](#frameevaluatehandlepagefunction-args)
- [frame.fill(selector, value, options)](#framefillselector-value-options)
- [frame.fill(selector, value[, options])](#framefillselector-value-options)
- [frame.focus(selector, options)](#framefocusselector-options)
- [frame.frameElement()](#frameframeelement)
- [frame.goto(url[, options])](#framegotourl-options)
@ -1705,7 +1697,6 @@ An example of getting text from an iframe element:
- [frame.setContent(html[, options])](#framesetcontenthtml-options)
- [frame.title()](#frametitle)
- [frame.tripleclick(selector[, options])](#frametripleclickselector-options)
- [frame.type(selector, text[, options])](#frametypeselector-text-options)
- [frame.uncheck(selector, [options])](#frameuncheckselector-options)
- [frame.url()](#frameurl)
- [frame.waitFor(selectorOrFunctionOrTimeout[, options[, ...args]])](#framewaitforselectororfunctionortimeout-options-args)
@ -1921,17 +1912,32 @@ console.log(await resultHandle.jsonValue());
await resultHandle.dispose();
```
#### frame.fill(selector, value, options)
#### frame.fill(selector, value[, options])
- `selector` <[string]> A selector to query page for.
- `value` <[string]> Value to fill for the `<input>`, `<textarea>` or `[contenteditable]` element.
- `options` <[Object]>
- `waitFor` <"visible"|"hidden"|"any"|"nowait"> Wait for element to become visible (`visible`), hidden (`hidden`), present in dom (`any`) or do not wait at all (`nowait`). Defaults to `visible`.
- `clear` <[boolean]> Whether to clear existing value before typing the new one. Defaults to `true`.
- `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 which resolves when the element matching `selector` is successfully filled. The promise will be rejected if there is no element matching `selector`.
This method focuses the element and triggers an `input` event after filling.
```js
// Fill with 'Hello'.
await frame.fill('#mytextarea', 'Hello');
// Leave 'Hello' in the text area and add 'World', resulting in 'HelloWorld'.
await frame.fill('#mytextarea', 'World', { clear: false });
```
If there's no text `<input>`, `<textarea>` or `[contenteditable]` element matching `selector`, the method throws an error.
> **NOTE** Pass empty string as a value to clear the input field (unless `clear` option is set to false).
> **NOTE** To trigger `keydown`, `keypress` or `keyup` events, use [keyboard.type](#keyboardtypetext-options).
> **NOTE** To press a special key, like `Control` or `ArrowDown`, use [keyboard.press](#keyboardpresskey-options).
#### frame.focus(selector, options)
- `selector` <[string]> A selector of an element to focus. If there are multiple elements satisfying the selector, the first will be focused.
- `options` <[Object]>
@ -2076,24 +2082,6 @@ Bear in mind that if the first or second click of the `tripleclick()` triggers a
> **NOTE** `frame.tripleclick()` dispatches three `click` events and a single `dblclick` event.
#### frame.type(selector, text[, options])
- `selector` <[string]> A selector of an element to type into. If there are multiple elements satisfying the selector, the first will be used.
- `text` <[string]> A text to type into a focused element.
- `options` <[Object]>
- `delay` <[number]> Time to wait between key presses in milliseconds. Defaults to 0.
- `waitFor` <"visible"|"hidden"|"any"|"nowait"> Wait for element to become visible (`visible`), hidden (`hidden`), present in dom (`any`) or do not wait at all (`nowait`). Defaults to `visible`.
- `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]>
Sends a `keydown`, `keypress`/`input`, and `keyup` event for each character in the text.
To press a special key, like `Control` or `ArrowDown`, use [`keyboard.press`](#keyboardpresskey-options).
```js
await frame.type('#mytextarea', 'Hello'); // Types instantly
await frame.type('#mytextarea', 'World', {delay: 100}); // Types slower, like a user
```
#### frame.uncheck(selector, [options])
- `selector` <[string]> A selector to search for uncheckbox to check. If there are multiple elements satisfying the selector, the first will be checked.
- `options` <[Object]>
@ -2279,7 +2267,7 @@ ElementHandle instances can be used as arguments in [`page.$eval()`](#pageevalse
- [elementHandle.click([options])](#elementhandleclickoptions)
- [elementHandle.contentFrame()](#elementhandlecontentframe)
- [elementHandle.dblclick([options])](#elementhandledblclickoptions)
- [elementHandle.fill(value)](#elementhandlefillvalue)
- [elementHandle.fill(value[, options])](#elementhandlefillvalue-options)
- [elementHandle.focus()](#elementhandlefocus)
- [elementHandle.hover([options])](#elementhandlehoveroptions)
- [elementHandle.ownerFrame()](#elementhandleownerframe)
@ -2290,7 +2278,6 @@ ElementHandle instances can be used as arguments in [`page.$eval()`](#pageevalse
- [elementHandle.setInputFiles(...files)](#elementhandlesetinputfilesfiles)
- [elementHandle.toString()](#elementhandletostring)
- [elementHandle.tripleclick([options])](#elementhandletripleclickoptions)
- [elementHandle.type(text[, options])](#elementhandletypetext-options)
- [elementHandle.uncheck([options])](#elementhandleuncheckoptions)
- [elementHandle.visibleRatio()](#elementhandlevisibleratio)
<!-- GEN:stop -->
@ -2410,13 +2397,36 @@ Bear in mind that if the first click of the `dblclick()` triggers a navigation e
> **NOTE** `elementHandle.dblclick()` dispatches two `click` events and a single `dblclick` event.
#### elementHandle.fill(value)
#### elementHandle.fill(value[, options])
- `value` <[string]> Value to set for the `<input>`, `<textarea>` or `[contenteditable]` element.
- `options` <[Object]>
- `clear` <[boolean]> Whether to clear existing value before typing the new one. Defaults to `true`.
- returns: <[Promise]> Promise which resolves when the element is successfully filled.
This method focuses the element and triggers an `input` event after filling.
```js
// Fill with 'Hello'.
await textAreaHandle.fill('Hello');
// Leave 'Hello' in the text area and add 'World', resulting in 'HelloWorld'.
await textAreaHandle.fill('World', { clear: false });
```
An example of filling a text field and then submitting the form:
```js
const elementHandle = await page.$('input');
await elementHandle.fill('some text');
await page.keyboard.press('Enter');
```
If element is not a text `<input>`, `<textarea>` or `[contenteditable]` element, the method throws an error.
> **NOTE** Pass empty string as a value to clear the input field (unless `clear` option is set to false).
> **NOTE** To trigger `keydown`, `keypress` or `keyup` events, use [keyboard.type](#keyboardtypetext-options).
> **NOTE** To press a special key, like `Control` or `ArrowDown`, use [keyboard.press](#keyboardpresskey-options).
#### elementHandle.focus()
- returns: <[Promise]>
@ -2526,28 +2536,6 @@ Bear in mind that if the first or second click of the `tripleclick()` triggers a
> **NOTE** `elementHandle.tripleclick()` dispatches three `click` events and a single `dblclick` event.
#### elementHandle.type(text[, options])
- `text` <[string]> A text to type into a focused element.
- `options` <[Object]>
- `delay` <[number]> Time to wait between key presses in milliseconds. Defaults to 0.
- returns: <[Promise]>
Focuses the element, and then sends a `keydown`, `keypress`/`input`, and `keyup` event for each character in the text.
To press a special key, like `Control` or `ArrowDown`, use [`elementHandle.press`](#elementhandlepresskey-options).
```js
await elementHandle.type('Hello'); // Types instantly
await elementHandle.type('World', {delay: 100}); // Types slower, like a user
```
An example of typing into a text field and then submitting the form:
```js
const elementHandle = await page.$('input');
await elementHandle.type('some text');
await elementHandle.press('Enter');
```
#### elementHandle.uncheck([options])
- `options` <[Object]>
- `waitForInteractable` <[boolean]> Whether to wait for element to become static (not moving) and receive pointer events at the click point. Defaults to true.

View file

@ -307,9 +307,10 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
}, ...options);
}
async fill(value: string): Promise<void> {
async fill(value: string, options?: { clear?: boolean }): Promise<void> {
const { clear = true } = (options || {});
assert(helper.isString(value), 'Value must be string. Found value "' + value + '" of type "' + (typeof value) + '"');
const error = await this._evaluateInUtility((node: Node, value: string) => {
const error = await this._evaluateInUtility((node: Node, value: string, clear: boolean) => {
if (node.nodeType !== Node.ELEMENT_NODE)
return 'Node is not of type HTMLElement';
const element = node as HTMLElement;
@ -338,7 +339,8 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
return 'Cannot fill a disabled input.';
if (input.readOnly)
return 'Cannot fill a readonly input.';
input.select();
if (clear)
input.select();
input.focus();
} else if (element.nodeName.toLowerCase() === 'textarea') {
const textarea = element as HTMLTextAreaElement;
@ -346,28 +348,32 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
return 'Cannot fill a disabled textarea.';
if (textarea.readOnly)
return 'Cannot fill a readonly textarea.';
textarea.selectionStart = 0;
textarea.selectionEnd = textarea.value.length;
textarea.focus();
if (clear) {
textarea.selectionStart = 0;
textarea.selectionEnd = textarea.value.length;
textarea.focus();
}
} else if (element.isContentEditable) {
const range = element.ownerDocument.createRange();
range.selectNodeContents(element);
const selection = element.ownerDocument.defaultView.getSelection();
if (!selection)
return 'Element belongs to invisible iframe.';
selection.removeAllRanges();
selection.addRange(range);
if (clear) {
const range = element.ownerDocument.createRange();
range.selectNodeContents(element);
const selection = element.ownerDocument.defaultView.getSelection();
if (!selection)
return 'Element belongs to invisible iframe.';
selection.removeAllRanges();
selection.addRange(range);
}
element.focus();
} else {
return 'Element is not an <input>, <textarea> or [contenteditable] element.';
}
return false;
}, value);
}, value, clear);
if (error)
throw new Error(error);
if (value)
await this._page.keyboard.sendCharacters(value);
else
else if (clear)
await this._page.keyboard.press('Delete');
}
@ -404,11 +410,6 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
throw new Error(errorMessage);
}
async type(text: string, options?: { delay?: number }) {
await this.focus();
await this._page.keyboard.type(text, options);
}
async press(key: string, options?: { delay?: number, text?: string }) {
await this.focus();
await this._page.keyboard.press(key, options);

View file

@ -799,9 +799,9 @@ export class Frame {
await handle.dispose();
}
async fill(selector: string, value: string, options?: WaitForOptions) {
async fill(selector: string, value: string, options?: { clear?: boolean } & WaitForOptions) {
const handle = await this._optionallyWaitForSelectorInUtilityContext(selector, options);
await handle.fill(value);
await handle.fill(value, options);
await handle.dispose();
}
@ -825,12 +825,6 @@ export class Frame {
return result;
}
async type(selector: string, text: string, options?: WaitForOptions & { delay?: number }) {
const handle = await this._optionallyWaitForSelectorInUtilityContext(selector, options);
await handle.type(text, options);
await handle.dispose();
}
async check(selector: string, options?: WaitForOptions & dom.WaitForInteractableOptions) {
const handle = await this._optionallyWaitForSelectorInUtilityContext(selector, options);
await handle.check(options);

View file

@ -497,7 +497,7 @@ export class Page extends platform.EventEmitter {
return this.mainFrame().tripleclick(selector, options);
}
async fill(selector: string, value: string, options?: frames.WaitForOptions) {
async fill(selector: string, value: string, options?: { clear?: boolean } & frames.WaitForOptions) {
return this.mainFrame().fill(selector, value, options);
}
@ -513,10 +513,6 @@ export class Page extends platform.EventEmitter {
return this.mainFrame().select(selector, value, options);
}
async type(selector: string, text: string, options?: frames.WaitForOptions & { delay?: number }) {
return this.mainFrame().type(selector, text, options);
}
async check(selector: string, options?: frames.WaitForOptions & dom.WaitForInteractableOptions) {
return this.mainFrame().check(selector, options);
}

View file

@ -39,7 +39,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT,
});
it('should move with the arrow keys', async({page, server}) => {
await page.goto(server.PREFIX + '/input/textarea.html');
await page.type('textarea', 'Hello World!');
await page.fill('textarea', 'Hello World!');
expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('Hello World!');
for (let i = 0; i < 'World!'.length; i++)
page.keyboard.press('ArrowLeft');
@ -230,7 +230,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT,
});
it('should type emoji', async({page, server}) => {
await page.goto(server.PREFIX + '/input/textarea.html');
await page.type('textarea', '👹 Tokyo street Japan 🇯🇵');
await page.fill('textarea', '👹 Tokyo street Japan 🇯🇵');
expect(await page.$eval('textarea', textarea => textarea.value)).toBe('👹 Tokyo street Japan 🇯🇵');
});
it('should type emoji into an iframe', async({page, server}) => {
@ -238,13 +238,13 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT,
await utils.attachFrame(page, 'emoji-test', server.PREFIX + '/input/textarea.html');
const frame = page.frames()[1];
const textarea = await frame.$('textarea');
await textarea.type('👹 Tokyo street Japan 🇯🇵');
await textarea.fill('👹 Tokyo street Japan 🇯🇵');
expect(await frame.$eval('textarea', textarea => textarea.value)).toBe('👹 Tokyo street Japan 🇯🇵');
});
it.skip(CHROMIUM && MAC)('should handle selectAll', async({page, server}) => {
await page.goto(server.PREFIX + '/input/textarea.html');
const textarea = await page.$('textarea');
await textarea.type('some text');
await textarea.fill('some text');
const modifier = MAC ? 'Meta' : 'Control';
await page.keyboard.down(modifier);
await page.keyboard.press('a');
@ -255,7 +255,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT,
it.skip(CHROMIUM && MAC)('should be able to prevent selectAll', async({page, server}) => {
await page.goto(server.PREFIX + '/input/textarea.html');
const textarea = await page.$('textarea');
await textarea.type('some text');
await textarea.fill('some text');
await page.$eval('textarea', textarea => {
textarea.addEventListener('keydown', event => {
if (event.key === 'a' && (event.metaKey || event.ctrlKey))

View file

@ -945,6 +945,15 @@ module.exports.describe = function({testRunner, expect, headless, playwright, FF
await page.fill('input', 'some value');
expect(await page.evaluate(() => result)).toBe('some value');
});
it('should not clear when options.clear is false', async({page, server}) => {
await page.goto(server.PREFIX + '/input/textarea.html');
await page.fill('input', 'some');
expect(await page.evaluate(() => result)).toBe('some');
await page.fill('input', 'more', { clear: false });
expect(await page.evaluate(() => result)).toBe('somemore');
await page.fill('input', 'less');
expect(await page.evaluate(() => result)).toBe('less');
});
it('should throw on unsupported inputs', async({page, server}) => {
await page.goto(server.PREFIX + '/input/textarea.html');
for (const type of ['color', 'date']) {