![require(esm) Backported to Node.js 20, Paving the Way for ESM-Only Packages](https://cdn.sanity.io/images/cgdhsj6q/production/be8ab80c8efa5907bc341c6fefe9aa20d239d890-1600x1097.png?w=400&fit=max&auto=format)
Security News
require(esm) Backported to Node.js 20, Paving the Way for ESM-Only Packages
require(esm) backported to Node.js 20, easing the transition to ESM-only packages and reducing complexity for developers as Node 18 nears end-of-life.
generator-web-starter-behat
Advanced tools
This is a quick-start setup for using Behat with Drupal projects built on web-starter. It is intended to introduce the framework and lower the barrier to entry for developers.
This quick-start is not intended to be a comprehensive guide for learning Behat. There are many resources for learning about Behat generally, including this talk: https://www.youtube.com/watch?v=NsI8kPc3kBA
This setup includes the following components:
This is a work in progress: Please note that there are many pieces that can change over time.
Once Web Starter has been provisioned and you have created your Drupal site within it, you should be able to immediately run one of the default tests included in this quick-start:
$ vagrant ssh
$ cd /vagrant/tests/behat
$ /usr/local/bin/phantomjs --webdriver=8643 & # Start PhantomJS in the background on port 8643.
$ bin/behat features/TESTS/test.feature # Run one of our test Behat features.
You should see output including the scenario and steps passed.
(For the other default test included in this quick-start, see the Selenium instructions below.)
In the event that you want to pull the latest Webstarter changes for Behat into an existing repo these files will be overridden
The purpose of these custom context class files is to make different custom steps for .feature files and custom methods available to the F1 team, hence why we expect to overwrite them on occasion.
In order to avoid overriding changed files in existing projects, templates for all yml and Selenium json files are available as '.example' files in the 'examples' folder.
If you want to reintegrate upstream changes to these files you can choose to copy the '.example' files over and overwrite or manually merge in the changes.
The behat.yml file comes with two default profiles, 'default' and 'selenium'.
The default profile uses PhantomJS to perform Behat tests via a headless browser. PhantomJS listens on port 8643:
wd_host: 'http://127.0.0.1:8643/wd/hub'
You must start PhantomJS before you are able to run any tests that require it:
/usr/local/bin/phantomjs --webdriver=8643 &
The Selenium profile uses the option java .jar file to run tests with the existing browsers on your local machine. Selenium listens on port 5555:
wd_host: 'http://127.0.0.1:5555/wd/hub'
See the Selenium 2 Information below on how to start Selenium Grid.
Using the following command to see the available steps that you can use to write tests:
bin/behat -dl
When writing tests, make sure to add your test .feature files to folders created under the tests/behat/features folder:
The purpose of adding a 'sub' folder is for the grunt-behat tasks, which are configured to look for all feature files under tests/behat/features/sub-folder/*
Example:
- tests
- behat
- features
- CRUD
- node_create.feature
- VIEWS
- view1.feature
- PANELS
- panel1.feature
- panel2.feature
When writing tests within feature files make sure to include @api
for making drush available in a feature:
@api
Scenario: I want to be able to show how to include Drush
The @javascript
tag allows you to test with javascript-enabled drivers. Two of the javascript enabled drivers bundled with this testing framework are PhantomJS and Selenium Grid. The @javascript tag is a requirement for behat to run either PhantomJS and Selenium2. Both included profiles have Goutte enabled by default, which is the headless browser driver. Without the @javascript
tag Goutte is selected to drive the tests.
@javascript
Scenario: I want to be able to show how to include Javascript
Web Starter includes the Behat-Grunt Plugin which can run multiple Behat tests and export the results to a JUNIT XML file.
Web Starter by default comes with six behat-grunt tasks:
behatLocal
which runs Behat jobs against PhantomJS. CalledbehatvmSelenium
which runs Behat jobs against Selenium. Called from Host machinebehatLocalSelenium
which runs Behat jobs against Selenium. Called from VM machinebehatJenkinsDev
which runs Behat jobs against PhantomJS from our build server for a Dev site (see Jenkins Integration below)behatJenkinsStage
which runs Behat jobs against PhantomJS from our build server for a Stage site (see Jenkins Integration below)/usr/local/bin/phantomjs --webdriver=8643 &
cd tests/behat && /usr/local/bin/composer update && cd ../../ && grunt behatJenkinsDev && killall phantomjs
Note that the command behatJenkinsDev
points to the dev environment and behatJenkinsStage
points to the stage environment.
brew cask install java
or https://java.com$ vagrant ssh
$ cd /vagrant/tests/behat/
$ java -jar selenium-server-standalone-{{ version }}.jar -role hub -nodeConfig nodeconfig.server.json
$ cd tests/behat
$ java -jar selenium-server-standalone-{{ version }}.jar -role node -nodeConfig nodeconfig.mac.json
$ vagrant ssh
$ cd /vagrant/tests/behat
$ bin/behat features/test-js.feature -p vm-selenium
$ cd tests/behat
$ ./chromedriver
Mink, the browser controller/emulator we use with Behat, has an Element API that allows for manipulating and interacting with page elements. It contains the powerful TraversalbelElement method which – not surprisingly – able to traverse the DOM by html elements. Two important classes of the TraversableElement method are the DocumentElement instance and the NodeElement class. The former represents the <html>
node of the page and the latter is used to represent any element inside the page.
The DocumentElement instance is accessible through the Session::getPage method:
$page = $session->getPage();
Elements have 2 main traversal methods: ElementInterface::findAll
returns an array of NodeElement instances matching the provided selector inside the current element while ElementInterface::find
returns the first match or null when there is none.
DOM Manipulation
The NodeElement class contains many methods for manipulating the DOM, a few examples include:
NodeElement::click
and NodeElement::press
methods let you click the links and press the buttons on the pageNodeElement::check
and NodeElement::uncheck
methods will check and uncheck a checkbox fieldNodeElement::focus
and NodeElement::blur
allows you to give and remove focus on an element$drag = $page->find(…);
$target = $page->find(…);
$dragged->dragTo($target);
Element Content and Text
The Element
class provides access to the content of the elements.
Element::getHtml
gets the inner HTML of the element, i.e. all the children of the element.Element::getOutterHtml
gets the outter HTML of the element, i.e. including the element itself.Element::getText
gets the text of the element. This method will strip the tags and unprinted characters out of the response, including newlines. So it’ll basically return the text that the user sees on the page.Regular Expression text matching
Behat will look for a matching step definition by matching the text of the step against the regular expressions defined by each step definition. In the following example:
Scenario: List 2 files in a directory with the -a option
Given I am in a directory "test"
And I have a file named "foo"
And I have a file named ".bar"
When I run "ls -a"
Then I should get:
"""
.
..
.bar
foo
"""
The first step's deffinition would look like:
/**
* @Given /^I am in a directory "([^"]*)"$/
*/
public function iAmInADirectory($dir)
{
if (!file_exists($dir)) {
mkdir($dir);
}
chdir($dir);
}
Where '@Given' is the keyword and the text after the keyword is the regular expression. All search patterns in the regular expression (e.g. ([^"]*)
) will become method arguments ($dir
).
Below is a list of all the predifind steps that use regular expressions:
/^I
select "([^"]*)" from "([^"]*)" chosen\.js select box$/
/^I
select "([^"]*)" from "([^"]*)" chosen\.js autoselect box$/
/^(?:|I )
press "(?P<button>(?:[^"]|\\")*)"$/
/^(?:|I )
follow "(?P<link>(?:[^"]|\\")*)"$/
/^(?:|I )
fill in "(?P<field>(?:[^"]|\\")*)"
with "(?P<value>(?:[^"]|\\")*)"$/
/^(?:|I )
fill in "(?P<field>(?:[^"]|\\")*)"
with:$/
/^(?:|I )
fill in "(?P<value>(?:[^"]|\\")*)"
for "(?P<field>(?:[^"]|\\")*)"$/
/^(?:|I )
fill in the following:$/
/^(?:|I )
select "(?P<option>(?:[^"]|\\")*)"
from "(?P<select>(?:[^"]|\\")*)"$/
/^(?:|I )
additionally select "(?P<option>(?:[^"]|\\")*)"
from "(?P<select>(?:[^"]|\\")*)"$/
/^(?:|I )
check "(?P<option>(?:[^"]|\\")*)"$/
/^(?:|I )
uncheck "(?P<option>(?:[^"]|\\")*)"$/
/^(?:|I )
attach the file "(?P<path>[^"]*)"
to "(?P<field>(?:[^"]|\\")*)"$/
/^(?:|I )
should be on "(?P<page>[^"]+)"$/
/^(?:|I )
should be on (?:|the )homepage$/
/^the (?i)url(?-i)
should match (?P<pattern>"(?:[^"]|\\")*")$/
/^the
response status code should be (?P<code>\d+)$/
/^the
response status code should not be (?P<code>\d+)$/
/^(?:|I )
should see "(?P<text>(?:[^"]|\\")*)"$/
/^(?:|I )
should not see "(?P<text>(?:[^"]|\\")*)"$/
/^(?:|I )
should see text matching (?P<pattern>"(?:[^"]|\\")*")$/
/^(?:|I )
should not see text matching (?P<pattern>"(?:[^"]|\\")*")$/
/^the
response should contain "(?P<text>(?:[^"]|\\")*)"$/
/^the
response should not contain "(?P<text>(?:[^"]|\\")*)"$/
/^(?:|I )
should see "(?P<text>(?:[^"]|\\")*)"
in the "(?P<element>[^"]*)" element$/
/^(?:|I )
should not see "(?P<text>(?:[^"]|\\")*)"
in the "(?P<element>[^"]*)" element$/
/^the "(?P<element>[^"]*)"
element should contain "(?P<value>(?:[^"]|\\")*)"$/
/^the "(?P<element>[^"]*)"
element should not contain "(?P<value>(?:[^"]|\\")*)"$/
/^(?:|I )
should see an? "(?P<element>[^"]*)" element$/
/^(?:|I )
should not see an? "(?P<element>[^"]*)" element$/
/^the "(?P<field>(?:[^"]|\\")*)"
field should contain "(?P<value>(?:[^"]|\\")*)"$/
/^the "(?P<field>(?:[^"]|\\")*)"
field should not contain "(?P<value>(?:[^"]|\\")*)"$/
/^(?:|I )
should see (?P<num>\d+) "(?P<element>[^"]*)" elements?$/
/^the "(?P<checkbox>(?:[^"]|\\")*)"
checkbox should be checked$/
/^the
checkbox "(?P<checkbox>(?:[^"]|\\")*)" (?:is|should be) checked$/
/^the "(?P<checkbox>(?:[^"]|\\")*)"
checkbox should not be checked$/
/^the
checkbox "(?P<checkbox>(?:[^"]|\\")*)"
should (?:be unchecked|not be checked)$/
/^the
checkbox "(?P<checkbox>(?:[^"]|\\")*)"
is (?:unchecked|not checked)$/
/^(?:|I )
should see (?P<num>\d+) "(?P<element>[^"]*)" elements?$/
/^the "(?P<checkbox>(?:[^"]|\\")*)"
checkbox should be checked$/
/^the
checkbox "(?P<checkbox>(?:[^"]|\\")*)" (?:is|should be) checked$/
/^the "(?P<checkbox>(?:[^"]|\\")*)"
checkbox should not be checked$/
A note about regular expressions and quoted strings:
(?P<option>(?:[^"]|\\")*)
does not support single quotesIn your folder you will notice this structure:
bootstrap
F1ContentUtilityContext.php
F1DrushUtilityContext.php
F1FundamentalContext.php
F1OGContext.php
FeatureContext.php
These files are provided by the Behat Generator for making your life a little easier. Behat not only allows for writing .feature tests but also calling custom steps which in turn call custom methods within these files which can execute multiple steps or custom logic for running tests. These files should be self-explanatory: F1ContentUtilityContext is for utility functions, F1DrushUtilityContext is for testing via drush, F1OGContext is for organic groups tests and the F1FundamentalContext context allows you to access all of the information about mocked content created through drupal, such as the nid, etc etc. The FeatureContext file is strictly for your project's custom methods which won't be overridden on a behat generator update.
Say that you are working on a function for your project that has been really helpful for doing a specific test. Depending on if the test is written in a non-specific way it might be a great candidate for other Drupal projects. Here is the beauty of the F1 Behat program. You can create a Pull Request off of the Behat Generator repo to make sure that the function you wrote gets incorporated back into the Behat Generator for others to use.
Example Function in F1ContentUtilityContext.php
function iVisitTheNode
If this function didn't exist in F1ContentUtilityContext.php, but you had created it in FeatureContext.php you would probably want to share it with other folks via the Behat Generator repo.
TODO: Provide link to repo, and an example PR
See: http://blog.jetbrains.com/phpstorm/2014/07/using-behat-in-phpstorm/
netstat -rn | awk '/^0\.0\.0\.0/ { print $2}'
export PHP_IDE_CONFIG="serverName=localhost" && export XDEBUG_CONFIG="idekey=PHPSTORM remote_host={{ host machine ip address }} remote_port=9000"
FAQs
A Behat sub-generator for web-starter
The npm package generator-web-starter-behat receives a total of 1 weekly downloads. As such, generator-web-starter-behat popularity was classified as not popular.
We found that generator-web-starter-behat demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
require(esm) backported to Node.js 20, easing the transition to ESM-only packages and reducing complexity for developers as Node 18 nears end-of-life.
Security News
PyPI now supports iOS and Android wheels, making it easier for Python developers to distribute mobile packages.
Security News
Create React App is officially deprecated due to React 19 issues and lack of maintenance—developers should switch to Vite or other modern alternatives.