@ -38,14 +38,20 @@
* /
* /
import { test , expect , Page } from '@playwright/test' ;
import { test , expect , Page } from '@playwright/test' ;
import { importUser } from './testUtils' ;
import { importUser , getOSSpecificTimeout } from './testUtils' ;
// Add timeout constants
// Update timeout constants for Linux
const ALERT_TIMEOUT = 5000 ;
const BASE_TIMEOUT = getOSSpecificTimeout ( ) ;
const NETWORK_TIMEOUT = 10000 ;
const ALERT_TIMEOUT = BASE_TIMEOUT / 6 ;
const NETWORK_TIMEOUT = BASE_TIMEOUT / 3 ;
const ANIMATION_TIMEOUT = 1000 ;
const ANIMATION_TIMEOUT = 1000 ;
test ( 'Add contact, record gift, confirm gift' , async ( { page } ) = > {
// Add test configuration to increase timeout
test . describe ( 'Contact Management' , ( ) = > {
// Increase timeout for all tests in this group
test . setTimeout ( BASE_TIMEOUT * 2 ) ;
test ( 'Add contact, record gift, confirm gift' , async ( { page } ) = > {
try {
try {
// Generate test data with error checking
// Generate test data with error checking
const randomString = await generateRandomString ( 16 ) ;
const randomString = await generateRandomString ( 16 ) ;
@ -133,6 +139,7 @@ test('Add contact, record gift, confirm gift', async ({ page }) => {
}
}
throw error ;
throw error ;
}
}
} ) ;
} ) ;
} ) ;
// Helper functions
// Helper functions
@ -158,22 +165,138 @@ async function dismissAlertWithRetry(page: Page, maxRetries = 3) {
}
}
async function recordGift ( page : Page , contactName : string , title : string , amount : number ) {
async function recordGift ( page : Page , contactName : string , title : string , amount : number ) {
// First navigate to home
const TIMEOUT = getOSSpecificTimeout ( ) ;
await page . goto ( './' ) ;
let retryCount = 3 ;
await page . getByTestId ( 'closeOnboardingAndFinish' ) . click ( ) ;
// Click on the contact name and wait for navigation
while ( retryCount > 0 ) {
await page . getByRole ( 'heading' , { name : contactName } ) . click ( ) ;
try {
await expect ( page . getByPlaceholder ( 'What was given' ) ) . toBeVisible ( { timeout : NETWORK_TIMEOUT } ) ;
console . log ( ` Gift recording attempt ${ 4 - retryCount } /3 ` ) ;
// Fill in gift details
// First navigate to home and ensure it's loaded
await page . getByPlaceholder ( 'What was given' ) . fill ( title ) ;
await page . goto ( './' , { timeout : TIMEOUT } ) ;
await page . getByRole ( 'spinbutton' ) . fill ( amount . toString ( ) ) ;
await Promise . all ( [
await page . getByRole ( 'button' , { name : 'Sign & Send' } ) . click ( ) ;
page . waitForLoadState ( 'networkidle' , { timeout : TIMEOUT } ) ,
page . waitForLoadState ( 'domcontentloaded' , { timeout : TIMEOUT } )
] ) ;
// Wait for confirmation
// Handle onboarding first
await expect ( page . getByText ( 'That gift was recorded.' ) ) . toBeVisible ( { timeout : NETWORK_TIMEOUT } ) ;
const onboardingButton = page . getByTestId ( 'closeOnboardingAndFinish' ) ;
await page . locator ( 'div[role="alert"] button > svg.fa-xmark' ) . click ( ) ; // dismiss info alert
if ( await onboardingButton . isVisible ( ) ) {
console . log ( 'Closing onboarding dialog...' ) ;
await onboardingButton . click ( ) ;
await expect ( onboardingButton ) . toBeHidden ( ) ;
await page . waitForTimeout ( 1000 ) ;
}
// Navigate to contact's details page
await page . goto ( './contacts' , { timeout : TIMEOUT } ) ;
await page . waitForLoadState ( 'networkidle' , { timeout : TIMEOUT } ) ;
// Debug current state
console . log ( 'Current URL before clicking contact:' , await page . url ( ) ) ;
console . log ( 'Looking for contact:' , contactName ) ;
// Find and click contact name
const contactHeading = page . getByRole ( 'heading' , { name : contactName } ) . first ( ) ;
await expect ( contactHeading ) . toBeVisible ( { timeout : TIMEOUT } ) ;
await contactHeading . click ( ) ;
// Wait for navigation
await page . waitForLoadState ( 'networkidle' , { timeout : TIMEOUT } ) ;
console . log ( 'Current URL after clicking contact:' , await page . url ( ) ) ;
// Look for gift recording UI elements
const giftButton = page . locator ( [
'button:has-text("Record Gift")' ,
'button:has-text("Give")' ,
'[data-testid="recordGiftButton"]' ,
'a:has-text("Record Gift")' ,
'a:has-text("Give")'
] . join ( ',' ) ) ;
// Debug UI state
const allButtons = await page . locator ( 'button, a' ) . allInnerTexts ( ) ;
console . log ( 'Available buttons:' , allButtons ) ;
// Check if we need to click info first
const infoIcon = page . locator ( 'svg.fa-circle-info' ) . first ( ) ;
if ( await infoIcon . isVisible ( ) ) {
console . log ( 'Found info icon, clicking it first' ) ;
await infoIcon . click ( ) ;
await page . waitForLoadState ( 'networkidle' , { timeout : TIMEOUT } ) ;
}
// Now look for gift button again
if ( await giftButton . count ( ) === 0 ) {
console . log ( 'Gift button not found, taking screenshot' ) ;
await page . screenshot ( { path : 'test-results/missing-gift-button.png' , fullPage : true } ) ;
console . log ( 'Page content:' , await page . content ( ) ) ;
throw new Error ( 'Gift button not found on page' ) ;
}
await expect ( giftButton ) . toBeVisible ( { timeout : TIMEOUT } ) ;
await expect ( giftButton ) . toBeEnabled ( { timeout : TIMEOUT } ) ;
await giftButton . click ( ) ;
// Wait for navigation and form
await Promise . all ( [
page . waitForLoadState ( 'networkidle' , { timeout : TIMEOUT } ) ,
page . waitForLoadState ( 'domcontentloaded' , { timeout : TIMEOUT } )
] ) ;
const giftInput = page . getByPlaceholder ( 'What was given' ) ;
await expect ( giftInput ) . toBeVisible ( { timeout : TIMEOUT } ) ;
// Fill form with verification between steps
await giftInput . fill ( title ) ;
await page . waitForTimeout ( 500 ) ;
const amountInput = page . getByRole ( 'spinbutton' ) ;
await expect ( amountInput ) . toBeVisible ( { timeout : TIMEOUT } ) ;
await amountInput . fill ( amount . toString ( ) ) ;
await page . waitForTimeout ( 500 ) ;
// Submit and wait for response
const submitButton = page . getByRole ( 'button' , { name : 'Sign & Send' } ) ;
await expect ( submitButton ) . toBeEnabled ( { timeout : TIMEOUT } ) ;
await submitButton . click ( ) ;
// Wait for confirmation with API check
const confirmationTimeout = Date . now ( ) + TIMEOUT ;
while ( Date . now ( ) < confirmationTimeout ) {
const isVisible = await page . getByText ( 'That gift was recorded.' ) . isVisible ( ) ;
if ( isVisible ) break ;
await page . waitForTimeout ( 1000 ) ;
}
await expect ( page . getByText ( 'That gift was recorded.' ) ) . toBeVisible ( { timeout : 1000 } ) ;
// If we get here, everything worked
console . log ( 'Gift recording successful' ) ;
return ;
} catch ( error ) {
retryCount -- ;
console . log ( ` Gift recording attempt failed, ${ retryCount } retries remaining ` ) ;
console . error ( 'Error:' , error instanceof Error ? error.message : String ( error ) ) ;
// Take screenshot on failure
if ( ! page . isClosed ( ) ) {
await page . screenshot ( {
path : ` test-results/gift-recording-failure- ${ 4 - retryCount } .png ` ,
fullPage : true
} ) ;
}
if ( retryCount === 0 ) {
console . error ( 'All gift recording attempts failed' ) ;
throw error ;
}
await page . waitForTimeout ( 5000 ) ;
}
}
}
}
async function switchToUser00 ( page : Page ) {
async function switchToUser00 ( page : Page ) {
@ -192,91 +315,55 @@ async function switchToUser00(page: Page) {
}
}
async function confirmGift ( page : Page , title : string ) {
async function confirmGift ( page : Page , title : string ) {
await page . goto ( './' ) ;
const TIMEOUT = getOSSpecificTimeout ( ) ;
await page . getByTestId ( 'closeOnboardingAndFinish' ) . click ( ) ;
// Wait for the gift to be visible and clickable
const giftElement = page . locator ( 'li' ) . filter ( { hasText : title } ) ;
await expect ( giftElement ) . toBeVisible ( { timeout : NETWORK_TIMEOUT } ) ;
// Route all API requests to port 3000
await page . route ( '**/api/**' , async route = > {
const url = new URL ( route . request ( ) . url ( ) ) ;
if ( url . port === '8081' ) {
const newUrl = ` http://localhost:3000 ${ url . pathname } ${ url . search } ` ;
console . log ( ` Redirecting ${ url . toString ( ) } to ${ newUrl } ` ) ;
route . continue ( { url : newUrl } ) ;
} else {
route . continue ( ) ;
}
} ) ;
await giftElement . locator ( 'a' ) . click ( ) ;
// Wait for both load states with a try-catch
try {
try {
await Promise . all ( [
await page . goto ( './' , { timeout : TIMEOUT } ) ;
page . waitForLoadState ( 'networkidle' , { timeout : NETWORK_TIMEOUT } ) ,
await page . waitForLoadState ( 'networkidle' , { timeout : TIMEOUT } ) ;
page . waitForLoadState ( 'domcontentloaded' , { timeout : NETWORK_TIMEOUT } )
] ) ;
// Close onboarding if present
} catch ( e ) {
const onboardingButton = page . getByTestId ( 'closeOnboardingAndFinish' ) ;
console . log ( 'Load state error:' , e . message ) ;
if ( await onboardingButton . isVisible ( ) ) {
}
await onboardingButton . click ( ) ;
await page . waitForTimeout ( 1000 ) ;
// Debug: Log all headings and content
const headings = await page . locator ( 'h1, h2, h3, h4, h5, h6' ) . allInnerTexts ( ) ;
console . log ( 'Gift page headings:' , headings ) ;
// Log the current URL
console . log ( 'Current URL:' , page . url ( ) ) ;
// Check for error message and retry if needed
const errorMessage = page . getByText ( 'Something went wrong retrieving claim data' ) ;
const isError = await errorMessage . isVisible ( ) ;
if ( isError ) {
console . log ( 'Error detected, will retry' ) ;
await page . waitForTimeout ( 2000 ) ; // Increased delay
await page . goto ( './' ) ;
await page . waitForTimeout ( 2000 ) ; // Increased delay
await giftElement . locator ( 'a' ) . click ( ) ;
await page . waitForLoadState ( 'networkidle' , { timeout : NETWORK_TIMEOUT } ) ;
}
}
// Wait for either the confirm link or button with increased timeout
// Debug: Log page content
const confirmLink = page . getByTestId ( 'confirmGiftLink' ) ;
console . log ( 'Page content before finding gift:' , await page . content ( ) ) ;
const confirmButton = page . getByTestId ( 'confirmGiftButton' ) ;
console . log ( 'Waiting for confirm element to be visible...' ) ;
// Wait for and find the gift element
const giftElement = page . locator ( 'li, div' ) . filter ( { hasText : title } ) . first ( ) ;
await expect ( giftElement ) . toBeVisible ( { timeout : TIMEOUT } ) ;
console . log ( 'Found gift element' ) ;
try {
// Click and wait for navigation
// Try both selectors with a longer timeout
await giftElement . click ( ) ;
const confirmElement = await Promise . race ( [
await Promise . all ( [
confirmLink . waitFor ( { state : 'visible' , timeout : NETWORK_TIMEOUT * 2 } ) . then ( ( ) = > confirmLink ) ,
page . waitForLoadState ( 'networkidle' , { timeout : TIMEOUT } ) ,
confirmButton . waitFor ( { state : 'visible' , timeout : NETWORK_ TIMEOUT * 2 } ) . then ( ( ) = > confirmButton )
page . waitForLoadState ( 'domcontentloaded' , { timeout : TIMEOUT } )
] ) ;
] ) ;
// Log success and click
// Debug: Log available elements
console . log ( 'Found confirm element, clicking...' ) ;
console . log ( 'Page content after navigation:' , await page . content ( ) ) ;
await confirmElement . click ( ) ;
} catch ( e ) {
console . log ( 'Error finding confirm element:' , e . message ) ;
// Log the page content for debugging
console . log ( 'Page content:' , await page . content ( ) ) ;
throw e ;
}
// Handle confirmation dialog
// Try multiple selectors for confirm button
const confirmDialogButton = page . getByRole ( 'button' , { name : 'Confirm' } ) ;
const confirmElement = page . locator ( [
await expect ( confirmDialogButton ) . toBeVisible ( { timeout : NETWORK_TIMEOUT } ) ;
'[data-testid="confirmGiftLink"]' ,
await confirmDialogButton . click ( ) ;
'[data-testid="confirmGiftButton"]' ,
'button:has-text("Confirm")' ,
'a:has-text("Confirm")'
] . join ( ',' ) ) ;
const yesButton = page . getByRole ( 'button' , { name : 'Yes' } ) ;
await expect ( confirmElement ) . toBeVisible ( { timeout : TIMEOUT } ) ;
await expect ( yesButton ) . toBeVisible ( { timeout : NETWORK_TIMEOUT } ) ;
await confirmElement . click ( ) ;
await yesButton . click ( ) ;
// Wait for confirmation
// Wait for confirmation
await expect ( page . getByText ( 'Confirmation submitted.' ) ) . toBeVisible ( { timeout : NETWORK_TIMEOUT } ) ;
await expect ( page . getByText ( 'Confirmation submitted.' ) ) . toBeVisible ( { timeout : TIMEOUT } ) ;
} catch ( error ) {
console . error ( 'Confirmation failed:' , error ) ;
await page . screenshot ( { path : 'test-results/confirmation-failure.png' } ) ;
throw error ;
}
}
}
async function handleRegistrationAlert ( page : Page ) {
async function handleRegistrationAlert ( page : Page ) {