Пользователь пишет, что страница ломается. Вы в точности повторяете его шаги. Всё работает. Вы закрываете тикет как не воспроизводится, а через три недели о том же пишут ещё четыре человека.
Фразу «у меня всё работает» принято считать шуткой, но это точное и полезное утверждение: баг зависит от какой-то разницы между вашим окружением и окружением пользователя. Так вопрос без ответа превращается в решаемый. Не «почему этот баг такой странный», а «чем отличаются эти две машины?»
Возможных отличий не так много. В браузерных приложениях почти все они укладываются в восемь категорий, и у каждой есть узнаваемый признак.
1. Состояние
Самая частая причина. Вы воспроизводите баг на только что загруженной странице. А пользователь сидит в этой вкладке уже два часа.
Одностраничное приложение, открытое долго, накапливает историю: устаревшие записи в клиентском кеше, вебсокет, который переподключился с другой сессией, стор, который изменил код со страницы, где пользователь был час назад, токен доступа, обновившийся посреди работы. У вашей свежезагруженной страницы этой истории нет.
Признак: после обновления страницы всё работает. Если автор отчёта пишет «помогает обновить страницу», дело почти наверняка в этом, и воспроизводить нужно не его шаги, а его шаги после двадцати минут работы с чем-то другим.
Как поймать: по списку того, что пользователь действительно делал за сессию, по порядку, а не того, что он счёл важным. Такой список даёт журнал действий, а поле «шаги воспроизведения» не даст никогда: нужный шаг — как раз тот, о котором пользователь не подумал упомянуть.
2. Тайминги
На вашей машине всё быстрое. Локальный API, прогретый кеш, быстрый диск. На телефоне в поезде запрос, который у вас занимает 40 мс, идёт 4 секунды — и состояние гонки, которое у вас не проявляется никогда, у пользователя срабатывает стабильно.
Признак: баг плавающий. «Бывает, наверное, один раз из пяти». Плавающий баг почти всегда означает тайминги: две операции, которые соревнуются, настолько близки по длительности, что обычный разброс меняет победителя.
Как поймать: по таймингам сетевых запросов. Посмотрите, какие запросы шли одновременно и сколько длился каждый. Классическая картина: два запроса стартовали вместе, а код рассчитывал, что первый и закончится первым.
Как воспроизвести: замедлить. Троттлинг сети и процессора в DevTools превращает очень многие баги из «не воспроизводится» в «воспроизводится каждый раз». Это самые ценные пять минут во всей статье.
3. Окружение
Браузер, версия, ОС и — то, что недооценивают сильнее всего, — размер вьюпорта.
Вы тестируете на 1440px в десктопном Chrome. Пользователь — на 390px в Safari на iOS или на 1280px с масштабом браузера 125%, а это не то же самое, что 1024px, и разница ломает вёрстку. Safari до сих пор заметно расходится с остальными в разборе дат, backdrop-фильтрах, поведении прокрутки и в том, как обращается с хранилищем в приватном режиме.
Признак: у автора отчёта баг стабилен, у вас его нет никогда. На его машине воспроизводится в 100% случаев.
Как поймать: браузер, ОС и вьюпорт записываются в отчёт автоматически. В этой категории самый большой разрыв между «исправить проще простого, когда знаешь причину» и «угадать невозможно». Поэтому тратить переписку с поддержкой на вопрос о версии браузера особенно жалко.
4. Пользователь и права доступа
Вы вошли как админ, под своим аккаунтом, в своей организации. Пользователь — участник с правами только на чтение в команде из сорока человек, на тарифе, где выключен фича-флаг.
Баги, которые зависят от роли, систематически пропускают: разработчики почти никогда не ходят по своему продукту под пользователем с минимальными правами.
Признак: воспроизводится у одной группы пользователей и не воспроизводится у другой. Часто о нём сообщают сразу несколько человек с одной ролью, и это похоже на внезапную регрессию, хотя сломано уже несколько месяцев.
Как поймать: прикладывайте к отчёту роль пользователя, тариф и состояние флагов. В любом приличном SDK можно добавить свой контекст — в BugDump это identify() для того, кто пользователь, и setContext() для любого состояния, — и роль с тарифом стоит прикладывать в первую очередь.
5. Данные
В аккаунте пользователя есть запись с эмодзи в названии, или null там, где вы ждали строку, или 4000 строк там, где в ваших фикстурах 12, или валюта без дробной части, или дата из 1970 года, потому что импорт пошёл не так.
Признак: воспроизводится на одной конкретной записи и больше нигде. Автор отчёта часто так и пишет: «так только в этом проекте», — и этим отдаёт вам готовый диагноз.
Как поймать: по ID записи и ответу API, на котором всё упало. Тело ответа с проблемным значением сразу закрывает вопрос. Во многом поэтому тела ответов так полезны — и поэтому же с ними нужно обращаться осторожно: в том же ответе, где лежит битая дата, лежит и всё остальное о пользователе.
6. Расширения и внедрённые скрипты
Блокировщики рекламы режут ваш эндпоинт аналитики, а код не готов к тому, что этот запрос упадёт. Менеджеры паролей встраивают свои элементы в DOM вашей формы и ломают обработку событий. Расширения-переводчики переписывают текстовые узлы и рушат согласование дерева в React. Корпоративные прокси переписывают ответы.
Признак: никто не может воспроизвести баг, включая других пользователей, а в консоли — запрос, упавший без статуса, или ошибка DOM, указывающая на элемент, который вы никогда не рендерили.
Как поймать: выдают упавшие запросы без ответа. А ещё воспроизведение сессии, в котором виден DOM, которого ваше приложение не создаёт.
7. Сама сеть
Страницы входа в публичный Wi-Fi, корпоративный перехват TLS, сбои DNS, узел CDN, который отдаёт одному региону устаревший бандл, деплой в процессе, когда HTML уже новый, а чанка, на который он ссылается, уже нет.
Признак: отчёты собираются в кучку по месту или по времени. Несколько отчётов за короткий промежуток или все из одной страны.
Как поймать: по времени отчётов и URL ресурса, который не загрузился. ChunkLoadError в консоли почти по определению указывает на эту категорию и значит, что ваш деплой удаляет старые чанки раньше, чем с ними закончили работать старые вкладки.
8. Время
Часовые пояса, переход на летнее время, границы месяцев, форматы дат в разных локалях и компьютер, у которого часы сбиты на одиннадцать минут, — этого хватает, чтобы токен казался просроченным сразу по прибытии.
Признак: воспроизводится только в определённые часы, или только у пользователей из определённых регионов, или только в конце месяца.
Как поймать: по часовому поясу и местному времени автора отчёта рядом с серверным временем. Записать это ничего не стоит, а категорию оно определяет с первого взгляда.
Метод
Когда отчёт не воспроизводится, стараться усерднее бесполезно. Полезно отнести его к одной из восьми категорий выше, а у каждой есть вопрос, который быстро её выявляет:
Помогает обновить страницу? → состояние
Баг плавающий? → тайминги
У пользователя воспроизводится в 100% случаев? → окружение
У него та же роль или тариф, что у других авторов отчётов? → пользователь и права
Дело в одной конкретной записи? → данные
Запрос упал без статуса? → расширения или сеть
Отчёты собраны в кучку по времени или по месту? → сеть
Только в определённые часы? → время
На каждый из этих вопросов отвечают данные, а не разговор. В этом и настоящая причина, почему «не воспроизводится» встречается так часто: улики, по которым баг можно было бы отнести к его категории, были в браузере пользователя в тот момент, когда всё сломалось, но их никто не сохранил.
Записывайте разницу, а не описание
Приводить это в систему стоит потому, что невоспроизводимые баги — не редкие пограничные случаи. Именно они остаются открытыми. Воспроизводимые исправляют на той же неделе. Бэклог состоит из остальных, а остальные — почти целиком проблема данных.
Если отчёт приходит с браузером, ОС, вьюпортом, часовым поясом, ролью пользователя, историей сессии, консолью и сетевым логом, категорию угадывать не нужно: разница между машиной пользователя и вашей записана в отчёте. Именно это BugDump собирает автоматически, с одного тега script, поэтому на восемь вопросов выше есть ответ раньше, чем кто-то задаст хоть один.
Самое редкое и ценное из всего этого — второй автор отчёта. Один невоспроизводимый отчёт — загадка. Три таких отчёта с тремя разными окружениями — уже контролируемый эксперимент: общие переменные и есть причина, а различающиеся — шум. Собирать дубликаты отчётов в одну задачу — не наведение порядка. Как раз для багов, которые вы не можете воспроизвести, это и есть диагноз.
Читайте также
Что делать с баг-репортом, который не воспроизводится — частые причины таких багов, контекст, который их раскрывает, и как не дать им вернуться.
От 40 отчётов к 6 багам: как должен работать триаж — почему отчёты и задачи стоит разделять и как дубликаты становятся лучшим материалом для отладки.
Анатомия баг-репорта: что собирает BugDump и зачем — каждый сигнал, который собирает виджет BugDump, и какие из них на деле помогают найти баг.