From dd3de062524b722e45e71711decff7d5a0ee94f9 Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Tue, 5 Aug 2025 00:50:38 +0000 Subject: [PATCH] Add comprehensive contact editing test suite with helper function Adds 8 new test cases covering contact editing functionality including basic information editing, contact methods management, dropdown functionality, error handling, and navigation scenarios. Includes safeCloseAlert helper function to handle alert dismissal when blocked by dialogs. Tests validate save/cancel operations, method type selection, and complex multi-method scenarios. --- test-playwright/45-contact-import.spec.ts | 603 ++++++++++++++++++++++ 1 file changed, 603 insertions(+) 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