Skip to content

Commit e122474

Browse files
authored
feat: address mocking failures (#235)
* feat(native-spy): add getMockImplementation method to retrieve current mock implementation - Implemented `getMockImplementation` method in the mock function to allow users to access the current mock implementation. - Updated the `Mock` interface in `types.ts` to include the new method. - Added tests to verify the functionality of `getMockImplementation`, ensuring it returns the correct implementation or undefined when none is set. * feat(electron): bind getMockImplementation method to wrapperMock for improved mock access * feat(tauri): bind getMockImplementation method to wrapperMock for enhanced mock functionality * feat(native-spy): add test for initial implementation retrieval in mock function - Introduced a new test case to verify that the mock function returns the initial implementation passed to it. - This enhances the testing coverage for the `getMockImplementation` method, ensuring it behaves as expected. * chore: standardize package.json files array formatting across multiple packages * test(electron): add mocking.spec.ts to the test suite for enhanced coverage * test(e2e): update test configurations to exclude specific tests for electron and tauri environments - Added exclusion for window and deeplink tests in electron configuration. - Updated tauri configurations to maintain exclusion of window and deeplink tests, removing mocking tests from the exclusion list. - Ensured consistency across test specifications for improved test execution. * feat(native-spy): enhance mock function with original implementation support - Added `FnOptions` interface to allow passing an original function to the mock. - Updated the `fn` function to call the original function if provided, improving mock behavior. - Adjusted mock restoration to revert to the original function when available. * feat(electron): enhance restore functionality for mocked Electron APIs - Improved the `restoreElectronFunctionality` method to handle mock restoration more robustly, including fallback to original functions stored in `globalThis.originalApi`. - Updated the `createMock` function to capture and store original functions for better restoration capabilities. - Enhanced logging to provide detailed results of the restoration process, aiding in debugging and verification. * fix(native-spy): improve mock function behavior and state management - Enhanced the `fn` function to better handle mock restoration and state tracking. - Introduced a `mockRestored` flag to manage the restoration state of mocks. - Updated the implementation logic to ensure correct handling of resolved and rejected values. - Cleared implementation queue during mock resets to prevent unintended behavior. * feat(electron-service): enhance mock function logging and implementation handling - Added debug logging to track calls to inner mock functions, improving traceability during testing. - Refined the implementation logic in `createMock` to ensure proper handling of function implementations, enhancing mock behavior. * feat(native-spy): enhance mock function state management and last call tracking - Updated the `fn` function to include a non-enumerable `lastCall` property on the state, allowing for better tracking of the most recent call arguments. - Added a `lastCall` property to the mock function, making it enumerable for easier access. - Refined comments for clarity and alignment with vitest's approach to state management. * feat(electron-service): add lastCall property to mock for improved call tracking - Introduced a computed `lastCall` property on the mock function, providing convenient access to the most recent call made. - Enhanced the mock's state management capabilities, aligning with previous improvements in tracking and logging. * test(electron): add restoreMocks capability to Electron configuration and update mocking tests - Introduced a `restoreMocks` property in the Electron capabilities configuration to enable automatic restoration of mocks. - Updated the mocking tests to remove clipboard state management, ensuring that mocks are restored to their original state after execution. * refactor(electron-service): streamline mock restoration logic and improve call tracking - Simplified the `restoreElectronFunctionality` method to enhance clarity and efficiency in restoring mocks. - Updated the fallback restoration logic to ensure consistent behavior and improved return values. - Refined the `createMock` function to enhance call tracking and logging, ensuring better traceability during testing. * refactor(native-spy): remove redundant implementation reset in mock functions - Eliminated unnecessary resetting of the implementation function in `mockReturnValueOnce` and `mockResolvedValueOnce`, streamlining the mock behavior. - Improved clarity and efficiency in the mock function's state management. * feat(e2e-apps): add 'Show Dialog' button and functionality to invoke dialog - Introduced a new button in the Electron app's UI to trigger a dialog. - Implemented event listener to invoke the 'show-open-dialog' API when the button is clicked, enhancing user interaction. * feat(electron): enhance mock functionality and error handling - Introduced a new `createPrototypeMock` function to facilitate mocking of class methods, improving flexibility in mock creation. - Enhanced error handling in the `execute` function to throw descriptive errors when script execution fails, improving debugging capabilities. - Updated the `mock` method signature to support both class and function mocking, streamlining the API for users. - Improved the `mockRejectedValue` method to handle Error instances more effectively, ensuring accurate error propagation. * test(e2e): improve type handling and mock behavior in tests - Updated type handling in mock implementations to ensure proper type assertions, enhancing type safety. - Adjusted test expectations to reflect accurate call counts for mocked methods, improving test reliability. - Refined mock restoration logic to verify original class names instead of instances, ensuring consistency in mock behavior. * refactor(electron): extract mock creation into factory - Added `createClassMock` function to facilitate mocking of Electron classes, allowing for dynamic method creation based on class prototypes. - Implemented `createPrototypeMock` to streamline the mocking of individual class methods, improving flexibility in mock creation. - Introduced `mockFactory` to centralize mock method handling, enhancing maintainability and clarity. - Updated tests to validate new mocking capabilities and ensure accurate behavior of class and prototype mocks. * docs(electron): improve ElectronClassMock jsdoc - Enhanced the type definition for ElectronClassMock to better separate lifecycle members from dynamic method mocks. - Clarified the return types for core mock methods and improved type safety for dynamic method access. - Updated comments to provide a clearer understanding of the design rationale behind the type structure. * refactor(e2e): improve type handling for Electron Tray mock - Updated the Tray constructor calls in tests to correctly handle additional arguments, enhancing type safety with TypeScript. - Adjusted method chaining for setTitle and setToolTip to reflect accurate return types, ensuring proper mock behavior in tests. * feat(fixtures): add 'Show Dialog' button and event listener to forge e2e fixture - Added a new button to the Electron app's UI for triggering a dialog. - Implemented an event listener to invoke the 'show-open-dialog' API when the button is clicked, enhancing user interaction. * test(native-spy): add tests for mockReturnValue, mockResolvedValue, and mockRejectedValue after mockRestore - Added new test cases to verify that mockReturnValue, mockResolvedValue, and mockRejectedValue can be called after mockRestore, ensuring correct behavior of mock functions. - Enhanced test coverage for the native-spy package by validating the restoration and reconfiguration of mock functions. * fix(native-spy): correct mock return value handling - Updated the mock function to ensure that the `defaultReturnValue` is correctly returned when defined, improving the accuracy of mock behavior. - Removed redundant code that previously handled `defaultReturnValue`, streamlining the logic for returning values in mock functions. * refactor(electron-service): remove restoreElectronFunctionality call from mockRestore - Eliminated the call to restoreElectronFunctionality in the mockRestore method, simplifying the mock restoration process. - Updated import statements to reflect the removal of unused functionality, enhancing code clarity. * feat(tauri): enhance mock functionality and synchronization - Improved the Tauri mocking implementation by simplifying the mock call tracking and synchronization process. - Updated the `isMockFunction` method to accept both command names and mock function objects, enhancing usability. - Added automatic synchronization of mock calls after executing commands, ensuring accurate call tracking. - Enhanced error handling for undefined responses in the execute function, allowing for better handling of edge cases. - Introduced tests to validate the new synchronization behavior and the updated `isMockFunction` logic, improving overall test coverage. * refactor(electron-service): improve mockRejectedValue handling in mockFactory - Simplified the logic in `mockRejectedValue` and `mockRejectedValueOnce` methods to handle Error instances more effectively. - Enhanced type handling for both API and class method mocks, ensuring consistent behavior across different mock types. - Improved error message propagation when rejecting promises, providing clearer context for error handling in tests. * refactor(tauri-plugin): simplify invoke interception logic - Removed complex proxy-based interception strategies for the invoke method, streamlining the setup process. - Updated logging to reflect the fallback to mock routing via window.__wdio_mocks__ when defineProperty fails, improving clarity on interception status. - Enhanced code readability by eliminating unnecessary fallback strategies while maintaining mock functionality. * fix(tauri): correct return type for mockReturnThis method - Updated the return type of the `mockReturnThis` method in the TauriMockInstance interface to return `Promise<TauriMock>` instead of `Promise<unknown>`, improving type safety and clarity. - Adjusted the implementation in the `createMock` function to ensure consistent return type handling, enhancing overall mock functionality. * fix(tauri): restore synchronous mockClear for consistent state management - Temporarily reverted the mockClear function to synchronous behavior to prevent race conditions during state clearing in the createMock function. - Ensured that outerMockClear is called immediately before outerMockReset, maintaining the integrity of mock state management. * fix(tauri): update resetAllMocks and restoreAllMocks to handle undefined context - Modified the `resetAllMocks` and `restoreAllMocks` functions to accept an optional `TauriServiceContext`, ensuring compatibility with undefined context scenarios. - Adjusted the execution calls in both functions to replace `undefined` with an empty string when no command prefix is provided, improving consistency in mock handling. - Updated related tests to reflect these changes, ensuring accurate expectations for the execution calls. * chore(actions): update cache restore action to v5 and clean up whitespace - Upgraded the cache restore action from v4 to v5 for improved performance and features. - Removed unnecessary whitespace in the script for better readability and consistency. * fix(e2e): increase mocha timeout for improved test stability - Updated the mocha timeout setting from 60 seconds to 120 seconds to accommodate longer-running tests, enhancing overall test reliability. * refactor(e2e): adjust clipboard mock handling in restoreAllMocks test - Removed the clipboard write operation from the beforeEach hook to ensure mocks are restored before any clipboard interactions. - Moved the clipboard write operation to after the restoreAllMocks call, clarifying the test flow and ensuring accurate mock state during execution. * fix(e2e): enforce sequential test execution in embedded mode - Set maxInstances to 1 for embedded mode tests to ensure that the shared Tauri app instance is properly managed. - Added comments to clarify the need for sequential execution due to global shared state across workers, preventing potential conflicts during test runs. * chore(actions): update vscode-server-action to v1.4.0 for improved debugging - Upgraded the vscode-server-action from v1.3.0 to v1.4.0 across multiple workflow files to enhance debugging capabilities during test failures. - Ensured consistent timeout settings for the debugging sessions in the respective workflows. * fix(e2e): simplify title verification in main window test - Replaced the manual title retrieval and expectation with a more concise matcher using `toHaveTitle`, improving readability and maintainability of the test code. - Ensured that the test still accurately verifies the application window title for the Electron E2E test app. * chore(actions): upgrade cache save action to v5 for enhanced performance - Updated the cache save action from v4 to v5 to leverage improved features and performance. - Ensured compatibility with the latest caching mechanisms in the workflow configuration. * fix(tauri): enhance error handling in mock rejection methods - Updated `mockRejectedValue` and `mockRejectedValueOnce` to differentiate between `Error` instances and other values, ensuring that error messages are properly passed when an `Error` is rejected. - Improved type handling in the mock functions to enhance clarity and maintainability of the code. * fix(e2e): enhance error message preservation in Tauri mocking tests - Added new test cases to verify that `mockRejectedValue` and `mockRejectedValueOnce` correctly preserve error messages when rejecting with `Error` objects. - Ensured that the error messages are accurately returned in the execution context, improving the robustness of error handling in the mocking framework. * chore(actions): upgrade vscode-server-action to v1.5.0 for enhanced debugging - Updated the vscode-server-action from v1.4.0 to v1.5.0 across multiple workflow files to improve debugging capabilities during test failures. - Ensured consistent timeout settings for the debugging sessions in the respective workflows. * fix(electron): ensure synchronous mockClear behavior in class and mock methods - Temporarily restored synchronous behavior for mockClear in both createClassMock and buildMockMethods to prevent race conditions with async operations. - Updated comments for clarity on the purpose of these changes, enhancing maintainability and understanding of the code flow. * fix(tauri): clear stale mocks at session start for embedded driver provider - Implemented logic to reset `window.__wdio_mocks__` at the beginning of each session when using the embedded driver provider, ensuring a clean state for tests. - Added tests to verify the clearing of stale mocks for embedded providers and confirm that non-embedded providers do not clear mocks. - Enhanced error handling to gracefully manage failures during the clearing process.
1 parent 9dfa3a0 commit e122474

45 files changed

Lines changed: 2017 additions & 900 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/_ci-e2e-tauri-all-providers.reusable.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,6 @@ jobs:
539539
540540
- name: 🐛 Debug Build on Failure
541541
if: failure()
542-
uses: goosewobbler/vscode-server-action@v1.3.0
542+
uses: goosewobbler/vscode-server-action@v1.5.0
543543
with:
544544
timeout: '300000'

.github/workflows/_ci-e2e.reusable.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ jobs:
253253
# Provide an interactive debugging session on failure
254254
# This allows manual investigation of the environment
255255
- name: 🐛 Debug Build on Failure
256-
uses: goosewobbler/vscode-server-action@v1.3.0
256+
uses: goosewobbler/vscode-server-action@v1.5.0
257257
if: failure()
258258
with:
259259
timeout: '300000'

.github/workflows/_ci-package.reusable.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ jobs:
241241
# Provide an interactive debugging session on failure
242242
# This allows manual investigation of the environment
243243
- name: 🐛 Debug Build on Failure
244-
uses: goosewobbler/vscode-server-action@v1.3.0
244+
uses: goosewobbler/vscode-server-action@v1.5.0
245245
if: failure()
246246
with:
247247
timeout: '180000'

.github/workflows/actions/download-archive/action.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ runs:
6060
- name: 🗄️ Restore Cache
6161
id: cache-restore
6262
if: inputs.use_cache != 'false-only'
63-
uses: actions/cache/restore@v4
63+
uses: actions/cache/restore@v5
6464
env:
6565
ACTIONS_CACHE_SERVICE_V2: 'true'
6666
with:
@@ -194,11 +194,11 @@ runs:
194194
WORKSPACE="${{ github.workspace }}"
195195
INPUT_PATH="${{ inputs.path }}"
196196
ARCHIVE_PATH="$INPUT_PATH/${{ inputs.filename }}"
197-
197+
198198
if [ "${{ steps.cache-restore.outputs.cache-hit }}" == "true" ]; then
199199
# Cache hit: zip file was restored to inputs.path, need to extract it
200200
echo "Cache hit detected, extracting zip file..."
201-
201+
202202
if [ -f "$ARCHIVE_PATH" ]; then
203203
echo "Found zip file at $ARCHIVE_PATH, extracting..."
204204
SOURCE_DIR="${RUNNER_TEMP}/artifact-extract-${{ github.run_id }}-${{ github.run_attempt }}"

.github/workflows/actions/upload-archive/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ runs:
236236
# Cross-platform archive enabled - works for TypeScript packages (wdio-desktop-build)
237237
# Tauri apps use 7zip for cross-platform compatible ZIPs
238238
- name: 🗄️ Cache Artifact (Platform-specific)
239-
uses: actions/cache/save@v4
239+
uses: actions/cache/save@v5
240240
env:
241241
ACTIONS_CACHE_SERVICE_V2: 'true'
242242
with:

e2e/test/electron/mocking.spec.ts

Lines changed: 26 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -275,12 +275,6 @@ describe('Electron Mocking', () => {
275275
});
276276

277277
it('should restore existing mocks', async () => {
278-
// Ensure clipboard has expected initial state
279-
await browser.electron.execute((electron) => {
280-
electron.clipboard.clear();
281-
electron.clipboard.writeText('some real clipboard text');
282-
});
283-
284278
const mockGetName = await browser.electron.mock('app', 'getName');
285279
const mockReadText = await browser.electron.mock('clipboard', 'readText');
286280
await mockGetName.mockReturnValue('mocked appName');
@@ -291,8 +285,6 @@ describe('Electron Mocking', () => {
291285
const appName = await browser.electron.execute((electron) => electron.app.getName());
292286
const clipboardText = await browser.electron.execute((electron) => electron.clipboard.readText());
293287
expect(appName).toBe(getExpectedAppName());
294-
295-
// Clipboard should be restored to original state
296288
expect(clipboardText).toBe('some real clipboard text');
297289
});
298290

@@ -896,8 +888,8 @@ describe('Electron Mocking', () => {
896888

897889
// Mock setName to store values and getName to return them
898890
let storedName = '';
899-
await mockSetName.mockImplementation((name: string) => {
900-
storedName = name;
891+
await mockSetName.mockImplementation((name: unknown) => {
892+
storedName = name as string;
901893
});
902894
await mockGetName.mockImplementation(() => storedName);
903895

@@ -952,14 +944,12 @@ describe('Electron Mocking', () => {
952944
const mockNonExistentFunc = await browser.electron.mock('app', 'nonExistentMethod');
953945
expect(mockNonExistentFunc).toBeDefined();
954946

955-
// The mock exists but won't intercept actual calls
947+
// The mock is installed on the API, so calls are intercepted
956948
await browser.electron.execute((electron) => {
957-
// This should call the real app method, not our mock
958-
const result = (electron.app as any).nonExistentMethod?.();
959-
expect(result).toBeUndefined();
949+
(electron.app as any).nonExistentMethod?.();
960950
});
961951

962-
expect(mockNonExistentFunc).toHaveBeenCalledTimes(0);
952+
expect(mockNonExistentFunc).toHaveBeenCalledTimes(1);
963953
});
964954

965955
it('should handle mocking non-existent class', async () => {
@@ -969,11 +959,10 @@ describe('Electron Mocking', () => {
969959
expect(mockNonExistentClass.__constructor).toBeDefined();
970960

971961
// Constructor should exist but not intercept real instantiation
972-
await browser.electron.execute((electron) => {
973-
// This should not be intercepted by our mock
974-
const instance = (electron as any).NonExistentClass ? new (electron as any).NonExistentClass() : null;
975-
expect(instance).toBeNull();
962+
const instance = await browser.electron.execute((electron) => {
963+
return (electron as any).NonExistentClass ? new (electron as any).NonExistentClass() : null;
976964
});
965+
expect(instance).toBeNull();
977966

978967
expect(mockNonExistentClass.__constructor).toHaveBeenCalledTimes(0);
979968
});
@@ -1043,6 +1032,7 @@ describe('Electron Mocking', () => {
10431032

10441033
await browser.electron.execute((electron) => {
10451034
new electron.Tray('/path/to/icon1.png');
1035+
// @ts-expect-error — Tray constructor accepts extra args at runtime but types are narrow
10461036
new electron.Tray('/path/to/icon2.png', { title: 'Test' });
10471037
});
10481038

@@ -1059,7 +1049,8 @@ describe('Electron Mocking', () => {
10591049

10601050
await browser.electron.execute((electron) => {
10611051
const tray = new electron.Tray('/path/to/icon.png');
1062-
tray.setTitle('App').setToolTip('Menu'); // Should work if chaining works
1052+
// @ts-expect-error — mockReturnThis makes setTitle return `this`; types don't reflect it
1053+
tray.setTitle('App').setToolTip('Menu');
10631054
});
10641055

10651056
expect(mockTray.setToolTip).toHaveBeenCalledWith('Menu');
@@ -1069,8 +1060,8 @@ describe('Electron Mocking', () => {
10691060
describe('Class Mock Methods', () => {
10701061
describe('mockRestore', () => {
10711062
it('should restore the original class implementation', async () => {
1072-
// Get original Tray constructor for comparison
1073-
const originalTray = await browser.electron.execute((electron) => electron.Tray);
1063+
// Get original Tray constructor name for comparison (functions serialize to {} via CDP)
1064+
const originalTrayName = await browser.electron.execute((electron) => electron.Tray.name);
10741065

10751066
const mockTray = await browser.electron.mock('Tray');
10761067

@@ -1087,12 +1078,13 @@ describe('Electron Mocking', () => {
10871078
// Restore original class
10881079
await mockTray.mockRestore();
10891080

1090-
// Verify original class is restored
1091-
const restoredTray = await browser.electron.execute((electron) => electron.Tray);
1092-
expect(restoredTray).toBe(originalTray);
1081+
// Verify original class is restored (compare by name since functions can't be serialized via CDP)
1082+
const restoredTrayName = await browser.electron.execute((electron) => electron.Tray.name);
1083+
expect(restoredTrayName).toBe(originalTrayName);
10931084

1094-
// Constructor tracking should be gone
1095-
await browser.electron.execute((electron) => new electron.Tray('/path/to/icon.png'));
1085+
// Constructor tracking should be gone after restore
1086+
// Note: real Tray constructor throws with invalid path, so we ignore the error
1087+
await browser.electron.execute((electron) => new electron.Tray('/path/to/icon.png')).catch(() => {});
10961088
expect(mockTray.__constructor.mock.calls).toStrictEqual([]);
10971089
});
10981090
});
@@ -1149,7 +1141,7 @@ describe('Electron Mocking', () => {
11491141
});
11501142

11511143
it('should work with restoreAllMocks', async () => {
1152-
const originalTray = await browser.electron.execute((electron) => electron.Tray);
1144+
const originalTrayName = await browser.electron.execute((electron) => electron.Tray.name);
11531145

11541146
const mockTray = await browser.electron.mock('Tray');
11551147
await mockTray.setTitle.mockReturnValue('mocked');
@@ -1164,9 +1156,9 @@ describe('Electron Mocking', () => {
11641156
// Restore all mocks
11651157
await browser.electron.restoreAllMocks();
11661158

1167-
// Verify original class is restored
1168-
const restoredTray = await browser.electron.execute((electron) => electron.Tray);
1169-
expect(restoredTray).toBe(originalTray);
1159+
// Verify original class is restored (compare by name since functions can't be serialized via CDP)
1160+
const restoredTrayName = await browser.electron.execute((electron) => electron.Tray.name);
1161+
expect(restoredTrayName).toBe(originalTrayName);
11701162
});
11711163
});
11721164

@@ -1177,6 +1169,7 @@ describe('Electron Mocking', () => {
11771169

11781170
await browser.electron.execute((electron) => {
11791171
new electron.Tray('/path/to/icon1.png');
1172+
// @ts-expect-error — Tray constructor accepts extra args at runtime but types are narrow
11801173
new electron.Tray('/path/to/icon2.png', { title: 'Test' });
11811174
});
11821175

@@ -1201,7 +1194,7 @@ describe('Electron Mocking', () => {
12011194
const mockDialog = await browser.electron.mock('dialog', 'showOpenDialog');
12021195

12031196
await browser.electron.execute((electron) => new electron.Tray('/path/to/icon.png'));
1204-
await browser.electron.execute((electron) => electron.dialog.showOpenDialog());
1197+
await browser.electron.execute((electron) => electron.dialog.showOpenDialog({}));
12051198
await browser.electron.execute((electron) => new electron.Tray('/path/to/other.png'));
12061199

12071200
const constructorOrder = mockTray.__constructor.mock.invocationCallOrder;
@@ -1285,7 +1278,7 @@ describe('Electron Mocking', () => {
12851278
it('should support mockImplementation for instance methods', async () => {
12861279
const mockTray = await browser.electron.mock('Tray');
12871280

1288-
await mockTray.setTitle.mockImplementation((title) => `Mocked: ${title}`);
1281+
await mockTray.setTitle.mockImplementation((title: unknown) => `Mocked: ${String(title)}`);
12891282

12901283
const result = await browser.electron.execute((electron) => {
12911284
const tray = new electron.Tray('/path/to/icon.png');

e2e/test/electron/standalone/api.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const isScript = appDirName.includes('script');
2424
const getExpectedAppName = (): string => {
2525
// In script mode, the app name will always be "Electron"
2626
// In binary mode, use the package name
27-
return isScript ? 'Electron' : globalThis.packageJson?.name || packageJson.name;
27+
return isScript ? 'Electron' : (globalThis.packageJson?.name ?? packageJson.name ?? '');
2828
};
2929

3030
// Get app name and check against expected value

e2e/test/electron/window.spec.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,7 @@ describe('application window tests', () => {
5252
} else {
5353
const elem = await browser.$('.switch-main-window');
5454
await elem.click();
55-
// Verify the app switched to main window with an Electron E2E test app title
56-
const title = await browser.getTitle();
57-
expect(title).toMatch(/Electron.*E2E Test App/);
55+
await expect(browser).toHaveTitle(/Electron.*E2E Test App/);
5856
}
5957
});
6058
});

e2e/test/tauri/mocking.spec.ts

Lines changed: 66 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -123,12 +123,6 @@ describe('Tauri Mocking', () => {
123123
});
124124

125125
describe('browser.tauri.restoreAllMocks', () => {
126-
beforeEach(async () => {
127-
await browser.tauri.execute(async ({ core }) => {
128-
await core.invoke('write_clipboard', { content: 'real clipboard text' });
129-
});
130-
});
131-
132126
it('should restore existing mocks', async () => {
133127
const mockReadClipboard = await browser.tauri.mock('read_clipboard');
134128
const mockGetPlatformInfo = await browser.tauri.mock('get_platform_info');
@@ -138,6 +132,11 @@ describe('Tauri Mocking', () => {
138132

139133
await browser.tauri.restoreAllMocks();
140134

135+
// Write to clipboard AFTER restoring — any write_clipboard mock from prior tests is now gone
136+
await browser.tauri.execute(async ({ core }) => {
137+
await core.invoke('write_clipboard', { content: 'real clipboard text' });
138+
});
139+
141140
const clipboardContent = await browser.tauri.execute(async ({ core }) => await core.invoke('read_clipboard'));
142141
const platformInfo = await browser.tauri.execute(async ({ core }) => await core.invoke('get_platform_info'));
143142

@@ -165,18 +164,12 @@ describe('Tauri Mocking', () => {
165164
describe('mockImplementation', () => {
166165
it('should use the specified implementation for an existing mock', async () => {
167166
const mockReadClipboard = await browser.tauri.mock('read_clipboard');
168-
let callsCount = 0;
169167

170-
await mockReadClipboard.mockImplementation(() => {
171-
if (typeof callsCount !== 'undefined') {
172-
callsCount++;
173-
}
174-
return 'mocked clipboard value';
175-
});
168+
await mockReadClipboard.mockImplementation(() => 'mocked clipboard value');
176169

177170
const result = await browser.tauri.execute(async ({ core }) => await core.invoke('read_clipboard'));
178171

179-
expect(callsCount).toBe(1);
172+
expect(mockReadClipboard.mock.calls).toHaveLength(1);
180173
expect(result).toBe('mocked clipboard value');
181174
});
182175
});
@@ -296,6 +289,21 @@ describe('Tauri Mocking', () => {
296289

297290
expect(error).toBe('This is a mock error');
298291
});
292+
293+
it('should reject with an Error object preserving message', async () => {
294+
const mockGetPlatformInfo = await browser.tauri.mock('get_platform_info');
295+
await mockGetPlatformInfo.mockRejectedValue(new Error('connection failed'));
296+
297+
const error = await browser.tauri.execute(async ({ core }) => {
298+
try {
299+
return await core.invoke('get_platform_info');
300+
} catch (e) {
301+
return e instanceof Error ? e.message : String(e);
302+
}
303+
});
304+
305+
expect(error).toBe('connection failed');
306+
});
299307
});
300308

301309
describe('mockRejectedValueOnce', () => {
@@ -331,6 +339,27 @@ describe('Tauri Mocking', () => {
331339
info = await getInfo();
332340
expect(info).toBe('default error');
333341
});
342+
343+
it('should reject with Error objects preserving message', async () => {
344+
const mockGetPlatformInfo = await browser.tauri.mock('get_platform_info');
345+
346+
await mockGetPlatformInfo.mockRejectedValue(new Error('default error'));
347+
await mockGetPlatformInfo.mockRejectedValueOnce(new Error('first error'));
348+
await mockGetPlatformInfo.mockRejectedValueOnce(new Error('second error'));
349+
350+
const getMsg = async () =>
351+
await browser.tauri.execute(async ({ core }) => {
352+
try {
353+
return await core.invoke('get_platform_info');
354+
} catch (e) {
355+
return e instanceof Error ? e.message : String(e);
356+
}
357+
});
358+
359+
expect(await getMsg()).toBe('first error');
360+
expect(await getMsg()).toBe('second error');
361+
expect(await getMsg()).toBe('default error');
362+
});
334363
});
335364

336365
describe('mockClear', () => {
@@ -558,21 +587,38 @@ describe('Tauri Mocking', () => {
558587
});
559588
});
560589

561-
describe('update() synchronization', () => {
590+
describe('synchronization', () => {
562591
it('should synchronize mock calls after update', async () => {
563592
const mockReadClipboard = await browser.tauri.mock('read_clipboard');
564593

565-
await browser.tauri.execute(async ({ core }) => {
566-
await core.invoke('read_clipboard');
567-
await core.invoke('read_clipboard');
594+
// Use browser.execute (raw WebDriver, no auto-sync) to invoke the inner mock directly
595+
await browser.execute(() => {
596+
// @ts-expect-error - window is available in browser context
597+
const mockFn = window.__wdio_mocks__?.read_clipboard;
598+
if (typeof mockFn === 'function') {
599+
mockFn();
600+
mockFn();
601+
}
568602
});
569603

570-
// Before update, calls should be empty
604+
// Before update(), outer mock has no calls (inner mock has 2)
571605
expect(mockReadClipboard.mock.calls).toStrictEqual([]);
572606

573607
await mockReadClipboard.update();
574608

575-
// After update, calls should be synchronized
609+
// After update(), calls are synchronized from inner mock
610+
expect(mockReadClipboard.mock.calls).toHaveLength(2);
611+
});
612+
613+
it('should auto-synchronize mock calls after execute', async () => {
614+
const mockReadClipboard = await browser.tauri.mock('read_clipboard');
615+
616+
await browser.tauri.execute(async ({ core }) => {
617+
await core.invoke('read_clipboard');
618+
await core.invoke('read_clipboard');
619+
});
620+
621+
// After tauri.execute (auto-synced), calls should be synchronized without calling update()
576622
expect(mockReadClipboard.mock.calls).toHaveLength(2);
577623
});
578624

0 commit comments

Comments
 (0)