diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6c5c567fc2..4d0206635e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -71,6 +71,20 @@ jobs: - name: Run tests run: pnpm test + - name: Upload test results to Trunk + # Run even when tests fail so flaky/failed results are still reported, + # but never let an upload problem fail the job. + if: ${{ !cancelled() }} + continue-on-error: true + uses: trunk-io/analytics-uploader@385f1ccdf345b4532dc4b6c665dd432b702b8e28 # v2.1.2 + with: + # Scope to each package root. A bare **/junit.xml glob descends into + # node_modules, where pnpm symlinks workspace packages, and uploads + # every report many times over. + junit-paths: "apps/*/junit.xml,packages/*/junit.xml" + org-slug: posthog-inc + token: ${{ secrets.TRUNK_API_TOKEN }} + integration-test: needs: changes # Fail closed: if change detection itself failed, run instead of skipping. @@ -143,6 +157,17 @@ jobs: env: CI: true + - name: Upload test results to Trunk + # Run even when E2E tests fail so flaky/failed results are still + # reported, but never let an upload problem fail the job. + if: ${{ !cancelled() }} + continue-on-error: true + uses: trunk-io/analytics-uploader@385f1ccdf345b4532dc4b6c665dd432b702b8e28 # v2.1.2 + with: + junit-paths: "apps/code/tests/e2e/junit.xml" + org-slug: posthog-inc + token: ${{ secrets.TRUNK_API_TOKEN }} + - name: Upload Playwright report uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 if: failure() diff --git a/.gitignore b/.gitignore index 8373d4d549..ec38bfdcec 100644 --- a/.gitignore +++ b/.gitignore @@ -57,6 +57,8 @@ test-results/ *storybook.log .session-store.json .playwright-mcp +# Trunk Flaky Tests JUnit reports (one per package, uploaded from CI) +junit.xml # Downloaded binaries apps/code/resources/codex-acp/ diff --git a/apps/code/tests/e2e/playwright.config.ts b/apps/code/tests/e2e/playwright.config.ts index ff646109c7..6ceadd14ca 100644 --- a/apps/code/tests/e2e/playwright.config.ts +++ b/apps/code/tests/e2e/playwright.config.ts @@ -6,10 +6,18 @@ export default defineConfig({ testDir: "./tests", testMatch: "**/*.spec.ts", timeout: 60000, - retries: isCI ? 2 : 0, + // No retries: Trunk Flaky Tests needs raw pass/fail results to detect flakes. + retries: 0, // Must run serially - Electron app has single instance lock workers: 1, - reporter: isCI ? [["github"], ["html", { open: "never" }]] : [["list"]], + // junit.xml (resolved next to this config) is uploaded to Trunk in CI. + reporter: isCI + ? [ + ["junit", { outputFile: "junit.xml" }], + ["github"], + ["html", { open: "never" }], + ] + : [["junit", { outputFile: "junit.xml" }], ["list"]], outputDir: "../playwright-results", use: { trace: "retain-on-failure", diff --git a/apps/code/vitest.config.ts b/apps/code/vitest.config.ts index 2203fb613b..f450d60b3d 100644 --- a/apps/code/vitest.config.ts +++ b/apps/code/vitest.config.ts @@ -1,6 +1,7 @@ import path from "node:path"; import react from "@vitejs/plugin-react"; import { defineConfig } from "vitest/config"; +import { trunkTestOptions } from "../../vitest.config.base"; import { rendererAliases } from "./vite.shared.mjs"; export default defineConfig({ @@ -10,6 +11,7 @@ export default defineConfig({ }, test: { globals: true, + ...trunkTestOptions, environment: "jsdom", setupFiles: ["./src/shared/test/setup.ts"], exclude: ["**/node_modules/**", "**/dist/**", "tests/e2e/**"], diff --git a/apps/mobile/vitest.config.ts b/apps/mobile/vitest.config.ts index c31342dec9..7fab851857 100644 --- a/apps/mobile/vitest.config.ts +++ b/apps/mobile/vitest.config.ts @@ -1,11 +1,13 @@ import path from "node:path"; import react from "@vitejs/plugin-react"; import { defineConfig } from "vitest/config"; +import { trunkTestOptions } from "../../vitest.config.base"; export default defineConfig({ plugins: [react()], test: { globals: true, + ...trunkTestOptions, environment: "node", setupFiles: ["./src/test/setup.ts"], exclude: ["**/node_modules/**", "**/dist/**"], diff --git a/packages/agent/vitest.config.ts b/packages/agent/vitest.config.ts index 0a4a270c77..d4f20521ac 100644 --- a/packages/agent/vitest.config.ts +++ b/packages/agent/vitest.config.ts @@ -1,5 +1,6 @@ import { resolve } from "node:path"; import { defineConfig } from "vitest/config"; +import { trunkTestOptions } from "../../vitest.config.base"; export default defineConfig({ resolve: { @@ -9,6 +10,7 @@ export default defineConfig({ }, test: { globals: true, + ...trunkTestOptions, environment: "node", include: ["src/**/*.test.ts"], exclude: ["**/node_modules/**", "**/dist/**"], diff --git a/packages/electron-trpc/vitest.config.ts b/packages/electron-trpc/vitest.config.ts index 11f94927d0..8af3839d0c 100644 --- a/packages/electron-trpc/vitest.config.ts +++ b/packages/electron-trpc/vitest.config.ts @@ -2,11 +2,13 @@ import path from "node:path"; import { fileURLToPath } from "node:url"; import { defineConfig } from "vite"; +import { trunkTestOptions } from "../../vitest.config.base"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); export default defineConfig({ test: { + ...trunkTestOptions, coverage: { all: true, include: ["src/**/*"], diff --git a/packages/git/vitest.config.ts b/packages/git/vitest.config.ts index 6011860bb8..8f79078441 100644 --- a/packages/git/vitest.config.ts +++ b/packages/git/vitest.config.ts @@ -1,8 +1,10 @@ import { defineConfig } from "vitest/config"; +import { trunkTestOptions } from "../../vitest.config.base"; export default defineConfig({ test: { globals: true, + ...trunkTestOptions, environment: "node", include: ["src/**/*.test.ts"], exclude: ["**/node_modules/**", "**/.git/**"], diff --git a/packages/shared/vitest.config.ts b/packages/shared/vitest.config.ts index 5e398e4eaf..407c36b953 100644 --- a/packages/shared/vitest.config.ts +++ b/packages/shared/vitest.config.ts @@ -1,8 +1,10 @@ import { defineConfig } from "vitest/config"; +import { trunkTestOptions } from "../../vitest.config.base"; export default defineConfig({ test: { globals: true, + ...trunkTestOptions, environment: "node", include: ["src/**/*.test.ts"], exclude: ["**/node_modules/**", "**/dist/**"], diff --git a/packages/ui/vitest.config.ts b/packages/ui/vitest.config.ts index ec577392ce..81eb433cd9 100644 --- a/packages/ui/vitest.config.ts +++ b/packages/ui/vitest.config.ts @@ -1,6 +1,7 @@ import { fileURLToPath } from "node:url"; import react from "@vitejs/plugin-react"; import { defineConfig } from "vitest/config"; +import { trunkTestOptions } from "../../vitest.config.base"; export default defineConfig({ plugins: [react()], @@ -20,6 +21,7 @@ export default defineConfig({ }, test: { globals: true, + ...trunkTestOptions, environment: "jsdom", setupFiles: ["./src/test/setup.ts"], include: ["src/**/*.test.ts", "src/**/*.test.tsx"], diff --git a/packages/workspace-server/vitest.config.ts b/packages/workspace-server/vitest.config.ts index 5e398e4eaf..407c36b953 100644 --- a/packages/workspace-server/vitest.config.ts +++ b/packages/workspace-server/vitest.config.ts @@ -1,8 +1,10 @@ import { defineConfig } from "vitest/config"; +import { trunkTestOptions } from "../../vitest.config.base"; export default defineConfig({ test: { globals: true, + ...trunkTestOptions, environment: "node", include: ["src/**/*.test.ts"], exclude: ["**/node_modules/**", "**/dist/**"], diff --git a/vitest.config.base.ts b/vitest.config.base.ts new file mode 100644 index 0000000000..7039f83683 --- /dev/null +++ b/vitest.config.base.ts @@ -0,0 +1,18 @@ +// Shared Vitest test options for Trunk Flaky Tests uploads. Imported and spread +// into each package's `test` block so the junit reporter and retry policy stay +// defined in one place. See the upload steps in .github/workflows/test.yml. +// +// `outputFile` is relative, so each config writes ./junit.xml next to itself; +// CI globs apps/*/junit.xml and packages/*/junit.xml. The explicit type keeps +// the reporter tuple assignable across the Vitest 2.x and 4.x versions in use. +export const trunkTestOptions: { + retry: number; + reporters: (string | [string, Record])[]; +} = { + // Disable retries so flaky-test detection sees raw pass/fail results. + retry: 0, + reporters: [ + "default", + ["junit", { outputFile: "./junit.xml", addFileAttribute: true }], + ], +};