diff --git a/docs/src/api/class-dialog.md b/docs/src/api/class-dialog.md index 406febe549e90..e093705b264d7 100644 --- a/docs/src/api/class-dialog.md +++ b/docs/src/api/class-dialog.md @@ -1,4 +1,3 @@ - # class: Dialog [Dialog] objects are dispatched by page via the [`event: Page.dialog`] event. diff --git a/docs/src/api/class-electron.md b/docs/src/api/class-electron.md new file mode 100644 index 0000000000000..6a5489c422b9a --- /dev/null +++ b/docs/src/api/class-electron.md @@ -0,0 +1,76 @@ +# class: Electron +* langs: js + +Playwright has **experimental** support for Electron automation. You can access electron namespace via: + +```js +const { _electron } = require('playwright'); +``` + +An example of the Electron automation script would be: + +```js +const { _electron: electron } = require('playwright'); + +(async () => { + // Launch Electron app. + const electronApp = await electron.launch({ args: ['main.js'] }); + + // Evaluation expression in the Electron context. + const appPath = await electronApp.evaluate(async (electron) => { + // This runs in the main Electron process, |electron| parameter + // here is always the result of the require('electron') in the main + // app script. + return electron.getAppPath(); + }); + + // Get the first window that the app opens, wait if necessary. + const window = await electronApp.firstWindow(); + // Print the title. + console.log(await window.title()); + // Capture a screenshot. + await window.screenshot({ path: 'intro.png' }); + // Direct Electron console to Node terminal. + window.on('console', console.log); + // Click button. + await window.click('text=Click me'); +})(); +``` + +Note that since you don't need Playwright to install web browsers when testing Electron, you can omit browser download via setting the following environment variable when installing Playwright: + +```sh js +$ PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm i -D playwright +``` + +## async method: Electron.launch +- returns: <[ElectronApplication]> + +Launches electron application specified with the [`option: executablePath`]. + +### option: Electron.launch.executablePath +- `executablePath` <[string]> + +Launches given Electron application. If not specified, launches the default Electron +executable installed in this package, located at `node_modules/.bin/electron`. + +### option: Electron.launch.args +- `args` <[Array]<[string]>> + +Additional arguments to pass to the application when launching. You typically pass the main +script name here. + +### option: Electron.launch.cwd +- `cwd` <[string]> + +Current working directory to launch application from. + +### option: Electron.launch.env +- `env` <[Object]<[string], [string]>> + +Specifies environment variables that will be visible to Electron. Defaults to `process.env`. + +#### option: Electron.launch.timeout +- `timeout` <[float]> + +Maximum time in milliseconds to wait for the application to start. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. diff --git a/docs/src/api/class-electronapplication.md b/docs/src/api/class-electronapplication.md new file mode 100644 index 0000000000000..77af88ff46418 --- /dev/null +++ b/docs/src/api/class-electronapplication.md @@ -0,0 +1,129 @@ +# class: ElectronApplication +* langs: js + +Electron application representation. You can use [`method: Electron.launch`] to +obtain the application instance. This instance you can control main electron process +as well as work with Electron windows: + +```js +const { _electron: electron } = require('playwright'); + +(async () => { + // Launch Electron app. + const electronApp = await electron.launch({ args: ['main.js'] }); + + // Evaluation expression in the Electron context. + const appPath = await electronApp.evaluate(async (electron) => { + // This runs in the main Electron process, |electron| parameter + // here is always the result of the require('electron') in the main + // app script. + return electron.getAppPath(); + }); + + // Get the first window that the app opens, wait if necessary. + const window = await electronApp.firstWindow(); + // Print the title. + console.log(await window.title()); + // Capture a screenshot. + await window.screenshot({ path: 'intro.png' }); + // Direct Electron console to Node terminal. + window.on('console', console.log); + // Click button. + await window.click('text=Click me'); +})(); +``` + +## event: ElectronApplication.close + +This event is issued when the application closes. + +## event: ElectronApplication.window +- type: <[Page]> + +This event is issued for every window that is created **and loaded** in Electron. It contains a [Page] that can +be used for Playwright automation. + +## async method: ElectronApplication.close + +Closes Electron application. + +## method: ElectronApplication.context +- type: <[BrowserContext]> + +This method returns browser context that can be used for setting up context-wide routing, etc. + +## async method: ElectronApplication.evaluate +- returns: <[Serializable]> + +Returns the return value of [`param: expression`]. + +If the function passed to the [`method: ElectronApplication.evaluate`] returns a [Promise], then +[`method: ElectronApplication.evaluate`] would wait for the promise to resolve and return its value. + +If the function passed to the [`method: ElectronApplication.evaluate`] returns a non-[Serializable] value, then +[`method: ElectronApplication.evaluate`] returns `undefined`. Playwright also supports transferring +some additional values that are not serializable by `JSON`: `-0`, `NaN`, `Infinity`, `-Infinity`. + +### param: ElectronApplication.evaluate.expression = %%-evaluate-expression-%% + +### param: ElectronApplication.evaluate.arg +- `arg` <[EvaluationArgument]> + +Optional argument to pass to [`param: expression`]. + +## async method: ElectronApplication.evaluateHandle +- returns: <[JSHandle]> + +Returns the return value of [`param: expression`] as a [JSHandle]. + +The only difference between [`method: ElectronApplication.evaluate`] and [`method: ElectronApplication.evaluateHandle`] is that [`method: ElectronApplication.evaluateHandle`] returns [JSHandle]. + +If the function passed to the [`method: ElectronApplication.evaluateHandle`] returns a [Promise], then +[`method: ElectronApplication.evaluateHandle`] would wait for the promise to resolve and return its value. + +### param: ElectronApplication.evaluateHandle.expression = %%-evaluate-expression-%% + +### param: ElectronApplication.evaluateHandle.arg +- `arg` <[EvaluationArgument]> + +## async method: ElectronApplication.firstWindow +- returns: <[Page]> + +Convenience method that waits for the first application window to be opened. +Typically your script will start with: + +```js + const electronApp = await electron.launch({ + args: ['main.js'] + }); + const window = await electronApp.firstWindow(); + // ... +``` + +## async method: ElectronApplication.waitForEvent +- returns: <[any]> + +Waits for event to fire and passes its value into the predicate function. Returns when the predicate returns truthy value. Will throw an error if the application is closed before the event is fired. Returns the event data value. + +```js +const [window] = await Promise.all([ + electronApp.waitForEvent('window'), + mainWindow.click('button') +]); +``` + +### param: ElectronApplication.waitForEvent.event = %%-wait-for-event-event-%% + +### param: ElectronApplication.waitForEvent.optionsOrPredicate +* langs: js +- `optionsOrPredicate` <[function]|[Object]> + - `predicate` <[function]> receives the event data and resolves to truthy value when the waiting should resolve. + - `timeout` <[float]> maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to + disable timeout. The default value can be changed by using the [`method: BrowserContext.setDefaultTimeout`]. + +Either a predicate that receives an event or an options object. Optional. + +## method: ElectronApplication.windows +- returns: <[Array]<[Page]>> + +Convenience method that returns all the opened windows. diff --git a/docs/src/api/class-elementhandle.md b/docs/src/api/class-elementhandle.md index 556d3769ac7de..fb898204ef5a7 100644 --- a/docs/src/api/class-elementhandle.md +++ b/docs/src/api/class-elementhandle.md @@ -247,7 +247,7 @@ Optional event-specific initialization properties. - alias-js: $eval - returns: <[Serializable]> -Returns the return value of [`param: expression`] +Returns the return value of [`param: expression`]. The method finds an element matching the specified selector in the `ElementHandle`s subtree and passes it as a first argument to [`param: expression`]. See [Working with selectors](./selectors.md) for more @@ -291,7 +291,7 @@ Optional argument to pass to [`param: expression`] - alias-js: $$eval - returns: <[Serializable]> -Returns the return value of [`param: expression`] +Returns the return value of [`param: expression`]. The method finds all elements matching the specified selector in the `ElementHandle`'s subtree and passes an array of matched elements as a first argument to [`param: expression`]. See diff --git a/docs/src/api/class-frame.md b/docs/src/api/class-frame.md index a7ea739aec65f..513b1cfc33a99 100644 --- a/docs/src/api/class-frame.md +++ b/docs/src/api/class-frame.md @@ -300,7 +300,7 @@ Optional event-specific initialization properties. - alias-js: $eval - returns: <[Serializable]> -Returns the return value of [`param: expression`] +Returns the return value of [`param: expression`]. The method finds an element matching the specified selector within the frame and passes it as a first argument to [`param: expression`]. See [Working with selectors](./selectors.md) for more details. If no @@ -344,7 +344,7 @@ Optional argument to pass to [`param: expression`] - alias-js: $$eval - returns: <[Serializable]> -Returns the return value of [`param: expression`] +Returns the return value of [`param: expression`]. The method finds all elements matching the specified selector within the frame and passes an array of matched elements as a first argument to [`param: expression`]. See [Working with selectors](./selectors.md) for @@ -379,14 +379,14 @@ Optional argument to pass to [`param: expression`] ## async method: Frame.evaluate - returns: <[Serializable]> -Returns the return value of [`param: expression`] +Returns the return value of [`param: expression`]. If the function passed to the [`method: Frame.evaluate`] returns a [Promise], then [`method: Frame.evaluate`] would wait for the promise to resolve and return its value. If the function passed to the [`method: Frame.evaluate`] returns a non-[Serializable] value, then -[`method: Frame.evaluate`] returns `undefined`. DevTools Protocol also supports transferring some additional values that -are not serializable by `JSON`: `-0`, `NaN`, `Infinity`, `-Infinity`, and bigint literals. +[`method: Frame.evaluate`] returns `undefined`. Playwright also supports transferring some +additional values that are not serializable by `JSON`: `-0`, `NaN`, `Infinity`, `-Infinity`. ```js const result = await frame.evaluate(([x, y]) => { @@ -455,10 +455,10 @@ Optional argument to pass to [`param: expression`] ## async method: Frame.evaluateHandle - returns: <[JSHandle]> -Returns the return value of [`param: expression`] as in-page object (JSHandle). +Returns the return value of [`param: expression`] as a [JSHandle]. The only difference between [`method: Frame.evaluate`] and [`method: Frame.evaluateHandle`] is that -[method: Frame.evaluateHandle`] returns in-page object (JSHandle). +[method: Frame.evaluateHandle`] returns [JSHandle]. If the function, passed to the [`method: Frame.evaluateHandle`], returns a [Promise], then [`method: Frame.evaluateHandle`] would wait for the promise to resolve and return its value. diff --git a/docs/src/api/class-jshandle.md b/docs/src/api/class-jshandle.md index 16de6728a29b5..9d8cd01a3092a 100644 --- a/docs/src/api/class-jshandle.md +++ b/docs/src/api/class-jshandle.md @@ -37,7 +37,7 @@ The `jsHandle.dispose` method stops referencing the element handle. ## async method: JSHandle.evaluate - returns: <[Serializable]> -Returns the return value of [`param: expression`] +Returns the return value of [`param: expression`]. This method passes this handle as the first argument to [`param: expression`]. @@ -71,12 +71,11 @@ Optional argument to pass to [`param: expression`] ## async method: JSHandle.evaluateHandle - returns: <[JSHandle]> -Returns the return value of [`param: expression`] as in-page object (JSHandle). +Returns the return value of [`param: expression`] as a [JSHandle]. This method passes this handle as the first argument to [`param: expression`]. -The only difference between `jsHandle.evaluate` and `jsHandle.evaluateHandle` is that `jsHandle.evaluateHandle` returns -in-page object (JSHandle). +The only difference between `jsHandle.evaluate` and `jsHandle.evaluateHandle` is that `jsHandle.evaluateHandle` returns [JSHandle]. If the function passed to the `jsHandle.evaluateHandle` returns a [Promise], then `jsHandle.evaluateHandle` would wait for the promise to resolve and return its value. diff --git a/docs/src/api/class-page.md b/docs/src/api/class-page.md index 8020d8e06bd1c..bc8da6fafb077 100644 --- a/docs/src/api/class-page.md +++ b/docs/src/api/class-page.md @@ -808,8 +808,8 @@ If the function passed to the [`method: Page.evaluate`] returns a [Promise], the for the promise to resolve and return its value. If the function passed to the [`method: Page.evaluate`] returns a non-[Serializable] value, then -[`method: Page.evaluate`] resolves to `undefined`. DevTools Protocol also supports transferring some additional values -that are not serializable by `JSON`: `-0`, `NaN`, `Infinity`, `-Infinity`, and bigint literals. +[`method: Page.evaluate`] resolves to `undefined`. Playwright also supports transferring some +additional values that are not serializable by `JSON`: `-0`, `NaN`, `Infinity`, `-Infinity`. Passing argument to [`param: expression`]: @@ -882,10 +882,9 @@ Optional argument to pass to [`param: expression`] ## async method: Page.evaluateHandle - returns: <[JSHandle]> -Returns the value of the [`param: expression`] invocation as in-page object (JSHandle). +Returns the value of the [`param: expression`] invocation as a [JSHandle]. -The only difference between [`method: Page.evaluate`] and [`method: Page.evaluateHandle`] is that [`method: Page.evaluateHandle`] returns in-page -object (JSHandle). +The only difference between [`method: Page.evaluate`] and [`method: Page.evaluateHandle`] is that [`method: Page.evaluateHandle`] returns [JSHandle]. If the function passed to the [`method: Page.evaluateHandle`] returns a [Promise], then [`method: Page.evaluateHandle`] would wait for the promise to resolve and return its value. diff --git a/docs/src/api/class-worker.md b/docs/src/api/class-worker.md index b612c667803e0..89ee1961f4f06 100644 --- a/docs/src/api/class-worker.md +++ b/docs/src/api/class-worker.md @@ -35,14 +35,13 @@ Emitted when this dedicated [WebWorker](https://developer.mozilla.org/en-US/docs ## async method: Worker.evaluate - returns: <[Serializable]> -Returns the return value of [`param: expression`] +Returns the return value of [`param: expression`]. -If the function passed to the `worker.evaluate` returns a [Promise], then `worker.evaluate` would wait for the promise +If the function passed to the [`method: Worker.evaluate`] returns a [Promise], then [`method: Worker.evaluate`] would wait for the promise to resolve and return its value. -If the function passed to the `worker.evaluate` returns a non-[Serializable] value, then `worker.evaluate` returns -`undefined`. DevTools Protocol also supports transferring some additional values that are not serializable by `JSON`: -`-0`, `NaN`, `Infinity`, `-Infinity`, and bigint literals. +If the function passed to the [`method: Worker.evaluate`] returns a non-[Serializable] value, then [`method: Worker.evaluate`] returns `undefined`. Playwright also supports transferring some +additional values that are not serializable by `JSON`: `-0`, `NaN`, `Infinity`, `-Infinity`. ### param: Worker.evaluate.expression = %%-evaluate-expression-%% @@ -54,12 +53,13 @@ Optional argument to pass to [`param: expression`] ## async method: Worker.evaluateHandle - returns: <[JSHandle]> -Returns the return value of [`param: expression`] as in-page object (JSHandle). +Returns the return value of [`param: expression`] as a [JSHandle]. -The only difference between `worker.evaluate` and `worker.evaluateHandle` is that `worker.evaluateHandle` returns -in-page object (JSHandle). +The only difference between [`method: Worker.evaluate`] and +[`method: Worker.evaluateHandle`] is that [`method: Worker.evaluateHandle`] +returns [JSHandle]. -If the function passed to the `worker.evaluateHandle` returns a [Promise], then `worker.evaluateHandle` would wait for +If the function passed to the [`method: Worker.evaluateHandle`] returns a [Promise], then [`method: Worker.evaluateHandle`] would wait for the promise to resolve and return its value. ### param: Worker.evaluateHandle.expression = %%-evaluate-expression-%% diff --git a/docs/src/api/javascript.md b/docs/src/api/javascript.md index 3dbcfdb8da1f9..36e164684972d 100644 --- a/docs/src/api/javascript.md +++ b/docs/src/api/javascript.md @@ -14,3 +14,5 @@ ### param: Page.waitForFunction.expression = %%-js-evaluate-pagefunction-%% ### param: Worker.evaluate.expression = %%-js-worker-evaluate-workerfunction-%% ### param: Worker.evaluateHandle.expression = %%-js-worker-evaluate-workerfunction-%% +### param: ElectronApplication.evaluate.expression = %%-js-electron-evaluate-workerfunction-%% +### param: ElectronApplication.evaluateHandle.expression = %%-js-electron-evaluate-workerfunction-%% diff --git a/docs/src/api/params.md b/docs/src/api/params.md index c5abedd4193e1..cddc8b032ce9e 100644 --- a/docs/src/api/params.md +++ b/docs/src/api/params.md @@ -157,32 +157,39 @@ Sets a consistent viewport for each page. Defaults to an 1280x720 viewport. `nul ## evaluate-expression - `expression` <[string]> -JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, -it is interpreted as a function. Otherwise, evaluated as an expression. +JavaScript expression to be evaluated in the browser context. If it looks like +a function declaration, it is interpreted as a function. Otherwise, evaluated +as an expression. ## js-evaluate-pagefunction * langs: js - `pageFunction` <[function]|[string]> -Function to be evaluated in the page context +Function to be evaluated in the page context. ## js-evalonselector-pagefunction * langs: js - `pageFunction` <[function]\([Element]\)> -Function to be evaluated in the page context +Function to be evaluated in the page context. ## js-evalonselectorall-pagefunction * langs: js - `pageFunction` <[function]\([Array]<[Element]>\)> -Function to be evaluated in the page context +Function to be evaluated in the page context. ## js-worker-evaluate-workerfunction * langs: js - `pageFunction` <[function]|[string]> -Function to be evaluated in the worker context +Function to be evaluated in the worker context. + +## js-electron-evaluate-workerfunction +* langs: js +- `pageFunction` <[function]|[Electron]> + +Function to be evaluated in the worker context. ## python-context-option-viewport * langs: python diff --git a/docs/src/debug.md b/docs/src/debug.md index 9a66b02246071..7db45b7b265fc 100644 --- a/docs/src/debug.md +++ b/docs/src/debug.md @@ -132,7 +132,7 @@ composite selectors. To use this: ### Evaluate Source Maps -PWDEBUG also enables source maps for [`page.evaluate` executions](./core-concepts.md#evaluation). +PWDEBUG also enables source maps for [`method: Page.evaluate`] [executions](./core-concepts.md#evaluation). This improves the debugging experience for JavaScript executions in the page context. Highlight selectors diff --git a/packages/installation-tests/installation-tests.sh b/packages/installation-tests/installation-tests.sh index 9acc0369fc9d9..0372674e2799e 100755 --- a/packages/installation-tests/installation-tests.sh +++ b/packages/installation-tests/installation-tests.sh @@ -311,7 +311,7 @@ function test_electron_types { npm install electron@9.0 npm install -D typescript@3.8 npm install -D @types/node@10.17 - echo "import { Page, electron, ElectronApplication, ElectronLauncher } from 'playwright-electron';" > "test.ts" + echo "import { Page, electron, ElectronApplication, Electron } from 'playwright-electron';" > "test.ts" echo "Running tsc" npx tsc "test.ts" diff --git a/packages/installation-tests/sanity-electron.js b/packages/installation-tests/sanity-electron.js index 85489243e6576..4c5bf0f676169 100644 --- a/packages/installation-tests/sanity-electron.js +++ b/packages/installation-tests/sanity-electron.js @@ -18,9 +18,7 @@ const playwright = require('playwright-electron'); const path = require('path'); (async () => { - const electronName = process.platform === 'win32' ? 'electron.cmd' : 'electron'; - const electronPath = path.join(__dirname, 'node_modules', '.bin', electronName); - const application = await playwright.electron.launch(electronPath, { + const application = await playwright.electron.launch({ args: [path.join(__dirname, 'electron-app.js')], }); const appPath = await application.evaluate(async ({ app }) => app.getAppPath()); diff --git a/packages/playwright-electron/README.md b/packages/playwright-electron/README.md index 911488ac4e493..3e4266fe002c8 100644 --- a/packages/playwright-electron/README.md +++ b/packages/playwright-electron/README.md @@ -61,7 +61,6 @@ app.whenReady().then(createWindow); ```js const { electron } = require('playwright-electron'); const assert = require('assert'); -const electronPath = require('electron'); const path = require('path') describe('Sanity checks', function () { @@ -69,8 +68,7 @@ describe('Sanity checks', function () { beforeEach(async () => { // Before each test start Electron application. - this.app = await electron.launch(electronPath, { - path: electronPath, + this.app = await electron.launch({ args: [path.join(__dirname, '..')] // loads index.js }); }); diff --git a/packages/playwright-electron/index.d.ts b/packages/playwright-electron/index.d.ts index b83cfea185c6b..95e790b5217c0 100644 --- a/packages/playwright-electron/index.d.ts +++ b/packages/playwright-electron/index.d.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { ElectronLauncher } from './types/electron'; +import { Electron } from './types/types'; export * from './types/types'; -export * from './types/electron'; -export const electron: ElectronLauncher; +export const electron: Electron; diff --git a/src/client/api.ts b/src/client/api.ts index e64e693130762..9350833cbe252 100644 --- a/src/client/api.ts +++ b/src/client/api.ts @@ -22,6 +22,7 @@ export { BrowserType } from './browserType'; export { ConsoleMessage } from './consoleMessage'; export { Dialog } from './dialog'; export { Download } from './download'; +export { Electron, ElectronApplication } from './electron'; export { ElementHandle } from './elementHandle'; export { FileChooser } from './fileChooser'; export { Logger } from './types'; diff --git a/src/client/electron.ts b/src/client/electron.ts index 0dc8c8dca0d12..ed50f9e2289c6 100644 --- a/src/client/electron.ts +++ b/src/client/electron.ts @@ -14,28 +14,27 @@ * limitations under the License. */ +import * as structs from '../../types/structs'; +import * as api from '../../types/types'; import * as channels from '../protocol/channels'; +import { TimeoutSettings } from '../utils/timeoutSettings'; import { BrowserContext } from './browserContext'; import { ChannelOwner } from './channelOwner'; +import type { ChromiumBrowserContext } from './chromiumBrowserContext'; +import { envObjectToArray } from './clientHelper'; +import { Events } from './events'; +import { JSHandle, parseResult, serializeArgument } from './jsHandle'; import { Page } from './page'; -import { serializeArgument, parseResult, JSHandle } from './jsHandle'; -import { TimeoutSettings } from '../utils/timeoutSettings'; +import { Env, WaitForEventOptions } from './types'; import { Waiter } from './waiter'; -import { Events } from './events'; -import { WaitForEventOptions, Env, Logger } from './types'; -import { envObjectToArray } from './clientHelper'; -import * as electronApi from '../../types/electron'; -import * as structs from '../../types/structs'; -import type { ChromiumBrowserContext } from './chromiumBrowserContext'; type ElectronOptions = Omit & { env?: Env, - logger?: Logger, }; type ElectronAppType = typeof import('electron'); -export class Electron extends ChannelOwner implements electronApi.ElectronLauncher { +export class Electron extends ChannelOwner implements api.Electron { static from(electron: channels.ElectronChannel): Electron { return (electron as any)._object; } @@ -44,21 +43,18 @@ export class Electron extends ChannelOwner { - const logger = options.logger; - options = { ...options, logger: undefined }; + async launch(options: ElectronOptions = {}): Promise { return this._wrapApiCall('electron.launch', async () => { const params: channels.ElectronLaunchParams = { ...options, - env: options.env ? envObjectToArray(options.env) : undefined, - executablePath, + env: envObjectToArray(options.env ? options.env : process.env), }; return ElectronApplication.from((await this._channel.launch(params)).electronApplication); - }, logger); + }); } } -export class ElectronApplication extends ChannelOwner implements electronApi.ElectronApplication { +export class ElectronApplication extends ChannelOwner implements api.ElectronApplication { private _context?: BrowserContext; private _windows = new Set(); private _timeoutSettings = new TimeoutSettings(); @@ -80,12 +76,12 @@ export class ElectronApplication extends ChannelOwner this.emit(Events.ElectronApplication.Close)); } - windows(): electronApi.ElectronPage[] { + windows(): Page[] { // TODO: add ElectronPage class inherting from Page. - return [...this._windows] as any as electronApi.ElectronPage[]; + return [...this._windows]; } - async firstWindow(): Promise { + async firstWindow(): Promise { return this._wrapApiCall('electronApplication.firstWindow', async () => { if (this._windows.size) return this._windows.values().next().value; @@ -93,13 +89,6 @@ export class ElectronApplication extends ChannelOwner { - return this._wrapApiCall('electronApplication.newBrowserWindow', async () => { - const result = await this._channel.newBrowserWindow({ arg: serializeArgument(options) }); - return Page.from(result.page) as any as electronApi.ElectronPage; - }); - } - context(): ChromiumBrowserContext { return this._context! as ChromiumBrowserContext; } diff --git a/src/dispatchers/electronDispatcher.ts b/src/dispatchers/electronDispatcher.ts index e3d7703a096ad..1bd167b843d6c 100644 --- a/src/dispatchers/electronDispatcher.ts +++ b/src/dispatchers/electronDispatcher.ts @@ -28,7 +28,7 @@ export class ElectronDispatcher extends Dispatcher { - const electronApplication = await this._object.launch(params.executablePath, params); + const electronApplication = await this._object.launch(params); return { electronApplication: new ElectronApplicationDispatcher(this._scope, electronApplication) }; } } @@ -49,11 +49,6 @@ export class ElectronApplicationDispatcher extends Dispatcher { - const page = await this._object.newBrowserWindow(parseArgument(params.arg)); - return { page: lookupDispatcher(page) }; - } - async evaluateExpression(params: channels.ElectronApplicationEvaluateExpressionParams): Promise { const handle = this._object._nodeElectronHandle!; return { value: serializeResult(await handle._evaluateExpression(params.expression, params.isFunction, true /* returnByValue */, parseArgument(params.arg))) }; diff --git a/src/protocol/channels.ts b/src/protocol/channels.ts index 4d65e25224203..0a72f8868f427 100644 --- a/src/protocol/channels.ts +++ b/src/protocol/channels.ts @@ -2452,22 +2452,17 @@ export interface ElectronChannel extends Channel { launch(params: ElectronLaunchParams, metadata?: Metadata): Promise; } export type ElectronLaunchParams = { - executablePath: string, + executablePath?: string, args?: string[], cwd?: string, env?: NameValue[], - handleSIGINT?: boolean, - handleSIGTERM?: boolean, - handleSIGHUP?: boolean, timeout?: number, }; export type ElectronLaunchOptions = { + executablePath?: string, args?: string[], cwd?: string, env?: NameValue[], - handleSIGINT?: boolean, - handleSIGTERM?: boolean, - handleSIGHUP?: boolean, timeout?: number, }; export type ElectronLaunchResult = { @@ -2480,7 +2475,6 @@ export interface ElectronApplicationChannel extends Channel { on(event: 'context', callback: (params: ElectronApplicationContextEvent) => void): this; on(event: 'close', callback: (params: ElectronApplicationCloseEvent) => void): this; on(event: 'window', callback: (params: ElectronApplicationWindowEvent) => void): this; - newBrowserWindow(params: ElectronApplicationNewBrowserWindowParams, metadata?: Metadata): Promise; evaluateExpression(params: ElectronApplicationEvaluateExpressionParams, metadata?: Metadata): Promise; evaluateExpressionHandle(params: ElectronApplicationEvaluateExpressionHandleParams, metadata?: Metadata): Promise; close(params?: ElectronApplicationCloseParams, metadata?: Metadata): Promise; @@ -2493,15 +2487,6 @@ export type ElectronApplicationWindowEvent = { page: PageChannel, browserWindow: JSHandleChannel, }; -export type ElectronApplicationNewBrowserWindowParams = { - arg: SerializedArgument, -}; -export type ElectronApplicationNewBrowserWindowOptions = { - -}; -export type ElectronApplicationNewBrowserWindowResult = { - page: PageChannel, -}; export type ElectronApplicationEvaluateExpressionParams = { expression: string, isFunction: boolean, diff --git a/src/protocol/protocol.yml b/src/protocol/protocol.yml index 1ab915ce76cc3..8cd3171aa05f0 100644 --- a/src/protocol/protocol.yml +++ b/src/protocol/protocol.yml @@ -2080,7 +2080,6 @@ CDPSession: params: json? - Electron: type: interface @@ -2088,7 +2087,7 @@ Electron: launch: parameters: - executablePath: string + executablePath: string? args: type: array? items: string @@ -2096,26 +2095,16 @@ Electron: env: type: array? items: NameValue - handleSIGINT: boolean? - handleSIGTERM: boolean? - handleSIGHUP: boolean? timeout: number? returns: electronApplication: ElectronApplication - ElectronApplication: type: interface commands: - newBrowserWindow: - parameters: - arg: SerializedArgument - returns: - page: Page - evaluateExpression: parameters: expression: string diff --git a/src/protocol/validator.ts b/src/protocol/validator.ts index b7a70a113ff60..4978c65c6a19d 100644 --- a/src/protocol/validator.ts +++ b/src/protocol/validator.ts @@ -911,18 +911,12 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme { }); scheme.CDPSessionDetachParams = tOptional(tObject({})); scheme.ElectronLaunchParams = tObject({ - executablePath: tString, + executablePath: tOptional(tString), args: tOptional(tArray(tString)), cwd: tOptional(tString), env: tOptional(tArray(tType('NameValue'))), - handleSIGINT: tOptional(tBoolean), - handleSIGTERM: tOptional(tBoolean), - handleSIGHUP: tOptional(tBoolean), timeout: tOptional(tNumber), }); - scheme.ElectronApplicationNewBrowserWindowParams = tObject({ - arg: tType('SerializedArgument'), - }); scheme.ElectronApplicationEvaluateExpressionParams = tObject({ expression: tString, isFunction: tBoolean, diff --git a/src/server/chromium/protocol.ts b/src/server/chromium/protocol.ts index 498d95fabc1a6..1d073bb63d2a4 100644 --- a/src/server/chromium/protocol.ts +++ b/src/server/chromium/protocol.ts @@ -14827,7 +14827,7 @@ other objects in their object group. export type RemoteObjectId = string; /** * Primitive value which cannot be JSON-stringified. Includes values `-0`, `NaN`, `Infinity`, -`-Infinity`, and bigint literals. +`-Infinity`. */ export type UnserializableValue = string; /** diff --git a/src/server/electron/electron.ts b/src/server/electron/electron.ts index 26b8b326469f7..14551b1347a50 100644 --- a/src/server/electron/electron.ts +++ b/src/server/electron/electron.ts @@ -35,12 +35,10 @@ import * as readline from 'readline'; import { RecentLogsCollector } from '../../utils/debugLogger'; export type ElectronLaunchOptionsBase = { + executablePath?: string, args?: string[], cwd?: string, env?: types.EnvArray, - handleSIGINT?: boolean, - handleSIGTERM?: boolean, - handleSIGHUP?: boolean, timeout?: number, }; @@ -95,21 +93,6 @@ export class ElectronApplication extends EventEmitter { this.emit(ElectronApplication.Events.Window, page); } - async newBrowserWindow(options: any): Promise { - const windowId = await this._nodeElectronHandle!.evaluate(async ({ BrowserWindow }, options) => { - const win = new BrowserWindow(options); - win.loadURL('about:blank'); - return win.id; - }, options); - - for (const page of this._windows) { - if (page._browserWindowId === windowId) - return page; - } - - return await this._waitForEvent(ElectronApplication.Events.Window, (page: ElectronPage) => page._browserWindowId === windowId); - } - context(): BrowserContext { return this._browserContext; } @@ -145,12 +128,9 @@ export class Electron { this._playwrightOptions = playwrightOptions; } - async launch(executablePath: string, options: ElectronLaunchOptionsBase = {}): Promise { + async launch(options: ElectronLaunchOptionsBase = {}): Promise { const { args = [], - handleSIGINT = true, - handleSIGTERM = true, - handleSIGHUP = true, } = options; const controller = new ProgressController(); controller.setLogName('browser'); @@ -166,12 +146,9 @@ export class Electron { const browserLogsCollector = new RecentLogsCollector(); const { launchedProcess, gracefullyClose, kill } = await launchProcess({ - executablePath, + executablePath: options.executablePath || require('electron/index.js'), args: electronArguments, env: options.env ? envArrayToObject(options.env) : process.env, - handleSIGINT, - handleSIGTERM, - handleSIGHUP, log: (message: string) => { progress.log(message); browserLogsCollector.log(message); diff --git a/test/coverage.js b/test/coverage.js index 86cb4e9cb33cb..cd5267f0088ba 100644 --- a/test/coverage.js +++ b/test/coverage.js @@ -65,6 +65,8 @@ function apiForBrowser(browserName) { const api = require('../lib/client/api'); const otherBrowsers = ['chromium', 'webkit', 'firefox'].filter(name => name.toLowerCase() !== browserName.toLowerCase()); const filteredKeys = Object.keys(api).filter(apiName => { + if (apiName.toLowerCase().startsWith('electron')) + return browserName === 'chromium'; return !otherBrowsers.some(otherName => apiName.toLowerCase().startsWith(otherName)); }); const filteredAPI = {}; diff --git a/test/electron/electron-app.spec.ts b/test/electron/electron-app.spec.ts index 01f1115ac1540..9e3cd9a31365b 100644 --- a/test/electron/electron-app.spec.ts +++ b/test/electron/electron-app.spec.ts @@ -14,122 +14,82 @@ * limitations under the License. */ +import path from 'path'; import { folio } from './electron.fixture'; const { it, expect, describe } = folio; -import path from 'path'; -const electronName = process.platform === 'win32' ? 'electron.cmd' : 'electron'; - describe('electron app', (suite, { browserName }) => { suite.skip(browserName !== 'chromium'); }, () => { it('should fire close event', async ({ playwright }) => { - const electronPath = path.join(__dirname, '..', '..', 'node_modules', '.bin', electronName); - const application = await playwright._electron.launch(electronPath, { + const electronApp = await playwright._electron.launch({ args: [path.join(__dirname, 'testApp.js')], }); const events = []; - application.on('close', () => events.push('application')); - application.context().on('close', () => events.push('context')); - await application.close(); + electronApp.on('close', () => events.push('application')); + electronApp.context().on('close', () => events.push('context')); + await electronApp.close(); expect(events.join('|')).toBe('context|application'); // Give it some time to fire more events - there should not be any. await new Promise(f => setTimeout(f, 1000)); expect(events.join('|')).toBe('context|application'); }); - it('should script application', async ({ application }) => { - const appPath = await application.evaluate(async ({ app }) => app.getAppPath()); + it('should script application', async ({ electronApp }) => { + const appPath = await electronApp.evaluate(async ({ app }) => app.getAppPath()); expect(appPath).toContain('electron'); }); - it('should create window', async ({ application }) => { - const [ page ] = await Promise.all([ - application.waitForEvent('window'), - application.evaluate(({ BrowserWindow }) => { - const window = new BrowserWindow({ width: 800, height: 600 }); - window.loadURL('data:text/html,Hello World 1'); - }) - ]); - await page.waitForLoadState('domcontentloaded'); - expect(await page.title()).toBe('Hello World 1'); - }); - - it('should create window 2', async ({ application }) => { - const page = await application.newBrowserWindow({ width: 800, height: 600 }); - await page.goto('data:text/html,Hello World 2'); - expect(await page.title()).toBe('Hello World 2'); + it('should return windows', async ({ electronApp, newWindow }) => { + const window = await newWindow(); + expect(electronApp.windows()).toEqual([window]); }); - it('should create multiple windows', async ({ application }) => { - const createPage = async ordinal => { - const page = await application.newBrowserWindow({ width: 800, height: 600 }); - await Promise.all([ - page.waitForNavigation(), - page.browserWindow.evaluate((window, ordinal) => window.loadURL(`data:text/html,Hello World ${ordinal}`), ordinal) - ]); - return page; - }; - - const page1 = await createPage(1); - await createPage(2); - await createPage(3); - await page1.close(); - await createPage(4); - const titles = []; - for (const window of application.windows()) - titles.push(await window.title()); - expect(titles).toEqual(['Hello World 2', 'Hello World 3', 'Hello World 4']); + it('should evaluate handle', async ({ electronApp }) => { + const appHandle = await electronApp.evaluateHandle(({ app }) => app); + expect(await electronApp.evaluate(({ app }, appHandle) => app === appHandle, appHandle)).toBeTruthy(); }); - it('should route network', async ({ application }) => { - await application.context().route('**/empty.html', (route, request) => { + it('should route network', async ({ electronApp, newWindow }) => { + await electronApp.context().route('**/empty.html', (route, request) => { route.fulfill({ status: 200, contentType: 'text/html', body: 'Hello World', }); }); - const page = await application.newBrowserWindow({ width: 800, height: 600 }); - await page.goto('https://localhost:1000/empty.html'); - expect(await page.title()).toBe('Hello World'); + const window = await newWindow(); + await window.goto('https://localhost:1000/empty.html'); + expect(await window.title()).toBe('Hello World'); }); - it('should support init script', async ({ application }) => { - await application.context().addInitScript('window.magic = 42;'); - const page = await application.newBrowserWindow({ width: 800, height: 600 }); - await page.goto('data:text/html,'); - expect(await page.evaluate(() => window['copy'])).toBe(42); + it('should support init script', async ({ electronApp, newWindow }) => { + await electronApp.context().addInitScript('window.magic = 42;'); + const window = await newWindow(); + await window.goto('data:text/html,'); + expect(await window.evaluate(() => window['copy'])).toBe(42); }); - it('should expose function', async ({ application }) => { - await application.context().exposeFunction('add', (a, b) => a + b); - const page = await application.newBrowserWindow({ width: 800, height: 600 }); - await page.goto('data:text/html,'); - expect(await page.evaluate(() => window['result'])).toBe(42); + it('should expose function', async ({ electronApp, newWindow }) => { + await electronApp.context().exposeFunction('add', (a, b) => a + b); + const window = await newWindow(); + await window.goto('data:text/html,'); + expect(await window.evaluate(() => window['result'])).toBe(42); }); - it('should wait for first window', async ({ application }) => { - application.evaluate(({ BrowserWindow }) => { + it('should wait for first window', async ({ electronApp }) => { + await electronApp.evaluate(({ BrowserWindow }) => { const window = new BrowserWindow({ width: 800, height: 600 }); window.loadURL('data:text/html,Hello World!'); }); - const window = await application.firstWindow(); + const window = await electronApp.firstWindow(); expect(await window.title()).toBe('Hello World!'); }); - it('should have a clipboard instance', async ({ application }) => { + it('should have a clipboard instance', async ({ electronApp }) => { const clipboardContentToWrite = 'Hello from Playwright'; - await application.evaluate(async ({clipboard}, text) => clipboard.writeText(text), clipboardContentToWrite); - const clipboardContentRead = await application.evaluate(async ({clipboard}) => clipboard.readText()); - await expect(clipboardContentRead).toEqual(clipboardContentToWrite); - }); - - it('should be able to send CDP messages', async ({application, window}) => { - const context = await application.context(); - const client = await context.newCDPSession(window); - await client.send('Runtime.enable'); - const evalResponse = await client.send('Runtime.evaluate', {expression: '1 + 2', returnByValue: true}); - expect(evalResponse.result.value).toBe(3); + await electronApp.evaluate(async ({clipboard}, text) => clipboard.writeText(text), clipboardContentToWrite); + const clipboardContentRead = await electronApp.evaluate(async ({clipboard}) => clipboard.readText()); + expect(clipboardContentRead).toEqual(clipboardContentToWrite); }); }); diff --git a/test/electron/electron.fixture.ts b/test/electron/electron.fixture.ts index 15d72bf26d758..528448ac5372b 100644 --- a/test/electron/electron.fixture.ts +++ b/test/electron/electron.fixture.ts @@ -15,30 +15,44 @@ */ import { folio as base } from '../fixtures'; -import type { ElectronApplication, ElectronPage } from '../../types/electron'; import path from 'path'; - -const electronName = process.platform === 'win32' ? 'electron.cmd' : 'electron'; +import { ElectronApplication, Page } from '../..'; type TestState = { - application: ElectronApplication; - window: ElectronPage; + electronApp: ElectronApplication; + window: Page; + newWindow: () => Promise; }; const fixtures = base.extend(); -fixtures.application.init(async ({ playwright }, run) => { - const electronPath = path.join(__dirname, '..', '..', 'node_modules', '.bin', electronName); - const application = await playwright._electron.launch(electronPath, { +fixtures.electronApp.init(async ({ playwright }, run) => { + const application = await playwright._electron.launch({ args: [path.join(__dirname, 'testApp.js')], }); await run(application); await application.close(); }); -fixtures.window.init(async ({ application }, run) => { - const page = await application.newBrowserWindow({ width: 800, height: 600 }); - await run(page); - await page.close(); +fixtures.newWindow.init(async ({ electronApp }, run) => { + const windows = []; + const newWindow = async () => { + const [ window ] = await Promise.all([ + electronApp.waitForEvent('window'), + electronApp.evaluate(electron => { + const window = new electron.BrowserWindow({ width: 800, height: 600 }); + window.loadURL('data:text/html,Hello World 1'); + }) + ]); + windows.push(window); + return window; + }; + await run(newWindow); + for (const window of windows) + await window.close(); +}); + +fixtures.window.init(async ({ newWindow }, run) => { + await run(await newWindow()); }); export const folio = fixtures.build(); diff --git a/test/electron/testApp.js b/test/electron/testApp.js index 493b45a009948..7c7ffd8839e8e 100644 --- a/test/electron/testApp.js +++ b/test/electron/testApp.js @@ -1,3 +1,3 @@ -const { app } = require('electron'); +const { app, BrowserWindow } = require('electron'); -app.on('window-all-closed', e => e.preventDefault()); \ No newline at end of file +app.on('window-all-closed', e => e.preventDefault()); diff --git a/test/fixtures.ts b/test/fixtures.ts index 7c520072c3f48..d411c81ad952d 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -20,7 +20,7 @@ import fs from 'fs'; import path from 'path'; import util from 'util'; import os from 'os'; -import type { Browser, BrowserContext, BrowserType, Page } from '../index'; +import type { Browser, BrowserContext, BrowserType, Electron, Page } from '../index'; import { Connection } from '../lib/client/connection'; import { Transport } from '../lib/protocol/transport'; import { installCoverageHooks } from './coverage'; @@ -28,7 +28,6 @@ import { folio as httpFolio } from './http.fixtures'; import { folio as playwrightFolio } from './playwright.fixtures'; import { PlaywrightClient } from '../lib/remote/playwrightClient'; import type { Android } from '../types/android'; -import type { ElectronLauncher } from '../types/electron'; export { expect, config } from 'folio'; const removeFolderAsync = util.promisify(require('rimraf')); @@ -134,7 +133,7 @@ fixtures.playwright.override(async ({ browserName, testWorkerIndex, platform, mo stdio: 'pipe' }); spawnedProcess.stderr.pipe(process.stderr); - await new Promise(f => { + await new Promise(f => { spawnedProcess.stdout.on('data', data => { if (data.toString().includes('Listening on')) f(); @@ -195,5 +194,5 @@ export const afterAll = folio.afterAll; declare module '../index' { const _android: Android; - const _electron: ElectronLauncher; + const _electron: Electron; } diff --git a/types/electron.d.ts b/types/electron.d.ts deleted file mode 100644 index 43f0b648d2b44..0000000000000 --- a/types/electron.d.ts +++ /dev/null @@ -1,57 +0,0 @@ -/** - * 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 { Logger, Page, JSHandle, ChromiumBrowserContext } from './types'; -import { BrowserWindow, BrowserWindowConstructorOptions } from 'electron'; - -export type ElectronLaunchOptions = { - args?: string[], - cwd?: string, - env?: {[key: string]: string|number|boolean}, - handleSIGINT?: boolean, - handleSIGTERM?: boolean, - handleSIGHUP?: boolean, - timeout?: number, - logger?: Logger, -}; - -export interface ElectronLauncher { - launch(executablePath: string, options?: ElectronLaunchOptions): Promise; -} - -export interface ElectronApplication { - on(event: 'window', listener: (page : ElectronPage) => void): this; - addListener(event: 'window', listener: (page : ElectronPage) => void): this; - waitForEvent(event: 'window', optionsOrPredicate?: { predicate?: (page : ElectronPage) => boolean, timeout?: number }): Promise; - - on(event: 'close', listener: (exitCode? : number) => void): this; - addListener(event: 'close', listener: (exitCode? : number) => void): this; - waitForEvent(event: 'close', optionsOrPredicate?: { predicate?: (exitCode? : number) => boolean, timeout?: number }): Promise; - - context(): ChromiumBrowserContext; - windows(): ElectronPage[]; - firstWindow(): Promise; - newBrowserWindow(options?: BrowserWindowConstructorOptions): Promise; - close(): Promise; - evaluate: HandleToElectron['evaluate']; - evaluateHandle: HandleToElectron['evaluateHandle']; -} - -export interface ElectronPage extends Page { - browserWindow: JSHandle; -} - -type HandleToElectron = JSHandle; diff --git a/types/protocol.d.ts b/types/protocol.d.ts index 498d95fabc1a6..1d073bb63d2a4 100644 --- a/types/protocol.d.ts +++ b/types/protocol.d.ts @@ -14827,7 +14827,7 @@ other objects in their object group. export type RemoteObjectId = string; /** * Primitive value which cannot be JSON-stringified. Includes values `-0`, `NaN`, `Infinity`, -`-Infinity`, and bigint literals. +`-Infinity`. */ export type UnserializableValue = string; /** diff --git a/types/types.d.ts b/types/types.d.ts index 28e20bfcbff60..b2f1bca226a4f 100644 --- a/types/types.d.ts +++ b/types/types.d.ts @@ -85,8 +85,8 @@ export interface Page { * [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#pageevaluatepagefunction-arg) returns a * non-[Serializable] value, then * [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#pageevaluatepagefunction-arg) resolves - * to `undefined`. DevTools Protocol also supports transferring some additional values that are not serializable by `JSON`: - * `-0`, `NaN`, `Infinity`, `-Infinity`, and bigint literals. + * to `undefined`. Playwright also supports transferring some additional values that are not serializable by `JSON`: `-0`, + * `NaN`, `Infinity`, `-Infinity`. * * Passing argument to `pageFunction`: * @@ -116,21 +116,21 @@ export interface Page { * * Shortcut for main frame's * [frame.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frameevaluatepagefunction-arg). - * @param pageFunction Function to be evaluated in the page context + * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to `pageFunction` */ evaluate(pageFunction: PageFunction, arg: Arg): Promise; evaluate(pageFunction: PageFunction, arg?: any): Promise; /** - * Returns the value of the `pageFunction` invocation as in-page object (JSHandle). + * Returns the value of the `pageFunction` invocation as a [JSHandle]. * * The only difference between * [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#pageevaluatepagefunction-arg) and * [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#pageevaluatehandlepagefunction-arg) * is that * [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#pageevaluatehandlepagefunction-arg) - * returns in-page object (JSHandle). + * returns [JSHandle]. * * If the function passed to the * [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#pageevaluatehandlepagefunction-arg) @@ -159,7 +159,7 @@ export interface Page { * await resultHandle.dispose(); * ``` * - * @param pageFunction Function to be evaluated in the page context + * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to `pageFunction` */ evaluateHandle(pageFunction: PageFunction, arg: Arg): Promise>; @@ -204,7 +204,7 @@ export interface Page { * Shortcut for main frame's * [frame.$eval(selector, pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frameevalselector-pagefunction-arg). * @param selector A selector to query for. See [working with selectors](https://playwright.dev/docs/selectors) for more details. - * @param pageFunction Function to be evaluated in the page context + * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to `pageFunction` */ $eval(selector: K, pageFunction: PageFunctionOn, arg: Arg): Promise; @@ -227,7 +227,7 @@ export interface Page { * ``` * * @param selector A selector to query for. See [working with selectors](https://playwright.dev/docs/selectors) for more details. - * @param pageFunction Function to be evaluated in the page context + * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to `pageFunction` */ $$eval(selector: K, pageFunction: PageFunctionOn, arg: Arg): Promise; @@ -266,7 +266,7 @@ export interface Page { * * Shortcut for main frame's * [frame.waitForFunction(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#framewaitforfunctionpagefunction-arg-options). - * @param pageFunction Function to be evaluated in the page context + * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to `pageFunction` * @param options */ @@ -3178,7 +3178,7 @@ export interface Page { */ export interface Frame { /** - * Returns the return value of `pageFunction` + * Returns the return value of `pageFunction`. * * If the function passed to the * [frame.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frameevaluatepagefunction-arg) returns @@ -3190,8 +3190,8 @@ export interface Frame { * [frame.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frameevaluatepagefunction-arg) returns * a non-[Serializable] value, then * [frame.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frameevaluatepagefunction-arg) returns - * `undefined`. DevTools Protocol also supports transferring some additional values that are not serializable by `JSON`: - * `-0`, `NaN`, `Infinity`, `-Infinity`, and bigint literals. + * `undefined`. Playwright also supports transferring some additional values that are not serializable by `JSON`: `-0`, + * `NaN`, `Infinity`, `-Infinity`. * * ```js * const result = await frame.evaluate(([x, y]) => { @@ -3215,19 +3215,19 @@ export interface Frame { * await bodyHandle.dispose(); * ``` * - * @param pageFunction Function to be evaluated in the page context + * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to `pageFunction` */ evaluate(pageFunction: PageFunction, arg: Arg): Promise; evaluate(pageFunction: PageFunction, arg?: any): Promise; /** - * Returns the return value of `pageFunction` as in-page object (JSHandle). + * Returns the return value of `pageFunction` as a [JSHandle]. * * The only difference between * [frame.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frameevaluatepagefunction-arg) and * [frame.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frameevaluatehandlepagefunction-arg) - * is that [method: Frame.evaluateHandle`] returns in-page object (JSHandle). + * is that [method: Frame.evaluateHandle`] returns [JSHandle]. * * If the function, passed to the * [frame.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frameevaluatehandlepagefunction-arg), @@ -3256,7 +3256,7 @@ export interface Frame { * await resultHandle.dispose(); * ``` * - * @param pageFunction Function to be evaluated in the page context + * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to `pageFunction` */ evaluateHandle(pageFunction: PageFunction, arg: Arg): Promise>; @@ -3283,7 +3283,7 @@ export interface Frame { $$(selector: string): Promise[]>; /** - * Returns the return value of `pageFunction` + * Returns the return value of `pageFunction`. * * The method finds an element matching the specified selector within the frame and passes it as a first argument to * `pageFunction`. See [Working with selectors](https://playwright.dev/docs/selectors) for more details. If no elements match the selector, the @@ -3302,7 +3302,7 @@ export interface Frame { * ``` * * @param selector A selector to query for. See [working with selectors](https://playwright.dev/docs/selectors) for more details. - * @param pageFunction Function to be evaluated in the page context + * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to `pageFunction` */ $eval(selector: K, pageFunction: PageFunctionOn, arg: Arg): Promise; @@ -3311,7 +3311,7 @@ export interface Frame { $eval(selector: string, pageFunction: PageFunctionOn, arg?: any): Promise; /** - * Returns the return value of `pageFunction` + * Returns the return value of `pageFunction`. * * The method finds all elements matching the specified selector within the frame and passes an array of matched elements * as a first argument to `pageFunction`. See [Working with selectors](https://playwright.dev/docs/selectors) for more details. @@ -3327,7 +3327,7 @@ export interface Frame { * ``` * * @param selector A selector to query for. See [working with selectors](https://playwright.dev/docs/selectors) for more details. - * @param pageFunction Function to be evaluated in the page context + * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to `pageFunction` */ $$eval(selector: K, pageFunction: PageFunctionOn, arg: Arg): Promise; @@ -3362,7 +3362,7 @@ export interface Frame { * await frame.waitForFunction(selector => !!document.querySelector(selector), selector); * ``` * - * @param pageFunction Function to be evaluated in the page context + * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to `pageFunction` * @param options */ @@ -5102,29 +5102,42 @@ export interface BrowserContext { */ export interface Worker { /** - * Returns the return value of `pageFunction` + * Returns the return value of `pageFunction`. * - * If the function passed to the `worker.evaluate` returns a [Promise], then `worker.evaluate` would wait for the promise - * to resolve and return its value. + * If the function passed to the + * [worker.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-worker#workerevaluatepagefunction-arg) + * returns a [Promise], then + * [worker.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-worker#workerevaluatepagefunction-arg) + * would wait for the promise to resolve and return its value. * - * If the function passed to the `worker.evaluate` returns a non-[Serializable] value, then `worker.evaluate` returns - * `undefined`. DevTools Protocol also supports transferring some additional values that are not serializable by `JSON`: - * `-0`, `NaN`, `Infinity`, `-Infinity`, and bigint literals. - * @param pageFunction Function to be evaluated in the worker context + * If the function passed to the + * [worker.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-worker#workerevaluatepagefunction-arg) + * returns a non-[Serializable] value, then + * [worker.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-worker#workerevaluatepagefunction-arg) + * returns `undefined`. Playwright also supports transferring some additional values that are not serializable by `JSON`: + * `-0`, `NaN`, `Infinity`, `-Infinity`. + * @param pageFunction Function to be evaluated in the worker context. * @param arg Optional argument to pass to `pageFunction` */ evaluate(pageFunction: PageFunction, arg: Arg): Promise; evaluate(pageFunction: PageFunction, arg?: any): Promise; /** - * Returns the return value of `pageFunction` as in-page object (JSHandle). + * Returns the return value of `pageFunction` as a [JSHandle]. * - * The only difference between `worker.evaluate` and `worker.evaluateHandle` is that `worker.evaluateHandle` returns - * in-page object (JSHandle). + * The only difference between + * [worker.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-worker#workerevaluatepagefunction-arg) and + * [worker.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-worker#workerevaluatehandlepagefunction-arg) + * is that + * [worker.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-worker#workerevaluatehandlepagefunction-arg) + * returns [JSHandle]. * - * If the function passed to the `worker.evaluateHandle` returns a [Promise], then `worker.evaluateHandle` would wait for - * the promise to resolve and return its value. - * @param pageFunction Function to be evaluated in the worker context + * If the function passed to the + * [worker.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-worker#workerevaluatehandlepagefunction-arg) + * returns a [Promise], then + * [worker.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-worker#workerevaluatehandlepagefunction-arg) + * would wait for the promise to resolve and return its value. + * @param pageFunction Function to be evaluated in the worker context. * @param arg Optional argument to pass to `pageFunction` */ evaluateHandle(pageFunction: PageFunction, arg: Arg): Promise>; @@ -5178,7 +5191,7 @@ export interface Worker { */ export interface JSHandle { /** - * Returns the return value of `pageFunction` + * Returns the return value of `pageFunction`. * * This method passes this handle as the first argument to `pageFunction`. * @@ -5192,19 +5205,19 @@ export interface JSHandle { * expect(await tweetHandle.evaluate(node => node.innerText)).toBe('10 retweets'); * ``` * - * @param pageFunction Function to be evaluated in the page context + * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to `pageFunction` */ evaluate(pageFunction: PageFunctionOn, arg: Arg): Promise; evaluate(pageFunction: PageFunctionOn, arg?: any): Promise; /** - * Returns the return value of `pageFunction` as in-page object (JSHandle). + * Returns the return value of `pageFunction` as a [JSHandle]. * * This method passes this handle as the first argument to `pageFunction`. * * The only difference between `jsHandle.evaluate` and `jsHandle.evaluateHandle` is that `jsHandle.evaluateHandle` returns - * in-page object (JSHandle). + * [JSHandle]. * * If the function passed to the `jsHandle.evaluateHandle` returns a [Promise], then `jsHandle.evaluateHandle` would wait * for the promise to resolve and return its value. @@ -5212,7 +5225,7 @@ export interface JSHandle { * See * [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#pageevaluatehandlepagefunction-arg) * for more details. - * @param pageFunction Function to be evaluated in the page context + * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to `pageFunction` */ evaluateHandle(pageFunction: PageFunctionOn, arg: Arg): Promise>; @@ -5300,7 +5313,7 @@ export interface ElementHandle extends JSHandle { $$(selector: string): Promise[]>; /** - * Returns the return value of `pageFunction` + * Returns the return value of `pageFunction`. * * The method finds an element matching the specified selector in the `ElementHandle`s subtree and passes it as a first * argument to `pageFunction`. See [Working with selectors](https://playwright.dev/docs/selectors) for more details. If no elements match the @@ -5319,7 +5332,7 @@ export interface ElementHandle extends JSHandle { * ``` * * @param selector A selector to query for. See [working with selectors](https://playwright.dev/docs/selectors) for more details. - * @param pageFunction Function to be evaluated in the page context + * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to `pageFunction` */ $eval(selector: K, pageFunction: PageFunctionOn, arg: Arg): Promise; @@ -5328,7 +5341,7 @@ export interface ElementHandle extends JSHandle { $eval(selector: string, pageFunction: PageFunctionOn, arg?: any): Promise; /** - * Returns the return value of `pageFunction` + * Returns the return value of `pageFunction`. * * The method finds all elements matching the specified selector in the `ElementHandle`'s subtree and passes an array of * matched elements as a first argument to `pageFunction`. See [Working with selectors](https://playwright.dev/docs/selectors) for more details. @@ -5352,7 +5365,7 @@ export interface ElementHandle extends JSHandle { * ``` * * @param selector A selector to query for. See [working with selectors](https://playwright.dev/docs/selectors) for more details. - * @param pageFunction Function to be evaluated in the page context + * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to `pageFunction` */ $$eval(selector: K, pageFunction: PageFunctionOn, arg: Arg): Promise; @@ -6952,6 +6965,179 @@ type AccessibilityNode = { export const selectors: Selectors; export const devices: Devices & DeviceDescriptor[]; +/** + * Electron application representation. You can use + * [electron.launch([options])](https://playwright.dev/docs/api/class-electron#electronlaunchoptions) to obtain the + * application instance. This instance you can control main electron process as well as work with Electron windows: + * + * ```js + * const { _electron: electron } = require('playwright'); + * + * (async () => { + * // Launch Electron app. + * const electronApp = await electron.launch({ args: ['main.js'] }); + * + * // Evaluation expression in the Electron context. + * const appPath = await electronApp.evaluate(async (electron) => { + * // This runs in the main Electron process, |electron| parameter + * // here is always the result of the require('electron') in the main + * // app script. + * return electron.getAppPath(); + * }); + * + * // Get the first window that the app opens, wait if necessary. + * const window = await electronApp.firstWindow(); + * // Print the title. + * console.log(await window.title()); + * // Capture a screenshot. + * await window.screenshot({ path: 'intro.png' }); + * // Direct Electron console to Node terminal. + * window.on('console', console.log); + * // Click button. + * await window.click('text=Click me'); + * })(); + * ``` + * + */ +export interface ElectronApplication { + /** + * Returns the return value of `pageFunction`. + * + * If the function passed to the + * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electronapplicationevaluatepagefunction-arg) + * returns a [Promise], then + * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electronapplicationevaluatepagefunction-arg) + * would wait for the promise to resolve and return its value. + * + * If the function passed to the + * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electronapplicationevaluatepagefunction-arg) + * returns a non-[Serializable] value, then + * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electronapplicationevaluatepagefunction-arg) + * returns `undefined`. Playwright also supports transferring some additional values that are not serializable by `JSON`: + * `-0`, `NaN`, `Infinity`, `-Infinity`. + * @param pageFunction Function to be evaluated in the worker context. + * @param arg Optional argument to pass to `pageFunction`. + */ + evaluate(pageFunction: PageFunctionOn, arg: Arg): Promise; + evaluate(pageFunction: PageFunctionOn, arg?: any): Promise; + + /** + * Returns the return value of `pageFunction` as a [JSHandle]. + * + * The only difference between + * [electronApplication.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-electronapplication#electronapplicationevaluatepagefunction-arg) + * and + * [electronApplication.evaluateHandle(pageFunction, arg)](https://playwright.dev/docs/api/class-electronapplication#electronapplicationevaluatehandlepagefunction-arg) + * is that + * [electronApplication.evaluateHandle(pageFunction, arg)](https://playwright.dev/docs/api/class-electronapplication#electronapplicationevaluatehandlepagefunction-arg) + * returns [JSHandle]. + * + * If the function passed to the + * [electronApplication.evaluateHandle(pageFunction, arg)](https://playwright.dev/docs/api/class-electronapplication#electronapplicationevaluatehandlepagefunction-arg) + * returns a [Promise], then + * [electronApplication.evaluateHandle(pageFunction, arg)](https://playwright.dev/docs/api/class-electronapplication#electronapplicationevaluatehandlepagefunction-arg) + * would wait for the promise to resolve and return its value. + * @param pageFunction Function to be evaluated in the worker context. + * @param arg + */ + evaluateHandle(pageFunction: PageFunctionOn, arg: Arg): Promise>; + evaluateHandle(pageFunction: PageFunctionOn, arg?: any): Promise>; + /** + * This event is issued when the application closes. + */ + on(event: 'close', listener: () => void): this; + + /** + * This event is issued for every window that is created **and loaded** in Electron. It contains a [Page] that can be used + * for Playwright automation. + */ + on(event: 'window', listener: (page: Page) => void): this; + + /** + * This event is issued when the application closes. + */ + once(event: 'close', listener: () => void): this; + + /** + * This event is issued for every window that is created **and loaded** in Electron. It contains a [Page] that can be used + * for Playwright automation. + */ + once(event: 'window', listener: (page: Page) => void): this; + + /** + * This event is issued when the application closes. + */ + addListener(event: 'close', listener: () => void): this; + + /** + * This event is issued for every window that is created **and loaded** in Electron. It contains a [Page] that can be used + * for Playwright automation. + */ + addListener(event: 'window', listener: (page: Page) => void): this; + + /** + * This event is issued when the application closes. + */ + removeListener(event: 'close', listener: () => void): this; + + /** + * This event is issued for every window that is created **and loaded** in Electron. It contains a [Page] that can be used + * for Playwright automation. + */ + removeListener(event: 'window', listener: (page: Page) => void): this; + + /** + * This event is issued when the application closes. + */ + off(event: 'close', listener: () => void): this; + + /** + * This event is issued for every window that is created **and loaded** in Electron. It contains a [Page] that can be used + * for Playwright automation. + */ + off(event: 'window', listener: (page: Page) => void): this; + + /** + * Closes Electron application. + */ + close(): Promise; + + /** + * This method returns browser context that can be used for setting up context-wide routing, etc. + */ + context(): BrowserContext; + + /** + * Convenience method that waits for the first application window to be opened. Typically your script will start with: + * + * ```js + * const electronApp = await electron.launch({ + * args: ['main.js'] + * }); + * const window = await electronApp.firstWindow(); + * // ... + * ``` + * + */ + firstWindow(): Promise; + + /** + * This event is issued when the application closes. + */ + waitForEvent(event: 'close', optionsOrPredicate?: { predicate?: () => boolean, timeout?: number } | (() => boolean)): Promise; + + /** + * This event is issued for every window that is created **and loaded** in Electron. It contains a [Page] that can be used + * for Playwright automation. + */ + waitForEvent(event: 'window', optionsOrPredicate?: { predicate?: (page: Page) => boolean, timeout?: number } | ((page: Page) => boolean)): Promise; + + + /** + * Convenience method that returns all the opened windows. + */ + windows(): Array;} + // This is required to not export everything by default. See https://github.com/Microsoft/TypeScript/issues/19545#issuecomment-340490459 export {}; @@ -7979,6 +8165,80 @@ export interface Download { url(): string; } +/** + * Playwright has **experimental** support for Electron automation. You can access electron namespace via: + * + * ```js + * const { _electron } = require('playwright'); + * ``` + * + * An example of the Electron automation script would be: + * + * ```js + * const { _electron: electron } = require('playwright'); + * + * (async () => { + * // Launch Electron app. + * const electronApp = await electron.launch({ args: ['main.js'] }); + * + * // Evaluation expression in the Electron context. + * const appPath = await electronApp.evaluate(async (electron) => { + * // This runs in the main Electron process, |electron| parameter + * // here is always the result of the require('electron') in the main + * // app script. + * return electron.getAppPath(); + * }); + * + * // Get the first window that the app opens, wait if necessary. + * const window = await electronApp.firstWindow(); + * // Print the title. + * console.log(await window.title()); + * // Capture a screenshot. + * await window.screenshot({ path: 'intro.png' }); + * // Direct Electron console to Node terminal. + * window.on('console', console.log); + * // Click button. + * await window.click('text=Click me'); + * })(); + * ``` + * + * Note that since you don't need Playwright to install web browsers when testing Electron, you can omit browser download + * via setting the following environment variable when installing Playwright: + * + * ```sh js + * $ PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm i -D playwright + * ``` + * + */ +export interface Electron { + /** + * Launches electron application specified with the `executablePath`. + * @param options + */ + launch(options?: { + /** + * Additional arguments to pass to the application when launching. You typically pass the main script name here. + */ + args?: Array; + + /** + * Current working directory to launch application from. + */ + cwd?: string; + + /** + * Specifies environment variables that will be visible to Electron. Defaults to `process.env`. + */ + env?: { [key: string]: string; }; + + /** + * Launches given Electron application. If not specified, launches the default Electron executable installed in this + * package, located at `node_modules/.bin/electron`. + */ + executablePath?: string; + }): Promise; +} + /** * [FileChooser] objects are dispatched by the page in the * [page.on('filechooser')](https://playwright.dev/docs/api/class-page#pageonfilechooser) event. diff --git a/utils/doclint/api_parser.js b/utils/doclint/api_parser.js index f4521fbc66f53..b2195360cfeff 100644 --- a/utils/doclint/api_parser.js +++ b/utils/doclint/api_parser.js @@ -44,8 +44,12 @@ class ApiParser { md.visitAll(api, node => { if (node.type === 'h1') this.parseClass(node); + }); + md.visitAll(api, node => { if (node.type === 'h2') this.parseMember(node); + }); + md.visitAll(api, node => { if (node.type === 'h3') this.parseArgument(node); }); @@ -131,6 +135,8 @@ class ApiParser { arg.name = name; const existingArg = method.argsArray.find(m => m.name === arg.name); if (existingArg) { + if (!arg.langs || !arg.langs.only) + throw new Error('Override does not have lang: ' + spec.text); for (const lang of arg.langs.only) { existingArg.langs.overrides = existingArg.langs.overrides || {}; existingArg.langs.overrides[lang] = arg; diff --git a/utils/generate_types/overrides.d.ts b/utils/generate_types/overrides.d.ts index 38c4487877651..9f502a21ba499 100644 --- a/utils/generate_types/overrides.d.ts +++ b/utils/generate_types/overrides.d.ts @@ -222,5 +222,13 @@ type AccessibilityNode = { export const selectors: Selectors; export const devices: Devices & DeviceDescriptor[]; +export interface ElectronApplication { + evaluate(pageFunction: PageFunctionOn, arg: Arg): Promise; + evaluate(pageFunction: PageFunctionOn, arg?: any): Promise; + + evaluateHandle(pageFunction: PageFunctionOn, arg: Arg): Promise>; + evaluateHandle(pageFunction: PageFunctionOn, arg?: any): Promise>; +} + // This is required to not export everything by default. See https://github.com/Microsoft/TypeScript/issues/19545#issuecomment-340490459 export {};