npm.io
3.1.2 • Published 1 month ago

electron-playwright-helpers

Licence
MIT
Version
3.1.2
Deps
1
Size
316 kB
Vulns
0
Weekly
0
Stars
82

Electron Playwright Helpers

npm version npm downloads

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.

Migrating from v1.x to v2.0

Version 2.0 introduces significant improvements to handle flakiness issues that appeared with Electron 27+ and Playwright. Starting with Electron 27, Playwright's evaluate() calls became unreliable, often throwing errors like "context or browser has been closed" or "Execution context was destroyed" seemingly at random.

What's New in v2.0
Built-in Retry Logic

All helper functions now automatically retry operations that fail due to Playwright context issues. This happens transparently - your existing code will work without changes, but will be more reliable.

New Utility Functions
  • retry(fn, options) - Wrap any Playwright call to automatically retry on context errors
  • retryUntilTruthy(fn, options) - Like Playwright's page.waitForFunction() but with automatic retry on errors
  • setRetryOptions(options) - Configure default retry behavior globally
  • getRetryOptions() - Get current retry configuration
  • resetRetryOptions() - Reset retry options to defaults
Conditional Dialog Stubbing (New!)
  • stubDialogMatchers(app, stubs, options) - Stub dialogs with conditional matching based on dialog options
  • clearDialogMatchers(app) - Clear dialog matcher stubs
Breaking Changes
1. Node.js 18+ Required

Version 2.0 requires Node.js 18 or later due to modern JavaScript features like structuredClone().

2. IPC Helper Function Signatures

IPC helpers now accept an optional RetryOptions object as the last argument:

// v1.x
await ipcRendererSend(page, 'my-channel', arg1, arg2)

// v2.0 - still works exactly the same
await ipcRendererSend(page, 'my-channel', arg1, arg2)

// v2.0 - with retry options
await ipcRendererSend(page, 'my-channel', arg1, arg2, { timeout: 10000 })

This applies to: ipcRendererSend, ipcRendererInvoke, ipcRendererEmit, ipcRendererCallFirstListener, ipcMainEmit, ipcMainCallFirstListener, ipcMainInvokeHandler

3. Menu Helper Function Signatures

Menu helpers now accept an optional RetryOptions object:

// v1.x
await clickMenuItemById(electronApp, 'my-menu-item')

// v2.0 - still works exactly the same
await clickMenuItemById(electronApp, 'my-menu-item')

// v2.0 - with retry options
await clickMenuItemById(electronApp, 'my-menu-item', { timeout: 10000 })
Migration Steps

For most projects, upgrading is straightforward:

  1. Update Node.js to version 18 or later
  2. Update the package: npm install electron-playwright-helpers@latest
  3. Test your suite - existing code should work without changes
Customizing Retry Behavior

If you need to adjust retry behavior globally:

import { setRetryOptions, resetRetryOptions } from 'electron-playwright-helpers'

// Increase timeout for slow CI environments
setRetryOptions({
  timeout: 10000,  // 10 seconds (default: 5000)
  poll: 500,       // poll every 500ms (default: 200)
})

// Reset to defaults
resetRetryOptions()

Or disable retries for specific calls:

await ipcRendererSend(page, 'channel', arg, { disable: true })
"Resulting promise was garbage collected." is not retried by default

Playwright awaits your promise through the debugger protocol, and V8's inspector tracks it with a weak handle. If nothing in the target process references that promise, it gets collected before it can settle and the reply comes back as this error.

Two quite different things produce it, and they want opposite responses.

Case 1 — your evaluate() callback returned a dangling promise.

// this promise is unreachable the moment evaluate() returns - it can never settle
await electronApp.evaluate(() => new Promise(() => {}))
  • It is deterministic, not intermittent. The same call fails the same way every time, so retrying only burns the timeout and buries the real cause under a "Timeout after 5000ms" message.
  • Your callback body already ran. Its side effects happened; only the reply was lost. That makes an automatic retry actively unsafe for anything non-idempotent.

The fix is in the callback: return a value, or return a promise that something retains - one backed by an Electron API call, a timer, or an event listener. Promises from real Electron APIs are held by the native side; those were unaffected across 300 iterations with garbage collection forced at the await point.

Case 2 — a raw Playwright channel call lost an internal promise mid-flight.

