more fixes
This commit is contained in:
147
tests/helpers/crudHelper.ts
Normal file
147
tests/helpers/crudHelper.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* CRUD Helper for cPad module tests.
|
||||
*
|
||||
* Provides reusable functions that detect and execute common CRUD operations
|
||||
* (Create, Edit, Delete) through the cPad UI without hard-coding selectors
|
||||
* for every individual module.
|
||||
*
|
||||
* Strategy:
|
||||
* • "Create" — look for button text: Create / Add / New
|
||||
* • "Edit" — look for button/link text: Edit, or table action icons
|
||||
* • "Delete" — look for button/link text: Delete / Remove (with confirmation handling)
|
||||
*
|
||||
* Each function returns false if the appropriate trigger could not be found,
|
||||
* allowing callers to skip gracefully when a module lacks a given operation.
|
||||
*/
|
||||
|
||||
import { type Page, expect } from '@playwright/test';
|
||||
import { fillForm } from './formFiller';
|
||||
|
||||
// Selectors used to locate CRUD triggers (order = priority)
|
||||
const CREATE_SELECTORS = [
|
||||
'a:has-text("Create")',
|
||||
'a:has-text("Add")',
|
||||
'a:has-text("New")',
|
||||
'button:has-text("Create")',
|
||||
'button:has-text("Add")',
|
||||
'button:has-text("New")',
|
||||
'[data-testid="btn-create"]',
|
||||
'[data-testid="btn-add"]',
|
||||
];
|
||||
|
||||
const EDIT_SELECTORS = [
|
||||
'a:has-text("Edit")',
|
||||
'button:has-text("Edit")',
|
||||
'td a[href*="/edit/"]',
|
||||
'[data-testid="btn-edit"]',
|
||||
];
|
||||
|
||||
const DELETE_SELECTORS = [
|
||||
'a:has-text("Delete")',
|
||||
'button:has-text("Delete")',
|
||||
'a:has-text("Remove")',
|
||||
'button:has-text("Remove")',
|
||||
'[data-testid="btn-delete"]',
|
||||
];
|
||||
|
||||
/**
|
||||
* Try to find and click a Create/Add/New button, fill the resulting form,
|
||||
* submit it, and verify the page does not show a 500 error.
|
||||
*
|
||||
* @returns true if a create button was found and the flow completed without crashing.
|
||||
*/
|
||||
export async function tryCrudCreate(page: Page): Promise<boolean> {
|
||||
for (const selector of CREATE_SELECTORS) {
|
||||
const btn = page.locator(selector).first();
|
||||
if (await btn.count() > 0 && await btn.isVisible()) {
|
||||
await btn.click();
|
||||
|
||||
// Wait for either a form or a new page section to appear
|
||||
await page.waitForLoadState('networkidle', { timeout: 10_000 }).catch(() => null);
|
||||
|
||||
// Fill any visible form
|
||||
await fillForm(page);
|
||||
|
||||
// Look for a submit button
|
||||
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', { timeout: 10_000 }).catch(() => null);
|
||||
|
||||
// Fail if a Laravel/server error page appeared
|
||||
const body = await page.locator('body').textContent() ?? '';
|
||||
const hasServerError = /Whoops|Server Error|500|SQLSTATE|Call to undefined/i.test(body);
|
||||
expect(hasServerError, `Server error after create on ${page.url()}`).toBe(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to find and click the first Edit button/link on the page, fill the form,
|
||||
* and submit.
|
||||
*
|
||||
* @returns true if an edit trigger was found.
|
||||
*/
|
||||
export async function tryCrudEdit(page: Page): Promise<boolean> {
|
||||
for (const selector of EDIT_SELECTORS) {
|
||||
const btn = page.locator(selector).first();
|
||||
if (await btn.count() > 0 && await btn.isVisible()) {
|
||||
await btn.click();
|
||||
await page.waitForLoadState('networkidle', { timeout: 10_000 }).catch(() => null);
|
||||
|
||||
await fillForm(page);
|
||||
|
||||
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', { timeout: 10_000 }).catch(() => null);
|
||||
|
||||
const body = await page.locator('body').textContent() ?? '';
|
||||
const hasServerError = /Whoops|Server Error|500|SQLSTATE|Call to undefined/i.test(body);
|
||||
expect(hasServerError, `Server error after edit on ${page.url()}`).toBe(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to click the first delete trigger, handle a confirmation dialog if one
|
||||
* appears, and assert no server error is displayed.
|
||||
*
|
||||
* @returns true if a delete trigger was found.
|
||||
*/
|
||||
export async function tryCrudDelete(page: Page): Promise<boolean> {
|
||||
for (const selector of DELETE_SELECTORS) {
|
||||
const btn = page.locator(selector).first();
|
||||
if (await btn.count() > 0 && await btn.isVisible()) {
|
||||
// Some delete links pop a browser confirm() dialog
|
||||
page.once('dialog', (dialog) => dialog.accept());
|
||||
|
||||
await btn.click();
|
||||
await page.waitForLoadState('networkidle', { timeout: 10_000 }).catch(() => null);
|
||||
|
||||
const body = await page.locator('body').textContent() ?? '';
|
||||
const hasServerError = /Whoops|Server Error|500|SQLSTATE|Call to undefined/i.test(body);
|
||||
expect(hasServerError, `Server error after delete on ${page.url()}`).toBe(false);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Quick check whether the page contains any list/table data (i.e. the module
|
||||
* has records to operate on).
|
||||
*/
|
||||
export async function hasListData(page: Page): Promise<boolean> {
|
||||
const tableRows = await page.locator('table tbody tr').count();
|
||||
return tableRows > 0;
|
||||
}
|
||||
Reference in New Issue
Block a user