|
|
|
@@ -39,11 +39,22 @@ async function waitForBridge(page: Page) {
|
|
|
|
|
throw new Error('Debug bridge (window.__TEST__) not available after 30s');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Run n game ticks synchronously via the debug bridge.
|
|
|
|
|
* Each tick advances the game by HOURS_PER_TICK (0.04) hours.
|
|
|
|
|
* 50 ticks ≈ 1 in-game hour, 1200 ticks ≈ 1 in-game day.
|
|
|
|
|
*/
|
|
|
|
|
async function runTicks(page: Page, n: number) {
|
|
|
|
|
await page.evaluate((count: number) => {
|
|
|
|
|
(window as any).__TEST__.runTicks(count);
|
|
|
|
|
}, n);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ─── Test ─────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
test.describe('Combat Happy-Path: Spire Climb → Combat → Mana Recovery → Exit', () => {
|
|
|
|
|
|
|
|
|
|
test('climb spire, fight until mana drains, gather mana to recover, descend, exit', async ({ page }) => {
|
|
|
|
|
test('climb spire, fight until mana drains, gather mana, descend, exit', async ({ page }) => {
|
|
|
|
|
test.setTimeout(600_000);
|
|
|
|
|
|
|
|
|
|
const errors: string[] = [];
|
|
|
|
@@ -61,53 +72,56 @@ test.describe('Combat Happy-Path: Spire Climb → Combat → Mana Recovery → E
|
|
|
|
|
console.log('[TEST] Bridge ready!');
|
|
|
|
|
|
|
|
|
|
// ══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
// STEP 2: Set up prerequisites via Debug tab
|
|
|
|
|
// STEP 2: Set up prerequisites via Debug tab UI
|
|
|
|
|
// ══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
console.log('[TEST] Step 2: Setting up prerequisites...');
|
|
|
|
|
console.log('[TEST] Step 2: Setting up prerequisites via Debug tab...');
|
|
|
|
|
await clickTab(page, 'debug');
|
|
|
|
|
await waitForMs(page, 500);
|
|
|
|
|
|
|
|
|
|
// ── 2a. Unlock all elements ──────────────────────────────────────────────
|
|
|
|
|
console.log('[TEST] 2a. Unlocking all elements...');
|
|
|
|
|
const elementsHeader = page.locator('button', { hasText: /^Elements$/ }).first();
|
|
|
|
|
if (await elementsHeader.isVisible({ timeout: 3000 })) {
|
|
|
|
|
await elementsHeader.click();
|
|
|
|
|
await waitForMs(page, 300);
|
|
|
|
|
// ── 2a. Fill raw mana using the debug buttons ────────────────────────────
|
|
|
|
|
console.log('[TEST] 2a. Filling raw mana via debug buttons...');
|
|
|
|
|
const fillManaBtn = page.getByTestId('debug-mana-fill');
|
|
|
|
|
await expect(fillManaBtn).toBeVisible({ timeout: 5000 });
|
|
|
|
|
await fillManaBtn.click();
|
|
|
|
|
await waitForMs(page, 500);
|
|
|
|
|
|
|
|
|
|
// Add +10K several times for plenty of mana
|
|
|
|
|
const plus10KBtn = page.getByTestId('debug-mana-add-10k');
|
|
|
|
|
await expect(plus10KBtn).toBeVisible({ timeout: 5000 });
|
|
|
|
|
for (let i = 0; i < 10; i++) {
|
|
|
|
|
await plus10KBtn.click();
|
|
|
|
|
await waitForMs(page, 100);
|
|
|
|
|
}
|
|
|
|
|
await clickBtn(page, 'Unlock All Elements');
|
|
|
|
|
await waitForMs(page, 500);
|
|
|
|
|
|
|
|
|
|
// ── 2b. Boost max mana via Raw Mana Mastery discipline XP ────────────────
|
|
|
|
|
console.log('[TEST] 2b. Boosting max mana via Raw Mana Mastery XP...');
|
|
|
|
|
await page.evaluate(() => {
|
|
|
|
|
const disc = (window as any).__TEST__.useDisciplineStore;
|
|
|
|
|
if (!disc) return;
|
|
|
|
|
const state = disc.getState();
|
|
|
|
|
const existing = state.disciplines['raw-mastery'];
|
|
|
|
|
const newXP = (existing?.xp || 0) + 20000;
|
|
|
|
|
disc.setState({
|
|
|
|
|
disciplines: {
|
|
|
|
|
...state.disciplines,
|
|
|
|
|
'raw-mastery': { id: 'raw-mastery', xp: newXP, paused: false },
|
|
|
|
|
},
|
|
|
|
|
totalXP: state.totalXP + 20000,
|
|
|
|
|
concurrentLimit: Math.max(
|
|
|
|
|
state.concurrentLimit,
|
|
|
|
|
Math.min(4 + Math.floor((state.totalXP + 20000) / 500), 7),
|
|
|
|
|
),
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Find the Raw Mana Mastery discipline row via data-testid
|
|
|
|
|
const rawManaRow = page.getByTestId('debug-discipline-row-raw-mastery');
|
|
|
|
|
await expect(rawManaRow).toBeVisible({ timeout: 5000 });
|
|
|
|
|
|
|
|
|
|
// The +1K button within that row
|
|
|
|
|
const plus1KBtn = page.getByTestId('debug-discipline-add1k-raw-mastery');
|
|
|
|
|
await expect(plus1KBtn).toBeVisible({ timeout: 5000 });
|
|
|
|
|
|
|
|
|
|
// Click +1K fifteen times to get 15,000 XP
|
|
|
|
|
for (let i = 0; i < 15; i++) {
|
|
|
|
|
await plus1KBtn.click();
|
|
|
|
|
await waitForMs(page, 50);
|
|
|
|
|
}
|
|
|
|
|
await waitForMs(page, 300);
|
|
|
|
|
|
|
|
|
|
// Verify discipline XP was set via the bridge
|
|
|
|
|
const rawMasteryXP = await page.evaluate(() =>
|
|
|
|
|
(window as any).__TEST__.useDisciplineStore.getState().disciplines?.['raw-mastery']?.xp || 0
|
|
|
|
|
);
|
|
|
|
|
console.log(`[TEST] Raw Mana Mastery XP: ${rawMasteryXP}`);
|
|
|
|
|
expect(rawMasteryXP).toBe(20000);
|
|
|
|
|
expect(rawMasteryXP).toBeGreaterThan(0);
|
|
|
|
|
|
|
|
|
|
// ── 2c. Fill mana to max ─────────────────────────────────────────────────
|
|
|
|
|
console.log('[TEST] 2c. Filling mana to max...');
|
|
|
|
|
await clickBtn(page, 'Fill Mana');
|
|
|
|
|
await fillManaBtn.click();
|
|
|
|
|
await waitForMs(page, 500);
|
|
|
|
|
|
|
|
|
|
const manaAfterFill = await page.evaluate(() =>
|
|
|
|
@@ -133,10 +147,9 @@ test.describe('Combat Happy-Path: Spire Climb → Combat → Mana Recovery → E
|
|
|
|
|
console.log('[TEST] Spire combat page loaded!');
|
|
|
|
|
|
|
|
|
|
// ══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
// STEP 4: Stay in combat — let the game auto-tick and fight
|
|
|
|
|
// STEP 4: Fight in the Spire — run ticks to clear several rooms/floors
|
|
|
|
|
// manaBolt costs 3 raw mana per cast, deals 5 damage.
|
|
|
|
|
// Floor 1 HP = 151, so ~31 casts to clear = ~258 seconds.
|
|
|
|
|
// We let it fight for 120 seconds to clear several floors.
|
|
|
|
|
// Floor 1 HP = ~151. We run enough ticks to clear multiple floors.
|
|
|
|
|
// ══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
console.log('[TEST] Step 4: Fighting in the Spire...');
|
|
|
|
|
|
|
|
|
@@ -148,8 +161,11 @@ test.describe('Combat Happy-Path: Spire Climb → Combat → Mana Recovery → E
|
|
|
|
|
);
|
|
|
|
|
console.log(`[TEST] Starting: Floor ${startFloor}, Mana ${startMana}`);
|
|
|
|
|
|
|
|
|
|
console.log('[TEST] Letting combat run for 120 seconds...');
|
|
|
|
|
await waitForMs(page, 120000);
|
|
|
|
|
// Run 6000 ticks (~2 minutes of game time, ~5 in-game hours).
|
|
|
|
|
// This should clear several floors worth of enemies.
|
|
|
|
|
console.log('[TEST] Running 6000 ticks of combat...');
|
|
|
|
|
await runTicks(page, 6000);
|
|
|
|
|
await waitForMs(page, 500); // let React re-render
|
|
|
|
|
|
|
|
|
|
const floorAfterCombat = await page.evaluate(() =>
|
|
|
|
|
(window as any).__TEST__.useCombatStore.getState().currentFloor
|
|
|
|
@@ -161,99 +177,23 @@ test.describe('Combat Happy-Path: Spire Climb → Combat → Mana Recovery → E
|
|
|
|
|
expect(floorAfterCombat).toBeGreaterThan(startFloor);
|
|
|
|
|
|
|
|
|
|
// ══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
// STEP 5: Exit the Spire to access the Gather button on the main page
|
|
|
|
|
// The Gather button (ManaDisplay) is in the LeftPanel which is only
|
|
|
|
|
// rendered on the main game page, not in the SpireCombatPage view.
|
|
|
|
|
// We exit spire, gather mana, then re-enter.
|
|
|
|
|
// STEP 5: Continue fighting to drain more mana ─────────────────────────────
|
|
|
|
|
// ══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
console.log('[TEST] Step 5: Exiting spire to gather mana...');
|
|
|
|
|
|
|
|
|
|
// First descend to floor 1 (Exit Spire button only shows on floor 1)
|
|
|
|
|
for (let i = 0; i < 200; i++) {
|
|
|
|
|
const floorNow = await page.evaluate(() =>
|
|
|
|
|
(window as any).__TEST__.useCombatStore.getState().currentFloor
|
|
|
|
|
);
|
|
|
|
|
if (floorNow <= 1) break;
|
|
|
|
|
|
|
|
|
|
const climbDownBtn = page.getByRole('button', { name: /climb down/i }).first();
|
|
|
|
|
const btnVisible = await climbDownBtn.isVisible({ timeout: 2000 }).catch(() => false);
|
|
|
|
|
if (btnVisible) {
|
|
|
|
|
await climbDownBtn.click();
|
|
|
|
|
await waitForMs(page, 500);
|
|
|
|
|
} else {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Click Exit Spire button (visible on floor 1)
|
|
|
|
|
const exitBtn = page.getByRole('button', { name: /exit spire/i }).first();
|
|
|
|
|
await expect(exitBtn).toBeVisible({ timeout: 10000 });
|
|
|
|
|
await exitBtn.click();
|
|
|
|
|
await waitForMs(page, 2000);
|
|
|
|
|
|
|
|
|
|
const spireModeAfterExit = await page.evaluate(() =>
|
|
|
|
|
(window as any).__TEST__.useCombatStore.getState().spireMode
|
|
|
|
|
);
|
|
|
|
|
expect(spireModeAfterExit).toBe(false);
|
|
|
|
|
console.log('[TEST] Exited spire, back on main page');
|
|
|
|
|
|
|
|
|
|
// ══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
// STEP 6: Hold "Gather +X Mana" button to recover mana quickly
|
|
|
|
|
// The Gather button is in the LeftPanel's ManaDisplay component.
|
|
|
|
|
// Holding it fires gatherMana() via requestAnimationFrame loop.
|
|
|
|
|
// ══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
console.log('[TEST] Step 6: Holding Gather button to recover mana...');
|
|
|
|
|
|
|
|
|
|
const gatherBtn = page.getByRole('button', { name: /gather.*mana/i }).first();
|
|
|
|
|
await expect(gatherBtn).toBeVisible({ timeout: 10000 });
|
|
|
|
|
|
|
|
|
|
const manaBeforeGather = await page.evaluate(() =>
|
|
|
|
|
(window as any).__TEST__.useManaStore.getState().rawMana
|
|
|
|
|
);
|
|
|
|
|
console.log(`[TEST] Mana before gather: ${manaBeforeGather}`);
|
|
|
|
|
|
|
|
|
|
// Hold the gather button for 10 seconds
|
|
|
|
|
await gatherBtn.hover();
|
|
|
|
|
await page.mouse.down();
|
|
|
|
|
await waitForMs(page, 10000);
|
|
|
|
|
await page.mouse.up();
|
|
|
|
|
console.log('[TEST] Released Gather button.');
|
|
|
|
|
|
|
|
|
|
const manaAfterGather = await page.evaluate(() =>
|
|
|
|
|
(window as any).__TEST__.useManaStore.getState().rawMana
|
|
|
|
|
);
|
|
|
|
|
console.log(`[TEST] Mana after gathering: ${manaAfterGather}`);
|
|
|
|
|
expect(manaAfterGather).toBeGreaterThanOrEqual(manaBeforeGather);
|
|
|
|
|
|
|
|
|
|
// ══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
// STEP 7: Re-enter the Spire and continue fighting with recovered mana
|
|
|
|
|
// ══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
console.log('[TEST] Step 7: Re-entering the Spire with recovered mana...');
|
|
|
|
|
|
|
|
|
|
await clickTab(page, 'spells');
|
|
|
|
|
console.log('[TEST] Step 5: Continuing combat to drain more mana...');
|
|
|
|
|
await runTicks(page, 3000);
|
|
|
|
|
await waitForMs(page, 500);
|
|
|
|
|
|
|
|
|
|
const climbBtn2 = page.getByRole('button', { name: /climb the spire/i }).first();
|
|
|
|
|
await expect(climbBtn2).toBeVisible({ timeout: 10000 });
|
|
|
|
|
await climbBtn2.click();
|
|
|
|
|
await waitForMs(page, 2000);
|
|
|
|
|
|
|
|
|
|
await expect(page.getByText('Floor 1').first()).toBeVisible({ timeout: 10000 });
|
|
|
|
|
console.log('[TEST] Re-entered spire!');
|
|
|
|
|
|
|
|
|
|
// Let combat continue
|
|
|
|
|
console.log('[TEST] Letting combat run for 60 more seconds...');
|
|
|
|
|
await waitForMs(page, 60000);
|
|
|
|
|
|
|
|
|
|
const floorAfterMoreCombat = await page.evaluate(() =>
|
|
|
|
|
(window as any).__TEST__.useCombatStore.getState().currentFloor
|
|
|
|
|
const manaAfterMoreCombat = await page.evaluate(() =>
|
|
|
|
|
(window as any).__TEST__.useManaStore.getState().rawMana
|
|
|
|
|
);
|
|
|
|
|
console.log(`[TEST] After more combat: Floor ${floorAfterMoreCombat}`);
|
|
|
|
|
console.log(`[TEST] Mana after extended combat: ${manaAfterMoreCombat}`);
|
|
|
|
|
|
|
|
|
|
// ══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
// STEP 8: Descend the spire back to floor 1
|
|
|
|
|
// STEP 6: Descend the spire back to floor 1 ───────────────────────────────
|
|
|
|
|
// Each "Climb Down" click descends one floor. We verify the floor actually
|
|
|
|
|
// decrements after each click.
|
|
|
|
|
// ══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
console.log('[TEST] Step 8: Descending to floor 1...');
|
|
|
|
|
console.log('[TEST] Step 6: Descending to floor 1...');
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < 200; i++) {
|
|
|
|
|
const floorNow = await page.evaluate(() =>
|
|
|
|
@@ -265,8 +205,15 @@ test.describe('Combat Happy-Path: Spire Climb → Combat → Mana Recovery → E
|
|
|
|
|
const btnVisible = await climbDownBtn.isVisible({ timeout: 2000 }).catch(() => false);
|
|
|
|
|
if (btnVisible) {
|
|
|
|
|
await climbDownBtn.click();
|
|
|
|
|
await waitForMs(page, 500);
|
|
|
|
|
// Wait for the floor to actually decrement
|
|
|
|
|
const expectedFloor = floorNow - 1;
|
|
|
|
|
await page.waitForFunction(
|
|
|
|
|
(target: number) => (window as any).__TEST__.useCombatStore.getState().currentFloor === target,
|
|
|
|
|
expectedFloor,
|
|
|
|
|
{ timeout: 5000 }
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
console.log('[TEST] Climb Down button not visible, breaking');
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -278,28 +225,39 @@ test.describe('Combat Happy-Path: Spire Climb → Combat → Mana Recovery → E
|
|
|
|
|
expect(floorAfterDescend).toBe(1);
|
|
|
|
|
|
|
|
|
|
// ══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
// STEP 9: Exit the Spire
|
|
|
|
|
// STEP 7: Exit the Spire ───────────────────────────────────────────────────
|
|
|
|
|
// The Exit Spire button should only be visible on floor 1.
|
|
|
|
|
// ══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
console.log('[TEST] Step 9: Exiting the Spire...');
|
|
|
|
|
console.log('[TEST] Step 7: Exiting the Spire...');
|
|
|
|
|
|
|
|
|
|
const exitBtn2 = page.getByRole('button', { name: /exit spire/i }).first();
|
|
|
|
|
await expect(exitBtn2).toBeVisible({ timeout: 10000 });
|
|
|
|
|
await exitBtn2.click();
|
|
|
|
|
// Verify we are on floor 1 and Exit Spire button is visible
|
|
|
|
|
const exitBtn = page.getByRole('button', { name: /exit spire/i }).first();
|
|
|
|
|
await expect(exitBtn).toBeVisible({ timeout: 10000 });
|
|
|
|
|
|
|
|
|
|
// Verify the button is NOT visible when not on floor 1 by checking that
|
|
|
|
|
// the current floor is indeed 1 (the button's rendering condition)
|
|
|
|
|
const floorBeforeExit = await page.evaluate(() =>
|
|
|
|
|
(window as any).__TEST__.useCombatStore.getState().currentFloor
|
|
|
|
|
);
|
|
|
|
|
expect(floorBeforeExit).toBe(1);
|
|
|
|
|
|
|
|
|
|
await exitBtn.click();
|
|
|
|
|
await waitForMs(page, 2000);
|
|
|
|
|
|
|
|
|
|
const spireModeFinal = await page.evaluate(() =>
|
|
|
|
|
const spireModeAfterExit = await page.evaluate(() =>
|
|
|
|
|
(window as any).__TEST__.useCombatStore.getState().spireMode
|
|
|
|
|
);
|
|
|
|
|
expect(spireModeFinal).toBe(false);
|
|
|
|
|
console.log(`[TEST] Spire mode after exit: ${spireModeAfterExit}`);
|
|
|
|
|
expect(spireModeAfterExit).toBe(false);
|
|
|
|
|
|
|
|
|
|
// Verify we are back on the main game page
|
|
|
|
|
await expect(page.getByRole('tab', { name: /spells/i }).first()).toBeVisible({ timeout: 10000 });
|
|
|
|
|
console.log('[TEST] Back on main game page!');
|
|
|
|
|
|
|
|
|
|
// ══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
// STEP 10: Verify final state
|
|
|
|
|
// STEP 8: Verify final state ──────────────────────────────────────────────
|
|
|
|
|
// ══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
console.log('[TEST] Step 10: Verifying final state...');
|
|
|
|
|
console.log('[TEST] Step 8: Verifying final state...');
|
|
|
|
|
|
|
|
|
|
const maxFloorReached = await page.evaluate(() =>
|
|
|
|
|
(window as any).__TEST__.useCombatStore.getState().maxFloorReached
|
|
|
|
|