Dev Diary: Building godegit.dev - Phase 2: Test-Driven Development in Practice
How we implemented comprehensive Test-Driven Development for our website build, writing failing tests first for brand compliance, performance, and accessibility. Learn our approach to TDD with Playwright and Vitest in a real-world project.


All tests failing as expected - the foundation of proper TDD
Test-Driven Development: Making Failure a Feature
Continuing our godegit.dev development diary, Phase 2 focuses on something most developers either love or dread: testing. But this isn't just about testing—it's about Test-Driven Development (TDD) done right, where failing tests become your roadmap to success.
Why TDD for a Marketing Website?
You might wonder: "Why use TDD for a marketing website? Isn't that overkill?" Our experience building godegit taught us that seemingly simple tools often hide complex requirements. A marketing website needs to:
- Maintain strict brand compliance across all components
- Deliver excellent performance for user experience and SEO
- Be accessible to all users regardless of ability
- Work consistently across devices and browsers
- Handle content management without breaking
Traditional development often catches these requirements too late. TDD ensures we build exactly what we need, nothing more, nothing less.
Our TDD Philosophy: Red, Green, Refactor
Red: Write a failing test that describes the desired behavior Green: Write the minimal code to make the test pass Refactor: Improve the code while keeping tests green
But we added a fourth step specific to our brand requirements: Validate: Ensure the implementation maintains brand compliance
Setting Up the Testing Infrastructure
Playwright for End-to-End Testing
Playwright became our choice for E2E testing because of its excellent developer experience and cross-browser support:
// playwright.config.ts
export default defineConfig({
testDir: './tests/e2e',
fullyParallel: true,
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
{ name: 'Mobile Chrome', use: { ...devices['Pixel 5'] } },
{ name: 'Mobile Safari', use: { ...devices['iPhone 12'] } },
{ name: 'Tablet', use: { ...devices['iPad Pro'] } },
],
webServer: {
command: 'pnpm dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
})
Vitest for Unit Testing
For component testing, Vitest's integration with Vue and its speed made it perfect:
// vitest.config.ts
export default defineConfig({
plugins: [vue()],
test: {
environment: 'jsdom',
setupFiles: ['./tests/setup.ts'],
globals: true,
},
resolve: {
alias: {
'@': path.resolve(__dirname, '.'),
'~': path.resolve(__dirname, '.'),
},
},
})
Phase 2 Task Breakdown: 13 Failing Tests
We wrote 13 specific tests that had to fail before we could start implementing:
E2E Tests (8 Tests)
1. Brand Compliance Test
// tests/e2e/brand-compliance.spec.ts
test('should use exact brand colors', async ({ page }) => {
await page.goto('/')
// Test brand dark color (#1A1A1A)
const darkElements = page.locator('[class*="text-brand-dark"]')
await expect(darkElements.first()).toHaveCSS('color', 'rgb(26, 26, 26)')
// Test brand accent color (#2196F3)
const accentElements = page.locator('[class*="text-brand-accent"]')
await expect(accentElements.first()).toHaveCSS('color', 'rgb(33, 150, 243)')
})
test('should use brand typography system', async ({ page }) => {
await page.goto('/')
// Test heading font family
const headings = page.locator('h1, h2, h3')
await expect(headings.first()).toHaveCSS('font-family', /Inter/)
// Test code font family
const codeElements = page.locator('code, pre')
if ((await codeElements.count()) > 0) {
await expect(codeElements.first()).toHaveCSS('font-family', /mono/)
}
})
2. Performance Test
// tests/e2e/performance.spec.ts
test('should meet Core Web Vitals standards', async ({ page }) => {
await page.goto('/')
// Measure performance
const performanceEntries = await page.evaluate(() => {
return JSON.stringify(performance.getEntriesByType('navigation'))
})
const navigation = JSON.parse(performanceEntries)[0]
const loadTime = navigation.loadEventEnd - navigation.loadEventStart
// Should load in under 1 second
expect(loadTime).toBeLessThan(1000)
})
3. Accessibility Test
// tests/e2e/accessibility.spec.ts
test('should meet WCAG 2.1 AA standards', async ({ page }) => {
await page.goto('/')
// Test focus indicators
await page.keyboard.press('Tab')
const focusedElement = page.locator(':focus')
await expect(focusedElement).toHaveCSS('outline-width', '2px')
// Test heading hierarchy
const headings = await page.locator('h1, h2, h3, h4, h5, h6').all()
expect(headings.length).toBeGreaterThan(0)
// First heading should be h1
await expect(page.locator('h1').first()).toBeVisible()
})
Unit Tests (5 Tests)
4. Hero Component Test
// tests/unit/components/Marketing/Hero.spec.ts
test('should render with brand compliance', () => {
expect(() => {
mount(Hero)
}).toThrow()
// When implemented, test should verify:
// - Brand color usage in text and backgrounds
// - Proper heading hierarchy (h1 for main title)
// - Brand typography implementation
// - Copy-to-clipboard functionality
// - Responsive design behavior
})
The Beautiful Failure: All Tests Red
After writing all 13 tests, we ran them:
$ pnpm test
FAIL tests/e2e/brand-compliance.spec.ts
FAIL tests/e2e/landing-page.spec.ts
FAIL tests/e2e/documentation.spec.ts
FAIL tests/e2e/blog.spec.ts
FAIL tests/e2e/changelog.spec.ts
FAIL tests/e2e/responsive.spec.ts
FAIL tests/e2e/performance.spec.ts
FAIL tests/e2e/accessibility.spec.ts
FAIL tests/unit/components/Marketing/Hero.spec.ts
FAIL tests/unit/components/Marketing/Features.spec.ts
FAIL tests/unit/components/Marketing/Installation.spec.ts
FAIL tests/unit/components/AppHeader.spec.ts
FAIL tests/unit/components/Content/DocNav.spec.ts
Test Files 13 failed (13)
Tests 21 failed (21)
Perfect! This is exactly what we wanted. Every test failed because we hadn't implemented anything yet. These failures became our implementation checklist.
Advanced Testing Patterns We Developed
1. Brand Compliance Testing Pattern
We created reusable functions for testing brand compliance:
// tests/utils/brand-testing.ts
export async function testBrandColors(page: Page) {
const colorTests = [
{
selector: '[class*="bg-brand-dark"]',
property: 'background-color',
expected: 'rgb(26, 26, 26)',
},
{
selector: '[class*="text-brand-accent"]',
property: 'color',
expected: 'rgb(33, 150, 243)',
},
]
for (const test of colorTests) {
const elements = page.locator(test.selector)
if ((await elements.count()) > 0) {
await expect(elements.first()).toHaveCSS(test.property, test.expected)
}
}
}
export async function testBrandTypography(page: Page) {
// Test heading fonts
const headings = page.locator('h1, h2, h3, h4, h5, h6')
if ((await headings.count()) > 0) {
await expect(headings.first()).toHaveCSS('font-family', /Inter|Lato/)
}
}
2. Performance Testing Integration
We integrated real performance metrics into our tests:
// tests/utils/performance.ts
export async function measurePagePerformance(page: Page) {
const performanceMetrics = await page.evaluate(() => {
const perfData = performance.getEntriesByType(
'navigation'
)[0] as PerformanceNavigationTiming
return {
domContentLoaded:
perfData.domContentLoadedEventEnd - perfData.domContentLoadedEventStart,
loadComplete: perfData.loadEventEnd - perfData.loadEventStart,
firstPaint:
performance
.getEntriesByType('paint')
.find(p => p.name === 'first-paint')?.startTime || 0,
}
})
return performanceMetrics
}
3. Responsive Design Testing
We automated responsive design testing across multiple device sizes:
// tests/e2e/responsive.spec.ts
const viewports = [
{ name: 'Mobile', width: 375, height: 667 },
{ name: 'Tablet', width: 768, height: 1024 },
{ name: 'Desktop', width: 1280, height: 720 },
{ name: 'Large Desktop', width: 1920, height: 1080 },
]
for (const viewport of viewports) {
test(`should be responsive on ${viewport.name}`, async ({ page }) => {
await page.setViewportSize(viewport)
await page.goto('/')
// Test mobile menu visibility
if (viewport.width < 768) {
await expect(page.locator('[data-testid="menu-toggle"]')).toBeVisible()
await expect(page.locator('.desktop-nav')).toBeHidden()
} else {
await expect(page.locator('[data-testid="menu-toggle"]')).toBeHidden()
await expect(page.locator('.desktop-nav')).toBeVisible()
}
})
}
Challenges and Solutions
Challenge 1: Testing Before Implementation
Problem: How do you write meaningful tests for components that don't exist?
Solution: We focused on testing contracts and behaviors rather than implementation details:
// Instead of testing internal structure
test('should have correct DOM structure', () => {
// This would break with any refactoring
})
// We test behavior and user-visible outcomes
test('should display installation command when copy button is clicked', () => {
// This test cares about user experience, not implementation
})
Challenge 2: Brand Compliance Automation
Problem: How do you automate subjective design requirements?
Solution: We translated brand guidelines into measurable criteria:
// Brand guideline: "Use brand colors consistently"
// Becomes: Test that all brand color classes render correct RGB values
// Brand guideline: "Maintain visual hierarchy"
// Becomes: Test heading tag sequence (h1 -> h2 -> h3)
// Brand guideline: "Ensure accessibility"
// Becomes: Test focus indicators, color contrast, keyboard navigation
Challenge 3: Performance Testing Reliability
Problem: Performance tests can be flaky due to system variations.
Solution: We used relative performance goals and multiple measurements:
test('should perform consistently', async ({ page }) => {
const measurements = []
// Take multiple measurements
for (let i = 0; i < 3; i++) {
await page.reload()
const metrics = await measurePagePerformance(page)
measurements.push(metrics.loadComplete)
}
// Test average performance
const average = measurements.reduce((a, b) => a + b) / measurements.length
expect(average).toBeLessThan(1000)
// Test consistency (no measurement should be 3x average)
measurements.forEach(measurement => {
expect(measurement).toBeLessThan(average * 3)
})
})
The TDD Payoff
By the end of Phase 2, we had:
- 21 failing tests that defined exactly what we needed to build
- Clear acceptance criteria for every component and page
- Automated brand compliance checking
- Performance benchmarks built into our workflow
- Accessibility requirements as first-class concerns
These tests became our implementation roadmap. Each subsequent phase would focus on turning red tests green while maintaining the quality standards we'd established.
What's Next: Phase 3 - Component Implementation
In our next dev diary entry, we'll show how these failing tests guided our component implementation. You'll see how TDD influenced our architectural decisions and how we achieved 100% test pass rates while building a maintainable codebase.
Coming up in Phase 3:
- Building components to satisfy failing tests
- Brand system implementation in Vue components
- Performance optimization guided by test metrics
- Accessibility features driven by test requirements
Key TDD Takeaways
- Failing Tests Are Features: They define your requirements precisely
- Test Behaviors, Not Implementation: Focus on what users experience
- Automate Subjective Requirements: Turn design guidelines into measurable criteria
- Performance is a Feature: Make speed a testable requirement
- Accessibility from Day One: Build inclusive design into your test suite
TDD isn't just about testing—it's about thoughtful design and clear communication of intent. When done right, your tests become living documentation of what your application should do and how it should behave.
Next time, we'll dive into the exciting part: making all these tests pass while building beautiful, performant components.
Full test suite: View our complete test implementation on GitHub

