feat(firefox): implemented *.fill (#63)
This commit is contained in:
parent
c4c8d498bd
commit
3190044c00
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { assert, debugError, helper } from '../helper';
|
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 { CDPSession } from './Connection';
|
||||||
import { ExecutionContext } from './ExecutionContext';
|
import { ExecutionContext } from './ExecutionContext';
|
||||||
import { Frame } from './Frame';
|
import { Frame } from './Frame';
|
||||||
|
|
@ -321,34 +321,7 @@ export class ElementHandle extends JSHandle {
|
||||||
|
|
||||||
async fill(value: string): Promise<void> {
|
async fill(value: string): Promise<void> {
|
||||||
assert(helper.isString(value), 'Value must be string. Found value "' + value + '" of type "' + (typeof value) + '"');
|
assert(helper.isString(value), 'Value must be string. Found value "' + value + '" of type "' + (typeof value) + '"');
|
||||||
const error = await this.evaluate((element: HTMLElement) => {
|
const error = await this.evaluate(fillFunction);
|
||||||
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;
|
|
||||||
});
|
|
||||||
if (error)
|
if (error)
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
await this.focus();
|
await this.focus();
|
||||||
|
|
|
||||||
|
|
@ -281,6 +281,13 @@ export class Frame {
|
||||||
return result;
|
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) {
|
async type(selector: string, text: string, options: { delay: (number | undefined); } | undefined) {
|
||||||
const handle = await this.$(selector);
|
const handle = await this.$(selector);
|
||||||
assert(handle, 'No node found for selector: ' + selector);
|
assert(handle, 'No node found for selector: ' + selector);
|
||||||
|
|
|
||||||
|
|
@ -15,15 +15,15 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {assert, debugError, helper} from '../helper';
|
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import {ExecutionContext} from './ExecutionContext';
|
import { assert, debugError, helper } from '../helper';
|
||||||
import {Frame} from './FrameManager';
|
import { ClickOptions, fillFunction, MultiClickOptions, selectFunction, SelectOption } from '../input';
|
||||||
import { JugglerSession } from './Connection';
|
import { JugglerSession } from './Connection';
|
||||||
import { MultiClickOptions, ClickOptions, selectFunction, SelectOption } from '../input';
|
|
||||||
import Injected from '../injected/injected';
|
import Injected from '../injected/injected';
|
||||||
|
|
||||||
type SelectorRoot = Element | ShadowRoot | Document;
|
type SelectorRoot = Element | ShadowRoot | Document;
|
||||||
|
import { ExecutionContext } from './ExecutionContext';
|
||||||
|
import { Frame } from './FrameManager';
|
||||||
|
|
||||||
export class JSHandle {
|
export class JSHandle {
|
||||||
_context: ExecutionContext;
|
_context: ExecutionContext;
|
||||||
|
|
@ -361,6 +361,15 @@ export class ElementHandle extends JSHandle {
|
||||||
return this.evaluate(selectFunction, ...options);
|
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; }> {
|
async _clickablePoint(): Promise<{ x: number; y: number; }> {
|
||||||
const result = await this._session.send('Page.getContentQuads', {
|
const result = await this._session.send('Page.getContentQuads', {
|
||||||
frameId: this._frameId,
|
frameId: this._frameId,
|
||||||
|
|
|
||||||
|
|
@ -460,88 +460,92 @@ export class Page extends EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async evaluate(pageFunction, ...args) {
|
evaluate(pageFunction, ...args) {
|
||||||
return await this.mainFrame().evaluate(pageFunction, ...args);
|
return this.mainFrame().evaluate(pageFunction, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
async addScriptTag(options: { content?: string; path?: string; type?: string; url?: string; }): Promise<ElementHandle> {
|
addScriptTag(options: { content?: string; path?: string; type?: string; url?: string; }): Promise<ElementHandle> {
|
||||||
return await this.mainFrame().addScriptTag(options);
|
return this.mainFrame().addScriptTag(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async addStyleTag(options: { content?: string; path?: string; url?: string; }): Promise<ElementHandle> {
|
addStyleTag(options: { content?: string; path?: string; url?: string; }): Promise<ElementHandle> {
|
||||||
return await this.mainFrame().addStyleTag(options);
|
return this.mainFrame().addStyleTag(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async click(selector: string, options?: ClickOptions) {
|
click(selector: string, options?: ClickOptions) {
|
||||||
return await this.mainFrame().click(selector, options);
|
return this.mainFrame().click(selector, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async dblclick(selector: string, options?: MultiClickOptions) {
|
dblclick(selector: string, options?: MultiClickOptions) {
|
||||||
return this.mainFrame().dblclick(selector, options);
|
return this.mainFrame().dblclick(selector, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async tripleclick(selector: string, options?: MultiClickOptions) {
|
tripleclick(selector: string, options?: MultiClickOptions) {
|
||||||
return this.mainFrame().tripleclick(selector, options);
|
return this.mainFrame().tripleclick(selector, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async type(selector: string, text: string, options: { delay: (number | undefined); } | undefined) {
|
fill(selector: string, value: string) {
|
||||||
return await this._frameManager.mainFrame().type(selector, text, options);
|
return this.mainFrame().fill(selector, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
async focus(selector: string) {
|
select(selector: string, ...values: Array<string>): Promise<Array<string>> {
|
||||||
return await this._frameManager.mainFrame().focus(selector);
|
return this._frameManager.mainFrame().select(selector, ...values);
|
||||||
}
|
}
|
||||||
|
|
||||||
async hover(selector: string) {
|
type(selector: string, text: string, options: { delay: (number | undefined); } | undefined) {
|
||||||
return await this._frameManager.mainFrame().hover(selector);
|
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> {
|
focus(selector: string) {
|
||||||
return await this._frameManager.mainFrame().waitFor(selectorOrFunctionOrTimeout, options, ...args);
|
return this._frameManager.mainFrame().focus(selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForFunction(pageFunction: Function | string, options: { polling?: string | number; timeout?: number; } | undefined = {}, ...args): Promise<JSHandle> {
|
hover(selector: string) {
|
||||||
return await this._frameManager.mainFrame().waitForFunction(pageFunction, options, ...args);
|
return this._frameManager.mainFrame().hover(selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForSelector(selector: string, options: { timeout?: number; visible?: boolean; hidden?: boolean; } | undefined = {}): Promise<ElementHandle> {
|
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().waitForSelector(selector, options);
|
return this._frameManager.mainFrame().waitFor(selectorOrFunctionOrTimeout, options, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForXPath(xpath: string, options: { timeout?: number; visible?: boolean; hidden?: boolean; } | undefined = {}): Promise<ElementHandle> {
|
waitForFunction(pageFunction: Function | string, options: { polling?: string | number; timeout?: number; } | undefined = {}, ...args): Promise<JSHandle> {
|
||||||
return await this._frameManager.mainFrame().waitForXPath(xpath, options);
|
return this._frameManager.mainFrame().waitForFunction(pageFunction, options, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
async title(): Promise<string> {
|
waitForSelector(selector: string, options: { timeout?: number; visible?: boolean; hidden?: boolean; } | undefined = {}): Promise<ElementHandle> {
|
||||||
return await this._frameManager.mainFrame().title();
|
return this._frameManager.mainFrame().waitForSelector(selector, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $(selector: string): Promise<ElementHandle | null> {
|
waitForXPath(xpath: string, options: { timeout?: number; visible?: boolean; hidden?: boolean; } | undefined = {}): Promise<ElementHandle> {
|
||||||
return await this._frameManager.mainFrame().$(selector);
|
return this._frameManager.mainFrame().waitForXPath(xpath, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$(selector: string): Promise<Array<ElementHandle>> {
|
title(): Promise<string> {
|
||||||
return await this._frameManager.mainFrame().$$(selector);
|
return this._frameManager.mainFrame().title();
|
||||||
}
|
}
|
||||||
|
|
||||||
async $eval(selector: string, pageFunction: Function | string, ...args: Array<any>): Promise<(object | undefined)> {
|
$(selector: string): Promise<ElementHandle | null> {
|
||||||
return await this._frameManager.mainFrame().$eval(selector, pageFunction, ...args);
|
return this._frameManager.mainFrame().$(selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$eval(selector: string, pageFunction: Function | string, ...args: Array<any>): Promise<(object | undefined)> {
|
$$(selector: string): Promise<Array<ElementHandle>> {
|
||||||
return await this._frameManager.mainFrame().$$eval(selector, pageFunction, ...args);
|
return this._frameManager.mainFrame().$$(selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $x(expression: string): Promise<Array<ElementHandle>> {
|
$eval(selector: string, pageFunction: Function | string, ...args: Array<any>): Promise<(object | undefined)> {
|
||||||
return await this._frameManager.mainFrame().$x(expression);
|
return this._frameManager.mainFrame().$eval(selector, pageFunction, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
async evaluateHandle(pageFunction, ...args) {
|
$$eval(selector: string, pageFunction: Function | string, ...args: Array<any>): Promise<(object | undefined)> {
|
||||||
return await this._frameManager.mainFrame().evaluateHandle(pageFunction, ...args);
|
return this._frameManager.mainFrame().$$eval(selector, pageFunction, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
async select(selector: string, ...values: Array<string>): Promise<Array<string>> {
|
$x(expression: string): Promise<Array<ElementHandle>> {
|
||||||
return await this._frameManager.mainFrame().select(selector, ...values);
|
return this._frameManager.mainFrame().$x(expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
evaluateHandle(pageFunction, ...args) {
|
||||||
|
return this._frameManager.mainFrame().evaluateHandle(pageFunction, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
async close(options: any = {}) {
|
async close(options: any = {}) {
|
||||||
|
|
|
||||||
29
src/input.ts
29
src/input.ts
|
|
@ -140,3 +140,32 @@ export const selectFunction = (element: HTMLSelectElement, ...optionsToSelect: (
|
||||||
element.dispatchEvent(new Event('change', { 'bubbles': true }));
|
element.dispatchEvent(new Event('change', { 'bubbles': true }));
|
||||||
return options.filter(option => option.selected).map(option => option.value);
|
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;
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import { assert, debugError, helper } from '../helper';
|
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 { TargetSession } from './Connection';
|
||||||
import { ExecutionContext } from './ExecutionContext';
|
import { ExecutionContext } from './ExecutionContext';
|
||||||
import { FrameManager } from './FrameManager';
|
import { FrameManager } from './FrameManager';
|
||||||
|
|
@ -250,34 +250,7 @@ export class ElementHandle extends JSHandle {
|
||||||
|
|
||||||
async fill(value: string): Promise<void> {
|
async fill(value: string): Promise<void> {
|
||||||
assert(helper.isString(value), 'Value must be string. Found value "' + value + '" of type "' + (typeof value) + '"');
|
assert(helper.isString(value), 'Value must be string. Found value "' + value + '" of type "' + (typeof value) + '"');
|
||||||
const error = await this.evaluate((element: HTMLElement) => {
|
const error = await this.evaluate(fillFunction);
|
||||||
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;
|
|
||||||
});
|
|
||||||
if (error)
|
if (error)
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
await this.focus();
|
await this.focus();
|
||||||
|
|
|
||||||
|
|
@ -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}) => {
|
it('should fill textarea', async({page, server}) => {
|
||||||
await page.goto(server.PREFIX + '/input/textarea.html');
|
await page.goto(server.PREFIX + '/input/textarea.html');
|
||||||
await page.fill('textarea', 'some value');
|
await page.fill('textarea', 'some value');
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue