## DevLoop - Closed-Loop Development System - Orchestrator for plan → build → test → analyze cycle - Gemini planning via OpenCode CLI - Claude implementation via CLI bridge - Playwright browser testing integration - Test runner with API, filesystem, and browser tests - Persistent state in .devloop/ directory - CLI tool: tools/devloop_cli.py Usage: python tools/devloop_cli.py start 'Create new feature' python tools/devloop_cli.py plan 'Fix bug in X' python tools/devloop_cli.py test --study support_arm python tools/devloop_cli.py browser --level full ## HTML Reports (optimization_engine/reporting/) - Interactive Plotly-based reports - Convergence plot, Pareto front, parallel coordinates - Parameter importance analysis - Self-contained HTML (offline-capable) - Tailwind CSS styling ## Playwright E2E Tests - Home page tests - Test results in test-results/ ## LAC Knowledge Base Updates - Session insights (failures, workarounds, patterns) - Optimization memory for arm support study
172 lines
6.8 KiB
TypeScript
172 lines
6.8 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
|
|
/**
|
|
* Home Page E2E Tests
|
|
*
|
|
* Tests the study list page at /
|
|
* Covers: study loading, topic expansion, navigation
|
|
*/
|
|
|
|
test.describe('Home Page - Study List', () => {
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
// Navigate to home page
|
|
await page.goto('/');
|
|
});
|
|
|
|
test('displays page header', async ({ page }) => {
|
|
// Check header is visible
|
|
await expect(page.locator('header')).toBeVisible();
|
|
|
|
// Check for key header elements - Studies heading (exact match to avoid Inbox Studies)
|
|
await expect(page.getByRole('heading', { name: 'Studies', exact: true })).toBeVisible({ timeout: 10000 });
|
|
});
|
|
|
|
test('shows aggregate statistics cards', async ({ page }) => {
|
|
// Wait for stats to load
|
|
await expect(page.getByText('Total Studies')).toBeVisible();
|
|
await expect(page.getByText('Running')).toBeVisible();
|
|
await expect(page.getByText('Total Trials')).toBeVisible();
|
|
await expect(page.getByText('Best Overall')).toBeVisible();
|
|
});
|
|
|
|
test('loads studies table with topic folders', async ({ page }) => {
|
|
// Wait for studies section (exact match to avoid Inbox Studies)
|
|
await expect(page.getByRole('heading', { name: 'Studies', exact: true })).toBeVisible();
|
|
|
|
// Wait for loading to complete - either see folders or empty state
|
|
// Folders have "trials" text in them
|
|
const folderLocator = page.locator('button:has-text("trials")');
|
|
const emptyStateLocator = page.getByText('No studies found');
|
|
|
|
// Wait for either studies loaded or empty state (10s timeout)
|
|
await expect(folderLocator.first().or(emptyStateLocator)).toBeVisible({ timeout: 10000 });
|
|
});
|
|
|
|
test('expands topic folder to show studies', async ({ page }) => {
|
|
// Wait for folders to load
|
|
const folderButton = page.locator('button:has-text("trials")').first();
|
|
|
|
// Wait for folder to be visible (studies loaded)
|
|
await expect(folderButton).toBeVisible({ timeout: 10000 });
|
|
|
|
// Click to expand
|
|
await folderButton.click();
|
|
|
|
// After expansion, study rows should be visible (they have status badges)
|
|
// Status badges contain: running, completed, idle, paused, not_started
|
|
const statusBadges = page.locator('span:has-text("running"), span:has-text("completed"), span:has-text("idle"), span:has-text("paused"), span:has-text("not_started")');
|
|
await expect(statusBadges.first()).toBeVisible({ timeout: 5000 });
|
|
});
|
|
|
|
test('clicking study shows preview panel', async ({ page }) => {
|
|
// Wait for and expand first folder
|
|
const folderButton = page.locator('button:has-text("trials")').first();
|
|
await expect(folderButton).toBeVisible({ timeout: 10000 });
|
|
await folderButton.click();
|
|
|
|
// Wait for expanded content and click first study row
|
|
const studyRow = page.locator('.bg-dark-850\\/50 > div').first();
|
|
await expect(studyRow).toBeVisible({ timeout: 5000 });
|
|
await studyRow.click();
|
|
|
|
// Preview panel should show with buttons - use exact match to avoid header nav button
|
|
await expect(page.getByRole('button', { name: 'Canvas', exact: true })).toBeVisible({ timeout: 5000 });
|
|
await expect(page.getByRole('button', { name: 'Open' })).toBeVisible();
|
|
});
|
|
|
|
test('Open button navigates to dashboard', async ({ page }) => {
|
|
// Wait for and expand first folder
|
|
const folderButton = page.locator('button:has-text("trials")').first();
|
|
await expect(folderButton).toBeVisible({ timeout: 10000 });
|
|
await folderButton.click();
|
|
|
|
// Wait for and click study row
|
|
const studyRow = page.locator('.bg-dark-850\\/50 > div').first();
|
|
await expect(studyRow).toBeVisible({ timeout: 5000 });
|
|
await studyRow.click();
|
|
|
|
// Wait for and click Open button
|
|
const openButton = page.getByRole('button', { name: 'Open' });
|
|
await expect(openButton).toBeVisible({ timeout: 5000 });
|
|
await openButton.click();
|
|
|
|
// Should navigate to dashboard
|
|
await expect(page).toHaveURL(/\/dashboard/);
|
|
});
|
|
|
|
test('Canvas button navigates to canvas view', async ({ page }) => {
|
|
// Wait for and expand first folder
|
|
const folderButton = page.locator('button:has-text("trials")').first();
|
|
await expect(folderButton).toBeVisible({ timeout: 10000 });
|
|
await folderButton.click();
|
|
|
|
// Wait for and click study row
|
|
const studyRow = page.locator('.bg-dark-850\\/50 > div').first();
|
|
await expect(studyRow).toBeVisible({ timeout: 5000 });
|
|
await studyRow.click();
|
|
|
|
// Wait for and click Canvas button (exact match to avoid header nav)
|
|
const canvasButton = page.getByRole('button', { name: 'Canvas', exact: true });
|
|
await expect(canvasButton).toBeVisible({ timeout: 5000 });
|
|
await canvasButton.click();
|
|
|
|
// Should navigate to canvas
|
|
await expect(page).toHaveURL(/\/canvas\//);
|
|
});
|
|
|
|
test('refresh button reloads studies', async ({ page }) => {
|
|
// Find the main studies section refresh button (the one with visible text "Refresh")
|
|
const refreshButton = page.getByText('Refresh');
|
|
await expect(refreshButton).toBeVisible({ timeout: 5000 });
|
|
|
|
// Click refresh
|
|
await refreshButton.click();
|
|
|
|
// Should show loading state or complete quickly
|
|
// Just verify no errors occurred (exact match to avoid Inbox Studies)
|
|
await expect(page.getByRole('heading', { name: 'Studies', exact: true })).toBeVisible();
|
|
});
|
|
});
|
|
|
|
/**
|
|
* Inbox Section Tests
|
|
*
|
|
* Tests the new study intake workflow
|
|
*/
|
|
test.describe('Home Page - Inbox Section', () => {
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.goto('/');
|
|
});
|
|
|
|
test('displays inbox section with header', async ({ page }) => {
|
|
// Check for Study Inbox heading (section is expanded by default)
|
|
const inboxHeading = page.getByRole('heading', { name: 'Study Inbox' });
|
|
await expect(inboxHeading).toBeVisible({ timeout: 10000 });
|
|
});
|
|
|
|
test('inbox section shows pending count', async ({ page }) => {
|
|
// Section should show pending studies count
|
|
const pendingText = page.getByText(/\d+ pending studies/);
|
|
await expect(pendingText).toBeVisible({ timeout: 10000 });
|
|
});
|
|
|
|
test('inbox has new study button', async ({ page }) => {
|
|
// Section is expanded by default, look for the New Study button
|
|
const newStudyButton = page.getByRole('button', { name: /New Study/ });
|
|
await expect(newStudyButton).toBeVisible({ timeout: 10000 });
|
|
});
|
|
|
|
test('clicking new study shows create form', async ({ page }) => {
|
|
// Click the New Study button
|
|
const newStudyButton = page.getByRole('button', { name: /New Study/ });
|
|
await expect(newStudyButton).toBeVisible({ timeout: 10000 });
|
|
await newStudyButton.click();
|
|
|
|
// Form should expand with input fields
|
|
const studyNameInput = page.getByPlaceholder(/my_study/i).or(page.locator('input[type="text"]').first());
|
|
await expect(studyNameInput).toBeVisible({ timeout: 5000 });
|
|
});
|
|
});
|