Testland
Browse all skills & agents

qase-io-case-management

Author and manage Qase.io test cases via the Public API v1 - create cases, organise into suites, attach structured steps, link to Jira/Linear/GitHub, manage shared steps, and bulk-import via JSON. Covers Token header auth, /case/{project_code} CRUD endpoints, the steps array with action / expected_result / data shape, and shared-step reuse. Use for pre-execution case authoring in teams using Qase as a modern lightweight TCM.

Install with skills.sh (any agent)

npx skills add testland/qa --skill qase-io-case-management
View source

qase-io-case-management

Overview

Qase.io is a modern lightweight TCM popular with smaller / agile teams that find TestRail / Xray heavy. It offers a clean Public API v1 (Token-based auth, REST + OpenAPI spec) and a simpler data model than its competitors.

Per developers.qase.io (Cloudflare-protected; cite by stable URL).

For canonical anatomy, see test-case-anatomy-reference.

When to use

  • Authoring cases in teams using Qase.io as TCM.
  • Bulk-importing from CSV / legacy TCM into Qase.
  • Programmatic case management (mass-edit, tagging, status transitions).
  • Backing test-case quality scans for Qase-using teams.

Authoring

Authentication

Qase Public API v1 uses Token header authentication:

export QASE_TOKEN="<api-token-from-qase.io-settings>"
import requests, os

BASE = "https://api.qase.io/v1"
HEADERS = {
    "Token": os.environ["QASE_TOKEN"],
    "Content-Type": "application/json",
}

Note the header is literally Token (not Authorization), which is unusual.

Create a case

POST /case/{project_code}:

def create_case(project_code, title, description=None, preconditions=None,
                postconditions=None, steps=None, suite_id=None,
                severity=4, priority=2, type=1, automation=0,
                status=1, params=None):
    """
    severity: 1=Blocker, 2=Critical, 3=Major, 4=Normal, 5=Minor, 6=Trivial
    priority: 1=High, 2=Medium, 3=Low
    type:     1=Functional, 2=Smoke, 3=Regression, 4=Security, etc. (per project enum)
    automation: 0=Manual, 1=Automated, 2=To-be-automated
    status:   0=Actual, 1=Draft, 2=Deprecated
    steps: list of {"action": "...", "expected_result": "...", "data": "..."}
    """
    body = {
        "title": title,
        "description": description,
        "preconditions": preconditions,
        "postconditions": postconditions,
        "severity": severity,
        "priority": priority,
        "type": type,
        "automation": automation,
        "status": status,
        "suite_id": suite_id,
        "steps": steps or [],
        "params": params or {},
    }
    r = requests.post(f"{BASE}/case/{project_code}",
                      json=body, headers=HEADERS)
    r.raise_for_status()
    return r.json()

Severity + priority + type enums

The full enum tables, the endpoint map, and response shapes are in references/qase-api-reference.md. The create_case docstring above lists the values you need inline; note Qase priority is inverted (1=High, not 1=Critical).

Steps

steps = [
    {"action": "Navigate to /login",
     "expected_result": "Login form rendered",
     "data": ""},
    {"action": "Enter alice@example.com + correct password",
     "expected_result": "Submit button enabled",
     "data": "alice@example.com / pw123"},
    {"action": "Click Submit",
     "expected_result": "Redirected to /dashboard within 2 s",
     "data": ""},
]
case = create_case("AUTH", "Login redirects to dashboard",
                   steps=steps, suite_id=42,
                   severity=3, priority=1)

Suites (test suite hierarchy)

def create_suite(project_code, title, description=None, parent_id=None):
    body = {"title": title, "description": description,
            "parent_id": parent_id}
    r = requests.post(f"{BASE}/suite/{project_code}",
                      json=body, headers=HEADERS)
    r.raise_for_status()
    return r.json()

Suites nest; create the hierarchy first, then place cases.

Shared steps

A unique Qase feature: define a step once, reuse across cases.

def create_shared_step(project_code, title, action, expected_result, data=None):
    r = requests.post(
        f"{BASE}/shared_step/{project_code}",
        json={"title": title, "action": action,
              "expected_result": expected_result, "data": data},
        headers=HEADERS,
    )
    r.raise_for_status()
    return r.json()

# Reference shared step in a case
steps = [
    {"shared_step_hash": shared_step_hash},
    {"action": "...", "expected_result": "..."},
]

Update a case

PATCH /case/{project_code}/{id}:

def update_case(project_code, case_id, **fields):
    r = requests.patch(f"{BASE}/case/{project_code}/{case_id}",
                       json=fields, headers=HEADERS)
    r.raise_for_status()
    return r.json()

Get + list

case = requests.get(f"{BASE}/case/{project_code}/{case_id}",
                    headers=HEADERS).json()

