From ff9ee94508edea2e3dfd9991d39929b431d8b656 Mon Sep 17 00:00:00 2001 From: thephez Date: Tue, 28 Apr 2026 17:11:06 -0400 Subject: [PATCH 1/9] test(dashmint-lab): add Playwright read-only e2e suite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Run against real testnet — no mocks. Covers browse-only queries, sub-tab visibility, How-it-works tab, sort cycling, rarity rendering, and the mint-tab login overlay. Write-tier flows are stubbed as skipped placeholders pending a login fixture. Co-Authored-By: Claude Opus 4.7 (1M context) --- example-apps/dashmint-lab/package-lock.json | 64 +++++++++++++++++++ example-apps/dashmint-lab/package.json | 3 + .../dashmint-lab/playwright.config.ts | 34 ++++++++++ .../dashmint-lab/test/e2e/browse-only.spec.ts | 27 ++++++++ .../dashmint-lab/test/e2e/fixtures.ts | 30 +++++++++ .../test/e2e/how-it-works.spec.ts | 8 +++ .../dashmint-lab/test/e2e/mint-gating.spec.ts | 30 +++++++++ .../dashmint-lab/test/e2e/rarity.spec.ts | 23 +++++++ .../dashmint-lab/test/e2e/sort.spec.ts | 19 ++++++ example-apps/dashmint-lab/vite.config.ts | 1 + 10 files changed, 239 insertions(+) create mode 100644 example-apps/dashmint-lab/playwright.config.ts create mode 100644 example-apps/dashmint-lab/test/e2e/browse-only.spec.ts create mode 100644 example-apps/dashmint-lab/test/e2e/fixtures.ts create mode 100644 example-apps/dashmint-lab/test/e2e/how-it-works.spec.ts create mode 100644 example-apps/dashmint-lab/test/e2e/mint-gating.spec.ts create mode 100644 example-apps/dashmint-lab/test/e2e/rarity.spec.ts create mode 100644 example-apps/dashmint-lab/test/e2e/sort.spec.ts diff --git a/example-apps/dashmint-lab/package-lock.json b/example-apps/dashmint-lab/package-lock.json index a55eee6..7e19e4f 100644 --- a/example-apps/dashmint-lab/package-lock.json +++ b/example-apps/dashmint-lab/package-lock.json @@ -17,6 +17,7 @@ }, "devDependencies": { "@eslint/js": "^9.39.4", + "@playwright/test": "^1.59.1", "@testing-library/react": "^16.3.2", "@types/node": "^24.12.2", "@types/react": "^19.2.14", @@ -751,6 +752,22 @@ "url": "https://github.com/sponsors/Boshen" } }, + "node_modules/@playwright/test": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.59.1.tgz", + "integrity": "sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.59.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@rolldown/binding-android-arm64": { "version": "1.0.0-rc.15", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.15.tgz", @@ -3399,6 +3416,53 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/playwright": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz", + "integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.59.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz", + "integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/postcss": { "version": "8.5.10", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", diff --git a/example-apps/dashmint-lab/package.json b/example-apps/dashmint-lab/package.json index ba8f6e0..5fb5de7 100644 --- a/example-apps/dashmint-lab/package.json +++ b/example-apps/dashmint-lab/package.json @@ -12,6 +12,8 @@ "format:check": "prettier --check .", "lint": "eslint .", "test": "vitest run", + "test:e2e": "playwright test", + "test:e2e:ui": "playwright test --ui", "preview": "vite preview" }, "dependencies": { @@ -24,6 +26,7 @@ }, "devDependencies": { "@eslint/js": "^9.39.4", + "@playwright/test": "^1.59.1", "@testing-library/react": "^16.3.2", "@types/node": "^24.12.2", "@types/react": "^19.2.14", diff --git a/example-apps/dashmint-lab/playwright.config.ts b/example-apps/dashmint-lab/playwright.config.ts new file mode 100644 index 0000000..90a9964 --- /dev/null +++ b/example-apps/dashmint-lab/playwright.config.ts @@ -0,0 +1,34 @@ +import { defineConfig, devices } from "@playwright/test"; + +const PORT = 5180; + +export default defineConfig({ + testDir: "./test/e2e", + testMatch: "**/*.spec.ts", + fullyParallel: false, + forbidOnly: !!process.env.CI, + retries: 1, + workers: 1, + timeout: 120_000, + expect: { timeout: 30_000 }, + reporter: process.env.CI ? "list" : [["list"], ["html", { open: "never" }]], + + use: { + baseURL: `http://localhost:${PORT}`, + trace: "retain-on-failure", + }, + + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + ], + + webServer: { + command: `npx vite --port ${PORT} --strictPort`, + url: `http://localhost:${PORT}`, + reuseExistingServer: !process.env.CI, + timeout: 120_000, + }, +}); diff --git a/example-apps/dashmint-lab/test/e2e/browse-only.spec.ts b/example-apps/dashmint-lab/test/e2e/browse-only.spec.ts new file mode 100644 index 0000000..33e6e1e --- /dev/null +++ b/example-apps/dashmint-lab/test/e2e/browse-only.spec.ts @@ -0,0 +1,27 @@ +import { test, expect } from "./fixtures"; + +test("browse-only: All tab loads and Marketplace filters by price", async ({ + page, +}) => { + // The default contract is the pre-deployed testnet card contract; the + // expectation is just "the query came back and the grid rendered" — either + // we see article tiles or the empty-state copy. Either way means "Loading…" + // has cleared. + await page.getByRole("button", { name: "All" }).click(); + await expect(page.getByText(/loading…/i)).toBeHidden({ timeout: 90_000 }); + const allCards = page.locator("article"); + const noCards = page.getByText(/no cards found/i); + await expect(allCards.first().or(noCards)).toBeVisible(); + + await page.getByRole("button", { name: "Marketplace" }).click(); + await expect(page.getByText(/loading…/i)).toBeHidden({ timeout: 90_000 }); + const marketCards = page.locator("article"); + const noSales = page.getByText(/no cards for sale right now/i); + await expect(marketCards.first().or(noSales)).toBeVisible(); +}); + +test("browse-only: Yours sub-tab is hidden when not authenticated", async ({ + page, +}) => { + await expect(page.getByRole("button", { name: "Yours" })).toHaveCount(0); +}); diff --git a/example-apps/dashmint-lab/test/e2e/fixtures.ts b/example-apps/dashmint-lab/test/e2e/fixtures.ts new file mode 100644 index 0000000..ce1a93b --- /dev/null +++ b/example-apps/dashmint-lab/test/e2e/fixtures.ts @@ -0,0 +1,30 @@ +/** + * Shared Playwright fixtures for dashmint-lab E2E tests. + * + * These run against real Dash Platform testnet. No mocks — the app boots + * normally, connects via @dashevo/evo-sdk, and queries the default + * pre-deployed card contract. + * + * The fixture currently only handles navigation and a generous default + * timeout while testnet round-trips complete. Login + write helpers will + * land in a follow-up once the read-only suite is green. + */ +import { test as base, expect, type Page } from "@playwright/test"; + +interface AppFixture { + page: Page; +} + +export const test = base.extend({ + page: async ({ page }, use) => { + await page.goto("/"); + // Wait until the SDK has connected (sidebar shows "Connected") so any + // Collection query the spec triggers has a live SDK to talk to. + await expect(page.getByText("Connected").first()).toBeVisible({ + timeout: 60_000, + }); + await use(page); + }, +}); + +export { expect, type Page }; diff --git a/example-apps/dashmint-lab/test/e2e/how-it-works.spec.ts b/example-apps/dashmint-lab/test/e2e/how-it-works.spec.ts new file mode 100644 index 0000000..ad9206a --- /dev/null +++ b/example-apps/dashmint-lab/test/e2e/how-it-works.spec.ts @@ -0,0 +1,8 @@ +import { test, expect } from "./fixtures"; + +test("How it works tab renders without login", async ({ page }) => { + await page.getByRole("button", { name: /how it works/i }).click(); + await expect( + page.getByRole("heading", { name: /how it works/i }), + ).toBeVisible(); +}); diff --git a/example-apps/dashmint-lab/test/e2e/mint-gating.spec.ts b/example-apps/dashmint-lab/test/e2e/mint-gating.spec.ts new file mode 100644 index 0000000..52b204f --- /dev/null +++ b/example-apps/dashmint-lab/test/e2e/mint-gating.spec.ts @@ -0,0 +1,30 @@ +import { test, expect } from "./fixtures"; + +test("Mint tab shows the login overlay when not logged in", async ({ + page, +}) => { + // Sidebar nav buttons are labelled "✦ Mint" / "▤ Collection" etc. — match + // by regex (or filter to navigation) rather than exact, which would only + // hit the bare text "Mint". + await page + .getByRole("navigation") + .getByRole("button", { name: /mint/i }) + .click(); + await expect( + page.getByText(/login as contract owner to access this feature/i), + ).toBeVisible(); +}); + +test.skip( + "non-owner sees the contract-owner gating overlay (write tier — needs login)", + async () => { + /* TODO: implement when login fixture lands. */ + }, +); + +test.skip( + "contract owner can access the Mint form (write tier — needs login)", + async () => { + /* TODO: implement when login fixture lands. */ + }, +); diff --git a/example-apps/dashmint-lab/test/e2e/rarity.spec.ts b/example-apps/dashmint-lab/test/e2e/rarity.spec.ts new file mode 100644 index 0000000..5526b54 --- /dev/null +++ b/example-apps/dashmint-lab/test/e2e/rarity.spec.ts @@ -0,0 +1,23 @@ +import { test, expect } from "./fixtures"; + +test("rendered cards always carry a rarity tag (common/rare/legendary)", async ({ + page, +}) => { + await page.getByRole("button", { name: "All" }).click(); + await expect(page.getByText(/loading…/i)).toBeHidden({ timeout: 90_000 }); + + const grid = page.locator("article"); + const empty = page.getByText(/no cards found/i); + await expect(grid.first().or(empty)).toBeVisible(); + + if (await empty.isVisible().catch(() => false)) { + test.skip(true, "No cards in default contract; nothing to assert."); + } + + const count = await grid.count(); + expect(count).toBeGreaterThan(0); + + for (let i = 0; i < Math.min(count, 5); i += 1) { + await expect(grid.nth(i)).toContainText(/common|rare|legendary/i); + } +}); diff --git a/example-apps/dashmint-lab/test/e2e/sort.spec.ts b/example-apps/dashmint-lab/test/e2e/sort.spec.ts new file mode 100644 index 0000000..ba4ae47 --- /dev/null +++ b/example-apps/dashmint-lab/test/e2e/sort.spec.ts @@ -0,0 +1,19 @@ +import { test, expect } from "./fixtures"; + +test("Collection sort button cycles labels (Rarity → Name → Owner → Price)", async ({ + page, +}) => { + await page.getByRole("button", { name: "All" }).click(); + await expect(page.getByText(/loading…/i)).toBeHidden({ timeout: 90_000 }); + + const sortBtn = page.getByRole("button", { name: /^sort:/i }); + await expect(sortBtn).toContainText(/rarity/i); + await sortBtn.click(); + await expect(sortBtn).toContainText(/name/i); + await sortBtn.click(); + await expect(sortBtn).toContainText(/owner/i); + await sortBtn.click(); + await expect(sortBtn).toContainText(/price/i); + await sortBtn.click(); + await expect(sortBtn).toContainText(/rarity/i); +}); diff --git a/example-apps/dashmint-lab/vite.config.ts b/example-apps/dashmint-lab/vite.config.ts index 2a5ac2e..274d206 100644 --- a/example-apps/dashmint-lab/vite.config.ts +++ b/example-apps/dashmint-lab/vite.config.ts @@ -26,5 +26,6 @@ export default defineConfig({ test: { environment: "node", include: ["test/**/*.test.{ts,tsx}"], + exclude: ["test/e2e/**", "node_modules/**"], }, }); From 65b8c4f0fdadb4970ed9e5ca1fced498925dd7a3 Mon Sep 17 00:00:00 2001 From: thephez Date: Tue, 28 Apr 2026 17:27:53 -0400 Subject: [PATCH 2/9] test(dashmint-lab): expand Playwright read-only e2e coverage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds LoginModal entry-point coverage (sidebar + Mint overlay, Esc/backdrop close, advanced settings toggle), HowItWorks section + operations-table assertions, name-sort alphabetical verification, CardTile owner explorer link and overflow menu, browse-only Buy → LoginModal prompt, bogus contract-id resilience, and a mobile-drawer toggle smoke test. Ignores playwright-report/ and test-results/. Co-Authored-By: Claude Opus 4.7 (1M context) --- example-apps/dashmint-lab/.gitignore | 4 ++ .../test/e2e/bogus-contract.spec.ts | 32 +++++++++ .../test/e2e/buy-prompts-login.spec.ts | 20 ++++++ .../dashmint-lab/test/e2e/card-tile.spec.ts | 36 ++++++++++ .../test/e2e/how-it-works.spec.ts | 26 ++++++- .../dashmint-lab/test/e2e/login-modal.spec.ts | 68 +++++++++++++++++++ .../test/e2e/mobile-drawer.spec.ts | 27 ++++++++ .../dashmint-lab/test/e2e/sort.spec.ts | 19 ++++++ 8 files changed, 231 insertions(+), 1 deletion(-) create mode 100644 example-apps/dashmint-lab/test/e2e/bogus-contract.spec.ts create mode 100644 example-apps/dashmint-lab/test/e2e/buy-prompts-login.spec.ts create mode 100644 example-apps/dashmint-lab/test/e2e/card-tile.spec.ts create mode 100644 example-apps/dashmint-lab/test/e2e/login-modal.spec.ts create mode 100644 example-apps/dashmint-lab/test/e2e/mobile-drawer.spec.ts diff --git a/example-apps/dashmint-lab/.gitignore b/example-apps/dashmint-lab/.gitignore index a547bf3..1500d7f 100644 --- a/example-apps/dashmint-lab/.gitignore +++ b/example-apps/dashmint-lab/.gitignore @@ -12,6 +12,10 @@ dist dist-ssr *.local +# Artifacts +playwright-report/ +test-results/ + # Editor directories and files .vscode/* !.vscode/extensions.json diff --git a/example-apps/dashmint-lab/test/e2e/bogus-contract.spec.ts b/example-apps/dashmint-lab/test/e2e/bogus-contract.spec.ts new file mode 100644 index 0000000..730443a --- /dev/null +++ b/example-apps/dashmint-lab/test/e2e/bogus-contract.spec.ts @@ -0,0 +1,32 @@ +import { test as base, expect } from "@playwright/test"; + +// This spec doesn't use the shared `page` fixture because it needs to seed +// localStorage *before* the first navigation and assert the app survives a +// bogus contract id without crashing. +const test = base; + +test("App stays usable when localStorage holds a bogus contract id", async ({ + page, +}) => { + await page.addInitScript(() => { + window.localStorage.setItem( + "dashmint-lab.contractId", + "0".repeat(44), + ); + }); + await page.goto("/"); + + // Sidebar still renders even if the contract fetch fails. + await expect( + page.getByRole("navigation").getByRole("button", { name: /collection/i }), + ).toBeVisible({ timeout: 60_000 }); + + await page.getByRole("button", { name: "All" }).click(); + await expect(page.getByText(/loading…/i)).toBeHidden({ timeout: 90_000 }); + + // Either the empty-state copy or zero rendered articles is acceptable — the + // important property is that the page didn't blow up. + const articles = page.locator("article"); + const empty = page.getByText(/no cards found/i); + await expect(articles.first().or(empty)).toBeVisible(); +}); diff --git a/example-apps/dashmint-lab/test/e2e/buy-prompts-login.spec.ts b/example-apps/dashmint-lab/test/e2e/buy-prompts-login.spec.ts new file mode 100644 index 0000000..7bd52bd --- /dev/null +++ b/example-apps/dashmint-lab/test/e2e/buy-prompts-login.spec.ts @@ -0,0 +1,20 @@ +import { test, expect } from "./fixtures"; + +test("Marketplace Buy button opens LoginModal when not authenticated", async ({ + page, +}) => { + await page.getByRole("button", { name: "Marketplace" }).click(); + await expect(page.getByText(/loading…/i)).toBeHidden({ timeout: 90_000 }); + + const cards = page.locator("article"); + if ((await cards.count()) === 0) { + test.skip(true, "Marketplace is empty on testnet; nothing to buy."); + } + + // The browse-only Buy button is the outlined variant — same accessible name. + await cards.first().getByRole("button", { name: /^buy$/i }).click(); + await expect(page.getByRole("dialog")).toBeVisible(); + await expect( + page.getByRole("dialog").getByPlaceholder("mnemonic phrase"), + ).toBeVisible(); +}); diff --git a/example-apps/dashmint-lab/test/e2e/card-tile.spec.ts b/example-apps/dashmint-lab/test/e2e/card-tile.spec.ts new file mode 100644 index 0000000..5d9a49a --- /dev/null +++ b/example-apps/dashmint-lab/test/e2e/card-tile.spec.ts @@ -0,0 +1,36 @@ +import { test, expect } from "./fixtures"; + +test("CardTile owner chip links to the platform explorer", async ({ page }) => { + await page.getByRole("button", { name: "All" }).click(); + await expect(page.getByText(/loading…/i)).toBeHidden({ timeout: 90_000 }); + + const cards = page.locator("article"); + if ((await cards.count()) === 0) { + test.skip(true, "No cards on the default contract; nothing to inspect."); + } + + const firstCard = cards.first(); + const ownerLink = firstCard.locator( + 'a[href^="https://testnet.platform-explorer.com/identity/"]', + ); + await expect(ownerLink.first()).toBeVisible(); +}); + +test("CardTile overflow menu shows Copy ID and View on Explorer", async ({ + page, +}) => { + await page.getByRole("button", { name: "All" }).click(); + await expect(page.getByText(/loading…/i)).toBeHidden({ timeout: 90_000 }); + + const cards = page.locator("article"); + if ((await cards.count()) === 0) { + test.skip(true, "No cards on the default contract; nothing to inspect."); + } + + const firstCard = cards.first(); + await firstCard.getByRole("button", { name: /more actions/i }).click(); + await expect(firstCard.getByRole("button", { name: /copy id/i })).toBeVisible(); + await expect( + firstCard.getByRole("button", { name: /view on explorer/i }), + ).toBeVisible(); +}); diff --git a/example-apps/dashmint-lab/test/e2e/how-it-works.spec.ts b/example-apps/dashmint-lab/test/e2e/how-it-works.spec.ts index ad9206a..a2edc41 100644 --- a/example-apps/dashmint-lab/test/e2e/how-it-works.spec.ts +++ b/example-apps/dashmint-lab/test/e2e/how-it-works.spec.ts @@ -1,8 +1,32 @@ import { test, expect } from "./fixtures"; -test("How it works tab renders without login", async ({ page }) => { +test("How it works tab renders the screen header", async ({ page }) => { await page.getByRole("button", { name: /how it works/i }).click(); await expect( page.getByRole("heading", { name: /how it works/i }), ).toBeVisible(); }); + +test("How it works tab renders all four sections", async ({ page }) => { + await page.getByRole("button", { name: /how it works/i }).click(); + await expect( + page.getByRole("heading", { name: /what is dashmint lab\?/i }), + ).toBeVisible(); + await expect( + page.getByRole("heading", { name: /platform operations at a glance/i }), + ).toBeVisible(); + await expect( + page.getByRole("heading", { name: /coming from ethereum\?/i }), + ).toBeVisible(); + await expect( + page.getByRole("heading", { name: /reading order/i }), + ).toBeVisible(); +}); + +test("Operations table maps Mint card → sdk.documents.create", async ({ + page, +}) => { + await page.getByRole("button", { name: /how it works/i }).click(); + const row = page.locator("tr", { hasText: "Mint card" }); + await expect(row).toContainText("sdk.documents.create"); +}); diff --git a/example-apps/dashmint-lab/test/e2e/login-modal.spec.ts b/example-apps/dashmint-lab/test/e2e/login-modal.spec.ts new file mode 100644 index 0000000..d94e492 --- /dev/null +++ b/example-apps/dashmint-lab/test/e2e/login-modal.spec.ts @@ -0,0 +1,68 @@ +import { test, expect } from "./fixtures"; + +test("sidebar Login button opens the LoginModal with mnemonic input", async ({ + page, +}) => { + await page + .getByRole("navigation") + .getByRole("button", { name: /login/i }) + .click(); + const dialog = page.getByRole("dialog"); + await expect(dialog).toBeVisible(); + await expect(dialog.getByPlaceholder("mnemonic phrase")).toBeVisible(); +}); + +test("Advanced settings toggle reveals the identity index input", async ({ + page, +}) => { + await page + .getByRole("navigation") + .getByRole("button", { name: /login/i }) + .click(); + const dialog = page.getByRole("dialog"); + await expect(dialog).toBeVisible(); + await dialog.getByRole("button", { name: /advanced settings/i }).click(); + await expect( + dialog.getByText(/identity index/i).first(), + ).toBeVisible(); + await expect(dialog.locator('input[type="number"]')).toBeVisible(); +}); + +test("Esc closes the LoginModal", async ({ page }) => { + await page + .getByRole("navigation") + .getByRole("button", { name: /login/i }) + .click(); + const dialog = page.getByRole("dialog"); + await expect(dialog).toBeVisible(); + await page.keyboard.press("Escape"); + await expect(dialog).toBeHidden(); +}); + +test("Backdrop click closes the LoginModal", async ({ page }) => { + await page + .getByRole("navigation") + .getByRole("button", { name: /login/i }) + .click(); + const dialog = page.getByRole("dialog"); + await expect(dialog).toBeVisible(); + // Click the modal backdrop (just outside the dialog content). + await page.mouse.click(5, 5); + await expect(dialog).toBeHidden(); +}); + +test("Mint-tab overlay Login button opens the LoginModal", async ({ page }) => { + await page + .getByRole("navigation") + .getByRole("button", { name: /mint/i }) + .click(); + // Overlay is shown; click the Login CTA inside it (main column, not sidebar). + await page + .getByRole("main") + .getByRole("button", { name: /login/i }) + .click(); + await expect(page.getByRole("dialog")).toBeVisible(); + await expect( + page.getByRole("dialog").getByPlaceholder("mnemonic phrase"), + ).toBeVisible(); +}); diff --git a/example-apps/dashmint-lab/test/e2e/mobile-drawer.spec.ts b/example-apps/dashmint-lab/test/e2e/mobile-drawer.spec.ts new file mode 100644 index 0000000..fa708d7 --- /dev/null +++ b/example-apps/dashmint-lab/test/e2e/mobile-drawer.spec.ts @@ -0,0 +1,27 @@ +import { test as base, expect } from "@playwright/test"; + +// Mobile viewport spec — bypass the shared fixture so we can size the +// viewport before navigation. +const test = base; +test.use({ viewport: { width: 390, height: 844 } }); + +test("Mobile hamburger toggles the navigation drawer", async ({ page }) => { + await page.goto("/"); + + const hamburger = page.getByRole("button", { name: /open menu/i }); + await expect(hamburger).toBeVisible(); + + // The desktop nav is hidden behind a CSS translate on small viewports; the + // sidebar's