Skip to content

Importing CI Results

4 min read

Hawzu treats your CI/CD system as an execution engine and its report as the source of truth. Nothing reads your repository or makes assumptions about Playwright, pytest, Selenium or TestNG — if your framework can emit one of the formats below, Hawzu can import it.

curl --data-binary @results.xml \
     -H 'Content-Type: application/xml' \
     -H 'Authorization: Bearer <token>' \
     'https://app.hawzu.com/api/v1/workspace/<ws>/project/<proj>/import/results?run_title=CI%20build%20123'
Formatformat valueTypical source
JUnit / xUnit XMLjunitPlaywright, pytest, Jest, TestNG, Selenium, PHPUnit
TRXtrxdotnet test, MSTest, Visual Studio
NUnit 3 XMLnunitNUnit
Cucumber JSONcucumberCucumber, Behave, SpecFlow, Godog
Allure result JSONallureAllure-instrumented suites

Omit format and Hawzu detects it. Zip archives are accepted and unpacked — which is what a GitHub Actions artifact always is, and what an Allure results directory looks like.

The execution_id parameter decides how the run is scoped, and the difference matters.

Without execution_id — the report defines the run

Section titled “Without execution_id — the report defines the run”

A new run is created containing exactly the test cases the report matched. Zero setup, ideal for a pipeline that just wants its results recorded.

The trade-off: the run’s scope is whatever the pipeline happened to contain, so it can never tell you what your automation doesn’t cover.

With execution_id — results land on a run you built

Section titled “With execution_id — results land on a run you built”

Someone creates an automated test run in the app and adds the test cases the pipeline is meant to cover. The report then maps onto them:

  • Cases the report covers → results applied normally.
  • Cases in the run the report never mentions → left Not Executed. This is real signal: it is the gap between what you meant to automate and what actually ran.
  • Report rows that aren’t in the run → returned in unmatched and recorded separately. They do not affect status counts, analytics, test case history or release readiness.

Matching on the test’s name alone is a trap — renaming a test silently breaks it. Hawzu resolves each report row in this order:

  1. An explicit Hawzu code stated by the report. The most robust option, because it survives renaming the test.

  2. A code token embedded in the test name or classname, e.g. CHK-123 logs in successfully. No tooling required — just put the code in the test title.

  3. An exact match on the test case title. A convenient fallback; an ambiguous title is never guessed at.

FormatHow
JUnit<property name="testcase_code" value="CHK-123"/> on the <testcase> (or the suite)
TRXAn MSTest trait named testcase_code
NUnit<property name="testcase_code" value="CHK-123"/>
CucumberA scenario tag: @CHK-123
AllureA label named testcase_code, or a CHK-123 tag

An end-to-end test often stands in for several manual test cases. Name them all and each one receives the same verdict:

test('CHK-101 CHK-102 CHK-103 full login flow', async ({ page }) => { /* … */ });

Or state them explicitly — as one delimited value, or as repeated properties:

<property name="testcase_code" value="CHK-101,CHK-102"/>

If the run doesn’t contain one of the named cases, that case is listed individually in the run’s Unmatched tab, so a partially covered test is visible rather than quietly leaving cases Not Executed.

Playwright is the common case, and needs no reporter plumbing — putting the code in the test title is enough.

  1. Name each test with its Hawzu code.

    test('CHK-101 user can log in with valid credentials', async ({ page }) => { /* … */ });
    test('CHK-102 login fails with a wrong password',      async ({ page }) => { /* … */ });
  2. Emit JUnit XML.

    reporter: [['junit', { outputFile: 'results/junit.xml' }]],
  3. Upload the report even when tests failif: always() is the part people miss. Without it a red suite skips the upload, and Hawzu reports that the build produced no readable test report.

    on:
      workflow_dispatch:      # required for Hawzu to start this workflow
    
    jobs:
      test:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - uses: actions/setup-node@v4
            with: { node-version: 22 }
          - run: npm ci
          - run: npx playwright install --with-deps
          - run: npx playwright test
          - uses: actions/upload-artifact@v4
            if: always()      # a failing suite is exactly the result QA needs
            with:
              name: junit
              path: results/junit.xml

Results normalise to Hawzu’s statuses:

HawzuComes from
PassedA passing test
FailedA failure or error
SkippedA skipped/ignored test, or a JUnit <skipped>
BlockedAllure broken, NUnit/TRX Inconclusive, TRX Blocked, a Cucumber undefined/pending step — cases where the test never reached a verdict, so the feature was never actually judged

JUnit has no way to express Blocked. To set it, add <property name="hawzu_status" value="Blocked"/> to the test case.

When several report rows map to one test case — parametrised or retried suites — the worst status wins. A case that failed any variant is not green.

The failure message, duration and stack trace are kept as the result comment.

{
  "execution_id": "aB3xY9…",
  "matched_count": 42,
  "results_applied": 42,
  "unmatched": [
    { "name": "legacy admin smoke", "reason": "test case CHK-9 is not in this run" }
  ],
  "run": { /* the full run, including status_counts */ }
}

Set on_unmatched=error to make the whole import fail with 400 when anything doesn’t match, writing nothing. The default, skip, imports what it can and reports the rest.

Everything above is CI pushing to Hawzu. Hawzu can also pull: connect Jenkins, GitHub Actions or GitLab CI under Integrations, add a pipeline under Project Settings → Automation, then create an automated test run and press Run. Hawzu triggers the build, watches it, downloads the report and imports it — no API calls of your own.

Pipelines that already run on their own schedule can notify Hawzu instead of being polled; each automation configuration exposes a signed inbound webhook URL for that.