/ tests / fixtures / ConnectedSession.ts
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  }