Files
core2026/app/game-frontend/e2e/pastPlays.spec.ts
T

123 lines
5.3 KiB
TypeScript

import { mkdir, writeFile } from 'node:fs/promises';
import { resolve } from 'node:path';
import { expect, test, type Page, type Route } from '@playwright/test';
const response = (data: unknown) => ({ result: { data } });
const operationNames = (route: Route) =>
decodeURIComponent(new URL(route.request().url()).pathname.split('/trpc/')[1] ?? '').split(',');
const installArchive = async (page: Page) => {
await page.addInitScript(() => {
localStorage.setItem('sammo-game-token', 'ga_archive');
localStorage.setItem('sammo-game-profile', 'che:default');
});
await page.route('**/che/api/trpc/**', async (route) => {
const results = operationNames(route).map((operation) => {
if (operation === 'lobby.info') return response({ myGeneral: null });
if (operation === 'archive.myPastPlays') {
return response({
seasons: [
{
serverId: 'che_2024_01',
date: '2024-01-31T00:00:00.000Z',
season: 51,
scenario: 2,
scenarioName: '천하쟁패',
generals: [
{
generalNo: 17,
name: '관우',
lastYearMonth: 21403,
nationId: 2,
nationName: '촉',
nationColor: '#800000',
leadership: 91,
strength: 98,
intel: 77,
experience: 23000,
dedication: 1200,
officerLevel: 12,
personal: '대담',
special: '상재',
special2: '신산',
},
],
},
],
});
}
return { error: { message: `unhandled ${operation}`, data: { code: 'BAD_REQUEST' } } };
});
await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(results) });
});
};
test('past plays is available without a current general and preserves desktop interaction geometry', async ({
page,
}) => {
await installArchive(page);
await page.setViewportSize({ width: 1200, height: 900 });
await page.goto('past-plays');
const root = page.locator('.past-plays-page');
await expect(root).toBeVisible();
await expect(page.getByRole('heading', { name: '내 지난 플레이 보기' })).toBeVisible();
await expect(page.getByText('천하쟁패 · 51기')).toBeVisible();
await expect(page.locator('.general-name')).toHaveText('관우');
const geometry = await root.evaluate((element) => {
const rect = element.getBoundingClientRect();
const titleRect = element.querySelector('.title-row')!.getBoundingClientRect();
const tableRect = element.querySelector('table')!.getBoundingClientRect();
const style = getComputedStyle(element);
return {
x: rect.x,
width: rect.width,
minHeight: rect.height,
title: { x: titleRect.x, y: titleRect.y, width: titleRect.width },
tableWidth: tableRect.width,
color: style.color,
backgroundColor: style.backgroundColor,
};
});
expect(geometry).toMatchObject({
x: 100,
width: 1000,
title: { x: 100, y: 0, width: 1000 },
tableWidth: 1000,
color: 'rgb(238, 238, 238)',
backgroundColor: 'rgb(21, 21, 21)',
});
const refresh = page.getByRole('button', { name: '새로고침' });
await refresh.hover();
expect(await refresh.evaluate((element) => getComputedStyle(element).color)).toBe('rgb(135, 206, 235)');
await refresh.focus();
expect(await refresh.evaluate((element) => document.activeElement === element)).toBe(true);
const artifactRoot = process.env.PAST_PLAYS_ARTIFACT_DIR;
if (artifactRoot) {
const output = resolve(artifactRoot);
await mkdir(output, { recursive: true });
await writeFile(resolve(output, 'desktop-computed-dom.json'), `${JSON.stringify(geometry, null, 2)}\n`);
await page.screenshot({ path: resolve(output, 'desktop.png'), fullPage: true });
}
});
test('past plays keeps the legacy-width table scrollable on a mobile viewport', async ({ page }) => {
await installArchive(page);
await page.setViewportSize({ width: 390, height: 844 });
await page.goto('past-plays');
const scroll = page.locator('.table-scroll');
const metrics = await scroll.evaluate((element) => ({
clientWidth: element.clientWidth,
scrollWidth: element.scrollWidth,
overflowX: getComputedStyle(element).overflowX,
}));
// The shared legacy shell keeps its historical 500 px minimum canvas.
expect(metrics).toEqual({ clientWidth: 500, scrollWidth: 820, overflowX: 'auto' });
await expect(page.locator('.title-row')).toHaveCSS('flex-direction', 'column');
});