diff --git a/package.json b/package.json index 5f552c7b38..02c35c6579 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "node": ">=10.17.0" }, "playwright": { - "chromium_revision": "718525", + "chromium_revision": "719491", "firefox_revision": "1004", "webkit_revision": "1001" }, diff --git a/src/chromium/ExecutionContext.ts b/src/chromium/ExecutionContext.ts index 6165ed5e77..42177aae89 100644 --- a/src/chromium/ExecutionContext.ts +++ b/src/chromium/ExecutionContext.ts @@ -151,7 +151,7 @@ export class ExecutionContext implements types.EvaluationContext { } } - async _adoptBackendNodeId(backendNodeId: Protocol.DOM.BackendNodeId) { + async _adoptBackendNodeId(backendNodeId: Protocol.DOM.BackendNodeId): Promise { const {object} = await this._client.send('DOM.resolveNode', { backendNodeId, executionContextId: this._contextId, diff --git a/src/chromium/FrameManager.ts b/src/chromium/FrameManager.ts index 68bd8e07c6..6026b608e6 100644 --- a/src/chromium/FrameManager.ts +++ b/src/chromium/FrameManager.ts @@ -25,7 +25,7 @@ import { LifecycleWatcher } from './LifecycleWatcher'; import { NetworkManager, Response } from './NetworkManager'; import { Page } from './Page'; import { Protocol } from './protocol'; -import { ElementHandle, createJSHandle } from './JSHandle'; +import { ElementHandle } from './JSHandle'; const UTILITY_WORLD_NAME = '__playwright_utility_world__'; diff --git a/src/chromium/JSHandle.ts b/src/chromium/JSHandle.ts index 77c065d4de..c1f1cbbe3f 100644 --- a/src/chromium/JSHandle.ts +++ b/src/chromium/JSHandle.ts @@ -34,7 +34,7 @@ type Point = { y: number; }; -export function createJSHandle(context: ExecutionContext, remoteObject: Protocol.Runtime.RemoteObject) { +export function createJSHandle(context: ExecutionContext, remoteObject: Protocol.Runtime.RemoteObject): JSHandle { const frame = context.frame(); if (remoteObject.subtype === 'node' && frame) { const frameManager = frame._delegate as FrameManager; diff --git a/src/chromium/Page.ts b/src/chromium/Page.ts index d34c03cdfd..06e70570f6 100644 --- a/src/chromium/Page.ts +++ b/src/chromium/Page.ts @@ -18,7 +18,6 @@ import { EventEmitter } from 'events'; import * as fs from 'fs'; import * as mime from 'mime'; -import * as path from 'path'; import { assert, debugError, helper } from '../helper'; import { ClickOptions, MultiClickOptions, PointerActionOptions, SelectOption, mediaTypes, mediaColorSchemes } from '../input'; import { TimeoutSettings } from '../TimeoutSettings'; @@ -76,7 +75,6 @@ export class Page extends EventEmitter { _javascriptEnabled = true; private _viewport: Viewport | null = null; private _screenshotTaskQueue: TaskQueue; - private _fileChooserInterceptionIsDisabled = false; private _fileChooserInterceptors = new Set<(chooser: FileChooser) => void>(); private _disconnectPromise: Promise | undefined; private _emulatedMediaType: string | undefined; @@ -135,7 +133,7 @@ export class Page extends EventEmitter { client.on('Runtime.exceptionThrown', exception => this._handleException(exception.exceptionDetails)); client.on('Inspector.targetCrashed', event => this._onTargetCrashed()); client.on('Log.entryAdded', event => this._onLogEntryAdded(event)); - client.on('Page.fileChooserOpened', event => this._onFileChooser(event)); + client.on('Page.fileChooserOpened', event => this._onFileChooserOpened(event)); this._target._isClosedPromise.then(() => { this.emit(Events.Page.Close); this._closed = true; @@ -148,27 +146,25 @@ export class Page extends EventEmitter { this._client.send('Target.setAutoAttach', {autoAttach: true, waitForDebuggerOnStart: false, flatten: true}), this._client.send('Performance.enable', {}), this._client.send('Log.enable', {}), - this._client.send('Page.setInterceptFileChooserDialog', {enabled: true}).catch(e => { - this._fileChooserInterceptionIsDisabled = true; - }), + this._client.send('Page.setInterceptFileChooserDialog', {enabled: true}) ]); } - _onFileChooser(event: Protocol.Page.fileChooserOpenedPayload) { - if (!this._fileChooserInterceptors.size) { - this._client.send('Page.handleFileChooser', { action: 'fallback' }).catch(debugError); + async _onFileChooserOpened(event: Protocol.Page.fileChooserOpenedPayload) { + if (!this._fileChooserInterceptors.size) return; - } + const frame = this._frameManager.frame(event.frameId); + const context = await frame._utilityContext(); + const handle = await context._adoptBackendNodeId(event.backendNodeId); const interceptors = Array.from(this._fileChooserInterceptors); this._fileChooserInterceptors.clear(); - const fileChooser = new FileChooser(this._client, event); + const multiple = await handle.evaluate((element: HTMLInputElement) => !!element.multiple); + const fileChooser = new FileChooser(handle, multiple); for (const interceptor of interceptors) interceptor.call(null, fileChooser); } async waitForFileChooser(options: { timeout?: number; } = {}): Promise { - if (this._fileChooserInterceptionIsDisabled) - throw new Error('File chooser handling does not work with multiple connections to the same page'); const { timeout = this._timeoutSettings.timeout(), } = options; @@ -732,13 +728,13 @@ export class ConsoleMessage { } export class FileChooser { - private _client: CDPSession; + private _element: ElementHandle; private _multiple: boolean; private _handled = false; - constructor(client: CDPSession, event: Protocol.Page.fileChooserOpenedPayload) { - this._client = client; - this._multiple = event.mode !== 'selectSingle'; + constructor(element: ElementHandle, multiple: boolean) { + this._element = element; + this._multiple = multiple; } isMultiple(): boolean { @@ -748,18 +744,11 @@ export class FileChooser { async accept(filePaths: string[]): Promise { assert(!this._handled, 'Cannot accept FileChooser which is already handled!'); this._handled = true; - const files = filePaths.map(filePath => path.resolve(filePath)); - await this._client.send('Page.handleFileChooser', { - action: 'accept', - files, - }); + await this._element.setInputFiles(...filePaths); } async cancel(): Promise { assert(!this._handled, 'Cannot cancel FileChooser which is already handled!'); this._handled = true; - await this._client.send('Page.handleFileChooser', { - action: 'cancel', - }); } } diff --git a/src/firefox/Browser.ts b/src/firefox/Browser.ts index 1e2ec8437e..984dd169b7 100644 --- a/src/firefox/Browser.ts +++ b/src/firefox/Browser.ts @@ -304,11 +304,10 @@ export class BrowserContext extends EventEmitter { } async setCookies(cookies: SetNetworkCookieParam[]) { - const items = cookies.map(cookie => { + cookies.forEach(cookie => { const item = Object.assign({}, cookie); assert(item.url !== 'about:blank', `Blank page can not have cookie "${item.name}"`); assert(!String.prototype.startsWith.call(item.url || '', 'data:'), `Data URL page can not have cookie "${item.name}"`); - return item; }); await this._connection.send('Browser.setCookies', { browserContextId: this._browserContextId || undefined, diff --git a/src/firefox/JSHandle.ts b/src/firefox/JSHandle.ts index 4ad47fdfe9..767ef8e0f2 100644 --- a/src/firefox/JSHandle.ts +++ b/src/firefox/JSHandle.ts @@ -15,7 +15,6 @@ * limitations under the License. */ -import * as fs from 'fs'; import { assert, debugError, helper } from '../helper'; import Injected from '../injected/injected'; import * as input from '../input'; @@ -25,7 +24,6 @@ import { ExecutionContext } from './ExecutionContext'; import { Frame } from './FrameManager'; type SelectorRoot = Element | ShadowRoot | Document; -const readFileAsync = helper.promisify(fs.readFile); export class JSHandle { _context: ExecutionContext; diff --git a/src/firefox/Page.ts b/src/firefox/Page.ts index 145d43cc80..b3476d75a0 100644 --- a/src/firefox/Page.ts +++ b/src/firefox/Page.ts @@ -5,7 +5,7 @@ import { TimeoutError } from '../Errors'; import { assert, debugError, helper, RegisteredListener } from '../helper'; import { TimeoutSettings } from '../TimeoutSettings'; import { BrowserContext, Target } from './Browser'; -import { Connection, JugglerSession, JugglerSessionEvents } from './Connection'; +import { JugglerSession, JugglerSessionEvents } from './Connection'; import { Dialog } from './Dialog'; import { Events } from './events'; import { Accessibility } from './features/accessibility'; @@ -35,7 +35,6 @@ export class Page extends EventEmitter { private _eventListeners: RegisteredListener[]; private _viewport: Viewport; private _disconnectPromise: Promise; - private _fileChooserInterceptionIsDisabled = false; private _fileChooserInterceptors = new Set<(chooser: FileChooser) => void>(); static async create(session: JugglerSession, target: Target, defaultViewport: Viewport | null) { @@ -44,9 +43,7 @@ export class Page extends EventEmitter { session.send('Runtime.enable'), session.send('Network.enable'), session.send('Page.enable'), - session.send('Page.setInterceptFileChooserDialog', { enabled: true }).catch(e => { - page._fileChooserInterceptionIsDisabled = true; - }), + session.send('Page.setInterceptFileChooserDialog', { enabled: true }) ]); if (defaultViewport) @@ -534,8 +531,6 @@ export class Page extends EventEmitter { } async waitForFileChooser(options: { timeout?: number; } = {}): Promise { - if (this._fileChooserInterceptionIsDisabled) - throw new Error('File chooser handling does not work with multiple connections to the same page'); const { timeout = this._timeoutSettings.timeout(), } = options; @@ -549,11 +544,9 @@ export class Page extends EventEmitter { } async _onFileChooserOpened({executionContextId, element}) { - const context = this._frameManager.executionContextById(executionContextId); - if (!this._fileChooserInterceptors.size) { - this._session.send('Page.handleFileChooser', { action: 'fallback' }).catch(debugError); + if (!this._fileChooserInterceptors.size) return; - } + const context = this._frameManager.executionContextById(executionContextId); const handle = createHandle(context, element) as ElementHandle; const interceptors = Array.from(this._fileChooserInterceptors); this._fileChooserInterceptors.clear(); diff --git a/src/input.ts b/src/input.ts index 0a8f795155..cae8290856 100644 --- a/src/input.ts +++ b/src/input.ts @@ -351,16 +351,16 @@ export const loadFiles = async (items: (string|FilePayload)[]): Promise { if (typeof item === 'string') { const file: FilePayload = { - name: path.basename(item), - type: 'application/octet-stream', - data: (await readFileAsync(item)).toString('base64') + 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) => { diff --git a/test/chromiumonly.spec.js b/test/chromiumonly.spec.js index 9e81c78562..a5e1141570 100644 --- a/test/chromiumonly.spec.js +++ b/test/chromiumonly.spec.js @@ -93,23 +93,6 @@ module.exports.addLauncherTests = function({testRunner, expect, defaultBrowserOp await disconnectedEventPromise; }); }); - - describe('Page.waitForFileChooser', () => { - it('should fail gracefully when trying to work with filechoosers within multiple connections', async() => { - // 1. Launch a browser and connect to all pages. - const originalBrowser = await playwright.launch(defaultBrowserOptions); - await originalBrowser.pages(); - // 2. Connect a remote browser and connect to first page. - const remoteBrowser = await playwright.connect({browserWSEndpoint: originalBrowser.chromium.wsEndpoint()}); - const [page] = await remoteBrowser.pages(); - // 3. Make sure |page.waitForFileChooser()| does not work with multiclient. - let error = null; - await page.waitForFileChooser().catch(e => error = e); - expect(error.message).toBe('File chooser handling does not work with multiple connections to the same page'); - originalBrowser.close(); - }); - - }); }); };