You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1020 lines
39 KiB
1020 lines
39 KiB
/**
|
|
* Contact Import End-to-End Tests
|
|
*
|
|
* This comprehensive test suite validates Time Safari's contact import functionality
|
|
* across all supported import methods and edge cases. The tests ensure that users
|
|
* can reliably import contacts through various mechanisms while maintaining data
|
|
* integrity and providing appropriate feedback.
|
|
*
|
|
* ## How the Contact Import Page Works
|
|
*
|
|
* ### User Workflow
|
|
* 1. **Entry Point**: Users can reach contact import through multiple paths:
|
|
* - Direct navigation to `/contact-import` with URL parameters
|
|
* - Pasting contact data in the contacts page input field
|
|
* - Manual entry via textarea on the import page
|
|
*
|
|
* 2. **Data Processing**: The system parses contact data in multiple formats:
|
|
* - JSON arrays: `[{"did":"did:ethr:0x...","name":"Alice"}]`
|
|
* - Individual contact strings: `"did:ethr:0x..., Alice, publicKey"`
|
|
* - JWT tokens (future implementation)
|
|
*
|
|
* 3. **Contact Analysis**: For each contact, the system:
|
|
* - Validates the DID format and required fields
|
|
* - Checks if the contact already exists in the database
|
|
* - Compares field values for existing contacts to detect differences
|
|
* - Categorizes contacts as "New" or "Existing"
|
|
*
|
|
* 4. **UI Presentation**: The ContactImportView displays:
|
|
* - A list of all contacts with checkboxes for selection
|
|
* - Status indicators ("New" in green, "Existing" in orange)
|
|
* - Field comparison tables for existing contacts with differences
|
|
* - Visibility settings checkbox for activity sharing
|
|
* - Import button to execute the selected contacts
|
|
*
|
|
* 5. **Import Process**: When users click "Import Selected Contacts":
|
|
* - Selected contacts are processed in batch
|
|
* - New contacts are added to the database
|
|
* - Existing contacts are updated with new field values
|
|
* - Success/error feedback is displayed
|
|
* - Users are redirected back to the contacts list
|
|
*
|
|
* ### Page Structure and Components
|
|
*
|
|
* **ContactImportView.vue**:
|
|
* - **Header**: Back navigation and "Contact Import" title
|
|
* - **Loading State**: Spinner while processing contact data
|
|
* - **Visibility Settings**: Checkbox for making activity visible to imported contacts
|
|
* - **Contact List**: Each contact displayed with:
|
|
* - Checkbox for selection
|
|
* - Contact name and DID
|
|
* - Status indicator (New/Existing)
|
|
* - Field comparison table for existing contacts
|
|
* - **Import Button**: Executes the import process
|
|
* - **Empty State**: Message when no contacts are found
|
|
*
|
|
* **ContactInputForm Component** (in ContactsView):
|
|
* - **Input Field**: Accepts contact data in various formats
|
|
* - **Add Button**: Triggers contact processing
|
|
* - **QR Scanner**: Alternative input method
|
|
* - **Validation**: Real-time validation of input format
|
|
*
|
|
* ### Data Flow
|
|
*
|
|
* 1. **Input Processing**:
|
|
* ```typescript
|
|
* // Contact data can be provided as:
|
|
* const contactData = 'Paste this: [{"did":"did:ethr:0x...","name":"Alice"}]';
|
|
* // or individual format:
|
|
* const contactData = 'did:ethr:0x..., Alice, publicKey';
|
|
* ```
|
|
*
|
|
* 2. **Contact Validation**:
|
|
* - DID format validation (must be valid DID)
|
|
* - Required field checking (name is optional but recommended)
|
|
* - Duplicate detection against existing contacts
|
|
*
|
|
* 3. **Field Comparison**:
|
|
* - For existing contacts, compare all fields
|
|
* - Display differences in a table format
|
|
* - Allow users to see what will be updated
|
|
*
|
|
* 4. **Batch Import**:
|
|
* - Process all selected contacts in a single transaction
|
|
* - Handle both new additions and updates
|
|
* - Maintain data integrity throughout the process
|
|
*
|
|
* ### Error Handling
|
|
*
|
|
* The system handles various error scenarios:
|
|
* - **Invalid Data**: Malformed JSON, missing fields, wrong types
|
|
* - **Network Issues**: Failed data retrieval from URLs
|
|
* - **Database Errors**: Storage quota exceeded, connection issues
|
|
* - **UI Errors**: Modal conflicts, alert dismissal failures
|
|
*
|
|
* ### Performance Considerations
|
|
*
|
|
* - **Large Contact Lists**: Efficient processing of 10+ contacts
|
|
* - **Field Comparison**: Optimized comparison algorithms
|
|
* - **Batch Operations**: Database transactions for multiple contacts
|
|
* - **Memory Management**: Proper cleanup of temporary data
|
|
*
|
|
* ## Test Coverage Overview
|
|
*
|
|
* ### Import Methods Tested
|
|
* 1. **Direct Contact Addition**: Single contact via contacts page input
|
|
* 2. **Batch Import via Text**: Multiple contacts pasted into input field
|
|
* 3. **URL Query Parameters**: Contact data passed via URL parameters
|
|
* 4. **Manual Data Input**: Contact data entered via textarea
|
|
* 5. **Selective Import**: Checkbox-based contact selection
|
|
*
|
|
* ### Error Scenarios Validated
|
|
* - Invalid JWT format detection and handling
|
|
* - Malformed contact data validation
|
|
* - Empty contact array handling
|
|
* - Missing required fields detection
|
|
* - Wrong data type handling
|
|
* - Network error simulation
|
|
*
|
|
* ### Performance and Reliability
|
|
* - Large contact list import performance
|
|
* - Alert dismissal reliability
|
|
* - Modal dialog handling
|
|
* - Duplicate contact detection
|
|
* - State management and cleanup
|
|
*
|
|
* ## Test Data Strategy
|
|
*
|
|
* Each test uses unique contact data generated with timestamps and random
|
|
* suffixes to prevent conflicts with existing database contacts. This ensures
|
|
* test isolation and reliable results across multiple test runs.
|
|
*
|
|
* ## Key Implementation Details
|
|
*
|
|
* ### Performance Monitoring
|
|
* The test suite includes a PerformanceMonitor class that tracks execution
|
|
* times for critical operations, helping identify performance bottlenecks
|
|
* and ensuring consistent test behavior across different environments.
|
|
*
|
|
* ### Error Handling Patterns
|
|
* Tests implement robust error handling for UI interactions, including:
|
|
* - Modal dialog detection and dismissal
|
|
* - Alert message verification
|
|
* - Network error simulation
|
|
* - Graceful degradation for failed operations
|
|
*
|
|
* ### State Management
|
|
* Each test maintains clean state through:
|
|
* - Pre-test user import and contact cleanup
|
|
* - Post-test contact cleanup
|
|
* - Isolated test data generation
|
|
* - Proper resource cleanup
|
|
*
|
|
* ## Usage Examples
|
|
*
|
|
* ### Basic Contact Import
|
|
* ```typescript
|
|
* // Navigate to contacts page
|
|
* await page.goto('./contacts');
|
|
*
|
|
* // Add contact via input field
|
|
* await page.getByPlaceholder('URL or DID, Name, Public Key')
|
|
* .fill('did:ethr:0x123..., Alice Test');
|
|
*
|
|
* // Submit and verify
|
|
* await page.locator('button > svg.fa-plus').click();
|
|
* await expect(page.locator('div[role="alert"] span:has-text("Success")'))
|
|
* .toBeVisible();
|
|
* ```
|
|
*
|
|
* ### Batch Contact Import
|
|
* ```typescript
|
|
* // Prepare contact data
|
|
* const contactData = 'Paste this: [{"did":"did:ethr:0x123...","name":"Alice"}]';
|
|
*
|
|
* // Navigate and input data
|
|
* await page.goto('./contacts');
|
|
* await page.getByPlaceholder('URL or DID, Name, Public Key').fill(contactData);
|
|
* await page.locator('button > svg.fa-plus').click();
|
|
*
|
|
* // Verify import page and import contacts
|
|
* await expect(page.getByRole('heading', { name: 'Contact Import' })).toBeVisible();
|
|
* await page.locator('button:has-text("Import Selected Contacts")').click();
|
|
* ```
|
|
*
|
|
* ## Test Architecture
|
|
*
|
|
* ### PerformanceMonitor Class
|
|
* Provides detailed timing information for test operations, helping identify
|
|
* performance issues and ensuring consistent test behavior.
|
|
*
|
|
* ### Test Data Generation
|
|
* Creates unique contact data with timestamps and random suffixes to prevent
|
|
* conflicts and ensure test isolation.
|
|
*
|
|
* ### Error Simulation
|
|
* Tests various error conditions including invalid data formats, network
|
|
* failures, and malformed inputs to ensure robust error handling.
|
|
*
|
|
* @author Matthew Raymer
|
|
* @date 2025-08-04
|
|
*/
|
|
|
|
import { test, expect, Page } from '@playwright/test';
|
|
import {
|
|
importUser,
|
|
getOSSpecificTimeout,
|
|
createTestJwt,
|
|
cleanupTestContacts,
|
|
addTestContact,
|
|
verifyContactExists,
|
|
verifyContactCount
|
|
} from './testUtils';
|
|
|
|
/**
|
|
* Performance monitoring utility for tracking test execution times
|
|
*
|
|
* This class provides detailed timing information for test operations,
|
|
* helping identify performance bottlenecks and ensuring consistent
|
|
* test behavior across different environments and platforms.
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* const perfMonitor = new PerformanceMonitor(browserName);
|
|
* perfMonitor.start('test setup');
|
|
*
|
|
* await perfMonitor.measureAsync('database operation', async () => {
|
|
* // Perform database operation
|
|
* });
|
|
*
|
|
* perfMonitor.end('test setup');
|
|
* ```
|
|
*/
|
|
class PerformanceMonitor {
|
|
private startTime: number = 0;
|
|
private checkpoints: Map<string, number> = new Map();
|
|
private browserName: string = '';
|
|
|
|
constructor(browserName: string) {
|
|
this.browserName = browserName;
|
|
}
|
|
|
|
/**
|
|
* Start timing a test operation
|
|
* @param label - Descriptive label for the operation being timed
|
|
*/
|
|
start(label: string = 'test') {
|
|
this.startTime = Date.now();
|
|
this.checkpoints.clear();
|
|
}
|
|
|
|
/**
|
|
* Record a checkpoint during test execution
|
|
* @param name - Name of the checkpoint
|
|
*/
|
|
checkpoint(name: string) {
|
|
const elapsed = Date.now() - this.startTime;
|
|
this.checkpoints.set(name, elapsed);
|
|
}
|
|
|
|
/**
|
|
* End timing and log final results
|
|
* @param label - Descriptive label for the completed operation
|
|
* @returns Total execution time in milliseconds
|
|
*/
|
|
end(label: string = 'test'): number {
|
|
const totalTime = Date.now() - this.startTime;
|
|
return totalTime;
|
|
}
|
|
|
|
/**
|
|
* Measure execution time of an async operation
|
|
* @param name - Name of the operation being measured
|
|
* @param operation - Async function to measure
|
|
* @returns Result of the operation
|
|
*/
|
|
async measureAsync<T>(name: string, operation: () => Promise<T>): Promise<T> {
|
|
const start = Date.now();
|
|
try {
|
|
const result = await operation();
|
|
const elapsed = Date.now() - start;
|
|
return result;
|
|
} catch (error) {
|
|
const elapsed = Date.now() - start;
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Contact data structure for test scenarios
|
|
*/
|
|
interface TestContact {
|
|
did: string;
|
|
name: string;
|
|
publicKey: string;
|
|
}
|
|
|
|
/**
|
|
* Generate unique test contacts with random DIDs to prevent conflicts
|
|
*
|
|
* This function creates contact data with timestamps and random suffixes
|
|
* to ensure test isolation and prevent conflicts with existing contacts
|
|
* in the database. Each test run will have unique contact data.
|
|
*
|
|
* @returns Object containing unique test contacts (Alice, Bob, Charlie)
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* const testContacts = generateUniqueTestContacts();
|
|
* // testContacts.alice.did = "did:ethr:0x111d15564f824D56C7a07b913aA7aDd03382aA39abc123"
|
|
* // testContacts.bob.name = "Bob Test 1703123456789"
|
|
* ```
|
|
*/
|
|
function generateUniqueTestContacts(): Record<string, TestContact> {
|
|
const timestamp = Date.now();
|
|
const randomSuffix = Math.random().toString(36).substring(2, 8);
|
|
|
|
return {
|
|
alice: {
|
|
did: `did:ethr:0x111d15564f824D56C7a07b913aA7aDd03382aA39${randomSuffix}`,
|
|
name: `Alice Test ${timestamp}`,
|
|
publicKey: `alice-public-key-${randomSuffix}`
|
|
},
|
|
bob: {
|
|
did: `did:ethr:0x222BB77E6Ff3774d34c751f3c1260866357B677b${randomSuffix}`,
|
|
name: `Bob Test ${timestamp}`,
|
|
publicKey: `bob-public-key-${randomSuffix}`
|
|
},
|
|
charlie: {
|
|
did: `did:ethr:0x333CC88F7Gg488e45d862f4d237097f748C788c${randomSuffix}`,
|
|
name: `Charlie Test ${timestamp}`,
|
|
publicKey: `charlie-public-key-${randomSuffix}`
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Invalid test data for error scenario testing
|
|
*
|
|
* This object contains various types of invalid data used to test
|
|
* error handling scenarios in the contact import functionality.
|
|
*/
|
|
const INVALID_DATA = {
|
|
malformedJwt: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.invalid.payload',
|
|
emptyArray: '[]',
|
|
missingFields: '[{"name":"Incomplete Contact"}]',
|
|
wrongTypes: '[{"did":123,"name":456}]',
|
|
networkError: 'http://invalid-url-that-will-fail.com/contacts'
|
|
};
|
|
|
|
test.describe('Contact Import Functionality', () => {
|
|
let perfMonitor: PerformanceMonitor;
|
|
|
|
test.beforeEach(async ({ page, browserName }) => {
|
|
perfMonitor = new PerformanceMonitor(browserName);
|
|
perfMonitor.start('test setup');
|
|
|
|
// Import test user and clean up existing contacts
|
|
await perfMonitor.measureAsync('import user', () => importUser(page, '00'));
|
|
const testContacts = generateUniqueTestContacts();
|
|
await perfMonitor.measureAsync('cleanup contacts', () => cleanupTestContacts(page, Object.values(testContacts).map(c => c.name)));
|
|
|
|
perfMonitor.checkpoint('setup complete');
|
|
});
|
|
|
|
test.afterEach(async ({ page, browserName }) => {
|
|
perfMonitor.checkpoint('test complete');
|
|
|
|
// Clean up test contacts after each test
|
|
const testContacts = generateUniqueTestContacts();
|
|
await perfMonitor.measureAsync('final cleanup', () => cleanupTestContacts(page, Object.values(testContacts).map(c => c.name)));
|
|
|
|
perfMonitor.end('test teardown');
|
|
});
|
|
|
|
/**
|
|
* Test basic contact addition functionality
|
|
*
|
|
* This test validates the fundamental contact addition workflow:
|
|
* 1. Navigate to contacts page
|
|
* 2. Fill in contact information
|
|
* 3. Submit the form
|
|
* 4. Verify success feedback
|
|
* 5. Confirm contact appears in list
|
|
*
|
|
* This serves as a baseline test to ensure the core contact
|
|
* management functionality works correctly before testing more
|
|
* complex import scenarios.
|
|
*/
|
|
test('Basic contact addition works', async ({ page, browserName }) => {
|
|
perfMonitor.start('Basic contact addition works');
|
|
|
|
const testContacts = generateUniqueTestContacts();
|
|
|
|
// Navigate to contacts page
|
|
await perfMonitor.measureAsync('navigate to contacts', () => page.goto('./contacts'));
|
|
|
|
// Add a contact normally using the standard input field
|
|
await perfMonitor.measureAsync('fill contact input', () =>
|
|
page.getByPlaceholder('URL or DID, Name, Public Key').fill(`${testContacts.alice.did}, ${testContacts.alice.name}`)
|
|
);
|
|
|
|
await perfMonitor.measureAsync('click add button', () =>
|
|
page.locator('button > svg.fa-plus').click()
|
|
);
|
|
|
|
// Verify success feedback is displayed
|
|
await perfMonitor.measureAsync('wait for success alert', () =>
|
|
expect(page.locator('div[role="alert"] span:has-text("Success")')).toBeVisible()
|
|
);
|
|
|
|
await perfMonitor.measureAsync('dismiss alert', () =>
|
|
page.locator('div[role="alert"] button > svg.fa-xmark').first().click()
|
|
);
|
|
|
|
// Verify contact appears in the contacts list
|
|
await perfMonitor.measureAsync('verify contact in list', () =>
|
|
expect(page.locator(`li[data-testid="contactListItem"] h2:has-text("${testContacts.alice.name}")`).first()).toBeVisible()
|
|
);
|
|
|
|
perfMonitor.end('Basic contact addition works');
|
|
});
|
|
|
|
/**
|
|
* Test single contact import via contacts page input
|
|
*
|
|
* This test validates the contact import workflow when users
|
|
* paste contact data into the input field on the contacts page.
|
|
* The test ensures that:
|
|
* - Contact data is properly parsed
|
|
* - New contacts are detected correctly
|
|
* - Import process completes successfully
|
|
* - Contacts appear in the final list
|
|
*/
|
|
test('Import single contact via contacts page input', async ({ page }) => {
|
|
// Use standardized contact data format for consistent testing
|
|
const contactData = 'Paste this: [{ "did": "did:ethr:0x111d15564f824D56C7a07b913aA7aDd03382aA39", "name": "User #111" }, { "did": "did:ethr:0x222BB77E6Ff3774d34c751f3c1260866357B677b", "name": "User #222", "publicKeyBase64": "asdf1234"}] ';
|
|
|
|
// Navigate to contacts page and input contact data
|
|
await page.goto('./contacts');
|
|
await page.getByPlaceholder('URL or DID, Name, Public Key').fill(contactData);
|
|
await page.locator('button > svg.fa-plus').click();
|
|
|
|
// Verify that contacts are detected as new
|
|
await expect(page.locator('li', { hasText: 'New' }).first()).toBeVisible();
|
|
|
|
// Execute the import process
|
|
await page.locator('button', { hasText: 'Import' }).click();
|
|
|
|
// Verify success message is displayed
|
|
await expect(page.locator('div[role="alert"] span:has-text("Success")')).toBeVisible();
|
|
await page.locator('div[role="alert"] button > svg.fa-xmark').first().click();
|
|
|
|
// Verify contacts appear in the contacts list
|
|
await page.goto('./contacts');
|
|
await expect(page.getByTestId('contactListItem')).toHaveCount(2);
|
|
});
|
|
|
|
/**
|
|
* Test multiple contact import via contacts page input
|
|
*
|
|
* This test validates batch contact import functionality when
|
|
* users paste multiple contact records into the input field.
|
|
* The test ensures that:
|
|
* - Multiple contacts are properly parsed
|
|
* - All contacts are detected as new
|
|
* - Import process handles multiple records
|
|
* - All contacts appear in the final list
|
|
*/
|
|
test('Import multiple contacts via contacts page input', async ({ page }) => {
|
|
// Use standardized contact data format with multiple contacts
|
|
const contactsData = 'Paste this: [{ "did": "did:ethr:0x111d15564f824D56C7a07b913aA7aDd03382aA39", "name": "User #111" }, { "did": "did:ethr:0x222BB77E6Ff3774d34c751f3c1260866357B677b", "name": "User #222", "publicKeyBase64": "asdf1234"}] ';
|
|
|
|
// Navigate to contacts page and input contact data
|
|
await page.goto('./contacts');
|
|
await page.getByPlaceholder('URL or DID, Name, Public Key').fill(contactsData);
|
|
await page.locator('button > svg.fa-plus').click();
|
|
|
|
// Verify redirect to contact import page
|
|
await expect(page.getByRole('heading', { name: 'Contact Import' })).toBeVisible();
|
|
|
|
// Verify all contacts are detected as new
|
|
await expect(page.locator('li', { hasText: 'New' })).toHaveCount(2);
|
|
await expect(page.locator('li', { hasText: 'User #111' })).toBeVisible();
|
|
await expect(page.locator('li', { hasText: 'User #222' })).toBeVisible();
|
|
|
|
// Execute import for all contacts
|
|
await page.locator('button', { hasText: 'Import Selected Contacts' }).click();
|
|
|
|
// Verify success feedback
|
|
await expect(page.locator('div[role="alert"] span:has-text("Success")').first()).toBeVisible();
|
|
await page.locator('div[role="alert"] button > svg.fa-xmark').first().click();
|
|
|
|
// Verify all contacts appear in the final list
|
|
await page.goto('./contacts');
|
|
await expect(page.getByTestId('contactListItem').first()).toBeVisible();
|
|
});
|
|
|
|
/**
|
|
* Test manual contact data input via textarea
|
|
*
|
|
* This test validates the manual contact data input workflow
|
|
* where users directly enter contact information in a textarea.
|
|
* The test ensures that:
|
|
* - Contact data is properly parsed from textarea
|
|
* - Import page loads correctly
|
|
* - New contacts are detected
|
|
* - Import process completes successfully
|
|
*/
|
|
test('Manual contact data input via textarea', async ({ page, browserName }) => {
|
|
perfMonitor.start('Manual contact data input via textarea');
|
|
|
|
// Navigate to contacts page
|
|
await perfMonitor.measureAsync('navigate to contacts', () => page.goto('./contacts'));
|
|
|
|
// Use standardized contact data format
|
|
const contactData = 'Paste this: [{ "did": "did:ethr:0x111d15564f824D56C7a07b913aA7aDd03382aA39", "name": "User #111" }, { "did": "did:ethr:0x222BB77E6Ff3774d34c751f3c1260866357B677b", "name": "User #222", "publicKeyBase64": "asdf1234"}] ';
|
|
|
|
await perfMonitor.measureAsync('fill contact data', () =>
|
|
page.getByPlaceholder('URL or DID, Name, Public Key').fill(contactData)
|
|
);
|
|
|
|
await perfMonitor.measureAsync('click add button', () =>
|
|
page.locator('button > svg.fa-plus').click()
|
|
);
|
|
|
|
// Verify redirect to contact import page
|
|
await perfMonitor.measureAsync('wait for contact import page', () =>
|
|
expect(page.getByRole('heading', { name: 'Contact Import' })).toBeVisible()
|
|
);
|
|
|
|
// Verify contact is detected as new
|
|
await perfMonitor.measureAsync('verify new contact detected', () =>
|
|
expect(page.locator('li', { hasText: 'New' }).first()).toBeVisible()
|
|
);
|
|
|
|
// Execute import process
|
|
await perfMonitor.measureAsync('click import button', () =>
|
|
page.locator('button', { hasText: 'Import Selected Contacts' }).click()
|
|
);
|
|
|
|
// Verify success feedback
|
|
await perfMonitor.measureAsync('wait for success message', () =>
|
|
expect(page.locator('div[role="alert"] span:has-text("Success")').first()).toBeVisible()
|
|
);
|
|
|
|
perfMonitor.end('Manual contact data input via textarea');
|
|
});
|
|
|
|
/**
|
|
* Test duplicate contact detection and field comparison
|
|
*
|
|
* This test validates the system's ability to detect existing
|
|
* contacts and compare their fields when attempting to import
|
|
* duplicate data. The test ensures that:
|
|
* - Existing contacts are properly detected
|
|
* - Field comparison works correctly
|
|
* - Import process handles duplicates gracefully
|
|
* - Success feedback is provided
|
|
*/
|
|
test('Duplicate contact detection and field comparison', async ({ page }) => {
|
|
const testContacts = generateUniqueTestContacts();
|
|
|
|
// First, add a contact normally to create an existing contact
|
|
await page.goto('./contacts');
|
|
await page.getByPlaceholder('URL or DID, Name, Public Key').fill(
|
|
`${testContacts.alice.did}, ${testContacts.alice.name}`
|
|
);
|
|
await page.locator('button > svg.fa-plus').click();
|
|
await expect(page.locator('div[role="alert"] span:has-text("Success")')).toBeVisible();
|
|
await page.locator('div[role="alert"] button > svg.fa-xmark').first().click();
|
|
|
|
// Now try to import the same contact with different data
|
|
const contactData = `Paste this: ${JSON.stringify([{
|
|
...testContacts.alice,
|
|
publicKey: 'different-key'
|
|
}])}`;
|
|
|
|
await page.getByPlaceholder('URL or DID, Name, Public Key').fill(contactData);
|
|
await page.locator('button > svg.fa-plus').click();
|
|
|
|
// Verify duplicate detection
|
|
await expect(page.locator('li', { hasText: 'Existing' })).toHaveCount(1);
|
|
|
|
// Import the contact anyway to test the process
|
|
await page.locator('button', { hasText: 'Import Selected Contacts' }).click();
|
|
|
|
// Verify success feedback
|
|
await expect(page.locator('div[role="alert"] span:has-text("Success")').first()).toBeVisible();
|
|
});
|
|
|
|
/**
|
|
* Test error handling for invalid JWT format
|
|
*
|
|
* This test validates the system's ability to handle invalid
|
|
* JWT tokens gracefully. The test ensures that:
|
|
* - Invalid JWT format is detected
|
|
* - Appropriate error messages are displayed
|
|
* - System remains stable despite invalid input
|
|
*/
|
|
test('Error handling: Invalid JWT format', async ({ page }) => {
|
|
// Navigate to contact import page with invalid JWT
|
|
await page.goto('./contact-import?jwt=invalid.jwt.token');
|
|
|
|
// Verify appropriate error handling
|
|
await expect(page.locator('div', { hasText: 'There are no contacts' }).first()).toBeVisible();
|
|
});
|
|
|
|
/**
|
|
* Test error handling for empty contact array
|
|
*
|
|
* This test validates the system's response when attempting
|
|
* to import an empty array of contacts. The test ensures that:
|
|
* - Empty arrays are handled gracefully
|
|
* - Appropriate messages are displayed
|
|
* - System remains stable
|
|
*/
|
|
test('Error handling: Empty contact array', async ({ page }) => {
|
|
const emptyData = encodeURIComponent(INVALID_DATA.emptyArray);
|
|
await page.goto(`./contact-import?contacts=${emptyData}`);
|
|
|
|
// Verify appropriate message for empty import
|
|
await expect(page.locator('div', { hasText: 'There are no contacts' }).first()).toBeVisible();
|
|
});
|
|
|
|
/**
|
|
* Test error handling for missing required fields
|
|
*
|
|
* This test validates the system's ability to handle contact
|
|
* data that is missing required fields. The test ensures that:
|
|
* - Malformed data is detected
|
|
* - Appropriate error messages are displayed
|
|
* - System remains stable
|
|
*/
|
|
test('Error handling: Missing required fields', async ({ page }) => {
|
|
const malformedData = encodeURIComponent(INVALID_DATA.missingFields);
|
|
await page.goto(`./contact-import?contacts=${malformedData}`);
|
|
|
|
// Verify error handling for malformed data
|
|
await expect(page.locator('div', { hasText: 'There are no contacts' })).toBeVisible();
|
|
});
|
|
|
|
/**
|
|
* Test error handling for wrong data types
|
|
*
|
|
* This test validates the system's ability to handle contact
|
|
* data with incorrect data types. The test ensures that:
|
|
*
|
|
* - Type errors are detected
|
|
* - Appropriate error messages are displayed
|
|
* - System remains stable
|
|
*/
|
|
test('Error handling: Wrong data types', async ({ page }) => {
|
|
// Navigate to contact import page with invalid data
|
|
await page.goto('./contact-import?contacts=invalid-data');
|
|
|
|
// Verify error handling for wrong data types
|
|
await expect(page.locator('div', { hasText: 'There are no contacts' }).first()).toBeVisible();
|
|
});
|
|
|
|
/**
|
|
* Test selective contact import with checkboxes
|
|
*
|
|
* This test validates the checkbox-based selection mechanism
|
|
* for importing contacts. The test ensures that:
|
|
*
|
|
* - Checkboxes work correctly for contact selection
|
|
* - Only selected contacts are imported
|
|
* - Import process completes successfully
|
|
* - Correct number of contacts appear in final list
|
|
*/
|
|
test('Selective contact import with checkboxes', async ({ page }) => {
|
|
const testContacts = generateUniqueTestContacts();
|
|
const contactsData = encodeURIComponent(JSON.stringify([
|
|
testContacts.alice,
|
|
testContacts.bob,
|
|
testContacts.charlie
|
|
]));
|
|
await page.goto(`./contact-import?contacts=${contactsData}`);
|
|
|
|
// Uncheck one contact to test selective import
|
|
await page.locator('input[type="checkbox"]').nth(1).uncheck();
|
|
|
|
// Import selected contacts
|
|
await page.locator('button:has-text("Import Selected Contacts")').click();
|
|
|
|
// Verify success feedback
|
|
await expect(page.locator('div[role="alert"] span:has-text("Success")')).toBeVisible();
|
|
await page.locator('div[role="alert"] button > svg.fa-xmark').click();
|
|
|
|
// Verify only selected contacts were imported
|
|
await page.goto('./contacts');
|
|
await expect(page.getByTestId('contactListItem')).toHaveCount(2);
|
|
});
|
|
|
|
/**
|
|
* Test visibility settings for imported contacts
|
|
*
|
|
* This test validates the visibility settings functionality
|
|
* for imported contacts. The test ensures that:
|
|
* - Visibility checkboxes work correctly
|
|
* - Settings are applied during import
|
|
* - Import process completes successfully
|
|
*/
|
|
test('Visibility settings for imported contacts', async ({ page }) => {
|
|
const testContacts = generateUniqueTestContacts();
|
|
const contactsData = encodeURIComponent(JSON.stringify([
|
|
testContacts.alice,
|
|
testContacts.bob
|
|
]));
|
|
await page.goto(`./contact-import?contacts=${contactsData}`);
|
|
|
|
// Check visibility checkbox
|
|
await page.locator('input[type="checkbox"]').first().check();
|
|
await expect(page.locator('span', { hasText: 'Make my activity visible' })).toBeVisible();
|
|
|
|
// Import contacts
|
|
await page.locator('button:has-text("Import Selected Contacts")').click();
|
|
|
|
// Verify success feedback
|
|
await expect(page.locator('div[role="alert"] span:has-text("Success")')).toBeVisible();
|
|
});
|
|
|
|
/**
|
|
* Test import with existing contacts - all duplicates
|
|
*
|
|
* This test validates the system's behavior when attempting
|
|
* to import contacts that already exist in the database.
|
|
* The test ensures that:
|
|
* - Existing contacts are properly detected
|
|
* - All contacts are marked as existing
|
|
* - Import process handles duplicates gracefully
|
|
*/
|
|
test('Import with existing contacts - all duplicates', async ({ page, browserName }) => {
|
|
perfMonitor.start('Import with existing contacts - all duplicates');
|
|
|
|
// First, add all test contacts to create existing contacts
|
|
const testContacts = generateUniqueTestContacts();
|
|
await perfMonitor.measureAsync('navigate to contacts', () => page.goto('./contacts'));
|
|
|
|
for (let i = 0; i < Object.values(testContacts).length; i++) {
|
|
const contact = Object.values(testContacts)[i];
|
|
perfMonitor.checkpoint(`adding contact ${i + 1}`);
|
|
|
|
await perfMonitor.measureAsync(`fill contact ${i + 1}`, () =>
|
|
page.getByPlaceholder('URL or DID, Name, Public Key').fill(`${contact.did}, ${contact.name}`)
|
|
);
|
|
|
|
await perfMonitor.measureAsync(`click add button ${i + 1}`, () =>
|
|
page.locator('button > svg.fa-plus').click()
|
|
);
|
|
|
|
await perfMonitor.measureAsync(`wait for success alert ${i + 1}`, () =>
|
|
expect(page.locator('div[role="alert"] span:has-text("Success")')).toBeVisible()
|
|
);
|
|
|
|
// For the 3rd contact, skip alert dismissal to avoid timeout issues
|
|
if (i < 2) {
|
|
await perfMonitor.measureAsync(`dismiss alert ${i + 1}`, async () => {
|
|
try {
|
|
await page.locator('div[role="alert"] button > svg.fa-xmark').first().click();
|
|
} catch (error) {
|
|
// Handle modal dialog if present
|
|
try {
|
|
const modalDialog = page.locator('div.absolute.inset-0.h-screen');
|
|
const isModalVisible = await modalDialog.isVisible().catch(() => false);
|
|
|
|
if (isModalVisible) {
|
|
const modalDismissButton = page.locator('div[role="dialog"] button, .modal button, .dialog button').first();
|
|
const isModalButtonVisible = await modalDismissButton.isVisible().catch(() => false);
|
|
|
|
if (isModalButtonVisible) {
|
|
await modalDismissButton.click();
|
|
}
|
|
|
|
await page.locator('div[role="alert"] button > svg.fa-xmark').first().click();
|
|
} else {
|
|
const activityModal = page.locator('p.text-sm:has-text("They were added, and your activity is visible")');
|
|
const isActivityModalVisible = await activityModal.isVisible().catch(() => false);
|
|
|
|
const anyModal = page.locator('div.fixed.z-\\[90\\], div.fixed.z-\\[100\\], div.absolute.inset-0');
|
|
const isAnyModalVisible = await anyModal.isVisible().catch(() => false);
|
|
|
|
if (isActivityModalVisible) {
|
|
const activityModalButton = page.locator('button:has-text("OK"), button:has-text("Dismiss"), button:has-text("Close")').first();
|
|
const isActivityButtonVisible = await activityModalButton.isVisible().catch(() => false);
|
|
|
|
if (isActivityButtonVisible) {
|
|
await activityModalButton.click();
|
|
} else {
|
|
await page.locator('div.absolute.inset-0.h-screen').click({ position: { x: 10, y: 10 } });
|
|
}
|
|
|
|
await page.waitForTimeout(1000);
|
|
await page.locator('div[role="alert"] button > svg.fa-xmark').first().click();
|
|
} else if (isAnyModalVisible) {
|
|
const modalButton = page.locator('button').first();
|
|
const isModalButtonVisible = await modalButton.isVisible().catch(() => false);
|
|
|
|
if (isModalButtonVisible) {
|
|
await modalButton.click();
|
|
} else {
|
|
await page.locator('div.absolute.inset-0.h-screen').click({ position: { x: 10, y: 10 } });
|
|
}
|
|
|
|
await page.waitForTimeout(1000);
|
|
await page.locator('div[role="alert"] button > svg.fa-xmark').first().click();
|
|
} else {
|
|
await page.locator('div[role="alert"] button > svg.fa-xmark').first().click({ force: true });
|
|
}
|
|
}
|
|
} catch (modalError) {
|
|
try {
|
|
await page.locator('div[role="alert"] button > svg.fa-xmark').first().click({ force: true });
|
|
} catch (finalError) {
|
|
// If page is closed, we can't dismiss the alert, but the test can continue
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
perfMonitor.checkpoint('all contacts added');
|
|
|
|
// Try to import the same contacts again
|
|
const contactsData = encodeURIComponent(JSON.stringify(Object.values(testContacts)));
|
|
await perfMonitor.measureAsync('navigate to contact import', () =>
|
|
page.goto(`./contact-import?contacts=${contactsData}`)
|
|
);
|
|
|
|
// Verify all are detected as existing
|
|
await perfMonitor.measureAsync('verify existing contacts', () =>
|
|
expect(page.locator('li', { hasText: 'Existing' })).toHaveCount(3)
|
|
);
|
|
|
|
perfMonitor.end('Import with existing contacts - all duplicates');
|
|
});
|
|
|
|
/**
|
|
* Test mixed new and existing contacts
|
|
*
|
|
* This test validates the system's ability to handle imports
|
|
* that contain both new and existing contacts. The test ensures that:
|
|
* - New contacts are properly detected
|
|
* - Existing contacts are properly detected
|
|
* - Import process handles mixed scenarios correctly
|
|
* - Success feedback is provided
|
|
*/
|
|
test('Mixed new and existing contacts', async ({ page }) => {
|
|
// Add one existing contact first
|
|
const testContacts = generateUniqueTestContacts();
|
|
await page.goto('./contacts');
|
|
await page.getByPlaceholder('URL or DID, Name, Public Key').fill(
|
|
`${testContacts.alice.did}, ${testContacts.alice.name}`
|
|
);
|
|
await page.locator('button > svg.fa-plus').click();
|
|
await expect(page.locator('div[role="alert"] span:has-text("Success")')).toBeVisible();
|
|
await page.locator('div[role="alert"] button > svg.fa-xmark').first().click();
|
|
|
|
// Import mix of new and existing contacts
|
|
const mixedContacts = [
|
|
testContacts.alice, // existing
|
|
testContacts.bob, // new
|
|
testContacts.charlie // new
|
|
];
|
|
const contactsData = encodeURIComponent(JSON.stringify(mixedContacts));
|
|
await page.goto(`./contact-import?contacts=${contactsData}`);
|
|
|
|
// Verify correct detection
|
|
await expect(page.locator('li', { hasText: 'Existing' })).toHaveCount(1);
|
|
await expect(page.locator('li', { hasText: 'New' }).first()).toBeVisible();
|
|
|
|
// Import selected contacts
|
|
await page.locator('button:has-text("Import Selected Contacts")').click();
|
|
|
|
// Verify success feedback
|
|
await expect(page.locator('div[role="alert"] span:has-text("Success")')).toBeVisible();
|
|
});
|
|
|
|
/**
|
|
* Test error logging verification
|
|
*
|
|
* This test validates that error logging appears correctly
|
|
* when invalid data is provided. The test ensures that:
|
|
* - Invalid JWT format is handled gracefully
|
|
* - Malformed data is handled gracefully
|
|
* - Appropriate error messages are displayed
|
|
*/
|
|
test('Error logging verification', async ({ page }) => {
|
|
// Test with invalid JWT
|
|
await page.goto('./contact-import');
|
|
await page.locator('textarea[placeholder="Contact-import data"]').fill(INVALID_DATA.malformedJwt);
|
|
await page.locator('button:has-text("Check Import")').click();
|
|
|
|
// Verify appropriate error message is displayed
|
|
await expect(page.locator('div', { hasText: 'There are no contacts' }).first()).toBeVisible();
|
|
|
|
// Test with malformed data
|
|
const malformedData = encodeURIComponent(INVALID_DATA.missingFields);
|
|
await page.goto(`./contact-import?contacts=${malformedData}`);
|
|
|
|
// Verify error handling
|
|
await expect(page.locator('div', { hasText: 'There are no contacts' }).first()).toBeVisible();
|
|
});
|
|
|
|
/**
|
|
* Test network error handling simulation
|
|
*
|
|
* This test simulates network errors by using invalid URLs
|
|
* to validate the system's error handling capabilities.
|
|
* The test ensures that:
|
|
* - Network errors are handled gracefully
|
|
* - Appropriate error messages are displayed
|
|
* - System remains stable
|
|
*/
|
|
test('Network error handling simulation', async ({ page }) => {
|
|
await page.goto('./contact-import');
|
|
|
|
// Try to import from an invalid URL
|
|
await page.locator('textarea[placeholder="Contact-import data"]').fill(INVALID_DATA.networkError);
|
|
await page.locator('button:has-text("Check Import")').click();
|
|
|
|
// Verify error handling
|
|
await expect(page.locator('div', { hasText: 'There are no contacts' }).first()).toBeVisible();
|
|
});
|
|
|
|
/**
|
|
* Test large contact import performance
|
|
*
|
|
* This test validates the system's performance when handling
|
|
* larger contact lists. The test ensures that:
|
|
* - Large contact lists are processed efficiently
|
|
* - All contacts are detected correctly
|
|
* - Import process completes successfully
|
|
* - Performance remains acceptable
|
|
*/
|
|
test('Large contact import performance', async ({ page, browserName }) => {
|
|
perfMonitor.start('Large contact import performance');
|
|
|
|
// Test performance with larger contact lists
|
|
const largeContactList: TestContact[] = [];
|
|
for (let i = 0; i < 10; i++) {
|
|
largeContactList.push({
|
|
did: `did:ethr:0x${i.toString().padStart(40, '0')}`,
|
|
name: `Contact ${i}`,
|
|
publicKey: `public-key-${i}`
|
|
});
|
|
}
|
|
|
|
const contactsData = encodeURIComponent(JSON.stringify(largeContactList));
|
|
await perfMonitor.measureAsync('navigate to contact import', () =>
|
|
page.goto(`./contact-import?contacts=${contactsData}`)
|
|
);
|
|
|
|
// Verify all contacts are detected
|
|
await perfMonitor.measureAsync('verify new contacts detected', () =>
|
|
expect(page.locator('li', { hasText: 'New' })).toHaveCount(10)
|
|
);
|
|
|
|
// Import all contacts
|
|
await perfMonitor.measureAsync('click import button', () =>
|
|
page.locator('button:has-text("Import Selected Contacts")').click()
|
|
);
|
|
|
|
// Verify success feedback
|
|
await perfMonitor.measureAsync('wait for success message', () =>
|
|
expect(page.locator('div[role="alert"] span:has-text("Success")')).toBeVisible()
|
|
);
|
|
|
|
perfMonitor.end('Large contact import performance');
|
|
});
|
|
|
|
/**
|
|
* Test alert dismissal performance
|
|
*
|
|
* This test validates the performance and reliability of
|
|
* alert dismissal functionality. The test ensures that:
|
|
* - Alerts are dismissed efficiently
|
|
* - Different dismissal strategies work correctly
|
|
* - Performance remains acceptable
|
|
*/
|
|
test('Alert dismissal performance test', async ({ page, browserName }) => {
|
|
perfMonitor.start('Alert dismissal performance test');
|
|
|
|
// Add a contact to trigger an alert
|
|
const testContacts = generateUniqueTestContacts();
|
|
await perfMonitor.measureAsync('navigate to contacts', () => page.goto('./contacts'));
|
|
|
|
await perfMonitor.measureAsync('fill contact input', () =>
|
|
page.getByPlaceholder('URL or DID, Name, Public Key').fill(`${testContacts.alice.did}, ${testContacts.alice.name}`)
|
|
);
|
|
|
|
await perfMonitor.measureAsync('click add button', () =>
|
|
page.locator('button > svg.fa-plus').click()
|
|
);
|
|
|
|
await perfMonitor.measureAsync('wait for success alert', () =>
|
|
expect(page.locator('div[role="alert"] span:has-text("Success")')).toBeVisible()
|
|
);
|
|
|
|
// Test alert dismissal performance
|
|
await perfMonitor.measureAsync('dismiss alert (detailed)', async () => {
|
|
const alertButton = page.locator('div[role="alert"] button > svg.fa-xmark').first();
|
|
|
|
// Wait for button to be stable
|
|
await alertButton.waitFor({ state: 'visible', timeout: 10000 });
|
|
|
|
// Try clicking with different strategies
|
|
try {
|
|
await alertButton.click({ timeout: 5000 });
|
|
} catch (error) {
|
|
// Try force click if normal click fails
|
|
await alertButton.click({ force: true, timeout: 5000 });
|
|
}
|
|
});
|
|
|
|
perfMonitor.end('Alert dismissal performance test');
|
|
});
|
|
});
|