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
420 lines
16 KiB
TypeScript
420 lines
16 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
import { login, logout, isLoggedIn } from '../helpers/auth.helper';
|
|
import { navigateToDataSync, clickNavLink } from '../helpers/navigation.helper';
|
|
import { getDataGridRowCount, getBadgeStyle, clickButton, StatusBadgeStyles } from '../helpers/radzen.helper';
|
|
|
|
test.describe('Data Sync Requests Page', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await navigateToDataSync(page);
|
|
});
|
|
|
|
test.describe('Page Load and Display', () => {
|
|
test('page loads and displays Data Sync Requests heading', async ({ page }) => {
|
|
// Verify page title
|
|
await expect(page).toHaveTitle(/Data Sync Requests - JDE Scoping Tool/);
|
|
|
|
// Verify heading is visible
|
|
await expect(page.locator('h4:has-text("Data Sync Requests")')).toBeVisible();
|
|
});
|
|
|
|
test('page shows filter card', async ({ page }) => {
|
|
// Verify filter card is visible
|
|
const filterCard = page.locator('.rz-card');
|
|
await expect(filterCard.first()).toBeVisible();
|
|
});
|
|
|
|
test('page shows New Request button', async ({ page }) => {
|
|
const newRequestButton = page.locator('button:has-text("New Request")');
|
|
await expect(newRequestButton).toBeVisible();
|
|
});
|
|
|
|
test('page shows Reload Pipelines button', async ({ page }) => {
|
|
const reloadButton = page.locator('button:has-text("Reload Pipelines")');
|
|
await expect(reloadButton).toBeVisible();
|
|
});
|
|
|
|
test('no error notification on page load', async ({ page }) => {
|
|
const errorNotification = page.locator('.rz-notification-error');
|
|
await expect(errorNotification).not.toBeVisible({ timeout: 5000 });
|
|
});
|
|
});
|
|
|
|
test.describe('Filter Controls', () => {
|
|
test('show pending only checkbox is visible', async ({ page }) => {
|
|
const checkbox = page.locator('.rz-chkbox');
|
|
await expect(checkbox).toBeVisible();
|
|
});
|
|
|
|
test('show pending only label is visible', async ({ page }) => {
|
|
await expect(page.locator('text=Show pending only')).toBeVisible();
|
|
});
|
|
|
|
test('refresh button is visible', async ({ page }) => {
|
|
const refreshButton = page.locator('button:has-text("Refresh")');
|
|
await expect(refreshButton).toBeVisible();
|
|
});
|
|
|
|
test('clicking refresh button reloads data', async ({ page }) => {
|
|
// Wait for initial load
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Click refresh button
|
|
const refreshButton = page.locator('button:has-text("Refresh")');
|
|
await refreshButton.click();
|
|
|
|
// Wait for reload
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Verify no error
|
|
const errorNotification = page.locator('.rz-notification-error');
|
|
await expect(errorNotification).not.toBeVisible({ timeout: 5000 });
|
|
});
|
|
|
|
test('filter checkbox toggles pending only filter', async ({ page }) => {
|
|
// Wait for initial load
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Find and click the checkbox
|
|
const checkbox = page.locator('.rz-chkbox').first();
|
|
await checkbox.click();
|
|
|
|
// Wait for filter to apply
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Verify no error occurred
|
|
const errorNotification = page.locator('.rz-notification-error');
|
|
await expect(errorNotification).not.toBeVisible({ timeout: 5000 });
|
|
});
|
|
});
|
|
|
|
test.describe('Data Grid Display', () => {
|
|
test('data grid or empty state message is visible', async ({ page }) => {
|
|
// Wait for loading to complete
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Either data grid is visible or info message about no requests
|
|
const dataGrid = page.locator('.rz-data-grid');
|
|
const noRequestsMessage = page.locator('text=No sync requests found');
|
|
|
|
const gridVisible = await dataGrid.isVisible({ timeout: 3000 }).catch(() => false);
|
|
const messageVisible = await noRequestsMessage.isVisible({ timeout: 3000 }).catch(() => false);
|
|
|
|
expect(gridVisible || messageVisible).toBe(true);
|
|
});
|
|
});
|
|
|
|
test.describe('Data Grid Columns', () => {
|
|
test('data grid shows Pipeline column when requests exist', async ({ page }) => {
|
|
await page.waitForTimeout(2000);
|
|
const dataGrid = page.locator('.rz-data-grid');
|
|
|
|
if (await dataGrid.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
const pipelineHeader = page.locator('.rz-data-grid th:has-text("Pipeline")');
|
|
await expect(pipelineHeader).toBeVisible();
|
|
}
|
|
});
|
|
|
|
test('data grid shows Type column when requests exist', async ({ page }) => {
|
|
await page.waitForTimeout(2000);
|
|
const dataGrid = page.locator('.rz-data-grid');
|
|
|
|
if (await dataGrid.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
const typeHeader = page.locator('.rz-data-grid th:has-text("Type")');
|
|
await expect(typeHeader).toBeVisible();
|
|
}
|
|
});
|
|
|
|
test('data grid shows Requested column when requests exist', async ({ page }) => {
|
|
await page.waitForTimeout(2000);
|
|
const dataGrid = page.locator('.rz-data-grid');
|
|
|
|
if (await dataGrid.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
const requestedHeader = page.locator('.rz-data-grid th:has-text("Requested")');
|
|
await expect(requestedHeader).toBeVisible();
|
|
}
|
|
});
|
|
|
|
test('data grid shows By column when requests exist', async ({ page }) => {
|
|
await page.waitForTimeout(2000);
|
|
const dataGrid = page.locator('.rz-data-grid');
|
|
|
|
if (await dataGrid.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
const byHeader = page.locator('.rz-data-grid th:has-text("By")');
|
|
await expect(byHeader).toBeVisible();
|
|
}
|
|
});
|
|
|
|
test('data grid shows Status column when requests exist', async ({ page }) => {
|
|
await page.waitForTimeout(2000);
|
|
const dataGrid = page.locator('.rz-data-grid');
|
|
|
|
if (await dataGrid.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
const statusHeader = page.locator('.rz-data-grid th:has-text("Status")');
|
|
await expect(statusHeader).toBeVisible();
|
|
}
|
|
});
|
|
|
|
test('data grid shows Actions column when requests exist', async ({ page }) => {
|
|
await page.waitForTimeout(2000);
|
|
const dataGrid = page.locator('.rz-data-grid');
|
|
|
|
if (await dataGrid.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
const actionsHeader = page.locator('.rz-data-grid th:has-text("Actions")');
|
|
await expect(actionsHeader).toBeVisible();
|
|
}
|
|
});
|
|
|
|
test('all expected columns are present when requests exist', async ({ page }) => {
|
|
await page.waitForTimeout(2000);
|
|
const dataGrid = page.locator('.rz-data-grid');
|
|
|
|
if (await dataGrid.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
const headers = page.locator('.rz-data-grid th');
|
|
const headerTexts = await headers.allTextContents();
|
|
|
|
// Verify all expected columns
|
|
expect(headerTexts.some(h => h.includes('Pipeline'))).toBe(true);
|
|
expect(headerTexts.some(h => h.includes('Type'))).toBe(true);
|
|
expect(headerTexts.some(h => h.includes('Requested'))).toBe(true);
|
|
expect(headerTexts.some(h => h.includes('By'))).toBe(true);
|
|
expect(headerTexts.some(h => h.includes('Status'))).toBe(true);
|
|
expect(headerTexts.some(h => h.includes('Actions'))).toBe(true);
|
|
}
|
|
});
|
|
});
|
|
|
|
test.describe('Status Badges', () => {
|
|
test('Pending status displays with appropriate badge style', async ({ page }) => {
|
|
await page.waitForTimeout(2000);
|
|
|
|
const pendingBadge = page.locator('.rz-data-grid .rz-badge:has-text("Pending")').first();
|
|
|
|
if (await pendingBadge.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
const style = await getBadgeStyle(pendingBadge);
|
|
// Pending typically uses warning style
|
|
expect(style).toBeTruthy();
|
|
}
|
|
});
|
|
|
|
test('Completed status displays with success badge style', async ({ page }) => {
|
|
await page.waitForTimeout(2000);
|
|
|
|
const completedBadge = page.locator('.rz-data-grid .rz-badge:has-text("Completed")').first();
|
|
|
|
if (await completedBadge.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
const style = await getBadgeStyle(completedBadge);
|
|
expect(style).toBe('success');
|
|
}
|
|
});
|
|
|
|
test('Failed status displays with danger badge style', async ({ page }) => {
|
|
await page.waitForTimeout(2000);
|
|
|
|
const failedBadge = page.locator('.rz-data-grid .rz-badge:has-text("Failed")').first();
|
|
|
|
if (await failedBadge.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
const style = await getBadgeStyle(failedBadge);
|
|
expect(style).toBe('danger');
|
|
}
|
|
});
|
|
});
|
|
|
|
test.describe('Action Buttons', () => {
|
|
test('Cancel button appears only for Pending requests', async ({ page }) => {
|
|
await page.waitForTimeout(2000);
|
|
const rowCount = await getDataGridRowCount(page);
|
|
|
|
if (rowCount > 0) {
|
|
// Get all rows
|
|
const rows = page.locator('.rz-data-grid tbody tr');
|
|
const rowCountNum = await rows.count();
|
|
|
|
for (let i = 0; i < rowCountNum; i++) {
|
|
const row = rows.nth(i);
|
|
const statusBadge = row.locator('.rz-badge');
|
|
const statusText = await statusBadge.textContent();
|
|
const cancelButton = row.locator('button:has-text("Cancel")');
|
|
|
|
if (statusText?.trim() === 'Pending') {
|
|
// Pending rows should have Cancel button
|
|
await expect(cancelButton).toBeVisible();
|
|
} else {
|
|
// Non-pending rows should not have Cancel button
|
|
await expect(cancelButton).not.toBeVisible();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
test('New Request button opens dialog', async ({ page }) => {
|
|
// Click New Request button
|
|
const newRequestButton = page.locator('button:has-text("New Request")');
|
|
await newRequestButton.click();
|
|
|
|
// Wait for dialog to appear
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Should see a dialog (Radzen dialog)
|
|
const dialog = page.locator('.rz-dialog');
|
|
const dialogVisible = await dialog.isVisible({ timeout: 5000 }).catch(() => false);
|
|
|
|
// If dialog opened, verify it's there; if not, verify no error
|
|
if (!dialogVisible) {
|
|
const errorNotification = page.locator('.rz-notification-error');
|
|
await expect(errorNotification).not.toBeVisible({ timeout: 2000 });
|
|
}
|
|
});
|
|
});
|
|
|
|
test.describe('Data Grid Features', () => {
|
|
test('data grid supports sorting when requests exist', async ({ page }) => {
|
|
await page.waitForTimeout(2000);
|
|
const dataGrid = page.locator('.rz-data-grid');
|
|
|
|
if (await dataGrid.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
// Click on Pipeline column header to sort
|
|
const pipelineHeader = page.locator('.rz-data-grid th:has-text("Pipeline")');
|
|
await pipelineHeader.click();
|
|
await page.waitForTimeout(500);
|
|
|
|
// Verify no error occurred
|
|
const errorNotification = page.locator('.rz-notification-error');
|
|
await expect(errorNotification).not.toBeVisible({ timeout: 2000 });
|
|
}
|
|
});
|
|
|
|
test('data grid supports pagination with 20 items per page', async ({ page }) => {
|
|
await page.waitForTimeout(2000);
|
|
const dataGrid = page.locator('.rz-data-grid');
|
|
|
|
if (await dataGrid.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
// Look for pager component
|
|
const pager = page.locator('.rz-pager');
|
|
await expect(pager).toBeVisible({ timeout: 5000 });
|
|
}
|
|
});
|
|
|
|
test('data grid supports column resize', async ({ page }) => {
|
|
await page.waitForTimeout(2000);
|
|
const dataGrid = page.locator('.rz-data-grid');
|
|
|
|
if (await dataGrid.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
// Verify no error occurred
|
|
const errorNotification = page.locator('.rz-notification-error');
|
|
await expect(errorNotification).not.toBeVisible({ timeout: 2000 });
|
|
}
|
|
});
|
|
});
|
|
|
|
test.describe('Date Formatting', () => {
|
|
test('requested date is formatted correctly (MM/dd hh:mm)', async ({ page }) => {
|
|
await page.waitForTimeout(2000);
|
|
const dataGrid = page.locator('.rz-data-grid');
|
|
|
|
if (await dataGrid.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
const rowCount = await getDataGridRowCount(page);
|
|
|
|
if (rowCount > 0) {
|
|
// Get text from Requested column (index 2)
|
|
const requestedCell = page.locator('.rz-data-grid tbody tr').first().locator('td').nth(2);
|
|
const dateText = await requestedCell.textContent();
|
|
|
|
if (dateText && dateText.trim()) {
|
|
// Verify date format matches MM/dd hh:mm pattern
|
|
const datePattern = /^\d{2}\/\d{2} \d{2}:\d{2}$/;
|
|
expect(dateText.trim()).toMatch(datePattern);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
test.describe('Empty State', () => {
|
|
test('displays info message when no sync requests found', async ({ page }) => {
|
|
await page.waitForTimeout(2000);
|
|
const rowCount = await getDataGridRowCount(page);
|
|
|
|
if (rowCount === 0) {
|
|
// Should show info message
|
|
const noRequestsMessage = page.locator('text=No sync requests found');
|
|
await expect(noRequestsMessage).toBeVisible();
|
|
}
|
|
});
|
|
|
|
test('empty state message suggests creating new request', async ({ page }) => {
|
|
await page.waitForTimeout(2000);
|
|
const rowCount = await getDataGridRowCount(page);
|
|
|
|
if (rowCount === 0) {
|
|
// Message should mention "New Request"
|
|
const helpMessage = page.locator('text=Click "New Request" to create one');
|
|
await expect(helpMessage).toBeVisible();
|
|
}
|
|
});
|
|
});
|
|
|
|
test.describe('Loading State', () => {
|
|
test('shows loading indicator while fetching data', async ({ page }) => {
|
|
// Navigate fresh to catch loading state
|
|
await page.goto('/data-sync/requests');
|
|
await page.waitForLoadState('networkidle', { timeout: 60000 });
|
|
|
|
// If there's a loading indicator, it should eventually disappear
|
|
const loadingIndicator = page.locator('text=Loading requests');
|
|
|
|
// Wait for page to settle
|
|
await page.waitForTimeout(3000);
|
|
|
|
// After waiting, loading should be gone
|
|
await expect(loadingIndicator).not.toBeVisible({ timeout: 10000 });
|
|
});
|
|
});
|
|
|
|
test.describe('Header Buttons', () => {
|
|
test('Reload Pipelines button has correct tooltip', async ({ page }) => {
|
|
const reloadButton = page.locator('button:has-text("Reload Pipelines")');
|
|
const title = await reloadButton.getAttribute('title');
|
|
expect(title).toContain('Admin only');
|
|
});
|
|
|
|
test('New Request button has add icon', async ({ page }) => {
|
|
const newRequestButton = page.locator('button:has-text("New Request")');
|
|
const icon = newRequestButton.locator('.rz-button-icon-left, .material-icons');
|
|
|
|
// The button should have an icon
|
|
await expect(newRequestButton).toBeVisible();
|
|
});
|
|
|
|
test('Reload Pipelines button has sync icon', async ({ page }) => {
|
|
const reloadButton = page.locator('button:has-text("Reload Pipelines")');
|
|
|
|
// The button should be visible
|
|
await expect(reloadButton).toBeVisible();
|
|
});
|
|
});
|
|
|
|
test.describe('Filter Behavior', () => {
|
|
test('toggling pending only filter changes displayed data', async ({ page }) => {
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Get initial row count
|
|
const initialRowCount = await getDataGridRowCount(page);
|
|
|
|
// Toggle the checkbox
|
|
const checkbox = page.locator('.rz-chkbox').first();
|
|
await checkbox.click();
|
|
|
|
// Wait for filter to apply
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Get new row count
|
|
const newRowCount = await getDataGridRowCount(page);
|
|
|
|
// Counts may or may not be different depending on data
|
|
// Just verify no error occurred
|
|
const errorNotification = page.locator('.rz-notification-error');
|
|
await expect(errorNotification).not.toBeVisible({ timeout: 2000 });
|
|
});
|
|
});
|
|
});
|