Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

github-actions-template.yaml 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. # GitHub Actions CI/CD Pipeline for Test Execution
  2. # Generated by BMad TEA Agent - Test Architect Module
  3. # Optimized for: Parallel Sharding, Burn-In Loop
  4. # Stack: {test_stack_type} | Framework: {test_framework}
  5. #
  6. # Variables to customize per project:
  7. # INSTALL_CMD - dependency install command (e.g., npm ci, pnpm install --frozen-lockfile, yarn --frozen-lockfile)
  8. # TEST_CMD - main test command (e.g., npm run test:e2e, npm test, npx vitest)
  9. # LINT_CMD - lint command (e.g., npm run lint)
  10. # BROWSER_INSTALL - browser install command (frontend/fullstack only; omit for backend)
  11. # BROWSER_CACHE_PATH - browser cache path (frontend/fullstack only; omit for backend)
  12. name: Test Pipeline
  13. on:
  14. push:
  15. branches: [main, develop]
  16. pull_request:
  17. branches: [main, develop]
  18. schedule:
  19. # Weekly burn-in on Sundays at 2 AM UTC
  20. - cron: "0 2 * * 0"
  21. concurrency:
  22. group: ${{ github.workflow }}-${{ github.ref }}
  23. cancel-in-progress: true
  24. jobs:
  25. # Lint stage - Code quality checks
  26. lint:
  27. name: Lint
  28. runs-on: ubuntu-latest
  29. timeout-minutes: 5
  30. steps:
  31. - uses: actions/checkout@v4
  32. - name: Determine Node version
  33. id: node-version
  34. run: |
  35. if [ -f .nvmrc ]; then
  36. echo "value=$(cat .nvmrc)" >> "$GITHUB_OUTPUT"
  37. echo "Using Node from .nvmrc"
  38. else
  39. echo "value=24" >> "$GITHUB_OUTPUT"
  40. echo "Using default Node 24 (current LTS)"
  41. fi
  42. - name: Setup Node.js
  43. uses: actions/setup-node@v4
  44. with:
  45. node-version: ${{ steps.node-version.outputs.value }}
  46. cache: "npm"
  47. - name: Install dependencies
  48. run: npm ci # Replace with INSTALL_CMD
  49. - name: Run linter
  50. run: npm run lint # Replace with LINT_CMD
  51. # Test stage - Parallel execution with sharding
  52. test:
  53. name: Test (Shard ${{ matrix.shard }})
  54. runs-on: ubuntu-latest
  55. timeout-minutes: 30
  56. needs: lint
  57. strategy:
  58. fail-fast: false
  59. matrix:
  60. shard: [1, 2, 3, 4]
  61. steps:
  62. - uses: actions/checkout@v4
  63. - name: Determine Node version
  64. id: node-version
  65. run: |
  66. if [ -f .nvmrc ]; then
  67. echo "value=$(cat .nvmrc)" >> "$GITHUB_OUTPUT"
  68. echo "Using Node from .nvmrc"
  69. else
  70. echo "value=22" >> "$GITHUB_OUTPUT"
  71. echo "Using default Node 22 (current LTS)"
  72. fi
  73. - name: Setup Node.js
  74. uses: actions/setup-node@v4
  75. with:
  76. node-version: ${{ steps.node-version.outputs.value }}
  77. cache: "npm"
  78. - name: Cache Playwright browsers
  79. uses: actions/cache@v4
  80. with:
  81. path: ~/.cache/ms-playwright
  82. key: ${{ runner.os }}-playwright-${{ hashFiles('**/package-lock.json') }}
  83. restore-keys: |
  84. ${{ runner.os }}-playwright-
  85. - name: Install dependencies
  86. run: npm ci # Replace with INSTALL_CMD
  87. # Frontend/Fullstack only — remove this step for backend-only stacks
  88. - name: Install Playwright browsers
  89. run: npx playwright install --with-deps chromium # Replace with BROWSER_INSTALL
  90. - name: Run tests (shard ${{ matrix.shard }}/4)
  91. run: npm run test:e2e -- --shard=${{ matrix.shard }}/4 # Replace with TEST_CMD + shard args
  92. - name: Upload test results
  93. if: failure()
  94. uses: actions/upload-artifact@v4
  95. with:
  96. name: test-results-${{ matrix.shard }}
  97. path: |
  98. test-results/
  99. playwright-report/
  100. retention-days: 30
  101. # Burn-in stage - Flaky test detection
  102. burn-in:
  103. name: Burn-In (Flaky Detection)
  104. runs-on: ubuntu-latest
  105. timeout-minutes: 60
  106. needs: test
  107. # Only run burn-in on PRs to main/develop or on schedule
  108. if: github.event_name == 'pull_request' || github.event_name == 'schedule'
  109. steps:
  110. - uses: actions/checkout@v4
  111. - name: Determine Node version
  112. id: node-version
  113. run: |
  114. if [ -f .nvmrc ]; then
  115. echo "value=$(cat .nvmrc)" >> "$GITHUB_OUTPUT"
  116. echo "Using Node from .nvmrc"
  117. else
  118. echo "value=22" >> "$GITHUB_OUTPUT"
  119. echo "Using default Node 22 (current LTS)"
  120. fi
  121. - name: Setup Node.js
  122. uses: actions/setup-node@v4
  123. with:
  124. node-version: ${{ steps.node-version.outputs.value }}
  125. cache: "npm"
  126. # Frontend/Fullstack only — remove this step for backend-only stacks
  127. - name: Cache Playwright browsers
  128. uses: actions/cache@v4
  129. with:
  130. path: ~/.cache/ms-playwright # Replace with BROWSER_CACHE_PATH
  131. key: ${{ runner.os }}-playwright-${{ hashFiles('**/package-lock.json') }}
  132. - name: Install dependencies
  133. run: npm ci # Replace with INSTALL_CMD
  134. # Frontend/Fullstack only — remove this step for backend-only stacks
  135. - name: Install Playwright browsers
  136. run: npx playwright install --with-deps chromium # Replace with BROWSER_INSTALL
  137. # Note: Burn-in targets UI flakiness. For backend-only stacks, remove this job entirely.
  138. - name: Run burn-in loop (10 iterations)
  139. run: |
  140. echo "🔥 Starting burn-in loop - detecting flaky tests"
  141. for i in {1..10}; do
  142. echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  143. echo "🔥 Burn-in iteration $i/10"
  144. echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  145. npm run test:e2e || exit 1 # Replace with TEST_CMD
  146. done
  147. echo "✅ Burn-in complete - no flaky tests detected"
  148. - name: Upload burn-in failure artifacts
  149. if: failure()
  150. uses: actions/upload-artifact@v4
  151. with:
  152. name: burn-in-failures
  153. path: |
  154. test-results/
  155. playwright-report/
  156. retention-days: 30
  157. # Report stage - Aggregate and publish results
  158. report:
  159. name: Test Report
  160. runs-on: ubuntu-latest
  161. needs: [test, burn-in]
  162. if: always()
  163. steps:
  164. - name: Download all artifacts
  165. uses: actions/download-artifact@v4
  166. with:
  167. path: artifacts
  168. - name: Generate summary
  169. run: |
  170. echo "## Test Execution Summary" >> $GITHUB_STEP_SUMMARY
  171. echo "" >> $GITHUB_STEP_SUMMARY
  172. echo "- **Status**: ${{ needs.test.result }}" >> $GITHUB_STEP_SUMMARY
  173. echo "- **Burn-in**: ${{ needs.burn-in.result }}" >> $GITHUB_STEP_SUMMARY
  174. echo "- **Shards**: 4" >> $GITHUB_STEP_SUMMARY
  175. echo "" >> $GITHUB_STEP_SUMMARY
  176. if [ "${{ needs.burn-in.result }}" == "failure" ]; then
  177. echo "⚠️ **Flaky tests detected** - Review burn-in artifacts" >> $GITHUB_STEP_SUMMARY
  178. fi
  179. # ============================================================================
  180. # EXTENSION PATTERNS — Script Injection Prevention
  181. # ============================================================================
  182. # When extending this template into reusable workflows, manual dispatch
  183. # workflows, or composite actions, NEVER use ${{ inputs.* }} directly in
  184. # run: blocks. Always pass through env: intermediaries.
  185. #
  186. # KEY PRINCIPLE: Inputs must be DATA, not COMMANDS.
  187. # Pass inputs through env: and interpolate as quoted arguments into fixed
  188. # commands. NEVER accept command-shaped inputs (e.g., install-command,
  189. # test-command) that get executed as shell code — even through env:.
  190. #
  191. # --- Reusable Workflow (workflow_call) ---
  192. #
  193. # on:
  194. # workflow_call:
  195. # inputs:
  196. # test-grep:
  197. # description: 'Test grep filter (data only — not a command)'
  198. # type: string
  199. # required: false
  200. # default: ''
  201. # base-ref:
  202. # description: 'Base branch for diff'
  203. # type: string
  204. # required: false
  205. # default: 'main'
  206. # burn-in-count:
  207. # description: 'Number of burn-in iterations'
  208. # type: string
  209. # required: false
  210. # default: '10'
  211. #
  212. # jobs:
  213. # test:
  214. # runs-on: ubuntu-latest
  215. # steps:
  216. # - uses: actions/checkout@v4
  217. # # Fixed command — not derived from inputs
  218. # - name: Install dependencies
  219. # run: npm ci
  220. # # ✅ SAFE — input is DATA passed as an argument to a fixed command
  221. # - name: Run tests
  222. # env:
  223. # TEST_GREP: ${{ inputs.test-grep }}
  224. # run: |
  225. # # Security: inputs passed through env: to prevent script injection
  226. # if [ -n "$TEST_GREP" ]; then
  227. # npx playwright test --grep "$TEST_GREP"
  228. # else
  229. # npx playwright test
  230. # fi
  231. #
  232. # --- Manual Dispatch (workflow_dispatch) ---
  233. #
  234. # on:
  235. # workflow_dispatch:
  236. # inputs:
  237. # test-grep:
  238. # description: 'Test grep filter (data only — not a command)'
  239. # type: string
  240. # required: false
  241. # environment:
  242. # description: 'Target environment'
  243. # type: choice
  244. # options: [staging, production]
  245. #
  246. # jobs:
  247. # run-tests:
  248. # runs-on: ubuntu-latest
  249. # steps:
  250. # - uses: actions/checkout@v4
  251. # # ✅ SAFE — input is DATA interpolated into a fixed command
  252. # - name: Run selected tests
  253. # env:
  254. # TEST_GREP: ${{ inputs.test-grep }}
  255. # run: |
  256. # # Security: inputs passed through env: to prevent script injection
  257. # npx playwright test --grep "$TEST_GREP"
  258. #
  259. # --- Composite Action (action.yml) ---
  260. #
  261. # inputs:
  262. # test-grep:
  263. # description: 'Test grep filter (data only — not a command)'
  264. # required: false
  265. # default: ''
  266. # burn-in-count:
  267. # description: 'Number of burn-in iterations'
  268. # required: false
  269. # default: '10'
  270. #
  271. # runs:
  272. # using: composite
  273. # steps:
  274. # # ✅ SAFE — inputs are DATA arguments to fixed commands
  275. # - name: Run burn-in
  276. # shell: bash
  277. # env:
  278. # TEST_GREP: ${{ inputs.test-grep }}
  279. # BURN_IN_COUNT: ${{ inputs.burn-in-count }}
  280. # run: |
  281. # # Security: inputs passed through env: to prevent script injection
  282. # for i in $(seq 1 "$BURN_IN_COUNT"); do
  283. # echo "Burn-in iteration $i/$BURN_IN_COUNT"
  284. # npx playwright test --grep "$TEST_GREP" || exit 1
  285. # done
  286. #
  287. # ❌ NEVER DO THIS:
  288. # # Direct ${{ inputs.* }} in run: — GitHub expression injection
  289. # - run: npx playwright test --grep "${{ inputs.test-grep }}"
  290. #
  291. # # Executing input-derived env var as a command — still command injection
  292. # - env:
  293. # CMD: ${{ inputs.test-command }}
  294. # run: $CMD
  295. # ============================================================================