Browse Source

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.
pull/159/head
Matthew Raymer 2 weeks ago
parent
commit
dd3de06252
  1. 603
      test-playwright/45-contact-import.spec.ts

603
test-playwright/45-contact-import.spec.ts

@ -346,6 +346,35 @@ function generateUniqueTestContacts(): Record<string, TestContact> {
}; };
} }
/**
* 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 * 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'); 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');
});
}); });
Loading…
Cancel
Save