/** * Configuration module tests for cPad. * * Routes: * /cp/configuration — main configuration overview (alias) * /cp/config — canonical config route * * Coverage: * • Both config URLs load without errors * • No console/server errors * • Configuration form is present and contains input elements * • Smart form filler can fill the form without crashing * • Tab-based navigation within config (if applicable) works * * Run: * npx playwright test tests/cpad/modules/configuration.spec.ts --project=cpad */ import { test, expect } from '@playwright/test'; import { CP_PATH, attachErrorListeners } from '../../helpers/auth'; import { fillForm, hasForm } from '../../helpers/formFiller'; const CONFIG_URLS = [ `${CP_PATH}/configuration`, `${CP_PATH}/config`, ] as const; test.describe('cPad Configuration Module', () => { for (const configUrl of CONFIG_URLS) { test(`config page (${configUrl}) loads without errors`, async ({ page }) => { const { consoleErrors, networkErrors } = attachErrorListeners(page); await page.goto(configUrl); await page.waitForLoadState('networkidle'); expect(page.url()).not.toContain('/login'); await expect(page.locator('body')).toBeVisible(); const bodyText = await page.locator('body').textContent() ?? ''; const hasError = /Whoops|Server Error|SQLSTATE|Call to undefined/.test(bodyText); expect(hasError, `Server error at ${configUrl}`).toBe(false); expect(consoleErrors.length, `Console errors: ${consoleErrors.join(' | ')}`).toBe(0); expect(networkErrors.length, `HTTP 5xx: ${networkErrors.join(' | ')}`).toBe(0); }); } test('configuration page (/cp/config) contains a form or settings inputs', async ({ page }) => { await page.goto(`${CP_PATH}/config`); await page.waitForLoadState('networkidle'); if (page.url().includes('/login')) { test.skip(true, 'Not authenticated'); return; } const pageHasForm = await hasForm(page); expect(pageHasForm, '/cp/config should contain a form or input fields').toBe(true); }); test('smart form filler can fill configuration form without errors', async ({ page }) => { const { networkErrors } = attachErrorListeners(page); await page.goto(`${CP_PATH}/config`); await page.waitForLoadState('networkidle'); if (page.url().includes('/login')) { test.skip(true, 'Not authenticated'); return; } // Fill the form — should not throw await fillForm(page); // No 5xx errors triggered by the fill operations expect(networkErrors.length, `HTTP 5xx during form fill: ${networkErrors.join(' | ')}`).toBe(0); }); test('configuration tab navigation works (if tabs present)', async ({ page }) => { const { consoleErrors, networkErrors } = attachErrorListeners(page); await page.goto(`${CP_PATH}/config`); await page.waitForLoadState('networkidle'); if (page.url().includes('/login')) { test.skip(true, 'Not authenticated'); return; } // Look for tab buttons (Bootstrap / AdminLTE tabs) const tabSelectors = [ '[role="tab"]', '.nav-tabs .nav-link', '.nav-pills .nav-link', 'a[data-toggle="tab"]', 'a[data-bs-toggle="tab"]', ]; for (const sel of tabSelectors) { const tabs = page.locator(sel); const count = await tabs.count(); if (count > 1) { // Click each tab and verify no server errors for (let i = 0; i < Math.min(count, 8); i++) { const tab = tabs.nth(i); if (await tab.isVisible()) { await tab.click(); await page.waitForLoadState('domcontentloaded').catch(() => null); const bodyText = await page.locator('body').textContent() ?? ''; const hasError = /Whoops|Server Error|SQLSTATE/.test(bodyText); expect(hasError, `Error after clicking tab ${i} at ${sel}`).toBe(false); } } break; // found tabs, done } } expect(consoleErrors.length, `Console errors: ${consoleErrors.join(' | ')}`).toBe(0); expect(networkErrors.length, `HTTP 5xx: ${networkErrors.join(' | ')}`).toBe(0); }); });