You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
210 lines
7.9 KiB
210 lines
7.9 KiB
/**
|
|
* @file Bulk Gift Recording Test Suite
|
|
* @description Tests TimeSafari's gift recording functionality under load by creating
|
|
* multiple gift records in sequence. Limited to 9 gifts to stay under 30-second timeout.
|
|
*
|
|
* This test verifies:
|
|
* 1. Scalability
|
|
* - System handles multiple gift recordings (9)
|
|
* - Performance remains stable across iterations
|
|
* - No data corruption during bulk operations
|
|
*
|
|
* 2. Data Integrity
|
|
* - Each gift has unique identifiers
|
|
* - All gifts properly stored and retrievable
|
|
* - No cross-contamination between gift data
|
|
*
|
|
* 3. UI/UX Stability
|
|
* - Interface remains responsive during bulk operations
|
|
* - Success notifications display correctly
|
|
* - Alert dismissal works consistently
|
|
*
|
|
* Test Flow:
|
|
* 1. Setup Phase
|
|
* - Generate arrays of unique strings for titles
|
|
* - Generate array of random numbers for amounts
|
|
* - Import User 00 (test account)
|
|
*
|
|
* 2. Bulk Recording (9 iterations)
|
|
* - Navigate to home
|
|
* - Handle first-time onboarding dialog
|
|
* - Select recipient (Unnamed/Unknown)
|
|
* - Fill gift details from arrays
|
|
* - Sign and submit
|
|
* - Verify success
|
|
* - Dismiss notification
|
|
* - Verify gift in list (optimized)
|
|
*
|
|
* Test Data:
|
|
* - Gift Count: 9 (optimized for timeout limits)
|
|
* - Title Format: "Gift [unique-string]"
|
|
* - Amount: Random numbers array
|
|
* - Recipient: "Unnamed/Unknown" (constant)
|
|
*
|
|
* Key Selectors:
|
|
* - Gift input: '[placeholder="What was given"]'
|
|
* - Amount input: '[role="spinbutton"]'
|
|
* - Submit button: '[name="Sign & Send"]'
|
|
* - Success alert: 'div[role="alert"]'
|
|
* - Alert dismiss: 'button > svg.fa-xmark'
|
|
*
|
|
* Performance Considerations:
|
|
* - Limited to 9 gifts to avoid timeout
|
|
* - Handles UI lag between operations
|
|
* - Manages memory usage during bulk operations
|
|
* - Optimized navigation: single page.goto() per iteration
|
|
* - Efficient verification: waits for DOM updates instead of full page reload
|
|
*
|
|
* Error Handling:
|
|
* - Closes onboarding dialog only on first iteration
|
|
* - Verifies each gift individually
|
|
* - Maintains operation even if individual recordings fail
|
|
*
|
|
* Related Files:
|
|
* - Gift recording view: src/views/RecordGiftView.vue
|
|
* - JWT creation: sw_scripts/safari-notifications.js
|
|
* - Endorser API: src/libs/endorserServer.ts
|
|
* - Test utilities: ./testUtils.ts
|
|
*
|
|
* @see Documentation in usage-guide.md for gift recording workflows
|
|
* @requires @playwright/test
|
|
* @requires ./testUtils - For user management and array generation
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* // Generate test data arrays
|
|
* const uniqueStrings = await createUniqueStringsArray(giftCount);
|
|
* const randomNumbers = await createRandomNumbersArray(giftCount);
|
|
*
|
|
* // Record gifts in sequence
|
|
* for (let i = 0; i < giftCount; i++) {
|
|
* await page.goto('./');
|
|
* await page.getByPlaceholder('What was given').fill(finalTitles[i]);
|
|
* await page.getByRole('spinbutton').fill(finalNumbers[i].toString());
|
|
* await page.getByRole('button', { name: 'Sign & Send' }).click();
|
|
* }
|
|
* ```
|
|
*/
|
|
|
|
import { test, expect } from '@playwright/test';
|
|
import { importUserFromAccount, createUniqueStringsArray, createRandomNumbersArray } from './testUtils';
|
|
import { createPerformanceCollector, attachPerformanceData, assertPerformanceMetrics } from './performanceUtils';
|
|
|
|
test('Record 9 new gifts', async ({ page }, testInfo) => {
|
|
test.slow(); // Set timeout longer
|
|
|
|
// STEP 1: Initialize the performance collector
|
|
const perfCollector = await createPerformanceCollector(page);
|
|
|
|
const giftCount = 9;
|
|
const standardTitle = 'Gift ';
|
|
const finalTitles: string[] = [];
|
|
const finalNumbers: number[] = [];
|
|
|
|
// STEP 2: Create arrays for field input
|
|
await perfCollector.measureUserAction('generate-test-data', async () => {
|
|
const uniqueStrings = await createUniqueStringsArray(giftCount);
|
|
const randomNumbers = await createRandomNumbersArray(giftCount);
|
|
|
|
// Populate arrays
|
|
for (let i = 0; i < giftCount; i++) {
|
|
finalTitles.push(standardTitle + uniqueStrings[i]);
|
|
finalNumbers.push(randomNumbers[i]);
|
|
}
|
|
});
|
|
|
|
// STEP 3: Import user 00
|
|
await perfCollector.measureUserAction('import-user-account', async () => {
|
|
await importUserFromAccount(page, '00');
|
|
});
|
|
|
|
// STEP 4: Initial navigation and metrics collection
|
|
await perfCollector.measureUserAction('initial-navigation', async () => {
|
|
await page.goto('./');
|
|
});
|
|
const initialMetrics = await perfCollector.collectNavigationMetrics('initial-home-load');
|
|
await testInfo.attach('initial-page-load-metrics', {
|
|
contentType: 'application/json',
|
|
body: JSON.stringify(initialMetrics, null, 2)
|
|
});
|
|
|
|
// STEP 5: Record new gifts with optimized navigation
|
|
for (let i = 0; i < giftCount; i++) {
|
|
// Only navigate on first iteration
|
|
if (i === 0) {
|
|
await perfCollector.measureUserAction(`navigate-home-iteration-${i + 1}`, async () => {
|
|
await page.goto('./', { waitUntil: 'networkidle' });
|
|
});
|
|
|
|
await perfCollector.measureUserAction('close-onboarding', async () => {
|
|
await page.getByTestId('closeOnboardingAndFinish').click();
|
|
});
|
|
} else {
|
|
// For subsequent iterations, just wait for the page to be ready
|
|
await perfCollector.measureUserAction(`wait-for-page-ready-iteration-${i + 1}`, async () => {
|
|
await page.waitForLoadState('domcontentloaded');
|
|
});
|
|
}
|
|
|
|
await perfCollector.measureUserAction(`select-recipient-iteration-${i + 1}`, async () => {
|
|
await page.getByRole('button', { name: 'Person' }).click();
|
|
await page.getByRole('listitem').filter({ hasText: 'Unnamed' }).locator('svg').click();
|
|
});
|
|
|
|
await perfCollector.measureUserAction(`fill-gift-details-iteration-${i + 1}`, async () => {
|
|
await page.getByPlaceholder('What was given').fill(finalTitles[i]);
|
|
await page.getByRole('spinbutton').fill(finalNumbers[i].toString());
|
|
});
|
|
|
|
await perfCollector.measureUserAction(`submit-gift-iteration-${i + 1}`, async () => {
|
|
await page.getByRole('button', { name: 'Sign & Send' }).click();
|
|
|
|
// Wait for success and dismiss
|
|
await expect(page.getByText('That gift was recorded.')).toBeVisible();
|
|
await page.locator('div[role="alert"] button > svg.fa-xmark').click();
|
|
});
|
|
|
|
// Optimized verification: use real-time DOM monitoring instead of page reload
|
|
await perfCollector.measureUserAction(`verify-gift-in-list-iteration-${i + 1}`, async () => {
|
|
// Wait for any network activity to settle after gift submission
|
|
await page.waitForLoadState('networkidle', { timeout: 3000 });
|
|
|
|
// Real-time DOM monitoring: wait for the gift to appear in the activity list
|
|
await page.waitForFunction(
|
|
(giftTitle) => {
|
|
const activityList = document.querySelector('ul#listLatestActivity');
|
|
if (!activityList) return false;
|
|
|
|
const listItems = activityList.querySelectorAll('li');
|
|
for (const item of listItems) {
|
|
if (item.textContent?.includes(giftTitle)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
finalTitles[i],
|
|
{ timeout: 8000 }
|
|
);
|
|
|
|
// Additional verification: ensure the gift is actually visible
|
|
await expect(page.locator('ul#listLatestActivity li')
|
|
.filter({ hasText: finalTitles[i] })
|
|
.first())
|
|
.toBeVisible({ timeout: 2000 });
|
|
});
|
|
}
|
|
|
|
// STEP 6: Attach and validate performance data
|
|
const { webVitals, performanceReport, summary } = await attachPerformanceData(testInfo, perfCollector);
|
|
|
|
// Calculate average navigation time only if we have metrics
|
|
if (perfCollector.navigationMetrics.length > 0) {
|
|
const avgNavigationTime = perfCollector.navigationMetrics.reduce((sum, nav) =>
|
|
sum + nav.metrics.loadComplete, 0) / perfCollector.navigationMetrics.length;
|
|
assertPerformanceMetrics(webVitals, initialMetrics, avgNavigationTime);
|
|
} else {
|
|
// If no navigation metrics, just validate web vitals
|
|
assertPerformanceMetrics(webVitals, initialMetrics, 0);
|
|
}
|
|
});
|