Migrating from TestRail, Zephyr, Xray, qase, or a spreadsheet

Migrating from TestRail, Zephyr, Xray, qase, or a spreadsheet

Bring your existing test cases into Lathe Studio with the import wizard: upload the export from your current tool, check the dry-run report, and import. This page covers what comes across, what does not, and the exact recipe for the one thing no export file contains — your attachments.


What comes across — and what does not#

Comes across: test cases, section structure, steps (actions and expected results), priorities, statuses, tags, and external IDs. Cases keep the ID from your old tool, so a second import can update the same cases instead of duplicating them.

Does not come across: test runs, run results, and attachments.

  • Runs and results are not part of the import — they live in the execution history of your old tool and are out of scope for a case migration.
  • Attachments are a different story, and it surprises people: no vendor's export file contains the image bytes. TestRail's XML export, for example, lists each attachment by name and URL — a pointer back to your own TestRail instance, not the file itself. The same is true of CSV and Excel exports from every tool: the export describes the attachment; the file stays in your system. The recipe for fetching them yourself is below.

Plan your migration as: import the cases (this page), then fetch the attachments with the manifest recipe, then start fresh runs in Lathe.

Migrating your attachments#

When you import a TestRail XML export, Lathe reads the attachment references and hands you a Download attachment manifest button (on the preview step). The manifest is a CSV with one row per file: the name to save it as, its TestRail attachment ID, its case, and its source URL.

Every file is named after its test case with a two-digit sequence — Login screen_01.png, Login screen_02.png — numbered in the order the case shows them (preconditions, then steps, then files attached to the case). Sorted by name, a case's images sit together in order. Two cases with the same title share one sequence, so no file overwrites another.

Lathe's step text does not store images, so where a step had an inline image, the imported step carries a marker with that same name — [Image: Login screen_01] — at the spot the image was. The marker tells you exactly which downloaded file belongs there.

Because the export file never contained the files, fetching them is a job for your own machine, against your own TestRail instance, with your own TestRail API key:

  1. In TestRail, create an API key (My Settings → API Keys).
  2. Save the manifest as testrail-attachment-manifest.csv next to this script and run it. Every file lands in one folder, testrail-attachments.
#!/usr/bin/env bash
# Runs on YOUR machine against YOUR TestRail instance.
# Fill these in:
TESTRAIL_URL="https://your-instance.testrail.com"
API_USER="you@example.com"
API_KEY="your-testrail-api-key"

MANIFEST="testrail-attachment-manifest.csv"
OUT_DIR="testrail-attachments"
mkdir -p "$OUT_DIR"

# One download per manifest row, saved under the manifest's "Save As" name.
# The first three columns (Save As, Attachment ID, Extension) never contain
# commas, so a plain split reads them.
grep -vE '^(Save As|Note),' "$MANIFEST" | while IFS=, read -r save_as att_id ext _rest; do
  if [ -z "$att_id" ]; then
    echo "skipped, no attachment ID: $save_as" >&2
    continue
  fi
  tmp="$OUT_DIR/.downloading"
  result=$(curl -s -u "$API_USER:$API_KEY" -o "$tmp" -w '%{http_code} %{content_type}' \
    "$TESTRAIL_URL/index.php?/api/v2/get_attachment/$att_id")
  code="${result%% *}"
  ctype="${result#* }"
  if [ "$code" != "200" ]; then
    echo "failed ($code): $save_as" >&2
    rm -f "$tmp"
    continue
  fi
  # Inline images have no file name, so their extension comes from the download.
  if [ -z "$ext" ]; then
    case "$ctype" in
      image/png*) ext=png ;;
      image/jpeg*) ext=jpg ;;
      image/gif*) ext=gif ;;
      image/webp*) ext=webp ;;
      application/pdf*) ext=pdf ;;
      text/plain*) ext=txt ;;
      *) ext=bin ;;
    esac
  fi
  mv "$tmp" "$OUT_DIR/$save_as.$ext"
done

Anything the script could not fetch is listed as failed or skipped, with its name, so you can retry just those.

Lathe never sees your TestRail credentials or the downloaded files — the script runs entirely between your machine and your instance, and we cannot fetch them for you (that would mean holding credentials to a customer system, which we do not do).

How Lathe's model differs from TestRail's#

TestRail organizes work as project → suite → section → test case, and uses multiple suites to keep parallel bodies of work apart. Lathe organizes the same work along a different axis: project → section → test case, with multiple releases (plus builds and Regression Sets) inside one project. The ability to run several workstreams at once is there — it lives in releases rather than in suites.

A multi-suite TestRail project therefore maps naturally to Lathe: export one file per suite, import each into the same Lathe project, and you get parallel section trees — with the concurrency managed by releases:

TestRailLathe
ProjectProject
Suite(top-level sections — export one file per suite)
Section / sub-sectionSection / sub-section
Test caseTest case
Milestones & runs/plansReleases & builds
Baselines (parallel variants)Releases + Regression Sets

Neither model is missing what the other has — they just place the parallel-work axis in different spots.

Import limits#