def list_cases(project_code, limit=100):
    cases = []
    offset = 0
    while True:
        r = requests.get(f"{BASE}/case/{project_code}",
                         params={"limit": limit, "offset": offset},
                         headers=HEADERS)
        r.raise_for_status()
        data = r.json().get("result", {})
        cases.extend(data.get("entities", []))
        if len(data.get("entities", [])) < limit:
            break
        offset += limit
    return cases

Response shape: {"status": true, "result": {"total", "filtered", "count", "entities": [...]}}.

Running

Bulk import via CSV

Wrap each row in try/except, throttle to ~60 req/min, and tally successes so one bad row does not abort the run:

import csv, time

rows = list(csv.DictReader(open("legacy.csv")))
created, failed = [], []
for row in rows:
    steps = [
        {"action": s, "expected_result": e, "data": d}
        for s, e, d in zip(
            row["steps"].split("|"),
            row["expected"].split("|"),
            (row.get("data") or "").split("|"),
        )
    ]
    try:
        r = create_case(
            project_code=row["project"],
            title=row["title"],
            preconditions=row.get("preconditions"),
            steps=steps,
            severity=int(row.get("severity", 4)),
            priority=int(row.get("priority", 2)),
            suite_id=int(row["suite_id"]),
        )
        created.append(r["result"]["id"])
    except Exception as e:
        failed.append((row["title"], str(e)))
    time.sleep(1)  # ~60 req/min; avoids 429s

Verify: assert len(created) + len(failed) == len(rows) and that failed is empty before treating the import as done. If rows failed, inspect each (title, error), fix the source row (or back off on 429s), and re-run only the failed titles.

Link cases to issues

Qase supports linking via the tags / external_issues field (per project integration):

update_case(project_code, case_id, tags=["jira:ENG-123"])

The platform supports first-class integrations with Jira / GitHub / Linear; configure in Qase UI.

Parsing results

POST /case/{project_code} returns {"status": true, "result": {"id": N}}. Build permalink:

url = f"https://app.qase.io/project/{project_code}?case={case_id}"

CI integration

- name: Sync Qase cases
  env:
    QASE_TOKEN: ${{ secrets.QASE_TOKEN }}
  run: python scripts/sync-qase.py

Have sync-qase.py exit non-zero when any case fails to sync, and after the run verify the repository case count matches the source count (compare list_cases length against the CSV row count); a mismatch means a silent partial sync - fail the job and re-run.

For result reporting after CI runs, use the qase-pytest / qase-cypress / qase-playwright reporters that post to /result/{project_code} (different surface from this case- management API).

Anti-patterns

Anti-patternWhy it failsFix
Authorization: Bearer <token>Qase uses Token header, not AuthorizationSet Token header directly
Hard-coded severity/priority integersEasy to mix up the inverted Qase conventionUse named constants per the enum table
Inlining shared steps everywhereRepeated maintenance, driftDefine shared steps; reference via shared_step_hash
Single suite for everythingHard to navigate at scaleSuite per feature area
Skipping automation fieldCoverage reports incompleteSet automation field per case
Bulk-create without rate throttling429s on >100 cases / minThrottle to ~60 req / min

Limitations

  • Smaller market share. Fewer integrations than TestRail / Xray; some tools (specific CI plugins) may not exist.
  • Inverted priority enum. Qase priority 1=High (vs IEEE convention 1=Critical); careful when mapping cross-tool.
  • No layered scenario. Steps are flat (no nesting like Allure TestOps).
  • Custom field discipline. Tenant-specific; scripts must discover field IDs.
  • Public API v1 only. API v2 announced but not yet stable at publication.

References

  • Qase Public API v1 docs - developers.qase.io (Cloudflare- protected; cite by stable URL).
  • Qase API reference (Swagger / OpenAPI) - developers.qase.io.
  • qase-python SDK - github.com/qase-tms/qase-python.
  • Sibling references: test-case-anatomy-reference, severity-vs-priority-reference.
  • Sibling skills: testrail-case-management, xray-case-management, zephyr-scale-case-management, allure-testops-case-management.

Qase.io Public API v1 reference

View source (opens in new window)

Qase.io Public API v1 reference

Field enums, endpoint map, and response shapes for the Qase Public API v1. Per developers.qase.io schema definitions (Cloudflare-protected; cite by stable URL).

Field enums

FieldValues
severity1=Blocker, 2=Critical, 3=Major, 4=Normal, 5=Minor, 6=Trivial
priority1=High, 2=Medium, 3=Low
type1=Functional, 2=Smoke, 3=Regression, 4=Security, 5=Usability, 6=Performance, 7=Acceptance, 8=Compatibility (defaults; configurable)
automation0=Manual, 1=Automated, 2=To-be-automated
status0=Actual, 1=Draft, 2=Deprecated

Map per severity-vs-priority-reference (in the qa-defect-management plugin); note Qase priority is reversed from the defect-management convention (1=High here vs 1=Critical in IEEE 1044).

