aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-02-11 09:44:48 -0800
committerAdam Soltys <[email protected]>2026-02-11 15:34:35 -0800
commit2cc4969678f4d073ebddec0e7e9f2aac707fb761 (patch)
tree7b196647aa87432e4eb420a43d0b9e839078133b
parentb484f522ef032c0469db976a422fc667ca402855 (diff)
downloadcoinos-ui-2cc4969678f4d073ebddec0e7e9f2aac707fb761.tar.gz
coinos-ui-2cc4969678f4d073ebddec0e7e9f2aac707fb761.zip
playwright test
-rw-r--r--bun.lock9
-rw-r--r--package.json2
-rw-r--r--playwright.config.ts37
-rw-r--r--src/components/Balance.svelte8
-rw-r--r--src/routes/(static)/login/+page.svelte10
-rw-r--r--test/e2e/login-balance.spec.ts23
6 files changed, 85 insertions, 4 deletions
diff --git a/bun.lock b/bun.lock
index b8c83485..a38ab114 100644
--- a/bun.lock
+++ b/bun.lock
@@ -19,6 +19,7 @@
"@capacitor/core": "^8.0.2",
"@capacitor/ios": "^8.0.2",
"@iconify/iconify": "^3.1.1",
+ "@playwright/test": "^1.54.2",
"@scure/base": "^2.0.0",
"@scure/bip39": "^2.0.1",
"@scure/btc-signer": "^2.0.1",
@@ -206,6 +207,8 @@
"@paralleldrive/cuid2": ["@paralleldrive/[email protected]", "", { "dependencies": { "@noble/hashes": "^1.1.5" } }, "sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA=="],
+ "@playwright/test": ["@playwright/[email protected]", "", { "dependencies": { "playwright": "1.58.2" }, "bin": { "playwright": "cli.js" } }, "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA=="],
+
"@polka/url": ["@polka/[email protected]", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
"@prettier/plugin-xml": ["@prettier/[email protected]", "", { "dependencies": { "@xml-tools/parser": "^1.0.11", "prettier": ">=2.4.0" } }, "sha512-UWRmygBsyj4bVXvDiqSccwT1kmsorcwQwaIy30yVh8T+Gspx4OlC0shX1y+ZuwXZvgnafmpRYKks0bAu9urJew=="],
@@ -924,6 +927,10 @@
"pify": ["[email protected]", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="],
+ "playwright": ["[email protected]", "", { "dependencies": { "playwright-core": "1.58.2" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A=="],
+
+ "playwright-core": ["[email protected]", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg=="],
+
"plist": ["[email protected]", "", { "dependencies": { "@xmldom/xmldom": "^0.8.8", "base64-js": "^1.5.1", "xmlbuilder": "^15.1.1" } }, "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ=="],
"pngjs": ["[email protected]", "", {}, "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="],
@@ -1368,6 +1375,8 @@
"path-type/pify": ["[email protected]", "", {}, "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg=="],
+ "playwright/fsevents": ["[email protected]", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
+
"plist/@xmldom/xmldom": ["@xmldom/[email protected]", "", {}, "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw=="],
"prebuild-install/tar-fs": ["[email protected]", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg=="],
diff --git a/package.json b/package.json
index 9d72de2e..49da557a 100644
--- a/package.json
+++ b/package.json
@@ -2,6 +2,7 @@
"name": "coinos",
"version": "1.0.9",
"devDependencies": {
+ "@playwright/test": "^1.54.2",
"@capacitor/android": "^8.0.2",
"@capacitor/assets": "^3.0.5",
"@capacitor/cli": "^8.0.2",
@@ -57,6 +58,7 @@
"buildstaging": "vite build --mode staging",
"docker": "docker build . -t coinos-ui",
"preview": "vite preview",
+ "test:e2e": "playwright test",
"lint": "prettier --check .",
"format": "prettier --write ."
},
diff --git a/playwright.config.ts b/playwright.config.ts
new file mode 100644
index 00000000..f245b4e4
--- /dev/null
+++ b/playwright.config.ts
@@ -0,0 +1,37 @@
+import { defineConfig, devices } from "@playwright/test";
+
+const port = Number(process.env.E2E_PORT || 4173);
+const host = process.env.E2E_HOST || "127.0.0.1";
+const baseURL = process.env.E2E_BASE_URL || `http://${host}:${port}`;
+
+export default defineConfig({
+ testDir: "./test/e2e",
+ timeout: 30_000,
+ expect: {
+ timeout: 10_000,
+ },
+ fullyParallel: true,
+ forbidOnly: !!process.env.CI,
+ retries: process.env.CI ? 2 : 0,
+ workers: process.env.CI ? 1 : undefined,
+ reporter: [["list"]],
+ use: {
+ baseURL,
+ trace: "on-first-retry",
+ },
+ projects: [
+ {
+ name: "chromium",
+ use: { ...devices["Desktop Chrome"] },
+ },
+ ],
+ webServer: process.env.E2E_SKIP_WEBSERVER
+ ? undefined
+ : {
+ command: `PUBLIC_RECAPTCHA_SITE_KEY= bunx vite dev --host ${host} --port ${port}`,
+ url: baseURL,
+ reuseExistingServer: true,
+ stdout: "pipe",
+ stderr: "pipe",
+ },
+});
diff --git a/src/components/Balance.svelte b/src/components/Balance.svelte
index cf61e0b0..4ceaa65a 100644
--- a/src/components/Balance.svelte
+++ b/src/components/Balance.svelte
@@ -22,13 +22,17 @@
<div>
{#if user.haspin && !$pin}
- <button onclick={toggleShow} class="flex gap-2 text-xl">
+ <button
+ onclick={toggleShow}
+ class="flex gap-2 text-xl"
+ data-testid="balance-show"
+ >
<iconify-icon noobserver icon="ph:eye-slash-bold" width="32"
></iconify-icon>
<div class="my-auto">{$t("user.showBalance")}</div>
</button>
{:else if !isNaN(rate)}
- <div class="flex gap-4">
+ <div class="flex gap-4" data-testid="balance-value">
<Amount
amount={Math.max(balance - (id === user.id ? locked || 0 : 0), 0)}
{rate}
diff --git a/src/routes/(static)/login/+page.svelte b/src/routes/(static)/login/+page.svelte
index d25c6e51..23f772d3 100644
--- a/src/routes/(static)/login/+page.svelte
+++ b/src/routes/(static)/login/+page.svelte
@@ -66,7 +66,7 @@
const getRecaptchaToken = () =>
new Promise((resolve, reject) => {
- if (isTor) return resolve("");
+ if (isTor || !recaptchaSiteKey) return resolve("");
if (!browser || !grecaptcha) return reject(new Error("captcha unavailable"));
grecaptcha.ready(() => {
grecaptcha
@@ -182,6 +182,7 @@
<input
name="username"
+ data-testid="login-username"
type="text"
required
bind:value={username}
@@ -198,7 +199,12 @@
>
</div>
- <button type="submit" class="btn btn-accent" bind:this={btn}>
+ <button
+ type="submit"
+ class="btn btn-accent"
+ bind:this={btn}
+ data-testid="login-submit"
+ >
{$t("login.signIn")}
</button>
diff --git a/test/e2e/login-balance.spec.ts b/test/e2e/login-balance.spec.ts
new file mode 100644
index 00000000..75148bc4
--- /dev/null
+++ b/test/e2e/login-balance.spec.ts
@@ -0,0 +1,23 @@
+import { expect, test } from "@playwright/test";
+
+const username = process.env.E2E_USERNAME || "bob";
+const password = process.env.E2E_PASSWORD || "pw";
+
+test("login and report account balance", async ({ page }) => {
+ await page.goto("/login");
+
+ await page.getByTestId("login-username").fill(username);
+ await page.locator('input[name="password"]').first().fill(password);
+ await page.getByTestId("login-submit").click();
+
+ await expect(page).toHaveURL(new RegExp(`/${username}(?:[/?#]|$)`));
+
+ const balances = page.getByTestId("balance-value");
+ await expect(balances.first()).toBeVisible();
+
+ const balanceText = (await balances.first().innerText()).trim();
+ const hasNumericBalance = /\d/.test(balanceText);
+ expect(hasNumericBalance).toBeTruthy();
+
+ console.log(`[e2e] ${username} visible balance: ${balanceText}`);
+});