Зачем нам это, почему бы сразу не написать чек лист?
В начале немного о себе, мое основное занятие - обеспечение качества на вверенных проектах, я Senior QA в комп��нии Umbrella IT. В работе часто приходится сталкиваться с необходимостью составления такой тестовой документации как тест кейсы и чек листы. Кому как больше нравится, но то, что на это занятие тратится часть времени, которое можно было бы использовать на что-то более прикладное или творческое, меня несколько печалит.
Подмечу, что для “конвертирования” творческой мысли в ветвящуюся последовательность логических действий я давно использую такой инструмент, как mind map. В контексте тестирования он позволяет не только быстро отразить иерархию логики проверок, которая прямо сейчас у меня “на уме”, но и через визуальное подкрепление наводит на новые идеи, - чем можно эти проверки расширить, что добавить и как оптимизировать. К сожалению, на сколько mind map прекрасно подходит для формирования вариаций проверок, на столько же он и неудобен для исполнения этих проверок. А каждый раз вручную копипастить деревья из mind map в чек листы используемой на проекте Test Management System (далее TMS) конечно можно, но это совершенно не творческое и жутко затратное по времени занятие. Отсюда и возникает необходимость наличия инструмента для автоматической конвертации mind map в чек листы
Инструкция к применению
Делаем mind map
В выборе инструмента формирования mind map будем отталкиваться от того, что можно получить бесплатно. Да, можно срезать углы, используя платные планы Miro + Mind map downloader или XMInd + встроенный экспорт в Excel. Но, во-первых это не так интересно, а во-вторых не факт, что вам будет удобно использовать формат экспорта, который предоставляют эти инструменты “из коробки”. Мой вы��ор пал на XMInd (бесплатный план) так как он поддерживает локальную установку на разные платформы, работу из облака + формат его сейвов подходит для конвертации open source инструментами.
Сформируем какую-нибудь простую карту исключительно для демонстрации и сохраним ее. Для дальнейших операций сразу обозначим на каком уровне иерархии карты у нас будут папки/секции/разделы, на каком кейсы/чек листы, а на каком шаги. Заранее примем тот факт, что последний уровень иерархии - это вариации/условия внутри шага тест кейса/чек листа.
Тут сразу подсвечу, что для дальнейшего успешного маппинга в иерархию TMS необходимо расставить метки иерархии уровней в именах наших топиков/узлов. На нашей карте я расставил следующие флаги:
“Level.”, “Level2.” и “Level3.” - флаги уровней иерархии подразделов
“Case.” - флаг, который показывает, где начинается выделенный тест кейс/чек лист
“Step.” - флаг, который показывает выделенный шаг тест кейса/чек листа

Переводим mind map в JSON
Как промежуточный формат для конвертации, который удобно передать в обработку чему угодно (нейросети, скрипту, API и тд), будем использовать JSON. Тут заведомо можно сказать, что сохраненный файл с расширением xmind это ZIP архив, в котором есть файл content.json, а уже это JSON представление вашей карты. Впрочем в нем достаточно много лишних для нас полей, для получения чего-то более удобного и лаконичного воспользуемся свободным конвертером xmindparser. Установить и использовать его можно различными способами, в этой статье будем пользоваться заранее установленным pip (документация по установке pip). Наши действия:
Устанавливаем xmindparser командой:
pip install xmindparser
.Переходим в директорию с установленным xmindparser. Тут можно чуть больше потрудиться и добавить путь к исполняемому файлу в список команд консоли ОС, но статья не об этом, поэтому идем простым и быстрым путем. Команда для получения информации об установленном пакете:
pip show xmindparser
Конвертируем файл xmind с заранее сохраненной mind map в формат JSON командой:
xmindparser ВАШ_ФАЙЛ.xmind -json

На этом этапе мы уже получаем удобоваримый и понятный JSON файл, отражающий иерархию наших проверок. Как раз его мы и будем использовать для конвертации в чек листы.

Делаем из JSON чек листы
Самое простое, что можно использовать это конвертировать полученный JSON через доступную вам нейросеть в чек листы формата офисных приложений (редактиру��мые документы или таблицы). Это конечно самое простое, ну а мы попробуем пойти дальше, - с помощью локальных приложений на Python конвертировать полученный JSON в что-то с возможностью импортирования в TMS. В качестве TMS выберем Testops и Testrail. Заранее подсвечу, что в этой статье мы будем создавай чек листы, не смотря на то, что в этих TMS создаются тест кейсы. Причиной тому - цель этой статьи, а именно показать быстрый и простой способ сократить затраты на работу с тестовой документацией.
Testops
К сожалению Testops не умеет импортировать тест кейсы из файлов с JSON форматом, поэтому будем конвертировать в CSV. Сам Testops довольно гибко настраивается и от проекта к проекту у него может быть довольно разнообразный набор обязательных полей с различными именами. Предположим, что в нашем примере у кейсов Testops есть обязательные поля:
“name” - имя тест кейса/чек листа
“scenario” - сам сценарий, с шагами
Внутри scenario шаги начинаются с новой строки с флагом “[step *]”, например [step 1] и [step 2]
“Section1” - верхний раздел, уровень иерархии
“Section2” - вложенный раздел, уровень иерархии

