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 D — Settings Panel Tests (D1D3)
// Tests the redesigned VS Code-style settings panel with 6+1 category tabs,
@ -9,14 +10,14 @@ import { browser, expect } from '@wdio/globals';
async function openSettings(): Promise<void> {
const panel = await browser.$('.settings-panel');
if (!(await panel.isDisplayed().catch(() => false))) {
await browser.execute(() => {
await exec(() => {
const btn = document.querySelector('[data-testid="settings-btn"]');
if (btn) (btn as HTMLElement).click();
});
await (await browser.$('.sidebar-panel')).waitForDisplayed({ timeout: 5000 });
}
await browser.waitUntil(
async () => (await browser.execute(() => document.querySelectorAll('.settings-panel').length) as number) >= 1,
async () => (await exec(() => document.querySelectorAll('.settings-panel').length) as number) >= 1,
{ timeout: 5000, timeoutMsg: 'Settings panel did not render within 5s' },
);
await browser.pause(300);
@ -25,7 +26,7 @@ async function openSettings(): Promise<void> {
async function closeSettings(): Promise<void> {
const panel = await browser.$('.sidebar-panel');
if (await panel.isDisplayed().catch(() => false)) {
await browser.execute(() => {
await exec(() => {
const btn = document.querySelector('.settings-close') || document.querySelector('.panel-close');
if (btn) (btn as HTMLElement).click();
});
@ -34,7 +35,7 @@ async function closeSettings(): Promise<void> {
}
async function clickCategory(label: string): Promise<void> {
await browser.execute((lbl) => {
await exec((lbl) => {
const items = document.querySelectorAll('.sidebar-item');
for (const el of items) {
if (el.textContent?.includes(lbl)) { (el as HTMLElement).click(); return; }
@ -44,7 +45,7 @@ async function clickCategory(label: string): Promise<void> {
}
async function scrollToTop(): Promise<void> {
await browser.execute(() => { document.querySelector('.settings-content')?.scrollTo(0, 0); });
await exec(() => { document.querySelector('.settings-content')?.scrollTo(0, 0); });
await browser.pause(200);
}
@ -80,20 +81,20 @@ describe('Scenario D1 — Settings Panel Categories', () => {
it('should show search bar and filter results', async () => {
await expect(await browser.$('.settings-search')).toBeDisplayed();
await browser.execute(() => {
await exec(() => {
const input = document.querySelector('.settings-search') as HTMLInputElement;
if (input) { input.value = 'font'; input.dispatchEvent(new Event('input', { bubbles: true })); }
});
await browser.pause(500);
const results = await browser.$$('.search-result');
expect(results.length).toBeGreaterThan(0);
const hasFont = await browser.execute(() => {
const hasFont = await exec(() => {
const labels = document.querySelectorAll('.search-result .sr-label');
return Array.from(labels).some(l => l.textContent?.toLowerCase().includes('font'));
});
expect(hasFont).toBe(true);
// Clear search
await browser.execute(() => {
await exec(() => {
const input = document.querySelector('.settings-search') as HTMLInputElement;
if (input) { input.value = ''; input.dispatchEvent(new Event('input', { bubbles: true })); }
});
@ -108,7 +109,7 @@ describe('Scenario D2 — Appearance Settings', () => {
after(async () => { await closeSettings(); });
it('should show theme dropdown with 17+ built-in themes grouped by category', async () => {
await browser.execute(() => {
await exec(() => {
const btn = document.querySelector('.appearance .custom-dropdown .dropdown-btn');
if (btn) (btn as HTMLElement).click();
});
@ -118,7 +119,7 @@ describe('Scenario D2 — Appearance Settings', () => {
const items = await browser.$$('.theme-menu .dropdown-item');
expect(items.length).toBeGreaterThanOrEqual(17);
// Close dropdown
await browser.execute(() => {
await exec(() => {
const btn = document.querySelector('.appearance .custom-dropdown .dropdown-btn');
if (btn) (btn as HTMLElement).click();
});
@ -128,17 +129,17 @@ describe('Scenario D2 — Appearance Settings', () => {
it('should show font size steppers with -/+ buttons', async () => {
const steppers = await browser.$$('.stepper');
expect(steppers.length).toBeGreaterThanOrEqual(1);
const before = await browser.execute(() => document.querySelector('.stepper span')?.textContent ?? '');
const before = await exec(() => document.querySelector('.stepper span')?.textContent ?? '');
const sizeBefore = parseInt(before as string, 10);
await browser.execute(() => {
await exec(() => {
const btns = document.querySelectorAll('.stepper button');
if (btns.length >= 2) (btns[1] as HTMLElement).click(); // + button
});
await browser.pause(300);
const after = await browser.execute(() => document.querySelector('.stepper span')?.textContent ?? '');
const after = await exec(() => document.querySelector('.stepper span')?.textContent ?? '');
expect(parseInt(after as string, 10)).toBe(sizeBefore + 1);
// Revert
await browser.execute(() => {
await exec(() => {
const btns = document.querySelectorAll('.stepper button');
if (btns.length >= 1) (btns[0] as HTMLElement).click();
});
@ -146,7 +147,7 @@ describe('Scenario D2 — Appearance Settings', () => {
});
it('should show terminal cursor style selector (Block/Line/Underline)', async () => {
await browser.execute(() => {
await exec(() => {
document.getElementById('setting-cursor-style')?.scrollIntoView({ behavior: 'instant', block: 'center' });
});
await browser.pause(300);
@ -154,14 +155,14 @@ describe('Scenario D2 — Appearance Settings', () => {
await expect(segmented).toBeDisplayed();
const buttons = await browser.$$('.segmented button');
expect(buttons.length).toBe(3);
const activeText = await browser.execute(() =>
const activeText = await exec(() =>
document.querySelector('.segmented button.active')?.textContent?.trim() ?? '',
);
expect(activeText).toBe('Block');
});
it('should show scrollback lines input', async () => {
await browser.execute(() => {
await exec(() => {
document.getElementById('setting-scrollback')?.scrollIntoView({ behavior: 'instant', block: 'center' });
});
await browser.pause(300);
@ -178,7 +179,7 @@ describe('Scenario D2 — Appearance Settings', () => {
describe('Scenario D3 — Theme Editor', () => {
before(async () => { await openSettings(); await clickCategory('Appearance'); await scrollToTop(); });
after(async () => {
await browser.execute(() => {
await exec(() => {
const btn = Array.from(document.querySelectorAll('.editor .btn'))
.find(b => b.textContent?.trim() === 'Cancel');
if (btn) (btn as HTMLElement).click();
@ -194,7 +195,7 @@ describe('Scenario D3 — Theme Editor', () => {
});
it('should open theme editor with color pickers when clicked', async () => {
await browser.execute(() => {
await exec(() => {
const btn = document.querySelector('.new-theme-btn');
if (btn) (btn as HTMLElement).click();
});
@ -215,11 +216,11 @@ describe('Scenario D3 — Theme Editor', () => {
});
it('should have Cancel and Save buttons', async () => {
const hasCancel = await browser.execute(() =>
const hasCancel = await exec(() =>
Array.from(document.querySelectorAll('.editor .footer .btn')).some(b => b.textContent?.trim() === 'Cancel'),
);
expect(hasCancel).toBe(true);
const hasSave = await browser.execute(() =>
const hasSave = await exec(() =>
Array.from(document.querySelectorAll('.editor .footer .btn')).some(b => b.textContent?.trim() === 'Save'),
);
expect(hasSave).toBe(true);