/** * @file Gift Recording Test Suite * @description Tests TimeSafari's core gift recording functionality with integrated performance tracking * * This test covers a complete gift recording flow in TimeSafari with integrated performance tracking. * * Focus areas: * - Performance monitoring for every major user step * - Gift creation, recording, and verification * - Public server integration and validation * - Validation of both behavior and responsiveness * * @version 1.0.0 * @author Matthew Raymer * @lastModified 2025-08-02 * * ================================================================================ * TEST OVERVIEW * ================================================================================ * * This test verifies the complete gift recording workflow from data generation to * public verification, ensuring end-to-end functionality works correctly with * comprehensive performance monitoring. * * Core Test Objectives: * 1. Gift Creation & Recording * - Random gift title generation with uniqueness * - Random non-zero amount assignment (1-99 range) * - Proper form filling and validation * - JWT signing and submission with performance tracking * * 2. Gift Verification & Display * - Gift appears in home view after recording * - Details match input data exactly * - Verifiable claim details are accessible * - UI elements display correctly * * 3. Public Verification & Integration * - Gift viewable on public endorser server * - Claim details properly exposed via API * - Cross-platform compatibility (Chromium/Firefox) * * ================================================================================ * TEST FLOW & PROCESS * ================================================================================ * * Phase 1: Data Generation & Preparation * ──────────────────────────────────────────────────────────────────────────────── * 1. Generate unique test data: * - Random 4-character string for gift ID uniqueness * - Random amount between 1-99 (non-zero validation) * - Combine with "Gift " prefix for standard format * * 2. User preparation: * - Import User 00 (test account with known state) * - Navigate to home page * - Handle onboarding dialog closure * * Phase 2: Gift Recording Process * ──────────────────────────────────────────────────────────────────────────────── * 3. Recipient selection: * - Click "Person" button to open recipient picker * - Select "Unnamed/Unknown" recipient * - Verify selection is applied * * 4. Gift details entry: * - Fill gift title with generated unique string * - Enter random amount in number field * - Validate form state before submission * * 5. Submission and signing: * - Click "Sign & Send" button * - Wait for JWT signing process * - Verify success notification appears * - Dismiss any info alerts * * Phase 3: Verification & Validation * ──────────────────────────────────────────────────────────────────────────────── * 6. Home view verification: * - Refresh home page to load new gift * - Locate gift in activity list by title * - Click info link to view details * * 7. Details verification: * - Verify "Verifiable Claim Details" heading * - Confirm gift title matches exactly * - Expand Details section for extended info * * 8. Public server integration: * - Click "View on Public Server" link * - Verify popup opens with correct URL * - Validate public server accessibility * * ================================================================================ * TEST DATA SPECIFICATIONS * ================================================================================ * * Gift Title Format: "Gift [4-char-random]" * - Prefix: "Gift " (with space) * - Random component: 4-character alphanumeric string * - Example: "Gift a7b3", "Gift x9y2" * * Amount Range: 1-99 (inclusive) * - Minimum: 1 (non-zero validation) * - Maximum: 99 (reasonable upper bound) * - Type: Integer only * - Example: 42, 7, 99 * * Recipient: "Unnamed/Unknown" * - Standard test recipient * - No specific DID or contact info * - Used for all test gifts * * ================================================================================ * SELECTOR REFERENCE * ================================================================================ * * Form Elements: * - Gift title input: '[data-testid="giftTitle"]' or 'input[placeholder="What was given"]' * - Amount input: 'input[type="number"]' or 'input[role="spinbutton"]' * - Submit button: 'button[name="Sign & Send"]' * - Person button: 'button[name="Person"]' * - Recipient list: 'ul[role="listbox"]' * * Navigation & UI: * - Onboarding close: '[data-testid="closeOnboardingAndFinish"]' * - Home page: './' (relative URL) * - Alert dismissal: 'div[role="alert"] button > svg.fa-xmark' * - Success message: 'text="That gift was recorded."' * * Verification Elements: * - Gift list item: 'li:first-child' (filtered by title) * - Info link: '[data-testid="circle-info-link"]' * - Details heading: 'h2[name="Verifiable Claim Details"]' * - Details section: 'h2[name="Details", exact="true"]' * - Public server link: 'a[name="View on the Public Server"]' * * ================================================================================ * ERROR HANDLING & DEBUGGING * ================================================================================ * * Common Failure Points: * 1. Onboarding Dialog * - Issue: Dialog doesn't close properly * - Debug: Check if closeOnboardingAndFinish button exists * - Fix: Add wait for dialog to be visible before clicking * * 2. Recipient Selection * - Issue: "Unnamed" recipient not found * - Debug: Check if recipient list is populated * - Fix: Add wait for list to load before filtering * * 3. Form Submission * - Issue: "Sign & Send" button not clickable * - Debug: Check if form is valid and all fields filled * - Fix: Add validation before submission * * 4. Success Verification * - Issue: Success message doesn't appear * - Debug: Check network requests and JWT signing * - Fix: Add longer timeout for signing process * * 5. Home View Refresh * - Issue: Gift doesn't appear in list * - Debug: Check if gift was actually recorded * - Fix: Add wait for home view to reload * * 6. Public Server Integration * - Issue: Popup doesn't open or wrong URL * - Debug: Check if public server is accessible * - Fix: Verify endorser server configuration * * Debugging Commands: * ```bash * # Run with trace for detailed debugging * npx playwright test 30-record-gift.spec.ts --trace on * * # Run with headed browser for visual debugging * npx playwright test 30-record-gift.spec.ts --headed * * # Run with slow motion for step-by-step debugging * npx playwright test 30-record-gift.spec.ts --debug * ``` * * ================================================================================ * BROWSER COMPATIBILITY * ================================================================================ * * Tested Browsers: * - Chromium: Primary target, full functionality * - Firefox: Secondary target, may have timing differences * * Browser-Specific Considerations: * - Firefox: May require longer timeouts for form interactions * - Chromium: Generally faster, more reliable * - Both: Popup handling may differ slightly * * ================================================================================ * PERFORMANCE CONSIDERATIONS * ================================================================================ * * Expected Timings: * - Data generation: < 1ms * - User import: 2-5 seconds * - Form filling: 1-2 seconds * - JWT signing: 3-8 seconds * - Home refresh: 2-4 seconds * - Public server: 1-3 seconds * * Total expected runtime: 10-20 seconds * * Performance Monitoring: * - Monitor JWT signing time (most variable) * - Track home view refresh time * - Watch for memory leaks in popup handling * * ================================================================================ * MAINTENANCE GUIDELINES * ================================================================================ * * When Modifying This Test: * 1. Update version number and lastModified date * 2. Test on both Chromium and Firefox * 3. Verify with different random data sets * 4. Check that public server integration still works * 5. Update selector references if UI changes * * Related Files to Monitor: * - src/views/RecordGiftView.vue (gift recording UI) * - src/views/HomeView.vue (gift display) * - sw_scripts/safari-notifications.js (JWT signing) * - src/libs/endorserServer.ts (API integration) * - test-playwright/testUtils.ts (user management) * * ================================================================================ * INTEGRATION POINTS * ================================================================================ * * Dependencies: * - User 00 must be available in test data * - Endorser server must be running and accessible * - Public server must be configured correctly * - JWT signing must be functional * * API Endpoints Used: * - POST /api/claims (gift recording) * - GET /api/claims (public verification) * - WebSocket connections for real-time updates * * ================================================================================ * SECURITY CONSIDERATIONS * ================================================================================ * * Test Data Security: * - Random data prevents test interference * - No sensitive information in test gifts * - Public server verification is read-only * * JWT Handling: * - Test uses test user credentials * - Signing process is isolated * - No production keys used * * ================================================================================ * RELATED DOCUMENTATION * ================================================================================ * * @see test-playwright/testUtils.ts - User management utilities * @see test-playwright/README.md - General testing guidelines * @see docs/user-guides/gift-recording.md - User workflow documentation * @see src/views/RecordGiftView.vue - Implementation details * @see sw_scripts/safari-notifications.js - JWT signing implementation * * @example Complete test execution * ```bash * # Run this specific test * npx playwright test 30-record-gift.spec.ts * * # Run with detailed output * npx playwright test 30-record-gift.spec.ts --reporter=list * * # Run in headed mode for debugging * npx playwright test 30-record-gift.spec.ts --headed * ``` */ import { test, expect } from '@playwright/test'; import { importUserFromAccount } from './testUtils'; import { createPerformanceCollector, attachPerformanceData, assertPerformanceMetrics } from './performanceUtils'; /** * @test Record something given * @description End-to-end test of gift recording functionality with performance tracking * @tags gift-recording, e2e, user-workflow, performance * @timeout 45000ms (45 seconds for JWT signing and API calls) * * @process * 1. Generate unique test data * 2. Import test user and navigate to home * 3. Record gift with random title and amount * 4. Verify gift appears in home view * 5. Check public server integration * * @data * - Gift title: "Gift [random-4-chars]" * - Amount: Random 1-99 * - Recipient: "Unnamed/Unknown" * * @verification * - Success notification appears * - Gift visible in home view * - Details match input data * - Public server accessible * * @browsers chromium, firefox * @retries 2 (for flaky network conditions) */ test('Record something given', async ({ page }, testInfo) => { // STEP 1: Initialize the performance collector const perfCollector = await createPerformanceCollector(page); // STEP 2: Generate unique test data const randomString = Math.random().toString(36).substring(2, 6); const randomNonZeroNumber = Math.floor(Math.random() * 99) + 1; const standardTitle = 'Gift '; const finalTitle = standardTitle + randomString; // STEP 3: Import user 00 and navigate to home page await perfCollector.measureUserAction('import-user-account', async () => { await importUserFromAccount(page, '00'); }); await perfCollector.measureUserAction('initial-navigation', async () => { await page.goto('./'); }); const initialMetrics = await perfCollector.collectNavigationMetrics('home-page-load'); await testInfo.attach('initial-page-load-metrics', { contentType: 'application/json', body: JSON.stringify(initialMetrics, null, 2) }); // STEP 4: Close onboarding dialog await perfCollector.measureUserAction('close-onboarding', async () => { await page.getByTestId('closeOnboardingAndFinish').click(); }); // STEP 4.5: Close any additional dialogs that might be blocking await perfCollector.measureUserAction('close-additional-dialogs', async () => { // Wait a moment for any dialogs to appear await page.waitForTimeout(1000); // Try to close any remaining dialogs const closeButtons = page.locator('button[aria-label*="close"], button[aria-label*="Close"], .dialog-overlay button, [role="dialog"] button'); const count = await closeButtons.count(); for (let i = 0; i < count; i++) { try { await closeButtons.nth(i).click({ timeout: 2000 }); } catch (e) { // Ignore errors if button is not clickable } } // Wait for any animations to complete await page.waitForTimeout(500); }); // STEP 5: Select recipient await perfCollector.measureUserAction('select-recipient', async () => { await page.getByRole('button', { name: 'Person' }).click(); await page.getByRole('listitem').filter({ hasText: 'Unnamed' }).locator('svg').click(); }); // STEP 6: Fill gift details await perfCollector.measureUserAction('fill-gift-details', async () => { await page.getByPlaceholder('What was given').fill(finalTitle); await page.getByRole('spinbutton').fill(randomNonZeroNumber.toString()); }); // STEP 7: Submit gift and verify success await perfCollector.measureUserAction('submit-gift', async () => { await page.getByRole('button', { name: 'Sign & Send' }).click(); await expect(page.getByText('That gift was recorded.')).toBeVisible(); await page.locator('div[role="alert"] button > svg.fa-xmark').click(); }); // STEP 8: Refresh home view and locate gift await perfCollector.measureUserAction('refresh-home-view', async () => { // Try page.reload() instead of goto to see if that helps await page.reload(); }); await perfCollector.collectNavigationMetrics('home-refresh-load'); // Wait for feed to load and gift to appear await perfCollector.measureUserAction('wait-for-feed-load', async () => { // Wait for the feed container to be present await page.locator('ul').first().waitFor({ state: 'visible', timeout: 15000 }); // Wait for any feed items to load (not just the first one) await page.locator('li').first().waitFor({ state: 'visible', timeout: 15000 }); // Debug: Check what's actually in the feed const feedItems = page.locator('li'); const count = await feedItems.count(); // Try to find our gift in any position, not just first let giftFound = false; for (let i = 0; i < count; i++) { try { const itemText = await feedItems.nth(i).textContent(); if (itemText?.includes(finalTitle)) { giftFound = true; break; } } catch (e) { // Continue to next item } } if (!giftFound) { // Wait a bit more and try again await page.waitForTimeout(3000); // Check again const newCount = await feedItems.count(); for (let i = 0; i < newCount; i++) { try { const itemText = await feedItems.nth(i).textContent(); if (itemText?.includes(finalTitle)) { giftFound = true; break; } } catch (e) { // Continue to next item } } } if (!giftFound) { throw new Error(`Gift with title "${finalTitle}" not found in feed after waiting`); } }); // Find the gift item (could be in any position) const item = page.locator('li').filter({ hasText: finalTitle }); // STEP 9: View gift details await perfCollector.measureUserAction('view-gift-details', async () => { // Debug: Check what elements are actually present // Wait for the item to be visible await item.waitFor({ state: 'visible', timeout: 10000 }); // Check if the circle-info-link exists const circleInfoLink = item.locator('[data-testid="circle-info-link"]'); const isVisible = await circleInfoLink.isVisible(); // If not visible, let's see what's in the item if (!isVisible) { const itemHtml = await item.innerHTML(); } await circleInfoLink.click(); }); await expect(page.getByRole('heading', { name: 'Verifiable Claim Details' })).toBeVisible(); await expect(page.getByText(finalTitle, { exact: true })).toBeVisible(); // STEP 10: Expand details and open public server const page1Promise = page.waitForEvent('popup'); await perfCollector.measureUserAction('expand-details', async () => { await page.getByRole('heading', { name: 'Details', exact: true }).click(); }); await perfCollector.measureUserAction('open-public-server', async () => { await page.getByRole('link', { name: 'View on the Public Server' }).click(); }); const page1 = await page1Promise; // STEP 11: Attach and validate performance data const { webVitals, performanceReport, summary } = await attachPerformanceData(testInfo, perfCollector); const avgNavigationTime = perfCollector.navigationMetrics.reduce((sum, nav) => sum + nav.metrics.loadComplete, 0) / perfCollector.navigationMetrics.length; assertPerformanceMetrics(webVitals, initialMetrics, avgNavigationTime); });