Files
jdescopingtool/TestScripts/playwright/helpers/validation.helper.ts
T
Joseph Doherty ee044d03e0 feat: add health check endpoint, file upload result handling, and Playwright E2E tests
- Add /health endpoint with anonymous access for monitoring
- Add FileUploadResult<T> model and PostMultipartForFileResultAsync for proper upload response handling
- Add ApiResult.Success() factory method for interface types
- Refactor Login.razor for cleaner code
- Add comprehensive Playwright E2E test suite with fixtures and helpers
2026-01-30 07:12:20 -05:00

188 lines
7.4 KiB
TypeScript

import { Page, expect, Locator } from '@playwright/test';
/**
* Check if a form field has a validation error
* @param page - Playwright page object
* @param fieldName - The name attribute of the field
* @returns true if field has validation error styling
*/
export async function fieldHasError(page: Page, fieldName: string): Promise<boolean> {
const field = page.locator(`[name="${fieldName}"]`);
const classes = await field.getAttribute('class') ?? '';
// Radzen validation error class
return classes.includes('rz-state-invalid') || classes.includes('invalid');
}
/**
* Check if the validation summary has any errors
* @param page - Playwright page object
* @returns true if validation errors are present
*/
export async function hasValidationErrors(page: Page): Promise<boolean> {
// Check for Radzen error notifications (Validation Error notifications)
const errorNotification = page.locator('.rz-notification-error:has-text("Validation Error")');
if (await errorNotification.isVisible({ timeout: 1000 }).catch(() => false)) {
return true;
}
// Check for Radzen validation summary
const validationSummary = page.locator('.rz-validation-summary');
if (await validationSummary.isVisible({ timeout: 1000 }).catch(() => false)) {
const errors = validationSummary.locator('.validation-message, .rz-message');
const count = await errors.count();
return count > 0;
}
// Check for standard validation messages
const validationMessages = page.locator('.validation-message');
return (await validationMessages.count()) > 0;
}
/**
* Get all validation error messages
* @param page - Playwright page object
* @returns Array of error message texts
*/
export async function getValidationErrors(page: Page): Promise<string[]> {
const errors: string[] = [];
// Get errors from notification toasts (Validation Error notifications)
const notificationErrors = page.locator('.rz-notification-error');
const notificationCount = await notificationErrors.count();
for (let i = 0; i < notificationCount; i++) {
const notification = notificationErrors.nth(i);
// Check if it's a validation error notification
const title = await notification.locator('div').first().textContent() ?? '';
if (title.includes('Validation Error')) {
// Get the message (second div in the notification)
const message = await notification.locator('div').nth(1).textContent();
if (message) errors.push(message.trim());
}
}
// Get errors from validation summary
const summaryErrors = page.locator('.rz-validation-summary .validation-message, .rz-validation-summary .rz-message');
const summaryCount = await summaryErrors.count();
for (let i = 0; i < summaryCount; i++) {
const text = await summaryErrors.nth(i).textContent();
if (text && !errors.includes(text.trim())) errors.push(text.trim());
}
// Get inline validation messages
const inlineErrors = page.locator('.field-validation-error, .validation-message');
const inlineCount = await inlineErrors.count();
for (let i = 0; i < inlineCount; i++) {
const text = await inlineErrors.nth(i).textContent();
if (text && !errors.includes(text.trim())) {
errors.push(text.trim());
}
}
return errors;
}
/**
* Assert that a specific validation error message is present
* @param page - Playwright page object
* @param expectedMessage - The expected error message (partial match)
*/
export async function assertValidationError(page: Page, expectedMessage: string): Promise<void> {
const errors = await getValidationErrors(page);
const found = errors.some(e => e.toLowerCase().includes(expectedMessage.toLowerCase()));
expect(found, `Expected validation error containing "${expectedMessage}" but found: ${errors.join(', ')}`).toBe(true);
}
/**
* Assert no validation errors are present
* @param page - Playwright page object
*/
export async function assertNoValidationErrors(page: Page): Promise<void> {
const hasErrors = await hasValidationErrors(page);
if (hasErrors) {
const errors = await getValidationErrors(page);
expect(hasErrors, `Expected no validation errors but found: ${errors.join(', ')}`).toBe(false);
}
}
/**
* Check if a specific form field is required and shows error when empty
* @param page - Playwright page object
* @param fieldLocator - Locator for the field
* @returns true if field shows required validation
*/
export async function isFieldRequired(page: Page, fieldLocator: Locator): Promise<boolean> {
// Check for required attribute
const required = await fieldLocator.getAttribute('required');
if (required !== null) return true;
// Check for aria-required attribute
const ariaRequired = await fieldLocator.getAttribute('aria-required');
return ariaRequired === 'true';
}
/**
* Check if a panel shows a validation message for missing required items
* @param page - Playwright page object
* @param panelHeader - The header text of the panel
* @returns true if panel has validation error
*/
export async function panelHasValidationError(page: Page, panelHeader: string): Promise<boolean> {
const panel = page.locator(`.rz-card:has-text("${panelHeader}")`);
const validationMessage = panel.locator('.validation-message, .rz-message-error, .text-danger');
return await validationMessage.isVisible({ timeout: 1000 }).catch(() => false);
}
/**
* Wait for validation to complete (after form submission attempt)
* @param page - Playwright page object
* @param timeout - Maximum time to wait (default: 2000ms)
*/
export async function waitForValidation(page: Page, timeout: number = 2000): Promise<void> {
await page.waitForTimeout(timeout);
}
/**
* Check if the Submit button is enabled (form is valid)
* @param page - Playwright page object
* @returns true if submit button is enabled
*/
export async function isSubmitEnabled(page: Page): Promise<boolean> {
const submitButton = page.locator('button:has-text("Submit Search")');
return !(await submitButton.isDisabled());
}
/**
* Attempt to submit and expect validation failure
* @param page - Playwright page object
* @param expectedError - Expected error message (partial match)
*/
export async function submitAndExpectError(page: Page, expectedError?: string): Promise<void> {
await page.locator('button:has-text("Submit Search")').click();
await waitForValidation(page);
expect(await hasValidationErrors(page), 'Expected validation errors after submit').toBe(true);
if (expectedError) {
await assertValidationError(page, expectedError);
}
}
/**
* Validation error message constants for common scenarios
*/
export const ValidationMessages = {
SEARCH_NAME_REQUIRED: 'Search name is required',
SEARCH_TYPE_REQUIRED: 'Search type is required',
MIN_DATE_REQUIRED: 'Minimum date is required',
MAX_DATE_REQUIRED: 'Maximum date is required',
INVALID_DATE_RANGE: 'Minimum date must be before or equal to maximum date',
WORK_ORDER_REQUIRED: 'At least one work order is required',
PROFIT_CENTER_REQUIRED: 'At least one profit center is required',
WORK_CENTER_REQUIRED: 'At least one work center is required',
OPERATOR_REQUIRED: 'At least one operator is required',
ITEM_NUMBER_REQUIRED: 'At least one item number is required',
COMPONENT_LOT_REQUIRED: 'At least one component lot is required',
PART_OPERATION_REQUIRED: 'At least one part operation is required',
} as const;