test(e2e): admin user registration
This commit is contained in:
parent
c9a6a11831
commit
6e9ecb35c5
9 changed files with 222 additions and 2 deletions
|
|
@ -1,3 +1,4 @@
|
||||||
|
SERVER_IP=localhost
|
||||||
DATABASE_URL=./data/zerobyte.db
|
DATABASE_URL=./data/zerobyte.db
|
||||||
RESTIC_PASS_FILE=./data/restic.pass
|
RESTIC_PASS_FILE=./data/restic.pass
|
||||||
RESTIC_CACHE_DIR=./data/restic/cache
|
RESTIC_CACHE_DIR=./data/restic/cache
|
||||||
|
|
|
||||||
8
.gitignore
vendored
8
.gitignore
vendored
|
|
@ -20,3 +20,11 @@ data/
|
||||||
.env*
|
.env*
|
||||||
!.env.example
|
!.env.example
|
||||||
!.env.test
|
!.env.test
|
||||||
|
|
||||||
|
# Playwright
|
||||||
|
node_modules/
|
||||||
|
/test-results/
|
||||||
|
/playwright-report/
|
||||||
|
/blob-report/
|
||||||
|
/playwright/.cache/
|
||||||
|
/playwright/.auth/
|
||||||
|
|
|
||||||
15
bun.lock
15
bun.lock
|
|
@ -70,17 +70,20 @@
|
||||||
"@faker-js/faker": "^10.2.0",
|
"@faker-js/faker": "^10.2.0",
|
||||||
"@happy-dom/global-registrator": "^20.1.0",
|
"@happy-dom/global-registrator": "^20.1.0",
|
||||||
"@hey-api/openapi-ts": "^0.90.2",
|
"@hey-api/openapi-ts": "^0.90.2",
|
||||||
|
"@playwright/test": "^1.57.0",
|
||||||
"@react-router/dev": "^7.12.0",
|
"@react-router/dev": "^7.12.0",
|
||||||
"@tailwindcss/typography": "^0.5.19",
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
"@tailwindcss/vite": "^4.1.17",
|
"@tailwindcss/vite": "^4.1.17",
|
||||||
"@tanstack/react-query-devtools": "^5.91.2",
|
"@tanstack/react-query-devtools": "^5.91.2",
|
||||||
"@testing-library/dom": "^10.4.1",
|
"@testing-library/dom": "^10.4.1",
|
||||||
"@testing-library/react": "^16.3.1",
|
"@testing-library/react": "^16.3.1",
|
||||||
|
"@types/better-sqlite3": "^7.6.13",
|
||||||
"@types/bun": "^1.3.4",
|
"@types/bun": "^1.3.4",
|
||||||
"@types/node": "^25.0.3",
|
"@types/node": "^25.0.3",
|
||||||
"@types/react": "^19.2.7",
|
"@types/react": "^19.2.7",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@types/semver": "^7.7.1",
|
"@types/semver": "^7.7.1",
|
||||||
|
"better-sqlite3": "^12.6.0",
|
||||||
"dotenv-cli": "^11.0.0",
|
"dotenv-cli": "^11.0.0",
|
||||||
"drizzle-kit": "^0.31.7",
|
"drizzle-kit": "^0.31.7",
|
||||||
"lightningcss": "^1.30.2",
|
"lightningcss": "^1.30.2",
|
||||||
|
|
@ -364,6 +367,8 @@
|
||||||
|
|
||||||
"@oxlint/win32-x64": ["@oxlint/win32-x64@1.38.0", "", { "os": "win32", "cpu": "x64" }, "sha512-7IuZMYiZiOcgg5zHvpJY6jRlEwh8EB/uq7GsoQJO9hANq96TIjyntGByhIjFSsL4asyZmhTEki+MO/u5Fb/WQA=="],
|
"@oxlint/win32-x64": ["@oxlint/win32-x64@1.38.0", "", { "os": "win32", "cpu": "x64" }, "sha512-7IuZMYiZiOcgg5zHvpJY6jRlEwh8EB/uq7GsoQJO9hANq96TIjyntGByhIjFSsL4asyZmhTEki+MO/u5Fb/WQA=="],
|
||||||
|
|
||||||
|
"@playwright/test": ["@playwright/test@1.57.0", "", { "dependencies": { "playwright": "1.57.0" }, "bin": { "playwright": "cli.js" } }, "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA=="],
|
||||||
|
|
||||||
"@prisma/client": ["@prisma/client@5.22.0", "", { "peerDependencies": { "prisma": "*" }, "optionalPeers": ["prisma"] }, "sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA=="],
|
"@prisma/client": ["@prisma/client@5.22.0", "", { "peerDependencies": { "prisma": "*" }, "optionalPeers": ["prisma"] }, "sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA=="],
|
||||||
|
|
||||||
"@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="],
|
"@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="],
|
||||||
|
|
@ -566,6 +571,8 @@
|
||||||
|
|
||||||
"@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="],
|
"@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="],
|
||||||
|
|
||||||
|
"@types/better-sqlite3": ["@types/better-sqlite3@7.6.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA=="],
|
||||||
|
|
||||||
"@types/bun": ["@types/bun@1.3.5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="],
|
"@types/bun": ["@types/bun@1.3.5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="],
|
||||||
|
|
||||||
"@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="],
|
"@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="],
|
||||||
|
|
@ -660,7 +667,7 @@
|
||||||
|
|
||||||
"better-call": ["better-call@1.1.7", "", { "dependencies": { "@better-auth/utils": "^0.3.0", "@better-fetch/fetch": "^1.1.4", "rou3": "^0.7.10", "set-cookie-parser": "^2.7.1" }, "peerDependencies": { "zod": "^4.0.0" }, "optionalPeers": ["zod"] }, "sha512-6gaJe1bBIEgVebQu/7q9saahVzvBsGaByEnE8aDVncZEDiJO7sdNB28ot9I6iXSbR25egGmmZ6aIURXyQHRraQ=="],
|
"better-call": ["better-call@1.1.7", "", { "dependencies": { "@better-auth/utils": "^0.3.0", "@better-fetch/fetch": "^1.1.4", "rou3": "^0.7.10", "set-cookie-parser": "^2.7.1" }, "peerDependencies": { "zod": "^4.0.0" }, "optionalPeers": ["zod"] }, "sha512-6gaJe1bBIEgVebQu/7q9saahVzvBsGaByEnE8aDVncZEDiJO7sdNB28ot9I6iXSbR25egGmmZ6aIURXyQHRraQ=="],
|
||||||
|
|
||||||
"better-sqlite3": ["better-sqlite3@12.5.0", "", { "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" } }, "sha512-WwCZ/5Diz7rsF29o27o0Gcc1Du+l7Zsv7SYtVPG0X3G/uUI1LqdxrQI7c9Hs2FWpqXXERjW9hp6g3/tH7DlVKg=="],
|
"better-sqlite3": ["better-sqlite3@12.6.0", "", { "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" } }, "sha512-FXI191x+D6UPWSze5IzZjhz+i9MK9nsuHsmTX9bXVl52k06AfZ2xql0lrgIUuzsMsJ7Vgl5kIptvDgBLIV3ZSQ=="],
|
||||||
|
|
||||||
"bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="],
|
"bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="],
|
||||||
|
|
||||||
|
|
@ -1238,6 +1245,10 @@
|
||||||
|
|
||||||
"pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="],
|
"pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="],
|
||||||
|
|
||||||
|
"playwright": ["playwright@1.57.0", "", { "dependencies": { "playwright-core": "1.57.0" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw=="],
|
||||||
|
|
||||||
|
"playwright-core": ["playwright-core@1.57.0", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ=="],
|
||||||
|
|
||||||
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
||||||
|
|
||||||
"postcss-selector-parser": ["postcss-selector-parser@6.0.10", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w=="],
|
"postcss-selector-parser": ["postcss-selector-parser@6.0.10", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w=="],
|
||||||
|
|
@ -1598,6 +1609,8 @@
|
||||||
|
|
||||||
"pkg-types/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
"pkg-types/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||||
|
|
||||||
|
"playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
|
||||||
|
|
||||||
"pretty-format/react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="],
|
"pretty-format/react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="],
|
||||||
|
|
||||||
"raw-body/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
|
"raw-body/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
|
||||||
|
|
|
||||||
|
|
@ -39,3 +39,20 @@ services:
|
||||||
- /etc/localtime:/etc/localtime:ro
|
- /etc/localtime:/etc/localtime:ro
|
||||||
- /var/lib/zerobyte:/var/lib/zerobyte
|
- /var/lib/zerobyte:/var/lib/zerobyte
|
||||||
- ~/.config/rclone:/root/.config/rclone
|
- ~/.config/rclone:/root/.config/rclone
|
||||||
|
|
||||||
|
zerobyte-e2e:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
target: production
|
||||||
|
container_name: zerobyte
|
||||||
|
restart: unless-stopped
|
||||||
|
devices:
|
||||||
|
- /dev/fuse:/dev/fuse
|
||||||
|
cap_add:
|
||||||
|
- SYS_ADMIN
|
||||||
|
ports:
|
||||||
|
- "4096:4096"
|
||||||
|
volumes:
|
||||||
|
- /etc/localtime:/etc/localtime:ro
|
||||||
|
- ./data:/var/lib/zerobyte
|
||||||
|
|
|
||||||
77
e2e/0001-initial-setup.spec.ts
Normal file
77
e2e/0001-initial-setup.spec.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
import fs from "fs";
|
||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
import { resetDatabase } from "./helpers/db";
|
||||||
|
import { createTestAccount } from "./helpers/account";
|
||||||
|
|
||||||
|
// Run tests in serial mode to avoid conflicts during onboarding
|
||||||
|
test.describe.configure({ mode: "serial" });
|
||||||
|
|
||||||
|
test.beforeEach(async () => {
|
||||||
|
await resetDatabase();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should redirect to onboarding", async ({ page }) => {
|
||||||
|
await page.goto("/");
|
||||||
|
|
||||||
|
await page.waitForURL(/onboarding/);
|
||||||
|
|
||||||
|
await expect(page).toHaveTitle(/Zerobyte - Onboarding/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("user can register a new account", async ({ page }) => {
|
||||||
|
await page.goto("/onboarding");
|
||||||
|
|
||||||
|
await page.getByRole("textbox", { name: "Email" }).click();
|
||||||
|
await page.getByRole("textbox", { name: "Email" }).fill("test@test.com");
|
||||||
|
|
||||||
|
await page.getByRole("textbox", { name: "Username" }).fill("test");
|
||||||
|
|
||||||
|
await page.getByRole("textbox", { name: "Password", exact: true }).fill("password");
|
||||||
|
await page.getByRole("textbox", { name: "Confirm Password" }).fill("password");
|
||||||
|
|
||||||
|
await page.getByRole("button", { name: "Create admin user" }).click();
|
||||||
|
|
||||||
|
await expect(page.getByText("Download Your Recovery Key")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("user can download recovery key", async ({ page }) => {
|
||||||
|
await createTestAccount(page);
|
||||||
|
|
||||||
|
await page.getByRole("textbox", { name: "Confirm Your Password" }).fill("test");
|
||||||
|
await page.getByRole("button", { name: "Download Recovery Key" }).click();
|
||||||
|
|
||||||
|
// Should not be able to download with invalid confirm password
|
||||||
|
await expect(page.getByText("Invalid password")).toBeVisible();
|
||||||
|
|
||||||
|
await page.getByRole("textbox", { name: "Confirm Your Password" }).fill("password");
|
||||||
|
|
||||||
|
const downloadPromise = page.waitForEvent("download");
|
||||||
|
await page.getByRole("button", { name: "Download Recovery Key" }).click();
|
||||||
|
|
||||||
|
const download = await downloadPromise;
|
||||||
|
|
||||||
|
expect(download.suggestedFilename()).toBe("restic.pass");
|
||||||
|
await download.saveAs("./data/restic.pass");
|
||||||
|
|
||||||
|
const fileContent = await fs.promises.readFile("./data/restic.pass", "utf8");
|
||||||
|
|
||||||
|
expect(fileContent).toHaveLength(64);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("can't create another admin user after initial setup", async ({ page }) => {
|
||||||
|
await createTestAccount(page);
|
||||||
|
|
||||||
|
await page.goto("/onboarding");
|
||||||
|
|
||||||
|
await page.getByRole("textbox", { name: "Email" }).click();
|
||||||
|
await page.getByRole("textbox", { name: "Email" }).fill("test@test.com");
|
||||||
|
|
||||||
|
await page.getByRole("textbox", { name: "Username" }).fill("test");
|
||||||
|
|
||||||
|
await page.getByRole("textbox", { name: "Password", exact: true }).fill("password");
|
||||||
|
await page.getByRole("textbox", { name: "Confirm Password" }).fill("password");
|
||||||
|
|
||||||
|
await page.getByRole("button", { name: "Create admin user" }).click();
|
||||||
|
|
||||||
|
await expect(page.getByText("Admin user already exists")).toBeVisible();
|
||||||
|
});
|
||||||
24
e2e/helpers/account.ts
Normal file
24
e2e/helpers/account.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { expect, type Page } from "@playwright/test";
|
||||||
|
import { db } from "./db";
|
||||||
|
|
||||||
|
export const createTestAccount = async (page: Page) => {
|
||||||
|
const existingUsers = await db.query.usersTable.findFirst();
|
||||||
|
|
||||||
|
if (existingUsers) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.goto("/onboarding");
|
||||||
|
|
||||||
|
await page.getByRole("textbox", { name: "Email" }).click();
|
||||||
|
await page.getByRole("textbox", { name: "Email" }).fill("test@test.com");
|
||||||
|
|
||||||
|
await page.getByRole("textbox", { name: "Username" }).fill("test");
|
||||||
|
|
||||||
|
await page.getByRole("textbox", { name: "Password", exact: true }).fill("password");
|
||||||
|
await page.getByRole("textbox", { name: "Confirm Password" }).fill("password");
|
||||||
|
|
||||||
|
await page.getByRole("button", { name: "Create admin user" }).click();
|
||||||
|
|
||||||
|
await expect(page.getByText("Download Your Recovery Key")).toBeVisible();
|
||||||
|
};
|
||||||
22
e2e/helpers/db.ts
Normal file
22
e2e/helpers/db.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import Database from "better-sqlite3";
|
||||||
|
import { drizzle } from "drizzle-orm/better-sqlite3";
|
||||||
|
import path from "node:path";
|
||||||
|
import { DATABASE_URL } from "~/server/core/constants";
|
||||||
|
import * as schema from "~/server/db/schema";
|
||||||
|
|
||||||
|
const sqlite = new Database(path.join("./data", DATABASE_URL));
|
||||||
|
|
||||||
|
export const db = drizzle({ client: sqlite, schema: schema });
|
||||||
|
|
||||||
|
export const resetDatabase = async () => {
|
||||||
|
for (const table of Object.values(schema)) {
|
||||||
|
if ("getSQL" in table) {
|
||||||
|
await db
|
||||||
|
.delete(table)
|
||||||
|
.execute()
|
||||||
|
.catch(() => {
|
||||||
|
/* Ignore errors */
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -12,12 +12,16 @@
|
||||||
"tsc": "react-router typegen && tsc",
|
"tsc": "react-router typegen && tsc",
|
||||||
"start:dev": "docker compose down && docker compose up --build zerobyte-dev",
|
"start:dev": "docker compose down && docker compose up --build zerobyte-dev",
|
||||||
"start:prod": "docker compose down && docker compose up --build zerobyte-prod",
|
"start:prod": "docker compose down && docker compose up --build zerobyte-prod",
|
||||||
|
"start:e2e": "docker compose down && docker compose up --build zerobyte-e2e",
|
||||||
"gen:api-client": "openapi-ts",
|
"gen:api-client": "openapi-ts",
|
||||||
"gen:migrations": "drizzle-kit generate",
|
"gen:migrations": "drizzle-kit generate",
|
||||||
"studio": "drizzle-kit studio",
|
"studio": "drizzle-kit studio",
|
||||||
"test:server": "dotenv -e .env.test -- bun test app/server --preload ./app/test/setup.ts",
|
"test:server": "dotenv -e .env.test -- bun test app/server --preload ./app/test/setup.ts",
|
||||||
"test:client": "dotenv -e .env.test -- bun test app/client --preload ./app/test/setup-client.ts",
|
"test:client": "dotenv -e .env.test -- bun test app/client --preload ./app/test/setup-client.ts",
|
||||||
"test": "bun run test:server && bun run test:client"
|
"test": "bun run test:server && bun run test:client",
|
||||||
|
"test:e2e": "NODE_ENV=test dotenv -e .env.local -- playwright test",
|
||||||
|
"test:e2e:ui": "NODE_ENV=test dotenv -e .env.local -- playwright test --ui",
|
||||||
|
"test:codegen": "playwright codegen localhost:4096"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dnd-kit/core": "^6.3.1",
|
"@dnd-kit/core": "^6.3.1",
|
||||||
|
|
@ -85,17 +89,20 @@
|
||||||
"@faker-js/faker": "^10.2.0",
|
"@faker-js/faker": "^10.2.0",
|
||||||
"@happy-dom/global-registrator": "^20.1.0",
|
"@happy-dom/global-registrator": "^20.1.0",
|
||||||
"@hey-api/openapi-ts": "^0.90.2",
|
"@hey-api/openapi-ts": "^0.90.2",
|
||||||
|
"@playwright/test": "^1.57.0",
|
||||||
"@react-router/dev": "^7.12.0",
|
"@react-router/dev": "^7.12.0",
|
||||||
"@tailwindcss/typography": "^0.5.19",
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
"@tailwindcss/vite": "^4.1.17",
|
"@tailwindcss/vite": "^4.1.17",
|
||||||
"@tanstack/react-query-devtools": "^5.91.2",
|
"@tanstack/react-query-devtools": "^5.91.2",
|
||||||
"@testing-library/dom": "^10.4.1",
|
"@testing-library/dom": "^10.4.1",
|
||||||
"@testing-library/react": "^16.3.1",
|
"@testing-library/react": "^16.3.1",
|
||||||
|
"@types/better-sqlite3": "^7.6.13",
|
||||||
"@types/bun": "^1.3.4",
|
"@types/bun": "^1.3.4",
|
||||||
"@types/node": "^25.0.3",
|
"@types/node": "^25.0.3",
|
||||||
"@types/react": "^19.2.7",
|
"@types/react": "^19.2.7",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@types/semver": "^7.7.1",
|
"@types/semver": "^7.7.1",
|
||||||
|
"better-sqlite3": "^12.6.0",
|
||||||
"dotenv-cli": "^11.0.0",
|
"dotenv-cli": "^11.0.0",
|
||||||
"drizzle-kit": "^0.31.7",
|
"drizzle-kit": "^0.31.7",
|
||||||
"lightningcss": "^1.30.2",
|
"lightningcss": "^1.30.2",
|
||||||
|
|
|
||||||
51
playwright.config.ts
Normal file
51
playwright.config.ts
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
import "dotenv/config";
|
||||||
|
import { defineConfig, devices } from "@playwright/test";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
testDir: "./e2e",
|
||||||
|
fullyParallel: true,
|
||||||
|
forbidOnly: !!process.env.CI,
|
||||||
|
retries: process.env.CI ? 2 : 0,
|
||||||
|
workers: process.env.CI ? 1 : undefined,
|
||||||
|
reporter: "html",
|
||||||
|
use: {
|
||||||
|
baseURL: `http://${process.env.SERVER_IP}:4096`,
|
||||||
|
video: "retain-on-failure",
|
||||||
|
trace: "on-first-retry",
|
||||||
|
},
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: "chromium",
|
||||||
|
use: { ...devices["Desktop Chrome"] },
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "firefox",
|
||||||
|
use: { ...devices["Desktop Firefox"] },
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "webkit",
|
||||||
|
use: { ...devices["Desktop Safari"] },
|
||||||
|
},
|
||||||
|
|
||||||
|
// {
|
||||||
|
// name: 'Mobile Chrome',
|
||||||
|
// use: { ...devices['Pixel 5'] },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: 'Mobile Safari',
|
||||||
|
// use: { ...devices['iPhone 12'] },
|
||||||
|
// },
|
||||||
|
|
||||||
|
/* Test against branded browsers. */
|
||||||
|
// {
|
||||||
|
// name: 'Microsoft Edge',
|
||||||
|
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: 'Google Chrome',
|
||||||
|
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
||||||
|
// },
|
||||||
|
],
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue