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); }); });