import { expect, test, type Page } from '@playwright/test'; async function activeScenes(page: Page): Promise { return page.evaluate(() => window.__HONEY_GAME__.scene.getScenes(true).map((scene) => scene.scene.key)); } test('title, movement, maps, corruption controls, and ending flow', async ({ page }) => { const errors: string[] = []; page.on('console', (message) => { if (message.type() === 'error') errors.push(message.text()); }); page.on('pageerror', (error) => errors.push(error.message)); await page.goto('/'); await expect(page.locator('.name-form')).toBeVisible(); await expect(page.locator('#player-name')).toHaveAttribute('placeholder', '小满'); await page.screenshot({ path: '.qa/title.png' }); await page.locator('#player-name').fill('验收员'); await page.getByRole('button', { name: '进入蜜糖村' }).click(); await expect.poll(() => activeScenes(page)).toContain('HouseScene'); await expect.poll(() => activeScenes(page)).toContain('UIScene'); const startX = await page.evaluate(() => (window.__HONEY_GAME__.scene.getScene('HouseScene') as unknown as { player: { x: number } }).player.x); await page.keyboard.down('ArrowRight'); await page.waitForTimeout(250); await page.keyboard.up('ArrowRight'); const movedX = await page.evaluate(() => (window.__HONEY_GAME__.scene.getScene('HouseScene') as unknown as { player: { x: number } }).player.x); expect(movedX).toBeGreaterThan(startX); for (const [key, animation] of [['w', 'walk-up'], ['s', 'walk-down'], ['a', 'walk-left'], ['d', 'walk-right']] as const) { await page.keyboard.down(key); await page.waitForTimeout(80); const activeAnimation = await page.evaluate(() => (window.__HONEY_GAME__.scene.getScene('HouseScene') as unknown as { player: { anims: { currentAnim?: { key: string } } } }).player.anims.currentAnim?.key); expect(activeAnimation).toBe(animation); await page.keyboard.up(key); } await page.keyboard.down('ArrowLeft'); await page.waitForTimeout(1600); await page.keyboard.up('ArrowLeft'); const boundedX = await page.evaluate(() => (window.__HONEY_GAME__.scene.getScene('HouseScene') as unknown as { player: { x: number } }).player.x); expect(boundedX).toBeGreaterThanOrEqual(5); await page.keyboard.press('Backquote'); await expect(page.locator('.debug-panel')).toBeVisible(); await page.locator('[data-field="happiness"]').fill('100'); await page.locator('[data-action="happiness"]').click(); await page.locator('[data-field="awareness"]').fill('72'); await page.locator('[data-action="awareness"]').click(); await page.locator('[data-field="day"]').fill('7'); await page.locator('[data-action="day"]').click(); const state = await page.evaluate(() => { const d = window.__HONEY_GAME__.registry.get('director') as { snapshot: { happiness: number; awareness: number; day: number }; stage: string }; return { ...d.snapshot, stage: d.stage }; }); expect(state).toMatchObject({ happiness: 100, awareness: 72, day: 7, stage: 'S4' }); await page.screenshot({ path: '.qa/debug.png' }); const maps = ['HouseScene', 'PlazaScene', 'BakeryScene', 'ShopScene', 'ClinicScene', 'LakeScene', 'FieldScene', 'WellScene', 'MistScene', 'WhiteScene']; for (const key of maps) { await page.locator('[data-field="scene"]').selectOption(key); await page.locator('[data-action="teleport"]').click(); await expect.poll(() => activeScenes(page)).toContain(key); } await page.locator('[data-field="scene"]').selectOption('PlazaScene'); await page.locator('[data-action="teleport"]').click(); await expect.poll(() => activeScenes(page)).toContain('PlazaScene'); await page.keyboard.press('Backquote'); await page.screenshot({ path: '.qa/plaza-s4.png' }); await page.keyboard.press('Backquote'); await page.locator('[data-field="happiness"]').fill('40'); await page.locator('[data-action="happiness"]').click(); await page.locator('[data-field="day"]').fill('1'); await page.locator('[data-action="day"]').click(); await page.keyboard.press('Backquote'); await page.waitForTimeout(2000); await page.screenshot({ path: '.qa/plaza-s1.png' }); await page.evaluate(() => { const ui = window.__HONEY_GAME__.scene.getScene('UIScene') as unknown as { showDialogue: (options: object) => Promise }; void ui.showDialogue({ speaker: '阿福', text: '早上好呀!今天的幸福任务贴在板上啦。祝你开心!', portrait: 2 }); }); await page.waitForTimeout(100); await page.screenshot({ path: '.qa/dialogue.png' }); await page.keyboard.press('e'); await page.keyboard.press('Backquote'); await page.locator('[data-action="clues"]').click(); const clueCount = await page.evaluate(() => (window.__HONEY_GAME__.registry.get('director') as { snapshot: { clues: Set } }).snapshot.clues.size); expect(clueCount).toBe(9); await page.locator('[data-ending="E3"]').click(); await expect.poll(() => activeScenes(page)).toContain('EndingScene'); await page.waitForTimeout(3200); await page.screenshot({ path: '.qa/e3.png' }); expect(errors).toEqual([]); }); test('integer scaling stays sharp at desktop and compact viewport', async ({ page }) => { await page.goto('/'); await page.locator('#player-name').press('Enter'); await expect.poll(() => activeScenes(page)).toContain('HouseScene'); const desktop = await page.locator('canvas').boundingBox(); expect(desktop?.width).toBe(960); expect(desktop?.height).toBe(540); await page.setViewportSize({ width: 640, height: 360 }); await page.waitForTimeout(250); const compact = await page.locator('canvas').boundingBox(); expect(compact?.width).toBe(640); expect(compact?.height).toBe(360); }); test('all four forced endings render their specified evidence', async ({ page }) => { const startEnding = async (ending: string, name = '小满') => { await page.goto('/'); await page.locator('#player-name').fill(name); await page.locator('#player-name').press('Enter'); await expect.poll(() => activeScenes(page)).toContain('HouseScene'); await page.evaluate((id) => (window.__HONEY_GAME__.registry.get('director') as { emit: (event: string, ending: string) => void }).emit('force-ending', id), ending); await expect.poll(() => activeScenes(page)).toContain('EndingScene'); }; const texts = () => page.evaluate(() => (window.__HONEY_GAME__.scene.getScene('EndingScene') as unknown as { children: { list: Array<{ type?: string; text?: string }> } }).children.list .filter((child) => child.type === 'Text') .map((text) => text.text ?? '')); await startEnding('E1'); expect((await texts()).join('\n')).toContain('从此,每一天都很幸福。\n\n每一天。'); await startEnding('E2'); await page.waitForTimeout(2200); expect((await texts()).join('\n')).toContain('疗程重新开始。'); await startEnding('E3', '真实姓名'); await page.waitForTimeout(3200); const record = (await texts()).join('\n'); expect(record).toContain('蜜糖谷情绪疗养中心'); expect(record).toContain('姓名:真实姓名'); expect(record).toContain('编号:PT-07'); expect(record).toMatch(/疗程时长:\d{2}:\d{2}/); await page.waitForTimeout(2900); expect((await texts()).join('\n')).toContain('微笑消失了。'); await startEnding('E4'); expect((await texts()).join('\n')).toContain('系统检测到疗程无效。'); });