BioRAFT DrupalCon Seattle 2019 @YesCT | Karl bit.ly/BehatAuto Using Behat as a Webapp Automation Tool 1
BioRAFT DrupalCon Seattle 2019 @YesCT | Karl bit.ly/BehatAuto
Using Behat as a Webapp Automation Tool
1
BioRAFT DrupalCon Seattle 2019 @YesCT | Karl bit.ly/BehatAuto
Using Behat as a Webapp Automation Tool
2
Karl DeBisschop https://www.drupal.org/u/kdebisschop (d.o contact)
Cathy Theys https://www.drupal.org/u/yesct (@YesCT, slack, twitter)
BioRAFT DrupalCon Seattle 2019 @YesCT | Karl bit.ly/BehatAuto
Application can manage training, equipment inspections, chemical inventory, research compliance (animal exposure, transgenic materials, etc), and more. Goal of software is to make research safer while at the same time freeing investigators from administrative overhead.
10 Developers, agile development process, docker-based infrastructure.
80 live customer sites
3Karl | @YesCT
BioRAFT DrupalCon Seattle 2019 @YesCT | Karl bit.ly/BehatAuto
The Problem● We were unable to migrate customers from a legacy Drupal Application
○ Drush is not a viable option● We use a selenium-based ruby utility in a terminal to press buttons
○ Requires different skills and infrastructure
4
The Alternative● Use behat● Integrate with DevOps Automation Platforms
○ Jenkins○ Rundeck
BioRAFT DrupalCon Seattle 2019 @YesCT | Karl bit.ly/BehatAuto
Behat● What is behat?
○ Behat is a testing framework that performs actions on a web site and tests assertions about the expected behavior
○ It uses “nearly English” Gherkin language to describe features of the site and outcomes of the actions being tested
● Written in PHP, available via composer● Used for behavioral testing in Drupal 8● Here we are using it to operate controls on a
running web site and not a test environment
5
BioRAFT DrupalCon Seattle 2019 @YesCT | Karl bit.ly/BehatAuto
Rundeck● Runs the behat scenarios● Presents forms and buttons for running behat● Different permissions for ops, developer, feature owner...● Stores “secrets” like admin password
6
BioRAFT DrupalCon Seattle 2019 @YesCT | Karl bit.ly/BehatAuto9
Karl | @YesCT
BioRAFT DrupalCon Seattle 2019 @YesCT | Karl bit.ly/BehatAuto10
Karl | @YesCT
BioRAFT DrupalCon Seattle 2019 @YesCT | Karl bit.ly/BehatAuto11
AdminPassword populated with value from RunDeck secrets
Karl | @YesCT
BioRAFT DrupalCon Seattle 2019 @YesCT | Karl bit.ly/BehatAuto12
Karl | @YesCT
BioRAFT DrupalCon Seattle 2019 @YesCT | Karl bit.ly/BehatAuto
FeatureContext
13
use Behat\Mink\Driver\GoutteDriver;use Behat\Mink\Exception\ExpectationException;use Behat\Mink\Exception\UnsupportedDriverActionException;use Behat\MinkExtension\Context\MinkContext;
class FeatureContext extends MinkContext {
protected $bioraftUsers = [];
/** * @param array $bioraft_users List of users/pass from behat.yml. */ public function __construct(array $bioraft_users = []) { $this->bioraftUsers = $bioraft_users; }
Karl | @YesCT
BioRAFT DrupalCon Seattle 2019 @YesCT | Karl bit.ly/BehatAuto
Behat YAML Configuration File (behat.yml)
14
default: suites: default: contexts: - FeatureContext: bioraft_users: admin: display_name: "Site Admin" password: ------------ ⇐ @option.AdminPassword@ extensions: Behat\MinkExtension: browser_name: chrome base_url: $site_name goutte: guzzle_parameters: Lakion\Behat\MinkDebugExtension:
Karl | @YesCT
BioRAFT DrupalCon Seattle 2019 @YesCT | Karl bit.ly/BehatAuto
Behat Environment and Invocation$ export BEHAT_PARAMS='{ "extensions" : { "Behat\\MinkExtension" : { "Base_url" : "http://earth.127.0.0.1.nip.io:8083/"}}}'
$ vendor/bin/behat features/save_themes.feature
15Karl | @YesCT
BioRAFT DrupalCon Seattle 2019 @YesCT | Karl bit.ly/BehatAuto
Example 1: ThemesCustomer data copied to mirror nightly, but theme cache hashes do not match
16
Feature: Reset Themes As an administrative user, I need to save themes.
Scenario: Save Themes and Clear Cache: Given I am logged in as "admin" And I go to "/admin/build/themes/settings/bioraft3x" And I press "Save configuration" Then I should see "The configuration options have been saved." And I should not see any errors
And I go to "/admin/build/themes/settings/bioraftmobile3x" And I press "Save configuration" Then I should see "The configuration options have been saved." And I should not see any errors
BioRAFT DrupalCon Seattle 2019 @YesCT | Karl bit.ly/BehatAuto
Example 1: Themes
17
/** @Given I am logged in as :name */public function assertLoggedInByName($name) { $session = $this->getSession(); $session->visit($this->locatePath('/user')); $page = $session->getPage(); $page->fillField('name', $name); $page->fillField('pass', $this->bioraftUsers[$name]['password']); $page->pressButton('Log in'); $this->assertSession() ->pageTextContains($this->bioraftUsers[$name]['display_name']);}
BioRAFT DrupalCon Seattle 2019 @YesCT | Karl bit.ly/BehatAuto
Example 1: Themes
18
/** @Then I should not see any errors */public function assertNoErrorDsm() { $container = $this->getSession()->getPage(); $error_dsm_nodes = $container->findAll('css', 'div.messages.error'); if (count($error_dsm_nodes)) { $message = 'An error occurred during processing.'; throw new ExpectationException($message, $this->getSession()); }}
BioRAFT DrupalCon Seattle 2019 @YesCT | Karl bit.ly/BehatAuto
Example 1: Themes
19
$ vendor/bin/behat features/save_themes.featureFeature: Reset Themes As an administrative user, I need to save themes.
Scenario: Save Themes and Clear Cache: # features/save_themes.feature:4 Given I am logged in as " admin" # FeatureContext::assertLoggedInByName() And I go to " /admin/build/themes/settings/bioraft3x" # FeatureContext::visit() And I press " Save configuration" # FeatureContext::pressButton() Then I should see " The configuration options have been saved." # FeatureContext::assertPageContainsText() And I should not see any errors # FeatureContext::assertNoErrorDsm() And I go to " /admin/build/themes/settings/bioraftmobile3x" # FeatureContext::visit() And I press " Save configuration" # FeatureContext::pressButton() Then I should see " The configuration options have been saved." # FeatureContext::assertPageContainsText() And I should not see any errors # FeatureContext::assertNoErrorDsm()
1 scenario (1 passed)9 steps (9 passed)0m11.61s (11.91Mb)
BioRAFT DrupalCon Seattle 2019 @YesCT | Karl bit.ly/BehatAuto
Example 2: Module Updates
22
Feature: Update Modules As an administrative user, I need to run update.php.
Scenario: Update PHP: Given I am logged in as "admin" And I go to "/update.php" And I click on the element with xpath " //ol/li/a[1]" Then I should see "Drupal database update"
And I press "Update" Then I should see "Starting updates"
Then I wait "5" ⇐ This can change for large updates!!!
And I go to "/update.php?op=finished" Then I should see "Updates were attempted."
Karl | @YesCT
BioRAFT DrupalCon Seattle 2019 @YesCT | Karl bit.ly/BehatAuto
Example 2: Module Updates
23
/** @When I click on the element with xpath :xpath */public function iClickOnTheElementWithXpath($xpath) { $session = $this->getSession(); $handler = $session->getSelectorsHandler(); $locator = $handler->selectorToXpath('xpath', $xpath); $element = $session->getPage()->find('xpath', $locator);
if (NULL === $element) { throw new \InvalidArgumentException("Could not evaluate XPath: $xpath"); }
$element->click();}
Karl | @YesCT
BioRAFT DrupalCon Seattle 2019 @YesCT | Karl bit.ly/BehatAuto
Example 2: Module Updates
24
$ vendor/bin/behat features/update_php.feature Feature: Update Modules As an administrative user, I need to run update.php.
Scenario: Update PHP: # features/update_php.feature:4 Given I am logged in as "admin" # FeatureContext::assertLoggedInByName() And I go to "/update.php" # FeatureContext::visit() And I click on the element with xpath "//ol/li/a[1]" # FeatureContext::iClickOnTheElementWithXpath() Then I should see "Drupal database update" # FeatureContext::assertPageContainsText() And I press "Update" # FeatureContext::pressButton() Then I should see "Starting updates" # FeatureContext::assertPageContainsText() Then I wait "5" # FeatureContext::wait() And I go to "/update.php?op=finished" # FeatureContext::visit() Then I should see "Updates were attempted." # FeatureContext::assertPageContainsText()
1 scenario (1 passed)9 steps (9 passed)0m15.66s (11.48Mb)
Karl | @YesCT
BioRAFT DrupalCon Seattle 2019 @YesCT | Karl bit.ly/BehatAuto
Example 3: Complex Custom Functions
25
function truncate_cache_tables($redirect = FALSE){ db_query("truncate table {cache}"); db_query("truncate table {cache_content}"); db_query("truncate table {cache_filter}"); db_query("truncate table {cache_menu}"); db_query("truncate table {cache_page};"); db_query("truncate table {cache_views}");
$form['text'] = array('#type'=>'item', '#value'=>'Truncated cache tables for this site.');
_taxonomy_access_update_db(); # ⇐ 140 lines of code
if ($redirect) { drupal_set_message("Caches have been cleared."); drupal_goto('frontpage_panel'); }
return $form;}
BioRAFT DrupalCon Seattle 2019 @YesCT | Karl bit.ly/BehatAuto
Example 3: Complex Custom Functions
26
Feature: Rebuild Taxonomy and Clear JS Cache As an administrative user, I need to update taxonomy access.
Scenario: Rebuild Taxonomy and Clear JS Cache: Given I am logged in as "admin" And I go to "/admin/raft/truncate_tables" Then I should see "Caches have been cleared."
And I go to "/clearjscache" Then I should see "Javascript cache cleared."
BioRAFT DrupalCon Seattle 2019 @YesCT | Karl bit.ly/BehatAuto
Example 3: Complex Custom Functions
27
$ vendor/bin/behat features/clear_cache.feature Feature: Rebuild Taxonomy and Clear JS Cache As an administrative user, I need to update taxonomy access.
Scenario: Rebuild Taxonomy and Clear JS Cache: # features/clear_cache.feature:4 Given I am logged in as " admin" # FeatureContext::assertLoggedInByName()
And I go to " /admin/raft/truncate_tables" # FeatureContext::visit() Then I should see " Caches have been cleared." # FeatureContext::assertPageContainsText() And I go to " /clearjscache" # FeatureContext::visit()
Then I should see " Javascript cache cleared." # FeatureContext::assertPageContainsText()
1 scenario (1 passed)5 steps (5 passed)0m10.25s (11.51Mb)
BioRAFT DrupalCon Seattle 2019 @YesCT | Karl bit.ly/BehatAuto
Example 4: Password Manipulation
28
Feature: Change Password As an administrative user, I need to change password.
Scenario: Change User 1 Password: Given I am logged in as "admin" And I go to "/user/1/edit" And I fill in "edit-pass-pass1" with "Seattle" And I fill in "edit-pass-pass2" with "Seattle" And I press "Submit" Then I should see "The changes have been saved." Then I should not see any errors
Karl | @YesCT
BioRAFT DrupalCon Seattle 2019 @YesCT | Karl bit.ly/BehatAuto
Example 4: Password Manipulation
29
$ vendor/bin/behat features/change_password.feature Feature: Change Password As an administrative user, I need to change password.
Scenario: Change User 1 Password: # features/change_password.feature:4 Given I am logged in as "admin" # FeatureContext::assertLoggedInByName() And I go to "/user/1/edit" # FeatureContext::visit() And I fill in "edit-pass-pass1" with "Seattle" # FeatureContext::fillField() And I fill in "edit-pass-pass2" with "Seattle" # FeatureContext::fillField() And I press "Submit" # FeatureContext::pressButton() Then I should see "The changes have been saved." # FeatureContext::assertPageContainsText() Then I should not see any errors # FeatureContext::assertNoErrorDsm()
1 scenario (1 passed)7 steps (7 passed)0m7.66s (11.88Mb)
Karl | @YesCT
BioRAFT DrupalCon Seattle 2019 @YesCT | Karl bit.ly/BehatAuto
Failure
30
$ vendor/bin/behat features/clear_cache.feature Feature: Rebuild Taxonomy and Clear JS Cache As an administrative user, I need to update taxonomy access.
Scenario: Rebuild Taxonomy and Clear JS Cache: # features/clear_cache.feature:4 Given I am logged in as "admin" # FeatureContext::assertLoggedInByName() And I go to "/admin/raft/truncate_tables" # FeatureContext::visit() Then I should see "Caches have been cleared." # FeatureContext::assertPageContainsText() And I go to "/clearjscache" # FeatureContext::visit() Then I should see "Caches have been cleared." # FeatureContext::assertPageContainsText() The text "Caches have been cleared." was not found anywhere in the text of the current page. (Behat\Mink\Exception\ResponseTextException)--- Failed scenarios: features/clear_cache.feature:41 scenario (1 failed)5 steps (4 passed, 1 failed)0m10.25s (11.51Mb)$ echo $?1
BioRAFT DrupalCon Seattle 2019 @YesCT | Karl bit.ly/BehatAuto
Possible Enhancements● Use JavaScript for update.php or handle timeout better● Run as a Docker container
○ Further reduces footprint on RunDeck server● Embed in Legacy Docker container
○ Also reduces footprint on RunDeck○ Requires second PHP interpreter on all legacy containers
31Karl | @YesCT
BioRAFT DrupalCon Seattle 2019 @YesCT | Karl bit.ly/BehatAuto
Questions
Karl DeBisschop https://www.drupal.org/u/kdebisschop (d.o contact)
Cathy Theys https://www.drupal.org/u/yesct (@YesCT, slack, twitter)
32
BioRAFT DrupalCon Seattle 2019 @YesCT | Karl bit.ly/BehatAuto33
Join us for contribution opportunitiesFriday, April 12, 2019
9:00-18:00Room: 602
Mentored Contribution
First TimeContributor Workshop
GeneralContribution
#DrupalContributions
9:00-12:00Room: 606
9:00-18:00Room: 6A
BioRAFT DrupalCon Seattle 2019 @YesCT | Karl bit.ly/BehatAuto34
What did you think?Locate this session at the DrupalCon Seattle website:
https://events.drupal.org/node/22562Take the Survey!
https://www.surveymonkey.com/r/DrupalConSeattle