/** * 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 { 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 { 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 { 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 { const tableRows = await page.locator('table tbody tr').count(); return tableRows > 0; }