惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Cisco Talos Blog
Cisco Talos Blog
V
V2EX
C
Check Point Blog
GbyAI
GbyAI
D
Docker
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
B
Blog RSS Feed
H
Hackread – Cybersecurity News, Data Breaches, AI and More
N
Netflix TechBlog - Medium
T
Troy Hunt's Blog
博客园 - Franky
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Microsoft Security Blog
Microsoft Security Blog
P
Privacy & Cybersecurity Law Blog
WordPress大学
WordPress大学
The Cloudflare Blog
S
SegmentFault 最新的问题
Latest news
Latest news
Microsoft Azure Blog
Microsoft Azure Blog
P
Proofpoint News Feed
I
InfoQ
博客园 - 【当耐特】
NISL@THU
NISL@THU
A
About on SuperTechFans
T
Tailwind CSS Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
The Hacker News
The Hacker News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Scott Helme
Scott Helme
雷峰网
雷峰网
C
CXSECURITY Database RSS Feed - CXSecurity.com
Security Latest
Security Latest
V
Vulnerabilities – Threatpost
Security Archives - TechRepublic
Security Archives - TechRepublic
A
Arctic Wolf
Hacker News: Ask HN
Hacker News: Ask HN
N
News and Events Feed by Topic
IT之家
IT之家
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
aimingoo的专栏
aimingoo的专栏
T
Threat Research - Cisco Blogs
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
阮一峰的网络日志
阮一峰的网络日志
SecWiki News
SecWiki News
大猫的无限游戏
大猫的无限游戏
S
Security Affairs
The Register - Security
The Register - Security
www.infosecurity-magazine.com
www.infosecurity-magazine.com
L
LINUX DO - 热门话题
T
Tor Project blog

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Подводные камни распараллеливания автотестов
SkMK (Сбер) · 2026-04-23 · via Все публикации подряд на Хабре

Подводные камни распараллеливания автотестов

Уровень сложностиСредний

Время на прочтение12 мин

Охват и читатели1.3K

Мнение

С первого взгляда кажется, что для параллельного запуска автотестов достаточно просто нажать кнопку «Вкл.». Однако не всё так просто — различные параллельные действия пользователя приводят к логическим конфликтам: дублирующиеся записи в базах данных, несогласованность одновременно выполняемых операций, невозможность гарантированно воспроизвести некоторые сценарии, и многое другое. 

Меня зовут Марина Скалина, я QA‑автоматизатор в СберТехе, в команде Platform V Kintsugi — это графическая консоль для сопровождения PostgreSQL и всех СУБД, основанных на PostgreSQL (как наша же СУБД Pangolin). Поговорим о том, что стоит учесть, если вашей команде тестирования необходимо перейти на параллельный прогон.

Примечание: здесь и далее в статье я остановлюсь на конкретных примерах на Python, так как автотесты в нашей команде разрабатываются именно на нём. Но все описанные действия можно реализовать и в других стеках, например, уже встроенный в Go t.Parallel() является аналогом pytest‑xdist.

Первое, о чём стоит подумать: а в чём будет заключаться распараллеливание? В своей практике я встречала два подхода.

Масштабирование тестового окружения как единого целого

Рассмотрим этот подход на примере использования Docker‑окружения для тестирования. Для микросервисных приложений удобно использовать docker‑compose: он запускает контейнер для каждого из сервисов, учитывая необходимый порядок их поднятия, порты для открытия и объём для хранения данных. По умолчанию Compose разворачивает приложение под единой сетью, к которой подключается каждый из контейнеров. В таком случае они доступны другим контейнерам в этой сети.

К примеру, если файл compose.yaml будет выглядеть так:

services:
  my_app_ui:
    build: .
    ports:
      - "8000:8000"
  db:
    image: postgres
    ports:
      - "5432:5432" 

...то при выполнении команды docker compose up в рамках одной сети «default» будет поднято два контейнера, которые смогут находить друг друга по именам my_app_ui и db и получать IP‑адреса. Так, код приложения сможет подключиться к базе данных по URL‑адресу postgres://db:5432 и использовать её.

Как должна поменяться команда запуска, чтобы на одном хосте работало несколько копий приложения независимо друг от друга? Для этого нужно всего лишь добавить следующую опцию: --network  — она задаст имя сети, под которой будут находиться контейнеры приложения:

docker compose --network=replica_1 up

...здесь приложение будет запущено под сетью «replica_1».

