Skip to content

Helper functions for running Electron end-to-end tests using Playwright.

License

Notifications You must be signed in to change notification settings

spaceagetv/electron-playwright-helpers

Repository files navigation

Electron Playwright Helpers

NPM

Helper functions to make it easier to use Playwright for end-to-end testing with Electron. Parse packaged Electron projects so you can run tests on them. Click Electron menu items, send IPC messages, get menu structures, stub dialog.showOpenDialog() results, etc.

Installation

npm i -D electron-playwright-helpers

Usage

For a full example of how to use this library, see the electron-playwright-example project. But here's a quick example:

Javascript:

const eph = require('electron-playwright-helpers')
// - or cherry pick -
const { findLatestBuild, parseElectronApp, clickMenuItemById } = require('electron-playwright-helpers')

let electronApp: ElectronApplication

test.beforeAll(async () => {
  // find the latest build in the out directory
  const latestBuild = findLatestBuild()
  // parse the packaged Electron app and find paths and other info
  const appInfo = parseElectronApp(latestBuild)
  electronApp = await electron.launch({
    args: [appInfo.main], // main file from package.json
    executablePath: appInfo.executable // path to the Electron executable
  })
})

test.afterAll(async () => {
  await electronApp.close()
})

test('open a file', async () => {
  // stub electron dialog so dialog.showOpenDialog() 
  // will return a file path without opening a dialog
  await eph.stubDialog(electronApp, 'showOpenDialog', { filePaths: ['/path/to/file'] })

  // call the click method of menu item in the Electron app's application menu
  await eph.clickMenuItemById(electronApp, 'open-file')

  // get the result of an ipcMain.handle() function
  const result = await eph.ipcMainInvokeHandler(electronApp, 'get-open-file-path')
  
  // result should be the file path
  expect(result).toBe('/path/to/file')
})

Typescript:

import * as eph from 'electron-playwright-helpers'
// - or cherry pick -
import { electronWaitForFunction, ipcMainCallFirstListener, clickMenuItemById } from 'electron-playwright-helpers'

// then same as Javascript above

Contributing

Yes, please! Pull requests are always welcome. Feel free to add or suggest new features, fix bugs, etc.

Please use Conventional Commit messages for your commits. This project uses semantic-release to automatically publish new versions to NPM. The commit messages are used to determine the version number and changelog. We're also using Prettier as our code format and ESlint to enforce formatting, so please make sure your code is formatted before submitting a PR.

Additional Resources

API

Functions

findLatestBuild(buildDirectory)string

Parses the out directory to find the latest build of your Electron project. Use npm run package (or similar) to build your app prior to testing.

Assumptions: We assume that your build will be in the out directory, and that the build directory will be named with a hyphen-delimited platform name, e.g. out/my-app-win-x64. If your build directory is not out, you can pass the name of the directory as the buildDirectory parameter. If your build directory is not named with a hyphen-delimited platform name, this function will not work. However, you can pass the build path into parseElectronApp() directly.

parseElectronApp(buildDir)ElectronAppInfo

Given a directory containing an Electron app build, or the path to the app itself (directory on Mac, executable on Windows), return a bunch of metadata, including the path to the app's executable and the path to the app's main file.

Format of the data returned is an object with the following properties:

  • executable: path to the app's executable file
  • main: path to the app's main (JS) file
  • name: name of the app
  • resourcesDir: path to the app's resources directory
  • asar: true if the app is using asar
  • platform: OS platform
  • arch: architecture
  • packageJson: the JSON.parse()'d contents of the package.json file.
electronWaitForFunction(electronApp, fn, arg)Promise.<void>

Wait for a function to evaluate to true in the main Electron process. This really should be part of the Playwright API, but it's not.

This function is to electronApp.evaluate() as page.waitForFunction() is page.evaluate().

stubDialog(app, method, value)Promise.<void>

Stub a single dialog method. This is a convenience function that calls stubMultipleDialogs for a single method.

Playwright does not have a way to interact with Electron dialog windows, so this function allows you to substitute the dialog module's methods during your tests. By stubbing the dialog module, your Electron application will not display any dialog windows, and you can control the return value of the dialog methods. You're basically saying "when my application calls dialog.showOpenDialog, return this value instead". This allows you to test your application's behavior when the user selects a file, or cancels the dialog, etc.

Note: Each dialog method can only be stubbed with one value at a time, so you will want to call stubDialog before each time that you expect your application to call the dialog method.

stubMultipleDialogs(app, mocks)Promise.<void>

Stub methods of the Electron dialog module.

