test: add authenticated admin browser workflow
Quality / test (push) Canceled after 0s

This commit is contained in:
2026-09-08 22:35:30 -04:00
parent d02718e6be
commit 4f71d3d8de
6 changed files with 85 additions and 2 deletions
+10
View File
@@ -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.
@@ -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();
});
+24
View File
@@ -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",
+4 -2
View File
@@ -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"
}
}
+12
View File
@@ -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']],
});
+4
View File
@@ -0,0 +1,4 @@
{
"status": "passed",
"failedTests": []
}