mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
feat: implement ElementHandle.uploadFile for WebDriver BiDi (#11963)
This commit is contained in:
parent
414f43388b
commit
accf2b6ca8
@ -70,6 +70,7 @@ This is an exciting step towards a more unified and efficient cross-browser auto
|
|||||||
|
|
||||||
- Input
|
- Input
|
||||||
|
|
||||||
|
- ElementHandle.uploadFile
|
||||||
- ElementHandle.click
|
- ElementHandle.click
|
||||||
- Keyboard.down
|
- Keyboard.down
|
||||||
- Keyboard.press
|
- Keyboard.press
|
||||||
@ -141,7 +142,6 @@ This is an exciting step towards a more unified and efficient cross-browser auto
|
|||||||
- Other methods:
|
- Other methods:
|
||||||
|
|
||||||
- Browser.userAgent()
|
- Browser.userAgent()
|
||||||
- ElementHandle.uploadFile()
|
|
||||||
- Frame.isOOPFrame()
|
- Frame.isOOPFrame()
|
||||||
- Frame.waitForDevicePrompt()
|
- Frame.waitForDevicePrompt()
|
||||||
- HTTPResponse.buffer()
|
- HTTPResponse.buffer()
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
import type * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
import type * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
||||||
|
|
||||||
import {ElementHandle, type AutofillData} from '../api/ElementHandle.js';
|
import {ElementHandle, type AutofillData} from '../api/ElementHandle.js';
|
||||||
import {UnsupportedOperation} from '../common/Errors.js';
|
|
||||||
import {throwIfDisposed} from '../util/decorators.js';
|
import {throwIfDisposed} from '../util/decorators.js';
|
||||||
|
|
||||||
import type {BidiFrame} from './Frame.js';
|
import type {BidiFrame} from './Frame.js';
|
||||||
@ -90,7 +89,31 @@ export class BidiElementHandle<
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
override uploadFile(this: ElementHandle<HTMLInputElement>): never {
|
override async uploadFile(
|
||||||
throw new UnsupportedOperation();
|
this: BidiElementHandle<HTMLInputElement>,
|
||||||
|
...files: string[]
|
||||||
|
): Promise<void> {
|
||||||
|
// Locate all files and confirm that they exist.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
||||||
|
let path: typeof import('path');
|
||||||
|
try {
|
||||||
|
path = await import('path');
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof TypeError) {
|
||||||
|
throw new Error(
|
||||||
|
`JSHandle#uploadFile can only be used in Node-like environments.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
files = files.map(file => {
|
||||||
|
if (path.win32.isAbsolute(file) || path.posix.isAbsolute(file)) {
|
||||||
|
return file;
|
||||||
|
} else {
|
||||||
|
return path.resolve(file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await this.frame.setFiles(this, files);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,7 @@ import {BidiCdpSession} from './CDPSession.js';
|
|||||||
import type {BrowsingContext} from './core/BrowsingContext.js';
|
import type {BrowsingContext} from './core/BrowsingContext.js';
|
||||||
import {BidiDeserializer} from './Deserializer.js';
|
import {BidiDeserializer} from './Deserializer.js';
|
||||||
import {BidiDialog} from './Dialog.js';
|
import {BidiDialog} from './Dialog.js';
|
||||||
|
import type {BidiElementHandle} from './ElementHandle.js';
|
||||||
import {ExposeableFunction} from './ExposedFunction.js';
|
import {ExposeableFunction} from './ExposedFunction.js';
|
||||||
import {BidiHTTPRequest, requests} from './HTTPRequest.js';
|
import {BidiHTTPRequest, requests} from './HTTPRequest.js';
|
||||||
import type {BidiHTTPResponse} from './HTTPResponse.js';
|
import type {BidiHTTPResponse} from './HTTPResponse.js';
|
||||||
@ -519,6 +520,15 @@ export class BidiFrame extends Frame {
|
|||||||
concurrency,
|
concurrency,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@throwIfDetached
|
||||||
|
async setFiles(element: BidiElementHandle, files: string[]): Promise<void> {
|
||||||
|
await this.browsingContext.setFiles(
|
||||||
|
// SAFETY: ElementHandles are always remote references.
|
||||||
|
element.remoteValue() as Bidi.Script.SharedReference,
|
||||||
|
files
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isConsoleLogEntry(
|
function isConsoleLogEntry(
|
||||||
|
@ -524,6 +524,21 @@ export class BrowsingContext extends EventEmitter<{
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@throwIfDisposed<BrowsingContext>(context => {
|
||||||
|
// SAFETY: Disposal implies this exists.
|
||||||
|
return context.#reason!;
|
||||||
|
})
|
||||||
|
async setFiles(
|
||||||
|
element: Bidi.Script.SharedReference,
|
||||||
|
files: string[]
|
||||||
|
): Promise<void> {
|
||||||
|
await this.#session.send('input.setFiles', {
|
||||||
|
context: this.id,
|
||||||
|
element,
|
||||||
|
files,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[disposeSymbol](): void {
|
[disposeSymbol](): void {
|
||||||
this.#reason ??=
|
this.#reason ??=
|
||||||
'Browsing context already closed, probably because the user context closed.';
|
'Browsing context already closed, probably because the user context closed.';
|
||||||
|
@ -106,6 +106,10 @@ export interface Commands {
|
|||||||
params: Bidi.Input.ReleaseActionsParameters;
|
params: Bidi.Input.ReleaseActionsParameters;
|
||||||
returnType: Bidi.EmptyResult;
|
returnType: Bidi.EmptyResult;
|
||||||
};
|
};
|
||||||
|
'input.setFiles': {
|
||||||
|
params: Bidi.Input.SetFilesParameters;
|
||||||
|
returnType: Bidi.EmptyResult;
|
||||||
|
};
|
||||||
|
|
||||||
'permissions.setPermission': {
|
'permissions.setPermission': {
|
||||||
params: Bidi.Permissions.SetPermissionParameters;
|
params: Bidi.Permissions.SetPermissionParameters;
|
||||||
|
@ -395,6 +395,12 @@
|
|||||||
"parameters": ["cdp", "firefox"],
|
"parameters": ["cdp", "firefox"],
|
||||||
"expectations": ["SKIP"]
|
"expectations": ["SKIP"]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[input.spec] input tests ElementHandle.uploadFile *",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["firefox", "webDriverBiDi"],
|
||||||
|
"expectations": ["FAIL"]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[jshandle.spec] JSHandle JSHandle.jsonValue should not throw for circular objects",
|
"testIdPattern": "[jshandle.spec] JSHandle JSHandle.jsonValue should not throw for circular objects",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
@ -1728,18 +1734,6 @@
|
|||||||
"parameters": ["firefox", "webDriverBiDi"],
|
"parameters": ["firefox", "webDriverBiDi"],
|
||||||
"expectations": ["FAIL"]
|
"expectations": ["FAIL"]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"testIdPattern": "[input.spec] input tests input should upload the file",
|
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
|
||||||
"parameters": ["chrome", "webDriverBiDi"],
|
|
||||||
"expectations": ["FAIL"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"testIdPattern": "[input.spec] input tests input should upload the file",
|
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
|
||||||
"parameters": ["firefox", "webDriverBiDi"],
|
|
||||||
"expectations": ["FAIL"]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"testIdPattern": "[input.spec] input tests Page.waitForFileChooser should prioritize exact timeout over default timeout",
|
"testIdPattern": "[input.spec] input tests Page.waitForFileChooser should prioritize exact timeout over default timeout",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
@ -17,14 +17,13 @@ const FILE_TO_UPLOAD = path.join(__dirname, '/../assets/file-to-upload.txt');
|
|||||||
describe('input tests', function () {
|
describe('input tests', function () {
|
||||||
setupTestBrowserHooks();
|
setupTestBrowserHooks();
|
||||||
|
|
||||||
describe('input', function () {
|
describe('ElementHandle.uploadFile', function () {
|
||||||
it('should upload the file', async () => {
|
it('should upload the file', async () => {
|
||||||
const {page, server} = await getTestState();
|
const {page, server} = await getTestState();
|
||||||
|
|
||||||
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);
|
|
||||||
using input = (await page.$('input'))!;
|
using input = (await page.$('input'))!;
|
||||||
await page.evaluate((e: HTMLElement) => {
|
await input.evaluate(e => {
|
||||||
(globalThis as any)._inputEvents = [];
|
(globalThis as any)._inputEvents = [];
|
||||||
e.addEventListener('change', ev => {
|
e.addEventListener('change', ev => {
|
||||||
return (globalThis as any)._inputEvents.push(ev.type);
|
return (globalThis as any)._inputEvents.push(ev.type);
|
||||||
@ -32,34 +31,63 @@ describe('input tests', function () {
|
|||||||
e.addEventListener('input', ev => {
|
e.addEventListener('input', ev => {
|
||||||
return (globalThis as any)._inputEvents.push(ev.type);
|
return (globalThis as any)._inputEvents.push(ev.type);
|
||||||
});
|
});
|
||||||
}, input);
|
});
|
||||||
await input.uploadFile(filePath);
|
|
||||||
|
const file = path.relative(process.cwd(), FILE_TO_UPLOAD);
|
||||||
|
await input.uploadFile(file);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
await page.evaluate((e: HTMLInputElement) => {
|
await input.evaluate(e => {
|
||||||
return e.files![0]!.name;
|
return e.files?.[0]?.name;
|
||||||
}, input)
|
})
|
||||||
).toBe('file-to-upload.txt');
|
).toBe('file-to-upload.txt');
|
||||||
expect(
|
expect(
|
||||||
await page.evaluate((e: HTMLInputElement) => {
|
await input.evaluate(e => {
|
||||||
return e.files![0]!.type;
|
return e.files?.[0]?.type;
|
||||||
}, input)
|
})
|
||||||
).toBe('text/plain');
|
).toBe('text/plain');
|
||||||
expect(
|
expect(
|
||||||
await page.evaluate(() => {
|
await page.evaluate(() => {
|
||||||
return (globalThis as any)._inputEvents;
|
return (globalThis as any)._inputEvents;
|
||||||
})
|
})
|
||||||
).toEqual(['input', 'change']);
|
).toEqual(['input', 'change']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should read the file', async () => {
|
||||||
|
const {page, server} = await getTestState();
|
||||||
|
|
||||||
|
await page.goto(server.PREFIX + '/input/fileupload.html');
|
||||||
|
using input = (await page.$('input'))!;
|
||||||
|
await input.evaluate(e => {
|
||||||
|
(globalThis as any)._inputEvents = [];
|
||||||
|
e.addEventListener('change', ev => {
|
||||||
|
return (globalThis as any)._inputEvents.push(ev.type);
|
||||||
|
});
|
||||||
|
e.addEventListener('input', ev => {
|
||||||
|
return (globalThis as any)._inputEvents.push(ev.type);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const file = path.relative(process.cwd(), FILE_TO_UPLOAD);
|
||||||
|
await input.uploadFile(file);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
await page.evaluate((e: HTMLInputElement) => {
|
await input.evaluate(e => {
|
||||||
|
const file = e.files?.[0];
|
||||||
|
if (!file) {
|
||||||
|
throw new Error('No file found');
|
||||||
|
}
|
||||||
|
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
const promise = new Promise(fulfill => {
|
const promise = new Promise(fulfill => {
|
||||||
return (reader.onload = fulfill);
|
reader.addEventListener('load', fulfill);
|
||||||
});
|
});
|
||||||
reader.readAsText(e.files![0]!);
|
reader.readAsText(file);
|
||||||
|
|
||||||
return promise.then(() => {
|
return promise.then(() => {
|
||||||
return reader.result;
|
return reader.result;
|
||||||
});
|
});
|
||||||
}, input)
|
})
|
||||||
).toBe('contents of the file');
|
).toBe('contents of the file');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user