Playwright does not have a way to interact with Electron dialog windows, so this function allows you to mock the dialog module's methods during your tests. By mocking the dialog module, your Electron application will not display any dialog windows, and you can control the return value of the dialog methods. You're basically saying "when my application calls dialog.showOpenDialog, return this value instead". This allows you to test your application's behavior when the user selects a file, or cancels the dialog, etc.

stubAllDialogs(app)Promise.<void>

Stub all dialog methods. This is a convenience function that calls stubMultipleDialogs for all dialog methods. This is useful if you want to ensure that dialogs are not displayed during your tests. However, you may want to use stubDialog or stubMultipleDialogs to control the return value of specific dialog methods (e.g. showOpenDialog) during your tests.

ipcMainEmit(electronApp, message, ...args)Promise.<boolean>

Emit an ipcMain message from the main process. This will trigger all ipcMain listeners for the message.

This does not transfer data between main and renderer processes. It simply emits an event in the main process.

ipcMainCallFirstListener(electronApp, message, ...args)Promise.<unknown>

Call the first listener for a given ipcMain message in the main process and return its result.

NOTE: ipcMain listeners usually don't return a value, but we're using this to retrieve test data from the main process.

Generally, it's probably better to use ipcMainInvokeHandler() instead.

ipcMainInvokeHandler(electronApp, message, ...args)Promise.<unknown>

Get the return value of an ipcMain.handle() function

ipcRendererSend(page, channel, ...args)Promise.<unknown>

Send an ipcRenderer.send() (to main process) from a given window.

Note: nodeIntegration must be true and contextIsolation must be false in the webPreferences for this BrowserWindow.

ipcRendererInvoke(page, message, ...args)Promise.<unknown>

Send an ipcRenderer.invoke() from a given window.

Note: nodeIntegration must be true and contextIsolation must be false in the webPreferences for this window

ipcRendererCallFirstListener(page, message, ...args)Promise.<unknown>

Call just the first listener for a given ipcRenderer channel in a given window. UNLIKE MOST Electron ipcRenderer listeners, this function SHOULD return a value.

This function does not send data between main and renderer processes. It simply retrieves data from the renderer process.

Note: nodeIntegration must be true for this BrowserWindow.

ipcRendererEmit(page, message, ...args)Promise.<boolean>

Emit an IPC message to a given window. This will trigger all ipcRenderer listeners for the message.

This does not transfer data between main and renderer processes. It simply emits an event in the renderer process.

Note: nodeIntegration must be true for this window

clickMenuItemById(electronApp, id)Promise.<void>

Execute the .click() method on the element with the given id. NOTE: All menu testing functions will only work with items in the application menu.

clickMenuItem(electronApp, property, value)Promise.<void>

Click the first matching menu item by any of its properties. This is useful for menu items that don't have an id. HOWEVER, this is not as fast or reliable as using clickMenuItemById() if the menu item has an id.

NOTE: All menu testing functions will only work with items in the application menu.

getMenuItemAttribute(electronApp, menuId, attribute)Promise.<string>

Get a given attribute the MenuItem with the given id.

getMenuItemById(electronApp, menuId)Promise.<MenuItemPartial>

Get information about the MenuItem with the given id

getApplicationMenu(electronApp)Promise.<Array.<MenuItemPartial>>

Get the current state of the application menu. Contains only primitive values and submenus.. Very similar to menu construction template structure in Electron.

findMenuItem(electronApp, property, value, menuItems)Promise.<MenuItemPartial>

Find a MenuItem by any of its properties

waitForMenuItem(electronApp, id)Promise.<void>

Wait for a MenuItem to exist

waitForMenuItemStatus(electronApp, id, property, value)Promise.<void>

Wait for a MenuItem to have a specific attribute value. For example, wait for a MenuItem to be enabled... or be visible.. etc

addTimeoutToPromise(promise, timeoutMs, timeoutMessage)Promise.<T>

Add a timeout to any Promise

addTimeout(functionName, timeoutMs, timeoutMessage, ...args)Promise.<T>

Add a timeout to any helper function from this library which returns a Promise.

Typedefs

ElectronAppInfo

Format of the data returned from parseElectronApp()

findLatestBuild(buildDirectory) ⇒ string

Parses the out directory to find the latest build of your Electron project. Use npm run package (or similar) to build your app prior to testing.

Assumptions: We assume that your build will be in the out directory, and that the build directory will be named with a hyphen-delimited platform name, e.g. out/my-app-win-x64. If your build directory is not out, you can pass the name of the directory as the buildDirectory parameter. If your build directory is not named with a hyphen-delimited platform name, this function will not work. However, you can pass the build path into parseElectronApp() directly.

