import { test, expect } from '@playwright/test'; test.describe('IPFS Portal', () => { const BASE = 'http://localhost:3445'; test('health endpoint returns 200', async ({ request }) => { const r = await request.get(`${BASE}/api/health`); expect(r.ok()).toBeTruthy(); const body = await r.json(); expect(body).toHaveProperty('status', 'ok'); expect(body).toHaveProperty('kuboApi'); }); test('dashboard loads without errors', async ({ page }) => { const errors: string[] = []; page.on('console', (msg) => { if (msg.type() === 'error') errors.push(msg.text()); }); page.on('pageerror', (err) => errors.push('PAGE: ' + err.message)); await page.goto(`${BASE}/dashboard`, { waitUntil: 'networkidle' }); await expect(page.locator('h1')).toContainText('Dashboard'); const critical = errors.filter( (e) => !e.includes('favicon') && !e.includes('DevTools') && !e.includes('404'), ); expect(critical).toHaveLength(0); }); test('explorer page renders', async ({ page }) => { await page.goto(`${BASE}/explorer`, { waitUntil: 'networkidle' }); await expect(page.locator('h1')).toContainText(/Explorer/i); }); test('peers page loads', async ({ page }) => { await page.goto(`${BASE}/peers`, { waitUntil: 'networkidle' }); await expect(page.locator('h1')).toContainText(/Peers/i); }); test('pins page loads', async ({ page }) => { await page.goto(`${BASE}/pins`, { waitUntil: 'networkidle' }); await expect(page.locator('h1')).toContainText(/Pins/i); }); test('upload page renders with tabs and drop zone', async ({ page }) => { await page.goto(`${BASE}/upload`, { waitUntil: 'networkidle' }); await expect(page.locator('h1')).toContainText(/Upload/i); // Quick Upload tab should show drop zone with the highlighted "Click to browse" text await expect(page.getByText('Click to browse', { exact: true })).toBeVisible(); // Tab switcher should exist await expect(page.getByRole('button', { name: /Quick Upload/i })).toBeVisible(); await expect(page.getByRole('button', { name: /Crypto Payment/i })).toBeVisible(); }); test('upload page crypto tab switches correctly', async ({ page }) => { await page.goto(`${BASE}/upload`, { waitUntil: 'networkidle' }); // Switch to Crypto tab await page.getByRole('button', { name: 'Crypto Payment' }).click(); await expect(page.locator('text=Select a file to see pricing')).toBeVisible(); }); test('IPNS page loads keys', async ({ page }) => { await page.goto(`${BASE}/ipns`, { waitUntil: 'networkidle' }); await expect(page.locator('h1')).toContainText(/IPNS/i); }); test('history page loads with empty state', async ({ page }) => { await page.goto(`${BASE}/history`, { waitUntil: 'networkidle' }); await expect(page.locator('h1')).toContainText(/Upload History/i); await expect(page.locator('text=No upload history yet')).toBeVisible(); await expect(page.locator('text=Upload your first file')).toBeVisible(); }); test('settings page loads with editable form', async ({ page }) => { await page.goto(`${BASE}/settings`, { waitUntil: 'networkidle' }); await expect(page.locator('h1')).toContainText('Settings'); // Should show input fields const inputs = page.locator('input'); const count = await inputs.count(); expect(count).toBeGreaterThanOrEqual(3); // Save and Reset buttons await expect(page.locator('button:has-text("Save Settings")')).toBeVisible(); await expect(page.locator('button:has-text("Reset Defaults")')).toBeVisible(); }); test('IPFS node info via proxy', async ({ request }) => { const r = await request.get(`${BASE}/api/node/info`); expect(r.ok()).toBeTruthy(); const body = await r.json(); expect(body).toHaveProperty('ID'); expect(body).toHaveProperty('AgentVersion'); }); test('swarm peers endpoint', async ({ request }) => { const r = await request.get(`${BASE}/api/peers`); expect(r.ok()).toBeTruthy(); const body = await r.json(); expect(body).toHaveProperty('Peers'); }); test('repo stats endpoint', async ({ request }) => { const r = await request.get(`${BASE}/api/repo/stats`); expect(r.ok()).toBeTruthy(); const body = await r.json(); expect(body).toHaveProperty('RepoSize'); expect(body).toHaveProperty('NumObjects'); }); test('sidebar navigation links exist and work', async ({ page }) => { await page.goto(`${BASE}/dashboard`, { waitUntil: 'networkidle' }); await expect(page.locator('h1')).toContainText('Dashboard'); const sidebar = page.locator('aside'); await expect(sidebar).toBeVisible(); const links = sidebar.getByRole('link'); const count = await links.count(); expect(count).toBeGreaterThanOrEqual(8); // History added for (const name of ['Explorer', 'Pins', 'Peers', 'History']) { await sidebar.getByRole('link', { name }).click(); await expect(page).toHaveURL(new RegExp(name.toLowerCase())); } }); /* ── Payment Flow ── */ test('upload page crypto tab shows payment panel after file selection', async ({ page }) => { await page.goto(`${BASE}/upload`, { waitUntil: 'networkidle' }); // Switch to Crypto tab await page.getByRole('button', { name: 'Crypto Payment' }).click(); // Create a dummy file and set it on the crypto file input const fileInput = page.locator('input[type="file"]').last(); await fileInput.setInputFiles({ name: 'test.txt', mimeType: 'text/plain', buffer: Buffer.from('hello ipfs payment test'), }); // Payment panel should now be visible with "Crypto Payment" heading await expect(page.getByRole('heading', { name: 'Crypto Payment' })).toBeVisible(); // Should either show "not deployed" or "Connect Wallet" const notDeployed = page.locator('text=Payment contract not deployed'); const connectWallet = page.locator('button:has-text("Connect Wallet")'); const isNotDeployed = await notDeployed.isVisible().catch(() => false); if (isNotDeployed) { await expect(notDeployed).toBeVisible(); } else { await expect(connectWallet).toBeVisible(); } }); test('payment verification API accepts address query', async ({ request }) => { // Test with a zero address to verify the API structure works const r = await request.get(`${BASE}/api/payment/verify?address=0x0000000000000000000000000000000000000000`); expect(r.ok()).toBeTruthy(); const body = await r.json(); // Should either show deployed or not expect(body).toHaveProperty('deployed'); if (body.deployed) { expect(body).toHaveProperty('stats'); expect(body.stats).toHaveProperty('totalBytes'); } }); });