From 78b6d837be5127e99a95ef597c72b33cfb0a2efa Mon Sep 17 00:00:00 2001 From: Sam Maister Date: Mon, 8 Apr 2024 12:53:10 +0100 Subject: [PATCH 1/4] fix: remove preload guard --- src/index.ts | 5 ----- src/preload.ts | 3 --- src/types.ts | 5 ----- test/preload.spec.ts | 9 --------- 4 files changed, 22 deletions(-) diff --git a/src/index.ts b/src/index.ts index 4f4a3497..06f9e1b1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,11 +12,6 @@ import type { WdioElectronWindowObj, } from './types.js'; -/** - * set this environment variable so that the preload script can be loaded - */ -process.env.WDIO_ELECTRON = 'true'; - export const launcher = ElectronLaunchService; export default ElectronWorkerService; diff --git a/src/preload.ts b/src/preload.ts index cd93d98b..ac2ac01d 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -5,9 +5,6 @@ const invoke = async (channel: Channel, ...data: unknown[]) => { if (!Object.values(Channel).includes(channel)) { throw new Error(`Channel "${channel}" is invalid!`); } - if (!process.env.WDIO_ELECTRON) { - throw new Error('Electron APIs can not be invoked outside of WDIO'); - } return ipcRenderer.invoke(channel, ...data); }; diff --git a/src/types.ts b/src/types.ts index cbe49562..2df49a5e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,11 +1,6 @@ import type * as Electron from 'electron'; import type { Mock } from '@vitest/spy'; -/** - * Set this environment variable so that the preload script can be loaded - */ -process.env.WDIO_ELECTRON = 'true'; - export type Fn = (...args: unknown[]) => unknown; export type AsyncFn = (...args: unknown[]) => Promise; export type AbstractFn = Fn | AsyncFn; diff --git a/test/preload.spec.ts b/test/preload.spec.ts index 194a7418..d9d1877e 100644 --- a/test/preload.spec.ts +++ b/test/preload.spec.ts @@ -15,18 +15,9 @@ vi.mock('electron', () => ({ describe('preload', () => { beforeEach(async () => { - process.env.WDIO_ELECTRON = 'true'; await import('../src/preload.js'); }); - it('should throw an error when the WDIO_ELECTRON environment variable does not exist', async () => { - delete process.env.WDIO_ELECTRON; - await expect(window.wdioElectron.execute('look', ['some', 'args'])).rejects.toThrow( - 'Electron APIs can not be invoked outside of WDIO', - ); - expect(ipcRendererInvokeMock).not.toHaveBeenCalled(); - }); - it('should call invoke with the expected params', async () => { await window.wdioElectron.execute('look', ['some', 'args']); const ipcChannelName = 'wdio-electron.execute'; From e019fdfcd055f4cc2eedb34b6b1f7c9f76ff0610 Mon Sep 17 00:00:00 2001 From: Sam Maister Date: Mon, 8 Apr 2024 13:55:37 +0100 Subject: [PATCH 2/4] refactor: remove unnecesary invalid channel check --- src/preload.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/preload.ts b/src/preload.ts index ac2ac01d..681921a7 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -1,12 +1,7 @@ import { contextBridge, ipcRenderer } from 'electron'; import { Channel } from './constants.js'; -const invoke = async (channel: Channel, ...data: unknown[]) => { - if (!Object.values(Channel).includes(channel)) { - throw new Error(`Channel "${channel}" is invalid!`); - } - return ipcRenderer.invoke(channel, ...data); -}; +const invoke = async (channel: Channel, ...data: unknown[]) => ipcRenderer.invoke(channel, ...data); contextBridge.exposeInMainWorld('wdioElectron', { execute: (script: string, args: unknown[]) => invoke(Channel.Execute, script, args), From 79aac3500ee62e9df8cb8e2981c89ba38a02c3a0 Mon Sep 17 00:00:00 2001 From: Sam Maister Date: Mon, 8 Apr 2024 14:49:14 +0100 Subject: [PATCH 3/4] docs: more specific security instructions around API access, vite example, clarify non-bundled case --- docs/electron-apis/accessing-apis.md | 82 +++++++++++++++++++++------- 1 file changed, 63 insertions(+), 19 deletions(-) diff --git a/docs/electron-apis/accessing-apis.md b/docs/electron-apis/accessing-apis.md index 95447fbb..0e33e839 100644 --- a/docs/electron-apis/accessing-apis.md +++ b/docs/electron-apis/accessing-apis.md @@ -1,6 +1,68 @@ # Accessing Electron APIs -If you wish to access the Electron APIs then you will need to import (or require) the preload and main scripts in your app. To import 3rd-party packages (node_modules) in your `preload.js`, you have to disable sandboxing in your `BrowserWindow` config. +If you wish to access the Electron APIs then you will need to import (or require) the preload and main scripts in your app. + +Somewhere near the top of your `preload.js`, load `wdio-electron-service/preload` conditionally, e.g.: + +```ts +if (process.env.NODE_ENV === 'test') { + import('wdio-electron-service/preload'); +} +``` + +And somewhere near the top of your main index file (app entry point), load `wdio-electron-service/main` conditionally, e.g.: + +```ts +if (process.env.NODE_ENV === 'test') { + import('wdio-electron-service/main'); +} +``` + +**_For security reasons it is encouraged to ensure electron main process access is only available when the app is being tested._** + +This is the reason for the above dynamic imports wrapped in conditionals. An alternative approach is to use a separate test index file for both your preload and main entry points, e.g. + +`main/index.test.ts` + +```ts +import('wdio-electron-service/main'); +import('./index.js'); +``` + +`preload/index.test.ts` + +```ts +import('wdio-electron-service/preload'); +import('./index.js'); +``` + +You can then switch the test and production entry points of the application depending on the presence of an environment variable. + +e.g. for a Vite-based application: + +```ts +export default defineConfig(({ mode }) => { + const isProd = mode === 'production'; + const isTest = process.env.TEST === 'true'; + + return { + main: { + // ... + entry: { main: isTest ? 'src/main/index.test.ts' : 'src/main/index.ts' }, + // ... + }, + preload: { + // ... + entry: { preload: isTest ? 'src/preload/index.test.ts' : 'src/preload/index.ts' }, + // ... + }, + }; +}); +``` + +### Additional steps for non-bundled preload scripts + +If you are not bundling your preload script you will be unable to import 3rd-party packages (node_modules) in your `preload.js`. In this case you have to ensure sandboxing is disabled in your `BrowserWindow` config. It is not recommended to disable sandbox mode in production; to control this behaviour you can set the `NODE_ENV` environment variable when executing WDIO: @@ -22,24 +84,6 @@ new BrowserWindow({ }); ``` -Then somewhere near the top of your `preload.js`, load `wdio-electron-service/preload` conditionally, e.g.: - -```ts -if (process.env.NODE_ENV === 'test') { - import('wdio-electron-service/preload'); -} -``` - -And somewhere near the top of your main index file (app entry point), load `wdio-electron-service/main` conditionally, e.g.: - -```ts -if (process.env.NODE_ENV === 'test') { - import('wdio-electron-service/main'); -} -``` - -For security reasons it is encouraged to use dynamic imports wrapped in conditionals to ensure electron main process access is only available when the app is being tested. - ## Execute Scripts Arbitrary scripts can be executed within the context of your Electron application main process using `browser.electron.execute(...)`. This allows Electron APIs to be accessed in a fluid way, in case you wish to manipulate your application at runtime or trigger certain events. From 486a91601ca2f5aaf18a87fa3f149669acfdc8b7 Mon Sep 17 00:00:00 2001 From: Sam Maister Date: Mon, 8 Apr 2024 15:25:22 +0100 Subject: [PATCH 4/4] docs: standardise around the TEST env var used in examples, improve code samples --- README.md | 28 +++++++---- docs/common-issues.md | 18 ++++++-- .../chromedriver-configuration.md | 6 +-- docs/configuration/service-configuration.md | 8 ++-- docs/electron-apis/accessing-apis.md | 46 +++++++++++++++---- 5 files changed, 77 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 772058cb..4029fa04 100644 --- a/README.md +++ b/README.md @@ -37,10 +37,10 @@ Next, create your WDIO configuration file. If you need some inspiration for this You will need to add `electron` to your services array and set an Electron capability, e.g.: -```js -// wdio.conf.js +`wdio.conf.ts` + +```ts export const config = { - outputDir: 'logs', // ... services: ['electron'], capabilities: [ @@ -58,14 +58,22 @@ This will spin up an instance of your app in the same way that WDIO handles brow If you use [Electron Forge](https://www.electronforge.io/) or [Electron Builder](https://www.electron.build/) to package your app then the service will automatically attempt to find the path to your bundled Electron application. You can provide a custom path to the binary via custom service capabilities, e.g.: +`wdio.conf.ts` + ```ts -capabilities: [{ - browserName: 'electron', - 'wdio:electronServiceOptions': { - appBinaryPath: './path/to/bundled/electron/app.exe', - appArgs: ['foo', 'bar=baz'], - }, -}], +export const config = { + // ... + capabilities: [ + { + 'browserName': 'electron', + 'wdio:electronServiceOptions': { + appBinaryPath: './path/to/bundled/electron/app.exe', + appArgs: ['foo', 'bar=baz'], + }, + }, + ], + // ... +}; ``` ## Documentation diff --git a/docs/common-issues.md b/docs/common-issues.md index ce45d36b..a62f5e8d 100644 --- a/docs/common-issues.md +++ b/docs/common-issues.md @@ -14,16 +14,28 @@ $ npx electron-packager --no-prune #### Electron Forge +`package.json` + ```json -"package": "NODE_ENV=test electron-forge package" +{ + // ... + "scripts": { + // ... + "package": "TEST=true electron-forge package" + // ... + } + // ... +} ``` +`forge.config.js` + ```ts -// forge.config.js module.exports = { + // ... packagerConfig: { asar: true, - prune: process.env.NODE_ENV !== 'test', + prune: process.env.TEST !== 'true', }, // ... }; diff --git a/docs/configuration/chromedriver-configuration.md b/docs/configuration/chromedriver-configuration.md index 8c322b0d..e1a8a957 100644 --- a/docs/configuration/chromedriver-configuration.md +++ b/docs/configuration/chromedriver-configuration.md @@ -6,10 +6,10 @@ If you are not specifying a Chromedriver binary then the service will download and use the appropriate version for your app's Electron version. The Electron version of your app is determined by the version of `electron` or `electron-nightly` in your `package.json`, however you may want to override this behaviour - for instance, if the app you are testing is in a different repo from the tests. You can specify the Electron version manually by setting the `browserVersion` capability, as shown in the example configuration below: -```js -// wdio.conf.js +_`wdio.conf.ts`_ + +```ts export const config = { - outputDir: 'logs', // ... services: ['electron'], capabilities: [ diff --git a/docs/configuration/service-configuration.md b/docs/configuration/service-configuration.md index 3a38ed32..825ee0aa 100644 --- a/docs/configuration/service-configuration.md +++ b/docs/configuration/service-configuration.md @@ -2,6 +2,8 @@ The service can be configured by setting `wdio:electronServiceOptions` either on the service level or capability level, in which capability level configurations take precedence, e.g. the following WebdriverIO configuration: +_`wdio.conf.ts`_ + ```ts export const config = { // ... @@ -28,10 +30,10 @@ export const config = { ...would result in the following configuration object: -```js +```json { - appBinaryPath: '/foo/bar/myOtherApp', - appArgs: ['foo', 'bar'] + "appBinaryPath": "/foo/bar/myOtherApp", + "appArgs": ["foo", "bar"] } ``` diff --git a/docs/electron-apis/accessing-apis.md b/docs/electron-apis/accessing-apis.md index 0e33e839..3ae9ac0a 100644 --- a/docs/electron-apis/accessing-apis.md +++ b/docs/electron-apis/accessing-apis.md @@ -2,44 +2,60 @@ If you wish to access the Electron APIs then you will need to import (or require) the preload and main scripts in your app. -Somewhere near the top of your `preload.js`, load `wdio-electron-service/preload` conditionally, e.g.: +Somewhere near the top of your preload script, load `wdio-electron-service/preload` conditionally, e.g.: + +_`preload/index.ts`_ ```ts -if (process.env.NODE_ENV === 'test') { +if (process.env.TEST === 'true') { import('wdio-electron-service/preload'); } ``` And somewhere near the top of your main index file (app entry point), load `wdio-electron-service/main` conditionally, e.g.: +_`main/index.ts`_ + ```ts -if (process.env.NODE_ENV === 'test') { +if (process.env.TEST === 'true') { import('wdio-electron-service/main'); } ``` **_For security reasons it is encouraged to ensure electron main process access is only available when the app is being tested._** -This is the reason for the above dynamic imports wrapped in conditionals. An alternative approach is to use a separate test index file for both your preload and main entry points, e.g. +This is the reason for the above dynamic imports wrapped in conditionals. You will need to specify the TEST environment variable at the top of your WDIO config file: + +_`wdio.conf.ts`_ + +```ts +// ... +process.env.TEST = 'true'; +// ... +``` + +An alternative approach is to use a separate test index file for both your preload and main entry points, e.g. -`main/index.test.ts` +_`main/index.test.ts`_ ```ts import('wdio-electron-service/main'); import('./index.js'); ``` -`preload/index.test.ts` +_`preload/index.test.ts`_ ```ts import('wdio-electron-service/preload'); import('./index.js'); ``` -You can then switch the test and production entry points of the application depending on the presence of an environment variable. +You can then switch the test and production entry points of the application depending on the presence of the TEST environment variable. e.g. for a Vite-based application: +_`vite.config.ts`_ + ```ts export default defineConfig(({ mode }) => { const isProd = mode === 'production'; @@ -66,14 +82,24 @@ If you are not bundling your preload script you will be unable to import 3rd-par It is not recommended to disable sandbox mode in production; to control this behaviour you can set the `NODE_ENV` environment variable when executing WDIO: +_`package.json`_ + ```json -"wdio": "NODE_ENV=test wdio run wdio.conf.js" +// ... +"scripts": { + // ... + "wdio": "TEST=true wdio run wdio.conf.js", + // ... +} +// ... ``` -In your BrowserWindow configuration, set the sandbox option depending on the NODE_ENV variable: +In your BrowserWindow configuration, set the sandbox option depending on the TEST variable: + +_`main/index.ts`_ ```ts -const isTest = process.env.NODE_ENV === 'test'; +const isTest = process.env.TEST === 'true'; new BrowserWindow({ webPreferences: {