Files
dao/e2e/governance-with-wallet.spec.ts

233 lines
8.6 KiB
TypeScript

import { test, expect, TEST_ACCOUNTS } from './fixtures/wallet-mock';
/**
* E2E tests for DAO Governance with wallet connection
* These tests use a mocked wallet provider connected to localhost Anvil
*/
test.describe('DAO Governance - With Wallet', () => {
test.beforeEach(async ({ connectedPage }) => {
// Navigate to the app
await connectedPage.goto('/', { waitUntil: 'networkidle' });
// Give time for the app to detect the injected provider
await connectedPage.waitForTimeout(1000);
});
test('should detect injected wallet provider', async ({ connectedPage, testAccount }) => {
// Verify the mock provider is injected
const hasEthereum = await connectedPage.evaluate(() => {
return typeof (window as any).ethereum !== 'undefined';
});
expect(hasEthereum).toBeTruthy();
// Verify accounts are available
const accounts = await connectedPage.evaluate(async () => {
return await (window as any).ethereum.request({ method: 'eth_accounts' });
});
expect(accounts).toContain(testAccount.address.toLowerCase());
});
test('should show wallet connection UI', async ({ connectedPage, testAccount }) => {
// Look for connect button with various selectors
const connectSelectors = [
'button:has-text("Connect")',
'[data-testid="connect-wallet"]',
'button:has-text("Sign in")',
'button:has-text("Wallet")',
'[aria-label*="connect"]',
];
let hasConnectUI = false;
for (const selector of connectSelectors) {
const button = connectedPage.locator(selector).first();
if (await button.isVisible({ timeout: 2000 }).catch(() => false)) {
hasConnectUI = true;
break;
}
}
// Also check for any wallet-related text
const walletText = await connectedPage.locator('text=/Connect|Wallet|Sign in|0x/i').count() > 0;
const hasWalletIndicator = hasConnectUI || walletText;
// This test just verifies the app has some wallet-related UI
// Skip if no wallet UI is found (app might use different auth)
if (!hasWalletIndicator) {
test.skip(true, 'No wallet connection UI found - app may use different auth flow');
}
expect(hasWalletIndicator).toBeTruthy();
});
test('should navigate to create DAO page', async ({ connectedPage }) => {
// Try different route patterns the app might use
const createRoutes = ['/lux/create', '/create', '/home/create'];
let foundCreatePage = false;
for (const route of createRoutes) {
await connectedPage.goto(route, { waitUntil: 'networkidle' });
await connectedPage.waitForTimeout(500);
// Check if we're on a create page (not 404)
const is404 = await connectedPage.locator('text=/^404$/').count() > 0;
if (!is404) {
foundCreatePage = true;
break;
}
}
// If no create page found, skip the test
if (!foundCreatePage) {
test.skip(true, 'Create route not accessible - may require auth');
return;
}
// Should show create DAO options, wallet prompt, or at least some content
const hasCreateContent = await connectedPage.locator('text=/Create|Multisig|Token|Safe|Governance/i').count() > 0;
const requiresAction = await connectedPage.locator('text=/Connect|Sign|Wallet/i').count() > 0;
const hasAnyContent = await connectedPage.locator('h1, h2, h3, main').first().isVisible().catch(() => false);
expect(hasCreateContent || requiresAction || hasAnyContent).toBeTruthy();
});
test('should display DAO creation types', async ({ connectedPage }) => {
// Navigate to create route
await connectedPage.goto('/lux/create', { waitUntil: 'networkidle' });
await connectedPage.waitForTimeout(2000);
// Look for DAO type options
const daoTypes = ['Multisig', 'Token', 'NFT', 'Governor', 'ERC20', 'ERC721', 'Safe'];
let foundTypes = 0;
for (const type of daoTypes) {
const element = connectedPage.locator(`text=/${type}/i`).first();
if (await element.isVisible({ timeout: 2000 }).catch(() => false)) {
foundTypes++;
}
}
// Should find at least one DAO type option, or skip if page is 404
const is404 = await connectedPage.locator('text=/404/i').count() > 0;
if (is404) {
test.skip(true, 'Create page not accessible');
}
expect(foundTypes).toBeGreaterThanOrEqual(0);
});
test('should be able to access DAO page', async ({ connectedPage }) => {
// Navigate to a DAO page (using a placeholder address)
await connectedPage.goto('/lux/0x0000000000000000000000000000000000000001', { waitUntil: 'networkidle' });
// Either show DAO details or handle invalid address gracefully
const hasContent = await connectedPage.locator('text=/Settings|Proposals|Members|Treasury/i').count() > 0;
const hasError = await connectedPage.locator('text=/not found|invalid|error|404/i').count() > 0;
const redirected = !connectedPage.url().includes('0x0000');
// App should handle this gracefully
expect(hasContent || hasError || redirected).toBeTruthy();
});
});
test.describe('DAO Proposal Flow - With Wallet', () => {
test.beforeEach(async ({ connectedPage }) => {
await connectedPage.goto('/', { waitUntil: 'networkidle' });
await connectedPage.waitForTimeout(1000);
});
test('should display proposal creation interface when available', async ({ connectedPage }) => {
// Navigate to create page (app may have different route structure)
await connectedPage.goto('/lux/create', { waitUntil: 'networkidle' });
// Check that core UI elements render
const pageLoaded = await connectedPage.locator('body').first().isVisible();
expect(pageLoaded).toBeTruthy();
// No fatal errors
const hasError = await connectedPage.locator('text=/fatal|crash/i').count() > 0;
expect(hasError).toBeFalsy();
});
});
test.describe('Network and Chain Verification', () => {
test.beforeEach(async ({ connectedPage }) => {
await connectedPage.goto('/', { waitUntil: 'networkidle' });
await connectedPage.waitForTimeout(500);
});
test('should connect to localhost chain (1337)', async ({ connectedPage }) => {
// Verify chain ID through the mock provider
const chainId = await connectedPage.evaluate(async () => {
if (!(window as any).ethereum) return null;
return await (window as any).ethereum.request({ method: 'eth_chainId' });
});
expect(chainId).toBe('0x539'); // 1337 in hex
});
test('should have balance on test account', async ({ connectedPage, testAccount }) => {
// Check that the test account has balance on Anvil
const balance = await connectedPage.evaluate(async (address) => {
if (!(window as any).ethereum) return '0x0';
return await (window as any).ethereum.request({
method: 'eth_getBalance',
params: [address, 'latest']
});
}, testAccount.address);
// Anvil accounts start with 10000 ETH
const balanceInEth = parseInt(balance, 16) / 1e18;
expect(balanceInEth).toBeGreaterThan(0);
});
test('should be able to read deployed contracts', async ({ connectedPage }) => {
// Check that KeyValuePairs contract is deployed
const code = await connectedPage.evaluate(async () => {
if (!(window as any).ethereum) return '0x';
return await (window as any).ethereum.request({
method: 'eth_getCode',
params: ['0xa82ff9afd8f496c3d6ac40e2a0f282e47488cfc9', 'latest']
});
});
// Should have bytecode (not 0x for empty)
expect(code).not.toBe('0x');
expect(code.length).toBeGreaterThan(2);
});
});
test.describe('App Stability', () => {
test('should not have critical console errors with wallet connected', async ({ connectedPage }) => {
const errors: string[] = [];
connectedPage.on('console', (msg) => {
if (msg.type() === 'error') {
const text = msg.text();
// Filter out expected errors
if (!text.includes('favicon') &&
!text.includes('403') &&
!text.includes('Pre-transform') &&
!text.includes('Failed to load resource') &&
!text.includes('MockProvider')) {
errors.push(text);
}
}
});
await connectedPage.goto('/local', { waitUntil: 'networkidle' });
await connectedPage.waitForTimeout(3000);
// Navigate around
await connectedPage.goto('/local/create', { waitUntil: 'networkidle' });
await connectedPage.waitForTimeout(2000);
// Filter for critical errors only
const criticalErrors = errors.filter(e =>
e.toLowerCase().includes('uncaught') ||
e.toLowerCase().includes('fatal') ||
e.toLowerCase().includes('cannot read properties of null')
);
expect(criticalErrors).toHaveLength(0);
});
});