Задача
Иногда у владельцев сервисов, менеджеров проекта и SEO-специалистов возникает желание подглядеть за пользователем, как он нажимает кнопочки и обо что разбивает лоб. Случается, что подобное желание подглядывать позволяет выявить интерфейсные проблемы, что может косвенно влиять на эффективность работы сервиса, а то и прибыль.
Мне известно несколько способов решения этой проблемы:
- набрать группу подопытных для usability-тестирования;
- интегрировать на сайт сторонний сервис, записывающий действия пользователей;
- написать свое решение.
Группа тестирования
Вполне себе вариант, требующий, тем не менее, решения ряда вопросов:
- требуется тщательно отобрать состав команды тестирования, чтобы в него входило как можно больше пользователей разных возрастных групп (из состава целевой аудитории, конечно же), разных квалификаций и опыта использования. При этом размер команды тоже играет значение — большее число позволит получить больше данных для анализа, но и обойдется большими затратами;
- нужно оборудовать место для проведения исследований. Как минимум офисная площадь, вычислительная техника, средства аудио и видеозаписи;
- необходимо отвлечь от основного процесса сотрудников или привлечь дополнительных для организации процесса и анализа результатов.
Даже успешное решение вышеуказанных вопросов не гарантирует более-менее достоверного результата, а затраты на организацию процесса уже пугают. Кроме того есть риск потери граничных случаев, не таких уж и редких при использовании в бою.
Пожалуй не в этот раз.
Сторонний сервис аналитики
Большинство сервисов предлагает тепловые карты кликов и состояния прокрутки, что позволяет более-менее достоверно определить то, что пользователи увидели на сайте и на что обратили внимание.
Некоторые предлагают анализ форм, key logging и отслеживание выделения и копирования, что позволяет отследить еще и сложные схемы человеко-машинного взаимодействия.
Навскидку нашлась пара сервисов:
- WebVisor от Яндекса;
- HotJar.
За неимением экономически обоснованной цели, берем WebVisor. Сервис обещал запомнить и проиграть для нас всю пользовательскую сессию, т.е. повторить поведение пользователя на ресурсе от начала и до конца. Первичная интеграция тривиальная, достаточно просто добавить код на страницы.
Сразу стоит отметить, что заведомо деструктивные для состояния сессии запросы (т.е., все кроме GET) игнорируются.
Существует две версии. Принцип работы у них примерно одинаковый. Сначала получаем параметры аутентификации пользователя и передаем на сервер, который скачивает текущую страницу и сохраняет ее. На стороне клиента собирает действия пользователя: перемещения мышь, клики, ввод данных и прочее. Внедренный код транслирует действия на сервер с временной меткой (дельта от начала или дата/время, не выяснял).
Позже можно зайти на страницу сессий пользователя в сервисе Яндекс.Метрика и проиграть пользовательские действия. Вроде все хорошо, но есть ряд проблем.
Версия первая:
- некорректно транслирует события нажатия на сложный, видоизменяемый интерфейс. В частном случае, клик на иконку “крестик” модального окна не закрывает это модальное окно;
- любые действия, приводящие к AJAX-запросам, за исключением GET, обламываются и поведение интерфейса проигрывается некорректно, что опять не дает понимания того, что видел пользователь;
- произвольно теряет действия пользователя.
Версия вторая находится в состоянии бета-тестирования и содержит те же недостатки, плюс иногда, как порядочная бета, не работает от слова “совсем”.
До HotJar руки не дошли. Если у вас имеется опыт интеграции с SPA — поделитесь, пожалуйста, но у меня есть подозрения, что там будут те же страдания.
Попытка собрать свое решение
И тут плавно подходим к моменту “а, дай-ка, попробую!”.
Что нужно реализовать:
- получение начального состояния страницы. Не потерять контекст пользователя (для разных пользователей страница может значительно отличаться);
- отслеживание размеров объекта window;
- отслеживание прокручивания элементов страницы, включая сам документ;
- регистрацию пользовательской активности. Следить за перемещением мыши, клики, ввод данных в поля формы;
- компактный протокол обмена с сервером.
Сбор аналитики следует рассматривать для двух вариантов качества подключения к сети — для низкоскоростного нестабильного канала и для более благоприятных случаев.
Начальное состояние
Начальное состояние можно получить двумя способами:
- взять HTML и стили по событию window.load. Это создаст нагрузку на сеть, поэтому при плохом качестве сети придется отказаться от этого варианта;
- запросить страницу на стороне сервера. Необходимо передать контекст пользователя. Можно неявно украсть cookie или попросить явно передать URL и параметры авторизации в момент инициализации библиотеки.
В обоих случаях нужно отключить все скрипты со сохраняемой страницы, так как отклик интерфейса на действия пользователя будем делать самостоятельно по записям действий пользователя.
Остается вопрос со стилями и статическими данными. В общем случае их можно оставить как есть, но возможны спецэффекты в случае изменения статики в момент между получением начального состояния и моментом просмотра записанной активности.
В момент загрузки страницы сохраняем текущие размеры объекта window.
Отслеживание событий
Нужно отслеживать все события DOM-поддерева независимо от использования stopImmediatePropagation() / stopPropagation(). Для этого будем использовать addEventListener() с параметром useCapture=true.
Для отслеживания изменений в структуре документа и атрибутах можно использовать механизм — MutationObserver. DOM Node для регистрации обработчиков примем в параметрах инициализации, по умолчанию — document.
Также необходимо следить за положение прокрутки окна document.scroll и изменением размеров окна window.resize.
Для идентификации объекта события будем строить CSS селектор.
Протокол обмена
За неимением практических данных будем использовать формат JSON для обмена. Для борьбы с блокировщиками рекламы и всякого прочего, стоит использовать GET запросы и возвращать маленькое изображение, допустим, формата GIF. Для примера, URL /path/to/api/[JSON string].gif.
Стоит помнить, что URL — не резиновый, у него есть ограничение в 2000 символов. Достаточно маленькая величина, с учетом отправки информации об изменении структуры документа, поэтому стоит сразу озаботится сжатием данных, допустим, с применением алгоритма GZIP, JavaScript-реализация которого существует. Чтобы передать сжатые данные, придется дополнительно закодировать их в BASE64.Также нужно предусмотреть передачу по частям, но для проверки концепции будет излишне на данном этапе.
Практический результат
Исходный код прототипа клиентской библиотеки здесь.
Для экспериментов был выбран реальный проект:
- адаптивная верстка (HTML5, CSS3);
- Elm. Виджеты (формы регистрации/авторизации, многошаговые формы создания пользовательского контента) и профиль пользователя (SPA);
- встроенные карты Google Maps.
Интеграция проблем не вызвала, падения производительности браузера визуально не наблюдается.
Генерируемый трафик оказался не так велик, как ожидалось. С учетом ограничений сохранять раз в 10 секунд или, по накоплению, 100 событий данные не превышали 4 килобайт. Коэффициент сжатия сильно плавает от единиц до десятков, но для достаточно больших объемов (десятки килобайт) лежит в районе десятков, что логично, т.к. данные текстовые и есть много повторяющихся подстрок.
В сухом остатке
Прототип показал свою жизнеспособность. Чтобы совсем стало ясно, необходимо реализовать плеер и серверную часть, где предвидится ряд проблем:
- получение начального стейта, борьба с дубликатами данных. Возможно, придется кэшировать связанные данные. Есть подозрение, что придется использовать headless браузер для первичного проигрывания пользовательской активности с целью определения статических элементов;
- кроссбраузерная поддержка плеера. Зоопарк и межверсионное многообразие никто не отменял.
Стоит не забывать об особенностях при изменении атрибутов. Например, атрибут style нельзя просто взять через коллекцию атрибутов (Element.attributes), придется использовать свойство HTMLElement.style.cssText. Количество таких нюансов на данный момент не поддается оценке.
Если использовать headless браузер с предварительным проигрыванием пользовательской активности, то стоит рассмотреть вариант записи видео. В этом случае отпадает необходимость в плеере, но увеличивается необходимое количество вычислительных ресурсов и размер хранилища результирующих данных, что не всегда может быть рациональным.