@@ -18,6 +18,49 @@ concurrency:
1818 cancel-in-progress : true
1919
2020jobs :
21+ # Detect what files changed to enable selective test execution
22+ changes :
23+ name : Detect Changes
24+ runs-on : ubuntu-latest
25+ outputs :
26+ docs-only : ${{ steps.filter.outputs.docs-only }}
27+ browser-only : ${{ steps.filter.outputs.browser-only }}
28+ node-backend : ${{ steps.filter.outputs.node-backend }}
29+ ci-config : ${{ steps.filter.outputs.ci-config }}
30+ steps :
31+ - name : Checkout code
32+ uses : actions/checkout@v4
33+
34+ - name : Detect file changes
35+ uses : dorny/paths-filter@v3
36+ id : filter
37+ with :
38+ filters : |
39+ docs-only:
40+ - 'docs/**'
41+ - '*.md'
42+ - 'LICENSE'
43+ - '.vscode/**'
44+ browser-only:
45+ - 'src/browser/**'
46+ - 'src/styles/**'
47+ - 'src/components/**'
48+ - 'src/hooks/**'
49+ - '*.stories.tsx'
50+ - '.storybook/**'
51+ node-backend:
52+ - 'src/node/**'
53+ - 'src/cli/**'
54+ - 'src/desktop/**'
55+ - 'tests/integration/**'
56+ ci-config:
57+ - '.github/**'
58+ - 'jest.config.cjs'
59+ - 'babel.config.cjs'
60+ - 'package.json'
61+ - 'bun.lockb'
62+ - 'tsconfig*.json'
63+
2164 static-check :
2265 name : Static Checks (lint + typecheck + fmt)
2366 runs-on : ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}
@@ -69,22 +112,34 @@ jobs:
69112
70113 test :
71114 name : Unit Tests
115+ needs : [changes]
72116 runs-on : ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}
73117 steps :
74118 - name : Checkout code
75119 uses : actions/checkout@v4
76120 with :
77121 fetch-depth : 0 # Required for git describe to find tags
78122
123+ - name : Skip (docs-only changes)
124+ if : needs.changes.outputs.docs-only == 'true' && needs.changes.outputs.node-backend != 'true' && needs.changes.outputs.browser-only != 'true' && needs.changes.outputs.ci-config != 'true'
125+ run : |
126+ echo "::notice::Skipping unit tests - docs-only changes"
127+ echo "## Unit Tests Skipped" >> $GITHUB_STEP_SUMMARY
128+ echo "Only documentation files changed." >> $GITHUB_STEP_SUMMARY
129+
79130 - uses : ./.github/actions/setup-mux
131+ if : needs.changes.outputs.docs-only != 'true' || needs.changes.outputs.node-backend == 'true' || needs.changes.outputs.browser-only == 'true' || needs.changes.outputs.ci-config == 'true'
80132
81133 - name : Build worker files
134+ if : needs.changes.outputs.docs-only != 'true' || needs.changes.outputs.node-backend == 'true' || needs.changes.outputs.browser-only == 'true' || needs.changes.outputs.ci-config == 'true'
82135 run : make build-main
83136
84137 - name : Run tests with coverage
138+ if : needs.changes.outputs.docs-only != 'true' || needs.changes.outputs.node-backend == 'true' || needs.changes.outputs.browser-only == 'true' || needs.changes.outputs.ci-config == 'true'
85139 run : bun test --coverage --coverage-reporter=lcov ${{ github.event.inputs.test_filter || 'src' }}
86140
87141 - name : Upload coverage to Codecov
142+ if : needs.changes.outputs.docs-only != 'true' || needs.changes.outputs.node-backend == 'true' || needs.changes.outputs.browser-only == 'true' || needs.changes.outputs.ci-config == 'true'
88143 uses : codecov/codecov-action@v5
89144 with :
90145 token : ${{ secrets.CODECOV_TOKEN }}
@@ -94,80 +149,68 @@ jobs:
94149
95150 integration-test :
96151 name : Integration Tests
152+ needs : [changes]
97153 timeout-minutes : 10
98154 runs-on : ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}
99155 steps :
100156 - name : Checkout code
101157 uses : actions/checkout@v4
102158 with :
103- fetch-depth : 0 # Required for git describe to find tags and diff
104-
105- - uses : ./.github/actions/setup-mux
106-
107- - name : Build worker files
108- run : make build-main
109-
110- # Try to restore coverage map from cache for selective testing
111- - name : Restore coverage map
112- if : github.event.inputs.test_filter == ''
113- id : coverage-map-cache
114- uses : actions/cache/restore@v4
115- with :
116- path : coverage-map.json
117- key : coverage-map-${{ github.sha }}
118- restore-keys : |
119- coverage-map-latest-
120- coverage-map-
159+ fetch-depth : 0 # Required for git describe to find tags
121160
122- - name : Select affected tests
123- if : github.event.inputs.test_filter == '' && steps.coverage-map-cache.outputs.cache-hit != ''
124- id : select-tests
161+ # Skip conditions: docs-only OR browser-only (no backend changes)
162+ - name : Check if tests should run
163+ id : should-run
125164 run : |
126- set +e
127- SELECTED=$(bun scripts/selective-tests/select-affected-tests.ts \
128- --map coverage-map.json \
129- --base origin/${{ github.base_ref || 'main' }} \
130- --head ${{ github.sha }} \
131- --output jest \
132- --verbose 2>&1)
133- EXIT_CODE=$?
134- set -e
135-
136- # Extract just the test list (last line of output)
137- TEST_LIST=$(echo "$SELECTED" | tail -1)
138-
139- echo "exit_code=$EXIT_CODE" >> $GITHUB_OUTPUT
140- if [[ $EXIT_CODE -eq 0 ]]; then
141- echo "selected_tests=$TEST_LIST" >> $GITHUB_OUTPUT
142- echo "run_selective=true" >> $GITHUB_OUTPUT
165+ DOCS_ONLY="${{ needs.changes.outputs.docs-only }}"
166+ BROWSER_ONLY="${{ needs.changes.outputs.browser-only }}"
167+ NODE_BACKEND="${{ needs.changes.outputs.node-backend }}"
168+ CI_CONFIG="${{ needs.changes.outputs.ci-config }}"
169+
170+ # Run if: manual filter provided, OR node-backend changed, OR ci-config changed
171+ # Skip if: docs-only OR (browser-only AND NOT node-backend AND NOT ci-config)
172+ if [[ -n "${{ github.event.inputs.test_filter }}" ]]; then
173+ echo "run=true" >> $GITHUB_OUTPUT
174+ echo "reason=Manual test filter provided" >> $GITHUB_OUTPUT
175+ elif [[ "$NODE_BACKEND" == "true" ]] || [[ "$CI_CONFIG" == "true" ]]; then
176+ echo "run=true" >> $GITHUB_OUTPUT
177+ echo "reason=Backend or CI config changes detected" >> $GITHUB_OUTPUT
178+ elif [[ "$DOCS_ONLY" == "true" ]]; then
179+ echo "run=false" >> $GITHUB_OUTPUT
180+ echo "reason=Docs-only changes" >> $GITHUB_OUTPUT
181+ elif [[ "$BROWSER_ONLY" == "true" ]]; then
182+ echo "run=false" >> $GITHUB_OUTPUT
183+ echo "reason=Browser-only changes (no backend impact)" >> $GITHUB_OUTPUT
143184 else
144- echo "run_selective=false" >> $GITHUB_OUTPUT
185+ # Default: run tests (safety fallback)
186+ echo "run=true" >> $GITHUB_OUTPUT
187+ echo "reason=Default - running all tests" >> $GITHUB_OUTPUT
145188 fi
146189
147- - name : Run selective integration tests
148- if : steps.select-tests .outputs.run_selective == 'true' && steps.select-tests.outputs.selected_tests != '--testPathPattern=^$ '
190+ - name : Skip integration tests
191+ if : steps.should-run .outputs.run != 'true '
149192 run : |
150- echo "Running selective tests: ${{ steps.select-tests.outputs.selected_tests }}"
151- TEST_INTEGRATION=1 bun x jest --coverage --maxWorkers=100% --silent ${{ steps.select-tests.outputs.selected_tests }}
152- env :
153- OPENAI_API_KEY : ${{ secrets.OPENAI_API_KEY }}
154- ANTHROPIC_API_KEY : ${{ secrets.ANTHROPIC_API_KEY }}
193+ echo "::notice::Skipping integration tests - ${{ steps.should-run.outputs.reason }}"
194+ echo "## Integration Tests Skipped" >> $GITHUB_STEP_SUMMARY
195+ echo "${{ steps.should-run.outputs.reason }}" >> $GITHUB_STEP_SUMMARY
155196
156- - name : Skip tests (no affected tests)
157- if : steps.select-tests.outputs.run_selective == 'true' && steps.select-tests.outputs.selected_tests == '--testPathPattern=^$'
158- run : |
159- echo "::notice::No integration tests affected by changes - skipping"
160- echo "No integration tests were affected by the changes in this PR." >> $GITHUB_STEP_SUMMARY
197+ - uses : ./.github/actions/setup-mux
198+ if : steps.should-run.outputs.run == 'true'
161199
162- - name : Run all integration tests (fallback or manual filter)
163- if : steps.select-tests.outputs.run_selective != 'true'
200+ - name : Build worker files
201+ if : steps.should-run.outputs.run == 'true'
202+ run : make build-main
203+
204+ - name : Run integration tests
205+ if : steps.should-run.outputs.run == 'true'
164206 # --silent suppresses per-test output (17+ test files × workers = overwhelming logs)
165207 run : TEST_INTEGRATION=1 bun x jest --coverage --maxWorkers=100% --silent ${{ github.event.inputs.test_filter || 'tests' }}
166208 env :
167209 OPENAI_API_KEY : ${{ secrets.OPENAI_API_KEY }}
168210 ANTHROPIC_API_KEY : ${{ secrets.ANTHROPIC_API_KEY }}
169211
170212 - name : Upload coverage to Codecov
213+ if : steps.should-run.outputs.run == 'true'
171214 uses : codecov/codecov-action@v5
172215 with :
173216 token : ${{ secrets.CODECOV_TOKEN }}
@@ -177,32 +220,64 @@ jobs:
177220
178221 storybook-test :
179222 name : Storybook Interaction Tests
223+ needs : [changes]
180224 runs-on : ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}
181- if : github.event.inputs.test_filter == ''
182225 steps :
183226 - name : Checkout code
184227 uses : actions/checkout@v4
185228 with :
186229 fetch-depth : 0 # Required for git describe to find tags
187230
231+ - name : Check if tests should run
232+ id : should-run
233+ run : |
234+ DOCS_ONLY="${{ needs.changes.outputs.docs-only }}"
235+ BROWSER_ONLY="${{ needs.changes.outputs.browser-only }}"
236+ NODE_BACKEND="${{ needs.changes.outputs.node-backend }}"
237+ CI_CONFIG="${{ needs.changes.outputs.ci-config }}"
238+ MANUAL_FILTER="${{ github.event.inputs.test_filter }}"
239+
240+ # Skip if manual filter (user wants specific tests) or docs-only
241+ if [[ -n "$MANUAL_FILTER" ]]; then
242+ echo "run=false" >> $GITHUB_OUTPUT
243+ echo "reason=Manual test filter provided" >> $GITHUB_OUTPUT
244+ elif [[ "$DOCS_ONLY" == "true" ]] && [[ "$BROWSER_ONLY" != "true" ]] && [[ "$NODE_BACKEND" != "true" ]] && [[ "$CI_CONFIG" != "true" ]]; then
245+ echo "run=false" >> $GITHUB_OUTPUT
246+ echo "reason=Docs-only changes" >> $GITHUB_OUTPUT
247+ else
248+ echo "run=true" >> $GITHUB_OUTPUT
249+ fi
250+
251+ - name : Skip storybook tests
252+ if : steps.should-run.outputs.run != 'true'
253+ run : |
254+ echo "::notice::Skipping storybook tests - ${{ steps.should-run.outputs.reason }}"
255+ echo "## Storybook Tests Skipped" >> $GITHUB_STEP_SUMMARY
256+ echo "${{ steps.should-run.outputs.reason }}" >> $GITHUB_STEP_SUMMARY
257+
188258 - uses : ./.github/actions/setup-mux
259+ if : steps.should-run.outputs.run == 'true'
189260
190261 - uses : ./.github/actions/setup-playwright
262+ if : steps.should-run.outputs.run == 'true'
191263
192264 - name : Build Storybook
265+ if : steps.should-run.outputs.run == 'true'
193266 run : make storybook-build
194267
195268 - name : Serve Storybook
269+ if : steps.should-run.outputs.run == 'true'
196270 run : |
197271 bun x http-server storybook-static -p 6006 &
198272 sleep 5
199273
200274 - name : Run Storybook tests
275+ if : steps.should-run.outputs.run == 'true'
201276 run : make test-storybook
202277
203278 e2e-test :
204279 name : E2E Tests (${{ matrix.os }})
205- if : github.event.inputs.test_filter == ''
280+ needs : [changes]
206281 strategy :
207282 fail-fast : false
208283 matrix :
@@ -222,24 +297,53 @@ jobs:
222297 with :
223298 fetch-depth : 0 # Required for git describe to find tags
224299
300+ - name : Check if tests should run
301+ id : should-run
302+ run : |
303+ DOCS_ONLY="${{ needs.changes.outputs.docs-only }}"
304+ BROWSER_ONLY="${{ needs.changes.outputs.browser-only }}"
305+ NODE_BACKEND="${{ needs.changes.outputs.node-backend }}"
306+ CI_CONFIG="${{ needs.changes.outputs.ci-config }}"
307+ MANUAL_FILTER="${{ github.event.inputs.test_filter }}"
308+
309+ # Skip if manual filter or docs-only
310+ if [[ -n "$MANUAL_FILTER" ]]; then
311+ echo "run=false" >> $GITHUB_OUTPUT
312+ echo "reason=Manual test filter provided" >> $GITHUB_OUTPUT
313+ elif [[ "$DOCS_ONLY" == "true" ]] && [[ "$BROWSER_ONLY" != "true" ]] && [[ "$NODE_BACKEND" != "true" ]] && [[ "$CI_CONFIG" != "true" ]]; then
314+ echo "run=false" >> $GITHUB_OUTPUT
315+ echo "reason=Docs-only changes" >> $GITHUB_OUTPUT
316+ else
317+ echo "run=true" >> $GITHUB_OUTPUT
318+ fi
319+
320+ - name : Skip E2E tests
321+ if : steps.should-run.outputs.run != 'true'
322+ run : |
323+ echo "::notice::Skipping E2E tests - ${{ steps.should-run.outputs.reason }}"
324+ echo "## E2E Tests Skipped" >> $GITHUB_STEP_SUMMARY
325+ echo "${{ steps.should-run.outputs.reason }}" >> $GITHUB_STEP_SUMMARY
326+
225327 - uses : ./.github/actions/setup-mux
328+ if : steps.should-run.outputs.run == 'true'
226329
227330 - name : Install xvfb (Linux)
228- if : matrix.os == 'linux'
331+ if : steps.should-run.outputs.run == 'true' && matrix.os == 'linux'
229332 run : |
230333 sudo apt-get update
231334 sudo apt-get install -y xvfb
232335
233336 - uses : ./.github/actions/setup-playwright
337+ if : steps.should-run.outputs.run == 'true'
234338
235339 - name : Run comprehensive e2e tests (Linux)
236- if : matrix.os == 'linux'
340+ if : steps.should-run.outputs.run == 'true' && matrix.os == 'linux'
237341 run : xvfb-run -a make test-e2e
238342 env :
239343 ELECTRON_DISABLE_SANDBOX : 1
240344
241345 - name : Run window lifecycle e2e tests (macOS)
242- if : matrix.os == 'macos'
346+ if : steps.should-run.outputs.run == 'true' && matrix.os == 'macos'
243347 run : make test-e2e PLAYWRIGHT_ARGS="tests/e2e/scenarios/windowLifecycle.spec.ts"
244348
245349 docker-smoke-test :
@@ -311,23 +415,49 @@ jobs:
311415
312416 mux-server-smoke-test :
313417 name : Mux Server Smoke Test
418+ needs : [changes]
314419 runs-on : ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}
315- # Tests oRPC/WebSocket flows that catch MockBrowserWindow bugs
316420 steps :
317421 - name : Checkout code
318422 uses : actions/checkout@v4
319423 with :
320424 fetch-depth : 0 # Required for git describe to find tags
321425
426+ - name : Check if tests should run
427+ id : should-run
428+ run : |
429+ DOCS_ONLY="${{ needs.changes.outputs.docs-only }}"
430+ NODE_BACKEND="${{ needs.changes.outputs.node-backend }}"
431+ CI_CONFIG="${{ needs.changes.outputs.ci-config }}"
432+
433+ # Skip if docs-only, run if backend or CI changes
434+ if [[ "$DOCS_ONLY" == "true" ]] && [[ "$NODE_BACKEND" != "true" ]] && [[ "$CI_CONFIG" != "true" ]]; then
435+ echo "run=false" >> $GITHUB_OUTPUT
436+ echo "reason=Docs-only changes" >> $GITHUB_OUTPUT
437+ else
438+ echo "run=true" >> $GITHUB_OUTPUT
439+ fi
440+
441+ - name : Skip smoke test
442+ if : steps.should-run.outputs.run != 'true'
443+ run : |
444+ echo "::notice::Skipping mux server smoke test - ${{ steps.should-run.outputs.reason }}"
445+ echo "## Mux Server Smoke Test Skipped" >> $GITHUB_STEP_SUMMARY
446+ echo "${{ steps.should-run.outputs.reason }}" >> $GITHUB_STEP_SUMMARY
447+
322448 - uses : ./.github/actions/setup-mux
449+ if : steps.should-run.outputs.run == 'true'
323450
324451 - name : Build application
452+ if : steps.should-run.outputs.run == 'true'
325453 run : make build
326454
327455 - name : Pack npm package
456+ if : steps.should-run.outputs.run == 'true'
328457 run : npm pack
329458
330459 - name : Run smoke test
460+ if : steps.should-run.outputs.run == 'true'
331461 env :
332462 SERVER_PORT : 3001
333463 SERVER_HOST : localhost
0 commit comments