Skip to main content

CI recipes

Working pipeline snippets for the common shapes. All of them assume SEKEE_API_TOKEN is set as a secret and that you know your project's projectId.

GitHub Actions — one job

- name: Upload test reports
if: always() # a failed test run is exactly what you want uploaded
run: |
npx @sekee/sekee-cli upload-reports \
--url ${{ secrets.SEKEE_API_URL }} \
--name "PR #${{ github.event.number }} Tests" \
--projectID ${{ secrets.SEKEE_PROJECT_ID }} \
--source "GitHub Actions" \
--tags "pr,automated" \
--reports "./cypress/reports"
env:
SEKEE_API_TOKEN: ${{ secrets.SEKEE_API_TOKEN }}

:::tip if: always() Without it the upload is skipped whenever tests fail, which is the run you most wanted to see. This is the single most common mistake in a Sekee pipeline. :::

GitHub Actions — parallel shards

Three jobs: open the run, report into it from each shard, close it.

jobs:
init:
runs-on: ubuntu-latest
outputs:
run-id: ${{ steps.init.outputs.run-id }}
steps:
- id: init
run: |
OUTPUT=$(npx @sekee/sekee-cli init \
--url ${{ secrets.SEKEE_API_URL }} \
--name "PR #${{ github.event.number }}" \
--projectID ${{ secrets.SEKEE_PROJECT_ID }} \
--source "GitHub Actions" --tags "pr")
echo "run-id=$(echo $OUTPUT | grep -oP 'Run ID: \K\d+')" >> $GITHUB_OUTPUT
env:
SEKEE_API_TOKEN: ${{ secrets.SEKEE_API_TOKEN }}

test:
needs: init
strategy:
fail-fast: false # one shard failing must not skip the others' uploads
matrix:
shard: [1, 2, 3]
runs-on: ubuntu-latest
steps:
- run: npx playwright test --shard=${{ matrix.shard }}/3
- name: Report this shard
if: always()
run: |
npx @sekee/sekee-cli thread \
--url ${{ secrets.SEKEE_API_URL }} \
--runId ${{ needs.init.outputs.run-id }} \
--name "Shard ${{ matrix.shard }}" \
--report "./results.xml"
env:
SEKEE_API_TOKEN: ${{ secrets.SEKEE_API_TOKEN }}

complete:
needs: [init, test]
if: always() # close the run even if a shard failed
runs-on: ubuntu-latest
steps:
- run: |
npx @sekee/sekee-cli complete \
--url ${{ secrets.SEKEE_API_URL }} \
--runId ${{ needs.init.outputs.run-id }}
env:
SEKEE_API_TOKEN: ${{ secrets.SEKEE_API_TOKEN }}

The two if: always() and the fail-fast: false are all doing the same job: making sure a red test run still produces a complete record.

GitLab CI

upload-reports:
stage: report
when: always
image: node:20
script:
- npx @sekee/sekee-cli upload-reports
--url "$SEKEE_API_URL"
--name "Pipeline $CI_PIPELINE_IID"
--projectID "$SEKEE_PROJECT_ID"
--source "GitLab CI"
--tags "$CI_COMMIT_REF_NAME"
--reports "./reports"

Jenkins (declarative)

post {
always {
withEnv(["SEKEE_API_TOKEN=${env.SEKEE_API_TOKEN}"]) {
sh '''
npx @sekee/sekee-cli upload-reports \
--url "$SEKEE_API_URL" \
--name "Build ${BUILD_NUMBER}" \
--projectID "$SEKEE_PROJECT_ID" \
--source "Jenkins" \
--reports "./target/surefire-reports"
'''
}
}
}

Producing the XML

Sekee reads JUnit-style XML. Common producers:

FrameworkHow
Playwrightreporter: [['junit', { outputFile: 'results.xml' }]]
Cypressmocha-junit-reporter, or cypress-multi-reporters alongside another
Jestjest-junit
Vitest--reporter=junit --outputFile=results.xml
pytestpytest --junitxml=results.xml
Maven / GradleSurefire and Gradle's test task write it by default

Naming runs so they are findable

The run name is what you will scan a list of. Include the thing that distinguishes this run from the next one — a PR number, a build number, a branch — rather than the thing they all share.

  • Good: PR #412 — checkout, Nightly 2026-09-07, main @ a1b2c3d
  • Less good: Test run, CI, Automated tests

Use --source for where it ran and --tags for how you will slice it later.