// no callback of yours involved - the promise that got collected is Playwright's
const win = await electronApp.browserWindow(page)
const title = await win.getProperty('title')

Here you wrote no callback, so there is nothing to fix in your code. This one really is transient, and retrying is the right response.

The message is identical in both cases, so this library cannot tell them apart. It does not retry by default, because silently repeating a side effect is the worse of the two failures. If you are in case 2, opt in - ideally scoped to the specific reads that need it rather than set globally, so that case 1 keeps failing loudly:

import { retry, getRetryOptions } from 'electron-playwright-helpers'

/** only ever wrap idempotent reads in this - a retried action can fire twice */
function retryThroughGC<T>(fn: () => Promise<T>) {
  const { errorMatch } = getRetryOptions()
  const existing = Array.isArray(errorMatch) ? errorMatch : [errorMatch]
  return retry(fn, { errorMatch: [...existing, 'promise was garbage collected'] })
}

const win = await retryThroughGC(() => electronApp.browserWindow(page))

To turn the old retry-anyway behavior back on everywhere instead, note that errorMatch replaces the default list rather than extending it, so repeat the defaults you still want:

import { retry, setRetryOptions } from 'electron-playwright-helpers'

const errorMatch = [
  'context or browser has been closed',
  'Execution context was destroyed',
  "reading 'getOwnerBrowserWindow'",
  'promise was garbage collected',
]

// per call...
await retry(() => electronApp.evaluate(myFn), { errorMatch })
// ...or globally
setRetryOptions({ errorMatch })

Note that on Playwright < 1.62 you will never see this error by name. Those versions have no branch for the underlying protocol message, so it falls through to the generic "Execution context was destroyed" and is retried as if it were a teardown error.

Using the New Retry Functions

If you have custom Playwright evaluate() calls that aren't using our helpers, wrap them with retry():

import { retry, retryUntilTruthy } from 'electron-playwright-helpers'

// Wrap evaluate calls to handle context errors
const result = await retry(() =>
  electronApp.evaluate(({ app }) => app.getName())
)

// Wait for a condition with automatic error recovery
await retryUntilTruthy(() =>
  page.evaluate(() => document.body.classList.contains('ready'))
)

Additional Resources

API

Constants

dialogMatcherDefaults

Union type of all dialog matcher stubs.

Functions

toSerializableMatcher()

Convert a string or RegExp to a serializable StringMatcher. RegExp objects cannot be transferred via Playwright's evaluate(), so we serialize them as {source, flags}.

matchesPattern()

Check if a value matches a StringMatcher. Used inside app.evaluate() where the matcher is already serialized.

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().

Gives up once options.timeout has elapsed, so a function that never returns true rejects rather than polling forever.

evaluateWithRetry(electronApp, fn, arg, retries, retryIntervalMs)Promise.<R>

Electron's evaluate function can be flakey, throwing an error saying the execution context has been destroyed. This function retries the evaluation several times to see if it can run the evaluation without an error. If it fails after the retries, it throws the error.

isSerializedNativeImageSuccess()

Type guard to check if a SerializedNativeImage is a success case

isSerializedNativeImageError()

Type guard to check if a SerializedNativeImage is an error case

retryUntilTruthy(fn)Promise.<T>

Retries a given function until it returns a truthy value or the timeout is reached.

This offers similar functionality to Playwright's page.waitForFunction() method – but with more flexibility and control over the retry attempts. It also defaults to ignoring common errors due to the way that Playwright handles browser contexts.

matchesPattern()

Helper to match a string against a pattern (string or RegExp). For strings, performs a substring match (includes). For RegExp, tests the pattern against the value.

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.

stubDialogMatchers(app, stubs, options)

Stub dialog methods with matchers that check dialog options before returning values. This allows you to set up multiple different return values based on the dialog's title, message, buttons, or other options.

Matchers are checked in order - the first matching stub wins. If no stub matches, either an error is thrown (if throwOnUnmatched is true) or the default value is returned.

clearDialogMatchers(app)

Clear all dialog matcher stubs and restore original dialog methods. Note: This requires the app to have stored the original methods, which is not done by default. You may need to restart the app to fully restore dialog functionality.

ipcMainEmit(electronApp, message, ...args, retryOptions)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, retryOptions)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, retryOptions)Promise.<unknown>

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

ipcRendererSend(page, channel, ...args, retryOptions)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, retryOptions)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, retryOptions)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, retryOptions)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.