Если же после этого также выполнить команду:

docker compose --network=replica_2 up

...то мы получим второй экземпляр приложения, который будет работать под собственной сетью. Его контейнеры не смогут увидеть что‑либо, расположенное вне сети replica_2. 

Для параллельного запуска останется предварительно обработать список тестов и разделить их примерно поровну по количеству поднятых копий приложения, а затем запустить индивидуальный набор тестов для каждого из окружений.

Подход позволяет гибко управлять необходимым количеством реплик приложения и в несколько раз сокращает длительность прогона автотестов. Реплики независимы друг от друга, и запускаемые тесты тоже никак не влияют друг на друга.

Однако у подхода есть и недостатки. Главный — это то, что каждая новая реплика приложения занимает ресурсы. В какой‑то момент это может ограничить дальнейшее масштабирование. То же количество агентов в проекте, их оперативная память и ресурсы процессора расходуются легко, и не всегда есть возможность запросить больше. Поэтому наша команда решила выбрать другой подход к распараллеливанию.

Управление потоками при запуске автотестов в рамках единого окружения

В этом случае внедряют опцию для распараллеливания запуска автотестов. Например, при работе с тестами на Python и использованием Pytest удобно внедрить плагин pytest‑xdist, который запускает тесты, распределив их между несколькими рабочими процессами. Для этого необходимо добавить опцию ‑n или ‑numprocesses. Если в качестве значения указать auto, то pytest‑xdist распределит тесты по стольким процессам, сколько физических процессорных ядер есть у компьютера.

К примеру, если использовать команду pytest ‑n 2, то тесты будут запущены в два потока. В нашей команде какое‑то время использовали коэффициент 4, однако новые автотесты так быстро расширялись, что однажды нас перестало устраивать более чем 1,5 часа для прогона тестов интерфейса по ветке, и мы подняли коэффициент до 8 — теперь в большинстве случаев мы стали укладываться в 50 минут, что позволило нам быстрее тестировать новые pull request'ы. Замечу, что хотя максимальное значение параметра ‑n в pytest‑xdist не имеет жёсткого программного ограничения в самом плагине, на практике оно упирается в ресурсы системы: если поставить слишком большое значение, то тесты начнут выполняться медленнее или система зависнет, поэтому важно подобрать оптимальное значение.

Если тесты независимы друг от друга, то на этом можно ставить «Выполнено» в задаче по распараллеливанию. Однако в таком случае бывает сложно разграничить тесты при написании так, чтобы они не вызывали конфликтов из‑за одновременного запуска. Причины могут быть разные, рассмотрим их далее.

При одновременном запуске тесты продолжают пользоваться одной тестовой средой, а значит, одними и теми же базами. В некоторых случаях «лишние» объекты, появившиеся в таблицах баз данных от параллельно идущих тестов, не влияют на результат текущего теста. Однако, например, если вы проверяете количество записей, объектов или уникальные объекты, то это может привести к ошибкам и сбоям тестов. 

Предположим, что в нашем приложении можно задать пользователю email. Впоследствии адрес можно изменить, но добавление второго адреса для того же пользователя не предполагается. У нас есть два теста, один из которых добавляет адрес, а другой изменяет его, предварительно в настройке теста уже добавляя email конкретного пользователя в базу данных (в ней хранятся адреса всех пользователей) и удаляя после завершения теста. При одновременном запуске тестов запрос на добавление почтового адреса может на самом деле не добавить его, а изменить ранее присвоенный, так как для пользователя в базе данных запись с адресом уже имеется (если вдруг тест на изменение начнётся чуть раньше). В таком случае тест окажется некорректным, так как основная его проверка по добавлению не будет выполнена.

Одно из быстрых решений для устранения подобной проблемы — добавить фикстуру, чтобы под каждый поток создавать уникального пользователя. Это позволит разграничить объекты в базе данных по идентификатору пользователя и проверить, что email записывается действительно для нужного. Такой подход довольно простой и помогает решить немало ситуаций с падающими тестами, но не всегда этого бывает достаточно для полной изоляции тестов. 

Следует помнить о том, что хотя пользователей мы разграничили, но окружение, в котором они находятся, осталось прежним. Это означает, что объекты приложения, не привязанные к пользователю, будут видны во всех потоках. Такое положение дел может привести к конфликту, если пользователи в тестах обратятся к одному и тому же объекту, например, как‑то изменяя его параметры или проводя иные действия. 