Создадим папку для этого приложения, назовем ее xmind_to_csv
. Внутри этой папки разместим файл с именем mindmap.json - это наш ранее полученный JSON. Там же создадим файл xmind_json_to_csv.py
в который сохраним “навайбкоденное” приложение-конвертер на Python.
Код приложения-конвертера из JSON в CSV для импорта чек листов в Testops
import json
import csv
import re
# Загружаем JSON
with open("mindmap.json", encoding="utf-8") as f:
data = json.load(f)
rows = []
all_section_columns = set() # для динамических Section колонок
def process_case(case, section_levels):
"""Обрабатывает Case, формирует сценарий и добавляет строку"""
name = case["title"].replace("Case. ", "")
scenario_lines = []
if "topics" in case:
for step_index, step in enumerate(case["topics"], start=1):
if step["title"].startswith("Step."):
step_name = step["title"].replace("Step. ", "")
scenario_lines.append(f"[step {step_index}] {step_name}")
if "topics" in step:
for i, t in enumerate(step["topics"], start=1):
scenario_lines.append(f"{i}. {t['title']}")
scenario_lines.append("") # пустая строка между блоками шагов
scenario = "\n".join(scenario_lines).strip()
# Формируем строку с динамическими Section колонками
row = {"name": name, "scenario": scenario}
for i, sec in enumerate(section_levels, start=1):
col_name = f"Section{i}"
row[col_name] = sec
all_section_columns.add(col_name)
rows.append(row)
def traverse(node, section_levels=None):
if section_levels is None:
section_levels = []
title = node.get("title", "")
# Если верхний уровень функционала (не Case и не Step), добавляем как Section1, если ещё пусто
if not section_levels:
section_levels = [title]
# Проверяем, является ли уровень Level, Level2, Level3 и т.д.
match = re.match(r"(Leve?l\d*\.?)\s*(.+)", title, re.IGNORECASE)
if match:
# Добавляем уровень в конец списка
section_levels = section_levels + [match.group(2).strip()]
# Если Case, обрабатываем
if title.startswith("Case."):
process_case(node, section_levels)
# Рекурсивный обход детей
for child in node.get("topics", []):
traverse(child, section_levels)
# Запускаем обход
for item in data:
traverse(item["topic"])
# Определяем все колонки
section_columns_sorted = sorted(all_section_columns, key=lambda x: int(x.replace("Section", "")))
fieldnames = ["name", "scenario"] + section_columns_sorted
# Записываем CSV
with open("test_cases.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(rows)
print("CSV успешно создано!")
Запускаем приложение командой python xmind_json_to_csv.py
и получаем в нашей папке файл test_cases.csv
, который можем успешно импортировать в Testops
Подсвечу, что процесс импорта может сильно отличаться от проекта к проекту. Тут я покажу пример, как это может быть:
Внутри вашей группы или подпроекта жмем Import
скриншот
На открывшейся странице кликаем по кнопке Submit и вторым действием выбираем CSV файл который мы ранее создали, после чего опять жмем кнопку Submit
скриншот
На следующей странице мы можем настроить вручную особенности маппинга нашей таблицы (наличие заголовков, разделители и тд), но в этой статье ограничимся настройками по умолчанию. Жмем на кнопку Parse file
скриншот
Далее мы определяем какая колонка CSV файла будет мапиться в какое поле наших чек листов. После распределения всех колонок по нужным нам полям можно прожать кнопку Show preview и в левой панели посмотреть как будут выглядеть будущие чек листы. Если результат, увиденный в preview вас устраивает, - нажимайте на кнопку Submit import.
скриншот
В зависимости от настроек проекта вас могут попросить выбрать статусы создаваемых кейсов. Выбираем и жмем Submit.
Дожидаемся завершения обработки.
скриншот
Проверяем импортированные кейсы. В зависимости от ваших потребностей вы можете расширять список полей, которые получаете из mind map. Эта статья призвана показать сам механизм/возможность подобной автоматизации. Остальное может быть настроено под ваши локальные потребности.
Успешный результат импорта чек листов в Testops
Testrail
И опять таки к еще одному сожалению Testrail тоже не умеет импортировать тест кейсы из файлов с JSON форматом. Попробуем расширить возможности нашего конвертера. Так как выше предложенная библиотека xmindparser может быть импортирована сразу в наше приложение, я решил сразу привести наше локальное приложение к виду полноценного CLI приложения, в котором на вход мы сможем подавать как файлы JSON, ранее полученные с помощью xmindparser, так и сразу файлы с расширением “.xmind” с возможностью указания в какой формат CSV мы хотим его сконвертировать, для Testops или Testrail.
Для Testrail маппинг будет несколько проще, так как “из коробки” экспорт и импорт тест кейсов не поддерживает иерархию Секций/Подсекций. Необходимая иерархия будет добавлена в имя самого чек листа. Если у вас есть желание и возможность внедрить поддержку Секций/Подсекций Testrail в импорт - вы можете на все усмотрение, изменив код приложения, под нужды своего проекта. Код приложения будет выглядеть следующим образом:
Код CLI приложения для конвертации xmind и JSON в CSV для импорта чек листов в Testops и Testrail
import json
import csv
import re
import sys
import os
import logging
# Импортируем парсер XMind
try:
from xmindparser import xmind_to_dict, config as xmind_config
except ImportError:
print("❗ Модуль 'xmindparser' не установлен. Установите его командой:")
print(" pip install xmindparser")
sys.exit(1)
# ---------- Настройки парсера XMind ----------
xmind_config.update({
'logName': 'xmind_parser',
'logLevel': logging.ERROR,
'logFormat': '%(asctime)s %(levelname)-8s: %(message)s',
'showTopicId': False,
'hideEmptyValue': True
})
# ---------- Вспомогательная функция ----------
def load_input_file(input_path):
"""Загружает JSON или XMind, возвращает структуру данных (dict/list)."""
if not os.path.isfile(input_path):
print(f"❗ Ошибка: файл '{input_path}' не найден.")
sys.exit(1)
ext = os.path.splitext(input_path)[1].lower()
if ext == ".json":
with open(input_path, encoding="utf-8") as f:
data = json.load(f)
return data
elif ext == ".xmind":
print(f"📘 Обнаружен XMind-файл, выполняется парсинг: {input_path}")
data = xmind_to_dict(input_path)
return data
else:
print("❗ Поддерживаются только файлы .json или .xmind")
sys.exit(1)
# ---------- TESTOPS ----------
def convert_json_to_csv_testops(data, output_path):
"""Преобразует структуру JSON MindMap в CSV по логике TestOps"""
rows = []
all_section_columns = set()
def process_case(case, section_levels):
name = case["title"].replace("Case. ", "")
scenario_lines = []
if "topics" in case:
for step_index, step in enumerate(case["topics"], start=1):
if step["title"].startswith("Step."):
step_name = step["title"].replace("Step. ", "")
scenario_lines.append(f"[step {step_index}] {step_name}")
if "topics" in step:
for i, t in enumerate(step["topics"], start=1):
scenario_lines.append(f"{i}. {t['title']}")
scenario_lines.append("")
scenario = "\n".join(scenario_lines).strip()
row = {"name": name, "scenario": scenario}
for i, sec in enumerate(section_levels, start=1):
col_name = f"Section{i}"
row[col_name] = sec
all_section_columns.add(col_name)
rows.append(row)
def traverse(node, section_levels=None):
if section_levels is None:
section_levels = []
title = node.get("title", "")
if not section_levels:
section_levels = [title]
match = re.match(r"(Leve?l\d*\.?)\s*(.+)", title, re.IGNORECASE)
if match:
section_levels = section_levels + [match.group(2).strip()]
if title.startswith("Case."):
process_case(node, section_levels)
for child in node.get("topics", []):
traverse(child, section_levels)
for item in data:
traverse(item["topic"])
section_columns_sorted = sorted(all_section_columns, key=lambda x: int(x.replace("Section", "")))
fieldnames = ["name", "scenario"] + section_columns_sorted
with open(output_path, "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(rows)
print(f"✅ CSV (TestOps) успешно создано: {output_path}")
# ---------- TESTRAIL ----------
def convert_json_to_csv_testrail(data, output_path):
"""Преобразует структуру JSON MindMap в CSV по логике TestRail"""
rows = []
def process_case(case, level_titles):
case_name = case["title"].replace("Case. ", "").strip()
full_title_parts = [case_name] + [lvl for lvl in level_titles if lvl]
full_title = ". ".join(full_title_parts)
steps = []
if "topics" in case:
for step in case["topics"]:
if step["title"].startswith("Step."):
step_name = step["title"].replace("Step. ", "").strip()
substeps = []
if "topics" in step:
for i, t in enumerate(step["topics"], start=1):
substeps.append(f"{i}. {t['title']}")
step_text = step_name
if substeps:
step_text += "\n" + "\n".join(substeps)
steps.append(step_text)
for idx, step_text in enumerate(steps):
rows.append({
"Title": full_title if idx == 0 else "",
"Steps (Step)": step_text
})
def traverse(node, current_levels=None):
if current_levels is None:
current_levels = []
title = node.get("title", "")
match = re.match(r"(Leve?l\d*\.?)\s*(.+)", title, re.IGNORECASE)
local_levels = current_levels.copy()
if match:
local_levels.append(match.group(2).strip())
if title.startswith("Case."):
process_case(node, local_levels)
for child in node.get("topics", []):
traverse(child, local_levels)
for item in data:
traverse(item["topic"])
with open(output_path, "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=["Title", "Steps (Step)"])
writer.writeheader()
writer.writerows(rows)
print(f"✅ CSV (TestRail) успешно создано: {output_path}")
# ---------- MAIN ----------
def main():
if len(sys.argv) < 3:
print("❗ Использование:")
print(" python json_to_csv.py путь_к_json_или_xmind_файлу -testops")
print(" python json_to_csv.py путь_к_json_или_xmind_файлу -testrail")
sys.exit(1)
input_path = sys.argv[1]
mode = sys.argv[2].lower()
data = load_input_file(input_path)
base_name = os.path.splitext(os.path.basename(input_path))[0]
dir_name = os.path.dirname(input_path)
if mode == "-testops":
output_path = os.path.join(dir_name, f"{base_name}_testops.csv")
convert_json_to_csv_testops(data, output_path)
elif mode == "-testrail":
output_path = os.path.join(dir_name, f"{base_name}_testrail.csv")
convert_json_to_csv_testrail(data, output_path)
else:
print("⚙️ Неизвестный режим. Доступные ключи:")
print(" -testops — экспорт для TestOps")
print(" -testrail — экспорт для TestRail")
if __name__ == "__main__":
main()
Так как наше приложение прибавило в функционале, немного переименуем исполняемый файл, теперь это xmind_to_csv.py
.
Запускаем скрипт командой python xmind_json_to_csv.py file_name -key
, например python xmind_json_to_csv.py auth_regtest.xmind -testrail
или python xmind_json_to_csv.py auth_regtest_xmind -testops
и получаем наш CSV файл (в нашем случае при конвертации файла auth_regtest.xmind
получаем auth_regtest_testrail.csv
) . Импортируем наши чек листы в Testrail:
Переходим в ��ужную Секцию нашего проекта кликаем по кнопке Импорта сущностей и выбираем импорт из CSV
скриншот
Далее в открывшемся окне:
выбираем импортируемый файл
настраиваем опции чтения CSV (как правило менять опции не нужно, чтение успешно с настройками по умолчанию)
жмем по кнопке Next
скриншот
На следующем шаге:
выбираем опцию использования нескольких строк для одного чек листа
выбираем разделителем для чек листов колонку Tille
настраиваем маппинг колонок таблицы на поля тест кейса/чек листа
жмем по кнопке перехода на следующий шаг
скриншот
Следующий шаг пропускаем, этот маппинг выходит за скоуп этой статьи, жмем Next
На последнем шаге нас встречает превью наших будущих чек листов, если все устраивает - жмем Import
скриншот
Если импорт был успешен, нам предложат скачать файл конфигурации маппинга (пригодится, в последующих импортах, чтобы не настраивать маппинг каждый раз заново), жмем на кнопку Close. Наши чек листы успешно импортированы, можно пользоваться.

Заключение
В заключении еще раз подсвечу, предложенное решение не позволяет наполнить создаваемые сущности полями с предусловиями и ожидаемыми результатами для генерируемых шагов, что является необходимым для создания полноценных тест кейсов. Данная статья призвана показать краткий и простой путь для конвертирования mind map в чек листы вышеуказанных TMS, что позволяет удобно создавать вариации различных условий и состояний для планируемого процесса тестирования и быстро конвертировать эти вариации в удобный чек лист для непосредственного исполнения самих проверок.
Версии использованных приложений и библиотек на момент написания статьи:
Xmind версия 25.07
Xmindparser версия 1.0.9
Python версия 3.14