A click is not idempotent, so this call is not retried by default (disable: true). If the click tears down the execution context - by quitting the app or closing the window, for example - the resulting error is swallowed, since the click did happen. Any other error is thrown. Passing { disable: false } re-enables retries, at the risk of clicking twice.

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.

As with clickMenuItemById(), the click itself is never retried. See that function for how errors thrown by the click are handled.

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. Returns serializable values including primitives, objects, arrays, and other non-recursive data structures.

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

Get the current state of the application menu. Contains serializable values including primitives, objects, arrays, and other non-recursive data structures. 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.

retry(fn, [options])Promise.<T>

Retries a function until it returns without throwing an error.

Starting with Electron 27, Playwright can get very flakey when running code in Electron's main or renderer processes. It will throw errors like "context or browser has been closed" or "Execution context was destroyed" when the execution context a call was dispatched into goes away underneath it. Playwright has no recovery for this on the Electron main process - it resolves a single require('electron') handle when the app launches and never re-acquires it - so retrying is the only option available from the outside. This function retries a given function until it returns without throwing one of these errors, or until the timeout is reached.

Note that "Resulting promise was garbage collected." is deliberately not retried. Despite appearances it is not a flake: it means the evaluate callback returned a promise nothing in the target process references, so V8 collected it before it settled. Every attempt fails identically, and the callback body has already run. retry() throws that one immediately, with an explanation attached.

setRetryOptions(options)

Sets the default retry() options. These options will be used for all subsequent calls to retry() unless overridden. You can reset the defaults at any time by calling resetRetryOptions().

getRetryOptions()

Gets the current default retry options.

resetRetryOptions()

Resets the retry options to their default values.

The default values are:

  • disable: false
  • poll: 200
  • timeout: 5000
  • errorMatch: ['context or browser has been closed', 'Execution context was destroyed', "reading 'getOwnerBrowserWindow'"]
errToString(err)

Converts an unknown error to a string representation.

This function handles different types of errors and attempts to convert them to a string in a meaningful way. It checks if the error is an object with a toString method and uses that method if available. If the error is a string, it returns the string directly. For other types, it converts the error to a JSON string.

getWindowByUrl(electronApp, pattern, options)

Get all windows whose URL matches the given pattern.

getWindowByTitle(electronApp, pattern, options)

Get all windows whose title matches the given pattern.

getWindowByMatcher(electronApp, matcher, options)

Get all windows that match the provided matcher function.

waitForWindowByUrl(electronApp, pattern, options)

Wait for a window whose URL matches the given pattern.

This function checks existing windows first, then listens for new windows. It uses polling to handle windows that may have their URL change after opening.

waitForWindowByTitle(electronApp, pattern, options)

Wait for a window whose title matches the given pattern.

This function checks existing windows first, then listens for new windows. It uses polling to handle windows that may have their title change after opening.

waitForWindowByMatcher(electronApp, matcher, options)

Wait for a window that matches the provided matcher function.

This function:

  1. Checks existing windows first
  2. Listens for new window events
  3. Polls existing windows periodically (to catch URL/title changes)

Typedefs

ElectronAppInfo

Format of the data returned from parseElectronApp()

dialogMatcherDefaults

Union type of all dialog matcher stubs.

Kind: global constant

toSerializableMatcher()

Convert a string or RegExp to a serializable StringMatcher. RegExp objects cannot be transferred via Playwright's evaluate(), so we serialize them as {source, flags}.

Kind: global function

matchesPattern()

Check if a value matches a StringMatcher. Used inside app.evaluate() where the matcher is already serialized.

Kind: global function

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().

Gives up once options.timeout has elapsed, so a function that never returns true rejects rather than polling forever.

Kind: global function
Throws:

  • Error

    if the function has not returned true before options.timeout elapses

Fulfil: void Resolves when the function returns true

Param Type Default 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

[options.timeout] number 5000

how long to keep polling, in total, before giving up

[options.poll] number 100

how long to wait between polls after a falsy result

[options.retryTimeout] number 5000

how long a single evaluate() may take before it is retried

[options.retryPoll] number 200

how long to wait before retrying after an error

[options.retryErrorMatch] string | Array.<string> | RegExp

errors to retry. Others throw immediately

