Migrate Playwright suite to .NET UI tests and deprecate TS project

This commit is contained in:
Joseph Doherty
2026-02-06 18:44:40 -05:00
parent 4e56ea3435
commit 562f7e9e37
105 changed files with 1119 additions and 0 deletions
@@ -0,0 +1,553 @@
import { test, expect } from '@playwright/test';
import path from 'path';
import { navigateToSearchPage } from '../helpers/navigation.helper';
import { selectSearchType, SearchTypes, enterSearchName, clickSubmitSearch, confirmSubmitSearch } from '../helpers/search-type.helper';
import { uploadFile, clearUploadedData, downloadTemplate, getUploadedItemCount, componentLotConfig, getTestFile, TestFiles, getTestDataPath } from '../helpers/file-upload.helper';
import { assertNoErrorNotification, dataGridIsEmpty, confirmDialog, hasErrorNotification, waitForNotification } from '../helpers/radzen.helper';
import { hasValidationErrors, submitAndExpectError, assertValidationError, ValidationMessages, getValidationErrors } from '../helpers/validation.helper';
/**
* Component Lot Search (Type 20) - E2E Tests
*
* Based on manual test scripts from TestScripts/SearchPage/20_ComponentLot.md
* Tests the Component Lot search functionality which allows users to search by
* one or more component lot numbers without requiring a time span or other filters.
*/
test.describe('Component Lot Search (Type 20)', () => {
test.beforeEach(async ({ page }) => {
await navigateToSearchPage(page);
});
/**
* POSITIVE TEST CASES
*/
test.describe('Positive Tests', () => {
test('TC-020-P01: Single Component Lot Search', async ({ page }) => {
// Step 1-2: Navigate to Submit Search page (done in beforeEach), enter search name
await enterSearchName(page, 'TC-020-P01 Single Lot Test');
// Step 3: Select "Component Lot" search type (Type 20)
await selectSearchType(page, SearchTypes.COMPONENT_LOT);
// Step 4-6: Upload single lot file and verify it appears in grid
await expect(page.locator(`text=${componentLotConfig.panelHeader}`)).toBeVisible();
const testFile = getTestFile(TestFiles.SINGLE_LOT);
await uploadFile(page, componentLotConfig, testFile);
// Verify lot appears in the grid (count should be 1)
const count = await getUploadedItemCount(page, componentLotConfig);
expect(count).toBe(1);
// Verify no error notification
await assertNoErrorNotification(page);
// Step 7: Click Submit Search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Expected Results: Search is created successfully (no errors)
await assertNoErrorNotification(page);
});
test('TC-020-P02: Multiple Component Lots Search', async ({ page }) => {
// Step 1-2: Navigate and enter search name
await enterSearchName(page, 'TC-020-P02 Multiple Lots Test');
// Step 3: Select "Component Lot" search type
await selectSearchType(page, SearchTypes.COMPONENT_LOT);
// Step 4-7: Upload multiple lots file
await expect(page.locator(`text=${componentLotConfig.panelHeader}`)).toBeVisible();
const testFile = getTestFile(TestFiles.MULTIPLE_LOTS);
await uploadFile(page, componentLotConfig, testFile);
// Verify multiple lots appear in the grid
const count = await getUploadedItemCount(page, componentLotConfig);
expect(count).toBeGreaterThan(1);
// Verify no error
await assertNoErrorNotification(page);
// Step 8: Click Submit Search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Expected Results: Search is created with all lots
await assertNoErrorNotification(page);
});
test('TC-020-P03: Component Lot with Multiple Item Associations', async ({ page }) => {
// Step 1-2: Navigate and enter search name
await enterSearchName(page, 'TC-020-P03 Multi-Item Lot Test');
// Step 3: Select "Component Lot" search type
await selectSearchType(page, SearchTypes.COMPONENT_LOT);
// Step 4-5: Upload file containing lot 00009106 (associated with multiple items)
// This tests that the system correctly handles lots that map to multiple items
await expect(page.locator(`text=${componentLotConfig.panelHeader}`)).toBeVisible();
// Use multiple_lots file which may contain lots with multiple item associations
const testFile = getTestFile(TestFiles.MULTIPLE_LOTS);
await uploadFile(page, componentLotConfig, testFile);
// Verify lots appear in the grid
const count = await getUploadedItemCount(page, componentLotConfig);
expect(count).toBeGreaterThan(0);
// Verify no error
await assertNoErrorNotification(page);
// Step 6: Click Submit Search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Expected Results: Search is created successfully
// Results should include all work orders where these lots were used
await assertNoErrorNotification(page);
});
test('TC-020-P04: Component Lot Search with Maximum Entries', async ({ page }) => {
// Step 1-2: Navigate and enter search name
await enterSearchName(page, 'TC-020-P04 Max Lots Test');
// Step 3: Select "Component Lot" search type
await selectSearchType(page, SearchTypes.COMPONENT_LOT);
// Step 4-5: Upload file with many lots
await expect(page.locator(`text=${componentLotConfig.panelHeader}`)).toBeVisible();
const testFile = getTestFile(TestFiles.MULTIPLE_LOTS);
await uploadFile(page, componentLotConfig, testFile);
// Verify lots appear in the grid
const count = await getUploadedItemCount(page, componentLotConfig);
expect(count).toBeGreaterThan(0);
// Verify no error
await assertNoErrorNotification(page);
// Step 6: Click Submit Search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Expected Results: Search is created with all lots
await assertNoErrorNotification(page);
});
test('TC-020-P05: Component Lot Search - Remove and Re-add (Clear Data)', async ({ page }) => {
// Step 1-2: Navigate and enter search name
await enterSearchName(page, 'TC-020-P05 Remove Re-add Lot Test');
// Step 3: Select "Component Lot" search type
await selectSearchType(page, SearchTypes.COMPONENT_LOT);
// Step 4-5: Upload multiple lots
await expect(page.locator(`text=${componentLotConfig.panelHeader}`)).toBeVisible();
const testFile = getTestFile(TestFiles.MULTIPLE_LOTS);
await uploadFile(page, componentLotConfig, testFile);
// Verify lots were added
let count = await getUploadedItemCount(page, componentLotConfig);
expect(count).toBeGreaterThan(0);
// Step 6: Clear all data (simulates removing entries)
await clearUploadedData(page, componentLotConfig);
// Step 7: Verify grid is empty
const isEmpty = await dataGridIsEmpty(page);
expect(isEmpty).toBe(true);
// Step 8: Re-add with single lot
const singleFile = getTestFile(TestFiles.SINGLE_LOT);
await uploadFile(page, componentLotConfig, singleFile);
// Verify single lot appears
count = await getUploadedItemCount(page, componentLotConfig);
expect(count).toBe(1);
// Step 9: Click Submit Search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Expected Results: Search is created with only the re-added lot
await assertNoErrorNotification(page);
});
test('TC-020-P06: Component Lot Search - Duplicate Prevention', async ({ page }) => {
// Step 1-2: Navigate and enter search name
await enterSearchName(page, 'TC-020-P06 Lot Duplicate Prevention Test');
// Step 3: Select "Component Lot" search type
await selectSearchType(page, SearchTypes.COMPONENT_LOT);
// Step 4: Upload single lot
await expect(page.locator(`text=${componentLotConfig.panelHeader}`)).toBeVisible();
const testFile = getTestFile(TestFiles.SINGLE_LOT);
await uploadFile(page, componentLotConfig, testFile);
// Get initial count
const initialCount = await getUploadedItemCount(page, componentLotConfig);
expect(initialCount).toBe(1);
// Step 5: Upload the same file again (attempt to add duplicate)
await uploadFile(page, componentLotConfig, testFile);
// Step 6: Observe system behavior
// Expected Results: System prevents duplicate or displays validation message
// Lot should appear only once in the list
const finalCount = await getUploadedItemCount(page, componentLotConfig);
// The count should either:
// 1. Stay the same (duplicates prevented)
// 2. Increase (if duplicates allowed) but system may show warning
// This test verifies the behavior is consistent
expect(finalCount).toBeGreaterThanOrEqual(initialCount);
});
test('TC-020-P07: Component Lot with Leading Zeros Handling', async ({ page }) => {
// Step 1-2: Navigate and enter search name
await enterSearchName(page, 'TC-020-P07 Leading Zeros Test');
// Step 3: Select "Component Lot" search type
await selectSearchType(page, SearchTypes.COMPONENT_LOT);
// Step 4-6: Upload file - system should handle lot numbers with leading zeros
await expect(page.locator(`text=${componentLotConfig.panelHeader}`)).toBeVisible();
// The test file should contain lot numbers which may have leading zeros
// (e.g., "00000099" should be handled correctly)
const testFile = getTestFile(TestFiles.SINGLE_LOT);
await uploadFile(page, componentLotConfig, testFile);
// Verify lot was added
const count = await getUploadedItemCount(page, componentLotConfig);
expect(count).toBe(1);
// Verify no error notification
await assertNoErrorNotification(page);
// Submit search to verify the data can be processed
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Expected Results: System handles leading zeros consistently
await assertNoErrorNotification(page);
});
test('TC-020-P08: Download Template contains correct format', async ({ page }) => {
// Select Component Lot search type
await selectSearchType(page, SearchTypes.COMPONENT_LOT);
// Verify panel is visible
await expect(page.locator(`text=${componentLotConfig.panelHeader}`)).toBeVisible();
// Download the template
const download = await downloadTemplate(page, componentLotConfig);
// Verify filename
const filename = download.suggestedFilename();
expect(filename).toContain('.xlsx');
expect(filename.toLowerCase()).toContain('lot');
});
});
/**
* NEGATIVE TEST CASES
*/
test.describe('Negative Tests', () => {
test('TC-020-N01: Missing Search Name', async ({ page }) => {
// Step 1: Navigate to Submit Search page (done in beforeEach)
// Step 2: Leave search name field empty
// (Don't enter any name)
// Step 3: Select "Component Lot" search type
await selectSearchType(page, SearchTypes.COMPONENT_LOT);
// Step 4: Add lot
const testFile = getTestFile(TestFiles.SINGLE_LOT);
await uploadFile(page, componentLotConfig, testFile);
// Step 5: Click Submit Search
await clickSubmitSearch(page);
// Expected Results: Validation error for missing search name
// Search is NOT created, user remains on Submit Search page
const hasErrors = await hasValidationErrors(page);
expect(hasErrors).toBe(true);
// Verify the specific error message
await assertValidationError(page, 'name');
});
test('TC-020-N02: No Search Type Selected', async ({ page }) => {
// Step 1: Navigate to Submit Search page (done in beforeEach)
// Step 2: Enter search name
await enterSearchName(page, 'TC-020-N02 No Type Test');
// Step 3: Do NOT select any search type
// Step 4: Attempt to add component lots - should not be possible without search type
// The component lot input panel should not be visible without selecting a search type
const isPanelVisible = await page.locator(`text=${componentLotConfig.panelHeader}`).isVisible({ timeout: 2000 }).catch(() => false);
expect(isPanelVisible).toBe(false);
// Step 5: Click Submit Search anyway
await clickSubmitSearch(page);
// Expected Results: Validation error for missing search type
const hasErrors = await hasValidationErrors(page);
expect(hasErrors).toBe(true);
});
test('TC-020-N03: Empty Component Lot List', async ({ page }) => {
// Step 1: Navigate to Submit Search page (done in beforeEach)
// Step 2: Enter search name
await enterSearchName(page, 'TC-020-N03 Empty Lots Test');
// Step 3: Select "Component Lot" search type
await selectSearchType(page, SearchTypes.COMPONENT_LOT);
// Verify panel is visible
await expect(page.locator(`text=${componentLotConfig.panelHeader}`)).toBeVisible();
// Step 4: Do NOT add any component lots
// Verify grid shows no records
const isEmpty = await dataGridIsEmpty(page);
expect(isEmpty).toBe(true);
// Step 5: Click Submit Search
await clickSubmitSearch(page);
// Expected Results: Validation error indicating at least one component lot is required
const hasErrors = await hasValidationErrors(page);
expect(hasErrors).toBe(true);
});
test('TC-020-N04: Invalid Lot Number Format', async ({ page }) => {
// Step 1-2: Navigate and enter search name
await enterSearchName(page, 'TC-020-N04 Invalid Lot Format Test');
// Step 3: Select "Component Lot" search type
await selectSearchType(page, SearchTypes.COMPONENT_LOT);
// Step 4-5: Upload file with invalid lot format (ABCD1234)
await expect(page.locator(`text=${componentLotConfig.panelHeader}`)).toBeVisible();
// Use invalid workorders file as a proxy for invalid format
// (Both test invalid alphanumeric entries)
const testFile = getTestFile(TestFiles.INVALID_WORKORDERS);
await uploadFile(page, componentLotConfig, testFile);
// Expected Results:
// Either error notification appears OR invalid data is not added to list
const hasError = await hasErrorNotification(page);
const isEmpty = await dataGridIsEmpty(page);
// One of these should be true - either upload failed or no valid records were added
// If items were added despite being invalid, submit should fail
if (!hasError && !isEmpty) {
await clickSubmitSearch(page);
const hasValidationError = await hasValidationErrors(page);
expect(hasValidationError || hasError || isEmpty).toBe(true);
}
});
test('TC-020-N05: Component Lot with Special Characters', async ({ page }) => {
// Step 1-2: Navigate and enter search name
await enterSearchName(page, 'TC-020-N05 Lot Special Characters Test');
// Step 3: Select "Component Lot" search type
await selectSearchType(page, SearchTypes.COMPONENT_LOT);
// Step 4-5: Upload file with special characters (00000099!@#)
await expect(page.locator(`text=${componentLotConfig.panelHeader}`)).toBeVisible();
const testFile = getTestFile(TestFiles.SPECIAL_CHARS_WORKORDERS);
await uploadFile(page, componentLotConfig, testFile);
// Expected Results:
// System should reject invalid characters
const hasError = await hasErrorNotification(page);
const isEmpty = await dataGridIsEmpty(page);
// Either upload should fail or no valid records should be added
// If data was added, submit should fail validation
if (!hasError && !isEmpty) {
await clickSubmitSearch(page);
const hasValidationError = await hasValidationErrors(page);
expect(hasValidationError || hasError || isEmpty).toBe(true);
}
});
test('TC-020-N06: Empty Component Lot Input (Empty File)', async ({ page }) => {
// Step 1-2: Navigate and enter search name
await enterSearchName(page, 'TC-020-N06 Empty Lot Input Test');
// Step 3: Select "Component Lot" search type
await selectSearchType(page, SearchTypes.COMPONENT_LOT);
// Step 4-5: Upload empty file
await expect(page.locator(`text=${componentLotConfig.panelHeader}`)).toBeVisible();
const testFile = getTestFile(TestFiles.EMPTY_FILE);
await uploadFile(page, componentLotConfig, testFile);
// Wait for any processing
await page.waitForTimeout(1000);
// Expected Results: Grid should remain empty or show error
const isEmpty = await dataGridIsEmpty(page);
// Try to submit
await clickSubmitSearch(page);
// Should fail validation since no lots were added
const hasErrors = await hasValidationErrors(page);
expect(hasErrors || isEmpty).toBe(true);
});
test('TC-020-N07: Invalid File Format (Non-Excel)', async ({ page }) => {
// Step 1-2: Navigate and enter search name
await enterSearchName(page, 'TC-020-N07 Invalid File Format Test');
// Step 3: Select "Component Lot" search type
await selectSearchType(page, SearchTypes.COMPONENT_LOT);
// Step 4-5: Upload invalid format file (.txt instead of .xlsx)
await expect(page.locator(`text=${componentLotConfig.panelHeader}`)).toBeVisible();
const testFile = getTestFile(TestFiles.INVALID_FORMAT);
await uploadFile(page, componentLotConfig, testFile);
// Wait for any error processing
await page.waitForTimeout(2000);
// Expected Results:
// System should show error notification for invalid file format
// OR grid should remain empty
const hasError = await hasErrorNotification(page);
const isEmpty = await dataGridIsEmpty(page);
// At least one of these should be true
expect(hasError || isEmpty).toBe(true);
});
test('TC-020-N08: Negative Lot Number', async ({ page }) => {
// This test validates that the system handles negative numbers appropriately
// Using the special chars file as proxy for invalid numeric input
await enterSearchName(page, 'TC-020-N08 Negative Lot Number Test');
await selectSearchType(page, SearchTypes.COMPONENT_LOT);
await expect(page.locator(`text=${componentLotConfig.panelHeader}`)).toBeVisible();
// Upload file with special/invalid characters
const testFile = getTestFile(TestFiles.SPECIAL_CHARS_WORKORDERS);
await uploadFile(page, componentLotConfig, testFile);
// Expected Results:
// System should reject invalid format (negative numbers would have minus sign)
const hasError = await hasErrorNotification(page);
const isEmpty = await dataGridIsEmpty(page);
if (!hasError && !isEmpty) {
await clickSubmitSearch(page);
const hasValidationError = await hasValidationErrors(page);
expect(hasValidationError || hasError || isEmpty).toBe(true);
}
});
test('TC-020-N09: Submit Without Confirmation', async ({ page }) => {
// Setup: Create a valid search
await enterSearchName(page, 'TC-020-N09 Confirmation Test');
await selectSearchType(page, SearchTypes.COMPONENT_LOT);
const testFile = getTestFile(TestFiles.SINGLE_LOT);
await uploadFile(page, componentLotConfig, testFile);
// Click Submit but do NOT confirm
await clickSubmitSearch(page);
// Wait for dialog
await page.waitForSelector('text=Confirm Submit', { timeout: 5000 });
// Click Cancel instead of OK
await page.locator('button:has-text("Cancel")').click();
await page.waitForTimeout(500);
// Expected Results: Search should NOT be created, user remains on page
// The page should still show the search form
await expect(page.locator(`text=${componentLotConfig.panelHeader}`)).toBeVisible();
});
});
/**
* EDGE CASE TESTS
*/
test.describe('Edge Cases', () => {
test('TC-020-E01: Switch from Component Lot to Different Search Type', async ({ page }) => {
// Select Component Lot first
await selectSearchType(page, SearchTypes.COMPONENT_LOT);
await expect(page.locator(`text=${componentLotConfig.panelHeader}`)).toBeVisible();
// Upload some data
const testFile = getTestFile(TestFiles.SINGLE_LOT);
await uploadFile(page, componentLotConfig, testFile);
// Verify data was added
let count = await getUploadedItemCount(page, componentLotConfig);
expect(count).toBeGreaterThan(0);
// Switch to a different search type
await selectSearchType(page, SearchTypes.WORK_ORDER);
// The Component Lot panel should be hidden
const isPanelVisible = await page.locator(`text=${componentLotConfig.panelHeader}`).isVisible({ timeout: 2000 }).catch(() => false);
expect(isPanelVisible).toBe(false);
// Work Order panel should now be visible
await expect(page.locator('text=Filter by Work Order')).toBeVisible();
});
test('TC-020-E02: Switch Back to Component Lot After Changing Type', async ({ page }) => {
// Select Component Lot
await selectSearchType(page, SearchTypes.COMPONENT_LOT);
await expect(page.locator(`text=${componentLotConfig.panelHeader}`)).toBeVisible();
// Upload data
const testFile = getTestFile(TestFiles.SINGLE_LOT);
await uploadFile(page, componentLotConfig, testFile);
// Get count
const initialCount = await getUploadedItemCount(page, componentLotConfig);
expect(initialCount).toBeGreaterThan(0);
// Switch to Work Order
await selectSearchType(page, SearchTypes.WORK_ORDER);
// Switch back to Component Lot
await selectSearchType(page, SearchTypes.COMPONENT_LOT);
// Verify panel is visible again
await expect(page.locator(`text=${componentLotConfig.panelHeader}`)).toBeVisible();
// Check if data was preserved (implementation-dependent)
const finalCount = await getUploadedItemCount(page, componentLotConfig);
// Data may or may not be preserved depending on implementation
// Just verify the panel is functional
expect(finalCount).toBeGreaterThanOrEqual(0);
});
});
});
@@ -0,0 +1,419 @@
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 });
});
});
});
@@ -0,0 +1,238 @@
import { test, expect } from '@playwright/test';
import { login, logout, isLoggedIn } from '../helpers/auth.helper';
import { navigateToLoginPage, navigateToSearchesDashboard } from '../helpers/navigation.helper';
test.describe('Login Page', () => {
test.describe('Login Form Display', () => {
test('login page displays correctly with form elements', async ({ page }) => {
await navigateToLoginPage(page);
// Verify page title
await expect(page).toHaveTitle(/Login - JDE Scoping Tool/);
// Verify "Authentication Required" heading is visible
await expect(page.locator('text=Authentication Required')).toBeVisible();
// Verify username field is visible
const usernameField = page.locator('input[name="Username"]');
await expect(usernameField).toBeVisible();
// Verify password field is visible
const passwordField = page.locator('input[name="Password"]');
await expect(passwordField).toBeVisible();
// Verify login button is visible
const loginButton = page.locator('button[type="submit"]:has-text("Login")');
await expect(loginButton).toBeVisible();
});
test('login form is contained within a RadzenCard', async ({ page }) => {
await navigateToLoginPage(page);
// Verify the form is inside a RadzenCard
const card = page.locator('.rz-card');
await expect(card).toBeVisible();
await expect(card.locator('text=Authentication Required')).toBeVisible();
});
test('form fields have correct labels', async ({ page }) => {
await navigateToLoginPage(page);
// Verify Username label
await expect(page.locator('text=Username')).toBeVisible();
// Verify Password label
await expect(page.locator('text=Password')).toBeVisible();
});
});
test.describe('Successful Login', () => {
test('successful login with valid credentials redirects to home page', async ({ page }) => {
await navigateToLoginPage(page);
// Fill in credentials
await page.locator('input[name="Username"]').fill('testuser');
await page.locator('input[name="Password"]').fill('testpass');
// Click login button
await page.locator('button[type="submit"]:has-text("Login")').click();
// Wait for navigation
await page.waitForLoadState('networkidle', { timeout: 60000 });
// Should be redirected away from login page
// The home page is /searches or /
await expect(page).not.toHaveURL(/\/login/);
});
test('login using helper function works correctly', async ({ page }) => {
await navigateToLoginPage(page);
// Use the login helper
await login(page);
// Verify user is logged in
const loggedIn = await isLoggedIn(page);
expect(loggedIn).toBe(true);
});
test('login redirects to searches page after success', async ({ page }) => {
await navigateToLoginPage(page);
await login(page);
// Wait for navigation to complete
await page.waitForLoadState('networkidle', { timeout: 60000 });
// Should be on searches page (home) or search page
const url = page.url();
expect(url).toMatch(/\/(searches|search)?$/);
});
test('login with returnUrl redirects to correct page', async ({ page }) => {
// Navigate to login with returnUrl parameter
await page.goto('/login?returnUrl=/refresh-status');
await page.waitForLoadState('networkidle', { timeout: 60000 });
// Fill in credentials
await page.locator('input[name="Username"]').fill('testuser');
await page.locator('input[name="Password"]').fill('testpass');
// Click login button
await page.locator('button[type="submit"]:has-text("Login")').click();
// Wait for navigation
await page.waitForTimeout(3000);
await page.waitForLoadState('networkidle', { timeout: 60000 });
// Should eventually reach the requested page or home
// Note: Exact behavior depends on auth implementation
await expect(page.locator('text=Authentication Required')).not.toBeVisible({ timeout: 10000 });
});
});
test.describe('Logout Functionality', () => {
test('logout button is visible when logged in', async ({ page }) => {
await navigateToSearchesDashboard(page);
// Verify logout button is visible
const logoutButton = page.locator('button:has-text("Logout")');
await expect(logoutButton).toBeVisible();
});
test('logout functionality works correctly', async ({ page }) => {
// First login
await navigateToSearchesDashboard(page);
// Verify we're logged in
expect(await isLoggedIn(page)).toBe(true);
// Perform logout
await logout(page);
// Wait for logout to complete
await page.waitForTimeout(2000);
// After logout, should either be on login page or logged out
// Check if login form appears or logout button disappears
const loginForm = page.locator('text=Authentication Required');
const logoutButton = page.locator('button:has-text("Logout")');
// Either login form is visible OR logout button is not visible
const isOnLoginPage = await loginForm.isVisible({ timeout: 5000 }).catch(() => false);
const logoutButtonGone = !(await logoutButton.isVisible({ timeout: 2000 }).catch(() => false));
expect(isOnLoginPage || logoutButtonGone).toBe(true);
});
});
test.describe('Protected Page Redirection', () => {
test('accessing protected page without login redirects to login', async ({ page }) => {
// Clear any existing auth state by going to a fresh page
await page.goto('/search/queue');
await page.waitForLoadState('networkidle', { timeout: 60000 });
// Should either be redirected to login or see login form
const loginForm = page.locator('text=Authentication Required');
// Wait for either login form or the actual page content
await page.waitForTimeout(3000);
// If login form is visible, the redirect worked
// If not, it means we had cached auth - either way the test passes
const isOnLoginPage = await loginForm.isVisible({ timeout: 5000 }).catch(() => false);
// If not on login page, we should be authenticated
if (!isOnLoginPage) {
const logoutButton = page.locator('button:has-text("Logout")');
await expect(logoutButton).toBeVisible({ timeout: 5000 });
}
});
test('accessing refresh-status without login redirects to login', async ({ page }) => {
await page.goto('/refresh-status');
await page.waitForLoadState('networkidle', { timeout: 60000 });
// Wait for page to settle
await page.waitForTimeout(3000);
// Should see login form if not authenticated
const loginForm = page.locator('text=Authentication Required');
const pageTitle = page.locator('text=Cache Refresh Status');
// Either on login page OR already authenticated showing the actual page
const isOnLoginPage = await loginForm.isVisible({ timeout: 5000 }).catch(() => false);
const isOnRefreshPage = await pageTitle.isVisible({ timeout: 5000 }).catch(() => false);
expect(isOnLoginPage || isOnRefreshPage).toBe(true);
});
test('accessing data-sync without login redirects to login', async ({ page }) => {
await page.goto('/data-sync/requests');
await page.waitForLoadState('networkidle', { timeout: 60000 });
await page.waitForTimeout(3000);
const loginForm = page.locator('text=Authentication Required');
const pageTitle = page.locator('text=Data Sync Requests');
const isOnLoginPage = await loginForm.isVisible({ timeout: 5000 }).catch(() => false);
const isOnDataSyncPage = await pageTitle.isVisible({ timeout: 5000 }).catch(() => false);
expect(isOnLoginPage || isOnDataSyncPage).toBe(true);
});
});
test.describe('Login Form Behavior', () => {
test('login button shows busy state during login', async ({ page }) => {
await navigateToLoginPage(page);
await page.locator('input[name="Username"]').fill('testuser');
await page.locator('input[name="Password"]').fill('testpass');
// Click login and immediately check for busy state
const loginButton = page.locator('button[type="submit"]');
await loginButton.click();
// The button text changes to "Logging in..." during the process
// This may be very fast, so we just verify the login completes
await page.waitForLoadState('networkidle', { timeout: 60000 });
});
test('form inputs are disabled during login process', async ({ page }) => {
await navigateToLoginPage(page);
await page.locator('input[name="Username"]').fill('testuser');
await page.locator('input[name="Password"]').fill('testpass');
// The form inputs should be disabled while _isLoading is true
// This happens quickly, so we verify the end state
await page.locator('button[type="submit"]:has-text("Login")').click();
// Wait for login to complete
await page.waitForLoadState('networkidle', { timeout: 60000 });
// Should be redirected after successful login
await expect(page.locator('text=Authentication Required')).not.toBeVisible({ timeout: 10000 });
});
});
});
@@ -0,0 +1,307 @@
import { test, expect } from '@playwright/test';
import { login, logout, isLoggedIn } from '../helpers/auth.helper';
import { navigateToRefreshStatus, clickNavLink } from '../helpers/navigation.helper';
import { getDataGridRowCount, getBadgeStyle, clickButton, StatusBadgeStyles } from '../helpers/radzen.helper';
test.describe('Refresh Status Page', () => {
test.beforeEach(async ({ page }) => {
await navigateToRefreshStatus(page);
});
test.describe('Page Load and Display', () => {
test('page loads and displays Cache Refresh Status heading', async ({ page }) => {
// Verify page title
await expect(page).toHaveTitle(/Cache Refresh Status - JDE Scoping Tool/);
// Verify heading is visible
await expect(page.locator('h4:has-text("Cache Refresh Status")')).toBeVisible();
});
test('page shows date filter panel', async ({ page }) => {
// Verify filter panel card is visible
const filterCard = page.locator('.rz-card').first();
await expect(filterCard).toBeVisible();
});
test('page shows data grid component', async ({ page }) => {
// Wait for loading to complete and grid to appear
await page.waitForTimeout(2000);
const dataGrid = page.locator('.rz-data-grid');
await expect(dataGrid).toBeVisible({ timeout: 15000 });
});
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('Date Filter Panel', () => {
test('start time date picker is visible', async ({ page }) => {
await expect(page.locator('text=Start Time')).toBeVisible();
});
test('end time date picker is visible', async ({ page }) => {
await expect(page.locator('text=End Time')).toBeVisible();
});
test('filter button is visible', async ({ page }) => {
const filterButton = page.locator('button:has-text("Filter")');
await expect(filterButton).toBeVisible();
});
test('date pickers have default values (last 7 days)', async ({ page }) => {
// The page defaults to last 7 days
// Just verify the date pickers are present and functional
const datePickers = page.locator('.rz-datepicker');
const count = await datePickers.count();
expect(count).toBeGreaterThanOrEqual(2);
});
test('clicking filter button triggers data reload', async ({ page }) => {
// Wait for initial load
await page.waitForTimeout(2000);
// Click filter button
const filterButton = page.locator('button:has-text("Filter")');
await filterButton.click();
// Wait for data to reload
await page.waitForTimeout(2000);
// Verify no error
const errorNotification = page.locator('.rz-notification-error');
await expect(errorNotification).not.toBeVisible({ timeout: 5000 });
});
test('date filtering works with custom date range', async ({ page }) => {
// Wait for initial load
await page.waitForTimeout(2000);
// The date pickers should be interactive
const startDatePicker = page.locator('.rz-datepicker').first();
await expect(startDatePicker).toBeVisible();
// Click filter to reload with current dates
await clickButton(page, 'Filter');
// Wait for reload
await page.waitForTimeout(2000);
// Verify no error occurred
const errorNotification = page.locator('.rz-notification-error');
await expect(errorNotification).not.toBeVisible({ timeout: 5000 });
});
});
test.describe('Data Grid Columns', () => {
test('data grid shows Start column', async ({ page }) => {
await page.waitForTimeout(2000);
const startHeader = page.locator('.rz-data-grid th:has-text("Start")');
await expect(startHeader).toBeVisible();
});
test('data grid shows End column', async ({ page }) => {
await page.waitForTimeout(2000);
const endHeader = page.locator('.rz-data-grid th:has-text("End")');
await expect(endHeader).toBeVisible();
});
test('data grid shows Branch column', async ({ page }) => {
await page.waitForTimeout(2000);
const branchHeader = page.locator('.rz-data-grid th:has-text("Branch")');
await expect(branchHeader).toBeVisible();
});
test('data grid shows Profit Center column', async ({ page }) => {
await page.waitForTimeout(2000);
const pcHeader = page.locator('.rz-data-grid th:has-text("Profit Center")');
await expect(pcHeader).toBeVisible();
});
test('data grid shows Work Center column', async ({ page }) => {
await page.waitForTimeout(2000);
const wcHeader = page.locator('.rz-data-grid th:has-text("Work Center")');
await expect(wcHeader).toBeVisible();
});
test('data grid shows Was Successful column', async ({ page }) => {
await page.waitForTimeout(2000);
const successHeader = page.locator('.rz-data-grid th:has-text("Was Successful")');
await expect(successHeader).toBeVisible();
});
test('all entity record count columns are present', async ({ page }) => {
await page.waitForTimeout(2000);
const headers = page.locator('.rz-data-grid th');
const headerTexts = await headers.allTextContents();
// Verify key columns exist
expect(headerTexts.some(h => h.includes('Branch'))).toBe(true);
expect(headerTexts.some(h => h.includes('Profit Center'))).toBe(true);
expect(headerTexts.some(h => h.includes('Work Center'))).toBe(true);
expect(headerTexts.some(h => h.includes('Item'))).toBe(true);
expect(headerTexts.some(h => h.includes('Lot'))).toBe(true);
expect(headerTexts.some(h => h.includes('Work Order'))).toBe(true);
});
});
test.describe('Success/Failure Badges', () => {
test('success badge displays with success style (YES)', async ({ page }) => {
await page.waitForTimeout(2000);
const yesBadge = page.locator('.rz-data-grid .rz-badge:has-text("YES")').first();
if (await yesBadge.isVisible({ timeout: 3000 }).catch(() => false)) {
const style = await getBadgeStyle(yesBadge);
expect(style).toBe('success');
}
});
test('failure badge displays with danger style (NO)', async ({ page }) => {
await page.waitForTimeout(2000);
const noBadge = page.locator('.rz-data-grid .rz-badge:has-text("NO")').first();
if (await noBadge.isVisible({ timeout: 3000 }).catch(() => false)) {
const style = await getBadgeStyle(noBadge);
expect(style).toBe('danger');
}
});
test('Was Successful column shows badges not text', async ({ page }) => {
await page.waitForTimeout(2000);
const rowCount = await getDataGridRowCount(page);
if (rowCount > 0) {
// The Was Successful column should contain badges
const successBadges = page.locator('.rz-data-grid tbody .rz-badge');
const badgeCount = await successBadges.count();
// Should have at least one badge (for each row's Was Successful column)
if (rowCount > 0) {
expect(badgeCount).toBeGreaterThan(0);
}
}
});
});
test.describe('Data Grid Features', () => {
test('data grid supports sorting', async ({ page }) => {
await page.waitForTimeout(2000);
// Click on Start column header to sort
const startHeader = page.locator('.rz-data-grid th:has-text("Start")');
await startHeader.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);
// 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);
// Verify the grid is visible and no errors
const dataGrid = page.locator('.rz-data-grid');
await expect(dataGrid).toBeVisible();
const errorNotification = page.locator('.rz-notification-error');
await expect(errorNotification).not.toBeVisible({ timeout: 2000 });
});
test('data grid text is center aligned', async ({ page }) => {
await page.waitForTimeout(2000);
// The grid has Style="text-align: center;"
const dataGrid = page.locator('.rz-data-grid');
const style = await dataGrid.getAttribute('style');
// Verify text-align center is set
if (style) {
expect(style.includes('text-align: center') || style.includes('text-align:center')).toBe(true);
}
});
});
test.describe('Date Formatting', () => {
test('start date is formatted correctly (MM/dd/yyyy hh:mm tt)', async ({ page }) => {
await page.waitForTimeout(2000);
const rowCount = await getDataGridRowCount(page);
if (rowCount > 0) {
// Get text from Start column (first column)
const startCell = page.locator('.rz-data-grid tbody tr').first().locator('td').first();
const dateText = await startCell.textContent();
if (dateText && dateText.trim()) {
// Verify date format matches MM/dd/yyyy hh:mm tt pattern
const datePattern = /^\d{2}\/\d{2}\/\d{4} \d{2}:\d{2} (AM|PM)$/;
expect(dateText.trim()).toMatch(datePattern);
}
}
});
test('end date is formatted correctly when present', async ({ page }) => {
await page.waitForTimeout(2000);
const rowCount = await getDataGridRowCount(page);
if (rowCount > 0) {
// Get text from End column (second column)
const endCell = page.locator('.rz-data-grid tbody tr').first().locator('td').nth(1);
const dateText = await endCell.textContent();
if (dateText && dateText.trim()) {
// Verify date format matches MM/dd/yyyy hh:mm tt pattern
const datePattern = /^\d{2}\/\d{2}\/\d{4} \d{2}:\d{2} (AM|PM)$/;
expect(dateText.trim()).toMatch(datePattern);
}
}
});
});
test.describe('Record Count Display', () => {
test('record count columns show numeric values', async ({ page }) => {
await page.waitForTimeout(2000);
const rowCount = await getDataGridRowCount(page);
if (rowCount > 0) {
// Get text from Branch column (third column)
const branchCell = page.locator('.rz-data-grid tbody tr').first().locator('td').nth(2);
const countText = await branchCell.textContent();
if (countText && countText.trim()) {
// Should be a number
const count = parseInt(countText.trim(), 10);
expect(Number.isInteger(count) || countText.trim() === '').toBe(true);
}
}
});
});
test.describe('Loading State', () => {
test('shows loading indicator while fetching data', async ({ page }) => {
// Navigate fresh to catch loading state
await page.goto('/refresh-status');
await page.waitForLoadState('networkidle', { timeout: 60000 });
// If there's a loading indicator, it should eventually disappear
const loadingIndicator = page.locator('text=Loading refresh status');
// Either loading is visible briefly or already gone
await page.waitForTimeout(3000);
// After waiting, loading should be gone
await expect(loadingIndicator).not.toBeVisible({ timeout: 10000 });
});
});
});
@@ -0,0 +1,204 @@
import { test, expect, Page } from '@playwright/test';
import path from 'path';
const TEST_DATA_DIR = path.join(__dirname, '..', 'test-data');
// Test credentials - FakeAuthService accepts any credentials in development mode
const TEST_USERNAME = 'testuser';
const TEST_PASSWORD = 'testpass';
// Helper to login to the application
async function login(page: Page) {
// Check if we're on the login page
const loginForm = page.locator('text=Authentication Required');
if (await loginForm.isVisible({ timeout: 5000 }).catch(() => false)) {
// Wait for form inputs to be ready
await page.locator('input[name="Username"]').waitFor({ state: 'visible', timeout: 10000 });
// Fill credentials
await page.locator('input[name="Username"]').fill(TEST_USERNAME);
await page.locator('input[name="Password"]').fill(TEST_PASSWORD);
// Click the submit button in the form
await page.locator('button[type="submit"]:has-text("LOGIN")').click();
// Wait for login to process
await page.waitForTimeout(3000);
// After login, force navigation to search page to refresh state
await page.goto('/search');
await page.waitForLoadState('networkidle', { timeout: 60000 });
// If still on login page after navigation, login failed
if (await loginForm.isVisible({ timeout: 2000 }).catch(() => false)) {
throw new Error('Login failed - still on login page after attempt');
}
}
}
// Helper to navigate to search page and wait for Blazor WASM to be ready
async function navigateToSearchPage(page: Page) {
// Navigate to the search page
await page.goto('/search');
// Wait for page to load initially
await page.waitForLoadState('networkidle', { timeout: 60000 });
// Check if we need to login
await login(page);
// Wait for the page to have meaningful content - either Radzen dropdown or Search text
await page.locator('.rz-dropdown').or(page.locator('text=Search Details')).first().waitFor({
state: 'visible',
timeout: 120000
});
// Additional wait for Radzen components to fully initialize
await page.waitForTimeout(2000);
}
// Helper to select search type from dropdown
async function selectSearchType(page: Page, searchType: string) {
// Click the search type dropdown
await page.locator('.rz-dropdown').first().click();
// Wait for dropdown to open and select the option
await page.locator(`text="${searchType}"`).click();
// Wait for the filter panel to appear
await page.waitForTimeout(500);
}
// Helper to upload file via RadzenUpload
async function uploadFile(page: Page, filePath: string) {
// RadzenUpload creates a hidden file input - find it and set the file
const fileInput = page.locator('input[type="file"]');
await fileInput.setInputFiles(filePath);
// Wait for upload to complete (notification appears or grid updates)
await page.waitForTimeout(2000);
}
test.describe('Work Order Search', () => {
test.beforeEach(async ({ page }) => {
await navigateToSearchPage(page);
});
test('TC-010-P01: Upload single work order and verify it appears in grid', async ({ page }) => {
// Select Work Order search type
await selectSearchType(page, 'Work Order');
// Verify Filter by Work Order panel is visible
await expect(page.locator('text=Filter by Work Order')).toBeVisible();
// Enter search name
await page.fill('input[placeholder=" "]', 'TC-010 Single Work Order Test');
// Upload the work order file
const testFile = path.join(TEST_DATA_DIR, 'single_workorder.xlsx');
await uploadFile(page, testFile);
// Verify work order appears in the grid
// Note: The actual work order may not be found in the database if it doesn't exist
// This test verifies the upload mechanism works
const gridText = await page.locator('.rz-data-grid').textContent();
// Check that either the work order appears or "No records" if not in DB
// The important thing is no error notification
const errorNotification = page.locator('.rz-notification-error');
await expect(errorNotification).not.toBeVisible({ timeout: 5000 });
});
test('TC-010-P02: Upload multiple work orders', async ({ page }) => {
await selectSearchType(page, 'Work Order');
const testFile = path.join(TEST_DATA_DIR, 'multiple_workorders.xlsx');
await uploadFile(page, testFile);
// Verify no error
const errorNotification = page.locator('.rz-notification-error');
await expect(errorNotification).not.toBeVisible({ timeout: 5000 });
});
test('TC-010-P03: Download template contains correct format', async ({ page }) => {
await selectSearchType(page, 'Work Order');
// Click download template
const downloadPromise = page.waitForEvent('download');
await page.click('text=Download Template');
const download = await downloadPromise;
expect(download.suggestedFilename()).toContain('.xlsx');
});
test('TC-010-P04: Clear data removes all entries', async ({ page }) => {
await selectSearchType(page, 'Work Order');
// First upload some data
const testFile = path.join(TEST_DATA_DIR, 'single_workorder.xlsx');
await uploadFile(page, testFile);
// Click Clear Data
await page.click('text=Clear Data');
// Wait for and confirm the dialog (uses "OK" button, not "Yes")
await page.waitForSelector('text=Confirm Clear', { timeout: 5000 });
await page.click('button:has-text("OK")');
await page.waitForTimeout(500);
// Verify grid shows "No records to display"
await expect(page.locator('text=No records to display')).toBeVisible();
});
});
test.describe('Component Lot Search', () => {
test.beforeEach(async ({ page }) => {
await navigateToSearchPage(page);
});
test('TC-020-P01: Upload component lot file', async ({ page }) => {
await selectSearchType(page, 'Component Lot');
await expect(page.locator('text=Filter By Component Lot')).toBeVisible();
const testFile = path.join(TEST_DATA_DIR, 'single_lot.xlsx');
await uploadFile(page, testFile);
const errorNotification = page.locator('.rz-notification-error');
await expect(errorNotification).not.toBeVisible({ timeout: 5000 });
});
});
test.describe('Time Span + Profit Center + Item Number Search', () => {
test.beforeEach(async ({ page }) => {
await navigateToSearchPage(page);
});
test('TC-060-P01: Upload item numbers file', async ({ page }) => {
await selectSearchType(page, 'Time Span + Profit Center + Item Number');
await expect(page.locator('text=Filter by Item Number')).toBeVisible();
const testFile = path.join(TEST_DATA_DIR, 'single_item.xlsx');
await uploadFile(page, testFile);
const errorNotification = page.locator('.rz-notification-error');
await expect(errorNotification).not.toBeVisible({ timeout: 5000 });
});
});
test.describe('Time Span + Profit Center + Item/Operation/MIS Search', () => {
test.beforeEach(async ({ page }) => {
await navigateToSearchPage(page);
});
test('TC-070-P01: Upload part operations file', async ({ page }) => {
await selectSearchType(page, 'Time Span + Profit Center + Item/Operation/MIS');
await expect(page.locator('text=Filter By Item/Operation/MIS')).toBeVisible();
const testFile = path.join(TEST_DATA_DIR, 'single_operation.xlsx');
await uploadFile(page, testFile);
const errorNotification = page.locator('.rz-notification-error');
await expect(errorNotification).not.toBeVisible({ timeout: 5000 });
});
});
@@ -0,0 +1,232 @@
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();
});
});
});
@@ -0,0 +1,248 @@
import { test, expect } from '@playwright/test';
import { login, logout, isLoggedIn } from '../helpers/auth.helper';
import { navigateToSearchesDashboard, clickNavLink } from '../helpers/navigation.helper';
import { getDataGridRowCount, doubleClickDataGridRow, getBadgeStyle, clickButton, StatusBadgeStyles } from '../helpers/radzen.helper';
test.describe('Searches Dashboard', () => {
test.beforeEach(async ({ page }) => {
await navigateToSearchesDashboard(page);
});
test.describe('Page Load and Display', () => {
test('page loads and displays search list heading', async ({ page }) => {
// Verify page title
await expect(page).toHaveTitle(/Searches - JDE Scoping Tool/);
// Verify "Searches" heading is visible
await expect(page.locator('h4:has-text("Searches")')).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 }) => {
// Verify no error notification
const errorNotification = page.locator('.rz-notification-error');
await expect(errorNotification).not.toBeVisible({ timeout: 5000 });
});
});
test.describe('Data Grid Columns', () => {
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 Status column', async ({ page }) => {
const statusHeader = page.locator('.rz-data-grid th:has-text("Status")');
await expect(statusHeader).toBeVisible();
});
test('data grid shows Actions column', async ({ page }) => {
const actionsHeader = page.locator('.rz-data-grid th:has-text("Actions")');
await expect(actionsHeader).toBeVisible();
});
test('all expected columns are present', async ({ page }) => {
const headers = page.locator('.rz-data-grid th');
// Get all header texts
const headerTexts = await headers.allTextContents();
// Verify expected columns exist
expect(headerTexts.some(h => h.includes('Name'))).toBe(true);
expect(headerTexts.some(h => h.includes('Submitted'))).toBe(true);
expect(headerTexts.some(h => h.includes('Status'))).toBe(true);
expect(headerTexts.some(h => h.includes('Actions'))).toBe(true);
});
});
test.describe('Action Buttons', () => {
test('Start New Search button is visible', async ({ page }) => {
const newSearchButton = page.locator('button:has-text("Start New Search")');
await expect(newSearchButton).toBeVisible();
});
test('View Search Queue button is visible', async ({ page }) => {
const queueButton = page.locator('button:has-text("View Search Queue")');
await expect(queueButton).toBeVisible();
});
test('clicking Start New Search navigates to search page', async ({ page }) => {
await clickButton(page, 'Start New Search');
// Wait for navigation
await page.waitForLoadState('networkidle', { timeout: 30000 });
// Should navigate to /search
await expect(page).toHaveURL(/\/search$/);
});
test('clicking View Search Queue navigates to queue page', async ({ page }) => {
await clickButton(page, 'View Search Queue');
// Wait for navigation
await page.waitForLoadState('networkidle', { timeout: 30000 });
// Should navigate to /search/queue
await expect(page).toHaveURL(/\/search\/queue/);
});
});
test.describe('Status Badges', () => {
test('status badges display with correct styling for Error status', async ({ page }) => {
// Look for any Error badge in the grid
const errorBadge = page.locator('.rz-data-grid .rz-badge:has-text("Error")').first();
// If an error badge exists, verify its style
if (await errorBadge.isVisible({ timeout: 2000 }).catch(() => false)) {
const style = await getBadgeStyle(errorBadge);
expect(style).toBe('danger');
}
});
test('status badges display with correct styling for Ended status', async ({ page }) => {
const endedBadge = page.locator('.rz-data-grid .rz-badge:has-text("Ended")').first();
if (await endedBadge.isVisible({ timeout: 2000 }).catch(() => false)) {
const style = await getBadgeStyle(endedBadge);
expect(style).toBe('success');
}
});
test('status badges display with correct styling for Running status', 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('status badges display with correct styling for Queued status', 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('Row Interactions', () => {
test('View button is present in Actions column', async ({ page }) => {
const rowCount = await getDataGridRowCount(page);
if (rowCount > 0) {
// Check for View button in first row
const viewButton = page.locator('.rz-data-grid tbody tr').first().locator('button:has-text("View")');
await expect(viewButton).toBeVisible();
}
});
test('clicking View button navigates to search details', async ({ page }) => {
const rowCount = await getDataGridRowCount(page);
if (rowCount > 0) {
// Click View button in first row
const viewButton = page.locator('.rz-data-grid tbody tr').first().locator('button:has-text("View")');
await viewButton.click();
// Wait for navigation
await page.waitForLoadState('networkidle', { timeout: 30000 });
// Should navigate to /search/{id}
await expect(page).toHaveURL(/\/search\/\d+/);
}
});
test('double-click row navigates to search details', async ({ page }) => {
const rowCount = await getDataGridRowCount(page);
if (rowCount > 0) {
// Double-click first row
await doubleClickDataGridRow(page, 0);
// Wait for navigation
await page.waitForLoadState('networkidle', { timeout: 30000 });
// Should navigate to /search/{id}
await expect(page).toHaveURL(/\/search\/\d+/);
}
});
});
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 sort indicator appears (ascending or descending)
const sortIndicator = page.locator('.rz-data-grid th .rz-sortable-column-icon');
// Just verify the click didn't cause an error
const errorNotification = page.locator('.rz-notification-error');
await expect(errorNotification).not.toBeVisible({ timeout: 2000 });
});
test('data grid supports pagination', async ({ page }) => {
// Look for pager component
const pager = page.locator('.rz-pager');
// Pager should be visible (may show even with few records)
await expect(pager).toBeVisible({ timeout: 5000 });
});
test('data grid supports column resize', async ({ page }) => {
// The grid has AllowColumnResize="true"
// Verify resize handles exist
const resizeHandle = page.locator('.rz-data-grid .rz-resizable-handle');
// If there are any resize handles, the feature is enabled
const handleCount = await resizeHandle.count();
// This may be 0 if no columns are resizable in the current view
// Just verify no error occurred
const errorNotification = page.locator('.rz-notification-error');
await expect(errorNotification).not.toBeVisible({ timeout: 2000 });
});
});
test.describe('Empty State', () => {
test('displays appropriate message when no searches exist', async ({ page }) => {
const rowCount = await getDataGridRowCount(page);
if (rowCount === 0) {
// Should show "No records to display" or similar message
const noRecordsMessage = page.locator('text=No records to display');
await expect(noRecordsMessage).toBeVisible();
}
});
});
test.describe('Date Formatting', () => {
test('submitted date is formatted correctly (MM/dd/yyyy hh:mm:ss tt)', async ({ page }) => {
const rowCount = await getDataGridRowCount(page);
if (rowCount > 0) {
// Get text from Submitted column (index 1)
const submittedCell = page.locator('.rz-data-grid tbody tr').first().locator('td').nth(1);
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);
}
}
});
});
});
@@ -0,0 +1,522 @@
import { test, expect } from '@playwright/test';
import { navigateToSearchPage } from '../helpers/navigation.helper';
import { selectSearchType, SearchTypes, enterSearchName, clickSubmitSearch, confirmSubmitSearch } from '../helpers/search-type.helper';
import { setDateRange, setMinDate, setMaxDate, clearDateRange, TestDateRanges } from '../helpers/date-picker.helper';
import { uploadFile, itemNumberConfig, getTestFile, TestFiles, getUploadedItemCount, clearUploadedData, isFileUploadPanelVisible } from '../helpers/file-upload.helper';
import { assertNoErrorNotification, hasSuccessNotification } from '../helpers/radzen.helper';
import { hasValidationErrors, submitAndExpectError, ValidationMessages } from '../helpers/validation.helper';
import path from 'path';
/**
* Test suite for Search Type 140: Time Span + Item Number
*
* This search type allows users to search for work order data within
* a specific date range, filtered by one or more item numbers.
*
* Filters Enabled: Timespan (Start Date, End Date), Item Number
*
* NOTE: Item numbers are uploaded via file (Excel), not autocomplete.
* Valid item numbers are 11-character codes (e.g., 00003300100).
*/
test.describe('Search Type 140: Time Span + Item Number', () => {
test.beforeEach(async ({ page }) => {
await navigateToSearchPage(page);
});
// Valid item number test data
const validItemNumbers = [
'00003200700', '00003200800', '00003200900', '00003201500', '00003205000',
'00003205100', '00003205200', '00003208800', '00003208900', '00003209000',
'00003300100', '00003300200', '00003300300', '00003304900', '00003305000',
];
const TEST_DATA_DIR = path.join(__dirname, '..', 'test-data');
// ============================================================================
// POSITIVE TEST CASES
// ============================================================================
test.describe('Positive Tests', () => {
test('TC-140-P01: Single item number', async ({ page }) => {
// Select search type
await selectSearchType(page, SearchTypes.TIMESPAN_ITEM);
// Verify filter panels are visible
await expect(page.locator('text=Filter by Item Number')).toBeVisible();
await expect(page.locator('text=Filter by Time Span')).toBeVisible();
// Enter search name
await enterSearchName(page, 'TC-140-P01 Single Item');
// Set date range
await setDateRange(page, '2019-01-01', '2020-09-01');
// Upload item number file
const testFile = path.join(TEST_DATA_DIR, 'single_item.xlsx');
await uploadFile(page, itemNumberConfig, testFile);
// Verify item number appears in the list
const itemCount = await getUploadedItemCount(page, itemNumberConfig);
expect(itemCount).toBeGreaterThanOrEqual(1);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Verify no error notification
await assertNoErrorNotification(page);
});
test('TC-140-P02: Multiple item numbers', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_ITEM);
await enterSearchName(page, 'TC-140-P02 Multiple Items');
// Set date range
await setDateRange(page, '2018-01-01', '2020-09-01');
// Upload multiple items file
const testFile = path.join(TEST_DATA_DIR, 'multiple_items.xlsx');
await uploadFile(page, itemNumberConfig, testFile);
// Verify items appear in the list
const itemCount = await getUploadedItemCount(page, itemNumberConfig);
expect(itemCount).toBeGreaterThanOrEqual(1);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-140-P03: Many item numbers', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_ITEM);
await enterSearchName(page, 'TC-140-P03 Many Items');
// Set date range
await setDateRange(page, '2018-01-01', '2019-12-31');
// Upload multiple items file
const testFile = path.join(TEST_DATA_DIR, 'multiple_items.xlsx');
await uploadFile(page, itemNumberConfig, testFile);
// Verify items appear
const itemCount = await getUploadedItemCount(page, itemNumberConfig);
expect(itemCount).toBeGreaterThanOrEqual(1);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-140-P04: Recent date range', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_ITEM);
await enterSearchName(page, 'TC-140-P04 Recent Range');
// Set recent date range
await setDateRange(page, TestDateRanges.RECENT.min, TestDateRanges.RECENT.max);
// Upload item number file
const testFile = path.join(TEST_DATA_DIR, 'single_item.xlsx');
await uploadFile(page, itemNumberConfig, testFile);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-140-P05: Historical date range', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_ITEM);
await enterSearchName(page, 'TC-140-P05 Historical Range');
// Set historical date range
await setDateRange(page, TestDateRanges.HISTORICAL.min, TestDateRanges.HISTORICAL.max);
// Upload item number file
const testFile = path.join(TEST_DATA_DIR, 'single_item.xlsx');
await uploadFile(page, itemNumberConfig, testFile);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-140-P06: Narrow date range (single month)', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_ITEM);
await enterSearchName(page, 'TC-140-P06 Single Month');
// Set narrow date range (single month)
await setDateRange(page, '2020-06-01', '2020-06-30');
// Upload item number file
const testFile = path.join(TEST_DATA_DIR, 'single_item.xlsx');
await uploadFile(page, itemNumberConfig, testFile);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-140-P07: Same start and end date', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_ITEM);
await enterSearchName(page, 'TC-140-P07 Same Day');
// Set same day date range
await setDateRange(page, '2020-05-15', '2020-05-15');
// Upload item number file
const testFile = path.join(TEST_DATA_DIR, 'single_item.xlsx');
await uploadFile(page, itemNumberConfig, testFile);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-140-P08: Items from different series', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_ITEM);
await enterSearchName(page, 'TC-140-P08 Different Series');
// Set date range
await setDateRange(page, '2018-01-01', '2020-09-01');
// Upload multiple items file (contains items from different series)
const testFile = path.join(TEST_DATA_DIR, 'multiple_items.xlsx');
await uploadFile(page, itemNumberConfig, testFile);
// Verify items appear
const itemCount = await getUploadedItemCount(page, itemNumberConfig);
expect(itemCount).toBeGreaterThanOrEqual(1);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-140-P09: Boundary date - start of data range', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_ITEM);
await enterSearchName(page, 'TC-140-P09 Start Boundary');
// Set earliest date boundary
await setDateRange(page, TestDateRanges.START_BOUNDARY.min, TestDateRanges.START_BOUNDARY.max);
// Upload item number file
const testFile = path.join(TEST_DATA_DIR, 'single_item.xlsx');
await uploadFile(page, itemNumberConfig, testFile);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-140-P10: Boundary date - end of data range', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_ITEM);
await enterSearchName(page, 'TC-140-P10 End Boundary');
// Set latest date boundary
await setDateRange(page, TestDateRanges.END_BOUNDARY.min, TestDateRanges.END_BOUNDARY.max);
// Upload item number file
const testFile = path.join(TEST_DATA_DIR, 'single_item.xlsx');
await uploadFile(page, itemNumberConfig, testFile);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-140-P11: Download template', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_ITEM);
// Verify filter panel is visible
await expect(page.locator('text=Filter by Item Number')).toBeVisible();
// Click download template
const panel = page.locator(`.rz-card:has-text("${itemNumberConfig.panelHeader}")`);
const downloadPromise = page.waitForEvent('download');
await panel.locator('button:has-text("Download Template")').click();
const download = await downloadPromise;
expect(download.suggestedFilename()).toContain('.xlsx');
});
test('TC-140-P12: Clear data removes all entries', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_ITEM);
await enterSearchName(page, 'TC-140-P12 Clear Data');
// Set date range
await setDateRange(page, '2019-01-01', '2020-09-01');
// Upload item number file
const testFile = path.join(TEST_DATA_DIR, 'single_item.xlsx');
await uploadFile(page, itemNumberConfig, testFile);
// Verify items uploaded
let itemCount = await getUploadedItemCount(page, itemNumberConfig);
expect(itemCount).toBeGreaterThanOrEqual(1);
// Clear data
await clearUploadedData(page, itemNumberConfig);
// Verify list is empty
itemCount = await getUploadedItemCount(page, itemNumberConfig);
expect(itemCount).toBe(0);
});
});
// ============================================================================
// NEGATIVE TEST CASES
// ============================================================================
test.describe('Negative Tests', () => {
test('TC-140-N01: Missing date range', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_ITEM);
await enterSearchName(page, 'TC-140-N01 No Dates');
// Do NOT set any dates
// Upload item number file
const testFile = path.join(TEST_DATA_DIR, 'single_item.xlsx');
await uploadFile(page, itemNumberConfig, testFile);
// Attempt to submit
await submitAndExpectError(page);
});
test('TC-140-N02: Missing start date only', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_ITEM);
await enterSearchName(page, 'TC-140-N02 No Start Date');
// Only set maximum date
await setMaxDate(page, '2020-09-01');
// Upload item number file
const testFile = path.join(TEST_DATA_DIR, 'single_item.xlsx');
await uploadFile(page, itemNumberConfig, testFile);
// Attempt to submit
await submitAndExpectError(page);
});
test('TC-140-N03: Missing end date only', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_ITEM);
await enterSearchName(page, 'TC-140-N03 No End Date');
// Only set minimum date
await setMinDate(page, '2019-01-01');
// Upload item number file
const testFile = path.join(TEST_DATA_DIR, 'single_item.xlsx');
await uploadFile(page, itemNumberConfig, testFile);
// Attempt to submit
await submitAndExpectError(page);
});
test('TC-140-N04: Missing item number', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_ITEM);
await enterSearchName(page, 'TC-140-N04 No Item');
// Set valid date range
await setDateRange(page, '2019-01-01', '2020-09-01');
// Do NOT upload any items
// Verify item list is empty
const itemCount = await getUploadedItemCount(page, itemNumberConfig);
expect(itemCount).toBe(0);
// Attempt to submit
await submitAndExpectError(page);
});
test('TC-140-N05: Start date after end date', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_ITEM);
await enterSearchName(page, 'TC-140-N05 Invalid Date Range');
// Set invalid date range (start after end)
await setDateRange(page, '2020-09-01', '2019-01-01');
// Upload item number file
const testFile = path.join(TEST_DATA_DIR, 'single_item.xlsx');
await uploadFile(page, itemNumberConfig, testFile);
// Attempt to submit
await submitAndExpectError(page);
});
test('TC-140-N06: Empty item number value', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_ITEM);
await enterSearchName(page, 'TC-140-N06 Empty Item');
// Set valid date range
await setDateRange(page, '2019-01-01', '2020-09-01');
// Try to upload empty file
const testFile = path.join(TEST_DATA_DIR, 'empty_file.xlsx');
await uploadFile(page, itemNumberConfig, testFile);
// Wait for upload processing
await page.waitForTimeout(1000);
// Verify item list is empty
const itemCount = await getUploadedItemCount(page, itemNumberConfig);
expect(itemCount).toBe(0);
// Attempt to submit
await submitAndExpectError(page);
});
test('TC-140-N07: Missing search name', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_ITEM);
// Do NOT enter search name
// Set valid date range
await setDateRange(page, '2019-01-01', '2020-09-01');
// Upload item number file
const testFile = path.join(TEST_DATA_DIR, 'single_item.xlsx');
await uploadFile(page, itemNumberConfig, testFile);
// Attempt to submit
await submitAndExpectError(page);
// Verify user remains on the page
await expect(page.locator('text=Filter by Item Number')).toBeVisible();
});
test('TC-140-N08: Whitespace-only item number', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_ITEM);
await enterSearchName(page, 'TC-140-N08 Whitespace Item');
// Set valid date range
await setDateRange(page, '2019-01-01', '2020-09-01');
// Note: For file uploads, whitespace-only entries would be in the file
// This test verifies that empty/invalid uploads don't create valid entries
// Don't upload any file - verify empty state
const itemCount = await getUploadedItemCount(page, itemNumberConfig);
expect(itemCount).toBe(0);
// Attempt to submit
await submitAndExpectError(page);
});
test('TC-140-N09: No search type selected', async ({ page }) => {
// Enter search name without selecting search type
await enterSearchName(page, 'TC-140-N09 No Type');
// Verify filter panels are not visible
const itemPanelVisible = await isFileUploadPanelVisible(page, itemNumberConfig);
expect(itemPanelVisible).toBe(false);
// Attempt to submit
await submitAndExpectError(page);
});
test('TC-140-N10: Invalid date format', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_ITEM);
await enterSearchName(page, 'TC-140-N10 Invalid Date Format');
// Try to enter invalid date format
const minDateInput = page.locator('input[name="MinimumDt"]');
await minDateInput.fill('31-12-2019');
await setMaxDate(page, '2020-09-01');
// Upload item number file
const testFile = path.join(TEST_DATA_DIR, 'single_item.xlsx');
await uploadFile(page, itemNumberConfig, testFile);
// Attempt to submit
await submitAndExpectError(page);
});
test('TC-140-N11: Future date range', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_ITEM);
await enterSearchName(page, 'TC-140-N11 Future Dates');
// Set future date range
await setDateRange(page, TestDateRanges.FUTURE.min, TestDateRanges.FUTURE.max);
// Upload item number file
const testFile = path.join(TEST_DATA_DIR, 'single_item.xlsx');
await uploadFile(page, itemNumberConfig, testFile);
// Submit search - may be accepted but will return no results
await clickSubmitSearch(page);
// Check if there's a validation error or if it proceeds
const hasErrors = await hasValidationErrors(page);
if (!hasErrors) {
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
}
});
test('TC-140-N12: Invalid file format', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_ITEM);
await enterSearchName(page, 'TC-140-N12 Invalid File Format');
// Set valid date range
await setDateRange(page, '2019-01-01', '2020-09-01');
// Try to upload invalid format file
const testFile = path.join(TEST_DATA_DIR, 'invalid_format.txt');
await uploadFile(page, itemNumberConfig, testFile);
// Wait for upload processing
await page.waitForTimeout(1000);
// Either an error notification should appear or items should not be uploaded
const itemCount = await getUploadedItemCount(page, itemNumberConfig);
expect(itemCount).toBe(0);
});
test('TC-140-N13: Whitespace-only search name', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_ITEM);
// Enter whitespace-only search name
await enterSearchName(page, ' ');
// Set valid date range
await setDateRange(page, '2019-01-01', '2020-09-01');
// Upload item number file
const testFile = path.join(TEST_DATA_DIR, 'single_item.xlsx');
await uploadFile(page, itemNumberConfig, testFile);
// Attempt to submit
await submitAndExpectError(page);
});
test('TC-140-N14: Missing all required filters', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_ITEM);
await enterSearchName(page, 'TC-140-N14 No Filters');
// Do NOT set any dates
// Do NOT upload any items
// Attempt to submit
await submitAndExpectError(page);
});
});
});
@@ -0,0 +1,469 @@
import { test, expect } from '@playwright/test';
import { navigateToSearchPage } from '../helpers/navigation.helper';
import { selectSearchType, SearchTypes, enterSearchName, clickSubmitSearch, confirmSubmitSearch } from '../helpers/search-type.helper';
import { setDateRange, setMinDate, setMaxDate, clearDateRange, TestDateRanges } from '../helpers/date-picker.helper';
import {
addOperator,
addOperators,
clearAutocompleteItems,
operatorConfig,
getAutocompleteItemCount,
removeAutocompleteItem,
isAutocompletePanelVisible,
TestAutocompleteData,
} from '../helpers/autocomplete.helper';
import { assertNoErrorNotification, hasSuccessNotification } from '../helpers/radzen.helper';
import { hasValidationErrors, submitAndExpectError, ValidationMessages } from '../helpers/validation.helper';
/**
* Test suite for Search Type 50: Time Span + Operator
*
* This search type allows users to find work orders within a specified
* date range that were processed by specific operators.
*
* Filters Enabled: Timespan (Min Date, Max Date), Operator
*/
test.describe('Search Type 50: Time Span + Operator', () => {
test.beforeEach(async ({ page }) => {
await navigateToSearchPage(page);
});
// Valid operator test data (user IDs)
const validOperators = [
'ADAMSSN', 'AGNEWA', 'AGNEWL', 'ALASMARB', 'ALEXIUCG',
'ALLENHY', 'ALLENNI', 'ALURUM', 'ALVESM1', 'APONTEVE',
'ARCHILAHI', 'ARGUELLC', 'ASHARK', 'ASLANESA', 'AVRAAMIL',
'AYINDED', 'AYOUBR', 'BACKL', 'BAIZEJ', 'BAKERB',
];
// ============================================================================
// POSITIVE TEST CASES
// ============================================================================
test.describe('Positive Tests', () => {
test('TC-050-P01: Single operator with valid date range', async ({ page }) => {
// Select search type
await selectSearchType(page, SearchTypes.TIMESPAN_OPERATOR);
// Verify filter panels are visible
await expect(page.locator('text=Filter by Operator')).toBeVisible();
await expect(page.locator('text=Filter by Time Span')).toBeVisible();
// Enter search name
await enterSearchName(page, 'TC-050-P01 Single Operator Test');
// Set date range
await setDateRange(page, '2019-01-01', '2019-12-31');
// Add operator
await addOperator(page, 'ADAMSSN');
// Verify operator appears in the list
const itemCount = await getAutocompleteItemCount(page, operatorConfig);
expect(itemCount).toBe(1);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Verify no error notification
await assertNoErrorNotification(page);
});
test('TC-050-P02: Multiple operators with valid date range', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_OPERATOR);
await enterSearchName(page, 'TC-050-P02 Multiple Operators Test');
// Set date range
await setDateRange(page, '2018-01-01', '2019-12-31');
// Add multiple operators
await addOperators(page, ['ADAMSSN', 'AGNEWA', 'ALEXIUCG']);
// Verify all operators appear in the list
const itemCount = await getAutocompleteItemCount(page, operatorConfig);
expect(itemCount).toBe(3);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-050-P03: Recent date range with single operator', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_OPERATOR);
await enterSearchName(page, 'TC-050-P03 Recent Date Range Test');
// Set recent date range
await setDateRange(page, TestDateRanges.RECENT.min, TestDateRanges.RECENT.max);
// Add operator
await addOperator(page, 'APONTEVE');
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-050-P04: Historical date range with multiple operators', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_OPERATOR);
await enterSearchName(page, 'TC-050-P04 Historical Range Test');
// Set historical date range
await setDateRange(page, TestDateRanges.HISTORICAL.min, TestDateRanges.HISTORICAL.max);
// Add multiple operators
await addOperators(page, ['BACKL', 'BAIZEJ']);
// Verify operators in list
const itemCount = await getAutocompleteItemCount(page, operatorConfig);
expect(itemCount).toBe(2);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-050-P05: Same day date range', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_OPERATOR);
await enterSearchName(page, 'TC-050-P05 Same Day Test');
// Set same day date range
await setDateRange(page, '2019-06-15', '2019-06-15');
// Add operator
await addOperator(page, 'ALLENHY');
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-050-P06: Maximum number of operators', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_OPERATOR);
await enterSearchName(page, 'TC-050-P06 Many Operators Test');
// Set date range
await setDateRange(page, '2018-01-01', '2020-09-01');
// Add 10 operators
const tenOperators = validOperators.slice(0, 10);
await addOperators(page, tenOperators);
// Verify all operators in list
const itemCount = await getAutocompleteItemCount(page, operatorConfig);
expect(itemCount).toBe(10);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-050-P07: Boundary date - start of data range', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_OPERATOR);
await enterSearchName(page, 'TC-050-P07 Start Boundary');
// Set earliest date boundary
await setDateRange(page, TestDateRanges.START_BOUNDARY.min, TestDateRanges.START_BOUNDARY.max);
// Add operator
await addOperator(page, 'ADAMSSN');
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-050-P08: Boundary date - end of data range', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_OPERATOR);
await enterSearchName(page, 'TC-050-P08 End Boundary');
// Set latest date boundary
await setDateRange(page, TestDateRanges.END_BOUNDARY.min, TestDateRanges.END_BOUNDARY.max);
// Add operator
await addOperator(page, 'AGNEWA');
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-050-P09: Operator remove and re-add', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_OPERATOR);
await enterSearchName(page, 'TC-050-P09 Operator Remove Re-add');
// Set date range
await setDateRange(page, '2019-01-01', '2019-12-31');
// Add operators
await addOperator(page, 'ADAMSSN');
await addOperator(page, 'AGNEWA');
// Verify both are added
let itemCount = await getAutocompleteItemCount(page, operatorConfig);
expect(itemCount).toBe(2);
// Remove first operator
await removeAutocompleteItem(page, operatorConfig, 0);
// Verify only one remains
itemCount = await getAutocompleteItemCount(page, operatorConfig);
expect(itemCount).toBe(1);
// Add another operator
await addOperator(page, 'ALEXIUCG');
// Verify two operators in list
itemCount = await getAutocompleteItemCount(page, operatorConfig);
expect(itemCount).toBe(2);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
});
// ============================================================================
// NEGATIVE TEST CASES
// ============================================================================
test.describe('Negative Tests', () => {
test('TC-050-N01: Missing search name', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_OPERATOR);
// Do NOT enter search name
// Set valid date range
await setDateRange(page, '2019-01-01', '2019-12-31');
// Add operator
await addOperator(page, 'ADAMSSN');
// Attempt to submit
await submitAndExpectError(page);
// Verify user remains on the page
await expect(page.locator('text=Filter by Operator')).toBeVisible();
});
test('TC-050-N02: Missing operator', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_OPERATOR);
await enterSearchName(page, 'TC-050-N02 Missing Operator Test');
// Set valid date range
await setDateRange(page, '2019-01-01', '2019-12-31');
// Do NOT add any operators
// Verify operator list is empty
const itemCount = await getAutocompleteItemCount(page, operatorConfig);
expect(itemCount).toBe(0);
// Attempt to submit
await submitAndExpectError(page);
});
test('TC-050-N03: Missing minimum date', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_OPERATOR);
await enterSearchName(page, 'TC-050-N03 Missing Min Date Test');
// Only set maximum date
await setMaxDate(page, '2019-12-31');
// Add operator
await addOperator(page, 'ADAMSSN');
// Attempt to submit
await submitAndExpectError(page);
});
test('TC-050-N04: Missing maximum date', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_OPERATOR);
await enterSearchName(page, 'TC-050-N04 Missing Max Date Test');
// Only set minimum date
await setMinDate(page, '2019-01-01');
// Add operator
await addOperator(page, 'ADAMSSN');
// Attempt to submit
await submitAndExpectError(page);
});
test('TC-050-N05: Invalid date range (min > max)', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_OPERATOR);
await enterSearchName(page, 'TC-050-N05 Invalid Date Range Test');
// Set invalid date range (min date after max date)
await setDateRange(page, '2020-01-01', '2019-01-01');
// Add operator
await addOperator(page, 'ADAMSSN');
// Attempt to submit
await submitAndExpectError(page);
});
test('TC-050-N06: Invalid date format', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_OPERATOR);
await enterSearchName(page, 'TC-050-N06 Invalid Date Format Test');
// Try to enter invalid date format
const minDateInput = page.locator('input[name="MinimumDt"]');
await minDateInput.fill('13/45/2019');
await setMaxDate(page, '2019-12-31');
// Add operator
await addOperator(page, 'ADAMSSN');
// Attempt to submit
await submitAndExpectError(page);
});
test('TC-050-N07: Empty operator value', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_OPERATOR);
await enterSearchName(page, 'TC-050-N07 Empty Operator Test');
// Set valid date range
await setDateRange(page, '2019-01-01', '2019-12-31');
// Try to add empty operator
const panel = page.locator(`.rz-card:has-text("${operatorConfig.panelHeader}")`);
const autocomplete = panel.locator('.rz-autocomplete input');
await autocomplete.fill('');
// Try to click Add button (should not add empty value)
const addButton = panel.locator('button:has-text("Add")');
await addButton.click();
await page.waitForTimeout(300);
// Verify operator list is still empty
const itemCount = await getAutocompleteItemCount(page, operatorConfig);
expect(itemCount).toBe(0);
// Attempt to submit
await submitAndExpectError(page);
});
test('TC-050-N08: Whitespace-only search name', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_OPERATOR);
// Enter whitespace-only search name
await enterSearchName(page, ' ');
// Set valid date range
await setDateRange(page, '2019-01-01', '2019-12-31');
// Add operator
await addOperator(page, 'ADAMSSN');
// Attempt to submit
await submitAndExpectError(page);
});
test('TC-050-N09: Missing all required filters', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_OPERATOR);
await enterSearchName(page, 'TC-050-N09 No Filters Test');
// Do NOT set any dates
// Do NOT add any operators
// Attempt to submit
await submitAndExpectError(page);
});
test('TC-050-N10: Invalid operator code', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_OPERATOR);
await enterSearchName(page, 'TC-050-N10 Invalid Operator Code');
// Set valid date range
await setDateRange(page, '2019-01-01', '2019-12-31');
// Try to add invalid operator
const panel = page.locator(`.rz-card:has-text("${operatorConfig.panelHeader}")`);
const autocomplete = panel.locator('.rz-autocomplete input');
await autocomplete.fill('INVALIDOPERATOR123');
// Wait for autocomplete to search
await page.waitForTimeout(500);
// Verify no autocomplete suggestions appear
const dropdown = page.locator('.rz-autocomplete-list');
const dropdownVisible = await dropdown.isVisible({ timeout: 2000 }).catch(() => false);
if (dropdownVisible) {
const items = dropdown.locator('.rz-autocomplete-list-item');
const count = await items.count();
expect(count).toBe(0);
}
// Verify operator list is still empty
const itemCount = await getAutocompleteItemCount(page, operatorConfig);
expect(itemCount).toBe(0);
});
test('TC-050-N11: Future date range', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_OPERATOR);
await enterSearchName(page, 'TC-050-N11 Future Dates');
// Set future date range
await setDateRange(page, TestDateRanges.FUTURE.min, TestDateRanges.FUTURE.max);
// Add operator
await addOperator(page, 'ADAMSSN');
// Submit search - may be accepted but will return no results
await clickSubmitSearch(page);
// Check if there's a validation error or if it proceeds
const hasErrors = await hasValidationErrors(page);
if (!hasErrors) {
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
}
});
test('TC-050-N12: Duplicate operator entry', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_OPERATOR);
await enterSearchName(page, 'TC-050-N12 Duplicate Operator');
// Set valid date range
await setDateRange(page, '2019-01-01', '2019-12-31');
// Add operator
await addOperator(page, 'ADAMSSN');
// Verify one entry
let itemCount = await getAutocompleteItemCount(page, operatorConfig);
expect(itemCount).toBe(1);
// Attempt to add the same operator again
await addOperator(page, 'ADAMSSN');
// Wait for any duplicate handling
await page.waitForTimeout(500);
// Verify only one entry remains (duplicate should be rejected)
itemCount = await getAutocompleteItemCount(page, operatorConfig);
expect(itemCount).toBe(1);
});
});
});
@@ -0,0 +1,265 @@
import { test, expect, Page } from '@playwright/test';
import { navigateToSearchPage } from '../helpers/navigation.helper';
import { selectSearchType, SearchTypes, enterSearchName, clickSubmitSearch, confirmSubmitSearch } from '../helpers/search-type.helper';
import { setDateRange, setMinDate, setMaxDate, TestDateRanges } from '../helpers/date-picker.helper';
import { addProfitCenter, addProfitCenters, profitCenterConfig, TestAutocompleteData, getAutocompleteItemCount } from '../helpers/autocomplete.helper';
import { assertNoErrorNotification, waitForNotification } from '../helpers/radzen.helper';
import { hasValidationErrors, submitAndExpectError } from '../helpers/validation.helper';
/**
* Test suite for Search Type 90: Time Span + Profit Center + Extract MIS
*
* This search type allows users to search by a date range combined with profit center(s)
* and the Extract MIS boolean flag. When Extract MIS is enabled, the search extracts
* MIS (Manufacturing Information System) data associated with work orders.
*
* Filters Enabled:
* - Timespan (Min Date, Max Date)
* - Profit Center
* - Extract MIS (checkbox)
*/
/**
* Helper to set the Extract MIS checkbox state
* @param page - Playwright page object
* @param enabled - Whether to enable (true) or disable (false) Extract MIS
*/
async function setExtractMIS(page: Page, enabled: boolean): Promise<void> {
// Find the Extract MIS checkbox panel
const extractMisPanel = page.locator('.rz-card:has-text("Extract MIS")').or(page.locator(':has-text("Extract MIS")'));
// Find checkbox within the panel or by label
const checkbox = page.locator('input[type="checkbox"]').first();
// Get current state
const isChecked = await checkbox.isChecked();
// Toggle if needed
if (isChecked !== enabled) {
await checkbox.click();
await page.waitForTimeout(300);
}
}
/**
* Helper to check if Extract MIS is enabled
* @param page - Playwright page object
* @returns true if Extract MIS checkbox is checked
*/
async function isExtractMISEnabled(page: Page): Promise<boolean> {
const checkbox = page.locator('input[type="checkbox"]').first();
return await checkbox.isChecked();
}
test.describe('Search Type 90: Time Span + Profit Center + Extract MIS', () => {
test.beforeEach(async ({ page }) => {
await navigateToSearchPage(page);
await selectSearchType(page, SearchTypes.TIMESPAN_PC_EXTRACTMIS);
});
test.describe('Positive Test Cases', () => {
test('TC-090-P01: Single Profit Center with Extract MIS Enabled', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 090-P01 Single PC Extract MIS');
// Set date range
await setDateRange(page, TestDateRanges.RECENT.min, TestDateRanges.RECENT.max);
// Add profit center
await addProfitCenter(page, '1PM');
// Enable Extract MIS checkbox
await setExtractMIS(page, true);
// Verify checkbox is enabled
expect(await isExtractMISEnabled(page)).toBe(true);
// Verify no error notification
await assertNoErrorNotification(page);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Verify success notification
await waitForNotification(page, 'success', 10000);
});
test('TC-090-P02: Multiple Profit Centers with Extract MIS Enabled', async ({ page }) => {
await enterSearchName(page, 'Test 090-P02 Multiple PC Extract MIS');
await setDateRange(page, TestDateRanges.MID_RANGE.min, TestDateRanges.MID_RANGE.max);
// Add multiple profit centers
await addProfitCenters(page, ['1AM', '1BM', '1CM']);
// Enable Extract MIS
await setExtractMIS(page, true);
// Verify all profit centers were added
const pcCount = await getAutocompleteItemCount(page, profitCenterConfig);
expect(pcCount).toBe(3);
await assertNoErrorNotification(page);
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await waitForNotification(page, 'success', 10000);
});
test('TC-090-P03: Single Profit Center with Extract MIS Disabled', async ({ page }) => {
await enterSearchName(page, 'Test 090-P03 Single PC No Extract MIS');
await setDateRange(page, '2019-01-01', '2019-12-31');
await addProfitCenter(page, '2DM');
// Disable Extract MIS
await setExtractMIS(page, false);
// Verify checkbox is disabled
expect(await isExtractMISEnabled(page)).toBe(false);
await assertNoErrorNotification(page);
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await waitForNotification(page, 'success', 10000);
});
test('TC-090-P04: Historical Date Range with Multiple Profit Centers', async ({ page }) => {
await enterSearchName(page, 'Test 090-P04 Historical Multi PC');
await setDateRange(page, TestDateRanges.HISTORICAL.min, TestDateRanges.HISTORICAL.max);
// Add multiple profit centers
await addProfitCenters(page, ['3TM', '4IM']);
// Enable Extract MIS
await setExtractMIS(page, true);
const pcCount = await getAutocompleteItemCount(page, profitCenterConfig);
expect(pcCount).toBe(2);
await assertNoErrorNotification(page);
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await waitForNotification(page, 'success', 10000);
});
test('TC-090-P05: All Available Profit Centers', async ({ page }) => {
await enterSearchName(page, 'Test 090-P05 All Profit Centers');
await setDateRange(page, '2018-01-01', '2020-09-01');
// Add all profit centers
await addProfitCenters(page, TestAutocompleteData.profitCenters);
// Enable Extract MIS
await setExtractMIS(page, true);
// Verify all 9 profit centers were added
const pcCount = await getAutocompleteItemCount(page, profitCenterConfig);
expect(pcCount).toBe(9);
await assertNoErrorNotification(page);
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await waitForNotification(page, 'success', 10000);
});
test('TC-090-P06: Same Day Date Range with Extract MIS', async ({ page }) => {
await enterSearchName(page, 'Test 090-P06 Same Day');
await setDateRange(page, TestDateRanges.SAME_DAY.min, TestDateRanges.SAME_DAY.max);
await addProfitCenter(page, '1CM');
await setExtractMIS(page, true);
await assertNoErrorNotification(page);
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await waitForNotification(page, 'success', 10000);
});
});
test.describe('Negative Test Cases', () => {
test('TC-090-N01: Missing Required Date Range', async ({ page }) => {
await enterSearchName(page, 'Test 090-N01 Missing Dates');
// Leave minimum date empty
// Leave maximum date empty
await addProfitCenter(page, '1PM');
await setExtractMIS(page, true);
await submitAndExpectError(page);
});
test('TC-090-N02: Missing Profit Center', async ({ page }) => {
await enterSearchName(page, 'Test 090-N02 Missing Profit Center');
await setDateRange(page, TestDateRanges.RECENT.min, TestDateRanges.RECENT.max);
// Do NOT add any profit center
await setExtractMIS(page, true);
await submitAndExpectError(page);
});
test('TC-090-N03: Invalid Date Range (End Before Start)', async ({ page }) => {
await enterSearchName(page, 'Test 090-N03 Invalid Date Range');
await setDateRange(page, TestDateRanges.INVALID_REVERSED.min, TestDateRanges.INVALID_REVERSED.max);
await addProfitCenter(page, '1PM');
await setExtractMIS(page, true);
await submitAndExpectError(page);
});
test('TC-090-N04: Missing Minimum Date Only', async ({ page }) => {
await enterSearchName(page, 'Test 090-N04 Missing Min Date');
// Leave minimum date empty
await setMaxDate(page, '2020-09-01');
await addProfitCenter(page, '1AM');
await setExtractMIS(page, true);
await submitAndExpectError(page);
});
test('TC-090-N05: Missing Maximum Date Only', async ({ page }) => {
await enterSearchName(page, 'Test 090-N05 Missing Max Date');
await setMinDate(page, '2020-01-01');
// Leave maximum date empty
await addProfitCenter(page, '1BM');
await setExtractMIS(page, true);
await submitAndExpectError(page);
});
test('TC-090-N06: Missing Search Name', async ({ page }) => {
// Leave search name empty
await setDateRange(page, TestDateRanges.RECENT.min, TestDateRanges.RECENT.max);
await addProfitCenter(page, '1PM');
await setExtractMIS(page, true);
await submitAndExpectError(page);
});
test('TC-090-N07: Whitespace-Only Search Name', async ({ page }) => {
await enterSearchName(page, ' ');
await setDateRange(page, TestDateRanges.RECENT.min, TestDateRanges.RECENT.max);
await addProfitCenter(page, '1PM');
await setExtractMIS(page, true);
await submitAndExpectError(page);
});
test('TC-090-N08: Missing All Required Filters', async ({ page }) => {
await enterSearchName(page, 'Test 090-N08 No Filters');
// Leave minimum date empty
// Leave maximum date empty
// Do not add any profit centers
// Extract MIS is just a flag, doesn't cause validation error by itself
await clickSubmitSearch(page);
await page.waitForTimeout(1000);
// Should have validation errors
expect(await hasValidationErrors(page)).toBe(true);
});
});
});
@@ -0,0 +1,262 @@
import { test, expect } from '@playwright/test';
import { navigateToSearchPage } from '../helpers/navigation.helper';
import { selectSearchType, SearchTypes, enterSearchName, clickSubmitSearch, confirmSubmitSearch } from '../helpers/search-type.helper';
import { setDateRange, setMinDate, setMaxDate, clearMinDate, clearMaxDate, TestDateRanges } from '../helpers/date-picker.helper';
import { addProfitCenter, addProfitCenters, profitCenterConfig, TestAutocompleteData, getAutocompleteItemCount } from '../helpers/autocomplete.helper';
import { uploadFile, itemNumberConfig, getTestFile, TestFiles, getUploadedItemCount } from '../helpers/file-upload.helper';
import { assertNoErrorNotification, waitForNotification } from '../helpers/radzen.helper';
import { hasValidationErrors, submitAndExpectError, ValidationMessages } from '../helpers/validation.helper';
/**
* Test suite for Search Type 60: Time Span + Profit Center + Item Number
*
* This search type allows users to find work orders within a specified date range,
* filtered by profit center (branch code) and item number.
*
* Filters Enabled:
* - Timespan (Min Date, Max Date)
* - Profit Center
* - Item Number (via file upload)
*/
test.describe('Search Type 60: Time Span + Profit Center + Item Number', () => {
test.beforeEach(async ({ page }) => {
await navigateToSearchPage(page);
await selectSearchType(page, SearchTypes.TIMESPAN_PC_ITEM);
});
test.describe('Positive Test Cases', () => {
test('TC-060-P01: Single Profit Center and Single Item Number', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Type60 Single PC and Item Test');
// Set date range
await setDateRange(page, '2018-01-01', '2019-12-31');
// Add profit center
await addProfitCenter(page, '1PM');
// Upload item numbers file
await uploadFile(page, itemNumberConfig, getTestFile(TestFiles.SINGLE_ITEM));
// Verify no error notification
await assertNoErrorNotification(page);
// Verify item was uploaded
const itemCount = await getUploadedItemCount(page, itemNumberConfig);
expect(itemCount).toBeGreaterThan(0);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Verify success notification
await waitForNotification(page, 'success', 10000);
});
test('TC-060-P02: Multiple Profit Centers with Single Item Number', async ({ page }) => {
await enterSearchName(page, 'Type60 Multiple PC Test');
await setDateRange(page, '2018-01-01', '2020-09-01');
// Add multiple profit centers
await addProfitCenters(page, ['1AM', '1BM', '1CM']);
// Upload single item file
await uploadFile(page, itemNumberConfig, getTestFile(TestFiles.SINGLE_ITEM));
// Verify all profit centers were added
const pcCount = await getAutocompleteItemCount(page, profitCenterConfig);
expect(pcCount).toBe(3);
await assertNoErrorNotification(page);
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await waitForNotification(page, 'success', 10000);
});
test('TC-060-P03: Single Profit Center with Multiple Item Numbers', async ({ page }) => {
await enterSearchName(page, 'Type60 Multiple Items Test');
await setDateRange(page, '2019-01-01', '2019-12-31');
await addProfitCenter(page, '2DM');
// Upload multiple items file
await uploadFile(page, itemNumberConfig, getTestFile(TestFiles.MULTIPLE_ITEMS));
const itemCount = await getUploadedItemCount(page, itemNumberConfig);
expect(itemCount).toBeGreaterThan(1);
await assertNoErrorNotification(page);
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await waitForNotification(page, 'success', 10000);
});
test('TC-060-P04: Multiple Profit Centers with Multiple Item Numbers', async ({ page }) => {
await enterSearchName(page, 'Type60 Multiple PC and Items Test');
await setDateRange(page, '2018-01-01', '2020-09-01');
// Add multiple profit centers
await addProfitCenters(page, ['1PM', '2SM', '3TM']);
// Upload multiple items file
await uploadFile(page, itemNumberConfig, getTestFile(TestFiles.MULTIPLE_ITEMS));
const pcCount = await getAutocompleteItemCount(page, profitCenterConfig);
expect(pcCount).toBe(3);
const itemCount = await getUploadedItemCount(page, itemNumberConfig);
expect(itemCount).toBeGreaterThan(1);
await assertNoErrorNotification(page);
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await waitForNotification(page, 'success', 10000);
});
test('TC-060-P05: Recent Date Range', async ({ page }) => {
await enterSearchName(page, 'Type60 Recent Range Test');
await setDateRange(page, TestDateRanges.RECENT.min, TestDateRanges.RECENT.max);
await addProfitCenter(page, '4IM');
await uploadFile(page, itemNumberConfig, getTestFile(TestFiles.SINGLE_ITEM));
await assertNoErrorNotification(page);
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await waitForNotification(page, 'success', 10000);
});
test('TC-060-P06: Historical Date Range', async ({ page }) => {
await enterSearchName(page, 'Type60 Historical Range Test');
await setDateRange(page, TestDateRanges.HISTORICAL.min, TestDateRanges.HISTORICAL.max);
await addProfitCenter(page, '5SM');
await uploadFile(page, itemNumberConfig, getTestFile(TestFiles.SINGLE_ITEM));
await assertNoErrorNotification(page);
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await waitForNotification(page, 'success', 10000);
});
test('TC-060-P07: Same Day Date Range', async ({ page }) => {
await enterSearchName(page, 'Type60 Same Day Test');
await setDateRange(page, '2019-06-15', '2019-06-15');
await addProfitCenter(page, '1AM');
await uploadFile(page, itemNumberConfig, getTestFile(TestFiles.SINGLE_ITEM));
await assertNoErrorNotification(page);
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await waitForNotification(page, 'success', 10000);
});
test('TC-060-P08: All Profit Centers', async ({ page }) => {
await enterSearchName(page, 'Type60 All Profit Centers Test');
await setDateRange(page, '2018-01-01', '2020-09-01');
// Add all profit centers
await addProfitCenters(page, TestAutocompleteData.profitCenters);
await uploadFile(page, itemNumberConfig, getTestFile(TestFiles.SINGLE_ITEM));
// Verify all 9 profit centers were added
const pcCount = await getAutocompleteItemCount(page, profitCenterConfig);
expect(pcCount).toBe(9);
await assertNoErrorNotification(page);
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await waitForNotification(page, 'success', 10000);
});
});
test.describe('Negative Test Cases', () => {
test('TC-060-N01: Missing Search Name', async ({ page }) => {
// Leave search name empty
await setDateRange(page, '2018-01-01', '2019-12-31');
await addProfitCenter(page, '1PM');
await uploadFile(page, itemNumberConfig, getTestFile(TestFiles.SINGLE_ITEM));
await submitAndExpectError(page);
});
test('TC-060-N02: Missing Profit Center', async ({ page }) => {
await enterSearchName(page, 'Type60 Missing PC Test');
await setDateRange(page, '2018-01-01', '2019-12-31');
// Do not add any profit centers
await uploadFile(page, itemNumberConfig, getTestFile(TestFiles.SINGLE_ITEM));
await submitAndExpectError(page);
});
test('TC-060-N03: Missing Item Number', async ({ page }) => {
await enterSearchName(page, 'Type60 Missing Item Test');
await setDateRange(page, '2018-01-01', '2019-12-31');
await addProfitCenter(page, '1PM');
// Do not upload any item numbers
await submitAndExpectError(page);
});
test('TC-060-N04: Missing Minimum Date', async ({ page }) => {
await enterSearchName(page, 'Type60 Missing Min Date Test');
// Only set max date
await setMaxDate(page, '2019-12-31');
await addProfitCenter(page, '1PM');
await uploadFile(page, itemNumberConfig, getTestFile(TestFiles.SINGLE_ITEM));
await submitAndExpectError(page);
});
test('TC-060-N05: Missing Maximum Date', async ({ page }) => {
await enterSearchName(page, 'Type60 Missing Max Date Test');
// Only set min date
await setMinDate(page, '2018-01-01');
await addProfitCenter(page, '1PM');
await uploadFile(page, itemNumberConfig, getTestFile(TestFiles.SINGLE_ITEM));
await submitAndExpectError(page);
});
test('TC-060-N06: Invalid Date Range (Min > Max)', async ({ page }) => {
await enterSearchName(page, 'Type60 Invalid Date Range Test');
await setDateRange(page, TestDateRanges.INVALID_REVERSED.min, TestDateRanges.INVALID_REVERSED.max);
await addProfitCenter(page, '1PM');
await uploadFile(page, itemNumberConfig, getTestFile(TestFiles.SINGLE_ITEM));
await submitAndExpectError(page);
});
test('TC-060-N07: Whitespace-Only Search Name', async ({ page }) => {
await enterSearchName(page, ' ');
await setDateRange(page, '2018-01-01', '2019-12-31');
await addProfitCenter(page, '1PM');
await uploadFile(page, itemNumberConfig, getTestFile(TestFiles.SINGLE_ITEM));
await submitAndExpectError(page);
});
test('TC-060-N08: Missing All Required Filters', async ({ page }) => {
await enterSearchName(page, 'Type60 No Filters Test');
// Leave minimum date empty
// Leave maximum date empty
// Do not add any profit centers
// Do not add any item numbers
await clickSubmitSearch(page);
await page.waitForTimeout(1000);
// Should have validation errors
expect(await hasValidationErrors(page)).toBe(true);
});
});
});
@@ -0,0 +1,332 @@
import { test, expect } from '@playwright/test';
import { navigateToSearchPage } from '../helpers/navigation.helper';
import { selectSearchType, SearchTypes, enterSearchName, clickSubmitSearch, confirmSubmitSearch } from '../helpers/search-type.helper';
import { setDateRange, setMinDate, setMaxDate, TestDateRanges } from '../helpers/date-picker.helper';
import {
addProfitCenter,
addProfitCenters,
addOperator,
addOperators,
profitCenterConfig,
operatorConfig,
TestAutocompleteData,
getAutocompleteItemCount
} from '../helpers/autocomplete.helper';
import { assertNoErrorNotification, waitForNotification } from '../helpers/radzen.helper';
import { hasValidationErrors, submitAndExpectError } from '../helpers/validation.helper';
/**
* Test suite for Search Type 160: Time Span + Profit Center + Operator
*
* This search type combines Time Span, Profit Center, and Operator filters.
* It allows users to search for work order data within a specific date range,
* filtered by profit center (branch code) and operator (user ID).
*
* Filters Enabled:
* - Timespan (Start Date, End Date)
* - Profit Center (via autocomplete)
* - Operator (via autocomplete)
*/
test.describe('Search Type 160: Time Span + Profit Center + Operator', () => {
test.beforeEach(async ({ page }) => {
await navigateToSearchPage(page);
await selectSearchType(page, SearchTypes.TIMESPAN_PC_OPERATOR);
});
test.describe('Positive Test Cases', () => {
test('TC-160-P01: Single Profit Center and Single Operator', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'TC-160-P01 Single Values');
// Set date range
await setDateRange(page, '2019-01-01', '2019-12-31');
// Add profit center
await addProfitCenter(page, '1CM');
// Add operator
await addOperator(page, 'ALEXIUCG');
// Verify no error notification
await assertNoErrorNotification(page);
// Verify items were added
const pcCount = await getAutocompleteItemCount(page, profitCenterConfig);
expect(pcCount).toBe(1);
const opCount = await getAutocompleteItemCount(page, operatorConfig);
expect(opCount).toBe(1);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Verify success notification
await waitForNotification(page, 'success', 10000);
});
test('TC-160-P02: Multiple Profit Centers with Single Operator', async ({ page }) => {
await enterSearchName(page, 'TC-160-P02 Multiple Profit Centers');
await setDateRange(page, '2018-01-01', '2019-12-31');
// Add multiple profit centers
await addProfitCenters(page, ['1AM', '1BM', '1CM']);
// Add single operator
await addOperator(page, 'ADAMSSN');
// Verify all profit centers were added
const pcCount = await getAutocompleteItemCount(page, profitCenterConfig);
expect(pcCount).toBe(3);
await assertNoErrorNotification(page);
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await waitForNotification(page, 'success', 10000);
});
test('TC-160-P03: Single Profit Center with Multiple Operators', async ({ page }) => {
await enterSearchName(page, 'TC-160-P03 Multiple Operators');
await setDateRange(page, '2019-01-01', '2020-09-01');
await addProfitCenter(page, '1PM');
// Add multiple operators
await addOperators(page, ['AGNEWA', 'AGNEWL', 'ALASMARB']);
// Verify all operators were added
const opCount = await getAutocompleteItemCount(page, operatorConfig);
expect(opCount).toBe(3);
await assertNoErrorNotification(page);
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await waitForNotification(page, 'success', 10000);
});
test('TC-160-P04: Multiple Profit Centers and Multiple Operators', async ({ page }) => {
await enterSearchName(page, 'TC-160-P04 Multiple All');
await setDateRange(page, '2018-01-01', '2020-09-01');
// Add multiple profit centers
await addProfitCenters(page, ['2DM', '2SM']);
// Add multiple operators
await addOperators(page, ['ALLENHY', 'ALLENNI']);
const pcCount = await getAutocompleteItemCount(page, profitCenterConfig);
expect(pcCount).toBe(2);
const opCount = await getAutocompleteItemCount(page, operatorConfig);
expect(opCount).toBe(2);
await assertNoErrorNotification(page);
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await waitForNotification(page, 'success', 10000);
});
test('TC-160-P05: Recent Date Range', async ({ page }) => {
await enterSearchName(page, 'TC-160-P05 Recent Range');
await setDateRange(page, TestDateRanges.RECENT.min, TestDateRanges.RECENT.max);
await addProfitCenter(page, '3TM');
await addOperator(page, 'ALURUM');
await assertNoErrorNotification(page);
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await waitForNotification(page, 'success', 10000);
});
test('TC-160-P06: Historical Date Range', async ({ page }) => {
await enterSearchName(page, 'TC-160-P06 Historical Range');
await setDateRange(page, TestDateRanges.HISTORICAL.min, TestDateRanges.HISTORICAL.max);
await addProfitCenter(page, '4IM');
await addOperator(page, 'ALVESM1');
await assertNoErrorNotification(page);
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await waitForNotification(page, 'success', 10000);
});
test('TC-160-P07: Narrow Date Range (Single Month)', async ({ page }) => {
await enterSearchName(page, 'TC-160-P07 Single Month');
await setDateRange(page, '2019-06-01', '2019-06-30');
await addProfitCenter(page, '5SM');
await addOperator(page, 'APONTEVE');
await assertNoErrorNotification(page);
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await waitForNotification(page, 'success', 10000);
});
test('TC-160-P08: All Profit Centers', async ({ page }) => {
await enterSearchName(page, 'TC-160-P08 All Profit Centers');
await setDateRange(page, '2019-01-01', '2019-12-31');
// Add all profit centers
await addProfitCenters(page, TestAutocompleteData.profitCenters);
await addOperator(page, 'ADAMSSN');
// Verify all 9 profit centers were added
const pcCount = await getAutocompleteItemCount(page, profitCenterConfig);
expect(pcCount).toBe(9);
await assertNoErrorNotification(page);
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await waitForNotification(page, 'success', 10000);
});
test('TC-160-P09: Many Operators', async ({ page }) => {
await enterSearchName(page, 'TC-160-P09 Many Operators');
await setDateRange(page, '2018-01-01', '2020-09-01');
await addProfitCenter(page, '1CM');
// Add multiple operators
await addOperators(page, ['ADAMSSN', 'AGNEWA', 'AGNEWL', 'ALASMARB', 'ALEXIUCG']);
// Verify all 5 operators were added
const opCount = await getAutocompleteItemCount(page, operatorConfig);
expect(opCount).toBe(5);
await assertNoErrorNotification(page);
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await waitForNotification(page, 'success', 10000);
});
test('TC-160-P10: Same Start and End Date', async ({ page }) => {
await enterSearchName(page, 'TC-160-P10 Same Day');
await setDateRange(page, '2019-07-15', '2019-07-15');
await addProfitCenter(page, '1PM');
await addOperator(page, 'ADAMSSN');
await assertNoErrorNotification(page);
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await waitForNotification(page, 'success', 10000);
});
});
test.describe('Negative Test Cases', () => {
test('TC-160-N01: Missing Date Range', async ({ page }) => {
await enterSearchName(page, 'TC-160-N01 No Dates');
// Leave minimum date empty
// Leave maximum date empty
await addProfitCenter(page, '1CM');
await addOperator(page, 'ALEXIUCG');
await submitAndExpectError(page);
});
test('TC-160-N02: Missing Profit Center', async ({ page }) => {
await enterSearchName(page, 'TC-160-N02 No Profit Center');
await setDateRange(page, '2019-01-01', '2019-12-31');
// Do not add any profit center
await addOperator(page, 'ALEXIUCG');
await submitAndExpectError(page);
});
test('TC-160-N03: Missing Operator', async ({ page }) => {
await enterSearchName(page, 'TC-160-N03 No Operator');
await setDateRange(page, '2019-01-01', '2019-12-31');
await addProfitCenter(page, '1CM');
// Do not add any operator
await submitAndExpectError(page);
});
test('TC-160-N04: Start Date After End Date', async ({ page }) => {
await enterSearchName(page, 'TC-160-N04 Invalid Date Range');
await setDateRange(page, TestDateRanges.INVALID_REVERSED.min, TestDateRanges.INVALID_REVERSED.max);
await addProfitCenter(page, '1CM');
await addOperator(page, 'ALEXIUCG');
await submitAndExpectError(page);
});
test('TC-160-N05: Missing Search Name', async ({ page }) => {
// Leave search name empty
await setDateRange(page, '2019-01-01', '2019-12-31');
await addProfitCenter(page, '1CM');
await addOperator(page, 'ALEXIUCG');
await submitAndExpectError(page);
});
test('TC-160-N06: Missing Start Date Only', async ({ page }) => {
await enterSearchName(page, 'TC-160-N06 No Start Date');
// Leave minimum date empty
await setMaxDate(page, '2019-12-31');
await addProfitCenter(page, '1CM');
await addOperator(page, 'ALEXIUCG');
await submitAndExpectError(page);
});
test('TC-160-N07: Missing End Date Only', async ({ page }) => {
await enterSearchName(page, 'TC-160-N07 No End Date');
await setMinDate(page, '2019-01-01');
// Leave maximum date empty
await addProfitCenter(page, '1CM');
await addOperator(page, 'ALEXIUCG');
await submitAndExpectError(page);
});
test('TC-160-N08: Whitespace-Only Search Name', async ({ page }) => {
await enterSearchName(page, ' ');
await setDateRange(page, '2019-01-01', '2019-12-31');
await addProfitCenter(page, '1CM');
await addOperator(page, 'ALEXIUCG');
await submitAndExpectError(page);
});
test('TC-160-N09: Missing Profit Center and Operator', async ({ page }) => {
await enterSearchName(page, 'TC-160-N09 No PC or Operator');
await setDateRange(page, '2019-01-01', '2019-12-31');
// Do not add any profit center
// Do not add any operator
await clickSubmitSearch(page);
await page.waitForTimeout(1000);
// Should have validation errors
expect(await hasValidationErrors(page)).toBe(true);
});
test('TC-160-N10: Missing All Required Filters', async ({ page }) => {
await enterSearchName(page, 'TC-160-N10 No Filters');
// Leave minimum date empty
// Leave maximum date empty
// Do not add any profit centers
// Do not add any operators
await clickSubmitSearch(page);
await page.waitForTimeout(1000);
// Should have validation errors
expect(await hasValidationErrors(page)).toBe(true);
});
});
});
@@ -0,0 +1,263 @@
import { test, expect } from '@playwright/test';
import { navigateToSearchPage } from '../helpers/navigation.helper';
import { selectSearchType, SearchTypes, enterSearchName, clickSubmitSearch, confirmSubmitSearch } from '../helpers/search-type.helper';
import { setDateRange, setMinDate, setMaxDate, TestDateRanges } from '../helpers/date-picker.helper';
import { addProfitCenter, addProfitCenters, profitCenterConfig, TestAutocompleteData, getAutocompleteItemCount } from '../helpers/autocomplete.helper';
import { uploadFile, partOperationConfig, getTestFile, TestFiles, getUploadedItemCount } from '../helpers/file-upload.helper';
import { assertNoErrorNotification, waitForNotification } from '../helpers/radzen.helper';
import { hasValidationErrors, submitAndExpectError } from '../helpers/validation.helper';
/**
* Test suite for Search Type 70: Time Span + Profit Center + Item/Operation/MIS
*
* This search type allows users to find work orders within a specified date range,
* filtered by profit center and part operations (Item Number, Operation Number,
* MIS Number, and MIS Revision).
*
* Filters Enabled:
* - Timespan (Min Date, Max Date)
* - Profit Center
* - Part Operations (Item Number, Operation Number, MIS Number, MIS Revision) via file upload
*/
test.describe('Search Type 70: Time Span + Profit Center + Item/Operation/MIS', () => {
test.beforeEach(async ({ page }) => {
await navigateToSearchPage(page);
await selectSearchType(page, SearchTypes.TIMESPAN_PC_PARTOP);
});
test.describe('Positive Test Cases', () => {
test('TC-070-P01: Single Profit Center with Single Part Operation', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Type70 Single PC and Part Op Test');
// Set date range
await setDateRange(page, '2018-01-01', '2020-09-01');
// Add profit center
await addProfitCenter(page, '1PM');
// Upload part operations file (contains Item, Operation, MIS, Revision)
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
// Verify no error notification
await assertNoErrorNotification(page);
// Verify part operation was uploaded
const opCount = await getUploadedItemCount(page, partOperationConfig);
expect(opCount).toBeGreaterThan(0);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Verify success notification
await waitForNotification(page, 'success', 10000);
});
test('TC-070-P02: Multiple Profit Centers with Single Part Operation', async ({ page }) => {
await enterSearchName(page, 'Type70 Multiple PC Test');
await setDateRange(page, '2018-01-01', '2020-09-01');
// Add multiple profit centers
await addProfitCenters(page, ['1AM', '1BM', '1CM']);
// Upload single part operation file
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
// Verify all profit centers were added
const pcCount = await getAutocompleteItemCount(page, profitCenterConfig);
expect(pcCount).toBe(3);
await assertNoErrorNotification(page);
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await waitForNotification(page, 'success', 10000);
});
test('TC-070-P03: Single Profit Center with Multiple Part Operations', async ({ page }) => {
await enterSearchName(page, 'Type70 Multiple Part Ops Test');
await setDateRange(page, '2018-01-01', '2020-09-01');
await addProfitCenter(page, '2DM');
// Upload multiple part operations file
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.MULTIPLE_OPERATIONS));
const opCount = await getUploadedItemCount(page, partOperationConfig);
expect(opCount).toBeGreaterThan(1);
await assertNoErrorNotification(page);
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await waitForNotification(page, 'success', 10000);
});
test('TC-070-P04: Multiple Profit Centers with Multiple Part Operations', async ({ page }) => {
await enterSearchName(page, 'Type70 Multiple PC and Part Ops Test');
await setDateRange(page, '2018-01-01', '2020-09-01');
// Add multiple profit centers
await addProfitCenters(page, ['1PM', '2SM', '3TM']);
// Upload multiple part operations file
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.MULTIPLE_OPERATIONS));
const pcCount = await getAutocompleteItemCount(page, profitCenterConfig);
expect(pcCount).toBe(3);
const opCount = await getUploadedItemCount(page, partOperationConfig);
expect(opCount).toBeGreaterThan(1);
await assertNoErrorNotification(page);
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await waitForNotification(page, 'success', 10000);
});
test('TC-070-P05: Recent Date Range', async ({ page }) => {
await enterSearchName(page, 'Type70 Recent Range Test');
await setDateRange(page, TestDateRanges.RECENT.min, TestDateRanges.RECENT.max);
await addProfitCenter(page, '4IM');
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await assertNoErrorNotification(page);
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await waitForNotification(page, 'success', 10000);
});
test('TC-070-P06: Historical Date Range', async ({ page }) => {
await enterSearchName(page, 'Type70 Historical Range Test');
await setDateRange(page, TestDateRanges.HISTORICAL.min, TestDateRanges.HISTORICAL.max);
await addProfitCenter(page, '5SM');
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await assertNoErrorNotification(page);
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await waitForNotification(page, 'success', 10000);
});
test('TC-070-P07: Same Day Date Range', async ({ page }) => {
await enterSearchName(page, 'Type70 Same Day Test');
await setDateRange(page, '2019-06-15', '2019-06-15');
await addProfitCenter(page, '1AM');
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await assertNoErrorNotification(page);
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await waitForNotification(page, 'success', 10000);
});
test('TC-070-P08: All Profit Centers with Single Part Operation', async ({ page }) => {
await enterSearchName(page, 'Type70 All Profit Centers Test');
await setDateRange(page, '2018-01-01', '2020-09-01');
// Add all profit centers
await addProfitCenters(page, TestAutocompleteData.profitCenters);
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
// Verify all 9 profit centers were added
const pcCount = await getAutocompleteItemCount(page, profitCenterConfig);
expect(pcCount).toBe(9);
await assertNoErrorNotification(page);
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await waitForNotification(page, 'success', 10000);
});
});
test.describe('Negative Test Cases', () => {
test('TC-070-N01: Missing Search Name', async ({ page }) => {
// Leave search name empty
await setDateRange(page, '2018-01-01', '2020-09-01');
await addProfitCenter(page, '1PM');
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await submitAndExpectError(page);
});
test('TC-070-N02: Missing Profit Center', async ({ page }) => {
await enterSearchName(page, 'Type70 Missing PC Test');
await setDateRange(page, '2018-01-01', '2020-09-01');
// Do not add any profit centers
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await submitAndExpectError(page);
});
test('TC-070-N03: Missing Part Operation', async ({ page }) => {
await enterSearchName(page, 'Type70 Missing Part Op Test');
await setDateRange(page, '2018-01-01', '2020-09-01');
await addProfitCenter(page, '1PM');
// Do not upload any part operations
await submitAndExpectError(page);
});
test('TC-070-N04: Missing Minimum Date', async ({ page }) => {
await enterSearchName(page, 'Type70 Missing Min Date Test');
// Only set max date
await setMaxDate(page, '2020-09-01');
await addProfitCenter(page, '1PM');
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await submitAndExpectError(page);
});
test('TC-070-N05: Missing Maximum Date', async ({ page }) => {
await enterSearchName(page, 'Type70 Missing Max Date Test');
// Only set min date
await setMinDate(page, '2018-01-01');
await addProfitCenter(page, '1PM');
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await submitAndExpectError(page);
});
test('TC-070-N06: Invalid Date Range (Min > Max)', async ({ page }) => {
await enterSearchName(page, 'Type70 Invalid Date Range Test');
await setDateRange(page, TestDateRanges.INVALID_REVERSED.min, TestDateRanges.INVALID_REVERSED.max);
await addProfitCenter(page, '1PM');
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await submitAndExpectError(page);
});
test('TC-070-N07: Whitespace-Only Search Name', async ({ page }) => {
await enterSearchName(page, ' ');
await setDateRange(page, '2018-01-01', '2020-09-01');
await addProfitCenter(page, '1PM');
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await submitAndExpectError(page);
});
test('TC-070-N08: Missing All Required Filters', async ({ page }) => {
await enterSearchName(page, 'Type70 No Filters Test');
// Leave minimum date empty
// Leave maximum date empty
// Do not add any profit centers
// Do not add any part operations
await clickSubmitSearch(page);
await page.waitForTimeout(1000);
// Should have validation errors
expect(await hasValidationErrors(page)).toBe(true);
});
});
});
@@ -0,0 +1,525 @@
/**
* Playwright E2E tests for Search Type 80:
* Time Span + Profit Center + Work Order + Item/Operation/MIS
*
* This is the most comprehensive search type, allowing users to find work orders
* within a specified date range, filtered by profit center, specific work order
* numbers, and part operations (Item Number, Operation Number, MIS Number, MIS Revision).
*
* Filters Enabled:
* - Timespan (Min Date, Max Date)
* - Profit Center (autocomplete)
* - Work Order (file upload)
* - Part Operations (file upload)
*/
import { test, expect } from '@playwright/test';
import { navigateToSearchPage } from '../helpers/navigation.helper';
import { selectSearchType, SearchTypes, enterSearchName, clickSubmitSearch, confirmSubmitSearch } from '../helpers/search-type.helper';
import { setDateRange, setMinDate, setMaxDate, clearMinDate, clearMaxDate, TestDateRanges } from '../helpers/date-picker.helper';
import {
addProfitCenter,
addProfitCenters,
profitCenterConfig,
getAutocompleteItemCount,
isAutocompletePanelVisible,
TestAutocompleteData
} from '../helpers/autocomplete.helper';
import {
uploadFile,
workOrderConfig,
partOperationConfig,
getTestFile,
TestFiles,
getUploadedItemCount,
isFileUploadPanelVisible
} from '../helpers/file-upload.helper';
import { assertNoErrorNotification, hasErrorNotification } from '../helpers/radzen.helper';
import { hasValidationErrors, submitAndExpectError, ValidationMessages } from '../helpers/validation.helper';
// Test data constants for Type 80
const TYPE_80_NAME = SearchTypes.TIMESPAN_PC_WO_PARTOP;
// Valid profit centers from test documentation
const VALID_PROFIT_CENTERS = ['1AM', '1BM', '1CM', '1PM', '2DM', '2SM', '3TM', '4IM', '5SM'];
// Standard date ranges for testing
const STANDARD_DATE_RANGE = { min: '2018-01-01', max: '2020-09-01' };
const RECENT_DATE_RANGE = TestDateRanges.RECENT;
const HISTORICAL_DATE_RANGE = TestDateRanges.HISTORICAL;
const SAME_DAY_DATE_RANGE = { min: '2019-06-15', max: '2019-06-15' };
const INVALID_DATE_RANGE = TestDateRanges.INVALID_REVERSED;
test.describe('Search Type 80: Time Span + Profit Center + Work Order + Item/Operation/MIS', () => {
test.beforeEach(async ({ page }) => {
await navigateToSearchPage(page);
});
test.describe('Positive Test Cases', () => {
test('TC-080-P01: Single value for all filters', async ({ page }) => {
// Step 1: Enter search name
await enterSearchName(page, 'TC-080-P01 Single Values Test');
// Step 2: Select search type
await selectSearchType(page, TYPE_80_NAME);
// Verify all filter panels are visible
expect(await isAutocompletePanelVisible(page, profitCenterConfig)).toBe(true);
expect(await isFileUploadPanelVisible(page, workOrderConfig)).toBe(true);
expect(await isFileUploadPanelVisible(page, partOperationConfig)).toBe(true);
// Step 3: Set date range
await setDateRange(page, STANDARD_DATE_RANGE.min, STANDARD_DATE_RANGE.max);
// Step 4: Add single profit center
await addProfitCenter(page, '1AM');
expect(await getAutocompleteItemCount(page, profitCenterConfig)).toBe(1);
// Step 5: Upload single work order file
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.SINGLE_WORKORDER));
// Step 6: Upload single part operation file
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
// Step 7: Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Verify no error notification
await assertNoErrorNotification(page);
});
test('TC-080-P02: Multiple profit centers with single work order and part operation', async ({ page }) => {
await enterSearchName(page, 'TC-080-P02 Multiple PC Test');
await selectSearchType(page, TYPE_80_NAME);
await setDateRange(page, STANDARD_DATE_RANGE.min, STANDARD_DATE_RANGE.max);
// Add multiple profit centers
await addProfitCenters(page, ['1AM', '1BM', '1CM']);
expect(await getAutocompleteItemCount(page, profitCenterConfig)).toBe(3);
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.SINGLE_WORKORDER));
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-080-P03: Single profit center with multiple work orders', async ({ page }) => {
await enterSearchName(page, 'TC-080-P03 Multiple WO Test');
await selectSearchType(page, TYPE_80_NAME);
await setDateRange(page, STANDARD_DATE_RANGE.min, STANDARD_DATE_RANGE.max);
await addProfitCenter(page, '1PM');
// Upload multiple work orders
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.MULTIPLE_WORKORDERS));
const woCount = await getUploadedItemCount(page, workOrderConfig);
expect(woCount).toBeGreaterThan(1);
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-080-P04: Single profit center and work order with multiple part operations', async ({ page }) => {
await enterSearchName(page, 'TC-080-P04 Multiple Part Ops Test');
await selectSearchType(page, TYPE_80_NAME);
await setDateRange(page, STANDARD_DATE_RANGE.min, STANDARD_DATE_RANGE.max);
await addProfitCenter(page, '2DM');
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.SINGLE_WORKORDER));
// Upload multiple part operations
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.MULTIPLE_OPERATIONS));
const opCount = await getUploadedItemCount(page, partOperationConfig);
expect(opCount).toBeGreaterThan(1);
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-080-P05: Multiple values for all filter types', async ({ page }) => {
await enterSearchName(page, 'TC-080-P05 All Multiple Values Test');
await selectSearchType(page, TYPE_80_NAME);
await setDateRange(page, STANDARD_DATE_RANGE.min, STANDARD_DATE_RANGE.max);
// Multiple profit centers
await addProfitCenters(page, ['1PM', '2SM', '3TM']);
expect(await getAutocompleteItemCount(page, profitCenterConfig)).toBe(3);
// Multiple work orders
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.MULTIPLE_WORKORDERS));
// Multiple part operations
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.MULTIPLE_OPERATIONS));
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-080-P06: Recent date range', async ({ page }) => {
await enterSearchName(page, 'TC-080-P06 Recent Range Test');
await selectSearchType(page, TYPE_80_NAME);
// Use recent date range
await setDateRange(page, RECENT_DATE_RANGE.min, RECENT_DATE_RANGE.max);
await addProfitCenter(page, '4IM');
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.SINGLE_WORKORDER));
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-080-P07: Historical date range', async ({ page }) => {
await enterSearchName(page, 'TC-080-P07 Historical Range Test');
await selectSearchType(page, TYPE_80_NAME);
// Use historical date range
await setDateRange(page, HISTORICAL_DATE_RANGE.min, HISTORICAL_DATE_RANGE.max);
await addProfitCenter(page, '5SM');
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.SINGLE_WORKORDER));
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-080-P08: Same day date range', async ({ page }) => {
await enterSearchName(page, 'TC-080-P08 Same Day Test');
await selectSearchType(page, TYPE_80_NAME);
// Use same day for min and max
await setDateRange(page, SAME_DAY_DATE_RANGE.min, SAME_DAY_DATE_RANGE.max);
await addProfitCenter(page, '1AM');
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.SINGLE_WORKORDER));
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-080-P09: All profit centers', async ({ page }) => {
await enterSearchName(page, 'TC-080-P09 All Profit Centers Test');
await selectSearchType(page, TYPE_80_NAME);
await setDateRange(page, STANDARD_DATE_RANGE.min, STANDARD_DATE_RANGE.max);
// Add all valid profit centers
await addProfitCenters(page, VALID_PROFIT_CENTERS);
expect(await getAutocompleteItemCount(page, profitCenterConfig)).toBe(VALID_PROFIT_CENTERS.length);
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.SINGLE_WORKORDER));
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-080-P10: Maximum work orders', async ({ page }) => {
await enterSearchName(page, 'TC-080-P10 Many Work Orders Test');
await selectSearchType(page, TYPE_80_NAME);
await setDateRange(page, STANDARD_DATE_RANGE.min, STANDARD_DATE_RANGE.max);
await addProfitCenter(page, '1AM');
// Upload maximum work orders file
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.MULTIPLE_WORKORDERS));
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
});
test.describe('Negative Test Cases', () => {
test('TC-080-N01: Missing search name', async ({ page }) => {
// Do NOT enter search name
await selectSearchType(page, TYPE_80_NAME);
await setDateRange(page, STANDARD_DATE_RANGE.min, STANDARD_DATE_RANGE.max);
await addProfitCenter(page, '1AM');
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.SINGLE_WORKORDER));
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await submitAndExpectError(page);
});
test('TC-080-N02: Missing profit center', async ({ page }) => {
await enterSearchName(page, 'TC-080-N02 Missing PC Test');
await selectSearchType(page, TYPE_80_NAME);
await setDateRange(page, STANDARD_DATE_RANGE.min, STANDARD_DATE_RANGE.max);
// Do NOT add profit center
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.SINGLE_WORKORDER));
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await submitAndExpectError(page);
});
test('TC-080-N03: Missing work order', async ({ page }) => {
await enterSearchName(page, 'TC-080-N03 Missing WO Test');
await selectSearchType(page, TYPE_80_NAME);
await setDateRange(page, STANDARD_DATE_RANGE.min, STANDARD_DATE_RANGE.max);
await addProfitCenter(page, '1AM');
// Do NOT upload work order
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await submitAndExpectError(page);
});
test('TC-080-N04: Missing part operation', async ({ page }) => {
await enterSearchName(page, 'TC-080-N04 Missing Part Op Test');
await selectSearchType(page, TYPE_80_NAME);
await setDateRange(page, STANDARD_DATE_RANGE.min, STANDARD_DATE_RANGE.max);
await addProfitCenter(page, '1AM');
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.SINGLE_WORKORDER));
// Do NOT upload part operation
await submitAndExpectError(page);
});
test('TC-080-N05: Missing minimum date', async ({ page }) => {
await enterSearchName(page, 'TC-080-N05 Missing Min Date Test');
await selectSearchType(page, TYPE_80_NAME);
// Only set max date, leave min date empty
await setMaxDate(page, STANDARD_DATE_RANGE.max);
await addProfitCenter(page, '1AM');
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.SINGLE_WORKORDER));
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await submitAndExpectError(page);
});
test('TC-080-N06: Missing maximum date', async ({ page }) => {
await enterSearchName(page, 'TC-080-N06 Missing Max Date Test');
await selectSearchType(page, TYPE_80_NAME);
// Only set min date, leave max date empty
await setMinDate(page, STANDARD_DATE_RANGE.min);
await addProfitCenter(page, '1AM');
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.SINGLE_WORKORDER));
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await submitAndExpectError(page);
});
test('TC-080-N07: Invalid date range (min > max)', async ({ page }) => {
await enterSearchName(page, 'TC-080-N07 Invalid Date Range Test');
await selectSearchType(page, TYPE_80_NAME);
// Set min date after max date
await setDateRange(page, INVALID_DATE_RANGE.min, INVALID_DATE_RANGE.max);
await addProfitCenter(page, '1AM');
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.SINGLE_WORKORDER));
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await submitAndExpectError(page);
});
test('TC-080-N08: Missing both dates', async ({ page }) => {
await enterSearchName(page, 'TC-080-N08 Missing Both Dates Test');
await selectSearchType(page, TYPE_80_NAME);
// Do not set any dates
await clearMinDate(page);
await clearMaxDate(page);
await addProfitCenter(page, '1AM');
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.SINGLE_WORKORDER));
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await submitAndExpectError(page);
});
test('TC-080-N09: Whitespace-only search name', async ({ page }) => {
// Enter whitespace-only search name
await enterSearchName(page, ' ');
await selectSearchType(page, TYPE_80_NAME);
await setDateRange(page, STANDARD_DATE_RANGE.min, STANDARD_DATE_RANGE.max);
await addProfitCenter(page, '1AM');
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.SINGLE_WORKORDER));
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await submitAndExpectError(page);
});
test('TC-080-N10: Missing all required filters', async ({ page }) => {
await enterSearchName(page, 'TC-080-N10 No Filters Test');
await selectSearchType(page, TYPE_80_NAME);
// Do not set any filters - leave dates, profit center, work order, and part operation empty
await clearMinDate(page);
await clearMaxDate(page);
await submitAndExpectError(page);
});
test('TC-080-N11: Empty work order file', async ({ page }) => {
await enterSearchName(page, 'TC-080-N11 Empty WO File Test');
await selectSearchType(page, TYPE_80_NAME);
await setDateRange(page, STANDARD_DATE_RANGE.min, STANDARD_DATE_RANGE.max);
await addProfitCenter(page, '1AM');
// Upload empty file
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.EMPTY_FILE));
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
// Should either show error notification or validation error
const hasError = await hasErrorNotification(page) || await hasValidationErrors(page);
// If upload completes without immediate error, submit should fail
if (!hasError) {
await submitAndExpectError(page);
}
});
test('TC-080-N12: Empty part operation file', async ({ page }) => {
await enterSearchName(page, 'TC-080-N12 Empty Part Op File Test');
await selectSearchType(page, TYPE_80_NAME);
await setDateRange(page, STANDARD_DATE_RANGE.min, STANDARD_DATE_RANGE.max);
await addProfitCenter(page, '1AM');
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.SINGLE_WORKORDER));
// Upload empty file for part operations
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.EMPTY_FILE));
// Should either show error notification or validation error
const hasError = await hasErrorNotification(page) || await hasValidationErrors(page);
// If upload completes without immediate error, submit should fail
if (!hasError) {
await submitAndExpectError(page);
}
});
});
test.describe('Filter Panel Visibility', () => {
test('TC-080-V01: All filter panels visible when search type selected', async ({ page }) => {
await selectSearchType(page, TYPE_80_NAME);
// Verify all four filter panels are visible
await expect(page.locator('text=Filter by Time Span')).toBeVisible();
await expect(page.locator('text=Filter by Profit Center')).toBeVisible();
await expect(page.locator('text=Filter by Work Order')).toBeVisible();
await expect(page.locator('text=Filter By Item/Operation/MIS')).toBeVisible();
});
test('TC-080-V02: Date inputs are available in time span panel', async ({ page }) => {
await selectSearchType(page, TYPE_80_NAME);
// Verify date input fields exist
const minDateInput = page.locator('input[name="MinimumDt"]');
const maxDateInput = page.locator('input[name="MaximumDt"]');
await expect(minDateInput).toBeVisible();
await expect(maxDateInput).toBeVisible();
});
test('TC-080-V03: Autocomplete available for profit center', async ({ page }) => {
await selectSearchType(page, TYPE_80_NAME);
// Verify autocomplete component exists in profit center panel
const panel = page.locator(`.rz-card:has-text("${profitCenterConfig.panelHeader}")`);
const autocomplete = panel.locator('.rz-autocomplete');
await expect(autocomplete).toBeVisible();
});
test('TC-080-V04: File upload available for work order', async ({ page }) => {
await selectSearchType(page, TYPE_80_NAME);
// Verify file input exists in work order panel
const panel = page.locator(`.rz-card:has-text("${workOrderConfig.panelHeader}")`);
const fileInput = panel.locator('input[type="file"]');
await expect(fileInput).toBeAttached();
});
test('TC-080-V05: File upload available for part operation', async ({ page }) => {
await selectSearchType(page, TYPE_80_NAME);
// Verify file input exists in part operation panel
const panel = page.locator(`.rz-card:has-text("${partOperationConfig.panelHeader}")`);
const fileInput = panel.locator('input[type="file"]');
await expect(fileInput).toBeAttached();
});
});
test.describe('Template Downloads', () => {
test('TC-080-T01: Download work order template', async ({ page }) => {
await selectSearchType(page, TYPE_80_NAME);
const panel = page.locator(`.rz-card:has-text("${workOrderConfig.panelHeader}")`);
const downloadPromise = page.waitForEvent('download');
await panel.locator('button:has-text("Download Template")').click();
const download = await downloadPromise;
expect(download.suggestedFilename()).toContain('.xlsx');
});
test('TC-080-T02: Download part operation template', async ({ page }) => {
await selectSearchType(page, TYPE_80_NAME);
const panel = page.locator(`.rz-card:has-text("${partOperationConfig.panelHeader}")`);
const downloadPromise = page.waitForEvent('download');
await panel.locator('button:has-text("Download Template")').click();
const download = await downloadPromise;
expect(download.suggestedFilename()).toContain('.xlsx');
});
});
});
@@ -0,0 +1,410 @@
import { test, expect } from '@playwright/test';
import { navigateToSearchPage } from '../helpers/navigation.helper';
import { selectSearchType, SearchTypes, enterSearchName, clickSubmitSearch, confirmSubmitSearch } from '../helpers/search-type.helper';
import { setDateRange, setMinDate, setMaxDate, clearDateRange, TestDateRanges } from '../helpers/date-picker.helper';
import {
addProfitCenter,
addProfitCenters,
clearAutocompleteItems,
profitCenterConfig,
getAutocompleteItemCount,
removeAutocompleteItem,
isAutocompletePanelVisible,
} from '../helpers/autocomplete.helper';
import { assertNoErrorNotification, hasSuccessNotification } from '../helpers/radzen.helper';
import { hasValidationErrors, submitAndExpectError, ValidationMessages } from '../helpers/validation.helper';
/**
* Test suite for Search Type 30: Time Span + Profit Center
*
* This search type allows users to search by a date range combined with
* one or more profit center (branch) codes.
*
* Filters Enabled: Timespan, Profit Center
*/
test.describe('Search Type 30: Time Span + Profit Center', () => {
test.beforeEach(async ({ page }) => {
await navigateToSearchPage(page);
});
// ============================================================================
// POSITIVE TEST CASES
// ============================================================================
test.describe('Positive Tests', () => {
test('TC-030-P01: Single profit center with standard date range', async ({ page }) => {
// Select search type
await selectSearchType(page, SearchTypes.TIMESPAN_PROFIT_CENTER);
// Verify filter panel is visible
await expect(page.locator('text=Filter by Profit Center')).toBeVisible();
await expect(page.locator('text=Filter by Time Span')).toBeVisible();
// Enter search name
await enterSearchName(page, 'TC-030-P01 Single Profit Center');
// Set date range
await setDateRange(page, '2020-01-01', '2020-09-01');
// Add profit center
await addProfitCenter(page, '1AM');
// Verify profit center appears in the list
const itemCount = await getAutocompleteItemCount(page, profitCenterConfig);
expect(itemCount).toBe(1);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Verify no error notification
await assertNoErrorNotification(page);
});
test('TC-030-P02: Multiple profit centers search', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_PROFIT_CENTER);
await enterSearchName(page, 'TC-030-P02 Multiple Profit Centers');
// Set date range
await setDateRange(page, '2019-01-01', '2019-12-31');
// Add multiple profit centers
await addProfitCenters(page, ['1AM', '1PM', '2DM']);
// Verify all profit centers appear in the list
const itemCount = await getAutocompleteItemCount(page, profitCenterConfig);
expect(itemCount).toBe(3);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-030-P03: All profit centers search', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_PROFIT_CENTER);
await enterSearchName(page, 'TC-030-P03 All Profit Centers');
// Set date range
await setDateRange(page, '2018-01-01', '2018-12-31');
// Add all profit centers
const allProfitCenters = ['1AM', '1BM', '1CM', '1PM', '2DM', '2SM', '3TM', '4IM', '5SM'];
await addProfitCenters(page, allProfitCenters);
// Verify all profit centers appear in the list
const itemCount = await getAutocompleteItemCount(page, profitCenterConfig);
expect(itemCount).toBe(9);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-030-P04: Minimum date range (same day)', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_PROFIT_CENTER);
await enterSearchName(page, 'TC-030-P04 Same Day Range');
// Set same day date range
await setDateRange(page, TestDateRanges.SAME_DAY.min, TestDateRanges.SAME_DAY.max);
// Add profit center
await addProfitCenter(page, '1PM');
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-030-P05: Boundary date - start of data range', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_PROFIT_CENTER);
await enterSearchName(page, 'TC-030-P05 Start Boundary');
// Set earliest date boundary (1905-01-20 to 1905-12-31)
await setDateRange(page, TestDateRanges.START_BOUNDARY.min, TestDateRanges.START_BOUNDARY.max);
// Add profit center
await addProfitCenter(page, '1AM');
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-030-P06: Boundary date - end of data range', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_PROFIT_CENTER);
await enterSearchName(page, 'TC-030-P06 End Boundary');
// Set latest date boundary (2020-08-01 to 2020-09-01)
await setDateRange(page, TestDateRanges.END_BOUNDARY.min, TestDateRanges.END_BOUNDARY.max);
// Add profit center
await addProfitCenter(page, '1CM');
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-030-P07: Historical date range search', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_PROFIT_CENTER);
await enterSearchName(page, 'TC-030-P07 Historical Search');
// Set historical date range (2016-01-01 to 2017-12-31)
await setDateRange(page, TestDateRanges.HISTORICAL.min, TestDateRanges.HISTORICAL.max);
// Add profit center
await addProfitCenter(page, '2SM');
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-030-P08: Profit center remove and re-add', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_PROFIT_CENTER);
await enterSearchName(page, 'TC-030-P08 PC Remove Re-add');
// Set date range
await setDateRange(page, '2019-01-01', '2019-12-31');
// Add profit centers
await addProfitCenter(page, '1AM');
await addProfitCenter(page, '1BM');
// Verify both are added
let itemCount = await getAutocompleteItemCount(page, profitCenterConfig);
expect(itemCount).toBe(2);
// Remove first profit center (1AM)
await removeAutocompleteItem(page, profitCenterConfig, 0);
// Verify only one remains
itemCount = await getAutocompleteItemCount(page, profitCenterConfig);
expect(itemCount).toBe(1);
// Add another profit center
await addProfitCenter(page, '1CM');
// Verify two profit centers in list (1BM and 1CM)
itemCount = await getAutocompleteItemCount(page, profitCenterConfig);
expect(itemCount).toBe(2);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
});
// ============================================================================
// NEGATIVE TEST CASES
// ============================================================================
test.describe('Negative Tests', () => {
test('TC-030-N01: Missing search name', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_PROFIT_CENTER);
// Do NOT enter search name
// Set valid date range
await setDateRange(page, '2020-01-01', '2020-09-01');
// Add profit center
await addProfitCenter(page, '1AM');
// Attempt to submit
await submitAndExpectError(page);
// Verify user remains on the page
await expect(page.locator('text=Filter by Profit Center')).toBeVisible();
});
test('TC-030-N02: No search type selected', async ({ page }) => {
// Enter search name without selecting search type
await enterSearchName(page, 'TC-030-N02 No Type');
// Verify filter panels are not visible (search type not selected)
const profitCenterPanelVisible = await isAutocompletePanelVisible(page, profitCenterConfig);
expect(profitCenterPanelVisible).toBe(false);
// Attempt to submit
await submitAndExpectError(page);
});
test('TC-030-N03: Missing minimum date', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_PROFIT_CENTER);
await enterSearchName(page, 'TC-030-N03 Missing Min Date');
// Only set maximum date
await setMaxDate(page, '2020-09-01');
// Add profit center
await addProfitCenter(page, '1AM');
// Attempt to submit
await submitAndExpectError(page);
});
test('TC-030-N04: Missing maximum date', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_PROFIT_CENTER);
await enterSearchName(page, 'TC-030-N04 Missing Max Date');
// Only set minimum date
await setMinDate(page, '2020-01-01');
// Add profit center
await addProfitCenter(page, '1AM');
// Attempt to submit
await submitAndExpectError(page);
});
test('TC-030-N05: Empty profit center list', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_PROFIT_CENTER);
await enterSearchName(page, 'TC-030-N05 Empty Profit Centers');
// Set valid date range
await setDateRange(page, '2020-01-01', '2020-09-01');
// Do NOT add any profit centers
// Verify profit center list is empty
const itemCount = await getAutocompleteItemCount(page, profitCenterConfig);
expect(itemCount).toBe(0);
// Attempt to submit
await submitAndExpectError(page);
});
test('TC-030-N06: Invalid date range (min > max)', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_PROFIT_CENTER);
await enterSearchName(page, 'TC-030-N06 Invalid Date Range');
// Set invalid date range (min date after max date)
await setDateRange(page, TestDateRanges.INVALID_REVERSED.min, TestDateRanges.INVALID_REVERSED.max);
// Add profit center
await addProfitCenter(page, '1AM');
// Attempt to submit
await submitAndExpectError(page);
});
test('TC-030-N07: Invalid profit center code', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_PROFIT_CENTER);
await enterSearchName(page, 'TC-030-N07 Invalid PC Code');
// Set valid date range
await setDateRange(page, '2020-01-01', '2020-09-01');
// Try to add invalid profit center
// The autocomplete should not find any matches for "INVALID"
const panel = page.locator(`.rz-card:has-text("${profitCenterConfig.panelHeader}")`);
const autocomplete = panel.locator('.rz-autocomplete input');
await autocomplete.fill('INVALID');
// Wait for autocomplete to search
await page.waitForTimeout(500);
// Verify no autocomplete suggestions appear
const dropdown = page.locator('.rz-autocomplete-list');
const dropdownVisible = await dropdown.isVisible({ timeout: 2000 }).catch(() => false);
// If dropdown is not visible or empty, the invalid code is rejected
if (dropdownVisible) {
const items = dropdown.locator('.rz-autocomplete-list-item');
const count = await items.count();
expect(count).toBe(0);
}
// Verify profit center list is still empty
const itemCount = await getAutocompleteItemCount(page, profitCenterConfig);
expect(itemCount).toBe(0);
});
test('TC-030-N08: Future date range', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_PROFIT_CENTER);
await enterSearchName(page, 'TC-030-N08 Future Dates');
// Set future date range
await setDateRange(page, TestDateRanges.FUTURE.min, TestDateRanges.FUTURE.max);
// Add profit center
await addProfitCenter(page, '1AM');
// Submit search - may be accepted but will return no results
// or may show validation warning
await clickSubmitSearch(page);
// Check if there's a validation error or if it proceeds
const hasErrors = await hasValidationErrors(page);
if (!hasErrors) {
// If accepted, confirm the submission
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
}
// If there are validation errors, that's also acceptable behavior
});
test('TC-030-N09: Invalid date format', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_PROFIT_CENTER);
await enterSearchName(page, 'TC-030-N09 Invalid Date Format');
// Try to enter invalid date format directly
// The Radzen date picker should prevent or reject invalid formats
const minDateInput = page.locator('input[name="MinimumDt"]');
await minDateInput.fill('31-12-2020'); // Invalid format
await setMaxDate(page, '2020-09-01');
// Add profit center
await addProfitCenter(page, '1AM');
// Attempt to submit - should fail validation
await submitAndExpectError(page);
});
test('TC-030-N10: Profit center with special characters', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_PROFIT_CENTER);
await enterSearchName(page, 'TC-030-N10 PC Special Chars');
// Set valid date range
await setDateRange(page, '2020-01-01', '2020-09-01');
// Try to add profit center with special characters
const panel = page.locator(`.rz-card:has-text("${profitCenterConfig.panelHeader}")`);
const autocomplete = panel.locator('.rz-autocomplete input');
await autocomplete.fill('1AM!@#');
// Wait for autocomplete to search
await page.waitForTimeout(500);
// Verify no autocomplete suggestions appear for invalid input
const dropdown = page.locator('.rz-autocomplete-list');
const dropdownVisible = await dropdown.isVisible({ timeout: 2000 }).catch(() => false);
if (dropdownVisible) {
const items = dropdown.locator('.rz-autocomplete-list-item');
const count = await items.count();
expect(count).toBe(0);
}
// Verify profit center list is still empty
const itemCount = await getAutocompleteItemCount(page, profitCenterConfig);
expect(itemCount).toBe(0);
});
});
});
@@ -0,0 +1,331 @@
/**
* E2E Tests for Search Type 110: Time Span + Work Center + Extract MIS
*
* This search type allows users to search by a date range combined with work center(s)
* and the Extract MIS boolean flag. When this search type is selected, the ExtractMisData
* flag is automatically set to true - there is no interactive checkbox.
*
* Required filters:
* - Timespan (Min Date to Max Date)
* - Work Center (one or more)
* - Extract MIS (automatically enabled when this search type is selected)
*/
import { test, expect } from '@playwright/test';
import { navigateToSearchPage } from '../helpers/navigation.helper';
import { selectSearchType, SearchTypes, enterSearchName, clickSubmitSearch, confirmSubmitSearch } from '../helpers/search-type.helper';
import { setDateRange, setMinDate, setMaxDate, TestDateRanges } from '../helpers/date-picker.helper';
import { addWorkCenter, addWorkCenters, workCenterConfig, getAutocompleteItemCount } from '../helpers/autocomplete.helper';
import { assertNoErrorNotification, hasErrorNotification } from '../helpers/radzen.helper';
import { hasValidationErrors } from '../helpers/validation.helper';
// Valid test data from manual test scripts
const TEST_WORK_CENTERS = {
SINGLE_CA: '11275CA',
SINGLE_AS: '0083AS',
MULTIPLE_CA: ['10595CA', '11275CA', '11350CA'],
MULTIPLE_AS: ['1010AS', '1011AS'],
MIXED_SUFFIXES: ['0696AS', '13316CA', '1700CB'],
MANY: ['0083AS', '0278AS', '0424AS', '0586AS', '0696AS', '1010AS'],
};
test.describe('Search Type 110: Time Span + Work Center + Extract MIS', () => {
test.beforeEach(async ({ page }) => {
await navigateToSearchPage(page);
await selectSearchType(page, SearchTypes.TIMESPAN_WC_EXTRACTMIS);
});
test.describe('Positive Test Cases', () => {
test('TC-110-P01: Single Work Center with Extract MIS Enabled', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 110-P01 Single WC Extract MIS');
// Set date range
await setDateRange(page, '2018-01-01', '2020-09-01');
// Add single work center
await addWorkCenter(page, TEST_WORK_CENTERS.SINGLE_CA);
// Note: Extract MIS is automatically enabled when this search type is selected
// Verify the Extract MIS checkbox is visible and checked
const extractMisCheckbox = page.locator('text=Extract MIS data');
await expect(extractMisCheckbox).toBeVisible();
// Verify work center was added
const wcCount = await getAutocompleteItemCount(page, workCenterConfig);
expect(wcCount).toBeGreaterThanOrEqual(1);
// Verify no error notification
await assertNoErrorNotification(page);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Should navigate to search page
await expect(page).toHaveURL(/\/search\/\d+/);
});
test('TC-110-P02: Multiple Work Centers with Extract MIS Enabled', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 110-P02 Multi WC Extract MIS');
// Set date range (recent)
await setDateRange(page, '2019-01-01', '2020-09-01');
// Add multiple work centers
await addWorkCenters(page, TEST_WORK_CENTERS.MULTIPLE_CA);
// Verify work centers were added
const wcCount = await getAutocompleteItemCount(page, workCenterConfig);
expect(wcCount).toBe(3);
// Verify no error notification
await assertNoErrorNotification(page);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Should navigate to search page
await expect(page).toHaveURL(/\/search\/\d+/);
});
test('TC-110-P03: Single Work Center (AS suffix) with Extract MIS', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 110-P03 Single WC AS Suffix');
// Set date range (mid-range)
await setDateRange(page, '2018-01-01', '2019-12-31');
// Add single work center with AS suffix
await addWorkCenter(page, TEST_WORK_CENTERS.SINGLE_AS);
// Verify no error notification
await assertNoErrorNotification(page);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Should navigate to search page
await expect(page).toHaveURL(/\/search\/\d+/);
});
test('TC-110-P04: Historical Date Range with Multiple Work Centers', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 110-P04 Historical Multi WC');
// Set historical date range
await setDateRange(page, TestDateRanges.HISTORICAL.min, TestDateRanges.HISTORICAL.max);
// Add multiple work centers
await addWorkCenters(page, TEST_WORK_CENTERS.MULTIPLE_AS);
// Verify work centers were added
const wcCount = await getAutocompleteItemCount(page, workCenterConfig);
expect(wcCount).toBe(2);
// Verify no error notification
await assertNoErrorNotification(page);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Should navigate to search page
await expect(page).toHaveURL(/\/search\/\d+/);
});
test('TC-110-P05: Work Center Code Variants with Extract MIS', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 110-P05 WC Code Variants');
// Set date range
await setDateRange(page, '2018-01-01', '2020-09-01');
// Add work centers with different suffixes (AS, CA, CB)
await addWorkCenters(page, TEST_WORK_CENTERS.MIXED_SUFFIXES);
// Verify all work centers were added
const wcCount = await getAutocompleteItemCount(page, workCenterConfig);
expect(wcCount).toBe(3);
// Verify no error notification
await assertNoErrorNotification(page);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Should navigate to search page
await expect(page).toHaveURL(/\/search\/\d+/);
});
test('TC-110-P06: Recent Date Range with Single Work Center', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 110-P06 Recent Range');
// Set recent date range
await setDateRange(page, TestDateRanges.RECENT.min, TestDateRanges.RECENT.max);
// Add work center
await addWorkCenter(page, '14305CA');
// Verify no error notification
await assertNoErrorNotification(page);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Should navigate to search page
await expect(page).toHaveURL(/\/search\/\d+/);
});
test('TC-110-P07: Large Work Center Selection', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 110-P07 Many Work Centers');
// Set date range
await setDateRange(page, '2018-01-01', '2020-09-01');
// Add many work centers
await addWorkCenters(page, TEST_WORK_CENTERS.MANY);
// Verify all work centers were added
const wcCount = await getAutocompleteItemCount(page, workCenterConfig);
expect(wcCount).toBe(6);
// Verify no error notification
await assertNoErrorNotification(page);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Should navigate to search page
await expect(page).toHaveURL(/\/search\/\d+/);
});
});
test.describe('Negative Test Cases', () => {
test('TC-110-N01: Missing Required Date Range', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 110-N01 Missing Dates');
// Do NOT set date range (leave empty)
// Add work center
await addWorkCenter(page, TEST_WORK_CENTERS.SINGLE_CA);
// Extract MIS is automatically enabled
// Attempt to submit
await clickSubmitSearch(page);
// Should show validation error
expect(await hasValidationErrors(page) || await hasErrorNotification(page)).toBe(true);
});
test('TC-110-N02: Missing Work Center', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 110-N02 Missing Work Center');
// Set date range
await setDateRange(page, '2018-01-01', '2020-09-01');
// Do NOT add any work center
// Extract MIS is automatically enabled
// Attempt to submit
await clickSubmitSearch(page);
// Should show validation error
expect(await hasValidationErrors(page) || await hasErrorNotification(page)).toBe(true);
});
test('TC-110-N03: Invalid Date Range (End Before Start)', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 110-N03 Invalid Date Range');
// Set invalid date range (max before min)
await setDateRange(page, TestDateRanges.INVALID_REVERSED.min, TestDateRanges.INVALID_REVERSED.max);
// Add work center
await addWorkCenter(page, TEST_WORK_CENTERS.SINGLE_CA);
// Attempt to submit
await clickSubmitSearch(page);
// Should show validation error
expect(await hasValidationErrors(page) || await hasErrorNotification(page)).toBe(true);
});
test('TC-110-N04: Missing Minimum Date Only', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 110-N04 Missing Min Date');
// Set only max date
await setMaxDate(page, '2020-09-01');
// Add work center
await addWorkCenter(page, TEST_WORK_CENTERS.SINGLE_AS);
// Attempt to submit
await clickSubmitSearch(page);
// Should show validation error
expect(await hasValidationErrors(page) || await hasErrorNotification(page)).toBe(true);
});
test('TC-110-N05: Missing Maximum Date Only', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 110-N05 Missing Max Date');
// Set only min date
await setMinDate(page, '2018-01-01');
// Add work center
await addWorkCenter(page, TEST_WORK_CENTERS.SINGLE_CA);
// Attempt to submit
await clickSubmitSearch(page);
// Should show validation error
expect(await hasValidationErrors(page) || await hasErrorNotification(page)).toBe(true);
});
test('TC-110-N06: Missing Search Name', async ({ page }) => {
// Do NOT enter search name
// Set date range
await setDateRange(page, '2018-01-01', '2020-09-01');
// Add work center
await addWorkCenter(page, TEST_WORK_CENTERS.SINGLE_CA);
// Attempt to submit
await clickSubmitSearch(page);
// Should show validation error
expect(await hasValidationErrors(page) || await hasErrorNotification(page)).toBe(true);
});
test('TC-110-N07: All Required Filters Missing', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 110-N07 All Filters Missing');
// Do NOT set date range
// Do NOT add work center
// (Extract MIS is automatically enabled but requires other filters)
// Attempt to submit
await clickSubmitSearch(page);
// Should show validation error
expect(await hasValidationErrors(page) || await hasErrorNotification(page)).toBe(true);
});
});
});
@@ -0,0 +1,358 @@
/**
* E2E Tests for Search Type 100: Time Span + Work Center + Item Number
*
* This search type allows users to search by a date range combined with work center(s)
* and item number(s). It finds work orders processed through specific work centers
* for specific items within the given time range.
*
* Required filters:
* - Timespan (Min Date to Max Date)
* - Work Center (one or more)
* - Item Number (one or more via file upload)
*/
import { test, expect } from '@playwright/test';
import { navigateToSearchPage } from '../helpers/navigation.helper';
import { selectSearchType, SearchTypes, enterSearchName, clickSubmitSearch, confirmSubmitSearch } from '../helpers/search-type.helper';
import { setDateRange, setMinDate, setMaxDate, TestDateRanges } from '../helpers/date-picker.helper';
import { addWorkCenter, addWorkCenters, workCenterConfig, getAutocompleteItemCount } from '../helpers/autocomplete.helper';
import { uploadFile, itemNumberConfig, getTestFile, TestFiles, getUploadedItemCount } from '../helpers/file-upload.helper';
import { assertNoErrorNotification, hasErrorNotification } from '../helpers/radzen.helper';
import { hasValidationErrors, submitAndExpectError, ValidationMessages } from '../helpers/validation.helper';
// Valid test data from manual test scripts
const TEST_WORK_CENTERS = {
SINGLE: '10595CA',
AS_SUFFIX: ['0083AS', '0278AS', '0424AS'],
CA_SUFFIX: ['11275CA', '11350CA', '11355CA'],
CB_SUFFIX: '1700CB',
MIXED: ['1010AS', '14305CA'],
};
test.describe('Search Type 100: Time Span + Work Center + Item Number', () => {
test.beforeEach(async ({ page }) => {
await navigateToSearchPage(page);
await selectSearchType(page, SearchTypes.TIMESPAN_WC_ITEM);
});
test.describe('Positive Test Cases', () => {
test('TC-100-P01: Single Work Center with Single Item Number', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 100-P01 Single WC Single Item');
// Set date range (mid-range)
await setDateRange(page, '2018-01-01', '2019-12-31');
// Add single work center
await addWorkCenter(page, TEST_WORK_CENTERS.SINGLE);
// Upload single item file
await uploadFile(page, itemNumberConfig, getTestFile(TestFiles.SINGLE_ITEM));
// Verify work center was added
const wcCount = await getAutocompleteItemCount(page, workCenterConfig);
expect(wcCount).toBeGreaterThanOrEqual(1);
// Verify no error notification
await assertNoErrorNotification(page);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Should navigate to search page or show queued status
await expect(page).toHaveURL(/\/search\/\d+/);
});
test('TC-100-P02: Single Work Center with Multiple Item Numbers', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 100-P02 Single WC Multi Items');
// Set date range (recent)
await setDateRange(page, '2019-01-01', '2020-09-01');
// Add single work center
await addWorkCenter(page, '11275CA');
// Upload multiple items file
await uploadFile(page, itemNumberConfig, getTestFile(TestFiles.MULTIPLE_ITEMS));
// Verify items were uploaded
const itemCount = await getUploadedItemCount(page, itemNumberConfig);
expect(itemCount).toBeGreaterThan(1);
// Verify no error notification
await assertNoErrorNotification(page);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Should navigate to search page
await expect(page).toHaveURL(/\/search\/\d+/);
});
test('TC-100-P03: Multiple Work Centers with Single Item Number', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 100-P03 Multi WC Single Item');
// Set date range (mid-range)
await setDateRange(page, '2018-01-01', '2019-12-31');
// Add multiple work centers (AS suffix)
await addWorkCenters(page, TEST_WORK_CENTERS.AS_SUFFIX);
// Verify work centers were added
const wcCount = await getAutocompleteItemCount(page, workCenterConfig);
expect(wcCount).toBe(3);
// Upload single item file
await uploadFile(page, itemNumberConfig, getTestFile(TestFiles.SINGLE_ITEM));
// Verify no error notification
await assertNoErrorNotification(page);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Should navigate to search page
await expect(page).toHaveURL(/\/search\/\d+/);
});
test('TC-100-P04: Multiple Work Centers with Multiple Item Numbers', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 100-P04 Multi WC Multi Items');
// Set date range (wide)
await setDateRange(page, '2017-01-01', '2020-09-01');
// Add multiple work centers
await addWorkCenter(page, '10595CA');
await addWorkCenter(page, '11350CA');
// Verify work centers were added
const wcCount = await getAutocompleteItemCount(page, workCenterConfig);
expect(wcCount).toBe(2);
// Upload multiple items file
await uploadFile(page, itemNumberConfig, getTestFile(TestFiles.MULTIPLE_ITEMS));
// Verify no error notification
await assertNoErrorNotification(page);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Should navigate to search page
await expect(page).toHaveURL(/\/search\/\d+/);
});
test('TC-100-P05: Historical Date Range with Work Center and Items', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 100-P05 Historical Search');
// Set historical date range
await setDateRange(page, TestDateRanges.HISTORICAL.min, TestDateRanges.HISTORICAL.max);
// Add work center
await addWorkCenter(page, TEST_WORK_CENTERS.CB_SUFFIX);
// Upload item file
await uploadFile(page, itemNumberConfig, getTestFile(TestFiles.SINGLE_ITEM));
// Verify no error notification
await assertNoErrorNotification(page);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Should navigate to search page
await expect(page).toHaveURL(/\/search\/\d+/);
});
test('TC-100-P06: Work Center Code Variants (AS vs CA suffix)', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 100-P06 WC Code Variants');
// Set date range
await setDateRange(page, '2018-01-01', '2020-09-01');
// Add work centers with different suffixes
await addWorkCenter(page, '1010AS'); // AS suffix
await addWorkCenter(page, '14305CA'); // CA suffix
// Verify both were added
const wcCount = await getAutocompleteItemCount(page, workCenterConfig);
expect(wcCount).toBe(2);
// Upload item file
await uploadFile(page, itemNumberConfig, getTestFile(TestFiles.SINGLE_ITEM));
// Verify no error notification
await assertNoErrorNotification(page);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Should navigate to search page
await expect(page).toHaveURL(/\/search\/\d+/);
});
});
test.describe('Negative Test Cases', () => {
test('TC-100-N01: Missing Required Date Range', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 100-N01 Missing Dates');
// Do NOT set date range (leave empty)
// Add work center
await addWorkCenter(page, TEST_WORK_CENTERS.SINGLE);
// Upload item file
await uploadFile(page, itemNumberConfig, getTestFile(TestFiles.SINGLE_ITEM));
// Attempt to submit
await clickSubmitSearch(page);
// Should show validation error
expect(await hasValidationErrors(page) || await hasErrorNotification(page)).toBe(true);
});
test('TC-100-N02: Missing Work Center', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 100-N02 Missing Work Center');
// Set date range
await setDateRange(page, '2018-01-01', '2019-12-31');
// Do NOT add any work center
// Upload item file
await uploadFile(page, itemNumberConfig, getTestFile(TestFiles.SINGLE_ITEM));
// Attempt to submit
await clickSubmitSearch(page);
// Should show validation error
expect(await hasValidationErrors(page) || await hasErrorNotification(page)).toBe(true);
});
test('TC-100-N03: Missing Item Number', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 100-N03 Missing Item Number');
// Set date range
await setDateRange(page, '2018-01-01', '2019-12-31');
// Add work center
await addWorkCenter(page, TEST_WORK_CENTERS.SINGLE);
// Do NOT upload any item file
// Attempt to submit
await clickSubmitSearch(page);
// Should show validation error
expect(await hasValidationErrors(page) || await hasErrorNotification(page)).toBe(true);
});
test('TC-100-N04: Invalid Date Range (End Before Start)', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 100-N04 Invalid Date Range');
// Set invalid date range (max before min)
await setDateRange(page, TestDateRanges.INVALID_REVERSED.min, TestDateRanges.INVALID_REVERSED.max);
// Add work center
await addWorkCenter(page, TEST_WORK_CENTERS.SINGLE);
// Upload item file
await uploadFile(page, itemNumberConfig, getTestFile(TestFiles.SINGLE_ITEM));
// Attempt to submit
await clickSubmitSearch(page);
// Should show validation error
expect(await hasValidationErrors(page) || await hasErrorNotification(page)).toBe(true);
});
test('TC-100-N05: Missing Minimum Date Only', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 100-N05 Missing Min Date');
// Set only max date
await setMaxDate(page, '2019-12-31');
// Add work center
await addWorkCenter(page, '11275CA');
// Upload item file
await uploadFile(page, itemNumberConfig, getTestFile(TestFiles.SINGLE_ITEM));
// Attempt to submit
await clickSubmitSearch(page);
// Should show validation error
expect(await hasValidationErrors(page) || await hasErrorNotification(page)).toBe(true);
});
test('TC-100-N06: Missing Maximum Date Only', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 100-N06 Missing Max Date');
// Set only min date
await setMinDate(page, '2018-01-01');
// Add work center
await addWorkCenter(page, '0083AS');
// Upload item file
await uploadFile(page, itemNumberConfig, getTestFile(TestFiles.SINGLE_ITEM));
// Attempt to submit
await clickSubmitSearch(page);
// Should show validation error
expect(await hasValidationErrors(page) || await hasErrorNotification(page)).toBe(true);
});
test('TC-100-N07: Missing Search Name', async ({ page }) => {
// Do NOT enter search name
// Set date range
await setDateRange(page, '2018-01-01', '2019-12-31');
// Add work center
await addWorkCenter(page, TEST_WORK_CENTERS.SINGLE);
// Upload item file
await uploadFile(page, itemNumberConfig, getTestFile(TestFiles.SINGLE_ITEM));
// Attempt to submit
await clickSubmitSearch(page);
// Should show validation error
expect(await hasValidationErrors(page) || await hasErrorNotification(page)).toBe(true);
});
test('TC-100-N08: All Required Filters Missing', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 100-N08 All Filters Missing');
// Do NOT set date range
// Do NOT add work center
// Do NOT upload item file
// Attempt to submit
await clickSubmitSearch(page);
// Should show validation error
expect(await hasValidationErrors(page) || await hasErrorNotification(page)).toBe(true);
});
});
});
@@ -0,0 +1,492 @@
/**
* E2E Tests for Search Type 150: Time Span + Work Center + Operator
*
* This search type allows users to search by a date range combined with work center(s)
* and operator(s). It finds work order data within a specific date range, filtered by
* work center and operator (user ID).
*
* Required filters:
* - Timespan (Min Date to Max Date)
* - Work Center (one or more)
* - Operator (one or more)
*/
import { test, expect } from '@playwright/test';
import { navigateToSearchPage } from '../helpers/navigation.helper';
import { selectSearchType, SearchTypes, enterSearchName, clickSubmitSearch, confirmSubmitSearch } from '../helpers/search-type.helper';
import { setDateRange, setMinDate, setMaxDate, TestDateRanges } from '../helpers/date-picker.helper';
import {
addWorkCenter,
addWorkCenters,
addOperator,
addOperators,
workCenterConfig,
operatorConfig,
getAutocompleteItemCount
} from '../helpers/autocomplete.helper';
import { assertNoErrorNotification, hasErrorNotification } from '../helpers/radzen.helper';
import { hasValidationErrors } from '../helpers/validation.helper';
// Valid test data from manual test scripts
const TEST_WORK_CENTERS = {
SINGLE: '14305CA',
MULTIPLE_AS: ['0083AS', '0278AS', '0424AS'],
SINGLE_CA: '10595CA',
MULTIPLE_CA: ['11275CA', '11350CA'],
MIXED: ['0083AS', '10595CA', '1700CB'], // AS, CA, CB formats
MANY: ['0586AS', '0696AS', '1010AS', '1011AS'],
HISTORICAL: '13316CA',
NARROW: '15660CA',
RECENT: '11355CA',
};
const TEST_OPERATORS = {
SINGLE: 'AGNEWA',
MULTIPLE: ['AGNEWA', 'AGNEWL', 'ALASMARB'],
PAIR: ['ALEXIUCG', 'ALLENHY'],
MANY: ['APONTEVE', 'ARCHILAHI', 'ARGUELLC', 'ASHARK'],
HISTORICAL: 'ALURUM',
NARROW: 'ALVESM1',
RECENT: 'ALLENNI',
MIXED: 'ASLANESA',
FIRST: 'ADAMSSN',
};
test.describe('Search Type 150: Time Span + Work Center + Operator', () => {
test.beforeEach(async ({ page }) => {
await navigateToSearchPage(page);
await selectSearchType(page, SearchTypes.TIMESPAN_WC_OPERATOR);
});
test.describe('Positive Test Cases', () => {
test('TC-150-P01: Single Work Center and Single Operator', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'TC-150-P01 Single Values');
// Set date range (mid-range)
await setDateRange(page, '2019-01-01', '2019-12-31');
// Add single work center
await addWorkCenter(page, TEST_WORK_CENTERS.SINGLE);
// Add single operator
await addOperator(page, TEST_OPERATORS.SINGLE);
// Verify work center was added
const wcCount = await getAutocompleteItemCount(page, workCenterConfig);
expect(wcCount).toBeGreaterThanOrEqual(1);
// Verify operator was added
const opCount = await getAutocompleteItemCount(page, operatorConfig);
expect(opCount).toBeGreaterThanOrEqual(1);
// Verify no error notification
await assertNoErrorNotification(page);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Should navigate to search page
await expect(page).toHaveURL(/\/search\/\d+/);
});
test('TC-150-P02: Multiple Work Centers with Single Operator', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'TC-150-P02 Multiple Work Centers');
// Set date range
await setDateRange(page, '2018-01-01', '2019-12-31');
// Add multiple work centers
await addWorkCenters(page, TEST_WORK_CENTERS.MULTIPLE_AS);
// Verify work centers were added
const wcCount = await getAutocompleteItemCount(page, workCenterConfig);
expect(wcCount).toBe(3);
// Add single operator
await addOperator(page, TEST_OPERATORS.FIRST);
// Verify operator was added
const opCount = await getAutocompleteItemCount(page, operatorConfig);
expect(opCount).toBe(1);
// Verify no error notification
await assertNoErrorNotification(page);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Should navigate to search page
await expect(page).toHaveURL(/\/search\/\d+/);
});
test('TC-150-P03: Single Work Center with Multiple Operators', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'TC-150-P03 Multiple Operators');
// Set date range (recent)
await setDateRange(page, '2019-01-01', '2020-09-01');
// Add single work center
await addWorkCenter(page, TEST_WORK_CENTERS.SINGLE_CA);
// Add multiple operators
await addOperators(page, TEST_OPERATORS.MULTIPLE);
// Verify work center was added
const wcCount = await getAutocompleteItemCount(page, workCenterConfig);
expect(wcCount).toBe(1);
// Verify operators were added
const opCount = await getAutocompleteItemCount(page, operatorConfig);
expect(opCount).toBe(3);
// Verify no error notification
await assertNoErrorNotification(page);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Should navigate to search page
await expect(page).toHaveURL(/\/search\/\d+/);
});
test('TC-150-P04: Multiple Work Centers and Multiple Operators', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'TC-150-P04 Multiple All');
// Set date range (wide)
await setDateRange(page, '2018-01-01', '2020-09-01');
// Add multiple work centers
await addWorkCenters(page, TEST_WORK_CENTERS.MULTIPLE_CA);
// Add multiple operators
await addOperators(page, TEST_OPERATORS.PAIR);
// Verify work centers were added
const wcCount = await getAutocompleteItemCount(page, workCenterConfig);
expect(wcCount).toBe(2);
// Verify operators were added
const opCount = await getAutocompleteItemCount(page, operatorConfig);
expect(opCount).toBe(2);
// Verify no error notification
await assertNoErrorNotification(page);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Should navigate to search page
await expect(page).toHaveURL(/\/search\/\d+/);
});
test('TC-150-P05: Recent Date Range', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'TC-150-P05 Recent Range');
// Set recent date range
await setDateRange(page, TestDateRanges.RECENT.min, TestDateRanges.RECENT.max);
// Add work center
await addWorkCenter(page, TEST_WORK_CENTERS.RECENT);
// Add operator
await addOperator(page, TEST_OPERATORS.RECENT);
// Verify no error notification
await assertNoErrorNotification(page);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Should navigate to search page
await expect(page).toHaveURL(/\/search\/\d+/);
});
test('TC-150-P06: Historical Date Range', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'TC-150-P06 Historical Range');
// Set historical date range
await setDateRange(page, TestDateRanges.HISTORICAL.min, TestDateRanges.HISTORICAL.max);
// Add work center
await addWorkCenter(page, TEST_WORK_CENTERS.HISTORICAL);
// Add operator
await addOperator(page, TEST_OPERATORS.HISTORICAL);
// Verify no error notification
await assertNoErrorNotification(page);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Should navigate to search page
await expect(page).toHaveURL(/\/search\/\d+/);
});
test('TC-150-P07: Narrow Date Range (Single Month)', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'TC-150-P07 Single Month');
// Set narrow date range (single month)
await setDateRange(page, '2019-06-01', '2019-06-30');
// Add work center
await addWorkCenter(page, TEST_WORK_CENTERS.NARROW);
// Add operator
await addOperator(page, TEST_OPERATORS.NARROW);
// Verify no error notification
await assertNoErrorNotification(page);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Should navigate to search page
await expect(page).toHaveURL(/\/search\/\d+/);
});
test('TC-150-P08: Many Work Centers and Many Operators', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'TC-150-P08 Many Values');
// Set date range
await setDateRange(page, '2018-01-01', '2020-09-01');
// Add many work centers
await addWorkCenters(page, TEST_WORK_CENTERS.MANY);
// Verify work centers were added
const wcCount = await getAutocompleteItemCount(page, workCenterConfig);
expect(wcCount).toBe(4);
// Add many operators
await addOperators(page, TEST_OPERATORS.MANY);
// Verify operators were added
const opCount = await getAutocompleteItemCount(page, operatorConfig);
expect(opCount).toBe(4);
// Verify no error notification
await assertNoErrorNotification(page);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Should navigate to search page
await expect(page).toHaveURL(/\/search\/\d+/);
});
test('TC-150-P09: Mixed Work Center Formats', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'TC-150-P09 Mixed Formats');
// Set date range (mid-range)
await setDateRange(page, '2019-01-01', '2019-12-31');
// Add work centers with different formats (AS, CA, CB)
await addWorkCenters(page, TEST_WORK_CENTERS.MIXED);
// Verify all work centers were added
const wcCount = await getAutocompleteItemCount(page, workCenterConfig);
expect(wcCount).toBe(3);
// Add operator
await addOperator(page, TEST_OPERATORS.MIXED);
// Verify no error notification
await assertNoErrorNotification(page);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Should navigate to search page
await expect(page).toHaveURL(/\/search\/\d+/);
});
});
test.describe('Negative Test Cases', () => {
test('TC-150-N01: Missing Date Range', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'TC-150-N01 No Dates');
// Do NOT set date range (leave empty)
// Add work center
await addWorkCenter(page, TEST_WORK_CENTERS.SINGLE);
// Add operator
await addOperator(page, TEST_OPERATORS.SINGLE);
// Attempt to submit
await clickSubmitSearch(page);
// Should show validation error
expect(await hasValidationErrors(page) || await hasErrorNotification(page)).toBe(true);
});
test('TC-150-N02: Missing Work Center', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'TC-150-N02 No Work Center');
// Set date range
await setDateRange(page, '2019-01-01', '2019-12-31');
// Do NOT add any work center
// Add operator
await addOperator(page, TEST_OPERATORS.SINGLE);
// Attempt to submit
await clickSubmitSearch(page);
// Should show validation error
expect(await hasValidationErrors(page) || await hasErrorNotification(page)).toBe(true);
});
test('TC-150-N03: Missing Operator', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'TC-150-N03 No Operator');
// Set date range
await setDateRange(page, '2019-01-01', '2019-12-31');
// Add work center
await addWorkCenter(page, TEST_WORK_CENTERS.SINGLE);
// Do NOT add any operator
// Attempt to submit
await clickSubmitSearch(page);
// Should show validation error
expect(await hasValidationErrors(page) || await hasErrorNotification(page)).toBe(true);
});
test('TC-150-N04: Start Date After End Date', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'TC-150-N04 Invalid Date Range');
// Set invalid date range (max before min)
await setDateRange(page, TestDateRanges.INVALID_REVERSED.min, TestDateRanges.INVALID_REVERSED.max);
// Add work center
await addWorkCenter(page, TEST_WORK_CENTERS.SINGLE);
// Add operator
await addOperator(page, TEST_OPERATORS.SINGLE);
// Attempt to submit
await clickSubmitSearch(page);
// Should show validation error
expect(await hasValidationErrors(page) || await hasErrorNotification(page)).toBe(true);
});
test('TC-150-N05: Missing Search Name', async ({ page }) => {
// Do NOT enter search name
// Set date range
await setDateRange(page, '2019-01-01', '2019-12-31');
// Add work center
await addWorkCenter(page, TEST_WORK_CENTERS.SINGLE);
// Add operator
await addOperator(page, TEST_OPERATORS.SINGLE);
// Attempt to submit
await clickSubmitSearch(page);
// Should show validation error
expect(await hasValidationErrors(page) || await hasErrorNotification(page)).toBe(true);
});
test('TC-150-N06: Missing Start Date Only', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'TC-150-N06 No Start Date');
// Set only max date
await setMaxDate(page, '2019-12-31');
// Add work center
await addWorkCenter(page, TEST_WORK_CENTERS.SINGLE);
// Add operator
await addOperator(page, TEST_OPERATORS.SINGLE);
// Attempt to submit
await clickSubmitSearch(page);
// Should show validation error
expect(await hasValidationErrors(page) || await hasErrorNotification(page)).toBe(true);
});
test('TC-150-N07: Missing End Date Only', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'TC-150-N07 No End Date');
// Set only min date
await setMinDate(page, '2019-01-01');
// Add work center
await addWorkCenter(page, TEST_WORK_CENTERS.SINGLE);
// Add operator
await addOperator(page, TEST_OPERATORS.SINGLE);
// Attempt to submit
await clickSubmitSearch(page);
// Should show validation error
expect(await hasValidationErrors(page) || await hasErrorNotification(page)).toBe(true);
});
test('TC-150-N08: All Required Filters Missing', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'TC-150-N08 All Missing');
// Do NOT set date range
// Do NOT add work center
// Do NOT add operator
// Attempt to submit
await clickSubmitSearch(page);
// Should show validation error
expect(await hasValidationErrors(page) || await hasErrorNotification(page)).toBe(true);
});
test('TC-150-N09: Missing Work Center and Operator', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'TC-150-N09 No WC No Op');
// Set date range
await setDateRange(page, '2019-01-01', '2019-12-31');
// Do NOT add work center
// Do NOT add operator
// Attempt to submit
await clickSubmitSearch(page);
// Should show validation error
expect(await hasValidationErrors(page) || await hasErrorNotification(page)).toBe(true);
});
});
});
@@ -0,0 +1,411 @@
/**
* E2E Tests for Search Type 120: Time Span + Work Center + Item/Operation/MIS
*
* This search type allows users to search by a date range combined with work center(s)
* and part operation(s). Part operations are defined by a combination of Item Number,
* Operation Number, MIS Number, and MIS Revision.
*
* Required filters:
* - Timespan (Min Date to Max Date)
* - Work Center (one or more)
* - Item/Operation/MIS (one or more part operations via file upload)
*/
import { test, expect } from '@playwright/test';
import { navigateToSearchPage } from '../helpers/navigation.helper';
import { selectSearchType, SearchTypes, enterSearchName, clickSubmitSearch, confirmSubmitSearch } from '../helpers/search-type.helper';
import { setDateRange, setMinDate, setMaxDate, TestDateRanges } from '../helpers/date-picker.helper';
import { addWorkCenter, addWorkCenters, workCenterConfig, getAutocompleteItemCount } from '../helpers/autocomplete.helper';
import { uploadFile, partOperationConfig, getTestFile, TestFiles, getUploadedItemCount } from '../helpers/file-upload.helper';
import { assertNoErrorNotification, hasErrorNotification } from '../helpers/radzen.helper';
import { hasValidationErrors } from '../helpers/validation.helper';
// Valid test data from manual test scripts
const TEST_WORK_CENTERS = {
SINGLE_AS: '0083AS',
SINGLE_CA: '10595CA',
MULTIPLE_CA: ['11275CA', '11350CA', '11355CA'],
MULTIPLE_AS: ['0083AS', '0278AS'],
MIXED_SUFFIXES: ['0424AS', '14305CA', '1700CB'],
HISTORICAL: '1010AS',
WITH_SAME_MIS: '13316CA',
};
// Note: Part operations are uploaded via file containing columns:
// Item Number, Operation Number, MIS Number, MIS Revision
test.describe('Search Type 120: Time Span + Work Center + Item/Operation/MIS', () => {
test.beforeEach(async ({ page }) => {
await navigateToSearchPage(page);
await selectSearchType(page, SearchTypes.TIMESPAN_WC_PARTOP);
});
test.describe('Positive Test Cases', () => {
test('TC-120-P01: Single Work Center with Single Part Operation', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 120-P01 Single WC Single PartOp');
// Set date range
await setDateRange(page, '2018-01-01', '2020-09-01');
// Add single work center
await addWorkCenter(page, TEST_WORK_CENTERS.SINGLE_AS);
// Upload single part operation file
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
// Verify work center was added
const wcCount = await getAutocompleteItemCount(page, workCenterConfig);
expect(wcCount).toBeGreaterThanOrEqual(1);
// Verify no error notification
await assertNoErrorNotification(page);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Should navigate to search page
await expect(page).toHaveURL(/\/search\/\d+/);
});
test('TC-120-P02: Single Work Center with Multiple Part Operations', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 120-P02 Single WC Multi PartOps');
// Set date range
await setDateRange(page, '2018-01-01', '2020-09-01');
// Add single work center
await addWorkCenter(page, TEST_WORK_CENTERS.SINGLE_CA);
// Upload multiple part operations file
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.MULTIPLE_OPERATIONS));
// Verify part operations were uploaded
const partOpCount = await getUploadedItemCount(page, partOperationConfig);
expect(partOpCount).toBeGreaterThan(1);
// Verify no error notification
await assertNoErrorNotification(page);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Should navigate to search page
await expect(page).toHaveURL(/\/search\/\d+/);
});
test('TC-120-P03: Multiple Work Centers with Single Part Operation', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 120-P03 Multi WC Single PartOp');
// Set date range (recent)
await setDateRange(page, '2019-01-01', '2020-09-01');
// Add multiple work centers
await addWorkCenters(page, TEST_WORK_CENTERS.MULTIPLE_CA);
// Verify work centers were added
const wcCount = await getAutocompleteItemCount(page, workCenterConfig);
expect(wcCount).toBe(3);
// Upload single part operation file
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
// Verify no error notification
await assertNoErrorNotification(page);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Should navigate to search page
await expect(page).toHaveURL(/\/search\/\d+/);
});
test('TC-120-P04: Multiple Work Centers with Multiple Part Operations', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 120-P04 Multi WC Multi PartOps');
// Set date range (wide)
await setDateRange(page, '2017-01-01', '2020-09-01');
// Add multiple work centers
await addWorkCenters(page, TEST_WORK_CENTERS.MULTIPLE_AS);
// Verify work centers were added
const wcCount = await getAutocompleteItemCount(page, workCenterConfig);
expect(wcCount).toBe(2);
// Upload multiple part operations file
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.MULTIPLE_OPERATIONS));
// Verify no error notification
await assertNoErrorNotification(page);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Should navigate to search page
await expect(page).toHaveURL(/\/search\/\d+/);
});
test('TC-120-P05: Historical Date Range with Part Operations', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 120-P05 Historical PartOp Search');
// Set historical date range
await setDateRange(page, TestDateRanges.HISTORICAL.min, TestDateRanges.HISTORICAL.max);
// Add work center
await addWorkCenter(page, TEST_WORK_CENTERS.HISTORICAL);
// Upload part operation file
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
// Verify no error notification
await assertNoErrorNotification(page);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Should navigate to search page
await expect(page).toHaveURL(/\/search\/\d+/);
});
test('TC-120-P06: Multiple Part Operations with Same MIS Number', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 120-P06 Same MIS Diff Items');
// Set date range
await setDateRange(page, '2018-01-01', '2020-09-01');
// Add work center
await addWorkCenter(page, TEST_WORK_CENTERS.WITH_SAME_MIS);
// Upload multiple part operations file (items share same MIS number)
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.MULTIPLE_OPERATIONS));
// Verify no error notification
await assertNoErrorNotification(page);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Should navigate to search page
await expect(page).toHaveURL(/\/search\/\d+/);
});
test('TC-120-P07: Work Center Code Variants with Part Operations', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 120-P07 WC Variants PartOps');
// Set date range
await setDateRange(page, '2018-01-01', '2020-09-01');
// Add work centers with different suffixes (AS, CA, CB)
await addWorkCenters(page, TEST_WORK_CENTERS.MIXED_SUFFIXES);
// Verify all work centers were added
const wcCount = await getAutocompleteItemCount(page, workCenterConfig);
expect(wcCount).toBe(3);
// Upload part operation file
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
// Verify no error notification
await assertNoErrorNotification(page);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Should navigate to search page
await expect(page).toHaveURL(/\/search\/\d+/);
});
});
test.describe('Negative Test Cases', () => {
test('TC-120-N01: Missing Required Date Range', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 120-N01 Missing Dates');
// Do NOT set date range (leave empty)
// Add work center
await addWorkCenter(page, TEST_WORK_CENTERS.SINGLE_AS);
// Upload part operation file
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
// Attempt to submit
await clickSubmitSearch(page);
// Should show validation error
expect(await hasValidationErrors(page) || await hasErrorNotification(page)).toBe(true);
});
test('TC-120-N02: Missing Work Center', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 120-N02 Missing Work Center');
// Set date range
await setDateRange(page, '2018-01-01', '2020-09-01');
// Do NOT add any work center
// Upload part operation file
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
// Attempt to submit
await clickSubmitSearch(page);
// Should show validation error
expect(await hasValidationErrors(page) || await hasErrorNotification(page)).toBe(true);
});
test('TC-120-N03: Missing Part Operation', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 120-N03 Missing Part Operation');
// Set date range
await setDateRange(page, '2018-01-01', '2020-09-01');
// Add work center
await addWorkCenter(page, TEST_WORK_CENTERS.SINGLE_AS);
// Do NOT upload any part operation file
// Attempt to submit
await clickSubmitSearch(page);
// Should show validation error
expect(await hasValidationErrors(page) || await hasErrorNotification(page)).toBe(true);
});
test('TC-120-N04: Invalid Date Range (End Before Start)', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 120-N04 Invalid Date Range');
// Set invalid date range (max before min)
await setDateRange(page, TestDateRanges.INVALID_REVERSED.min, TestDateRanges.INVALID_REVERSED.max);
// Add work center
await addWorkCenter(page, TEST_WORK_CENTERS.SINGLE_AS);
// Upload part operation file
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
// Attempt to submit
await clickSubmitSearch(page);
// Should show validation error
expect(await hasValidationErrors(page) || await hasErrorNotification(page)).toBe(true);
});
test('TC-120-N05: Missing Minimum Date Only', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 120-N05 Missing Min Date');
// Set only max date
await setMaxDate(page, '2020-09-01');
// Add work center
await addWorkCenter(page, TEST_WORK_CENTERS.SINGLE_CA);
// Upload part operation file
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
// Attempt to submit
await clickSubmitSearch(page);
// Should show validation error
expect(await hasValidationErrors(page) || await hasErrorNotification(page)).toBe(true);
});
test('TC-120-N06: Missing Maximum Date Only', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 120-N06 Missing Max Date');
// Set only min date
await setMinDate(page, '2018-01-01');
// Add work center
await addWorkCenter(page, '11275CA');
// Upload part operation file
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
// Attempt to submit
await clickSubmitSearch(page);
// Should show validation error
expect(await hasValidationErrors(page) || await hasErrorNotification(page)).toBe(true);
});
test('TC-120-N07: Missing Search Name', async ({ page }) => {
// Do NOT enter search name
// Set date range
await setDateRange(page, '2018-01-01', '2020-09-01');
// Add work center
await addWorkCenter(page, TEST_WORK_CENTERS.SINGLE_AS);
// Upload part operation file
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
// Attempt to submit
await clickSubmitSearch(page);
// Should show validation error
expect(await hasValidationErrors(page) || await hasErrorNotification(page)).toBe(true);
});
test('TC-120-N08: All Required Filters Missing', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 120-N08 All Filters Missing');
// Do NOT set date range
// Do NOT add work center
// Do NOT upload part operation file
// Attempt to submit
await clickSubmitSearch(page);
// Should show validation error
expect(await hasValidationErrors(page) || await hasErrorNotification(page)).toBe(true);
});
test('TC-120-N09: Empty Part Operation File', async ({ page }) => {
// Enter search name
await enterSearchName(page, 'Test 120-N09 Empty PartOp File');
// Set date range
await setDateRange(page, '2018-01-01', '2020-09-01');
// Add work center
await addWorkCenter(page, TEST_WORK_CENTERS.SINGLE_AS);
// Upload empty file (if available)
try {
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.EMPTY_FILE));
} catch {
// If empty file test data doesn't exist, skip this check
test.skip();
return;
}
// Attempt to submit
await clickSubmitSearch(page);
// Should show validation error or empty grid
expect(await hasValidationErrors(page) || await hasErrorNotification(page)).toBe(true);
});
});
});
@@ -0,0 +1,557 @@
/**
* Playwright E2E tests for Search Type 130:
* Time Span + Work Center + Work Order + Item/Operation/MIS
*
* This search type allows users to search for work order data within a specific
* date range, filtered by work center, work order number, and part operation details.
*
* Filters Enabled:
* - Timespan (Start Date, End Date)
* - Work Center (autocomplete)
* - Work Order (file upload)
* - Item/Operation/MIS (Part Operations - file upload)
*/
import { test, expect } from '@playwright/test';
import { navigateToSearchPage } from '../helpers/navigation.helper';
import { selectSearchType, SearchTypes, enterSearchName, clickSubmitSearch, confirmSubmitSearch } from '../helpers/search-type.helper';
import { setDateRange, setMinDate, setMaxDate, clearMinDate, clearMaxDate, TestDateRanges } from '../helpers/date-picker.helper';
import {
addWorkCenter,
addWorkCenters,
workCenterConfig,
getAutocompleteItemCount,
isAutocompletePanelVisible
} from '../helpers/autocomplete.helper';
import {
uploadFile,
workOrderConfig,
partOperationConfig,
getTestFile,
TestFiles,
getUploadedItemCount,
isFileUploadPanelVisible
} from '../helpers/file-upload.helper';
import { assertNoErrorNotification, hasErrorNotification } from '../helpers/radzen.helper';
import { hasValidationErrors, submitAndExpectError, ValidationMessages } from '../helpers/validation.helper';
// Test data constants for Type 130
const TYPE_130_NAME = SearchTypes.TIMESPAN_WC_WO_PARTOP;
// Valid work centers from test documentation
const VALID_WORK_CENTERS = ['0083AS', '0278AS', '0424AS', '0586AS', '0696AS', '1010AS', '1011AS', '10595CA', '11275CA', '11350CA'];
// Standard date ranges for testing
const STANDARD_DATE_RANGE = { min: '2018-01-01', max: '2020-09-01' };
const RECENT_DATE_RANGE = TestDateRanges.RECENT;
const HISTORICAL_DATE_RANGE = TestDateRanges.HISTORICAL;
const NARROW_DATE_RANGE = { min: '2020-06-01', max: '2020-06-30' };
const INVALID_DATE_RANGE = TestDateRanges.INVALID_REVERSED;
test.describe('Search Type 130: Time Span + Work Center + Work Order + Item/Operation/MIS', () => {
test.beforeEach(async ({ page }) => {
await navigateToSearchPage(page);
});
test.describe('Positive Test Cases', () => {
test('TC-130-P01: Single values for all filters', async ({ page }) => {
// Step 1: Enter search name
await enterSearchName(page, 'TC-130-P01 Single Values');
// Step 2: Select search type
await selectSearchType(page, TYPE_130_NAME);
// Verify all filter panels are visible
expect(await isAutocompletePanelVisible(page, workCenterConfig)).toBe(true);
expect(await isFileUploadPanelVisible(page, workOrderConfig)).toBe(true);
expect(await isFileUploadPanelVisible(page, partOperationConfig)).toBe(true);
// Step 3: Set date range
await setDateRange(page, STANDARD_DATE_RANGE.min, STANDARD_DATE_RANGE.max);
// Step 4: Add single work center
await addWorkCenter(page, '1010AS');
expect(await getAutocompleteItemCount(page, workCenterConfig)).toBe(1);
// Step 5: Upload single work order file
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.SINGLE_WORKORDER));
// Step 6: Upload single part operation file
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
// Step 7: Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Verify no error notification
await assertNoErrorNotification(page);
});
test('TC-130-P02: Multiple work centers', async ({ page }) => {
await enterSearchName(page, 'TC-130-P02 Multiple Work Centers');
await selectSearchType(page, TYPE_130_NAME);
await setDateRange(page, '2019-01-01', STANDARD_DATE_RANGE.max);
// Add multiple work centers
await addWorkCenters(page, ['0083AS', '1010AS', '1011AS']);
expect(await getAutocompleteItemCount(page, workCenterConfig)).toBe(3);
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.SINGLE_WORKORDER));
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-130-P03: Multiple work orders', async ({ page }) => {
await enterSearchName(page, 'TC-130-P03 Multiple Work Orders');
await selectSearchType(page, TYPE_130_NAME);
await setDateRange(page, STANDARD_DATE_RANGE.min, '2019-12-31');
await addWorkCenter(page, '0424AS');
// Upload multiple work orders
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.MULTIPLE_WORKORDERS));
const woCount = await getUploadedItemCount(page, workOrderConfig);
expect(woCount).toBeGreaterThan(1);
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-130-P04: Multiple part operations', async ({ page }) => {
await enterSearchName(page, 'TC-130-P04 Multiple Part Ops');
await selectSearchType(page, TYPE_130_NAME);
await setDateRange(page, STANDARD_DATE_RANGE.min, STANDARD_DATE_RANGE.max);
await addWorkCenter(page, '0586AS');
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.SINGLE_WORKORDER));
// Upload multiple part operations
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.MULTIPLE_OPERATIONS));
const opCount = await getUploadedItemCount(page, partOperationConfig);
expect(opCount).toBeGreaterThan(1);
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-130-P05: All filters with multiple values', async ({ page }) => {
await enterSearchName(page, 'TC-130-P05 All Multiple Values');
await selectSearchType(page, TYPE_130_NAME);
await setDateRange(page, STANDARD_DATE_RANGE.min, STANDARD_DATE_RANGE.max);
// Multiple work centers
await addWorkCenters(page, ['1010AS', '11275CA']);
expect(await getAutocompleteItemCount(page, workCenterConfig)).toBe(2);
// Multiple work orders
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.MULTIPLE_WORKORDERS));
// Multiple part operations
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.MULTIPLE_OPERATIONS));
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-130-P06: Historical date range', async ({ page }) => {
await enterSearchName(page, 'TC-130-P06 Historical Range');
await selectSearchType(page, TYPE_130_NAME);
// Use historical date range
await setDateRange(page, HISTORICAL_DATE_RANGE.min, HISTORICAL_DATE_RANGE.max);
await addWorkCenter(page, '10595CA');
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.SINGLE_WORKORDER));
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-130-P07: Narrow date range (same month)', async ({ page }) => {
await enterSearchName(page, 'TC-130-P07 Narrow Date Range');
await selectSearchType(page, TYPE_130_NAME);
// Use narrow date range (single month)
await setDateRange(page, NARROW_DATE_RANGE.min, NARROW_DATE_RANGE.max);
await addWorkCenter(page, '0696AS');
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.SINGLE_WORKORDER));
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-130-P08: Recent date range', async ({ page }) => {
await enterSearchName(page, 'TC-130-P08 Recent Date Range');
await selectSearchType(page, TYPE_130_NAME);
// Use recent date range
await setDateRange(page, RECENT_DATE_RANGE.min, RECENT_DATE_RANGE.max);
await addWorkCenter(page, '0278AS');
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.SINGLE_WORKORDER));
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-130-P09: All work centers with different formats', async ({ page }) => {
await enterSearchName(page, 'TC-130-P09 Mixed Work Center Formats');
await selectSearchType(page, TYPE_130_NAME);
await setDateRange(page, STANDARD_DATE_RANGE.min, STANDARD_DATE_RANGE.max);
// Add work centers with different formats (AS vs CA suffix)
await addWorkCenters(page, ['0083AS', '10595CA', '11275CA']);
expect(await getAutocompleteItemCount(page, workCenterConfig)).toBe(3);
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.SINGLE_WORKORDER));
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-130-P10: Maximum work orders file', async ({ page }) => {
await enterSearchName(page, 'TC-130-P10 Max Work Orders');
await selectSearchType(page, TYPE_130_NAME);
await setDateRange(page, STANDARD_DATE_RANGE.min, STANDARD_DATE_RANGE.max);
await addWorkCenter(page, '1011AS');
// Upload maximum work orders
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.MULTIPLE_WORKORDERS));
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
});
test.describe('Negative Test Cases', () => {
test('TC-130-N01: Missing required date range', async ({ page }) => {
await enterSearchName(page, 'TC-130-N01 No Dates');
await selectSearchType(page, TYPE_130_NAME);
// Do NOT set any dates
await clearMinDate(page);
await clearMaxDate(page);
await addWorkCenter(page, '1010AS');
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.SINGLE_WORKORDER));
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await submitAndExpectError(page);
});
test('TC-130-N02: Missing work center', async ({ page }) => {
await enterSearchName(page, 'TC-130-N02 No Work Center');
await selectSearchType(page, TYPE_130_NAME);
await setDateRange(page, STANDARD_DATE_RANGE.min, STANDARD_DATE_RANGE.max);
// Do NOT add work center
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.SINGLE_WORKORDER));
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await submitAndExpectError(page);
});
test('TC-130-N03: Missing work order', async ({ page }) => {
await enterSearchName(page, 'TC-130-N03 No Work Order');
await selectSearchType(page, TYPE_130_NAME);
await setDateRange(page, STANDARD_DATE_RANGE.min, STANDARD_DATE_RANGE.max);
await addWorkCenter(page, '1010AS');
// Do NOT upload work order
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await submitAndExpectError(page);
});
test('TC-130-N04: Missing part operation', async ({ page }) => {
await enterSearchName(page, 'TC-130-N04 No Part Op');
await selectSearchType(page, TYPE_130_NAME);
await setDateRange(page, STANDARD_DATE_RANGE.min, STANDARD_DATE_RANGE.max);
await addWorkCenter(page, '1010AS');
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.SINGLE_WORKORDER));
// Do NOT upload part operation
await submitAndExpectError(page);
});
test('TC-130-N05: Start date after end date', async ({ page }) => {
await enterSearchName(page, 'TC-130-N05 Invalid Date Range');
await selectSearchType(page, TYPE_130_NAME);
// Set min date after max date
await setDateRange(page, INVALID_DATE_RANGE.min, INVALID_DATE_RANGE.max);
await addWorkCenter(page, '1010AS');
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.SINGLE_WORKORDER));
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await submitAndExpectError(page);
});
test('TC-130-N06: Missing minimum date only', async ({ page }) => {
await enterSearchName(page, 'TC-130-N06 Missing Min Date');
await selectSearchType(page, TYPE_130_NAME);
// Only set max date
await clearMinDate(page);
await setMaxDate(page, STANDARD_DATE_RANGE.max);
await addWorkCenter(page, '1010AS');
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.SINGLE_WORKORDER));
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await submitAndExpectError(page);
});
test('TC-130-N07: Missing search name', async ({ page }) => {
// Do NOT enter search name
await selectSearchType(page, TYPE_130_NAME);
await setDateRange(page, STANDARD_DATE_RANGE.min, STANDARD_DATE_RANGE.max);
await addWorkCenter(page, '1010AS');
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.SINGLE_WORKORDER));
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await submitAndExpectError(page);
});
test('TC-130-N08: Missing maximum date only', async ({ page }) => {
await enterSearchName(page, 'TC-130-N08 Missing Max Date');
await selectSearchType(page, TYPE_130_NAME);
// Only set min date
await setMinDate(page, STANDARD_DATE_RANGE.min);
await clearMaxDate(page);
await addWorkCenter(page, '1010AS');
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.SINGLE_WORKORDER));
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await submitAndExpectError(page);
});
test('TC-130-N09: Whitespace-only search name', async ({ page }) => {
// Enter whitespace-only search name
await enterSearchName(page, ' ');
await selectSearchType(page, TYPE_130_NAME);
await setDateRange(page, STANDARD_DATE_RANGE.min, STANDARD_DATE_RANGE.max);
await addWorkCenter(page, '1010AS');
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.SINGLE_WORKORDER));
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
await submitAndExpectError(page);
});
test('TC-130-N10: Missing all required filters', async ({ page }) => {
await enterSearchName(page, 'TC-130-N10 No Filters');
await selectSearchType(page, TYPE_130_NAME);
// Do not set any filters
await clearMinDate(page);
await clearMaxDate(page);
await submitAndExpectError(page);
});
test('TC-130-N11: Empty work order file', async ({ page }) => {
await enterSearchName(page, 'TC-130-N11 Empty WO File');
await selectSearchType(page, TYPE_130_NAME);
await setDateRange(page, STANDARD_DATE_RANGE.min, STANDARD_DATE_RANGE.max);
await addWorkCenter(page, '1010AS');
// Upload empty file
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.EMPTY_FILE));
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
// Should either show error notification or validation error
const hasError = await hasErrorNotification(page) || await hasValidationErrors(page);
if (!hasError) {
await submitAndExpectError(page);
}
});
test('TC-130-N12: Empty part operation file', async ({ page }) => {
await enterSearchName(page, 'TC-130-N12 Empty Part Op File');
await selectSearchType(page, TYPE_130_NAME);
await setDateRange(page, STANDARD_DATE_RANGE.min, STANDARD_DATE_RANGE.max);
await addWorkCenter(page, '1010AS');
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.SINGLE_WORKORDER));
// Upload empty file for part operations
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.EMPTY_FILE));
// Should either show error notification or validation error
const hasError = await hasErrorNotification(page) || await hasValidationErrors(page);
if (!hasError) {
await submitAndExpectError(page);
}
});
});
test.describe('Filter Panel Visibility', () => {
test('TC-130-V01: All filter panels visible when search type selected', async ({ page }) => {
await selectSearchType(page, TYPE_130_NAME);
// Verify all four filter panels are visible
await expect(page.locator('text=Filter by Time Span')).toBeVisible();
await expect(page.locator('text=Filter by Work Center')).toBeVisible();
await expect(page.locator('text=Filter by Work Order')).toBeVisible();
await expect(page.locator('text=Filter By Item/Operation/MIS')).toBeVisible();
});
test('TC-130-V02: Date inputs are available in time span panel', async ({ page }) => {
await selectSearchType(page, TYPE_130_NAME);
// Verify date input fields exist
const minDateInput = page.locator('input[name="MinimumDt"]');
const maxDateInput = page.locator('input[name="MaximumDt"]');
await expect(minDateInput).toBeVisible();
await expect(maxDateInput).toBeVisible();
});
test('TC-130-V03: Autocomplete available for work center', async ({ page }) => {
await selectSearchType(page, TYPE_130_NAME);
// Verify autocomplete component exists in work center panel
const panel = page.locator(`.rz-card:has-text("${workCenterConfig.panelHeader}")`);
const autocomplete = panel.locator('.rz-autocomplete');
await expect(autocomplete).toBeVisible();
});
test('TC-130-V04: File upload available for work order', async ({ page }) => {
await selectSearchType(page, TYPE_130_NAME);
// Verify file input exists in work order panel
const panel = page.locator(`.rz-card:has-text("${workOrderConfig.panelHeader}")`);
const fileInput = panel.locator('input[type="file"]');
await expect(fileInput).toBeAttached();
});
test('TC-130-V05: File upload available for part operation', async ({ page }) => {
await selectSearchType(page, TYPE_130_NAME);
// Verify file input exists in part operation panel
const panel = page.locator(`.rz-card:has-text("${partOperationConfig.panelHeader}")`);
const fileInput = panel.locator('input[type="file"]');
await expect(fileInput).toBeAttached();
});
});
test.describe('Template Downloads', () => {
test('TC-130-T01: Download work order template', async ({ page }) => {
await selectSearchType(page, TYPE_130_NAME);
const panel = page.locator(`.rz-card:has-text("${workOrderConfig.panelHeader}")`);
const downloadPromise = page.waitForEvent('download');
await panel.locator('button:has-text("Download Template")').click();
const download = await downloadPromise;
expect(download.suggestedFilename()).toContain('.xlsx');
});
test('TC-130-T02: Download part operation template', async ({ page }) => {
await selectSearchType(page, TYPE_130_NAME);
const panel = page.locator(`.rz-card:has-text("${partOperationConfig.panelHeader}")`);
const downloadPromise = page.waitForEvent('download');
await panel.locator('button:has-text("Download Template")').click();
const download = await downloadPromise;
expect(download.suggestedFilename()).toContain('.xlsx');
});
});
test.describe('Data Grid Functionality', () => {
test('TC-130-G01: Work order grid shows uploaded data', async ({ page }) => {
await selectSearchType(page, TYPE_130_NAME);
await uploadFile(page, workOrderConfig, getTestFile(TestFiles.SINGLE_WORKORDER));
// Verify data appears in grid (or no error if data not in DB)
const count = await getUploadedItemCount(page, workOrderConfig);
// Count can be 0 if work order not found in DB, but should not error
await assertNoErrorNotification(page);
});
test('TC-130-G02: Part operation grid shows uploaded data', async ({ page }) => {
await selectSearchType(page, TYPE_130_NAME);
await uploadFile(page, partOperationConfig, getTestFile(TestFiles.SINGLE_OPERATION));
// Verify data appears in grid (or no error if data not in DB)
const count = await getUploadedItemCount(page, partOperationConfig);
await assertNoErrorNotification(page);
});
test('TC-130-G03: Work center grid shows added items', async ({ page }) => {
await selectSearchType(page, TYPE_130_NAME);
await addWorkCenter(page, '1010AS');
const count = await getAutocompleteItemCount(page, workCenterConfig);
expect(count).toBe(1);
});
});
});
@@ -0,0 +1,505 @@
import { test, expect } from '@playwright/test';
import { navigateToSearchPage } from '../helpers/navigation.helper';
import { selectSearchType, SearchTypes, enterSearchName, clickSubmitSearch, confirmSubmitSearch } from '../helpers/search-type.helper';
import { setDateRange, setMinDate, setMaxDate, clearDateRange, TestDateRanges } from '../helpers/date-picker.helper';
import {
addWorkCenter,
addWorkCenters,
clearAutocompleteItems,
workCenterConfig,
getAutocompleteItemCount,
removeAutocompleteItem,
isAutocompletePanelVisible,
} from '../helpers/autocomplete.helper';
import { assertNoErrorNotification, hasSuccessNotification } from '../helpers/radzen.helper';
import { hasValidationErrors, submitAndExpectError, ValidationMessages } from '../helpers/validation.helper';
/**
* Test suite for Search Type 40: Time Span + Work Center
*
* This search type allows users to search by a date range combined with
* one or more work center codes.
*
* Filters Enabled: Timespan, Work Center
*/
test.describe('Search Type 40: Time Span + Work Center', () => {
test.beforeEach(async ({ page }) => {
await navigateToSearchPage(page);
});
// Valid work center test data
const validWorkCenters = {
AS_SUFFIX: ['0083AS', '0278AS', '0424AS', '0586AS', '0696AS', '1010AS', '1011AS'],
CA_SUFFIX: ['10595CA', '11275CA', '11350CA', '11355CA', '13316CA', '14305CA', '15660CA', '200038CA', '200039CA', '200041CA', '200042CA'],
CB_SUFFIX: ['1700CB'],
NO_SUFFIX: ['200039'],
};
// ============================================================================
// POSITIVE TEST CASES
// ============================================================================
test.describe('Positive Tests', () => {
test('TC-040-P01: Single work center with standard date range', async ({ page }) => {
// Select search type
await selectSearchType(page, SearchTypes.TIMESPAN_WORK_CENTER);
// Verify filter panels are visible
await expect(page.locator('text=Filter by Work Center')).toBeVisible();
await expect(page.locator('text=Filter by Time Span')).toBeVisible();
// Enter search name
await enterSearchName(page, 'TC-040-P01 Single Work Center');
// Set date range
await setDateRange(page, '2019-01-01', '2019-12-31');
// Add work center
await addWorkCenter(page, '0083AS');
// Verify work center appears in the list
const itemCount = await getAutocompleteItemCount(page, workCenterConfig);
expect(itemCount).toBe(1);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Verify no error notification
await assertNoErrorNotification(page);
});
test('TC-040-P02: Multiple work centers search', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_WORK_CENTER);
await enterSearchName(page, 'TC-040-P02 Multiple Work Centers');
// Set date range
await setDateRange(page, '2018-01-01', '2019-12-31');
// Add multiple work centers from different categories
await addWorkCenters(page, ['0083AS', '10595CA', '1700CB']);
// Verify all work centers appear in the list
const itemCount = await getAutocompleteItemCount(page, workCenterConfig);
expect(itemCount).toBe(3);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-040-P03: Work centers by suffix type (AS)', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_WORK_CENTER);
await enterSearchName(page, 'TC-040-P03 AS Work Centers');
// Set date range
await setDateRange(page, '2019-01-01', '2019-12-31');
// Add all AS-suffix work centers
await addWorkCenters(page, validWorkCenters.AS_SUFFIX);
// Verify all work centers appear in the list
const itemCount = await getAutocompleteItemCount(page, workCenterConfig);
expect(itemCount).toBe(7);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-040-P04: Work centers by suffix type (CA)', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_WORK_CENTER);
await enterSearchName(page, 'TC-040-P04 CA Work Centers');
// Set date range
await setDateRange(page, '2018-01-01', '2020-09-01');
// Add all CA-suffix work centers
await addWorkCenters(page, validWorkCenters.CA_SUFFIX);
// Verify all work centers appear in the list
const itemCount = await getAutocompleteItemCount(page, workCenterConfig);
expect(itemCount).toBe(11);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-040-P05: All work centers search', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_WORK_CENTER);
await enterSearchName(page, 'TC-040-P05 All Work Centers');
// Set date range
await setDateRange(page, '2019-06-01', '2019-12-31');
// Add all work centers from all categories
const allWorkCenters = [
...validWorkCenters.AS_SUFFIX,
...validWorkCenters.CA_SUFFIX,
...validWorkCenters.CB_SUFFIX,
...validWorkCenters.NO_SUFFIX,
];
await addWorkCenters(page, allWorkCenters);
// Verify all work centers appear in the list
const itemCount = await getAutocompleteItemCount(page, workCenterConfig);
expect(itemCount).toBe(20);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-040-P06: Minimum date range (same day)', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_WORK_CENTER);
await enterSearchName(page, 'TC-040-P06 WC Same Day Range');
// Set same day date range
await setDateRange(page, '2019-07-15', '2019-07-15');
// Add work center
await addWorkCenter(page, '10595CA');
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-040-P07: Boundary date - start of data range', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_WORK_CENTER);
await enterSearchName(page, 'TC-040-P07 WC Start Boundary');
// Set earliest date boundary
await setDateRange(page, TestDateRanges.START_BOUNDARY.min, TestDateRanges.START_BOUNDARY.max);
// Add work center
await addWorkCenter(page, '0083AS');
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-040-P08: Boundary date - end of data range', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_WORK_CENTER);
await enterSearchName(page, 'TC-040-P08 WC End Boundary');
// Set latest date boundary
await setDateRange(page, TestDateRanges.END_BOUNDARY.min, TestDateRanges.END_BOUNDARY.max);
// Add work center
await addWorkCenter(page, '11275CA');
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-040-P09: Historical date range search', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_WORK_CENTER);
await enterSearchName(page, 'TC-040-P09 WC Historical Search');
// Set historical date range
await setDateRange(page, TestDateRanges.HISTORICAL.min, TestDateRanges.HISTORICAL.max);
// Add work center
await addWorkCenter(page, '14305CA');
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-040-P10: Work center remove and re-add', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_WORK_CENTER);
await enterSearchName(page, 'TC-040-P10 WC Remove Re-add');
// Set date range
await setDateRange(page, '2019-01-01', '2019-12-31');
// Add work centers
await addWorkCenter(page, '0083AS');
await addWorkCenter(page, '0278AS');
// Verify both are added
let itemCount = await getAutocompleteItemCount(page, workCenterConfig);
expect(itemCount).toBe(2);
// Remove first work center (0083AS)
await removeAutocompleteItem(page, workCenterConfig, 0);
// Verify only one remains
itemCount = await getAutocompleteItemCount(page, workCenterConfig);
expect(itemCount).toBe(1);
// Add another work center
await addWorkCenter(page, '0424AS');
// Verify two work centers in list
itemCount = await getAutocompleteItemCount(page, workCenterConfig);
expect(itemCount).toBe(2);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
test('TC-040-P11: Work center with no suffix (200039)', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_WORK_CENTER);
await enterSearchName(page, 'TC-040-P11 WC No Suffix');
// Set date range
await setDateRange(page, '2019-01-01', '2019-12-31');
// Add work center without suffix
await addWorkCenter(page, '200039');
// Verify work center appears in the list
const itemCount = await getAutocompleteItemCount(page, workCenterConfig);
expect(itemCount).toBe(1);
// Submit search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
});
});
// ============================================================================
// NEGATIVE TEST CASES
// ============================================================================
test.describe('Negative Tests', () => {
test('TC-040-N01: Missing search name', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_WORK_CENTER);
// Do NOT enter search name
// Set valid date range
await setDateRange(page, '2019-01-01', '2019-12-31');
// Add work center
await addWorkCenter(page, '0083AS');
// Attempt to submit
await submitAndExpectError(page);
// Verify user remains on the page
await expect(page.locator('text=Filter by Work Center')).toBeVisible();
});
test('TC-040-N02: No search type selected', async ({ page }) => {
// Enter search name without selecting search type
await enterSearchName(page, 'TC-040-N02 No Type');
// Verify filter panels are not visible
const workCenterPanelVisible = await isAutocompletePanelVisible(page, workCenterConfig);
expect(workCenterPanelVisible).toBe(false);
// Attempt to submit
await submitAndExpectError(page);
});
test('TC-040-N03: Missing minimum date', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_WORK_CENTER);
await enterSearchName(page, 'TC-040-N03 WC Missing Min Date');
// Only set maximum date
await setMaxDate(page, '2019-12-31');
// Add work center
await addWorkCenter(page, '0083AS');
// Attempt to submit
await submitAndExpectError(page);
});
test('TC-040-N04: Missing maximum date', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_WORK_CENTER);
await enterSearchName(page, 'TC-040-N04 WC Missing Max Date');
// Only set minimum date
await setMinDate(page, '2019-01-01');
// Add work center
await addWorkCenter(page, '0083AS');
// Attempt to submit
await submitAndExpectError(page);
});
test('TC-040-N05: Empty work center list', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_WORK_CENTER);
await enterSearchName(page, 'TC-040-N05 Empty Work Centers');
// Set valid date range
await setDateRange(page, '2019-01-01', '2019-12-31');
// Do NOT add any work centers
// Verify work center list is empty
const itemCount = await getAutocompleteItemCount(page, workCenterConfig);
expect(itemCount).toBe(0);
// Attempt to submit
await submitAndExpectError(page);
});
test('TC-040-N06: Invalid date range (min > max)', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_WORK_CENTER);
await enterSearchName(page, 'TC-040-N06 WC Invalid Date Range');
// Set invalid date range (min date after max date)
await setDateRange(page, '2019-12-31', '2019-01-01');
// Add work center
await addWorkCenter(page, '0083AS');
// Attempt to submit
await submitAndExpectError(page);
});
test('TC-040-N07: Invalid work center code', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_WORK_CENTER);
await enterSearchName(page, 'TC-040-N07 Invalid WC Code');
// Set valid date range
await setDateRange(page, '2019-01-01', '2019-12-31');
// Try to add invalid work center
const panel = page.locator(`.rz-card:has-text("${workCenterConfig.panelHeader}")`);
const autocomplete = panel.locator('.rz-autocomplete input');
await autocomplete.fill('INVALIDWC');
// Wait for autocomplete to search
await page.waitForTimeout(500);
// Verify no autocomplete suggestions appear
const dropdown = page.locator('.rz-autocomplete-list');
const dropdownVisible = await dropdown.isVisible({ timeout: 2000 }).catch(() => false);
if (dropdownVisible) {
const items = dropdown.locator('.rz-autocomplete-list-item');
const count = await items.count();
expect(count).toBe(0);
}
// Verify work center list is still empty
const itemCount = await getAutocompleteItemCount(page, workCenterConfig);
expect(itemCount).toBe(0);
});
test('TC-040-N08: Future date range', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_WORK_CENTER);
await enterSearchName(page, 'TC-040-N08 WC Future Dates');
// Set future date range
await setDateRange(page, TestDateRanges.FUTURE.min, TestDateRanges.FUTURE.max);
// Add work center
await addWorkCenter(page, '0083AS');
// Submit search - may be accepted but will return no results
await clickSubmitSearch(page);
// Check if there's a validation error or if it proceeds
const hasErrors = await hasValidationErrors(page);
if (!hasErrors) {
await confirmSubmitSearch(page);
await assertNoErrorNotification(page);
}
});
test('TC-040-N09: Invalid date format', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_WORK_CENTER);
await enterSearchName(page, 'TC-040-N09 WC Invalid Date Format');
// Try to enter invalid date format
const minDateInput = page.locator('input[name="MinimumDt"]');
await minDateInput.fill('31-12-2019');
await setMaxDate(page, '2019-12-31');
// Add work center
await addWorkCenter(page, '0083AS');
// Attempt to submit
await submitAndExpectError(page);
});
test('TC-040-N10: Work center with special characters', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_WORK_CENTER);
await enterSearchName(page, 'TC-040-N10 WC Special Chars');
// Set valid date range
await setDateRange(page, '2019-01-01', '2019-12-31');
// Try to add work center with special characters
const panel = page.locator(`.rz-card:has-text("${workCenterConfig.panelHeader}")`);
const autocomplete = panel.locator('.rz-autocomplete input');
await autocomplete.fill('0083AS!@#');
// Wait for autocomplete to search
await page.waitForTimeout(500);
// Verify no autocomplete suggestions appear for invalid input
const dropdown = page.locator('.rz-autocomplete-list');
const dropdownVisible = await dropdown.isVisible({ timeout: 2000 }).catch(() => false);
if (dropdownVisible) {
const items = dropdown.locator('.rz-autocomplete-list-item');
const count = await items.count();
expect(count).toBe(0);
}
// Verify work center list is still empty
const itemCount = await getAutocompleteItemCount(page, workCenterConfig);
expect(itemCount).toBe(0);
});
test('TC-040-N11: Duplicate work center entry', async ({ page }) => {
await selectSearchType(page, SearchTypes.TIMESPAN_WORK_CENTER);
await enterSearchName(page, 'TC-040-N11 WC Duplicate');
// Set valid date range
await setDateRange(page, '2019-01-01', '2019-12-31');
// Add work center
await addWorkCenter(page, '0083AS');
// Verify one entry
let itemCount = await getAutocompleteItemCount(page, workCenterConfig);
expect(itemCount).toBe(1);
// Attempt to add the same work center again
await addWorkCenter(page, '0083AS');
// Wait for any duplicate handling
await page.waitForTimeout(500);
// Verify only one entry remains (duplicate should be rejected)
itemCount = await getAutocompleteItemCount(page, workCenterConfig);
expect(itemCount).toBe(1);
});
});
});
@@ -0,0 +1,450 @@
import { test, expect } from '@playwright/test';
import path from 'path';
import { navigateToSearchPage } from '../helpers/navigation.helper';
import { selectSearchType, SearchTypes, enterSearchName, clickSubmitSearch, confirmSubmitSearch } from '../helpers/search-type.helper';
import { uploadFile, clearUploadedData, downloadTemplate, getUploadedItemCount, workOrderConfig, getTestFile, TestFiles, getTestDataPath } from '../helpers/file-upload.helper';
import { assertNoErrorNotification, dataGridIsEmpty, confirmDialog, hasErrorNotification, waitForNotification } from '../helpers/radzen.helper';
import { hasValidationErrors, submitAndExpectError, assertValidationError, ValidationMessages, getValidationErrors } from '../helpers/validation.helper';
/**
* Work Order Search (Type 10) - E2E Tests
*
* Based on manual test scripts from TestScripts/SearchPage/10_WorkOrder.md
* Tests the Work Order search functionality which allows users to search by
* one or more work order numbers without requiring a time span or other filters.
*
* NOTE: There is a known application bug where the FileApiClient expects IReadOnlyList<T>
* but the API returns FileUploadResult<T>. Tests check for no error first, then
* verify counts only when upload succeeds.
*/
test.describe('Work Order Search (Type 10)', () => {
test.beforeEach(async ({ page }) => {
await navigateToSearchPage(page);
});
/**
* POSITIVE TEST CASES
* NOTE: These tests are skipped due to a known API response parsing bug.
* The API returns FileUploadResult<T> but the client expects IReadOnlyList<T>.
* See: NEW/src/JdeScoping.Client/Services/FileApiClient.cs (UploadWorkOrdersAsync)
* vs: NEW/src/JdeScoping.Api/Controllers/FileIOController.WorkOrders.cs (FileUploadResult)
*/
test.describe('Positive Tests', () => {
test('TC-010-P01: Single Work Order Search', async ({ page }) => {
// Step 1-2: Navigate to Submit Search page (done in beforeEach), enter search name
await enterSearchName(page, 'TC-010-P01 Single Work Order Test');
// Step 3: Select "Work Order" search type (Type 10)
await selectSearchType(page, SearchTypes.WORK_ORDER);
// Step 4-6: Upload single work order file and verify it appears in grid
await expect(page.locator(`text=${workOrderConfig.panelHeader}`)).toBeVisible();
const testFile = getTestFile(TestFiles.SINGLE_WORKORDER);
await uploadFile(page, workOrderConfig, testFile);
// First check for errors - if upload mechanism works, there should be no error
const hasError = await hasErrorNotification(page);
if (!hasError) {
// Verify work order appears in the grid (count should be 1)
const count = await getUploadedItemCount(page, workOrderConfig);
expect(count).toBe(1);
// Step 7: Click Submit Search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Expected Results: Search is created successfully (no errors)
await assertNoErrorNotification(page);
} else {
// Upload failed - this is a known issue with API response parsing
// Test passes if no error notification (upload mechanism works)
test.info().annotations.push({ type: 'issue', description: 'Upload failed due to API response parsing issue' });
expect(hasError).toBe(false); // This will fail, marking the test
}
});
test('TC-010-P02: Multiple Work Orders Search', async ({ page }) => {
// Step 1-2: Navigate and enter search name
await enterSearchName(page, 'TC-010-P02 Multiple Work Orders Test');
// Step 3: Select "Work Order" search type
await selectSearchType(page, SearchTypes.WORK_ORDER);
// Step 4-7: Upload multiple work orders file
await expect(page.locator(`text=${workOrderConfig.panelHeader}`)).toBeVisible();
const testFile = getTestFile(TestFiles.MULTIPLE_WORKORDERS);
await uploadFile(page, workOrderConfig, testFile);
// First check for errors
const hasError = await hasErrorNotification(page);
if (!hasError) {
// Verify multiple work orders appear in the grid
const count = await getUploadedItemCount(page, workOrderConfig);
expect(count).toBeGreaterThan(1);
// Step 8: Click Submit Search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Expected Results: Search is created with all work orders
await assertNoErrorNotification(page);
} else {
test.info().annotations.push({ type: 'issue', description: 'Upload failed due to API response parsing issue' });
expect(hasError).toBe(false);
}
});
test('TC-010-P03: Work Order Search with Maximum Entries', async ({ page }) => {
// Step 1-2: Navigate and enter search name
await enterSearchName(page, 'TC-010-P03 Max Work Orders Test');
// Step 3: Select "Work Order" search type
await selectSearchType(page, SearchTypes.WORK_ORDER);
// Step 4-5: Upload file with all 15 work orders from test data
await expect(page.locator(`text=${workOrderConfig.panelHeader}`)).toBeVisible();
const testFile = getTestDataPath('max_workorders.xlsx');
await uploadFile(page, workOrderConfig, testFile);
// First check for errors
const hasError = await hasErrorNotification(page);
if (!hasError) {
// Verify all work orders appear in the grid
const count = await getUploadedItemCount(page, workOrderConfig);
expect(count).toBe(15);
// Step 6: Click Submit Search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Expected Results: Search is created with all 15 work orders
await assertNoErrorNotification(page);
} else {
test.info().annotations.push({ type: 'issue', description: 'Upload failed due to API response parsing issue' });
expect(hasError).toBe(false);
}
});
test('TC-010-P04: Work Order Search - Remove and Re-add (Clear Data)', async ({ page }) => {
// Step 1-2: Navigate and enter search name
await enterSearchName(page, 'TC-010-P04 Remove Re-add Test');
// Step 3: Select "Work Order" search type
await selectSearchType(page, SearchTypes.WORK_ORDER);
// Step 4-5: Upload multiple work orders
await expect(page.locator(`text=${workOrderConfig.panelHeader}`)).toBeVisible();
const testFile = getTestFile(TestFiles.MULTIPLE_WORKORDERS);
await uploadFile(page, workOrderConfig, testFile);
// First check for errors
const hasError = await hasErrorNotification(page);
if (!hasError) {
// Verify work orders were added
let count = await getUploadedItemCount(page, workOrderConfig);
expect(count).toBeGreaterThan(0);
// Step 6: Clear all data (simulates removing entries)
await clearUploadedData(page, workOrderConfig);
// Step 7: Verify grid is empty
const isEmpty = await dataGridIsEmpty(page);
expect(isEmpty).toBe(true);
// Step 8: Re-add with single work order
const singleFile = getTestFile(TestFiles.SINGLE_WORKORDER);
await uploadFile(page, workOrderConfig, singleFile);
const hasError2 = await hasErrorNotification(page);
if (!hasError2) {
// Verify single work order appears
count = await getUploadedItemCount(page, workOrderConfig);
expect(count).toBe(1);
// Step 9: Click Submit Search
await clickSubmitSearch(page);
await confirmSubmitSearch(page);
// Expected Results: Search is created with only the re-added work order
await assertNoErrorNotification(page);
} else {
test.info().annotations.push({ type: 'issue', description: 'Re-upload failed due to API response parsing issue' });
expect(hasError2).toBe(false);
}
} else {
test.info().annotations.push({ type: 'issue', description: 'Upload failed due to API response parsing issue' });
expect(hasError).toBe(false);
}
});
test('TC-010-P05: Work Order Search - Duplicate Prevention', async ({ page }) => {
// Step 1-2: Navigate and enter search name
await enterSearchName(page, 'TC-010-P05 Duplicate Prevention Test');
// Step 3: Select "Work Order" search type
await selectSearchType(page, SearchTypes.WORK_ORDER);
// Step 4: Upload single work order
await expect(page.locator(`text=${workOrderConfig.panelHeader}`)).toBeVisible();
const testFile = getTestFile(TestFiles.SINGLE_WORKORDER);
await uploadFile(page, workOrderConfig, testFile);
// First check for errors
const hasError = await hasErrorNotification(page);
if (!hasError) {
// Get initial count
const initialCount = await getUploadedItemCount(page, workOrderConfig);
expect(initialCount).toBe(1);
// Step 5: Upload the same file again (attempt to add duplicate)
await uploadFile(page, workOrderConfig, testFile);
// Step 6: Observe system behavior
// Expected Results: System prevents duplicate or displays validation message
// Work order should appear only once in the list
const finalCount = await getUploadedItemCount(page, workOrderConfig);
// The count should either:
// 1. Stay the same (duplicates prevented)
// 2. Increase (if duplicates allowed) but system may show warning
// This test verifies the behavior is consistent
expect(finalCount).toBeGreaterThanOrEqual(initialCount);
} else {
test.info().annotations.push({ type: 'issue', description: 'Upload failed due to API response parsing issue' });
expect(hasError).toBe(false);
}
});
test('TC-010-P06: Download Template contains correct format', async ({ page }) => {
// Select Work Order search type
await selectSearchType(page, SearchTypes.WORK_ORDER);
// Verify panel is visible
await expect(page.locator(`text=${workOrderConfig.panelHeader}`)).toBeVisible();
// Download the template
const download = await downloadTemplate(page, workOrderConfig);
// Verify filename
const filename = download.suggestedFilename();
expect(filename).toContain('.xlsx');
expect(filename.toLowerCase()).toContain('workorder');
});
});
/**
* NEGATIVE TEST CASES
*/
test.describe('Negative Tests', () => {
test('TC-010-N01: Missing Search Name', async ({ page }) => {
// Step 1: Navigate to Submit Search page (done in beforeEach)
// Step 2: Leave search name field empty
// (Don't enter any name)
// Step 3: Select "Work Order" search type
await selectSearchType(page, SearchTypes.WORK_ORDER);
// Step 4: Add work order
const testFile = getTestFile(TestFiles.SINGLE_WORKORDER);
await uploadFile(page, workOrderConfig, testFile);
// Step 5: Click Submit Search
await clickSubmitSearch(page);
// Expected Results: Validation error for missing search name
// Search is NOT created, user remains on Submit Search page
const hasErrors = await hasValidationErrors(page);
expect(hasErrors).toBe(true);
// Verify the specific error message
await assertValidationError(page, 'name');
});
test('TC-010-N02: No Search Type Selected', async ({ page }) => {
// Step 1: Navigate to Submit Search page (done in beforeEach)
// Step 2: Enter search name
await enterSearchName(page, 'TC-010-N02 No Type Test');
// Step 3: Do NOT select any search type
// Step 4: Attempt to add work orders - should not be possible without search type
// The work order input panel should not be visible without selecting a search type
const isPanelVisible = await page.locator(`text=${workOrderConfig.panelHeader}`).isVisible({ timeout: 2000 }).catch(() => false);
expect(isPanelVisible).toBe(false);
// Step 5: Click Submit Search anyway
await clickSubmitSearch(page);
// Expected Results: Validation error for missing search type
const hasErrors = await hasValidationErrors(page);
expect(hasErrors).toBe(true);
});
test('TC-010-N03: Empty Work Order List', async ({ page }) => {
// Step 1: Navigate to Submit Search page (done in beforeEach)
// Step 2: Enter search name
await enterSearchName(page, 'TC-010-N03 Empty Work Orders Test');
// Step 3: Select "Work Order" search type
await selectSearchType(page, SearchTypes.WORK_ORDER);
// Verify panel is visible
await expect(page.locator(`text=${workOrderConfig.panelHeader}`)).toBeVisible();
// Step 4: Do NOT add any work orders
// Verify grid shows no records
const isEmpty = await dataGridIsEmpty(page);
expect(isEmpty).toBe(true);
// Step 5: Click Submit Search
await clickSubmitSearch(page);
// Expected Results: Validation error indicating at least one work order is required
const hasErrors = await hasValidationErrors(page);
expect(hasErrors).toBe(true);
});
test('TC-010-N04: Invalid Work Order Format', async ({ page }) => {
// Step 1-2: Navigate and enter search name
await enterSearchName(page, 'TC-010-N04 Invalid Format Test');
// Step 3: Select "Work Order" search type
await selectSearchType(page, SearchTypes.WORK_ORDER);
// Step 4-5: Upload file with invalid work order format (ABC123XYZ)
await expect(page.locator(`text=${workOrderConfig.panelHeader}`)).toBeVisible();
const testFile = getTestFile(TestFiles.INVALID_WORKORDERS);
await uploadFile(page, workOrderConfig, testFile);
// Expected Results:
// Either error notification appears OR invalid data is not added to list
// The system should either reject the upload or show validation on submit
const hasError = await hasErrorNotification(page);
const isEmpty = await dataGridIsEmpty(page);
// One of these should be true - either upload failed or no valid records were added
// If items were added despite being invalid, submit should fail
if (!hasError && !isEmpty) {
await clickSubmitSearch(page);
const hasValidationError = await hasValidationErrors(page);
expect(hasValidationError || hasError || isEmpty).toBe(true);
}
});
test('TC-010-N05: Work Order with Special Characters', async ({ page }) => {
// Step 1-2: Navigate and enter search name
await enterSearchName(page, 'TC-010-N05 Special Characters Test');
// Step 3: Select "Work Order" search type
await selectSearchType(page, SearchTypes.WORK_ORDER);
// Step 4-5: Upload file with special characters (99059700!@#)
await expect(page.locator(`text=${workOrderConfig.panelHeader}`)).toBeVisible();
const testFile = getTestFile(TestFiles.SPECIAL_CHARS_WORKORDERS);
await uploadFile(page, workOrderConfig, testFile);
// Expected Results:
// System should reject invalid characters
const hasError = await hasErrorNotification(page);
const isEmpty = await dataGridIsEmpty(page);
// Either upload should fail or no valid records should be added
// If data was added, submit should fail validation
if (!hasError && !isEmpty) {
await clickSubmitSearch(page);
const hasValidationError = await hasValidationErrors(page);
expect(hasValidationError || hasError || isEmpty).toBe(true);
}
});
test('TC-010-N06: Empty Work Order Input (Empty File)', async ({ page }) => {
// Step 1-2: Navigate and enter search name
await enterSearchName(page, 'TC-010-N06 Empty Input Test');
// Step 3: Select "Work Order" search type
await selectSearchType(page, SearchTypes.WORK_ORDER);
// Step 4-5: Upload empty file
await expect(page.locator(`text=${workOrderConfig.panelHeader}`)).toBeVisible();
const testFile = getTestFile(TestFiles.EMPTY_FILE);
await uploadFile(page, workOrderConfig, testFile);
// Wait for any processing
await page.waitForTimeout(1000);
// Expected Results: Grid should remain empty or show error
const isEmpty = await dataGridIsEmpty(page);
// Try to submit
await clickSubmitSearch(page);
// Should fail validation since no work orders were added
const hasErrors = await hasValidationErrors(page);
expect(hasErrors || isEmpty).toBe(true);
});
test('TC-010-N07: Invalid File Format (Non-Excel)', async ({ page }) => {
// Step 1-2: Navigate and enter search name
await enterSearchName(page, 'TC-010-N07 Invalid File Format Test');
// Step 3: Select "Work Order" search type
await selectSearchType(page, SearchTypes.WORK_ORDER);
// Step 4-5: Upload invalid format file (.txt instead of .xlsx)
await expect(page.locator(`text=${workOrderConfig.panelHeader}`)).toBeVisible();
const testFile = getTestFile(TestFiles.INVALID_FORMAT);
await uploadFile(page, workOrderConfig, testFile);
// Wait for any error processing
await page.waitForTimeout(2000);
// Expected Results:
// System should show error notification for invalid file format
// OR grid should remain empty
const hasError = await hasErrorNotification(page);
const isEmpty = await dataGridIsEmpty(page);
// At least one of these should be true
expect(hasError || isEmpty).toBe(true);
});
// Skip: Depends on file upload working (which has known API bug)
test('TC-010-N08: Submit Without Confirmation', async ({ page }) => {
// Setup: Create a valid search
await enterSearchName(page, 'TC-010-N08 Confirmation Test');
await selectSearchType(page, SearchTypes.WORK_ORDER);
const testFile = getTestFile(TestFiles.SINGLE_WORKORDER);
await uploadFile(page, workOrderConfig, testFile);
// Click Submit but do NOT confirm
await clickSubmitSearch(page);
// Wait for dialog
await page.waitForSelector('text=Confirm Submit', { timeout: 5000 });
// Click Cancel instead of OK
await page.locator('button:has-text("Cancel")').click();
await page.waitForTimeout(500);
// Expected Results: Search should NOT be created, user remains on page
// The page should still show the search form
await expect(page.locator(`text=${workOrderConfig.panelHeader}`)).toBeVisible();
});
});
});