Skip to content

Commit 2088d93

Browse files
committed
test(web): add e2e database setup and playwright infrastructure
1 parent 104b2ef commit 2088d93

File tree

12 files changed

+209
-9
lines changed

12 files changed

+209
-9
lines changed

.github/workflows/nightly-e2e.yml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,17 @@ jobs:
6969
- name: Install Playwright browsers
7070
run: cd web && bunx playwright install --with-deps chromium
7171

72-
- name: Run documentation E2E tests
73-
run: cd web && bun run e2e:docs --project=chromium
72+
- name: Run web E2E tests (chromium)
73+
run: cd web && bun run e2e --project=chromium
7474

7575
- name: Upload Playwright report on failure
7676
if: failure()
7777
uses: actions/upload-artifact@v4
7878
with:
79-
name: playwright-docs-report
79+
name: playwright-report
8080
path: debug/playwright-report/
8181
retention-days: 7
82+
83+
- name: Cleanup e2e database
84+
if: always()
85+
run: bun --cwd packages/internal db:e2e:down

bun.lock

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"zod": "^4.2.1"
4242
},
4343
"overrides": {
44+
"baseline-browser-mapping": "^2.9.14",
4445
"zod": "^4.2.1",
4546
"signal-exit": "3.0.7"
4647
},

packages/internal/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@
4949
"db:generate": "drizzle-kit generate --config=./src/db/drizzle.config.ts",
5050
"db:migrate": "drizzle-kit push --config=./src/db/drizzle.config.ts",
5151
"db:start": "docker compose -f ./src/db/docker-compose.yml up --wait && bun run db:generate && (timeout 1 || sleep 1) && bun run db:migrate",
52+
"db:e2e:setup": "bun ./src/db/e2e-setup.ts",
53+
"db:e2e:down": "docker compose -f ./src/db/docker-compose.e2e.yml down --volumes",
5254
"db:studio": "drizzle-kit studio --config=./src/db/drizzle.config.ts"
5355
},
5456
"sideEffects": false,
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
name: manicode-e2e
2+
services:
3+
db:
4+
image: postgres:16
5+
restart: "no"
6+
ports:
7+
- 5433:5432
8+
environment:
9+
POSTGRES_USER: manicode_user_local
10+
POSTGRES_PASSWORD: secretpassword_local
11+
POSTGRES_DB: manicode_db_e2e
12+
healthcheck:
13+
test: ["CMD-SHELL", "pg_isready -U manicode_user_local -d manicode_db_e2e"]
14+
interval: 2s
15+
timeout: 5s
16+
retries: 10
17+
start_period: 5s
18+
volumes:
19+
- db_data_e2e:/var/lib/postgresql/data
20+
volumes:
21+
db_data_e2e:
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/**
2+
* Shared constants for E2E testing infrastructure.
3+
* Used by e2e-setup.ts, playwright.config.ts, and playwright-runner.test.ts
4+
*/
5+
6+
export const DEFAULT_E2E_DATABASE_URL =
7+
'postgresql://manicode_user_local:secretpassword_local@localhost:5433/manicode_db_e2e'
8+
9+
export const getE2EDatabaseUrl = (): string =>
10+
process.env.E2E_DATABASE_URL || DEFAULT_E2E_DATABASE_URL
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import path from 'path'
2+
import { spawnSync } from 'node:child_process'
3+
import { fileURLToPath, URL } from 'node:url'
4+
5+
import { drizzle } from 'drizzle-orm/postgres-js'
6+
import { migrate } from 'drizzle-orm/postgres-js/migrator'
7+
import { eq } from 'drizzle-orm'
8+
import postgres from 'postgres'
9+
10+
import * as schema from './schema'
11+
import { getE2EDatabaseUrl } from './e2e-constants'
12+
13+
const databaseUrl = getE2EDatabaseUrl()
14+
15+
// Safeguard: prevent accidentally running e2e migrations against non-local databases
16+
if (process.env.E2E_DATABASE_URL && process.env.ALLOW_REMOTE_E2E_DATABASE !== 'true') {
17+
const parsedUrl = new URL(databaseUrl)
18+
const hostname = parsedUrl.hostname
19+
20+
if (hostname !== 'localhost' && hostname !== '127.0.0.1') {
21+
console.error(
22+
`Refusing to run e2e migrations against non-local database host "${hostname}". ` +
23+
'Set ALLOW_REMOTE_E2E_DATABASE=true to override.'
24+
)
25+
process.exit(1)
26+
}
27+
}
28+
29+
process.env.E2E_DATABASE_URL = databaseUrl
30+
process.env.DATABASE_URL = databaseUrl
31+
32+
const here = path.dirname(fileURLToPath(import.meta.url))
33+
const composeFile = path.join(here, 'docker-compose.e2e.yml')
34+
35+
const run = (command: string, args: string[]) => {
36+
const result = spawnSync(command, args, { cwd: here, stdio: 'inherit' })
37+
if (result.error) {
38+
const errno = result.error as NodeJS.ErrnoException
39+
if (errno.code === 'ENOENT') {
40+
console.error(
41+
`Error: '${command}' command not found. Please ensure ${command} is installed and in your PATH.`
42+
)
43+
} else {
44+
console.error(`Error executing '${command}':`, result.error.message)
45+
}
46+
process.exit(1)
47+
}
48+
if (result.status !== 0) {
49+
process.exit(result.status ?? 1)
50+
}
51+
}
52+
53+
const waitForPostgres = async (
54+
url: string,
55+
maxAttempts = 30,
56+
delayMs = 1000
57+
): Promise<void> => {
58+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
59+
let testClient: ReturnType<typeof postgres> | null = null
60+
try {
61+
testClient = postgres(url, { max: 1, connect_timeout: 5 })
62+
await testClient`SELECT 1`
63+
return
64+
} catch (error) {
65+
if (attempt === maxAttempts) {
66+
throw new Error(
67+
`Failed to connect to Postgres after ${maxAttempts} attempts: ${error}`
68+
)
69+
}
70+
console.log(
71+
`Waiting for Postgres to be ready... (attempt ${attempt}/${maxAttempts})`
72+
)
73+
await new Promise((resolve) => setTimeout(resolve, delayMs))
74+
} finally {
75+
if (testClient) {
76+
await testClient.end()
77+
}
78+
}
79+
}
80+
}
81+
82+
run('docker', ['compose', '-f', composeFile, 'up', '-d', '--wait'])
83+
84+
await waitForPostgres(databaseUrl)
85+
86+
const client = postgres(databaseUrl, { max: 1 })
87+
const db = drizzle(client, { schema })
88+
89+
try {
90+
await migrate(db, { migrationsFolder: path.join(here, 'migrations') })
91+
92+
const userEmail = 'e2e@codebuff.com'
93+
const fallbackUserId = 'e2e-user'
94+
95+
await db
96+
.insert(schema.user)
97+
.values({
98+
id: fallbackUserId,
99+
email: userEmail,
100+
name: 'E2E User',
101+
handle: 'e2e-user',
102+
})
103+
.onConflictDoNothing()
104+
105+
const [userRow] = await db
106+
.select({ id: schema.user.id })
107+
.from(schema.user)
108+
.where(eq(schema.user.email, userEmail))
109+
.limit(1)
110+
111+
const userId = userRow?.id ?? fallbackUserId
112+
const publisherId = 'codebuff'
113+
114+
await db
115+
.insert(schema.publisher)
116+
.values({
117+
id: publisherId,
118+
name: 'Codebuff',
119+
verified: true,
120+
user_id: userId,
121+
created_by: userId,
122+
})
123+
.onConflictDoNothing()
124+
125+
await db
126+
.insert(schema.agentConfig)
127+
.values({
128+
id: 'base',
129+
version: '1.2.3',
130+
publisher_id: publisherId,
131+
data: {
132+
name: 'Base',
133+
description: 'desc',
134+
tags: ['test'],
135+
},
136+
})
137+
.onConflictDoNothing()
138+
139+
console.log('E2E database setup completed successfully')
140+
} finally {
141+
await client.end()
142+
}

