Code-first workflow

 

The code-first workflow is ideal for technical teams who manage test design directly in their codebase. It allows you to sync automated test results with TestRail without manually creating or managing test cases in the TestRail UI. 

This approach keeps your test management lightweight and integrated into your CI/CD pipeline while still giving your team visibility and reporting capabilities via TestRail.

 

TestRail - CLI Workflow Images (Final) _4 Steps.png

Why use Code-First Workflow?

  • Avoids the overhead of duplicating test cases in TestRail
  • Keeps test design and logic in your automation code
  • Automatically syncs and creates test cases based on your test execution
  • Ideal for engineering teams who "code their tests first" before managing them in a tool

 

Pros Cons
  • Automatically creates test cases in TestRail based on your automation results
  • Automatically links test results to TestRail cases using the automation_id
  • Test cases are defined and managed entirely in your codebase
  • No need to manually create or maintain test cases in TestRail
  • Ideal for technical teams who prefer writing and managing tests in code
  • Fits well into CI/CD pipelines for automated test reporting
  • Mapping can break if test names or folder structure change - leading to duplicate cases
  • If automation_id is missing or inconsistent, duplicate cases may be created
  • Difficult to retroactively map to existing TestRail cases
  • Less transparency for QA managers or non-technical users who don’t work in the codebase
  • Doesn’t reuse manual test assets like shared steps or workflows
  • Harder to audit test coverage or history from the UI
  • Not ideal for teams with both manual and automated testers

 

Code-First vs. Specification-First differences

💡 If you're working with test cases that already exist in TestRail, consider the specification-first approach instead. Read more about the specification-first automation approach.
Feature Code-First Specification-first
Case design starts in... Automation Code TestRail UI
Test Cases created in... Automatically by CLI Pre-created manually or via API
Ideal for... Technical, code-focused teams QA-led teams with planning in TestRail
Risk of duplication Higher if mappings aren't managed Lower
Mapping source automation_id (e.g., classname.name) TestRail case IDs

 

Uploading Test Results using the CLI

 

Prerequisites

Before you start uploading test results:

  1. Install TestRail CLI: Follow the TestRail CLI installation guide.
  2. Add a custom field to TestRail:
    • Go to Admin > Customizations > Add Field
    • Create a field named (System name) automation_id
    • Field type: String
    • Applies to: Test Cases
    • This field is used to map code-based tests to TestRail test cases.
💡 If you need more information please refer to the Configuring custom fields page.

 

Upload the Test Results

1. Prepare your JUnit XML Report

Here’s a sample JUnit-style test result file:

<testsuites name="test suites root">
  <testsuite failures="0" errors="0" skipped="1" tests="1" time="0.05" name="tests.LoginTests">
    <testcase classname="tests.LoginTests" name="test_case_1" time="159">
      <skipped message="Please skip">skipped by user</skipped>
    </testcase>
    <testcase classname="tests.LoginTests" name="test_case_2" time="650"/>
    <testcase classname="tests.LoginTests" name="test_case_3" time="159">
      <failure message="Fail due to...">failed due to…</failure>
    </testcase>
  </testsuite>
</testsuites>

 

2. Upload with the CLI

This command uploads the test results and creates test cases automatically if needed:

# Uploads results.xml to TestRail using code-first mapping
trcli -y \
  -h https://<INSTANCE-NAME>.testrail.io \
  --project "TRCLI Test" \
  --username <YOUR_EMAIL> \
  --password <API_KEY_OR_PASSWORD> \
  parse_junit \
  --title "Automated Tests Run" \
  -f results.xml

 

Parameters explained:

  • -y: Skips confirmation prompts, ideal for CI usage
  • --project: Your TestRail project name
  • --title: Name for the test run
  • -f: Path to your JUnit XML results file

 

How the mapping works

Each test case is mapped using the automation_id field, which is automatically generated from the test class and name in the format:

<classname>.<testname>

For example,

<testcase classname="tests.LoginTests" name="test_case_1"/>

Results in the automation_id:

tests.LoginTests.test_case_1

 

TestRail will match this against existing cases using the automation_id. If it can’t find one, it will create a new test case for you.

💡 Avoid duplication: If you already have test cases in TestRail, make sure to populate their automation_id fields before uploading results.

 

💡 Test case maintenance: Regularly audit the automation_id field to ensure mappings stay accurate.

 

⚠️ Warning: Changes in test names or structure can lead to duplicate test cases. 

Renaming a test or moving its location in code will result in a new automation_id, which may create duplicates.

 

Understanding Test Case Mapping via automation_id

One of the core features of the code-first workflow is automatic test case mapping using the automation_id field in TestRail. This allows your automation code and TestRail test cases to stay in sync—without needing to manually add TestRail case IDs to your test code.

Each time you upload results with the CLI, it tries to match test cases based on this automation_id. If it doesn’t find a match, it creates a new test case automatically.

