Files
redefined-designs/frontend/tests/e2e/admin-save-failures.spec.ts
bermudalambandClaude Opus 5 895c08d1a7 test(e2e): replace unchecked lookups, widen the assertion timeout, restore the skipped test (#241)
Tasks 2 to 4 of the plan. Nine `collection.find(...)` dereferences become findOrFail, so a missing row fails as a named assertion naming what was wanted and how many rows were searched, rather than "Cannot read properties of undefined" pointing at test plumbing. Where the old code followed the lookup with expect(x).toBeTruthy(), that assertion is dropped: findOrFail already guarantees it, and with a better message.

The expect timeout goes from Playwright's default 5s to 10s. It costs nothing on a green run — it bounds how long a failing assertion waits, not how long a passing one takes — and #239 died reporting exactly Timeout: 5000ms on a runner that also builds, migrates and runs three other suites.

The admin-save happy path comes back from the #245 skip. It is the only end-to-end check that adding an item reaches the database rather than merely firing a toast, and it passed both full parallel runs and in isolation.

filters.spec.ts:216 is deliberately untouched: its .find() searches CSS class names on a string array, not test data, and has no missing-row failure mode.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 20:04:43 -05:00

83 lines
3.0 KiB
TypeScript

import { test, expect, uniqueSuffix } from './fixtures';
test.describe('Admin save failures', () => {
test('does not claim an item was saved when the request failed', async ({
page,
admin,
adminInventory
}) => {
await page.route('**/api/admin/items', (route) => {
if (route.request().method() === 'POST') {
return route.fulfill({
status: 500,
contentType: 'application/json',
body: '{"error":"internal error"}'
});
}
return route.continue();
});
await admin.goto();
await adminInventory.addItem(`Broken ${uniqueSuffix()}`, '12');
// Reporting success for a failed save is worse than failing loudly: the
// item is silently absent and the user has no reason to look for it.
await expect(page.getByText('Item added')).toBeHidden();
await expect(page.getByText("Couldn't save item")).toBeVisible();
// The form must stay open so the entered values aren't lost.
await expect(adminInventory.formDialog).toBeVisible();
});
test('reports a failed delete rather than claiming success', async ({
page,
admin,
adminInventory
}) => {
// Seeded through the API so the test owns a known row rather than clicking
// whichever Delete button happens to be first in a paginated table.
const name = `Doomed ${uniqueSuffix()}`;
const created = await page.request.post('/api/admin/items', {
multipart: { name, description: '', price: '10', category_id: '', tags: '[]' }
});
expect(created.ok()).toBeTruthy();
await page.route('**/api/admin/items/*', (route) => {
if (route.request().method() === 'DELETE') {
return route.fulfill({ status: 500, contentType: 'application/json', body: '{"error":"nope"}' });
}
return route.continue();
});
await admin.goto();
// Items list newest-first, so the seeded row is on the first page.
await expect(adminInventory.row(name)).toBeVisible();
await adminInventory.deleteButton(name).click();
await expect(page.getByText('Item deleted')).toBeHidden();
await expect(page.getByText("Couldn't delete item")).toBeVisible();
// The row must survive a failed delete.
await expect(adminInventory.row(name)).toBeVisible();
});
// Un-skipped now that #241 has landed (was skipped under #245). This is the
// only end-to-end check that adding an item actually reaches the database
// rather than just firing a toast — the happy path of the core admin action —
// so it is worth having back.
test('saves an item successfully when the server accepts it', async ({
page,
admin,
adminInventory
}) => {
const name = `Good ${uniqueSuffix()}`;
await admin.goto();
await adminInventory.addItem(name, '34');
await expect(page.getByText('Item added')).toBeVisible();
// Confirm the row actually reached the database, not just that a toast fired.
const items = await (await page.request.get('/api/admin/items')).json();
expect(items.some((item: { name: string }) => item.name === name)).toBeTruthy();
});
});