veza/tools/tests/e2e/specs/docs.spec.ts
2025-12-03 22:56:50 +01:00

336 lines
No EOL
11 KiB
TypeScript

import { test, expect } from '@playwright/test';
const DOCS_URL = process.env.DOCS_ORIGIN || 'https://docs.lab.veza';
test.describe('Documentation Site', () => {
test('should load documentation homepage', async ({ page }) => {
await page.goto(DOCS_URL);
// Check main elements
await expect(page.locator('h1, .hero__title')).toBeVisible();
// Check for Veza branding
await expect(page.locator('text=/veza/i')).toBeVisible();
// Check navigation
await expect(page.locator('nav, .navbar')).toBeVisible();
});
test('should have working navigation menu', async ({ page }) => {
await page.goto(DOCS_URL);
// Check for main nav items
const navItems = ['Getting Started', 'API', 'Guides', 'Reference'];
for (const item of navItems) {
const navLink = page.locator(`nav >> text=/${item}/i`);
if (await navLink.isVisible({ timeout: 2000 }).catch(() => false)) {
expect(navLink).toBeTruthy();
}
}
});
test('should load Getting Started page', async ({ page }) => {
await page.goto(DOCS_URL);
// Click Getting Started
const gettingStartedLink = page.locator('a:has-text("Getting Started")').first();
if (await gettingStartedLink.isVisible()) {
await gettingStartedLink.click();
// Should navigate to getting started
await expect(page).toHaveURL(/getting[_-]started|intro|quickstart/);
// Should have content
await expect(page.locator('h1, h2').first()).toBeVisible();
await expect(page.locator('main, .markdown')).toContainText(/install|setup|quick/i);
}
});
test('should have working search functionality', async ({ page }) => {
await page.goto(DOCS_URL);
// Find search button/input
const searchButton = page.locator('button[aria-label*="Search"], .DocSearch-Button');
if (await searchButton.isVisible()) {
await searchButton.click();
// Search modal should open
const searchInput = page.locator('input[placeholder*="Search"], .DocSearch-Input');
await expect(searchInput).toBeVisible();
// Type search query
await searchInput.fill('authentication');
// Results should appear
await expect(page.locator('.DocSearch-Hit, [role="option"]').first()).toBeVisible({ timeout: 5000 });
}
});
test('should have working sidebar navigation', async ({ page }) => {
await page.goto(`${DOCS_URL}/docs/intro`);
// Check for sidebar
const sidebar = page.locator('aside, .sidebar, nav[aria-label*="Docs"]');
if (await sidebar.isVisible()) {
// Should have multiple sections
const sections = await sidebar.locator('ul > li').count();
expect(sections).toBeGreaterThan(0);
// Click a sidebar item
const firstLink = sidebar.locator('a').nth(1); // Skip first which might be current
const linkText = await firstLink.textContent();
if (linkText) {
await firstLink.click();
// Should navigate
await expect(page.locator(`h1:has-text("${linkText}"), h2:has-text("${linkText}")`)).toBeVisible({ timeout: 5000 });
}
}
});
test('should have table of contents for long pages', async ({ page }) => {
await page.goto(`${DOCS_URL}/docs/intro`);
// Look for TOC
const toc = page.locator('.table-of-contents, .toc, nav[aria-label*="Table of contents"]');
if (await toc.isVisible()) {
// Should have links
const tocLinks = await toc.locator('a').count();
expect(tocLinks).toBeGreaterThan(0);
// Click a TOC link
const firstTocLink = toc.locator('a').first();
const href = await firstTocLink.getAttribute('href');
if (href?.startsWith('#')) {
await firstTocLink.click();
// Should scroll to section
const targetId = href.substring(1);
const targetElement = page.locator(`#${targetId}`);
await expect(targetElement).toBeInViewport();
}
}
});
test('should have working code examples', async ({ page }) => {
await page.goto(DOCS_URL);
// Navigate to a page with code examples
await page.goto(`${DOCS_URL}/docs/api/authentication`);
// Look for code blocks
const codeBlocks = page.locator('pre code, .prism-code');
if (await codeBlocks.first().isVisible({ timeout: 5000 }).catch(() => false)) {
// Check syntax highlighting
const firstBlock = codeBlocks.first();
const hasHighlighting = await firstBlock.locator('.token, .keyword, .string').count() > 0;
expect(hasHighlighting).toBeTruthy();
// Check for copy button
const copyButton = firstBlock.locator('xpath=ancestor::*').locator('button[aria-label*="Copy"], .copy-button').first();
if (await copyButton.isVisible()) {
await copyButton.click();
// Should show copied feedback
await expect(page.locator('text=/copied/i')).toBeVisible({ timeout: 2000 });
}
}
});
test('should have responsive design', async ({ page }) => {
await page.goto(DOCS_URL);
// Test mobile view
await page.setViewportSize({ width: 375, height: 667 });
// Mobile menu should be available
const mobileMenuButton = page.locator('button[aria-label*="Menu"], .navbar__toggle');
if (await mobileMenuButton.isVisible()) {
await mobileMenuButton.click();
// Mobile nav should open
const mobileNav = page.locator('.navbar__items--right, .navbar-sidebar');
await expect(mobileNav).toBeVisible();
}
// Reset viewport
await page.setViewportSize({ width: 1280, height: 720 });
});
test('should have dark mode toggle', async ({ page }) => {
await page.goto(DOCS_URL);
// Find theme toggle
const themeToggle = page.locator('button[aria-label*="Dark"], button[aria-label*="Theme"], .toggle_node');
if (await themeToggle.isVisible()) {
// Get initial theme
const initialTheme = await page.evaluate(() => {
return document.documentElement.getAttribute('data-theme') ||
document.documentElement.classList.contains('dark') ? 'dark' : 'light';
});
// Toggle theme
await themeToggle.click();
// Theme should change
const newTheme = await page.evaluate(() => {
return document.documentElement.getAttribute('data-theme') ||
document.documentElement.classList.contains('dark') ? 'dark' : 'light';
});
expect(newTheme).not.toBe(initialTheme);
}
});
test('should have version selector', async ({ page }) => {
await page.goto(DOCS_URL);
// Look for version dropdown
const versionSelector = page.locator('.navbar__item.dropdown, [aria-label*="Version"]');
if (await versionSelector.isVisible()) {
await versionSelector.click();
// Should show version options
const versionOptions = page.locator('.dropdown__menu a, [role="menuitem"]');
const optionCount = await versionOptions.count();
expect(optionCount).toBeGreaterThan(0);
}
});
test('should have language selector', async ({ page }) => {
await page.goto(DOCS_URL);
// Look for language selector
const langSelector = page.locator('[aria-label*="Language"], .navbar__item.dropdown:has-text("EN")');
if (await langSelector.isVisible()) {
await langSelector.click();
// Should show language options
const langOptions = page.locator('.dropdown__menu a, [role="menuitem"]');
if (await langOptions.first().isVisible()) {
const optionCount = await langOptions.count();
expect(optionCount).toBeGreaterThan(0);
}
}
});
test('should have working footer links', async ({ page }) => {
await page.goto(DOCS_URL);
// Scroll to footer
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
// Check footer links
const footer = page.locator('footer');
if (await footer.isVisible()) {
// Check for common footer links
const footerLinks = ['GitHub', 'Discord', 'Twitter', 'Privacy', 'Terms'];
for (const linkText of footerLinks) {
const link = footer.locator(`a:has-text("${linkText}")`);
if (await link.isVisible({ timeout: 1000 }).catch(() => false)) {
const href = await link.getAttribute('href');
expect(href).toBeTruthy();
}
}
}
});
test('should handle 404 pages gracefully', async ({ page }) => {
await page.goto(`${DOCS_URL}/non-existent-page-404`);
// Should show 404 page
await expect(page.locator('text=/404|not found|page.*not.*exist/i')).toBeVisible();
// Should have link back to home
const homeLink = page.locator('a:has-text("Home"), a:has-text("Back")');
await expect(homeLink).toBeVisible();
});
test('should have API reference section', async ({ page }) => {
await page.goto(DOCS_URL);
// Navigate to API reference
const apiLink = page.locator('a:has-text("API"), a:has-text("Reference")').first();
if (await apiLink.isVisible()) {
await apiLink.click();
// Should show API documentation
await expect(page.locator('h1, h2').filter({ hasText: /API|Reference/ })).toBeVisible();
// Should have endpoint documentation
await expect(page.locator('text=/GET|POST|PUT|DELETE/')).toBeVisible();
}
});
test('should handle anchor links', async ({ page }) => {
await page.goto(`${DOCS_URL}/docs/intro`);
// Find a heading with an anchor
const heading = page.locator('h2[id], h3[id]').first();
if (await heading.isVisible()) {
const headingId = await heading.getAttribute('id');
if (headingId) {
// Navigate to anchor
await page.goto(`${DOCS_URL}/docs/intro#${headingId}`);
// Heading should be in viewport
await expect(heading).toBeInViewport();
}
}
});
test('should have edit on GitHub link', async ({ page }) => {
await page.goto(`${DOCS_URL}/docs/intro`);
// Look for edit link
const editLink = page.locator('a:has-text("Edit"), a[href*="github.com"][href*="edit"]');
if (await editLink.isVisible()) {
const href = await editLink.getAttribute('href');
expect(href).toContain('github.com');
expect(href).toContain('edit');
}
});
test('should load quickly', async ({ page }) => {
const startTime = Date.now();
await page.goto(DOCS_URL, { waitUntil: 'domcontentloaded' });
const loadTime = Date.now() - startTime;
// Should load in under 3 seconds
expect(loadTime).toBeLessThan(3000);
// Check for performance metrics
const metrics = await page.evaluate(() => {
const nav = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;
return {
domContentLoaded: nav.domContentLoadedEventEnd - nav.domContentLoadedEventStart,
loadComplete: nav.loadEventEnd - nav.loadEventStart,
};
});
expect(metrics.domContentLoaded).toBeLessThan(1000);
});
});