perf(electron-service): batch updateAllMocks into a single CDP round-trip#292
perf(electron-service): batch updateAllMocks into a single CDP round-trip#292goosewobbler wants to merge 5 commits into
Conversation
Adds two optional hooks to ElectronMockInstance — __accessor (JSON-serialisable descriptor of how to reach the inner mock) and __applyCalls (local diff-apply, no I/O). The new ElectronMockReadAccessor type discriminates on api / prototype / constructor / browser. Used by electron-service to batch every mock's call-data read into a single CDP round-trip; see #268. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… mock Each mock variant (api, prototype, constructor, browser-mode) now exposes the new accessor descriptor and a local diff-apply method. The existing per-mock update() API is preserved verbatim and now delegates its apply phase to the same helper, so explicit user calls still work end-to-end. No behavior change yet — the scheduler refactor in the next commit is what actually batches reads. Refs #268. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…trip
The scheduler now splits registered mocks by accessor kind and routes each
subset through one round-trip:
- Native mocks (api / prototype / constructor) ride a single
browser.electron.execute() whose inner script walks every accessor and
returns {[mockId]: {calls, results}}.
- Browser-mode mocks ride one browser.execute() against window.__wdio_mocks__,
with raw payloads post-processed via interceptor.parseCallData() to
reconstruct Error markers.
Each mock's __applyCalls() distributes its slice locally with no further I/O.
Mocks without an accessor (test doubles) fall through to the per-mock
update() path. Per-mock mock.update() is unchanged for explicit user calls.
Tests:
- Updated "should update mocks after overridden element command executes" to
assert __applyCalls is invoked (the new sync path).
- Added "should batch updates for ≥2 mocks into a single
browser.electron.execute call" covering all three native accessor kinds.
- Added "should batch updates for ≥2 browser-mode mocks into a single
browser.execute call" with parseCallData round-trip assertion.
- Added "should split native and browser-mode mocks across exactly two
round-trips" proving the routing split fires both transports exactly once.
Closes #268.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Release Preview — no release
Updated automatically by ReleaseKit |
Greptile SummaryThis PR replaces the per-mock
Confidence Score: 5/5Safe to merge — the batch path is well-tested with four new unit tests covering all transport variants and failure isolation, and the legacy fallback keeps existing doubles working without changes. The refactor is behaviour-preserving: mock.update() delegates to the same applyCalls closure the scheduler uses, so per-mock and batched paths are always in sync. The one asymmetry found (missing ?? fallback before parseCallData in the browser-mode loop) is guarded at runtime because parseCallData already accepts null from the existing per-mock path. packages/electron-service/src/service.ts — specifically the batchUpdateBrowserModeMocks post-execute loop, which lacks the explicit null-fallback that its native counterpart uses.
|
| Filename | Overview |
|---|---|
| packages/electron-service/src/service.ts | Core scheduler rewrite: splits mocks into native/browser/legacy buckets and batches each in a single round-trip. Minor asymmetry — batchUpdateBrowserModeMocks lacks the ?? fallback that batchUpdateNativeMocks uses before calling parseCallData. |
| packages/electron-service/src/mock.ts | Extracts applyCalls closure shared by scheduler batch path and mock.update(); adds __accessor/__applyCalls to both the inner mock and the wrapper mock. Behaviour-preserving refactor. |
| packages/electron-service/src/classMock.ts | Extracts applyConstructorCalls/applyCalls closures for prototype and constructor mocks; both __accessor and __applyCalls are wired consistently alongside mock.update() delegation. |
| packages/electron-service/test/service.spec.ts | Adds four new scheduler tests covering native-batch, browser-mode batch, mixed-transport split, and per-reader failure isolation; updates the existing element-override test to use the new accessor pattern. |
| packages/native-types/src/electron.ts | Adds ElectronMockReadAccessor union type and optional __accessor/__applyCalls fields to ElectronMockInstance; clearly documented as service-internal. |
| packages/electron-service/src/mockFactory.ts | Exports MockApplyData interface and re-exports ElectronMockReadAccessor as MockReadAccessor alias for use across service modules. Pure type additions. |
| packages/native-types/src/index.ts | Adds ElectronMockReadAccessor to the public re-export list. Trivial barrel update. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Element command triggered\nclick / setValue / etc.] --> B[MockUpdateScheduler#runOnce]
B --> C{Classify each mock\nby __accessor.kind}
C -->|kind = api/prototype/constructor| D[native bucket]
C -->|kind = browser| E[browserMode bucket]
C -->|no __accessor| F[legacy bucket]
D --> G[batchUpdateNativeMocks\nbrowser.electron.execute — 1 CDP call]
E --> H[batchUpdateBrowserModeMocks\nbrowser.execute — 1 renderer call]
F --> I[per-mock m.update\none call per legacy mock]
G --> J[Inner Electron script\ndiscriminates api/prototype/constructor\nreturns Record<mockId, slice>]
J --> K[Outer loop: __applyCalls per mock\nno further I/O]
H --> L[Inner browser script\nnew Function per channel read\nreturns Record<mockId, raw>]
L --> M[parseCallData per slice\n__applyCalls per mock]
G & H & I --> N[Promise.all resolves\nAll mocks synced]
Reviews (3): Last reviewed commit: "refactor(electron-service): isolate per-..." | Re-trigger Greptile
- Browser-mode batch (P2 #1 + #2): switch batchUpdateBrowserModeMocks from a string-interpolated wrapper script to browser.execute(fn, ids, scripts). Null-byte mockStore keys now ride WebDriver's serialisation boundary, and the per-channel read scripts are sourced from browserInterceptor.buildCallDataReadScript() — same contract per-mock update() uses, so Error serialisation can't drift between the two paths. - Native batch (P2 #3): inner script's per-mock try/catch now emits an __error marker on the slice. The outer code log.warns it before forwarding to __applyCalls so the scheduler doesn't stall, but vacuous test passes from silently-zeroed call data are surfaced. - New unit test asserts the __error marker propagates from the native batch inner script through to __applyCalls. Refs #268. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…de batch
The browser-mode batch wrapper's `new Function(...)()` invocation was missing
the per-reader try/catch that batchUpdateNativeMocks already has — a single
malformed channel script or runtime throw would tank the whole batch instead
of being scoped to one mock.
Each reader is now wrapped in its own try/catch that emits the same
{ calls: [], results: [], invocationCallOrder: [], __error } shape used by the
native batch. The outer loop log.warns __error before forwarding the slice to
__applyCalls, preserving observability without stalling the scheduler.
Added a test asserting one bad reader doesn't prevent healthy mocks from
receiving their slices.
Refs #268.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Summary
browser.electron.execute) and one browser-mode (browser.execute) round-trip per batch regardless of mock count.__accessorand a local__applyCalls(data)method. The batched scheduler reads all mocks in one execute and dispatches slices to each mock's__applyCallswith no further I/O.mock.update()is unchanged for explicit user calls andwithImplementation.Commit layout
feat(native-types)— addsElectronMockReadAccessorplus the two new optional hooks onElectronMockInstance(types only).refactor(electron-service)— wires__accessor+__applyCallsonto every mock variant; behavior-preserving (update()now delegates its apply phase to the same helper).perf(electron-service)— rewritesMockUpdateScheduler#runOnceto split by accessor kind and batch each subset; updates one existing scheduler test and adds three new ones.Test plan
pnpm --filter @wdio/electron-service test— 510 tests pass (was 507 before the refactor; 3 new + 1 updated).pnpm --filter @wdio/electron-service lint— clean (pre-existing warnings only).pnpm --filter @wdio/electron-service typecheckandpnpm exec turbo run typecheck— all 21 tasks pass.should trigger mock updates when DOM interactions occurine2e/test/electron/mocking.spec.ts— needs a built app to run; please verify in CI.New unit tests
should batch updates for ≥2 mocks into a single browser.electron.execute call— covers all three native accessor kinds (api, prototype, constructor) and asserts exactly one CDP call.should batch updates for ≥2 browser-mode mocks into a single browser.execute call— registers two browser-mode mocks, asserts exactly onebrowser.executecall, and verifies the payload round-trips throughparseCallData.should split native and browser-mode mocks across exactly two round-trips— proves the routing split fires both transports exactly once when both kinds are registered.The existing concurrent-scheduler and recovery tests still pass via a small legacy fallback that calls
mock.update()for any mock without an accessor — kept narrowly so plain test doubles ({ update: vi.fn() }) continue to work without restructuring.Sibling work (follow-up)
The issue notes that
@wdio/tauri-servicehas the same per-mock-update cost. Not included here; should be filed/folded as a follow-up once this pattern lands.🤖 Generated with Claude Code