50 Playwright E2E tests across 13 spec files covering all routes and user flows (items CRUD, check-out/in, locations, labels, scanning, search/filter). Uses vitest as runner with playwright-core for browser automation (bun-compatible alternative to @playwright/test). Includes Gherkin .feature files as living documentation, test support infrastructure (IDB seeding, item factories, assertion helpers, layout measurement), and HANDOFF.md covering project state, deployment, and open design decisions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
121 lines
3.7 KiB
TypeScript
121 lines
3.7 KiB
TypeScript
/**
|
|
* @feature Dashboard
|
|
* @see e2e/features/pages/dashboard.feature
|
|
*/
|
|
import { describe, test, expect } from 'vitest';
|
|
import { setupBrowser } from '../../support/browser';
|
|
import { waitForAppReady, seedItems, clearItems } from '../../support/seed';
|
|
import { buildItem, buildCheckedOutItem, buildConsumableItem, resetCounter } from '../../support/item-factory';
|
|
import { assertMinTouchTarget, assertWithinViewport } from '../../support/layout';
|
|
import { expectVisible, expectText } from '../../support/expect';
|
|
|
|
const { getPage } = setupBrowser();
|
|
|
|
describe('Dashboard', () => {
|
|
test('empty state shows call-to-action', async () => {
|
|
const page = getPage();
|
|
await page.goto('/');
|
|
await waitForAppReady(page);
|
|
await clearItems(page);
|
|
await page.reload();
|
|
await waitForAppReady(page);
|
|
|
|
await expectVisible(page.getByText('No items yet'));
|
|
await expectVisible(page.getByText('Add First Item'));
|
|
await expectText(page.getByRole('heading', { level: 1 }), 'SolidHaus');
|
|
});
|
|
|
|
test('stats grid shows inventory summary', async () => {
|
|
const page = getPage();
|
|
resetCounter();
|
|
await page.goto('/');
|
|
await waitForAppReady(page);
|
|
|
|
const items = Array.from({ length: 5 }, (_, i) =>
|
|
buildItem({ name: `Item ${i + 1}`, category: i % 2 === 0 ? 'Electronics' : 'Kitchen' })
|
|
);
|
|
await seedItems(page, items);
|
|
await page.reload();
|
|
await waitForAppReady(page);
|
|
|
|
await expectVisible(page.getByText('Total Items'));
|
|
await expectVisible(page.getByText('Checked Out'));
|
|
await expectVisible(page.getByText('Overdue'));
|
|
await expectVisible(page.getByText('Low Stock'));
|
|
});
|
|
|
|
test('checked-out section appears when items are out', async () => {
|
|
const page = getPage();
|
|
await page.goto('/');
|
|
await waitForAppReady(page);
|
|
|
|
const item = buildCheckedOutItem({ name: 'Drill' });
|
|
await seedItems(page, [item]);
|
|
await page.reload();
|
|
await waitForAppReady(page);
|
|
|
|
await expectVisible(page.getByText('Drill').first());
|
|
});
|
|
|
|
test('low-stock section appears for depleted consumables', async () => {
|
|
const page = getPage();
|
|
await page.goto('/');
|
|
await waitForAppReady(page);
|
|
|
|
const item = buildConsumableItem({
|
|
name: 'Batteries',
|
|
currentQuantity: 2,
|
|
originalQuantity: 10,
|
|
lowThreshold: 5,
|
|
});
|
|
await seedItems(page, [item]);
|
|
await page.reload();
|
|
await waitForAppReady(page);
|
|
|
|
await expectVisible(page.getByText('Batteries').first());
|
|
});
|
|
|
|
test('recently updated items are shown', async () => {
|
|
const page = getPage();
|
|
resetCounter();
|
|
await page.goto('/');
|
|
await waitForAppReady(page);
|
|
|
|
const items = Array.from({ length: 3 }, (_, i) => buildItem({ name: `Item ${i + 1}` }));
|
|
await seedItems(page, items);
|
|
await page.reload();
|
|
await waitForAppReady(page);
|
|
|
|
await expectVisible(page.getByText('Recently Updated'));
|
|
});
|
|
|
|
test('navigation bar is visible and accessible', async () => {
|
|
const page = getPage();
|
|
await page.goto('/');
|
|
await waitForAppReady(page);
|
|
|
|
const navLinks = page.locator('nav a');
|
|
const count = await navLinks.count();
|
|
expect(count).toBe(5);
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
await assertMinTouchTarget(navLinks.nth(i));
|
|
}
|
|
await assertWithinViewport(page.locator('nav'), page);
|
|
});
|
|
|
|
test('dashboard screenshot', async () => {
|
|
const page = getPage();
|
|
resetCounter();
|
|
await page.goto('/');
|
|
await waitForAppReady(page);
|
|
|
|
const items = Array.from({ length: 3 }, (_, i) => buildItem({ name: `Item ${i + 1}` }));
|
|
await seedItems(page, items);
|
|
await page.reload();
|
|
await waitForAppReady(page);
|
|
|
|
const screenshot = await page.screenshot();
|
|
expect(screenshot).toBeInstanceOf(Buffer);
|
|
});
|
|
});
|