336 lines
No EOL
11 KiB
TypeScript
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);
|
|
});
|
|
}); |