You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							173 lines
						
					
					
						
							6.9 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							173 lines
						
					
					
						
							6.9 KiB
						
					
					
				| /** | |
|  * End-to-End Contact Management Tests | |
|  *  | |
|  * Comprehensive test suite for Time Safari's contact management and gift recording features. | |
|  * Tests run sequentially to avoid state conflicts and API rate limits. | |
|  *  | |
|  * Test Flow: | |
|  * 1. Contact Creation & Verification | |
|  *    - Add contact using DID | |
|  *    - Verify contact appears in list | |
|  *    - Rename contact and verify change | |
|  *    - Check contact appears in "Record Something" section | |
|  *  | |
|  * 2. Gift Recording Flow | |
|  *    - Generate unique gift details | |
|  *    - Record gift to contact | |
|  *    - Verify gift confirmation | |
|  *    - Check gift appears in activity feed | |
|  *  | |
|  * 3. Contact Import/Export Tests | |
|  *    - Copy contact details to clipboard | |
|  *    - Delete existing contact | |
|  *    - Import contact from clipboard | |
|  *    - Verify imported contact details | |
|  *  | |
|  * Test Data Generation: | |
|  * - Gift titles: "Gift " + 16-char random string | |
|  * - Gift amounts: Random 1-99 value | |
|  * - Contact names: Predefined test values | |
|  * - DIDs: Uses test accounts (e.g., did:ethr:0x000...) | |
|  *  | |
|  * Key Selectors: | |
|  * - Contact list: 'li[data-testid="contactListItem"]' | |
|  * - Gift recording: '#sectionRecordSomethingGiven' | |
|  * - Contact name: '[data-testid="contactName"] input' | |
|  * - Alert dialogs: 'div[role="alert"]' | |
|  *  | |
|  * Timeouts & Retries: | |
|  * - Uses OS-specific timeouts (longer for Linux) | |
|  * - Implements retry logic for network operations | |
|  * - Waits for UI animations and state changes | |
|  *  | |
|  * Alert Handling: | |
|  * - Closes onboarding dialogs | |
|  * - Handles registration prompts | |
|  * - Verifies alert dismissal | |
|  *  | |
|  * State Requirements: | |
|  * - Clean database state | |
|  * - No existing contacts for test DIDs | |
|  * - Available API rate limits | |
|  *  | |
|  * @example Basic contact addition | |
|  * ```typescript | |
|  * await page.goto('./contacts'); | |
|  * await page.getByPlaceholder('URL or DID, Name, Public Key') | |
|  *   .fill('did:ethr:0x000...., User Name'); | |
|  * await page.locator('button > svg.fa-plus').click(); | |
|  * ``` | |
|  */ | |
| import { test, expect } from '@playwright/test'; | |
| import { importUser } from './testUtils'; | |
| 
 | |
