THIS IS A REFERENCE TEMPLATE, NOT A READY TO RUN CONFIG.

BEFORE USING THIS FILE:
  1. Delete this header.
  2. Delete every // comment. The migration tool parses strict JSON and fails on comments.
  3. Delete the parameters you do not need. Almost all of them are optional and most migrations use a handful.
  4. Replace the placeholder values with your own.

Parameter descriptions: https://qameta.io/3p-migration-script/api2api/testrail

{
  // ---------------------------------------------------------------------------------------------
  // Common properties, see https://qameta.io/3p-migration-script/common/
  // ---------------------------------------------------------------------------------------------

  "type": "testrail",                    // Migration type. Always "testrail" for this config. Mandatory
  "debug": true,                         // Debug log
  "requestLog": false,                   // Http request log of the Allure TestOps client
  "requestBodyLog": false,               // Http request body log of the Allure TestOps client. Very verbose
  "oneByOne": false,                     // Migrate test cases one by one instead of in batches. Slower, but easier to read in the log
  "withMigrationTag": true,              // Keep the "migration:tms-id" tag that links a TestOps test case to its TestRail id.
                                         // DO NOT set it to false on the first migration: without the tag every rerun
                                         // creates new test cases instead of updating the existing ones

  // ---------------------------------------------------------------------------------------------
  // Target Allure TestOps instance, see https://qameta.io/3p-migration-script/testops/
  // ---------------------------------------------------------------------------------------------

  "allureConfig": {
    "endpoint": "https://YOURDOMAIN.testops.cloud/",  // Allure TestOps url. Mandatory
    "token": "TOKEN_HERE",               // Allure TestOps API token of a user with admin rights. Mandatory
    "sslTrustAll": true,                 // Skip ssl validation, for self signed certificates
    "projectId": 1,                      // Id of an existing TestOps project to migrate into. Mandatory
    "migrationTagPrefix": "testrail",    // Prefix of the migration tag. Defaults to "migration"
    "additionalTag": "migration-2026-q1", // Extra tag added to every migrated test case. Handy for finding or cleaning up one run
    "folderCfPrefix": "Section",         // Name of the custom fields built from the TestRail section tree: Section1, Section2, and so on
    "stepNamePrefix": "Step",            // Name given to a step that has no name of its own: Step 0, Step 1, and so on
    "issueTrackerId": 1,                 // Id of the issue tracker integration in TestOps, used when the "refs" field is migrated as issues
    "workflowId": -1,                    // Workflow to assign to migrated test cases. Leave -1 to use the project default
    "createOwner": true,                 // Create a read only user for an owner that does not exist in TestOps yet
    "allowedUsernameAsEmail": false,     // Set to true if usernames in your TestOps instance are email addresses
    "newCfApi": true,                    // Set to true for TestOps 4.25.1 and newer
    "retryCount": 3                      // How many times a failed TestOps request is retried
  },

  // ---------------------------------------------------------------------------------------------
  // Source TestRail instance
  // ---------------------------------------------------------------------------------------------

  "testrailConfig": {

    // --- connection -----------------------------------------------------------------------------

    "endpoint": "https://COMPANY.testrail.io/",  // TestRail url. Mandatory
    "username": "user@company.com",      // TestRail username, usually the email. Mandatory
    "password": "TESTRAIL_API_TOKEN",    // API token generated in the TestRail UI, or the account password when you use LDAP. Mandatory
    "sslTrustAll": true,                 // Skip ssl validation, for self signed certificates
    "requestLog": false,                 // Http request log of the TestRail client. Set it here, the root level flag does not cover TestRail
    "requestBodyLog": false,             // Http request body log of the TestRail client
    "connectTimeoutInMin": 1,            // Connection timeout in minutes
    "writeTimeoutInMin": 5,              // Write timeout in minutes
    "readTimeoutInMin": 10,              // Read timeout in minutes. Raise it if large attachments time out

    // --- scope ----------------------------------------------------------------------------------

    "projectId": 1,                      // TestRail project id. Mandatory
    "suiteIds": [1, 2],                  // Migrate only these suites. Remove the parameter to migrate every suite of the project
    "caseIds": [101, 102, 103],          // Migrate only these cases. Remove it to migrate everything in scope.
                                         // Keep a few ids here for a trial run before you migrate the whole project
    "withDeleted": false,                // Also migrate cases marked as is_deleted. Works only when oldBulkApi is false
    "onlyCfMigration": false,            // Update only the custom fields listed below on already migrated cases.
                                         // Scenario, description and attachments are left untouched.
                                         // Use it to fix a wrong custom field mapping without repeating the whole migration

    // --- field format and attachments -----------------------------------------------------------

    "htmlFields": false,                 // Set to true when your fields were filled with the TestRail WYSIWYG editor and hold HTML.
                                         // The description, precondition, expected result and step fields are then converted to
                                         // markdown first, images become attachments and tables are migrated as tables.
                                         // Leave it false when the fields hold plain markdown: the conversion escapes markdown syntax
    "oldAttachmentApi": true,            // Download attachments through /api/v2/get_attachment/<id> with the API token above.
                                         // This is also the setting for TestRail older than 7.1.
                                         // Set it to false only if you provide testRailSessionCookie, otherwise attachments are skipped
    "testRailSessionCookie": "tr_session=00000000-0000-0000-0000-000000000000",
                                         // Session cookie of a signed in TestRail user, taken from the Network tab of the browser
                                         // dev tools. Used only when oldAttachmentApi is false. It expires, so oldAttachmentApi
                                         // is the better choice for a migration you intend to repeat
    "oldBulkApi": false,                 // Set to true for TestRail older than 7.1, where the bulk endpoints are not paginated
    "attachmentsLinkPattern": "!\\[]\\((?<link>https://some\\.url/[a-zA-Z0-9]+/(?<name>[a-zA-Z0-9-]+)\\.jpg)\\)",
                                         // Converts a link to an externally hosted image into an html attachment.
                                         // The "link" and "name" capture groups are both required

    // --- field names ----------------------------------------------------------------------------
    // Set these only if your TestRail fields are named differently from the defaults.
    // To see the real names: GET <TR-url>/index.php?/api/v2/get_case/{case_id} with basic auth and the credentials above

    "descriptionFieldName": "custom_description",        // Default: custom_description
    "expectedFieldName": "custom_expected",              // Default: custom_expected
    "stepsSeparatedFieldName": "custom_steps_separated", // Default: custom_steps_separated

    // Which TestRail fields go into the TestOps precondition, and under which heading.
    // customFieldName is the TestRail name, displayName is the heading shown above that block.
    // Leave displayName empty to migrate the value without a heading
    "preconditionStructure": [
      {
        "customFieldName": "custom_preconds",
        "displayName": ""
      },
      {
        "customFieldName": "custom_env",
        "displayName": "Environment"
      }
    ],

    // --- scenario -------------------------------------------------------------------------------

    "usePrimarySeparatedSteps": false,   // When a case has both custom_steps and custom_steps_separated,
                                         // set this to true to build the scenario from the separated steps
    "stepsFieldAsSingleStep": false,     // Migrate the whole custom_steps value as one step instead of splitting it line by line
    "stepsAsPrecondition": false,        // Append the custom_steps value to the precondition instead of building a scenario from it
    "useParentDescription": false,       // Prepend the description of the parent sections and suites to the test case description
    "withFieldStepMapping": true,        // Set to false if the migration fails with "LinkedHashMap cannot be cast to Field$Step"
    "suitePattern": "{suite_name}",      // Value of the suite custom field. Can be extended: "{project_name} {suite_name}"

    // --- links, tags, owner ---------------------------------------------------------------------

    "refsAsLinks": false,                // Migrate the TestRail "refs" field as links on the test case.
                                         // When false, refs are migrated as issues into the tracker set by allureConfig.issueTrackerId
    "refsSeparator": ",",                // Separator used to split the "refs" field into several values
    "customTagSeparator": ",",           // Separator for a tag field holding several values, for example "tag1, tag2, tag3"
    "customFieldsAsLinks": [             // TestRail custom fields whose values are migrated as TestOps links
      "custom_link",
      "custom_docs_url"
    ],
    "createdByAsOwner": false,           // Migrate the TestRail created_by field as the TestOps member (owner).
                                         // Needs a created_by entry in customFieldsMapping, or allureConfig.createOwner set to true

    // --- custom fields --------------------------------------------------------------------------

    "requiredCfMapping": true,           // When true, a value without a mapping below is skipped.
                                         // When false, the raw TestRail id is written into the custom field as is

    // TestRail field -> TestOps custom field name.
    // Three names are special and are not created as custom fields:
    //   TESTOPS_TAG        migrates the value as a tag
    //   TESTOPS_TEST_LAYER migrates the value as the test layer
    //   TESTOPS_COMMENT    migrates the value as a comment
    "customFields": {
      "suite_id": "Suite",
      "priority_id": "Priority",
      "custom_system": "System",
      "custom_tester": "Owner",
      "custom_tag": "TESTOPS_TAG",
      "type_id": "TESTOPS_TEST_LAYER",
      "custom_comment": "TESTOPS_COMMENT"
    },

    // TestRail stores dropdown and user fields as ids. Map each id to the value you want in TestOps.
    // "name" is the TestRail field name, the keys of "values" are TestRail ids
    "customFieldsMapping": [
      {
        "name": "priority_id",
        "values": {
          "1": "Low",
          "2": "Medium",
          "3": "High",
          "4": "Critical"
        }
      },
      {
        "name": "type_id",
        "values": {
          "1": "Acceptance",
          "6": "Functional",
          "9": "Regression",
          "15": "Smoke"
        }
      },
      {
        "name": "custom_tester",
        "values": {
          "1": "tester.one",
          "2": "tester.two"
        }
      },
      {
        // TestRail user id -> TestOps username. Needed when createdByAsOwner is true
        "name": "created_by",
        "values": {
          "1": "username1",
          "2": "username2"
        }
      }
    ],

    // --- status and workflow --------------------------------------------------------------------
    // "name" is the TestRail field holding the status, "workflowId" is the TestOps workflow,
    // and the keys of "values" are TestRail status ids.
    // The first mapping that matches a case wins, so list the more specific field first
    "statusMapping": [
      {
        "name": "custom_status",
        "workflowId": 1,
        "values": {
          "1": "Draft",
          "2": "Review",
          "3": "Active"
        }
      },
      {
        "name": "custom_status_auto",
        "workflowId": 2,
        "values": {
          "1": "Start",
          "2": "Automated"
        }
      }
    ]
  }
}
