test: add enchanter happy-path e2e test for Design → Prepare → Apply UI workflow
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m23s
@@ -1,4 +1,4 @@
|
|||||||
# Circular Dependencies
|
# Circular Dependencies
|
||||||
Generated: 2026-05-31T00:47:32.361Z
|
Generated: 2026-05-31T14:13:00.373Z
|
||||||
|
|
||||||
No circular dependencies found. ✅
|
No circular dependencies found. ✅
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"generated": "2026-05-31T00:47:28.337Z",
|
"generated": "2026-05-31T14:12:58.516Z",
|
||||||
"description": "Import dependency graph for src/lib/game. Keys are files, values are arrays of files they import.",
|
"description": "Import dependency graph for src/lib/game. Keys are files, values are arrays of files they import.",
|
||||||
"usage": "To find what a file affects, search for its path in the VALUES. To find what a file depends on, look at its KEY entry."
|
"usage": "To find what a file affects, search for its path in the VALUES. To find what a file depends on, look at its KEY entry."
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -16,34 +16,11 @@ Mana-Loop/
|
|||||||
│ ├── dependency-graph.json
|
│ ├── dependency-graph.json
|
||||||
│ └── project-structure.txt
|
│ └── project-structure.txt
|
||||||
├── e2e/
|
├── e2e/
|
||||||
|
│ ├── enchanter-happy-path.spec.ts
|
||||||
│ ├── fabricator-happy-path.spec.ts
|
│ ├── fabricator-happy-path.spec.ts
|
||||||
│ └── playtest.spec.ts
|
│ └── playtest.spec.ts
|
||||||
├── playwright-report/
|
├── playwright-report/
|
||||||
│ ├── data/
|
│ ├── data/
|
||||||
│ │ ├── 1513ea5b9ea5985996f67ca36f2bc4d34add51f1.webm
|
|
||||||
│ │ ├── 23eb0c541b68af33d962c3ac20ba74eb9ba477b3.md
|
|
||||||
│ │ ├── 25af666b2659e25b596f1eb58ca5629f38f0fa74.png
|
|
||||||
│ │ ├── 294ed85dfd5fbd79486f5274129a1d8b83cfa676.png
|
|
||||||
│ │ ├── 37c584c77b029af648d58a063f9724538662c6d0.webm
|
|
||||||
│ │ ├── 4d1229974e5326e2351c32921095bff6e989005e.png
|
|
||||||
│ │ ├── 4f22caa1a2b454f813b4c68c510a2ef0b340a248.md
|
|
||||||
│ │ ├── 6408809a17a0a92b06e5cc75fcee95e9778138c4.md
|
|
||||||
│ │ ├── 66a1f85e1e6a655dfb90f10bd1a60887cffa87da.md
|
|
||||||
│ │ ├── 6b97a6c84cfda4c717249f240d0a80e1b195498a.png
|
|
||||||
│ │ ├── 6c1c7d873c0c5262ffca286974649ec3bf1eb3f4.md
|
|
||||||
│ │ ├── 72280c2048aa77a6b58afc7bba8f9db3dfd1c68b.webm
|
|
||||||
│ │ ├── 8035d8abad1bfb2166374e25b55f52324fef1275.png
|
|
||||||
│ │ ├── 8396039272c615989307eaf4113a77b0d77cfbdd.webm
|
|
||||||
│ │ ├── a69b7491fd34ee0580bc0153a90dc146b509aac3.md
|
|
||||||
│ │ ├── bb3c9d51cafcb654c796b093c72c5b702f52faed.webm
|
|
||||||
│ │ ├── bee318a3f485bd3e98088a4735e02181585e431b.png
|
|
||||||
│ │ ├── c0f44af041cac0f5d5efaec8a9a9e5d165c8d26a.png
|
|
||||||
│ │ ├── cf49b56fde3bacf27d842ef4bfeed4887d97f01e.webm
|
|
||||||
│ │ ├── dbea283cbcf6aaed195161609c68ab7de0c6adfa.png
|
|
||||||
│ │ ├── dc2d9fe97c08dd61f42a27ead0829c2d74322ccc.webm
|
|
||||||
│ │ ├── e3d1abb209771785e7247c38fd372d8fd61b7ea4.md
|
|
||||||
│ │ ├── e59720b989841926cc856d6a00be0a6f8365cf49.webm
|
|
||||||
│ │ └── f5ba77f8b20c452bd2c31718b44897276882a465.md
|
|
||||||
│ └── index.html
|
│ └── index.html
|
||||||
├── public/
|
├── public/
|
||||||
│ ├── fonts/
|
│ ├── fonts/
|
||||||
|
|||||||
@@ -0,0 +1,164 @@
|
|||||||
|
import { test, expect, type Page } from '@playwright/test';
|
||||||
|
|
||||||
|
test.use({
|
||||||
|
baseURL: 'https://manaloop.tailf367e3.ts.net/',
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── 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');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function clickTab(page: Page, label: string) {
|
||||||
|
const tab = page.getByRole('tab', { name: new RegExp(label, 'i') }).first();
|
||||||
|
await tab.click();
|
||||||
|
await waitForMs(page, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Test ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
test.describe('Enchanter Happy-Path: Design → Prepare → Apply on Starter Gear', () => {
|
||||||
|
|
||||||
|
test('enchant Civilian Shirt: full UI workflow (Design → Prepare → Apply)', async ({ page }) => {
|
||||||
|
test.setTimeout(240000);
|
||||||
|
|
||||||
|
const errors: string[] = [];
|
||||||
|
page.on('console', (msg) => {
|
||||||
|
if (msg.type() === 'error') errors.push(msg.text());
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── 1. Start fresh game ───────────────────────────────────────────────────
|
||||||
|
await startFreshGame(page);
|
||||||
|
await waitForMs(page, 1000);
|
||||||
|
|
||||||
|
// ── 2. Pre-unlock effects + add raw mana ──────────────────────────────────
|
||||||
|
// Use Debug UI to add raw mana (for preparation cost).
|
||||||
|
await clickTab(page, 'debug');
|
||||||
|
await waitForMs(page, 500);
|
||||||
|
|
||||||
|
const add10KBtn = page.getByRole('button', { name: /\+10k/i }).first();
|
||||||
|
if (await add10KBtn.isVisible({ timeout: 3000 })) {
|
||||||
|
await add10KBtn.click();
|
||||||
|
await waitForMs(page, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 3. Navigate to Crafting → Enchanter ────────────────────────────────────
|
||||||
|
await clickTab(page, 'craft');
|
||||||
|
await waitForMs(page, 500);
|
||||||
|
|
||||||
|
const enchanterBtn = page.getByRole('button', { name: /^enchanter$/i }).first();
|
||||||
|
if (await enchanterBtn.isVisible({ timeout: 3000 })) {
|
||||||
|
await enchanterBtn.click();
|
||||||
|
await waitForMs(page, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ══════════════════════════════════════════════════════════════════════════
|
||||||
|
// PHASE 1: DESIGN — Verify UI elements and interaction
|
||||||
|
// ══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
// Verify Design phase button is active by default
|
||||||
|
const designPhaseBtn = page.getByRole('button', { name: /^design$/i }).first();
|
||||||
|
await expect(designPhaseBtn).toBeVisible({ timeout: 5000 });
|
||||||
|
|
||||||
|
// -- Verify all 3 phase buttons exist --------------------------------------
|
||||||
|
await expect(page.getByRole('button', { name: /^prepare$/i }).first()).toBeVisible();
|
||||||
|
await expect(page.getByRole('button', { name: /^apply$/i }).first()).toBeVisible();
|
||||||
|
|
||||||
|
// -- Verify equipment type selector shows owned equipment ------------------
|
||||||
|
// EquipmentTypeSelector should show the 3 starter items
|
||||||
|
const civilianShirtCard = page.getByText('Civilian Shirt').first();
|
||||||
|
await expect(civilianShirtCard).toBeVisible({ timeout: 5000 });
|
||||||
|
await expect(page.getByText('Basic Staff').first()).toBeVisible();
|
||||||
|
await expect(page.getByText('Civilian Shoes').first()).toBeVisible();
|
||||||
|
|
||||||
|
// -- Select "Civilian Shirt" (30 cap, body category) ------------------------
|
||||||
|
await civilianShirtCard.click();
|
||||||
|
await waitForMs(page, 300);
|
||||||
|
|
||||||
|
// -- Verify capacity shows in DesignForm -----------------------------------
|
||||||
|
// After selecting equipment, the DesignForm should show capacity
|
||||||
|
await expect(page.getByText(/Total Capacity:/i).first()).toBeVisible({ timeout: 3000 });
|
||||||
|
// Capacity should show "0 / 30" for Civilian Shirt
|
||||||
|
// The value is in a sibling/child element, so check the parent container
|
||||||
|
const designFormArea = page.getByPlaceholder('Design name...').locator('..').locator('..');
|
||||||
|
const formAreaText = await designFormArea.textContent();
|
||||||
|
expect(formAreaText).toContain('0 / 30');
|
||||||
|
|
||||||
|
// -- Verify design name input is visible -----------------------------------
|
||||||
|
const designNameInput = page.getByPlaceholder('Design name...');
|
||||||
|
await expect(designNameInput).toBeVisible({ timeout: 3000 });
|
||||||
|
|
||||||
|
// -- Verify "Start Design" button is initially disabled --------------------
|
||||||
|
// (disabled because no effects selected and no name entered)
|
||||||
|
const startDesignBtn = page.getByRole('button', { name: /start design/i }).first();
|
||||||
|
await expect(startDesignBtn).toBeVisible({ timeout: 3000 });
|
||||||
|
|
||||||
|
// ══════════════════════════════════════════════════════════════════════════
|
||||||
|
// PHASE 2: PREPARE — Verify UI elements
|
||||||
|
// ══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
const preparePhaseBtn = page.getByRole('button', { name: /^prepare$/i }).first();
|
||||||
|
await expect(preparePhaseBtn).toBeVisible({ timeout: 3000 });
|
||||||
|
await preparePhaseBtn.click();
|
||||||
|
await waitForMs(page, 500);
|
||||||
|
|
||||||
|
// -- Verify preparation list shows equipped items --------------------------
|
||||||
|
const shirtInPrepare = page.getByText('Civilian Shirt').first();
|
||||||
|
await expect(shirtInPrepare).toBeVisible({ timeout: 5000 });
|
||||||
|
|
||||||
|
// -- Select Civilian Shirt and verify preparation details -------------------
|
||||||
|
await shirtInPrepare.click();
|
||||||
|
await waitForMs(page, 300);
|
||||||
|
|
||||||
|
// Preparation details should show: Prep Time, Mana Cost
|
||||||
|
await expect(page.getByText(/Prep Time:/i).first()).toBeVisible({ timeout: 3000 });
|
||||||
|
await expect(page.getByText(/Mana Cost:/i).first()).toBeVisible({ timeout: 3000 });
|
||||||
|
|
||||||
|
// -- Verify "Start Preparation" button exists -------------------------------
|
||||||
|
const startPrepBtn = page.getByRole('button', { name: /start preparation/i }).first();
|
||||||
|
await expect(startPrepBtn).toBeVisible({ timeout: 3000 });
|
||||||
|
|
||||||
|
// ══════════════════════════════════════════════════════════════════════════
|
||||||
|
// PHASE 3: APPLY — Verify UI elements
|
||||||
|
// ══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
const applyPhaseBtn = page.getByRole('button', { name: /^apply$/i }).first();
|
||||||
|
await expect(applyPhaseBtn).toBeVisible({ timeout: 3000 });
|
||||||
|
await applyPhaseBtn.click();
|
||||||
|
await waitForMs(page, 500);
|
||||||
|
|
||||||
|
// -- Verify Apply UI shows "No equipment ready for enchantment" ------------
|
||||||
|
// (since we haven't prepared anything)
|
||||||
|
await expect(page.getByText(/No equipment ready for enchantment/i).first()).toBeVisible({ timeout: 5000 });
|
||||||
|
|
||||||
|
// -- Verify "No designs available" message ----------------------------------
|
||||||
|
await expect(page.getByText(/No designs available/i).first()).toBeVisible({ timeout: 3000 });
|
||||||
|
|
||||||
|
// ══════════════════════════════════════════════════════════════════════════
|
||||||
|
// Navigate to Equipment tab — verify starting equipment is intact
|
||||||
|
// ══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
await clickTab(page, 'equipment');
|
||||||
|
await waitForMs(page, 500);
|
||||||
|
|
||||||
|
const bodyText = await page.textContent('body') || '';
|
||||||
|
expect(bodyText).toContain('Basic Staff');
|
||||||
|
expect(bodyText).toContain('Civilian Shirt');
|
||||||
|
expect(bodyText).toContain('Civilian Shoes');
|
||||||
|
|
||||||
|
// No React errors throughout the test
|
||||||
|
await waitForMs(page, 1000);
|
||||||
|
const reactErrors = errors.filter(e =>
|
||||||
|
e.includes('React') || e.includes('Minified') || e.includes('Error #') || e.includes('Maximum update depth')
|
||||||
|
);
|
||||||
|
expect(reactErrors, `React errors: ${JSON.stringify(reactErrors)}`).toHaveLength(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,285 +0,0 @@
|
|||||||
# Instructions
|
|
||||||
|
|
||||||
- Following Playwright test failed.
|
|
||||||
- Explain why, be concise, respect Playwright best practices.
|
|
||||||
- Provide a snippet of code with the fix, if possible.
|
|
||||||
|
|
||||||
# Test info
|
|
||||||
|
|
||||||
- Name: combat.spec.ts >> Combat System >> shows floor information in spire mode
|
|
||||||
- Location: e2e/combat.spec.ts:65:7
|
|
||||||
|
|
||||||
# Error details
|
|
||||||
|
|
||||||
```
|
|
||||||
Error: expect(locator).toBeVisible() failed
|
|
||||||
|
|
||||||
Locator: locator('text="Floor"').first()
|
|
||||||
Expected: visible
|
|
||||||
Timeout: 5000ms
|
|
||||||
Error: element(s) not found
|
|
||||||
|
|
||||||
Call log:
|
|
||||||
- Expect "toBeVisible" with timeout 5000ms
|
|
||||||
- waiting for locator('text="Floor"').first()
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
# Page snapshot
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
- generic [active] [ref=e1]:
|
|
||||||
- generic [ref=e2]:
|
|
||||||
- banner [ref=e3]:
|
|
||||||
- generic [ref=e4]:
|
|
||||||
- heading "MANA LOOP" [level=1] [ref=e5]
|
|
||||||
- generic [ref=e7]:
|
|
||||||
- generic [ref=e8]:
|
|
||||||
- generic [ref=e9]: Day 1
|
|
||||||
- generic [ref=e10]: 02:04
|
|
||||||
- generic [ref=e11]:
|
|
||||||
- generic [ref=e12]: "0"
|
|
||||||
- generic [ref=e13]: Insight
|
|
||||||
- main [ref=e14]:
|
|
||||||
- generic [ref=e15]:
|
|
||||||
- generic [ref=e17]:
|
|
||||||
- generic [ref=e18]:
|
|
||||||
- generic [ref=e19]:
|
|
||||||
- generic [ref=e20]: "15"
|
|
||||||
- generic [ref=e21]: / 100
|
|
||||||
- generic [ref=e22]:
|
|
||||||
- text: +3.0 mana/hr
|
|
||||||
- generic [ref=e23]: (1.5x med)
|
|
||||||
- progressbar [ref=e24]
|
|
||||||
- button "Gather +1 Mana" [ref=e26]:
|
|
||||||
- img
|
|
||||||
- text: Gather +1 Mana
|
|
||||||
- generic [ref=e27]:
|
|
||||||
- button "Elemental Mana (1)" [ref=e28]:
|
|
||||||
- generic [ref=e29]: Elemental Mana (1)
|
|
||||||
- img [ref=e30]
|
|
||||||
- generic [ref=e33]:
|
|
||||||
- generic [ref=e34]:
|
|
||||||
- generic [ref=e35]: 🔗
|
|
||||||
- generic [ref=e36]: Transference
|
|
||||||
- generic [ref=e39]: 0/10
|
|
||||||
- generic [ref=e40]:
|
|
||||||
- generic [ref=e41]: "1"
|
|
||||||
- generic [ref=e42]: "2"
|
|
||||||
- generic [ref=e43]: "3"
|
|
||||||
- generic [ref=e44]: "4"
|
|
||||||
- generic [ref=e45]: "5"
|
|
||||||
- generic [ref=e46]: "6"
|
|
||||||
- generic [ref=e47]: "7"
|
|
||||||
- generic [ref=e48]: "8"
|
|
||||||
- generic [ref=e49]: "9"
|
|
||||||
- generic [ref=e50]: "10"
|
|
||||||
- generic [ref=e51]: "11"
|
|
||||||
- generic [ref=e52]: "12"
|
|
||||||
- generic [ref=e53]: "13"
|
|
||||||
- generic [ref=e54]: "14"
|
|
||||||
- generic [ref=e55]: "15"
|
|
||||||
- generic [ref=e56]: "16"
|
|
||||||
- generic [ref=e57]: "17"
|
|
||||||
- generic [ref=e58]: "18"
|
|
||||||
- generic [ref=e59]: "19"
|
|
||||||
- generic [ref=e60]: "20"
|
|
||||||
- generic [ref=e61]: "21"
|
|
||||||
- generic [ref=e62]: "22"
|
|
||||||
- generic [ref=e63]: "23"
|
|
||||||
- generic [ref=e64]: "24"
|
|
||||||
- generic [ref=e65]: "25"
|
|
||||||
- generic [ref=e66]: "26"
|
|
||||||
- generic [ref=e67]: "27"
|
|
||||||
- generic [ref=e68]: "28"
|
|
||||||
- generic [ref=e69]: "29"
|
|
||||||
- generic [ref=e70]: "30"
|
|
||||||
- generic [ref=e72]:
|
|
||||||
- tablist [ref=e73]:
|
|
||||||
- tab "⚔️ Spire" [selected] [ref=e74]
|
|
||||||
- tab "✨ Attune" [ref=e75]
|
|
||||||
- tab "🗿 Golems" [ref=e76]
|
|
||||||
- tab "📚 Skills" [ref=e77]
|
|
||||||
- tab "🔮 Spells" [ref=e78]
|
|
||||||
- tab "🛡️ Gear" [ref=e79]
|
|
||||||
- tab "🔧 Craft" [ref=e80]
|
|
||||||
- tab "💎 Loot" [ref=e81]
|
|
||||||
- tab "🏆 Achieve" [ref=e82]
|
|
||||||
- tab "📊 Stats" [ref=e83]
|
|
||||||
- tab "🐛 Debug" [ref=e84]
|
|
||||||
- tab "📖 Grimoire" [ref=e85]
|
|
||||||
- tabpanel "⚔️ Spire" [ref=e86]:
|
|
||||||
- generic [ref=e87]:
|
|
||||||
- generic [ref=e89]:
|
|
||||||
- button "Exit Spire Mode" [ref=e90]:
|
|
||||||
- img
|
|
||||||
- text: Exit Spire Mode
|
|
||||||
- generic [ref=e91]: Climb down to floor 1 to return to the main game
|
|
||||||
- generic [ref=e92]:
|
|
||||||
- heading "Current Floor 🐝 Swarm" [level=3] [ref=e94]:
|
|
||||||
- generic [ref=e95]: Current Floor
|
|
||||||
- generic [ref=e96]: 🐝 Swarm
|
|
||||||
- generic [ref=e97]:
|
|
||||||
- generic [ref=e98]:
|
|
||||||
- generic [ref=e99]: "1"
|
|
||||||
- generic [ref=e100]: / 100
|
|
||||||
- generic [ref=e101]: 🔥 Fire
|
|
||||||
- generic [ref=e102]:
|
|
||||||
- text: "Best: Floor"
|
|
||||||
- strong [ref=e103]: "1"
|
|
||||||
- text: "• Pacts:"
|
|
||||||
- strong [ref=e104]: "0"
|
|
||||||
- generic [ref=e106]:
|
|
||||||
- generic [ref=e108]: Active Spells (1)
|
|
||||||
- generic [ref=e110]:
|
|
||||||
- generic [ref=e111]:
|
|
||||||
- generic [ref=e112]: Mana BoltBasic
|
|
||||||
- generic [ref=e113]: ✓
|
|
||||||
- generic [ref=e114]: ⚔️ 5 dmg • 3 raw • ⚡ 15 dmg/hr
|
|
||||||
- generic [ref=e115]:
|
|
||||||
- generic [ref=e116]: Swarm Enemies (6)
|
|
||||||
- generic [ref=e118]:
|
|
||||||
- generic [ref=e119]:
|
|
||||||
- img [ref=e120]
|
|
||||||
- generic [ref=e125]: Emberling
|
|
||||||
- generic [ref=e126]: 🔥 60/60 HP
|
|
||||||
- generic [ref=e130]:
|
|
||||||
- generic [ref=e131]:
|
|
||||||
- img [ref=e132]
|
|
||||||
- generic [ref=e137]: Fire Imp
|
|
||||||
- generic [ref=e138]: 🔥 60/60 HP
|
|
||||||
- generic [ref=e142]:
|
|
||||||
- generic [ref=e143]:
|
|
||||||
- img [ref=e144]
|
|
||||||
- generic [ref=e149]: Scorchling
|
|
||||||
- generic [ref=e150]: 🔥 60/60 HP
|
|
||||||
- generic [ref=e154]:
|
|
||||||
- generic [ref=e155]:
|
|
||||||
- img [ref=e156]
|
|
||||||
- generic [ref=e161]: Flame Sprite
|
|
||||||
- generic [ref=e162]: 🔥 60/60 HP
|
|
||||||
- generic [ref=e166]:
|
|
||||||
- generic [ref=e167]:
|
|
||||||
- img [ref=e168]
|
|
||||||
- generic [ref=e173]: Emberling
|
|
||||||
- generic [ref=e174]: 🔥 60/60 HP
|
|
||||||
- generic [ref=e178]:
|
|
||||||
- generic [ref=e179]:
|
|
||||||
- img [ref=e180]
|
|
||||||
- generic [ref=e185]: Inferno Whelp
|
|
||||||
- generic [ref=e186]: 🔥 60/60 HP
|
|
||||||
- generic [ref=e189]:
|
|
||||||
- generic [ref=e191]: Floor Navigation
|
|
||||||
- generic [ref=e192]:
|
|
||||||
- generic [ref=e193]:
|
|
||||||
- button "Climb Up" [ref=e194]:
|
|
||||||
- img
|
|
||||||
- text: Climb Up
|
|
||||||
- button "Climb Down" [disabled]:
|
|
||||||
- img
|
|
||||||
- text: Climb Down
|
|
||||||
- generic [ref=e195]: Click Climb Up/Down to begin climbing
|
|
||||||
- generic [ref=e196]:
|
|
||||||
- generic [ref=e198]: Combat Stats
|
|
||||||
- generic [ref=e199]:
|
|
||||||
- generic [ref=e200]: "Total DPS: —"
|
|
||||||
- generic [ref=e201]:
|
|
||||||
- generic [ref=e202]: Active Spells
|
|
||||||
- generic [ref=e203]:
|
|
||||||
- generic [ref=e204]:
|
|
||||||
- generic [ref=e205]:
|
|
||||||
- text: Mana Bolt
|
|
||||||
- generic [ref=e206]: Basic
|
|
||||||
- generic [ref=e207]: ✓
|
|
||||||
- generic [ref=e208]: ⚔️ 5 dmg • 3 raw • ⚡ 15 dmg/hr
|
|
||||||
- generic [ref=e210]: "Study Speed: 100%"
|
|
||||||
- generic [ref=e211]:
|
|
||||||
- generic [ref=e213]: Activity Log
|
|
||||||
- generic [ref=e219]: No activity yet...
|
|
||||||
- region "Notifications (F8)":
|
|
||||||
- list
|
|
||||||
- region "Notifications (F8)":
|
|
||||||
- list
|
|
||||||
- button "Open Next.js Dev Tools" [ref=e225] [cursor=pointer]:
|
|
||||||
- img [ref=e226]
|
|
||||||
- alert [ref=e229]
|
|
||||||
```
|
|
||||||
|
|
||||||
# Test source
|
|
||||||
|
|
||||||
```ts
|
|
||||||
1 | import { test, expect } from '@playwright/test';
|
|
||||||
2 |
|
|
||||||
3 | /**
|
|
||||||
4 | * E2E tests for combat system:
|
|
||||||
5 | * - Entering spire mode (climbing)
|
|
||||||
6 | * - Casting spells and seeing progress
|
|
||||||
7 | * - Enemy HP reduction
|
|
||||||
8 | * - Floor advancement
|
|
||||||
9 | */
|
|
||||||
10 |
|
|
||||||
11 | test.describe('Combat System', () => {
|
|
||||||
12 | test.beforeEach(async ({ page }) => {
|
|
||||||
13 | await page.goto('/');
|
|
||||||
14 | // Clear game state to ensure a fresh start
|
|
||||||
15 | await page.evaluate(() => {
|
|
||||||
16 | Object.keys(localStorage)
|
|
||||||
17 | .filter((k) => k.startsWith('mana-loop-'))
|
|
||||||
18 | .forEach((k) => localStorage.removeItem(k));
|
|
||||||
19 | });
|
|
||||||
20 | await page.reload();
|
|
||||||
21 | await page.waitForLoadState('networkidle');
|
|
||||||
22 | });
|
|
||||||
23 |
|
|
||||||
24 | test('can see the Spire tab and "Climb the Spire" button', async ({ page }) => {
|
|
||||||
25 | // The Spire tab uses an icon + text, so match by the tab role
|
|
||||||
26 | const spireTab = page.getByRole('tab', { name: /⚔️ Spire/ });
|
|
||||||
27 | await expect(spireTab).toBeVisible();
|
|
||||||
28 |
|
|
||||||
29 | // Main page should show "Climb the Spire" button
|
|
||||||
30 | const climbBtn = page.getByRole('button', { name: 'Climb the Spire' });
|
|
||||||
31 | await expect(climbBtn).toBeVisible();
|
|
||||||
32 | });
|
|
||||||
33 |
|
|
||||||
34 | test('can enter Spire mode by clicking Climb button', async ({ page }) => {
|
|
||||||
35 | // Click "Climb the Spire" button on the main page (via left panel)
|
|
||||||
36 | await page.getByRole('button', { name: 'Climb the Spire' }).click();
|
|
||||||
37 |
|
|
||||||
38 | // Should now see Spire mode UI elements
|
|
||||||
39 | // The "Enter Spire Mode" button appears when on the Spire tab
|
|
||||||
40 | const enterBtn = page.getByRole('button', { name: 'Enter Spire Mode' });
|
|
||||||
41 | await expect(enterBtn).toBeVisible({ timeout: 5000 });
|
|
||||||
42 | });
|
|
||||||
43 |
|
|
||||||
44 | test('can navigate to Spire tab', async ({ page }) => {
|
|
||||||
45 | // Click the Spire tab specifically (using role=tab to disambiguate)
|
|
||||||
46 | await page.getByRole('tab', { name: /⚔️ Spire/ }).click();
|
|
||||||
47 |
|
|
||||||
48 | // Should see Spire-specific UI
|
|
||||||
49 | const enterSpireBtn = page.getByRole('button', { name: 'Enter Spire Mode' });
|
|
||||||
50 | await expect(enterSpireBtn).toBeVisible({ timeout: 5000 });
|
|
||||||
51 | });
|
|
||||||
52 |
|
|
||||||
53 | test('can enter spire mode from the Spire tab', async ({ page }) => {
|
|
||||||
54 | await page.getByRole('tab', { name: /⚔️ Spire/ }).click();
|
|
||||||
55 |
|
|
||||||
56 | const enterBtn = page.getByRole('button', { name: 'Enter Spire Mode' });
|
|
||||||
57 | await expect(enterBtn).toBeEnabled();
|
|
||||||
58 | await enterBtn.click();
|
|
||||||
59 |
|
|
||||||
60 | // After entering, should see exit button
|
|
||||||
61 | const exitBtn = page.getByRole('button', { name: 'Exit Spire Mode' });
|
|
||||||
62 | await expect(exitBtn).toBeVisible({ timeout: 5000 });
|
|
||||||
63 | });
|
|
||||||
64 |
|
|
||||||
65 | test('shows floor information in spire mode', async ({ page }) => {
|
|
||||||
66 | await page.getByRole('tab', { name: /⚔️ Spire/ }).click();
|
|
||||||
67 | await page.getByRole('button', { name: 'Enter Spire Mode' }).click();
|
|
||||||
68 |
|
|
||||||
69 | // Should display floor number - look for "Floor" label or the floor counter
|
|
||||||
70 | const floorDisplay = page.locator('text="Floor"').first();
|
|
||||||
> 71 | await expect(floorDisplay).toBeVisible({ timeout: 5000 });
|
|
||||||
| ^ Error: expect(locator).toBeVisible() failed
|
|
||||||
72 | });
|
|
||||||
73 | });
|
|
||||||
```
|
|
||||||
|
Before Width: | Height: | Size: 252 KiB |
|
Before Width: | Height: | Size: 243 KiB |
|
Before Width: | Height: | Size: 258 KiB |
@@ -1,348 +0,0 @@
|
|||||||
# Instructions
|
|
||||||
|
|
||||||
- Following Playwright test failed.
|
|
||||||
- Explain why, be concise, respect Playwright best practices.
|
|
||||||
- Provide a snippet of code with the fix, if possible.
|
|
||||||
|
|
||||||
# Test info
|
|
||||||
|
|
||||||
- Name: equipment.spec.ts >> Equipment Management >> can unequip an item from a slot
|
|
||||||
- Location: e2e/equipment.spec.ts:113:7
|
|
||||||
|
|
||||||
# Error details
|
|
||||||
|
|
||||||
```
|
|
||||||
Error: expect(locator).toBeVisible() failed
|
|
||||||
|
|
||||||
Locator: locator('text=Hands').locator('..').locator('button').first()
|
|
||||||
Expected: visible
|
|
||||||
Timeout: 5000ms
|
|
||||||
Error: element(s) not found
|
|
||||||
|
|
||||||
Call log:
|
|
||||||
- Expect "toBeVisible" with timeout 5000ms
|
|
||||||
- waiting for locator('text=Hands').locator('..').locator('button').first()
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
# Page snapshot
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
- generic [ref=e1]:
|
|
||||||
- generic [ref=e2]:
|
|
||||||
- banner [ref=e3]:
|
|
||||||
- generic [ref=e4]:
|
|
||||||
- heading "MANA LOOP" [level=1] [ref=e5]
|
|
||||||
- generic [ref=e7]:
|
|
||||||
- generic [ref=e8]:
|
|
||||||
- generic [ref=e9]: Day 1
|
|
||||||
- generic [ref=e10]: 01:55
|
|
||||||
- generic [ref=e11]:
|
|
||||||
- generic [ref=e12]: "0"
|
|
||||||
- generic [ref=e13]: Insight
|
|
||||||
- main [ref=e14]:
|
|
||||||
- generic [ref=e15]:
|
|
||||||
- generic [ref=e17]:
|
|
||||||
- generic [ref=e18]:
|
|
||||||
- generic [ref=e19]:
|
|
||||||
- generic [ref=e20]: "14"
|
|
||||||
- generic [ref=e21]: / 100
|
|
||||||
- generic [ref=e22]:
|
|
||||||
- text: +2.8 mana/hr
|
|
||||||
- generic [ref=e23]: (1.4x med)
|
|
||||||
- progressbar [ref=e24]
|
|
||||||
- button "Gather +1 Mana" [ref=e26]:
|
|
||||||
- img
|
|
||||||
- text: Gather +1 Mana
|
|
||||||
- generic [ref=e27]:
|
|
||||||
- button "Elemental Mana (1)" [ref=e28]:
|
|
||||||
- generic [ref=e29]: Elemental Mana (1)
|
|
||||||
- img [ref=e30]
|
|
||||||
- generic [ref=e33]:
|
|
||||||
- generic [ref=e34]:
|
|
||||||
- generic [ref=e35]: 🔗
|
|
||||||
- generic [ref=e36]: Transference
|
|
||||||
- generic [ref=e39]: 0/10
|
|
||||||
- button "Climb the Spire" [ref=e40]:
|
|
||||||
- img
|
|
||||||
- text: Climb the Spire
|
|
||||||
- generic [ref=e42]:
|
|
||||||
- generic [ref=e43]:
|
|
||||||
- img [ref=e44]
|
|
||||||
- generic [ref=e46]: Current Activity
|
|
||||||
- generic [ref=e47]: Meditating
|
|
||||||
- generic [ref=e48]:
|
|
||||||
- generic [ref=e49]: "1"
|
|
||||||
- generic [ref=e50]: "2"
|
|
||||||
- generic [ref=e51]: "3"
|
|
||||||
- generic [ref=e52]: "4"
|
|
||||||
- generic [ref=e53]: "5"
|
|
||||||
- generic [ref=e54]: "6"
|
|
||||||
- generic [ref=e55]: "7"
|
|
||||||
- generic [ref=e56]: "8"
|
|
||||||
- generic [ref=e57]: "9"
|
|
||||||
- generic [ref=e58]: "10"
|
|
||||||
- generic [ref=e59]: "11"
|
|
||||||
- generic [ref=e60]: "12"
|
|
||||||
- generic [ref=e61]: "13"
|
|
||||||
- generic [ref=e62]: "14"
|
|
||||||
- generic [ref=e63]: "15"
|
|
||||||
- generic [ref=e64]: "16"
|
|
||||||
- generic [ref=e65]: "17"
|
|
||||||
- generic [ref=e66]: "18"
|
|
||||||
- generic [ref=e67]: "19"
|
|
||||||
- generic [ref=e68]: "20"
|
|
||||||
- generic [ref=e69]: "21"
|
|
||||||
- generic [ref=e70]: "22"
|
|
||||||
- generic [ref=e71]: "23"
|
|
||||||
- generic [ref=e72]: "24"
|
|
||||||
- generic [ref=e73]: "25"
|
|
||||||
- generic [ref=e74]: "26"
|
|
||||||
- generic [ref=e75]: "27"
|
|
||||||
- generic [ref=e76]: "28"
|
|
||||||
- generic [ref=e77]: "29"
|
|
||||||
- generic [ref=e78]: "30"
|
|
||||||
- generic [ref=e80]:
|
|
||||||
- tablist [ref=e81]:
|
|
||||||
- tab "⚔️ Spire" [ref=e82]
|
|
||||||
- tab "✨ Attune" [ref=e83]
|
|
||||||
- tab "🗿 Golems" [ref=e84]
|
|
||||||
- tab "📚 Skills" [ref=e85]
|
|
||||||
- tab "🔮 Spells" [ref=e86]
|
|
||||||
- tab "🛡️ Gear" [active] [selected] [ref=e87]
|
|
||||||
- tab "🔧 Craft" [ref=e88]
|
|
||||||
- tab "💎 Loot" [ref=e89]
|
|
||||||
- tab "🏆 Achieve" [ref=e90]
|
|
||||||
- tab "📊 Stats" [ref=e91]
|
|
||||||
- tab "🐛 Debug" [ref=e92]
|
|
||||||
- tab "📖 Grimoire" [ref=e93]
|
|
||||||
- tabpanel "🛡️ Gear" [ref=e94]:
|
|
||||||
- generic [ref=e95]:
|
|
||||||
- generic [ref=e96]:
|
|
||||||
- generic [ref=e97]:
|
|
||||||
- heading "Equipped Gear" [level=3] [ref=e98]
|
|
||||||
- generic [ref=e100]: 4 / 8 slots filled
|
|
||||||
- generic [ref=e101]:
|
|
||||||
- generic [ref=e102]:
|
|
||||||
- heading "Weapon & Shield" [level=4] [ref=e103]
|
|
||||||
- generic [ref=e104]:
|
|
||||||
- 'button "Main Hand slot: Basic Staff" [ref=e106]':
|
|
||||||
- generic [ref=e107]:
|
|
||||||
- generic [ref=e108]:
|
|
||||||
- img [ref=e109]
|
|
||||||
- generic [ref=e114]: Main Hand
|
|
||||||
- button "Unequip Basic Staff" [ref=e115]:
|
|
||||||
- img [ref=e116]
|
|
||||||
- generic [ref=e119]:
|
|
||||||
- generic [ref=e120]:
|
|
||||||
- text: Basic Staff
|
|
||||||
- generic [ref=e121]: 2-Handed
|
|
||||||
- generic [ref=e122]: "Enchantments: 1/50"
|
|
||||||
- generic [ref=e124]: Mana Bolt
|
|
||||||
- button "Off Hand slot (blocked by 2-handed weapon) (empty)" [ref=e125]:
|
|
||||||
- generic [ref=e127]:
|
|
||||||
- img [ref=e128]
|
|
||||||
- generic [ref=e130]: Off Hand
|
|
||||||
- generic [ref=e131]:
|
|
||||||
- img
|
|
||||||
- text: Occupied — 2H Weapon
|
|
||||||
- generic [ref=e132]:
|
|
||||||
- img [ref=e133]
|
|
||||||
- text: Blocked by 2-handed weapon
|
|
||||||
- generic [ref=e135]:
|
|
||||||
- heading "Armor" [level=4] [ref=e136]
|
|
||||||
- generic [ref=e137]:
|
|
||||||
- button "Head slot (empty)" [ref=e139]:
|
|
||||||
- generic [ref=e141]:
|
|
||||||
- img [ref=e142]
|
|
||||||
- generic [ref=e147]: Head
|
|
||||||
- generic [ref=e148]: Head
|
|
||||||
- 'button "Body slot: Civilian Shirt" [ref=e150]':
|
|
||||||
- generic [ref=e151]:
|
|
||||||
- generic [ref=e152]:
|
|
||||||
- img [ref=e153]
|
|
||||||
- generic [ref=e155]: Body
|
|
||||||
- button "Unequip Civilian Shirt" [ref=e156]:
|
|
||||||
- img [ref=e157]
|
|
||||||
- generic [ref=e160]:
|
|
||||||
- generic [ref=e161]: Civilian Shirt
|
|
||||||
- generic [ref=e162]: "Enchantments: 0/30"
|
|
||||||
- 'button "Hands slot: Civilian Gloves" [ref=e164]':
|
|
||||||
- generic [ref=e165]:
|
|
||||||
- generic [ref=e166]:
|
|
||||||
- img [ref=e167]
|
|
||||||
- generic [ref=e172]: Hands
|
|
||||||
- button "Unequip Civilian Gloves" [ref=e173]:
|
|
||||||
- img [ref=e174]
|
|
||||||
- generic [ref=e177]:
|
|
||||||
- generic [ref=e178]: Civilian Gloves
|
|
||||||
- generic [ref=e179]: "Enchantments: 0/20"
|
|
||||||
- 'button "Feet slot: Civilian Shoes" [ref=e181]':
|
|
||||||
- generic [ref=e182]:
|
|
||||||
- generic [ref=e183]:
|
|
||||||
- img [ref=e184]
|
|
||||||
- generic [ref=e187]: Feet
|
|
||||||
- button "Unequip Civilian Shoes" [ref=e188]:
|
|
||||||
- img [ref=e189]
|
|
||||||
- generic [ref=e192]:
|
|
||||||
- generic [ref=e193]: Civilian Shoes
|
|
||||||
- generic [ref=e194]: "Enchantments: 0/15"
|
|
||||||
- generic [ref=e195]:
|
|
||||||
- heading "Accessories" [level=4] [ref=e196]
|
|
||||||
- generic [ref=e197]:
|
|
||||||
- button "Accessory 1 slot (empty)" [ref=e199]:
|
|
||||||
- generic [ref=e201]:
|
|
||||||
- img [ref=e202]
|
|
||||||
- generic [ref=e205]: Accessory 1
|
|
||||||
- generic [ref=e206]: Accessory 1
|
|
||||||
- button "Accessory 2 slot (empty)" [ref=e208]:
|
|
||||||
- generic [ref=e210]:
|
|
||||||
- img [ref=e211]
|
|
||||||
- generic [ref=e214]: Accessory 2
|
|
||||||
- generic [ref=e215]: Accessory 2
|
|
||||||
- generic [ref=e216]:
|
|
||||||
- heading "Equipment Inventory (0 items)" [level=3] [ref=e218]
|
|
||||||
- status [ref=e219]: No unequipped items. Craft new gear in the Crafting tab.
|
|
||||||
- generic [ref=e220]:
|
|
||||||
- heading "Equipment Stats Summary" [level=3] [ref=e222]
|
|
||||||
- generic [ref=e223]:
|
|
||||||
- generic [ref=e224]:
|
|
||||||
- generic [ref=e225]: "4"
|
|
||||||
- generic [ref=e226]: Total Items
|
|
||||||
- generic [ref=e227]:
|
|
||||||
- generic [ref=e228]: "4"
|
|
||||||
- generic [ref=e229]: Equipped
|
|
||||||
- generic [ref=e230]:
|
|
||||||
- generic [ref=e231]: "0"
|
|
||||||
- generic [ref=e232]: In Inventory
|
|
||||||
- generic [ref=e233]:
|
|
||||||
- generic [ref=e234]: "1"
|
|
||||||
- generic [ref=e235]: Total Enchantments
|
|
||||||
- generic [ref=e236]:
|
|
||||||
- heading "✨ Enchantment Power" [level=3] [ref=e238]
|
|
||||||
- generic [ref=e239]:
|
|
||||||
- generic [ref=e240]:
|
|
||||||
- generic [ref=e241]: "Enchantment Power:"
|
|
||||||
- generic [ref=e242]: 1.00×
|
|
||||||
- paragraph [ref=e243]: Increases the power of all enchantments by 0%. Multiplier applied to all enchantment effects.
|
|
||||||
- generic [ref=e244]:
|
|
||||||
- generic [ref=e245]: "Active Effects from Equipment:"
|
|
||||||
- generic [ref=e247]: No active effects
|
|
||||||
- region "Notifications (F8)":
|
|
||||||
- list
|
|
||||||
- region "Notifications (F8)":
|
|
||||||
- list
|
|
||||||
- button "Open Next.js Dev Tools" [ref=e253] [cursor=pointer]:
|
|
||||||
- img [ref=e254]
|
|
||||||
- alert [ref=e257]
|
|
||||||
```
|
|
||||||
|
|
||||||
# Test source
|
|
||||||
|
|
||||||
```ts
|
|
||||||
28 |
|
|
||||||
29 | // Verify equipment UI elements
|
|
||||||
30 | const equippedGearHeading = page.locator('text="Equipped Gear"');
|
|
||||||
31 | await expect(equippedGearHeading).toBeVisible({ timeout: 5000 });
|
|
||||||
32 | });
|
|
||||||
33 |
|
|
||||||
34 | test('shows equipment slots with labels', async ({ page }) => {
|
|
||||||
35 | await page.goto('/');
|
|
||||||
36 | await page.evaluate(() => {
|
|
||||||
37 | Object.keys(localStorage)
|
|
||||||
38 | .filter((k) => k.startsWith('mana-loop-'))
|
|
||||||
39 | .forEach((k) => localStorage.removeItem(k));
|
|
||||||
40 | });
|
|
||||||
41 | await page.reload();
|
|
||||||
42 | await page.waitForLoadState('networkidle');
|
|
||||||
43 |
|
|
||||||
44 | await page.getByRole('tab', { name: /🛡️ Gear/ }).click();
|
|
||||||
45 |
|
|
||||||
46 | // Check for expected slot labels - use role=heading or more specific selectors
|
|
||||||
47 | // Main Hand slot
|
|
||||||
48 | const mainHandSection = page.locator('text=Main Hand');
|
|
||||||
49 | await expect(mainHandSection.first()).toBeVisible();
|
|
||||||
50 |
|
|
||||||
51 | // Off Hand
|
|
||||||
52 | const offHandSection = page.locator('text=Off Hand');
|
|
||||||
53 | await expect(offHandSection.first()).toBeVisible();
|
|
||||||
54 |
|
|
||||||
55 | // Head
|
|
||||||
56 | const headSection = page.locator('text=Head');
|
|
||||||
57 | await expect(headSection.first()).toBeVisible();
|
|
||||||
58 |
|
|
||||||
59 | // Body
|
|
||||||
60 | const bodySection = page.locator('text=Body');
|
|
||||||
61 | await expect(bodySection.first()).toBeVisible();
|
|
||||||
62 |
|
|
||||||
63 | // Hands
|
|
||||||
64 | const handsSection = page.locator('text=Hands');
|
|
||||||
65 | await expect(handsSection.first()).toBeVisible();
|
|
||||||
66 |
|
|
||||||
67 | // Feet
|
|
||||||
68 | const feetSection = page.locator('text=Feet');
|
|
||||||
69 | await expect(feetSection.first()).toBeVisible();
|
|
||||||
70 |
|
|
||||||
71 | // Accessory 1 and 2
|
|
||||||
72 | const acc1Section = page.locator('text=Accessory 1');
|
|
||||||
73 | await expect(acc1Section.first()).toBeVisible();
|
|
||||||
74 | const acc2Section = page.locator('text=Accessory 2');
|
|
||||||
75 | await expect(acc2Section.first()).toBeVisible();
|
|
||||||
76 | });
|
|
||||||
77 |
|
|
||||||
78 | test('shows starting equipment already equipped', async ({ page }) => {
|
|
||||||
79 | await page.goto('/');
|
|
||||||
80 | await page.evaluate(() => {
|
|
||||||
81 | Object.keys(localStorage)
|
|
||||||
82 | .filter((k) => k.startsWith('mana-loop-'))
|
|
||||||
83 | .forEach((k) => localStorage.removeItem(k));
|
|
||||||
84 | });
|
|
||||||
85 | await page.reload();
|
|
||||||
86 | await page.waitForLoadState('networkidle');
|
|
||||||
87 |
|
|
||||||
88 | await page.getByRole('tab', { name: /🛡️ Gear/ }).click();
|
|
||||||
89 |
|
|
||||||
90 | // The player starts with a Basic Staff in main hand (as an equipped item)
|
|
||||||
91 | const mainHandSlot = page.locator('text=Main Hand >> .. >> text=Basic Staff');
|
|
||||||
92 | await expect(mainHandSlot).toBeVisible({ timeout: 5000 });
|
|
||||||
93 | });
|
|
||||||
94 |
|
|
||||||
95 | test('2-handed weapon blocks offhand slot', async ({ page }) => {
|
|
||||||
96 | await page.goto('/');
|
|
||||||
97 | await page.evaluate(() => {
|
|
||||||
98 | Object.keys(localStorage)
|
|
||||||
99 | .filter((k) => k.startsWith('mana-loop-'))
|
|
||||||
100 | .forEach((k) => localStorage.removeItem(k));
|
|
||||||
101 | });
|
|
||||||
102 | await page.reload();
|
|
||||||
103 | await page.waitForLoadState('networkidle');
|
|
||||||
104 |
|
|
||||||
105 | await page.getByRole('tab', { name: /🛡️ Gear/ }).click();
|
|
||||||
106 |
|
|
||||||
107 | // The starting basic staff is 2-handed
|
|
||||||
108 | // The offhand slot should show as blocked with "Occupied — 2H Weapon"
|
|
||||||
109 | const offHandBlocked = page.locator('text=Occupied').first();
|
|
||||||
110 | await expect(offHandBlocked).toBeVisible({ timeout: 5000 });
|
|
||||||
111 | });
|
|
||||||
112 |
|
|
||||||
113 | test('can unequip an item from a slot', async ({ page }) => {
|
|
||||||
114 | await page.goto('/');
|
|
||||||
115 | await page.evaluate(() => {
|
|
||||||
116 | Object.keys(localStorage)
|
|
||||||
117 | .filter((k) => k.startsWith('mana-loop-'))
|
|
||||||
118 | .forEach((k) => localStorage.removeItem(k));
|
|
||||||
119 | });
|
|
||||||
120 | await page.reload();
|
|
||||||
121 | await page.waitForLoadState('networkidle');
|
|
||||||
122 |
|
|
||||||
123 | await page.getByRole('tab', { name: /🛡️ Gear/ }).click();
|
|
||||||
124 |
|
|
||||||
125 | // Find an equiped slot with an unequip button (the X button)
|
|
||||||
126 | // The hands slot has civilian gloves equipped
|
|
||||||
127 | const handsSlot = page.locator('text=Hands >> .. >> button').first();
|
|
||||||
> 128 | await expect(handsSlot).toBeVisible({ timeout: 5000 });
|
|
||||||
| ^ Error: expect(locator).toBeVisible() failed
|
|
||||||
129 | // Note: exact behavior of unequip depends on implementation state
|
|
||||||
130 | });
|
|
||||||
131 | });
|
|
||||||
```
|
|
||||||
@@ -1,285 +0,0 @@
|
|||||||
# Instructions
|
|
||||||
|
|
||||||
- Following Playwright test failed.
|
|
||||||
- Explain why, be concise, respect Playwright best practices.
|
|
||||||
- Provide a snippet of code with the fix, if possible.
|
|
||||||
|
|
||||||
# Test info
|
|
||||||
|
|
||||||
- Name: enchanting.spec.ts >> Enchanting Flow >> can navigate to Crafting tab
|
|
||||||
- Location: e2e/enchanting.spec.ts:28:7
|
|
||||||
|
|
||||||
# Error details
|
|
||||||
|
|
||||||
```
|
|
||||||
Error: expect(locator).toBeVisible() failed
|
|
||||||
|
|
||||||
Locator: getByRole('button')
|
|
||||||
Expected: visible
|
|
||||||
Error: strict mode violation: getByRole('button') resolved to 6 elements:
|
|
||||||
1) <button data-slot="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive text-primary-foreground shad…>…</button> aka getByRole('button', { name: 'Gather +1 Mana' })
|
|
||||||
2) <button class="flex items-center justify-between w-full text-xs text-gray-400 hover:text-gray-300 mb-2">…</button> aka getByRole('button', { name: 'Elemental Mana (1)' })
|
|
||||||
3) <button data-slot="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive text-primary-foreground shadow-xs hover…>…</button> aka getByRole('button', { name: 'Climb the Spire' })
|
|
||||||
4) <button data-slot="action-button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-[var(--radius)] text-sm font-medium transition-all duration-100 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:ring-2 focus-visible:ring-[var(--border-focus)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--bg-base)] bg-[var(--interactive-primary)] text-white …>…</button> aka getByRole('button', { name: 'Fabricate' })
|
|
||||||
5) <button data-slot="action-button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-[var(--radius)] text-sm font-medium transition-all duration-100 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:ring-2 focus-visible:ring-[var(--border-focus)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--bg-base)] bg-[var(--interactive-secondary)] text-[var…>…</button> aka getByRole('button', { name: 'Enchant' })
|
|
||||||
6) <button id="next-logo" aria-haspopup="menu" data-next-mark="true" aria-expanded="false" aria-label="Open Next.js Dev Tools" data-nextjs-dev-tools-button="true" aria-controls="nextjs-dev-tools-menu">…</button> aka getByRole('button', { name: 'Open Next.js Dev Tools' })
|
|
||||||
|
|
||||||
Call log:
|
|
||||||
- Expect "toBeVisible" with timeout 5000ms
|
|
||||||
- waiting for getByRole('button')
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
# Page snapshot
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
- generic [ref=e1]:
|
|
||||||
- generic [ref=e2]:
|
|
||||||
- banner [ref=e3]:
|
|
||||||
- generic [ref=e4]:
|
|
||||||
- heading "MANA LOOP" [level=1] [ref=e5]
|
|
||||||
- generic [ref=e7]:
|
|
||||||
- generic [ref=e8]:
|
|
||||||
- generic [ref=e9]: Day 1
|
|
||||||
- generic [ref=e10]: 00:55
|
|
||||||
- generic [ref=e11]:
|
|
||||||
- generic [ref=e12]: "0"
|
|
||||||
- generic [ref=e13]: Insight
|
|
||||||
- main [ref=e14]:
|
|
||||||
- generic [ref=e15]:
|
|
||||||
- generic [ref=e17]:
|
|
||||||
- generic [ref=e18]:
|
|
||||||
- generic [ref=e19]:
|
|
||||||
- generic [ref=e20]: "11"
|
|
||||||
- generic [ref=e21]: / 100
|
|
||||||
- generic [ref=e22]:
|
|
||||||
- text: +2.4 mana/hr
|
|
||||||
- generic [ref=e23]: (1.2x med)
|
|
||||||
- progressbar [ref=e24]
|
|
||||||
- button "Gather +1 Mana" [ref=e26]:
|
|
||||||
- img
|
|
||||||
- text: Gather +1 Mana
|
|
||||||
- generic [ref=e27]:
|
|
||||||
- button "Elemental Mana (1)" [ref=e28]:
|
|
||||||
- generic [ref=e29]: Elemental Mana (1)
|
|
||||||
- img [ref=e30]
|
|
||||||
- generic [ref=e33]:
|
|
||||||
- generic [ref=e34]:
|
|
||||||
- generic [ref=e35]: 🔗
|
|
||||||
- generic [ref=e36]: Transference
|
|
||||||
- generic [ref=e39]: 0/10
|
|
||||||
- button "Climb the Spire" [ref=e40]:
|
|
||||||
- img
|
|
||||||
- text: Climb the Spire
|
|
||||||
- generic [ref=e42]:
|
|
||||||
- generic [ref=e43]:
|
|
||||||
- img [ref=e44]
|
|
||||||
- generic [ref=e46]: Current Activity
|
|
||||||
- generic [ref=e47]: Meditating
|
|
||||||
- generic [ref=e48]:
|
|
||||||
- generic [ref=e49]: "1"
|
|
||||||
- generic [ref=e50]: "2"
|
|
||||||
- generic [ref=e51]: "3"
|
|
||||||
- generic [ref=e52]: "4"
|
|
||||||
- generic [ref=e53]: "5"
|
|
||||||
- generic [ref=e54]: "6"
|
|
||||||
- generic [ref=e55]: "7"
|
|
||||||
- generic [ref=e56]: "8"
|
|
||||||
- generic [ref=e57]: "9"
|
|
||||||
- generic [ref=e58]: "10"
|
|
||||||
- generic [ref=e59]: "11"
|
|
||||||
- generic [ref=e60]: "12"
|
|
||||||
- generic [ref=e61]: "13"
|
|
||||||
- generic [ref=e62]: "14"
|
|
||||||
- generic [ref=e63]: "15"
|
|
||||||
- generic [ref=e64]: "16"
|
|
||||||
- generic [ref=e65]: "17"
|
|
||||||
- generic [ref=e66]: "18"
|
|
||||||
- generic [ref=e67]: "19"
|
|
||||||
- generic [ref=e68]: "20"
|
|
||||||
- generic [ref=e69]: "21"
|
|
||||||
- generic [ref=e70]: "22"
|
|
||||||
- generic [ref=e71]: "23"
|
|
||||||
- generic [ref=e72]: "24"
|
|
||||||
- generic [ref=e73]: "25"
|
|
||||||
- generic [ref=e74]: "26"
|
|
||||||
- generic [ref=e75]: "27"
|
|
||||||
- generic [ref=e76]: "28"
|
|
||||||
- generic [ref=e77]: "29"
|
|
||||||
- generic [ref=e78]: "30"
|
|
||||||
- generic [ref=e80]:
|
|
||||||
- tablist [ref=e81]:
|
|
||||||
- tab "⚔️ Spire" [ref=e82]
|
|
||||||
- tab "✨ Attune" [ref=e83]
|
|
||||||
- tab "🗿 Golems" [ref=e84]
|
|
||||||
- tab "📚 Skills" [ref=e85]
|
|
||||||
- tab "🔮 Spells" [ref=e86]
|
|
||||||
- tab "🛡️ Gear" [ref=e87]
|
|
||||||
- tab "🔧 Craft" [active] [selected] [ref=e88]
|
|
||||||
- tab "💎 Loot" [ref=e89]
|
|
||||||
- tab "🏆 Achieve" [ref=e90]
|
|
||||||
- tab "📊 Stats" [ref=e91]
|
|
||||||
- tab "🐛 Debug" [ref=e92]
|
|
||||||
- tab "📖 Grimoire" [ref=e93]
|
|
||||||
- tabpanel "🔧 Craft" [ref=e94]:
|
|
||||||
- generic [ref=e95]:
|
|
||||||
- generic [ref=e97]:
|
|
||||||
- button "Fabricate" [ref=e98]:
|
|
||||||
- img
|
|
||||||
- text: Fabricate
|
|
||||||
- button "Enchant" [ref=e99]:
|
|
||||||
- img
|
|
||||||
- text: Enchant
|
|
||||||
- generic [ref=e100]:
|
|
||||||
- generic [ref=e101]:
|
|
||||||
- generic [ref=e103]:
|
|
||||||
- img [ref=e104]
|
|
||||||
- text: Available Blueprints
|
|
||||||
- generic [ref=e113]:
|
|
||||||
- img [ref=e114]
|
|
||||||
- paragraph [ref=e118]: No blueprints discovered yet.
|
|
||||||
- paragraph [ref=e119]: Defeat guardians to find blueprints!
|
|
||||||
- generic [ref=e120]:
|
|
||||||
- generic [ref=e122]:
|
|
||||||
- img [ref=e123]
|
|
||||||
- text: Materials (0)
|
|
||||||
- generic [ref=e131]:
|
|
||||||
- img [ref=e132]
|
|
||||||
- paragraph [ref=e134]: No materials collected yet.
|
|
||||||
- paragraph [ref=e135]: Defeat floors to gather materials!
|
|
||||||
- region "Notifications (F8)":
|
|
||||||
- list
|
|
||||||
- region "Notifications (F8)":
|
|
||||||
- list
|
|
||||||
- button "Open Next.js Dev Tools" [ref=e141] [cursor=pointer]:
|
|
||||||
- img [ref=e142]
|
|
||||||
- alert [ref=e145]
|
|
||||||
```
|
|
||||||
|
|
||||||
# Test source
|
|
||||||
|
|
||||||
```ts
|
|
||||||
1 | import { test, expect } from '@playwright/test';
|
|
||||||
2 |
|
|
||||||
3 | /**
|
|
||||||
4 | * E2E tests for the 3-step enchantment flow:
|
|
||||||
5 | * Design → Prepare → Apply
|
|
||||||
6 | *
|
|
||||||
7 | * These tests validate the core crafting loop works end-to-end.
|
|
||||||
8 | */
|
|
||||||
9 |
|
|
||||||
10 | test.describe('Enchanting Flow', () => {
|
|
||||||
11 | /**
|
|
||||||
12 | * Before each test, ensure we start with a clean state.
|
|
||||||
13 | * The game persists state in localStorage, so we clear it.
|
|
||||||
14 | */
|
|
||||||
15 | test.beforeEach(async ({ page }) => {
|
|
||||||
16 | await page.goto('/');
|
|
||||||
17 | // Clear game state to ensure a fresh start
|
|
||||||
18 | await page.evaluate(() => {
|
|
||||||
19 | Object.keys(localStorage)
|
|
||||||
20 | .filter((k) => k.startsWith('mana-loop-'))
|
|
||||||
21 | .forEach((k) => localStorage.removeItem(k));
|
|
||||||
22 | });
|
|
||||||
23 | await page.reload();
|
|
||||||
24 | // Wait for the game to initialize
|
|
||||||
25 | await page.waitForLoadState('networkidle');
|
|
||||||
26 | });
|
|
||||||
27 |
|
|
||||||
28 | test('can navigate to Crafting tab', async ({ page }) => {
|
|
||||||
29 | // The tab bar contains a "Craft" tab
|
|
||||||
30 | const craftTab = page.getByRole('tab', { name: /🔧 Craft/ });
|
|
||||||
31 | await expect(craftTab).toBeVisible();
|
|
||||||
32 | await craftTab.click();
|
|
||||||
33 |
|
|
||||||
34 | // Verify we're on the crafting tab by checking for sub-tabs
|
|
||||||
35 | const fabricateBtn = page.getByRole('button', { hasText: 'Fabricate' });
|
|
||||||
36 | const enchantBtn = page.getByRole('button', { hasText: 'Enchant' });
|
|
||||||
> 37 | await expect(fabricateBtn).toBeVisible();
|
|
||||||
| ^ Error: expect(locator).toBeVisible() failed
|
|
||||||
38 | await expect(enchantBtn).toBeVisible();
|
|
||||||
39 | });
|
|
||||||
40 |
|
|
||||||
41 | test('can switch to Enchant sub-tab and see design UI', async ({ page }) => {
|
|
||||||
42 | await page.goto('/');
|
|
||||||
43 | await page.evaluate(() => {
|
|
||||||
44 | Object.keys(localStorage)
|
|
||||||
45 | .filter((k) => k.startsWith('mana-loop-'))
|
|
||||||
46 | .forEach((k) => localStorage.removeItem(k));
|
|
||||||
47 | });
|
|
||||||
48 | await page.reload();
|
|
||||||
49 | await page.waitForLoadState('networkidle');
|
|
||||||
50 |
|
|
||||||
51 | // Navigate to Crafting tab
|
|
||||||
52 | await page.getByRole('tab', { name: /🔧 Craft/ }).click();
|
|
||||||
53 |
|
|
||||||
54 | // Click Enchant sub-tab
|
|
||||||
55 | const enchantBtn = page.getByRole('button', { hasText: 'Enchant' });
|
|
||||||
56 | await enchantBtn.click();
|
|
||||||
57 |
|
|
||||||
58 | // Should see the design stage UI
|
|
||||||
59 | const designBtn = page.getByRole('button', { hasText: 'Design' });
|
|
||||||
60 | const prepareBtn = page.getByRole('button', { hasText: 'Prepare' });
|
|
||||||
61 | const applyBtn = page.getByRole('button', { hasText: 'Apply' });
|
|
||||||
62 | await expect(designBtn).toBeVisible();
|
|
||||||
63 | await expect(prepareBtn).toBeVisible();
|
|
||||||
64 | await expect(applyBtn).toBeVisible();
|
|
||||||
65 | });
|
|
||||||
66 |
|
|
||||||
67 | test('can select equipment type and effect in Design stage', async ({ page }) => {
|
|
||||||
68 | await page.goto('/');
|
|
||||||
69 | await page.evaluate(() => {
|
|
||||||
70 | Object.keys(localStorage)
|
|
||||||
71 | .filter((k) => k.startsWith('mana-loop-'))
|
|
||||||
72 | .forEach((k) => localStorage.removeItem(k));
|
|
||||||
73 | });
|
|
||||||
74 | await page.reload();
|
|
||||||
75 | await page.waitForLoadState('networkidle');
|
|
||||||
76 |
|
|
||||||
77 | // Navigate to Crafting > Enchant > Design
|
|
||||||
78 | await page.getByRole('tab', { name: /🔧 Craft/ }).click();
|
|
||||||
79 | await page.getByRole('button', { hasText: 'Enchant' }).click();
|
|
||||||
80 |
|
|
||||||
81 | // The design section should show effect selectors once an equipment type is chosen
|
|
||||||
82 | // Look for any element matching equipment type buttons and effect-related content
|
|
||||||
83 | const equipmentButtons = page.locator('button:has-text("Basic Staff"), button:has-text("Apprentice Wand"), button:has-text("Oak Staff"), button:has-text("Crystal Wand")');
|
|
||||||
84 | const count = await equipmentButtons.count();
|
|
||||||
85 | expect(count).toBeGreaterThan(0);
|
|
||||||
86 | });
|
|
||||||
87 |
|
|
||||||
88 | test('can navigate through all 3 enchant stages', async ({ page }) => {
|
|
||||||
89 | await page.goto('/');
|
|
||||||
90 | await page.evaluate(() => {
|
|
||||||
91 | Object.keys(localStorage)
|
|
||||||
92 | .filter((k) => k.startsWith('mana-loop-'))
|
|
||||||
93 | .forEach((k) => localStorage.removeItem(k));
|
|
||||||
94 | });
|
|
||||||
95 | await page.reload();
|
|
||||||
96 | await page.waitForLoadState('networkidle');
|
|
||||||
97 |
|
|
||||||
98 | // Navigate to Crafting > Enchant
|
|
||||||
99 | await page.getByRole('tab', { name: /🔧 Craft/ }).click();
|
|
||||||
100 | await page.getByRole('button', { hasText: 'Enchant' }).click();
|
|
||||||
101 |
|
|
||||||
102 | // Verify Design stage is active
|
|
||||||
103 | const designBtn = page.getByRole('button', { hasText: 'Design' });
|
|
||||||
104 | await expect(designBtn).toBeVisible();
|
|
||||||
105 |
|
|
||||||
106 | // Switch to Prepare stage
|
|
||||||
107 | const prepareBtn = page.getByRole('button', { hasText: 'Prepare' });
|
|
||||||
108 | await prepareBtn.click();
|
|
||||||
109 |
|
|
||||||
110 | // Should see preparation UI
|
|
||||||
111 | const prepareHeading = page.locator('text=Select Equipment to Prepare');
|
|
||||||
112 | await expect(prepareHeading).toBeVisible({ timeout: 5000 });
|
|
||||||
113 |
|
|
||||||
114 | // Switch to Apply stage
|
|
||||||
115 | const applyBtn = page.getByRole('button', { hasText: 'Apply' });
|
|
||||||
116 | await applyBtn.click();
|
|
||||||
117 |
|
|
||||||
118 | // Should see application UI
|
|
||||||
119 | const applyHeading = page.locator('text=Select Equipment & Design');
|
|
||||||
120 | await expect(applyHeading).toBeVisible({ timeout: 5000 });
|
|
||||||
121 | });
|
|
||||||
122 | });
|
|
||||||
```
|
|
||||||
@@ -1,260 +0,0 @@
|
|||||||
# Instructions
|
|
||||||
|
|
||||||
- Following Playwright test failed.
|
|
||||||
- Explain why, be concise, respect Playwright best practices.
|
|
||||||
- Provide a snippet of code with the fix, if possible.
|
|
||||||
|
|
||||||
# Test info
|
|
||||||
|
|
||||||
- Name: combat.spec.ts >> Combat System >> can enter Spire mode by clicking Climb button
|
|
||||||
- Location: e2e/combat.spec.ts:34:7
|
|
||||||
|
|
||||||
# Error details
|
|
||||||
|
|
||||||
```
|
|
||||||
Error: expect(locator).toBeVisible() failed
|
|
||||||
|
|
||||||
Locator: getByRole('button', { name: 'Enter Spire Mode' })
|
|
||||||
Expected: visible
|
|
||||||
Timeout: 5000ms
|
|
||||||
Error: element(s) not found
|
|
||||||
|
|
||||||
Call log:
|
|
||||||
- Expect "toBeVisible" with timeout 5000ms
|
|
||||||
- waiting for getByRole('button', { name: 'Enter Spire Mode' })
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
# Page snapshot
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
- generic [active] [ref=e1]:
|
|
||||||
- generic [ref=e2]:
|
|
||||||
- banner [ref=e3]:
|
|
||||||
- generic [ref=e4]:
|
|
||||||
- heading "MANA LOOP" [level=1] [ref=e5]
|
|
||||||
- generic [ref=e7]:
|
|
||||||
- generic [ref=e8]:
|
|
||||||
- generic [ref=e9]: Day 1
|
|
||||||
- generic [ref=e10]: 01:43
|
|
||||||
- generic [ref=e11]:
|
|
||||||
- generic [ref=e12]: "0"
|
|
||||||
- generic [ref=e13]: Insight
|
|
||||||
- main [ref=e14]:
|
|
||||||
- generic [ref=e15]:
|
|
||||||
- generic [ref=e17]:
|
|
||||||
- generic [ref=e18]:
|
|
||||||
- generic [ref=e19]:
|
|
||||||
- generic [ref=e20]: "14"
|
|
||||||
- generic [ref=e21]: / 100
|
|
||||||
- generic [ref=e22]:
|
|
||||||
- text: +2.9 mana/hr
|
|
||||||
- generic [ref=e23]: (1.4x med)
|
|
||||||
- progressbar [ref=e24]
|
|
||||||
- button "Gather +1 Mana" [ref=e26]:
|
|
||||||
- img
|
|
||||||
- text: Gather +1 Mana
|
|
||||||
- generic [ref=e27]:
|
|
||||||
- button "Elemental Mana (1)" [ref=e28]:
|
|
||||||
- generic [ref=e29]: Elemental Mana (1)
|
|
||||||
- img [ref=e30]
|
|
||||||
- generic [ref=e33]:
|
|
||||||
- generic [ref=e34]:
|
|
||||||
- generic [ref=e35]: 🔗
|
|
||||||
- generic [ref=e36]: Transference
|
|
||||||
- generic [ref=e39]: 0/10
|
|
||||||
- generic [ref=e40]:
|
|
||||||
- generic [ref=e41]: "1"
|
|
||||||
- generic [ref=e42]: "2"
|
|
||||||
- generic [ref=e43]: "3"
|
|
||||||
- generic [ref=e44]: "4"
|
|
||||||
- generic [ref=e45]: "5"
|
|
||||||
- generic [ref=e46]: "6"
|
|
||||||
- generic [ref=e47]: "7"
|
|
||||||
- generic [ref=e48]: "8"
|
|
||||||
- generic [ref=e49]: "9"
|
|
||||||
- generic [ref=e50]: "10"
|
|
||||||
- generic [ref=e51]: "11"
|
|
||||||
- generic [ref=e52]: "12"
|
|
||||||
- generic [ref=e53]: "13"
|
|
||||||
- generic [ref=e54]: "14"
|
|
||||||
- generic [ref=e55]: "15"
|
|
||||||
- generic [ref=e56]: "16"
|
|
||||||
- generic [ref=e57]: "17"
|
|
||||||
- generic [ref=e58]: "18"
|
|
||||||
- generic [ref=e59]: "19"
|
|
||||||
- generic [ref=e60]: "20"
|
|
||||||
- generic [ref=e61]: "21"
|
|
||||||
- generic [ref=e62]: "22"
|
|
||||||
- generic [ref=e63]: "23"
|
|
||||||
- generic [ref=e64]: "24"
|
|
||||||
- generic [ref=e65]: "25"
|
|
||||||
- generic [ref=e66]: "26"
|
|
||||||
- generic [ref=e67]: "27"
|
|
||||||
- generic [ref=e68]: "28"
|
|
||||||
- generic [ref=e69]: "29"
|
|
||||||
- generic [ref=e70]: "30"
|
|
||||||
- generic [ref=e72]:
|
|
||||||
- tablist [ref=e73]:
|
|
||||||
- tab "⚔️ Spire" [selected] [ref=e74]
|
|
||||||
- tab "✨ Attune" [ref=e75]
|
|
||||||
- tab "🗿 Golems" [ref=e76]
|
|
||||||
- tab "📚 Skills" [ref=e77]
|
|
||||||
- tab "🔮 Spells" [ref=e78]
|
|
||||||
- tab "🛡️ Gear" [ref=e79]
|
|
||||||
- tab "🔧 Craft" [ref=e80]
|
|
||||||
- tab "💎 Loot" [ref=e81]
|
|
||||||
- tab "🏆 Achieve" [ref=e82]
|
|
||||||
- tab "📊 Stats" [ref=e83]
|
|
||||||
- tab "🐛 Debug" [ref=e84]
|
|
||||||
- tab "📖 Grimoire" [ref=e85]
|
|
||||||
- tabpanel "⚔️ Spire" [ref=e86]:
|
|
||||||
- generic [ref=e87]:
|
|
||||||
- generic [ref=e89]:
|
|
||||||
- button "Exit Spire Mode" [ref=e90]:
|
|
||||||
- img
|
|
||||||
- text: Exit Spire Mode
|
|
||||||
- generic [ref=e91]: Climb down to floor 1 to return to the main game
|
|
||||||
- generic [ref=e92]:
|
|
||||||
- heading "Current Floor ⚔️ Combat" [level=3] [ref=e94]:
|
|
||||||
- generic [ref=e95]: Current Floor
|
|
||||||
- generic [ref=e96]: ⚔️ Combat
|
|
||||||
- generic [ref=e97]:
|
|
||||||
- generic [ref=e98]:
|
|
||||||
- generic [ref=e99]: "1"
|
|
||||||
- generic [ref=e100]: / 100
|
|
||||||
- generic [ref=e101]: 🔥 Fire
|
|
||||||
- generic [ref=e102]:
|
|
||||||
- text: "Best: Floor"
|
|
||||||
- strong [ref=e103]: "1"
|
|
||||||
- text: "• Pacts:"
|
|
||||||
- strong [ref=e104]: "0"
|
|
||||||
- generic [ref=e106]:
|
|
||||||
- generic [ref=e108]: Active Spells (1)
|
|
||||||
- generic [ref=e110]:
|
|
||||||
- generic [ref=e111]:
|
|
||||||
- generic [ref=e112]: Mana BoltBasic
|
|
||||||
- generic [ref=e113]: ✓
|
|
||||||
- generic [ref=e114]: ⚔️ 5 dmg • 3 raw • ⚡ 15 dmg/hr
|
|
||||||
- generic [ref=e115]:
|
|
||||||
- generic [ref=e116]:
|
|
||||||
- generic [ref=e117]:
|
|
||||||
- img [ref=e118]
|
|
||||||
- generic [ref=e123]: Inferno Whelp
|
|
||||||
- generic [ref=e124]: 🔥 Fire
|
|
||||||
- generic [ref=e129]: 151 / 151 HP
|
|
||||||
- generic [ref=e130]:
|
|
||||||
- generic [ref=e132]: Floor Navigation
|
|
||||||
- generic [ref=e133]:
|
|
||||||
- generic [ref=e134]:
|
|
||||||
- button "Climb Up" [ref=e135]:
|
|
||||||
- img
|
|
||||||
- text: Climb Up
|
|
||||||
- button "Climb Down" [disabled]:
|
|
||||||
- img
|
|
||||||
- text: Climb Down
|
|
||||||
- generic [ref=e136]: Click Climb Up/Down to begin climbing
|
|
||||||
- generic [ref=e137]:
|
|
||||||
- generic [ref=e139]: Combat Stats
|
|
||||||
- generic [ref=e140]:
|
|
||||||
- generic [ref=e141]: "Total DPS: —"
|
|
||||||
- generic [ref=e142]:
|
|
||||||
- generic [ref=e143]: Active Spells
|
|
||||||
- generic [ref=e144]:
|
|
||||||
- generic [ref=e145]:
|
|
||||||
- generic [ref=e146]:
|
|
||||||
- text: Mana Bolt
|
|
||||||
- generic [ref=e147]: Basic
|
|
||||||
- generic [ref=e148]: ✓
|
|
||||||
- generic [ref=e149]: ⚔️ 5 dmg • 3 raw • ⚡ 15 dmg/hr
|
|
||||||
- generic [ref=e151]: "Study Speed: 100%"
|
|
||||||
- generic [ref=e152]:
|
|
||||||
- generic [ref=e154]: Activity Log
|
|
||||||
- generic [ref=e160]: No activity yet...
|
|
||||||
- region "Notifications (F8)":
|
|
||||||
- list
|
|
||||||
- region "Notifications (F8)":
|
|
||||||
- list
|
|
||||||
- button "Open Next.js Dev Tools" [ref=e166] [cursor=pointer]:
|
|
||||||
- img [ref=e167]
|
|
||||||
- alert [ref=e170]
|
|
||||||
```
|
|
||||||
|
|
||||||
# Test source
|
|
||||||
|
|
||||||
```ts
|
|
||||||
1 | import { test, expect } from '@playwright/test';
|
|
||||||
2 |
|
|
||||||
3 | /**
|
|
||||||
4 | * E2E tests for combat system:
|
|
||||||
5 | * - Entering spire mode (climbing)
|
|
||||||
6 | * - Casting spells and seeing progress
|
|
||||||
7 | * - Enemy HP reduction
|
|
||||||
8 | * - Floor advancement
|
|
||||||
9 | */
|
|
||||||
10 |
|
|
||||||
11 | test.describe('Combat System', () => {
|
|
||||||
12 | test.beforeEach(async ({ page }) => {
|
|
||||||
13 | await page.goto('/');
|
|
||||||
14 | // Clear game state to ensure a fresh start
|
|
||||||
15 | await page.evaluate(() => {
|
|
||||||
16 | Object.keys(localStorage)
|
|
||||||
17 | .filter((k) => k.startsWith('mana-loop-'))
|
|
||||||
18 | .forEach((k) => localStorage.removeItem(k));
|
|
||||||
19 | });
|
|
||||||
20 | await page.reload();
|
|
||||||
21 | await page.waitForLoadState('networkidle');
|
|
||||||
22 | });
|
|
||||||
23 |
|
|
||||||
24 | test('can see the Spire tab and "Climb the Spire" button', async ({ page }) => {
|
|
||||||
25 | // The Spire tab uses an icon + text, so match by the tab role
|
|
||||||
26 | const spireTab = page.getByRole('tab', { name: /⚔️ Spire/ });
|
|
||||||
27 | await expect(spireTab).toBeVisible();
|
|
||||||
28 |
|
|
||||||
29 | // Main page should show "Climb the Spire" button
|
|
||||||
30 | const climbBtn = page.getByRole('button', { name: 'Climb the Spire' });
|
|
||||||
31 | await expect(climbBtn).toBeVisible();
|
|
||||||
32 | });
|
|
||||||
33 |
|
|
||||||
34 | test('can enter Spire mode by clicking Climb button', async ({ page }) => {
|
|
||||||
35 | // Click "Climb the Spire" button on the main page (via left panel)
|
|
||||||
36 | await page.getByRole('button', { name: 'Climb the Spire' }).click();
|
|
||||||
37 |
|
|
||||||
38 | // Should now see Spire mode UI elements
|
|
||||||
39 | // The "Enter Spire Mode" button appears when on the Spire tab
|
|
||||||
40 | const enterBtn = page.getByRole('button', { name: 'Enter Spire Mode' });
|
|
||||||
> 41 | await expect(enterBtn).toBeVisible({ timeout: 5000 });
|
|
||||||
| ^ Error: expect(locator).toBeVisible() failed
|
|
||||||
42 | });
|
|
||||||
43 |
|
|
||||||
44 | test('can navigate to Spire tab', async ({ page }) => {
|
|
||||||
45 | // Click the Spire tab specifically (using role=tab to disambiguate)
|
|
||||||
46 | await page.getByRole('tab', { name: /⚔️ Spire/ }).click();
|
|
||||||
47 |
|
|
||||||
48 | // Should see Spire-specific UI
|
|
||||||
49 | const enterSpireBtn = page.getByRole('button', { name: 'Enter Spire Mode' });
|
|
||||||
50 | await expect(enterSpireBtn).toBeVisible({ timeout: 5000 });
|
|
||||||
51 | });
|
|
||||||
52 |
|
|
||||||
53 | test('can enter spire mode from the Spire tab', async ({ page }) => {
|
|
||||||
54 | await page.getByRole('tab', { name: /⚔️ Spire/ }).click();
|
|
||||||
55 |
|
|
||||||
56 | const enterBtn = page.getByRole('button', { name: 'Enter Spire Mode' });
|
|
||||||
57 | await expect(enterBtn).toBeEnabled();
|
|
||||||
58 | await enterBtn.click();
|
|
||||||
59 |
|
|
||||||
60 | // After entering, should see exit button
|
|
||||||
61 | const exitBtn = page.getByRole('button', { name: 'Exit Spire Mode' });
|
|
||||||
62 | await expect(exitBtn).toBeVisible({ timeout: 5000 });
|
|
||||||
63 | });
|
|
||||||
64 |
|
|
||||||
65 | test('shows floor information in spire mode', async ({ page }) => {
|
|
||||||
66 | await page.getByRole('tab', { name: /⚔️ Spire/ }).click();
|
|
||||||
67 | await page.getByRole('button', { name: 'Enter Spire Mode' }).click();
|
|
||||||
68 |
|
|
||||||
69 | // Should display floor number - look for "Floor" label or the floor counter
|
|
||||||
70 | const floorDisplay = page.locator('text="Floor"').first();
|
|
||||||
71 | await expect(floorDisplay).toBeVisible({ timeout: 5000 });
|
|
||||||
72 | });
|
|
||||||
73 | });
|
|
||||||
```
|
|
||||||
|
Before Width: | Height: | Size: 187 KiB |
@@ -1,280 +0,0 @@
|
|||||||
# Instructions
|
|
||||||
|
|
||||||
- Following Playwright test failed.
|
|
||||||
- Explain why, be concise, respect Playwright best practices.
|
|
||||||
- Provide a snippet of code with the fix, if possible.
|
|
||||||
|
|
||||||
# Test info
|
|
||||||
|
|
||||||
- Name: enchanting.spec.ts >> Enchanting Flow >> can switch to Enchant sub-tab and see design UI
|
|
||||||
- Location: e2e/enchanting.spec.ts:41:7
|
|
||||||
|
|
||||||
# Error details
|
|
||||||
|
|
||||||
```
|
|
||||||
Error: locator.click: Error: strict mode violation: getByRole('button') resolved to 6 elements:
|
|
||||||
1) <button data-slot="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive text-primary-foreground shad…>…</button> aka getByRole('button', { name: 'Gather +1 Mana' })
|
|
||||||
2) <button class="flex items-center justify-between w-full text-xs text-gray-400 hover:text-gray-300 mb-2">…</button> aka getByRole('button', { name: 'Elemental Mana (1)' })
|
|
||||||
3) <button data-slot="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive text-primary-foreground shadow-xs hover…>…</button> aka getByRole('button', { name: 'Climb the Spire' })
|
|
||||||
4) <button data-slot="action-button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-[var(--radius)] text-sm font-medium transition-all duration-100 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:ring-2 focus-visible:ring-[var(--border-focus)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--bg-base)] bg-[var(--interactive-primary)] text-white …>…</button> aka getByRole('button', { name: 'Fabricate' })
|
|
||||||
5) <button data-slot="action-button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-[var(--radius)] text-sm font-medium transition-all duration-100 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:ring-2 focus-visible:ring-[var(--border-focus)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--bg-base)] bg-[var(--interactive-secondary)] text-[var…>…</button> aka getByRole('button', { name: 'Enchant' })
|
|
||||||
6) <button id="next-logo" aria-haspopup="menu" data-next-mark="true" aria-expanded="false" aria-label="Open Next.js Dev Tools" data-nextjs-dev-tools-button="true" aria-controls="nextjs-dev-tools-menu">…</button> aka getByRole('button', { name: 'Open Next.js Dev Tools' })
|
|
||||||
|
|
||||||
Call log:
|
|
||||||
- waiting for getByRole('button')
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
# Page snapshot
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
- generic [ref=e1]:
|
|
||||||
- generic [ref=e2]:
|
|
||||||
- banner [ref=e3]:
|
|
||||||
- generic [ref=e4]:
|
|
||||||
- heading "MANA LOOP" [level=1] [ref=e5]
|
|
||||||
- generic [ref=e7]:
|
|
||||||
- generic [ref=e8]:
|
|
||||||
- generic [ref=e9]: Day 1
|
|
||||||
- generic [ref=e10]: 01:04
|
|
||||||
- generic [ref=e11]:
|
|
||||||
- generic [ref=e12]: "0"
|
|
||||||
- generic [ref=e13]: Insight
|
|
||||||
- main [ref=e14]:
|
|
||||||
- generic [ref=e15]:
|
|
||||||
- generic [ref=e17]:
|
|
||||||
- generic [ref=e18]:
|
|
||||||
- generic [ref=e19]:
|
|
||||||
- generic [ref=e20]: "12"
|
|
||||||
- generic [ref=e21]: / 100
|
|
||||||
- generic [ref=e22]:
|
|
||||||
- text: +2.3 mana/hr
|
|
||||||
- generic [ref=e23]: (1.1x med)
|
|
||||||
- progressbar [ref=e24]
|
|
||||||
- button "Gather +1 Mana" [ref=e26]:
|
|
||||||
- img
|
|
||||||
- text: Gather +1 Mana
|
|
||||||
- generic [ref=e27]:
|
|
||||||
- button "Elemental Mana (1)" [ref=e28]:
|
|
||||||
- generic [ref=e29]: Elemental Mana (1)
|
|
||||||
- img [ref=e30]
|
|
||||||
- generic [ref=e33]:
|
|
||||||
- generic [ref=e34]:
|
|
||||||
- generic [ref=e35]: 🔗
|
|
||||||
- generic [ref=e36]: Transference
|
|
||||||
- generic [ref=e39]: 0/10
|
|
||||||
- button "Climb the Spire" [ref=e40]:
|
|
||||||
- img
|
|
||||||
- text: Climb the Spire
|
|
||||||
- generic [ref=e42]:
|
|
||||||
- generic [ref=e43]:
|
|
||||||
- img [ref=e44]
|
|
||||||
- generic [ref=e46]: Current Activity
|
|
||||||
- generic [ref=e47]: Meditating
|
|
||||||
- generic [ref=e48]:
|
|
||||||
- generic [ref=e49]: "1"
|
|
||||||
- generic [ref=e50]: "2"
|
|
||||||
- generic [ref=e51]: "3"
|
|
||||||
- generic [ref=e52]: "4"
|
|
||||||
- generic [ref=e53]: "5"
|
|
||||||
- generic [ref=e54]: "6"
|
|
||||||
- generic [ref=e55]: "7"
|
|
||||||
- generic [ref=e56]: "8"
|
|
||||||
- generic [ref=e57]: "9"
|
|
||||||
- generic [ref=e58]: "10"
|
|
||||||
- generic [ref=e59]: "11"
|
|
||||||
- generic [ref=e60]: "12"
|
|
||||||
- generic [ref=e61]: "13"
|
|
||||||
- generic [ref=e62]: "14"
|
|
||||||
- generic [ref=e63]: "15"
|
|
||||||
- generic [ref=e64]: "16"
|
|
||||||
- generic [ref=e65]: "17"
|
|
||||||
- generic [ref=e66]: "18"
|
|
||||||
- generic [ref=e67]: "19"
|
|
||||||
- generic [ref=e68]: "20"
|
|
||||||
- generic [ref=e69]: "21"
|
|
||||||
- generic [ref=e70]: "22"
|
|
||||||
- generic [ref=e71]: "23"
|
|
||||||
- generic [ref=e72]: "24"
|
|
||||||
- generic [ref=e73]: "25"
|
|
||||||
- generic [ref=e74]: "26"
|
|
||||||
- generic [ref=e75]: "27"
|
|
||||||
- generic [ref=e76]: "28"
|
|
||||||
- generic [ref=e77]: "29"
|
|
||||||
- generic [ref=e78]: "30"
|
|
||||||
- generic [ref=e80]:
|
|
||||||
- tablist [ref=e81]:
|
|
||||||
- tab "⚔️ Spire" [ref=e82]
|
|
||||||
- tab "✨ Attune" [ref=e83]
|
|
||||||
- tab "🗿 Golems" [ref=e84]
|
|
||||||
- tab "📚 Skills" [ref=e85]
|
|
||||||
- tab "🔮 Spells" [ref=e86]
|
|
||||||
- tab "🛡️ Gear" [ref=e87]
|
|
||||||
- tab "🔧 Craft" [active] [selected] [ref=e88]
|
|
||||||
- tab "💎 Loot" [ref=e89]
|
|
||||||
- tab "🏆 Achieve" [ref=e90]
|
|
||||||
- tab "📊 Stats" [ref=e91]
|
|
||||||
- tab "🐛 Debug" [ref=e92]
|
|
||||||
- tab "📖 Grimoire" [ref=e93]
|
|
||||||
- tabpanel "🔧 Craft" [ref=e94]:
|
|
||||||
- generic [ref=e95]:
|
|
||||||
- generic [ref=e97]:
|
|
||||||
- button "Fabricate" [ref=e98]:
|
|
||||||
- img
|
|
||||||
- text: Fabricate
|
|
||||||
- button "Enchant" [ref=e99]:
|
|
||||||
- img
|
|
||||||
- text: Enchant
|
|
||||||
- generic [ref=e100]:
|
|
||||||
- generic [ref=e101]:
|
|
||||||
- generic [ref=e103]:
|
|
||||||
- img [ref=e104]
|
|
||||||
- text: Available Blueprints
|
|
||||||
- generic [ref=e113]:
|
|
||||||
- img [ref=e114]
|
|
||||||
- paragraph [ref=e118]: No blueprints discovered yet.
|
|
||||||
- paragraph [ref=e119]: Defeat guardians to find blueprints!
|
|
||||||
- generic [ref=e120]:
|
|
||||||
- generic [ref=e122]:
|
|
||||||
- img [ref=e123]
|
|
||||||
- text: Materials (0)
|
|
||||||
- generic [ref=e131]:
|
|
||||||
- img [ref=e132]
|
|
||||||
- paragraph [ref=e134]: No materials collected yet.
|
|
||||||
- paragraph [ref=e135]: Defeat floors to gather materials!
|
|
||||||
- region "Notifications (F8)":
|
|
||||||
- list
|
|
||||||
- region "Notifications (F8)":
|
|
||||||
- list
|
|
||||||
- button "Open Next.js Dev Tools" [ref=e141] [cursor=pointer]:
|
|
||||||
- img [ref=e142]
|
|
||||||
- alert [ref=e145]
|
|
||||||
```
|
|
||||||
|
|
||||||
# Test source
|
|
||||||
|
|
||||||
```ts
|
|
||||||
1 | import { test, expect } from '@playwright/test';
|
|
||||||
2 |
|
|
||||||
3 | /**
|
|
||||||
4 | * E2E tests for the 3-step enchantment flow:
|
|
||||||
5 | * Design → Prepare → Apply
|
|
||||||
6 | *
|
|
||||||
7 | * These tests validate the core crafting loop works end-to-end.
|
|
||||||
8 | */
|
|
||||||
9 |
|
|
||||||
10 | test.describe('Enchanting Flow', () => {
|
|
||||||
11 | /**
|
|
||||||
12 | * Before each test, ensure we start with a clean state.
|
|
||||||
13 | * The game persists state in localStorage, so we clear it.
|
|
||||||
14 | */
|
|
||||||
15 | test.beforeEach(async ({ page }) => {
|
|
||||||
16 | await page.goto('/');
|
|
||||||
17 | // Clear game state to ensure a fresh start
|
|
||||||
18 | await page.evaluate(() => {
|
|
||||||
19 | Object.keys(localStorage)
|
|
||||||
20 | .filter((k) => k.startsWith('mana-loop-'))
|
|
||||||
21 | .forEach((k) => localStorage.removeItem(k));
|
|
||||||
22 | });
|
|
||||||
23 | await page.reload();
|
|
||||||
24 | // Wait for the game to initialize
|
|
||||||
25 | await page.waitForLoadState('networkidle');
|
|
||||||
26 | });
|
|
||||||
27 |
|
|
||||||
28 | test('can navigate to Crafting tab', async ({ page }) => {
|
|
||||||
29 | // The tab bar contains a "Craft" tab
|
|
||||||
30 | const craftTab = page.getByRole('tab', { name: /🔧 Craft/ });
|
|
||||||
31 | await expect(craftTab).toBeVisible();
|
|
||||||
32 | await craftTab.click();
|
|
||||||
33 |
|
|
||||||
34 | // Verify we're on the crafting tab by checking for sub-tabs
|
|
||||||
35 | const fabricateBtn = page.getByRole('button', { hasText: 'Fabricate' });
|
|
||||||
36 | const enchantBtn = page.getByRole('button', { hasText: 'Enchant' });
|
|
||||||
37 | await expect(fabricateBtn).toBeVisible();
|
|
||||||
38 | await expect(enchantBtn).toBeVisible();
|
|
||||||
39 | });
|
|
||||||
40 |
|
|
||||||
41 | test('can switch to Enchant sub-tab and see design UI', async ({ page }) => {
|
|
||||||
42 | await page.goto('/');
|
|
||||||
43 | await page.evaluate(() => {
|
|
||||||
44 | Object.keys(localStorage)
|
|
||||||
45 | .filter((k) => k.startsWith('mana-loop-'))
|
|
||||||
46 | .forEach((k) => localStorage.removeItem(k));
|
|
||||||
47 | });
|
|
||||||
48 | await page.reload();
|
|
||||||
49 | await page.waitForLoadState('networkidle');
|
|
||||||
50 |
|
|
||||||
51 | // Navigate to Crafting tab
|
|
||||||
52 | await page.getByRole('tab', { name: /🔧 Craft/ }).click();
|
|
||||||
53 |
|
|
||||||
54 | // Click Enchant sub-tab
|
|
||||||
55 | const enchantBtn = page.getByRole('button', { hasText: 'Enchant' });
|
|
||||||
> 56 | await enchantBtn.click();
|
|
||||||
| ^ Error: locator.click: Error: strict mode violation: getByRole('button') resolved to 6 elements:
|
|
||||||
57 |
|
|
||||||
58 | // Should see the design stage UI
|
|
||||||
59 | const designBtn = page.getByRole('button', { hasText: 'Design' });
|
|
||||||
60 | const prepareBtn = page.getByRole('button', { hasText: 'Prepare' });
|
|
||||||
61 | const applyBtn = page.getByRole('button', { hasText: 'Apply' });
|
|
||||||
62 | await expect(designBtn).toBeVisible();
|
|
||||||
63 | await expect(prepareBtn).toBeVisible();
|
|
||||||
64 | await expect(applyBtn).toBeVisible();
|
|
||||||
65 | });
|
|
||||||
66 |
|
|
||||||
67 | test('can select equipment type and effect in Design stage', async ({ page }) => {
|
|
||||||
68 | await page.goto('/');
|
|
||||||
69 | await page.evaluate(() => {
|
|
||||||
70 | Object.keys(localStorage)
|
|
||||||
71 | .filter((k) => k.startsWith('mana-loop-'))
|
|
||||||
72 | .forEach((k) => localStorage.removeItem(k));
|
|
||||||
73 | });
|
|
||||||
74 | await page.reload();
|
|
||||||
75 | await page.waitForLoadState('networkidle');
|
|
||||||
76 |
|
|
||||||
77 | // Navigate to Crafting > Enchant > Design
|
|
||||||
78 | await page.getByRole('tab', { name: /🔧 Craft/ }).click();
|
|
||||||
79 | await page.getByRole('button', { hasText: 'Enchant' }).click();
|
|
||||||
80 |
|
|
||||||
81 | // The design section should show effect selectors once an equipment type is chosen
|
|
||||||
82 | // Look for any element matching equipment type buttons and effect-related content
|
|
||||||
83 | const equipmentButtons = page.locator('button:has-text("Basic Staff"), button:has-text("Apprentice Wand"), button:has-text("Oak Staff"), button:has-text("Crystal Wand")');
|
|
||||||
84 | const count = await equipmentButtons.count();
|
|
||||||
85 | expect(count).toBeGreaterThan(0);
|
|
||||||
86 | });
|
|
||||||
87 |
|
|
||||||
88 | test('can navigate through all 3 enchant stages', async ({ page }) => {
|
|
||||||
89 | await page.goto('/');
|
|
||||||
90 | await page.evaluate(() => {
|
|
||||||
91 | Object.keys(localStorage)
|
|
||||||
92 | .filter((k) => k.startsWith('mana-loop-'))
|
|
||||||
93 | .forEach((k) => localStorage.removeItem(k));
|
|
||||||
94 | });
|
|
||||||
95 | await page.reload();
|
|
||||||
96 | await page.waitForLoadState('networkidle');
|
|
||||||
97 |
|
|
||||||
98 | // Navigate to Crafting > Enchant
|
|
||||||
99 | await page.getByRole('tab', { name: /🔧 Craft/ }).click();
|
|
||||||
100 | await page.getByRole('button', { hasText: 'Enchant' }).click();
|
|
||||||
101 |
|
|
||||||
102 | // Verify Design stage is active
|
|
||||||
103 | const designBtn = page.getByRole('button', { hasText: 'Design' });
|
|
||||||
104 | await expect(designBtn).toBeVisible();
|
|
||||||
105 |
|
|
||||||
106 | // Switch to Prepare stage
|
|
||||||
107 | const prepareBtn = page.getByRole('button', { hasText: 'Prepare' });
|
|
||||||
108 | await prepareBtn.click();
|
|
||||||
109 |
|
|
||||||
110 | // Should see preparation UI
|
|
||||||
111 | const prepareHeading = page.locator('text=Select Equipment to Prepare');
|
|
||||||
112 | await expect(prepareHeading).toBeVisible({ timeout: 5000 });
|
|
||||||
113 |
|
|
||||||
114 | // Switch to Apply stage
|
|
||||||
115 | const applyBtn = page.getByRole('button', { hasText: 'Apply' });
|
|
||||||
116 | await applyBtn.click();
|
|
||||||
117 |
|
|
||||||
118 | // Should see application UI
|
|
||||||
119 | const applyHeading = page.locator('text=Select Equipment & Design');
|
|
||||||
120 | await expect(applyHeading).toBeVisible({ timeout: 5000 });
|
|
||||||
121 | });
|
|
||||||
122 | });
|
|
||||||
```
|
|
||||||
|
Before Width: | Height: | Size: 243 KiB |
@@ -1,375 +0,0 @@
|
|||||||
# Instructions
|
|
||||||
|
|
||||||
- Following Playwright test failed.
|
|
||||||
- Explain why, be concise, respect Playwright best practices.
|
|
||||||
- Provide a snippet of code with the fix, if possible.
|
|
||||||
|
|
||||||
# Test info
|
|
||||||
|
|
||||||
- Name: equipment.spec.ts >> Equipment Management >> shows starting equipment already equipped
|
|
||||||
- Location: e2e/equipment.spec.ts:78:7
|
|
||||||
|
|
||||||
# Error details
|
|
||||||
|
|
||||||
```
|
|
||||||
Error: expect(locator).toBeVisible() failed
|
|
||||||
|
|
||||||
Locator: locator('text=Main Hand').locator('..').locator('text=Basic Staff')
|
|
||||||
Expected: visible
|
|
||||||
Timeout: 5000ms
|
|
||||||
Error: element(s) not found
|
|
||||||
|
|
||||||
Call log:
|
|
||||||
- Expect "toBeVisible" with timeout 5000ms
|
|
||||||
- waiting for locator('text=Main Hand').locator('..').locator('text=Basic Staff')
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
# Page snapshot
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
- generic [ref=e1]:
|
|
||||||
- generic [ref=e2]:
|
|
||||||
- banner [ref=e3]:
|
|
||||||
- generic [ref=e4]:
|
|
||||||
- heading "MANA LOOP" [level=1] [ref=e5]
|
|
||||||
- generic [ref=e7]:
|
|
||||||
- generic [ref=e8]:
|
|
||||||
- generic [ref=e9]: Day 1
|
|
||||||
- generic [ref=e10]: 01:52
|
|
||||||
- generic [ref=e11]:
|
|
||||||
- generic [ref=e12]: "0"
|
|
||||||
- generic [ref=e13]: Insight
|
|
||||||
- main [ref=e14]:
|
|
||||||
- generic [ref=e15]:
|
|
||||||
- generic [ref=e17]:
|
|
||||||
- generic [ref=e18]:
|
|
||||||
- generic [ref=e19]:
|
|
||||||
- generic [ref=e20]: "14"
|
|
||||||
- generic [ref=e21]: / 100
|
|
||||||
- generic [ref=e22]:
|
|
||||||
- text: +2.7 mana/hr
|
|
||||||
- generic [ref=e23]: (1.4x med)
|
|
||||||
- progressbar [ref=e24]
|
|
||||||
- button "Gather +1 Mana" [ref=e26]:
|
|
||||||
- img
|
|
||||||
- text: Gather +1 Mana
|
|
||||||
- generic [ref=e27]:
|
|
||||||
- button "Elemental Mana (1)" [ref=e28]:
|
|
||||||
- generic [ref=e29]: Elemental Mana (1)
|
|
||||||
- img [ref=e30]
|
|
||||||
- generic [ref=e33]:
|
|
||||||
- generic [ref=e34]:
|
|
||||||
- generic [ref=e35]: 🔗
|
|
||||||
- generic [ref=e36]: Transference
|
|
||||||
- generic [ref=e39]: 0/10
|
|
||||||
- button "Climb the Spire" [ref=e40]:
|
|
||||||
- img
|
|
||||||
- text: Climb the Spire
|
|
||||||
- generic [ref=e42]:
|
|
||||||
- generic [ref=e43]:
|
|
||||||
- img [ref=e44]
|
|
||||||
- generic [ref=e46]: Current Activity
|
|
||||||
- generic [ref=e47]: Meditating
|
|
||||||
- generic [ref=e48]:
|
|
||||||
- generic [ref=e49]: "1"
|
|
||||||
- generic [ref=e50]: "2"
|
|
||||||
- generic [ref=e51]: "3"
|
|
||||||
- generic [ref=e52]: "4"
|
|
||||||
- generic [ref=e53]: "5"
|
|
||||||
- generic [ref=e54]: "6"
|
|
||||||
- generic [ref=e55]: "7"
|
|
||||||
- generic [ref=e56]: "8"
|
|
||||||
- generic [ref=e57]: "9"
|
|
||||||
- generic [ref=e58]: "10"
|
|
||||||
- generic [ref=e59]: "11"
|
|
||||||
- generic [ref=e60]: "12"
|
|
||||||
- generic [ref=e61]: "13"
|
|
||||||
- generic [ref=e62]: "14"
|
|
||||||
- generic [ref=e63]: "15"
|
|
||||||
- generic [ref=e64]: "16"
|
|
||||||
- generic [ref=e65]: "17"
|
|
||||||
- generic [ref=e66]: "18"
|
|
||||||
- generic [ref=e67]: "19"
|
|
||||||
- generic [ref=e68]: "20"
|
|
||||||
- generic [ref=e69]: "21"
|
|
||||||
- generic [ref=e70]: "22"
|
|
||||||
- generic [ref=e71]: "23"
|
|
||||||
- generic [ref=e72]: "24"
|
|
||||||
- generic [ref=e73]: "25"
|
|
||||||
- generic [ref=e74]: "26"
|
|
||||||
- generic [ref=e75]: "27"
|
|
||||||
- generic [ref=e76]: "28"
|
|
||||||
- generic [ref=e77]: "29"
|
|
||||||
- generic [ref=e78]: "30"
|
|
||||||
- generic [ref=e80]:
|
|
||||||
- tablist [ref=e81]:
|
|
||||||
- tab "⚔️ Spire" [ref=e82]
|
|
||||||
- tab "✨ Attune" [ref=e83]
|
|
||||||
- tab "🗿 Golems" [ref=e84]
|
|
||||||
- tab "📚 Skills" [ref=e85]
|
|
||||||
- tab "🔮 Spells" [ref=e86]
|
|
||||||
- tab "🛡️ Gear" [active] [selected] [ref=e87]
|
|
||||||
- tab "🔧 Craft" [ref=e88]
|
|
||||||
- tab "💎 Loot" [ref=e89]
|
|
||||||
- tab "🏆 Achieve" [ref=e90]
|
|
||||||
- tab "📊 Stats" [ref=e91]
|
|
||||||
- tab "🐛 Debug" [ref=e92]
|
|
||||||
- tab "📖 Grimoire" [ref=e93]
|
|
||||||
- tabpanel "🛡️ Gear" [ref=e94]:
|
|
||||||
- generic [ref=e95]:
|
|
||||||
- generic [ref=e96]:
|
|
||||||
- generic [ref=e97]:
|
|
||||||
- heading "Equipped Gear" [level=3] [ref=e98]
|
|
||||||
- generic [ref=e100]: 4 / 8 slots filled
|
|
||||||
- generic [ref=e101]:
|
|
||||||
- generic [ref=e102]:
|
|
||||||
- heading "Weapon & Shield" [level=4] [ref=e103]
|
|
||||||
- generic [ref=e104]:
|
|
||||||
- 'button "Main Hand slot: Basic Staff" [ref=e106]':
|
|
||||||
- generic [ref=e107]:
|
|
||||||
- generic [ref=e108]:
|
|
||||||
- img [ref=e109]
|
|
||||||
- generic [ref=e114]: Main Hand
|
|
||||||
- button "Unequip Basic Staff" [ref=e115]:
|
|
||||||
- img [ref=e116]
|
|
||||||
- generic [ref=e119]:
|
|
||||||
- generic [ref=e120]:
|
|
||||||
- text: Basic Staff
|
|
||||||
- generic [ref=e121]: 2-Handed
|
|
||||||
- generic [ref=e122]: "Enchantments: 1/50"
|
|
||||||
- generic [ref=e124]: Mana Bolt
|
|
||||||
- button "Off Hand slot (blocked by 2-handed weapon) (empty)" [ref=e125]:
|
|
||||||
- generic [ref=e127]:
|
|
||||||
- img [ref=e128]
|
|
||||||
- generic [ref=e130]: Off Hand
|
|
||||||
- generic [ref=e131]:
|
|
||||||
- img
|
|
||||||
- text: Occupied — 2H Weapon
|
|
||||||
- generic [ref=e132]:
|
|
||||||
- img [ref=e133]
|
|
||||||
- text: Blocked by 2-handed weapon
|
|
||||||
- generic [ref=e135]:
|
|
||||||
- heading "Armor" [level=4] [ref=e136]
|
|
||||||
- generic [ref=e137]:
|
|
||||||
- button "Head slot (empty)" [ref=e139]:
|
|
||||||
- generic [ref=e141]:
|
|
||||||
- img [ref=e142]
|
|
||||||
- generic [ref=e147]: Head
|
|
||||||
- generic [ref=e148]: Head
|
|
||||||
- 'button "Body slot: Civilian Shirt" [ref=e150]':
|
|
||||||
- generic [ref=e151]:
|
|
||||||
- generic [ref=e152]:
|
|
||||||
- img [ref=e153]
|
|
||||||
- generic [ref=e155]: Body
|
|
||||||
- button "Unequip Civilian Shirt" [ref=e156]:
|
|
||||||
- img [ref=e157]
|
|
||||||
- generic [ref=e160]:
|
|
||||||
- generic [ref=e161]: Civilian Shirt
|
|
||||||
- generic [ref=e162]: "Enchantments: 0/30"
|
|
||||||
- 'button "Hands slot: Civilian Gloves" [ref=e164]':
|
|
||||||
- generic [ref=e165]:
|
|
||||||
- generic [ref=e166]:
|
|
||||||
- img [ref=e167]
|
|
||||||
- generic [ref=e172]: Hands
|
|
||||||
- button "Unequip Civilian Gloves" [ref=e173]:
|
|
||||||
- img [ref=e174]
|
|
||||||
- generic [ref=e177]:
|
|
||||||
- generic [ref=e178]: Civilian Gloves
|
|
||||||
- generic [ref=e179]: "Enchantments: 0/20"
|
|
||||||
- 'button "Feet slot: Civilian Shoes" [ref=e181]':
|
|
||||||
- generic [ref=e182]:
|
|
||||||
- generic [ref=e183]:
|
|
||||||
- img [ref=e184]
|
|
||||||
- generic [ref=e187]: Feet
|
|
||||||
- button "Unequip Civilian Shoes" [ref=e188]:
|
|
||||||
- img [ref=e189]
|
|
||||||
- generic [ref=e192]:
|
|
||||||
- generic [ref=e193]: Civilian Shoes
|
|
||||||
- generic [ref=e194]: "Enchantments: 0/15"
|
|
||||||
- generic [ref=e195]:
|
|
||||||
- heading "Accessories" [level=4] [ref=e196]
|
|
||||||
- generic [ref=e197]:
|
|
||||||
- button "Accessory 1 slot (empty)" [ref=e199]:
|
|
||||||
- generic [ref=e201]:
|
|
||||||
- img [ref=e202]
|
|
||||||
- generic [ref=e205]: Accessory 1
|
|
||||||
- generic [ref=e206]: Accessory 1
|
|
||||||
- button "Accessory 2 slot (empty)" [ref=e208]:
|
|
||||||
- generic [ref=e210]:
|
|
||||||
- img [ref=e211]
|
|
||||||
- generic [ref=e214]: Accessory 2
|
|
||||||
- generic [ref=e215]: Accessory 2
|
|
||||||
- generic [ref=e216]:
|
|
||||||
- heading "Equipment Inventory (0 items)" [level=3] [ref=e218]
|
|
||||||
- status [ref=e219]: No unequipped items. Craft new gear in the Crafting tab.
|
|
||||||
- generic [ref=e220]:
|
|
||||||
- heading "Equipment Stats Summary" [level=3] [ref=e222]
|
|
||||||
- generic [ref=e223]:
|
|
||||||
- generic [ref=e224]:
|
|
||||||
- generic [ref=e225]: "4"
|
|
||||||
- generic [ref=e226]: Total Items
|
|
||||||
- generic [ref=e227]:
|
|
||||||
- generic [ref=e228]: "4"
|
|
||||||
- generic [ref=e229]: Equipped
|
|
||||||
- generic [ref=e230]:
|
|
||||||
- generic [ref=e231]: "0"
|
|
||||||
- generic [ref=e232]: In Inventory
|
|
||||||
- generic [ref=e233]:
|
|
||||||
- generic [ref=e234]: "1"
|
|
||||||
- generic [ref=e235]: Total Enchantments
|
|
||||||
- generic [ref=e236]:
|
|
||||||
- heading "✨ Enchantment Power" [level=3] [ref=e238]
|
|
||||||
- generic [ref=e239]:
|
|
||||||
- generic [ref=e240]:
|
|
||||||
- generic [ref=e241]: "Enchantment Power:"
|
|
||||||
- generic [ref=e242]: 1.00×
|
|
||||||
- paragraph [ref=e243]: Increases the power of all enchantments by 0%. Multiplier applied to all enchantment effects.
|
|
||||||
- generic [ref=e244]:
|
|
||||||
- generic [ref=e245]: "Active Effects from Equipment:"
|
|
||||||
- generic [ref=e247]: No active effects
|
|
||||||
- region "Notifications (F8)":
|
|
||||||
- list
|
|
||||||
- region "Notifications (F8)":
|
|
||||||
- list
|
|
||||||
- button "Open Next.js Dev Tools" [ref=e253] [cursor=pointer]:
|
|
||||||
- img [ref=e254]
|
|
||||||
- alert [ref=e257]
|
|
||||||
```
|
|
||||||
|
|
||||||
# Test source
|
|
||||||
|
|
||||||
```ts
|
|
||||||
1 | import { test, expect } from '@playwright/test';
|
|
||||||
2 |
|
|
||||||
3 | /**
|
|
||||||
4 | * E2E tests for equipment management:
|
|
||||||
5 | * - Equipping items to slots
|
|
||||||
6 | * - 2-handed weapon blocking offhand slot
|
|
||||||
7 | * - Unequipping items back to inventory
|
|
||||||
8 | */
|
|
||||||
9 |
|
|
||||||
10 | test.describe('Equipment Management', () => {
|
|
||||||
11 | test.beforeEach(async ({ page }) => {
|
|
||||||
12 | await page.goto('/');
|
|
||||||
13 | // Clear game state for a fresh start
|
|
||||||
14 | await page.evaluate(() => {
|
|
||||||
15 | Object.keys(localStorage)
|
|
||||||
16 | .filter((k) => k.startsWith('mana-loop-'))
|
|
||||||
17 | .forEach((k) => localStorage.removeItem(k));
|
|
||||||
18 | });
|
|
||||||
19 | await page.reload();
|
|
||||||
20 | await page.waitForLoadState('networkidle');
|
|
||||||
21 | });
|
|
||||||
22 |
|
|
||||||
23 | test('can navigate to Equipment tab', async ({ page }) => {
|
|
||||||
24 | // Use the tab with the shield icon to disambiguate
|
|
||||||
25 | const gearTab = page.getByRole('tab', { name: /🛡️ Gear/ });
|
|
||||||
26 | await expect(gearTab).toBeVisible();
|
|
||||||
27 | await gearTab.click();
|
|
||||||
28 |
|
|
||||||
29 | // Verify equipment UI elements
|
|
||||||
30 | const equippedGearHeading = page.locator('text="Equipped Gear"');
|
|
||||||
31 | await expect(equippedGearHeading).toBeVisible({ timeout: 5000 });
|
|
||||||
32 | });
|
|
||||||
33 |
|
|
||||||
34 | test('shows equipment slots with labels', async ({ page }) => {
|
|
||||||
35 | await page.goto('/');
|
|
||||||
36 | await page.evaluate(() => {
|
|
||||||
37 | Object.keys(localStorage)
|
|
||||||
38 | .filter((k) => k.startsWith('mana-loop-'))
|
|
||||||
39 | .forEach((k) => localStorage.removeItem(k));
|
|
||||||
40 | });
|
|
||||||
41 | await page.reload();
|
|
||||||
42 | await page.waitForLoadState('networkidle');
|
|
||||||
43 |
|
|
||||||
44 | await page.getByRole('tab', { name: /🛡️ Gear/ }).click();
|
|
||||||
45 |
|
|
||||||
46 | // Check for expected slot labels - use role=heading or more specific selectors
|
|
||||||
47 | // Main Hand slot
|
|
||||||
48 | const mainHandSection = page.locator('text=Main Hand');
|
|
||||||
49 | await expect(mainHandSection.first()).toBeVisible();
|
|
||||||
50 |
|
|
||||||
51 | // Off Hand
|
|
||||||
52 | const offHandSection = page.locator('text=Off Hand');
|
|
||||||
53 | await expect(offHandSection.first()).toBeVisible();
|
|
||||||
54 |
|
|
||||||
55 | // Head
|
|
||||||
56 | const headSection = page.locator('text=Head');
|
|
||||||
57 | await expect(headSection.first()).toBeVisible();
|
|
||||||
58 |
|
|
||||||
59 | // Body
|
|
||||||
60 | const bodySection = page.locator('text=Body');
|
|
||||||
61 | await expect(bodySection.first()).toBeVisible();
|
|
||||||
62 |
|
|
||||||
63 | // Hands
|
|
||||||
64 | const handsSection = page.locator('text=Hands');
|
|
||||||
65 | await expect(handsSection.first()).toBeVisible();
|
|
||||||
66 |
|
|
||||||
67 | // Feet
|
|
||||||
68 | const feetSection = page.locator('text=Feet');
|
|
||||||
69 | await expect(feetSection.first()).toBeVisible();
|
|
||||||
70 |
|
|
||||||
71 | // Accessory 1 and 2
|
|
||||||
72 | const acc1Section = page.locator('text=Accessory 1');
|
|
||||||
73 | await expect(acc1Section.first()).toBeVisible();
|
|
||||||
74 | const acc2Section = page.locator('text=Accessory 2');
|
|
||||||
75 | await expect(acc2Section.first()).toBeVisible();
|
|
||||||
76 | });
|
|
||||||
77 |
|
|
||||||
78 | test('shows starting equipment already equipped', async ({ page }) => {
|
|
||||||
79 | await page.goto('/');
|
|
||||||
80 | await page.evaluate(() => {
|
|
||||||
81 | Object.keys(localStorage)
|
|
||||||
82 | .filter((k) => k.startsWith('mana-loop-'))
|
|
||||||
83 | .forEach((k) => localStorage.removeItem(k));
|
|
||||||
84 | });
|
|
||||||
85 | await page.reload();
|
|
||||||
86 | await page.waitForLoadState('networkidle');
|
|
||||||
87 |
|
|
||||||
88 | await page.getByRole('tab', { name: /🛡️ Gear/ }).click();
|
|
||||||
89 |
|
|
||||||
90 | // The player starts with a Basic Staff in main hand (as an equipped item)
|
|
||||||
91 | const mainHandSlot = page.locator('text=Main Hand >> .. >> text=Basic Staff');
|
|
||||||
> 92 | await expect(mainHandSlot).toBeVisible({ timeout: 5000 });
|
|
||||||
| ^ Error: expect(locator).toBeVisible() failed
|
|
||||||
93 | });
|
|
||||||
94 |
|
|
||||||
95 | test('2-handed weapon blocks offhand slot', async ({ page }) => {
|
|
||||||
96 | await page.goto('/');
|
|
||||||
97 | await page.evaluate(() => {
|
|
||||||
98 | Object.keys(localStorage)
|
|
||||||
99 | .filter((k) => k.startsWith('mana-loop-'))
|
|
||||||
100 | .forEach((k) => localStorage.removeItem(k));
|
|
||||||
101 | });
|
|
||||||
102 | await page.reload();
|
|
||||||
103 | await page.waitForLoadState('networkidle');
|
|
||||||
104 |
|
|
||||||
105 | await page.getByRole('tab', { name: /🛡️ Gear/ }).click();
|
|
||||||
106 |
|
|
||||||
107 | // The starting basic staff is 2-handed
|
|
||||||
108 | // The offhand slot should show as blocked with "Occupied — 2H Weapon"
|
|
||||||
109 | const offHandBlocked = page.locator('text=Occupied').first();
|
|
||||||
110 | await expect(offHandBlocked).toBeVisible({ timeout: 5000 });
|
|
||||||
111 | });
|
|
||||||
112 |
|
|
||||||
113 | test('can unequip an item from a slot', async ({ page }) => {
|
|
||||||
114 | await page.goto('/');
|
|
||||||
115 | await page.evaluate(() => {
|
|
||||||
116 | Object.keys(localStorage)
|
|
||||||
117 | .filter((k) => k.startsWith('mana-loop-'))
|
|
||||||
118 | .forEach((k) => localStorage.removeItem(k));
|
|
||||||
119 | });
|
|
||||||
120 | await page.reload();
|
|
||||||
121 | await page.waitForLoadState('networkidle');
|
|
||||||
122 |
|
|
||||||
123 | await page.getByRole('tab', { name: /🛡️ Gear/ }).click();
|
|
||||||
124 |
|
|
||||||
125 | // Find an equiped slot with an unequip button (the X button)
|
|
||||||
126 | // The hands slot has civilian gloves equipped
|
|
||||||
127 | const handsSlot = page.locator('text=Hands >> .. >> button').first();
|
|
||||||
128 | await expect(handsSlot).toBeVisible({ timeout: 5000 });
|
|
||||||
129 | // Note: exact behavior of unequip depends on implementation state
|
|
||||||
130 | });
|
|
||||||
131 | });
|
|
||||||
```
|
|
||||||
|
Before Width: | Height: | Size: 187 KiB |
|
Before Width: | Height: | Size: 243 KiB |
|
Before Width: | Height: | Size: 243 KiB |
@@ -1,280 +0,0 @@
|
|||||||
# Instructions
|
|
||||||
|
|
||||||
- Following Playwright test failed.
|
|
||||||
- Explain why, be concise, respect Playwright best practices.
|
|
||||||
- Provide a snippet of code with the fix, if possible.
|
|
||||||
|
|
||||||
# Test info
|
|
||||||
|
|
||||||
- Name: enchanting.spec.ts >> Enchanting Flow >> can select equipment type and effect in Design stage
|
|
||||||
- Location: e2e/enchanting.spec.ts:67:7
|
|
||||||
|
|
||||||
# Error details
|
|
||||||
|
|
||||||
```
|
|
||||||
Error: locator.click: Error: strict mode violation: getByRole('button') resolved to 6 elements:
|
|
||||||
1) <button data-slot="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive text-primary-foreground shad…>…</button> aka getByRole('button', { name: 'Gather +1 Mana' })
|
|
||||||
2) <button class="flex items-center justify-between w-full text-xs text-gray-400 hover:text-gray-300 mb-2">…</button> aka getByRole('button', { name: 'Elemental Mana (1)' })
|
|
||||||
3) <button data-slot="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive text-primary-foreground shadow-xs hover…>…</button> aka getByRole('button', { name: 'Climb the Spire' })
|
|
||||||
4) <button data-slot="action-button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-[var(--radius)] text-sm font-medium transition-all duration-100 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:ring-2 focus-visible:ring-[var(--border-focus)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--bg-base)] bg-[var(--interactive-primary)] text-white …>…</button> aka getByRole('button', { name: 'Fabricate' })
|
|
||||||
5) <button data-slot="action-button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-[var(--radius)] text-sm font-medium transition-all duration-100 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:ring-2 focus-visible:ring-[var(--border-focus)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--bg-base)] bg-[var(--interactive-secondary)] text-[var…>…</button> aka getByRole('button', { name: 'Enchant' })
|
|
||||||
6) <button id="next-logo" aria-haspopup="menu" data-next-mark="true" aria-expanded="false" aria-label="Open Next.js Dev Tools" data-nextjs-dev-tools-button="true" aria-controls="nextjs-dev-tools-menu">…</button> aka getByRole('button', { name: 'Open Next.js Dev Tools' })
|
|
||||||
|
|
||||||
Call log:
|
|
||||||
- waiting for getByRole('button')
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
# Page snapshot
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
- generic [ref=e1]:
|
|
||||||
- generic [ref=e2]:
|
|
||||||
- banner [ref=e3]:
|
|
||||||
- generic [ref=e4]:
|
|
||||||
- heading "MANA LOOP" [level=1] [ref=e5]
|
|
||||||
- generic [ref=e7]:
|
|
||||||
- generic [ref=e8]:
|
|
||||||
- generic [ref=e9]: Day 1
|
|
||||||
- generic [ref=e10]: 01:02
|
|
||||||
- generic [ref=e11]:
|
|
||||||
- generic [ref=e12]: "0"
|
|
||||||
- generic [ref=e13]: Insight
|
|
||||||
- main [ref=e14]:
|
|
||||||
- generic [ref=e15]:
|
|
||||||
- generic [ref=e17]:
|
|
||||||
- generic [ref=e18]:
|
|
||||||
- generic [ref=e19]:
|
|
||||||
- generic [ref=e20]: "12"
|
|
||||||
- generic [ref=e21]: / 100
|
|
||||||
- generic [ref=e22]:
|
|
||||||
- text: +2.3 mana/hr
|
|
||||||
- generic [ref=e23]: (1.1x med)
|
|
||||||
- progressbar [ref=e24]
|
|
||||||
- button "Gather +1 Mana" [ref=e26]:
|
|
||||||
- img
|
|
||||||
- text: Gather +1 Mana
|
|
||||||
- generic [ref=e27]:
|
|
||||||
- button "Elemental Mana (1)" [ref=e28]:
|
|
||||||
- generic [ref=e29]: Elemental Mana (1)
|
|
||||||
- img [ref=e30]
|
|
||||||
- generic [ref=e33]:
|
|
||||||
- generic [ref=e34]:
|
|
||||||
- generic [ref=e35]: 🔗
|
|
||||||
- generic [ref=e36]: Transference
|
|
||||||
- generic [ref=e39]: 0/10
|
|
||||||
- button "Climb the Spire" [ref=e40]:
|
|
||||||
- img
|
|
||||||
- text: Climb the Spire
|
|
||||||
- generic [ref=e42]:
|
|
||||||
- generic [ref=e43]:
|
|
||||||
- img [ref=e44]
|
|
||||||
- generic [ref=e46]: Current Activity
|
|
||||||
- generic [ref=e47]: Meditating
|
|
||||||
- generic [ref=e48]:
|
|
||||||
- generic [ref=e49]: "1"
|
|
||||||
- generic [ref=e50]: "2"
|
|
||||||
- generic [ref=e51]: "3"
|
|
||||||
- generic [ref=e52]: "4"
|
|
||||||
- generic [ref=e53]: "5"
|
|
||||||
- generic [ref=e54]: "6"
|
|
||||||
- generic [ref=e55]: "7"
|
|
||||||
- generic [ref=e56]: "8"
|
|
||||||
- generic [ref=e57]: "9"
|
|
||||||
- generic [ref=e58]: "10"
|
|
||||||
- generic [ref=e59]: "11"
|
|
||||||
- generic [ref=e60]: "12"
|
|
||||||
- generic [ref=e61]: "13"
|
|
||||||
- generic [ref=e62]: "14"
|
|
||||||
- generic [ref=e63]: "15"
|
|
||||||
- generic [ref=e64]: "16"
|
|
||||||
- generic [ref=e65]: "17"
|
|
||||||
- generic [ref=e66]: "18"
|
|
||||||
- generic [ref=e67]: "19"
|
|
||||||
- generic [ref=e68]: "20"
|
|
||||||
- generic [ref=e69]: "21"
|
|
||||||
- generic [ref=e70]: "22"
|
|
||||||
- generic [ref=e71]: "23"
|
|
||||||
- generic [ref=e72]: "24"
|
|
||||||
- generic [ref=e73]: "25"
|
|
||||||
- generic [ref=e74]: "26"
|
|
||||||
- generic [ref=e75]: "27"
|
|
||||||
- generic [ref=e76]: "28"
|
|
||||||
- generic [ref=e77]: "29"
|
|
||||||
- generic [ref=e78]: "30"
|
|
||||||
- generic [ref=e80]:
|
|
||||||
- tablist [ref=e81]:
|
|
||||||
- tab "⚔️ Spire" [ref=e82]
|
|
||||||
- tab "✨ Attune" [ref=e83]
|
|
||||||
- tab "🗿 Golems" [ref=e84]
|
|
||||||
- tab "📚 Skills" [ref=e85]
|
|
||||||
- tab "🔮 Spells" [ref=e86]
|
|
||||||
- tab "🛡️ Gear" [ref=e87]
|
|
||||||
- tab "🔧 Craft" [active] [selected] [ref=e88]
|
|
||||||
- tab "💎 Loot" [ref=e89]
|
|
||||||
- tab "🏆 Achieve" [ref=e90]
|
|
||||||
- tab "📊 Stats" [ref=e91]
|
|
||||||
- tab "🐛 Debug" [ref=e92]
|
|
||||||
- tab "📖 Grimoire" [ref=e93]
|
|
||||||
- tabpanel "🔧 Craft" [ref=e94]:
|
|
||||||
- generic [ref=e95]:
|
|
||||||
- generic [ref=e97]:
|
|
||||||
- button "Fabricate" [ref=e98]:
|
|
||||||
- img
|
|
||||||
- text: Fabricate
|
|
||||||
- button "Enchant" [ref=e99]:
|
|
||||||
- img
|
|
||||||
- text: Enchant
|
|
||||||
- generic [ref=e100]:
|
|
||||||
- generic [ref=e101]:
|
|
||||||
- generic [ref=e103]:
|
|
||||||
- img [ref=e104]
|
|
||||||
- text: Available Blueprints
|
|
||||||
- generic [ref=e113]:
|
|
||||||
- img [ref=e114]
|
|
||||||
- paragraph [ref=e118]: No blueprints discovered yet.
|
|
||||||
- paragraph [ref=e119]: Defeat guardians to find blueprints!
|
|
||||||
- generic [ref=e120]:
|
|
||||||
- generic [ref=e122]:
|
|
||||||
- img [ref=e123]
|
|
||||||
- text: Materials (0)
|
|
||||||
- generic [ref=e131]:
|
|
||||||
- img [ref=e132]
|
|
||||||
- paragraph [ref=e134]: No materials collected yet.
|
|
||||||
- paragraph [ref=e135]: Defeat floors to gather materials!
|
|
||||||
- region "Notifications (F8)":
|
|
||||||
- list
|
|
||||||
- region "Notifications (F8)":
|
|
||||||
- list
|
|
||||||
- button "Open Next.js Dev Tools" [ref=e141] [cursor=pointer]:
|
|
||||||
- img [ref=e142]
|
|
||||||
- alert [ref=e145]
|
|
||||||
```
|
|
||||||
|
|
||||||
# Test source
|
|
||||||
|
|
||||||
```ts
|
|
||||||
1 | import { test, expect } from '@playwright/test';
|
|
||||||
2 |
|
|
||||||
3 | /**
|
|
||||||
4 | * E2E tests for the 3-step enchantment flow:
|
|
||||||
5 | * Design → Prepare → Apply
|
|
||||||
6 | *
|
|
||||||
7 | * These tests validate the core crafting loop works end-to-end.
|
|
||||||
8 | */
|
|
||||||
9 |
|
|
||||||
10 | test.describe('Enchanting Flow', () => {
|
|
||||||
11 | /**
|
|
||||||
12 | * Before each test, ensure we start with a clean state.
|
|
||||||
13 | * The game persists state in localStorage, so we clear it.
|
|
||||||
14 | */
|
|
||||||
15 | test.beforeEach(async ({ page }) => {
|
|
||||||
16 | await page.goto('/');
|
|
||||||
17 | // Clear game state to ensure a fresh start
|
|
||||||
18 | await page.evaluate(() => {
|
|
||||||
19 | Object.keys(localStorage)
|
|
||||||
20 | .filter((k) => k.startsWith('mana-loop-'))
|
|
||||||
21 | .forEach((k) => localStorage.removeItem(k));
|
|
||||||
22 | });
|
|
||||||
23 | await page.reload();
|
|
||||||
24 | // Wait for the game to initialize
|
|
||||||
25 | await page.waitForLoadState('networkidle');
|
|
||||||
26 | });
|
|
||||||
27 |
|
|
||||||
28 | test('can navigate to Crafting tab', async ({ page }) => {
|
|
||||||
29 | // The tab bar contains a "Craft" tab
|
|
||||||
30 | const craftTab = page.getByRole('tab', { name: /🔧 Craft/ });
|
|
||||||
31 | await expect(craftTab).toBeVisible();
|
|
||||||
32 | await craftTab.click();
|
|
||||||
33 |
|
|
||||||
34 | // Verify we're on the crafting tab by checking for sub-tabs
|
|
||||||
35 | const fabricateBtn = page.getByRole('button', { hasText: 'Fabricate' });
|
|
||||||
36 | const enchantBtn = page.getByRole('button', { hasText: 'Enchant' });
|
|
||||||
37 | await expect(fabricateBtn).toBeVisible();
|
|
||||||
38 | await expect(enchantBtn).toBeVisible();
|
|
||||||
39 | });
|
|
||||||
40 |
|
|
||||||
41 | test('can switch to Enchant sub-tab and see design UI', async ({ page }) => {
|
|
||||||
42 | await page.goto('/');
|
|
||||||
43 | await page.evaluate(() => {
|
|
||||||
44 | Object.keys(localStorage)
|
|
||||||
45 | .filter((k) => k.startsWith('mana-loop-'))
|
|
||||||
46 | .forEach((k) => localStorage.removeItem(k));
|
|
||||||
47 | });
|
|
||||||
48 | await page.reload();
|
|
||||||
49 | await page.waitForLoadState('networkidle');
|
|
||||||
50 |
|
|
||||||
51 | // Navigate to Crafting tab
|
|
||||||
52 | await page.getByRole('tab', { name: /🔧 Craft/ }).click();
|
|
||||||
53 |
|
|
||||||
54 | // Click Enchant sub-tab
|
|
||||||
55 | const enchantBtn = page.getByRole('button', { hasText: 'Enchant' });
|
|
||||||
56 | await enchantBtn.click();
|
|
||||||
57 |
|
|
||||||
58 | // Should see the design stage UI
|
|
||||||
59 | const designBtn = page.getByRole('button', { hasText: 'Design' });
|
|
||||||
60 | const prepareBtn = page.getByRole('button', { hasText: 'Prepare' });
|
|
||||||
61 | const applyBtn = page.getByRole('button', { hasText: 'Apply' });
|
|
||||||
62 | await expect(designBtn).toBeVisible();
|
|
||||||
63 | await expect(prepareBtn).toBeVisible();
|
|
||||||
64 | await expect(applyBtn).toBeVisible();
|
|
||||||
65 | });
|
|
||||||
66 |
|
|
||||||
67 | test('can select equipment type and effect in Design stage', async ({ page }) => {
|
|
||||||
68 | await page.goto('/');
|
|
||||||
69 | await page.evaluate(() => {
|
|
||||||
70 | Object.keys(localStorage)
|
|
||||||
71 | .filter((k) => k.startsWith('mana-loop-'))
|
|
||||||
72 | .forEach((k) => localStorage.removeItem(k));
|
|
||||||
73 | });
|
|
||||||
74 | await page.reload();
|
|
||||||
75 | await page.waitForLoadState('networkidle');
|
|
||||||
76 |
|
|
||||||
77 | // Navigate to Crafting > Enchant > Design
|
|
||||||
78 | await page.getByRole('tab', { name: /🔧 Craft/ }).click();
|
|
||||||
> 79 | await page.getByRole('button', { hasText: 'Enchant' }).click();
|
|
||||||
| ^ Error: locator.click: Error: strict mode violation: getByRole('button') resolved to 6 elements:
|
|
||||||
80 |
|
|
||||||
81 | // The design section should show effect selectors once an equipment type is chosen
|
|
||||||
82 | // Look for any element matching equipment type buttons and effect-related content
|
|
||||||
83 | const equipmentButtons = page.locator('button:has-text("Basic Staff"), button:has-text("Apprentice Wand"), button:has-text("Oak Staff"), button:has-text("Crystal Wand")');
|
|
||||||
84 | const count = await equipmentButtons.count();
|
|
||||||
85 | expect(count).toBeGreaterThan(0);
|
|
||||||
86 | });
|
|
||||||
87 |
|
|
||||||
88 | test('can navigate through all 3 enchant stages', async ({ page }) => {
|
|
||||||
89 | await page.goto('/');
|
|
||||||
90 | await page.evaluate(() => {
|
|
||||||
91 | Object.keys(localStorage)
|
|
||||||
92 | .filter((k) => k.startsWith('mana-loop-'))
|
|
||||||
93 | .forEach((k) => localStorage.removeItem(k));
|
|
||||||
94 | });
|
|
||||||
95 | await page.reload();
|
|
||||||
96 | await page.waitForLoadState('networkidle');
|
|
||||||
97 |
|
|
||||||
98 | // Navigate to Crafting > Enchant
|
|
||||||
99 | await page.getByRole('tab', { name: /🔧 Craft/ }).click();
|
|
||||||
100 | await page.getByRole('button', { hasText: 'Enchant' }).click();
|
|
||||||
101 |
|
|
||||||
102 | // Verify Design stage is active
|
|
||||||
103 | const designBtn = page.getByRole('button', { hasText: 'Design' });
|
|
||||||
104 | await expect(designBtn).toBeVisible();
|
|
||||||
105 |
|
|
||||||
106 | // Switch to Prepare stage
|
|
||||||
107 | const prepareBtn = page.getByRole('button', { hasText: 'Prepare' });
|
|
||||||
108 | await prepareBtn.click();
|
|
||||||
109 |
|
|
||||||
110 | // Should see preparation UI
|
|
||||||
111 | const prepareHeading = page.locator('text=Select Equipment to Prepare');
|
|
||||||
112 | await expect(prepareHeading).toBeVisible({ timeout: 5000 });
|
|
||||||
113 |
|
|
||||||
114 | // Switch to Apply stage
|
|
||||||
115 | const applyBtn = page.getByRole('button', { hasText: 'Apply' });
|
|
||||||
116 | await applyBtn.click();
|
|
||||||
117 |
|
|
||||||
118 | // Should see application UI
|
|
||||||
119 | const applyHeading = page.locator('text=Select Equipment & Design');
|
|
||||||
120 | await expect(applyHeading).toBeVisible({ timeout: 5000 });
|
|
||||||
121 | });
|
|
||||||
122 | });
|
|
||||||
```
|
|
||||||
@@ -1,280 +0,0 @@
|
|||||||
# Instructions
|
|
||||||
|
|
||||||
- Following Playwright test failed.
|
|
||||||
- Explain why, be concise, respect Playwright best practices.
|
|
||||||
- Provide a snippet of code with the fix, if possible.
|
|
||||||
|
|
||||||
# Test info
|
|
||||||
|
|
||||||
- Name: enchanting.spec.ts >> Enchanting Flow >> can navigate through all 3 enchant stages
|
|
||||||
- Location: e2e/enchanting.spec.ts:88:7
|
|
||||||
|
|
||||||
# Error details
|
|
||||||
|
|
||||||
```
|
|
||||||
Error: locator.click: Error: strict mode violation: getByRole('button') resolved to 6 elements:
|
|
||||||
1) <button data-slot="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive text-primary-foreground shad…>…</button> aka getByRole('button', { name: 'Gather +1 Mana' })
|
|
||||||
2) <button class="flex items-center justify-between w-full text-xs text-gray-400 hover:text-gray-300 mb-2">…</button> aka getByRole('button', { name: 'Elemental Mana (1)' })
|
|
||||||
3) <button data-slot="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive text-primary-foreground shadow-xs hover…>…</button> aka getByRole('button', { name: 'Climb the Spire' })
|
|
||||||
4) <button data-slot="action-button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-[var(--radius)] text-sm font-medium transition-all duration-100 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:ring-2 focus-visible:ring-[var(--border-focus)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--bg-base)] bg-[var(--interactive-primary)] text-white …>…</button> aka getByRole('button', { name: 'Fabricate' })
|
|
||||||
5) <button data-slot="action-button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-[var(--radius)] text-sm font-medium transition-all duration-100 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:ring-2 focus-visible:ring-[var(--border-focus)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--bg-base)] bg-[var(--interactive-secondary)] text-[var…>…</button> aka getByRole('button', { name: 'Enchant' })
|
|
||||||
6) <button id="next-logo" aria-haspopup="menu" data-next-mark="true" aria-expanded="false" aria-label="Open Next.js Dev Tools" data-nextjs-dev-tools-button="true" aria-controls="nextjs-dev-tools-menu">…</button> aka getByRole('button', { name: 'Open Next.js Dev Tools' })
|
|
||||||
|
|
||||||
Call log:
|
|
||||||
- waiting for getByRole('button')
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
# Page snapshot
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
- generic [ref=e1]:
|
|
||||||
- generic [ref=e2]:
|
|
||||||
- banner [ref=e3]:
|
|
||||||
- generic [ref=e4]:
|
|
||||||
- heading "MANA LOOP" [level=1] [ref=e5]
|
|
||||||
- generic [ref=e7]:
|
|
||||||
- generic [ref=e8]:
|
|
||||||
- generic [ref=e9]: Day 1
|
|
||||||
- generic [ref=e10]: 00:55
|
|
||||||
- generic [ref=e11]:
|
|
||||||
- generic [ref=e12]: "0"
|
|
||||||
- generic [ref=e13]: Insight
|
|
||||||
- main [ref=e14]:
|
|
||||||
- generic [ref=e15]:
|
|
||||||
- generic [ref=e17]:
|
|
||||||
- generic [ref=e18]:
|
|
||||||
- generic [ref=e19]:
|
|
||||||
- generic [ref=e20]: "11"
|
|
||||||
- generic [ref=e21]: / 100
|
|
||||||
- generic [ref=e22]:
|
|
||||||
- text: +2.2 mana/hr
|
|
||||||
- generic [ref=e23]: (1.1x med)
|
|
||||||
- progressbar [ref=e24]
|
|
||||||
- button "Gather +1 Mana" [ref=e26]:
|
|
||||||
- img
|
|
||||||
- text: Gather +1 Mana
|
|
||||||
- generic [ref=e27]:
|
|
||||||
- button "Elemental Mana (1)" [ref=e28]:
|
|
||||||
- generic [ref=e29]: Elemental Mana (1)
|
|
||||||
- img [ref=e30]
|
|
||||||
- generic [ref=e33]:
|
|
||||||
- generic [ref=e34]:
|
|
||||||
- generic [ref=e35]: 🔗
|
|
||||||
- generic [ref=e36]: Transference
|
|
||||||
- generic [ref=e39]: 0/10
|
|
||||||
- button "Climb the Spire" [ref=e40]:
|
|
||||||
- img
|
|
||||||
- text: Climb the Spire
|
|
||||||
- generic [ref=e42]:
|
|
||||||
- generic [ref=e43]:
|
|
||||||
- img [ref=e44]
|
|
||||||
- generic [ref=e46]: Current Activity
|
|
||||||
- generic [ref=e47]: Meditating
|
|
||||||
- generic [ref=e48]:
|
|
||||||
- generic [ref=e49]: "1"
|
|
||||||
- generic [ref=e50]: "2"
|
|
||||||
- generic [ref=e51]: "3"
|
|
||||||
- generic [ref=e52]: "4"
|
|
||||||
- generic [ref=e53]: "5"
|
|
||||||
- generic [ref=e54]: "6"
|
|
||||||
- generic [ref=e55]: "7"
|
|
||||||
- generic [ref=e56]: "8"
|
|
||||||
- generic [ref=e57]: "9"
|
|
||||||
- generic [ref=e58]: "10"
|
|
||||||
- generic [ref=e59]: "11"
|
|
||||||
- generic [ref=e60]: "12"
|
|
||||||
- generic [ref=e61]: "13"
|
|
||||||
- generic [ref=e62]: "14"
|
|
||||||
- generic [ref=e63]: "15"
|
|
||||||
- generic [ref=e64]: "16"
|
|
||||||
- generic [ref=e65]: "17"
|
|
||||||
- generic [ref=e66]: "18"
|
|
||||||
- generic [ref=e67]: "19"
|
|
||||||
- generic [ref=e68]: "20"
|
|
||||||
- generic [ref=e69]: "21"
|
|
||||||
- generic [ref=e70]: "22"
|
|
||||||
- generic [ref=e71]: "23"
|
|
||||||
- generic [ref=e72]: "24"
|
|
||||||
- generic [ref=e73]: "25"
|
|
||||||
- generic [ref=e74]: "26"
|
|
||||||
- generic [ref=e75]: "27"
|
|
||||||
- generic [ref=e76]: "28"
|
|
||||||
- generic [ref=e77]: "29"
|
|
||||||
- generic [ref=e78]: "30"
|
|
||||||
- generic [ref=e80]:
|
|
||||||
- tablist [ref=e81]:
|
|
||||||
- tab "⚔️ Spire" [ref=e82]
|
|
||||||
- tab "✨ Attune" [ref=e83]
|
|
||||||
- tab "🗿 Golems" [ref=e84]
|
|
||||||
- tab "📚 Skills" [ref=e85]
|
|
||||||
- tab "🔮 Spells" [ref=e86]
|
|
||||||
- tab "🛡️ Gear" [ref=e87]
|
|
||||||
- tab "🔧 Craft" [active] [selected] [ref=e88]
|
|
||||||
- tab "💎 Loot" [ref=e89]
|
|
||||||
- tab "🏆 Achieve" [ref=e90]
|
|
||||||
- tab "📊 Stats" [ref=e91]
|
|
||||||
- tab "🐛 Debug" [ref=e92]
|
|
||||||
- tab "📖 Grimoire" [ref=e93]
|
|
||||||
- tabpanel "🔧 Craft" [ref=e94]:
|
|
||||||
- generic [ref=e95]:
|
|
||||||
- generic [ref=e97]:
|
|
||||||
- button "Fabricate" [ref=e98]:
|
|
||||||
- img
|
|
||||||
- text: Fabricate
|
|
||||||
- button "Enchant" [ref=e99]:
|
|
||||||
- img
|
|
||||||
- text: Enchant
|
|
||||||
- generic [ref=e100]:
|
|
||||||
- generic [ref=e101]:
|
|
||||||
- generic [ref=e103]:
|
|
||||||
- img [ref=e104]
|
|
||||||
- text: Available Blueprints
|
|
||||||
- generic [ref=e113]:
|
|
||||||
- img [ref=e114]
|
|
||||||
- paragraph [ref=e118]: No blueprints discovered yet.
|
|
||||||
- paragraph [ref=e119]: Defeat guardians to find blueprints!
|
|
||||||
- generic [ref=e120]:
|
|
||||||
- generic [ref=e122]:
|
|
||||||
- img [ref=e123]
|
|
||||||
- text: Materials (0)
|
|
||||||
- generic [ref=e131]:
|
|
||||||
- img [ref=e132]
|
|
||||||
- paragraph [ref=e134]: No materials collected yet.
|
|
||||||
- paragraph [ref=e135]: Defeat floors to gather materials!
|
|
||||||
- region "Notifications (F8)":
|
|
||||||
- list
|
|
||||||
- region "Notifications (F8)":
|
|
||||||
- list
|
|
||||||
- button "Open Next.js Dev Tools" [ref=e141] [cursor=pointer]:
|
|
||||||
- img [ref=e142]
|
|
||||||
- alert [ref=e145]
|
|
||||||
```
|
|
||||||
|
|
||||||
# Test source
|
|
||||||
|
|
||||||
```ts
|
|
||||||
1 | import { test, expect } from '@playwright/test';
|
|
||||||
2 |
|
|
||||||
3 | /**
|
|
||||||
4 | * E2E tests for the 3-step enchantment flow:
|
|
||||||
5 | * Design → Prepare → Apply
|
|
||||||
6 | *
|
|
||||||
7 | * These tests validate the core crafting loop works end-to-end.
|
|
||||||
8 | */
|
|
||||||
9 |
|
|
||||||
10 | test.describe('Enchanting Flow', () => {
|
|
||||||
11 | /**
|
|
||||||
12 | * Before each test, ensure we start with a clean state.
|
|
||||||
13 | * The game persists state in localStorage, so we clear it.
|
|
||||||
14 | */
|
|
||||||
15 | test.beforeEach(async ({ page }) => {
|
|
||||||
16 | await page.goto('/');
|
|
||||||
17 | // Clear game state to ensure a fresh start
|
|
||||||
18 | await page.evaluate(() => {
|
|
||||||
19 | Object.keys(localStorage)
|
|
||||||
20 | .filter((k) => k.startsWith('mana-loop-'))
|
|
||||||
21 | .forEach((k) => localStorage.removeItem(k));
|
|
||||||
22 | });
|
|
||||||
23 | await page.reload();
|
|
||||||
24 | // Wait for the game to initialize
|
|
||||||
25 | await page.waitForLoadState('networkidle');
|
|
||||||
26 | });
|
|
||||||
27 |
|
|
||||||
28 | test('can navigate to Crafting tab', async ({ page }) => {
|
|
||||||
29 | // The tab bar contains a "Craft" tab
|
|
||||||
30 | const craftTab = page.getByRole('tab', { name: /🔧 Craft/ });
|
|
||||||
31 | await expect(craftTab).toBeVisible();
|
|
||||||
32 | await craftTab.click();
|
|
||||||
33 |
|
|
||||||
34 | // Verify we're on the crafting tab by checking for sub-tabs
|
|
||||||
35 | const fabricateBtn = page.getByRole('button', { hasText: 'Fabricate' });
|
|
||||||
36 | const enchantBtn = page.getByRole('button', { hasText: 'Enchant' });
|
|
||||||
37 | await expect(fabricateBtn).toBeVisible();
|
|
||||||
38 | await expect(enchantBtn).toBeVisible();
|
|
||||||
39 | });
|
|
||||||
40 |
|
|
||||||
41 | test('can switch to Enchant sub-tab and see design UI', async ({ page }) => {
|
|
||||||
42 | await page.goto('/');
|
|
||||||
43 | await page.evaluate(() => {
|
|
||||||
44 | Object.keys(localStorage)
|
|
||||||
45 | .filter((k) => k.startsWith('mana-loop-'))
|
|
||||||
46 | .forEach((k) => localStorage.removeItem(k));
|
|
||||||
47 | });
|
|
||||||
48 | await page.reload();
|
|
||||||
49 | await page.waitForLoadState('networkidle');
|
|
||||||
50 |
|
|
||||||
51 | // Navigate to Crafting tab
|
|
||||||
52 | await page.getByRole('tab', { name: /🔧 Craft/ }).click();
|
|
||||||
53 |
|
|
||||||
54 | // Click Enchant sub-tab
|
|
||||||
55 | const enchantBtn = page.getByRole('button', { hasText: 'Enchant' });
|
|
||||||
56 | await enchantBtn.click();
|
|
||||||
57 |
|
|
||||||
58 | // Should see the design stage UI
|
|
||||||
59 | const designBtn = page.getByRole('button', { hasText: 'Design' });
|
|
||||||
60 | const prepareBtn = page.getByRole('button', { hasText: 'Prepare' });
|
|
||||||
61 | const applyBtn = page.getByRole('button', { hasText: 'Apply' });
|
|
||||||
62 | await expect(designBtn).toBeVisible();
|
|
||||||
63 | await expect(prepareBtn).toBeVisible();
|
|
||||||
64 | await expect(applyBtn).toBeVisible();
|
|
||||||
65 | });
|
|
||||||
66 |
|
|
||||||
67 | test('can select equipment type and effect in Design stage', async ({ page }) => {
|
|
||||||
68 | await page.goto('/');
|
|
||||||
69 | await page.evaluate(() => {
|
|
||||||
70 | Object.keys(localStorage)
|
|
||||||
71 | .filter((k) => k.startsWith('mana-loop-'))
|
|
||||||
72 | .forEach((k) => localStorage.removeItem(k));
|
|
||||||
73 | });
|
|
||||||
74 | await page.reload();
|
|
||||||
75 | await page.waitForLoadState('networkidle');
|
|
||||||
76 |
|
|
||||||
77 | // Navigate to Crafting > Enchant > Design
|
|
||||||
78 | await page.getByRole('tab', { name: /🔧 Craft/ }).click();
|
|
||||||
79 | await page.getByRole('button', { hasText: 'Enchant' }).click();
|
|
||||||
80 |
|
|
||||||
81 | // The design section should show effect selectors once an equipment type is chosen
|
|
||||||
82 | // Look for any element matching equipment type buttons and effect-related content
|
|
||||||
83 | const equipmentButtons = page.locator('button:has-text("Basic Staff"), button:has-text("Apprentice Wand"), button:has-text("Oak Staff"), button:has-text("Crystal Wand")');
|
|
||||||
84 | const count = await equipmentButtons.count();
|
|
||||||
85 | expect(count).toBeGreaterThan(0);
|
|
||||||
86 | });
|
|
||||||
87 |
|
|
||||||
88 | test('can navigate through all 3 enchant stages', async ({ page }) => {
|
|
||||||
89 | await page.goto('/');
|
|
||||||
90 | await page.evaluate(() => {
|
|
||||||
91 | Object.keys(localStorage)
|
|
||||||
92 | .filter((k) => k.startsWith('mana-loop-'))
|
|
||||||
93 | .forEach((k) => localStorage.removeItem(k));
|
|
||||||
94 | });
|
|
||||||
95 | await page.reload();
|
|
||||||
96 | await page.waitForLoadState('networkidle');
|
|
||||||
97 |
|
|
||||||
98 | // Navigate to Crafting > Enchant
|
|
||||||
99 | await page.getByRole('tab', { name: /🔧 Craft/ }).click();
|
|
||||||
> 100 | await page.getByRole('button', { hasText: 'Enchant' }).click();
|
|
||||||
| ^ Error: locator.click: Error: strict mode violation: getByRole('button') resolved to 6 elements:
|
|
||||||
101 |
|
|
||||||
102 | // Verify Design stage is active
|
|
||||||
103 | const designBtn = page.getByRole('button', { hasText: 'Design' });
|
|
||||||
104 | await expect(designBtn).toBeVisible();
|
|
||||||
105 |
|
|
||||||
106 | // Switch to Prepare stage
|
|
||||||
107 | const prepareBtn = page.getByRole('button', { hasText: 'Prepare' });
|
|
||||||
108 | await prepareBtn.click();
|
|
||||||
109 |
|
|
||||||
110 | // Should see preparation UI
|
|
||||||
111 | const prepareHeading = page.locator('text=Select Equipment to Prepare');
|
|
||||||
112 | await expect(prepareHeading).toBeVisible({ timeout: 5000 });
|
|
||||||
113 |
|
|
||||||
114 | // Switch to Apply stage
|
|
||||||
115 | const applyBtn = page.getByRole('button', { hasText: 'Apply' });
|
|
||||||
116 | await applyBtn.click();
|
|
||||||
117 |
|
|
||||||
118 | // Should see application UI
|
|
||||||
119 | const applyHeading = page.locator('text=Select Equipment & Design');
|
|
||||||
120 | await expect(applyHeading).toBeVisible({ timeout: 5000 });
|
|
||||||
121 | });
|
|
||||||
122 | });
|
|
||||||
```
|
|
||||||