diff --git a/docs/src/api/class-electron.md b/docs/src/api/class-electron.md index 46fa2f9fda..b11abf6983 100644 --- a/docs/src/api/class-electron.md +++ b/docs/src/api/class-electron.md @@ -76,3 +76,20 @@ Specifies environment variables that will be visible to Electron. Defaults to `p - `timeout` <[float]> Maximum time in milliseconds to wait for the application to start. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. + +### option: Electron.launch.acceptdownloads = %%-context-option-acceptdownloads-%% +### option: Electron.launch.bypassCSP = %%-context-option-bypasscsp-%% +### option: Electron.launch.colorScheme = %%-context-option-colorscheme-%% +### option: Electron.launch.extraHTTPHeaders = %%-context-option-extrahttpheaders-%% +### option: Electron.launch.geolocation = %%-context-option-geolocation-%% +### option: Electron.launch.httpcredentials = %%-context-option-httpcredentials-%% +### option: Electron.launch.ignoreHTTPSErrors = %%-context-option-ignorehttpserrors-%% +### option: Electron.launch.locale = %%-context-option-locale-%% +### option: Electron.launch.offline = %%-context-option-offline-%% +### option: Electron.launch.recordhar = %%-context-option-recordhar-%% +### option: Electron.launch.recordhar.path = %%-context-option-recordhar-path-%% +### option: Electron.launch.recordhar.recordHarOmitContent = %%-context-option-recordhar-omit-content-%% +### option: Electron.launch.recordvideo = %%-context-option-recordvideo-%% +### option: Electron.launch.recordvideo.dir = %%-context-option-recordvideo-dir-%% +### option: Electron.launch.recordvideo.size = %%-context-option-recordvideo-size-%% +### option: Electron.launch.timezoneId = %%-context-option-timezoneid-%% diff --git a/src/client/electron.ts b/src/client/electron.ts index 255df6027b..9233b125c6 100644 --- a/src/client/electron.ts +++ b/src/client/electron.ts @@ -19,17 +19,19 @@ import * as structs from '../../types/structs'; import * as api from '../../types/types'; import * as channels from '../protocol/channels'; import { TimeoutSettings } from '../utils/timeoutSettings'; +import { headersObjectToArray } from '../utils/utils'; import { BrowserContext } from './browserContext'; import { ChannelOwner } from './channelOwner'; import { envObjectToArray } from './clientHelper'; import { Events } from './events'; import { JSHandle, parseResult, serializeArgument } from './jsHandle'; import { Page } from './page'; -import { Env, WaitForEventOptions } from './types'; +import { Env, WaitForEventOptions, Headers } from './types'; import { Waiter } from './waiter'; -type ElectronOptions = Omit & { +type ElectronOptions = Omit & { env?: Env, + extraHTTPHeaders?: Headers, }; type ElectronAppType = typeof import('electron'); @@ -48,6 +50,7 @@ export class Electron extends ChannelOwner Validator): Scheme { cwd: tOptional(tString), env: tOptional(tArray(tType('NameValue'))), timeout: tOptional(tNumber), + acceptDownloads: tOptional(tBoolean), + bypassCSP: tOptional(tBoolean), + colorScheme: tOptional(tEnum(['dark', 'light', 'no-preference'])), + extraHTTPHeaders: tOptional(tArray(tType('NameValue'))), + geolocation: tOptional(tObject({ + longitude: tNumber, + latitude: tNumber, + accuracy: tOptional(tNumber), + })), + httpCredentials: tOptional(tObject({ + username: tString, + password: tString, + })), + ignoreHTTPSErrors: tOptional(tBoolean), + locale: tOptional(tString), + offline: tOptional(tBoolean), + recordHar: tOptional(tObject({ + omitContent: tOptional(tBoolean), + path: tString, + })), + recordVideo: tOptional(tObject({ + dir: tString, + size: tOptional(tObject({ + width: tNumber, + height: tNumber, + })), + })), + timezoneId: tOptional(tString), }); scheme.ElectronApplicationBrowserWindowParams = tObject({ page: tChannel('Page'), diff --git a/src/server/electron/electron.ts b/src/server/electron/electron.ts index 65fa14f3b1..5295fe483f 100644 --- a/src/server/electron/electron.ts +++ b/src/server/electron/electron.ts @@ -22,7 +22,6 @@ import * as js from '../javascript'; import { Page } from '../page'; import { TimeoutSettings } from '../../utils/timeoutSettings'; import { WebSocketTransport } from '../transport'; -import * as types from '../types'; import { launchProcess, envArrayToObject } from '../processLauncher'; import { BrowserContext } from '../browserContext'; import type {BrowserWindow} from 'electron'; @@ -33,15 +32,7 @@ import * as childProcess from 'child_process'; import * as readline from 'readline'; import { RecentLogsCollector } from '../../utils/debugLogger'; import { internalCallMetadata, SdkObject } from '../instrumentation'; - -export type ElectronLaunchOptionsBase = { - sdkLanguage: string, - executablePath?: string, - args?: string[], - cwd?: string, - env?: types.EnvArray, - timeout?: number, -}; +import * as channels from '../../protocol/channels'; export class ElectronApplication extends SdkObject { static Events = { @@ -112,7 +103,7 @@ export class Electron extends SdkObject { this._playwrightOptions = playwrightOptions; } - async launch(options: ElectronLaunchOptionsBase): Promise { + async launch(options: channels.ElectronLaunchParams): Promise { const { args = [], } = options; @@ -164,7 +155,22 @@ export class Electron extends SdkObject { name: 'electron', isChromium: true, headful: true, - persistent: { sdkLanguage: options.sdkLanguage, noDefaultViewport: true }, + persistent: { + sdkLanguage: options.sdkLanguage, + noDefaultViewport: true, + acceptDownloads: options.acceptDownloads, + bypassCSP: options.bypassCSP, + colorScheme: options.colorScheme, + extraHTTPHeaders: options.extraHTTPHeaders, + geolocation: options.geolocation, + httpCredentials: options.httpCredentials, + ignoreHTTPSErrors: options.ignoreHTTPSErrors, + locale: options.locale, + offline: options.offline, + recordHar: options.recordHar, + recordVideo: options.recordVideo, + timezoneId: options.timezoneId, + }, browserProcess, protocolLogger: helper.debugProtocolLogger(), browserLogsCollector, diff --git a/tests/electron/electron-app.spec.ts b/tests/electron/electron-app.spec.ts index 8f50292743..93b15874b9 100644 --- a/tests/electron/electron-app.spec.ts +++ b/tests/electron/electron-app.spec.ts @@ -107,3 +107,22 @@ test('should return browser window', async ({ playwright }) => { expect(await bwHandle.evaluate((bw: BrowserWindow) => bw.title)).toBe('Electron'); await electronApp.close(); }); + +test('should bypass csp', async ({playwright, server}) => { + const app = await playwright._electron.launch({ + args: [require('path').join(__dirname, 'electron-app.js')], + bypassCSP: true, + }); + await app.evaluate(electron => { + const window = new electron.BrowserWindow({ + width: 800, + height: 600, + }); + window.loadURL('about:blank'); + }); + const page = await app.firstWindow(); + await page.goto(server.PREFIX + '/csp.html'); + await page.addScriptTag({content: 'window["__injected"] = 42;'}); + expect(await page.evaluate('window["__injected"]')).toBe(42); + await app.close(); +}); diff --git a/types/types.d.ts b/types/types.d.ts index 431238d567..81a4184225 100644 --- a/types/types.d.ts +++ b/types/types.d.ts @@ -9617,11 +9617,28 @@ export interface Electron { * @param options */ launch(options?: { + /** + * Whether to automatically download all the attachments. Defaults to `false` where all the downloads are canceled. + */ + acceptDownloads?: boolean; + /** * Additional arguments to pass to the application when launching. You typically pass the main script name here. */ args?: Array; + /** + * Toggles bypassing page's Content-Security-Policy. + */ + bypassCSP?: boolean; + + /** + * Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`. See + * [page.emulateMedia([options])](https://playwright.dev/docs/api/class-page#pageemulatemediaoptions) for more details. + * Defaults to `'light'`. + */ + colorScheme?: "light"|"dark"|"no-preference"; + /** * Current working directory to launch application from. */ @@ -9637,6 +9654,107 @@ export interface Electron { * package, located at `node_modules/.bin/electron`. */ executablePath?: string; + + /** + * An object containing additional HTTP headers to be sent with every request. All header values must be strings. + */ + extraHTTPHeaders?: { [key: string]: string; }; + + geolocation?: { + /** + * Latitude between -90 and 90. + */ + latitude: number; + + /** + * Longitude between -180 and 180. + */ + longitude: number; + + /** + * Non-negative accuracy value. Defaults to `0`. + */ + accuracy?: number; + }; + + /** + * Credentials for [HTTP authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication). + */ + httpCredentials?: { + username: string; + + password: string; + }; + + /** + * Whether to ignore HTTPS errors during navigation. Defaults to `false`. + */ + ignoreHTTPSErrors?: boolean; + + /** + * Specify user locale, for example `en-GB`, `de-DE`, etc. Locale will affect `navigator.language` value, `Accept-Language` + * request header value as well as number and date formatting rules. + */ + locale?: string; + + /** + * Whether to emulate network being offline. Defaults to `false`. + */ + offline?: boolean; + + /** + * Enables [HAR](http://www.softwareishard.com/blog/har-12-spec) recording for all pages into `recordHar.path` file. If not + * specified, the HAR is not recorded. Make sure to await + * [browserContext.close()](https://playwright.dev/docs/api/class-browsercontext#browsercontextclose) for the HAR to be + * saved. + */ + recordHar?: { + /** + * Optional setting to control whether to omit request content from the HAR. Defaults to `false`. + */ + omitContent?: boolean; + + /** + * Path on the filesystem to write the HAR file to. + */ + path: string; + }; + + /** + * Enables video recording for all pages into `recordVideo.dir` directory. If not specified videos are not recorded. Make + * sure to await [browserContext.close()](https://playwright.dev/docs/api/class-browsercontext#browsercontextclose) for + * videos to be saved. + */ + recordVideo?: { + /** + * Path to the directory to put videos into. + */ + dir: string; + + /** + * Optional dimensions of the recorded videos. If not specified the size will be equal to `viewport` scaled down to fit + * into 800x800. If `viewport` is not configured explicitly the video size defaults to 800x450. Actual picture of each page + * will be scaled down if necessary to fit the specified size. + */ + size?: { + /** + * Video frame width. + */ + width: number; + + /** + * Video frame height. + */ + height: number; + }; + }; + + /** + * Changes the timezone of the context. See + * [ICU's metaZones.txt](https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/metaZones.txt?rcl=faee8bc70570192d82d2978a71e2a615788597d1) + * for a list of supported timezone IDs. + */ + timezoneId?: string; }): Promise; }