diff --git a/frontend/e2e/workflows/2-scan-adjust.spec.ts b/frontend/e2e/workflows/2-scan-adjust.spec.ts new file mode 100644 index 00000000..904b2d64 --- /dev/null +++ b/frontend/e2e/workflows/2-scan-adjust.spec.ts @@ -0,0 +1,257 @@ +import { test, expect } from '@playwright/test'; +import * as auth from '../fixtures/auth'; +import * as assertions from '../utils/assertions'; +import * as helpers from '../utils/helpers'; +import { LOCAL_USERS, TEST_ITEMS } from '../fixtures/test-data'; + +test.describe('Scan and Adjust Workflow', () => { + const BASE_URL = process.env.BASE_URL || 'http://localhost:3000'; + + test.beforeEach(async ({ page }) => { + // Navigate to app and login + await page.goto(BASE_URL); + await auth.loginWithLocalUser(page, LOCAL_USERS.admin, BASE_URL); + await assertions.assertUserAuthenticated(page, BASE_URL); + + // Navigate to scanner page + await page.goto(`${BASE_URL}/scanner`); + await assertions.assertScannerReady(page); + }); + + test('should display scanner interface with camera view', async ({ page }) => { + await assertions.assertScannerReady(page); + + // Verify camera controls + const zoomButton = page.locator('[data-testid="zoom-control"]'); + const cameraIndicator = page.locator('[data-testid="camera-indicator"]'); + + await expect(zoomButton).toBeVisible(); + await expect(cameraIndicator).toBeVisible(); + }); + + test('should display scan input field', async ({ page }) => { + const scanInput = page.locator('[data-testid="scan-input"]'); + await expect(scanInput).toBeVisible(); + await expect(scanInput).toBeFocused(); + }); + + test('should handle barcode scan and match to inventory item', async ({ page }) => { + // Simulate barcode scan by entering barcode in input + const scanInput = page.locator('[data-testid="scan-input"]'); + + // Enter first test item's barcode + await scanInput.fill(TEST_ITEMS[0].barcode); + await scanInput.press('Enter'); + + // Should show stock adjustment form + await assertions.assertStockAdjustmentVisible(page, TEST_ITEMS[0].name); + + // Verify item details are shown + const itemName = page.locator('[data-testid="adjustment-item-name"]'); + await expect(itemName).toContainText(TEST_ITEMS[0].name); + }); + + test('should default to check-in operation', async ({ page }) => { + const scanInput = page.locator('[data-testid="scan-input"]'); + await scanInput.fill(TEST_ITEMS[0].barcode); + await scanInput.press('Enter'); + + // Check-in radio should be selected + const checkInRadio = page.locator('[data-testid="operation-checkin"]'); + await expect(checkInRadio).toBeChecked(); + }); + + test('should allow switching between check-in and check-out', async ({ page }) => { + const scanInput = page.locator('[data-testid="scan-input"]'); + await scanInput.fill(TEST_ITEMS[0].barcode); + await scanInput.press('Enter'); + + // Switch to check-out + const checkOutRadio = page.locator('[data-testid="operation-checkout"]'); + await checkOutRadio.click(); + await expect(checkOutRadio).toBeChecked(); + + // Switch back to check-in + const checkInRadio = page.locator('[data-testid="operation-checkin"]'); + await checkInRadio.click(); + await expect(checkInRadio).toBeChecked(); + }); + + test('should allow quantity adjustment', async ({ page }) => { + const scanInput = page.locator('[data-testid="scan-input"]'); + await scanInput.fill(TEST_ITEMS[0].barcode); + await scanInput.press('Enter'); + + // Change quantity + const quantityInput = page.locator('[data-testid="adjustment-quantity-input"]'); + await quantityInput.fill('10'); + + // Verify value changed + const value = await helpers.getInputValue(page, '[data-testid="adjustment-quantity-input"]'); + expect(value).toBe('10'); + }); + + test('should reject invalid quantities', async ({ page }) => { + const scanInput = page.locator('[data-testid="scan-input"]'); + await scanInput.fill(TEST_ITEMS[0].barcode); + await scanInput.press('Enter'); + + // Try invalid quantity + const quantityInput = page.locator('[data-testid="adjustment-quantity-input"]'); + await quantityInput.fill('-5'); + + // Submit should be disabled or show error + const submitButton = page.locator('[data-testid="adjustment-submit"]'); + if (await submitButton.isEnabled()) { + await submitButton.click(); + const errorToast = page.locator('[data-testid="toast-error"]'); + await expect(errorToast).toBeVisible({ timeout: 3000 }); + } + }); + + test('should submit stock adjustment successfully', async ({ page }) => { + const scanInput = page.locator('[data-testid="scan-input"]'); + await scanInput.fill(TEST_ITEMS[0].barcode); + await scanInput.press('Enter'); + + // Set quantity + const quantityInput = page.locator('[data-testid="adjustment-quantity-input"]'); + await quantityInput.fill('5'); + + // Submit + const submitButton = page.locator('[data-testid="adjustment-submit"]'); + await submitButton.click(); + + // Should show success message + const successToast = page.locator('[data-testid="toast-success"]'); + await expect(successToast).toBeVisible({ timeout: 5000 }); + }); + + test('should close adjustment form after successful submission', async ({ page }) => { + const scanInput = page.locator('[data-testid="scan-input"]'); + await scanInput.fill(TEST_ITEMS[0].barcode); + await scanInput.press('Enter'); + + const quantityInput = page.locator('[data-testid="adjustment-quantity-input"]'); + await quantityInput.fill('5'); + + const submitButton = page.locator('[data-testid="adjustment-submit"]'); + await submitButton.click(); + + // Wait for success and form to close + const adjustmentForm = page.locator('[data-testid="stock-adjustment-form"]'); + await expect(adjustmentForm).not.toBeVisible({ timeout: 5000 }); + + // Scanner input should be focused again + const scanner = page.locator('[data-testid="scan-input"]'); + await expect(scanner).toBeFocused(); + }); + + test('should handle unknown barcode', async ({ page }) => { + const scanInput = page.locator('[data-testid="scan-input"]'); + await scanInput.fill('999999999'); + await scanInput.press('Enter'); + + // Should show error or create new item dialog + const errorOrDialog = page.locator( + '[data-testid="toast-error"], [data-testid="new-item-dialog"]' + ); + await expect(errorOrDialog).toBeVisible({ timeout: 3000 }); + }); + + test('should support multiple consecutive scans', async ({ page }) => { + const scanInput = page.locator('[data-testid="scan-input"]'); + + // First scan + await scanInput.fill(TEST_ITEMS[0].barcode); + await scanInput.press('Enter'); + let adjustmentForm = page.locator('[data-testid="stock-adjustment-form"]'); + await expect(adjustmentForm).toBeVisible(); + + // Complete first adjustment + const quantityInput = page.locator('[data-testid="adjustment-quantity-input"]'); + await quantityInput.fill('5'); + const submitButton = page.locator('[data-testid="adjustment-submit"]'); + await submitButton.click(); + + // Wait for form to close + await expect(adjustmentForm).not.toBeVisible({ timeout: 5000 }); + + // Second scan + await scanInput.fill(TEST_ITEMS[1].barcode); + await scanInput.press('Enter'); + + // Second adjustment form should appear + adjustmentForm = page.locator('[data-testid="stock-adjustment-form"]'); + await expect(adjustmentForm).toBeVisible(); + }); + + test('should display item quantity history', async ({ page }) => { + const scanInput = page.locator('[data-testid="scan-input"]'); + await scanInput.fill(TEST_ITEMS[0].barcode); + await scanInput.press('Enter'); + + // Check if history is displayed + const historySection = page.locator('[data-testid="adjustment-history"]'); + if (await historySection.isVisible()) { + const historyItems = page.locator('[data-testid="history-item"]'); + expect(await historyItems.count()).toBeGreaterThan(0); + } + }); + + test('should allow canceling adjustment', async ({ page }) => { + const scanInput = page.locator('[data-testid="scan-input"]'); + await scanInput.fill(TEST_ITEMS[0].barcode); + await scanInput.press('Enter'); + + // Click cancel button + const cancelButton = page.locator('[data-testid="adjustment-cancel"]'); + if (await cancelButton.isVisible()) { + await cancelButton.click(); + + // Form should close + const adjustmentForm = page.locator('[data-testid="stock-adjustment-form"]'); + await expect(adjustmentForm).not.toBeVisible(); + } + }); + + test('should display current inventory count', async ({ page }) => { + const scanInput = page.locator('[data-testid="scan-input"]'); + await scanInput.fill(TEST_ITEMS[0].barcode); + await scanInput.press('Enter'); + + // Current quantity should be displayed + const currentQuantity = page.locator('[data-testid="current-quantity"]'); + await expect(currentQuantity).toBeVisible(); + + const quantityText = await currentQuantity.textContent(); + expect(quantityText).toMatch(/\d+/); + }); + + test('should show category in item details', async ({ page }) => { + const scanInput = page.locator('[data-testid="scan-input"]'); + await scanInput.fill(TEST_ITEMS[0].barcode); + await scanInput.press('Enter'); + + const categoryDisplay = page.locator('[data-testid="item-category"]'); + if (await categoryDisplay.isVisible()) { + const categoryText = await categoryDisplay.textContent(); + expect(categoryText).toContain(TEST_ITEMS[0].category); + } + }); + + test('should clear scan input after successful operation', async ({ page }) => { + const scanInput = page.locator('[data-testid="scan-input"]'); + await scanInput.fill(TEST_ITEMS[0].barcode); + await scanInput.press('Enter'); + + const quantityInput = page.locator('[data-testid="adjustment-quantity-input"]'); + await quantityInput.fill('5'); + + const submitButton = page.locator('[data-testid="adjustment-submit"]'); + await submitButton.click(); + + // Wait for form to close and input to clear + await expect(scanInput).toHaveValue('', { timeout: 5000 }); + }); +});