web/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,4 +110,8 @@ cd web
110110
bun run e2e
111111
```
112112

113+
The e2e runner starts a dedicated Postgres container on port 5433, migrates, and
114+
seeds minimal data for SSR. Override the connection with `E2E_DATABASE_URL` if
115+
needed.
116+
113117
<!-- Lighthouse CI workflow removed for now. Reintroduce later if needed. -->

web/next.config.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ const withMDX = createMDX({
99
},
1010
})
1111

12+
const DEV_ALLOWED_ORIGINS = ['localhost', '127.0.0.1']
13+
1214
/** @type {import('next').NextConfig} */
1315
const nextConfig = {
1416
eslint: {
@@ -19,6 +21,7 @@ const nextConfig = {
1921
// Disable TypeScript errors during builds
2022
ignoreBuildErrors: true,
2123
},
24+
allowedDevOrigins: DEV_ALLOWED_ORIGINS,
2225

2326
// Enable experimental features for better SSG performance
2427
experimental: {

web/package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@
2525
"test:watch": "jest --watchAll",
2626
"test:docs": "jest --testPathPattern=docs/",
2727
"test:docs:integrity": "jest --testPathPattern=content-integrity",
28-
"e2e": "playwright test",
29-
"e2e:ui": "playwright test --ui",
30-
"e2e:docs": "playwright test --grep @docs src/__tests__/e2e/docs.spec.ts",
28+
"e2e:setup": "bun --cwd ../packages/internal db:e2e:setup",
29+
"e2e": "bun run e2e:setup && playwright test",
30+
"e2e:ui": "bun run e2e:setup && playwright test --ui",
31+
"e2e:docs": "bun run e2e:setup && playwright test --grep @docs src/__tests__/e2e/docs.spec.ts",
3132
"discord:start": "bun run scripts/discord/index.ts",
3233
"discord:register": "bun run scripts/discord/register-commands.ts",
3334
"clean": "rm -rf .next"
@@ -111,6 +112,7 @@
111112
"@typescript-eslint/eslint-plugin": "^8.29.1",
112113
"@typescript-eslint/parser": "^8.29.1",
113114
"autoprefixer": "^10.4.21",
115+
"baseline-browser-mapping": "^2.9.14",
114116
"eslint": "^8.57.0",
115117
"eslint-config-next": "14.2.25",
116118
"eslint-config-prettier": "^9.1.0",

0 commit comments

Comments
 (0)