UNPKG

electron-playwright-helpers

Version:

Helper functions for Electron end-to-end testing using Playwright

461 lines 19.3 kB
"use strict"; 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