|
5 | 5 | * navigation works, and key features like code blocks display properly. |
6 | 6 | */ |
7 | 7 |
|
8 | | -import { test, expect } from '@playwright/test' |
| 8 | +export {} |
9 | 9 |
|
10 | | -test.describe('Documentation Pages', { tag: '@docs' }, () => { |
11 | | - test.describe('Doc Landing Page', () => { |
12 | | - test('loads the docs index page', async ({ page }) => { |
13 | | - await page.goto('/docs') |
| 10 | +const isBun = typeof Bun !== 'undefined' |
14 | 11 |
|
15 | | - // Should have documentation content or redirect to first doc |
16 | | - await expect(page).toHaveURL(/\/docs/) |
17 | | - }) |
18 | | - |
19 | | - test('has working navigation sidebar on desktop', async ({ page }) => { |
20 | | - // Set desktop viewport |
21 | | - await page.setViewportSize({ width: 1280, height: 720 }) |
22 | | - await page.goto('/docs/help/quick-start') |
| 12 | +if (isBun) { |
| 13 | + const { describe, it } = await import('bun:test') |
23 | 14 |
|
24 | | - // Sidebar should be visible on desktop |
25 | | - const sidebar = page.locator('[class*="lg:block"]').first() |
26 | | - await expect(sidebar).toBeVisible() |
27 | | - }) |
| 15 | + describe.skip('playwright-only', () => { |
| 16 | + it('skipped under bun test runner', () => {}) |
28 | 17 | }) |
29 | | - |
30 | | - test.describe('Quick Start Page', () => { |
31 | | - test.beforeEach(async ({ page }) => { |
32 | | - await page.goto('/docs/help/quick-start') |
33 | | - }) |
34 | | - |
35 | | - test('renders the page title', async ({ page }) => { |
36 | | - // Page should have a heading |
37 | | - const heading = page.locator('h1').first() |
38 | | - await expect(heading).toBeVisible() |
39 | | - await expect(heading).toContainText(/start|codebuff/i) |
40 | | - }) |
41 | | - |
42 | | - test('renders code blocks with syntax highlighting', async ({ page }) => { |
43 | | - // Should have code blocks |
44 | | - const codeBlocks = page.locator('pre code, [class*="prism"]') |
45 | | - const count = await codeBlocks.count() |
46 | | - expect(count).toBeGreaterThan(0) |
47 | | - }) |
48 | | - |
49 | | - test('has working internal links', async ({ page }) => { |
50 | | - // Find an internal link |
51 | | - const internalLinks = page.locator('article a[href^="/docs/"]') |
52 | | - const count = await internalLinks.count() |
53 | | - |
54 | | - if (count > 0) { |
55 | | - const firstLink = internalLinks.first() |
56 | | - const href = await firstLink.getAttribute('href') |
57 | | - |
58 | | - // Click and verify navigation |
59 | | - await firstLink.click() |
60 | | - await expect(page).toHaveURL( |
61 | | - new RegExp(href!.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), |
| 18 | +} else { |
| 19 | + const { test, expect } = await import('@playwright/test') |
| 20 | + |
| 21 | + test.describe('Documentation Pages', { tag: '@docs' }, () => { |
| 22 | + test.describe('Doc Landing Page', () => { |
| 23 | + test('loads the docs index page', async ({ page }) => { |
| 24 | + await page.goto('/docs') |
| 25 | + |
| 26 | + // Should have documentation content or redirect to first doc |
| 27 | + await expect(page).toHaveURL(/\/docs/) |
| 28 | + }) |
| 29 | + |
| 30 | + test('has working navigation sidebar on desktop', async ({ page }) => { |
| 31 | + // Set desktop viewport |
| 32 | + await page.setViewportSize({ width: 1280, height: 720 }) |
| 33 | + await page.goto('/docs/help/quick-start') |
| 34 | + |
| 35 | + // Sidebar should be visible on desktop |
| 36 | + const sidebar = page.locator('[class*="lg:block"]').first() |
| 37 | + await expect(sidebar).toBeVisible() |
| 38 | + }) |
| 39 | + }) |
| 40 | + |
| 41 | + test.describe('Quick Start Page', () => { |
| 42 | + test.beforeEach(async ({ page }) => { |
| 43 | + await page.goto('/docs/help/quick-start') |
| 44 | + }) |
| 45 | + |
| 46 | + test('renders the page title', async ({ page }) => { |
| 47 | + // Page should have a heading |
| 48 | + const heading = page.locator('h1').first() |
| 49 | + await expect(heading).toBeVisible() |
| 50 | + await expect(heading).toContainText(/start|codebuff/i) |
| 51 | + }) |
| 52 | + |
| 53 | + test('renders code blocks with syntax highlighting', async ({ page }) => { |
| 54 | + // Should have code blocks |
| 55 | + const codeBlocks = page.locator('pre code, [class*="prism"]') |
| 56 | + const count = await codeBlocks.count() |
| 57 | + expect(count).toBeGreaterThan(0) |
| 58 | + }) |
| 59 | + |
| 60 | + test('has working internal links', async ({ page }) => { |
| 61 | + // Find an internal link |
| 62 | + const internalLinks = page.locator('article a[href^="/docs/"]') |
| 63 | + const count = await internalLinks.count() |
| 64 | + |
| 65 | + if (count > 0) { |
| 66 | + const firstLink = internalLinks.first() |
| 67 | + const href = await firstLink.getAttribute('href') |
| 68 | + |
| 69 | + // Click and verify navigation |
| 70 | + await firstLink.click() |
| 71 | + await expect(page).toHaveURL( |
| 72 | + new RegExp(href!.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), |
| 73 | + ) |
| 74 | + } |
| 75 | + }) |
| 76 | + }) |
| 77 | + |
| 78 | + test.describe('Navigation', () => { |
| 79 | + test('prev/next navigation works', async ({ page }) => { |
| 80 | + await page.goto('/docs/help/quick-start') |
| 81 | + |
| 82 | + // Look for next button |
| 83 | + const nextButton = page.locator( |
| 84 | + 'a:has-text("Next"), a[href*="/docs/"]:has(svg)', |
62 | 85 | ) |
63 | | - } |
64 | | - }) |
65 | | - }) |
| 86 | + const count = await nextButton.count() |
66 | 87 |
|
67 | | - test.describe('Navigation', () => { |
68 | | - test('prev/next navigation works', async ({ page }) => { |
69 | | - await page.goto('/docs/help/quick-start') |
| 88 | + if (count > 0) { |
| 89 | + const initialUrl = page.url() |
| 90 | + await nextButton.first().click() |
70 | 91 |
|
71 | | - // Look for next button |
72 | | - const nextButton = page.locator( |
73 | | - 'a:has-text("Next"), a[href*="/docs/"]:has(svg)', |
74 | | - ) |
75 | | - const count = await nextButton.count() |
| 92 | + // Should navigate to a different page |
| 93 | + await page.waitForURL((url) => url.toString() !== initialUrl) |
| 94 | + } |
| 95 | + }) |
76 | 96 |
|
77 | | - if (count > 0) { |
78 | | - const initialUrl = page.url() |
79 | | - await nextButton.first().click() |
| 97 | + test('category pages load', async ({ page }) => { |
| 98 | + const categories = ['help', 'tips', 'advanced', 'agents'] |
80 | 99 |
|
81 | | - // Should navigate to a different page |
82 | | - await page.waitForURL((url) => url.toString() !== initialUrl) |
83 | | - } |
| 100 | + for (const category of categories) { |
| 101 | + const response = await page.goto(`/docs/${category}`) |
| 102 | + // Should either load successfully or redirect |
| 103 | + expect(response?.status()).toBeLessThan(500) |
| 104 | + } |
| 105 | + }) |
84 | 106 | }) |
85 | 107 |
|
86 | | - test('category pages load', async ({ page }) => { |
87 | | - const categories = ['help', 'tips', 'advanced', 'agents'] |
| 108 | + test.describe('Content Rendering', () => { |
| 109 | + test('FAQ page renders correctly', async ({ page }) => { |
| 110 | + await page.goto('/docs/help/faq') |
88 | 111 |
|
89 | | - for (const category of categories) { |
90 | | - const response = await page.goto(`/docs/${category}`) |
91 | | - // Should either load successfully or redirect |
92 | | - expect(response?.status()).toBeLessThan(500) |
93 | | - } |
94 | | - }) |
95 | | - }) |
| 112 | + // FAQ page should have questions |
| 113 | + const heading = page.locator('h1, h2').first() |
| 114 | + await expect(heading).toBeVisible() |
| 115 | + }) |
96 | 116 |
|
97 | | - test.describe('Content Rendering', () => { |
98 | | - test('FAQ page renders correctly', async ({ page }) => { |
99 | | - await page.goto('/docs/help/faq') |
| 117 | + test('best practices page renders', async ({ page }) => { |
| 118 | + await page.goto('/docs/tips/best-practices') |
100 | 119 |
|
101 | | - // FAQ page should have questions |
102 | | - const heading = page.locator('h1, h2').first() |
103 | | - await expect(heading).toBeVisible() |
104 | | - }) |
| 120 | + const heading = page.locator('h1').first() |
| 121 | + await expect(heading).toBeVisible() |
| 122 | + await expect(heading).toContainText(/best practices/i) |
| 123 | + }) |
105 | 124 |
|
106 | | - test('best practices page renders', async ({ page }) => { |
107 | | - await page.goto('/docs/tips/best-practices') |
| 125 | + test('agents overview renders mermaid diagrams or code', async ({ |
| 126 | + page, |
| 127 | + }) => { |
| 128 | + await page.goto('/docs/agents/overview') |
108 | 129 |
|
109 | | - const heading = page.locator('h1').first() |
110 | | - await expect(heading).toBeVisible() |
111 | | - await expect(heading).toContainText(/best practices/i) |
112 | | - }) |
| 130 | + // Should have either mermaid diagram or code block for the flowchart |
| 131 | + const mermaidOrCode = page.locator( |
| 132 | + '.mermaid, pre:has-text("flowchart"), [class*="mermaid"]', |
| 133 | + ) |
| 134 | + const count = await mermaidOrCode.count() |
113 | 135 |
|
114 | | - test('agents overview renders mermaid diagrams or code', async ({ |
115 | | - page, |
116 | | - }) => { |
117 | | - await page.goto('/docs/agents/overview') |
118 | | - |
119 | | - // Should have either mermaid diagram or code block for the flowchart |
120 | | - const mermaidOrCode = page.locator( |
121 | | - '.mermaid, pre:has-text("flowchart"), [class*="mermaid"]', |
122 | | - ) |
123 | | - const count = await mermaidOrCode.count() |
124 | | - |
125 | | - // Page should at least render without errors - mermaid may or may not render in test env |
126 | | - // We verify the page loaded by checking for the heading instead |
127 | | - const heading = page.locator('h1').first() |
128 | | - await expect(heading).toBeVisible() |
| 136 | + // Page should at least render without errors - mermaid may or may not render in test env |
| 137 | + // We verify the page loaded by checking for the heading instead |
| 138 | + const heading = page.locator('h1').first() |
| 139 | + await expect(heading).toBeVisible() |
| 140 | + }) |
129 | 141 | }) |
130 | | - }) |
131 | 142 |
|
132 | | - test.describe('Mobile Navigation', () => { |
133 | | - test('mobile menu button appears on small screens', async ({ page }) => { |
134 | | - // Set mobile viewport |
135 | | - await page.setViewportSize({ width: 375, height: 667 }) |
136 | | - await page.goto('/docs/help/quick-start') |
137 | | - |
138 | | - // Should have a mobile menu trigger (bottom sheet or hamburger) |
139 | | - const mobileMenu = page |
140 | | - .locator('button:has(svg), [class*="lg:hidden"]') |
141 | | - .first() |
142 | | - await expect(mobileMenu).toBeVisible() |
| 143 | + test.describe('Mobile Navigation', () => { |
| 144 | + test('mobile menu button appears on small screens', async ({ page }) => { |
| 145 | + // Set mobile viewport |
| 146 | + await page.setViewportSize({ width: 375, height: 667 }) |
| 147 | + await page.goto('/docs/help/quick-start') |
| 148 | + |
| 149 | + // Should have a mobile menu trigger (bottom sheet or hamburger) |
| 150 | + const mobileMenu = page |
| 151 | + .locator('button:has(svg), [class*="lg:hidden"]') |
| 152 | + .first() |
| 153 | + await expect(mobileMenu).toBeVisible() |
| 154 | + }) |
143 | 155 | }) |
144 | | - }) |
145 | 156 |
|
146 | | - test.describe('Accessibility', () => { |
147 | | - test('doc pages have proper heading hierarchy', async ({ page }) => { |
148 | | - await page.goto('/docs/help/quick-start') |
| 157 | + test.describe('Accessibility', () => { |
| 158 | + test('doc pages have proper heading hierarchy', async ({ page }) => { |
| 159 | + await page.goto('/docs/help/quick-start') |
149 | 160 |
|
150 | | - // Should have an h1 |
151 | | - const h1Count = await page.locator('h1').count() |
152 | | - expect(h1Count).toBeGreaterThanOrEqual(1) |
| 161 | + // Should have an h1 |
| 162 | + const h1Count = await page.locator('h1').count() |
| 163 | + expect(h1Count).toBeGreaterThanOrEqual(1) |
153 | 164 |
|
154 | | - // h1 should come before h2s in the main content |
155 | | - const headings = await page |
156 | | - .locator('article h1, article h2, article h3') |
157 | | - .allTextContents() |
158 | | - expect(headings.length).toBeGreaterThan(0) |
159 | | - }) |
| 165 | + // h1 should come before h2s in the main content |
| 166 | + const headings = await page |
| 167 | + .locator('article h1, article h2, article h3') |
| 168 | + .allTextContents() |
| 169 | + expect(headings.length).toBeGreaterThan(0) |
| 170 | + }) |
160 | 171 |
|
161 | | - test('links have discernible text', async ({ page }) => { |
162 | | - await page.goto('/docs/help/quick-start') |
| 172 | + test('links have discernible text', async ({ page }) => { |
| 173 | + await page.goto('/docs/help/quick-start') |
163 | 174 |
|
164 | | - const links = page.locator('article a') |
165 | | - const count = await links.count() |
| 175 | + const links = page.locator('article a') |
| 176 | + const count = await links.count() |
166 | 177 |
|
167 | | - for (let i = 0; i < Math.min(count, 10); i++) { |
168 | | - const link = links.nth(i) |
169 | | - const text = await link.textContent() |
170 | | - const ariaLabel = await link.getAttribute('aria-label') |
| 178 | + for (let i = 0; i < Math.min(count, 10); i++) { |
| 179 | + const link = links.nth(i) |
| 180 | + const text = await link.textContent() |
| 181 | + const ariaLabel = await link.getAttribute('aria-label') |
171 | 182 |
|
172 | | - // Link should have either text content or aria-label |
173 | | - const hasDiscernibleText = (text && text.trim().length > 0) || ariaLabel |
174 | | - expect(hasDiscernibleText).toBeTruthy() |
175 | | - } |
| 183 | + // Link should have either text content or aria-label |
| 184 | + const hasDiscernibleText = (text && text.trim().length > 0) || ariaLabel |
| 185 | + expect(hasDiscernibleText).toBeTruthy() |
| 186 | + } |
| 187 | + }) |
176 | 188 | }) |
177 | | - }) |
178 | 189 |
|
179 | | - test.describe('SEO', () => { |
180 | | - test('doc pages have meta description', async ({ page }) => { |
181 | | - await page.goto('/docs/help/quick-start') |
| 190 | + test.describe('SEO', () => { |
| 191 | + test('doc pages have meta description', async ({ page }) => { |
| 192 | + await page.goto('/docs/help/quick-start') |
182 | 193 |
|
183 | | - const metaDescription = page.locator('meta[name="description"]') |
184 | | - const content = await metaDescription.getAttribute('content') |
| 194 | + const metaDescription = page.locator('meta[name="description"]') |
| 195 | + const content = await metaDescription.getAttribute('content') |
185 | 196 |
|
186 | | - // Should have some description |
187 | | - expect(content).toBeTruthy() |
188 | | - }) |
| 197 | + // Should have some description |
| 198 | + expect(content).toBeTruthy() |
| 199 | + }) |
189 | 200 |
|
190 | | - test('doc pages have proper title', async ({ page }) => { |
191 | | - await page.goto('/docs/help/quick-start') |
| 201 | + test('doc pages have proper title', async ({ page }) => { |
| 202 | + await page.goto('/docs/help/quick-start') |
192 | 203 |
|
193 | | - const title = await page.title() |
194 | | - expect(title.length).toBeGreaterThan(0) |
195 | | - expect(title).not.toBe('undefined') |
| 204 | + const title = await page.title() |
| 205 | + expect(title.length).toBeGreaterThan(0) |
| 206 | + expect(title).not.toBe('undefined') |
| 207 | + }) |
196 | 208 | }) |
197 | 209 | }) |
198 | | -}) |
| 210 | +} |
0 commit comments