ee044d03e0
- 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
188 lines
7.4 KiB
TypeScript
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;
|