forked from trent_larson/crowd-funder-for-time-pwa
- Add robust feed item searching to handle background processing delays - Replace page.goto() with page.reload() for more reliable state refresh - Implement retry logic for gift detection in feed with 3-second wait - Add comprehensive debugging to identify browser-specific timing differences - Handle intermittent failures caused by batch processing and priority loading The test failures were caused by our feed optimizations (priority processing, batch display, background processing) which changed the timing of when new gifts appear in the feed. The fix ensures tests work reliably across both Chromium and Firefox while maintaining our 97.7% network request reduction. Test: Both browsers now pass consistently in ~11-12 seconds
492 lines
18 KiB
TypeScript
492 lines
18 KiB
TypeScript
/**
|
|
* @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);
|
|
}); |