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

Итак, я тестирую процесс входа в систему для https://community.smartsheet.com. Это может показаться слишком конкретным, но поверьте мне, здесь будет много информации, которая будет полезна для многих сценариев тестирования. Все это тяжело. Я пытался в прошлом с PhantomJS и просто сдался. Но теперь, когда Headless Chrome доступен и ядро ​​его интегрирует, у меня закончились оправдания, и этот тест действительно необходим, это наша самая хрупкая функция, и даже ее ручное тестирование отнимает много времени и сложно.

Вот один из возможных способов входа в систему с участием нового пользователя:

  1. Нажмите «Подключиться к Smartsheet» на сайте сообщества.
  2. Предоставьте электронное письмо.
  3. Введите пароль.
  4. Щелкните Разрешить.
  5. Укажите имя пользователя и примите Условия использования.

Примечание после 4. приложение Smartsheet перенаправит вас на сайт https://community.smartsheet.com, и это нельзя изменить. Конечно, мы хотим протестировать localhost, а это значит, что нам нужно попросить Chrome сделать вид, что 127.0.0.1 — это IP-адрес community.smartsheet.com. И здесь начинается самое интересное: хотя есть аргумент командной строки, который позволяет нам это сделать: chrome --host-rules='MAP community.smartsheet.com 127.0.0.1' Headless Chrome не еще не поддерживает это, запрос функции здесь. В официальной документации на ключи командной строки больше нет host-rules, он привык и, безусловно, работает до сих пор. В конце концов, вам нужно отредактировать /etc/hosts, этого никак не избежать. Но тогда ваши браузеры не смогут получить доступ к реальному сайту, поэтому на самом деле вам нужно запустить свой реальный Chrome с правилом хоста, сопоставляющим сайт сообщества с его реальным IP-адресом! Я люблю разработку веб-сайтов, это так же просто, как почесать правой ногой левое ухо, когда оно чешется.

После того, как вы это сделаете, посещение сайта сообщества с Chrome выдаст прекрасное предупреждение системы безопасности о том, что сертификат недействителен. Видите ли, это потому, что после правила хоста Chrome требует сертификат для IP-адреса, а не для хоста. Это, безусловно, имеет значение, потому что, если запрос функции, который я связал выше, будет выполнен, и Headless Chrome будет запущен с MAP правил хоста против локального Apache с использованием самозаверяющего сертификата, тогда сертификат также должен быть действительным для 127.0.0.1. Я обнаружил, что это руководство хорошо работает для самоподписанного сертификата, просто добавьте IP.1 = 127.0.0.1 в конец. И да, заставить самоподписанный сертификат работать без предупреждения стало настоящим трудом.

Итак, теперь у нас настроен Chrome, поэтому мы можем посетить сообщество вручную (если мы примем предупреждение о небезопасности), чтобы сравнить тест, а также автоматически с помощью Headless Chrome, который будет посещать локальный хост с самоподписанным сертификатом.

Теперь идет часть chromedriver. Как следует из названия, это то, что позволяет программно манипулировать браузером Chrome. Это совершенно необходимо, потому что Headless Chrome, как следует из названия, не имеет пользовательского интерфейса, поэтому нужно что-то делать. Это логично, но дальше следует банальная чушь: несмотря на то, что класс драйвера, предоставляемый Behat/Mink, называется драйвером Selenium, и мы собираемся использовать протокол веб-драйвера Selenium, и даже core/tests/README.md инструктирует установить селен, мы нет нужно! Chromedriver сам реализует протокол веб-драйвера, и этого достаточно. Тест, который я написал, работает, случайный тест, который я выбрал из ядра (JavascriptGetDrupalSettingsTest), работает, и, согласно mixologic, на тестовых ботах также не установлен Selenium. Чем меньше Java, тем лучше, на мой взгляд, и вообще, чем меньше движущихся частей, тем лучше. Видит бог, у нас достаточно движущихся, сломанных, незадокументированных деталей. Итак, мы можем просто установить chromedriver… за исключением того, что я не смог заставить его работать на Debian Jessie без установки libgconf-2–4.

