8b41f137d5
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m20s
- playwright.config.ts: change baseURL from dev site to localhost:3000 - combat-happy-path.spec.ts: fix climb button location (LeftPanel, not spire tab), fix descent via store, handle game-over from day overflow, reduce tick counts to avoid day 30 limit - fabricator-happy-path.spec.ts: set currentAction to meditate before crafting (required by startFabricatorCrafting) - playtest.spec.ts: rewrite from scratch — use localhost, window.__TEST__ bridge (not window.__debug), current tab names (no grimoire/element tabs), split into 3 files under 400-line limit - playtest-basic-ui.spec.ts: sections 1-3 (basic UI, stats, spire) - playtest-tabs.spec.ts: sections 4-11 (all tab navigation tests) - playtest-debug.spec.ts: sections 12-14 (debug tab, bridge, stress test)
291 lines
10 KiB
TypeScript
291 lines
10 KiB
TypeScript
import { test, expect, type Page } from '@playwright/test';
|
|
|
|
test.use({
|
|
baseURL: 'http://localhost:3000/',
|
|
});
|
|
|
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
|
|
async function waitForMs(page: Page, ms: number) {
|
|
await page.waitForTimeout(ms);
|
|
}
|
|
|
|
async function startFreshGame(page: Page) {
|
|
await page.goto('/');
|
|
await page.evaluate(() => localStorage.clear());
|
|
await page.reload();
|
|
await page.waitForLoadState('networkidle');
|
|
await waitForMs(page, 3000);
|
|
}
|
|
|
|
async function waitForBridge(page: Page) {
|
|
for (let attempt = 0; attempt < 30; attempt++) {
|
|
const ready = await page.evaluate(() => !!(window as any).__TEST__);
|
|
if (ready) return;
|
|
await waitForMs(page, 1000);
|
|
}
|
|
throw new Error('Debug bridge (window.__TEST__) not available after 30s');
|
|
}
|
|
|
|
async function clickTab(page: Page, label: string) {
|
|
const tab = page.getByRole('tab', { name: new RegExp(label, 'i') }).first();
|
|
if (await tab.isVisible({ timeout: 2000 })) {
|
|
await tab.click();
|
|
await waitForMs(page, 400);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// ─── Test Suite ──────────────────────────────────────────────────────────────
|
|
|
|
test.describe('Mana Loop - Comprehensive Playtest', () => {
|
|
|
|
// =========================================================================
|
|
// SECTION 4 - Disciplines Tab
|
|
// =========================================================================
|
|
test.describe('4 - Disciplines', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await startFreshGame(page);
|
|
});
|
|
|
|
test('navigate to Disciplines tab without crash', async ({ page }) => {
|
|
const errors: string[] = [];
|
|
page.on('console', (msg) => {
|
|
if (msg.type() === 'error') errors.push(msg.text());
|
|
});
|
|
await waitForMs(page, 500);
|
|
|
|
const visited = await clickTab(page, 'disciplines');
|
|
expect(visited).toBe(true);
|
|
|
|
await waitForMs(page, 500);
|
|
const reactErrors = errors.filter(e =>
|
|
e.includes('React') || e.includes('Minified') || e.includes('Error #')
|
|
);
|
|
expect(reactErrors, `React errors in Disciplines: ${JSON.stringify(reactErrors)}`).toHaveLength(0);
|
|
});
|
|
|
|
test('Raw Mana Mastery discipline is available', async ({ page }) => {
|
|
await waitForMs(page, 500);
|
|
const visited = await clickTab(page, 'disciplines');
|
|
if (visited) {
|
|
const bodyText = await page.textContent('body') || '';
|
|
expect(bodyText).toContain('Raw Mana Mastery');
|
|
}
|
|
});
|
|
});
|
|
|
|
// =========================================================================
|
|
// SECTION 5 - Crafting Tab
|
|
// =========================================================================
|
|
test.describe('5 - Crafting System', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await startFreshGame(page);
|
|
});
|
|
|
|
test('navigate to Crafting tab without crash', async ({ page }) => {
|
|
const errors: string[] = [];
|
|
page.on('console', (msg) => {
|
|
if (msg.type() === 'error') errors.push(msg.text());
|
|
});
|
|
await waitForMs(page, 500);
|
|
|
|
const visited = await clickTab(page, 'craft');
|
|
expect(visited).toBe(true);
|
|
|
|
await waitForMs(page, 500);
|
|
const reactErrors = errors.filter(e =>
|
|
e.includes('React') || e.includes('Minified') || e.includes('Error #')
|
|
);
|
|
expect(reactErrors, `React errors in Crafting: ${JSON.stringify(reactErrors)}`).toHaveLength(0);
|
|
});
|
|
|
|
test('Enchanter and Fabricator sub-tabs exist', async ({ page }) => {
|
|
await waitForMs(page, 500);
|
|
const visited = await clickTab(page, 'craft');
|
|
if (visited) {
|
|
const bodyText = await page.textContent('body') || '';
|
|
expect(bodyText).toContain('Enchanter');
|
|
expect(bodyText).toContain('Fabricator');
|
|
}
|
|
});
|
|
});
|
|
|
|
// =========================================================================
|
|
// SECTION 6 - Equipment Tab
|
|
// =========================================================================
|
|
test.describe('6 - Equipment & Inventory', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await startFreshGame(page);
|
|
});
|
|
|
|
test('navigate to Equipment tab without crash', async ({ page }) => {
|
|
const errors: string[] = [];
|
|
page.on('console', (msg) => {
|
|
if (msg.type() === 'error') errors.push(msg.text());
|
|
});
|
|
await waitForMs(page, 500);
|
|
|
|
const visited = await clickTab(page, 'equipment');
|
|
expect(visited).toBe(true);
|
|
|
|
await waitForMs(page, 500);
|
|
const reactErrors = errors.filter(e =>
|
|
e.includes('React') || e.includes('Minified') || e.includes('Error #')
|
|
);
|
|
expect(reactErrors, `React errors in Equipment: ${JSON.stringify(reactErrors)}`).toHaveLength(0);
|
|
});
|
|
|
|
test('starting equipment includes Basic Staff, Civilian Shirt, Civilian Shoes', async ({ page }) => {
|
|
await waitForMs(page, 500);
|
|
const visited = await clickTab(page, 'equipment');
|
|
if (visited) {
|
|
const bodyText = await page.textContent('body') || '';
|
|
expect(bodyText).toContain('Basic Staff');
|
|
expect(bodyText).toContain('Civilian Shirt');
|
|
expect(bodyText).toContain('Civilian Shoes');
|
|
}
|
|
});
|
|
});
|
|
|
|
// =========================================================================
|
|
// SECTION 7 - Attunements Tab
|
|
// =========================================================================
|
|
test.describe('7 - Attunements', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await startFreshGame(page);
|
|
});
|
|
|
|
test('navigate to Attunements tab without crash', async ({ page }) => {
|
|
const errors: string[] = [];
|
|
page.on('console', (msg) => {
|
|
if (msg.type() === 'error') errors.push(msg.text());
|
|
});
|
|
await waitForMs(page, 500);
|
|
|
|
const visited = await clickTab(page, 'attun');
|
|
expect(visited).toBe(true);
|
|
|
|
await waitForMs(page, 500);
|
|
const reactErrors = errors.filter(e =>
|
|
e.includes('React') || e.includes('Minified') || e.includes('Error #')
|
|
);
|
|
expect(reactErrors, `React errors in Attunements: ${JSON.stringify(reactErrors)}`).toHaveLength(0);
|
|
});
|
|
|
|
test('Enchanter is attuned at level 1 by default', async ({ page }) => {
|
|
await waitForMs(page, 500);
|
|
const visited = await clickTab(page, 'attun');
|
|
if (visited) {
|
|
const bodyText = await page.textContent('body') || '';
|
|
expect(bodyText).toContain('Enchanter');
|
|
}
|
|
});
|
|
});
|
|
|
|
// =========================================================================
|
|
// SECTION 8 - Prestige Tab
|
|
// =========================================================================
|
|
test.describe('8 - Prestige Tab', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await startFreshGame(page);
|
|
});
|
|
|
|
test('navigate to Prestige tab without crash', async ({ page }) => {
|
|
const errors: string[] = [];
|
|
page.on('console', (msg) => {
|
|
if (msg.type() === 'error') errors.push(msg.text());
|
|
});
|
|
await waitForMs(page, 500);
|
|
|
|
const visited = await clickTab(page, 'prestige');
|
|
expect(visited).toBe(true);
|
|
|
|
await waitForMs(page, 500);
|
|
const reactErrors = errors.filter(e =>
|
|
e.includes('React') || e.includes('Minified') || e.includes('Error #')
|
|
);
|
|
expect(reactErrors, `React errors in Prestige: ${JSON.stringify(reactErrors)}`).toHaveLength(0);
|
|
});
|
|
});
|
|
|
|
// =========================================================================
|
|
// SECTION 9 - Golemancy Tab
|
|
// =========================================================================
|
|
test.describe('9 - Golemancy Tab', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await startFreshGame(page);
|
|
});
|
|
|
|
test('navigate to Golemancy tab without crash', async ({ page }) => {
|
|
const errors: string[] = [];
|
|
page.on('console', (msg) => {
|
|
if (msg.type() === 'error') errors.push(msg.text());
|
|
});
|
|
await waitForMs(page, 500);
|
|
|
|
const visited = await clickTab(page, 'golem');
|
|
expect(visited).toBe(true);
|
|
|
|
await waitForMs(page, 500);
|
|
const reactErrors = errors.filter(e =>
|
|
e.includes('React') || e.includes('Minified') || e.includes('Error #')
|
|
);
|
|
expect(reactErrors, `React errors in Golemancy: ${JSON.stringify(reactErrors)}`).toHaveLength(0);
|
|
});
|
|
});
|
|
|
|
// =========================================================================
|
|
// SECTION 10 - Guardian Pacts Tab
|
|
// =========================================================================
|
|
test.describe('10 - Guardian Pacts Tab', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await startFreshGame(page);
|
|
});
|
|
|
|
test('navigate to Guardian Pacts tab without crash', async ({ page }) => {
|
|
const errors: string[] = [];
|
|
page.on('console', (msg) => {
|
|
if (msg.type() === 'error') errors.push(msg.text());
|
|
});
|
|
await waitForMs(page, 500);
|
|
|
|
const visited = await clickTab(page, 'pact');
|
|
expect(visited).toBe(true);
|
|
|
|
await waitForMs(page, 500);
|
|
const reactErrors = errors.filter(e =>
|
|
e.includes('React') || e.includes('Minified') || e.includes('Error #')
|
|
);
|
|
expect(reactErrors, `React errors in Guardian Pacts: ${JSON.stringify(reactErrors)}`).toHaveLength(0);
|
|
});
|
|
});
|
|
|
|
// =========================================================================
|
|
// SECTION 11 - Achievements Tab
|
|
// =========================================================================
|
|
test.describe('11 - Achievements Tab', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await startFreshGame(page);
|
|
});
|
|
|
|
test('navigate to Achievements tab without crash', async ({ page }) => {
|
|
const errors: string[] = [];
|
|
page.on('console', (msg) => {
|
|
if (msg.type() === 'error') errors.push(msg.text());
|
|
});
|
|
await waitForMs(page, 500);
|
|
|
|
const visited = await clickTab(page, 'achievement');
|
|
expect(visited).toBe(true);
|
|
|
|
await waitForMs(page, 500);
|
|
const reactErrors = errors.filter(e =>
|
|
e.includes('React') || e.includes('Minified') || e.includes('Error #')
|
|
);
|
|
expect(reactErrors, `React errors in Achievements: ${JSON.stringify(reactErrors)}`).toHaveLength(0);
|
|
});
|
|
});
|
|
});
|