import { expect, Page } from '@playwright/test'; // Import the seed and switch to the user based on the ID. // '01' -> user 111 // otherwise -> user 000 // (... which is a weird convention but I haven't taken the time to change it) export async function importUser(page: Page, id?: string): Promise { let seedPhrase, userName, did; // Set seed phrase and DID based on user ID switch(id) { case '01': seedPhrase = 'island fever beef wine urban aim vacant quit afford total poem flame service calm better adult neither color gaze forum month sister imitate excite'; userName = 'User One'; did = 'did:ethr:0x111d15564f824D56C7a07b913aA7aDd03382aA39'; break; default: // to user 00 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'; userName = 'User Zero'; did = 'did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F'; } // Import ID await page.goto('./start'); await page.getByText('You have a seed').click(); await page.getByPlaceholder('Seed Phrase').fill(seedPhrase); await page.getByRole('button', { name: 'Import' }).click(); // Check DID await expect(page.getByRole('code')).toContainText(did); // ... and ensure the app retrieves the registration status await expect(page.locator('#sectionUsageLimits').getByText('Checking')).toBeHidden(); return did; } export async function importUserAndCloseOnboarding(page: Page, id?: string): Promise { const did = await importUser(page, id); await page.goto('./'); await page.getByTestId('closeOnboardingAndFinish').click(); return did; } // This is to switch to someone already in the identity table. It doesn't include registration. export async function switchToUser(page: Page, did: string): Promise { // This is the direct approach but users have to tap on things so we'll do that instead. //await page.goto('./identity-switcher'); await page.goto('./account'); await page.getByRole('heading', { name: 'Advanced' }).click(); await page.getByRole('link', { name: 'Switch Identifier' }).click(); const didElem = await page.locator(`code:has-text("${did}")`); await didElem.isVisible(); await didElem.click(); // wait for the switch to happen and the account page to fully load await page.getByTestId('didWrapper').locator('code:has-text("did:")'); } function createContactName(did: string): string { return "User " + did.slice(11, 14); } export async function deleteContact(page: Page, did: string): Promise { console.log('[DEBUG] deleteContact: Starting deletion for DID:', did); await page.goto('./contacts'); console.log('[DEBUG] deleteContact: Navigated to contacts page'); // Wait for the page to load completely await page.waitForLoadState('networkidle'); console.log('[DEBUG] deleteContact: Page load completed'); // Check if there are any loading indicators or filters active const loadingIndicator = page.locator('[data-testid*="loading"], .loading, .spinner'); const loadingCount = await loadingIndicator.count(); console.log('[DEBUG] deleteContact: Loading indicators found:', loadingCount); // Check for any filter or state buttons that might be active const showGiveNumbers = page.locator('button:has-text("Hide Actions")'); const showGiveNumbersExists = await showGiveNumbers.count(); console.log('[DEBUG] deleteContact: "Hide Actions" button exists (showing give numbers):', showGiveNumbersExists > 0); if (showGiveNumbersExists > 0) { console.log('[DEBUG] deleteContact: Clicking "Hide Actions" to show normal view'); await showGiveNumbers.click(); await page.waitForTimeout(1000); // Wait for UI to update } const contactName = createContactName(did); console.log('[DEBUG] deleteContact: Looking for contact with name:', contactName); // First, let's see what contacts are actually on the page const contactItems = await page.locator('li[data-testid="contactListItem"]'); const contactCount = await contactItems.count(); console.log('[DEBUG] deleteContact: Found contact items on page:', contactCount); // Log all contact names visible on the page for (let i = 0; i < contactCount; i++) { const contactItem = contactItems.nth(i); const nameElement = contactItem.locator('h2'); const nameText = await nameElement.textContent(); console.log('[DEBUG] deleteContact: Contact', i, ':', nameText); } // If no contacts found, let's take a screenshot to see what's on the page if (contactCount === 0) { console.log('[DEBUG] deleteContact: No contacts found, taking screenshot'); await page.screenshot({ path: 'debug-no-contacts.png', fullPage: true }); console.log('[DEBUG] deleteContact: Screenshot saved as debug-no-contacts.png'); } // Try to find the contact list item with the expected name const contactListItem = page.locator(`li[data-testid="contactListItem"]:has(h2:has-text("${contactName}"))`); const contactExists = await contactListItem.count(); console.log('[DEBUG] deleteContact: Contact with name exists:', contactName, contactExists > 0); if (contactExists === 0) { console.error('[DEBUG] deleteContact: Contact not found on page:', contactName); throw new Error(`Contact "${contactName}" not found on contacts page`); } // Now click the info icon - fix the selector to match actual DOM structure // Try different selectors for the info icon const infoIconSelectors = [ `li[data-testid="contactListItem"]:has(h2:has-text("${contactName}")) font-awesome[icon="circle-info"]`, `li[data-testid="contactListItem"]:has(h2:has-text("${contactName}")) .fa-circle-info`, `li[data-testid="contactListItem"]:has(h2:has-text("${contactName}")) svg.fa-circle-info`, `li[data-testid="contactListItem"]:has(h2:has-text("${contactName}")) [class*="fa-circle-info"]` ]; let infoIcon: import('@playwright/test').Locator | null = null; let infoIconExists = 0; for (const selector of infoIconSelectors) { console.log('[DEBUG] deleteContact: Trying selector:', selector); const testIcon = page.locator(selector); const testCount = await testIcon.count(); console.log('[DEBUG] deleteContact: Selector result count:', testCount); if (testCount > 0) { infoIcon = testIcon; infoIconExists = testCount; console.log('[DEBUG] deleteContact: Found working selector:', selector); break; } } console.log('[DEBUG] deleteContact: Info icon exists:', infoIconExists > 0); if (infoIconExists === 0 || !infoIcon) { console.error('[DEBUG] deleteContact: Info icon not found for contact:', contactName); throw new Error(`Info icon not found for contact "${contactName}"`); } // go to the detail page for this contact await infoIcon.click(); console.log('[DEBUG] deleteContact: Clicked info icon, should be on detail page'); // Verify we're on the detail page const detailPageHeading = page.locator('h1:has-text("Identifier Details")'); await detailPageHeading.waitFor({ timeout: 10000 }); console.log('[DEBUG] deleteContact: Confirmed on detail page'); // delete the contact const deleteButton = page.locator('button > svg.fa-trash-can'); const deleteButtonExists = await deleteButton.count(); console.log('[DEBUG] deleteContact: Delete button exists:', deleteButtonExists > 0); if (deleteButtonExists === 0) { console.error('[DEBUG] deleteContact: Delete button not found'); throw new Error('Delete button not found on detail page'); } await deleteButton.click(); console.log('[DEBUG] deleteContact: Clicked delete button'); // Confirm deletion const confirmButton = page.locator('div[role="alert"] button:has-text("Yes")'); await confirmButton.waitFor({ timeout: 10000 }); console.log('[DEBUG] deleteContact: Confirmation dialog appeared'); await confirmButton.click(); console.log('[DEBUG] deleteContact: Clicked confirmation button'); // for some reason, .isHidden() (without expect) doesn't work await expect(page.locator('div[role="alert"] button:has-text("Yes")')).toBeHidden(); console.log('[DEBUG] deleteContact: Confirmation dialog dismissed, deletion complete'); } export async function generateNewEthrUser(page: Page): Promise { await page.goto('./start'); await page.getByTestId('newSeed').click(); await expect(page.locator('span:has-text("Created")')).toBeVisible(); await page.goto('./account'); const didElem = await page.getByTestId('didWrapper').locator('code:has-text("did:")'); const newDid = await didElem.innerText(); return newDid; } // Generate a new random user and register them. // Note that this makes 000 the active user. Use switchToUser to switch to this DID. export async function generateAndRegisterEthrUser(page: Page): Promise { console.log('[DEBUG] generateAndRegisterEthrUser: Starting user generation'); const newDid = await generateNewEthrUser(page); console.log('[DEBUG] generateAndRegisterEthrUser: Generated new DID:', newDid); await importUser(page, '000'); // switch to user 000 console.log('[DEBUG] generateAndRegisterEthrUser: Switched to user 000'); await page.goto('./contacts'); console.log('[DEBUG] generateAndRegisterEthrUser: Navigated to contacts page'); const contactName = createContactName(newDid); console.log('[DEBUG] generateAndRegisterEthrUser: Created contact name:', contactName); const contactInput = `${newDid}, ${contactName}`; console.log('[DEBUG] generateAndRegisterEthrUser: Filling contact input with:', contactInput); await page.getByPlaceholder('URL or DID, Name, Public Key').fill(contactInput); await page.locator('button > svg.fa-plus').click(); console.log('[DEBUG] generateAndRegisterEthrUser: Clicked add contact button'); // register them await page.locator('div[role="alert"] button:has-text("Yes")').click(); console.log('[DEBUG] generateAndRegisterEthrUser: Clicked registration confirmation'); // wait for it to disappear because the next steps may depend on alerts being gone await expect(page.locator('div[role="alert"] button:has-text("Yes")')).toBeHidden(); console.log('[DEBUG] generateAndRegisterEthrUser: Registration dialog dismissed'); await expect(page.locator('li', { hasText: contactName })).toBeVisible(); console.log('[DEBUG] generateAndRegisterEthrUser: Contact is now visible in list:', contactName); return newDid; } // Function to generate a random string of specified length export async function generateRandomString(length: number): Promise { return Math.random().toString(36).substring(2, 2 + length); } // Function to create an array of unique strings export async function createUniqueStringsArray(count: number): Promise { const stringsArray: string[] = []; const stringLength = 16; for (let i = 0; i < count; i++) { let randomString = await generateRandomString(stringLength); stringsArray.push(randomString); } return stringsArray; } // Function to create an array of two-digit non-zero numbers export async function createRandomNumbersArray(count: number): Promise { const numbersArray: number[] = []; for (let i = 0; i < count; i++) { let randomNumber = Math.floor(Math.random() * 99) + 1; numbersArray.push(randomNumber); } return numbersArray; } export function isLinuxEnvironment() { return process.platform === 'linux'; } export function getOSSpecificTimeout(): number { // Increase base timeout for Linux const isLinux = process.platform === 'linux'; return isLinux ? 180000 : 60000; // 3 minutes for Linux, 1 minute for others } export function getOSSpecificConfig() { if (isLinuxEnvironment()) { return { retries: 2, timeout: 90000, // Increased global timeout expect: { timeout: 30000 // Increased expect timeout }, // Add video recording for failed tests on Linux use: { video: 'retain-on-failure', trace: 'retain-on-failure' } }; } return {}; } // Add helper for test grouping export function isResourceIntensiveTest(testPath: string): boolean { return testPath.includes('35-record-gift-from-image-share') || testPath.includes('40-add-contact'); }