api(setInputFiles): introduce page/frame helpers, document, break compat (#1818)

This commit is contained in:
Pavel Feldman 2020-04-16 10:25:28 -07:00 committed by GitHub
parent 58bb874299
commit 2280126344
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 340 additions and 45 deletions

View file

@ -15,6 +15,7 @@
- [class: ConsoleMessage](#class-consolemessage)
- [class: Dialog](#class-dialog)
- [class: Download](#class-download)
- [class: FileChooser](#class-filechooser)
- [class: Keyboard](#class-keyboard)
- [class: Mouse](#class-mouse)
- [class: Request](#class-request)
@ -698,6 +699,7 @@ page.removeListener('request', logRequest);
- [page.setDefaultNavigationTimeout(timeout)](#pagesetdefaultnavigationtimeouttimeout)
- [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout)
- [page.setExtraHTTPHeaders(headers)](#pagesetextrahttpheadersheaders)
- [page.setInputFiles(selector, files[, options])](#pagesetinputfilesselector-files-options)
- [page.setViewportSize(viewportSize)](#pagesetviewportsizeviewportsize)
- [page.title()](#pagetitle)
- [page.type(selector, text[, options])](#pagetypeselector-text-options)
@ -757,15 +759,13 @@ Emitted when attachment download started. User can access basic file operations
> **NOTE** Browser context **must** be created with the `acceptDownloads` set to `true` when user needs access to the downloaded content. If `acceptDownloads` is not set or set to `false`, download events are emitted, but the actual download is not performed and user has no access to the downloaded files.
#### event: 'filechooser'
- <[Object]>
- `element` <[ElementHandle]> handle to the input element that was clicked
- `multiple` <[boolean]> Whether file chooser allow for [multiple](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#attr-multiple) file selection.
- <[FileChooser]>
Emitted when a file chooser is supposed to appear, such as after clicking the `<input type=file>`. Playwright can respond to it via setting the input files using [`elementHandle.setInputFiles`](#elementhandlesetinputfilesfiles-options) which can be uploaded in the end.
Emitted when a file chooser is supposed to appear, such as after clicking the `<input type=file>`. Playwright can respond to it via setting the input files using [`fileChooser.setFiles`](#filechoosersetfilesfiles-options) that can be uploaded after that.
```js
page.on('filechooser', async ({element, multiple}) => {
await element.setInputFiles('/tmp/myfile.pdf');
page.on('filechooser', async (fileChooser) => {
await fileChooser.setFiles('/tmp/myfile.pdf');
});
```
@ -1592,6 +1592,27 @@ The extra HTTP headers will be sent with every request the page initiates.
> **NOTE** page.setExtraHTTPHeaders does not guarantee the order of headers in the outgoing requests.
#### page.setInputFiles(selector, files[, options])
- `selector` <[string]> A selector to search for element to click. If there are multiple elements satisfying the selector, the first will be clicked.
- `files` <[string]|[Array]<[string]>|[Object]|[Array]<[Object]>>
- `name` <[string]> [File] name **required**
- `mimeType` <[string]> [File] type **required**
- `buffer` <[Buffer]> File content **required**
- `options` <[Object]>
- `waitUntil` <"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|"nowait"> Actions that cause navigations are waiting for those navigations to fire `domcontentloaded` by default. This behavior can be changed to either wait for another load phase or to omit the waiting altogether using `nowait`:
- `'domcontentloaded'` - consider navigation to be finished when the `DOMContentLoaded` event is fired.
- `'load'` - consider navigation to be finished when the `load` event is fired.
- `'networkidle0'` - consider navigation to be finished when there are no more than 0 network connections for at least `500` ms.
- `'networkidle2'` - consider navigation to be finished when there are no more than 2 network connections for at least `500` ms.
- `'nowait'` - do not wait.
- `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]>
This method expects `selector` to point to an [input element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input).
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). For empty array, clears the selected files.
#### page.setViewportSize(viewportSize)
- `viewportSize` <[Object]>
- `width` <[number]> page width in pixels. **required**
@ -1936,6 +1957,7 @@ An example of getting text from an iframe element:
- [frame.press(selector, key[, options])](#framepressselector-key-options)
- [frame.selectOption(selector, values[, options])](#frameselectoptionselector-values-options)
- [frame.setContent(html[, options])](#framesetcontenthtml-options)
- [frame.setInputFiles(selector, files[, options])](#framesetinputfilesselector-files-options)
- [frame.title()](#frametitle)
- [frame.type(selector, text[, options])](#frametypeselector-text-options)
- [frame.uncheck(selector, [options])](#frameuncheckselector-options)
@ -2320,6 +2342,26 @@ frame.selectOption('select#colors', 'red', 'green', 'blue');
- `'networkidle2'` - consider setting content to be finished when there are no more than 2 network connections for at least `500` ms.
- returns: <[Promise]>
#### frame.setInputFiles(selector, files[, options])
- `selector` <[string]> A selector to search for element to click. If there are multiple elements satisfying the selector, the first will be clicked.
- `files` <[string]|[Array]<[string]>|[Object]|[Array]<[Object]>>
- `name` <[string]> [File] name **required**
- `mimeType` <[string]> [File] type **required**
- `buffer` <[Buffer]> File content **required**
- `options` <[Object]>
- `waitUntil` <"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|"nowait"> Actions that cause navigations are waiting for those navigations to fire `domcontentloaded` by default. This behavior can be changed to either wait for another load phase or to omit the waiting altogether using `nowait`:
- `'domcontentloaded'` - consider navigation to be finished when the `DOMContentLoaded` event is fired.
- `'load'` - consider navigation to be finished when the `load` event is fired.
- `'networkidle0'` - consider navigation to be finished when there are no more than 0 network connections for at least `500` ms.
- `'networkidle2'` - consider navigation to be finished when there are no more than 2 network connections for at least `500` ms.
- `'nowait'` - do not wait.
- `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]>
This method expects `selector` to point to an [input element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input).
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). For empty array, clears the selected files.
#### frame.title()
- returns: <[Promise]<[string]>> The page's title.
@ -2836,8 +2878,8 @@ This method focuses the element and selects all its text content.
#### elementHandle.setInputFiles(files[, options])
- `files` <[string]|[Array]<[string]>|[Object]|[Array]<[Object]>>
- `name` <[string]> [File] name **required**
- `type` <[string]> [File] type **required**
- `data` <[string]> Base64-encoded data **required**
- `mimeType` <[string]> [File] type **required**
- `buffer` <[Buffer]> File content **required**
- `options` <[Object]>
- `waitUntil` <"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|"nowait"> Actions that cause navigations are waiting for those navigations to fire `domcontentloaded` by default. This behavior can be changed to either wait for another load phase or to omit the waiting altogether using `nowait`:
- `'domcontentloaded'` - consider navigation to be finished when the `DOMContentLoaded` event is fired.
@ -2850,7 +2892,7 @@ This method focuses the element and selects all its text content.
This method expects `elementHandle` to point to an [input element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input).
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). For empty array, clears the selected files.
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). For empty array, clears the selected files.
#### elementHandle.textContent()
- returns: <[Promise]<null|[string]>> Resolves to the `node.textContent`.
@ -3122,6 +3164,55 @@ Returns path to the downloaded file in case of successful download.
Returns downloaded url.
### class: FileChooser
[FileChooser] objects are dispatched by the page in the ['filechooser'](#event-filechooser) event.
```js
page.on('filechooser', async (fileChooser) => {
await fileChooser.setFiles('/tmp/myfile.pdf');
});
```
<!-- GEN:toc -->
- [fileChooser.element()](#filechooserelement)
- [fileChooser.isMultiple()](#filechooserismultiple)
- [fileChooser.page()](#filechooserpage)
- [fileChooser.setFiles(files[, options])](#filechoosersetfilesfiles-options)
<!-- GEN:stop -->
#### fileChooser.element()
- returns: <[ElementHandle]>
Returns input element associated with this file chooser.
#### fileChooser.isMultiple()
- returns: <[boolean]>
Returns whether this file chooser accepts multiple files.
#### fileChooser.page()
- returns: <[Page]>
Returns page this file chooser belongs to.
#### fileChooser.setFiles(files[, options])
- `files` <[string]|[Array]<[string]>|[Object]|[Array]<[Object]>>
- `name` <[string]> [File] name **required**
- `mimeType` <[string]> [File] type **required**
- `buffer` <[Buffer]> File content **required**
- `options` <[Object]>
- `waitUntil` <"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|"nowait"> Actions that cause navigations are waiting for those navigations to fire `domcontentloaded` by default. This behavior can be changed to either wait for another load phase or to omit the waiting altogether using `nowait`:
- `'domcontentloaded'` - consider navigation to be finished when the `DOMContentLoaded` event is fired.
- `'load'` - consider navigation to be finished when the `load` event is fired.
- `'networkidle0'` - consider navigation to be finished when there are no more than 0 network connections for at least `500` ms.
- `'networkidle2'` - consider navigation to be finished when there are no more than 2 network connections for at least `500` ms.
- `'nowait'` - do not wait.
- `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]>
Sets the value of the file input this chooser is associated with. 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). For empty array, clears the selected files.
### class: Keyboard
Keyboard provides an api for managing a virtual keyboard. The high level api is [`keyboard.type`](#keyboardtypetext-options), which takes raw characters and generates proper keydown, keypress/input, and keyup events on your page.

102
docs/uploadDownload.md Normal file
View file

@ -0,0 +1,102 @@
# Uploading and downloading files
## Upload a file
```js
// <input id=upload type=file>
await page.setInputFiles('input#upload', 'myfile.pdf');
```
You can select input files for upload using the `page.setInputFiles` method. It expects first arcument to point to an [input element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input) with the type `"file"`. Multiple files can be passed in the array. If some of the file paths are relative, they are resolved relative to the [current working directory](https://nodejs.org/api/process.html#process_process_cwd). Empty array clears the selected files.
#### Variations
```js
// Select multiple files.
page.setInputFiles('input#upload', ['file1.txt', 'file2.txt']);
// Upload buffer from memory, without reading from file.
page.setInputFiles('input#upload', {
name: 'file.txt',
mimeType: 'text/plain',
buffer: Buffer.from('this is test')
});
// Remove all the selected files
page.setInputFiles('input#upload', []);
```
#### API reference
- [`page.setInputFiles(selector, files[, options])`](https://github.com/microsoft/playwright/blob/master/docs/api.md#pagesetinputfilesselector-value-options)
- [`frame.setInputFiles(selector, files[, options])`](https://github.com/microsoft/playwright/blob/master/docs/api.md#framesetinputfilesselector-value-options)
- [`elementHandle.setInputFiles(files[, options])`](https://github.com/microsoft/playwright/blob/master/docs/api.md#elementhandlesetinputfilesfiles-options)
<br/>
<br/>
## Uploading file using dynamic input element
Sometimes element that picks files appears dynamically. When this happens, [`"filechooser"`](https://github.com/microsoft/playwright/blob/master/docs/api.md#event-filechooser) event is emitted on the page. It contains the [`FileChooser`](https://github.com/microsoft/playwright/blob/master/docs/api.md#class-filechooser) object that can be used to select files:
```js
const [ fileChooser ] = await Promise.all([
page.waitForEvent('filechooser'), // <-- start waiting for the file chooser
page.click('button#delayed-select-files') // <-- perform the action that directly or indirectly initiates it.
]);
// Now that both operations resolved, we can use the returned value to select files.
await fileChooser.setFiles(['file1.txt', 'file2.txt'])
```
#### Variations
If you have no idea what invokes the file chooser, you can still handle the event and select files from it:
```js
page.on('filechooser', async (fileChooser) => {
await fileChooser.setFiles(['file1.txt', 'file2.txt']);
});
```
Note that handling the event forks the control flow and makes script harder to follow. Your scenario might end while you are setting the files since your main control flow is not awaiting for this operation to resolve.
#### API reference
- [`FileChooser`](https://github.com/microsoft/playwright/blob/master/docs/api.md#class-filechooser)
- [`page.on('filechooser')`](https://github.com/microsoft/playwright/blob/master/docs/api.md#event-filechooser)
- [`page.waitForEvent(event)`](https://github.com/microsoft/playwright/blob/master/docs/api.md##pagewaitforeventevent-optionsorpredicate)
<br/>
<br/>
## Handle file downloads
```js
const [ dowload ] = await Promise.all([
page.waitForEvent('dowload'), // <-- start waiting for the download
page.click('button#delayed-dowload') // <-- perform the action that directly or indirectly initiates it.
]);
const path = await download.path();
```
For every attachment downloaded by the page, [`"download"`](https://github.com/microsoft/playwright/blob/master/docs/api.md#event-download) event is emitted. If you create a browser context with the `acceptDownloads: true`, all these attachments are going to be downloaded into a temporary folder. You can obtain the download url, file system path and payload stream using the [`Download`](https://github.com/microsoft/playwright/blob/master/docs/api.md#class-download) object from the event.
#### Variations
If you have no idea what initiates the download, you can still handle the event:
```js
page.on('download', download => download.path().then(console.log));
```
Note that handling the event forks the control flow and makes script harder to follow. Your scenario might end while you are downloading a file since your main control flow is not awaiting for this operation to resolve.
#### API reference
- [`Download`](https://github.com/microsoft/playwright/blob/master/docs/api.md#class-download)
- [`page.on('download')`](https://github.com/microsoft/playwright/blob/master/docs/api.md#event-download)
- [`page.waitForEvent(event)`](https://github.com/microsoft/playwright/blob/master/docs/api.md##pagewaitforeventevent-optionsorpredicate)
<br/>
<br/>

View file

@ -21,12 +21,13 @@ export { ConsoleMessage } from './console';
export { Dialog } from './dialog';
export { Download } from './download';
export { ElementHandle } from './dom';
export { FileChooser } from './fileChooser';
export { TimeoutError } from './errors';
export { Frame } from './frames';
export { Keyboard, Mouse } from './input';
export { JSHandle } from './javascript';
export { Request, Response, Route } from './network';
export { FileChooser, Page, Worker } from './page';
export { Page, Worker } from './page';
export { Selectors } from './selectors';
export { CRBrowser as ChromiumBrowser } from './chromium/crBrowser';

View file

@ -265,7 +265,8 @@ export class CRPage implements PageDelegate {
}
async setInputFiles(handle: dom.ElementHandle<HTMLInputElement>, files: types.FilePayload[]): Promise<void> {
await handle.evaluate(dom.setFileInputFunction, files);
await handle._evaluateInUtility(({ injected, node }, files) =>
injected.setInputFiles(node, files), dom.toFileTransferPayload(files));
}
async adoptElementHandle<T extends Node>(handle: dom.ElementHandle<T>, to: dom.FrameExecutionContext): Promise<dom.ElementHandle<T>> {

View file

@ -315,8 +315,8 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
if (typeof item === 'string') {
const file: types.FilePayload = {
name: path.basename(item),
type: mime.getType(item) || 'application/octet-stream',
data: await util.promisify(fs.readFile)(item, 'base64')
mimeType: mime.getType(item) || 'application/octet-stream',
buffer: await util.promisify(fs.readFile)(item)
};
filePayloads.push(file);
} else {
@ -439,15 +439,10 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
}
}
export const setFileInputFunction = async (element: HTMLInputElement, payloads: types.FilePayload[]) => {
const files = await Promise.all(payloads.map(async (file: types.FilePayload) => {
const result = await fetch(`data:${file.type};base64,${file.data}`);
return new File([await result.blob()], file.name, {type: file.type});
export function toFileTransferPayload(files: types.FilePayload[]): types.FileTransferPayload[] {
return files.map(file => ({
name: file.name,
type: file.mimeType,
data: file.buffer.toString('base64')
}));
const dt = new DataTransfer();
for (const file of files)
dt.items.add(file);
element.files = dt.files;
element.dispatchEvent(new Event('input', { 'bubbles': true }));
element.dispatchEvent(new Event('change', { 'bubbles': true }));
};
}

47
src/fileChooser.ts Normal file
View file

@ -0,0 +1,47 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ElementHandle } from './dom';
import { Page } from './page';
import * as types from './types';
export class FileChooser {
private _page: Page;
private _elementHandle: ElementHandle;
private _isMultiple: boolean;
constructor(page: Page, elementHandle: ElementHandle, isMultiple: boolean) {
this._page = page;
this._elementHandle = elementHandle;
this._isMultiple = isMultiple;
}
element(): ElementHandle {
return this._elementHandle;
}
isMultiple(): boolean {
return this._isMultiple;
}
page(): Page {
return this._page;
}
async setFiles(files: string | types.FilePayload | string[] | types.FilePayload[], options?: types.NavigatingActionWaitOptions) {
return this._elementHandle.setInputFiles(files, options);
}
}

View file

@ -433,7 +433,8 @@ export class FFPage implements PageDelegate {
}
async setInputFiles(handle: dom.ElementHandle<HTMLInputElement>, files: types.FilePayload[]): Promise<void> {
await handle.evaluate(dom.setFileInputFunction, files);
await handle._evaluateInUtility(({ injected, node }, files) =>
injected.setInputFiles(node, files), dom.toFileTransferPayload(files));
}
async adoptElementHandle<T extends Node>(handle: dom.ElementHandle<T>, to: dom.FrameExecutionContext): Promise<dom.ElementHandle<T>> {

View file

@ -718,6 +718,13 @@ export class Frame {
return result;
}
async setInputFiles(selector: string, files: string | types.FilePayload | string[] | types.FilePayload[], options?: types.NavigatingActionWaitOptions): Promise<void> {
const { handle, deadline } = await this._waitForSelectorInUtilityContext(selector, options);
const result = await handle.setInputFiles(files, helper.optionsWithUpdatedTimeout(options, deadline));
handle.dispose();
return result;
}
async type(selector: string, text: string, options?: { delay?: number } & types.NavigatingActionWaitOptions) {
const { handle, deadline } = await this._waitForSelectorInUtilityContext(selector, options);
await handle.type(text, helper.optionsWithUpdatedTimeout(options, deadline));

View file

@ -248,6 +248,29 @@ class Injected {
throw new Error('Not a checkbox');
}
async setInputFiles(node: Node, payloads: types.FileTransferPayload[]) {
if (node.nodeType !== Node.ELEMENT_NODE)
return 'Node is not of type HTMLElement';
const element: Element | undefined = node as Element;
if (element.nodeName !== 'INPUT')
return 'Not an <input> element';
const input = element as HTMLInputElement;
const type = (input.getAttribute('type') || '').toLowerCase();
if (type !== 'file')
return 'Not an input[type=file] element';
const files = await Promise.all(payloads.map(async file => {
const result = await fetch(`data:${file.type};base64,${file.data}`);
return new File([await result.blob()], file.name, {type: file.type});
}));
const dt = new DataTransfer();
for (const file of files)
dt.items.add(file);
input.files = dt.files;
input.dispatchEvent(new Event('input', { 'bubbles': true }));
input.dispatchEvent(new Event('change', { 'bubbles': true }));
}
async waitForDisplayedAtStablePosition(node: Node, timeout: number) {
if (!node.isConnected)
throw new Error('Element is not attached to the DOM');

View file

@ -30,6 +30,7 @@ import { ConsoleMessage, ConsoleMessageLocation } from './console';
import * as accessibility from './accessibility';
import { ExtendedEventEmitter } from './extendedEventEmitter';
import { EventEmitter } from 'events';
import { FileChooser } from './fileChooser';
export interface PageDelegate {
readonly rawMouse: input.RawMouse;
@ -83,11 +84,6 @@ type PageState = {
extraHTTPHeaders: network.Headers | null;
};
export type FileChooser = {
element: dom.ElementHandle,
multiple: boolean
};
export class Page extends ExtendedEventEmitter {
private _closed = false;
private _closedCallback: () => void;
@ -174,7 +170,7 @@ export class Page extends ExtendedEventEmitter {
handle.dispose();
return;
}
const fileChooser: FileChooser = { element: handle, multiple };
const fileChooser = new FileChooser(this, handle, multiple);
this.emit(Events.Page.FileChooser, fileChooser);
}
@ -458,6 +454,10 @@ export class Page extends ExtendedEventEmitter {
return this.mainFrame().selectOption(selector, values, options);
}
async setInputFiles(selector: string, files: string | types.FilePayload | string[] | types.FilePayload[], options?: types.NavigatingActionWaitOptions): Promise<void> {
return this.mainFrame().setInputFiles(selector, files, options);
}
async type(selector: string, text: string, options?: { delay?: number } & types.NavigatingActionWaitOptions) {
return this.mainFrame().type(selector, text, options);
}

View file

@ -94,6 +94,12 @@ export type SelectOption = {
};
export type FilePayload = {
name: string,
mimeType: string,
buffer: Buffer,
};
export type FileTransferPayload = {
name: string,
type: string,
data: string,

View file

@ -713,7 +713,7 @@ export class WKPage implements PageDelegate {
async setInputFiles(handle: dom.ElementHandle<HTMLInputElement>, files: types.FilePayload[]): Promise<void> {
const objectId = toRemoteObject(handle).objectId!;
await this._session.send('DOM.setInputFiles', { objectId, files });
await this._session.send('DOM.setInputFiles', { objectId, files: dom.toFileTransferPayload(files) });
}
async adoptElementHandle<T extends Node>(handle: dom.ElementHandle<T>, to: dom.FrameExecutionContext): Promise<dom.ElementHandle<T>> {

View file

@ -38,6 +38,25 @@ describe('input', function() {
});
});
describe('Page.setInputFiles', function() {
it('should work', async({page}) => {
await page.setContent(`<input type=file>`);
await page.setInputFiles('input', path.join(__dirname, '/assets/file-to-upload.txt'));
expect(await page.$eval('input', input => input.files.length)).toBe(1);
expect(await page.$eval('input', input => input.files[0].name)).toBe('file-to-upload.txt');
});
it('should set from memory', async({page}) => {
await page.setContent(`<input type=file>`);
await page.setInputFiles('input', {
name: 'test.txt',
mimeType: 'text/plain',
buffer: Buffer.from('this is a test')
});
expect(await page.$eval('input', input => input.files.length)).toBe(1);
expect(await page.$eval('input', input => input.files[0].name)).toBe('test.txt');
});
});
describe('Page.waitForFileChooser', function() {
it('should emit event', async({page, server}) => {
await page.setContent(`<input type=file>`);
@ -105,11 +124,13 @@ describe('Page.waitForFileChooser', function() {
});
it('should accept single file', async({page, server}) => {
await page.setContent(`<input type=file oninput='javascript:console.timeStamp()'>`);
const [{ element }] = await Promise.all([
const [fileChooser] = await Promise.all([
page.waitForEvent('filechooser'),
page.click('input'),
]);
await element.setInputFiles(FILE_TO_UPLOAD);
expect(fileChooser.page()).toBe(page);
expect(fileChooser.element()).toBeTruthy();
await fileChooser.setFiles(FILE_TO_UPLOAD);
expect(await page.$eval('input', input => input.files.length)).toBe(1);
expect(await page.$eval('input', input => input.files[0].name)).toBe('file-to-upload.txt');
});
@ -151,7 +172,7 @@ describe('Page.waitForFileChooser', function() {
it('should be able to read selected file', async({page, server}) => {
await page.setContent(`<input type=file>`);
const [, content] = await Promise.all([
page.waitForEvent('filechooser').then(({element}) => element.setInputFiles(FILE_TO_UPLOAD)),
page.waitForEvent('filechooser').then(fileChooser => fileChooser.setFiles(FILE_TO_UPLOAD)),
page.$eval('input', async picker => {
picker.click();
await new Promise(x => picker.oninput = x);
@ -166,7 +187,7 @@ describe('Page.waitForFileChooser', function() {
it('should be able to reset selected files with empty file list', async({page, server}) => {
await page.setContent(`<input type=file>`);
const [, fileLength1] = await Promise.all([
page.waitForEvent('filechooser').then(({element}) => element.setInputFiles(FILE_TO_UPLOAD)),
page.waitForEvent('filechooser').then(fileChooser => fileChooser.setFiles(FILE_TO_UPLOAD)),
page.$eval('input', async picker => {
picker.click();
await new Promise(x => picker.oninput = x);
@ -175,7 +196,7 @@ describe('Page.waitForFileChooser', function() {
]);
expect(fileLength1).toBe(1);
const [, fileLength2] = await Promise.all([
page.waitForEvent('filechooser').then(({element}) => element.setInputFiles([])),
page.waitForEvent('filechooser').then(fileChooser => fileChooser.setFiles([])),
page.$eval('input', async picker => {
picker.click();
await new Promise(x => picker.oninput = x);
@ -186,12 +207,12 @@ describe('Page.waitForFileChooser', function() {
});
it('should not accept multiple files for single-file input', async({page, server}) => {
await page.setContent(`<input type=file>`);
const [{ element }] = await Promise.all([
const [fileChooser] = await Promise.all([
page.waitForEvent('filechooser'),
page.click('input'),
]);
let error = null;
await element.setInputFiles([
await fileChooser.setFiles([
path.relative(process.cwd(), __dirname + '/assets/file-to-upload.txt'),
path.relative(process.cwd(), __dirname + '/assets/pptr.png')
]).catch(e => error = e);
@ -216,26 +237,26 @@ describe('Page.waitForFileChooser', function() {
describe('Page.waitForFileChooser isMultiple', () => {
it('should work for single file pick', async({page, server}) => {
await page.setContent(`<input type=file>`);
const [{ multiple }] = await Promise.all([
const [fileChooser] = await Promise.all([
page.waitForEvent('filechooser'),
page.click('input'),
]);
expect(multiple).toBe(false);
expect(fileChooser.isMultiple()).toBe(false);
});
it('should work for "multiple"', async({page, server}) => {
await page.setContent(`<input multiple type=file>`);
const [{ multiple }] = await Promise.all([
const [fileChooser] = await Promise.all([
page.waitForEvent('filechooser'),
page.click('input'),
]);
expect(multiple).toBe(true);
expect(fileChooser.isMultiple()).toBe(true);
});
it('should work for "webkitdirectory"', async({page, server}) => {
await page.setContent(`<input multiple webkitdirectory type=file>`);
const [{ multiple }] = await Promise.all([
const [fileChooser] = await Promise.all([
page.waitForEvent('filechooser'),
page.click('input'),
]);
expect(multiple).toBe(true);
expect(fileChooser.isMultiple()).toBe(true);
});
});