import { Page, expect, Locator } from '@playwright/test'; /** * Notification types in Radzen */ export type NotificationType = 'success' | 'error' | 'warning' | 'info'; /** * Badge style types matching Radzen badge styles */ export type BadgeStyle = 'danger' | 'success' | 'info' | 'warning' | 'primary' | 'secondary'; /** * Wait for a notification of a specific type to appear * @param page - Playwright page object * @param type - The notification type to wait for * @param timeout - Maximum time to wait (default: 5000ms) */ export async function waitForNotification( page: Page, type: NotificationType, timeout: number = 5000 ): Promise { const notificationClass = `.rz-notification-${type}`; await page.locator(notificationClass).waitFor({ state: 'visible', timeout }); } /** * Wait for any notification to appear * @param page - Playwright page object * @param timeout - Maximum time to wait (default: 5000ms) */ export async function waitForAnyNotification( page: Page, timeout: number = 5000 ): Promise { await page.locator('.rz-notification').waitFor({ state: 'visible', timeout }); } /** * Check if an error notification is visible * @param page - Playwright page object * @returns true if error notification is visible */ export async function hasErrorNotification(page: Page): Promise { return await page.locator('.rz-notification-error').isVisible({ timeout: 2000 }).catch(() => false); } /** * Check if a success notification is visible * @param page - Playwright page object * @returns true if success notification is visible */ export async function hasSuccessNotification(page: Page): Promise { return await page.locator('.rz-notification-success').isVisible({ timeout: 2000 }).catch(() => false); } /** * Assert that no error notification is visible * @param page - Playwright page object * @param timeout - Time to wait before asserting (default: 5000ms) */ export async function assertNoErrorNotification(page: Page, timeout: number = 5000): Promise { const errorNotification = page.locator('.rz-notification-error'); await expect(errorNotification).not.toBeVisible({ timeout }); } /** * Get the row count from a data grid * @param page - Playwright page object * @param gridLocator - Locator for the specific grid (or defaults to first grid) * @returns Number of rows in the grid body */ export async function getDataGridRowCount( page: Page, gridLocator?: Locator ): Promise { const grid = gridLocator ?? page.locator('.rz-data-grid').first(); // Check for "No records" message const noRecords = grid.locator('text=No records to display'); if (await noRecords.isVisible({ timeout: 1000 }).catch(() => false)) { return 0; } const rows = grid.locator('tbody tr'); return await rows.count(); } /** * Double-click a row in a data grid * @param page - Playwright page object * @param rowIndex - Zero-based index of the row to double-click * @param gridLocator - Locator for the specific grid (or defaults to first grid) */ export async function doubleClickDataGridRow( page: Page, rowIndex: number, gridLocator?: Locator ): Promise { const grid = gridLocator ?? page.locator('.rz-data-grid').first(); const rows = grid.locator('tbody tr'); await rows.nth(rowIndex).dblclick(); await page.waitForTimeout(500); } /** * Click a row in a data grid * @param page - Playwright page object * @param rowIndex - Zero-based index of the row to click * @param gridLocator - Locator for the specific grid (or defaults to first grid) */ export async function clickDataGridRow( page: Page, rowIndex: number, gridLocator?: Locator ): Promise { const grid = gridLocator ?? page.locator('.rz-data-grid').first(); const rows = grid.locator('tbody tr'); await rows.nth(rowIndex).click(); await page.waitForTimeout(300); } /** * Get text content from a specific cell in a data grid * @param page - Playwright page object * @param rowIndex - Zero-based row index * @param columnIndex - Zero-based column index * @param gridLocator - Locator for the specific grid (or defaults to first grid) * @returns Cell text content */ export async function getDataGridCellText( page: Page, rowIndex: number, columnIndex: number, gridLocator?: Locator ): Promise { const grid = gridLocator ?? page.locator('.rz-data-grid').first(); const cell = grid.locator(`tbody tr:nth-child(${rowIndex + 1}) td:nth-child(${columnIndex + 1})`); return await cell.textContent() ?? ''; } /** * Check if a data grid shows "No records to display" * @param page - Playwright page object * @param gridLocator - Locator for the specific grid (or defaults to first grid) * @returns true if no records message is visible */ export async function dataGridIsEmpty( page: Page, gridLocator?: Locator ): Promise { const grid = gridLocator ?? page.locator('.rz-data-grid').first(); const noRecords = grid.locator('text=No records to display'); return await noRecords.isVisible({ timeout: 2000 }).catch(() => false); } /** * Wait for dialog to appear with specific title * @param page - Playwright page object * @param title - Dialog title text * @param timeout - Maximum time to wait (default: 5000ms) */ export async function waitForDialog( page: Page, title: string, timeout: number = 5000 ): Promise { await page.locator(`.rz-dialog:has-text("${title}")`).waitFor({ state: 'visible', timeout }); } /** * Click OK button in a dialog * @param page - Playwright page object */ export async function clickDialogOk(page: Page): Promise { await page.locator('.rz-dialog button:has-text("OK")').click(); await page.waitForTimeout(300); } /** * Click Cancel button in a dialog * @param page - Playwright page object */ export async function clickDialogCancel(page: Page): Promise { await page.locator('.rz-dialog button:has-text("Cancel")').click(); await page.waitForTimeout(300); } /** * Confirm a dialog (waits for dialog and clicks OK) * @param page - Playwright page object * @param title - Dialog title to wait for */ export async function confirmDialog(page: Page, title: string): Promise { await waitForDialog(page, title); await clickDialogOk(page); } /** * Get the badge style from a badge element * @param badge - Locator for the badge element * @returns The badge style or null if not found */ export async function getBadgeStyle(badge: Locator): Promise { const classes = await badge.getAttribute('class') ?? ''; if (classes.includes('rz-badge-danger')) return 'danger'; if (classes.includes('rz-badge-success')) return 'success'; if (classes.includes('rz-badge-info')) return 'info'; if (classes.includes('rz-badge-warning')) return 'warning'; if (classes.includes('rz-badge-primary')) return 'primary'; if (classes.includes('rz-badge-secondary')) return 'secondary'; return null; } /** * Check if a button is disabled * @param page - Playwright page object * @param buttonText - Text content of the button * @returns true if button is disabled */ export async function isButtonDisabled(page: Page, buttonText: string): Promise { const button = page.locator(`button:has-text("${buttonText}")`); return await button.isDisabled(); } /** * Click a button by its text * @param page - Playwright page object * @param buttonText - Text content of the button */ export async function clickButton(page: Page, buttonText: string): Promise { await page.locator(`button:has-text("${buttonText}")`).click(); await page.waitForTimeout(300); } /** * Get the text content of a badge by its parent context * @param page - Playwright page object * @param contextText - Text near the badge to identify it * @returns Badge text content */ export async function getBadgeText(page: Page, contextText: string): Promise { const badge = page.locator(`:has-text("${contextText}") .rz-badge`).first(); return await badge.textContent() ?? ''; } /** * Status badge style mapping (from Blazor code) */ export const StatusBadgeStyles: Record = { 'Error': 'danger', 'Ended': 'success', 'Running': 'info', 'Queued': 'warning', 'New': 'secondary', };