From e4f28d4890a236fbaec35968a3713eb4b737d34b Mon Sep 17 00:00:00 2001 From: austincalvelage Date: Wed, 4 Nov 2026 09:14:44 -0700 Subject: [PATCH 1/4] ci(repo): scope express integration test cache to the packages it loads The express suite's cache key now depends on the builds of the packages its fixtures and harness load instead of every package's sources, so changes to unrelated SDKs reuse the cached result. The express build now hashes its tsdown config, Playwright output no longer enters the key, and the two harness-only secrets pass through instead of changing build hashes between the CI build and test steps. --- .changeset/scope-express-integration-cache.md | 2 ++ packages/express/turbo.json | 2 +- turbo.json | 31 +++++++++---------- 3 files changed, 17 insertions(+), 18 deletions(-) create mode 100644 .changeset/scope-express-integration-cache.md diff --git a/.changeset/scope-express-integration-cache.md b/.changeset/scope-express-integration-cache.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/scope-express-integration-cache.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/packages/express/turbo.json b/packages/express/turbo.json index 4379d6c1c35..7d7ea17d014 100644 --- a/packages/express/turbo.json +++ b/packages/express/turbo.json @@ -7,7 +7,7 @@ "**/package.json", "src/**", "tsconfig.json", - "tsup.config.ts", + "tsdown.config.mts", "!**/__tests__/**", "!**/__snapshots__/**", diff --git a/turbo.json b/turbo.json index 7c560eac6fe..0b80d5a1423 100644 --- a/turbo.json +++ b/turbo.json @@ -29,10 +29,14 @@ "VERCEL", "VERCEL_PROJECT_PRODUCTION_URL", "VERCEL_TARGET_ENV", - "VITE_CLERK_*" + "VITE_CLERK_*", + "!CLERK_E2E_OAUTH_PROVIDER_CLIENT_SECRET", + "!CLERK_PLATFORM_API_KEY" ], "globalPassThroughEnv": [ "AWS_SECRET_KEY", + "CLERK_E2E_OAUTH_PROVIDER_CLIENT_SECRET", + "CLERK_PLATFORM_API_KEY", "GITHUB_TOKEN", "INTEGRATION_TEST_RUN_KEY", "ACTIONS_RUNNER_DEBUG", @@ -271,23 +275,16 @@ "outputLogs": "new-only" }, "//#test:integration:express": { - "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS", "INTEGRATION_STAGING_INSTANCE_KEYS"], - "inputs": [ - "integration/**", - "packages/*/package.json", - "packages/*/src/**", - "packages/*/tsconfig.json", - "packages/*/tsconfig.build.json", - "packages/*/tsconfig.declarations.json", - "packages/*/tsdown.config.*", - "packages/*/tsup.config.*", - "packages/*/rspack.config.*", - "packages/*/vite.config.*", - "!packages/*/src/**/*.test.*", - "!packages/*/src/**/__tests__/**", - "!packages/*/src/**/__mocks__/**", - "!packages/*/src/**/__snapshots__/**" + "dependsOn": [ + "@clerk/backend#build", + "@clerk/clerk-js#build", + "@clerk/express#build", + "@clerk/shared#build", + "@clerk/testing#build", + "@clerk/ui#build" ], + "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS", "INTEGRATION_STAGING_INSTANCE_KEYS"], + "inputs": ["integration/**", "!integration/playwright-report/**", "!integration/test-results/**"], "outputs": ["integration/playwright-report/**"], "outputLogs": "new-only" }, From e225d21c989079ed2fe649a78a88eff12bb6ac46 Mon Sep 17 00:00:00 2001 From: austincalvelage Date: Wed, 4 Nov 2026 09:31:48 -0700 Subject: [PATCH 2/4] ci(repo): hash integration results against the platform and OAuth secrets Moving the two harness secrets to pass-through took them out of every integration suite's key. Declaring them in each integration task's env restores that without changing build hashes. --- turbo.json | 245 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 221 insertions(+), 24 deletions(-) diff --git a/turbo.json b/turbo.json index 0b80d5a1423..558d1823a8b 100644 --- a/turbo.json +++ b/turbo.json @@ -233,7 +233,15 @@ "outputs": [] }, "//#test:integration:ap-flows": { - "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS", "INTEGRATION_STAGING_INSTANCE_KEYS"], + "env": [ + "CLEANUP", + "CLERK_E2E_OAUTH_PROVIDER_CLIENT_SECRET", + "CLERK_PLATFORM_API_KEY", + "DEBUG", + "E2E_*", + "INTEGRATION_INSTANCE_KEYS", + "INTEGRATION_STAGING_INSTANCE_KEYS" + ], "inputs": [ "integration/**", "packages/*/package.json", @@ -254,7 +262,15 @@ "outputLogs": "new-only" }, "//#test:integration:generic": { - "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS", "INTEGRATION_STAGING_INSTANCE_KEYS"], + "env": [ + "CLEANUP", + "CLERK_E2E_OAUTH_PROVIDER_CLIENT_SECRET", + "CLERK_PLATFORM_API_KEY", + "DEBUG", + "E2E_*", + "INTEGRATION_INSTANCE_KEYS", + "INTEGRATION_STAGING_INSTANCE_KEYS" + ], "inputs": [ "integration/**", "packages/*/package.json", @@ -283,13 +299,29 @@ "@clerk/testing#build", "@clerk/ui#build" ], - "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS", "INTEGRATION_STAGING_INSTANCE_KEYS"], + "env": [ + "CLEANUP", + "CLERK_E2E_OAUTH_PROVIDER_CLIENT_SECRET", + "CLERK_PLATFORM_API_KEY", + "DEBUG", + "E2E_*", + "INTEGRATION_INSTANCE_KEYS", + "INTEGRATION_STAGING_INSTANCE_KEYS" + ], "inputs": ["integration/**", "!integration/playwright-report/**", "!integration/test-results/**"], "outputs": ["integration/playwright-report/**"], "outputLogs": "new-only" }, "//#test:integration:fastify": { - "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS", "INTEGRATION_STAGING_INSTANCE_KEYS"], + "env": [ + "CLEANUP", + "CLERK_E2E_OAUTH_PROVIDER_CLIENT_SECRET", + "CLERK_PLATFORM_API_KEY", + "DEBUG", + "E2E_*", + "INTEGRATION_INSTANCE_KEYS", + "INTEGRATION_STAGING_INSTANCE_KEYS" + ], "inputs": [ "integration/**", "packages/*/package.json", @@ -310,7 +342,15 @@ "outputLogs": "new-only" }, "//#test:integration:hono": { - "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS", "INTEGRATION_STAGING_INSTANCE_KEYS"], + "env": [ + "CLEANUP", + "CLERK_E2E_OAUTH_PROVIDER_CLIENT_SECRET", + "CLERK_PLATFORM_API_KEY", + "DEBUG", + "E2E_*", + "INTEGRATION_INSTANCE_KEYS", + "INTEGRATION_STAGING_INSTANCE_KEYS" + ], "inputs": [ "integration/**", "packages/*/package.json", @@ -331,7 +371,15 @@ "outputLogs": "new-only" }, "//#test:integration:mosaic": { - "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS", "INTEGRATION_STAGING_INSTANCE_KEYS"], + "env": [ + "CLEANUP", + "CLERK_E2E_OAUTH_PROVIDER_CLIENT_SECRET", + "CLERK_PLATFORM_API_KEY", + "DEBUG", + "E2E_*", + "INTEGRATION_INSTANCE_KEYS", + "INTEGRATION_STAGING_INSTANCE_KEYS" + ], "inputs": [ "integration/**", "packages/*/package.json", @@ -352,7 +400,15 @@ "outputLogs": "new-only" }, "//#test:integration:nextjs": { - "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS", "INTEGRATION_STAGING_INSTANCE_KEYS"], + "env": [ + "CLEANUP", + "CLERK_E2E_OAUTH_PROVIDER_CLIENT_SECRET", + "CLERK_PLATFORM_API_KEY", + "DEBUG", + "E2E_*", + "INTEGRATION_INSTANCE_KEYS", + "INTEGRATION_STAGING_INSTANCE_KEYS" + ], "inputs": [ "integration/**", "packages/*/package.json", @@ -373,7 +429,14 @@ "outputLogs": "new-only" }, "//#test:integration:nextjs:canary": { - "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS"], + "env": [ + "CLEANUP", + "CLERK_E2E_OAUTH_PROVIDER_CLIENT_SECRET", + "CLERK_PLATFORM_API_KEY", + "DEBUG", + "E2E_*", + "INTEGRATION_INSTANCE_KEYS" + ], "inputs": [ "integration/**", "packages/*/package.json", @@ -396,6 +459,8 @@ "//#test:integration:quickstart": { "env": [ "CLEANUP", + "CLERK_E2E_OAUTH_PROVIDER_CLIENT_SECRET", + "CLERK_PLATFORM_API_KEY", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS", @@ -422,7 +487,15 @@ "outputLogs": "new-only" }, "//#test:integration:astro": { - "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS", "INTEGRATION_STAGING_INSTANCE_KEYS"], + "env": [ + "CLEANUP", + "CLERK_E2E_OAUTH_PROVIDER_CLIENT_SECRET", + "CLERK_PLATFORM_API_KEY", + "DEBUG", + "E2E_*", + "INTEGRATION_INSTANCE_KEYS", + "INTEGRATION_STAGING_INSTANCE_KEYS" + ], "inputs": [ "integration/**", "packages/*/package.json", @@ -445,6 +518,8 @@ "//#test:integration:localhost": { "env": [ "CLEANUP", + "CLERK_E2E_OAUTH_PROVIDER_CLIENT_SECRET", + "CLERK_PLATFORM_API_KEY", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS", @@ -471,7 +546,16 @@ "outputLogs": "new-only" }, "//#test:integration:sessions": { - "env": ["CLEANUP", "DEBUG", "DISABLE_WEB_SECURITY", "E2E_*", "INTEGRATION_INSTANCE_KEYS", "NODE_EXTRA_CA_CERTS"], + "env": [ + "CLEANUP", + "CLERK_E2E_OAUTH_PROVIDER_CLIENT_SECRET", + "CLERK_PLATFORM_API_KEY", + "DEBUG", + "DISABLE_WEB_SECURITY", + "E2E_*", + "INTEGRATION_INSTANCE_KEYS", + "NODE_EXTRA_CA_CERTS" + ], "inputs": [ "integration/**", "packages/*/package.json", @@ -492,7 +576,16 @@ "outputLogs": "new-only" }, "//#test:integration:sessions:staging": { - "env": ["CLEANUP", "DEBUG", "DISABLE_WEB_SECURITY", "E2E_*", "INTEGRATION_INSTANCE_KEYS", "NODE_EXTRA_CA_CERTS"], + "env": [ + "CLEANUP", + "CLERK_E2E_OAUTH_PROVIDER_CLIENT_SECRET", + "CLERK_PLATFORM_API_KEY", + "DEBUG", + "DISABLE_WEB_SECURITY", + "E2E_*", + "INTEGRATION_INSTANCE_KEYS", + "NODE_EXTRA_CA_CERTS" + ], "inputs": [ "integration/**", "packages/*/package.json", @@ -513,7 +606,16 @@ "outputLogs": "new-only" }, "//#test:integration:handshake": { - "env": ["CLEANUP", "DEBUG", "DISABLE_WEB_SECURITY", "E2E_*", "INTEGRATION_INSTANCE_KEYS", "NODE_EXTRA_CA_CERTS"], + "env": [ + "CLEANUP", + "CLERK_E2E_OAUTH_PROVIDER_CLIENT_SECRET", + "CLERK_PLATFORM_API_KEY", + "DEBUG", + "DISABLE_WEB_SECURITY", + "E2E_*", + "INTEGRATION_INSTANCE_KEYS", + "NODE_EXTRA_CA_CERTS" + ], "inputs": [ "integration/**", "packages/*/package.json", @@ -534,7 +636,16 @@ "outputLogs": "new-only" }, "//#test:integration:handshake:staging": { - "env": ["CLEANUP", "DEBUG", "DISABLE_WEB_SECURITY", "E2E_*", "INTEGRATION_INSTANCE_KEYS", "NODE_EXTRA_CA_CERTS"], + "env": [ + "CLEANUP", + "CLERK_E2E_OAUTH_PROVIDER_CLIENT_SECRET", + "CLERK_PLATFORM_API_KEY", + "DEBUG", + "DISABLE_WEB_SECURITY", + "E2E_*", + "INTEGRATION_INSTANCE_KEYS", + "NODE_EXTRA_CA_CERTS" + ], "inputs": [ "integration/**", "packages/*/package.json", @@ -555,7 +666,14 @@ "outputLogs": "new-only" }, "//#test:integration:expo-web": { - "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS"], + "env": [ + "CLEANUP", + "CLERK_E2E_OAUTH_PROVIDER_CLIENT_SECRET", + "CLERK_PLATFORM_API_KEY", + "DEBUG", + "E2E_*", + "INTEGRATION_INSTANCE_KEYS" + ], "inputs": [ "integration/**", "packages/*/package.json", @@ -576,7 +694,15 @@ "outputLogs": "new-only" }, "//#test:integration:tanstack-react-start": { - "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS", "INTEGRATION_STAGING_INSTANCE_KEYS"], + "env": [ + "CLEANUP", + "CLERK_E2E_OAUTH_PROVIDER_CLIENT_SECRET", + "CLERK_PLATFORM_API_KEY", + "DEBUG", + "E2E_*", + "INTEGRATION_INSTANCE_KEYS", + "INTEGRATION_STAGING_INSTANCE_KEYS" + ], "inputs": [ "integration/**", "packages/*/package.json", @@ -597,7 +723,15 @@ "outputLogs": "new-only" }, "//#test:integration:vue": { - "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS", "INTEGRATION_STAGING_INSTANCE_KEYS"], + "env": [ + "CLEANUP", + "CLERK_E2E_OAUTH_PROVIDER_CLIENT_SECRET", + "CLERK_PLATFORM_API_KEY", + "DEBUG", + "E2E_*", + "INTEGRATION_INSTANCE_KEYS", + "INTEGRATION_STAGING_INSTANCE_KEYS" + ], "inputs": [ "integration/**", "packages/*/package.json", @@ -618,7 +752,15 @@ "outputLogs": "new-only" }, "//#test:integration:nuxt": { - "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS", "INTEGRATION_STAGING_INSTANCE_KEYS"], + "env": [ + "CLEANUP", + "CLERK_E2E_OAUTH_PROVIDER_CLIENT_SECRET", + "CLERK_PLATFORM_API_KEY", + "DEBUG", + "E2E_*", + "INTEGRATION_INSTANCE_KEYS", + "INTEGRATION_STAGING_INSTANCE_KEYS" + ], "inputs": [ "integration/**", "packages/*/package.json", @@ -639,7 +781,15 @@ "outputLogs": "new-only" }, "//#test:integration:react-router": { - "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS", "INTEGRATION_STAGING_INSTANCE_KEYS"], + "env": [ + "CLEANUP", + "CLERK_E2E_OAUTH_PROVIDER_CLIENT_SECRET", + "CLERK_PLATFORM_API_KEY", + "DEBUG", + "E2E_*", + "INTEGRATION_INSTANCE_KEYS", + "INTEGRATION_STAGING_INSTANCE_KEYS" + ], "inputs": [ "integration/**", "packages/*/package.json", @@ -660,7 +810,15 @@ "outputLogs": "new-only" }, "//#test:integration:billing": { - "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS", "INTEGRATION_STAGING_INSTANCE_KEYS"], + "env": [ + "CLEANUP", + "CLERK_E2E_OAUTH_PROVIDER_CLIENT_SECRET", + "CLERK_PLATFORM_API_KEY", + "DEBUG", + "E2E_*", + "INTEGRATION_INSTANCE_KEYS", + "INTEGRATION_STAGING_INSTANCE_KEYS" + ], "inputs": [ "integration/**", "packages/*/package.json", @@ -681,7 +839,15 @@ "outputLogs": "new-only" }, "//#test:integration:machine": { - "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS", "INTEGRATION_STAGING_INSTANCE_KEYS"], + "env": [ + "CLEANUP", + "CLERK_E2E_OAUTH_PROVIDER_CLIENT_SECRET", + "CLERK_PLATFORM_API_KEY", + "DEBUG", + "E2E_*", + "INTEGRATION_INSTANCE_KEYS", + "INTEGRATION_STAGING_INSTANCE_KEYS" + ], "inputs": [ "integration/**", "packages/*/package.json", @@ -702,7 +868,15 @@ "outputLogs": "new-only" }, "//#test:integration:custom": { - "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS", "INTEGRATION_STAGING_INSTANCE_KEYS"], + "env": [ + "CLEANUP", + "CLERK_E2E_OAUTH_PROVIDER_CLIENT_SECRET", + "CLERK_PLATFORM_API_KEY", + "DEBUG", + "E2E_*", + "INTEGRATION_INSTANCE_KEYS", + "INTEGRATION_STAGING_INSTANCE_KEYS" + ], "inputs": [ "integration/**", "packages/*/package.json", @@ -724,7 +898,15 @@ }, "//#test:integration:cache-components": { "dependsOn": ["@clerk/nextjs#build"], - "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS", "INTEGRATION_STAGING_INSTANCE_KEYS"], + "env": [ + "CLEANUP", + "CLERK_E2E_OAUTH_PROVIDER_CLIENT_SECRET", + "CLERK_PLATFORM_API_KEY", + "DEBUG", + "E2E_*", + "INTEGRATION_INSTANCE_KEYS", + "INTEGRATION_STAGING_INSTANCE_KEYS" + ], "inputs": [ "integration/**", "packages/*/package.json", @@ -746,7 +928,14 @@ }, "//#test:integration:chrome-extension": { "dependsOn": ["@clerk/nextjs#build"], - "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS"], + "env": [ + "CLEANUP", + "CLERK_E2E_OAUTH_PROVIDER_CLIENT_SECRET", + "CLERK_PLATFORM_API_KEY", + "DEBUG", + "E2E_*", + "INTEGRATION_INSTANCE_KEYS" + ], "inputs": [ "integration/**", "packages/*/package.json", @@ -768,7 +957,15 @@ }, "//#test:integration:electron": { "dependsOn": ["@clerk/electron#build"], - "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS", "INTEGRATION_STAGING_INSTANCE_KEYS"], + "env": [ + "CLEANUP", + "CLERK_E2E_OAUTH_PROVIDER_CLIENT_SECRET", + "CLERK_PLATFORM_API_KEY", + "DEBUG", + "E2E_*", + "INTEGRATION_INSTANCE_KEYS", + "INTEGRATION_STAGING_INSTANCE_KEYS" + ], "inputs": [ "integration/**", "packages/*/package.json", From 2d68083381a06abe855189b1fd0f11b0df594276 Mon Sep 17 00:00:00 2001 From: austincalvelage Date: Wed, 4 Nov 2026 09:56:06 -0700 Subject: [PATCH 3/4] test(e2e): check scoped integration suites declare the builds they load A scoped integration suite only reruns when one of its declared builds changes, so a fixture that starts loading a new SDK would silently stop triggering reruns. The new harness test derives each scoped suite's packages from its long-running apps, the app configs its tagged tests use, and the harness, and fails when turbo.json disagrees. CI now runs the integration harness tests. --- .github/workflows/ci.yml | 4 + .../__tests__/suiteBuildDependencies.test.ts | 106 ++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 integration/presets/__tests__/suiteBuildDependencies.test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 389b299907f..492de14c4d1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -382,6 +382,10 @@ jobs: if: matrix.node-version == '24.22.2' run: pnpm vitest run --root scripts verify-attach verify-flaky + - name: Run integration harness tests + if: matrix.node-version == '24.15.0' + run: pnpm vitest run --root integration + - name: Upload Turbo Summary uses: actions/upload-artifact@v4.6.2 if: ${{ env.TURBO_SUMMARIZE == 'true' }} diff --git a/integration/presets/__tests__/suiteBuildDependencies.test.ts b/integration/presets/__tests__/suiteBuildDependencies.test.ts new file mode 100644 index 00000000000..5671ccb75dc --- /dev/null +++ b/integration/presets/__tests__/suiteBuildDependencies.test.ts @@ -0,0 +1,106 @@ +import { readdirSync, readFileSync } from 'node:fs'; +import { join, resolve } from 'node:path'; + +import { beforeAll, describe, expect, it, vi } from 'vitest'; + +import type { ApplicationConfig } from '../../models/applicationConfig'; +import type { appConfigs as AppConfigs } from '..'; + +vi.mock('../instanceKeys', () => { + class AnyInstanceKeys extends Map { + get(key: string) { + return super.get(key) ?? { pk: 'pk_test_Y2xlcmsuZXhhbXBsZS5jb20k', sk: 'sk_test_placeholder' }; + } + } + return { instanceKeys: new AnyInstanceKeys() }; +}); + +vi.mock('../../models/longRunningApplication', () => ({ + longRunningApplication: (params: { id: string; config: ApplicationConfig }) => params, +})); + +const repoRoot = resolve(import.meta.dirname, '../../..'); +const testsDir = resolve(repoRoot, 'integration/tests'); + +const turbo = JSON.parse(readFileSync(join(repoRoot, 'turbo.json'), 'utf8')); +const rootPackage = JSON.parse(readFileSync(join(repoRoot, 'package.json'), 'utf8')); + +const SERVED_BY_GLOBAL_SETUP = ['@clerk/clerk-js', '@clerk/ui']; +const harnessPackages = Object.entries(rootPackage.devDependencies) + .filter(([name, version]) => name.startsWith('@clerk/') && version.startsWith('workspace:')) + .map(([name]) => name); + +const scopedSuites = Object.entries<{ dependsOn?: string[]; inputs: string[] }>(turbo.tasks) + .filter(([id, task]) => id.startsWith('//#test:integration:') && !task.inputs.some(i => i.startsWith('packages/'))) + .map(([id, task]) => ({ suite: id.replace('//#test:integration:', ''), dependsOn: task.dependsOn ?? [] })); + +const listTestFiles = (dir: string): string[] => + readdirSync(dir, { withFileTypes: true }).flatMap(entry => { + const path = join(dir, entry.name); + if (entry.isDirectory()) { + return listTestFiles(path); + } + return /\.test\.tsx?$/.test(entry.name) ? [path] : []; + }); + +const parseScript = (suite: string) => { + const script: string | undefined = rootPackage.scripts[`test:integration:${suite}`]; + return { + script, + appIds: script?.match(/E2E_APP_ID=(\S+)/)?.[1].split(','), + tag: script?.match(/--grep (@[\w:-]+)/)?.[1], + }; +}; + +let appConfigs: typeof AppConfigs; + +beforeAll(async () => { + vi.stubEnv('CLERK_PLATFORM_API_KEY', ''); + vi.stubEnv('E2E_STAGING', ''); + ({ appConfigs } = await import('..')); +}); + +const packagesLoadedBy = (suite: string) => { + const { appIds, tag } = parseScript(suite); + const longRunning = ( + appConfigs.longRunningApps.getByPattern(appIds!) as unknown as { config: ApplicationConfig }[] + ).map(app => app.config); + + const tagPattern = new RegExp(`${tag}(?![\\w-])`); + const referenced = listTestFiles(testsDir) + .map(file => readFileSync(file, 'utf8')) + .filter(source => tagPattern.test(source)) + .flatMap(source => [...source.matchAll(/appConfigs\.(\w+)\.(\w+)/g)]) + .filter(([, group]) => group !== 'envs' && group !== 'longRunningApps') + .map( + ([, group, name]) => (appConfigs as unknown as Record>)[group][name], + ); + + return [...longRunning, ...referenced].flatMap(config => config.pkglabDependencies); +}; + +describe('scoped integration suites declare the builds they load', () => { + it('finds at least one scoped suite', () => { + expect(scopedSuites.map(s => s.suite)).toContain('express'); + }); + + it('reads the harness packages from the root manifest', () => { + expect(harnessPackages).toEqual(expect.arrayContaining(['@clerk/backend', '@clerk/shared', '@clerk/testing'])); + }); + + describe.each(scopedSuites)('$suite', ({ suite, dependsOn }) => { + it('selects its apps with E2E_APP_ID and a --grep tag', () => { + const { script, appIds, tag } = parseScript(suite); + expect(script, `package.json has no test:integration:${suite} script`).toBeDefined(); + expect(appIds, `test:integration:${suite} must set E2E_APP_ID to be scoped`).toBeDefined(); + expect(tag, `test:integration:${suite} must pass --grep @tag to be scoped`).toBeDefined(); + }); + + it('depends on exactly the builds of the packages its fixtures and harness load', () => { + const expected = [...new Set([...harnessPackages, ...SERVED_BY_GLOBAL_SETUP, ...packagesLoadedBy(suite)])] + .map(name => `${name}#build`) + .sort(); + expect([...dependsOn].sort()).toEqual(expected); + }); + }); +}); From 5e22504c49b5caf081e4c5476985349c19d4b478 Mon Sep 17 00:00:00 2001 From: austincalvelage Date: Wed, 4 Nov 2026 12:47:37 -0700 Subject: [PATCH 4/4] ci(repo): run the integration harness tests before the package unit tests The harness tests include the check that scoped integration suites declare the builds they load. Running them after the package unit tests meant a unit test timeout skipped that check. They now build only the packages the harness imports and run first. --- .github/workflows/ci.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 492de14c4d1..1772dfea2ac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -360,6 +360,14 @@ jobs: turbo-team: ${{ vars.TURBO_TEAM }} turbo-token: ${{ secrets.TURBO_TOKEN }} + - name: Run integration harness tests + if: matrix.node-version == '24.15.0' + run: | + pnpm turbo build $TURBO_ARGS --filter=@clerk/backend --filter=@clerk/shared --filter=@clerk/testing + pnpm vitest run --root integration + env: + NODE_VERSION: ${{ matrix.node-version }} + - name: Run tests in packages run: | TURBO_ARGS=$(sed -E 's/--concurrency=[0-9]+/--concurrency=1/' <<< "$TURBO_ARGS") @@ -382,10 +390,6 @@ jobs: if: matrix.node-version == '24.22.2' run: pnpm vitest run --root scripts verify-attach verify-flaky - - name: Run integration harness tests - if: matrix.node-version == '24.15.0' - run: pnpm vitest run --root integration - - name: Upload Turbo Summary uses: actions/upload-artifact@v4.6.2 if: ${{ env.TURBO_SUMMARIZE == 'true' }}