Files
tfm_ainventory/frontend/e2e/workflows/3-ai-extraction.spec.ts

267 lines
10 KiB
TypeScript

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();
}
});
});