Files
Flight-Ops/e2e/admin.spec.ts
T
kamilkrzywda e24a5788c0
CI / build + unit + e2e (pull_request) Successful in 15m37s
v0.71.0: fix admin e2e — pass sessionId into page.evaluate
2026-09-24 09:56:05 +00:00

112 lines
5.6 KiB
TypeScript

import { expect, test } from '@playwright/test';
// v0.71.0 — Admin panel e2e: top-bar button → modal with login form; wrong credentials show an
// error; a session minted by the /api/test/admin-session hook (FLIGHT_TEST_HOOKS=1) authenticates
// WITHOUT the real admin password ever appearing in test code; then the generic DB grid is
// exercised end-to-end: table list → edit + save a cell (verified by an independent re-fetch) →
// add a row → delete it → logout back to the login form.
const SESSION_KEY = 'flight.session'; // must match components/AdminPanel.tsx
async function mintAdminSession(page: import('@playwright/test').Page): Promise<string> {
const res = await page.request.post('/api/test/admin-session');
expect(res.status(), 'admin-session hook should be enabled in the CI stack (FLIGHT_TEST_HOOKS=1)').toBe(200);
return ((await res.json()) as { sessionId: string }).sessionId;
}
test('admin panel: login form, wrong-creds error, session auth, DB grid CRUD, logout', async ({ page }) => {
await page.goto('/');
const adminBtn = page.getByTestId('admin-btn');
await expect(adminBtn).toBeVisible({ timeout: 30_000 });
// --- Open the modal: no stored session → login form.
await adminBtn.click();
const dialog = page.locator('[role="dialog"]');
await expect(dialog).toBeVisible();
await expect(page.getByTestId('admin-username')).toBeVisible();
await expect(page.getByTestId('admin-password')).toBeVisible();
// --- Wrong credentials → error line (the seeded admin's plaintext is never used here).
await page.getByTestId('admin-username').fill('kamilkrzywda');
await page.getByTestId('admin-password').fill('wrong-password-on-purpose');
await page.getByTestId('admin-login-submit').click();
await expect(page.getByTestId('admin-error')).toBeVisible({ timeout: 10_000 });
// --- Authenticate via the test hook + localStorage, then reopen the panel.
const sessionId = await mintAdminSession(page);
// Both values must be passed as arguments — the function runs in the browser, where Node-side
// variables are not in scope.
await page.evaluate(([key, sid]) => window.localStorage.setItem(key, sid), [SESSION_KEY, sessionId]);
await page.reload();
await expect(adminBtn).toBeVisible({ timeout: 30_000 });
await adminBtn.click();
// --- Authenticated admin → table list including packages and users.
const tables = page.getByTestId('admin-tables');
await expect(tables).toBeVisible({ timeout: 30_000 });
await expect(page.getByTestId('admin-table-packages')).toBeVisible();
await expect(page.getByTestId('admin-table-users')).toBeVisible();
// --- Open packages, edit one cell, Save; verify via an INDEPENDENT re-fetch of the same route.
await page.getByTestId('admin-table-packages').click();
const grid = page.getByTestId('admin-rows');
await expect(grid).toBeVisible({ timeout: 30_000 });
const rowidCell = page.getByTestId('admin-cell-0-__rowid');
const originalRowid = Number(await rowidCell.inputValue());
expect(Number.isInteger(originalRowid)).toBeTruthy();
const rewardCell = page.getByTestId('admin-cell-0-reward');
await rewardCell.fill('987654');
await page.getByTestId('admin-save-0').click();
// The grid re-fetches after save — the edited cell shows the new value…
await expect(rewardCell).toHaveValue('987654', { timeout: 15_000 });
// …and an independent request (not the grid's) confirms it persisted server-side.
const check = await page.request.get('/api/admin/tables/packages/rows?limit=500', {
headers: { 'X-Flight-Session': sessionId },
});
expect(check.status()).toBe(200);
const fetched = (await check.json()) as { rows: Array<Record<string, unknown>> };
const row = fetched.rows.find((r) => Number(r.__rowid) === originalRowid);
expect(row, 'edited packages row must still exist').toBeTruthy();
expect(Number(row?.reward)).toBe(987654);
// --- Add a row (users table — fewest required columns), then delete it again.
await page.getByTestId('admin-back').click();
await expect(tables).toBeVisible();
await page.getByTestId('admin-table-users').click();
const usersGrid = page.getByTestId('admin-rows');
await expect(usersGrid).toBeVisible({ timeout: 30_000 });
const existingRows = await page.getByTestId(/admin-save-\d+/).count();
await page.getByTestId('admin-add-row').click();
const n = existingRows; // the new empty row is appended last
await page.getByTestId(`admin-cell-${n}-id`).fill('user-e2e-admin-test');
await page.getByTestId(`admin-cell-${n}-username`).fill('e2e-admin-user');
await page.getByTestId(`admin-cell-${n}-password_hash`).fill('aa$bb'); // any string — dev-tool insert, not a real credential
await page.getByTestId(`admin-cell-${n}-created_at`).fill(String(Date.now()));
await page.getByTestId(`admin-save-${n}`).click();
// The inserted row is visible after the grid's post-save re-fetch.
const bodyRows = usersGrid.locator('tbody tr');
const findUserRow = async (): Promise<number> => {
const count = await bodyRows.count();
for (let i = 0; i < count; i++) {
if ((await page.getByTestId(`admin-cell-${i}-username`).inputValue()) === 'e2e-admin-user') return i;
}
return -1;
};
await expect.poll(findUserRow, { timeout: 15_000 }).toBeGreaterThanOrEqual(0);
const addedIdx = await findUserRow();
// Delete it again — gone after the post-delete re-fetch.
await page.getByTestId(`admin-delete-${addedIdx}`).click();
await expect.poll(findUserRow, { timeout: 15_000 }).toBe(-1);
// --- Logout returns to the login form.
await page.getByTestId('admin-logout').click();
await expect(page.getByTestId('admin-username')).toBeVisible({ timeout: 10_000 });
await expect(tables).toBeHidden();
});