Chromedriver что это
Перейти к содержимому

Chromedriver что это

  • автор:

Chromedriver что это

Скачай курс
в приложении

Перейти в приложение
Открыть мобильную версию сайта

© 2013 — 2023. Stepik

Наши условия использования и конфиденциальности

Get it on Google Play

Public user contributions licensed under cc-wiki license with attribution required

Как я чинил интерактивный логин, или Что там в кишках у //chrome/test/ChromeDriver?

Эта статья возникла из-за одной моей ошибки и двух багов — по одному в Chromium Headless и ChromeDriver. В результате пришлось собрать всё это из исходников (в статье есть подробнейшая инструкция), отладить цепочку взаимодействия SeleniumChromeDriverChromium в отладчике IntelliJ IDEA и Microsoft Visual Studio, покопаться в Java, C++ и JS.

Статья будет интересна тем, кто хочет понять, чем им грозит переход на Selenium + Headless Chrome и как с этим жить. А также всем, кто просто хочет немного поглумиться над несчастным быдлокодером.

Под катом есть пачка скриншотов (трафик!).

Кстати, всё это можно было бы сделать скринкастом — но нельзя, потому что время компиляции C++ необычайно долгое. Вместо этого тут будут скриншоты для привлечения внимания.

Вначале напомню названия участвующих продуктов. Их три штуки:

  • Chromium — это браузер. Небрендированная версия Google Chrome, без автообновлений, анальных следящих зондов, но и без несвободных компонентов типа кодеков.
  • Chromium ChromeDriver — это часть Chromium, которая отвечает за управление им с помощью внешних инструментов. Это standalone фронт-сервер (отдельный exe-файл), реализует стандарт W3C WebDriver и использует в качестве бэкенда Chrome DevTools.
  • Selenium и его Selenium Chrome Driver — эта штука предоставляет Java API для управления Хромиумом. Джава тут не принципиальна, на JavaScript и jWebDriver было бы то же самое (а на Puppeteer — возможно, нет, ибо он использует DevTools напрямую).

Всё вместе даёт нам в руки браузер, управляемый с помощью API на Java, JavaScript и так далее.

Вспомнили? Поехали дальше.

Завязка в том, что в одном из моих пет-проектов я парсю Хабр. Этот эксперимент называется Textorio, но там ещё нечего смотреть, я ещё даже не выкладывал код. Кстати, отправляется самый минимум запросов, и это не оказывает на Хабр вообще никакой нагрузки.

Для интерактивного логина на Хабр используется сочетание headed и headless Chromium. Вначале код пробует залогиниться на Хабру в автоматическом режиме с помощью headless-браузера и заранее сохранённых куков. Когда не получается (например, из-за гуглокапчи) — переходит в headed-режим и показывает пользователю окно логина.

Это обычная схема, которая используется и в другом софте — например, в Wirecast, которым я имею несчастье иногда пользоваться на стримах: таким образом там делается логин в Facebook, YouTube и Twitch. (По крайней мере это те направления, которые я сам использовал в стриминге. Может быть, их больше.) Но Wirecast использует свой велосипед, а я взял решение, достойное 2018 года (точнее, весны 2017) — полноценный Headless Chrome.

(на скриншоте у меня facepalm из-за того, что они не смогли посчитать высоту окна и показывают скроллбар справа)

На выбор повлияло ещё и то, что на Heisenbug и HolyJS удалось поговорить с кучей людей, которые его используют. И даже взять интервью у Виталия Слободина — бывшего разработчика PhantomJS, который сейчас активно топит за использование Headless Chromium. И даже пообщаться с Саймоном Стюартом — создателем WebDriver. Столько умных людей просто не могут ошибаться.

И действительно, сначала погружение в Headless Chrome оказалось мягким и приятным. Отладка проходила в полностью headed-режиме — так проще видеть, что браузер делает «под капотом». Но как дошло до продакшна, оказалось, что во время не-интерактивного логина куки, полученные в интерактивном режиме, полностью теряются!

Я чуть не вырвал себе все волосы на заднице. А потом наткнулся на эпичный тред в багтрекере Puppeteer. Puppeteer — это наш ближайший конкурент из мира JS, делают его такие же смузихлёбы, как мы, поэтому собирают они примерно такие же баги. Из этого трекера в дальнейшем было вытащено еще много удивительной инфы.

Вкратце, да — изначально Headless Chrome не мог вообще шарить никаких данных с headed-режимом. Но это не потому, что они такие умные и заботятся о безопасности (как предположила куча людей, из которых можно вспомнить Олега Царёва, например). А тупо потому, что раскладка файлов в user-data-dir в headless-режиме была другая. Со временем эту проблему начали решать, и в самых свежих канарейках (Chromium Canary — ночная сборка Хромиума) шаринг наконец заработал.

Впрочем, заработал только в одну сторону — сохраненное в headed можно использовать в headless, но не наоборот. Это связано с тем, что при выходе из headless Хромдрайвер прибивает его быстрее, чем Хромиум успевает сбросить данные на диск. Конкретную ссылку на баг я где-то потерял, если напишете в комментах — будет хорошо.

Но в моих целях интерактивного логина нужно только одно направление — из headed в headless, и так получилось, что оно уже работает в Канарейках.

Ну что ж, ясно, что надо переходить на Канарейку. Дело недолгое — подменить путь до chrome.exe.

Перезапустил тесты, и они тут же рассыпались в пыль.

Проблема возникала при выполнении вот такого тривиального кода:

public void setAttribute(WebElement element, String attName, String attValue)

На скриншоте видно плохо, поэтому повторю по буквам:
org.openqa.selenium.WebDriverException: unknown error: call function result missing ‘value’.