Допустим, наши объекты представляют собой подключения к СУБД на разных хостах и портах. При их создании мы даём им названия. Также допустим, что ранее при последовательном запуске тестов мы использовали объект с названием test_object. Постоянное имя тестового объекта — вообще не самая хорошая практика в тестировании, но особо заметно это становится при распараллеливании. Если в одном из потоков идёт тест на редактирование имени данного объекта, и во время такого теста имя изменится на test_object_changed, то все параллельно идущие тесты при обращении к этому объекту по прежнему имени просто не смогут его найти. Поэтому для каждого пользователя тестовый объект должен иметь уникальное имя.

Или другая ситуация: пусть для каждого объекта мы можем включить или выключить сбор некоторых метрик. Допустим, два теста проверяют, что метрика собирается, если опция включена, и наоборот. Если тесты будут проводиться на одном и том же объекте, то успешным будет только один из них — тот, что настроит сбор последним.

Добавление объектов для каждого пользователя, очевидно, увеличивает их общее количество в приложении в конкретный момент времени. Ведь каждый из пользователей может создавать не один, а те же три, пять и более объектов. Это сказывается на том, как объекты отображаются на странице, в какой-то момент они могут перестать быть «видны» в интерфейсе. В таких случаях могут быть разные ситуации: например, список из объектов можно листать, или же объекты можно просматривать постранично (есть и другие варианты в зависимости от реализации). Внезапно мы можем получить ошибку о том, что автотест не нашёл на странице объект, хотя на самом деле этот объект существует, но чтобы его «увидеть», нужно до него долистать. 

Хороший выход в такой ситуации — использовать поиск по списку или таблице или выбирать отображение бОльшего количества объектов на странице, если такие функции есть в интерфейсе. В ином случае придётся добавлять шаги по «поиску» объекта тем же перелистыванием страниц в таблице. Но стоит помнить, что не всегда они будут нужны, потому что общее количество объектов может меняться от случая к случаю.

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

Давайте отойдём от факторов, которые зависят от перехода к нескольким тестовым пользователям и рассмотрим другую типичную ситуацию. Предположим, что для объектов у нас могут возникать какие‑то уведомления, если, скажем, они вдруг стали нарушать некоторые условия. Например, метрики наших объектов (тех же СУБД на разных хостах и портах) пересекли установленные для них пороги, и мы ожидаем увидеть в интерфейсе счётчик таких уведомлений (по скольким метрикам произошли нарушения?). И, например, описание нарушений (по каким именно метрикам произошло нарушение, в какой момент времени?). Допустим, мы получаем уведомление, если вдруг на каком‑то из хостов сильно выросло потребление памяти, или слишком упала скорость передачи данных. Для каждого их этих случаев у нас есть отдельный тест интерфейса, который проверяет счётчик нарушений и их содержимое (больше или меньше порогового значения). Представим, что если эти два теста запустятся параллельно, то у хоста счётчик нарушений будет показывать «2» вместо «1» в обоих тестах, а также мы увидим два уведомления о нарушении. 

Мы же хотим проверить эти ситуации изолированно друг от друга.

Также в UI‑тестах встречаются действия, связанные с буфером обмена. К примеру, копирование названия объекта или идентификатора пользователя. Что будет, если подобные тесты запустить параллельно? Мы получим ситуацию, когда действие в одном тесте будет перебивать действие с буфером обмена в другом тесте, и, таким образом, перекрывать сохранённую информацию. 

Чтобы не допустить этого, логично предположить, что нам было бы полезно выделить группу таких тестов по нарушениям или связанных с буфером обмена, и запускать их в рамках одного потока, последовательно. Для этого можно обратиться к документации плагина pytest‑xdist и воспользоваться оттуда таким инструментом, как маркер xdist_group. Обозначим все тесты, связанные с нарушениями, следующим образом:

@pytest.mark.xdist_group(name="violations")

Этот маркер можно проставлять как для каждого теста отдельно (например, тесты с использованием буфером обмена могут быть логически не связаны друг с другом и находиться в разных файлах), так, скажем, и для класса тестов, объединённых общей тематикой (те же тесты на нарушения порогов).

К команде запуска тестов добавим опцию ‑dist loadgroup: она укажет на то, что распределять тесты нужно с учётом маркеров xdist_group (подробнее про эту и другие опции распределения можно посмотреть тут).

