feat(firefox): implemented *.fill (#63)

This commit is contained in:
Pavel Feldman 2019-11-22 16:55:35 -08:00 committed by Dmitry Gozman
parent c4c8d498bd
commit 3190044c00
7 changed files with 98 additions and 103 deletions

View File

@ -17,7 +17,7 @@
import * as path from 'path';
import { assert, debugError, helper } from '../helper';
import { ClickOptions, Modifier, MultiClickOptions, PointerActionOptions, SelectOption, selectFunction } from '../input';
import { ClickOptions, Modifier, MultiClickOptions, PointerActionOptions, SelectOption, selectFunction, fillFunction } from '../input';
import { CDPSession } from './Connection';
import { ExecutionContext } from './ExecutionContext';
import { Frame } from './Frame';
@ -321,34 +321,7 @@ export class ElementHandle extends JSHandle {
async fill(value: string): Promise<void> {
assert(helper.isString(value), 'Value must be string. Found value "' + value + '" of type "' + (typeof value) + '"');
const error = await this.evaluate((element: HTMLElement) => {
if (element.nodeType !== Node.ELEMENT_NODE)
return 'Node is not of type HTMLElement';
if (element.nodeName.toLowerCase() === 'input') {
const input = element as HTMLInputElement;
const type = input.getAttribute('type') || '';
const kTextInputTypes = new Set(['', 'password', 'search', 'tel', 'text', 'url']);
if (!kTextInputTypes.has(type.toLowerCase()))
return 'Cannot fill input of type "' + type + '".';
input.selectionStart = 0;
input.selectionEnd = input.value.length;
} else if (element.nodeName.toLowerCase() === 'textarea') {
const textarea = element as HTMLTextAreaElement;
textarea.selectionStart = 0;
textarea.selectionEnd = textarea.value.length;
} else if (element.isContentEditable) {
if (!element.ownerDocument || !element.ownerDocument.defaultView)
return 'Element does not belong to a window';
const range = element.ownerDocument.createRange();
range.selectNodeContents(element);
const selection = element.ownerDocument.defaultView.getSelection();
selection.removeAllRanges();
selection.addRange(range);
} else {
return 'Element is not an <input>, <textarea> or [contenteditable] element.';
}
return false;
});
const error = await this.evaluate(fillFunction);
if (error)
throw new Error(error);
await this.focus();

View File

@ -281,6 +281,13 @@ export class Frame {
return result;
}
async fill(selector: string, value: string) {
const handle = await this.$(selector);
assert(handle, 'No node found for selector: ' + selector);
await handle.fill(value);
await handle.dispose();
}
async type(selector: string, text: string, options: { delay: (number | undefined); } | undefined) {
const handle = await this.$(selector);
assert(handle, 'No node found for selector: ' + selector);

View File

@ -15,15 +15,15 @@
* limitations under the License.
*/
import {assert, debugError, helper} from '../helper';
import * as path from 'path';
import {ExecutionContext} from './ExecutionContext';
import {Frame} from './FrameManager';
import { assert, debugError, helper } from '../helper';
import { ClickOptions, fillFunction, MultiClickOptions, selectFunction, SelectOption } from '../input';
import { JugglerSession } from './Connection';
import { MultiClickOptions, ClickOptions, selectFunction, SelectOption } from '../input';
import Injected from '../injected/injected';
type SelectorRoot = Element | ShadowRoot | Document;
import { ExecutionContext } from './ExecutionContext';
import { Frame } from './FrameManager';
export class JSHandle {
_context: ExecutionContext;
@ -361,6 +361,15 @@ export class ElementHandle extends JSHandle {
return this.evaluate(selectFunction, ...options);
}
async fill(value: string): Promise<void> {
assert(helper.isString(value), 'Value must be string. Found value "' + value + '" of type "' + (typeof value) + '"');
const error = await this.evaluate(fillFunction);
if (error)
throw new Error(error);
await this.focus();
await this._frame._page.keyboard.sendCharacter(value);
}
async _clickablePoint(): Promise<{ x: number; y: number; }> {
const result = await this._session.send('Page.getContentQuads', {
frameId: this._frameId,

View File

@ -460,88 +460,92 @@ export class Page extends EventEmitter {
}
}
async evaluate(pageFunction, ...args) {
return await this.mainFrame().evaluate(pageFunction, ...args);
evaluate(pageFunction, ...args) {
return this.mainFrame().evaluate(pageFunction, ...args);
}
async addScriptTag(options: { content?: string; path?: string; type?: string; url?: string; }): Promise<ElementHandle> {
return await this.mainFrame().addScriptTag(options);
addScriptTag(options: { content?: string; path?: string; type?: string; url?: string; }): Promise<ElementHandle> {
return this.mainFrame().addScriptTag(options);
}
async addStyleTag(options: { content?: string; path?: string; url?: string; }): Promise<ElementHandle> {
return await this.mainFrame().addStyleTag(options);
addStyleTag(options: { content?: string; path?: string; url?: string; }): Promise<ElementHandle> {
return this.mainFrame().addStyleTag(options);
}
async click(selector: string, options?: ClickOptions) {
return await this.mainFrame().click(selector, options);
click(selector: string, options?: ClickOptions) {
return this.mainFrame().click(selector, options);
}
async dblclick(selector: string, options?: MultiClickOptions) {
dblclick(selector: string, options?: MultiClickOptions) {
return this.mainFrame().dblclick(selector, options);
}
async tripleclick(selector: string, options?: MultiClickOptions) {
tripleclick(selector: string, options?: MultiClickOptions) {
return this.mainFrame().tripleclick(selector, options);
}
async type(selector: string, text: string, options: { delay: (number | undefined); } | undefined) {
return await this._frameManager.mainFrame().type(selector, text, options);
fill(selector: string, value: string) {
return this.mainFrame().fill(selector, value);
}
async focus(selector: string) {
return await this._frameManager.mainFrame().focus(selector);
select(selector: string, ...values: Array<string>): Promise<Array<string>> {
return this._frameManager.mainFrame().select(selector, ...values);
}
async hover(selector: string) {
return await this._frameManager.mainFrame().hover(selector);
type(selector: string, text: string, options: { delay: (number | undefined); } | undefined) {
return this._frameManager.mainFrame().type(selector, text, options);
}
async waitFor(selectorOrFunctionOrTimeout: (string | number | Function), options: { polling?: string | number; timeout?: number; visible?: boolean; hidden?: boolean; } | undefined = {}, ...args: Array<any>): Promise<JSHandle> {
return await this._frameManager.mainFrame().waitFor(selectorOrFunctionOrTimeout, options, ...args);
focus(selector: string) {
return this._frameManager.mainFrame().focus(selector);
}
async waitForFunction(pageFunction: Function | string, options: { polling?: string | number; timeout?: number; } | undefined = {}, ...args): Promise<JSHandle> {
return await this._frameManager.mainFrame().waitForFunction(pageFunction, options, ...args);
hover(selector: string) {
return this._frameManager.mainFrame().hover(selector);
}
async waitForSelector(selector: string, options: { timeout?: number; visible?: boolean; hidden?: boolean; } | undefined = {}): Promise<ElementHandle> {
return await this._frameManager.mainFrame().waitForSelector(selector, options);
waitFor(selectorOrFunctionOrTimeout: (string | number | Function), options: { polling?: string | number; timeout?: number; visible?: boolean; hidden?: boolean; } | undefined = {}, ...args: Array<any>): Promise<JSHandle> {
return this._frameManager.mainFrame().waitFor(selectorOrFunctionOrTimeout, options, ...args);
}
async waitForXPath(xpath: string, options: { timeout?: number; visible?: boolean; hidden?: boolean; } | undefined = {}): Promise<ElementHandle> {
return await this._frameManager.mainFrame().waitForXPath(xpath, options);
waitForFunction(pageFunction: Function | string, options: { polling?: string | number; timeout?: number; } | undefined = {}, ...args): Promise<JSHandle> {
return this._frameManager.mainFrame().waitForFunction(pageFunction, options, ...args);
}
async title(): Promise<string> {
return await this._frameManager.mainFrame().title();
waitForSelector(selector: string, options: { timeout?: number; visible?: boolean; hidden?: boolean; } | undefined = {}): Promise<ElementHandle> {
return this._frameManager.mainFrame().waitForSelector(selector, options);
}
async $(selector: string): Promise<ElementHandle | null> {
return await this._frameManager.mainFrame().$(selector);
waitForXPath(xpath: string, options: { timeout?: number; visible?: boolean; hidden?: boolean; } | undefined = {}): Promise<ElementHandle> {
return this._frameManager.mainFrame().waitForXPath(xpath, options);
}
async $$(selector: string): Promise<Array<ElementHandle>> {
return await this._frameManager.mainFrame().$$(selector);
title(): Promise<string> {
return this._frameManager.mainFrame().title();
}
async $eval(selector: string, pageFunction: Function | string, ...args: Array<any>): Promise<(object | undefined)> {
return await this._frameManager.mainFrame().$eval(selector, pageFunction, ...args);
$(selector: string): Promise<ElementHandle | null> {
return this._frameManager.mainFrame().$(selector);
}
async $$eval(selector: string, pageFunction: Function | string, ...args: Array<any>): Promise<(object | undefined)> {
return await this._frameManager.mainFrame().$$eval(selector, pageFunction, ...args);
$$(selector: string): Promise<Array<ElementHandle>> {
return this._frameManager.mainFrame().$$(selector);
}
async $x(expression: string): Promise<Array<ElementHandle>> {
return await this._frameManager.mainFrame().$x(expression);
$eval(selector: string, pageFunction: Function | string, ...args: Array<any>): Promise<(object | undefined)> {
return this._frameManager.mainFrame().$eval(selector, pageFunction, ...args);
}
async evaluateHandle(pageFunction, ...args) {
return await this._frameManager.mainFrame().evaluateHandle(pageFunction, ...args);
$$eval(selector: string, pageFunction: Function | string, ...args: Array<any>): Promise<(object | undefined)> {
return this._frameManager.mainFrame().$$eval(selector, pageFunction, ...args);
}
async select(selector: string, ...values: Array<string>): Promise<Array<string>> {
return await this._frameManager.mainFrame().select(selector, ...values);
$x(expression: string): Promise<Array<ElementHandle>> {
return this._frameManager.mainFrame().$x(expression);
}
evaluateHandle(pageFunction, ...args) {
return this._frameManager.mainFrame().evaluateHandle(pageFunction, ...args);
}
async close(options: any = {}) {

View File

@ -140,3 +140,32 @@ export const selectFunction = (element: HTMLSelectElement, ...optionsToSelect: (
element.dispatchEvent(new Event('change', { 'bubbles': true }));
return options.filter(option => option.selected).map(option => option.value);
};
export const fillFunction = (element: HTMLElement) => {
if (element.nodeType !== Node.ELEMENT_NODE)
return 'Node is not of type HTMLElement';
if (element.nodeName.toLowerCase() === 'input') {
const input = element as HTMLInputElement;
const type = input.getAttribute('type') || '';
const kTextInputTypes = new Set(['', 'password', 'search', 'tel', 'text', 'url']);
if (!kTextInputTypes.has(type.toLowerCase()))
return 'Cannot fill input of type "' + type + '".';
input.selectionStart = 0;
input.selectionEnd = input.value.length;
} else if (element.nodeName.toLowerCase() === 'textarea') {
const textarea = element as HTMLTextAreaElement;
textarea.selectionStart = 0;
textarea.selectionEnd = textarea.value.length;
} else if (element.isContentEditable) {
if (!element.ownerDocument || !element.ownerDocument.defaultView)
return 'Element does not belong to a window';
const range = element.ownerDocument.createRange();
range.selectNodeContents(element);
const selection = element.ownerDocument.defaultView.getSelection();
selection.removeAllRanges();
selection.addRange(range);
} else {
return 'Element is not an <input>, <textarea> or [contenteditable] element.';
}
return false;
};

View File

@ -16,7 +16,7 @@
*/
import * as fs from 'fs';
import { assert, debugError, helper } from '../helper';
import { ClickOptions, MultiClickOptions, selectFunction, SelectOption } from '../input';
import { ClickOptions, MultiClickOptions, selectFunction, SelectOption, fillFunction } from '../input';
import { TargetSession } from './Connection';
import { ExecutionContext } from './ExecutionContext';
import { FrameManager } from './FrameManager';
@ -250,34 +250,7 @@ export class ElementHandle extends JSHandle {
async fill(value: string): Promise<void> {
assert(helper.isString(value), 'Value must be string. Found value "' + value + '" of type "' + (typeof value) + '"');
const error = await this.evaluate((element: HTMLElement) => {
if (element.nodeType !== Node.ELEMENT_NODE)
return 'Node is not of type HTMLElement';
if (element.nodeName.toLowerCase() === 'input') {
const input = element as HTMLInputElement;
const type = input.getAttribute('type') || '';
const kTextInputTypes = new Set(['', 'password', 'search', 'tel', 'text', 'url']);
if (!kTextInputTypes.has(type.toLowerCase()))
return 'Cannot fill input of type "' + type + '".';
input.selectionStart = 0;
input.selectionEnd = input.value.length;
} else if (element.nodeName.toLowerCase() === 'textarea') {
const textarea = element as HTMLTextAreaElement;
textarea.selectionStart = 0;
textarea.selectionEnd = textarea.value.length;
} else if (element.isContentEditable) {
if (!element.ownerDocument || !element.ownerDocument.defaultView)
return 'Element does not belong to a window';
const range = element.ownerDocument.createRange();
range.selectNodeContents(element);
const selection = element.ownerDocument.defaultView.getSelection();
selection.removeAllRanges();
selection.addRange(range);
} else {
return 'Element is not an <input>, <textarea> or [contenteditable] element.';
}
return false;
});
const error = await this.evaluate(fillFunction);
if (error)
throw new Error(error);
await this.focus();

View File

@ -1046,7 +1046,7 @@ module.exports.addTests = function({testRunner, expect, headless, playwright, FF
});
});
describe.skip(FFOX)('Page.fill', function() {
describe('Page.fill', function() {
it('should fill textarea', async({page, server}) => {
await page.goto(server.PREFIX + '/input/textarea.html');
await page.fill('textarea', 'some value');