| test('Create new project, then search for it', async ({ page }) => { | |
| 
 | |
|   // Generate a random string of 16 characters | |
|   let randomString = Math.random().toString(36).substring(2, 18); | |
| 
 | |
|   // In case the string is shorter than 16 characters, generate more characters until it is 16 characters long | |
|   while (randomString.length < 16) { | |
|     randomString += Math.random().toString(36).substring(2, 18); | |
|   } | |
|   const finalRandomString = randomString.substring(0, 16); | |
| 
 | |
|   // Standard texts | |
|   const standardTitle = 'Idea '; | |
|   const standardDescription = 'Description of Idea '; | |
|   const standardEdit = ' EDITED'; | |
|   const standardWebsite = 'https://example.com'; | |
|   const editedWebsite = 'https://example.com/edited'; | |
| 
 | |
|   // Set dates | |
|   const today = new Date(); | |
|   const oneMonthAhead = new Date(today.setDate(today.getDate() + 30)); | |
|   const twoMonthsAhead = new Date(today.setDate(today.getDate() + 30)); | |
|   const finalDate = oneMonthAhead.toISOString().split('T')[0]; | |
|   const editedDate = twoMonthsAhead.toISOString().split('T')[0]; | |
| 
 | |
|   // Set times | |
|   const now = new Date(); | |
|   const oneHourAhead = new Date(now.setHours(now.getHours() + 1)); | |
|   const twoHoursAhead = new Date(now.setHours(now.getHours() + 1)); | |
|   const finalHour = oneHourAhead.getHours().toString().padStart(2, '0'); | |
|   const editedHour = twoHoursAhead.getHours().toString().padStart(2, '0'); | |
|   const finalMinute = oneHourAhead.getMinutes().toString().padStart(2, '0'); | |
|   const finalTime = `${finalHour}:${finalMinute}`; | |
|   const editedTime = `${editedHour}:${finalMinute}`; | |
| 
 | |
|   // Combine texts with the random string | |
|   const finalTitle = standardTitle + finalRandomString; | |
|   const finalDescription = standardDescription + finalRandomString; | |
|   const editedTitle = finalTitle + standardEdit; | |
|   const editedDescription = finalDescription + standardEdit; | |
| 
 | |
|   // Import user 00 | |
|   await importUser(page, '00'); | |
| 
 | |
|   // Create new project | |
|   await page.goto('./projects'); | |
|   // Check if onboarding dialog exists and close it if present | |
|   try { | |
|     await page.getByTestId('closeOnboardingAndFinish').click({ timeout: 2000 }); | |
|     await page.waitForFunction(() => { | |
|       return !document.querySelector('.dialog-overlay'); | |
|     }, { timeout: 5000 }); | |
|   } catch (error) { | |
|     // No onboarding dialog present, continue | |
|     console.log('No onboarding dialog found on projects page'); | |
|   } | |
|   // Route back to projects page again, because the onboarding dialog was designed to route to HomeView when called from ProjectsView | |
|   await page.goto('./projects'); | |
|   await page.locator('button > svg.fa-plus').click(); | |
|   await page.getByPlaceholder('Idea Name').fill(finalTitle); | |
|   await page.getByPlaceholder('Description').fill(finalDescription); | |
|   await page.getByPlaceholder('Website').fill(standardWebsite); | |
|   await page.getByPlaceholder('Start Date').fill(finalDate); | |
|   await page.getByPlaceholder('Start Time').fill(finalTime); | |
|   await page.getByRole('button', { name: 'Save Project' }).click(); | |
| 
 | |
|   // Wait for project to be saved and page to update | |
|   await page.waitForLoadState('networkidle'); | |
|    | |
|   // Check texts | |
|   await expect(page.locator('h2')).toContainText(finalTitle); | |
|   await expect(page.locator('#Content')).toContainText(finalDescription); | |
| 
 | |
|   // Search for newly-created project in /projects | |
|   await page.goto('./projects'); | |
|   // Wait for projects list to load and then search for the project | |
|   await page.waitForLoadState('networkidle'); | |
|    | |
|   // Debug: Log all projects in the list | |
|   const projectItems = await page.locator('ul#listProjects li').all(); | |
|   console.log(`Found ${projectItems.length} projects in list`); | |
|   for (let i = 0; i < projectItems.length; i++) { | |
|     const text = await projectItems[i].textContent(); | |
|     console.log(`Project ${i}: ${text}`); | |
|   } | |
|    | |
|   await expect(page.locator('ul#listProjects li').filter({ hasText: finalTitle })).toBeVisible({ timeout: 10000 }); | |
| 
 | |
|   // Search for newly-created project in /discover | |
|   await page.goto('./discover'); | |
|   await page.getByPlaceholder('Search…').fill(finalRandomString); | |
|   await page.locator('#QuickSearch button').click(); | |
|   await expect(page.locator('ul#listDiscoverResults li').filter({ hasText: finalTitle })).toBeVisible(); | |
| 
 | |
|   // Edit the project | |
|   await page.locator('a').filter({ hasText: finalTitle }).first().click(); | |
|   await page.getByRole('button', { name: 'Edit' }).click(); | |
|   await expect(page.getByPlaceholder('Idea Name')).toHaveValue(finalTitle); // Check that textfield value has loaded before proceeding | |
|   await page.getByPlaceholder('Idea Name').fill(editedTitle); | |
|   await page.getByPlaceholder('Description').fill(editedDescription); | |
|   await page.getByPlaceholder('Website').fill(editedWebsite); | |
|   await page.getByPlaceholder('Start Date').fill(editedDate); | |
|   await page.getByPlaceholder('Start Time').fill(editedTime); | |
|   await page.getByRole('button', { name: 'Save Project' }).click(); | |
| 
 | |
|   // Check edits | |
|   await expect(page.locator('h2')).toContainText(editedTitle); | |
|   await page.getByText('Read More').click(); | |
|   await expect(page.locator('#Content')).toContainText(editedDescription); | |
| }); |