evaluateWithRetry(electronApp, fn, arg, retries, retryIntervalMs) ⇒ Promise.<R>

Electron's evaluate function can be flakey, throwing an error saying the execution context has been destroyed. This function retries the evaluation several times to see if it can run the evaluation without an error. If it fails after the retries, it throws the error.

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

  • the result of the evaluation
Param Type Description
electronApp ElectronApplication

the Playwright ElectronApplication

fn function

the function to evaluate in the main process

arg Any

an argument to pass to the function

retries

the number of times to retry the evaluation

retryIntervalMs

the interval between retries

isSerializedNativeImageSuccess()

Type guard to check if a SerializedNativeImage is a success case

Kind: global function

isSerializedNativeImageError()

Type guard to check if a SerializedNativeImage is an error case

Kind: global function

retryUntilTruthy(fn) ⇒ Promise.<T>

Retries a given function until it returns a truthy value or the timeout is reached.

This offers similar functionality to Playwright's page.waitForFunction() method – but with more flexibility and control over the retry attempts. It also defaults to ignoring common errors due to the way that Playwright handles browser contexts.

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

  • A promise that resolves to the truthy value returned by the function.
**Throws**:
  • Error
  • Throws an error if the timeout is reached before a truthy value is returned.
  • Param Type Default Description
    fn function

    The function to retry. It can return a promise or a value. It should NOT return void/undefined.

    [options.timeout] number 5000

    The maximum time in milliseconds to keep retrying the function. Defaults to 5000ms.

    [options.poll] number 100

    The delay between each retry attempt in milliseconds. Defaults to 100ms.

    [options.retryTimeout] number 5000

    The maximum time in milliseconds to wait for an individual try to return a result. Defaults to 5000ms.

    [options.retryPoll] number 200

    The delay between each retry attempt in milliseconds. Defaults to 200ms.

    [options.retryErrorMatch] string | Array.<string> | RegExp

    The error message or pattern to match against. Errors that don't match will throw immediately.

    Example

    test('my test', async () => {
      // this will fail immediately if Playwright's context gets weird:
      const oldWay = await page.waitForFunction(() => document.body.classList.contains('ready'))
    
     // this will not fail if Playwright's context gets weird:
      const newWay = await retryUntilTruthy(() =>
        page.evaluate(() => document.body.classList.contains('ready'))
      )
    })

    matchesPattern()

    Helper to match a string against a pattern (string or RegExp). For strings, performs a substring match (includes). For RegExp, tests the pattern against the value.

    Kind: global 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.

    stubDialogMatchers(app, stubs, options) ⇒

    Stub dialog methods with matchers that check dialog options before returning values. This allows you to set up multiple different return values based on the dialog's title, message, buttons, or other options.

    Matchers are checked in order - the first matching stub wins. If no stub matches, either an error is thrown (if throwOnUnmatched is true) or the default value is returned.

    Kind: global function
    Returns:

    A promise that resolves when the stubs are applied.


    Category: Dialog

    Param Description
    app

    The Playwright ElectronApplication instance.

    stubs

    Array of dialog matcher stubs to apply.

    options

    Optional configuration.

    Example

    // Set up multiple dialog stubs at the start of your test
    await stubDialogMatchers(app, [
      {
        method: 'showMessageBox',
        matcher: { title: /delete/i, buttons: /yes/i },
        value: { response: 1 }, // Click "Yes" for delete dialogs
      },
      {
        method: 'showMessageBox',
        matcher: { title: /save/i },
        value: { response: 0 }, // Click "Save" for save dialogs
      },
      {
        method: 'showOpenDialog',
        matcher: { title: 'Select Image' },
        value: { filePaths: ['/path/to/image.png'], canceled: false },
      },
      {
        method: 'showOpenDialog',
        matcher: {}, // Match all other open dialogs
        value: { canceled: true },
      },
    ])

    clearDialogMatchers(app) ⇒

    Clear all dialog matcher stubs and restore original dialog methods. Note: This requires the app to have stored the original methods, which is not done by default. You may need to restart the app to fully restore dialog functionality.

    Kind: global function
    Returns:

    A promise that resolves when the stubs are cleared.


    Category: Dialog

    Param Description
    app

    The Playwright ElectronApplication instance.

    ipcMainEmit(electronApp, message, ...args, retryOptions) ⇒ 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

    retryOptions RetryOptions

    optional - options for retrying upon error

    ipcMainCallFirstListener(electronApp, message, ...args, retryOptions) ⇒ 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

    retryOptions RetryOptions

    optional - options for retrying upon error

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

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

    Kind: global function
    Category: IPCMain
    Throws:

    • Error

      if no handler is registered for the channel, with an explanation of the usual causes appended

    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

    retryOptions RetryOptions

    optional - options for retrying upon error

    ipcRendererSend(page, channel, ...args, retryOptions) ⇒ 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()

    retryOptions RetryOptions

    optional last argument - options for retrying upon error

    ipcRendererInvoke(page, message, ...args, retryOptions) ⇒ 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()

    retryOptions RetryOptions

    optional last argument - options for retrying upon error

    ipcRendererCallFirstListener(page, message, ...args, retryOptions) ⇒ 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

    retryOptions RetryOptions

    optional - options for retrying upon error

    ipcRendererEmit(page, message, ...args, retryOptions) ⇒ 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

    retryOptions RetryOptions

    optional - options for retrying upon error

    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.

    A click is not idempotent, so this call is not retried by default (disable: true). If the click tears down the execution context - by quitting the app or closing the window, for example - the resulting error is swallowed, since the click did happen. Any other error is thrown. Passing { disable: false } re-enables retries, at the risk of clicking twice.

    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.

    As with clickMenuItemById(), the click itself is never retried. See that function for how errors thrown by the click are handled.

    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. Returns serializable values including primitives, objects, arrays, and other non-recursive data structures.

    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 serializable values including primitives, objects, arrays, and other non-recursive data structures. 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

    retry(fn, [options]) ⇒ Promise.<T>

    Retries a function until it returns without throwing an error.

    Starting with Electron 27, Playwright can get very flakey when running code in Electron's main or renderer processes. It will throw errors like "context or browser has been closed" or "Execution context was destroyed" when the execution context a call was dispatched into goes away underneath it. Playwright has no recovery for this on the Electron main process - it resolves a single require('electron') handle when the app launches and never re-acquires it - so retrying is the only option available from the outside. This function retries a given function until it returns without throwing one of these errors, or until the timeout is reached.

    Note that "Resulting promise was garbage collected." is deliberately not retried. Despite appearances it is not a flake: it means the evaluate callback returned a promise nothing in the target process references, so V8 collected it before it settled. Every attempt fails identically, and the callback body has already run. retry() throws that one immediately, with an explanation attached.

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

    A promise that resolves with the result of the function or rejects with an error or timeout message. With disable: true it can also resolve undefined, when a teardown error is swallowed.


    Category: Utilities

    Param Type Default Description
    fn function

    The function to retry.

    [options] RetryOptions {}

    The options for retrying the function.

    [options.timeout] number 5000

    The maximum time to wait before giving up in milliseconds.

    [options.poll] number 200

    The delay between each retry attempt in milliseconds.

    [options.errorMatch] string | Array.<string> | RegExp "['context or browser has been closed', 'Execution context was destroyed', &quot;reading 'getOwnerBrowserWindow'&quot;]"

    String(s) or regex to match against error message. If the error does not match, it will throw immediately. If it does match, it will retry.

    [options.disable] boolean false

    If true, only call the function once. See RetryOptions.disable.

    Example
    You can simply wrap your Playwright calls in this function to make them more reliable:

    test('my test', async () => {
      // instead of this:
      const oldWayRenderer = await page.evaluate(() => document.body.classList.contains('active'))
      const oldWayMain = await electronApp.evaluate(({}) => document.body.classList.contains('active'))
      // use this:
      const newWay = await retry(() =>
        page.evaluate(() => document.body.classList.contains('active'))
      )
      // note the `() =>` in front of the original function call
      // and the `await` keyword in front of `retry`,
      // but NOT in front of `page.evaluate`
    })

    setRetryOptions(options) ⇒

    Sets the default retry() options. These options will be used for all subsequent calls to retry() unless overridden. You can reset the defaults at any time by calling resetRetryOptions().

    Kind: global function
    Returns:

    The updated retry options.


    Category: Utilities

    Param Description
    options

    A partial object containing the retry options to be set.

    getRetryOptions() ⇒

    Gets the current default retry options.

    Kind: global function
    Returns:

    The current retry options.


    Category: Utilities

    resetRetryOptions()

    Resets the retry options to their default values.

    The default values are:

    • disable: false
    • poll: 200
    • timeout: 5000
    • errorMatch: ['context or browser has been closed', 'Execution context was destroyed', "reading 'getOwnerBrowserWindow'"]

    Kind: global function
    Category: Utilities

    errToString(err) ⇒

    Converts an unknown error to a string representation.

    This function handles different types of errors and attempts to convert them to a string in a meaningful way. It checks if the error is an object with a toString method and uses that method if available. If the error is a string, it returns the string directly. For other types, it converts the error to a JSON string.

    Kind: global function
    Returns:

    A string representation of the error.


    Category: Utilities

    Param Description
    err

    The unknown error to be converted to a string.

    getWindowByUrl(electronApp, pattern, options) ⇒

    Get all windows whose URL matches the given pattern.

    Kind: global function
    Returns:

    An array of matching Pages


    Category: Window Helpers

    Param Description
    electronApp

    The Playwright ElectronApplication

    pattern

    A string (substring match) or RegExp to match against the URL

    options

    Options with all: true to return all matches

    Example

    const allSettingsWindows = await getWindowByUrl(app, '/settings', { all: true })

    getWindowByTitle(electronApp, pattern, options) ⇒

    Get all windows whose title matches the given pattern.

    Kind: global function
    Returns:

    An array of matching Pages


    Category: Window Helpers

    Param Description
    electronApp

    The Playwright ElectronApplication

    pattern

    A string (substring match) or RegExp to match against the title

    options

    Options with all: true to return all matches

    Example

    const allNumberedWindows = await getWindowByTitle(app, /Window \d+/, { all: true })

    getWindowByMatcher(electronApp, matcher, options) ⇒

    Get all windows that match the provided matcher function.

    Kind: global function
    Returns:

    An array of matching Pages


    Category: Window Helpers

    Param Description
    electronApp

    The Playwright ElectronApplication

    matcher

    A function that receives a Page and returns true if it matches

    options

    Options with all: true to return all matches

    Example

    const allLargeWindows = await getWindowByMatcher(app, async (page) => {
      const size = await page.viewportSize()
      return size && size.width > 1000
    }, { all: true })

    waitForWindowByUrl(electronApp, pattern, options) ⇒

    Wait for a window whose URL matches the given pattern.

    This function checks existing windows first, then listens for new windows. It uses polling to handle windows that may have their URL change after opening.

    Kind: global function
    Returns:

    The matching Page


    Category: Window Helpers
    Throws:

    • Error if timeout is reached before a matching window is found

    Param Description
    electronApp

    The Playwright ElectronApplication

    pattern

    A string (substring match) or RegExp to match against the URL

    options

    Optional timeout and interval settings

    Example

    // Click something that opens a new window, then wait for it
    await page.click('#open-settings')
    const settingsWindow = await waitForWindowByUrl(app, '/settings', { timeout: 5000 })

    waitForWindowByTitle(electronApp, pattern, options) ⇒

    Wait for a window whose title matches the given pattern.

    This function checks existing windows first, then listens for new windows. It uses polling to handle windows that may have their title change after opening.

    Kind: global function
    Returns:

    The matching Page


    Category: Window Helpers
    Throws:

    • Error if timeout is reached before a matching window is found

    Param Description
    electronApp

    The Playwright ElectronApplication

    pattern

    A string (substring match) or RegExp to match against the title

    options

    Optional timeout and interval settings

    Example

    // Wait for a window with a specific title to appear
    const prefsWindow = await waitForWindowByTitle(app, 'Preferences', { timeout: 5000 })

    waitForWindowByMatcher(electronApp, matcher, options) ⇒

    Wait for a window that matches the provided matcher function.

    This function:

    1. Checks existing windows first
    2. Listens for new window events
    3. Polls existing windows periodically (to catch URL/title changes)

    Kind: global function
    Returns:

    The matching Page


    Category: Window Helpers
    Throws:

    • Error if timeout is reached before a matching window is found

    Param Description
    electronApp

    The Playwright ElectronApplication

    matcher

    A function that receives a Page and returns true if it matches

    options

    Optional timeout and interval settings

    Example

    const window = await waitForWindowByMatcher(app, async (page) => {
      const title = await page.title()
      return title.startsWith('Document:')
    }, { timeout: 10000 })

    Keywords