oyacloud
v1.4.0
Published
OyaCloud CLI for uploading test results from CI/CD pipelines and standalone executions
Maintainers
Readme
oyacloud
OyaCloud CLI for uploading test results from CI/CD pipelines and local runs.
Installation
npm install -g oyacloudNeeds Node.js 14 or later and direct outbound HTTPS (port 443) to api.oyaplus.com and Amazon S3
(*.amazonaws.com). Uploads through a proxy (HTTPS_PROXY) aren't supported yet — if your CI must
use one, contact [email protected].
Getting started
Create your project — Log in to app.oyaplus.com, go to Projects, and click New project. Your project API key is generated automatically.
Save your key (optional — skip if you'll always pass
--api-key):
oyacloud config set-key YOUR_PROJECT_KEY- Push your first results:
oyacloud upload \
--framework playwright_ts \
--environment staging \
--results-dir test-resultsUsage
oyacloud upload \
--framework playwright_ts \
--environment staging \
--results-dir test-results \
--api-key YOUR_PROJECT_KEYSupported frameworks
| Value | Framework | Alias |
|-------|-----------|-------|
| playwright_ts | Playwright (TypeScript/JavaScript) | |
| playwright_py | Playwright (Python) | |
| testng_java | TestNG (Java) | testng |
| junit_java | JUnit (Java) | junit |
| pytest_python | pytest (Python) | pytest |
| cypress_js | Cypress (JavaScript) | cypress |
Everything except playwright_ts and testng_java is read as JUnit XML — including
playwright_py, since pytest is what actually runs and reports there.
Where your reports are
Point --results-dir at the folder your test run writes its report to — a folder holding only
this run's files. The CLI looks inside it and uploads only the test reports for your framework.
| Framework | Report setting | --results-dir |
|-----------|----------------|-----------------|
| playwright_ts | add the JSON reporter, inside the results folder: reporter: [['html'], ['json', { outputFile: 'test-results/results.json' }]] | test-results |
| playwright_py | pytest --junitxml=test-results/results.xml (add --screenshot only-on-failure for screenshots) | test-results |
| pytest_python | pytest --junitxml=pytest-results/results.xml | pytest-results |
| testng_java | Maven Surefire (default) | target/surefire-reports |
| junit_java — Maven | Surefire / Failsafe (default) | target/surefire-reports |
| junit_java — Gradle | default | app/build/test-results/test (or build/test-results/test) |
| cypress_js | reporter: 'mocha-junit-reporter', mochaFile: 'cypress/results/results-[hash].xml', includePending: true | cypress/results |
Multi-module Maven builds have one reports folder per module — pass a pattern instead:
--results '**/target/*-reports/TEST-*.xml' (quote it).
Clean the folder before each run wherever the workspace is reused (local runs, persistent
CI agents): mvn clean test, ./gradlew clean test, rm -rf pytest-results,
rm -rf cypress/results. Playwright clears test-results/ itself.
What gets uploaded
Only your test reports — the CLI reads each file and keeps it only if it is a report for the
framework you named. Everything else stays on your machine and is listed in the log with the
reason: HTML reports, pom.xml, fixtures, TestNG's extra copies of its own results, traces and
videos.
OyaCloud · framework testng_java · 8 candidate files
✓ target/surefire-reports/testng-results.xml test report
– target/surefire-reports/TEST-TestSuite.xml copy of testng-results.xml
– target/surefire-reports/testng-failed.xml rerun definition, not results
Uploading 1 report (3 KB zipped) …
✅ Uploaded · build 3acde10a-…Screenshots: Playwright (TypeScript and Python) screenshots go along automatically when the
report names them and they are inside --results-dir. For Selenium or Cypress screenshots, pass
--attachments 'target/screenshots/*.png'. Screenshots over 5 MB are skipped.
Everything is sent as one zip in one upload, and processed in the background — the CLI doesn't wait for it. Counts, and anything that was merged or skipped during processing, are on the build in the app.
Run with --dry-run to see exactly what would be uploaded without uploading anything.
Exit codes
| Code | Meaning |
|------|---------|
| 0 | Uploaded |
| 2 | Nothing uploaded — no report for this framework in the folder, the folder doesn't exist, or the files are another framework's (the message says what to change) |
| 1 | Anything else — network, API key, plan limits, upload too large |
Limits
100 MB zipped, 200 MB unzipped and 2,000 files per upload, 50 MB per report, 5 MB per screenshot.
Options
| Option | Required | Description |
|--------|----------|-------------|
| --framework | Yes | Test framework (see table above) |
| --environment | For post-merge and regression | Environment under test (e.g. staging, qa, prod). Optional for PR and local builds — leave it out when a PR run has no deployed environment |
| --results-dir | Yes* | Folder holding this run's test reports (repeatable). Or --results |
| --results | No | Report files by pattern, e.g. '**/target/*-reports/TEST-*.xml' (repeatable; quote it) |
| --attachments | No | Screenshots the report doesn't name, e.g. 'target/screenshots/*.png' (repeatable; quote it) |
| --dry-run | No | Show what would be uploaded; upload nothing |
| --verbose | No | List every file that isn't uploaded, not just a summary |
| --api-key | No | Project API key (*skip if set via oyacloud config set-key) |
| --branch | No | Branch name |
| --commit | No | Commit SHA |
| --pr-number | No | PR or MR number |
| --target-branch | No | Target branch for PR builds (e.g. main) |
| --author | No | Author or user who triggered the run |
| --suite-name | No | Suite name label |
| --grep | No | Tag/pattern this run was filtered to (e.g. @smoke). Pass the same value you gave playwright test --grep — the report can't recover it afterwards. Auto-fills --suite-name when that isn't set. |
| --build-type | No | Override build-type detection (advanced): pr, post-merge, regression, or local |
Pass --grep whenever you filter a run. Without it a filtered run is recorded as a
full-suite run, and gets compared against full-suite runs of a completely different size.
CI/CD Integration
Pass your application's branch and commit — not the test automation repo's.
PR build
# GitHub Actions
- name: Upload test results
run: |
oyacloud upload \
--framework playwright_ts \
--environment staging \
--results-dir test-results \
--branch ${{ github.head_ref }} \
--commit ${{ github.event.pull_request.head.sha }} \
--pr-number ${{ github.event.pull_request.number }} \
--target-branch ${{ github.base_ref }} \
--api-key ${{ secrets.OYACLOUD_API_KEY }}// Jenkins
sh """
oyacloud upload \
--framework playwright_ts \
--environment staging \
--results-dir test-results \
--branch ${APP_BRANCH} \
--commit ${APP_COMMIT} \
--pr-number ${APP_PR_NUMBER} \
--target-branch ${APP_TARGET_BRANCH} \
--api-key ${OYACLOUD_API_KEY}
"""Post-merge build
# GitHub Actions
- name: Upload test results
run: |
oyacloud upload \
--framework playwright_ts \
--environment staging \
--results-dir test-results \
--branch ${{ github.ref_name }} \
--commit ${{ github.sha }} \
--api-key ${{ secrets.OYACLOUD_API_KEY }}Regression / nightly build
No branch or commit needed — just the environment you are testing against:
# GitHub Actions (scheduled)
- name: Upload test results
run: |
oyacloud upload \
--framework playwright_ts \
--environment prod \
--results-dir test-results \
--api-key ${{ secrets.OYACLOUD_API_KEY }}Multi-framework pipeline
A single project key works for all frameworks in the same pipeline:
# JUnit unit tests
oyacloud upload \
--framework junit_java \
--environment staging \
--results-dir target/surefire-reports \
--api-key ${{ secrets.OYACLOUD_API_KEY }}
# Playwright UI tests
oyacloud upload \
--framework playwright_ts \
--environment staging \
--results-dir test-results \
--api-key ${{ secrets.OYACLOUD_API_KEY }}Build type detection
OyaCloud classifies every build as pr, post-merge, regression or local — no
configuration needed. The first of these that applies wins:
--build-type, if you pass it.OYACLOUD_BUILD_TYPE, if that environment variable is set.Your CI platform's own trigger signal, when it's reliable:
| Platform | PR | Post-merge | Regression | |----------|----|-----------|-----------| | GitHub Actions |
pull_request|push|schedule| | GitLab CI |merge_request_event|push|schedule| | Azure DevOps |PullRequest|IndividualCI/BatchedCI|Schedule| | Jenkins · CircleCI · Bitbucket | PR detection only | — | — |GitLab runs
merge_request_eventpipelines only when your.gitlab-ci.ymlhas merge-request workflow rules (if: $CI_PIPELINE_SOURCE == "merge_request_event"); without them a branch with an open merge request runs apushpipeline and counts as post-merge.Jenkins: use a Multibranch Pipeline (branch, PR number, target branch) and set the Jenkins URL (Manage Jenkins → System) — the CLI recognises Jenkins by
JENKINS_URL. Jenkins doesn't say whether a build was started by a timer; for nightly builds setOYACLOUD_BUILD_TYPE=regression. Freestyle jobs provide only the commit — pass--branch "${GIT_BRANCH#origin/}"on a push job,--build-type regressionon a regression job. Execute shell runs withsh -xe, so to upload when tests fail and keep the result:status=0; <tests> || status=$?; oyacloud upload …; exit $status.The backend infers it from what you passed — a
--pr-numbermeans a PR build,--commitwith--branchmeans post-merge, neither means regression.
Environment and build type go together. Post-merge and regression builds are compared
against earlier runs in the same environment, so they need --environment; the upload is
refused without one. PR and local builds may leave it out. If no build type is given or
detected, the build counts as a regression and therefore needs an environment — pass
--build-type pr or --build-type local if it is neither.
Manual and dispatch triggers (workflow_dispatch, GitLab web/api, Azure Manual)
deliberately fall through to step 4: a manual re-run says nothing about whether code
actually merged.
Project API keys
Each project has its own API key. Generate it when you create the project, or from Projects → Configure → API Key.
Project keys are scoped — they identify which project the results belong to, so no --project flag is needed in the CLI.
To rotate a key: go to Projects → Configure → API Key → Rotate key. Update your pipelines before rotating as the old key stops working immediately.
Commands
oyacloud config set-key <apiKey>
Save API key locally. Run once after getting your project key.
oyacloud config show
Show current saved configuration (API key is masked).
Help
oyacloud --help
oyacloud upload --helpLicense
Proprietary. Copyright (c) 2026 OyaPlus LLC. All rights reserved.
You may install and run the CLI to use the OyaCloud service. You may not redistribute or modify it. See LICENSE for the full terms.
Versions up to and including 1.3.0 were published under the MIT licence and remain available under it.
