explorer.spec.ts (11480B)
1 import { test, expect } from '@playwright/test'; 2 3 test.describe('Dashboard', () => { 4 test('loads and shows headline stats', async ({ page }) => { 5 await page.goto('/'); 6 await expect(page.locator('.card .value').first()).toBeVisible({ timeout: 10000 }); 7 const cards = page.locator('.card'); 8 await expect(cards).toHaveCount(4); 9 await expect(cards.nth(0).locator('.value')).toHaveText('1047'); 10 await expect(cards.nth(1).locator('.value')).toHaveText('47.2%'); 11 }); 12 13 test('shows spinner then content', async ({ page }) => { 14 await page.goto('/'); 15 // Spinner should appear briefly (may be too fast to catch, but structure exists) 16 await expect(page.locator('.card .value').first()).toBeVisible({ timeout: 10000 }); 17 }); 18 19 test('shows histogram', async ({ page }) => { 20 await page.goto('/'); 21 await expect(page.locator('.card .value').first()).toBeVisible({ timeout: 10000 }); 22 const bars = page.locator('svg .bar'); 23 expect(await bars.count()).toBeGreaterThan(5); 24 await expect(page.locator('svg .bar[height]:not([height="0"])')).not.toHaveCount(0); 25 }); 26 27 test('shows category pass rates', async ({ page }) => { 28 await page.goto('/'); 29 await expect(page.locator('.card .value').first()).toBeVisible({ timeout: 10000 }); 30 const hbars = page.locator('.hbar'); 31 expect(await hbars.count()).toBeGreaterThan(5); 32 }); 33 34 test('shows year trends chart', async ({ page }) => { 35 await page.goto('/'); 36 await expect(page.locator('.card .value').first()).toBeVisible({ timeout: 10000 }); 37 await expect(page.locator('.trend-chart')).toBeVisible(); 38 }); 39 40 test('shows pipeline progress bar', async ({ page }) => { 41 await page.goto('/'); 42 await expect(page.locator('.pipeline-bar')).toBeVisible({ timeout: 10000 }); 43 await expect(page.locator('.pipeline-stat')).toContainText('of'); 44 await expect(page.locator('.pipeline-seg.deprecated')).toBeVisible(); 45 }); 46 47 test('shows named games', async ({ page }) => { 48 await page.goto('/'); 49 await expect(page.locator('.card .value').first()).toBeVisible({ timeout: 10000 }); 50 const games = page.locator('.game-row'); 51 expect(await games.count()).toBeGreaterThan(2); 52 }); 53 }); 54 55 test.describe('Papers Browser', () => { 56 test('shows paper table', async ({ page }) => { 57 await page.goto('/#/papers'); 58 await expect(page.locator('table tbody tr').first()).toBeVisible({ timeout: 10000 }); 59 expect(await page.locator('table tbody tr').count()).toBe(2688); 60 await expect(page.locator('#f-count')).toHaveText('2688 / 2688'); 61 }); 62 63 test('text search filters papers', async ({ page }) => { 64 await page.goto('/#/papers'); 65 await expect(page.locator('table tbody tr').first()).toBeVisible({ timeout: 10000 }); 66 await page.fill('#f-search', 'metr'); 67 const count = await page.locator('table tbody tr').count(); 68 expect(count).toBeGreaterThan(0); 69 expect(count).toBeLessThan(2688); 70 }); 71 72 test('archetype filter works', async ({ page }) => { 73 await page.goto('/#/papers'); 74 await expect(page.locator('table tbody tr').first()).toBeVisible({ timeout: 10000 }); 75 await page.selectOption('#f-archetype', 'Complete'); 76 const count = await page.locator('table tbody tr').count(); 77 expect(count).toBeGreaterThan(0); 78 expect(count).toBeLessThan(2688); 79 }); 80 81 test('clicking row navigates to paper detail', async ({ page }) => { 82 await page.goto('/#/papers'); 83 await expect(page.locator('table tbody tr').first()).toBeVisible({ timeout: 10000 }); 84 await page.locator('table tbody tr').first().click(); 85 await expect(page.locator('.paper-header h2')).toBeVisible({ timeout: 10000 }); 86 }); 87 88 test('sort by score works', async ({ page }) => { 89 await page.goto('/#/papers'); 90 await expect(page.locator('table tbody tr').first()).toBeVisible({ timeout: 10000 }); 91 await page.locator('thead th', { hasText: 'Score' }).click(); 92 const firstScore = await page.locator('table tbody tr').first().locator('td.score').textContent(); 93 // First entry is "--" (unscanned) or a low score 94 expect(firstScore!.trim()).toMatch(/^--|^\d/); 95 }); 96 }); 97 98 test.describe('Paper Detail', () => { 99 test('shows METR paper detail with links', async ({ page }) => { 100 await page.goto('/#/paper/metr-rct-2025'); 101 await expect(page.locator('.paper-header h2')).toBeVisible({ timeout: 10000 }); 102 await expect(page.locator('.paper-header h2')).toContainText('Measuring the Impact'); 103 await expect(page.locator('.paper-meta')).toContainText('69.8%'); 104 // Should have arXiv link 105 await expect(page.locator('.paper-links a', { hasText: 'arXiv' })).toBeVisible(); 106 }); 107 108 test('shows checklist items', async ({ page }) => { 109 await page.goto('/#/paper/metr-rct-2025'); 110 await expect(page.locator('.checklist-category').first()).toBeVisible({ timeout: 10000 }); 111 const items = page.locator('.checklist-item'); 112 expect(await items.count()).toBeGreaterThan(30); 113 }); 114 115 test('shows claims', async ({ page }) => { 116 await page.goto('/#/paper/metr-rct-2025'); 117 await expect(page.locator('.claim').first()).toBeVisible({ timeout: 10000 }); 118 expect(await page.locator('.claim').count()).toBeGreaterThan(3); 119 }); 120 121 test('shows red flags', async ({ page }) => { 122 await page.goto('/#/paper/metr-rct-2025'); 123 await expect(page.locator('.red-flag').first()).toBeVisible({ timeout: 10000 }); 124 expect(await page.locator('.red-flag').count()).toBeGreaterThan(2); 125 }); 126 127 test('back link works', async ({ page }) => { 128 await page.goto('/#/paper/metr-rct-2025'); 129 await expect(page.locator('.back-link')).toBeVisible({ timeout: 10000 }); 130 await page.locator('.back-link').click(); 131 await expect(page.locator('table tbody tr').first()).toBeVisible({ timeout: 10000 }); 132 }); 133 134 test('shows 404 for unknown paper', async ({ page }) => { 135 await page.goto('/#/paper/nonexistent-paper-9999'); 136 await expect(page.locator('text=Paper not found')).toBeVisible({ timeout: 10000 }); 137 }); 138 }); 139 140 test.describe('Citation Network', () => { 141 test('renders canvas', async ({ page }) => { 142 await page.goto('/#/network'); 143 await expect(page.locator('#network-canvas')).toBeVisible({ timeout: 10000 }); 144 }); 145 146 test('shows node count', async ({ page }) => { 147 await page.goto('/#/network'); 148 await expect(page.locator('#net-count')).toBeVisible({ timeout: 10000 }); 149 const text = await page.locator('#net-count').textContent(); 150 expect(text).toMatch(/\d+ nodes/); 151 }); 152 153 test('min connections filter works', async ({ page }) => { 154 await page.goto('/#/network'); 155 await expect(page.locator('#net-count')).toBeVisible({ timeout: 10000 }); 156 const before = await page.locator('#net-count').textContent(); 157 await page.fill('#net-min-conn', '5'); 158 await page.waitForTimeout(500); 159 const after = await page.locator('#net-count').textContent(); 160 const beforeNodes = parseInt(before!.match(/(\d+) nodes/)![1]); 161 const afterNodes = parseInt(after!.match(/(\d+) nodes/)![1]); 162 expect(afterNodes).toBeLessThan(beforeNodes); 163 }); 164 }); 165 166 test.describe('Tensions', () => { 167 test('shows three tension groups', async ({ page }) => { 168 await page.goto('/#/tensions'); 169 await expect(page.locator('.tension-group').first()).toBeVisible({ timeout: 10000 }); 170 expect(await page.locator('.tension-group').count()).toBe(6); 171 }); 172 173 test('shows tension claims', async ({ page }) => { 174 await page.goto('/#/tensions'); 175 await expect(page.locator('.tension-claim').first()).toBeVisible({ timeout: 10000 }); 176 expect(await page.locator('.tension-claim').count()).toBeGreaterThan(5); 177 }); 178 }); 179 180 test.describe('Navigation', () => { 181 test('nav links work', async ({ page }) => { 182 await page.goto('/'); 183 await expect(page.locator('.card .value').first()).toBeVisible({ timeout: 10000 }); 184 185 await page.locator('nav a', { hasText: 'Papers' }).click(); 186 await expect(page.locator('table')).toBeVisible({ timeout: 10000 }); 187 188 await page.locator('nav a', { hasText: 'Network' }).click(); 189 await expect(page.locator('#network-canvas')).toBeVisible({ timeout: 10000 }); 190 191 await page.locator('nav a', { hasText: 'Tensions' }).click(); 192 await expect(page.locator('.tension-group').first()).toBeVisible({ timeout: 10000 }); 193 194 await page.locator('nav a', { hasText: 'Dashboard' }).click(); 195 await expect(page.locator('.card .value').first()).toBeVisible({ timeout: 10000 }); 196 }); 197 198 test('active nav state updates', async ({ page }) => { 199 await page.goto('/#/papers'); 200 await expect(page.locator('table tbody tr').first()).toBeVisible({ timeout: 10000 }); 201 await expect(page.locator('nav a.active')).toHaveText('Papers'); 202 }); 203 }); 204 205 test.describe('Findings', () => { 206 test('loads and shows all sections', async ({ page }) => { 207 await page.goto('/#/findings'); 208 await expect(page.locator('.section').first()).toBeVisible({ timeout: 10000 }); 209 // Should have 17 sections 210 expect(await page.locator('.section').count()).toBe(17); 211 }); 212 213 test('shows per-question pass rates', async ({ page }) => { 214 await page.goto('/#/findings'); 215 await expect(page.locator('.section').first()).toBeVisible({ timeout: 10000 }); 216 await expect(page.locator('h2', { hasText: 'Per-Question Pass Rates' })).toBeVisible(); 217 // Should have horizontal bars 218 expect(await page.locator('.section').first().locator('.hbar').count()).toBeGreaterThan(10); 219 }); 220 221 test('shows year category trends with toggles', async ({ page }) => { 222 await page.goto('/#/findings'); 223 await expect(page.locator('#cat-toggles')).toBeVisible({ timeout: 10000 }); 224 const activeToggles = page.locator('.toggle-btn.active'); 225 expect(await activeToggles.count()).toBe(4); 226 // Click a toggle to deactivate 227 await activeToggles.first().click(); 228 expect(await page.locator('.toggle-btn.active').count()).toBe(3); 229 }); 230 231 test('shows funding gap', async ({ page }) => { 232 await page.goto('/#/findings'); 233 await expect(page.locator('h2', { hasText: 'Funding Disclosure Gap' })).toBeVisible({ timeout: 10000 }); 234 await expect(page.locator('text=pp gap')).toBeVisible(); 235 }); 236 237 test('shows reproducibility drill-down', async ({ page }) => { 238 await page.goto('/#/findings'); 239 await expect(page.locator('h2', { hasText: 'Reproducibility Drill-Down' })).toBeVisible({ timeout: 10000 }); 240 await expect(page.locator('text=fully reproducible')).toBeVisible(); 241 }); 242 243 test('shows 6 named games', async ({ page }) => { 244 await page.goto('/#/findings'); 245 const gamesSection = page.locator('.section', { has: page.locator('h2', { hasText: 'Named Games' }) }); 246 await expect(gamesSection).toBeVisible({ timeout: 10000 }); 247 expect(await gamesSection.locator('.game-row').count()).toBe(10); 248 }); 249 }); 250 251 test.describe('Theme', () => { 252 test('toggle switches theme', async ({ page }) => { 253 await page.goto('/'); 254 await expect(page.locator('.card .value').first()).toBeVisible({ timeout: 10000 }); 255 const themeBefore = await page.locator('html').getAttribute('data-theme'); 256 expect(themeBefore).toMatch(/^(dark|light)$/); 257 // Click toggle — should switch 258 await page.click('#theme-toggle'); 259 const themeAfter = await page.locator('html').getAttribute('data-theme'); 260 expect(themeAfter).not.toBe(themeBefore); 261 // Toggle back 262 await page.click('#theme-toggle'); 263 const themeBack = await page.locator('html').getAttribute('data-theme'); 264 expect(themeBack).toBe(themeBefore); 265 }); 266 });