From 34a88e5c809761b812fc07cc77278aa251985e6d Mon Sep 17 00:00:00 2001 From: hided62 Date: Fri, 31 Jul 2026 17:28:34 +0000 Subject: [PATCH] test: restore game frontend session fixtures --- app/game-frontend/e2e/auction.spec.ts | 5 +- app/game-frontend/e2e/board.spec.ts | 18 +- .../e2e/commandArguments.spec.ts | 185 +++++++++++------- app/game-frontend/e2e/inGameMenus.spec.ts | 4 + app/game-frontend/e2e/mainNavigation.spec.ts | 1 + .../e2e/nationGeneralSecret.spec.ts | 1 + app/game-frontend/e2e/npcPolicy.spec.ts | 1 + app/game-frontend/e2e/npcPossession.spec.ts | 1 + app/game-frontend/e2e/pastPlays.spec.ts | 1 + app/game-frontend/e2e/troop.spec.ts | 5 +- 10 files changed, 142 insertions(+), 80 deletions(-) diff --git a/app/game-frontend/e2e/auction.spec.ts b/app/game-frontend/e2e/auction.spec.ts index ecc4b2a..576afc9 100644 --- a/app/game-frontend/e2e/auction.spec.ts +++ b/app/game-frontend/e2e/auction.spec.ts @@ -5,9 +5,7 @@ import { fileURLToPath } from 'node:url'; const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../../..'); const imageRoots = [ - ...(process.env.FRONTEND_PARITY_IMAGE_ROOT - ? [resolve(process.env.FRONTEND_PARITY_IMAGE_ROOT, 'game')] - : []), + ...(process.env.FRONTEND_PARITY_IMAGE_ROOT ? [resolve(process.env.FRONTEND_PARITY_IMAGE_ROOT, 'game')] : []), resolve(repositoryRoot, '../image/game'), resolve(repositoryRoot, '../../image/game'), ]; @@ -195,6 +193,7 @@ const installFixture = async (page: Page, state: AuctionFixture) => { } await page.route('**/che/api/trpc/**', async (route) => { const results = operationNames(route).map((operation) => { + if (operation === 'auth.status') return response({ ok: true }); if (operation === 'lobby.info') { return response({ myGeneral: { id: 7, name: '유비' } }); } diff --git a/app/game-frontend/e2e/board.spec.ts b/app/game-frontend/e2e/board.spec.ts index e853fa1..8cfb15f 100644 --- a/app/game-frontend/e2e/board.spec.ts +++ b/app/game-frontend/e2e/board.spec.ts @@ -150,6 +150,7 @@ const installApi = async (page: Page, state: BoardFixture) => { const operations = operationNames(route); const results = operations.map((operation) => { state.requests.push(operation); + if (operation === 'auth.status') return response({ ok: true }); if (operation === 'lobby.info') { return response({ year: 197, @@ -227,8 +228,7 @@ const installApi = async (page: Page, state: BoardFixture) => { if (operation === 'messages.getContacts') return response({ nation: [] }); if (operation === 'turns.reserved.getGeneral') return response({ turns: [], revision: 0 }); if (operation === 'turns.reserved.getNation') return response({ turns: [], revision: 0 }); - if (operation === 'general.getRecentRecords') - return response({ global: [], general: [], history: [] }); + if (operation === 'general.getRecentRecords') return response({ global: [], general: [], history: [] }); if (operation === 'general.getFrontStatus') return response({ onlineUserCount: 1, @@ -443,9 +443,9 @@ test('denies direct secret-room rendering and disables its in-game menu for an o state.requests.length = 0; await page.goto(''); - await expect(page.getByRole('link', { name: '회의실', exact: true })).toBeVisible(); - await expect(page.locator('.game-shell__actions [aria-disabled="true"]').filter({ hasText: '기밀실' })).toBeVisible(); - await expect(page.getByRole('link', { name: '기밀실', exact: true })).toHaveCount(0); + const nationMenu = page.locator('.main-nation-menu').first(); + await expect(nationMenu.locator('[data-navigation-id="meeting"]')).toHaveAttribute('href', '/che/board'); + await expect(nationMenu.locator('[data-navigation-id="secret-board"]')).toHaveAttribute('aria-disabled', 'true'); }); test('enables both in-game menu entries for a chief or delegated secret role', async ({ page }) => { @@ -458,6 +458,10 @@ test('enables both in-game menu entries for a chief or delegated secret role', a }; await installApi(page, state); await page.goto(''); - await expect(page.getByRole('link', { name: '회의실', exact: true })).toBeVisible(); - await expect(page.getByRole('link', { name: '기밀실', exact: true })).toBeVisible(); + const nationMenu = page.locator('.main-nation-menu').first(); + await expect(nationMenu.locator('[data-navigation-id="meeting"]')).toHaveAttribute('href', '/che/board'); + await expect(nationMenu.locator('[data-navigation-id="secret-board"]')).toHaveAttribute( + 'href', + '/che/board/secret' + ); }); diff --git a/app/game-frontend/e2e/commandArguments.spec.ts b/app/game-frontend/e2e/commandArguments.spec.ts index 330217d..aa295f1 100644 --- a/app/game-frontend/e2e/commandArguments.spec.ts +++ b/app/game-frontend/e2e/commandArguments.spec.ts @@ -31,60 +31,84 @@ const inputOptions = { items: { horse: [{ value: 'None', label: '판매/해제' }] }, }; const commandTable = { - general: [{ - category: '군사', - values: [{ - key: 'che_화계', - name: '화계', - reqArg: true, - possible: true, - status: 'needsInput', - inputFields: [{ - key: 'destCityId', - label: '대상 도시', - kind: 'select', - required: true, - optionSource: 'cities', - }], - }], - }], - nation: [{ - category: '인사', - values: [{ - key: 'che_포상', - name: '포상', - reqArg: true, - possible: true, - status: 'needsInput', - inputFields: [ - { key: 'isGold', label: '물자', kind: 'boolean', required: true }, - { key: 'amount', label: '수량', kind: 'number', required: true, min: 0, step: 1 }, + general: [ + { + category: '군사', + values: [ { - key: 'destGeneralId', - label: '대상 장수', - kind: 'select', - required: true, - optionSource: 'generals', + key: 'che_화계', + name: '화계', + reqArg: true, + possible: true, + status: 'needsInput', + inputFields: [ + { + key: 'destCityId', + label: '대상 도시', + kind: 'select', + required: true, + optionSource: 'cities', + }, + ], }, ], - }], - }], + }, + ], + nation: [ + { + category: '인사', + values: [ + { + key: 'che_포상', + name: '포상', + reqArg: true, + possible: true, + status: 'needsInput', + inputFields: [ + { key: 'isGold', label: '물자', kind: 'boolean', required: true }, + { key: 'amount', label: '수량', kind: 'number', required: true, min: 0, step: 1 }, + { + key: 'destGeneralId', + label: '대상 장수', + kind: 'select', + required: true, + optionSource: 'generals', + }, + ], + }, + ], + }, + ], inputOptions, }; const generalContext = { general: { - id: 1, name: '장수', nationId: 1, cityId: 1, officerLevel: 5, npcState: 0, troopId: 0, - picture: null, imageServer: 0, stats: { leadership: 70, strength: 60, intelligence: 50 }, - gold: 1000, rice: 1000, crew: 500, train: 90, atmos: 90, injury: 0, experience: 0, - dedication: 0, items: { horse: 'None', weapon: 'None', book: 'None', item: 'None' }, + id: 1, + name: '장수', + nationId: 1, + cityId: 1, + officerLevel: 5, + npcState: 0, + troopId: 0, + picture: null, + imageServer: 0, + stats: { leadership: 70, strength: 60, intelligence: 50 }, + gold: 1000, + rice: 1000, + crew: 500, + train: 90, + atmos: 90, + injury: 0, + experience: 0, + dedication: 0, + items: { horse: 'None', weapon: 'None', book: 'None', item: 'None' }, }, city: { id: 1, name: '업', level: 8, region: 1, population: 1000, populationMax: 2000 }, nation: { id: 1, name: '아국', color: '#008000', level: 1 }, settings: {}, penalties: {}, }; -const turns = (count: number) => - Array.from({ length: count }, (_, index) => ({ index, action: '휴식', args: {} })); +const turns = (count: number) => Array.from({ length: count }, (_, index) => ({ index, action: '휴식', args: {} })); const chiefCenter = { me: { id: 1, officerLevel: 5, nationId: 1 }, nation: { id: 1, name: '아국', level: 1 }, @@ -114,28 +138,45 @@ const install = async (page: Page, rejectGeneral = false) => { const body = route.request().postDataJSON(); const results = names.map((name) => { if (name === 'general.me') return response(generalContext); - if (name === 'world.getMapLayout') return response({ - mapName: 'che', - cityList: [{ id: 1, name: '업', level: 8, region: 1, x: 100, y: 100, path: [] }], - regionMap: { 1: '하북' }, - levelMap: { 8: '특' }, - }); - if (name === 'lobby.info') return response({ - myGeneral: { id: 1, name: '장수' }, - year: 200, month: 1, turnTerm: 10, userCnt: 1, maxUserCnt: 100, npcCnt: 0, nationCnt: 2, - }); + if (name === 'world.getMapLayout') + return response({ + mapName: 'che', + cityList: [{ id: 1, name: '업', level: 8, region: 1, x: 100, y: 100, path: [] }], + regionMap: { 1: '하북' }, + levelMap: { 8: '특' }, + }); + if (name === 'auth.status') return response({ ok: true }); + if (name === 'lobby.info') + return response({ + myGeneral: { id: 1, name: '장수' }, + year: 200, + month: 1, + turnTerm: 10, + userCnt: 1, + maxUserCnt: 100, + npcCnt: 0, + nationCnt: 2, + }); if (name === 'join.getConfig') return response({}); - if (name === 'world.getMap') return response({ - result: true, version: 0, startYear: 180, year: 200, month: 1, - cityList: [[1, 8, 0, 1, 1, 1]], nationList: [[1, '아국', '#008000', 1]], - spyList: {}, shownByGeneralList: [], myCity: 1, myNation: 1, - }); + if (name === 'world.getMap') + return response({ + result: true, + version: 0, + startYear: 180, + year: 200, + month: 1, + cityList: [[1, 8, 0, 1, 1, 1]], + nationList: [[1, '아국', '#008000', 1]], + spyList: {}, + shownByGeneralList: [], + myCity: 1, + myNation: 1, + }); if (name === 'turns.getCommandTable') return response(commandTable); if (name === 'nation.getChiefCenter') return response(chiefCenter); if (name === 'turns.reserved.getGeneral') return response({ turns: turns(30), revision: 0 }); if (name === 'turns.reserved.getNation') return response({ turns: turns(12), revision: 0 }); - if (name === 'general.getRecentRecords') - return response({ global: [], general: [], history: [] }); + if (name === 'general.getRecentRecords') return response({ global: [], general: [], history: [] }); if (name === 'general.getFrontStatus') return response({ onlineUserCount: 1, @@ -145,12 +186,17 @@ const install = async (page: Page, rejectGeneral = false) => { lastExecuted: null, latestVote: null, }); - if (name === 'messages.getRecent') return response({ - private: [], national: [], public: [], diplomacy: [], sequence: -1, - hasMore: { private: false, national: false, public: false, diplomacy: false }, - latestRead: { private: 0, diplomacy: 0 }, - canRespondDiplomacy: false, - }); + if (name === 'messages.getRecent') + return response({ + private: [], + national: [], + public: [], + diplomacy: [], + sequence: -1, + hasMore: { private: false, national: false, public: false, diplomacy: false }, + latestRead: { private: 0, diplomacy: 0 }, + canRespondDiplomacy: false, + }); if (name === 'messages.getContacts') return response({ nation: [] }); if (name === 'board.getAccess') return response({ canMeeting: false, canSecret: false }); if (name === 'tournament.getState') return response({ stage: 0 }); @@ -222,8 +268,12 @@ test('keeps the entered command visible and reports a server validation error', await page.goto('/'); await page.getByRole('button', { name: /화계/ }).click(); await page.getByTestId('command-argument-form').locator('select').selectOption('2'); - await page.locator('.reserved-section').filter({ hasText: '일반 예턴' }) - .getByRole('button', { name: '배치' }).first().click(); + await page + .locator('.reserved-section') + .filter({ hasText: '일반 예턴' }) + .getByRole('button', { name: '배치' }) + .first() + .click(); await expect(page.getByRole('alert')).toContainText('대상 도시를 선택할 수 없습니다.'); await expect(page.getByTestId('command-argument-form').locator('select')).toHaveValue('2'); @@ -275,7 +325,8 @@ test('keeps the shared main and chief shell geometry and interaction states', as expect(await mainAction.evaluate((element) => document.activeElement === element)).toBe(true); await page.setViewportSize({ width: 1200, height: 900 }); - await page.goto('chief-center'); + await page.locator('.main-nation-menu').first().locator('[data-navigation-id="chief-center"]').click(); + await expect(page).toHaveURL(/\/che\/chief-center$/); await expect(page.getByRole('heading', { name: '사령부', exact: true })).toBeVisible(); const chiefDesktop = await page.locator('.chief-page').evaluate((element) => ({ width: element.getBoundingClientRect().width, diff --git a/app/game-frontend/e2e/inGameMenus.spec.ts b/app/game-frontend/e2e/inGameMenus.spec.ts index 7d0420f..2792f0c 100644 --- a/app/game-frontend/e2e/inGameMenus.spec.ts +++ b/app/game-frontend/e2e/inGameMenus.spec.ts @@ -163,6 +163,7 @@ const install = async (page: Page, state: FixtureState) => { rawPayload && typeof rawPayload === 'object' ? (rawPayload as TrpcRequestPayload) : undefined; const jsonInput = payload?.json ?? payload?.input?.json ?? (payload as Record | undefined) ?? {}; + if (operation === 'auth.status') return response({ ok: true }); if (operation === 'lobby.info') return response({ myGeneral: { id: 7, name: '검증장수' } }); if (operation === 'join.getConfig') return response({}); if (operation === 'general.me') { @@ -682,6 +683,7 @@ test('가오픈 장수 삭제는 레거시 표시와 확인을 보존하고 time expect(dialogs[0]).toBe('confirm:정말로 삭제하시겠습니까?'); expect(state.dieOnPrestartInputs).toHaveLength(0); + const timeoutReload = page.waitForEvent('framenavigated', (frame) => frame === page.mainFrame()); await deleteButton.click(); await expect.poll(() => state.dieOnPrestartInputs?.length).toBe(1); await expect @@ -691,6 +693,8 @@ test('가오픈 장수 삭제는 레거시 표시와 확인을 보존하고 time ) ) .toBe(true); + await timeoutReload; + await page.waitForLoadState('networkidle'); await expect(deleteButton).toBeVisible(); const pendingRequestId = await page.evaluate(() => sessionStorage.getItem('sam.pending.dieOnPrestart')); expect(pendingRequestId).toEqual(expect.stringMatching(/^[0-9a-f-]{36}$/i)); diff --git a/app/game-frontend/e2e/mainNavigation.spec.ts b/app/game-frontend/e2e/mainNavigation.spec.ts index be0b327..393f09d 100644 --- a/app/game-frontend/e2e/mainNavigation.spec.ts +++ b/app/game-frontend/e2e/mainNavigation.spec.ts @@ -125,6 +125,7 @@ const installFixture = async (page: Page, state: NavigationFixture) => { const operations = operationNames(route); state.operations.push(...operations); const results = operations.map((operation) => { + if (operation === 'auth.status') return response({ ok: true }); if (operation === 'lobby.info') { return response({ myGeneral: { id: 7, name: '메뉴검증장수' }, year: 185, month: 1, turnTerm: 10 }); } diff --git a/app/game-frontend/e2e/nationGeneralSecret.spec.ts b/app/game-frontend/e2e/nationGeneralSecret.spec.ts index 39a8f5a..f082eeb 100644 --- a/app/game-frontend/e2e/nationGeneralSecret.spec.ts +++ b/app/game-frontend/e2e/nationGeneralSecret.spec.ts @@ -34,6 +34,7 @@ const install = async (page: Page, secretAllowed = true) => { }); await page.route('**/che/api/trpc/**', async (route) => { const results = operations(route).map((operation) => { + if (operation === 'auth.status') return response({ ok: true }); if (operation === 'lobby.info') return response({ myGeneral: { id: 1, name: '테스트장수' } }); if (operation === 'join.getConfig') return response({}); if (operation === 'nation.getGeneralList') diff --git a/app/game-frontend/e2e/npcPolicy.spec.ts b/app/game-frontend/e2e/npcPolicy.spec.ts index c8dee62..fb7cfdc 100644 --- a/app/game-frontend/e2e/npcPolicy.spec.ts +++ b/app/game-frontend/e2e/npcPolicy.spec.ts @@ -154,6 +154,7 @@ const installFixture = async (page: Page, state: FixtureState) => { await page.route('**/che/api/trpc/**', async (route) => { const operations = operationName(route).split(','); const results = operations.map((operation) => { + if (operation === 'auth.status') return response({ ok: true }); if (operation === 'lobby.info') return response({ myGeneral: { id: 22, name: '정책담당' } }); if (operation === 'join.getConfig') return response({}); if (operation === 'npc.getPolicy') { diff --git a/app/game-frontend/e2e/npcPossession.spec.ts b/app/game-frontend/e2e/npcPossession.spec.ts index 37a2fb0..333c787 100644 --- a/app/game-frontend/e2e/npcPossession.spec.ts +++ b/app/game-frontend/e2e/npcPossession.spec.ts @@ -120,6 +120,7 @@ const installFixture = async (page: Page, state: FixtureState): Promise => const body = rawBody && typeof rawBody === 'object' ? (rawBody as Record) : {}; const results = operations.map((operation, index) => { const input = findInput(body[String(index)] ?? body); + if (operation === 'auth.status') return response({ ok: true }); if (operation === 'lobby.info') { return response({ myGeneral: state.hasGeneral ? { id: 1, name: '빙의후보1' } : null, diff --git a/app/game-frontend/e2e/pastPlays.spec.ts b/app/game-frontend/e2e/pastPlays.spec.ts index 4b6abbd..862702b 100644 --- a/app/game-frontend/e2e/pastPlays.spec.ts +++ b/app/game-frontend/e2e/pastPlays.spec.ts @@ -14,6 +14,7 @@ const installArchive = async (page: Page) => { }); await page.route('**/che/api/trpc/**', async (route) => { const results = operationNames(route).map((operation) => { + if (operation === 'auth.status') return response({ ok: true }); if (operation === 'lobby.info') return response({ myGeneral: null }); if (operation === 'archive.myPastPlays') { return response({ diff --git a/app/game-frontend/e2e/troop.spec.ts b/app/game-frontend/e2e/troop.spec.ts index f3bf016..c94bcb6 100644 --- a/app/game-frontend/e2e/troop.spec.ts +++ b/app/game-frontend/e2e/troop.spec.ts @@ -5,9 +5,7 @@ import { fileURLToPath } from 'node:url'; const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../../..'); const imageRoots = [ - ...(process.env.FRONTEND_PARITY_IMAGE_ROOT - ? [resolve(process.env.FRONTEND_PARITY_IMAGE_ROOT, 'game')] - : []), + ...(process.env.FRONTEND_PARITY_IMAGE_ROOT ? [resolve(process.env.FRONTEND_PARITY_IMAGE_ROOT, 'game')] : []), resolve(repositoryRoot, '../image/game'), resolve(repositoryRoot, '../../image/game'), ]; @@ -140,6 +138,7 @@ const installApiFixture = async (page: Page, state: FixtureState) => { await page.route('**/che/api/trpc/**', async (route) => { const operations = operationName(route).split(','); const results = operations.map((operation) => { + if (operation === 'auth.status') return response({ ok: true }); if (operation === 'lobby.info') { return response({ myGeneral: { id: state.me.id, name: '테스트 장수' } }); }