Take the sample JUnit XML snippet below. The CLI combines the classname and name attributes to build a unique automation ID for each test:

<testcase classname="tests.LoginTests" name="test_login_with_invalid_password" time="159"/>
<testcase classname="tests.LoginTests" name="test_login_with_valid_credentials" time="221"/>

This results in the following automation_id values:

tests.LoginTests.test_login_with_invalid_password
tests.LoginTests.test_login_with_valid_credentials

When you run the CLI, it matches these strings against the automation_id field in your TestRail test cases:

$ trcli -y \
  -h https://INSTANCE-NAME.testrail.io \
  --project "Login Tests" \
  --username user@domain.com \
  --password passwordORapikey \
  parse_junit \
  --title "Login Feature Tests" \
  -f login_tests_results.xml

CLI output:

Parsing JUnit report.
Processed 2 test cases in 1 section.
Found 1 matching TestRail case.
Adding 1 new test case to the suite.
Creating test run. Run created: https://INSTANCE-NAME.testrail.io/index.php?/runs/view/456
Adding results: 2/2, Done.

 

In this example:

  • One test case was already mapped via automation_id
  • One test case didn’t exist and was created automatically
  • Both results were uploaded in a single run

 

Mapping Lifecycle Example

Let’s say you later refactor a test:

- <testcase classname="tests.LoginTests" name="test_login_with_valid_credentials"/>
+ <testcase classname="tests.AuthTests" name="test_login_successful"/>

The new automation ID becomes:

tests.AuthTests.test_login_successful

If you upload this result without updating the automation_id in TestRail, the CLI will create a new test case, not update the existing one.

 

💡 Keep classname and test name stable to avoid breaking the link
💡 Pre-fill automation_id in existing TestRail cases if you want to match them
💡 Audit your TestRail project for duplicate automation IDs if you see unexpected results
💡 Use naming conventions for easier tracking (e.g., <package>.<module>.<test_name>)

 

Mapping from Real JUnit Report

Let’s take a closer look at this real-world JUnit XML snippet and how the TestRail CLI interprets it:

<testsuites name="test suites root">
  <testsuite failures="0" errors="0" skipped="1" tests="1" time="0.05" name="tests.LoginTests">
    <testcase classname="tests.LoginTests" name="test_case_1" time="159">
      <skipped type="pytest.skip" message="Please skip">
        skipped by user
      </skipped>
    </testcase>
    <testcase classname="tests.LoginTests" name="test_case_2" time="650"/>
    <testcase classname="tests.LoginTests" name="test_case_3" time="159">
      <failure type="pytest.failure" message="Fail due to...">
        failed due to…
      </failure>
    </testcase>
  </testsuite>
</testsuites>

The CLI will generate the following automation IDs:

TestCase Line automation_id Generated
<testcase classname="tests.LoginTests" name="test_case_1"/> tests.LoginTests.test_case_1
<testcase classname="tests.LoginTests" name="test_case_2"/> tests.LoginTests.test_case_2
<testcase classname="tests.LoginTests" name="test_case_3"/> tests.LoginTests.test_case_3

You can then run this command to push results to TestRail:

trcli -y \
  -h https://INSTANCE-NAME.testrail.io \
  --project "TRCLI Test" \
  --username user@domain.com \
  --password passwordORapikey \
  parse_junit \
  --title "Automated Tests Run" \
  -f results.xml

Result Summary from CLI

Parsing JUnit report.
Processed 3 test cases in 1 sections.
Found 3 test cases not matching any TestRail case.
Adding missing sections to the suite.
Adding missing test cases to the suite.
Adding test cases: 3/3, Done.
Creating test run. Run created: https://INSTANCE-NAME.testrail.io/index.php?/runs/view/123
Adding results: 3/3, Done.
Submitted 3 test results in 8.9 secs.

 

After execution:

  • New test cases are created in your TestRail project
Code first workflow ss3.png
  • Each one will have a unique automation_id matching the pattern
  • Results, including skipped and failed states, are reflected in TestRail
Test results

 

You can now view them in Test Runs & Results or Test Cases, with traceability back to your automation suite.

 

Final Notes

  • This workflow is optimal for teams who manage their QA process through code and need lightweight TestRail integration.
  • Keep your automation_id values consistent and up-to-date for best results.
    • If you would like to upload automation results for test cases that already exist in TestRail, be sure to update the automation_id for those test cases before uploading your automation results
    • If you change the test name or location in your automation suite later, that will create a new test case in TestRail, unless you also update the automation_id field for the test case in TestRail
  • You can review uploaded runs in TestRail > Test Runs & Results > Automated Tests Run.

 

🎓 Level up your testing skills with TestRail Academy!
Explore free, self-paced courses to get the most out of TestRail.

👉 Start learning now

1679917783_TestRail_Academy_Course_Banner_Image.png

 

Where to get help

 

Was this article helpful?
11 out of 32 found this helpful