Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion e2e/e2e-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@
"./widget": "./src/widget.ts"
},
"scripts": {
"typecheck": "tsc -p tsconfig.json --noEmit"
"typecheck": "tsc -p tsconfig.json --noEmit",
"deploy": "tsx src/deploy-cli.ts"
},
"dependencies": {
"@playwright/test": "^1.61.1"
},
"devDependencies": {
"@types/node": "^26.1.0",
"tsx": "^4.22.4",
"typescript": "^6.0.3"
}
}
22 changes: 17 additions & 5 deletions e2e/e2e-utils/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
import {tmpdir} from 'node:os'
import {join} from 'node:path'
import {defineConfig, devices, type ReporterDescription} from '@playwright/test'
import {E2E_PORTS, HARNESS_E2E_PORTS, type E2EApp, type HarnessApp} from './ports.js'

const DEFAULT_WEB_SERVER_TIMEOUT = 180_000

function reporters(): ReporterDescription[] {
if (!process.env.GITHUB_ACTIONS) return [['line']]
return [['line'], ['json', {outputFile: 'test-results.json'}]]
}

function serverEntry(command: string, port: number) {
function serverEntry(command: string, port: number, webServerTimeout: number) {
return {
command,
url: `http://localhost:${port}`,
reuseExistingServer: !process.env.CI,
stdout: 'pipe' as const,
timeout: 180_000,
timeout: webServerTimeout,
env: {CONCIV_E2E: '1'},
}
}

export function deployDir(app: E2EApp): string {
return join(tmpdir(), 'conciv-e2e-deploy', app)
}

export function e2eConfig(
app: E2EApp,
opts: {command: (port: number) => string; timeout?: number},
opts: {command: (port: number) => string; timeout?: number; webServerTimeout?: number},
): ReturnType<typeof defineConfig> {
const port = E2E_PORTS[app]
const timeout = opts.timeout ?? 90_000
Expand All @@ -30,7 +38,11 @@ export function e2eConfig(
expect: {timeout},
reporter: reporters(),
use: {baseURL: `http://localhost:${port}`},
webServer: serverEntry(`rm -rf .conciv && ${opts.command(port)}`, port),
webServer: serverEntry(
`rm -rf .conciv && ${opts.command(port)}`,
port,
opts.webServerTimeout ?? DEFAULT_WEB_SERVER_TIMEOUT,
),
projects: [{name: 'chromium', use: {...devices['Desktop Chrome']}}],
})
}
Expand Down Expand Up @@ -61,7 +73,7 @@ export function harnessMatrixConfig(opts: {
timeout: 150_000,
reporter: reporters(),
webServer: entries.map(([harness, port]) =>
serverEntry(`rm -rf .conciv-${harness} && ${opts.command(harness, port)}`, port),
serverEntry(`rm -rf .conciv-${harness} && ${opts.command(harness, port)}`, port, DEFAULT_WEB_SERVER_TIMEOUT),
),
projects: entries.map(([harness, port]) => ({
name: harness,
Expand Down
19 changes: 19 additions & 0 deletions e2e/e2e-utils/src/deploy-cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {deployPackedApp} from './deploy.js'
import {isE2EApp} from './ports.js'

async function main(): Promise<void> {
const [, , app, pnpmFilter] = process.argv
if (app === undefined || pnpmFilter === undefined) {
throw new Error('usage: deploy-cli.ts <app> <pnpmFilter>')
}
if (!isE2EApp(app)) {
throw new Error(`"${app}" is not a known e2e app`)
}
const target = await deployPackedApp(app, pnpmFilter)
console.log(`deployed ${pnpmFilter} to ${target}`)
}

main().catch((error: unknown) => {
console.error(error)
process.exitCode = 1
})
36 changes: 36 additions & 0 deletions e2e/e2e-utils/src/deploy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {execFile} from 'node:child_process'
import {existsSync, mkdirSync, rmSync} from 'node:fs'
import {dirname, join} from 'node:path'
import {promisify} from 'node:util'
import {deployDir} from './config.js'
import type {E2EApp} from './ports.js'

const execFileAsync = promisify(execFile)

function findWorkspaceRoot(startDir: string): string {
let dir = startDir
for (let depth = 0; depth < 12; depth++) {
if (existsSync(join(dir, 'pnpm-workspace.yaml'))) return dir
const parent = dirname(dir)
if (parent === dir) break
dir = parent
}
throw new Error(`workspace root (pnpm-workspace.yaml) not found above ${startDir}`)
}

function resolveFresh(): boolean {
return process.env.CONCIV_DEPLOY_FRESH === '1'
}

export async function deployPackedApp(app: E2EApp, pnpmFilter: string): Promise<string> {
const target = deployDir(app)
rmSync(target, {recursive: true, force: true})
mkdirSync(dirname(target), {recursive: true})
const resolveFlags = resolveFresh() ? [] : ['--prefer-offline']
const workspaceRoot = findWorkspaceRoot(process.cwd())
await execFileAsync('pnpm', ['--filter', pnpmFilter, 'deploy', '--legacy', '--prod=false', ...resolveFlags, target], {
cwd: workspaceRoot,
maxBuffer: 64 * 1024 * 1024,
})
return target
}
4 changes: 4 additions & 0 deletions e2e/e2e-utils/src/ports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export const E2E_PORTS = {

export type E2EApp = keyof typeof E2E_PORTS

export function isE2EApp(name: string): name is E2EApp {
return Object.hasOwn(E2E_PORTS, name)
}

export const HARNESS_E2E_PORTS = {
claude: 5271,
codex: 5272,
Expand Down
2 changes: 1 addition & 1 deletion e2e/nextjs-component/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"dev": "next dev",
"build": "next build",
"start": "next start",
"test:e2e": "playwright test"
"test:e2e": "pnpm --filter @conciv/e2e-utils run deploy nextjs-component conciv-e2e-nextjs-component && playwright test"
},
"dependencies": {
"@conciv/extension-terminal": "workspace:*",
Expand Down
11 changes: 3 additions & 8 deletions e2e/nextjs-component/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import {e2eConfig} from '@conciv/e2e-utils/config'
import {deployDir, e2eConfig} from '@conciv/e2e-utils/config'

export default e2eConfig('nextjs-component', {
command: (port) =>
[
'DEPLOY_DIR="$(mktemp -d)/app"',
`pnpm --filter conciv-e2e-nextjs-component deploy --legacy --prod=false "$DEPLOY_DIR"`,
`cd "$DEPLOY_DIR"`,
`pnpm exec next dev --port ${port}`,
].join(' && '),
command: (port) => `cd "${deployDir('nextjs-component')}" && pnpm exec next dev --port ${port}`,
webServerTimeout: 60_000,
})
2 changes: 1 addition & 1 deletion e2e/nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"dev": "next dev",
"build": "next build",
"start": "next start",
"test:e2e": "playwright test",
"test:e2e": "pnpm --filter @conciv/e2e-utils run deploy nextjs conciv-e2e-nextjs && playwright test",
"update-packed-lockfile": "tsx packed/update-lockfile.ts"
},
"dependencies": {
Expand Down
11 changes: 3 additions & 8 deletions e2e/nextjs/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import {e2eConfig} from '@conciv/e2e-utils/config'
import {deployDir, e2eConfig} from '@conciv/e2e-utils/config'

export default e2eConfig('nextjs', {
command: (port) =>
[
'DEPLOY_DIR="$(mktemp -d)/app"',
`pnpm --filter conciv-e2e-nextjs deploy --legacy --prod=false "$DEPLOY_DIR"`,
`cd "$DEPLOY_DIR"`,
`pnpm exec next dev --port ${port}`,
].join(' && '),
command: (port) => `cd "${deployDir('nextjs')}" && pnpm exec next dev --port ${port}`,
webServerTimeout: 60_000,
})
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion turbo.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
"test:e2e": {
"dependsOn": ["^build"],
"cache": false,
"env": ["CI", "GITHUB_ACTIONS", "CONCIV_E2E", "VITEST_MAX_FORKS", "VITEST_MAX_WORKERS"]
"env": ["CI", "GITHUB_ACTIONS", "CONCIV_E2E", "CONCIV_DEPLOY_FRESH", "VITEST_MAX_FORKS", "VITEST_MAX_WORKERS"]
}
}
}
Loading