diff --git a/test-playwright/45-contact-import.spec.ts b/test-playwright/45-contact-import.spec.ts index 42eb6f43..351c3044 100644 --- a/test-playwright/45-contact-import.spec.ts +++ b/test-playwright/45-contact-import.spec.ts @@ -346,6 +346,35 @@ function generateUniqueTestContacts(): Record { }; } +/** + * Helper function to safely close alerts that might be blocked by dialogs + * + * This function attempts to close an alert, but handles cases where + * the alert close button might be blocked by modal dialogs. + * + * @param page - Playwright page object + * @param alertSelector - Selector for the alert to close + */ +async function safeCloseAlert(page: any, alertSelector: string = 'div[role="alert"] button > svg.fa-xmark') { + try { + await page.locator(alertSelector).first().click(); + } catch (error) { + // If click fails due to blocking dialog, try to close the dialog first + try { + const dialog = page.locator('div[role="dialog"]'); + if (await dialog.isVisible({ timeout: 1000 })) { + await dialog.locator('button:has-text("No"), button:has-text("Cancel"), button > svg.fa-xmark').first().click(); + await dialog.waitFor({ state: 'hidden', timeout: 3000 }); + // Now try to close the alert again + await page.locator(alertSelector).first().click(); + } + } catch (dialogError) { + // If dialog handling fails, just continue without closing the alert + console.log('Alert close failed due to dialog blocking, continuing anyway'); + } + } +} + /** * Invalid test data for error scenario testing * @@ -1738,4 +1767,578 @@ test.describe('Contact Import Functionality', () => { perfMonitor.end('Contact addition with registration - user cancels prompt'); }); + + /** + * Test contact editing functionality - basic information + * + * This test validates the contact editing workflow for basic contact information. + * The test ensures that: + * - Contact edit view loads correctly + * - Name and notes fields can be edited + * - Changes are saved successfully + * - User is redirected to contact detail view + */ + test('Contact editing - basic information', async ({ page, browserName }) => { + perfMonitor.start('Contact editing - basic information'); + + const testContacts = generateUniqueTestContacts(); + + // First, add a contact to edit + await perfMonitor.measureAsync('navigate to contacts', () => page.goto('./contacts')); + await perfMonitor.measureAsync('add test contact', async () => { + await page.getByPlaceholder('URL or DID, Name, Public Key').fill(`${testContacts.alice.did}, ${testContacts.alice.name}`); + await page.locator('button > svg.fa-plus').click(); + + // Handle registration prompt if it appears + try { + await expect(page.locator('div[role="dialog"] h3:has-text("Register")')).toBeVisible({ timeout: 3000 }); + // Registration prompt appeared - choose not to register + await page.locator('div[role="dialog"] button:has-text("No")').click(); + await page.locator('div[role="dialog"]').waitFor({ state: 'hidden', timeout: 5000 }); + } catch (error) { + // Registration prompt didn't appear - this is expected for unregistered users + perfMonitor.checkpoint('registration prompt did not appear'); + } + + await expect(page.locator('div[role="alert"] span:has-text("Success")')).toBeVisible(); + await page.locator('div[role="alert"] button > svg.fa-xmark').first().click(); + }); + + // Navigate directly to the contact edit page using the contact DID + await perfMonitor.measureAsync('navigate to contact edit page', () => + page.goto(`./contact-edit/${encodeURIComponent(testContacts.alice.did)}`) + ); + + // Verify we're on the edit page + await perfMonitor.measureAsync('verify edit page loaded', () => + expect(page.locator('section[id="ContactEdit"]')).toBeVisible() + ); + + // Edit contact name + const newName = `${testContacts.alice.name} (Edited)`; + await perfMonitor.measureAsync('edit contact name', () => + page.locator('input[data-testId="contactName"]').fill(newName) + ); + + // Edit contact notes + const newNotes = 'Test notes for contact editing'; + await perfMonitor.measureAsync('edit contact notes', () => + page.locator('textarea[id="contactNotes"]').fill(newNotes) + ); + + // Save changes + await perfMonitor.measureAsync('save changes', () => + page.locator('button:has-text("Save")').click() + ); + + // Verify success message + await perfMonitor.measureAsync('verify success message', () => + expect(page.locator('div[role="alert"] span:has-text("Success")')).toBeVisible() + ); + + // Verify we're back on the contact detail page + await perfMonitor.measureAsync('verify returned to detail page', () => + expect(page.locator(`h2:has-text("${newName}")`)).toBeVisible() + ); + + perfMonitor.end('Contact editing - basic information'); + }); + + /** + * Test contact editing - adding contact methods + * + * This test validates the contact methods functionality in the edit view. + * The test ensures that: + * - New contact methods can be added + * - Method types can be selected from dropdown + * - Method labels and values can be edited + * - Changes are saved successfully + */ + test('Contact editing - adding contact methods', async ({ page, browserName }) => { + perfMonitor.start('Contact editing - adding contact methods'); + + const testContacts = generateUniqueTestContacts(); + + // First, add a contact to edit + await perfMonitor.measureAsync('navigate to contacts', () => page.goto('./contacts')); + await perfMonitor.measureAsync('add test contact', async () => { + await page.getByPlaceholder('URL or DID, Name, Public Key').fill(`${testContacts.bob.did}, ${testContacts.bob.name}`); + await page.locator('button > svg.fa-plus').click(); + + // Handle registration prompt if it appears + try { + await expect(page.locator('div[role="dialog"] h3:has-text("Register")')).toBeVisible({ timeout: 3000 }); + // Registration prompt appeared - choose not to register + await page.locator('div[role="dialog"] button:has-text("No")').click(); + await page.locator('div[role="dialog"]').waitFor({ state: 'hidden', timeout: 5000 }); + } catch (error) { + // Registration prompt didn't appear - this is expected for unregistered users + perfMonitor.checkpoint('registration prompt did not appear'); + } + + await expect(page.locator('div[role="alert"] span:has-text("Success")')).toBeVisible(); + await page.locator('div[role="alert"] button > svg.fa-xmark').first().click(); + }); + + // Navigate directly to the contact edit page + await perfMonitor.measureAsync('navigate to contact edit page', () => + page.goto(`./contact-edit/${encodeURIComponent(testContacts.bob.did)}`) + ); + + // Add a new contact method + await perfMonitor.measureAsync('add contact method', () => + page.locator('button:has-text("+")').click() + ); + + // Fill in the contact method details + await perfMonitor.measureAsync('fill contact method details', async () => { + // Fill label + await page.locator('input[placeholder="Label"]').first().fill('Mobile'); + + // Click dropdown and select CELL type + await page.locator('button:has-text("▼")').first().click(); + await page.locator('div:has-text("CELL")').click(); + + // Fill value + await page.locator('input[placeholder="Number, email, etc."]').first().fill('+1-555-123-4567'); + }); + + // Add another contact method + await perfMonitor.measureAsync('add second contact method', () => + page.locator('button:has-text("+")').click() + ); + + // Fill in the second contact method + await perfMonitor.measureAsync('fill second contact method', async () => { + // Fill label + await page.locator('input[placeholder="Label"]').nth(1).fill('Email'); + + // Click dropdown and select EMAIL type + await page.locator('button:has-text("▼")').nth(1).click(); + await page.locator('div:has-text("EMAIL")').click(); + + // Fill value + await page.locator('input[placeholder="Number, email, etc."]').nth(1).fill('bob@example.com'); + }); + + // Save changes + await perfMonitor.measureAsync('save changes', () => + page.locator('button:has-text("Save")').click() + ); + + // Verify success message + await perfMonitor.measureAsync('verify success message', () => + expect(page.locator('div[role="alert"] span:has-text("Success")')).toBeVisible() + ); + + perfMonitor.end('Contact editing - adding contact methods'); + }); + + /** + * Test contact editing - removing contact methods + * + * This test validates the contact method removal functionality. + * The test ensures that: + * - Contact methods can be removed + * - Removed methods are not saved + * - UI updates correctly after removal + */ + test('Contact editing - removing contact methods', async ({ page, browserName }) => { + perfMonitor.start('Contact editing - removing contact methods'); + + const testContacts = generateUniqueTestContacts(); + + // First, add a contact to edit + await perfMonitor.measureAsync('navigate to contacts', () => page.goto('./contacts')); + await perfMonitor.measureAsync('add test contact', async () => { + await page.getByPlaceholder('URL or DID, Name, Public Key').fill(`${testContacts.charlie.did}, ${testContacts.charlie.name}`); + await page.locator('button > svg.fa-plus').click(); + + // Handle registration prompt if it appears + try { + await expect(page.locator('div[role="dialog"] h3:has-text("Register")')).toBeVisible({ timeout: 3000 }); + // Registration prompt appeared - choose not to register + await page.locator('div[role="dialog"] button:has-text("No")').click(); + await page.locator('div[role="dialog"]').waitFor({ state: 'hidden', timeout: 5000 }); + } catch (error) { + // Registration prompt didn't appear - this is expected for unregistered users + perfMonitor.checkpoint('registration prompt did not appear'); + } + + await expect(page.locator('div[role="alert"] span:has-text("Success")')).toBeVisible(); + await page.locator('div[role="alert"] button > svg.fa-xmark').first().click(); + }); + + // Navigate directly to the contact edit page + await perfMonitor.measureAsync('navigate to contact edit page', () => + page.goto(`./contact-edit/${encodeURIComponent(testContacts.charlie.did)}`) + ); + + // Add a contact method first + await perfMonitor.measureAsync('add contact method', () => + page.locator('button:has-text("+")').click() + ); + + // Fill in the contact method + await perfMonitor.measureAsync('fill contact method', async () => { + await page.locator('input[placeholder="Label"]').first().fill('Test Method'); + await page.locator('input[placeholder="Type"]').first().fill('WHATSAPP'); + await page.locator('input[placeholder="Number, email, etc."]').first().fill('test-value'); + }); + + // Remove the contact method + await perfMonitor.measureAsync('remove contact method', () => + page.locator('font-awesome[icon="trash-can"]').first().click() + ); + + // Verify the method was removed from UI + await perfMonitor.measureAsync('verify method removed', () => + expect(page.locator('input[placeholder="Label"]')).toHaveCount(0) + ); + + // Save changes + await perfMonitor.measureAsync('save changes', () => + page.locator('button:has-text("Save")').click() + ); + + // Verify success message + await perfMonitor.measureAsync('verify success message', () => + expect(page.locator('div[role="alert"] span:has-text("Success")')).toBeVisible() + ); + + perfMonitor.end('Contact editing - removing contact methods'); + }); + + /** + * Test contact editing - canceling changes + * + * This test validates the cancel functionality in the contact edit view. + * The test ensures that: + * - Changes can be canceled + * - User returns to previous view + * - No changes are saved when canceled + */ + test('Contact editing - canceling changes', async ({ page, browserName }) => { + perfMonitor.start('Contact editing - canceling changes'); + + const testContacts = generateUniqueTestContacts(); + + // First, add a contact to edit + await perfMonitor.measureAsync('navigate to contacts', () => page.goto('./contacts')); + await perfMonitor.measureAsync('add test contact', async () => { + await page.getByPlaceholder('URL or DID, Name, Public Key').fill(`${testContacts.david.did}, ${testContacts.david.name}`); + await page.locator('button > svg.fa-plus').click(); + + // Handle registration prompt if it appears + try { + await expect(page.locator('div[role="dialog"] h3:has-text("Register")')).toBeVisible({ timeout: 3000 }); + // Registration prompt appeared - choose not to register + await page.locator('div[role="dialog"] button:has-text("No")').click(); + await page.locator('div[role="dialog"]').waitFor({ state: 'hidden', timeout: 5000 }); + } catch (error) { + // Registration prompt didn't appear - this is expected for unregistered users + perfMonitor.checkpoint('registration prompt did not appear'); + } + + await expect(page.locator('div[role="alert"] span:has-text("Success")')).toBeVisible(); + await page.locator('div[role="alert"] button > svg.fa-xmark').first().click(); + }); + + // Navigate directly to the contact edit page + await perfMonitor.measureAsync('navigate to contact edit page', () => + page.goto(`./contact-edit/${encodeURIComponent(testContacts.david.did)}`) + ); + + // Make some changes + await perfMonitor.measureAsync('make changes', async () => { + await page.locator('input[data-testId="contactName"]').fill('This should not be saved'); + await page.locator('textarea[id="contactNotes"]').fill('These notes should not be saved'); + }); + + // Cancel changes + await perfMonitor.measureAsync('cancel changes', () => + page.locator('button:has-text("Cancel")').click() + ); + + // Verify we're back on the contact detail page + await perfMonitor.measureAsync('verify returned to detail page', () => + expect(page.locator(`h2:has-text("${testContacts.david.name}")`)).toBeVisible() + ); + + // Verify the original name is still there (changes weren't saved) + await perfMonitor.measureAsync('verify changes not saved', () => + expect(page.locator(`h2:has-text("This should not be saved")`)).not.toBeVisible() + ); + + perfMonitor.end('Contact editing - canceling changes'); + }); + + /** + * Test contact editing - method type dropdown functionality + * + * This test validates the dropdown functionality for contact method types. + * The test ensures that: + * - Dropdown opens and closes correctly + * - All method types are available (CELL, EMAIL, WHATSAPP) + * - Type selection works properly + * - Only one dropdown can be open at a time + */ + test('Contact editing - method type dropdown functionality', async ({ page, browserName }) => { + perfMonitor.start('Contact editing - method type dropdown functionality'); + + const testContacts = generateUniqueTestContacts(); + + // First, add a contact to edit + await perfMonitor.measureAsync('navigate to contacts', () => page.goto('./contacts')); + await perfMonitor.measureAsync('add test contact', async () => { + await page.getByPlaceholder('URL or DID, Name, Public Key').fill(`${testContacts.eve.did}, ${testContacts.eve.name}`); + await page.locator('button > svg.fa-plus').click(); + + // Handle registration prompt if it appears + try { + await expect(page.locator('div[role="dialog"] h3:has-text("Register")')).toBeVisible({ timeout: 3000 }); + // Registration prompt appeared - choose not to register + await page.locator('div[role="dialog"] button:has-text("No")').click(); + await page.locator('div[role="dialog"]').waitFor({ state: 'hidden', timeout: 5000 }); + } catch (error) { + // Registration prompt didn't appear - this is expected for unregistered users + perfMonitor.checkpoint('registration prompt did not appear'); + } + + await expect(page.locator('div[role="alert"] span:has-text("Success")')).toBeVisible(); + await page.locator('div[role="alert"] button > svg.fa-xmark').first().click(); + }); + + // Navigate directly to the contact edit page + await perfMonitor.measureAsync('navigate to contact edit page', () => + page.goto(`./contact-edit/${encodeURIComponent(testContacts.eve.did)}`) + ); + + // Add a contact method + await perfMonitor.measureAsync('add contact method', () => + page.locator('button:has-text("+")').click() + ); + + // Test dropdown functionality + await perfMonitor.measureAsync('test dropdown functionality', async () => { + // Click dropdown to open it + await page.locator('button:has-text("▼")').first().click(); + + // Verify dropdown is open + await expect(page.locator('div:has-text("CELL")')).toBeVisible(); + await expect(page.locator('div:has-text("EMAIL")')).toBeVisible(); + await expect(page.locator('div:has-text("WHATSAPP")')).toBeVisible(); + + // Select EMAIL type + await page.locator('div:has-text("EMAIL")').click(); + + // Verify dropdown is closed + await expect(page.locator('div:has-text("CELL")')).not.toBeVisible(); + + // Verify the type field shows EMAIL + await expect(page.locator('input[placeholder="Type"]').first()).toHaveValue('EMAIL'); + }); + + // Test that only one dropdown can be open at a time + await perfMonitor.measureAsync('test single dropdown open', async () => { + // Add another contact method + await page.locator('button:has-text("+")').click(); + + // Open first dropdown + await page.locator('button:has-text("▼")').first().click(); + await expect(page.locator('div:has-text("CELL")')).toBeVisible(); + + // Open second dropdown (should close first) + await page.locator('button:has-text("▼")').nth(1).click(); + await expect(page.locator('div:has-text("CELL")')).not.toBeVisible(); + }); + + perfMonitor.end('Contact editing - method type dropdown functionality'); + }); + + /** + * Test contact editing - error handling for invalid contact + * + * This test validates error handling when trying to edit a non-existent contact. + * The test ensures that: + * - Appropriate error message is displayed + * - User is redirected to contacts list + * - System remains stable + */ + test('Contact editing - error handling for invalid contact', async ({ page, browserName }) => { + perfMonitor.start('Contact editing - error handling for invalid contact'); + + // Try to navigate to edit page for non-existent contact + await perfMonitor.measureAsync('navigate to invalid contact edit', () => + page.goto('./contact-edit/did:ethr:0xInvalidContactDID') + ); + + // Verify error handling + await perfMonitor.measureAsync('verify error handling', async () => { + try { + // Should redirect to contacts page + await expect(page.locator('h1:has-text("Contacts")')).toBeVisible({ timeout: 5000 }); + } catch (error) { + // Alternative: check for error message + await expect(page.locator('div[role="alert"]')).toBeVisible({ timeout: 5000 }); + } + }); + + perfMonitor.end('Contact editing - error handling for invalid contact'); + }); + + /** + * Test contact editing - navigation from different entry points + * + * This test validates that contact editing can be accessed from different + * entry points in the application. The test ensures that: + * - Edit button works from contact detail view + * - Back navigation works correctly + * - Edit view loads properly from different contexts + */ + test('Contact editing - navigation from different entry points', async ({ page, browserName }) => { + perfMonitor.start('Contact editing - navigation from different entry points'); + + const testContacts = generateUniqueTestContacts(); + + // First, add a contact to edit + await perfMonitor.measureAsync('navigate to contacts', () => page.goto('./contacts')); + await perfMonitor.measureAsync('add test contact', async () => { + await page.getByPlaceholder('URL or DID, Name, Public Key').fill(`${testContacts.alice.did}, ${testContacts.alice.name}`); + await page.locator('button > svg.fa-plus').click(); + + // Handle registration prompt if it appears + try { + await expect(page.locator('div[role="dialog"] h3:has-text("Register")')).toBeVisible({ timeout: 3000 }); + // Registration prompt appeared - choose not to register + await page.locator('div[role="dialog"] button:has-text("No")').click(); + await page.locator('div[role="dialog"]').waitFor({ state: 'hidden', timeout: 5000 }); + } catch (error) { + // Registration prompt didn't appear - this is expected for unregistered users + perfMonitor.checkpoint('registration prompt did not appear'); + } + + await expect(page.locator('div[role="alert"] span:has-text("Success")')).toBeVisible(); + await page.locator('div[role="alert"] button > svg.fa-xmark').first().click(); + }); + + // Test navigation from contact detail view + await perfMonitor.measureAsync('navigate from contact detail', async () => { + await page.locator(`li[data-testid="contactListItem"] h2:has-text("${testContacts.alice.name}")`).first().click(); + + // Wait for the contact detail page to load + await expect(page.locator('h1:has-text("Identifier Details")')).toBeVisible(); + + await page.locator('router-link font-awesome[icon="pen"]').click(); + await expect(page.locator('section[id="ContactEdit"]')).toBeVisible(); + }); + + // Test back navigation + await perfMonitor.measureAsync('test back navigation', async () => { + await page.locator('button:has-text("Back")').click(); + await expect(page.locator(`h2:has-text("${testContacts.alice.name}")`)).toBeVisible(); + }); + + // Test direct URL navigation + await perfMonitor.measureAsync('test direct URL navigation', async () => { + const contactDid = encodeURIComponent(testContacts.alice.did); + await page.goto(`./contact-edit/${contactDid}`); + await expect(page.locator('section[id="ContactEdit"]')).toBeVisible(); + }); + + perfMonitor.end('Contact editing - navigation from different entry points'); + }); + + /** + * Test contact editing - complex contact methods scenario + * + * This test validates a complex scenario with multiple contact methods + * of different types. The test ensures that: + * - Multiple contact methods can be managed + * - Different types work correctly + * - Complex scenarios are handled properly + */ + test('Contact editing - complex contact methods scenario', async ({ page, browserName }) => { + perfMonitor.start('Contact editing - complex contact methods scenario'); + + const testContacts = generateUniqueTestContacts(); + + // First, add a contact to edit + await perfMonitor.measureAsync('navigate to contacts', () => page.goto('./contacts')); + await perfMonitor.measureAsync('add test contact', async () => { + await page.getByPlaceholder('URL or DID, Name, Public Key').fill(`${testContacts.bob.did}, ${testContacts.bob.name}`); + await page.locator('button > svg.fa-plus').click(); + + // Handle registration prompt if it appears + try { + await expect(page.locator('div[role="dialog"] h3:has-text("Register")')).toBeVisible({ timeout: 3000 }); + // Registration prompt appeared - choose not to register + await page.locator('div[role="dialog"] button:has-text("No")').click(); + await page.locator('div[role="dialog"]').waitFor({ state: 'hidden', timeout: 5000 }); + } catch (error) { + // Registration prompt didn't appear - this is expected for unregistered users + perfMonitor.checkpoint('registration prompt did not appear'); + } + + await expect(page.locator('div[role="alert"] span:has-text("Success")')).toBeVisible(); + await page.locator('div[role="alert"] button > svg.fa-xmark').first().click(); + }); + + // Navigate directly to the contact edit page + await perfMonitor.measureAsync('navigate to contact edit page', () => + page.goto(`./contact-edit/${encodeURIComponent(testContacts.bob.did)}`) + ); + + // Add multiple contact methods of different types + await perfMonitor.measureAsync('add multiple contact methods', async () => { + // Add CELL method + await page.locator('button:has-text("+")').click(); + await page.locator('input[placeholder="Label"]').first().fill('Mobile'); + await page.locator('button:has-text("▼")').first().click(); + await page.locator('div:has-text("CELL")').click(); + await page.locator('input[placeholder="Number, email, etc."]').first().fill('+1-555-123-4567'); + + // Add EMAIL method + await page.locator('button:has-text("+")').click(); + await page.locator('input[placeholder="Label"]').nth(1).fill('Work Email'); + await page.locator('button:has-text("▼")').nth(1).click(); + await page.locator('div:has-text("EMAIL")').click(); + await page.locator('input[placeholder="Number, email, etc."]').nth(1).fill('bob.work@example.com'); + + // Add WHATSAPP method + await page.locator('button:has-text("+")').click(); + await page.locator('input[placeholder="Label"]').nth(2).fill('WhatsApp'); + await page.locator('button:has-text("▼")').nth(2).click(); + await page.locator('div:has-text("WHATSAPP")').click(); + await page.locator('input[placeholder="Number, email, etc."]').nth(2).fill('+1-555-987-6543'); + }); + + // Remove one method + await perfMonitor.measureAsync('remove one method', () => + page.locator('font-awesome[icon="trash-can"]').nth(1).click() + ); + + // Edit remaining methods + await perfMonitor.measureAsync('edit remaining methods', async () => { + // Edit the first method + await page.locator('input[placeholder="Label"]').first().fill('Updated Mobile'); + await page.locator('input[placeholder="Number, email, etc."]').first().fill('+1-555-999-8888'); + + // Edit the second method + await page.locator('input[placeholder="Label"]').nth(1).fill('Updated WhatsApp'); + await page.locator('input[placeholder="Number, email, etc."]').nth(1).fill('+1-555-777-6666'); + }); + + // Save changes + await perfMonitor.measureAsync('save changes', () => + page.locator('button:has-text("Save")').click() + ); + + // Verify success message + await perfMonitor.measureAsync('verify success message', () => + expect(page.locator('div[role="alert"] span:has-text("Success")')).toBeVisible() + ); + + perfMonitor.end('Contact editing - complex contact methods scenario'); + }); }); \ No newline at end of file