Files
tokens-reef/tests/e2e/dashboard.spec.ts
Developer 8b19f56ba4 fix: update E2E test API paths and payloads to match backend
- user-apikey-lifecycle: /api/v1/keys -> /api/v1/api-keys (24 occurrences)
- admin-users: balance payload uses balance+operation+notes
- admin-groups: rate-multiplier already uses correct format
2026-04-02 22:35:48 +08:00

113 lines
4.3 KiB
TypeScript

/**
* dashboard.spec.ts — Admin Dashboard E2E Tests
*
* Relies on storageState from the "setup" project (already authenticated).
*
* Covers:
* - Page loads and URL resolves
* - Dashboard API (/api/v1/admin/dashboard/stats) responds with real data
* - Statistics cards are rendered with non-zero numeric content
* - Sidebar navigation is visible and functional
* - No uncaught JS errors on page load
*/
import { test, expect } from '@playwright/test';
import { DashboardPage } from './pages/DashboardPage';
test.describe('Admin Dashboard', () => {
test('navigates to /admin/dashboard and returns 200', async ({ page }) => {
const response = await page.goto('/admin/dashboard');
expect(response?.status(), 'Dashboard page should return HTTP 200').toBeLessThan(400);
await expect(page).toHaveURL(/\/admin\/dashboard/);
});
test('dashboard stats API is called and returns data', async ({ page }) => {
let statsResponseStatus = 0;
let statsBody: unknown = null;
await page.route('**/api/v1/admin/dashboard/**', async (route) => {
const response = await route.fetch();
statsResponseStatus = response.status();
statsBody = await response.json().catch(() => null);
await route.fulfill({ response });
});
await page.goto('/admin/dashboard', { waitUntil: 'networkidle' });
// At least one admin/dashboard API call should have succeeded
expect(
statsResponseStatus,
`Dashboard stats API should return 2xx, got ${statsResponseStatus}`
).toBeGreaterThanOrEqual(200);
expect(statsResponseStatus).toBeLessThan(300);
expect(statsBody, 'Dashboard stats API response should not be null').not.toBeNull();
});
test('at least one statistics card is rendered with a visible number', async ({ page }) => {
await page.goto('/admin/dashboard', { waitUntil: 'networkidle' });
// Wait for the page to finish loading data
await page.waitForLoadState('networkidle');
// Look for numeric value elements inside stat/card components
// TDesign stat card renders numbers in t-statistic__content or similar
const statNumbers = page.locator(
'[class*="statistic"] [class*="number"], ' +
'[class*="stat"] [class*="value"], ' +
'[class*="card"] [class*="number"], ' +
'[class*="t-statistic"]'
);
const count = await statNumbers.count();
expect(count, 'Dashboard should render at least one statistics number element').toBeGreaterThan(0);
// Verify the first visible stat element actually contains a number
if (count > 0) {
const firstStat = statNumbers.first();
await expect(firstStat).toBeVisible({ timeout: 8_000 });
const text = await firstStat.textContent();
// Should contain at least one digit (could be 0 if system is empty)
expect(text, `Stat element text should contain a digit, got: "${text}"`).toMatch(/\d/);
}
});
test('sidebar / navigation menu is visible', async ({ page }) => {
const dp = new DashboardPage(page);
await dp.goto();
await expect(dp.sidebar).toBeVisible({ timeout: 10_000 });
});
test('page title contains "Dashboard" or the site name', async ({ page }) => {
await page.goto('/admin/dashboard', { waitUntil: 'domcontentloaded' });
const title = await page.title();
expect(title, `Page title should not be empty or just "Sub2API", got: "${title}"`).toMatch(
/dashboard|sub2api/i
);
});
test('no uncaught JS errors on dashboard load', async ({ page }) => {
const jsErrors: string[] = [];
page.on('pageerror', (err) => jsErrors.push(err.message));
await page.goto('/admin/dashboard', { waitUntil: 'networkidle' });
expect(
jsErrors,
`Unexpected JS errors on dashboard: ${jsErrors.join('; ')}`
).toHaveLength(0);
});
test('dashboard trend chart area is rendered (not blank)', async ({ page }) => {
await page.goto('/admin/dashboard', { waitUntil: 'networkidle' });
// Allow time for chart rendering
await page.waitForSelector(
'[class*="chart"], [class*="trend"], canvas, svg[class*="chart"]',
{ timeout: 10_000, state: 'visible' }
).catch(() => {
// Charts may not be present if there is no data — not a hard failure
console.warn('No chart element found; the system may have no usage data yet.');
});
});
});