Migrate Playwright suite to .NET UI tests and deprecate TS project
This commit is contained in:
@@ -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();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user