electron-playwright-helpers
Version:
Helper functions for Electron end-to-end testing using Playwright
461 lines • 19.3 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.toSerializableMatcher = toSerializableMatcher;
exports.matchesPattern = matchesPattern;
exports.stubDialogMatchers = stubDialogMatchers;
exports.clearDialogMatchers = clearDialogMatchers;
/**
* 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}.
*/
function toSerializableMatcher(pattern) {
if (pattern === undefined)
return undefined;
if (typeof pattern === 'string')
return pattern;
return { source: pattern.source, flags: pattern.flags };
}
/**
* Check if a value matches a StringMatcher.
* Used inside app.evaluate() where the matcher is already serialized.
*/
function matchesPattern(value, pattern) {
if (pattern === undefined)
return true;
if (value === undefined)
return false;
if (typeof pattern === 'string') {
return value === pattern;
}
// It's a serialized regex
const regex = new RegExp(pattern.source, pattern.flags);
return regex.test(value);
}
const dialogMatcherDefaults = {
showMessageBox: { response: 0, checkboxChecked: false },
showMessageBoxSync: 0,
showOpenDialog: { canceled: false, filePaths: [] },
showOpenDialogSync: [],
showSaveDialog: { canceled: false, filePath: undefined },
showSaveDialogSync: undefined,
showErrorBox: undefined,
showCertificateTrustDialog: undefined,
};
// ============================================================================
// Serialization Helpers
// ============================================================================
function serializeMessageBoxMatcher(matcher) {
return {
type: toSerializableMatcher(matcher.type),
message: toSerializableMatcher(matcher.message),
title: toSerializableMatcher(matcher.title),
detail: toSerializableMatcher(matcher.detail),
checkboxLabel: toSerializableMatcher(matcher.checkboxLabel),
buttons: toSerializableMatcher(matcher.buttons),
};
}
function serializeOpenDialogMatcher(matcher) {
return {
title: toSerializableMatcher(matcher.title),
defaultPath: toSerializableMatcher(matcher.defaultPath),
buttonLabel: toSerializableMatcher(matcher.buttonLabel),
message: toSerializableMatcher(matcher.message),
};
}
function serializeSaveDialogMatcher(matcher) {
return {
title: toSerializableMatcher(matcher.title),
defaultPath: toSerializableMatcher(matcher.defaultPath),
buttonLabel: toSerializableMatcher(matcher.buttonLabel),
message: toSerializableMatcher(matcher.message),
nameFieldLabel: toSerializableMatcher(matcher.nameFieldLabel),
};
}
function serializeErrorBoxMatcher(matcher) {
return {
title: toSerializableMatcher(matcher.title),
content: toSerializableMatcher(matcher.content),
};
}
function serializeCertificateTrustDialogMatcher(matcher) {
return {
message: toSerializableMatcher(matcher.message),
};
}
function serializeMatcherStub(stub) {
var _a, _b, _c;
switch (stub.method) {
case 'showMessageBox':
return {
method: 'showMessageBox',
matcher: serializeMessageBoxMatcher(stub.matcher),
value: {
...dialogMatcherDefaults.showMessageBox,
...stub.value,
},
};
case 'showMessageBoxSync':
return {
method: 'showMessageBoxSync',
matcher: serializeMessageBoxMatcher(stub.matcher),
value: (_a = stub.value) !== null && _a !== void 0 ? _a : dialogMatcherDefaults.showMessageBoxSync,
};
case 'showOpenDialog':
return {
method: 'showOpenDialog',
matcher: serializeOpenDialogMatcher(stub.matcher),
value: {
...dialogMatcherDefaults.showOpenDialog,
...stub.value,
},
};
case 'showOpenDialogSync':
return {
method: 'showOpenDialogSync',
matcher: serializeOpenDialogMatcher(stub.matcher),
value: (_b = stub.value) !== null && _b !== void 0 ? _b : dialogMatcherDefaults.showOpenDialogSync,
};
case 'showSaveDialog':
return {
method: 'showSaveDialog',
matcher: serializeSaveDialogMatcher(stub.matcher),
value: {
...dialogMatcherDefaults.showSaveDialog,
...stub.value,
},
};
case 'showSaveDialogSync':
return {
method: 'showSaveDialogSync',
matcher: serializeSaveDialogMatcher(stub.matcher),
value: (_c = stub.value) !== null && _c !== void 0 ? _c : dialogMatcherDefaults.showSaveDialogSync,
};
case 'showErrorBox':
return {
method: 'showErrorBox',
matcher: serializeErrorBoxMatcher(stub.matcher),
value: undefined,
};
case 'showCertificateTrustDialog':
return {
method: 'showCertificateTrustDialog',
matcher: serializeCertificateTrustDialogMatcher(stub.matcher),
value: undefined,
};
}
}
/**
* 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.
*
* @example
* ```ts
* // 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 },
* },
* ])
* ```
*
* @category Dialog
*
* @param app - The Playwright ElectronApplication instance.
* @param stubs - Array of dialog matcher stubs to apply.
* @param options - Optional configuration.
* @returns A promise that resolves when the stubs are applied.
*/
function stubDialogMatchers(app, stubs, options = {}) {
const { throwOnUnmatched = false } = options;
// Serialize all stubs for transfer across the evaluate boundary
const serializedStubs = stubs.map(serializeMatcherStub);
// Group stubs by method for efficient lookup
const stubsByMethod = new Map();
for (const stub of serializedStubs) {
const existing = stubsByMethod.get(stub.method) || [];
existing.push(stub);
stubsByMethod.set(stub.method, existing);
}
const stubsGrouped = Object.fromEntries(stubsByMethod);
const defaults = dialogMatcherDefaults;
return app.evaluate(({ dialog }, { stubsGrouped, throwOnUnmatched, defaults }) => {
// Helper to check if a value matches a pattern (runs inside Electron)
const matchesPattern = (value, pattern) => {
if (pattern === undefined)
return true;
if (value === undefined)
return false;
if (typeof pattern === 'string')
return value === pattern;
const regex = new RegExp(pattern.source, pattern.flags);
return regex.test(value);
};
// Check if MessageBoxOptions match a serialized matcher
const matchesMessageBox = (options, matcher) => {
if (!options)
return true;
if (!matchesPattern(options.type, matcher.type))
return false;
if (!matchesPattern(options.message, matcher.message))
return false;
if (!matchesPattern(options.title, matcher.title))
return false;
if (!matchesPattern(options.detail, matcher.detail))
return false;
if (!matchesPattern(options.checkboxLabel, matcher.checkboxLabel))
return false;
if (matcher.buttons !== undefined && options.buttons) {
// Check if any button matches
const buttonMatches = options.buttons.some((btn) => matchesPattern(btn, matcher.buttons));
if (!buttonMatches)
return false;
}
return true;
};
// Check if OpenDialogOptions match a serialized matcher
const matchesOpenDialog = (options, matcher) => {
if (!options)
return true;
if (!matchesPattern(options.title, matcher.title))
return false;
if (!matchesPattern(options.defaultPath, matcher.defaultPath))
return false;
if (!matchesPattern(options.buttonLabel, matcher.buttonLabel))
return false;
if (!matchesPattern(options.message, matcher.message))
return false;
return true;
};
// Check if SaveDialogOptions match a serialized matcher
const matchesSaveDialog = (options, matcher) => {
if (!options)
return true;
if (!matchesPattern(options.title, matcher.title))
return false;
if (!matchesPattern(options.defaultPath, matcher.defaultPath))
return false;
if (!matchesPattern(options.buttonLabel, matcher.buttonLabel))
return false;
if (!matchesPattern(options.message, matcher.message))
return false;
if (!matchesPattern(options.nameFieldLabel, matcher.nameFieldLabel))
return false;
return true;
};
// showMessageBox
if (stubsGrouped['showMessageBox']) {
const stubs = stubsGrouped['showMessageBox'];
dialog.showMessageBox = async (windowOrOptions, maybeOptions) => {
// Handle optional BrowserWindow first argument
const options = maybeOptions ||
(windowOrOptions &&
!('webContents' in windowOrOptions) &&
!('id' in windowOrOptions)
? windowOrOptions
: undefined);
for (const stub of stubs) {
if (matchesMessageBox(options, stub.matcher)) {
return stub.value;
}
}
if (throwOnUnmatched) {
throw new Error(`No matching stub for showMessageBox with options: ${JSON.stringify(options)}`);
}
return defaults.showMessageBox;
};
}
// showMessageBoxSync
if (stubsGrouped['showMessageBoxSync']) {
const stubs = stubsGrouped['showMessageBoxSync'];
dialog.showMessageBoxSync = (windowOrOptions, maybeOptions) => {
const options = maybeOptions ||
(windowOrOptions &&
!('webContents' in windowOrOptions) &&
!('id' in windowOrOptions)
? windowOrOptions
: undefined);
for (const stub of stubs) {
if (matchesMessageBox(options, stub.matcher)) {
return stub.value;
}
}
if (throwOnUnmatched) {
throw new Error(`No matching stub for showMessageBoxSync with options: ${JSON.stringify(options)}`);
}
return defaults.showMessageBoxSync;
};
}
// showOpenDialog
if (stubsGrouped['showOpenDialog']) {
const stubs = stubsGrouped['showOpenDialog'];
dialog.showOpenDialog = async (windowOrOptions, maybeOptions) => {
const options = maybeOptions ||
(windowOrOptions &&
!('webContents' in windowOrOptions) &&
!('id' in windowOrOptions)
? windowOrOptions
: undefined);
for (const stub of stubs) {
if (matchesOpenDialog(options, stub.matcher)) {
return stub.value;
}
}
if (throwOnUnmatched) {
throw new Error(`No matching stub for showOpenDialog with options: ${JSON.stringify(options)}`);
}
return defaults.showOpenDialog;
};
}
// showOpenDialogSync
if (stubsGrouped['showOpenDialogSync']) {
const stubs = stubsGrouped['showOpenDialogSync'];
dialog.showOpenDialogSync = (windowOrOptions, maybeOptions) => {
const options = maybeOptions ||
(windowOrOptions &&
!('webContents' in windowOrOptions) &&
!('id' in windowOrOptions)
? windowOrOptions
: undefined);
for (const stub of stubs) {
if (matchesOpenDialog(options, stub.matcher)) {
return stub.value;
}
}
if (throwOnUnmatched) {
throw new Error(`No matching stub for showOpenDialogSync with options: ${JSON.stringify(options)}`);
}
return defaults.showOpenDialogSync;
};
}
// showSaveDialog
if (stubsGrouped['showSaveDialog']) {
const stubs = stubsGrouped['showSaveDialog'];
dialog.showSaveDialog = async (windowOrOptions, maybeOptions) => {
const options = maybeOptions ||
(windowOrOptions &&
!('webContents' in windowOrOptions) &&
!('id' in windowOrOptions)
? windowOrOptions
: undefined);
for (const stub of stubs) {
if (matchesSaveDialog(options, stub.matcher)) {
return stub.value;
}
}
if (throwOnUnmatched) {
throw new Error(`No matching stub for showSaveDialog with options: ${JSON.stringify(options)}`);
}
return defaults.showSaveDialog;
};
}
// showSaveDialogSync
if (stubsGrouped['showSaveDialogSync']) {
const stubs = stubsGrouped['showSaveDialogSync'];
dialog.showSaveDialogSync = (windowOrOptions, maybeOptions) => {
const options = maybeOptions ||
(windowOrOptions &&
!('webContents' in windowOrOptions) &&
!('id' in windowOrOptions)
? windowOrOptions
: undefined);
for (const stub of stubs) {
if (matchesSaveDialog(options, stub.matcher)) {
return stub.value;
}
}
if (throwOnUnmatched) {
throw new Error(`No matching stub for showSaveDialogSync with options: ${JSON.stringify(options)}`);
}
return defaults.showSaveDialogSync;
};
}
// showErrorBox
if (stubsGrouped['showErrorBox']) {
const stubs = stubsGrouped['showErrorBox'];
dialog.showErrorBox = (title, content) => {
for (const stub of stubs) {
if (matchesPattern(title, stub.matcher.title) &&
matchesPattern(content, stub.matcher.content)) {
return;
}
}
if (throwOnUnmatched) {
throw new Error(`No matching stub for showErrorBox with title: ${title}, content: ${content}`);
}
};
}
// showCertificateTrustDialog
if (stubsGrouped['showCertificateTrustDialog']) {
const stubs = stubsGrouped['showCertificateTrustDialog'];
dialog.showCertificateTrustDialog = async (windowOrOptions, maybeOptions) => {
const options = maybeOptions ||
(windowOrOptions &&
!('webContents' in windowOrOptions) &&
!('id' in windowOrOptions)
? windowOrOptions
: undefined);
for (const stub of stubs) {
if (matchesPattern(options === null || options === void 0 ? void 0 : options.message, stub.matcher.message)) {
return;
}
}
if (throwOnUnmatched) {
throw new Error(`No matching stub for showCertificateTrustDialog with options: ${JSON.stringify(options)}`);
}
};
}
}, { stubsGrouped, throwOnUnmatched, defaults });
}
/**
* 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.
*
* @category Dialog
*
* @param app - The Playwright ElectronApplication instance.
* @returns A promise that resolves when the stubs are cleared.
*/
function clearDialogMatchers(app) {
// Since we can't easily restore the original methods without storing them first,
// the best we can do is stub them with pass-through that throws
// Users should restart the app for full restoration
return app.evaluate(({ dialog }) => {
const notRestored = (method) => () => {
throw new Error(`dialog.${method} was stubbed and cannot be restored. Restart the app to restore dialog functionality.`);
};
dialog.showMessageBox = notRestored('showMessageBox');
dialog.showMessageBoxSync = notRestored('showMessageBoxSync');
dialog.showOpenDialog = notRestored('showOpenDialog');
dialog.showOpenDialogSync = notRestored('showOpenDialogSync');
dialog.showSaveDialog = notRestored('showSaveDialog');
dialog.showSaveDialogSync = notRestored('showSaveDialogSync');
dialog.showErrorBox = notRestored('showErrorBox');
dialog.showCertificateTrustDialog = notRestored('showCertificateTrustDialog');
});
}
//# sourceMappingURL=dialog_matchers.js.map