From 8a427c62e8f5f2744df8cbd76625b174989488ef Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Tue, 14 Nov 2023 13:59:32 -0600 Subject: [PATCH] test all routes, test send + receive --- .github/workflows/playwright.yml | 2 +- e2e/load.spec.ts | 52 -------- e2e/restore.spec.ts | 2 +- e2e/roundtrip.spec.ts | 92 ++++++++++++++ e2e/routes.spec.ts | 207 +++++++++++++++++++++++++++++++ 5 files changed, 301 insertions(+), 54 deletions(-) create mode 100644 e2e/roundtrip.spec.ts create mode 100644 e2e/routes.spec.ts diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index e4121d7..f3d5fc1 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -64,7 +64,7 @@ jobs: VITE_FEEDBACK: https://feedback-staging.mutinywallet.com VITE_SCORER: https://scorer-staging.mutinywallet.com VITE_PRIMAL: https://primal-cache.mutinywallet.com/api - run: pnpm exec playwright test + run: pnpm exec playwright test --grep-invert @slow - uses: actions/upload-artifact@v3 if: always() with: diff --git a/e2e/load.spec.ts b/e2e/load.spec.ts index f29ee4c..c1ebf41 100644 --- a/e2e/load.spec.ts +++ b/e2e/load.spec.ts @@ -21,55 +21,3 @@ test("initial load", async ({ page }) => { console.log("Page loaded."); }); -test("first receive", async ({ page }) => { - // Click the receive button - await page.click("text=Receive"); - - // Expect the url to conain receive - await expect(page).toHaveURL(/.*receive/); - - // At least one h1 should show "0 sats" - await expect(page.locator("h1")).toContainText(["0 SATS"]); - - // At least one h2 should show "0 USD" - await expect(page.locator("h2")).toContainText(["$0 USD"]); - - // Click the 10k button - await page.click("text=10k"); - - // Now the h1 should show "10,000 sats" - await expect(page.locator("h1")).toContainText(["10,000 SATS"]); - - // Click the "Set Amount" button - await page.click("text=Set Amount"); - - // There should be a button with the text "Continue" and it should not be disabled - const continueButton = await page.locator("button", { hasText: "Continue" }); - await expect(continueButton).not.toBeDisabled(); - - // Wait one second - // TODO: figure out how to not get an error without waiting - await page.waitForTimeout(1000); - - continueButton.click(); - - await expect( - page.getByText("Keep Mutiny open to complete the payment.") - ).toBeVisible(); - - // Locate an SVG inside a div with id "qr" - const qrCode = await page.locator("#qr > svg"); - - await expect(qrCode).toBeVisible(); - - const value = await qrCode.getAttribute("value"); - - // The SVG's value property includes "bitcoin:t" - expect(value).toContain("bitcoin:t"); - - // Now click thie "Edit" button - await page.click("text=Edit"); - - // There should not be an h1 that says "Error" - await expect(page.locator("h1")).not.toContainText(["Error"]); -}); diff --git a/e2e/restore.spec.ts b/e2e/restore.spec.ts index 740643b..bc45749 100644 --- a/e2e/restore.spec.ts +++ b/e2e/restore.spec.ts @@ -4,7 +4,7 @@ test.beforeEach(async ({ page }) => { await page.goto("http://localhost:3420/"); }); -test("restore from seed", async ({ page }) => { +test("restore from seed @slow", async ({ page }) => { // should have 100k sats on-chain const TEST_SEED_WORDS = "rival hood review write spoon tide orange ill opera enrich clip acoustic"; diff --git a/e2e/roundtrip.spec.ts b/e2e/roundtrip.spec.ts new file mode 100644 index 0000000..39c6253 --- /dev/null +++ b/e2e/roundtrip.spec.ts @@ -0,0 +1,92 @@ +import { test, expect } from "@playwright/test"; + +test.beforeEach(async ({ page }) => { + await page.goto("http://localhost:3420/"); +}); + +test("rountrip receive and send", async ({ page }) => { + // Click the receive button + await page.click("text=Receive"); + + // Expect the url to conain receive + await expect(page).toHaveURL(/.*receive/); + + // At least one h1 should show "0 sats" + await expect(page.locator("h1")).toContainText(["0 SATS"]); + + // At least one h2 should show "0 USD" + await expect(page.locator("h2")).toContainText(["$0 USD"]); + + // Click the 100k button + await page.click("text=100k"); + + // Now the h1 should show "10,000 sats" + await expect(page.locator("h1")).toContainText(["100,000 SATS"]); + + // Click the "Set Amount" button + await page.click("text=Set Amount"); + + // There should be a button with the text "Continue" and it should not be disabled + const continueButton = await page.locator("button", { hasText: "Continue" }); + await expect(continueButton).not.toBeDisabled(); + + // Wait one second + // TODO: figure out how to not get an error without waiting + await page.waitForTimeout(1000); + + continueButton.click(); + + await expect( + page.getByText("Keep Mutiny open to complete the payment.") + ).toBeVisible(); + + // Locate an SVG inside a div with id "qr" + const qrCode = await page.locator("#qr > svg"); + + await expect(qrCode).toBeVisible(); + + const value = await qrCode.getAttribute("value"); + + // The SVG's value property includes "bitcoin:t" + expect(value).toContain("bitcoin:t"); + + const lightningInvoice = value?.split("lightning=")[1]; + + // Post the lightning invoice to the server + const _response = await fetch("https://faucet.mutinynet.com/api/lightning", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + bolt11: lightningInvoice + }) + }); + + // Wait for an h1 to appear in the dom that says "Payment Received" + await page.waitForSelector("text=Payment Received", { timeout: 30000 }); + + // Click the "Nice" button + await page.click("text=Nice"); + + // Now we send + await page.click("text=Send"); + + // In the textarea with the placeholder "bitcoin:..." type refund@lnurl-staging.mutinywallet.com + const sendInput = await page.locator("textarea"); + await sendInput.fill("refund@lnurl-staging.mutinywallet.com"); + + await page.click("text=Continue"); + + await page.click("text=Set Amount"); + + await page.click("text=10k"); + + await page.click("text=Set Amount"); + + await page.click("text=Confirm Send"); + + // Wait for an h1 to appear in the dom that says "Payment Received" + await page.waitForSelector("text=Payment Sent", { timeout: 30000 }); + }); + diff --git a/e2e/routes.spec.ts b/e2e/routes.spec.ts new file mode 100644 index 0000000..f57764e --- /dev/null +++ b/e2e/routes.spec.ts @@ -0,0 +1,207 @@ +import { expect, Page, test } from "@playwright/test"; + +const routes = [ + "/", + "/activity", + "/feedback", + "/gift", + "/receive", + "/redshift", + "/scanner", + "/send", + "/swap", + "/settings" +]; + +const settingsRoutes = [ + "/admin", + "/backup", + "/channels", + "/connections", + "/currency", + "/emergencykit", + "/encrypt", + "/gift", + "/lnurlauth", + "/plus", + "/restore", + "/servers", + "/syncnostrcontacts" +]; + +const settingsRoutesPrefixed = settingsRoutes.map((route) => { + return "/settings" + route; +}); + +const allRoutes = routes.concat(settingsRoutesPrefixed); + +// Create a JS Map of all routes so we can check them off one by one +const checklist = new Map(); +allRoutes.forEach((route) => { + checklist.set(route, false); +}); + +// Only works if there's a link to the route on the page +async function checkRoute( + page: Page, + route: string, + expectedHeader: string, + checklist: Map +) { + await page.locator(`a[href='${route}']`).first().click(); + await expect(page.locator("h1").first()).toHaveText(expectedHeader); + checklist.set(route, true); +} + +test.beforeEach(async ({ page }) => { + await page.goto("http://localhost:3420/"); +}); + +test("visit each route", async ({ page }) => { + // Start on the home page + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle(/Mutiny Wallet/); + + // Wait for an element matching the selector to appear in DOM. + await page.waitForSelector("text=0 SATS"); + + console.log("Page loaded."); + + // Wait for a while just to make sure we can load everything + await page.waitForTimeout(1000); + + checklist.set("/", true); + + await checkRoute(page, "/activity", "Activity", checklist); + await page.goBack(); + + // Navigate to settings + await checkRoute(page, "/settings", "Settings", checklist); + + // Mutiny+ + await checkRoute(page, "/settings/plus", "Mutiny+", checklist); + await page.goBack(); + + // Lightning Channels + await checkRoute( + page, + "/settings/channels", + "Lightning Channels", + checklist + ); + await page.goBack(); + + // Backup + await checkRoute(page, "/settings/backup", "Backup", checklist); + await page.goBack(); + + // Restore + await checkRoute(page, "/settings/restore", "Restore", checklist); + await page.goBack(); + + // Currency + await checkRoute(page, "/settings/currency", "Currency", checklist); + await page.goBack(); + + // Servers + await checkRoute(page, "/settings/servers", "Servers", checklist); + await page.goBack(); + + // Connections + await checkRoute( + page, + "/settings/connections", + "Wallet Connections", + checklist + ); + await page.goBack(); + + // LNURL Auth + await checkRoute(page, "/settings/lnurlauth", "LNURL Auth", checklist); + await page.goBack(); + + // Sync Nostr Contacts + await checkRoute( + page, + "/settings/syncnostrcontacts", + "Sync Nostr Contacts", + checklist + ); + await page.goBack(); + + // Emergency Kit + await checkRoute( + page, + "/settings/emergencykit", + "Emergency Kit", + checklist + ); + await page.goBack(); + + // Admin + await checkRoute(page, "/settings/admin", "Secret Debug Tools", checklist); + await page.goBack(); + + // Go back home + await page.goBack(); + + // Feedback + await checkRoute(page, "/feedback", "Give us feedback!", checklist); + await page.goBack(); + + // Receive is covered in another test + checklist.set("/receive", true); + + // Send is covered in another test + checklist.set("/send", true); + + // Scanner + await page.locator(`a[href='/scanner']`).first().click(); + await expect(page.locator("button").first()).toHaveText("Paste Something"); + checklist.set("/scanner", true); + + // Now we have to check routes that aren't linked to directly for whatever reason + await page.goto( + "http://localhost:3420/gift?amount=50000&nwc_uri=nostr%2Bwalletconnect%3A%2F%2Ff6d55dff6da0f23e0d609121905aaa8da5d2bad7759459402e2bee1162912556%3Frelay%3Dwss%253A%252F%252Fnostr.mutinywallet.com%252F%26secret%3D8a2d579a182e9091d36d5668eb1c3b301d98bc792d94e866526123df79568355" + ); + await expect(page.locator("h2").nth(1)).toHaveText( + "You've been gifted some sats!" + ); + checklist.set("/gift", true); + + // Visit connections with AutoZap params + const autoZapParams = + "/settings/connections?return_to=https%3A%2F%2Fwww.zapplepay.com%2Fautozap%2Fnpub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s&name=AutoZap-jb55&budget_renewal=day&max_amount=420"; + await page.goto("http://localhost:3420" + autoZapParams); + await expect(page.locator('[role="dialog"] h2 header').first()).toHaveText( + "Add Connection" + ); + + // Redshift + await page.goto("http://localhost:3420/redshift"); + await expect(page.locator("h1")).toHaveText("Redshift (coming soon)"); + checklist.set("/redshift", true); + await page.goBack(); + + // Swap + await page.goto("http://localhost:3420/swap"); + await expect(page.locator("h1")).toHaveText("Swap to Lightning"); + checklist.set("/swap", true); + + // Gift + await page.goto("http://localhost:3420/settings/gift"); + await expect(page.locator("h1")).toHaveText("Create Gift"); + checklist.set("/settings/gift", true); + + // Encrypt + await page.goto("http://localhost:3420/settings/encrypt"); + await expect(page.locator("h1")).toHaveText( + "Encrypt your seed words (optional)" + ); + checklist.set("/settings/encrypt", true); + + // print how many routes we've visited + checklist.forEach((value, key) => { + console.log(`${key}: ${value}`); + }); +});