При наименовании и присваивании групп стоит отталкиваться от того, какую базу или иное общее хранилище затрагивают тесты. В случае с тестами на нарушения все данные по ним находятся в одной и той же базе данных (таблице):

Тогда при первоначальном распределении тестов все тесты, отмеченные такой маркировкой, будут рассматриваться как единое целое и гарантированно попадут в один поток, как мы того и хотели. 

Но у xdist есть несколько ограничений, о которых стоит упомянуть.

  1. Стоит быть внимательными при работе с параметризованными тестами — в них при использовании xdist нельзя указывать неупорядоченные переменные вроде set: @pytest.mark.parametrize('param', {'(', '[', '..', ':', ';', '<'}) — при попытке распределения возникнет ошибка «Different tests were collected between gw0 and gw1. The difference is:». Вместо сетов можно использовать списки.

  2. Аналогичная ошибка возникнет, если в качестве параметра передать случайное значение: @pytest.mark.parametrize('param', [random.randint(1,9), random.random()]).

  3. Если в проекте используется библиотека pytest‑order для выстраивания тестов в определённом удобном или необходимом порядке, то при внедрении xdist её использование, скорее всего, придётся пересмотреть. Причина в том, что при использовании pytest‑xdist планирование тестов происходит в неупорядоченном виде, и порядок, заданный с помощью pytest‑order, обычно не сохраняется. Исключение: если при запуске используется параметр ‑dist=loadfile (подробнее — здесь), но для удобного разбиения тестов, напомню, используется ‑dist loadgroup и маркеры xdist_group.

Такое разбиение на группы ещё больше поможет сократить падающие и флакующие тесты.

Однако всё ещё есть риск поймать проблему из‑за распараллеливания. Каким же образом? Пользователи разные, по группам распределили — что ещё может пойти не так? 

Бывают ситуации, когда для тех же сквозных тестов интерфейса требуется поменять стандартную конфигурацию сервиса. В целом над параметрами сервисов для автотестов следует подумать заранее, чтобы минимизировать такие тесты, где параметры всё‑таки придется менять. В идеале, было бы здорово задать параметры так, чтобы совсем избежать перезапусков. Однако в некоторых случаях бывает удобно их подкрутить для некоторых тестов. Примером тому может служить параметр, который отвечает за частоту обновления информации на каком‑нибудь дашборде или виджете. Чтобы в самом тесте не ждать, допустим, пять минут, которые могут быть значением по умолчанию, то логично было бы поменять его на значение меньше — пусть 10 секунд. Здесь бывают разные ситуации: в одном случае новые значения параметров применяются сразу, в других для применения нового значения необходим перезапуск сервиса. И вот тут кроется основная проблема: пока сервис перезапускается для теста в одном из потоков, он не работает, а значит, тесты в других потоках будут падать из‑за его недоступности. 

В такой ситуации один из выходов — вынос подобного рода тестов в отдельный запуск. Тогда мы будем параллельно запускать тесты, не влияющие на сервисы, а затем — тесты, которые так или иначе влияют на доступность сервисов, они пойдут друг за другом. Если таких тестов немного, то это не сильно «раздует» общее время прогона. Если же их количество существенно, то стоит обратить внимание на масштабирование тестовой среды. Или же применить оба подхода вместе: для подобного «отдельного» запуска выделить дополнительный агент и пускать его одновременно с запуском распараллеленных ранее тестов.

Интересных моментов, с которыми можно столкнуться при параллельном запуске тестов, достаточно много, как для интерфейса, так и для бэкенда. Они могут быть связаны и с обращением к одним и тем же базам данных, особенно если в каких‑то операциях на них ставится блокировка. И с логикой распределения тестов по потокам, будь то распараллеливание тестовой среды или просто потоков запуска тестов, ведь мы не учитываем время прохождения конкретного теста, а оно может варьироваться, к примеру, от 15 секунд до нескольких минут. Или же, для случая использования нескольких контейнеров или агентов, придётся склеивать общий отчёт по тестам из нескольких отчётов с каждого контейнера или агента.


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

Спасибо за внимание!

P. S. Вообще, исторически так повелось, что мы в Kintsugi много пишем на Хабре о тестировании. Например, наш лид написал несколько руководств про работу с Chrome DevTools. Если интересно читать руководства для QA, то приходите в наше сообщество. Там мы делимся разным полезным и публикуем вакансии.