Context
Raised by Greptile as a P1 on PR #264:
installCommandOverrides() is called at line 94 whenever hasElectronApi is true, which is the normal native-mode path. The installed override calls updateAllMocks() after every click, doubleClick, setValue, and clearValue. In native mode updateAllMocks() calls each mock's update(), which issues a browser.electron.execute() CDP call per registered mock. This silently adds latency proportional to mock count for all native-mode users.
The behaviour predates PR #264 (the override was already there) — the refactor surfaced it but did not introduce it. An attempt to scope the override to browser mode (commit 43b767f4) broke a large number of E2E suites that rely on element-command auto-sync, and was reverted in 8e84e8f7. The override stays; the underlying cost still needs to be addressed.
Hot path
packages/electron-service/src/service.ts:474-508 — updateAllMocks() calls Promise.all(mocks.map(m => m.update())). With N registered mocks, every element command (click/doubleClick/setValue/clearValue) costs N CDP round-trips.
What each update() does
Each one is a read-only call returning JSON.parse(JSON.stringify(inner.mock.calls)). Three flavours:
packages/electron-service/src/mock.ts:117 — api mock: (electron, apiName, funcName) => electron[apiName][funcName].mock.calls
packages/electron-service/src/classMock.ts:55 — class method on a prototype
packages/electron-service/src/classMock.ts:139, 194 — class constructor mock
packages/electron-service/src/mock.ts:221 — browser-mode mock reading window.__wdio_mocks__[channel].mock.calls
Proposed refactor
- Expose the existing
MockAccessor (or an equivalent small descriptor) on each outer mock, e.g. mock.__accessor.
- Add an
__applyCalls(calls: unknown[][]) method on each outer mock that runs the existing diff-and-apply logic against the outer mock without any CDP call.
- Rewrite
updateAllMocks():
- Collect
[mockId, accessor] pairs from mockStore.getMocks().
- Issue one
browser.electron.execute() that takes the array, walks electron[...] for each accessor, and returns { [mockId]: calls }.
- Distribute:
mocks.forEach(([id, mock]) => mock.__applyCalls(result[id])).
- Apply the same shape to browser mode (single
browser.execute() that walks window.__wdio_mocks__[...]).
- Leave per-mock
await mock.update() untouched — when called explicitly by user code, the one-round-trip cost is an explicit choice and rare.
Sibling work
Tauri service (packages/tauri-service) has the same architecture and the same per-mock-update cost. File or fold a matching task once the Electron pattern lands.
Acceptance
updateAllMocks() performs at most one CDP / WDIO round-trip regardless of mock count.
- Existing unit tests under
packages/electron-service/test/ still pass, including the two native-mode override tests ("should install element command overrides with overwriteCommand" and "should update mocks after overridden element command executes").
- E2E test "should trigger mock updates when DOM interactions occur" (
e2e/test/electron/mocking.spec.ts) still passes.
- New unit test verifying that
updateAllMocks() issues exactly one browser.electron.execute call when ≥2 mocks are registered.
References
Context
Raised by Greptile as a P1 on PR #264:
The behaviour predates PR #264 (the override was already there) — the refactor surfaced it but did not introduce it. An attempt to scope the override to browser mode (commit
43b767f4) broke a large number of E2E suites that rely on element-command auto-sync, and was reverted in8e84e8f7. The override stays; the underlying cost still needs to be addressed.Hot path
packages/electron-service/src/service.ts:474-508—updateAllMocks()callsPromise.all(mocks.map(m => m.update())). With N registered mocks, every element command (click/doubleClick/setValue/clearValue) costs N CDP round-trips.What each
update()doesEach one is a read-only call returning
JSON.parse(JSON.stringify(inner.mock.calls)). Three flavours:packages/electron-service/src/mock.ts:117— api mock:(electron, apiName, funcName) => electron[apiName][funcName].mock.callspackages/electron-service/src/classMock.ts:55— class method on a prototypepackages/electron-service/src/classMock.ts:139, 194— class constructor mockpackages/electron-service/src/mock.ts:221— browser-mode mock readingwindow.__wdio_mocks__[channel].mock.callsProposed refactor
MockAccessor(or an equivalent small descriptor) on each outer mock, e.g.mock.__accessor.__applyCalls(calls: unknown[][])method on each outer mock that runs the existing diff-and-apply logic against the outer mock without any CDP call.updateAllMocks():[mockId, accessor]pairs frommockStore.getMocks().browser.electron.execute()that takes the array, walkselectron[...]for each accessor, and returns{ [mockId]: calls }.mocks.forEach(([id, mock]) => mock.__applyCalls(result[id])).browser.execute()that walkswindow.__wdio_mocks__[...]).await mock.update()untouched — when called explicitly by user code, the one-round-trip cost is an explicit choice and rare.Sibling work
Tauri service (
packages/tauri-service) has the same architecture and the same per-mock-update cost. File or fold a matching task once the Electron pattern lands.Acceptance
updateAllMocks()performs at most one CDP / WDIO round-trip regardless of mock count.packages/electron-service/test/still pass, including the two native-mode override tests ("should install element command overrides with overwriteCommand" and "should update mocks after overridden element command executes").e2e/test/electron/mocking.spec.ts) still passes.updateAllMocks()issues exactly onebrowser.electron.executecall when ≥2 mocks are registered.References
43b767f4(attempted scope-to-browser-mode fix that broke E2E)8e84e8f7(revert)