CI/CD interview question
Write a GitHub Actions workflow that runs tests in parallel shards and keeps the reports
Short answer
Trigger on pull_request and push, use a matrix to split the suite into shards, cache dependencies through setup-node or setup-java, set fail-fast false so every shard reports, and upload reports and traces with actions/upload-artifact@v4 under if: always() and a unique name per shard.
Answer
A good test workflow answers three questions: when does it run, how is the work split, and where do I look when it fails. Here is the workflow I would write for a Node-based Playwright suite; the same shape applies to Java with actions/setup-java@v4.
The workflow
name: e2e
on:
pull_request:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: e2e-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npx playwright install --with-deps chromium
- name: Run shard
run: npx playwright test --shard=${{ matrix.shard }}/4
env:
BASE_URL: ${{ vars.BASE_URL }}
TEST_USER_PASSWORD: ${{ secrets.TEST_USER_PASSWORD }}
- name: Upload report
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v4
with:
name: playwright-report-${{ matrix.shard }}
path: |
playwright-report/
test-results/
retention-days: 14
Why each piece is there
- Triggers:
pull_requestfor feedback before merge,pushtomainfor post-merge confirmation,workflow_dispatchfor manual reruns. A nightly full run would addschedulewith a cron expression. concurrencywithcancel-in-progress: pushing a new commit to a PR cancels the outdated run instead of wasting runners.permissions: contents: read: least privilege for theGITHUB_TOKEN.matrix+fail-fast: false: by default a failing matrix job cancels its siblings. For tests you want every shard to finish so you see all failures.- Caching:
setup-nodewithcache: npmcaches the npm download cache keyed on the lockfile.npm cigives a clean, lockfile-exact install. - Artifacts:
if: ${{ !cancelled() }}(oralways()) makes the upload run when tests fail, which is exactly when you need it. Inupload-artifact@v4, artifact names must be unique within a run, so each shard includes its number; uploading to the same name from several jobs fails. - Secrets come from
secrets, non-sensitive config fromvars. Secrets are not passed to workflows triggered from forks by default, so plan for that if you accept external PRs.
Merging results
Four separate reports are awkward to read. Playwright supports a blob reporter per shard and a follow-up job (needs: test) that downloads all blobs with actions/download-artifact@v4 (using pattern and merge-multiple: true) and runs npx playwright merge-reports. For JUnit-based tools, a test reporting action can publish the XML as a check summary.
Common mistakes
- Leaving
fail-fastat its default and losing results from other shards. - Uploading artifacts only on success.
- Using one artifact name across matrix jobs with v4.
- Putting credentials directly in the YAML.
What interviewers look for
- Correct, current syntax (
@v4actions, matrix,ifconditions). - Thoughtful handling of failure: all shards report, artifacts always uploaded.
- Security awareness: secrets context, minimal permissions.
A strong answer covers
- Triggers on pull_request/push, plus schedule or workflow_dispatch
- Matrix strategy for sharding with fail-fast disabled
- Dependency caching to speed up runs
- Upload reports as artifacts even on failure, with unique names
- Secrets via secrets context, minimal permissions, concurrency or timeout