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, contactName: string) { // Navigate to contacts page await page.goto('./contacts'); // Wait for page to load completely await page.waitForLoadState('networkidle'); // Check if we need to hide the "Show Actions" view first const loadingCount = await page.locator('.loading-indicator').count(); if (loadingCount > 0) { await page.locator('.loading-indicator').first().waitFor({ state: 'hidden' }); } // Check if "Hide Actions" button exists (meaning we're in the give numbers view) const showGiveNumbersExists = await page.getByRole('button', { name: 'Hide Actions' }).count(); if (showGiveNumbersExists > 0) { await page.getByRole('button', { name: 'Hide Actions' }).click(); } // Look for the contact by name const contactItems = page.locator('li[data-testid="contactListItem"]'); const contactCount = await contactItems.count(); // Debug: Print all contact names if no match found if (contactCount === 0) { await page.screenshot({ path: 'debug-no-contacts.png' }); throw new Error(`No contacts found on page. Screenshot saved as debug-no-contacts.png`); } // Check if our contact exists const contactExists = await contactItems.filter({ hasText: contactName }).count(); if (contactExists === 0) { // Try alternative selectors const selectors = [ 'li', 'div[data-testid="contactListItem"]', '.contact-item', '[data-testid*="contact"]' ]; for (const selector of selectors) { const testCount = await page.locator(selector).filter({ hasText: contactName }).count(); if (testCount > 0) { // Found working selector, use it const contactItem = page.locator(selector).filter({ hasText: contactName }).first(); // Look for info icon or delete button const infoIconExists = await contactItem.locator('svg.fa-info-circle').count(); if (infoIconExists > 0) { await contactItem.locator('svg.fa-info-circle').click(); await page.waitForLoadState('networkidle'); // Should now be on the contact detail page await expect(page.getByText('Contact Details')).toBeVisible(); // Look for delete button const deleteButtonExists = await page.getByRole('button', { name: 'Delete Contact' }).count(); if (deleteButtonExists > 0) { await page.getByRole('button', { name: 'Delete Contact' }).click(); // Handle confirmation dialog await expect(page.getByRole('button', { name: 'Yes, Delete' })).toBeVisible(); await page.getByRole('button', { name: 'Yes, Delete' }).click(); // Wait for dialog to close await expect(page.getByRole('button', { name: 'Yes, Delete' })).toBeHidden(); return; } } } } throw new Error(`Contact "${contactName}" not found on contacts page`); } // Use the standard flow const contactItem = contactItems.filter({ hasText: contactName }).first(); // Look for info icon const infoIconExists = await contactItem.locator('svg.fa-info-circle').count(); if (infoIconExists > 0) { await contactItem.locator('svg.fa-info-circle').click(); await page.waitForLoadState('networkidle'); // Should now be on the contact detail page await expect(page.getByText('Contact Details')).toBeVisible(); // Look for delete button const deleteButtonExists = await page.getByRole('button', { name: 'Delete Contact' }).count(); if (deleteButtonExists > 0) { await page.getByRole('button', { name: 'Delete Contact' }).click(); // Handle confirmation dialog await expect(page.getByRole('button', { name: 'Yes, Delete' })).toBeVisible(); await page.getByRole('button', { name: 'Yes, Delete' }).click(); // Wait for dialog to close await expect(page.getByRole('button', { name: 'Yes, Delete' })).toBeHidden(); } } } 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 { const newDid = await generateNewEthrUser(page); await importUser(page, '000'); // switch to user 000 await page.goto('./contacts'); const contactName = createContactName(newDid); const contactInput = `${newDid}, ${contactName}`; await page.getByPlaceholder('URL or DID, Name, Public Key').fill(contactInput); await page.locator('button > svg.fa-plus').click(); // Wait for the contact to be added first await expect(page.locator('li', { hasText: contactName })).toBeVisible(); // Wait longer for the registration alert to appear (it has a 1-second timeout) await page.waitForTimeout(2000); // Check if the registration alert is present const alertCount = await page.locator('div[role="alert"]').count(); if (alertCount > 0) { // Check if this is a registration alert (contains "Yes" button) const yesButtonCount = await page.locator('div[role="alert"] button:has-text("Yes")').count(); if (yesButtonCount > 0) { // register them await page.locator('div[role="alert"] button:has-text("Yes")').click(); // 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(); } } 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'); }