diff --git a/test-playwright/33-record-gift-x10.spec.ts b/test-playwright/33-record-gift-x10.spec.ts index f0dfeef4..65e09d6b 100644 --- a/test-playwright/33-record-gift-x10.spec.ts +++ b/test-playwright/33-record-gift-x10.spec.ts @@ -33,7 +33,7 @@ * - Sign and submit * - Verify success * - Dismiss notification - * - Verify gift in list + * - Verify gift in list (optimized) * * Test Data: * - Gift Count: 9 (optimized for timeout limits) @@ -52,6 +52,8 @@ * - 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 @@ -85,51 +87,124 @@ */ import { test, expect } from '@playwright/test'; -import { importUser, createUniqueStringsArray, createRandomNumbersArray } from './testUtils'; +import { importUserFromAccount, createUniqueStringsArray, createRandomNumbersArray } from './testUtils'; +import { createPerformanceCollector, attachPerformanceData, assertPerformanceMetrics } from './performanceUtils'; -test('Record 9 new gifts', async ({ page }) => { +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 = []; - const finalNumbers = []; + const finalTitles: string[] = []; + const finalNumbers: number[] = []; - // Create arrays for field input - const uniqueStrings = await createUniqueStringsArray(giftCount); - const randomNumbers = await createRandomNumbersArray(giftCount); + // 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]); - } + // 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'); + }); - // Import user 00 - await importUser(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) + }); - // Record new gifts with optimized waiting + // STEP 5: Record new gifts with optimized navigation for (let i = 0; i < giftCount; i++) { - // Record gift - await page.goto('./', { waitUntil: 'networkidle' }); + // Only navigate on first iteration if (i === 0) { - await page.getByTestId('closeOnboardingAndFinish').click(); + 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 page.getByRole('button', { name: 'Person' }).click(); - await page.getByRole('listitem').filter({ hasText: 'Unnamed' }).locator('svg').click(); - 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(); - - // 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(); - // Verify gift in list with network idle wait - await page.goto('./', { waitUntil: 'networkidle' }); - await expect(page.locator('ul#listLatestActivity li') - .filter({ hasText: finalTitles[i] }) - .first()) - .toBeVisible({ timeout: 3000 }); + 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); } }); \ No newline at end of file