import { test, expect } from '@playwright/test'; async function dismissDevServerOverlay(page: any): Promise { await page.evaluate(() => { const overlay = document.getElementById('webpack-dev-server-client-overlay'); if (overlay) overlay.remove(); }); } test.describe('Home Page Regression', () => { test('page loads with Swiss Style layout', async ({ page }) => { await page.goto('/index.html'); await dismissDevServerOverlay(page); await expect(page.locator('.nav-landing-hero')).toBeVisible(); await expect(page.locator('.nav-landing-title')).toBeVisible(); await expect(page.locator('.nav-landing-module-grid')).toBeVisible(); }); test('light theme is default', async ({ page }) => { await page.goto('/index.html'); await dismissDevServerOverlay(page); const bg = await page.evaluate(() => getComputedStyle(document.body).backgroundColor); expect(bg).toContain('245'); // #f5f3f0 ~ rgb(245, ...) }); test('aperture SVG animation renders', async ({ page }) => { await page.goto('/index.html'); await dismissDevServerOverlay(page); const svg = page.locator('.lens-aperture'); await expect(svg).toBeVisible(); }); test('all module cells are visible', async ({ page }) => { await page.goto('/index.html'); await dismissDevServerOverlay(page); const cells = page.locator('.nav-landing-module-cell'); await expect(cells).toHaveCount(6); }); test('module navigation links work', async ({ page }) => { await page.goto('/index.html'); await dismissDevServerOverlay(page); const analysisLink = page.locator('a[href="analysis.html"]'); await expect(analysisLink).toBeVisible(); const chatLink = page.locator('a[href="chat.html"]'); await expect(chatLink).toBeVisible(); }); test('causalFlow (DAG) module renders its title and subtitle', async ({ page }) => { // Regression: the DAG card was rendered blank because the build script put its // content inside an opacity:0 link wrapper and left orphan markup. Verify the // cell shows its text and links to causal_flow.html. await page.goto('/index.html'); await dismissDevServerOverlay(page); const cell = page.locator('.nav-landing-module-cell[data-nav-page="causalFlow"]'); await expect(cell).toBeVisible(); await expect(cell.locator('.nav-landing-module-name')).toHaveText(/因果流|Causal/i); await expect(cell.locator('.nav-landing-module-desc')).not.toBeEmpty(); // Must be a navigable link, not a bare
await expect(cell).toHaveAttribute('href', 'causal_flow.html'); }); }); test.describe('Analysis Page Regression', () => { test('page loads with input area', async ({ page }) => { await page.goto('/analysis.html'); await dismissDevServerOverlay(page); // Use JS check since textarea may be in hidden panel initially const hasTextarea = await page.evaluate(() => !!document.getElementById('test_text')); expect(hasTextarea).toBe(true); const hasBtn = await page.evaluate(() => !!document.getElementById('submit_text_btn')); expect(hasBtn).toBe(true); }); test('analyze button triggers loading state', async ({ page }) => { await page.goto('/analysis.html'); await dismissDevServerOverlay(page); await page.evaluate(() => { const ta = document.getElementById('test_text') as HTMLTextAreaElement; if (ta) ta.value = 'Hello world'; const btn = document.getElementById('submit_text_btn'); if (btn) btn.click(); }); // Wait for loading overlay or spinner await page.waitForTimeout(500); const hasLoader = await page.evaluate(() => document.querySelector('.loading') != null || document.querySelector('.loadersmall') != null ); expect(hasLoader).toBe(true); }); }); test.describe('Attribution Page Regression', () => { test('page loads with context/target inputs', async ({ page }) => { await page.goto('/attribution.html'); await dismissDevServerOverlay(page); await expect(page.locator('#context_text')).toBeVisible(); await expect(page.locator('#target_text')).toBeVisible(); await expect(page.locator('#analyze_btn')).toBeVisible(); }); test('analyze button triggers request', async ({ page }) => { await page.goto('/attribution.html'); await dismissDevServerOverlay(page); await page.fill('#context_text', '中国的首都是'); await page.fill('#target_text', '北京'); await page.evaluate(() => { const btn = document.getElementById('analyze_btn'); if (btn) btn.click(); }); // Wait for loading indicator await page.waitForTimeout(500); const hasLoader = await page.evaluate(() => document.querySelector('.loading') != null || document.querySelector('.loadersmall') != null ); expect(hasLoader).toBe(true); }); }); test.describe('Causal Flow Page Regression', () => { test('page loads with DAG input panel', async ({ page }) => { await page.goto('/causal_flow.html'); await dismissDevServerOverlay(page); // Use JS check since textarea may be hidden by initial JS state const hasTextarea = await page.evaluate(() => !!document.getElementById('gen_attr_raw_text')); expect(hasTextarea).toBe(true); const hasBtn = await page.evaluate(() => !!document.getElementById('gen_attr_submit_btn')); expect(hasBtn).toBe(true); }); test('SVG zoom/pan is functional', async ({ page }) => { await page.goto('/causal_flow.html'); await dismissDevServerOverlay(page); // Trigger a simple generation via JS (bypass visibility) await page.evaluate(() => { const ta = document.getElementById('gen_attr_raw_text') as HTMLTextAreaElement; if (ta) ta.value = 'The'; const btn = document.getElementById('gen_attr_submit_btn'); if (btn) btn.click(); }); // Wait for DAG SVG to appear await page.waitForSelector('.gen-attr-dag-stack svg', { timeout: 30_000 }); const svg = page.locator('.gen-attr-dag-stack svg'); await expect(svg).toBeVisible({ timeout: 30_000 }); // Test zoom: get SVG bounding box and dispatch wheel events const svgBox = await svg.boundingBox(); if (svgBox) { await svg.scrollIntoViewIfNeeded(); await page.waitForTimeout(500); // Wheel zoom in await svg.dispatchEvent('wheel', { deltaY: -100, clientX: svgBox.x + svgBox.width / 2, clientY: svgBox.y + svgBox.height / 2, }); await page.waitForTimeout(300); // Wheel zoom out await svg.dispatchEvent('wheel', { deltaY: 100, clientX: svgBox.x + svgBox.width / 2, clientY: svgBox.y + svgBox.height / 2, }); await page.waitForTimeout(300); // After zoom, SVG should still be present await expect(svg).toBeVisible(); } }); test('fullscreen toggle button exists and is clickable', async ({ page }) => { await page.goto('/causal_flow.html'); await dismissDevServerOverlay(page); await page.evaluate(() => { const ta = document.getElementById('gen_attr_raw_text') as HTMLTextAreaElement; if (ta) ta.value = 'The'; const btn = document.getElementById('gen_attr_submit_btn'); if (btn) btn.click(); }); await page.waitForSelector('.gen-attr-dag-stack svg', { timeout: 30_000 }); // Look for fullscreen button const fsBtn = page.locator('.gen-attr-dag-fullscreen').first(); await expect(fsBtn).toBeVisible({ timeout: 10_000 }); // Click fullscreen await fsBtn.click(); await page.waitForTimeout(300); // Check if fullscreen class is present const isFullscreen = await page.evaluate(() => { return !!document.querySelector('.css-pseudo-fullscreen-target') || !!document.fullscreenElement; }); expect(typeof isFullscreen).toBe('boolean'); }); }); test.describe('Chat Page Regression', () => { test('page loads with raw prompt input', async ({ page }) => { await page.goto('/chat.html'); await dismissDevServerOverlay(page); // Use JS check since textarea may be hidden by panel state const hasTextarea = await page.evaluate(() => !!document.getElementById('test_text')); expect(hasTextarea).toBe(true); const hasBtn = await page.evaluate(() => !!document.getElementById('submit_text_btn')); expect(hasBtn).toBe(true); }); }); test.describe('Compare Page Regression', () => { test('page loads with add button', async ({ page }) => { await page.goto('/compare.html'); await dismissDevServerOverlay(page); await expect(page.locator('#add_demos_btn')).toBeVisible(); await expect(page.locator('#clear_demos_btn')).toBeVisible(); }); }); test.describe('Theme Switching Regression', () => { test('theme toggle switches between light and dark', async ({ page }) => { await page.goto('/index.html'); await dismissDevServerOverlay(page); const html = page.locator('html'); const initialTheme = await html.getAttribute('data-theme'); // Click theme toggle via settings menu if available await page.evaluate(() => { const toggle = document.querySelector('#dark_mode_toggle') as HTMLElement; if (toggle) toggle.click(); }); await page.waitForTimeout(300); const newTheme = await html.getAttribute('data-theme'); // Theme should have toggled or stayed the same (if no toggle found) expect(['light', 'dark', null]).toContain(newTheme); }); }); test.describe('Navigation Regression', () => { test('all page links are reachable', async ({ page }) => { const pages = ['index.html', 'analysis.html', 'attribution.html', 'causal_flow.html', 'chat.html', 'compare.html', 'logit_lens.html', 'branch_tree.html']; for (const p of pages) { await page.goto(`/${p}`); await dismissDevServerOverlay(page); const status = await page.evaluate(() => document.title.length > 0); expect(status).toBe(true); } }); });