|
@ -33,7 +33,7 @@ |
|
|
* - Sign and submit |
|
|
* - Sign and submit |
|
|
* - Verify success |
|
|
* - Verify success |
|
|
* - Dismiss notification |
|
|
* - Dismiss notification |
|
|
* - Verify gift in list |
|
|
* - Verify gift in list (optimized) |
|
|
* |
|
|
* |
|
|
* Test Data: |
|
|
* Test Data: |
|
|
* - Gift Count: 9 (optimized for timeout limits) |
|
|
* - Gift Count: 9 (optimized for timeout limits) |
|
@ -52,6 +52,8 @@ |
|
|
* - Limited to 9 gifts to avoid timeout |
|
|
* - Limited to 9 gifts to avoid timeout |
|
|
* - Handles UI lag between operations |
|
|
* - Handles UI lag between operations |
|
|
* - Manages memory usage during bulk 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: |
|
|
* Error Handling: |
|
|
* - Closes onboarding dialog only on first iteration |
|
|
* - Closes onboarding dialog only on first iteration |
|
@ -85,17 +87,22 @@ |
|
|
*/ |
|
|
*/ |
|
|
|
|
|
|
|
|
import { test, expect } from '@playwright/test'; |
|
|
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
|
|
|
test.slow(); // Set timeout longer
|
|
|
|
|
|
|
|
|
|
|
|
// STEP 1: Initialize the performance collector
|
|
|
|
|
|
const perfCollector = await createPerformanceCollector(page); |
|
|
|
|
|
|
|
|
const giftCount = 9; |
|
|
const giftCount = 9; |
|
|
const standardTitle = 'Gift '; |
|
|
const standardTitle = 'Gift '; |
|
|
const finalTitles = []; |
|
|
const finalTitles: string[] = []; |
|
|
const finalNumbers = []; |
|
|
const finalNumbers: number[] = []; |
|
|
|
|
|
|
|
|
// Create arrays for field input
|
|
|
// STEP 2: Create arrays for field input
|
|
|
|
|
|
await perfCollector.measureUserAction('generate-test-data', async () => { |
|
|
const uniqueStrings = await createUniqueStringsArray(giftCount); |
|
|
const uniqueStrings = await createUniqueStringsArray(giftCount); |
|
|
const randomNumbers = await createRandomNumbersArray(giftCount); |
|
|
const randomNumbers = await createRandomNumbersArray(giftCount); |
|
|
|
|
|
|
|
@ -104,32 +111,100 @@ test('Record 9 new gifts', async ({ page }) => { |
|
|
finalTitles.push(standardTitle + uniqueStrings[i]); |
|
|
finalTitles.push(standardTitle + uniqueStrings[i]); |
|
|
finalNumbers.push(randomNumbers[i]); |
|
|
finalNumbers.push(randomNumbers[i]); |
|
|
} |
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// STEP 3: Import user 00
|
|
|
|
|
|
await perfCollector.measureUserAction('import-user-account', async () => { |
|
|
|
|
|
await importUserFromAccount(page, '00'); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
// Import user 00
|
|
|
// STEP 4: Initial navigation and metrics collection
|
|
|
await importUser(page, '00'); |
|
|
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++) { |
|
|
for (let i = 0; i < giftCount; i++) { |
|
|
// Record gift
|
|
|
// Only navigate on first iteration
|
|
|
await page.goto('./', { waitUntil: 'networkidle' }); |
|
|
|
|
|
if (i === 0) { |
|
|
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(); |
|
|
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('button', { name: 'Person' }).click(); |
|
|
await page.getByRole('listitem').filter({ hasText: 'Unnamed' }).locator('svg').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.getByPlaceholder('What was given').fill(finalTitles[i]); |
|
|
await page.getByRole('spinbutton').fill(finalNumbers[i].toString()); |
|
|
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(); |
|
|
await page.getByRole('button', { name: 'Sign & Send' }).click(); |
|
|
|
|
|
|
|
|
// Wait for success and dismiss
|
|
|
// Wait for success and dismiss
|
|
|
await expect(page.getByText('That gift was recorded.')).toBeVisible(); |
|
|
await expect(page.getByText('That gift was recorded.')).toBeVisible(); |
|
|
await page.locator('div[role="alert"] button > svg.fa-xmark').click(); |
|
|
await page.locator('div[role="alert"] button > svg.fa-xmark').click(); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
// Verify gift in list with network idle wait
|
|
|
// Optimized verification: use real-time DOM monitoring instead of page reload
|
|
|
await page.goto('./', { waitUntil: 'networkidle' }); |
|
|
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') |
|
|
await expect(page.locator('ul#listLatestActivity li') |
|
|
.filter({ hasText: finalTitles[i] }) |
|
|
.filter({ hasText: finalTitles[i] }) |
|
|
.first()) |
|
|
.first()) |
|
|
.toBeVisible({ timeout: 3000 }); |
|
|
.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); |
|
|
} |
|
|
} |
|
|
}); |
|
|
}); |