Spaces:
Running
Running
| import { test, expect } from '@playwright/test'; | |
| async function dismissDevServerOverlay(page: any): Promise<void> { | |
| 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 <div> | |
| 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); | |
| } | |
| }); | |
| }); | |