From 4f71d3d8de3250253dada0c8a436db3374c50331 Mon Sep 17 00:00:00 2001 From: FragginWagon Date: Tue, 8 Sep 2026 22:35:30 -0400 Subject: [PATCH] test: add authenticated admin browser workflow --- README.md | 10 ++++++ .../tests/e2e/admin-catalog-workflow.spec.js | 31 +++++++++++++++++++ package-lock.json | 24 ++++++++++++++ package.json | 6 ++-- playwright.config.js | 12 +++++++ test-results/.last-run.json | 4 +++ 6 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 apps/web/tests/e2e/admin-catalog-workflow.spec.js create mode 100644 playwright.config.js create mode 100644 test-results/.last-run.json diff --git a/README.md b/README.md index e4ab75c..f682e93 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,16 @@ npm run seed:demo The seed is non-destructive and exits without changes when the demo catalog already exists. +## Browser Workflow Tests + +With `npm run dev` running, configured local admin credentials, and demo lookup data available, run the authenticated browser workflow with: + +```sh +npm run test:e2e +``` + +The workflow covers login, admin overview loading, product creation, physical inventory creation, and navigation to image management. It skips when `INITIAL_ADMIN_EMAIL` or `INITIAL_ADMIN_PASSWORD` is not configured. + ## Initial Administrator After the development database is migrated, set `INITIAL_ADMIN_EMAIL`, `INITIAL_ADMIN_PASSWORD`, and `INITIAL_SELLER_NAME` in your local `.env`, then run `npm run provision:admin` once. The command creates the only initial administrator and linked Seller; it refuses to run after any User exists. diff --git a/apps/web/tests/e2e/admin-catalog-workflow.spec.js b/apps/web/tests/e2e/admin-catalog-workflow.spec.js new file mode 100644 index 0000000..1166ec4 --- /dev/null +++ b/apps/web/tests/e2e/admin-catalog-workflow.spec.js @@ -0,0 +1,31 @@ +import { expect, test } from '@playwright/test'; + +test('signs in and creates a product and physical copy from the admin workspace', async ({ page }) => { + const email = process.env.INITIAL_ADMIN_EMAIL; + const password = process.env.INITIAL_ADMIN_PASSWORD; + + test.skip(!email || !password, 'Set INITIAL_ADMIN_EMAIL and INITIAL_ADMIN_PASSWORD for the local authenticated workflow.'); + + await page.goto('/login'); + await page.getByLabel('Email').fill(email); + await page.getByLabel('Password').fill(password); + await page.getByRole('button', { name: 'Sign in' }).click(); + await expect(page).toHaveURL(/\/admin$/); + await expect(page.getByRole('heading', { name: 'Collector workspace' })).toBeVisible(); + + const category = page.locator('#product-category option').nth(1); + await expect(category).toBeAttached(); + await page.getByLabel('Name').fill(`Browser test collectible ${Date.now()}`); + await page.getByLabel('Category').selectOption({ index: 1 }); + await page.getByRole('button', { name: 'Create product' }).click(); + + await expect(page.locator('#inventory-product option').nth(1)).toBeAttached(); + await page.getByLabel('Product').selectOption({ index: 1 }); + await page.getByLabel('Condition').selectOption({ index: 1 }); + await page.getByRole('button', { name: 'Create copy' }).click(); + + await expect(page.getByText('Manage images')).toBeVisible(); + await page.getByText('Manage images').last().click(); + await expect(page).toHaveURL(/\/admin\/inventory\/[^/]+\/images$/); + await expect(page.getByRole('heading', { name: 'Manage images' })).toBeVisible(); +}); diff --git a/package-lock.json b/package-lock.json index 95c9bd0..384ac96 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "packages/*" ], "devDependencies": { + "@playwright/test": "^1.63.0", "@storybook/addon-a11y": "^8.6.14", "@storybook/addon-essentials": "^8.6.14", "@storybook/test": "^8.6.14", @@ -610,6 +611,9 @@ } } }, + "apps/worker": { + "name": "@pokemon-inventory/worker" + }, "node_modules/@adobe/css-tools": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.5.0.tgz", @@ -4014,6 +4018,22 @@ "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", "license": "MIT" }, + "node_modules/@playwright/test": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.63.0.tgz", + "integrity": "sha512-oxMK4vllB9RK5NQ2l1pq1IfOf2AvnEuj/vYGDj0H2nMtmtZpKtCwt/l00GEO6xjGfpBNAvjovvYdCm50dRQkpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.63.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/@pokemon-inventory/api": { "resolved": "apps/api", "link": true @@ -4030,6 +4050,10 @@ "resolved": "apps/web", "link": true }, + "node_modules/@pokemon-inventory/worker": { + "resolved": "apps/worker", + "link": true + }, "node_modules/@redis/bloom": { "version": "5.12.1", "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-5.12.1.tgz", diff --git a/package.json b/package.json index b353fed..f6b6af0 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "storybook": "npm run storybook --workspace=@pokemon-inventory/web", "build-storybook": "npm run build-storybook --workspace=@pokemon-inventory/web", "test-storybook": "concurrently --kill-others --success first \"npm run storybook\" \"wait-on http://127.0.0.1:6006 && npm run test-storybook --workspace=@pokemon-inventory/web\"", + "test:e2e": "playwright test", "provision:admin": "npm run provision:admin --workspace=@pokemon-inventory/api", "seed:demo": "npm run seed:demo --workspace=@pokemon-inventory/api", "test:application": "npm run test:application --workspace=@pokemon-inventory/api", @@ -28,6 +29,7 @@ "test": "npm run test:domain && npm run test:application && npm run test:http" }, "devDependencies": { + "@playwright/test": "^1.63.0", "@storybook/addon-a11y": "^8.6.14", "@storybook/addon-essentials": "^8.6.14", "@storybook/test": "^8.6.14", @@ -36,7 +38,7 @@ "concurrently": "^10.0.5", "dotenv": "^16.6.1", "drizzle-kit": "^0.31.8", - "storybook": "^8.6.14" - ,"wait-on": "^8.0.3" + "storybook": "^8.6.14", + "wait-on": "^8.0.3" } } diff --git a/playwright.config.js b/playwright.config.js new file mode 100644 index 0000000..a11e262 --- /dev/null +++ b/playwright.config.js @@ -0,0 +1,12 @@ +import { defineConfig } from '@playwright/test'; + +export default defineConfig({ + testDir: './apps/web/tests/e2e', + timeout: 30_000, + use: { + baseURL: process.env.E2E_BASE_URL ?? 'http://127.0.0.1:5173', + trace: 'on-first-retry', + screenshot: 'only-on-failure', + }, + reporter: [['list']], +}); diff --git a/test-results/.last-run.json b/test-results/.last-run.json new file mode 100644 index 0000000..cbcc1fb --- /dev/null +++ b/test-results/.last-run.json @@ -0,0 +1,4 @@ +{ + "status": "passed", + "failedTests": [] +} \ No newline at end of file