Endpoints

Verb + pathPurpose
POST /case/{project_code}Create a case
PATCH /case/{project_code}/{id}Update a case
GET /case/{project_code}/{id}Get one case
GET /case/{project_code}List cases (paginate with limit / offset)
POST /suite/{project_code}Create a suite
POST /shared_step/{project_code}Create a shared step
POST /result/{project_code}Post run results (different surface; not case authoring)

Response shapes

  • Create returns {"status": true, "result": {"id": N}}.
  • List returns {"status": true, "result": {"total", "filtered", "count", "entities": [...]}}.
  • Paginate a list until len(entities) < limit.

Shared-step reuse

Define a step once, then reference it inside a case via its shared_step_hash:

steps = [ {"shared_step_hash": shared_step_hash}, {"action": "...", "expected_result": "..."}, ]

Related skills

allure-testops-case-management

Author and manage Allure TestOps test cases via the REST API - create cases, manage projects + suites, attach scenarios with nested steps, link to Jira / GitHub issues, sync with allure-results from CI runs. Covers Bearer-token auth, /api/rs/testcase CRUD endpoints, nested-step `scenario` shape, and the unique Allure TestOps feature of linking automated results back to manual case definitions. Use for pre-execution case authoring in teams using Allure TestOps as the canonical TCM.

test-case-anatomy-reference

Pure-reference catalog of test-case anatomy - what fields a well-formed test case must have and what each field means. Enumerates the ISO/IEC/IEEE 29119-3:2021 test-case template fields (identifier, objective, preconditions, inputs, steps, expected results, postconditions, environment, traceability) and the ISTQB CTAL-TM specification-technique-driven additions (equivalence partition, boundary value, decision table, state transition). Maps the canonical anatomy to five tracker-specific schemas (TestRail, Xray, Zephyr Scale, Allure TestOps, Qase). Use as the authoritative source when authoring a case template, reviewing case quality, or migrating between tools.

test-case-review-rubric

Scores an already-written test case against six per-case quality axes (objective specificity, precondition executability, step granularity, step abstraction level, expected-result observability, traceability validity) and six set-level axes (partition coverage, boundary coverage, duplication, orphan and uncovered requirements, tier shape, identifier consistency). Derives a per-case PASS / WEAK / FAIL verdict and a set verdict from it without averaging, and marks every threshold as either standard-backed (ISTQB glossary, ISTQB CTFL v4.0, ISO/IEC/IEEE 29119-3:2021) or practitioner convention (step-count ceiling, tier bands, provenance threshold). Assumes the case field list and field cardinality are already defined by a test-case anatomy reference and judges content quality only. Use when reviewing a batch of hand-written test cases before promoting them to a release suite or handing them to an automation engineer.

testrail-case-management

Author and manage test cases in TestRail via REST API v2 - create cases, organise into suites + sections, update steps + expected results, bulk import from CSV/JSON, set automation status, link to references (Jira / requirements). Covers the Steps / Text / Exploratory templates, custom-field discovery (`get_case_fields`), and pagination on `get_cases`. Use for pre-execution case authoring and repository management. Do NOT use for submitting test-run results (pass/fail, status updates): posting results via add_results_for_cases is a separate post-execution concern.

traceability-matrix-builder

Build-an-X workflow that produces a requirements-to-tests traceability matrix from a TCM case repository + a requirements source (Jira / Linear / GitHub Issues). Walks the author through (1) extracting requirements with stable IDs, (2) extracting cases + their refs, (3) computing coverage (which requirements have at least one test, which tests verify which requirements, orphaned cases / orphaned requirements), (4) emitting a CSV / Markdown / HTML matrix, and (5) producing an executive summary (X% requirement coverage, Y orphans, Z over-tested). Use for test coverage audits, finding requirements-coverage gaps, sprint-end coverage reviews, compliance documentation, and traceability in regulated industries.

xray-case-management

Author and manage Xray test cases (Jira issues with Test issue type) via the GraphQL + REST APIs - create tests, attach steps, link preconditions, set testType (Manual / Cucumber / Generic), associate with requirements, bulk import via JSON. Covers OAuth client_id/client_secret auth, the GraphQL createTest mutation, the REST /api/v2/import/test/bulk endpoint, and the Cucumber-style scenario authoring path. Use for pre-execution case authoring in Jira-anchored teams using Xray. Distinct from Xray's test-execution / test-run features which post results.

zephyr-scale-case-management

Author and manage Zephyr Scale Cloud test cases via the REST API v2 - create tests, attach steps, link to Jira issues, organise into folders, manage test cycles. Covers Bearer-token auth, the /testcases endpoints, the testScript / steps shape, and folder hierarchy. Use for pre-execution case authoring in Jira-anchored teams using Zephyr Scale (formerly TM4J). Distinct from Zephyr's test-cycle / execution endpoints which post results.