/ * *
* Contact Management and Gift Recording Test Suite
*
* This test suite verifies the contact management and gift recording functionality
* of the application . It includes tests for adding contacts , recording gifts ,
* and confirming gifts .
*
* Key Components :
*
* 1 . Constants
* - ALERT_TIMEOUT : For alert - related operations ( 5000 ms )
* - NETWORK_TIMEOUT : For network operations ( 10000 ms )
* - ANIMATION_TIMEOUT : For animation completion ( 1000 ms )
*
* 2 . Main Test Cases
* - "Add contact, record gift, confirm gift"
* Tests complete flow of adding contact and managing gifts
* - "Without being registered, add contacts without registration"
* Verifies contact addition without registration
* - "Add contact, copy details, delete, and import"
* Tests contact import / export functionality
*
* 3 . Helper Functions
* - generateRandomString : Creates unique test identifiers
* - dismissAlertWithRetry : Handles alert dismissal with retry logic
* - recordGift : Encapsulates gift recording workflow
* - confirmGift : Manages gift confirmation process
*
* Best Practices :
* - Comprehensive error handling with try - catch blocks
* - Random test data generation
* - Consistent verification steps
* - Page object patterns for maintainability
* - Debug logging support
* - Cross - browser compatibility considerations
*
* @file 40 - add - contact . spec . ts
* /
import { test , expect , Page } from '@playwright/test' ;
import { importUser , getOSSpecificTimeout } from './testUtils' ;
const TEST_NAME = 'add-contact' ;
// Logging utility function - outputs clean, parseable log format
const log = ( type : 'INFO' | 'STEP' | 'SUCCESS' | 'WAIT' , message : string ) = > {
const timestamp = new Date ( ) . toISOString ( ) . split ( 'T' ) [ 1 ] . slice ( 0 , - 1 ) ; // HH:MM:SS format
console . log ( ` ${ timestamp } ${ type . padEnd ( 7 ) } ${ message } ` ) ;
} ;
// Update timeout constants for Linux
const BASE_TIMEOUT = getOSSpecificTimeout ( ) ;
const ALERT_TIMEOUT = BASE_TIMEOUT / 6 ;
const NETWORK_TIMEOUT = BASE_TIMEOUT / 3 ;
const ANIMATION_TIMEOUT = 1000 ;
// Screenshot helper function
async function captureScreenshot ( page : Page , name : string ) {
if ( ! page . isClosed ( ) ) {
// Screenshots are stored in test-results directory
// Example: test-results/add-contact-test-start.png
const filename = ` test-results/ ${ TEST_NAME } - ${ name . replace ( /\s+/g , '-' ) } .png ` ;
log ( 'INFO' , ` Capturing screenshot: ${ filename } ` ) ;
// Ensure directory exists
const fs = require ( 'fs' ) ;
if ( ! fs . existsSync ( 'test-results' ) ) {
fs . mkdirSync ( 'test-results' , { recursive : true } ) ;
}
await page . screenshot ( { path : filename , fullPage : true } ) ;
return filename ;
}
}
// 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 {
log ( 'INFO' , '▶ Starting: Add Contact and Gift Recording Test' ) ;
await captureScreenshot ( page , 'test-start' ) ;
const randomString = await generateRandomString ( 16 ) ;
const randomNonZeroNumber = Math . floor ( Math . random ( ) * 99 ) + 1 ;
if ( randomNonZeroNumber <= 0 ) throw new Error ( 'Failed to generate valid number' ) ;
const finalTitle = ` Gift ${ randomString } ` ;
const contactName = 'Contact #000 renamed' ;
const userName = 'User #000' ;
log ( 'INFO' , ` Test data generated - Title: ${ finalTitle } , Contact: ${ contactName } ` ) ;
log ( 'STEP' , '1. Import test user' ) ;
await importUser ( page , '01' ) ;
await captureScreenshot ( page , '1-after-user-import' ) ;
log ( 'STEP' , '2. Add new contact' ) ;
await page . goto ( './contacts' ) ;
await captureScreenshot ( page , '2-contacts-page' ) ;
await page . getByPlaceholder ( 'URL or DID, Name, Public Key' ) . fill ( ` did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F, ${ userName } ` ) ;
await page . locator ( 'button > svg.fa-plus' ) . click ( ) ;
await captureScreenshot ( page , '2-after-contact-added' ) ;
log ( 'WAIT' , 'Handling registration alert...' ) ;
await handleRegistrationAlert ( page ) ;
log ( 'SUCCESS' , 'Registration alert handled' ) ;
await captureScreenshot ( page , '2-after-alert-handled' ) ;
// Add a small delay to ensure UI is stable
await page . waitForTimeout ( 500 ) ;
// Verify contact was added and is clickable
const contactElement = page . locator ( 'li.border-b' ) ;
await expect ( contactElement ) . toContainText ( userName , { timeout : ANIMATION_TIMEOUT } ) ;
// Ensure no alerts are present before clicking
await expect ( page . locator ( 'div[role="alert"]' ) ) . toBeHidden ( ) ;
// Before clicking info icon
await captureScreenshot ( page , '3-before-info-click' ) ;
await page . locator ( ` li[data-testid="contactListItem"] h2:has-text(" ${ userName } ") + span svg.fa-circle-info ` ) . click ( { force : true } ) ;
// After navigation to details
await expect ( page . getByRole ( 'heading' , { name : 'Identifier Details' } ) ) . toBeVisible ( { timeout : NETWORK_TIMEOUT } ) ;
await captureScreenshot ( page , '3-contact-details' ) ;
// Click edit button and wait for navigation
await page . locator ( 'h2 svg.fa-pen' ) . click ( ) ;
// Debug: Log all headings on the page
const headings = await page . locator ( 'h1, h2, h3, h4, h5, h6' ) . allInnerTexts ( ) ;
log ( 'INFO' , ` Available page headings: ${ headings . join ( ', ' ) } ` ) ;
// Then look for the actual heading we expect to see
await expect ( page . getByRole ( 'heading' , { name : 'Contact Methods' } ) ) . toBeVisible ( { timeout : NETWORK_TIMEOUT } ) ;
// Now look for the input field
const nameInput = page . getByTestId ( 'contactName' ) . locator ( 'input' ) ;
await expect ( nameInput ) . toBeVisible ( { timeout : NETWORK_TIMEOUT } ) ;
await expect ( nameInput ) . toHaveValue ( userName ) ;
// Perform rename with verification
await nameInput . fill ( contactName ) ;
await page . getByRole ( 'button' , { name : 'Save' } ) . click ( ) ;
// Wait for save to complete and verify new name
await expect ( page . locator ( 'h2' , { hasText : contactName } ) ) . toBeVisible ( { timeout : NETWORK_TIMEOUT } ) ;
// Add screenshot before attempting gift recording
log ( 'STEP' , 'Preparing to record gift' ) ;
await captureScreenshot ( page , 'pre-gift-recording-attempt' ) ;
// Record gift with error handling
try {
await recordGift ( page , contactName , finalTitle , randomNonZeroNumber ) ;
} catch ( e ) {
// Capture state when gift recording fails
await captureScreenshot ( page , 'gift-recording-failure' ) ;
log ( 'INFO' , ` Gift recording failed: ${ e instanceof Error ? e.message : String ( e ) } ` ) ;
throw new Error ( ` Failed to record gift: ${ e instanceof Error ? e.message : String ( e ) } ` ) ;
}
// Switch users with verification
try {
await switchToUser00 ( page ) ;
} catch ( e ) {
throw new Error ( ` Failed to switch users: ${ e instanceof Error ? e.message : String ( e ) } ` ) ;
}
// Confirm gift with error handling
await confirmGift ( page , finalTitle ) ;
} catch ( error ) {
// Capture failure state
await captureScreenshot ( page , ` failure- ${ Date . now ( ) } ` ) ;
log ( 'INFO' , ` Test failed: ${ error instanceof Error ? error.message : String ( error ) } ` ) ;
if ( error instanceof Error && error . message . includes ( 'Edit Contact' ) ) {
log ( 'INFO' , ` Available elements: ${ await page . locator ( '*' ) . allInnerTexts ( ) } ` ) ;
}
throw error ;
}
} ) ;
} ) ;
// Helper functions
async function generateRandomString ( length : number ) : Promise < string > {
let result = Math . random ( ) . toString ( 36 ) . substring ( 2 , 18 ) ;
while ( result . length < length ) {
result += Math . random ( ) . toString ( 36 ) . substring ( 2 , 18 ) ;
}
return result . substring ( 0 , length ) ;
}
async function dismissAlertWithRetry ( page : Page , maxRetries = 3 ) {
for ( let i = 0 ; i < maxRetries ; i ++ ) {
try {
await page . locator ( 'div[role="alert"] button > svg.fa-xmark' ) . click ( ) ;
await expect ( page . locator ( 'div[role="alert"]' ) ) . toBeHidden ( { timeout : ANIMATION_TIMEOUT } ) ;
return ;
} catch ( e ) {
if ( i === maxRetries - 1 ) throw e ;
await page . waitForTimeout ( 1000 ) ; // Wait before retry
}
}
}
async function recordGift ( page : Page , contactName : string , title : string , amount : number ) {
const TIMEOUT = getOSSpecificTimeout ( ) ;
let retryCount = 3 ;
while ( retryCount > 0 ) {
try {
log ( 'STEP' , ` Gift recording attempt ${ 4 - retryCount } /3 ` ) ;
await captureScreenshot ( page , ` gift-recording-start-attempt- ${ 4 - retryCount } ` ) ;
log ( 'STEP' , 'Navigate to home page' ) ;
await page . goto ( './' , { timeout : TIMEOUT } ) ;
await Promise . all ( [
page . waitForLoadState ( 'networkidle' , { timeout : TIMEOUT } ) ,
page . waitForLoadState ( 'domcontentloaded' , { timeout : TIMEOUT } )
] ) ;
await captureScreenshot ( page , ` gift-recording-home-page- ${ 4 - retryCount } ` ) ;
// Handle onboarding first
const onboardingButton = page . getByTestId ( 'closeOnboardingAndFinish' ) ;
if ( await onboardingButton . isVisible ( ) ) {
log ( 'STEP' , '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
log ( 'INFO' , ` Current URL: ${ await page . url ( ) } ` ) ;
log ( 'INFO' , ` 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 } ) ;
log ( 'INFO' , ` Current URL after clicking contact: ${ await page . url ( ) } ` ) ;
// Before looking for gift button
await captureScreenshot ( page , ` pre-gift-button-search- ${ 4 - retryCount } ` ) ;
// 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 ( ) ;
log ( 'INFO' , ` Available buttons: ${ allButtons . join ( ', ' ) } ` ) ;
// Check if we need to click info first
const infoIcon = page . locator ( 'svg.fa-circle-info' ) . first ( ) ;
if ( await infoIcon . isVisible ( ) ) {
log ( 'STEP' , 'Clicking info icon' ) ;
await captureScreenshot ( page , ` pre-info-icon-click- ${ 4 - retryCount } ` ) ;
await infoIcon . click ( ) ;
await page . waitForLoadState ( 'networkidle' , { timeout : TIMEOUT } ) ;
await captureScreenshot ( page , ` post-info-icon-click- ${ 4 - retryCount } ` ) ;
}
// Now look for gift button again
if ( await giftButton . count ( ) === 0 ) {
log ( 'INFO' , 'Gift button not found, capturing screenshot and page state' ) ;
await captureScreenshot ( page , ` missing-gift-button- ${ 4 - retryCount } ` ) ;
// Capture more debug info
log ( 'INFO' , ` Current URL: ${ await page . url ( ) } ` ) ;
log ( 'INFO' , ` Page title: ${ await page . title ( ) } ` ) ;
const visibleElements = await page . locator ( 'button, a, h1, h2, h3, div[role="button"]' ) . allInnerTexts ( ) ;
log ( 'INFO' , ` Visible interactive elements: ${ visibleElements . join ( ', ' ) } ` ) ;
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 } ) ;
log ( 'SUCCESS' , 'Gift recording completed' ) ;
await captureScreenshot ( page , ` gift-recording-success- ${ 4 - retryCount } ` ) ;
return ;
} catch ( error ) {
retryCount -- ;
log ( 'INFO' , ` Gift recording attempt failed, ${ retryCount } retries remaining ` ) ;
log ( 'INFO' , ` Error details: ${ error instanceof Error ? error.message : String ( error ) } ` ) ;
await captureScreenshot ( page , ` gift-recording-failure-attempt- ${ 4 - retryCount } ` ) ;
if ( retryCount === 0 ) {
log ( 'INFO' , 'All gift recording attempts failed' ) ;
throw error ;
}
await page . waitForTimeout ( 5000 ) ;
}
}
}
async function switchToUser00 ( page : Page ) {
await page . goto ( './account' ) ;
await page . getByRole ( 'heading' , { name : 'Advanced' } ) . click ( ) ;
await page . getByRole ( 'link' , { name : 'Switch Identifier' } ) . click ( ) ;
await page . getByRole ( 'link' , { name : 'Add Another Identity…' } ) . click ( ) ;
await page . getByText ( 'You have a seed' ) . click ( ) ;
const seedPhrase = 'rigid shrug mobile smart veteran half all pond toilet brave review universe ship congress found yard skate elite apology jar uniform subway slender luggage' ;
await page . getByPlaceholder ( 'Seed Phrase' ) . fill ( seedPhrase ) ;
await page . getByRole ( 'button' , { name : 'Import' } ) . click ( ) ;
await expect ( page . getByRole ( 'code' ) ) . toContainText ( 'did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F' ,
{ timeout : NETWORK_TIMEOUT } ) ;
}
async function confirmGift ( page : Page , title : string ) {
const TIMEOUT = getOSSpecificTimeout ( ) ;
try {
await page . goto ( './' , { timeout : TIMEOUT } ) ;
await page . waitForLoadState ( 'networkidle' , { timeout : TIMEOUT } ) ;
// Close onboarding if present
const onboardingButton = page . getByTestId ( 'closeOnboardingAndFinish' ) ;
if ( await onboardingButton . isVisible ( ) ) {
await onboardingButton . click ( ) ;
await page . waitForTimeout ( 1000 ) ;
}
// Debug: Log page content
console . log ( 'Page content before finding gift:' , await page . content ( ) ) ;
// 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' ) ;
// Click and wait for navigation
await giftElement . click ( ) ;
await Promise . all ( [
page . waitForLoadState ( 'networkidle' , { timeout : TIMEOUT } ) ,
page . waitForLoadState ( 'domcontentloaded' , { timeout : TIMEOUT } )
] ) ;
// Debug: Log available elements
console . log ( 'Page content after navigation:' , await page . content ( ) ) ;
// Try multiple selectors for confirm button
const confirmElement = page . locator ( [
'[data-testid="confirmGiftLink"]' ,
'[data-testid="confirmGiftButton"]' ,
'button:has-text("Confirm")' ,
'a:has-text("Confirm")'
] . join ( ',' ) ) ;
await expect ( confirmElement ) . toBeVisible ( { timeout : TIMEOUT } ) ;
await confirmElement . click ( ) ;
// Wait for confirmation
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 ) {
// Wait for the registration alert
await expect ( page . locator ( 'div[role="alert"]' ) ) . toBeVisible ( { timeout : ALERT_TIMEOUT } ) ;
// Click "No" on registration prompt
await page . locator ( 'div[role="alert"] button:has-text("No")' ) . click ( ) ;
// Wait for info alert and dismiss it
await dismissAlertWithRetry ( page ) ;
// Ensure all alerts are gone before proceeding
await expect ( page . locator ( 'div[role="alert"]' ) ) . toBeHidden ( { timeout : ANIMATION_TIMEOUT } ) ;
}
test ( 'Without being registered, add contacts without registration' , async ( { page , context } ) = > {
await page . goto ( './account' ) ;
// wait until the DID shows on the page in the 'did' element
const didElem = await page . getByTestId ( 'didWrapper' ) . locator ( 'code' ) ;
const newDid = await didElem . innerText ( ) ;
expect ( newDid . trim ( ) ) . toEqual ( '' ) ;
// Add new contact without registering
await page . goto ( './contacts' ) ;
await page . getByPlaceholder ( 'URL or DID, Name, Public Key' ) . fill ( 'did:ethr:0x111d15564f824D56C7a07b913aA7aDd03382aA39, User #111' ) ;
await page . locator ( 'button > svg.fa-plus' ) . click ( ) ;
await expect ( page . locator ( 'div[role="alert"] span:has-text("Contact Added")' ) ) . toBeVisible ( ) ;
await page . locator ( 'div[role="alert"] button > svg.fa-xmark' ) . click ( ) ; // dismiss info alert
// wait for the alert to disappear, which also ensures that there is no "Register" button waiting
await expect ( page . locator ( 'div[role="alert"]' ) ) . toBeHidden ( ) ;
} ) ;
test ( 'Add contact, copy details, delete, and import from paste & from file' , async ( { page , context } ) = > {
await importUser ( page , '00' ) ;
// Add new contact
await page . goto ( './contacts' ) ;
await page . getByPlaceholder ( 'URL or DID, Name, Public Key' ) . fill ( 'did:ethr:0x111d15564f824D56C7a07b913aA7aDd03382aA39, User #111' ) ;
await page . locator ( 'button > svg.fa-plus' ) . click ( ) ;
await expect ( page . locator ( 'div[role="alert"]' ) ) . toBeVisible ( ) ;
await page . locator ( 'div[role="alert"] button:has-text("No")' ) . click ( ) ; // don't register
await page . locator ( 'div[role="alert"] button > svg.fa-xmark' ) . click ( ) ; // dismiss info alert
// wait for the alert to disappear
await expect ( page . locator ( 'div[role="alert"]' ) ) . toBeHidden ( ) ;
// Add another new contact
await page . getByPlaceholder ( 'URL or DID, Name, Public Key' ) . fill ( 'did:ethr:0x222BB77E6Ff3774d34c751f3c1260866357B677b, User #222, asdf1234' ) ;
await page . locator ( 'button > svg.fa-plus' ) . click ( ) ;
await expect ( page . locator ( 'div[role="alert"]' ) ) . toBeVisible ( ) ;
await page . locator ( 'div[role="alert"] button:has-text("No")' ) . click ( ) ; // don't register
await page . locator ( 'div[role="alert"] button > svg.fa-xmark' ) . click ( ) ; // dismiss info alert
await expect ( page . locator ( 'div[role="alert"]' ) ) . toBeHidden ( ) ;
await expect ( page . getByTestId ( 'contactListItem' ) ) . toHaveCount ( 2 ) ;
//// Copy contact details, export them, remove them, and paste to add them
// Copy contact details
await page . getByTestId ( 'contactCheckAllTop' ) . click ( ) ;
await page . getByTestId ( 'copySelectedContactsButtonTop' ) . click ( ) ;
await page . locator ( 'div[role="alert"] button > svg.fa-xmark' ) . click ( ) ; // dismiss alert
await expect ( page . locator ( 'div[role="alert"]' ) ) . toBeHidden ( ) ;
// I would prefer to copy from the clipboard, but the recommended approaches don't work.
// See a different clipboard solution below.
// see contact details on the second contact
await page . getByTestId ( 'contactListItem' ) . nth ( 1 ) . locator ( 'a' ) . click ( ) ;
await page . getByRole ( 'heading' , { name : 'Identifier Details' } ) . isVisible ( ) ;
// remove contact
await page . locator ( 'button > svg.fa-trash-can' ) . click ( ) ;
await page . locator ( 'div[role="alert"] button:has-text("Yes")' ) . click ( ) ;
// for some reason, .isHidden() (without expect) doesn't work
await expect ( page . locator ( 'div[role="alert"] button:has-text("Yes")' ) ) . toBeHidden ( ) ;
// Firefox has a problem when we run this against the test server. It doesn't load the feed.
// It says there's a CORS problem; maybe it's more strict than the other browsers.
// It works when we set the config to use a local server.
// Seems like we hit a similar problem above.
await page . locator ( 'div[role="alert"] button > svg.fa-xmark' ) . click ( ) ; // dismiss alert
await expect ( page . locator ( 'div[role="alert"]' ) ) . toBeHidden ( ) ;
// go to the contacts page and paste the copied contact details
await page . goto ( './contacts' ) ;
// check that there are fewer contacts
await expect ( page . getByTestId ( 'contactListItem' ) ) . toHaveCount ( 1 ) ;
const contactData = 'Paste this: [{ "did": "did:ethr:0x111d15564f824D56C7a07b913aA7aDd03382aA39", "name": "User #111" }, { "did": "did:ethr:0x222BB77E6Ff3774d34c751f3c1260866357B677b", "name": "User #222", "publicKeyBase64": "asdf1234"}] '
await page . getByPlaceholder ( 'URL or DID, Name, Public Key' ) . fill ( contactData ) ;
await page . locator ( 'button > svg.fa-plus' ) . click ( ) ;
// we're on the contact-import page
await expect ( page . locator ( 'li' , { hasText : 'New' } ) ) . toHaveCount ( 1 ) ;
await expect ( page . locator ( 'span' ) . filter ( { hasText : 'the same as' } ) ) . toBeVisible ( ) ;
await page . locator ( 'button' , { hasText : 'Import' } ) . click ( ) ;
// check that there are more contacts
await expect ( page . getByTestId ( 'contactListItem' ) ) . toHaveCount ( 2 ) ;
// Import via the file backup-import, with both new and existing contacts
await page . goto ( './account' ) ;
await page . getByRole ( 'heading' , { name : 'Advanced' } ) . click ( ) ;
const fileSelect = await page . locator ( 'input[type="file"]' )
fileSelect . setInputFiles ( './test-playwright/exported-data.json' ) ;
await page . locator ( 'button' , { hasText : 'Import Only Contacts' } ) . click ( ) ;
// we're on the contact-import page
await expect ( page . locator ( 'li' , { hasText : '- New' } ) ) . toHaveCount ( 3 ) ;
await expect ( page . locator ( 'li' , { hasText : '- Existing' } ) ) . toHaveCount ( 1 ) ;
await expect ( page . locator ( 'span' ) . filter ( { hasText : 'the same as' } ) ) . toBeHidden ( ) ;
await page . locator ( 'button' , { hasText : 'Import' } ) . click ( ) ;
// check that there are more contacts
await expect ( page . getByTestId ( 'contactListItem' ) ) . toHaveCount ( 5 ) ;
// The visibility error is because currently the server returns an error for the same person.
// But it should only show that one, for User #000.
} ) ;
test ( 'Copy contact to clipboard, then import ' , async ( { page , context } , testInfo ) = > {
await importUser ( page , '00' ) ;
await page . goto ( './account' ) ;
await page . getByRole ( 'heading' , { name : 'Advanced' } ) . click ( ) ;
const fileSelect = await page . locator ( 'input[type="file"]' )
fileSelect . setInputFiles ( './test-playwright/exported-data.json' ) ;
await page . locator ( 'button' , { hasText : 'Import Only Contacts' } ) . click ( ) ;
// we're on the contact-import page
await expect ( page . getByRole ( 'heading' , { name : "Contact Import" } ) ) . toBeVisible ( ) ;
await page . locator ( 'button' , { hasText : 'Import' } ) . click ( ) ;
await page . goto ( './contacts' ) ;
// Copy contact details
await page . getByTestId ( 'contactCheckAllTop' ) . click ( ) ;
// // There's a crazy amount of overlap in all the userAgent values. Ug.
// const agent = await page.evaluate(() => {
// return navigator.userAgent;
// });
// console.log("agent: ", agent);
const isFirefox = await page . evaluate ( ( ) = > {
return navigator . userAgent . includes ( 'Firefox' ) ;
} ) ;
const isWebkit = await page . evaluate ( ( ) = > {
return navigator . userAgent . includes ( 'Macintosh' ) || navigator . userAgent . includes ( 'iPhone' ) ;
} ) ;
if ( isWebkit ) {
log ( 'INFO' , 'Webkit detected - clipboard test skipped' ) ;
return ;
}
log ( 'STEP' , 'Running clipboard copy test' ) ;
await page . getByTestId ( 'copySelectedContactsButtonTop' ) . click ( ) ;
const clipboardText = await page . evaluate ( async ( ) = > {
return navigator . clipboard . readText ( ) ;
} ) ;
// look into the playwright.config file for the server URL
const webServer = testInfo . config . webServer ;
const clientServerUrl = webServer ? . url ;
const PATH_PART = clientServerUrl + "/contact-import/" ;
expect ( clipboardText ) . toContain ( PATH_PART ) ;
await page . locator ( 'div[role="alert"] button > svg.fa-xmark' ) . click ( ) ; // dismiss alert
await expect ( page . locator ( 'div[role="alert"]' ) ) . toBeHidden ( ) ;
await page . goto ( clipboardText ) ;
// we're on the contact-import page
await expect ( page . getByRole ( 'heading' , { name : "Contact Import" } ) ) . toBeVisible ( ) ;
await expect ( page . locator ( 'span' , { hasText : '4 contacts are the same' } ) ) . toBeVisible ( ) ;
} ) ;