Kind: global function
Returns: string -

  • path to the most recently modified build directory
**See**: parseElectronApp
Param Type Default Description
buildDirectory string "out"

optional - the directory to search for the latest build (path/name relative to package root or full path starting with /). Defaults to out.

parseElectronApp(buildDir) ⇒ ElectronAppInfo

Given a directory containing an Electron app build, or the path to the app itself (directory on Mac, executable on Windows), return a bunch of metadata, including the path to the app's executable and the path to the app's main file.

Format of the data returned is an object with the following properties:

  • executable: path to the app's executable file
  • main: path to the app's main (JS) file
  • name: name of the app
  • resourcesDir: path to the app's resources directory
  • asar: true if the app is using asar
  • platform: OS platform
  • arch: architecture
  • packageJson: the JSON.parse()'d contents of the package.json file.

Kind: global function
Returns: ElectronAppInfo -

metadata about the app

Param Type Description
buildDir string

absolute path to the build directory or the app itself

electronWaitForFunction(electronApp, fn, arg) ⇒ Promise.<void>

Wait for a function to evaluate to true in the main Electron process. This really should be part of the Playwright API, but it's not.

This function is to electronApp.evaluate() as page.waitForFunction() is page.evaluate().

Kind: global function
Fulfil: void Resolves when the function returns true

Param Type Description
electronApp ElectronApplication

the Playwright ElectronApplication

fn function

the function to evaluate in the main process - must return a boolean

arg Any

optional - an argument to pass to the function

ElectronAppInfo

Format of the data returned from parseElectronApp()

Kind: global typedef
Properties

Name Type Description
executable string

path to the Electron executable

main string

path to the main (JS) file

name string

name of the your application

resourcesDir string

path to the resources directory

asar boolean

whether the app is packaged as an asar archive

platform string

'darwin', 'linux', or 'win32'

arch string

'x64', 'x32', or 'arm64'

packageJson PackageJson

the JSON.parse()'d contents of the package.json file.

stubDialog(app, method, value) ⇒ Promise.<void>

Stub a single dialog method. This is a convenience function that calls stubMultipleDialogs for a single method.

Playwright does not have a way to interact with Electron dialog windows, so this function allows you to substitute the dialog module's methods during your tests. By stubbing the dialog module, your Electron application will not display any dialog windows, and you can control the return value of the dialog methods. You're basically saying "when my application calls dialog.showOpenDialog, return this value instead". This allows you to test your application's behavior when the user selects a file, or cancels the dialog, etc.

Note: Each dialog method can only be stubbed with one value at a time, so you will want to call stubDialog before each time that you expect your application to call the dialog method.

Kind: global function
Returns: Promise.<void> -

A promise that resolves when the mock is applied.


Category: Dialog
Fullfil: void - A promise that resolves when the mock is applied.
See: stubMultipleDialogs

Param Type Description
app ElectronApplication

The Playwright ElectronApplication instance.

method String

The dialog method to mock.

value ReturnType.<Electron.Dialog>

The value that your application will receive when calling this dialog method. See the Electron docs for the return value of each method.

Example

await stubDialog(app, 'showOpenDialog', {
 filePaths: ['/path/to/file'],
 canceled: false,
})
await clickMenuItemById(app, 'open-file')
// when time your application calls dialog.showOpenDialog,
// it will return the value you specified

stubMultipleDialogs(app, mocks) ⇒ Promise.<void>

Stub methods of the Electron dialog module.

Playwright does not have a way to interact with Electron dialog windows, so this function allows you to mock the dialog module's methods during your tests. By mocking the dialog module, your Electron application will not display any dialog windows, and you can control the return value of the dialog methods. You're basically saying "when my application calls dialog.showOpenDialog, return this value instead". This allows you to test your application's behavior when the user selects a file, or cancels the dialog, etc.

Kind: global function
Returns: Promise.<void> -

A promise that resolves when the mocks are applied.


Category: Dialog
Fullfil: void - A promise that resolves when the mocks are applied.

Param Type Description
app ElectronApplication

The Playwright ElectronApplication instance.

mocks Array.<DialogMethodStubPartial>

An array of dialog method mocks to apply.

Example

await stubMultipleDialogs(app, [
 {
   method: 'showOpenDialog',
   value: {
     filePaths: ['/path/to/file1', '/path/to/file2'],
     canceled: false,
   },
 },
 {
    method: 'showSaveDialog',
    value: {
      filePath: '/path/to/file',
      canceled: false,
    },
  },
])
await clickMenuItemById(app, 'save-file')
// when your application calls dialog.showSaveDialog,
// it will return the value you specified

