qTest Launch vs CI/CD API Integration: A Practical Decision Guide
Compare qTest Launch vs CI/CD REST API integration for pushing automated test results. Covers constraints, trade-offs, a GitLab CI validation script, and a decision checklist for teams using Jira and Jenkins/GitLab.
09 Nov 2026, 12:42 UTC

The Decision: How to Get Automated Results Into qTest
Teams using qTest with Jira and GitLab CI or Jenkins face a concrete architectural choice: adopt Tricentis' hosted qTest Launch agent, or build a direct REST API integration from the CI/CD pipeline. Both paths write results to the same qTest project, but they differ in network topology, framework support, maintenance ownership, and failure modes. This guide lays out the constraints, compares the options side by side, and gives you a repeatable validation you can run in a sandbox project today.
Constraints That Shape the Choice
- Network egress: Launch requires outbound HTTPS from test runners to
qtestnet.com(Cloud) or your on-prem hostname. If runners live in an air-gapped VPC, only the API path to an on-prem qTest instance works. - Framework coverage: Launch 2.x+ supports Java/TestNG, Cucumber, SpecFlow, Cypress, and Playwright via
@qtest/launch. Python/pytest, Go, Rust, or custom harnesses need the API route. - qTest version: Cloud and on-prem 9.x+ expose API v3 (Bearer token from
/oauth/tokenwithclient_credentialsgrant). On-prem 8.x uses v2 with a different payload shape — verify your version before scripting. - Token lifecycle: API integrations must handle 401 refresh without failing the pipeline; Launch manages its own token rotation.
- Payload size: Single-call
/auto-test-logsrejects bodies >10 MB (HTTP 413). Large logs or attachments require chunked upload via/test-logs/{id}/attachments.
Side-by-Side Comparison
| Dimension | qTest Launch | CI/CD REST API (v3) |
|---|---|---|
| Setup effort | Low — install agent, add @TestCaseId annotations | Medium — script token fetch, PID mapping, POST logic |
| Framework support | Java, .NET, Cypress, Playwright, Cucumber, SpecFlow | Any framework emitting JUnit XML, TRX, or custom JSON |
| Network path | Runner → Launch agent → qTest Cloud (extra hop) | Runner → qTest (direct, or via on-prem load balancer) |
| Result latency | Near real-time (agent streams) | Configurable — batch at job end or stream per suite |
| Mapping strategy | Auto via annotations (@TestCaseId("TC-100")) | Explicit PID in payload; team owns sync logic |
| Maintenance ownership | Tricentis (agent auto-updates; pin version for stability) | Team (scripts, token cache, PID audit jobs) |
| Enrichment control | Limited to built-in fields | Full — custom fields, attachments, environment tags, links |
| Air-gapped viable | No (requires egress to Tricentis) | Yes (on-prem qTest only) |
| Failure isolation | Agent outage = all runs stall | Per-pipeline; token expiry handled in script |
Trade-offs in Practice
When Launch Wins
If your stack is Java/TestNG or Cypress/Playwright, runners have internet access, and you want zero boilerplate, Launch eliminates mapping code. Annotations like @TestCaseId("TC-100") (Java) or @qtest.id('TC-100') (Cypress) auto-link results to the qTest test case. The agent streams logs as tests execute, so the qTest Execution page updates live.
Caution: Launch agent auto-updates can break framework compatibility. Pin the agent version in your Dockerfile or VM image:
FROM tricentis/qtest-launch:2.14.3 # pin, don't use :latest
When API Integration Wins
Choose the API path when you need:
- Support for pytest, Go test, custom harnesses, or any JUnit/XML producer
- Runners in a network segment without egress to qTest Cloud
- Full control over custom fields (e.g.,
Environment: staging-aws-us-east-1,BuildNumber: 4217,GitCommit: abc123) - Idempotent result posting that survives pipeline retries without duplicate runs
- Attachment uploads larger than 10 MB (chunked via
/test-logs/{id}/attachments)
The cost is owning the mapping layer. When qTest test cases are cloned or moved, their PIDs change. Automate a nightly PID audit via GET /api/v3/projects/{pid}/test-cases cross-referenced with Jira issue keys, or rely on the qTest-Jira sync if your admin has enabled it.
Concrete Validation: Smoke Test in a Sandbox Project
Run this validation in a disposable qTest project to measure setup time, log fidelity, and flake handling for both paths.
Prerequisites
- qTest Cloud or on-prem 9.x+ project (note
projectIdfrom URL) - API client credentials (Client ID/Secret) with
test_log:writescope - GitLab CI or Jenkins runner with internet access (for Launch test)
- One manual test case created in qTest — record its PID (e.g.,
TC-100→ PID12345)
Path A: CI/CD API Integration (GitLab CI Example)
# .gitlab-ci.yml
stages:
- test
- report
variables:
QTEST_URL: "https://your-instance.qtestnet.com"
QTEST_PROJECT_ID: "42" # your projectId
QTEST_CLIENT_ID: "$QTEST_CLIENT_ID" # CI variable
QTEST_CLIENT_SECRET: "$QTEST_CLIENT_SECRET"
TEST_CASE_PID: "12345" # PID from step above
get_token:
stage: test
script:
- |
TOKEN=$(curl -s -X POST "$QTEST_URL/oauth/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=$QTEST_CLIENT_ID" \
-d "client_secret=$QTEST_CLIENT_SECRET" \
| jq -r .access_token)
echo "QTEST_TOKEN=$TOKEN" >> token.env
artifacts:
reports:
dotenv: token.env
run_pytest:
stage: test
needs: [get_token]
script:
- pip install pytest pytest-junitxml
- pytest -q --junitxml=results.xml
artifacts:
reports:
junit: results.xml
when: always
post_results:
stage: report
needs: [run_pytest, get_token]
script:
- source token.env
- |
curl -s -X POST "$QTEST_URL/api/v3/projects/$QTEST_PROJECT_ID/auto-test-logs" \
-H "Authorization: Bearer $QTEST_TOKEN" \
-H "Content-Type: application/json" \
-d @- <
Verification: In qTest, open Test Execution → Automated. Find the run named "GitLab CI Run <ID>". Confirm:
- Status matches the pytest outcome (Pass/Fail)
- Custom field
Environmentshows "gitlab-ci" - Administration → Audit Log shows
AUTO_TEST_LOG_CREATEDwith your pipeline ID
Path B: qTest Launch (Same Repo, Cypress Example)
# cypress.config.js
const { defineConfig } = require('cypress');
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
require('@qtest/launch')(on, config, {
projectId: '42',
// token fetched by agent from QTEST_CLIENT_ID/SECRET env vars
});
},
},
});
# cypress/e2e/smoke.cy.js
describe('qTest Launch smoke', () => {
it('passes and maps to TC-100', { qtestId: 'TC-100' }, () => {
expect(true).to.be.true;
});
it('fails and maps to TC-101', { qtestId: 'TC-101' }, () => {
throw new Error('intentional failure');
});
});
# .gitlab-ci.yml (add to existing)
launch_test:
stage: test
image: cypress/browsers:node-20-chrome
variables:
QTEST_URL: "https://your-instance.qtestnet.com"
QTEST_CLIENT_ID: "$QTEST_CLIENT_ID"
QTEST_CLIENT_SECRET: "$QTEST_CLIENT_SECRET"
script:
- npm ci
- npx cypress run --config-file cypress.config.js
when: always
Verification: Same qTest Execution page. Compare:
- Setup time (minutes to first green run)
- Log fidelity — Launch streams console output per test; API posts only what you script
- Flake handling — re-run the pipeline; does Launch create a new run or update existing? (API path: your script decides)
Operational Checks Before Committing
- Rate limits: Cloud API defaults to ~100 req/min per token. Load-test:
for i in {1..500}; do curl -X POST ...; done— watch for 429. Implement exponential backoff in your script. - Launch agent health:
GET /api/v3/launch/agentsshould returnstatus: "ONLINE"andlast_heartbeat< 5 minutes ago. - Jira sync: After an automated run, open the linked Jira issue. The qTest panel must show the latest execution status and a link to the qTest run.
- Certificate trust (on-prem): If qTest uses self-signed certs, add the CA to the runner's trust store (
update-ca-certificates) or pass--cacert /path/to/bundle.pemto curl. Launch agent needs the same CA in its JVM trust store.
Decision Checklist
| Question | Choose Launch If | Choose API If |
|---|---|---|
| Primary frameworks? | Java/TestNG, Cypress, Playwright, Cucumber, SpecFlow | pytest, Go, custom, or mixed |
| Runner egress to qTest Cloud? | Yes | No (or on-prem only) |
| Need custom fields/attachments >10 MB? | No | Yes |
| Team capacity for mapping maintenance? | Low | Available |
| Air-gapped / strict egress policy? | No | Yes (on-prem qTest) |
| Want live streaming logs in qTest? | Yes | Build it yourself |
Limitations and Next Steps
This guide assumes qTest Cloud or on-prem 9.x+. On-prem 8.x uses API v2 — payload shapes differ (no auto-test-logs endpoint). Launch agent versions before 2.x lack Cypress/Playwright support. Validate your exact versions before investing in either path.
Run the smoke validation in a sandbox project this week. Measure actual setup time, not estimated. The API path typically takes 2–4 hours for a robust token-caching, idempotent script; Launch takes 30–60 minutes if your framework is supported. Choose based on the constraints that won't change in the next 12 months.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.