Далее следует небольшая забавная основная ошибка: если вы добавите <env name="MINK_DRIVER_CLASS" value="Drupal\FunctionalJavascriptTests\DrupalSelenium2Driver" /> к phpunit.xml, тогда вам нужно будет использовать там MINK_DRIVER_ARGS, MINK_DRIVER_ARGS_WEBDRIVER работает, только если тестовый класс устанавливает protected $minkDefaultDriverClass = DrupalSelenium2Driver::class;. Возможно, здесь лучше не использовать MINK_DRIVER_CLASS, потому что, скорее всего, еще не все основные тесты пройдены. В любом случае, если вы используете «голый» Chromedriver, как я предложил выше, вам нужно переопределить аргументы, потому что ваш порт, скорее всего, будет 9515, а не 4444. MINK_DRIVER_ARGS='["chrome", {"chromeOptions":{"args":["--disable-gpu", "--headless”]}}, "http://localhost:9515"]' — это минимум, который вам понадобится. Если вы прочитаете об этих вещах и найдете «возможности», которые вам нужны, добавьте их на том же уровне, что и chromeOptions. Обратите внимание, что класс Behat Selenium2Driver предоставляет альтернативный способ обработки переключателей и возможностей командной строки, но, насколько я могу судить, он не подлежит ремонту, и я не смог найти документацию по нему, поэтому просто игнорируйте его, эта структура данных работает.

Столько работы и мы еще не написали ни строчки теста, ну может одну, где задаем класс драйвера. Мой setUp устанавливает тему по умолчанию, как описано здесь. И вот, наконец, мы добрались до теста!

Фундаментальный секрет использования JavascriptTestBase и DrupalSelenium2Driver заключается в том, что он кажется асинхронным, chromedriver, по-видимому, возвращается до завершения навигации. Это удобно, нигде не документировано, и многие вспомогательные методы кажутся сломанными, потому что они не ждут. И кажется, нет лучшего способа, чем просто ждать, протокол веб-драйвера работает с простыми HTTP-запросами и поэтому не перезванивает после завершения навигации. Так, например, после использования clickLink вам нужно использовать $this->assertSession()->waitForElement или аналогичный метод ожидания из класса JSWebAssert. Конечно, я могу быть здесь совершенно неправ, но я боролся со своим тестом в течение нескольких дней, и все время от времени давало сбой, и в тот момент, когда я переключился на методы ожидания, он больше никогда не давал сбоев. Если эта презумпция верна, то необходимо внести поправки в довольно большое количество документации:

  1. Классы JavascriptTestBase и DrupalSelenium2Driver должны быть сшиты с JSWebAssert.
  2. https://www.drupal.org/node/2846936, чтобы уточнить, что с webdriver вам нужно использовать их каждый раз, когда вы нажимаете / нажимаете / отправляете, а не только тогда, когда JavaScript что-то делает.
  3. Заплатки doxygen против Behat/Mink наверное около миллиона мест. Способов onDriverInterface как минимум.
  4. https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#sessionsessionidurl
  5. http://mink.behat.org/en/latest/guides/interacting-with-pages.html

Довольно много мест, где об этом дотошно ничего не упоминается. После написания этого поста я обнаружил две вещи, которые вселяют в меня уверенность, что я прав: соответствующий пакет NPM имеет этот пример кода: await driver.wait(until.titleIs('webdriver - Google Search'), 1000);, а также README.txt chromedriver говорит следующее:

Обработчик выполняет соответствующую командную функцию, которая завершается асинхронно.

Резюме:

  1. Устроиться - самое сложное. Самоподписанные сертификаты стали удивительно сложными, документация Chromium стала грушевидной, основная документация еще больше отстала от кода, чем обычно.
  2. Навигация в Chromedriver является асинхронной, и в документации по PHP это не подчеркивается. Если вы привыкли к тестированию в Drupal и пробуете писать тесты как раньше, вам придется нелегко. Есть новые утверждения для объектаJSWebAssert, возвращенные $this->assertSession(), чтобы помочь.

Пс. Не используйте $this->getSession()->visit() для просмотра тестового сайта, поскольку в настоящее время он будет просматривать родительский сайт, оставьте использованиеdrupalGet.