Files
jdescopingtool/TestScripts/playwright/tests/search-queue.spec.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

233 lines
9.4 KiB
TypeScript

import { test, expect } from '@playwright/test';
import { login, logout, isLoggedIn } from '../helpers/auth.helper';
import { navigateToSearchQueue, navigateToSearchesDashboard, clickNavLink } from '../helpers/navigation.helper';
import { getDataGridRowCount, getBadgeStyle, clickButton, StatusBadgeStyles } from '../helpers/radzen.helper';
test.describe('Search Queue Page', () => {
test.beforeEach(async ({ page }) => {
await navigateToSearchQueue(page);
});
test.describe('Page Load and Display', () => {
test('page loads and displays Search Queue heading', async ({ page }) => {
// Verify page title
await expect(page).toHaveTitle(/Search Queue - JDE Scoping Tool/);
// Verify "Search Queue" heading is visible
await expect(page.locator('h4:has-text("Search Queue")')).toBeVisible();
});
test('page shows processor status panel', async ({ page }) => {
// Verify "Search Processor Status" section is visible
await expect(page.locator('text=Search Processor Status')).toBeVisible();
});
test('page shows data grid component', async ({ page }) => {
// Verify data grid is visible
const dataGrid = page.locator('.rz-data-grid');
await expect(dataGrid).toBeVisible({ timeout: 10000 });
});
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('Processor Status Panel', () => {
test('status panel is contained within a RadzenCard', async ({ page }) => {
const card = page.locator('.rz-card').filter({ hasText: 'Search Processor Status' });
await expect(card).toBeVisible();
});
test('status message field is visible', async ({ page }) => {
// Look for "Status Message" label
await expect(page.locator('text=Status Message')).toBeVisible();
});
test('last update timestamp field is visible', async ({ page }) => {
// Look for "Last Update Timestamp" label
await expect(page.locator('text=Last Update Timestamp')).toBeVisible();
});
test('status message textbox is read-only', async ({ page }) => {
// The status message input should be read-only
const statusInput = page.locator('.rz-card').filter({ hasText: 'Status Message' }).locator('input').first();
if (await statusInput.isVisible({ timeout: 2000 }).catch(() => false)) {
const readOnly = await statusInput.getAttribute('readonly');
// In Radzen, readonly inputs have the readonly attribute
expect(readOnly !== null || await statusInput.isDisabled()).toBeTruthy();
}
});
test('timestamp textbox is read-only', async ({ page }) => {
const timestampInput = page.locator('.rz-card').filter({ hasText: 'Last Update Timestamp' }).locator('input').first();
if (await timestampInput.isVisible({ timeout: 2000 }).catch(() => false)) {
const readOnly = await timestampInput.getAttribute('readonly');
expect(readOnly !== null || await timestampInput.isDisabled()).toBeTruthy();
}
});
});
test.describe('Data Grid Columns', () => {
test('data grid shows Owner column', async ({ page }) => {
const ownerHeader = page.locator('.rz-data-grid th:has-text("Owner")');
await expect(ownerHeader).toBeVisible();
});
test('data grid shows Name column', async ({ page }) => {
const nameHeader = page.locator('.rz-data-grid th:has-text("Name")');
await expect(nameHeader).toBeVisible();
});
test('data grid shows Submitted column', async ({ page }) => {
const submittedHeader = page.locator('.rz-data-grid th:has-text("Submitted")');
await expect(submittedHeader).toBeVisible();
});
test('data grid shows Started column', async ({ page }) => {
const startedHeader = page.locator('.rz-data-grid th:has-text("Started")');
await expect(startedHeader).toBeVisible();
});
test('data grid shows Ended column', async ({ page }) => {
const endedHeader = page.locator('.rz-data-grid th:has-text("Ended")');
await expect(endedHeader).toBeVisible();
});
test('data grid shows Status column', async ({ page }) => {
const statusHeader = page.locator('.rz-data-grid th:has-text("Status")');
await expect(statusHeader).toBeVisible();
});
test('all expected columns are present', async ({ page }) => {
const headers = page.locator('.rz-data-grid th');
const headerTexts = await headers.allTextContents();
// Verify all expected columns
expect(headerTexts.some(h => h.includes('Owner'))).toBe(true);
expect(headerTexts.some(h => h.includes('Name'))).toBe(true);
expect(headerTexts.some(h => h.includes('Submitted'))).toBe(true);
expect(headerTexts.some(h => h.includes('Started'))).toBe(true);
expect(headerTexts.some(h => h.includes('Ended'))).toBe(true);
expect(headerTexts.some(h => h.includes('Status'))).toBe(true);
});
});
test.describe('Queue Display', () => {
test('queue shows only queued and running searches', async ({ page }) => {
const rowCount = await getDataGridRowCount(page);
if (rowCount > 0) {
// Get all status badges in the grid
const statusBadges = page.locator('.rz-data-grid tbody .rz-badge');
const badgeCount = await statusBadges.count();
for (let i = 0; i < badgeCount; i++) {
const badgeText = await statusBadges.nth(i).textContent();
// Queue should only show Queued or Running statuses
// (Ended and Error are removed from queue)
if (badgeText) {
expect(['Queued', 'Running', 'New']).toContain(badgeText.trim());
}
}
}
});
});
test.describe('Status Badges', () => {
test('Running status displays with info style', async ({ page }) => {
const runningBadge = page.locator('.rz-data-grid .rz-badge:has-text("Running")').first();
if (await runningBadge.isVisible({ timeout: 2000 }).catch(() => false)) {
const style = await getBadgeStyle(runningBadge);
expect(style).toBe('info');
}
});
test('Queued status displays with warning style', async ({ page }) => {
const queuedBadge = page.locator('.rz-data-grid .rz-badge:has-text("Queued")').first();
if (await queuedBadge.isVisible({ timeout: 2000 }).catch(() => false)) {
const style = await getBadgeStyle(queuedBadge);
expect(style).toBe('warning');
}
});
});
test.describe('Data Grid Features', () => {
test('data grid supports sorting', async ({ page }) => {
// Click on Name column header to sort
const nameHeader = page.locator('.rz-data-grid th:has-text("Name")');
await nameHeader.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 }) => {
// Look for pager component
const pager = page.locator('.rz-pager');
await expect(pager).toBeVisible({ timeout: 5000 });
});
test('data grid supports column resize', async ({ page }) => {
// The grid has AllowColumnResize="true"
// Just verify no error on the page
const errorNotification = page.locator('.rz-notification-error');
await expect(errorNotification).not.toBeVisible({ timeout: 2000 });
});
});
test.describe('Date Formatting', () => {
test('submitted date is formatted correctly', async ({ page }) => {
const rowCount = await getDataGridRowCount(page);
if (rowCount > 0) {
// Get text from Submitted column (index 2, after Owner and Name)
const submittedCell = page.locator('.rz-data-grid tbody tr').first().locator('td').nth(2);
const dateText = await submittedCell.textContent();
if (dateText && dateText.trim()) {
// Verify date format matches MM/dd/yyyy hh:mm:ss tt pattern
const datePattern = /^\d{2}\/\d{2}\/\d{4} \d{2}:\d{2}:\d{2} (AM|PM)$/;
expect(dateText.trim()).toMatch(datePattern);
}
}
});
});
test.describe('Empty State', () => {
test('displays appropriate message when queue is empty', async ({ page }) => {
const rowCount = await getDataGridRowCount(page);
if (rowCount === 0) {
// Should show "No records to display" or similar
const noRecordsMessage = page.locator('text=No records to display');
await expect(noRecordsMessage).toBeVisible();
}
});
});
test.describe('Navigation', () => {
test('can navigate to queue from searches dashboard', async ({ page }) => {
// Start from searches dashboard
await navigateToSearchesDashboard(page);
// Click View Search Queue button
await clickButton(page, 'View Search Queue');
// Wait for navigation
await page.waitForLoadState('networkidle', { timeout: 30000 });
// Should be on queue page
await expect(page).toHaveURL(/\/search\/queue/);
await expect(page.locator('h4:has-text("Search Queue")')).toBeVisible();
});
});
});