How Tests Are Organized in the open-seo Project: Co-Located Unit Tests and Dedicated E2E Suites
The open-seo project organizes tests using a co-location strategy where unit and integration tests (.test.ts) live alongside source files in src/, while end-to-end tests (.spec.ts) reside in a dedicated e2e/ folder, powered by Vitest and Playwright respectively.
Understanding how tests are organized in the every-app/open-seo repository is essential for contributing effectively. The codebase maintains a clear separation between fast unit checks and comprehensive browser automation, with specific naming conventions and directory structures that scale with the application's growth.
Unit and Integration Tests: Co-Located with Source Code
The project places unit and integration tests directly next to the modules they verify, using Vitest as the test runner to ensure fast feedback during development.
File Pattern and Location
Tests use the *.test.ts naming convention and mirror the production folder hierarchy inside src/. When you open a source file, its corresponding test file sits in the same directory. For example, the MCP transport layer implementation at src/server/mcp/transport.ts has its corresponding test file at src/server/mcp/transport.test.ts. Similarly, business-logic services like src/server/features/keywords/services/research/saved-keywords.test.ts and low-level utilities such as src/server/lib/audit/url-utils.test.ts follow this pattern, as do React components and server helpers throughout the tree.
Framework and Execution
Vitest provides a Jest-compatible API (describe, it, expect) running in a Node.js environment. This setup validates pure-logic functions, database helpers, and API utilities without spinning up a browser. Execute the suite with:
# Run all unit tests once
pnpm test
# Watch mode for development
pnpm test:watch
Co-location keeps tests discoverable—developers see specifications immediately when navigating to a module—allowing the repository to scale without a massive, flat tests/ directory at the root.
End-to-End Tests: Browser Automation in e2e/
For full-stack validation, open-seo uses Playwright specifications stored in a top-level e2e/ folder, separated from the unit test suite to reflect their broader scope.
Directory Structure and File Pattern
E2E tests use the *.spec.ts pattern and reside exclusively under e2e/. Examples include e2e/keyword-research-navigation.spec.ts for keyword research user flows and e2e/domain-overview-filters.spec.ts for domain analytics validation. Performance-focused variants like domain-overview-filters.perf.spec.ts also live here, asserting timing thresholds under realistic conditions.
Framework and Execution
Playwright handles browser automation and assertions, configured via playwright.config.ts at the repository root. These tests spin up the entire application (via wrangler dev) to exercise user-facing interactions, including navigation, filtering, and data fetching with mocked DataForSEO responses.
Run E2E tests selectively or in full:
# Run all E2E specs
pnpm test:e2e
# Run only the domain overview filter suite
pnpm test:e2e:domain
# Run performance-specific tests
pnpm test:e2e:domain:perf
Test Scripts and Configuration
The scripts section in package.json defines clear entry points for both test layers, enabling straightforward CI integration and local development:
{
"scripts": {
"test": "vitest run",
"test:watch": "vitest",
"test:ci": "vitest run --reporter=dot",
"test:e2e": "playwright test",
"test:e2e:domain": "playwright test e2e/domain-overview-filters.spec.ts",
"test:e2e:domain:perf": "playwright test e2e/domain-overview-filters.perf.spec.ts",
"test:e2e:keywords": "playwright test e2e/keyword-research-navigation.spec.ts"
}
}
The CI pipeline defined in .github/workflows/ci.yml invokes these scripts to ensure both Vitest and Playwright suites pass on every pull request merge.
Adding New Tests to the Codebase
Creating a Unit Test
When adding a utility like src/server/lib/audit/url-utils.ts, create a sibling file:
// src/server/lib/audit/url-utils.test.ts
import { normalizeUrl } from './url-utils';
describe('normalizeUrl', () => {
it('removes trailing slashes', () => {
expect(normalizeUrl('https://example.com/')).toBe('https://example.com');
});
});
Vitest discovers this file automatically via the *.test.ts pattern when running pnpm test.
Creating an E2E Scenario
For new dashboard functionality, add a Playwright spec:
// e2e/dashboard-analytics.spec.ts
import { test, expect } from '@playwright/test';
test('dashboard displays analytics chart', async ({ page }) => {
await page.goto('http://localhost:3000/dashboard');
await expect(page.locator('#analytics-chart')).toBeVisible();
});
Execute with pnpm test:e2e to include the new spec in the full browser automation suite.
Summary
- Co-location strategy: Unit tests (
*.test.ts) live next to source files insrc/, while E2E tests (*.spec.ts) reside ine2e/. - Vitest powers unit tests: Provides fast, Jest-compatible execution for server utilities, React components, and business logic located alongside the code they verify.
- Playwright handles E2E: Manages browser automation for full-stack user flows like keyword research navigation and domain overview filtering, spinning up the full application via
wrangler dev. - NPM scripts unify execution:
pnpm testruns Vitest,pnpm test:e2eruns Playwright, with granular scripts available for specific suites such astest:e2e:domain. - Key configuration files:
package.jsondefines scripts,playwright.config.tsconfigures E2E settings, and.github/workflows/ci.ymlorchestrates continuous integration for both test layers.
Frequently Asked Questions
What testing frameworks does open-seo use?
The project uses Vitest for unit and integration tests and Playwright for end-to-end browser automation. Vitest runs in a Node.js environment with a Jest-compatible API, while Playwright executes real browser scenarios against a running instance of the application.
Why are unit tests co-located with source files instead of in a separate tests/ directory?
Co-location improves discoverability and maintainability. When developers open src/server/mcp/transport.ts, they immediately see transport.test.ts alongside it. This pattern scales naturally as the codebase grows, preventing a massive, unwieldy top-level tests/ directory and ensuring tests are grouped by feature rather than separated by file type.
How do I run only the domain overview filter tests?
Use the specific npm script defined in package.json:
pnpm test:e2e:domain
This command invokes playwright test e2e/domain-overview-filters.spec.ts directly, targeting only that specification file rather than the entire E2E suite.
Where are the Playwright configuration and CI workflows defined?
Playwright configuration lives in playwright.config.ts at the repository root. Continuous integration workflows that execute both Vitest and Playwright suites are defined in .github/workflows/ci.yml, ensuring all tests pass before merging pull requests.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →