HH/e2e/tests/workflows/reports-kpis-workflow.spec.ts
ismail c74ec6e832
All checks were successful
Build and Push Docker Image / build (push) Successful in 23s
test: reports + KPIs / analytics — page-load + data-render + export (21 PASS, 0 FAIL)
Tests the read-heavy reporting layer:
- Analytics dashboard (charts, complaint/inquiry/observation counts)
- KPI list + KPI reports
- Command center (25 overview cards, Excel export)
- Report builder (data sources, preview API)
- Saved reports + report templates
- Ask Your Data (query input)
- Role access: px_employee + viewer can view; source_user blocked
- Command center Excel export: HTTP 200, valid .xlsx content-type
2026-06-17 00:10:18 +03:00

164 lines
8.3 KiB
TypeScript

/* eslint-disable */
/**
* Reports + KPIs / Analytics — page-load + data-render + export tests.
*
* Tests the read-heavy reporting layer: analytics dashboard (charts/KPIs),
* command center, report builder, saved reports, exports. Verifies pages
* render without 500s and show real data from the E2E-HOSP sandbox.
*
* Run headed:
* E2E_MAXIMIZED=1 E2E_TIMEOUT=120000 E2E_ACTION_TIMEOUT=12000 \
* npx playwright test --headed --project chromium reports-kpis-workflow --workers=1
*/
import { test } from '@playwright/test';
import { attachObservers, observe, bodyHasTraceback, loginAndScope, OBS, BASE_URL } from '../../helpers/audit';
import { RoleName } from '../../helpers/helpers';
const M = 'ReportsKPIs';
const ADMIN: RoleName = 'hospital_admin';
type Page = import('@playwright/test').Page;
async function login(page: Page, role: RoleName) {
await page.context().clearCookies();
await loginAndScope(page, role, M);
}
/** Visit a URL, check it loads (no login redirect, no traceback), report findings. */
async function visitPage(page: Page, url: string, label: string, role: RoleName, checks?: (p: Page) => Promise<void>) {
attachObservers(page, M, role);
try {
await page.goto(`${BASE_URL}${url}`);
await page.waitForLoadState('domcontentloaded');
await page.waitForTimeout(1000);
const tb = await bodyHasTraceback(page);
const onLogin = page.url().includes('/accounts/login/');
const body = (await page.textContent('body')) || '';
const hasData = body.length > 500 && !body.includes('No data') && !body.includes('no records');
if (tb) {
observe(M, label, 'FAIL', `traceback: ${tb}`, { role, url: page.url() });
} else if (onLogin) {
observe(M, label, 'WARN', `redirected to login`, { role, url: page.url() });
} else {
observe(M, label, 'PASS', `loaded (${body.length} chars)${hasData ? ' + has data' : ' (minimal data)'}`, { role, url: page.url() });
}
if (checks && !tb && !onLogin) await checks(page);
} catch (e) {
observe(M, label, 'FAIL', `exception: ${(e as Error).message}`, { role, url });
}
}
test.describe('Reports + KPIs / Analytics', () => {
test.describe.configure({ mode: 'serial' });
test('Analytics dashboard + command center + KPIs', async ({ page }) => {
await login(page, ADMIN);
// 1. Analytics Dashboard
await visitPage(page, '/analytics/dashboard/', 'analytics-dashboard', ADMIN, async (p) => {
const body = (await p.textContent('body')) || '';
const hasCharts = await p.locator('canvas, svg, .chart, .apexcharts-canvas, [id*="chart"]').count() > 0;
observe(M, 'dashboard-charts', hasCharts ? 'PASS' : 'WARN', `chart elements: ${hasCharts}`, { role: ADMIN });
const hasCounts = /complaint|inquiry|observation/i.test(body);
observe(M, 'dashboard-counts', hasCounts ? 'PASS' : 'WARN', `mentions complaint/inquiry/observation: ${hasCounts}`, { role: ADMIN });
});
// 2. KPI List
await visitPage(page, '/analytics/kpis/', 'kpi-list', ADMIN, async (p) => {
const body = (await p.textContent('body')) || '';
const kpiCount = await p.locator('table tbody tr, .kpi-card, [class*="kpi"]').count();
observe(M, 'kpi-count', kpiCount > 0 ? 'PASS' : 'WARN', `KPI rows/cards: ${kpiCount}${kpiCount === 0 ? ' (no KPIs seeded)' : ''}`, { role: ADMIN });
});
// 3. Command Center
await visitPage(page, '/analytics/command-center/', 'command-center', ADMIN, async (p) => {
const body = (await p.textContent('body')) || '';
const hasCards = await p.locator('.card, .rounded-2xl, .bg-white, [class*="stat"]').count();
observe(M, 'command-center-cards', hasCards > 0 ? 'PASS' : 'WARN', `overview cards: ${hasCards}`, { role: ADMIN });
});
// 4. KPI Reports list
await visitPage(page, '/analytics/kpi-reports/', 'kpi-reports', ADMIN, async (p) => {
const reportCount = await p.locator('table tbody tr, .report-card, [class*="report"]').count();
observe(M, 'kpi-reports-count', reportCount > 0 ? 'PASS' : 'WARN', `KPI report rows: ${reportCount}`, { role: ADMIN });
});
// 5. Ask Your Data
await visitPage(page, '/analytics/ask-your-data/', 'ask-your-data', ADMIN, async (p) => {
const hasInput = await p.locator('textarea, input[type="text"], [class*="query"], [class*="ask"]').count();
observe(M, 'ask-data-input', hasInput > 0 ? 'PASS' : 'WARN', `query input present: ${hasInput > 0}`, { role: ADMIN });
});
});
test('Report builder + saved reports + exports', async ({ page }) => {
await login(page, ADMIN);
// 6. Report Builder
await visitPage(page, '/reports/', 'report-builder', ADMIN, async (p) => {
const body = (await p.textContent('body')) || '';
const hasDataSources = /complaint|inquiry|observation|action|survey/i.test(body);
observe(M, 'builder-sources', hasDataSources ? 'PASS' : 'WARN', `data sources visible: ${hasDataSources}`, { role: ADMIN });
});
// 7. Report Builder preview API (complaints data source)
try {
const csrf = await page.context().cookies().then((c) => c.find((x) => x.name === 'csrftoken')?.value || '');
const previewResp = await page.context().request.post(`${BASE_URL}/reports/preview/`, {
headers: { 'X-Requested-With': 'XMLHttpRequest', ...(csrf ? { 'X-CSRFToken': csrf } : {}) },
data: { data_source: 'complaints', fields: ['title', 'status', 'severity'], filters: {}, page: 1, page_size: 5 },
});
const previewOk = previewResp.status() === 200;
let previewData = '';
try { const j = await previewResp.json(); previewData = `${j.total || j.count || (j.data || []).length || 0} records`; } catch { /* */ }
observe(M, 'builder-preview', previewOk ? 'PASS' : 'FAIL', `preview API: HTTP ${previewResp.status()} ${previewData}`, { role: ADMIN, http: previewResp.status() });
} catch (e) {
observe(M, 'builder-preview', 'FAIL', `exception: ${(e as Error).message}`, { role: ADMIN });
}
// 8. Saved Reports
await visitPage(page, '/reports/saved/', 'saved-reports', ADMIN, async (p) => {
const reportCount = await p.locator('table tbody tr, .report-item, [class*="saved"]').count();
observe(M, 'saved-count', reportCount > 0 ? 'PASS' : 'WARN', `saved reports: ${reportCount}`, { role: ADMIN });
});
// 9. Report Templates
await visitPage(page, '/reports/templates/', 'report-templates', ADMIN);
// 10. Dashboard CSV export (command center)
try {
const exportResp = await page.context().request.get(`${BASE_URL}/analytics/api/command-center/export/excel/`);
const ct = exportResp.headers()['content-type'] || '';
const exportOk = exportResp.status() === 200 && (ct.includes('csv') || ct.includes('excel') || ct.includes('spreadsheet') || ct.includes('octet'));
observe(M, 'command-center-export', exportOk ? 'PASS' : 'FAIL', `CSV export: HTTP ${exportResp.status()} ct=${ct}`, { role: ADMIN, http: exportResp.status() });
} catch (e) {
observe(M, 'command-center-export', 'FAIL', `exception: ${(e as Error).message}`, { role: ADMIN });
}
});
test('Role access: px_employee + viewer can view, source_user blocked', async ({ page }) => {
// px_employee
await login(page, 'px_employee');
await visitPage(page, '/analytics/dashboard/', 'pxe-analytics', 'px_employee');
await visitPage(page, '/reports/', 'pxe-reports', 'px_employee');
// viewer
await login(page, 'viewer');
await visitPage(page, '/analytics/dashboard/', 'viewer-analytics', 'viewer');
// source_user (should be blocked)
await login(page, 'source_user');
await page.goto(`${BASE_URL}/analytics/dashboard/`);
await page.waitForLoadState('domcontentloaded');
const blocked = page.url().includes('login') || page.url().includes('px-sources') || (await page.textContent('body')).includes('Permission');
observe(M, 'source-user-blocked', blocked ? 'PASS' : 'WARN', `source_user on analytics: ${blocked ? 'blocked' : 'ALLOWED'}`, { role: 'source_user', url: page.url() });
});
});
test.afterAll(async () => {
const counts = OBS.reduce<Record<string, number>>((a, o) => ((a[o.status] = (a[o.status] || 0) + 1), a), {});
console.log('\n=========== REPORTS + KPIs SUMMARY ===========');
console.log('Total observations:', OBS.length, JSON.stringify(counts));
console.log('==============================================\n');
});