Обход ReCaptcha в Selenium тестах +13


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

Очень важный дисклеймер: обмануть капчу невозможно.

Если вы уже столкнулись с этой проблемой, и читаете эту статью, пытаясь нагуглить рецепт панацеи, то знайте, что его не существует. Тем более, в вашей голове уже скорее всего возникли инновационные мысли о том, чтобы сымитировать реалистичное поведение пользователя с помощью WebDriver, путём рандомного mouse overing'а элементов, кликов по инпутам, и бережно расставленных Thread.sleep(). Абсолютно точно известно, что этот подход работать не будет, не тратьте свое время попусту.

image

Получается, выхода нет?

Не все так пессимистично. Иногда достаточно постараться дать себе наиболее точный ответ на вопрос «Какая задача передо мной стоит?» и посмотреть на ситуацию шире. В большинстве случаев, вы поймете, что ваша цель не обмануть капчу, а обойти её, чтобы протестировать функционал, спрятанный за ней. На примере своего кейса, я поделюсь с вами найденными мною вариантами решения поставленной задачи.

Контекст: мы интегрировали часть своего продукта внутрь стороннего сервиса, и хотели мониторить, все ли в порядке на их стороне, т.к. они не занимаются покрытием third-party частей своей платформы. Чтобы получить доступ к нашему функционалу, сперва необходимо было залогиниться. Тут-то я и встретился с капчей лицом к лицу. Далее привожу все перепробованные мною варианты обхода данной проблемы.



Нерабочие


Залогиниться через Google или Facebook


Помимо классической аутентификации, присутствовали каноничные «Login with Google / Facebook». Само собой, там точно также присутствовали свои «капчи», поэтому этот вариант не помог решить проблему.

Имитация поведения пользователя


Да, я тоже это пробовал. Было забавно, но чересчур наивно.



Рабочие


Chrome / Firefox Profiles


Поговорим о первом «живом» варианте. В драйверах для этих браузеров (chromedriver / geckodriver) реализована возможность загружаться под заранее заготовленным User Profile. Он хранит в себе все сохраненные пароли, куки, сессии, и даже историю браузера и закладки. Т.е. таким образом мы попросту пропускали абсолютно неважный для нашей задачи шаг логина, и таким образом попадали сразу на страницу с объектом тестирования. Реализуется это следующим образом:

  1. Создаем «чистый» профиль браузера
  2. Вручную вводим капчу и логинимся на нужный ресурс
  3. Копируем необходимый профиль в наш проект (HOWTO для Firefox и Chrome)

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

Firefox:

// Инициализируем профиль
FirefoxProfile profile = new FirefoxProfile(new File("/путь/к/вашему/профилю"));
// Указываем профиль в передаваемых опциях
FirefoxOptions options = new FirefoxOptions();
options.setProfile(profile);
// Создаем браузер
WebDriver driver = new FirefoxDriver(options);

Chrome:

// Указываем профиль в передаваемых опциях
ChromeOptions options = new ChromeOptions();
options.addArgument("--user-data-dir=/путь/к/папке/с/профилями");
options.addArgument("--profile-directory=Название_папки_с_нужным_профилем");
// Создаем браузер
WebDriver driver = new ChromeDriver(options);

Этот подход хорошо показал себя при тестировании на локальной машине с установленным браузером и обычными gecko-/cromedriver’ами, но возникли проблемы при запуске на Jenkins. Мы поднимаем Selenium хаб и ноды внутри Kubernetes кластера, поэтому мы столкнулись с неприятностями в виде слишком долгого по времени маунта директории внутрь контейнера (чистый профиль в среднем весит около 25 MB, что немало), а так же некоторых проблем с CRUD правами браузера, который не мог вносить изменения в профайл в рантайме, и падал с “unknown error: failed to write prefs file” эксепшеном. Ко всему прочему, апдейтить профайл после достижения куками и сессиями своих Expiration Dates достаточно неудобно, да и не хотелось держать в проекте огромную папку с внутренностями профиля, поэтому в конечном итоге окончательным был выбран следующий вариант.

Cookies


“А ларчик просто открывался” — именно так можно было охарактеризовать ситуацию, после того, как мы просто добавили полученные вручную куки в драйвер. Алгоритм действий максимально прост и не зависит от выбранного браузера:

  1. Логинимся вручную
  2. Через Network смотрим Request Headers -> Cookie которые посылает наш браузер

Добавляем их в наши тесты следующим образом:

// Создаем cookie
private static final Cookie COOKIE = new Cookie("имя", "содержимое", "домен", "путь", new Date("дата"));
// Создаем браузер
WebDriver driver = new ChromeDriver(options);
// Добавляем cookie в браузер
driver.manage().addCookie(COOKIE);

Очевидный минус этого подхода — необходимость вручную менять куки после истечения их срока валидности. Но, в виду того что на тестируемой платформе этот срок составляет 3 месяца — мы и выбрали это решение.



А если мне не нужно логиниться?



А как же ситуация, когда речь идет не о авторизации и сессиях, а о совершении какого-либо одноразового действия (e.g. оформление заказа из корзины, регистрация нового пользователя и т.п.)? Здесь ситуация еще хуже. Два варианта которые я смог обнаружить, это:

  1. Договориться с вашими разработчиками о предоставлении вам некого workaround'а. Google предоставляет такую возможность, но помните, что вы осознанно делаете небольшую дыру в security.
  2. Воспользоваться сторонними платными сервисами, которые принимают с вашей стороны скриншот капчи, пытаются его декодировать, и отправляют вам расшифрованное значение. Сам я такой способ не пробовал и полностью рекомендовать его не могу.



Подведем итоги


Как вы могли убедиться — безвыходных ситуаций не бывает. Однако, будет глупо отрицать, что у абсолютно всех вышеперечисленных вариантов есть свои, достаточно весомые, минусы, так что выбор остается за вами.

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

P.S. Если вы знаете еще какие-либо работающие в реальной жизни решения — пожалуйста, опишите их в комментариях, будет очень интересно почитать.




К сожалению, не доступен сервер mySQL