import { mkdir, writeFile } from 'node:fs/promises'; import { resolve } from 'node:path'; import { expect, test, type Page, type Route } from '@playwright/test'; const basePath = `/${(process.env.PLAYWRIGHT_GAME_BASE_PATH ?? 'che').replace(/^\/+|\/+$/g, '')}`; const artifactRoot = process.env.LEGACY_LOG_ARTIFACT_DIR ? resolve(process.env.LEGACY_LOG_ARTIFACT_DIR) : null; const response = (data: unknown) => ({ result: { data } }); const operationNames = (route: Route): string[] => { const url = new URL(route.request().url()); return decodeURIComponent(url.pathname.slice(url.pathname.lastIndexOf('/trpc/') + 6)).split(','); }; const history = [ { id: 1, text: '안전 강조', }, { id: 2, text: '
정상 장수' + '강조' + '오염 이름
', }, ]; const publicResponse = (operation: string): unknown => { if (operation === 'public.getMapLayout') return response({ mapName: 'che', cityList: [] }); if (operation === 'public.getCachedMap') { return response({ year: 200, month: 1, cityList: [], nationList: [], history }); } if (operation === 'public.getWorldTrend') return response({ year: 200, month: 1, turnTerm: 10 }); if (operation === 'public.getNationList' || operation === 'public.getGeneralList') return response([]); throw new Error(`Unhandled legacy log fixture operation: ${operation}`); }; const installFixture = async (page: Page) => { await page.addInitScript(() => { delete (globalThis as Record).__legacyLogXss; localStorage.removeItem('sammo-game-token'); }); await page.route(`**${basePath}/api/trpc/**`, async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(operationNames(route).map(publicResponse)), }); }); }; for (const viewport of [ { name: 'desktop', width: 1200, height: 900 }, { name: 'mobile', width: 500, height: 900 }, ] as const) { test(`renders only rebuilt legacy log markup on ${viewport.name}`, async ({ page }) => { await installFixture(page); await page.setViewportSize({ width: viewport.width, height: viewport.height }); await page.goto('public'); const lines = page.locator('.recent-log-line'); await expect(lines).toHaveCount(2); await expect(lines.nth(0).locator('b')).toHaveText('안전 강조'); await expect(lines.nth(1).locator('.small_war_log .war_type_attack')).toHaveText('→'); await expect(lines.nth(1).locator('.ev_highlight')).toHaveText('강조'); await expect(lines.locator('script, img, svg, a, [onerror], [onclick], [style*="url"]')).toHaveCount(0); await expect(lines.nth(0)).toContainText('('b'); return { x: rect.x, y: rect.y, width: rect.width, height: rect.height, color: colorSpan ? getComputedStyle(colorSpan).color : null, fontWeight: bold ? getComputedStyle(bold).fontWeight : null, }; }) ); expect(geometry[0]?.width).toBeGreaterThan(0); expect(geometry[0]?.color).toBe('rgb(255, 0, 0)'); expect(Number(geometry[0]?.fontWeight)).toBeGreaterThanOrEqual(600); const refresh = page.getByRole('button', { name: '새로고침' }); await refresh.focus(); await expect(refresh).toBeFocused(); await refresh.hover(); if (artifactRoot) { await mkdir(artifactRoot, { recursive: true }); const name = `legacy-log-${basePath.slice(1)}-${viewport.name}`; await writeFile( resolve(artifactRoot, `${name}.json`), `${JSON.stringify({ basePath, viewport, geometry }, null, 2)}\n`, 'utf8' ); await page.screenshot({ path: resolve(artifactRoot, `${name}.png`), fullPage: true }); } }); }