stubAllDialogs(app) ⇒ Promise.<void>

Stub all dialog methods. This is a convenience function that calls stubMultipleDialogs for all dialog methods. This is useful if you want to ensure that dialogs are not displayed during your tests. However, you may want to use stubDialog or stubMultipleDialogs to control the return value of specific dialog methods (e.g. showOpenDialog) during your tests.

Kind: global function
Returns: Promise.<void> -

A promise that resolves when the mocks are applied.


Category: Dialog
Fullfil: void - A promise that resolves when the mocks are applied.
See: stubDialog

Param Type Description
app ElectronApplication

The Playwright ElectronApplication instance.

ipcMainEmit(electronApp, message, ...args) ⇒ Promise.<boolean>

Emit an ipcMain message from the main process. This will trigger all ipcMain listeners for the message.

This does not transfer data between main and renderer processes. It simply emits an event in the main process.

Kind: global function
Category: IPCMain
Fulfil: boolean true if there were listeners for this message
Reject: Error if there are no ipcMain listeners for the event

Param Type Description
electronApp ElectronApplication

the ElectronApplication object from Playwright

message string

the channel to call all ipcMain listeners for

...args unknown

one or more arguments to send

ipcMainCallFirstListener(electronApp, message, ...args) ⇒ Promise.<unknown>

Call the first listener for a given ipcMain message in the main process and return its result.

NOTE: ipcMain listeners usually don't return a value, but we're using this to retrieve test data from the main process.

Generally, it's probably better to use ipcMainInvokeHandler() instead.

Kind: global function
Category: IPCMain
Fulfil: unknown resolves with the result of the function
Reject: Error if there are no ipcMain listeners for the event

Param Type Description
electronApp ElectronApplication

the ElectronApplication object from Playwright

message string

the channel to call the first listener for

...args unknown

one or more arguments to send

ipcMainInvokeHandler(electronApp, message, ...args) ⇒ Promise.<unknown>

Get the return value of an ipcMain.handle() function

Kind: global function
Category: IPCMain
Fulfil: unknown resolves with the result of the function called in main process

Param Type Description
electronApp ElectronApplication

the ElectronApplication object from Playwright

message string

the channel to call the first listener for

...args unknown

one or more arguments to send

ipcRendererSend(page, channel, ...args) ⇒ Promise.<unknown>

Send an ipcRenderer.send() (to main process) from a given window.

Note: nodeIntegration must be true and contextIsolation must be false in the webPreferences for this BrowserWindow.

Kind: global function
Category: IPCRenderer
Fulfil: unknown resolves with the result of ipcRenderer.send()

Param Type Description
page Page

the Playwright Page to send the ipcRenderer.send() from

channel string

the channel to send the ipcRenderer.send() to

...args unknown

one or more arguments to send to the ipcRenderer.send()

ipcRendererInvoke(page, message, ...args) ⇒ Promise.<unknown>

Send an ipcRenderer.invoke() from a given window.

Note: nodeIntegration must be true and contextIsolation must be false in the webPreferences for this window

Kind: global function
Category: IPCRenderer
Fulfil: unknown resolves with the result of ipcRenderer.invoke()

Param Type Description
page Page

the Playwright Page to send the ipcRenderer.invoke() from

message string

the channel to send the ipcRenderer.invoke() to

...args unknown

one or more arguments to send to the ipcRenderer.invoke()

ipcRendererCallFirstListener(page, message, ...args) ⇒ Promise.<unknown>

Call just the first listener for a given ipcRenderer channel in a given window. UNLIKE MOST Electron ipcRenderer listeners, this function SHOULD return a value.

This function does not send data between main and renderer processes. It simply retrieves data from the renderer process.

Note: nodeIntegration must be true for this BrowserWindow.

Kind: global function
Category: IPCRenderer
Fulfil: unknown the result of the first ipcRenderer.on() listener

Param Type Description
page Page

The Playwright Page to with the ipcRenderer.on() listener

message string

The channel to call the first listener for

...args unknown

optional - One or more arguments to send to the ipcRenderer.on() listener

ipcRendererEmit(page, message, ...args) ⇒ Promise.<boolean>

Emit an IPC message to a given window. This will trigger all ipcRenderer listeners for the message.

This does not transfer data between main and renderer processes. It simply emits an event in the renderer process.

Note: nodeIntegration must be true for this window

Kind: global function
Category: IPCRenderer
Fulfil: boolean true if the event was emitted
Reject: Error if there are no ipcRenderer listeners for the event

Param Type Description
page Page

