ee044d03e0
- Add /health endpoint with anonymous access for monitoring - Add FileUploadResult<T> model and PostMultipartForFileResultAsync for proper upload response handling - Add ApiResult.Success() factory method for interface types - Refactor Login.razor for cleaner code - Add comprehensive Playwright E2E test suite with fixtures and helpers
523 lines
19 KiB
TypeScript
523 lines
19 KiB
TypeScript
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);
|
|
});
|
|
});
|
|
});
|