Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions frontend/e2e/app.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { test, expect } from '@playwright/test';

test.describe('TipStream Landing Page', () => {

Check failure on line 3 in frontend/e2e/app.spec.js

View workflow job for this annotation

GitHub Actions / Frontend Tests

e2e/app.spec.js

Error: Playwright Test did not expect test.describe() to be called here. Most common reasons include: - You are calling test.describe() in a configuration file. - You are calling test.describe() in a file that is imported by the configuration file. - You have two different versions of @playwright/test. This usually happens when one of the dependencies in your package.json depends on @playwright/test. ❯ TestTypeImpl._currentSuite node_modules/playwright/lib/common/testType.js:75:13 ❯ TestTypeImpl._describe node_modules/playwright/lib/common/testType.js:115:24 ❯ Function.describe node_modules/playwright/lib/transform/transform.js:282:12 ❯ e2e/app.spec.js:3:6
test('loads and shows hero section with branding', async ({ page }) => {
await page.goto('/');
await expect(page.locator('nav').first()).toBeVisible();
await expect(page.getByText('TipStream').first()).toBeVisible();
});

test('shows Connect Wallet button when not authenticated', async ({ page }) => {
await page.goto('/');
await expect(page.getByRole('button', { name: /Connect Wallet/i })).toBeVisible();
});

test('hero section has a Get Started call-to-action', async ({ page }) => {
await page.goto('/');
const cta = page.getByRole('button', { name: /Get Started|Connect/i }).first();
await expect(cta).toBeVisible();
});

test('displays network indicator in header', async ({ page }) => {
await page.goto('/');
await expect(page.getByText(/Mainnet|Testnet|Devnet/i)).toBeVisible();
});

test('footer contains branding and links', async ({ page }) => {
await page.goto('/');
await expect(page.locator('footer')).toBeVisible();
await expect(page.getByText('TipStream').first()).toBeVisible();
await expect(page.locator('footer a[href*="github"]').first()).toBeVisible();
});
});

test.describe('Theme Toggle', () => {
test('theme toggle button is accessible', async ({ page }) => {
await page.goto('/');
const toggle = page.getByRole('button', { name: /Switch to (dark|light) mode/i });
await expect(toggle).toBeVisible();
});

test('clicking theme toggle changes appearance', async ({ page }) => {
await page.goto('/');
const toggle = page.getByRole('button', { name: /Switch to (dark|light) mode/i });
await toggle.click();
// After toggle, the button label should change
await expect(
page.getByRole('button', { name: /Switch to (dark|light) mode/i })
).toBeVisible();
});
});

test.describe('Responsive Layout', () => {
test('renders correctly on mobile viewport', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 812 });
await page.goto('/');
await expect(page.locator('nav').first()).toBeVisible();
await expect(page.getByRole('button', { name: /Connect Wallet/i })).toBeVisible();
});

test('renders correctly on tablet viewport', async ({ page }) => {
await page.setViewportSize({ width: 768, height: 1024 });
await page.goto('/');
await expect(page.locator('nav').first()).toBeVisible();
});

test('renders correctly on desktop viewport', async ({ page }) => {
await page.setViewportSize({ width: 1440, height: 900 });
await page.goto('/');
await expect(page.locator('nav').first()).toBeVisible();
await expect(page.locator('footer')).toBeVisible();
});
});

test.describe('Page Performance', () => {
test('page loads within acceptable time', async ({ page }) => {
const start = Date.now();
await page.goto('/', { waitUntil: 'networkidle' });
const loadTime = Date.now() - start;
expect(loadTime).toBeLessThan(10000); // 10s max
});

test('no console errors on initial load', async ({ page }) => {
const errors = [];
page.on('console', (msg) => {
if (msg.type() === 'error') errors.push(msg.text());
});
await page.goto('/', { waitUntil: 'networkidle' });
// Filter out expected network errors (API calls to Hiro may fail in test env)
const realErrors = errors.filter(
e => !e.includes('api.hiro.so') && !e.includes('fetch') && !e.includes('Failed to load')
);
expect(realErrors).toHaveLength(0);
});
});

test.describe('Accessibility Basics', () => {
test('page has navigation landmark', async ({ page }) => {
await page.goto('/');
const navCount = await page.locator('nav').count();
expect(navCount).toBeGreaterThanOrEqual(1);
});

test('buttons have accessible names', async ({ page }) => {
await page.goto('/');
const buttons = page.getByRole('button');
const count = await buttons.count();
expect(count).toBeGreaterThan(0);
for (let i = 0; i < count; i++) {
const name = await buttons.nth(i).getAttribute('aria-label') ||
await buttons.nth(i).textContent();
expect(name?.trim().length).toBeGreaterThan(0);
}
});
});
64 changes: 64 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
},
"devDependencies": {
"@eslint/js": "^9.39.1",
"@playwright/test": "^1.58.2",
"@tailwindcss/postcss": "^4.1.18",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
Expand Down
24 changes: 24 additions & 0 deletions frontend/playwright.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { defineConfig } from '@playwright/test';

export default defineConfig({
testDir: './e2e',
timeout: 30000,
retries: 1,
use: {
baseURL: 'http://localhost:4173',
headless: true,
screenshot: 'only-on-failure',
},
projects: [
{
name: 'chromium',
use: { browserName: 'chromium' },
},
],
webServer: {
command: 'npx vite preview --port 4173',
port: 4173,
reuseExistingServer: true,
timeout: 30000,
},
});
Loading