UNPKG

@cygnus-wealth/wallet-integration-system

Version:

Multi-chain wallet integration system for CygnusWealth

281 lines (280 loc) 13.3 kB
import puppeteer from 'puppeteer'; import path from 'path'; import fs from 'fs'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Test configuration const TEST_MNEMONIC = 'test test test test test test test test test test test junk'; const TEST_PASSWORD = 'TestPassword123!'; class WalletE2ETest { browser = null; page = null; extensionId = ''; async setup(options = {}) { const { headless = false, slowMo = 50, metamaskVersion = '11.16.0' } = options; // Download MetaMask extension const extensionPath = await this.downloadMetaMaskExtension(metamaskVersion); // Launch browser with extension this.browser = await puppeteer.launch({ headless, slowMo, args: [ `--disable-extensions-except=${extensionPath}`, `--load-extension=${extensionPath}`, '--no-sandbox', '--disable-setuid-sandbox', '--disable-web-security', '--disable-features=IsolateOrigins,site-per-process' ], defaultViewport: null, }); // Wait a bit for extension to initialize await new Promise(resolve => setTimeout(resolve, 3000)); // Get extension ID by finding background page or any extension page const targets = await this.browser.targets(); let extensionTarget = targets.find(target => target.url().includes('chrome-extension://') && (target.type() === 'service_worker' || target.type() === 'background_page' || target.type() === 'page')); // If no service worker found, create a new page to trigger extension if (!extensionTarget) { const pages = await this.browser.pages(); for (const page of pages) { const url = page.url(); if (url.includes('chrome-extension://')) { extensionTarget = await page.target(); break; } } } if (extensionTarget) { const url = extensionTarget.url(); const matches = url.match(/chrome-extension:\/\/([a-z0-9]+)\//); if (matches) { this.extensionId = matches[1]; console.log('Found extension ID:', this.extensionId); } } // If still no extension ID, try to find it by opening a new tab if (!this.extensionId) { const newPage = await this.browser.newPage(); await newPage.goto('chrome://extensions'); await newPage.close(); // Check again const updatedTargets = await this.browser.targets(); const extTarget = updatedTargets.find(target => target.url().includes('chrome-extension://')); if (extTarget) { const url = extTarget.url(); const matches = url.match(/chrome-extension:\/\/([a-z0-9]+)\//); if (matches) { this.extensionId = matches[1]; } } } // Create main page this.page = await this.browser.newPage(); } async downloadMetaMaskExtension(version) { const extensionPath = path.join(__dirname, 'extensions', `metamask-${version}`); if (fs.existsSync(extensionPath)) { console.log('MetaMask extension already exists'); return extensionPath; } console.log(`Downloading MetaMask ${version}...`); // Create directory fs.mkdirSync(path.join(__dirname, 'extensions'), { recursive: true }); // Download from GitHub releases const downloadUrl = `https://github.com/MetaMask/metamask-extension/releases/download/v${version}/metamask-chrome-${version}.zip`; const response = await fetch(downloadUrl); if (!response.ok) { throw new Error(`Failed to download MetaMask: ${response.statusText}`); } const buffer = await response.arrayBuffer(); const zipPath = path.join(__dirname, 'extensions', `metamask-${version}.zip`); fs.writeFileSync(zipPath, Buffer.from(buffer)); // Extract using node's built-in zlib and tar const { execSync } = await import('child_process'); execSync(`unzip -q "${zipPath}" -d "${extensionPath}"`); fs.unlinkSync(zipPath); console.log('MetaMask downloaded successfully'); return extensionPath; } async setupMetaMaskWallet() { if (!this.browser) { throw new Error('Browser not initialized'); } // Try to find MetaMask page let metamaskPage = null; if (this.extensionId) { // Navigate to MetaMask using extension ID metamaskPage = await this.browser.newPage(); await metamaskPage.goto(`chrome-extension://${this.extensionId}/home.html`); } else { // Try to find MetaMask in existing pages const pages = await this.browser.pages(); metamaskPage = pages.find(page => page.url().includes('chrome-extension://')) || pages[0]; // If no MetaMask page found, wait and check for popup if (!metamaskPage || !metamaskPage.url().includes('chrome-extension://')) { await new Promise(resolve => setTimeout(resolve, 2000)); const updatedPages = await this.browser.pages(); metamaskPage = updatedPages.find(page => page.url().includes('chrome-extension://')) || updatedPages[0]; } } // Wait for MetaMask to load await metamaskPage.waitForSelector('button', { timeout: 10000 }); try { // Check if we need to go through onboarding const getStarted = await metamaskPage.$('button:has-text("Get started")'); if (getStarted) { await getStarted.click(); // Import wallet await metamaskPage.click('button:has-text("Import an existing wallet")'); await metamaskPage.click('button:has-text("I agree")'); // Enter seed phrase const words = TEST_MNEMONIC.split(' '); for (let i = 0; i < words.length; i++) { const input = await metamaskPage.$(`input[data-testid="import-srp__srp-word-${i}"]`); if (input) { await input.type(words[i]); } } await metamaskPage.click('button:has-text("Confirm Secret Recovery Phrase")'); // Set password await metamaskPage.type('input[data-testid="create-password-new"]', TEST_PASSWORD); await metamaskPage.type('input[data-testid="create-password-confirm"]', TEST_PASSWORD); await metamaskPage.click('input[data-testid="create-password-terms"]'); await metamaskPage.click('button:has-text("Import my wallet")'); // Complete setup await metamaskPage.waitForSelector('button:has-text("Got it")', { timeout: 10000 }); await metamaskPage.click('button:has-text("Got it")'); await metamaskPage.click('button:has-text("Next")'); await metamaskPage.click('button:has-text("Done")'); } await metamaskPage.close(); } catch (error) { console.error('Error setting up MetaMask:', error); throw error; } } async testWalletConnection() { if (!this.page || !this.browser) { throw new Error('Browser not initialized'); } // Create a test page with wallet connection await this.page.goto('data:text/html,<!DOCTYPE html><html><head><title>Wallet Test</title></head><body><h1>Wallet Integration Test</h1><button id="connect">Connect Wallet</button><div id="result"></div></body></html>'); // Inject test code await this.page.evaluate(() => { const button = document.getElementById('connect'); const result = document.getElementById('result'); button?.addEventListener('click', async () => { try { if (typeof window.ethereum !== 'undefined') { const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' }); result.textContent = `Connected: ${accounts[0]}`; } else { result.textContent = 'No wallet found'; } } catch (error) { result.textContent = `Error: ${error}`; } }); }); // Click connect await this.page.click('#connect'); // Wait for MetaMask popup await this.page.waitForTimeout(2000); // Find and handle MetaMask popup const pages = await this.browser.pages(); const popup = pages.find(p => p.url().includes('notification')); if (popup) { await popup.waitForSelector('button', { timeout: 10000 }); // Click through connection flow const nextButton = await popup.$('button:has-text("Next")'); if (nextButton) { await nextButton.click(); await popup.waitForSelector('button:has-text("Connect")'); await popup.click('button:has-text("Connect")'); } } // Wait for connection result await this.page.waitForTimeout(2000); // Verify connection const resultText = await this.page.$eval('#result', el => el.textContent); console.log('Connection result:', resultText); if (!resultText?.includes('Connected: 0x')) { throw new Error('Failed to connect wallet'); } } async testLibraryIntegration() { if (!this.page || !this.browser) { throw new Error('Browser not initialized'); } // Serve the built library await this.page.goto('data:text/html,<!DOCTYPE html><html><head><title>Library Test</title></head><body><h1>Wallet Library Test</h1><button id="test">Test Library</button><div id="output"></div><script type="module">window.runTest = async () => { const output = document.getElementById("output"); try { output.textContent = "Loading library..."; const WalletManager = (await import("' + process.cwd() + '/dist/services/WalletManager.js")).WalletManager; const { Chain, IntegrationSource } = await import("@cygnus-wealth/data-models"); output.textContent = "Connecting wallet..."; const manager = new WalletManager(); const connection = await manager.connectWallet(Chain.ETHEREUM, IntegrationSource.METAMASK); output.textContent = "Connected: " + connection.address; const balances = await manager.getBalancesByChain(Chain.ETHEREUM); output.textContent += "\\nBalances: " + balances.length; } catch (error) { output.textContent = "Error: " + error.message; } }; document.getElementById("test").onclick = runTest;</script></body></html>'); // Click test button await this.page.click('#test'); // Handle MetaMask popup if needed await this.page.waitForTimeout(2000); const pages = await this.browser.pages(); const popup = pages.find(p => p.url().includes('notification')); if (popup) { try { await popup.waitForSelector('button:has-text("Connect")', { timeout: 5000 }); await popup.click('button:has-text("Connect")'); } catch { // Popup might auto-close if already connected } } // Wait for result await this.page.waitForTimeout(3000); const output = await this.page.$eval('#output', el => el.textContent); console.log('Library test output:', output); if (!output?.includes('Connected: 0x')) { throw new Error('Library integration failed'); } } async cleanup() { if (this.browser) { await this.browser.close(); } } } // Run tests async function runE2ETests() { const test = new WalletE2ETest(); try { console.log('🚀 Starting E2E tests...'); // Setup console.log('📦 Setting up browser and MetaMask...'); await test.setup({ headless: false }); // Setup wallet console.log('🔐 Setting up MetaMask wallet...'); await test.setupMetaMaskWallet(); // Test connection console.log('🔌 Testing wallet connection...'); await test.testWalletConnection(); // Test library console.log('📚 Testing library integration...'); await test.testLibraryIntegration(); console.log('✅ All tests passed!'); } catch (error) { console.error('❌ Test failed:', error); process.exit(1); } finally { await test.cleanup(); } } // Export for use in test runners export { WalletE2ETest, runE2ETests }; // Run if executed directly if (import.meta.url === `file://${process.argv[1]}`) { runE2ETests(); }