Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,11 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Playwright
/playwright/.auth/
/playwright-report/
/test-results/
.env.test

# AI agents
.agents/
390 changes: 199 additions & 191 deletions app/blocks/inventory-management/add-item-modal.tsx

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions app/routes/inventory-management.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ export async function action({ request }: Route.ActionArgs) {
purchasePrice,
purchaseDate: new Date(purchaseDate),
condition,
colorway,
color: colorway,
notes,
status: "IN_STOCK",
},
Expand All @@ -230,7 +230,7 @@ export async function action({ request }: Route.ActionArgs) {
purchasePrice,
purchaseDate: new Date(purchaseDate),
condition,
colorway,
color: colorway,
notes,
},
});
Expand Down
23 changes: 23 additions & 0 deletions e2e/auth.setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { test as setup, expect } from '@playwright/test';
const authFile = 'playwright/.auth/user.json';

setup('authenticate', async ({ page }) => {
// Use environment variables for credentials, falling back to the demo user for local e2e dev
const username = process.env.E2E_USERNAME || 'demo@fliptrack.app';
const password = process.env.E2E_PASSWORD || 'password123';

await page.goto('/auth/login');

// Fill in the login form
// Using generic selectors that should match typical login forms if exact labels are unknown
// But wait, earlier I saw the button was 'text=Sign in to your account'
await page.locator('input[name="email"]').fill(username);
await page.locator('input[name="password"]').fill(password);
await page.locator('button', { hasText: /^Sign In$/ }).click();

// Wait for navigation to the dashboard
await page.waitForURL('**/app/dashboard*');

// Save storage state to a file
await page.context().storageState({ path: authFile });
});
43 changes: 43 additions & 0 deletions e2e/inventory.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { test, expect } from '@playwright/test';

test.describe('Inventory Management', () => {
test('can create a new inventory item', async ({ page }) => {
// Generate a unique item name to avoid collisions with existing data
const uniqueId = Date.now();
const itemName = `E2E Test Sneaker ${uniqueId}`;
const itemSku = `E2E-${uniqueId}`;

// Navigate to inventory page
await page.goto('/app/inventory');
await expect(page.getByRole('heading', { name: 'Inventory' })).toBeVisible();

// Open the Add Item modal via the header button (use .first() to distinguish from the always-mounted form submit button)
await page.getByRole('button', { name: 'Add Item' }).first().click();

// Verify modal opened — the modal title "Add Inventory Item" should be visible
await expect(page.getByText('Add Inventory Item')).toBeVisible();

// ── Step 0: Basic Info ──
// Labels lack for/id pairing, so we target inputs by their name attribute.
await page.locator('input[name="sku"]').fill(itemSku);
await page.locator('input[name="name"]').fill(itemName);
await page.locator('input[name="brand"]').fill('TestBrand');
await page.locator('input[name="size"]').fill('10');
await page.getByRole('button', { name: 'Next' }).click();

// ── Step 1: Purchase Details ──
await page.locator('input[name="purchasePrice"]').fill('150');
await page.locator('input[name="purchaseDate"]').fill('2025-01-15');
await page.getByRole('button', { name: 'Next' }).click();

// ── Step 2: Marketplace (optional fields — skip) ──
// Submit the form. On step 2, the submit button reads "Add Item".
await page.getByTestId('submit-add-item').click();

// Verify success toast appears (sonner toast)
await expect(page.getByText('Item added successfully')).toBeVisible();

// Verify the newly created item appears in the inventory table as a link
await expect(page.getByRole('link', { name: itemName })).toBeVisible();
});
});
11 changes: 11 additions & 0 deletions e2e/smoke.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { test, expect } from '@playwright/test';

test('application loads and displays dashboard for authenticated user', async ({ page }) => {
await page.goto('/app/dashboard');

// Verify we are not redirected to login
await expect(page).toHaveURL(/.*\/app\/dashboard/);

// Verify dashboard content is visible
await expect(page.getByRole('heading', { name: 'Dashboard', exact: true })).toBeVisible();
});
133 changes: 111 additions & 22 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"build": "prisma generate && react-router build",
"dev": "react-router dev",
"start": "react-router-serve ./build/server/index.js",
"test:e2e": "playwright test",
"typecheck": "react-router typegen && tsc",
"postinstall": "prisma generate"
},
Expand Down Expand Up @@ -34,6 +35,7 @@
"zod": "^4.4.3"
},
"devDependencies": {
"@playwright/test": "^1.61.1",
"@react-router/dev": "^7.16.0",
"@types/node": "^24.12.4",
"@types/react": "^19.2.16",
Expand Down
35 changes: 35 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { defineConfig, devices } from '@playwright/test';
import dotenv from 'dotenv';

// Read from .env.test
dotenv.config({ path: '.env.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://localhost:3000',
trace: 'on-first-retry',
},
projects: [
{ name: 'setup', testMatch: /.*\.setup\.ts/ },
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
storageState: 'playwright/.auth/user.json',
},
dependencies: ['setup'],
},
],
webServer: {
command: 'npm run build && npm run start',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
timeout: 120 * 1000,
},
});
Loading
Loading