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 } from '../fixtures/test-data'; test.describe('AI Extraction Workflow', () => { const BASE_URL = process.env.BASE_URL || 'http://localhost:8917'; 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 new item creation (AI extraction) await page.goto(`${BASE_URL}/inventory/new`); }); test('should display AI onboarding wizard', async ({ page }) => { const aiWizard = page.locator('[data-testid="ai-onboarding-wizard"]'); await expect(aiWizard).toBeVisible(); // Check for step indicators const stepIndicator = page.locator('[data-testid="wizard-step"]'); expect(await stepIndicator.count()).toBeGreaterThan(0); }); test('should show capture step in AI wizard', async ({ page }) => { const captureStep = page.locator('[data-testid="wizard-step-capture"]'); await expect(captureStep).toBeVisible(); // Camera view should be displayed const cameraView = page.locator('[data-testid="capture-camera"]'); await expect(cameraView).toBeVisible(); // Capture button should be visible const captureButton = page.locator('[data-testid="capture-button"]'); await expect(captureButton).toBeVisible(); }); test('should allow manual item entry as alternative to AI', async ({ page }) => { const manualEntryTab = page.locator('[data-testid="manual-entry-tab"]'); if (await manualEntryTab.isVisible()) { await manualEntryTab.click(); // Form should be displayed const itemForm = page.locator('[data-testid="item-manual-form"]'); await expect(itemForm).toBeVisible(); } }); test('should handle camera permission denied', async ({ page, context }) => { // Simulate camera permission denial await context.grantPermissions([]); // Reload to trigger permission check await page.reload(); // Should show error or fallback UI const errorMessage = page.locator('[data-testid="camera-permission-error"]'); const fallbackForm = page.locator('[data-testid="manual-entry-form"]'); const hasError = await errorMessage.isVisible().catch(() => false); const hasFallback = await fallbackForm.isVisible().catch(() => false); expect(hasError || hasFallback).toBe(true); }); test('should display AI provider selection', async ({ page }) => { const providerSelect = page.locator('[data-testid="ai-provider-select"]'); if (await providerSelect.isVisible()) { // At least Gemini or Claude should be available const options = page.locator('[data-testid="provider-option"]'); expect(await options.count()).toBeGreaterThan(0); } }); test('should show extraction results after AI processing', async ({ page }) => { // Click capture button (mock will return predefined response) const captureButton = page.locator('[data-testid="capture-button"]'); await captureButton.click(); // Show extraction overlay const extractionOverlay = page.locator('[data-testid="ai-extraction-overlay"]'); await expect(extractionOverlay).toBeVisible({ timeout: 10000 }); // Results form should appear const resultsForm = page.locator('[data-testid="extraction-results-form"]'); await expect(resultsForm).toBeVisible({ timeout: 15000 }); }); test('should display extracted fields for user confirmation', async ({ page }) => { const captureButton = page.locator('[data-testid="capture-button"]'); await captureButton.click(); // Wait for results const resultsForm = page.locator('[data-testid="extraction-results-form"]'); await expect(resultsForm).toBeVisible({ timeout: 15000 }); // Check for extracted fields const nameField = page.locator('[data-testid="extracted-name"]'); const categoryField = page.locator('[data-testid="extracted-category"]'); await expect(nameField).toBeVisible(); if (await categoryField.isVisible()) { // Category might be optional } }); test('should allow editing extracted values', async ({ page }) => { const captureButton = page.locator('[data-testid="capture-button"]'); await captureButton.click(); // Wait for results const resultsForm = page.locator('[data-testid="extraction-results-form"]'); await expect(resultsForm).toBeVisible({ timeout: 15000 }); // Edit extracted name const nameField = page.locator('[data-testid="extracted-name"]'); await nameField.fill('Modified Item Name'); const value = await helpers.getInputValue(page, '[data-testid="extracted-name"]'); expect(value).toBe('Modified Item Name'); }); test('should allow confirming and saving extracted item', async ({ page }) => { const captureButton = page.locator('[data-testid="capture-button"]'); await captureButton.click(); // Wait for results const resultsForm = page.locator('[data-testid="extraction-results-form"]'); await expect(resultsForm).toBeVisible({ timeout: 15000 }); // Confirm extraction const confirmButton = page.locator('[data-testid="confirm-extraction"]'); await confirmButton.click(); // Should show success and redirect const successToast = page.locator('[data-testid="toast-success"]'); await expect(successToast).toBeVisible({ timeout: 5000 }); // Should redirect to inventory or item detail page await page.waitForURL(/inventory|items/, { timeout: 5000 }); }); test('should allow rejecting AI extraction results', async ({ page }) => { const captureButton = page.locator('[data-testid="capture-button"]'); await captureButton.click(); // Wait for results const resultsForm = page.locator('[data-testid="extraction-results-form"]'); await expect(resultsForm).toBeVisible({ timeout: 15000 }); // Click reject/redo const rejectButton = page.locator('[data-testid="reject-extraction"]'); if (await rejectButton.isVisible()) { await rejectButton.click(); // Should return to capture step const captureStep = page.locator('[data-testid="wizard-step-capture"]'); await expect(captureStep).toBeVisible({ timeout: 3000 }); } }); test('should handle AI extraction errors gracefully', async ({ page }) => { // Mock API error await page.route('**/api/ai/extract', (route) => { route.abort('failed'); }); const captureButton = page.locator('[data-testid="capture-button"]'); await captureButton.click(); // Should show error message const errorToast = page.locator('[data-testid="toast-error"]'); await expect(errorToast).toBeVisible({ timeout: 10000 }); }); test('should support multi-item extraction from single image', async ({ page }) => { // If multiple items detected feature is supported const multiItemToggle = page.locator('[data-testid="multi-item-toggle"]'); if (await multiItemToggle.isVisible()) { await multiItemToggle.click(); const captureButton = page.locator('[data-testid="capture-button"]'); await captureButton.click(); // Results should show multiple items const itemRows = page.locator('[data-testid="extraction-item-row"]'); const count = await itemRows.count(); expect(count).toBeGreaterThanOrEqual(1); } }); test('should display confidence scores for extracted fields', async ({ page }) => { const captureButton = page.locator('[data-testid="capture-button"]'); await captureButton.click(); // Wait for results const resultsForm = page.locator('[data-testid="extraction-results-form"]'); await expect(resultsForm).toBeVisible({ timeout: 15000 }); // Check for confidence indicator const confidenceIndicator = page.locator('[data-testid="field-confidence"]'); if (await confidenceIndicator.isVisible()) { const score = await confidenceIndicator.textContent(); expect(score).toMatch(/\d+%|High|Medium|Low/); } }); test('should allow retaking capture', async ({ page }) => { // Mock to get to results const captureButton = page.locator('[data-testid="capture-button"]'); await captureButton.click(); const resultsForm = page.locator('[data-testid="extraction-results-form"]'); await expect(resultsForm).toBeVisible({ timeout: 15000 }); // Click retake button const retakeButton = page.locator('[data-testid="retake-button"]'); if (await retakeButton.isVisible()) { await retakeButton.click(); // Should return to camera const captureStep = page.locator('[data-testid="wizard-step-capture"]'); await expect(captureStep).toBeVisible({ timeout: 3000 }); } }); test('should validate required fields before submission', async ({ page }) => { const captureButton = page.locator('[data-testid="capture-button"]'); await captureButton.click(); const resultsForm = page.locator('[data-testid="extraction-results-form"]'); await expect(resultsForm).toBeVisible({ timeout: 15000 }); // Clear required field const nameField = page.locator('[data-testid="extracted-name"]'); await nameField.fill(''); // Try to confirm const confirmButton = page.locator('[data-testid="confirm-extraction"]'); if (await confirmButton.isEnabled()) { await confirmButton.click(); // Should show validation error const errorToast = page.locator('[data-testid="toast-error"]'); await expect(errorToast).toBeVisible({ timeout: 3000 }); } }); test('should show step progress indicator', async ({ page }) => { const progressBar = page.locator('[data-testid="wizard-progress"]'); if (await progressBar.isVisible()) { const currentStep = page.locator('[data-testid="current-step"]'); const totalSteps = page.locator('[data-testid="total-steps"]'); const current = await currentStep.textContent(); const total = await totalSteps.textContent(); expect(current).toBeTruthy(); expect(total).toBeTruthy(); } }); });