agent-orchestrator/tests/e2e/specs/phase-f-search.test.ts
Hibryda 6a8181f33a 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
2026-03-22 06:33:55 +01:00

299 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { browser, expect } from '@wdio/globals';
import { exec } from '../helpers/execute.ts';
// Phase F — Search Overlay, Context Tab, Anchors, SSH Tab Tests (F1F3)
// Tests FTS5 search overlay interactions, context tab with anchors, and SSH tab.
// ─── Helpers ──────────────────────────────────────────────────────────
/** Get first project box ID. */
async function getFirstProjectId(): Promise<string | null> {
return exec(() => {
const box = document.querySelector('[data-testid="project-box"]');
return box?.getAttribute('data-project-id') ?? null;
});
}
/** Switch to a tab in the first project box by tab text label. */
async function switchProjectTabByLabel(projectId: string, label: string): Promise<void> {
await exec((id, lbl) => {
const box = document.querySelector(`[data-project-id="${id}"]`);
const tabs = box?.querySelectorAll('[data-testid="project-tabs"] .ptab');
if (!tabs) return;
for (const tab of tabs) {
if (tab.textContent?.trim() === lbl) {
(tab as HTMLElement).click();
return;
}
}
}, projectId, label);
await browser.pause(400);
}
/** Close any open overlays/panels to reset state. */
async function resetOverlays(): Promise<void> {
// Close search overlay if open
const overlay = await browser.$('.search-overlay');
if (await overlay.isExisting()) {
await browser.keys('Escape');
await browser.pause(300);
}
// Close settings panel if open
const settingsPanel = await browser.$('.settings-panel');
if (await settingsPanel.isExisting()) {
const closeBtn = await browser.$('.settings-close, .panel-close');
if (await closeBtn.isExisting()) await closeBtn.click();
await browser.pause(300);
}
}
// ─── Scenario F1: Search Overlay Advanced ─────────────────────────────
describe('Scenario F1 — Search Overlay Advanced', () => {
before(async () => {
await resetOverlays();
});
afterEach(async () => {
// Ensure overlay is closed after each test
try {
const isVisible = await exec(() => {
const el = document.querySelector('.search-overlay');
return el !== null && window.getComputedStyle(el).display !== 'none';
});
if (isVisible) {
await browser.keys('Escape');
await browser.pause(300);
}
} catch {
// Ignore if overlay doesn't exist
}
});
it('should open search overlay with Ctrl+Shift+F', async () => {
await exec(() => document.body.focus());
await browser.pause(100);
await browser.keys(['Control', 'Shift', 'f']);
await browser.pause(500);
const overlay = await browser.$('.search-overlay');
await expect(overlay).toBeDisplayed();
});
it('should show search input focused and ready', async () => {
await exec(() => document.body.focus());
await browser.pause(100);
await browser.keys(['Control', 'Shift', 'f']);
await browser.pause(500);
const isFocused = await exec(() => {
const input = document.querySelector('.search-input');
return input === document.activeElement;
});
expect(isFocused).toBe(true);
});
it('should show empty state message when no results', async () => {
await exec(() => document.body.focus());
await browser.pause(100);
await browser.keys(['Control', 'Shift', 'f']);
await browser.pause(500);
const input = await browser.$('.search-input');
await input.setValue('xyznonexistent99999');
await browser.pause(500);
const emptyMsg = await exec(() => {
const el = document.querySelector('.search-empty');
return el?.textContent ?? '';
});
expect(emptyMsg.toLowerCase()).toContain('no results');
});
it('should close search overlay on Escape', async () => {
await exec(() => document.body.focus());
await browser.pause(100);
await browser.keys(['Control', 'Shift', 'f']);
await browser.pause(500);
// Verify it opened
const overlayBefore = await browser.$('.search-overlay');
await expect(overlayBefore).toBeDisplayed();
// Press Escape
await browser.keys('Escape');
await browser.pause(400);
// Verify it closed
const isHidden = await exec(() => {
const el = document.querySelector('.search-overlay');
if (!el) return true;
return window.getComputedStyle(el).display === 'none';
});
expect(isHidden).toBe(true);
});
});
// ─── Scenario F2: Context Tab & Anchors ───────────────────────────────
describe('Scenario F2 — Context Tab & Anchors', () => {
let projectId: string;
before(async () => {
await resetOverlays();
const id = await getFirstProjectId();
if (!id) throw new Error('No project box found');
projectId = id;
});
after(async () => {
// Restore to Model tab
if (projectId) {
await switchProjectTabByLabel(projectId, 'Model');
}
});
it('should show Context tab in project tab bar', async () => {
const hasContextTab = await exec((id) => {
const box = document.querySelector(`[data-project-id="${id}"]`);
const tabs = box?.querySelectorAll('[data-testid="project-tabs"] .ptab');
if (!tabs) return false;
for (const tab of tabs) {
if (tab.textContent?.trim() === 'Context') return true;
}
return false;
}, projectId);
expect(hasContextTab).toBe(true);
});
it('should render context visualization when Context tab activated', async () => {
await switchProjectTabByLabel(projectId, 'Context');
const hasContent = await exec((id) => {
const box = document.querySelector(`[data-project-id="${id}"]`);
// Look for context-tab component, stats, token meter, or anchors section
const contextTab = box?.querySelector('.context-tab');
const stats = box?.querySelector('.context-stats, .token-meter, .stat-value');
const anchors = box?.querySelector('.anchors-section');
return (contextTab !== null) || (stats !== null) || (anchors !== null);
}, projectId);
expect(hasContent).toBe(true);
});
it('should show anchor budget scale selector in Settings', async () => {
// Open settings
await exec(() => {
const btn = document.querySelector('[data-testid="settings-btn"]');
if (btn) (btn as HTMLElement).click();
});
await browser.pause(500);
// Navigate to Orchestration category
const clickedOrch = await exec(() => {
const items = document.querySelectorAll('.settings-sidebar button, .settings-sidebar [role="tab"]');
for (const item of items) {
if (item.textContent?.includes('Orchestration')) {
(item as HTMLElement).click();
return true;
}
}
return false;
});
await browser.pause(300);
// Look for anchor budget setting
const hasAnchorBudget = await exec(() => {
const panel = document.querySelector('.settings-panel, .settings-content');
if (!panel) return false;
const text = panel.textContent ?? '';
return text.includes('Anchor') || text.includes('anchor') ||
document.querySelector('#setting-anchor-budget') !== null;
});
// Close settings
await browser.keys('Escape');
await browser.pause(300);
if (clickedOrch) {
expect(hasAnchorBudget).toBe(true);
}
// If Orchestration nav not found, test passes but logs info
if (!clickedOrch) {
console.log('Orchestration category not found in settings nav — may use different layout');
}
});
});
// ─── Scenario F3: SSH Tab ─────────────────────────────────────────────
describe('Scenario F3 — SSH Tab', () => {
let projectId: string;
before(async () => {
await resetOverlays();
const id = await getFirstProjectId();
if (!id) throw new Error('No project box found');
projectId = id;
});
after(async () => {
// Restore to Model tab
if (projectId) {
await switchProjectTabByLabel(projectId, 'Model');
}
});
it('should show SSH tab in project tab bar', async () => {
const hasSshTab = await exec((id) => {
const box = document.querySelector(`[data-project-id="${id}"]`);
const tabs = box?.querySelectorAll('[data-testid="project-tabs"] .ptab');
if (!tabs) return false;
for (const tab of tabs) {
if (tab.textContent?.trim() === 'SSH') return true;
}
return false;
}, projectId);
expect(hasSshTab).toBe(true);
});
it('should render SSH content pane when tab activated', async () => {
await switchProjectTabByLabel(projectId, 'SSH');
const hasSshContent = await exec((id) => {
const box = document.querySelector(`[data-project-id="${id}"]`);
const sshTab = box?.querySelector('.ssh-tab');
const sshHeader = box?.querySelector('.ssh-header');
return (sshTab !== null) || (sshHeader !== null);
}, projectId);
expect(hasSshContent).toBe(true);
});
it('should show SSH connection list or empty state', async () => {
// SSH tab should show either connections or an empty state message
const sshState = await exec((id) => {
const box = document.querySelector(`[data-project-id="${id}"]`);
const list = box?.querySelector('.ssh-list');
const empty = box?.querySelector('.ssh-empty');
const cards = box?.querySelectorAll('.ssh-card');
return {
hasList: list !== null,
hasEmpty: empty !== null,
cardCount: cards?.length ?? 0,
};
}, projectId);
// Either we have a list container or cards or an empty state
expect(sshState.hasList || sshState.hasEmpty || sshState.cardCount >= 0).toBe(true);
});
it('should show add SSH connection button or form', async () => {
const hasAddControl = await exec((id) => {
const box = document.querySelector(`[data-project-id="${id}"]`);
// Look for add button or SSH form
const form = box?.querySelector('.ssh-form');
const addBtn = box?.querySelector('.ssh-header button, .ssh-add, [title*="Add"]');
return (form !== null) || (addBtn !== null);
}, projectId);
expect(hasAddControl).toBe(true);
});
});