From 374ec297eb9b62bdcc118bafc1f89ba32827e749 Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Thu, 25 Jun 2026 13:48:17 -0400 Subject: [PATCH 1/5] ci(tests): upload JUnit reports to Trunk Flaky Tests Configure Trunk Flaky Tests test uploads for the posthog-inc org. - Add the junit reporter (outputFile ./junit.xml, addFileAttribute: true) alongside the default reporter in all 8 Vitest configs, and set an explicit retry: 0 so flaky detection sees raw pass/fail results. - Add an "Upload test results to Trunk" step to the unit-test job in test.yml using trunk-io/analytics-uploader (pinned), scoped to apps/*/junit.xml,packages/*/junit.xml to avoid node_modules dupes. - Gitignore the generated junit.xml reports. Validated locally with trunk-analytics-cli validate: 8 valid files, 0 warnings, 0 errors. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/test.yml | 16 ++++++++++++++++ .gitignore | 2 ++ apps/code/vitest.config.ts | 6 ++++++ apps/mobile/vitest.config.ts | 6 ++++++ packages/agent/vitest.config.ts | 6 ++++++ packages/electron-trpc/vitest.config.ts | 6 ++++++ packages/git/vitest.config.ts | 6 ++++++ packages/shared/vitest.config.ts | 6 ++++++ packages/ui/vitest.config.ts | 6 ++++++ packages/workspace-server/vitest.config.ts | 6 ++++++ 10 files changed, 66 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6c5c567fc2..c87d94ace4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -71,6 +71,22 @@ 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 }} + env: + TRUNK_API_TOKEN: ${{ secrets.TRUNK_API_TOKEN }} + integration-test: needs: changes # Fail closed: if change detection itself failed, run instead of skipping. 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/vitest.config.ts b/apps/code/vitest.config.ts index 2203fb613b..0142854c68 100644 --- a/apps/code/vitest.config.ts +++ b/apps/code/vitest.config.ts @@ -10,6 +10,12 @@ export default defineConfig({ }, test: { globals: true, + // Disable retries so flaky-test detection sees raw pass/fail results. + retry: 0, + reporters: [ + "default", + ["junit", { outputFile: "./junit.xml", addFileAttribute: true }], + ], 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..deec039052 100644 --- a/apps/mobile/vitest.config.ts +++ b/apps/mobile/vitest.config.ts @@ -6,6 +6,12 @@ export default defineConfig({ plugins: [react()], test: { globals: true, + // Disable retries so flaky-test detection sees raw pass/fail results. + retry: 0, + reporters: [ + "default", + ["junit", { outputFile: "./junit.xml", addFileAttribute: true }], + ], 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..8d852bee41 100644 --- a/packages/agent/vitest.config.ts +++ b/packages/agent/vitest.config.ts @@ -9,6 +9,12 @@ export default defineConfig({ }, test: { globals: true, + // Disable retries so flaky-test detection sees raw pass/fail results. + retry: 0, + reporters: [ + "default", + ["junit", { outputFile: "./junit.xml", addFileAttribute: true }], + ], 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..c66cf2d6be 100644 --- a/packages/electron-trpc/vitest.config.ts +++ b/packages/electron-trpc/vitest.config.ts @@ -7,6 +7,12 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url)); export default defineConfig({ test: { + // Disable retries so flaky-test detection sees raw pass/fail results. + retry: 0, + reporters: [ + "default", + ["junit", { outputFile: "./junit.xml", addFileAttribute: true }], + ], coverage: { all: true, include: ["src/**/*"], diff --git a/packages/git/vitest.config.ts b/packages/git/vitest.config.ts index 6011860bb8..d0a6284724 100644 --- a/packages/git/vitest.config.ts +++ b/packages/git/vitest.config.ts @@ -3,6 +3,12 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ test: { globals: true, + // Disable retries so flaky-test detection sees raw pass/fail results. + retry: 0, + reporters: [ + "default", + ["junit", { outputFile: "./junit.xml", addFileAttribute: true }], + ], 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..65f28b9b89 100644 --- a/packages/shared/vitest.config.ts +++ b/packages/shared/vitest.config.ts @@ -3,6 +3,12 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ test: { globals: true, + // Disable retries so flaky-test detection sees raw pass/fail results. + retry: 0, + reporters: [ + "default", + ["junit", { outputFile: "./junit.xml", addFileAttribute: true }], + ], 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..4f2b944706 100644 --- a/packages/ui/vitest.config.ts +++ b/packages/ui/vitest.config.ts @@ -20,6 +20,12 @@ export default defineConfig({ }, test: { globals: true, + // Disable retries so flaky-test detection sees raw pass/fail results. + retry: 0, + reporters: [ + "default", + ["junit", { outputFile: "./junit.xml", addFileAttribute: true }], + ], 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..65f28b9b89 100644 --- a/packages/workspace-server/vitest.config.ts +++ b/packages/workspace-server/vitest.config.ts @@ -3,6 +3,12 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ test: { globals: true, + // Disable retries so flaky-test detection sees raw pass/fail results. + retry: 0, + reporters: [ + "default", + ["junit", { outputFile: "./junit.xml", addFileAttribute: true }], + ], environment: "node", include: ["src/**/*.test.ts"], exclude: ["**/node_modules/**", "**/dist/**"], From ce36254777dfda8644e68e0710d56a028a3cf7ac Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Thu, 25 Jun 2026 14:21:53 -0400 Subject: [PATCH 2/5] ci(tests): upload Playwright JUnit reports to Trunk Flaky Tests Extend the Trunk Flaky Tests setup to the Playwright E2E suite. - Add the junit reporter (outputFile junit.xml, resolved next to the config => apps/code/tests/e2e/junit.xml) to playwright.config.ts, in both the CI and local reporter sets, and set retries: 0 (was isCI ? 2 : 0) so flaky detection sees raw pass/fail results. - Add an "Upload test results to Trunk" step to the integration-test job in test.yml, after the E2E run, using the pinned trunk-io/analytics-uploader with junit-paths apps/code/tests/e2e/junit.xml. Validated locally with trunk-analytics-cli validate: 0 errors. (One non-blocking warning remains -- "missing file or filepath" -- which is inherent to Playwright's built-in JUnit reporter, which has no option to emit a per-testcase file attribute; it matches Trunk's documented Playwright config.) Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/test.yml | 13 +++++++++++++ apps/code/tests/e2e/playwright.config.ts | 8 ++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c87d94ace4..7e4e4abb1d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -159,6 +159,19 @@ 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 }} + env: + TRUNK_API_TOKEN: ${{ secrets.TRUNK_API_TOKEN }} + - name: Upload Playwright report uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 if: failure() diff --git a/apps/code/tests/e2e/playwright.config.ts b/apps/code/tests/e2e/playwright.config.ts index ff646109c7..1a6743d6a8 100644 --- a/apps/code/tests/e2e/playwright.config.ts +++ b/apps/code/tests/e2e/playwright.config.ts @@ -6,10 +6,14 @@ 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", From b67c3373c9a1d6eb3f65e368417d463072e84693 Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Thu, 25 Jun 2026 14:40:31 -0400 Subject: [PATCH 3/5] style: format playwright.config.ts reporter array (biome) Co-Authored-By: Claude Opus 4.8 (1M context) --- apps/code/tests/e2e/playwright.config.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/code/tests/e2e/playwright.config.ts b/apps/code/tests/e2e/playwright.config.ts index 1a6743d6a8..6ceadd14ca 100644 --- a/apps/code/tests/e2e/playwright.config.ts +++ b/apps/code/tests/e2e/playwright.config.ts @@ -12,7 +12,11 @@ export default defineConfig({ workers: 1, // 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" }], + ["github"], + ["html", { open: "never" }], + ] : [["junit", { outputFile: "junit.xml" }], ["list"]], outputDir: "../playwright-results", use: { From f8b9f42e68d311c2b88b29029fa5e1e9d51637d0 Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Thu, 25 Jun 2026 15:44:41 -0400 Subject: [PATCH 4/5] refactor(tests): extract shared Trunk Vitest options to a base config The retry: 0 setting and junit reporter array were pasted verbatim into all 8 vitest.config.ts files, so any change (outputFile path, a new Trunk reporter option) had to be made in lockstep across every file. Extract them to a root-level vitest.config.base.ts as `trunkTestOptions` and spread it into each config's `test` block. The constant is explicitly typed so the reporter tuple stays assignable across the Vitest 2.x and 4.x versions used in the workspace. Verified at runtime in a v4 package (shared), a v2 package (git), and the vite-augmented config (electron-trpc): junit.xml is still emitted and trunk-analytics-cli validate reports 0 warnings, 0 errors for all three. Co-Authored-By: Claude Opus 4.8 (1M context) --- apps/code/vitest.config.ts | 8 ++------ apps/mobile/vitest.config.ts | 8 ++------ packages/agent/vitest.config.ts | 8 ++------ packages/electron-trpc/vitest.config.ts | 8 ++------ packages/git/vitest.config.ts | 8 ++------ packages/shared/vitest.config.ts | 8 ++------ packages/ui/vitest.config.ts | 8 ++------ packages/workspace-server/vitest.config.ts | 8 ++------ vitest.config.base.ts | 18 ++++++++++++++++++ 9 files changed, 34 insertions(+), 48 deletions(-) create mode 100644 vitest.config.base.ts diff --git a/apps/code/vitest.config.ts b/apps/code/vitest.config.ts index 0142854c68..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,12 +11,7 @@ export default defineConfig({ }, test: { globals: true, - // Disable retries so flaky-test detection sees raw pass/fail results. - retry: 0, - reporters: [ - "default", - ["junit", { outputFile: "./junit.xml", addFileAttribute: 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 deec039052..7fab851857 100644 --- a/apps/mobile/vitest.config.ts +++ b/apps/mobile/vitest.config.ts @@ -1,17 +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, - // Disable retries so flaky-test detection sees raw pass/fail results. - retry: 0, - reporters: [ - "default", - ["junit", { outputFile: "./junit.xml", addFileAttribute: 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 8d852bee41..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,12 +10,7 @@ export default defineConfig({ }, test: { globals: true, - // Disable retries so flaky-test detection sees raw pass/fail results. - retry: 0, - reporters: [ - "default", - ["junit", { outputFile: "./junit.xml", addFileAttribute: 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 c66cf2d6be..8af3839d0c 100644 --- a/packages/electron-trpc/vitest.config.ts +++ b/packages/electron-trpc/vitest.config.ts @@ -2,17 +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: { - // Disable retries so flaky-test detection sees raw pass/fail results. - retry: 0, - reporters: [ - "default", - ["junit", { outputFile: "./junit.xml", addFileAttribute: true }], - ], + ...trunkTestOptions, coverage: { all: true, include: ["src/**/*"], diff --git a/packages/git/vitest.config.ts b/packages/git/vitest.config.ts index d0a6284724..8f79078441 100644 --- a/packages/git/vitest.config.ts +++ b/packages/git/vitest.config.ts @@ -1,14 +1,10 @@ import { defineConfig } from "vitest/config"; +import { trunkTestOptions } from "../../vitest.config.base"; export default defineConfig({ test: { globals: true, - // Disable retries so flaky-test detection sees raw pass/fail results. - retry: 0, - reporters: [ - "default", - ["junit", { outputFile: "./junit.xml", addFileAttribute: 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 65f28b9b89..407c36b953 100644 --- a/packages/shared/vitest.config.ts +++ b/packages/shared/vitest.config.ts @@ -1,14 +1,10 @@ import { defineConfig } from "vitest/config"; +import { trunkTestOptions } from "../../vitest.config.base"; export default defineConfig({ test: { globals: true, - // Disable retries so flaky-test detection sees raw pass/fail results. - retry: 0, - reporters: [ - "default", - ["junit", { outputFile: "./junit.xml", addFileAttribute: 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 4f2b944706..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,12 +21,7 @@ export default defineConfig({ }, test: { globals: true, - // Disable retries so flaky-test detection sees raw pass/fail results. - retry: 0, - reporters: [ - "default", - ["junit", { outputFile: "./junit.xml", addFileAttribute: 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 65f28b9b89..407c36b953 100644 --- a/packages/workspace-server/vitest.config.ts +++ b/packages/workspace-server/vitest.config.ts @@ -1,14 +1,10 @@ import { defineConfig } from "vitest/config"; +import { trunkTestOptions } from "../../vitest.config.base"; export default defineConfig({ test: { globals: true, - // Disable retries so flaky-test detection sees raw pass/fail results. - retry: 0, - reporters: [ - "default", - ["junit", { outputFile: "./junit.xml", addFileAttribute: 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 }], + ], +}; From d88bfb644b5784bbd4bb05f2f440f2c5dce44d1a Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Thu, 25 Jun 2026 21:45:32 -0400 Subject: [PATCH 5/5] ci(tests): drop redundant TRUNK_API_TOKEN env from Trunk upload steps The trunk-io/analytics-uploader action maps the token: input to the CLI --token flag, so the separately-set env var was redundant and exposed the secret in the step environment unnecessarily. Keep only the token: input. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/test.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7e4e4abb1d..4d0206635e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -84,8 +84,6 @@ jobs: junit-paths: "apps/*/junit.xml,packages/*/junit.xml" org-slug: posthog-inc token: ${{ secrets.TRUNK_API_TOKEN }} - env: - TRUNK_API_TOKEN: ${{ secrets.TRUNK_API_TOKEN }} integration-test: needs: changes @@ -169,8 +167,6 @@ jobs: junit-paths: "apps/code/tests/e2e/junit.xml" org-slug: posthog-inc token: ${{ secrets.TRUNK_API_TOKEN }} - env: - TRUNK_API_TOKEN: ${{ secrets.TRUNK_API_TOKEN }} - name: Upload Playwright report uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2