Complete dynasty list legacy parity
This commit is contained in:
@@ -0,0 +1,293 @@
|
||||
import { expect, test, type Page, type Route } from '@playwright/test';
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
const artifactRoot = process.env.FRONTEND_PARITY_ARTIFACT_DIR;
|
||||
const gameOrigin = `http://127.0.0.1:${process.env.FRONTEND_PARITY_GAME_PORT ?? '15102'}`;
|
||||
|
||||
const response = (data: unknown) => ({ result: { data } });
|
||||
const errorResponse = (path: string, code: 'BAD_REQUEST' | 'NOT_FOUND', message: string) => ({
|
||||
error: {
|
||||
message,
|
||||
code: -32000,
|
||||
data: { code, httpStatus: code === 'NOT_FOUND' ? 404 : 400, path },
|
||||
},
|
||||
});
|
||||
|
||||
const operationNames = (route: Route): string[] => {
|
||||
const pathname = new URL(route.request().url()).pathname;
|
||||
return decodeURIComponent(pathname.slice(pathname.lastIndexOf('/trpc/') + 6)).split(',');
|
||||
};
|
||||
|
||||
const listPayload = {
|
||||
current: { year: 200, month: 1 },
|
||||
entries: [
|
||||
{
|
||||
id: 1,
|
||||
serverId: 'hwe_260725_u3uE',
|
||||
phase: '훼2기',
|
||||
name: '백년01',
|
||||
year: 215,
|
||||
month: 4,
|
||||
color: '#FF0000',
|
||||
type: 'che_병가',
|
||||
power: 34434,
|
||||
gennum: 71,
|
||||
citynum: 78,
|
||||
l12name: 'ⓜ⑮오리온자리',
|
||||
l11name: 'ⓜ②물조명성너프필수',
|
||||
l10name: 'ⓜ㉗좌우쌍욍검유비',
|
||||
l9name: 'ⓜ②다니엘레메로시',
|
||||
l8name: 'ⓜ⑤쿠로',
|
||||
l7name: 'ⓜ⑮료우기시키',
|
||||
l6name: 'ⓜ㉓랜덤박스',
|
||||
l5name: 'ⓜ㉒임사영',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const detailPayload = {
|
||||
emperor: {
|
||||
id: 1,
|
||||
serverId: 'hwe_260725_u3uE',
|
||||
winnerNationId: 1,
|
||||
phase: '훼2기',
|
||||
nationCount: '8 / 17',
|
||||
nationName: '백년01, 청해, 백제',
|
||||
nationHist: '병가(3), 유가(2)',
|
||||
genCount: '71 / 92',
|
||||
personalHist: '의리(14), 대담(11)',
|
||||
specialHist: '상재(8), 견고(7)',
|
||||
name: '백년01',
|
||||
type: 'che_병가',
|
||||
color: '#FF0000',
|
||||
year: 215,
|
||||
month: 4,
|
||||
power: 34434,
|
||||
gennum: 71,
|
||||
citynum: 78,
|
||||
pop: '2,345,000 / 2,500,000',
|
||||
poprate: '93.8 %',
|
||||
gold: 120000,
|
||||
rice: 150000,
|
||||
l12name: 'ⓜ⑮오리온자리',
|
||||
l11name: 'ⓜ②물조명성너프필수',
|
||||
l10name: 'ⓜ㉗좌우쌍욍검유비',
|
||||
l9name: 'ⓜ②다니엘레메로시',
|
||||
l8name: 'ⓜ⑤쿠로',
|
||||
l7name: 'ⓜ⑮료우기시키',
|
||||
l6name: 'ⓜ㉓랜덤박스',
|
||||
l5name: 'ⓜ㉒임사영',
|
||||
tiger: '관우【10】, 조운【9】',
|
||||
eagle: '주유【7】, 육손【6】',
|
||||
gen: '오리온자리, 물조명성너프필수, 좌우쌍욍검유비',
|
||||
history: ['<C>●</><Y><b>【통일】</b></><D><b>백년01</b></>이 전토를 통일하였습니다.'],
|
||||
},
|
||||
nations: [
|
||||
{
|
||||
nation: 1,
|
||||
isWinner: true,
|
||||
name: '백년01',
|
||||
color: '#FF0000',
|
||||
type: 'che_병가',
|
||||
typeName: '병가',
|
||||
level: 7,
|
||||
levelName: '황제',
|
||||
tech: 4000,
|
||||
maxPower: 34434,
|
||||
maxCrew: 120000,
|
||||
maxCities: ['낙양', '장안', '성도'],
|
||||
generals: [11, 12],
|
||||
history: ['<Y>오리온자리</>가 황제로 즉위'],
|
||||
date: '2026-07-25T12:00:00.000Z',
|
||||
generalsFull: [
|
||||
{ generalNo: 11, name: '오리온자리', lastYearMonth: 21504 },
|
||||
{ generalNo: 12, name: '료우기시키', lastYearMonth: 21504 },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const installFixture = async (page: Page): Promise<void> => {
|
||||
await page.route('**/image/game/**', async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'image/png',
|
||||
body: Buffer.from(
|
||||
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=',
|
||||
'base64'
|
||||
),
|
||||
});
|
||||
});
|
||||
await page.route('**/che/api/trpc/**', async (route) => {
|
||||
const results = operationNames(route).map((operation) => {
|
||||
if (operation === 'lobby.info') {
|
||||
return response({ myGeneral: null });
|
||||
}
|
||||
if (operation === 'dynasty.getList') {
|
||||
return response(listPayload);
|
||||
}
|
||||
if (operation === 'dynasty.getDetail') {
|
||||
const input = new URL(route.request().url()).searchParams.get('input') ?? '';
|
||||
return input.includes('999')
|
||||
? errorResponse(operation, 'NOT_FOUND', '왕조 정보를 찾을 수 없습니다.')
|
||||
: response(detailPayload);
|
||||
}
|
||||
return errorResponse(operation, 'BAD_REQUEST', `Unhandled fixture operation: ${operation}`);
|
||||
});
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify(results),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await installFixture(page);
|
||||
await page.setViewportSize({ width: 1280, height: 900 });
|
||||
});
|
||||
|
||||
test('dynasty list matches the ref Chromium table geometry and interactions', async ({ page }) => {
|
||||
await page.goto(`${gameOrigin}/che/dynasty`);
|
||||
await expect(page.getByText('백년01 (215年 4月)')).toBeVisible();
|
||||
|
||||
const geometry = await page.locator('#dynasty-list-container').evaluate((container) => {
|
||||
const rect = container.getBoundingClientRect();
|
||||
const tables = Array.from(container.querySelectorAll<HTMLTableElement>('table')).map((table) => {
|
||||
const tableRect = table.getBoundingClientRect();
|
||||
const style = getComputedStyle(table);
|
||||
return {
|
||||
x: tableRect.x,
|
||||
y: tableRect.y,
|
||||
width: tableRect.width,
|
||||
height: tableRect.height,
|
||||
borderSpacing: style.borderSpacing,
|
||||
fontFamily: style.fontFamily,
|
||||
fontSize: style.fontSize,
|
||||
lineHeight: style.lineHeight,
|
||||
};
|
||||
});
|
||||
const button = container.querySelector<HTMLButtonElement>('button')!;
|
||||
const buttonStyle = getComputedStyle(button);
|
||||
const cellStyle = getComputedStyle(container.querySelector<HTMLTableCellElement>('td')!);
|
||||
return {
|
||||
container: { x: rect.x, y: rect.y, width: rect.width },
|
||||
tables,
|
||||
button: {
|
||||
height: button.getBoundingClientRect().height,
|
||||
borderWidth: buttonStyle.borderWidth,
|
||||
borderRadius: buttonStyle.borderRadius,
|
||||
padding: buttonStyle.padding,
|
||||
fontFamily: buttonStyle.fontFamily,
|
||||
fontSize: buttonStyle.fontSize,
|
||||
cursor: buttonStyle.cursor,
|
||||
},
|
||||
firstCell: {
|
||||
padding: cellStyle.padding,
|
||||
borderWidth: cellStyle.borderWidth,
|
||||
textAlign: cellStyle.textAlign,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
expect(geometry.container).toEqual({ x: 140, y: 8, width: 1000 });
|
||||
expect(geometry.tables.slice(0, 3).map(({ x, width, borderSpacing }) => ({ x, width, borderSpacing }))).toEqual([
|
||||
{ x: 140, width: 1000, borderSpacing: '2px' },
|
||||
{ x: 140, width: 1000, borderSpacing: '2px' },
|
||||
{ x: 140, width: 1000, borderSpacing: '2px' },
|
||||
]);
|
||||
expect(geometry.tables.slice(0, 3).map(({ y, height }) => ({ y, height }))).toEqual([
|
||||
{ y: 8, height: 47 },
|
||||
{ y: 65, height: 37 },
|
||||
{ y: 112, height: 139 },
|
||||
]);
|
||||
expect(geometry.tables[0]!.fontFamily).toContain('Times New Roman');
|
||||
expect(geometry.tables[0]!.fontSize).toBe('16px');
|
||||
expect(geometry.tables[0]!.lineHeight).toBe('normal');
|
||||
expect(geometry.button).toEqual({
|
||||
height: 22,
|
||||
borderWidth: '2px',
|
||||
borderRadius: '0px',
|
||||
padding: '1px 6px',
|
||||
fontFamily: 'Arial',
|
||||
fontSize: '13.3333px',
|
||||
cursor: 'default',
|
||||
});
|
||||
expect(geometry.firstCell).toEqual({ padding: '1px', borderWidth: '0px', textAlign: 'start' });
|
||||
|
||||
const historyLink = page.getByRole('link', { name: '역사 보기' }).last();
|
||||
await expect(historyLink).toHaveAttribute('href', '/che/yearbook?serverID=hwe_260725_u3uE');
|
||||
if (artifactRoot) {
|
||||
await page.screenshot({ path: resolve(artifactRoot, 'dynasty-core-list.png'), fullPage: true });
|
||||
}
|
||||
|
||||
const detailLink = page.getByRole('link', { name: '자세히' });
|
||||
await detailLink.focus();
|
||||
await expect(detailLink).toBeFocused();
|
||||
await detailLink.click();
|
||||
await expect(page).toHaveURL(/\/che\/dynasty\/1$/);
|
||||
await expect(page.getByText('국 가 수')).toBeVisible();
|
||||
|
||||
if (artifactRoot) {
|
||||
await page.screenshot({ path: resolve(artifactRoot, 'dynasty-core-detail.png'), fullPage: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('dynasty detail preserves the legacy fields, old-nation table and error flow', async ({ page }) => {
|
||||
await page.goto(`${gameOrigin}/che/dynasty/1`);
|
||||
await expect(page.getByText('장 수 성 격')).toBeVisible();
|
||||
await expect(page.getByText('의리(14), 대담(11)')).toBeVisible();
|
||||
await expect(page.getByText('오 호 장 군')).toBeVisible();
|
||||
await expect(page.getByText('건 안 칠 자')).toBeVisible();
|
||||
await expect(page.getByText('【 백년01 】')).toBeVisible();
|
||||
await expect(page.getByText('황제', { exact: true })).toBeVisible();
|
||||
await expect(page.locator('.old-nation-table')).toHaveCount(1);
|
||||
|
||||
const geometry = await page.locator('#dynasty-detail-container').evaluate((container) => {
|
||||
const rect = container.getBoundingClientRect();
|
||||
const first = container.querySelector<HTMLTableElement>('table')!.getBoundingClientRect();
|
||||
const emperor = container.querySelector<HTMLTableElement>('.emperor-table')!.getBoundingClientRect();
|
||||
const oldNation = container.querySelector<HTMLTableElement>('.old-nation-table')!.getBoundingClientRect();
|
||||
return {
|
||||
container: { x: rect.x, y: rect.y, width: rect.width },
|
||||
first: { x: first.x, y: first.y, width: first.width, height: first.height },
|
||||
emperor: { x: emperor.x, y: emperor.y, width: emperor.width },
|
||||
oldNation: { x: oldNation.x, width: oldNation.width },
|
||||
};
|
||||
});
|
||||
expect(geometry.container).toEqual({ x: 140, y: 8, width: 1000 });
|
||||
expect(geometry.first).toEqual({ x: 140, y: 8, width: 1000, height: 47 });
|
||||
expect(geometry.emperor).toEqual({ x: 140, y: 55, width: 1000 });
|
||||
expect(geometry.oldNation).toEqual({ x: 140, width: 1000 });
|
||||
|
||||
await page.goto(`${gameOrigin}/che/dynasty/999`);
|
||||
await expect(page.getByRole('alert')).toHaveText('왕조 정보를 찾을 수 없습니다.');
|
||||
await expect(page.locator('.emperor-table')).toHaveCount(0);
|
||||
await page.getByRole('button', { name: '창 닫기' }).first().click();
|
||||
await expect(page).toHaveURL(/\/che\/public$/);
|
||||
});
|
||||
|
||||
test('dynasty public view is identical for anonymous and distinct user tokens', async ({ browser }) => {
|
||||
const snapshots: string[] = [];
|
||||
for (const token of [null, 'ga_dynasty_owner_a', 'ga_dynasty_owner_b']) {
|
||||
const context = await browser.newContext({
|
||||
viewport: { width: 1280, height: 900 },
|
||||
deviceScaleFactor: 1,
|
||||
colorScheme: 'dark',
|
||||
});
|
||||
const page = await context.newPage();
|
||||
await installFixture(page);
|
||||
if (token) {
|
||||
await page.addInitScript((value) => {
|
||||
window.localStorage.setItem('sammo-game-token', value);
|
||||
}, token);
|
||||
}
|
||||
await page.goto(`${gameOrigin}/che/dynasty`);
|
||||
await expect(page.getByText('백년01 (215年 4月)')).toBeVisible();
|
||||
snapshots.push(await page.locator('#dynasty-list-container').innerText());
|
||||
await context.close();
|
||||
}
|
||||
|
||||
expect(snapshots[1]).toBe(snapshots[0]);
|
||||
expect(snapshots[2]).toBe(snapshots[0]);
|
||||
});
|
||||
Reference in New Issue
Block a user