Отчаянию моему не было предела. Если раньше было предельно ясно, как править такие ошибки, то сейчас происходила какая-то жесть. Никакие очевидные выкрутасы с Java-кодом не помогали.

Приведенный выше код на JavaScript объяснять особо нечего: он просто сетит выбранный атрибут на странице. При запуске скрипта на ChromeDriver, массив arguments заполняется тем, что передано в параметрах. Как именно параметры туда подсовываются — это отдельный вопрос, ведь первым аргументом идёт не id, а некий WebElement, который конкретно я получаю через XPath. Как это хранится, мы увидим ниже. Почему такого метода нет нативно в ChromeDriver и его надо писать самостоятельно — понятия не имею, спишем на молодость проекта Selenium :-). Возвращаемся к теме.

Интуитивно понятно, что эта версия ChromeDriver просто не совместима с используемой версией браузера, и нужно попытаться найти достаточно свежую версию ChromeDriver.

В принципе, эти достаточно свежие версии ChromeDriver есть в гуглооблаке. И вот тут начинается мой факап, благодаря которому появилась эта статья.

Я скопировал себе свежайшую версию Хромдрайвера (от второго января или что-то такое), запустился на ней… и получил всё ту же ошибку. Иначе говоря, новый Хромдрайвер несовместим с Канарейкой тоже? Как же так?!

На самом деле, свежий Хромдрайвер работает отлично. Проблема в том, что из-за каких-то мистических проблем с Windows, новый файл просто не скопировался. Если бы файл скопировался успешно, ошибки бы мы уже не увидели. Замнем для ясности, каким надо быть дебилом, чтобы не посмотреть на версию драйвера в логе, и перейдем к дальнейшему повествованию.

Затем были некоторые попытки размышлений.
Во-первых, нужно было читнуть спецификацию W3C Webdriver. Это очень важная спецификация, как мы поймём дальше.

После этого я выкачал Selenium Chrome Driver прямо в проект и начал люто его дебажить.

Сразу после чтения спеки W3C бросилось в глаза, что запрос делается не http://127.0.0.1:9999/session/sessionId/execute/sync , как написано в спеке, а на тот же URL, но только без Sync. Оказалось, в этом и есть разница между W3CHttpCommandCodec и JsonHttpCommandCodec , которые устанавливаются драйверу при инициализации.

А что если его подменить — мелькнула безумная мысль? Ищем метод-сеттер… и не находим.
Это поле — закрытое, и выставляется оно единственный раз при обработке команды NEW_SESSION . Собственно, диалект мы придумываем не сами внутри Selenium, а его сообщает нам Хромдрайвер, и на это решение никак не повлиять.

Но у нас же есть Reflection! Поле с URL-ом — final, поэтому будем менять целиком диалект.
Попробуем поменять диалект прямо перед отправкой executeScript в тесте:

public static CommandCodec commandDialectFromJsonToW3C(ChromeDriver driver) < CommandCodec oldCommandCodec = null; CommandExecutor commandExecutor = driver.getCommandExecutor(); try < Field commandCodecField = commandExecutor.getClass().getSuperclass().getSuperclass().getDeclaredField("commandCodec"); commandCodecField.setAccessible(true); oldCommandCodec = (CommandCodec) commandCodecField.get(commandExecutor); W3CHttpCommandCodec newCodec = new W3CHttpCommandCodec(); commandCodecField.set(commandExecutor, newCodec); >catch (Exception e) < staticlogger.error("Can't change dialect", e); >return oldCommandCodec; >

(От вида этого кода глаза начинают кровоточить, но в своё оправдание — я писал его в сильно изменённом состоянии сознания, а здесь привожу для исторической достоверности.)

Круто? Нет. Хромдрайвер отлично обработал обращение на execute/sync и ответил в точности той же самой ошибкой, что в начале поста. Гипотеза с диалектом не выстрелила.

Простым решением было бы убиться об стену.

Но мы не ищем лёгких путей. Настоящий индеец идёт путём Gentoo, пружинистым шагом ступая по граблям, костылям и подпоркам ночных релизов!

Было принято решение собрать из исходников идеально подходящие друг другу версии Chrome и Chromedriver. В перспективе это позволит нам не зависеть от Большого Дяди и успешности его работы по сборке канареек. А еще, пердолиться с ручной сборкой из исходников — это приятно.

В принципе, это несложно, учитывая наличие подробных инструкций, хотя есть нюансы.

Во-первых, желательно выделить какой-нибудь быстрый диск (у меня это был SSD на NVMe), который исключить из сканирования антивирусом. Дело в том, что будет скачано около 20 гигабайт мелких файлов (и около 10, если качать без истории в git). Даже просто удаление этих файлов с SSD занимает минут пять. Если их начинает проверять антивирус, всё это может затянуться на часы.

Если у компа есть еще свободные ресурсы — они будут нелишними. Ни 20 ядер, ни 64GB RAM, ни самый наибыстрейший SSD на твёрдом топливе — ничего этого лишним не будет, как мы увидим дальше.

Во-вторых, оказалось совершенно необходимым настроить git:

$ git config --global user.name "Oleg Chirukhin" $ git config --global user.email "oleg@textor.io" $ git config --global core.autocrlf false $ git config --global core.filemode false $ git config --global branch.autosetuprebase always

Это может оказаться неочевидным, но без трех нижних настроек ничего не работает.
Если этого не сделать, то будут валиться какие-то ошибки в питоне.

В-третьих, я использую Windows как основную платформу для большинства пользователей Хабра. Нужно установить Visual Studio не ниже update 3.2 с патчем 15063 (Creators Update). Подойдет и бесплатная версия. В онлайн-установщике нужно выбрать «Desktop development with C++» и в нём «MFC and ATL support».

