- phase-b-grid.test.ts (227 lines): multi-project grid, tab switching, status bar, accent colors, project icons, scroll, tab bar completeness - phase-b-llm.test.ts (211 lines): LLM-judged agent response, code gen, context tab, tool calls, cost display, session persistence - Original phase-b.test.ts (377 lines) deleted - New exhaustive tests added for grid layout and agent interaction
227 lines
8.8 KiB
TypeScript
227 lines
8.8 KiB
TypeScript
import { browser, expect } from '@wdio/globals';
|
|
|
|
// Phase B — Grid: Multi-project grid, tab switching, status bar.
|
|
// Scenarios B1-B3 + new grid/UI tests.
|
|
|
|
// ─── Helpers ──────────────────────────────────────────────────────────
|
|
|
|
async function getProjectIds(): Promise<string[]> {
|
|
return browser.execute(() => {
|
|
const boxes = document.querySelectorAll('[data-testid="project-box"]');
|
|
return Array.from(boxes).map((b) => b.getAttribute('data-project-id') ?? '').filter(Boolean);
|
|
});
|
|
}
|
|
|
|
async function focusProject(id: string): Promise<void> {
|
|
await browser.execute((pid) => {
|
|
const h = document.querySelector(`[data-project-id="${pid}"] .project-header`);
|
|
if (h) (h as HTMLElement).click();
|
|
}, id);
|
|
await browser.pause(300);
|
|
}
|
|
|
|
async function switchProjectTab(id: string, tabIndex: number): Promise<void> {
|
|
await browser.execute((pid, idx) => {
|
|
const tabs = document.querySelector(`[data-project-id="${pid}"]`)?.querySelectorAll('[data-testid="project-tabs"] .ptab');
|
|
if (tabs?.[idx]) (tabs[idx] as HTMLElement).click();
|
|
}, id, tabIndex);
|
|
await browser.pause(300);
|
|
}
|
|
|
|
async function getAgentStatus(id: string): Promise<string> {
|
|
return browser.execute((pid) => {
|
|
const p = document.querySelector(`[data-project-id="${pid}"] [data-testid="agent-pane"]`);
|
|
return p?.getAttribute('data-agent-status') ?? 'not-found';
|
|
}, id);
|
|
}
|
|
|
|
async function resetToModelTabs(): Promise<void> {
|
|
for (const id of await getProjectIds()) await switchProjectTab(id, 0);
|
|
}
|
|
|
|
// ─── Scenario B1: Multi-project grid renders correctly ────────────────
|
|
|
|
describe('Scenario B1 — Multi-Project Grid', () => {
|
|
before(async () => {
|
|
// Reset: ensure all projects on Model tab
|
|
await resetToModelTabs();
|
|
});
|
|
|
|
it('should render multiple project boxes', async () => {
|
|
await browser.waitUntil(
|
|
async () => {
|
|
const count = await browser.execute(() =>
|
|
document.querySelectorAll('[data-testid="project-box"]').length,
|
|
);
|
|
return (count as number) >= 1;
|
|
},
|
|
{ timeout: 10_000, timeoutMsg: 'No project boxes rendered within 10s' },
|
|
);
|
|
|
|
const ids = await getProjectIds();
|
|
expect(ids.length).toBeGreaterThanOrEqual(1);
|
|
const unique = new Set(ids);
|
|
expect(unique.size).toBe(ids.length);
|
|
});
|
|
|
|
it('should show project headers with CWD paths', async () => {
|
|
const headers = await browser.execute(() => {
|
|
const els = document.querySelectorAll('.project-header .cwd');
|
|
return Array.from(els).map((e) => e.textContent?.trim() ?? '');
|
|
});
|
|
for (const cwd of headers) {
|
|
expect(cwd.length).toBeGreaterThan(0);
|
|
}
|
|
});
|
|
|
|
it('should have independent agent panes per project', async () => {
|
|
const ids = await getProjectIds();
|
|
for (const id of ids) {
|
|
const status = await getAgentStatus(id);
|
|
expect(['idle', 'running', 'stalled']).toContain(status);
|
|
}
|
|
});
|
|
|
|
it('should focus project on click and show active styling', async () => {
|
|
const ids = await getProjectIds();
|
|
if (ids.length < 1) return;
|
|
|
|
await focusProject(ids[0]);
|
|
const isActive = await browser.execute((id) => {
|
|
const box = document.querySelector(`[data-project-id="${id}"]`);
|
|
return box?.classList.contains('active') ?? false;
|
|
}, ids[0]);
|
|
expect(isActive).toBe(true);
|
|
});
|
|
|
|
it('should show project-specific accent colors on each box border', async () => {
|
|
const accents = await browser.execute(() => {
|
|
const boxes = document.querySelectorAll('[data-testid="project-box"]');
|
|
return Array.from(boxes).map((b) => getComputedStyle(b as HTMLElement).getPropertyValue('--accent').trim());
|
|
});
|
|
for (const accent of accents) { expect(accent.length).toBeGreaterThan(0); }
|
|
});
|
|
|
|
it('should render project icons (emoji) in headers', async () => {
|
|
const icons = await browser.execute(() => {
|
|
const els = document.querySelectorAll('.project-header .project-icon, .project-header .emoji');
|
|
return Array.from(els).map((e) => e.textContent?.trim() ?? '');
|
|
});
|
|
if (icons.length > 0) {
|
|
for (const icon of icons) { expect(icon.length).toBeGreaterThan(0); }
|
|
}
|
|
});
|
|
|
|
it('should show project CWD tooltip on hover', async () => {
|
|
const ids = await getProjectIds();
|
|
if (ids.length < 1) return;
|
|
const titleAttr = await browser.execute((id) => {
|
|
const el = document.querySelector(`[data-project-id="${id}"] .project-header .cwd`);
|
|
return el?.getAttribute('title') ?? el?.textContent?.trim() ?? '';
|
|
}, ids[0]);
|
|
expect(titleAttr.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should highlight focused project with distinct border color', async () => {
|
|
const ids = await getProjectIds();
|
|
if (ids.length < 2) return;
|
|
await focusProject(ids[0]);
|
|
const isActive = await browser.execute((id) => {
|
|
return document.querySelector(`[data-project-id="${id}"]`)?.classList.contains('active') ?? false;
|
|
}, ids[0]);
|
|
expect(isActive).toBe(true);
|
|
});
|
|
|
|
it('should show all base tabs per project', async () => {
|
|
const ids = await getProjectIds();
|
|
if (ids.length < 1) return;
|
|
const tabLabels = await browser.execute((id) => {
|
|
const tabs = document.querySelector(`[data-project-id="${id}"]`)?.querySelectorAll('[data-testid="project-tabs"] .ptab');
|
|
return Array.from(tabs ?? []).map((t) => t.textContent?.trim() ?? '');
|
|
}, ids[0]);
|
|
for (const tab of ['Model', 'Docs', 'Context', 'Files', 'SSH', 'Memory', 'Metrics']) {
|
|
expect(tabLabels).toContain(tab);
|
|
}
|
|
});
|
|
|
|
it('should show terminal section at bottom of Model tab', async () => {
|
|
const ids = await getProjectIds();
|
|
if (ids.length < 1) return;
|
|
await switchProjectTab(ids[0], 0);
|
|
const hasTerminal = await browser.execute((id) => {
|
|
return document.querySelector(`[data-project-id="${id}"] [data-testid="terminal-tabs"], [data-project-id="${id}"] .terminal-section`) !== null;
|
|
}, ids[0]);
|
|
expect(hasTerminal).toBe(true);
|
|
});
|
|
});
|
|
|
|
// ─── Scenario B2: Independent tab switching across projects ───────────
|
|
|
|
describe('Scenario B2 — Independent Tab Switching', () => {
|
|
before(async () => {
|
|
await resetToModelTabs();
|
|
});
|
|
|
|
it('should allow different tabs active in different projects', async () => {
|
|
const ids = await getProjectIds();
|
|
if (ids.length < 2) { console.log('Skipping B2 — need 2+ projects'); return; }
|
|
await switchProjectTab(ids[0], 3); // Files tab
|
|
await switchProjectTab(ids[1], 0); // Model tab
|
|
const getActiveTab = (id: string) => browser.execute((pid) => {
|
|
return document.querySelector(`[data-project-id="${pid}"] [data-testid="project-tabs"] .ptab.active`)?.textContent?.trim() ?? '';
|
|
}, id);
|
|
const firstActive = await getActiveTab(ids[0]);
|
|
const secondActive = await getActiveTab(ids[1]);
|
|
expect(firstActive).not.toBe(secondActive);
|
|
await switchProjectTab(ids[0], 0);
|
|
});
|
|
|
|
it('should preserve scroll position when switching between projects', async () => {
|
|
const ids = await getProjectIds();
|
|
if (ids.length < 2) return;
|
|
await focusProject(ids[0]);
|
|
await focusProject(ids[1]);
|
|
await focusProject(ids[0]);
|
|
const activeTab = await browser.execute((id) => {
|
|
return document.querySelector(`[data-project-id="${id}"] [data-testid="project-tabs"] .ptab.active`)?.textContent?.trim() ?? '';
|
|
}, ids[0]);
|
|
expect(activeTab).toBe('Model');
|
|
});
|
|
});
|
|
|
|
// ─── Scenario B3: Status bar reflects fleet state ────────────────────
|
|
|
|
describe('Scenario B3 — Status Bar Fleet State', () => {
|
|
it('should show agent count in status bar', async () => {
|
|
const barText = await browser.execute(() => {
|
|
const bar = document.querySelector('[data-testid="status-bar"]');
|
|
return bar?.textContent ?? '';
|
|
});
|
|
expect(barText.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should show no burn rate when all agents idle', async () => {
|
|
const hasBurnRate = await browser.execute(() => {
|
|
const bar = document.querySelector('[data-testid="status-bar"]');
|
|
const burnEl = bar?.querySelector('.burn-rate');
|
|
const costEl = bar?.querySelector('.cost');
|
|
return { burn: burnEl?.textContent ?? null, cost: costEl?.textContent ?? null };
|
|
});
|
|
if (hasBurnRate.burn !== null) {
|
|
expect(hasBurnRate.burn).toMatch(/\$0|0\.00/);
|
|
}
|
|
if (hasBurnRate.cost !== null) {
|
|
expect(hasBurnRate.cost).toMatch(/\$0|0\.00/);
|
|
}
|
|
});
|
|
|
|
it('should update status bar counts when project focus changes', async () => {
|
|
const ids = await getProjectIds();
|
|
if (ids.length < 2) return;
|
|
await focusProject(ids[1]);
|
|
const barAfter = await browser.execute(() => {
|
|
return document.querySelector('[data-testid="status-bar"]')?.textContent ?? '';
|
|
});
|
|
expect(barAfter.length).toBeGreaterThan(0);
|
|
});
|
|
});
|