import { Page, Download } from '@playwright/test'; import path from 'path'; /** * Configuration for file upload filter panels */ export interface FileUploadConfig { /** Panel header text to identify the panel */ panelHeader: string; /** Template filename (for download verification) */ templateFilename: string; /** Expected columns in the template */ expectedColumns: string[]; } /** * Work Order upload configuration */ export const workOrderConfig: FileUploadConfig = { panelHeader: 'Filter by Work Order', templateFilename: 'WorkOrderTemplate.xlsx', expectedColumns: ['Work Order Number'], }; /** * Component Lot upload configuration */ export const componentLotConfig: FileUploadConfig = { panelHeader: 'Filter By Component Lot', templateFilename: 'ComponentLotTemplate.xlsx', expectedColumns: ['Lot Number', 'Item Number'], }; /** * Item Number upload configuration */ export const itemNumberConfig: FileUploadConfig = { panelHeader: 'Filter by Item Number', templateFilename: 'ItemNumberTemplate.xlsx', expectedColumns: ['Item Number'], }; /** * Part Operation (Item/Operation/MIS) upload configuration */ export const partOperationConfig: FileUploadConfig = { panelHeader: 'Filter By Item/Operation/MIS', templateFilename: 'PartOperationTemplate.xlsx', expectedColumns: ['Item Number', 'Operation Number', 'MIS Number', 'MIS Revision'], }; /** * Upload a file to a filter panel * @param page - Playwright page object * @param config - File upload configuration * @param filePath - Path to the file to upload */ export async function uploadFile( page: Page, config: FileUploadConfig, filePath: string ): Promise { // Find the panel by its header const panel = page.locator(`.rz-card:has-text("${config.panelHeader}")`); // Find the file input (RadzenUpload creates a hidden input) const fileInput = panel.locator('input[type="file"]'); // Set the file await fileInput.setInputFiles(filePath); // Wait for upload to complete await page.waitForTimeout(2000); } /** * Upload a file using the global file input (fallback for panels without specific locators) * @param page - Playwright page object * @param filePath - Path to the file to upload */ export async function uploadFileGlobal(page: Page, filePath: string): Promise { const fileInput = page.locator('input[type="file"]'); await fileInput.setInputFiles(filePath); await page.waitForTimeout(2000); } /** * Download a template file from a filter panel * @param page - Playwright page object * @param config - File upload configuration * @returns The Download object */ export async function downloadTemplate( page: Page, config: FileUploadConfig ): Promise { const panel = page.locator(`.rz-card:has-text("${config.panelHeader}")`); // Start waiting for download before clicking const downloadPromise = page.waitForEvent('download'); // Click the download template button await panel.locator('button:has-text("Download Template")').click(); // Wait for download to complete const download = await downloadPromise; return download; } /** * Clear uploaded data from a filter panel * @param page - Playwright page object * @param config - File upload configuration */ export async function clearUploadedData( page: Page, config: FileUploadConfig ): Promise { const panel = page.locator(`.rz-card:has-text("${config.panelHeader}")`); const clearButton = panel.locator('button:has-text("Clear Data")'); if (await clearButton.isVisible({ timeout: 2000 }).catch(() => false)) { await clearButton.click(); // Wait for and confirm the dialog await page.waitForSelector('text=Confirm Clear', { timeout: 5000 }); // Click the Ok button inside the dialog await page.locator('.rz-dialog-wrapper button').getByText('Ok', { exact: true }).click(); // Wait for the grid to update - either "No records" appears or grid rows are removed await page.waitForTimeout(1000); // Wait for dialog to close await page.waitForSelector('.rz-dialog-wrapper', { state: 'hidden', timeout: 5000 }).catch(() => {}); } } /** * Get the count of uploaded items in a filter panel * @param page - Playwright page object * @param config - File upload configuration * @returns Number of items in the grid */ export async function getUploadedItemCount( page: Page, config: FileUploadConfig ): Promise { const panel = page.locator(`.rz-card:has-text("${config.panelHeader}")`); const grid = panel.locator('.rz-data-grid'); // Check for "No records" message const noRecords = grid.locator('text=No records to display'); if (await noRecords.isVisible({ timeout: 1000 }).catch(() => false)) { return 0; } const rows = grid.locator('tbody tr'); return await rows.count(); } /** * Check if the file upload panel is visible * @param page - Playwright page object * @param config - File upload configuration * @returns true if panel is visible */ export async function isFileUploadPanelVisible( page: Page, config: FileUploadConfig ): Promise { const panel = page.locator(`text=${config.panelHeader}`); return await panel.isVisible({ timeout: 2000 }).catch(() => false); } /** * Verify the uploaded file shows items in the grid * @param page - Playwright page object * @param config - File upload configuration * @returns true if grid has items */ export async function hasUploadedItems( page: Page, config: FileUploadConfig ): Promise { const count = await getUploadedItemCount(page, config); return count > 0; } /** * Get the test data directory path */ export function getTestDataDir(): string { return path.join(__dirname, '..', 'test-data'); } /** * Get the path to a test data file * @param filename - The filename in the test-data directory */ export function getTestDataPath(filename: string): string { return path.join(getTestDataDir(), filename); } /** * Standard test files available in test-data directory */ export const TestFiles = { // Work Order files SINGLE_WORKORDER: 'single_workorder.xlsx', MULTIPLE_WORKORDERS: 'multiple_workorders.xlsx', // Component Lot files SINGLE_LOT: 'single_lot.xlsx', MULTIPLE_LOTS: 'multiple_lots.xlsx', // Item Number files SINGLE_ITEM: 'single_item.xlsx', MULTIPLE_ITEMS: 'multiple_items.xlsx', // Part Operation files SINGLE_OPERATION: 'single_operation.xlsx', MULTIPLE_OPERATIONS: 'multiple_operations.xlsx', // Invalid files (for negative testing) INVALID_FORMAT: 'invalid_format.txt', EMPTY_FILE: 'empty_file.xlsx', INVALID_WORKORDERS: 'invalid_workorders.xlsx', SPECIAL_CHARS_WORKORDERS: 'special_chars_workorders.xlsx', } as const; /** * Get full path to a standard test file * @param testFile - One of the TestFiles constants */ export function getTestFile(testFile: string): string { return getTestDataPath(testFile); }