Browse Source

Add performance monitoring to Playwright test suite

Enhance test files with comprehensive performance tracking:
- Add performance collector integration to usage limits, project gifts, and offer recording tests
- Implement detailed user action timing with measureUserAction wrapper
- Add navigation metrics collection and validation
- Include performance data attachments to test reports
- Add dialog overlay handling for improved test reliability

Files modified:
- test-playwright/10-check-usage-limits.spec.ts
- test-playwright/37-record-gift-on-project.spec.ts
- test-playwright/50-record-offer.spec.ts
pull/159/head
Matthew Raymer 3 weeks ago
parent
commit
835619fc66
  1. 42
      test-playwright/10-check-usage-limits.spec.ts
  2. 89
      test-playwright/37-record-gift-on-project.spec.ts
  3. 226
      test-playwright/50-record-offer.spec.ts

42
test-playwright/10-check-usage-limits.spec.ts

@ -60,29 +60,59 @@
*/
import { test, expect } from '@playwright/test';
import { importUser } from './testUtils';
import { createPerformanceCollector, attachPerformanceData, assertPerformanceMetrics } from './performanceUtils';
test('Check usage limits', async ({ page }) => {
// Check without ID first
test('Check usage limits', async ({ page }, testInfo) => {
// STEP 1: Initialize the performance collector
const perfCollector = await createPerformanceCollector(page);
// STEP 2: Check without ID first
await perfCollector.measureUserAction('navigate-to-account', async () => {
await page.goto('./account');
});
const initialMetrics = await perfCollector.collectNavigationMetrics('account-page-load');
await testInfo.attach('initial-page-load-metrics', {
contentType: 'application/json',
body: JSON.stringify(initialMetrics, null, 2)
});
await perfCollector.measureUserAction('verify-no-usage-limits', async () => {
await expect(page.locator('div.bg-slate-100.rounded-md').filter({ hasText: 'Usage Limits' })).toBeHidden();
});
// Import user 01
// STEP 3: Import user 01
await perfCollector.measureUserAction('import-user-account', async () => {
const did = await importUser(page, '01');
});
// Verify that "Usage Limits" section is visible
// STEP 4: Verify usage limits section
await perfCollector.measureUserAction('verify-usage-limits-section', async () => {
await expect(page.locator('#sectionUsageLimits')).toBeVisible();
await expect(page.locator('#sectionUsageLimits')).toContainText('You have done');
await expect(page.locator('#sectionUsageLimits')).toContainText('You have uploaded');
});
await perfCollector.measureUserAction('verify-usage-limit-texts', async () => {
await expect(page.getByText('Your claims counter resets')).toBeVisible();
await expect(page.getByText('Your registration counter resets')).toBeVisible();
await expect(page.getByText('Your image counter resets')).toBeVisible();
await expect(page.getByRole('button', { name: 'Recheck Limits' })).toBeVisible();
});
// Set name
// STEP 5: Set name
await perfCollector.measureUserAction('click-set-name-button', async () => {
await page.getByRole('button', { name: 'Set Your Name' }).click();
const name = 'User ' + did.slice(11, 14);
});
await perfCollector.measureUserAction('fill-and-save-name', async () => {
const name = 'User ' + '01'.slice(0, 2);
await page.getByPlaceholder('Name').fill(name);
await page.getByRole('button', { name: 'Save', exact: true }).click();
});
// STEP 6: 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);
});

89
test-playwright/37-record-gift-on-project.spec.ts

@ -1,50 +1,101 @@
import { test, expect, Page } from '@playwright/test';
import { importUser } from './testUtils';
import { createPerformanceCollector, attachPerformanceData, assertPerformanceMetrics } from './performanceUtils';
async function testProjectGive(page: Page, selector: string) {
async function testProjectGive(page: Page, selector: string, testInfo: any) {
// STEP 1: Initialize the performance collector
const perfCollector = await createPerformanceCollector(page);
// Generate a random string of a few characters
// STEP 2: Generate unique test data
const randomString = Math.random().toString(36).substring(2, 6);
// Generate a random non-zero single-digit number
const randomNonZeroNumber = Math.floor(Math.random() * 99) + 1;
// Standard title prefix
const standardTitle = 'Gift ';
// Combine title prefix with the random string
const finalTitle = standardTitle + randomString;
// find a project and enter a give to it and see that it shows
// STEP 3: Import user and navigate to discover
await perfCollector.measureUserAction('import-user-account', async () => {
await importUser(page, '00');
});
await perfCollector.measureUserAction('navigate-to-discover', async () => {
await page.goto('./discover');
});
const initialMetrics = await perfCollector.collectNavigationMetrics('discover-page-load');
await testInfo.attach('initial-page-load-metrics', {
contentType: 'application/json',
body: JSON.stringify(initialMetrics, null, 2)
});
await perfCollector.measureUserAction('close-onboarding', async () => {
await page.getByTestId('closeOnboardingAndFinish').click();
});
await perfCollector.measureUserAction('select-first-project', async () => {
await page.locator('ul#listDiscoverResults li:first-child a').click();
});
await page.locator('ul#listDiscoverResults li:first-child a').click()
// wait for the project page to load
// STEP 4: Wait for project page to load
await perfCollector.measureUserAction('wait-for-project-load', async () => {
await page.waitForLoadState('networkidle');
// click the give button, inside the first div
});
// STEP 5: Handle dialog overlays
await perfCollector.measureUserAction('close-dialog-overlays', async () => {
await page.waitForTimeout(1000);
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
}
}
await page.waitForTimeout(500);
});
// STEP 6: Record gift
await perfCollector.measureUserAction('click-give-button', async () => {
await page.getByTestId(selector).locator('div:first-child div button').click();
});
await perfCollector.measureUserAction('fill-gift-details', async () => {
await page.getByPlaceholder('What was given').fill(finalTitle);
await page.getByRole('spinbutton').fill(randomNonZeroNumber.toString());
});
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(); // dismiss info alert
await page.locator('div[role="alert"] button > svg.fa-xmark').click();
});
// refresh the page
// STEP 7: Verify gift appears in list
await perfCollector.measureUserAction('refresh-page', async () => {
await page.reload();
// check that the give is in the list
});
await perfCollector.measureUserAction('verify-gift-in-list', async () => {
await page
.getByTestId(selector)
.locator('div ul li:first-child')
.filter({ hasText: finalTitle })
.isVisible();
});
// STEP 8: 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);
}
test('Record a give to a project', async ({ page }) => {
await testProjectGive(page, 'gives-to');
test('Record a give to a project', async ({ page }, testInfo) => {
await testProjectGive(page, 'gives-to', testInfo);
});
test('Record a give from a project', async ({ page }) => {
await testProjectGive(page, 'gives-from');
test('Record a give from a project', async ({ page }, testInfo) => {
await testProjectGive(page, 'gives-from', testInfo);
});

226
test-playwright/50-record-offer.spec.ts

@ -1,79 +1,171 @@
import { test, expect, Page } from '@playwright/test';
import { importUser, importUserFromAccount } from './testUtils';
import { createPerformanceCollector, attachPerformanceData, assertPerformanceMetrics } from './performanceUtils';
test('Record an offer', async ({ page }) => {
test('Record an offer', async ({ page }, testInfo) => {
test.setTimeout(60000);
// Generate a random string of 3 characters, skipping the "0." at the beginning
// 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, 5);
// Standard title prefix
const description = `Offering of ${randomString}`;
const updatedDescription = `Updated ${description}`;
const randomNonZeroNumber = Math.floor(Math.random() * 998) + 1;
// Switch to user 0
// await importUser(page);
// Become User Zero
// STEP 3: Import user and navigate to discover page
await perfCollector.measureUserAction('import-user-account', async () => {
await importUserFromAccount(page, "00");
// Select a project
});
await perfCollector.measureUserAction('navigate-to-discover', async () => {
await page.goto('./discover');
});
const initialMetrics = await perfCollector.collectNavigationMetrics('discover-page-load');
await testInfo.attach('initial-page-load-metrics', {
contentType: 'application/json',
body: JSON.stringify(initialMetrics, null, 2)
});
// STEP 4: Close onboarding and select project
await perfCollector.measureUserAction('close-onboarding', async () => {
await page.getByTestId('closeOnboardingAndFinish').click();
});
await perfCollector.measureUserAction('select-project', async () => {
await page.locator('ul#listDiscoverResults li:nth-child(1)').click();
// Record an offer
await page.locator('button', { hasText: 'Edit' }).isVisible(); // since the 'edit' takes longer to show, wait for that (lest the click miss)
});
// STEP 5: Record an offer
await perfCollector.measureUserAction('wait-for-edit-button', async () => {
await page.locator('button', { hasText: 'Edit' }).isVisible();
});
await perfCollector.measureUserAction('click-offer-button', async () => {
await page.getByTestId('offerButton').click();
});
await perfCollector.measureUserAction('fill-offer-details', async () => {
await page.getByTestId('inputDescription').fill(description);
await page.getByTestId('inputOfferAmount').fill(randomNonZeroNumber.toString());
});
await perfCollector.measureUserAction('submit-offer', async () => {
expect(page.getByRole('button', { name: 'Sign & Send' }));
await page.getByRole('button', { name: 'Sign & Send' }).click();
await expect(page.getByText('That offer was recorded.')).toBeVisible();
await page.locator('div[role="alert"] button > svg.fa-xmark').click(); // dismiss info alert
// go to the offer and check the values
await page.locator('div[role="alert"] button > svg.fa-xmark').click();
});
// STEP 6: Navigate to projects and check offer
await perfCollector.measureUserAction('navigate-to-projects', async () => {
await page.goto('./projects');
});
await perfCollector.measureUserAction('click-offers-tab', async () => {
await page.getByRole('link', { name: 'Offers', exact: true }).click();
});
await perfCollector.measureUserAction('click-offer-details', async () => {
await page.locator('li').filter({ hasText: description }).locator('a').first().click();
});
await perfCollector.measureUserAction('verify-offer-details', async () => {
await expect(page.getByRole('heading', { name: 'Verifiable Claim Details' })).toBeVisible();
await expect(page.getByText(description, { exact: true })).toBeVisible();
await expect(page.getByText('Offered to a bigger plan')).toBeVisible();
});
// STEP 7: Expand details and check public server
const serverPagePromise = page.waitForEvent('popup');
// expand the Details section to see the extended details
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 serverPage = await serverPagePromise;
await perfCollector.measureUserAction('verify-public-server', async () => {
await expect(serverPage.getByText(description)).toBeVisible();
await expect(serverPage.getByText('did:none:HIDDEN')).toBeVisible();
// Now update that offer
});
// find the edit page and check the old values again
// STEP 8: Update the offer
await perfCollector.measureUserAction('navigate-back-to-projects', async () => {
await page.goto('./projects');
});
await perfCollector.measureUserAction('click-offers-tab-again', async () => {
await page.getByRole('link', { name: 'Offers', exact: true }).click();
});
await perfCollector.measureUserAction('click-offer-to-edit', async () => {
await page.locator('li').filter({ hasText: description }).locator('a').first().click();
});
await perfCollector.measureUserAction('click-edit-button', async () => {
await page.getByTestId('editClaimButton').click();
});
await perfCollector.measureUserAction('verify-edit-form', async () => {
await page.locator('heading', { hasText: 'What is offered' }).isVisible();
const itemDesc = await page.getByTestId('itemDescription');
await expect(itemDesc).toHaveValue(description);
const amount = await page.getByTestId('inputOfferAmount');
await expect(amount).toHaveValue(randomNonZeroNumber.toString());
// update the values
});
await perfCollector.measureUserAction('update-offer-values', async () => {
const itemDesc = await page.getByTestId('itemDescription');
await itemDesc.fill(updatedDescription);
const amount = await page.getByTestId('inputOfferAmount');
await amount.fill(String(randomNonZeroNumber + 1));
});
await perfCollector.measureUserAction('submit-updated-offer', async () => {
await page.getByRole('button', { name: 'Sign & Send' }).click();
await expect(page.getByText('That offer was recorded.')).toBeVisible();
await page.locator('div[role="alert"] button > svg.fa-xmark').click(); // dismiss info alert
// go to the offer claim again and check the updated values
await page.locator('div[role="alert"] button > svg.fa-xmark').click();
});
// STEP 9: Verify updated offer
await perfCollector.measureUserAction('navigate-to-projects-final', async () => {
await page.goto('./projects');
});
await perfCollector.measureUserAction('click-offers-tab-final', async () => {
await page.getByRole('link', { name: 'Offers', exact: true }).click();
});
await perfCollector.measureUserAction('click-updated-offer', async () => {
await page.locator('li').filter({ hasText: description }).locator('a').first().click();
});
await perfCollector.measureUserAction('verify-updated-offer', async () => {
const newItemDesc = page.getByTestId('description');
await expect(newItemDesc).toHaveText(updatedDescription);
// go to edit page
});
await perfCollector.measureUserAction('click-edit-button-final', async () => {
await page.getByTestId('editClaimButton').click();
});
await perfCollector.measureUserAction('verify-updated-amount', async () => {
const newAmount = page.getByTestId('inputOfferAmount');
await expect(newAmount).toHaveValue((randomNonZeroNumber + 1).toString());
// go to the home page and check that the offer is shown as new
});
// STEP 10: Check home page for new offers
await perfCollector.measureUserAction('navigate-to-home', async () => {
await page.goto('./');
});
await perfCollector.measureUserAction('verify-new-offers-indicator', async () => {
const offerNumElem = page.getByTestId('newOffersToUserProjectsActivityNumber');
// extract the number and check that it's greater than 0 or "50+"
const offerNumText = await offerNumElem.textContent();
if (offerNumText === null) {
throw new Error('Expected Activity Number greater than 0 but got null.');
@ -84,44 +176,116 @@ test('Record an offer', async ({ page }) => {
} else {
throw new Error(`Expected Activity Number of greater than 0 but got ${offerNumText}.`);
}
});
// click on the number of new offers to go to the list page
await perfCollector.measureUserAction('click-new-offers-number', async () => {
const offerNumElem = page.getByTestId('newOffersToUserProjectsActivityNumber');
await offerNumElem.click();
});
await perfCollector.measureUserAction('verify-new-offers-page', async () => {
await expect(page.getByText('New Offers To Your Projects', { exact: true })).toBeVisible();
// get the icon child of the showOffersToUserProjects
});
await perfCollector.measureUserAction('expand-offers-section', async () => {
await page.getByTestId('showOffersToUserProjects').locator('div > svg.fa-chevron-right').click();
});
await perfCollector.measureUserAction('verify-offer-in-list', async () => {
await expect(page.getByText(description)).toBeVisible();
});
// 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);
});
test('Affirm delivery of an offer', async ({ page }) => {
// go to the home page and check that the offer is shown as new
// await importUser(page);
test('Affirm delivery of an offer', async ({ page }, testInfo) => {
// STEP 1: Initialize the performance collector
const perfCollector = await createPerformanceCollector(page);
// STEP 2: Import user and navigate to home
await perfCollector.measureUserAction('import-user-account', async () => {
await importUserFromAccount(page, "00");
});
await perfCollector.measureUserAction('navigate-to-home', 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)
});
await perfCollector.measureUserAction('close-onboarding', async () => {
await page.getByTestId('closeOnboardingAndFinish').click();
});
// STEP 3: Check new offers indicator
await perfCollector.measureUserAction('verify-new-offers-indicator', async () => {
const offerNumElem = page.getByTestId('newOffersToUserProjectsActivityNumber');
await expect(offerNumElem).toBeVisible();
});
// STEP 4: Navigate to offers list
await perfCollector.measureUserAction('click-new-offers-number', async () => {
// Close any dialog overlays that might be blocking clicks
await page.waitForTimeout(1000);
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
}
}
// click on the number of new offers to go to the list page
// Wait for any animations to complete
await page.waitForTimeout(500);
const offerNumElem = page.getByTestId('newOffersToUserProjectsActivityNumber');
await offerNumElem.click();
});
// get the link that comes after the showOffersToUserProjects and click it
await perfCollector.measureUserAction('click-offers-link', async () => {
await page.getByTestId('showOffersToUserProjects').locator('a').click();
});
// get the first item of the list and click on the icon with file-lines
// STEP 5: Affirm delivery
await perfCollector.measureUserAction('select-first-offer', async () => {
const firstItem = page.getByTestId('listRecentOffersToUserProjects').locator('li').first();
await expect(firstItem).toBeVisible();
await firstItem.locator('svg.fa-file-lines').click();
});
await perfCollector.measureUserAction('verify-claim-details', async () => {
await expect(page.getByText('Verifiable Claim Details', { exact: true })).toBeVisible();
});
// click on the 'Affirm Delivery' button
await perfCollector.measureUserAction('click-affirm-delivery', async () => {
await page.getByRole('button', { name: 'Affirm Delivery' }).click();
// fill our offer info and submit
});
await perfCollector.measureUserAction('fill-delivery-details', async () => {
await page.getByPlaceholder('What was given').fill('Whatever the offer says');
await page.getByRole('spinbutton').fill('2');
});
await perfCollector.measureUserAction('submit-delivery', 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(); // dismiss info alert
await page.locator('div[role="alert"] button > svg.fa-xmark').click();
});
// STEP 6: 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);
});

Loading…
Cancel
Save