fix(e2e): cross-protocol browser.execute() — works with both WebDriver + CDP

Root cause: WebDriverIO devtools protocol wraps functions in a polyfill
that puts `return` inside eval() (not a function body) → "Illegal return".

Fix: exec() wrapper in helpers/execute.ts converts function args to IIFE
strings before passing to browser.execute(). Works identically on both
WebDriver (Tauri) and CDP/devtools (Electrobun CEF).

- 35 spec files updated (browser.execute → exec)
- 4 config files updated (string-form expressions)
- helpers/actions.ts + assertions.ts updated
- 560 vitest + 116 cargo passing
This commit is contained in:
Hibryda 2026-03-22 06:33:55 +01:00
parent 407e49cc32
commit 6a8181f33a
42 changed files with 630 additions and 541 deletions

View file

@ -1,4 +1,5 @@
import { browser, expect } from '@wdio/globals';
import { exec } from '../helpers/execute.ts';
// Phase C — UI Interaction Tests (C1C4)
// Command palette, search overlay, notification center, keyboard navigation.
@ -7,7 +8,7 @@ import { browser, expect } from '@wdio/globals';
/** Open command palette via Ctrl+K. */
async function openPalette(): Promise<void> {
await browser.execute(() => document.body.focus());
await exec(() => document.body.focus());
await browser.pause(100);
await browser.keys(['Control', 'k']);
const palette = await browser.$('[data-testid="command-palette"]');
@ -25,7 +26,7 @@ async function paletteSearch(query: string): Promise<string[]> {
const input = await browser.$('[data-testid="palette-input"]');
await input.setValue(query);
await browser.pause(300);
return browser.execute(() => {
return exec(() => {
const items = document.querySelectorAll('.palette-item .cmd-label');
return Array.from(items).map(el => el.textContent?.trim() ?? '');
});
@ -37,7 +38,7 @@ describe('Scenario C1 — Command Palette Hardening Commands', () => {
afterEach(async () => {
// Ensure palette is closed after each test
try {
const isVisible = await browser.execute(() => {
const isVisible = await exec(() => {
const el = document.querySelector('[data-testid="command-palette"]');
return el !== null && window.getComputedStyle(el).display !== 'none';
});
@ -79,14 +80,14 @@ describe('Scenario C1 — Command Palette Hardening Commands', () => {
await input.clearValue();
await browser.pause(200);
const itemCount = await browser.execute(() =>
const itemCount = await exec(() =>
document.querySelectorAll('.palette-item').length,
);
// v3 has 18+ commands
expect(itemCount).toBeGreaterThanOrEqual(10);
// Commands should be organized in groups (categories)
const groups = await browser.execute(() => {
const groups = await exec(() => {
const headers = document.querySelectorAll('.palette-category');
return Array.from(headers).map(h => h.textContent?.trim() ?? '');
});
@ -99,12 +100,12 @@ describe('Scenario C1 — Command Palette Hardening Commands', () => {
describe('Scenario C2 — Search Overlay (FTS5)', () => {
it('should open search overlay with Ctrl+Shift+F', async () => {
await browser.execute(() => document.body.focus());
await exec(() => document.body.focus());
await browser.pause(100);
await browser.keys(['Control', 'Shift', 'f']);
await browser.pause(500);
const overlay = await browser.execute(() => {
const overlay = await exec(() => {
// SearchOverlay uses .search-overlay class
const el = document.querySelector('.search-overlay, [data-testid="search-overlay"]');
return el !== null;
@ -113,7 +114,7 @@ describe('Scenario C2 — Search Overlay (FTS5)', () => {
});
it('should have search input focused', async () => {
const isFocused = await browser.execute(() => {
const isFocused = await exec(() => {
const input = document.querySelector('.search-overlay input, [data-testid="search-input"]') as HTMLInputElement | null;
if (!input) return false;
input.focus();
@ -123,7 +124,7 @@ describe('Scenario C2 — Search Overlay (FTS5)', () => {
});
it('should show no results for nonsense query', async () => {
await browser.execute(() => {
await exec(() => {
const input = document.querySelector('.search-overlay input, [data-testid="search-input"]') as HTMLInputElement | null;
if (input) {
input.value = 'zzz_nonexistent_xyz_999';
@ -132,7 +133,7 @@ describe('Scenario C2 — Search Overlay (FTS5)', () => {
});
await browser.pause(500); // 300ms debounce + render time
const resultCount = await browser.execute(() => {
const resultCount = await exec(() => {
const results = document.querySelectorAll('.search-result, .search-result-item');
return results.length;
});
@ -143,7 +144,7 @@ describe('Scenario C2 — Search Overlay (FTS5)', () => {
await browser.keys('Escape');
await browser.pause(300);
const overlay = await browser.execute(() => {
const overlay = await exec(() => {
const el = document.querySelector('.search-overlay, [data-testid="search-overlay"]');
if (!el) return false;
const style = window.getComputedStyle(el);
@ -157,7 +158,7 @@ describe('Scenario C2 — Search Overlay (FTS5)', () => {
describe('Scenario C3 — Notification Center', () => {
it('should render notification bell in status bar', async () => {
const hasBell = await browser.execute(() => {
const hasBell = await exec(() => {
const bar = document.querySelector('[data-testid="status-bar"]');
// NotificationCenter is in status bar with bell icon
const bell = bar?.querySelector('.notification-bell, .bell-icon, [data-testid="notification-bell"]');
@ -167,13 +168,13 @@ describe('Scenario C3 — Notification Center', () => {
});
it('should open notification panel on bell click', async () => {
await browser.execute(() => {
await exec(() => {
const bell = document.querySelector('.notification-bell, .bell-icon, [data-testid="notification-bell"]');
if (bell) (bell as HTMLElement).click();
});
await browser.pause(300);
const panelOpen = await browser.execute(() => {
const panelOpen = await exec(() => {
const panel = document.querySelector('.notification-panel, .notification-dropdown, [data-testid="notification-panel"]');
if (!panel) return false;
const style = window.getComputedStyle(panel);
@ -183,7 +184,7 @@ describe('Scenario C3 — Notification Center', () => {
});
it('should show empty state or notification history', async () => {
const content = await browser.execute(() => {
const content = await exec(() => {
const panel = document.querySelector('.notification-panel, .notification-dropdown, [data-testid="notification-panel"]');
return panel?.textContent ?? '';
});
@ -193,13 +194,13 @@ describe('Scenario C3 — Notification Center', () => {
it('should close notification panel on outside click', async () => {
// Click the backdrop overlay to close the panel
await browser.execute(() => {
await exec(() => {
const backdrop = document.querySelector('.notification-center .backdrop');
if (backdrop) (backdrop as HTMLElement).click();
});
await browser.pause(300);
const panelOpen = await browser.execute(() => {
const panelOpen = await exec(() => {
const panel = document.querySelector('.notification-panel, .notification-dropdown, [data-testid="notification-panel"]');
if (!panel) return false;
const style = window.getComputedStyle(panel);
@ -213,12 +214,12 @@ describe('Scenario C3 — Notification Center', () => {
describe('Scenario C4 — Keyboard-First Navigation', () => {
it('should toggle settings with Ctrl+Comma', async () => {
await browser.execute(() => document.body.focus());
await exec(() => document.body.focus());
await browser.pause(100);
await browser.keys(['Control', ',']);
await browser.pause(500);
const settingsVisible = await browser.execute(() => {
const settingsVisible = await exec(() => {
const panel = document.querySelector('.sidebar-panel');
if (!panel) return false;
const style = window.getComputedStyle(panel);
@ -232,14 +233,14 @@ describe('Scenario C4 — Keyboard-First Navigation', () => {
});
it('should toggle sidebar with Ctrl+B', async () => {
await browser.execute(() => document.body.focus());
await exec(() => document.body.focus());
await browser.pause(100);
// First open settings to have sidebar content
await browser.keys(['Control', ',']);
await browser.pause(300);
const initialState = await browser.execute(() => {
const initialState = await exec(() => {
const panel = document.querySelector('.sidebar-panel');
return panel !== null && window.getComputedStyle(panel).display !== 'none';
});
@ -248,7 +249,7 @@ describe('Scenario C4 — Keyboard-First Navigation', () => {
await browser.keys(['Control', 'b']);
await browser.pause(300);
const afterToggle = await browser.execute(() => {
const afterToggle = await exec(() => {
const panel = document.querySelector('.sidebar-panel');
if (!panel) return false;
return window.getComputedStyle(panel).display !== 'none';
@ -265,12 +266,12 @@ describe('Scenario C4 — Keyboard-First Navigation', () => {
});
it('should focus project with Alt+1', async () => {
await browser.execute(() => document.body.focus());
await exec(() => document.body.focus());
await browser.pause(100);
await browser.keys(['Alt', '1']);
await browser.pause(300);
const hasActive = await browser.execute(() => {
const hasActive = await exec(() => {
const active = document.querySelector('.project-box.active');
return active !== null;
});