Если вы, как и я, не доверяете гую, то дальше нужно скачать онлайн-установщик и из командной строки вызвать его со следующими параметрами:
vs_community.exe —add Microsoft.VisualStudio.Component.VC.ATLMFC —includeRecommended

Дальше нужно пойти в «установку и удаление программ» и найти там Windows Software Development Kit .
Если вы пользуетесь Visual Studio, то там Windows Software Development Kit представлен в виде множества разных версий. Нужно найти самую свежую в списке.

Дальше будет кнопка Modify (или Change ), потом список выбора, в котором надо выбрать радиобаттон с названием Change , и дальше — таблица дополнительных компонентов. Нужно выбрать Debugging Tools For Windows и нажать еще одну кнопку Change .

Если этого не сделать, всё будет валиться с Очень Странными Ошибками.
Если сильно повезет, там будет что-то человекочитаемое типа:

Exception: dbghelp.dll not found in "C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\dbghelp.dll" You must install the "Debugging Tools for Windows" feature from the Windows 10 SDK.

Если не повезет, там будет какая-то бесполезная хренотень.

Дальше нужно закачать скрипты Depot Tools для управления репозиторием. Гугл не ищет лёгких путей и хранит весь код Хромиума в адской монорепе в гите, которая еще и юзает ссылки на SVN. Чтобы управлять этим безобразием, им пришлось понаписать скриптов управления, которые состоят из батников, баша, питона и всего такого, под каждую платформу — разное.

В руководстве говорят, что на Windows не стоит закачивать зависимости из чего-то кроме cmd.exe .
Проверил: рекомендация того стоит! Я пробовал собирать из моего любимого msys2 и из git bash : в них были ошибки, которых нет в cmd.exe вообще.

Поэтому запускаем cmd.exe и начинаем заливать скрипты:

mkdir z:/git cd /d z:/git git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git

Дальше добавляем путь до depot tools в PATH (у меня это было Z:/git/depot_tools )()
Еще нужно добавить глобальную переменную окружения DEPOT_TOOLS_WIN_TOOLCHAIN , значение — 0 . Иначе сборщик начнет использовать не тот тулчейн.
Если часто пердолитесь с переменными окружения в Windows, то лучше всего использовать Rapid Environment Editor. Это не проплаченная реклама, эта программа реально сэкономила мне многие часы жизни.

cmd.exe cd /d Z:/git/depot_tools gclient

Затем идём заливать код Хромиума, так как Хромдрайвер является его частью.
Нужно создать пустую директорию и выполнить следующие команды:

mkdir chromium cd chromium fetch chromium

Вот в этом месте придётся долго ждать, там качается репозиторий на 20 гигов.

Самопальные depot tools, конечно, никому не известны. Но есть небольшой хелп тут и тут. Если хелпа не хватает — нужно читать самостоятельно и мучиться.

Кстати, гит жрёт значительные ресурсы. Так что, если вы, как и я, играете в Overwatch параллельно с такими операциями, можно выловить некислых лагов.

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

Кстати, выше можно было использовать fetch —no-history chromium , это экономит десяток гигабайт на жестком диске.
Но это годится, очевидно, только для получения самой свежей версии, а нам нужно вначале облапать историю и получить номер коммита.

Смотрим версию «стабильного» браузера: 63.0.3239.84 .

Гуглим, что вышла она 6 декабря.

Как нормально посмотреть информацию о времени сборки, я так и не понял (если кто знает — расскажите?). Но гугление помогает.

Дальше смотрим все коммиты за эту дату:
git log —after=»2017-12-05″ —until=»2017-12-06″

Хэш последнего коммита за эту дату: 5eaac482ef2f7f68eab47d1874a3d2a69efeff33

git new-branch hacking git reset --hard 5eaac482ef2f7f68eab47d1874a3d2a69efeff33

Переключение может занять значительное время. Адская монорепа даёт о себе знать. В частности, пока я переключался на ветку hacking, в мастер прилетело несколько коммитов.

Кстати, для чтения коммитов очень рекомендую использовать графический git-клиент вместо командной строки.
Дело в том, что цифры там выходят совершенно безумные. Между интересующим нас коммитом (который был всего месяц назад) — пять тысяч штук других коммитов. Руками читать это в консоли — очень больно.

Для пробы, я сгенерил только проект для ChromeDriver. Нужно было понять, работает ли вообще сборка.
gn gen —ide=vs2017 —filters=»//chrome/test/chromedriver» out\Release

Я бы использовал в качестве IDE любимый CLion, но его нет в списке поддерживаемых в Ninja.
Из оставшегося, очевидно, Вижуалка — самое разумное под Windows.

Использующаяся дальше команда gn — это Ninja, система сборки C++. О том, как пользоваться Ninja конкретно в Хромиуме, рассказано в отдельном документе:

Чтобы посмотреть все возможные аргументы, запускаем
gn args —list

Теперь используем вычитанные там опции:
gn args out\Release

Открывается текстовый редактор, куда мы вписываем релизные опции:

is_official_build = true is_component_build = false is_debug = false

Сохранить и закрыть. Ninja тут же начнет перегенерацию файлов проекта. Если он при этом повиснет — отменяем и еще по разу делаем gn args .

После чего запускаем сборку.
ninja -C out\Release chromedriver

Эта сборка происходит быстро.
Не замерял, но минут за десять сделаться должна — там всего 4207 файлов.

Теперь можно пойти в директорию out\Default и спокойно скопировать оттуда chromedriver.exe. Он запустится без зависимостей и других файлов в этой директории. Мысленно поздравляем себя: теперь мы не зависим от умных дядей, собирающих нам ночные сборки на оффсайте.

Отлично, первая проба сделана, теперь нужно было собрать настоящий Хромиум.