the Playwright Page to with the ipcRenderer.on() listener

message string

the channel to call all ipcRenderer listeners for

...args unknown

optional - one or more arguments to send

clickMenuItemById(electronApp, id) ⇒ Promise.<void>

Execute the .click() method on the element with the given id. NOTE: All menu testing functions will only work with items in the application menu.

Kind: global function
Category: Menu
Fulfil: void resolves with the result of the click() method - probably undefined

Param Type Description
electronApp ElectronApplication

the Electron application object (from Playwright)

id string

the id of the MenuItem to click

clickMenuItem(electronApp, property, value) ⇒ Promise.<void>

Click the first matching menu item by any of its properties. This is useful for menu items that don't have an id. HOWEVER, this is not as fast or reliable as using clickMenuItemById() if the menu item has an id.

NOTE: All menu testing functions will only work with items in the application menu.

Kind: global function
Category: Menu
Fulfil: void resolves with the result of the click() method - probably undefined

Param Type Description
electronApp ElectronApplication

the Electron application object (from Playwright)

property String

a property of the MenuItem to search for

value String | Number | Boolean

the value of the property to search for

getMenuItemAttribute(electronApp, menuId, attribute) ⇒ Promise.<string>

Get a given attribute the MenuItem with the given id.

Kind: global function
Category: Menu
Fulfil: string resolves with the attribute value

Param Type Description
electronApp ElectronApplication

the Electron application object (from Playwright)

menuId string

the id of the MenuItem to retrieve the attribute from

attribute string

the attribute to retrieve

getMenuItemById(electronApp, menuId) ⇒ Promise.<MenuItemPartial>

Get information about the MenuItem with the given id

Kind: global function
Category: Menu
Fulfil: MenuItemPartial the MenuItem with the given id

Param Type Description
electronApp ElectronApplication

the Electron application object (from Playwright)

menuId string

the id of the MenuItem to retrieve

getApplicationMenu(electronApp) ⇒ Promise.<Array.<MenuItemPartial>>

Get the current state of the application menu. Contains only primitive values and submenus.. Very similar to menu construction template structure in Electron.

Kind: global function
Category: Menu
Fulfil: MenuItemPartial[] an array of MenuItem-like objects

Param Type Description
electronApp ElectronApplication

the Electron application object (from Playwright)

findMenuItem(electronApp, property, value, menuItems) ⇒ Promise.<MenuItemPartial>

Find a MenuItem by any of its properties

Kind: global function
Category: Menu
Fulfil: MenuItemPartial the first MenuItem with the given property and value

Param Type Description
electronApp ElectronApplication

the Electron application object (from Playwright)

property string

the property to search for

value string

the value to search for

menuItems MenuItemPartial | Array.<MenuItemPartial>

optional - single MenuItem or array - if not provided, will be retrieved from the application menu

waitForMenuItem(electronApp, id) ⇒ Promise.<void>

Wait for a MenuItem to exist

Kind: global function
Category: Menu
Fulfil: void resolves when the MenuItem is found

Param Type Description
electronApp ElectronApplication

the Electron application object (from Playwright)

id string

the id of the MenuItem to wait for

waitForMenuItemStatus(electronApp, id, property, value) ⇒ Promise.<void>

Wait for a MenuItem to have a specific attribute value. For example, wait for a MenuItem to be enabled... or be visible.. etc

Kind: global function
Category: Menu
Fulfil: void resolves when the MenuItem with correct status is found

Param Type Description
electronApp ElectronApplication

the Electron application object (from Playwright)

id string

the id of the MenuItem to wait for

property string

the property to search for

value string | number | boolean

the value to search for

addTimeoutToPromise(promise, timeoutMs, timeoutMessage) ⇒ Promise.<T>

Add a timeout to any Promise

Kind: global function
Returns: Promise.<T> -

the result of the original promise if it resolves before the timeout


Category: Utilities
See: addTimeout

Param Default Description
promise

the promise to add a timeout to - must be a Promise

timeoutMs 5000

the timeout in milliseconds - defaults to 5000

timeoutMessage

optional - the message to return if the timeout is reached

addTimeout(functionName, timeoutMs, timeoutMessage, ...args) ⇒ Promise.<T>

Add a timeout to any helper function from this library which returns a Promise.

Kind: global function
Returns: Promise.<T> -

the result of the helper function if it resolves before the timeout


Category: Utilities

Param Default Description
functionName

the name of the helper function to call

timeoutMs 5000

the timeout in milliseconds - defaults to 5000

timeoutMessage

optional - the message to return if the timeout is reached

...args

any arguments to pass to the helper function