- Add comprehensive analytics components with export functionality - Implement territory management with manager performance tracking - Add seatmap components for venue layout management - Create customer management features with modal interface - Add advanced hooks for dashboard flags and territory data - Implement seat selection and venue management utilities - Add type definitions for ticketing and seatmap systems 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
219 lines
7.5 KiB
TypeScript
219 lines
7.5 KiB
TypeScript
/**
|
|
* QR Code System End-to-End Tests
|
|
* Tests QR validation, manual entry, and scanner integration
|
|
*/
|
|
|
|
import { test, expect } from '@playwright/test';
|
|
|
|
test.describe('QR Code System', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
// Navigate to scanner page with event ID
|
|
await page.goto('/scanner?eventId=test-event-123');
|
|
|
|
// Wait for scanner to initialize
|
|
await page.waitForSelector('[data-testid="scanner-interface"]', { timeout: 10000 });
|
|
});
|
|
|
|
test('should display manual entry button in scanner interface', async ({ page }) => {
|
|
// Check for manual entry button (hash icon)
|
|
const manualEntryButton = page.locator('button[title="Manual Entry"]');
|
|
await expect(manualEntryButton).toBeVisible();
|
|
|
|
// Verify button has hash icon
|
|
await expect(manualEntryButton.locator('svg')).toBeVisible();
|
|
});
|
|
|
|
test('should open manual entry modal when hash button clicked', async ({ page }) => {
|
|
// Click manual entry button
|
|
await page.click('button[title="Manual Entry"]');
|
|
|
|
// Verify modal opens
|
|
await expect(page.locator('h2:text("Manual Entry")')).toBeVisible();
|
|
|
|
// Verify keypad is present
|
|
await expect(page.locator('button:text("1")')).toBeVisible();
|
|
await expect(page.locator('button:text("2")')).toBeVisible();
|
|
await expect(page.locator('button:text("3")')).toBeVisible();
|
|
});
|
|
|
|
test('manual entry modal should have proper keyboard navigation', async ({ page }) => {
|
|
await page.click('button[title="Manual Entry"]');
|
|
|
|
// Test numeric input
|
|
await page.keyboard.press('1');
|
|
await page.keyboard.press('2');
|
|
await page.keyboard.press('3');
|
|
await page.keyboard.press('4');
|
|
await page.keyboard.press('A');
|
|
await page.keyboard.press('B');
|
|
await page.keyboard.press('C');
|
|
await page.keyboard.press('D');
|
|
|
|
// Verify code display shows entered characters
|
|
const codeDisplay = page.locator('div').filter({ hasText: /1234-ABCD/ }).first();
|
|
await expect(codeDisplay).toBeVisible();
|
|
});
|
|
|
|
test('should validate backup codes properly', async ({ page }) => {
|
|
await page.click('button[title="Manual Entry"]');
|
|
|
|
// Enter invalid code (too short)
|
|
await page.keyboard.press('1');
|
|
await page.keyboard.press('2');
|
|
await page.keyboard.press('3');
|
|
|
|
// Submit button should be disabled
|
|
const submitButton = page.locator('button:text("Submit")');
|
|
await expect(submitButton).toBeDisabled();
|
|
|
|
// Complete valid code
|
|
await page.keyboard.press('4');
|
|
await page.keyboard.press('A');
|
|
await page.keyboard.press('B');
|
|
await page.keyboard.press('C');
|
|
await page.keyboard.press('D');
|
|
|
|
// Submit button should be enabled
|
|
await expect(submitButton).toBeEnabled();
|
|
});
|
|
|
|
test('should handle manual entry submission', async ({ page }) => {
|
|
await page.click('button[title="Manual Entry"]');
|
|
|
|
// Enter a complete backup code
|
|
await page.keyboard.press('1');
|
|
await page.keyboard.press('2');
|
|
await page.keyboard.press('3');
|
|
await page.keyboard.press('4');
|
|
await page.keyboard.press('A');
|
|
await page.keyboard.press('B');
|
|
await page.keyboard.press('C');
|
|
await page.keyboard.press('D');
|
|
|
|
// Submit the code
|
|
await page.click('button:text("Submit")');
|
|
|
|
// Modal should close and scan result should appear
|
|
await expect(page.locator('h2:text("Manual Entry")')).toBeHidden();
|
|
|
|
// Should show scan result (either success or error)
|
|
await expect(page.locator('[class*="ring-2"]')).toBeVisible({ timeout: 5000 });
|
|
});
|
|
|
|
test('should close modal on escape key', async ({ page }) => {
|
|
await page.click('button[title="Manual Entry"]');
|
|
|
|
// Verify modal is open
|
|
await expect(page.locator('h2:text("Manual Entry")')).toBeVisible();
|
|
|
|
// Press escape
|
|
await page.keyboard.press('Escape');
|
|
|
|
// Modal should close
|
|
await expect(page.locator('h2:text("Manual Entry")')).toBeHidden();
|
|
});
|
|
|
|
test('should show letter keys when toggle is clicked', async ({ page }) => {
|
|
await page.click('button[title="Manual Entry"]');
|
|
|
|
// Initially letters should be hidden
|
|
await expect(page.locator('button:text("A")')).toBeHidden();
|
|
|
|
// Click show letters toggle
|
|
await page.click('button:text("Show Letters A-F")');
|
|
|
|
// Letters should now be visible
|
|
await expect(page.locator('button:text("A")')).toBeVisible();
|
|
await expect(page.locator('button:text("B")')).toBeVisible();
|
|
await expect(page.locator('button:text("F")')).toBeVisible();
|
|
|
|
// Toggle text should change
|
|
await expect(page.locator('button:text("Hide Letters A-F")')).toBeVisible();
|
|
});
|
|
|
|
test('should clear code when clear button clicked', async ({ page }) => {
|
|
await page.click('button[title="Manual Entry"]');
|
|
|
|
// Enter some characters
|
|
await page.keyboard.press('1');
|
|
await page.keyboard.press('2');
|
|
await page.keyboard.press('3');
|
|
|
|
// Verify characters are displayed
|
|
await expect(page.locator('div').filter({ hasText: /123/ }).first()).toBeVisible();
|
|
|
|
// Click clear button
|
|
await page.click('button:text("Clear")');
|
|
|
|
// Code should be cleared (showing underscores)
|
|
await expect(page.locator('div').filter({ hasText: /____-____/ }).first()).toBeVisible();
|
|
});
|
|
|
|
test('should delete characters when delete button clicked', async ({ page }) => {
|
|
await page.click('button[title="Manual Entry"]');
|
|
|
|
// Enter some characters
|
|
await page.keyboard.press('1');
|
|
await page.keyboard.press('2');
|
|
await page.keyboard.press('3');
|
|
await page.keyboard.press('4');
|
|
|
|
// Verify 4 characters
|
|
await expect(page.locator('div').filter({ hasText: /1234/ }).first()).toBeVisible();
|
|
|
|
// Click delete button
|
|
await page.locator('button').filter({ has: page.locator('svg') }).first().click();
|
|
|
|
// Should show 3 characters
|
|
await expect(page.locator('div').filter({ hasText: /123_/ }).first()).toBeVisible();
|
|
});
|
|
|
|
test('should have instructions updated for manual entry', async ({ page }) => {
|
|
// Check that scanner instructions mention manual entry
|
|
await expect(page.locator('text=Use # button for manual entry when QR is unreadable')).toBeVisible();
|
|
});
|
|
|
|
test('should work with touch events for mobile devices', async ({ page, isMobile }) => {
|
|
if (!isMobile) {
|
|
test.skip('Mobile-only test');
|
|
}
|
|
|
|
await page.click('button[title="Manual Entry"]');
|
|
|
|
// Test touch interactions on keypad buttons
|
|
await page.tap('button:text("1")');
|
|
await page.tap('button:text("2")');
|
|
await page.tap('button:text("3")');
|
|
await page.tap('button:text("4")');
|
|
|
|
// Verify touch input works
|
|
await expect(page.locator('div').filter({ hasText: /1234/ }).first()).toBeVisible();
|
|
});
|
|
|
|
test('should handle offline mode with manual entry', async ({ page }) => {
|
|
// Simulate offline mode
|
|
await page.route('**/*', (route) => {
|
|
if (route.request().url().includes('api')) {
|
|
route.abort('failed');
|
|
} else {
|
|
route.continue();
|
|
}
|
|
});
|
|
|
|
await page.click('button[title="Manual Entry"]');
|
|
|
|
// Enter valid backup code
|
|
const backupCode = '1234ABCD';
|
|
for (const char of backupCode) {
|
|
await page.keyboard.press(char);
|
|
}
|
|
|
|
// Submit should work even offline
|
|
await page.click('button:text("Submit")');
|
|
|
|
// Should show offline acceptance or queued status
|
|
await expect(
|
|
page.locator('text=Accepted (offline mode), text=Queued for verification').first()
|
|
).toBeVisible({ timeout: 5000 });
|
|
});
|
|
}); |