In the numbers the product enforces:

  • 10,000 rows per file. Above that, split the export by section and import each part separately.
  • 20 MB per file. Split a larger export by section.
  • .csv and .xlsx are accepted. Legacy .xls is not — open it in Excel and choose File → Save As → Excel Workbook (.xlsx).
  • If accented characters look wrong in the preview, re-export the file as UTF-8. Lathe decodes deliberately and tells you on the mapping step when it had to guess an encoding (for example, an Excel-for-Windows file saved as Windows-1252), and shows which separator it detected so you can override it.

The dry-run report — read it before you import#

Before anything is written, the wizard shows a dry-run report: how many rows will import, how many will be refused, and every value that did not match (an unrecognized priority, a section path with an empty segment, a step missing its action text). Fix the file, re-import, repeat — the report is the contract for what the import will do.

For a second pass, turn on Update existing test cases that match an external ID: rows whose external ID already exists in Lathe update those cases instead of being skipped. With the default off, matching rows are skipped and existing cases are left untouched.

One guide per tool#

In every case: open a project, go to the test case list, choose Import Test Cases, and pick the matching source platform. The wizard auto-maps the columns; check the mapping step, read the dry-run report, import.

TestRail

  1. Recommended — XML: in TestRail's Test Cases view, use Export → XML and export each suite separately. XML keeps the nested section structure and is read natively — columns, steps, and preconditions arrive mapped. Custom fields in the file's <custom> block are captured and offered as new custom fields on the preview step (uncheck any you want to drop).
  2. CSV/Excel: Export → CSV (or Excel) works too. Export all columns, one row per case — leave "separated steps on separate rows" off. Choose the TestRail preset: steps and expected results come from Steps (Step) and Steps (Expected Result), the full section path from Section Hierarchy, and priority values (Critical/High/Medium/Low) are recognized automatically. TestRail's Type column (Acceptance, Regression, Smoke & Sanity…) imports onto Lathe's Type; values the list doesn't recognize land as Other with a per-row warning on the dry-run report. Columns named custom_* — TestRail's own custom fields — are offered as new custom fields automatically (the mapping step shows the suggested label and type; switch any of them to Ignore if you do not want them). Images embedded in step text arrive as links to your TestRail instance, not as files.
  3. BDD cases: TestRail's Behaviour Driven Development template cases import as Gherkin cases, three ways. XML (recommended — the BDD scenario blocks are read natively), a .feature export (one file per case; TestRail's multi-Feature: layout is handled), or CSV/Excel with the BDD column (Test Script (BDD) / gherkin definition) mapped to Gherkin Content. One TestRail BDD case becomes one Lathe Gherkin case holding all its scenarios. @tags the project doesn't have yet are listed in the dry-run report with Create these N tags ticked by default — untick to import without them. The details of writing and running Gherkin cases live in the Gherkin guide; a file with no scenarios is refused with the reason, never imported as an empty case.

Zephyr Scale

  1. In the Test Cases search screen, select the cases you want, click More → Export to Excel.
  2. Save the workbook as CSV (or upload the .xlsx directly).
  3. Choose the Zephyr Scale preset. Name, folder, priority, status, labels, preconditions, and step-by-step steps map automatically. Known gap: the case-key column may not auto-map — if you want external IDs preserved, map it to External ID on the mapping step.

Xray

  1. In Jira's issue navigator, filter to issuetype = Test for your project.
  2. Use Export → CSV (you can add or remove columns before exporting).
  3. Choose the Xray preset. Issue key, summary, test type, and steps map automatically. Known gap: priority names come from your Jira configuration and vary between instances — check the Map Values step and set the mapping that matches your setup.

qase

  1. In your repository, open the … menu and choose Export, then CSV.
  2. Choose the qase preset. Case ID (both the current v2.id and legacy id forms), title, description, preconditions, priority, suite, steps, and the type column (TestRail-style values) map automatically; suite rows in the file are turned into sections. The severity, layer, and behavior columns are offered as new custom fields automatically (the options come from your file's own values). Known gap: tags may not auto-map if your export labels that column differently — map it manually if it matters to you.

A spreadsheet (or anything else)

Choose Generic CSV. The wizard auto-detects common column names (title, name, priority, status, tags, steps, …). Anything it does not recognize is left unmapped for you to assign, and the CSV template linked in the upload step shows the layout it likes best.

Custom fields during import

Any unmapped column can be pointed at an existing custom field of the project (every active field appears in the column's "Map to" list) or at New custom field… — the wizard asks for a label and a type, and select fields pick up their options from your file's own values. (Creating new fields this way needs the same permission as project settings; if you only have case-edit permission, map onto existing fields.)

The preview step's dry-run report lists the fields that will be created — nothing is created until you press Import — and flags any cell whose value does not fit its field (for example a select value that is not one of the options). A flagged cell is left empty for that row with a warning; the row still imports. Exporting the Test Library from a single project adds one column per custom field, so an export re-imports with its values restored.


Importing is free and unlimited on every plan — one plan, every feature, no per-import limits. If a file refuses to import, the error says exactly which limit it hit and what to do about it.