ninja -C out\Default chrome gn gen --ide=vs2017 --filters="//chrome" out\ChromeRelease gn args out\ChromeRelease is_official_build = true is_component_build = false is_debug = false ninja -C out\ChromeRelease chrome

Получается около 34465 файлов.
На i7 6700k, 32GB RAM, NVMe SSD Samsung 960 EVO — полная сборка Хромиума заняла около 5 часов.
При 100% загрузке CPU и 60% загрузке RAM. (Предполагаю, что для полной загрузки RAM не хватило ядер.)
Репозиторий после сборки весит около 40 гигабайт.

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

Всё это время на компьютере ничего делать не стоит.
За эти 5 часов я отлично выспался! Наступило хмурое утро 5-го января.

Ах, эта романтика родной Gentoo, долгие бессонные красноглазые ночи, проведённые за сборкой ночных билдов из мастера! Когда видишь собранный chrome.exe в консоли, это ощущается, как будто проделал долгую, приятную физическую работу.

С радостным возбуждением и раскалывающейся от боли головой (5 часов — это совсем немного) запускаю тесты на свеженьком браузере.

Ничего не работает.

Стало понятно, что нужно погружаться глубже.

В этот момент я впервые за праздники вспомнил о подаренной бутылке текилы и с трудом подавил непреодолимое желание нажраться в слюни.

На этот раз понадобилась дебажная сборка.

gn gen --ide=vs2017 --filters="//chrome/test/chromedriver" out\Hacking gn args out\Hacking is_official_build = false is_component_build = true is_debug = true ninja -C out\Hacking chromedriver

К сожалению, прямо этот коммит мгновенно валится с ошибкой:

Z:\git\cf\src>ninja -C out\Hacking chromedriver ninja: Entering directory `out\Hacking' [75/4177] CXX obj/base/base/precompile.cc.obj FAILED: obj/base/base/precompile.cc.obj ../../third_party/llvm-build/Release+Asserts/bin/clang-cl.exe /nologo /showIncludes @obj/base/base/precompile.cc.obj.rsp /c ../../build/precompile.cc /Foobj/base/base/precompile.cc.obj /Fd"obj/base/base_cc.pdb" Assertion failed: ID < FilenamesByID.size() && "Invalid FilenameID", file C:\b\rr\tmpcwzqyv\w\src\third_party\llvm\tools\clang\include\clang/Basic/SourceManagerInternals.h, line 105 Wrote crash dump file "C:\Users\olegchir\AppData\Local\Temp\clang-cl.exe-247121.dmp" LLVMSymbolizer: error reading file: PDB Error: Unable to load PDB. Make sure the file exists and is readable. Calling loadDataForExe

OMG, C:\b\rr\tmpcwzqyv\w\src\third_party ? Захардкоженный диск C, вы это серьезно?

Но это не важно, ибо, просматривая интернет, обнаруживаем, что бага не в кривом пути, а в Clang. И на тот момент (до которого мы откатились в гите) её еще не пофиксили. Сейчас уже всё ОК. На тот момент баг обходился отключением precompiled headers.

Кстати, я каждый раз из Windows Explorer удаляю out\Hacking , чтобы быть уверенным в результате. С Windows ни в чём нельзя быть уверенным просто так.

gn gen --ide=vs2017 --filters="//chrome/test/chromedriver" out\Hacking gn args out\Hacking is_official_build = false is_component_build = true is_debug = true enable_precompiled_headers = false ninja -C out\Hacking chromedriver

Остаётся открыть проект в Visual Studio:
devenv out\Hacking\all.sln

При открытии Вижуалка подвисает — это нормально.
Но если она подвисает дольше нескольких минут — надо закрывать и открывать заново.
Особенно если у вас ReSharper C++ или что-то такое.

Изучение исходника по кейворду execute дает единственный сколько-нибудь интересный файл: chromedriver\chrome\web_view_impl.cc

В нем нас интересует, в первую очередь, CallFunction — очевидно, она обрабатывает входящий запрос.

Status WebViewImpl::CallFunction(const std::string& frame, const std::string& function, const base::ListValue& args, std::unique_ptr* result) < std::string json; base::JSONWriter::Write(args, &json); std::string w3c = w3c_compliant_ ? "true" : "false"; // TODO(zachconrad): Second null should be array of shadow host ids. std::string expression = base::StringPrintf( "(%s).apply(null, [null, %s, %s, %s])", kCallFunctionScript, function.c_str(), json.c_str(), w3c.c_str()); std::unique_ptrtemp_result; Status status = EvaluateScript(frame, expression, &temp_result); if (status.IsError()) return status; return internal::ParseCallFunctionResult(*temp_result, result); >

После того, как ответ получен, перед отдачей клиенту оно парсит результат:

Status ParseCallFunctionResult(const base::Value& temp_result, std::unique_ptr* result) < const base::DictionaryValue* dict; if (!temp_result.GetAsDictionary(&dict)) return Status(kUnknownError, "call function result must be a dictionary"); int status_code; if (!dict->GetInteger("status", &status_code)) < return Status(kUnknownError, "call function result missing int 'status'"); >if (status_code != kOk) < std::string message; dict->GetString("value", &message); return Status(static_cast(status_code), message); > const base::Value* unscoped_value; if (!dict->Get("value", &unscoped_value)) < return Status(kUnknownError, "call function result missing 'value'"); >result->reset(unscoped_value->DeepCopy()); return Status(kOk); >

Внимательно присматриваемся к коду и… видим нашу ошибку — call function result missing 'value'!

Конечно, такое предположение еще надо подтвердить. «Code is cheap, show me debugger».

Для этого идём в IntelliJ IDEA и в Java-коде ставим брейкпоинт непосредственно перед отправкой executeScript:

После этого переходим в Вижуалку и выбираем там отладочное подключение к процессу:

И из списка выбираем chromedriver.exe :

Ставим брейкпоинт в начало функции WebViewImpl::CallFunction и смотрим, что произойдёт.

Дальше отпускаем код в Идее и натыкаемся на только что установленный брейкпоинт в Вижуалке.

В отладчике можно найти много интересных подробностей о соединении. Например, мы видим, что переданные в JS-функцию WebElement передаются по неким внутренним ссылкам, а не по XPath (по которому их искали изначально). Это очень круто, быстро и удобно. Функция передаётся тупо строкой — и все остальные параметры тоже завёрнуты в разумные типы современного C++.

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

Параметры команды в JSON-форме (получающиеся в результате выполнения base::JSONWriter::Write(args, &json); ) можно красиво посмотреть в отладчике:

Сомнительный параметр kCallFunctionScript оказался дичайшей простыней из JavaScript, засунутого в const char[] . Во имя сохранения психики хабровчан, выкладываю только небольшой фрагмент:

Самая интересная часть отладки происходит, когда мы подбираемся к функции ParseCallFunctionResult :

Как мы и ожидали, предположение сработало.

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

Я рванулся в Java+JS код и переписал исходную функцию, добавив в конце JS-кода retur true; :

public void setAttribute(WebElement element, String attName, String attValue)

Все тесты мгновенно позеленели.

Это, очевидно, ересь, и я уже начал готовиться файлить баг на трекер.

Но тут в голове что-то начало проясняться, и в минуту прозрения вспомнились слова из спеки W3C:

The Execute Script command executes a JavaScript function in the context of the current browsing context and returns the return value of the function.

Понимаете, returns. Они не следуют «Key words for use in RFCs to Indicate Requirement Levels». Они не используют слова must, must not, should и should not для индикации уровня значимости. Соответственно, с какой-то точки зрения, returns можно читать как must return. Или нет — это вопрос интерпретации.

С другой стороны, по спецификации и не требуется, чтобы мы передавали завершённую функцию. В конце концов, Хромдрайвер и так дописывает нам заголовок функции — так почему бы ему не приписать в конец и return?

Или с третьей стороны, функции в JS не могут вернуть ничего. Они всегда возвращают, как минимум, undefined .

Взглянем на таблицу соответствия:

null === undefined // false null == undefined // true null === null // true

Очевидно, что undefined отлично мапится на null , имеющийся в большинстве языков программирования (или имеющий эквиваленты). Нет никакой проблемы, чтобы транслировать undefined во что-то другое пустое.

С четвертой стороны, понятно, что проблема в стандарте. Конкретно в этом случае спецификация неполна. Оно оговаривает, что ExecuteScript обязательно вернёт то, что вернёт функция. Но не оговаривает, как он себя поведёт, если функция ничего не возвращает: текст спецификации можно интерпретировать и как «раз функция возвращает ничего, ExecuteScript это ничего и вернёт». Другая реализация ExecuteScript может стать несовместимой с новой версией спеки, так как сейчас возможны расклады:

  • функция ничего не возвращает -> ExecuteScript возвращает A
  • функция ничего не возвращает -> ExecuteScript возвращает B != A
  • наблюдаемое нами сейчас поведение с ошибкой (и в какой-то реализации ExecuteScript оно было изначально!)

Фиксировать любое поведение не получится. Если только разрешать возможность сразу всех трёх вариантов одновременно.

На основании всего этого, пришёл к выводу, что файлить баг бессмысленно. Раз у меня вызов executeScript всё равно проходит сквозь транслятор, то можно просто детектить там return, и если его нет — приписывать в конец. Или проще — сделать два варианта вызова — для «функции» (есть результат), и для «процедуры» (нет результата), и «процедуре» всегда в конец дописывать return true ; Или еще проще — каждый раз отслеживать наличие return вручную.

Но один вопрос не переставал глодать — а на кой чёрт поломали работающее поведение, какой коммит это притащил?

Тут я забрался в историю гита и нашёл неожиданное: коммит, в котором эту проблему уже исправили.

Коммит называется так: «Fix ExecuteScript when script returns no result».

Смотрим на содержимое коммита:

Вместо того, чтобы возвращать статус «неизвестная ошибка», предлагается возвращать статус «ок». А ЧТО, ТАК МОЖНО БЫЛО?

Но ведь этот коммит уже есть в самой свежей версии Хромдрайвера. Почему же оно у меня не работало?! Пробую скопировать файл chromedriver.exe на старое место — получаю ошибку. Идиот! Всё это время я запускался на устаревшем драйвере!

Убираю return true; из кода, обновляю свой бранч hacking до самой свежей версии Хромдрайвера, перезапускаю тесты. Тесты зеленеют.

Выводы

  • При работе с Headless Chrome приходится рыться в их багтрекере и в трекерах связанных проектов;
  • И отлаживать их взаимодействие между собой;
  • Особенно если ты идиот;
  • К счастью, всё это довольно просто и быстро делается и доступно простому смертному;
  • Даже если ты используешь Windows;
  • C++ собирается медленно, это не исправить никак;
  • Бранчи в гугловой репе переключаются медленно, это не исправить никак;
  • Нужно следить за используемыми версиями Chromium и ChromeDriver, синхронизировать их;
  • Selenium написан так, что отлаживать его проще, импортируя исходник в проект, а не хачить рефлекшном;
  • Функциям в executeScript всегда стоит возвращать какое-нибудь значение: на всякий случай, вдруг в будущем это поведение снова поменяют;
  • WebDriver — это стандарт W3C. Его можно и нужно читать;
  • На Heisenbug и HolyJS бывают люди типа Виталия Слободина — разработчика PhantomJS, и Саймона Стюарта — создателя WebDriver. Можно их отводить в уголок и мучить неприятными вопросами, вот как в этой статье.

У нас остался нерассмотренным еще один компонент — собственно, сам Chromium. Про него есть что сказать, но это будет уже совсем другая история, с новыми кровавыми подробностями.

  • Блог компании JUG Ru Group
  • JavaScript
  • Java
  • C++
  • Тестирование веб-сервисов

Установка драйвера chromedriver для Chrome Selenium

Для запуска тестов Selenium в Google Chrome, помимо самого браузера Chrome, должен быть установлен ChromeDriver. Установить ChromeDriver очень просто, так как он находится в свободном доступе в Интернете. Загрузите архив в зависимости от операционной системы, разархивируйте его и поместите исполняемый файл chromedriver в нужную директорию.

Какую версию chromedriver установить?

Мы должны установить именно ту версия которая была бы совместима с установленным Google Chrome на нашем ПК или VDS. В случае, если версии не совпадают, то мы получим данную ошибку:

selenium.common.exceptions.SessionNotCreatedException: Message: session not created: This version of ChromeDriver only supports Chrome version 81

Введите в адресную строку Google Chrome данный путь:

chrome : / / settings / help

У вас появится вот такое окно:

Версия chromedriver

Рисунок 1 — Узнаем версию браузера Google Chrome

Скачать ChromeDriver для Linux, Windows и Mac

На данный момент актуальная версия драйвера 81.0.40 хотя у меня установлен более старый Google Chrome и последняя версия мне не подойдет. Как видно на рисунке выше, мне нужна версия 79.0.39 у вас может быть другая версия, нужно её скачать.

Скачать драйвер ChromeDriver

Рисунок 2 — Официальный сайт Google для загрузки драйвера chromedriver

На момент прочтения этой статьи версия может быть другой. Всегда выбирайте более новую версию, чтобы не поймать старые баги которые уже давно исправили в новой версии. НО! Помните, что вам нужно обновить и свой браузер Google Chrome если вы хотите работать с новой версией ChromeDriver.

Установка ChromeDriver под Linux, Windows и Mac

  1. Заходим на сайт https://chromedriver.storage.googleapis.com/index.html?path=79.0.3945.36/ (Проверьте сайт с Рис. 2 на обновления, тут версия: 79.0.3945);
  2. Скачиваем архив под вашу операционную систему;
  3. Распаковываем файл и запоминаем где находится файл chromedriver или chromedriver.exe (Windows).

Архив Chromedriver

Рисунок 3 — Скаченный архив с ChromeDriver

Если у вас Linux дистрибутив или Mac, вам нужно дать файлу chromedriver нужные права на выполнения. Открываем терминал и вводим команды одна за другой.

Что такое Selenium WebDriver?

Эта статья является продолжением более общей статьи «Что такое Selenium?», в которой объясняется, какое положение занимает Selenium WebDriver среди других инструментов автоматизации веб-приложений.

Здесь я постараюсь рассказать более подробно о том, что такое Selenium WebDriver, и почему его бессмысленно сравнивать с TestComplete, QuickTest Pro и другими инструментами автоматизации тестирования. И дело не только в том, что Selenium WebDriver бесплатный и открытый – его столь же бессмысленно сравнивать с другими бесплатными инструментами, такими как Sahi или Robot Framework.

Потому что Selenium WebDriver – это не инструмент для автоматизации тестирования.

А что же это такое?

На этот вопрос можно дать несколько разных ответов, сначала я дам короткие ответы, а потом – более подробные.

Кроме того, я объясню, почему Selenium WebDriver имеет такой убогий и неудобный в использовании интерфейс (набор команд), почему он не генерирует красивые отчёты и почему несмотря на всё это он настолько популярен 🙂

На всякий случай оговорюсь, что хотя в этой статье речь идёт про WebDriver, многие аргументы справедливы и в отношении Selenium RC, но я не буду ничего говорить специально про эту устаревшую версию, потому что её место – на свалке истории.

Итак, что такое Selenium WebDriver?

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

  • спецификацию программного интерфейса для управления браузером,
  • референсные реализации этого интерфейса для нескольких браузеров,
  • набор клиентских библиотек для этого интерфейса на нескольких языках программирования.

Selenium WebDriver – это драйвер браузера

Наверняка каждый, кто сталкивался с компьютерами, даже не айтишник, знает слово «драйвер». Это такая маленькая программа, точнее программная библиотека, которая позволяет другим программам взаимодействовать с некоторым устройством. Драйвер принтера позволяет печатать что-нибудь на принтере. Драйвер диска позволяет читать и писать данные. Драйвер сетевой карты позволяет обмениваться данными с другими компьютерами по сети.

С драйвером пользователи не работают непосредственно. Они работают с прикладными программами, которые, посредством драйверов, взаимодействуют с теми или иными устройствами. Драйвер не имеет пользовательского интерфейса. Постойте, но ведь иногда бывает пользовательский интерфейс для настройки драйвера? Бывает. Но это интерфейс программы для настройки драйвера, а не самого драйвера. Драйвер имеет только программный интерфейс, его назначение состоит в том, чтобы дать возможность прикладным пользовательским программам взаимодействовать с устройством.

Так вот, Selenium WebDriver, или просто WebDriver – это драйвер браузера, то есть не имеющая пользовательского интерфейса программная библиотека, которая позволяет различным другим программам взаимодействовать с браузером, управлять его поведением, получать от браузера какие-то данные и заставлять браузер выполнять какие-то команды.

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

Структурирование, группировку и запуск тестов, а также генерацию отчётов о тестировании, обеспечивает фреймворк тестирования, такой как JUnit или TestNG для Java, NUnit или Gallio для .Net, RSpec или Cucumber для Ruby и так далее. Разработка тестов ведётся в среде Eclipse, Intellij IDEA, Visual Studio, RubyMine и так далее. Сборка осуществляется посредством Maven, Gradle, Ant, NAnt,Rake и так далее. Запуск тестов по расписанию и публикацию отчётов выполняет сервер непрерывной интеграции – Jenkins, CruiseControl, Bamboo, TeamCity и так далее. И всё это – самостоятельные инструменты, не имеющие отношения к проекту Selenium.

Впрочем, в рамках проекта Selenium разрабатывается не только драйвер, но ещё несколько сопутствующих продуктов – Selenium Server позволяет организовать удалённый запуск браузера, при помощи Selenium Grid можно построить кластер из Selenium-серверов. Они встают в один ряд с вышеперечисленными инструментами и фреймворками, потому что также участвуют в построении системы запуска тестов. Кроме того, имеется «рекордер», который называется Selenium IDE, он умеет записывать действия пользователя и генерировать код, в котором используется интерфейс WebDriver для выполнения записанных действий.

Но главным в проекте Selenium является именно WebDriver, это ключевой элемент экосистемы Selenium.

Существуют ли другие драйверы? Разумеется.

Внутри каждого коммерческого «интегрированного» инструмента имеются драйверы браузеров, но они как правило не могут быть использованы отдельно вне этого инструмента. Есть и бесплатные открытые драйверы – Watir предоставляет доступ к основным браузерам, WatiN имеет неплохой драйвер для браузера Internet Explorer, Sahi умеет работать с «большой пятёркой» браузеров.

Как сравнить Selenium WebDriver с другими инструментами?

Из всего вышенаписанного можно сделать вывод, что сравнивать WebDriver с каким-нибудь инструментом тестирования типа TestComplete или Sahi бессмысленно. Они находятся в разных весовых категориях. Это всё равно, что сравнивать драйвер принтера с текстовым редактором.

А что можно сравнивать?

  • какие браузеры и какие версии браузеров поддерживаются, в том числе мобильные,
  • какие операционные системы поддерживаются, в том числе мобильные,
  • можно ли управлять одновременно несколькими браузерами на одной машине, не возникает ли конфликтов,
  • можно ли управлять браузером на удалённой машине,
  • какие действия в браузере можно выполнять,
  • какие данные из браузера можно получать,
  • насколько точно драйвер эмулирует действия пользователя, то есть генерирует ли он все те же самые события в браузере, которые возникают при работе настоящего пользователя,
  • можно ли работать с диалоговыми окнами (alert, prompt),
  • можно ли работать с «нативными» окнами (диалог загрузки файлов),
  • можно ли работать с HTTPS-протоколами и сертификатами,
  • и так далее.

Что касается сравнения с «комплексным» инструментами типа TestComplete или Sahi, для этого нужно брать не WebDriver, а полный стек.

Например, стек для технологии Java может быть таким: Jenkins + Maven + Thucydices + JUnit+ WebDriver. К этому добавляются ещё все возможности языка программирования Java, плюс масса плагинов для Maven и Jenkins, а чтобы совсем всё было круто – можно запускать тесты в облаках, используя какой-нибудь сервис типа SauceLabs.

Вот тогда сравнение будет интересным. Но это уже заслуга не только WebDriver, важен весь стек, а не только драйвер браузера. Что касается WebDriver, стоит отметить лишь то, что он прекрасно встраивается практически в любой стек, это одно из его достоинств как «независимого» драйвера.

Разумеется, WebDriver может использоваться не только при тестировании. Ему вообще безразлично, кто и зачем хочет управлять браузером. Вы можете автоматизировать какие-то рутинные задачи. Можете сделать ботов, которые будут флудить в форумах. Можете сделать скрипт, который автоматически снимает скриншоты для документации. Всё что угодно. Драйверу всё равно. Он всего лишь предоставляет доступ к браузеру.

Кроме того, какой бы инструмент вы ни использовали – вполне возможно, что к нему удастся подключить WebDriver, который имеет реализации на самых разных языках – Java, C#, Ruby, Python. И тогда вы в дополнение ко всем возможностям вашего любимого инструмента добавите все достоинства WebDriver. Это стоит потраченных усилий, потому что среди драйверов на данный момент он лучший.

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

Selenium WebDriver – это спецификация интерфейса для управления браузером

Самое главное отличие WebDriver от всех остальных драйверов заключается в том, что это «стандартный» драйвер, а все остальные – «нестандартные».

И это не простая фигура речи.

Организация W3C действительно приняла WebDriver за основу при разработке стандарта интерфейса для управления браузером. Сейчас он находится в состоянии публичного рассмотрения.

Через год-полтора этот стандарт будет утверждён. И тогда реализация интерфейса WebDriver будет возложена на производителей браузеров, а WebDriver как независимый драйвер, возможно, в будущем исчезнет совсем, потому что он будет встроен непосредственно в браузеры.

Таким образом, можно сказать, что Selenium WebDriver это вообще не инструмент, а спецификация, документ, стандарт, описывающий, какой интерфейс браузеры должны предоставлять наружу, чтобы через этот интерфейс можно было браузером управлять.

Пока стандарт обсуждается, производители браузеров уже действуют. В рамках проекта Selenium было разработано несколько референсных реализаций для различных браузеров, но постепенно эта деятельность переходит в ведение производителей браузеров. Драйвер для браузера Chrome разрабатывается в рамках проекта Chromium, его делает та же команда, которая занимается разработкой самого браузера. Драйвер для браузера Opera разрабатывается в компании Opera Software. Драйвер для браузера Firefox пока разрабатывается участниками проекта Selenium, но в недрах компании Mozilla уже готовится ему замена, которая носит кодовое название Marionette. Этот новый драйвер для Firefox уже доступен в девелоперских сборках браузера. На очереди Internet Explorer и Safari, к их разработке сотрудники соответствующих компаний пока не подключились, но кое-какие сдвиги в этом направлении есть, потому что стандарт (даже будущий) обязывает.

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

А что случится после того, как во всех браузерах будет реализован этот стандарт?

Было бы логично ожидать, что производители инструментов тестирования не станут изобретать велосипеды, а будут управлять браузером через стандартный интерфейс. Можно сказать, что все инструменты станут использовать WebDriver для взаимодействия с браузером. Но это будет уже не Selenium WebDriver как независимый драйвер, а Selenium WebDriver как спецификация интерфейса.

Так почему же у него такой примитивный интерфейс?

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

Набор команд последовательно сокращался, были выброшены такие «повышающие удобство использования» команды как check, uncheck (для чекбоксов), select (для выпадающих списков). Все они сводятся к более простой команде click и поэтому они лишние. Сейчас в интерфейсе WebDriver осталась только одна избыточная команда – это submit, но может быть когда-нибудь и она будет устранена.

Кроме того, структура интерфейса проектировалась таким образом, чтобы можно было описать его на языке IDL (именно это сделано в стандарте W3C) и сделать реализации на различных языках программирования. Поэтому использовался минимум языковых идиом, минимум «скрытых» переменных, интерфейс «тупой и прямолинейный».

Но зато благодаря этой примитивности интерфейса сейчас для интерфейса WebDriver имеются реализации клиентских библиотек на Java, C#, Ruby, Python, JavaScript, PHP, Perl и даже Haskell!

И благодаря той же самой простоте WebDriver прекрасно интегрируется с любыми другими инструментами, встраивается в любой стек. В этом секрет его популярности и быстрого распространения – он не пытается «победить» другие инструменты, вместо этого он интегрируется с ними.

А как же удобство использования?

Эту задачу должны решать расширения, построенные на базе Selenium WebDriver. Именно они должны предоставлять расширенный набор команд, реализуя эти команды через примитивный интерфейс WebDriver. В дистрибутиве Selenium имеется класс Select, предназначенный для работы с выпадающими списками, который является наглядной демонстрацией того, как должны строиться расширения.

Постепенно появляются библиотеки, которые строятся на базе Selenium WebDriver и предоставляют более высокий уровень абстракции: Selenide, fluent-selenium, watir-webdriver, Thucidides. Популярные фреймворки для проектирования тестов позволяют наряду с другими драйверами использовать WebDriver. Среди таких фреймворков можно упомянуть Robot Framework, Capybara и тот же Thucidides.

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

Число таких расширений и инструментов будет расти, сложность тоже. Так что вскоре может так случиться, что вы, используя какой-то инструмент, будете выполнять тесты, даже не подозревая о том, что взаимодействие с браузером осуществляется через драйвер Selenium WebDriver.

Стоит ли тогда вообще изучать Selenium?

Может быть лучше изучать эти библиотеки и инструменты более высокого уровня?

  1. Какой бы инструмент вы ни использовали, вам нужно выбрать драйвер, управляющий браузером. Чтобы его выбрать, вы должны знать возможности драйвера – что он может, а чего не может. На этом уровне Selenium необходимо освоить каждому специалисту по автоматизации. При этом конкретно интерфейс WebDriver, если вы с ним работаете, изучать нет необходимости.
  2. Простой набор команд выучить проще, чем «расширенный», то есть Selenium освоить проще, чем его расширение. У этого явления есть и обратная сторона – если вы изучили расширенный набор команд, то внезапно оказывается, что набор команд WebDriver вы при этом тоже освоили.
  3. Расширения, как правило, языково-зависимые, потому что добавление удобства предполагает использование языковых идиом, типичных приёмов организации кода на том или ином языке программирования. Базовый интерфейс WebDriver простой, поэтому освоив его, вы сможете использовать его на любом языке, он будет выглядеть практически одинаково.
  4. Большинство библиотек, нацеленных на повышение удобства интерфейса, улучшают средства поиска элементов – дополнительные типы локаторов, более удобный способ описания локаторов и так далее. Примитивы, соответствующие действиям пользователя, в WebDriver уже и так достаточно хороши. Хотя, конечно, библиотеки будут реализовывать типовые «связки», то есть последовательности этих действий, аналогично тому, как это сделано в классе Select для выпадающих списков.
  5. Если вы используете «таблички» для описания тестов (как в Robot Framework) или специальный язык для описания на уровне предметной области (DSL, Domain Specific Language) – вам нет необходимости знать о примитивах WebDriver. Но если вы реализуете «фикстуры» для тестов, описываете действия, которыми можно будет оперировать в табличках, реализуете DSL – вам придётся работать непосредственно с WebDriver, либо с каким-то его расширением, но не слишком высокоуровневым.
  6. И самый последний аргумент, который, я надеюсь, со временем будет становиться всё менее актуальным – увы, пока хороших расширений катастрофически не хватает. Они обязательно появятся. Может быть, именно вы реализуете одно из таких расширений. Для этого вам понадобиться изучить интерфейс WebDriver. И те, кто будут пользоваться плодами вашего труда, смогут работать с более высокоуровневой библиотекой. А пока приходится использовать непосредственно WebDriver с небольшими надстройками над ним.

Надеюсь, всё вышесказанное позволит вам лучше понять, какое место Selenium WebDriver занимает в общей картине мира и как он соотносится с другими инструментами. Если всё ещё остались непонятные моменты – задавайте вопросы в комментариях, я постараюсь всё прояснить.

  • тестирование
  • автоматизация
  • webdriver

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *