Skip to content

Commit

Permalink
Merge pull request #510 from webdriverio-community/fix/remove-preload…
Browse files Browse the repository at this point in the history
…-guard

(fix): remove preload guards
  • Loading branch information
goosewobbler authored Apr 8, 2024
2 parents eef89d9 + 486a916 commit 24b0feb
Show file tree
Hide file tree
Showing 9 changed files with 133 additions and 68 deletions.
28 changes: 18 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand All @@ -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
Expand Down
18 changes: 15 additions & 3 deletions docs/common-issues.md
Original file line number Diff line number Diff line change
Expand Up @@ -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',
},
// ...
};
Expand Down
6 changes: 3 additions & 3 deletions docs/configuration/chromedriver-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand Down
8 changes: 5 additions & 3 deletions docs/configuration/service-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
// ...
Expand All @@ -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"]
}
```

Expand Down
112 changes: 91 additions & 21 deletions docs/electron-apis/accessing-apis.md
Original file line number Diff line number Diff line change
@@ -1,44 +1,114 @@
# 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.

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:
Somewhere near the top of your preload script, load `wdio-electron-service/preload` conditionally, e.g.:

```json
"wdio": "NODE_ENV=test wdio run wdio.conf.js"
_`preload/index.ts`_

```ts
if (process.env.TEST === 'true') {
import('wdio-electron-service/preload');
}
```

In your BrowserWindow configuration, set the sandbox option depending on the NODE_ENV variable:
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
const isTest = process.env.NODE_ENV === 'test';
if (process.env.TEST === 'true') {
import('wdio-electron-service/main');
}
```

new BrowserWindow({
webPreferences: {
sandbox: !isTest
preload: path.join(__dirname, 'preload.js'),
}
// ...
});
**_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. 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';
// ...
```

Then somewhere near the top of your `preload.js`, load `wdio-electron-service/preload` conditionally, e.g.:
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
if (process.env.NODE_ENV === 'test') {
import('wdio-electron-service/preload');
}
import('wdio-electron-service/main');
import('./index.js');
```

And somewhere near the top of your main index file (app entry point), load `wdio-electron-service/main` conditionally, e.g.:
_`preload/index.test.ts`_

```ts
if (process.env.NODE_ENV === 'test') {
import('wdio-electron-service/main');
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 the TEST environment variable.

e.g. for a Vite-based application:

_`vite.config.ts`_

```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:

_`package.json`_

```json
// ...
"scripts": {
// ...
"wdio": "TEST=true wdio run wdio.conf.js",
// ...
}
// ...
```

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.
In your BrowserWindow configuration, set the sandbox option depending on the TEST variable:

_`main/index.ts`_

```ts
const isTest = process.env.TEST === 'true';

new BrowserWindow({
webPreferences: {
sandbox: !isTest
preload: path.join(__dirname, 'preload.js'),
}
// ...
});
```

## Execute Scripts

Expand Down
5 changes: 0 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
10 changes: 1 addition & 9 deletions src/preload.ts
Original file line number Diff line number Diff line change
@@ -1,15 +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!`);
}
if (!process.env.WDIO_ELECTRON) {
throw new Error('Electron APIs can not be invoked outside of WDIO');
}
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),
Expand Down
5 changes: 0 additions & 5 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -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<unknown>;
export type AbstractFn = Fn | AsyncFn;
Expand Down
9 changes: 0 additions & 9 deletions test/preload.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down

0 comments on commit 24b0feb

Please sign in to comment.