mirror of
https://github.com/microsoft/playwright.git
synced 2025-01-07 03:39:48 +03:00
chrome(filechooser): align file chooser implementations (#88)
This commit is contained in:
parent
1c40eb0b28
commit
64d3e83ddf
18
docs/api.md
18
docs/api.md
@ -270,10 +270,10 @@
|
|||||||
* [elementHandle.press(key[, options])](#elementhandlepresskey-options)
|
* [elementHandle.press(key[, options])](#elementhandlepresskey-options)
|
||||||
* [elementHandle.screenshot([options])](#elementhandlescreenshotoptions)
|
* [elementHandle.screenshot([options])](#elementhandlescreenshotoptions)
|
||||||
* [elementHandle.select(...values)](#elementhandleselectvalues)
|
* [elementHandle.select(...values)](#elementhandleselectvalues)
|
||||||
|
* [elementHandle.setInputFiles(...files)](#elementhandlesetinputfilesfiles)
|
||||||
* [elementHandle.toString()](#elementhandletostring)
|
* [elementHandle.toString()](#elementhandletostring)
|
||||||
* [elementHandle.tripleclick([options])](#elementhandletripleclickoptions)
|
* [elementHandle.tripleclick([options])](#elementhandletripleclickoptions)
|
||||||
* [elementHandle.type(text[, options])](#elementhandletypetext-options)
|
* [elementHandle.type(text[, options])](#elementhandletypetext-options)
|
||||||
* [elementHandle.uploadFile(...filePaths)](#elementhandleuploadfilefilepaths)
|
|
||||||
- [class: Request](#class-request)
|
- [class: Request](#class-request)
|
||||||
* [request.failure()](#requestfailure)
|
* [request.failure()](#requestfailure)
|
||||||
* [request.frame()](#requestframe)
|
* [request.frame()](#requestframe)
|
||||||
@ -3541,6 +3541,15 @@ handle.select('red', 'green', 'blue');
|
|||||||
handle.select({ value: 'blue' }, { index: 2 }, 'red');
|
handle.select({ value: 'blue' }, { index: 2 }, 'red');
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### elementHandle.setInputFiles(...files)
|
||||||
|
- `...files` <...[string]|[Object]> Sets the value of the file input to these file paths or files. If some of the `filePaths` are relative paths, then they are resolved relative to the [current working directory](https://nodejs.org/api/process.html#process_process_cwd).
|
||||||
|
- `name` <[string]> <[File]> name
|
||||||
|
- `type` <[string]> <[File]> type
|
||||||
|
- `data` <[string]> Base64-encoded data
|
||||||
|
- returns: <[Promise]>
|
||||||
|
|
||||||
|
This method expects `elementHandle` to point to an [input element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input).
|
||||||
|
|
||||||
#### elementHandle.toString()
|
#### elementHandle.toString()
|
||||||
- returns: <[string]>
|
- returns: <[string]>
|
||||||
|
|
||||||
@ -3583,12 +3592,6 @@ await elementHandle.type('some text');
|
|||||||
await elementHandle.press('Enter');
|
await elementHandle.press('Enter');
|
||||||
```
|
```
|
||||||
|
|
||||||
#### elementHandle.uploadFile(...filePaths)
|
|
||||||
- `...filePaths` <...[string]> Sets the value of the file input to these paths. If some of the `filePaths` are relative paths, then they are resolved relative to the [current working directory](https://nodejs.org/api/process.html#process_process_cwd).
|
|
||||||
- returns: <[Promise]>
|
|
||||||
|
|
||||||
This method expects `elementHandle` to point to an [input element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input).
|
|
||||||
|
|
||||||
### class: Request
|
### class: Request
|
||||||
|
|
||||||
Whenever the page sends a request, such as for a network resource, the following events are emitted by playwright's page:
|
Whenever the page sends a request, such as for a network resource, the following events are emitted by playwright's page:
|
||||||
@ -3883,6 +3886,7 @@ TimeoutError is emitted whenever certain operations are terminated due to timeou
|
|||||||
[Element]: https://developer.mozilla.org/en-US/docs/Web/API/element "Element"
|
[Element]: https://developer.mozilla.org/en-US/docs/Web/API/element "Element"
|
||||||
[Error]: https://nodejs.org/api/errors.html#errors_class_error "Error"
|
[Error]: https://nodejs.org/api/errors.html#errors_class_error "Error"
|
||||||
[ExecutionContext]: #class-executioncontext "ExecutionContext"
|
[ExecutionContext]: #class-executioncontext "ExecutionContext"
|
||||||
|
[File]: #class-file "https://developer.mozilla.org/en-US/docs/Web/API/File"
|
||||||
[FileChooser]: #class-filechooser "FileChooser"
|
[FileChooser]: #class-filechooser "FileChooser"
|
||||||
[Frame]: #class-frame "Frame"
|
[Frame]: #class-frame "Frame"
|
||||||
[JSHandle]: #class-jshandle "JSHandle"
|
[JSHandle]: #class-jshandle "JSHandle"
|
||||||
|
@ -151,17 +151,21 @@ export class ExecutionContext implements types.EvaluationContext<JSHandle> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _adoptBackendNodeId(backendNodeId: Protocol.DOM.BackendNodeId) {
|
||||||
|
const {object} = await this._client.send('DOM.resolveNode', {
|
||||||
|
backendNodeId,
|
||||||
|
executionContextId: this._contextId,
|
||||||
|
});
|
||||||
|
return createJSHandle(this, object) as ElementHandle;
|
||||||
|
}
|
||||||
|
|
||||||
async _adoptElementHandle(elementHandle: ElementHandle): Promise<ElementHandle> {
|
async _adoptElementHandle(elementHandle: ElementHandle): Promise<ElementHandle> {
|
||||||
assert(elementHandle.executionContext() !== this, 'Cannot adopt handle that already belongs to this execution context');
|
assert(elementHandle.executionContext() !== this, 'Cannot adopt handle that already belongs to this execution context');
|
||||||
assert(this._frame, 'Cannot adopt handle without a Frame');
|
assert(this._frame, 'Cannot adopt handle without a Frame');
|
||||||
const nodeInfo = await this._client.send('DOM.describeNode', {
|
const nodeInfo = await this._client.send('DOM.describeNode', {
|
||||||
objectId: elementHandle._remoteObject.objectId,
|
objectId: elementHandle._remoteObject.objectId,
|
||||||
});
|
});
|
||||||
const {object} = await this._client.send('DOM.resolveNode', {
|
return this._adoptBackendNodeId(nodeInfo.node.backendNodeId);
|
||||||
backendNodeId: nodeInfo.node.backendNodeId,
|
|
||||||
executionContextId: this._contextId,
|
|
||||||
});
|
|
||||||
return createJSHandle(this, object) as ElementHandle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_injected(): Promise<JSHandle> {
|
_injected(): Promise<JSHandle> {
|
||||||
|
@ -15,10 +15,10 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as path from 'path';
|
|
||||||
import * as types from '../types';
|
|
||||||
import { assert, debugError, helper } from '../helper';
|
import { assert, debugError, helper } from '../helper';
|
||||||
import { ClickOptions, Modifier, MultiClickOptions, PointerActionOptions, SelectOption, selectFunction, fillFunction } from '../input';
|
import Injected from '../injected/injected';
|
||||||
|
import * as input from '../input';
|
||||||
|
import * as types from '../types';
|
||||||
import { CDPSession } from './Connection';
|
import { CDPSession } from './Connection';
|
||||||
import { ExecutionContext } from './ExecutionContext';
|
import { ExecutionContext } from './ExecutionContext';
|
||||||
import { Frame } from './Frame';
|
import { Frame } from './Frame';
|
||||||
@ -26,7 +26,6 @@ import { FrameManager } from './FrameManager';
|
|||||||
import { Page } from './Page';
|
import { Page } from './Page';
|
||||||
import { Protocol } from './protocol';
|
import { Protocol } from './protocol';
|
||||||
import { releaseObject, valueFromRemoteObject } from './protocolHelper';
|
import { releaseObject, valueFromRemoteObject } from './protocolHelper';
|
||||||
import Injected from '../injected/injected';
|
|
||||||
|
|
||||||
type SelectorRoot = Element | ShadowRoot | Document;
|
type SelectorRoot = Element | ShadowRoot | Document;
|
||||||
|
|
||||||
@ -236,7 +235,7 @@ export class ElementHandle extends JSHandle {
|
|||||||
return { point, scrollX, scrollY };
|
return { point, scrollX, scrollY };
|
||||||
}
|
}
|
||||||
|
|
||||||
async _performPointerAction(action: (point: Point) => Promise<void>, options?: PointerActionOptions): Promise<void> {
|
async _performPointerAction(action: (point: Point) => Promise<void>, options?: input.PointerActionOptions): Promise<void> {
|
||||||
await this._scrollIntoViewIfNeeded();
|
await this._scrollIntoViewIfNeeded();
|
||||||
let point: Point;
|
let point: Point;
|
||||||
if (options && options.relativePoint) {
|
if (options && options.relativePoint) {
|
||||||
@ -259,7 +258,7 @@ export class ElementHandle extends JSHandle {
|
|||||||
await this._scrollIntoViewIfNeeded();
|
await this._scrollIntoViewIfNeeded();
|
||||||
point = await this._clickablePoint();
|
point = await this._clickablePoint();
|
||||||
}
|
}
|
||||||
let restoreModifiers: Modifier[] | undefined;
|
let restoreModifiers: input.Modifier[] | undefined;
|
||||||
if (options && options.modifiers)
|
if (options && options.modifiers)
|
||||||
restoreModifiers = await this._page.keyboard._ensureModifiers(options.modifiers);
|
restoreModifiers = await this._page.keyboard._ensureModifiers(options.modifiers);
|
||||||
await action(point);
|
await action(point);
|
||||||
@ -289,23 +288,23 @@ export class ElementHandle extends JSHandle {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
hover(options?: PointerActionOptions): Promise<void> {
|
hover(options?: input.PointerActionOptions): Promise<void> {
|
||||||
return this._performPointerAction(point => this._page.mouse.move(point.x, point.y), options);
|
return this._performPointerAction(point => this._page.mouse.move(point.x, point.y), options);
|
||||||
}
|
}
|
||||||
|
|
||||||
click(options?: ClickOptions): Promise<void> {
|
click(options?: input.ClickOptions): Promise<void> {
|
||||||
return this._performPointerAction(point => this._page.mouse.click(point.x, point.y, options), options);
|
return this._performPointerAction(point => this._page.mouse.click(point.x, point.y, options), options);
|
||||||
}
|
}
|
||||||
|
|
||||||
dblclick(options?: MultiClickOptions): Promise<void> {
|
dblclick(options?: input.MultiClickOptions): Promise<void> {
|
||||||
return this._performPointerAction(point => this._page.mouse.dblclick(point.x, point.y, options), options);
|
return this._performPointerAction(point => this._page.mouse.dblclick(point.x, point.y, options), options);
|
||||||
}
|
}
|
||||||
|
|
||||||
tripleclick(options?: MultiClickOptions): Promise<void> {
|
tripleclick(options?: input.MultiClickOptions): Promise<void> {
|
||||||
return this._performPointerAction(point => this._page.mouse.tripleclick(point.x, point.y, options), options);
|
return this._performPointerAction(point => this._page.mouse.tripleclick(point.x, point.y, options), options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async select(...values: (string | ElementHandle | SelectOption)[]): Promise<string[]> {
|
async select(...values: (string | ElementHandle | input.SelectOption)[]): Promise<string[]> {
|
||||||
const options = values.map(value => typeof value === 'object' ? value : { value });
|
const options = values.map(value => typeof value === 'object' ? value : { value });
|
||||||
for (const option of options) {
|
for (const option of options) {
|
||||||
if (option instanceof ElementHandle)
|
if (option instanceof ElementHandle)
|
||||||
@ -317,22 +316,22 @@ export class ElementHandle extends JSHandle {
|
|||||||
if (option.index !== undefined)
|
if (option.index !== undefined)
|
||||||
assert(helper.isNumber(option.index), 'Indices must be numbers. Found index "' + option.index + '" of type "' + (typeof option.index) + '"');
|
assert(helper.isNumber(option.index), 'Indices must be numbers. Found index "' + option.index + '" of type "' + (typeof option.index) + '"');
|
||||||
}
|
}
|
||||||
return this.evaluate(selectFunction, ...options);
|
return this.evaluate(input.selectFunction, ...options);
|
||||||
}
|
}
|
||||||
|
|
||||||
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(fillFunction);
|
const error = await this.evaluate(input.fillFunction);
|
||||||
if (error)
|
if (error)
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
await this.focus();
|
await this.focus();
|
||||||
await this._page.keyboard.sendCharacters(value);
|
await this._page.keyboard.sendCharacters(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
async uploadFile(...filePaths: string[]) {
|
async setInputFiles(...files: (string|input.FilePayload)[]) {
|
||||||
const files = filePaths.map(filePath => path.resolve(filePath));
|
const multiple = await this.evaluate((element: HTMLInputElement) => !!element.multiple);
|
||||||
const objectId = this._remoteObject.objectId;
|
assert(multiple || files.length <= 1, 'Non-multiple file input can only accept single file!');
|
||||||
await this._client.send('DOM.setFileInputFiles', { objectId, files });
|
await this.evaluate(input.setFileInputFunction, await input.loadFiles(files));
|
||||||
}
|
}
|
||||||
|
|
||||||
async focus() {
|
async focus() {
|
||||||
|
@ -16,16 +16,15 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
|
||||||
import * as types from '../types';
|
|
||||||
import { assert, debugError, helper } from '../helper';
|
import { assert, debugError, helper } from '../helper';
|
||||||
import { ClickOptions, fillFunction, MultiClickOptions, selectFunction, SelectOption } from '../input';
|
|
||||||
import { JugglerSession } from './Connection';
|
|
||||||
import Injected from '../injected/injected';
|
import Injected from '../injected/injected';
|
||||||
|
import * as input from '../input';
|
||||||
type SelectorRoot = Element | ShadowRoot | Document;
|
import * as types from '../types';
|
||||||
|
import { JugglerSession } from './Connection';
|
||||||
import { ExecutionContext } from './ExecutionContext';
|
import { ExecutionContext } from './ExecutionContext';
|
||||||
import { Frame } from './FrameManager';
|
import { Frame } from './FrameManager';
|
||||||
|
|
||||||
|
type SelectorRoot = Element | ShadowRoot | Document;
|
||||||
const readFileAsync = helper.promisify(fs.readFile);
|
const readFileAsync = helper.promisify(fs.readFile);
|
||||||
|
|
||||||
export class JSHandle {
|
export class JSHandle {
|
||||||
@ -294,47 +293,28 @@ export class ElementHandle extends JSHandle {
|
|||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
async click(options?: ClickOptions) {
|
async click(options?: input.ClickOptions) {
|
||||||
await this._scrollIntoViewIfNeeded();
|
await this._scrollIntoViewIfNeeded();
|
||||||
const {x, y} = await this._clickablePoint();
|
const {x, y} = await this._clickablePoint();
|
||||||
await this._frame._page.mouse.click(x, y, options);
|
await this._frame._page.mouse.click(x, y, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async dblclick(options?: MultiClickOptions): Promise<void> {
|
async dblclick(options?: input.MultiClickOptions): Promise<void> {
|
||||||
await this._scrollIntoViewIfNeeded();
|
await this._scrollIntoViewIfNeeded();
|
||||||
const {x, y} = await this._clickablePoint();
|
const {x, y} = await this._clickablePoint();
|
||||||
await this._frame._page.mouse.dblclick(x, y, options);
|
await this._frame._page.mouse.dblclick(x, y, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async tripleclick(options?: MultiClickOptions): Promise<void> {
|
async tripleclick(options?: input.MultiClickOptions): Promise<void> {
|
||||||
await this._scrollIntoViewIfNeeded();
|
await this._scrollIntoViewIfNeeded();
|
||||||
const {x, y} = await this._clickablePoint();
|
const {x, y} = await this._clickablePoint();
|
||||||
await this._frame._page.mouse.tripleclick(x, y, options);
|
await this._frame._page.mouse.tripleclick(x, y, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async uploadFile(...files: Array<string>) {
|
async setInputFiles(...files: (string|input.FilePayload)[]) {
|
||||||
const multiple = await this.evaluate((element: HTMLInputElement) => !!element.multiple);
|
const multiple = await this.evaluate((element: HTMLInputElement) => !!element.multiple);
|
||||||
assert(multiple || files.length <= 1, 'Non-multiple file input can only accept single file!');
|
assert(multiple || files.length <= 1, 'Non-multiple file input can only accept single file!');
|
||||||
const blobs = await Promise.all(files.map(path => readFileAsync(path)));
|
await this.evaluate(input.setFileInputFunction, await input.loadFiles(files));
|
||||||
const payloads: FilePayload[] = [];
|
|
||||||
for (let i = 0; i < files.length; ++i) {
|
|
||||||
payloads.push({
|
|
||||||
name: path.basename(files[i]),
|
|
||||||
mimeType: 'application/octet-stream',
|
|
||||||
data: blobs[i].toString('base64')
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await this.evaluate(async (element: HTMLInputElement, payloads: FilePayload[]) => {
|
|
||||||
const files = await Promise.all(payloads.map(async (file: FilePayload) => {
|
|
||||||
const result = await fetch(`data:${file.mimeType};base64,${file.data}`);
|
|
||||||
return new File([await result.blob()], file.name);
|
|
||||||
}));
|
|
||||||
const dt = new DataTransfer();
|
|
||||||
for (const file of files)
|
|
||||||
dt.items.add(file);
|
|
||||||
element.files = dt.files;
|
|
||||||
element.dispatchEvent(new Event('input', { 'bubbles': true }));
|
|
||||||
}, payloads);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async hover() {
|
async hover() {
|
||||||
@ -357,7 +337,7 @@ export class ElementHandle extends JSHandle {
|
|||||||
await this._frame._page.keyboard.press(key, options);
|
await this._frame._page.keyboard.press(key, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async select(...values: (string | ElementHandle | SelectOption)[]): Promise<string[]> {
|
async select(...values: (string | ElementHandle | input.SelectOption)[]): Promise<string[]> {
|
||||||
const options = values.map(value => typeof value === 'object' ? value : { value });
|
const options = values.map(value => typeof value === 'object' ? value : { value });
|
||||||
for (const option of options) {
|
for (const option of options) {
|
||||||
if (option instanceof ElementHandle)
|
if (option instanceof ElementHandle)
|
||||||
@ -369,12 +349,12 @@ export class ElementHandle extends JSHandle {
|
|||||||
if (option.index !== undefined)
|
if (option.index !== undefined)
|
||||||
assert(helper.isNumber(option.index), 'Indices must be numbers. Found index "' + option.index + '" of type "' + (typeof option.index) + '"');
|
assert(helper.isNumber(option.index), 'Indices must be numbers. Found index "' + option.index + '" of type "' + (typeof option.index) + '"');
|
||||||
}
|
}
|
||||||
return this.evaluate(selectFunction, ...options);
|
return this.evaluate(input.selectFunction, ...options);
|
||||||
}
|
}
|
||||||
|
|
||||||
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(fillFunction);
|
const error = await this.evaluate(input.fillFunction);
|
||||||
if (error)
|
if (error)
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
await this.focus();
|
await this.focus();
|
||||||
|
@ -558,7 +558,7 @@ export class Page extends EventEmitter {
|
|||||||
const interceptors = Array.from(this._fileChooserInterceptors);
|
const interceptors = Array.from(this._fileChooserInterceptors);
|
||||||
this._fileChooserInterceptors.clear();
|
this._fileChooserInterceptors.clear();
|
||||||
const multiple = await handle.evaluate((element: HTMLInputElement) => !!element.multiple);
|
const multiple = await handle.evaluate((element: HTMLInputElement) => !!element.multiple);
|
||||||
const fileChooser = new FileChooser(this, this._session, handle, multiple);
|
const fileChooser = new FileChooser(handle, multiple);
|
||||||
for (const interceptor of interceptors)
|
for (const interceptor of interceptors)
|
||||||
interceptor.call(null, fileChooser);
|
interceptor.call(null, fileChooser);
|
||||||
}
|
}
|
||||||
@ -623,21 +623,12 @@ export type Viewport = {
|
|||||||
hasTouch?: boolean;
|
hasTouch?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
type MediaFeature = {
|
|
||||||
name: string,
|
|
||||||
value: string
|
|
||||||
};
|
|
||||||
|
|
||||||
export class FileChooser {
|
export class FileChooser {
|
||||||
private _page; Page;
|
|
||||||
private _client: JugglerSession;
|
|
||||||
private _element: ElementHandle;
|
private _element: ElementHandle;
|
||||||
private _multiple: boolean;
|
private _multiple: boolean;
|
||||||
private _handled = false;
|
private _handled = false;
|
||||||
|
|
||||||
constructor(page: Page, client: JugglerSession, element: ElementHandle, multiple: boolean) {
|
constructor(element: ElementHandle, multiple: boolean) {
|
||||||
this._page = page;
|
|
||||||
this._client = client;
|
|
||||||
this._element = element;
|
this._element = element;
|
||||||
this._multiple = multiple;
|
this._multiple = multiple;
|
||||||
}
|
}
|
||||||
@ -649,7 +640,7 @@ export class FileChooser {
|
|||||||
async accept(filePaths: string[]): Promise<any> {
|
async accept(filePaths: string[]): Promise<any> {
|
||||||
assert(!this._handled, 'Cannot accept FileChooser which is already handled!');
|
assert(!this._handled, 'Cannot accept FileChooser which is already handled!');
|
||||||
this._handled = true;
|
this._handled = true;
|
||||||
await this._element.uploadFile(...filePaths);
|
await this._element.setInputFiles(...filePaths);
|
||||||
}
|
}
|
||||||
|
|
||||||
async cancel(): Promise<any> {
|
async cancel(): Promise<any> {
|
||||||
|
38
src/input.ts
38
src/input.ts
@ -1,8 +1,11 @@
|
|||||||
// Copyright (c) Microsoft Corporation.
|
// Copyright (c) Microsoft Corporation.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
import { assert } from './helper';
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { assert, helper } from './helper';
|
||||||
import * as keyboardLayout from './USKeyboardLayout';
|
import * as keyboardLayout from './USKeyboardLayout';
|
||||||
|
const readFileAsync = helper.promisify(fs.readFile);
|
||||||
|
|
||||||
export type Modifier = 'Alt' | 'Control' | 'Meta' | 'Shift';
|
export type Modifier = 'Alt' | 'Control' | 'Meta' | 'Shift';
|
||||||
export type Button = 'left' | 'right' | 'middle';
|
export type Button = 'left' | 'right' | 'middle';
|
||||||
@ -344,5 +347,38 @@ export const fillFunction = (element: HTMLElement) => {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const loadFiles = async (items: (string|FilePayload)[]): Promise<FilePayload[]> => {
|
||||||
|
return Promise.all(items.map(async item => {
|
||||||
|
if (typeof item === 'string') {
|
||||||
|
const file: FilePayload = {
|
||||||
|
name: path.basename(item),
|
||||||
|
type: 'application/octet-stream',
|
||||||
|
data: (await readFileAsync(item)).toString('base64')
|
||||||
|
};
|
||||||
|
return file;
|
||||||
|
} else {
|
||||||
|
return item as FilePayload;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setFileInputFunction = async (element: HTMLInputElement, payloads: FilePayload[]) => {
|
||||||
|
const files = await Promise.all(payloads.map(async (file: FilePayload) => {
|
||||||
|
const result = await fetch(`data:${file.type};base64,${file.data}`);
|
||||||
|
return new File([await result.blob()], file.name);
|
||||||
|
}));
|
||||||
|
const dt = new DataTransfer();
|
||||||
|
for (const file of files)
|
||||||
|
dt.items.add(file);
|
||||||
|
element.files = dt.files;
|
||||||
|
element.dispatchEvent(new Event('input', { 'bubbles': true }));
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FilePayload = {
|
||||||
|
name: string,
|
||||||
|
type: string,
|
||||||
|
data: string
|
||||||
|
};
|
||||||
|
|
||||||
export const mediaTypes = new Set(['screen', 'print']);
|
export const mediaTypes = new Set(['screen', 'print']);
|
||||||
export const mediaColorSchemes = new Set(['dark', 'light', 'no-preference']);
|
export const mediaColorSchemes = new Set(['dark', 'light', 'no-preference']);
|
||||||
|
@ -17,7 +17,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, fillFunction } from '../input';
|
import * as input 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';
|
||||||
@ -217,25 +217,25 @@ export class ElementHandle extends JSHandle {
|
|||||||
await this._page.mouse.move(x, y);
|
await this._page.mouse.move(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
async click(options?: ClickOptions): Promise<void> {
|
async click(options?: input.ClickOptions): Promise<void> {
|
||||||
await this._scrollIntoViewIfNeeded();
|
await this._scrollIntoViewIfNeeded();
|
||||||
const {x, y} = await this._clickablePoint();
|
const {x, y} = await this._clickablePoint();
|
||||||
await this._page.mouse.click(x, y, options);
|
await this._page.mouse.click(x, y, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async dblclick(options?: MultiClickOptions): Promise<void> {
|
async dblclick(options?: input.MultiClickOptions): Promise<void> {
|
||||||
await this._scrollIntoViewIfNeeded();
|
await this._scrollIntoViewIfNeeded();
|
||||||
const {x, y} = await this._clickablePoint();
|
const {x, y} = await this._clickablePoint();
|
||||||
await this._page.mouse.dblclick(x, y, options);
|
await this._page.mouse.dblclick(x, y, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async tripleclick(options?: MultiClickOptions): Promise<void> {
|
async tripleclick(options?: input.MultiClickOptions): Promise<void> {
|
||||||
await this._scrollIntoViewIfNeeded();
|
await this._scrollIntoViewIfNeeded();
|
||||||
const {x, y} = await this._clickablePoint();
|
const {x, y} = await this._clickablePoint();
|
||||||
await this._page.mouse.tripleclick(x, y, options);
|
await this._page.mouse.tripleclick(x, y, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async select(...values: (string | ElementHandle | SelectOption)[]): Promise<string[]> {
|
async select(...values: (string | ElementHandle | input.SelectOption)[]): Promise<string[]> {
|
||||||
const options = values.map(value => typeof value === 'object' ? value : { value });
|
const options = values.map(value => typeof value === 'object' ? value : { value });
|
||||||
for (const option of options) {
|
for (const option of options) {
|
||||||
if (option instanceof ElementHandle)
|
if (option instanceof ElementHandle)
|
||||||
@ -247,18 +247,24 @@ export class ElementHandle extends JSHandle {
|
|||||||
if (option.index !== undefined)
|
if (option.index !== undefined)
|
||||||
assert(helper.isNumber(option.index), 'Indices must be numbers. Found index "' + option.index + '" of type "' + (typeof option.index) + '"');
|
assert(helper.isNumber(option.index), 'Indices must be numbers. Found index "' + option.index + '" of type "' + (typeof option.index) + '"');
|
||||||
}
|
}
|
||||||
return this.evaluate(selectFunction, ...options);
|
return this.evaluate(input.selectFunction, ...options);
|
||||||
}
|
}
|
||||||
|
|
||||||
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(fillFunction);
|
const error = await this.evaluate(input.fillFunction);
|
||||||
if (error)
|
if (error)
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
await this.focus();
|
await this.focus();
|
||||||
await this._page.keyboard.sendCharacters(value);
|
await this._page.keyboard.sendCharacters(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async setInputFiles(...files: (string|input.FilePayload)[]) {
|
||||||
|
const multiple = await this.evaluate((element: HTMLInputElement) => !!element.multiple);
|
||||||
|
assert(multiple || files.length <= 1, 'Non-multiple file input can only accept single file!');
|
||||||
|
await this.evaluate(input.setFileInputFunction, await input.loadFiles(files));
|
||||||
|
}
|
||||||
|
|
||||||
async focus() {
|
async focus() {
|
||||||
await this.evaluate(element => element.focus());
|
await this.evaluate(element => element.focus());
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ module.exports.addTests = function({testRunner, expect, defaultBrowserOptions, p
|
|||||||
const {it, fit, xit} = testRunner;
|
const {it, fit, xit} = testRunner;
|
||||||
const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;
|
const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;
|
||||||
|
|
||||||
describe.skip(FFOX)('DefaultBrowserContext', function() {
|
describe('DefaultBrowserContext', function() {
|
||||||
beforeEach(async state => {
|
beforeEach(async state => {
|
||||||
state.browser = await playwright.launch(defaultBrowserOptions);
|
state.browser = await playwright.launch(defaultBrowserOptions);
|
||||||
state.page = await state.browser.newPage();
|
state.page = await state.browser.newPage();
|
||||||
@ -34,7 +34,7 @@ module.exports.addTests = function({testRunner, expect, defaultBrowserOptions, p
|
|||||||
await page.evaluate(() => {
|
await page.evaluate(() => {
|
||||||
document.cookie = 'username=John Doe';
|
document.cookie = 'username=John Doe';
|
||||||
});
|
});
|
||||||
expect(await page.cookies()).toEqual([{
|
expect(await page.browserContext().cookies()).toEqual([{
|
||||||
name: 'username',
|
name: 'username',
|
||||||
value: 'John Doe',
|
value: 'John Doe',
|
||||||
domain: 'localhost',
|
domain: 'localhost',
|
||||||
@ -47,14 +47,15 @@ module.exports.addTests = function({testRunner, expect, defaultBrowserOptions, p
|
|||||||
sameSite: 'None',
|
sameSite: 'None',
|
||||||
}]);
|
}]);
|
||||||
});
|
});
|
||||||
it.skip(WEBKIT)('page.setCookie() should work', async({page, server}) => {
|
it.skip(WEBKIT)('context.setCookies() should work', async({page, server}) => {
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
await page.setCookie({
|
await page.browserContext().setCookies([{
|
||||||
|
url: server.EMPTY_PAGE,
|
||||||
name: 'username',
|
name: 'username',
|
||||||
value: 'John Doe'
|
value: 'John Doe'
|
||||||
});
|
}]);
|
||||||
expect(await page.evaluate(() => document.cookie)).toBe('username=John Doe');
|
expect(await page.evaluate(() => document.cookie)).toBe('username=John Doe');
|
||||||
expect(await page.cookies()).toEqual([{
|
expect(await page.browserContext().cookies()).toEqual([{
|
||||||
name: 'username',
|
name: 'username',
|
||||||
value: 'John Doe',
|
value: 'John Doe',
|
||||||
domain: 'localhost',
|
domain: 'localhost',
|
||||||
@ -67,30 +68,20 @@ module.exports.addTests = function({testRunner, expect, defaultBrowserOptions, p
|
|||||||
sameSite: 'None',
|
sameSite: 'None',
|
||||||
}]);
|
}]);
|
||||||
});
|
});
|
||||||
it.skip(WEBKIT)('page.deleteCookie() should work', async({page, server}) => {
|
it.skip(WEBKIT)('context.clearCookies() should work', async({page, server}) => {
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
await page.setCookie({
|
await page.browserContext().setCookies([{
|
||||||
|
url: server.EMPTY_PAGE,
|
||||||
name: 'cookie1',
|
name: 'cookie1',
|
||||||
value: '1'
|
value: '1'
|
||||||
}, {
|
}, {
|
||||||
|
url: server.EMPTY_PAGE,
|
||||||
name: 'cookie2',
|
name: 'cookie2',
|
||||||
value: '2'
|
value: '2'
|
||||||
});
|
|
||||||
expect(await page.evaluate('document.cookie')).toBe('cookie1=1; cookie2=2');
|
|
||||||
await page.deleteCookie({name: 'cookie2'});
|
|
||||||
expect(await page.evaluate('document.cookie')).toBe('cookie1=1');
|
|
||||||
expect(await page.cookies()).toEqual([{
|
|
||||||
name: 'cookie1',
|
|
||||||
value: '1',
|
|
||||||
domain: 'localhost',
|
|
||||||
path: '/',
|
|
||||||
expires: -1,
|
|
||||||
size: 8,
|
|
||||||
httpOnly: false,
|
|
||||||
secure: false,
|
|
||||||
session: true,
|
|
||||||
sameSite: 'None',
|
|
||||||
}]);
|
}]);
|
||||||
|
expect(await page.evaluate('document.cookie')).toBe('cookie1=1; cookie2=2');
|
||||||
|
await page.browserContext().clearCookies();
|
||||||
|
expect(await page.evaluate('document.cookie')).toBe('');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -27,7 +27,7 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
|
|||||||
await page.goto(server.PREFIX + '/input/fileupload.html');
|
await page.goto(server.PREFIX + '/input/fileupload.html');
|
||||||
const filePath = path.relative(process.cwd(), FILE_TO_UPLOAD);
|
const filePath = path.relative(process.cwd(), FILE_TO_UPLOAD);
|
||||||
const input = await page.$('input');
|
const input = await page.$('input');
|
||||||
await input.uploadFile(filePath);
|
await input.setInputFiles(filePath);
|
||||||
expect(await page.evaluate(e => e.files[0].name, input)).toBe('file-to-upload.txt');
|
expect(await page.evaluate(e => e.files[0].name, input)).toBe('file-to-upload.txt');
|
||||||
expect(await page.evaluate(e => {
|
expect(await page.evaluate(e => {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
|
@ -102,10 +102,10 @@ module.exports.addTests = function({testRunner, expect, FFOX, CHROME, WEBKIT}) {
|
|||||||
expect(requests[1].url()).toContain('/one-style.css');
|
expect(requests[1].url()).toContain('/one-style.css');
|
||||||
expect(requests[1].headers().referer).toContain('/one-style.html');
|
expect(requests[1].headers().referer).toContain('/one-style.html');
|
||||||
});
|
});
|
||||||
it('should properly return navigation response when URL has cookies', async({page, server}) => {
|
it('should properly return navigation response when URL has cookies', async({context, page, server}) => {
|
||||||
// Setup cookie.
|
// Setup cookie.
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
await page.setCookie({ name: 'foo', value: 'bar'});
|
await context.setCookies([{ url: server.EMPTY_PAGE, name: 'foo', value: 'bar'}]);
|
||||||
|
|
||||||
// Setup request interception.
|
// Setup request interception.
|
||||||
await page.interception.enable();
|
await page.interception.enable();
|
||||||
|
Loading…
Reference in New Issue
Block a user