Tutorial

Import your first report

Upload a small JUnit test report to a test project, follow it to a finished run, and clean up. When TestLookup accepts the report, it has only queued it. The run isn't finished yet.

Try the sample first

Prerequisites

You need three things:

The user workflow guide on GitHub describes the whole journey, from upload to a reviewed report. This page covers the first part: the upload, reading the file and the finished run.

Create a small test report

The command below creates a small JUnit report with one passing test and one failing test. That way, you’ll see both a saved result and a failure. Run this command, and the later ones, from the same folder:

Terminal window
cat > results.xml <<'XML'
<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="website-tutorial" tests="2" failures="1" errors="0" skipped="0" time="0.03">
<testcase classname="tutorial.Sample" name="passes" time="0.01" />
<testcase classname="tutorial.Sample" name="fails" time="0.02">
<failure message="expected true">example tutorial failure</failure>
</testcase>
</testsuite>
XML

Use made-up test data in this file. Don’t include production logs, passwords or keys, personal data or signed storage links.

Set up a test project and key

  1. Create a project for this exercise. Open Settings › Project › Projects and choose New Project. Give it a name and a slug, which is a short name used in web addresses. You need to be a member of the project with the QA engineer role or higher. A key acts with its owner’s role, so a key that belongs to a viewer can’t send results.

  2. Create a key for that project. Only an administrator can create a key that works for just one project. Open Settings › Project › Streaming API keys for the project, or use the keys API. TestLookup shows the key only once.

    If you aren’t an administrator, a personal key from Settings › Account › My API keys also works for this tutorial. But a personal key can reach every project you belong to.

    Don’t reuse an older key whose list of permissions is empty. Such keys can reach more than you expect.

    Details: the API call is POST /api/v1/keys, with the project_id of your project.

  3. Set the values in your shell. Keep the key in the shell, or in a temporary file that Git ignores. Never put it in a file you commit:

    Terminal window
    export TL_URL='http://localhost:8000'
    export TL_API_KEY='paste-your-key-here'
    curl --fail-with-body "$TL_URL/api/v1/projects" -H "X-API-Key: $TL_API_KEY"

    The reply lists the projects the key can see. Copy the ID of your test project:

    Terminal window
    export TL_PROJECT='paste-the-project-id-here'

Write down the project ID, the key’s name, the start time and the TestLookup version you’re running, as a Git commit or image name. Then you can compare your results later.

Upload

Send the file with this upload command:

Terminal window
curl --fail-with-body "$TL_URL/api/v1/ingest/file" \
-H "X-API-Key: $TL_API_KEY" \
-F "project_id=$TL_PROJECT" \
-F 'build_number=website-tutorial-1' \
-F 'format=junit' \
-F 'file=@results.xml'

You should get an accepted reply (HTTP 202) like the one below. Copy both IDs.

The task ID tracks the reading of this file. The run ID is the test run where TestLookup saves the results.

{"status":"accepted","run_id":"…","task_id":"…",…}

Uploads can be up to 50 MiB, as documented. Compressed archives have extra limits of their own:

Those extra limits don’t raise the 50 MiB upload limit. See the upload guide on GitHub.

Check the task, then the run

Check the upload task first, then the run:

Terminal window
export TL_TASK_ID='paste-the-task_id-here'
export TL_RUN_ID='paste-the-run_id-here'
curl --fail-with-body "$TL_URL/api/v1/ingest/uploads/$TL_TASK_ID" \
-H "X-API-Key: $TL_API_KEY"
curl --fail-with-body "$TL_URL/api/v1/runs/$TL_RUN_ID" \
-H "X-API-Key: $TL_API_KEY"

Repeat the first command until the task’s state is succeeded or failed. On the way, the task moves through pending, parsing and ingesting.

Parsing means reading the file, and ingesting means saving the results. Once the task has succeeded, the second command returns the run.

Until the results are saved, you may not be able to read the run yet. Wait a few seconds and try again.

TestLookup keeps the task record for 24 hours. So if the task check comes back “not found” (HTTP 404), the run may still exist.

The record may have expired, or failed to save, while the run was saved. The API guide on GitHub describes every request and reply.

Read the final status

Read the run’s status from the run itself. Don’t go by the number of results you sent, or by the accepted reply. When TestLookup marks the run as finished, it sets the status like this:

For this file, expect the task to reach succeeded, with both tests accepted: 1 passed and 1 failed. Expect the run to show status FAILED, 2 tests and a 50% pass rate.

If you see something else, write down the accepted and rejected counts, the final status and any analysis steps that were skipped.

What a recorded run showed

This run was recorded on September 21, 2026, with an earlier development version of TestLookup. That version came before the QA engineer role check was added to the upload methods, so details may differ on later versions.

A two-test file like the one above was uploaded to a throwaway project under Podman, an alternative to Docker. The upload was accepted (HTTP 202), with separate run and task IDs.

The task reached succeeded, with 2 results attempted, 0 rejected, 1 passed and 1 failed, and was marked complete. The run could be read, with status FAILED, 2 tests and a 50% pass rate.

Cleanup and common errors

When you’re done:

  1. Delete the report file you created.
  2. Delete the key under Settings › Project › Streaming API keys. For a personal key, use Settings › Account › My API keys instead. You can also delete a key through the API.
  3. Delete the test project if you no longer need it.
  4. On your own machine, stop TestLookup. Run the clean-up command only if you want to delete all of its data.
Terminal window
rm results.xml
make stop # stop TestLookup and keep its data
make clean CONFIRM=yes # only if you want to delete all of its data

Details: the API call to delete a key is DELETE /api/v1/keys/{key_id}.

Common errors:

ReplyWhat it meansWhat to do
401The key is missing, wrong or expired.Check the key you set in your shell.
403 “Requires at least QA_ENGINEER role to submit test results”The key’s owner has a lower role.Use a key that belongs to a QA engineer or higher.
403 (other)The key belongs to a different project, or you aren’t a member of this one.Check the project ID and the project’s members.
400TestLookup doesn’t support the format you named, or the file is empty.Check the file and the format field.
413The file or archive is over a size limit.Use a smaller file.
422A field is invalid, for example an empty build number.Read the error detail.
429The project has sent more uploads than its rate limit allows for now.Wait for the time in the Retry-After header, then try again.
503The server is overloaded or can’t store the upload. Or uploads in that format are switched off, which applies to Cypress and Playwright.Wait for the time in Retry-After if it’s there, or ask an administrator.
Task failed after an accepted replyTestLookup accepted the file but couldn’t read or save it.Read the task’s error.
Task not found (404)The task record expired or was never saved.Check the run instead.

If a problem keeps happening, check:

Next: connect a CI job, or review an AI report for the run you just created.