sekee-cli
The command-line interface for pushing XML test reports into Sekee from CI or a developer machine, and visual snapshots for Sekee to compare.
@sekee/sekee-cli
The same package carries the Playwright helper that captures those snapshots,
@sekee/sekee-cli/playwright. See Visual testing.
Installing
npm install -g @sekee/sekee-cli
Or run it without installing anything:
npx @sekee/sekee-cli upload-reports --help
Authentication
Every command reads a token from the SEKEE_API_TOKEN environment
variable. Create one at Administration → API Tokens; it is shown once.
# Linux / macOS
export SEKEE_API_TOKEN="your-api-token-here"
# Windows PowerShell
$env:SEKEE_API_TOKEN = "your-api-token-here"
To persist it locally, add the export to your shell profile:
echo 'export SEKEE_API_TOKEN="your-api-token-here"' >> ~/.zshrc
In CI, set it as a secret. Never commit it.
The token carries the organization, so it is also what scopes the upload. There is no separate organization argument.
Choosing a workflow
| Situation | Use |
|---|---|
| One test job, one report (or a directory of them), finishing at once | upload-reports |
| Parallel shards or suites finishing at different times | init → thread → complete |
| Playwright tests that capture visual snapshots | init → thread → snapshots → complete |
upload-reports
Uploads one or more XML reports and creates a completed run in one step.
sekee-cli upload-reports \
--url <api-url> \
--name <run-name> \
--projectID <project-id> \
--source <source-name> \
--reports <reports-path> \
[--tags <comma-separated-tags>] \
[--milestone <milestone-name>]
| Option | Alias | Description | Required |
|---|---|---|---|
--url | -u | Base URL of your Sekee API | Yes |
--name | -n | Name for the automation run | Yes |
--projectID | -p | Project ID (org-scoped) | Yes |
--source | -s | Source name for the run | Yes |
--reports | -r | Reports directory or glob pattern | Yes |
--tags | -t | Comma-separated tags | No |
--milestone | -m | Milestone name to add this run to | No |
:::note Recently changed
--milestone is new. See Milestones below.
:::
--reports accepts either a directory — in which case every **/*.xml
underneath it is collected — or a glob pattern. If it matches nothing, the
command fails rather than creating an empty run.
sekee-cli upload-reports \
--url https://api.sekee.io \
--name "Nightly Regression Tests" \
--projectID 1 \
--source "GitHub Actions" \
--tags "regression,nightly" \
--reports "./cypress/reports"
init
Creates a run with status running and prints its Run ID. Use this when several jobs will report into the same run.
sekee-cli init \
--url <api-url> \
--name <run-name> \
--projectID <project-id> \
--source <source-name> \
[--tags <comma-separated-tags>] \
[--milestone <milestone-name>]
| Option | Alias | Description | Required |
|---|---|---|---|
--url | -u | Base URL of your API | Yes |
--name | -n | Name for the run | Yes |
--projectID | -p | Project ID (org-scoped) | Yes |
--source | -s | Source name | Yes |
--tags | -t | Comma-separated tags | No |
--milestone | -m | Milestone name to add this run to | No |
sekee-cli init \
--url https://api.sekee.io \
--name "PR #42 Tests" \
--projectID 1 \
--source "CI" \
--tags "pr,automated"
# Output: Run ID: 5
Capture that id — every later call needs it.
thread
Appends a single XML report to a running run. The run's counts are incremented automatically. Call it once per worker or suite.
sekee-cli thread \
--url <api-url> \
--runId <run-id> \
--name <thread-name> \
--report <xml-file-path>
| Option | Alias | Description | Required |
|---|---|---|---|
--url | -u | Base URL of your API | Yes |
--runId | -i | Run ID returned by init | Yes |
--name | -n | Name of this thread/worker | Yes |
--report | -r | Path to one XML report | Yes |
--screenshots | Send the screenshots the report names (default on; --no-screenshots to skip) | No |
Note the singular: thread takes --report, one file. upload-reports takes
--reports, a directory or glob.
Screenshots
When the report names image attachments — Playwright writes a
[[ATTACHMENT|…]] line into a failed test's output for each screenshot —
thread uploads them with the report, and they appear beside that test in the
run, in the test case's automation history, and on the Errors page. Paths are
read relative to the report file, so run the command from anywhere.
A thread carries at most 50 screenshots and 90 MB; anything over, or missing on
disk, is listed and skipped. Anything Sekee refuses comes back as a warning,
and the report itself is stored either way — if the request carrying the
screenshots cannot get through at all, thread sends the report again on its
own and says so. upload-reports does not send
screenshots — use init, thread and complete.
:::note Recently changed
thread used to send the report alone. It sends screenshots from sekee-cli
1.1.0; pass --no-screenshots for the old behaviour.
:::
sekee-cli thread \
--url https://api.sekee.io \
--runId 5 \
--name "Worker 1 - Login Tests" \
--report "./reports/worker1-results.xml"
complete
Marks a running run as completed. Call it after every thread has finished.
sekee-cli complete --url <api-url> --runId <run-id>
| Option | Alias | Description | Required |
|---|---|---|---|
--url | -u | Base URL of your API | Yes |
--runId | -i | Run ID returned by init | Yes |
A run left un-completed stays running forever and reads as a job that never
came back — which is sometimes exactly the signal you want, so Sekee does not
time it out for you.
snapshots
Sends the visual snapshots your Playwright tests
captured with the helper to an existing run. Sekee compares each against its
approved baseline, and the CLI prints what it found and a link to review it.
Call it after the tests and thread, and before complete. Business plan.
sekee-cli snapshots \
--url <api-url> \
--runId <run-id> \
[--dir <snapshot-directory>] \
[--branch <branch>] \
[--default-branch <branch>] \
[--fail-on-changes]
| Option | Alias | Description | Required |
|---|---|---|---|
--url | -u | Base URL of your API | Yes |
--runId | -i | Run ID returned by init (or printed by upload-reports) | Yes |
--dir | Where the helper wrote the snapshots (default test-results/sekee-snapshots) | No | |
--branch | Branch the snapshots belong to (default: read from CI) | No | |
--default-branch | Your repository's default branch (default: read from CI, else main and master count as default) | No | |
--fail-on-changes | Exit 1 when a snapshot could not be compared, or changed and is not approved | No |
:::note Recently changed
snapshots and the Playwright helper are new in sekee-cli 1.2.0. Run
npm install --save-dev @sekee/sekee-cli@^1.2.0 in your test project for the
helper, and update a global install with npm i -g @sekee/sekee-cli.
:::
sekee-cli snapshots \
--url https://api.sekee.io \
--runId 5 \
--fail-on-changes
# ✔ Found 40 snapshot(s) in test-results/sekee-snapshots.
# Branch: feature/checkout, default branch: main
# ✔ 40 snapshot(s): 36 unchanged, 2 changed, 1 new, 1 failed
#
# Visual: 40 snapshot(s) — 36 unchanged, 2 changed, 1 new, 1 failed
# ⚠ checkout-payment (chromium) could not be compared: …
# Review: https://app.sekee.io/acme/visual/5
A run takes snapshots while it is running, and once completed until it is 24
hours old, so a run made by upload-reports can take them too.
Branches
Each branch has its own baselines, and compares against the default branch's until one of its own is approved. A run on the default branch, or with no branch at all, compares against and approves into the default branch's baselines. See Branches.
Unless --branch is given, the branch is read from CI, first match wins:
| CI | Variables |
|---|---|
| GitHub Actions | GITHUB_HEAD_REF (pull requests), then GITHUB_REF_NAME |
| GitLab | CI_MERGE_REQUEST_SOURCE_BRANCH_NAME, then CI_COMMIT_REF_NAME |
| CircleCI | CIRCLE_BRANCH |
| Bitbucket | BITBUCKET_BRANCH |
| Azure Pipelines | SYSTEM_PULLREQUEST_SOURCEBRANCH, then BUILD_SOURCEBRANCH |
| Jenkins | BRANCH_NAME |
A tag build has no branch, so it compares against the default branch's
baselines. The default branch comes from --default-branch, GitLab's
CI_DEFAULT_BRANCH, or the GitHub event. Without any of them, Sekee treats
main and master as the default.
Exit codes
| Exit | When |
|---|---|
0 | Every snapshot was sent and compared. With --fail-on-changes, also none failed, and every changed one is approved |
0 | The snapshot directory is empty or missing. Nothing was compared, so a shard whose tests capture no snapshots stays green, even with --fail-on-changes |
1 | Some snapshots could not be sent, so they were never compared. Always, with or without the flag |
1 | With --fail-on-changes: a snapshot is failed (Sekee could not compare it, and the CLI prints why), or changed and not approved. A rejected change still fails |
A new snapshot never fails the job. A first capture is a proposal for a baseline, approved in Sekee like a change.
Sending snapshots to the same run again compares nothing twice. Sekee answers with each one's result and what a person has decided since, which is what the flag reads. So after the changes that turned a job red are approved in Sekee, a re-run passes either way: re-running the failed job sends them to the same run, where they are now approved, and re-running everything opens a new run that compares against the new baselines. A re-run that captures a different image under the same name is answered with the run's first capture, and a warning. See Failing the build on a change.
Large suites
The CLI sends at most 50 files and 90 MB per request.
- When Sekee is busy comparing (
503), it waits as long as Sekee asks, up to two minutes, and sends the same files again, up to 5 times. - When a proxy refuses a request as too large (
413), it sends each half on its own. - A refused token, plan or run (
401,403,404), or a run that completed too long ago to take snapshots (400withRunClosed), stops the upload, since every other request would get the same answer.
The multi-step workflow end to end
# 1. Open a run
sekee-cli init \
--url https://api.sekee.io \
--name "PR #42 Tests" \
--projectID 1 --source "CI" --tags "pr,automated"
# → Run ID: 5
# 2. Each worker reports into it
sekee-cli thread --url https://api.sekee.io --runId 5 --name "Worker 1" --report "./reports/w1.xml"
sekee-cli thread --url https://api.sekee.io --runId 5 --name "Worker 2" --report "./reports/w2.xml"
sekee-cli thread --url https://api.sekee.io --runId 5 --name "Worker 3" --report "./reports/w3.xml"
# 3. If the tests capture visual snapshots, each worker sends its own
sekee-cli snapshots --url https://api.sekee.io --runId 5
# 4. Close it
sekee-cli complete --url https://api.sekee.io --runId 5
Milestones and warnings
--milestone "<name>" on upload-reports or init looks the name up
case-insensitively in the run's project and, if it matches an open
Milestone, attaches the run to it.
An unknown or closed name never fails the upload — the run is still created, without a Milestone, and the CLI prints the warning the API sends back:
⚠ Milestone "2.4 GA" not found in this project
⚠ Milestone "2.3 GA" is closed, so the run was not added to it
That is deliberate: a typo in a release name, or a Milestone somebody closed between when you wrote the CI script and when it ran, should not be the reason a test report went missing. See Milestones.
Environment variables
| Variable | Description | Required |
|---|---|---|
SEKEE_API_TOKEN | API token, from Administration → API Tokens | Yes |
SEKEE_SNAPSHOTS_DIR | Where the Playwright helper writes snapshots (default test-results/sekee-snapshots) | No |
Errors
| Error | Cause |
|---|---|
SEKEE_API_TOKEN not found | The environment variable is not set |
403 Forbidden | Invalid, expired, or missing API token |
400 Bad Request | Missing required fields, or the project was not found |
404 Not Found | The run id does not exist (thread / complete / snapshots) |
No XML files found using pattern: … | --reports matched nothing |
403 from snapshots | Invalid token, or visual testing is not on your plan (Business) |
400 from snapshots | The run completed and is more than 24 hours old (RunClosed), or the request was malformed |
✖ N snapshot(s) were not compared. | Those snapshots never reached Sekee; exit code 1 |
What it talks to
For the endpoints behind these commands, see the ingestion API reference.