more fixes
This commit is contained in:
123
tests/cpad/modules/configuration.spec.ts
Normal file
123
tests/cpad/modules/configuration.spec.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* 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);
|
||||
});
|
||||
});
|
||||
94
tests/cpad/modules/languages.spec.ts
Normal file
94
tests/cpad/modules/languages.spec.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Languages module tests for cPad.
|
||||
*
|
||||
* Routes under /cp/language/{type} — type can be: app, system
|
||||
*
|
||||
* Coverage:
|
||||
* • Language list pages load without errors
|
||||
* • No console/server errors
|
||||
* • Add-language page loads
|
||||
* • (Optional) CRUD flow when list data is available
|
||||
*
|
||||
* Run:
|
||||
* npx playwright test tests/cpad/modules/languages.spec.ts --project=cpad
|
||||
*/
|
||||
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { CP_PATH, attachErrorListeners } from '../../helpers/auth';
|
||||
import { hasListData, tryCrudCreate } from '../../helpers/crudHelper';
|
||||
|
||||
const LANGUAGE_TYPES = ['app', 'system'] as const;
|
||||
|
||||
test.describe('cPad Languages Module', () => {
|
||||
for (const type of LANGUAGE_TYPES) {
|
||||
const basePath = `${CP_PATH}/language/${type}`;
|
||||
|
||||
test(`language list (${type}) loads without errors`, async ({ page }) => {
|
||||
const { consoleErrors, networkErrors } = attachErrorListeners(page);
|
||||
|
||||
await page.goto(basePath);
|
||||
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 on ${basePath}`).toBe(false);
|
||||
|
||||
expect(consoleErrors.length, `Console errors: ${consoleErrors.join(' | ')}`).toBe(0);
|
||||
expect(networkErrors.length, `HTTP 5xx: ${networkErrors.join(' | ')}`).toBe(0);
|
||||
});
|
||||
|
||||
test(`language add page (${type}) loads`, async ({ page }) => {
|
||||
const { consoleErrors, networkErrors } = attachErrorListeners(page);
|
||||
|
||||
await page.goto(`${basePath}/add`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
expect(page.url()).not.toContain('/login');
|
||||
await expect(page.locator('body')).toBeVisible();
|
||||
|
||||
expect(consoleErrors.length, `Console errors: ${consoleErrors.join(' | ')}`).toBe(0);
|
||||
expect(networkErrors.length, `HTTP 5xx: ${networkErrors.join(' | ')}`).toBe(0);
|
||||
});
|
||||
|
||||
test(`language list (${type}) shows table or empty state`, async ({ page }) => {
|
||||
await page.goto(basePath);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Either a data table OR a "no data" message must be present
|
||||
const tableCount = await page.locator('table').count();
|
||||
const emptyMsgCount = await page.locator(
|
||||
':has-text("No languages"), :has-text("No records"), :has-text("Empty")',
|
||||
).count();
|
||||
|
||||
expect(tableCount + emptyMsgCount, 'Should show a table or an empty-state message').toBeGreaterThan(0);
|
||||
});
|
||||
}
|
||||
|
||||
test('language list (app) — CRUD: add language form submission', async ({ page }) => {
|
||||
const { networkErrors } = attachErrorListeners(page);
|
||||
|
||||
await page.goto(`${CP_PATH}/language/app/add`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
if (page.url().includes('/login')) {
|
||||
test.skip(true, 'Skipped: not authenticated');
|
||||
return;
|
||||
}
|
||||
|
||||
// Try submitting the add form
|
||||
const didCreate = await tryCrudCreate(page);
|
||||
if (!didCreate) {
|
||||
// The add page itself is the form; submit directly
|
||||
const submit = page.locator('button[type=submit], input[type=submit]').first();
|
||||
if (await submit.count() > 0 && await submit.isVisible()) {
|
||||
await submit.click();
|
||||
await page.waitForLoadState('networkidle');
|
||||
}
|
||||
}
|
||||
|
||||
expect(networkErrors.length, `HTTP 5xx after form submit: ${networkErrors.join(' | ')}`).toBe(0);
|
||||
});
|
||||
});
|
||||
112
tests/cpad/modules/translations.spec.ts
Normal file
112
tests/cpad/modules/translations.spec.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* Translations module tests for cPad.
|
||||
*
|
||||
* Routes under /cp/translation/{file}
|
||||
* The most common translation file is "app".
|
||||
*
|
||||
* Coverage:
|
||||
* • Main translation index page loads
|
||||
* • Translation list page for "app" file loads
|
||||
* • Add translation entry page loads
|
||||
* • Translation grid page loads
|
||||
* • No console/server errors on any page
|
||||
* • (Optional) inline edit via CRUD helper
|
||||
*
|
||||
* Run:
|
||||
* npx playwright test tests/cpad/modules/translations.spec.ts --project=cpad
|
||||
*/
|
||||
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { CP_PATH, attachErrorListeners } from '../../helpers/auth';
|
||||
|
||||
/** Translation file slugs to probe — add more as needed */
|
||||
const TRANSLATION_FILES = ['app'] as const;
|
||||
|
||||
/** Reusable page-health assertion */
|
||||
async function assertPageHealthy(page: import('@playwright/test').Page, path: string) {
|
||||
const consoleErrors: string[] = [];
|
||||
const networkErrors: string[] = [];
|
||||
|
||||
page.on('console', (msg) => {
|
||||
if (msg.type() === 'error') consoleErrors.push(msg.text());
|
||||
});
|
||||
page.on('pageerror', (err) => consoleErrors.push(err.message));
|
||||
page.on('response', (res) => {
|
||||
if (res.status() >= 500) networkErrors.push(`HTTP ${res.status()}: ${res.url()}`);
|
||||
});
|
||||
|
||||
await page.goto(path);
|
||||
await page.waitForLoadState('networkidle', { timeout: 20_000 });
|
||||
|
||||
expect(page.url(), `${path} must not redirect to login`).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 ${path}`).toBe(false);
|
||||
|
||||
expect(consoleErrors.length, `Console errors at ${path}: ${consoleErrors.join(' | ')}`).toBe(0);
|
||||
expect(networkErrors.length, `HTTP 5xx at ${path}: ${networkErrors.join(' | ')}`).toBe(0);
|
||||
}
|
||||
|
||||
test.describe('cPad Translations Module', () => {
|
||||
for (const file of TRANSLATION_FILES) {
|
||||
const base = `${CP_PATH}/translation/${file}`;
|
||||
|
||||
test(`translation main page (${file}) loads`, async ({ page }) => {
|
||||
await assertPageHealthy(page, base);
|
||||
});
|
||||
|
||||
test(`translation list page (${file}) loads`, async ({ page }) => {
|
||||
await assertPageHealthy(page, `${base}/list`);
|
||||
});
|
||||
|
||||
test(`translation add page (${file}) loads`, async ({ page }) => {
|
||||
await assertPageHealthy(page, `${base}/add`);
|
||||
});
|
||||
|
||||
test(`translation grid page (${file}) loads`, async ({ page }) => {
|
||||
await assertPageHealthy(page, `${base}/grid`);
|
||||
});
|
||||
|
||||
test(`translation list (${file}) renders content`, async ({ page }) => {
|
||||
await page.goto(`${base}/list`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
if (page.url().includes('/login')) {
|
||||
test.skip(true, 'Not authenticated');
|
||||
return;
|
||||
}
|
||||
|
||||
// A table, a grid element, or an empty-state message should be present
|
||||
const tableCount = await page.locator('table, .grid, [class*="grid"]').count();
|
||||
const emptyMsgCount = await page.locator(
|
||||
':has-text("No translations"), :has-text("No records"), :has-text("Empty")',
|
||||
).count();
|
||||
const inputCount = await page.locator('input[type=text], textarea').count();
|
||||
|
||||
expect(
|
||||
tableCount + emptyMsgCount + inputCount,
|
||||
'Translation list should contain table, grid, empty state, or editable fields',
|
||||
).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test(`translation add page (${file}) contains a form`, async ({ page }) => {
|
||||
await page.goto(`${base}/add`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
if (page.url().includes('/login')) {
|
||||
test.skip(true, 'Not authenticated');
|
||||
return;
|
||||
}
|
||||
|
||||
const formCount = await page.locator('form').count();
|
||||
const inputCount = await page.locator('input:not([type=hidden]), textarea').count();
|
||||
|
||||
expect(
|
||||
formCount + inputCount,
|
||||
'Add translation page should contain a form or input fields',
|
||||
).toBeGreaterThan(0);
|
||||
});
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user