@cygnus-wealth/wallet-integration-system
Version:
Multi-chain wallet integration system for CygnusWealth
281 lines (280 loc) • 13.3 kB
JavaScript
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();
}