- Configure workspace Git repository with comprehensive .gitignore - Add Odoo as submodule for ERP reference code - Include documentation: SETUP.md, GIT-STRUCTURE.md - Add gitignore templates for projects (backend, frontend, database) - Structure supports independent repos per project/subproject level Workspace includes: - core/ - Reusable patterns, modules, orchestration system - projects/ - Active projects (erp-suite, gamilit, trading-platform, etc.) - knowledge-base/ - Reference code and patterns (includes Odoo submodule) - devtools/ - Development tools and templates - customers/ - Client implementations template 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
198 lines
5.7 KiB
TypeScript
198 lines
5.7 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
|
|
/**
|
|
* Navigation and Routing E2E Tests
|
|
*
|
|
* Tests basic navigation, routing, and page loads
|
|
*/
|
|
|
|
test.describe('Basic Navigation', () => {
|
|
test('should load homepage without errors', async ({ page }) => {
|
|
// Navigate to homepage
|
|
const response = await page.goto('/');
|
|
|
|
// Page should load successfully
|
|
expect(response?.status()).toBe(200);
|
|
|
|
// Page should have a title
|
|
await expect(page).toHaveTitle(/GAMILIT|Gamificación/i);
|
|
|
|
// Page should not have console errors
|
|
const errors: string[] = [];
|
|
page.on('console', (msg) => {
|
|
if (msg.type() === 'error') {
|
|
errors.push(msg.text());
|
|
}
|
|
});
|
|
|
|
// Wait a bit for any async errors
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Should have minimal or no errors
|
|
expect(errors.length).toBeLessThan(5); // Allow for some dev warnings
|
|
});
|
|
|
|
test('should handle 404 pages', async ({ page }) => {
|
|
const response = await page.goto('/this-page-does-not-exist');
|
|
|
|
// Should either show 404 page or redirect
|
|
const url = page.url();
|
|
const statusCode = response?.status();
|
|
|
|
// Accept either a 404 status OR redirect to home/login
|
|
const isValid =
|
|
statusCode === 404 ||
|
|
url.includes('/login') ||
|
|
url === 'http://localhost:3005/';
|
|
|
|
expect(isValid).toBeTruthy();
|
|
});
|
|
|
|
test('should have responsive navigation menu', async ({ page }) => {
|
|
await page.goto('/');
|
|
|
|
// Check if navigation exists (either as sidebar or header)
|
|
const hasNav =
|
|
(await page.locator('nav').count()) > 0 ||
|
|
(await page.locator('[role="navigation"]').count()) > 0;
|
|
|
|
expect(hasNav).toBeTruthy();
|
|
});
|
|
|
|
test('should load without JavaScript errors', async ({ page }) => {
|
|
const jsErrors: Error[] = [];
|
|
|
|
page.on('pageerror', (error) => {
|
|
jsErrors.push(error);
|
|
});
|
|
|
|
await page.goto('/');
|
|
|
|
// Wait for page to fully load
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Should have no JavaScript errors
|
|
expect(jsErrors.length).toBe(0);
|
|
});
|
|
|
|
test('should have proper meta tags', async ({ page }) => {
|
|
await page.goto('/');
|
|
|
|
// Check for viewport meta tag (important for mobile)
|
|
const viewport = await page.locator('meta[name="viewport"]').getAttribute('content');
|
|
expect(viewport).toContain('width=device-width');
|
|
|
|
// Check for charset
|
|
const hasCharset =
|
|
(await page.locator('meta[charset]').count()) > 0 ||
|
|
(await page.locator('meta[charset="utf-8"]').count()) > 0 ||
|
|
(await page.locator('meta[charset="UTF-8"]').count()) > 0;
|
|
|
|
expect(hasCharset).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
test.describe('Protected Routes', () => {
|
|
test('should redirect to login when accessing protected routes unauthenticated', async ({
|
|
page,
|
|
}) => {
|
|
// Try to access a protected route (e.g., dashboard)
|
|
await page.goto('/dashboard');
|
|
|
|
// Should redirect to login page
|
|
await expect(page).toHaveURL(/\/login|\//);
|
|
});
|
|
|
|
test('should redirect to login when accessing student pages unauthenticated', async ({
|
|
page,
|
|
}) => {
|
|
await page.goto('/student/dashboard');
|
|
|
|
// Should redirect to login
|
|
await expect(page).toHaveURL(/\/login|\//);
|
|
});
|
|
|
|
test('should redirect to login when accessing teacher pages unauthenticated', async ({
|
|
page,
|
|
}) => {
|
|
await page.goto('/teacher/dashboard');
|
|
|
|
// Should redirect to login
|
|
await expect(page).toHaveURL(/\/login|\//);
|
|
});
|
|
});
|
|
|
|
test.describe('Performance', () => {
|
|
test('should load initial page within acceptable time', async ({ page }) => {
|
|
const startTime = Date.now();
|
|
|
|
await page.goto('/');
|
|
await page.waitForLoadState('domcontentloaded');
|
|
|
|
const loadTime = Date.now() - startTime;
|
|
|
|
// Should load within 5 seconds (generous for E2E)
|
|
expect(loadTime).toBeLessThan(5000);
|
|
});
|
|
|
|
test('should not have memory leaks on navigation', async ({ page }) => {
|
|
await page.goto('/');
|
|
|
|
// Navigate between pages multiple times
|
|
for (let i = 0; i < 3; i++) {
|
|
await page.goto('/');
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Try to navigate to other pages if they exist
|
|
const links = await page.locator('a[href^="/"]').all();
|
|
if (links.length > 0) {
|
|
await links[0].click();
|
|
await page.waitForLoadState('networkidle');
|
|
}
|
|
|
|
await page.goBack();
|
|
}
|
|
|
|
// If no errors thrown, test passes
|
|
expect(true).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
test.describe('Accessibility Basics', () => {
|
|
test('should have proper heading hierarchy', async ({ page }) => {
|
|
await page.goto('/');
|
|
|
|
// Check for h1
|
|
const h1Count = await page.locator('h1').count();
|
|
expect(h1Count).toBeGreaterThan(0);
|
|
expect(h1Count).toBeLessThanOrEqual(1); // Should have only one h1
|
|
});
|
|
|
|
test('should have skip to content link for keyboard users', async ({ page }) => {
|
|
await page.goto('/');
|
|
|
|
// Check for skip link (common accessibility practice)
|
|
const skipLink = page.locator('a[href="#main-content"], a[href="#content"]');
|
|
|
|
// Skip link may or may not be present, but if it is, it should work
|
|
if ((await skipLink.count()) > 0) {
|
|
await expect(skipLink).toBeAttached();
|
|
}
|
|
});
|
|
|
|
test('should be keyboard navigable', async ({ page }) => {
|
|
await page.goto('/');
|
|
|
|
// Press Tab key to navigate
|
|
await page.keyboard.press('Tab');
|
|
|
|
// Check if focus is visible somewhere
|
|
const focusedElement = await page.evaluate(() => {
|
|
return document.activeElement?.tagName;
|
|
});
|
|
|
|
// Should have moved focus to some element
|
|
expect(focusedElement).toBeTruthy();
|
|
});
|
|
});
|