ConnectedSession.ts
1 import type { Page } from '@playwright/test'; 2 import { waitForPageInteractive } from '../utils/wait-for-page-interactive'; 3 4 /** These addresses all start with 10000 ETH on the local testnet */ 5 export const TEST_ADDRESSES = [ 6 '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', // owns efstajas/drips-test-repo-1, 100000 TEST 7 '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', // owns efstajas/drips-test-repo-2 8 '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC', // owns efstajas/drips-test-repo-3 9 '0x90F79bf6EB2c4f870365E785982E1f101E93b906', // owns efstajas/drips-test-repo-4 10 '0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65', // owns efstajas/drips-test-repo-5 11 '0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc', // owns efstajas/drips-test-repo-6 12 '0x976EA74026E726554dB657fA54763abd0C3a0aa9', 13 '0x14dC79964da2C08b23698B3D3cc7Ca32193d9955', 14 '0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f', 15 '0xa0Ee7A142d267C1f36714E4a8F75612F20a79720', 16 ]; 17 18 export class ConnectedSession { 19 constructor( 20 public readonly page: Page, 21 public readonly address: string, 22 ) {} 23 24 async goto() { 25 await this.page.goto('http://localhost:5173/app'); 26 await this.page.waitForTimeout(1000); // Reduce flakiness of first nav, for some reason 27 28 // Wait for loading state to clear to avoid pointer-events: none blocking clicks 29 await waitForPageInteractive(this.page); 30 } 31 32 async connect() { 33 // Wait for page to be fully loaded and network to be idle to avoid race conditions 34 await this.page.waitForLoadState('networkidle'); 35 36 // Wait for the Connect button to be visible - this ensures the page has fully 37 // hydrated and is stable before we inject JavaScript 38 await this.page 39 .getByRole('button', { name: 'Connect', exact: true }) 40 .waitFor({ state: 'visible' }); 41 42 // insert hidden `span` with id E2E_ADDRESS into the DOM in order to tell 43 // the local testnet wallet store which address to use 44 // Retry mechanism to handle potential navigation timing issues 45 let retries = 3; 46 let lastError; 47 48 while (retries > 0) { 49 try { 50 await this.page.evaluate((addr) => { 51 const span = document.createElement('span'); 52 span.style.display = 'none'; 53 span.id = 'E2E_ADDRESS'; 54 span.textContent = addr; 55 document.body.appendChild(span); 56 }, this.address); 57 break; // Success, exit retry loop 58 } catch (error) { 59 lastError = error; 60 const errorMessage = error instanceof Error ? error.message : String(error); 61 62 if (errorMessage.includes('Execution context was destroyed') && retries > 1) { 63 // Wait for navigation to complete and retry 64 await this.page.waitForLoadState('networkidle'); 65 await this.page 66 .getByRole('button', { name: 'Connect', exact: true }) 67 .waitFor({ state: 'visible' }); 68 retries--; 69 continue; 70 } 71 // If it's a different error or last retry, throw 72 throw error; 73 } 74 } 75 76 if (retries === 0 && lastError) { 77 throw lastError; 78 } 79 80 await this.page.getByRole('button', { name: 'Connect', exact: true }).click(); 81 82 // wait for first 4 and last 4 characters of address to be visible 83 const addressLocator = this.page 84 .getByText(this.address.slice(0, 4) + '–' + this.address.slice(-4), { exact: true }) 85 .nth(0); 86 await addressLocator.waitFor({ state: 'visible' }); 87 } 88 89 async disconnect() { 90 // Currently, connected state is not persisted in local env, so we can just reload the 91 // page to "disconnect" 92 await this.page.reload(); 93 } 94 }