In order to run functional tests that require JavaScript be executed for the feature to work, the tests need to be run in a browser that supports JavaScript. This is accomplished by using the WebDriver API in combination with an application like ChromeDriver or Selenium, which can remotely control a browser.
The exact setup for running functional tests is dependent on your development environment. We'll walk through a couple of common examples including using Docker (via DDEV) and stand-alone applications.
In this tutorial we'll:
- Learn how to install and run ChromeDriver and other necessary tools either in a Docker environment, or locally.
- Configure the relevant PHPUnit environment variables so they contain values appropriate for our specific environment.
- Execute Drupal's functional JavaScript tests via the
phpunit
command.
By the end of this tutorial you should be able to install the applications required to run functional JavaScript tests in a browser, and know how to configure PHPUnit to make use of them.
Goal
Run Drupal's functional JavaScript tests with WebDriver.
Prerequisites
- A Drupal development site
- Command-line/terminal access to the development site
- The site needs to have the development dependencies installed. See Install Drupal Development Requirements with Composer.
- Introduction to Testing in Drupal
- Software Testing Overview
- Frameworks for Testing in Drupal
- MacOS: Homebrew
- Linux/Windows: npm
Note: The best place to run tests is on a development site. Generally speaking, it's a bad idea to run tests on a production site.
Set up dev environment to run functional JavaScript tests
Drupal provides a way to run JavaScript from within the test environment. Tests which use this framework are called functional JavaScript tests. You can learn more about this framework in Frameworks for Testing in Drupal.
To run a functional JavaScript test using WebDriver you need to:
- Have a web server and database that can run Drupal. Generally whatever you use for development should work fine. We've found using PHP's built-in server and sqlLite easy to set up.
- Install ChromeDriver, Selenium, or another WebDriver API-compatible application for driving a browser.
- Configure PHP unit to use the correct web host, and connect it with your WebDriver tool of choice.
WebDriver is a W3C API that allows processes, like ChromeDriver, or Selenium, to control browsers like Firefox and Chrome. We've found the easiest way is to use ChromeDriver. Though Selenium works as well and will allow you to drive browsers other than Chrome.
We're going to walk through 2 different setups below:
- Using the Docker environment, DDEV
- Running everything on your local machine
Run functional JavaScript tests in DDEV
While you can set up any environment to run tests (and we outline how below) we're fans of DDEV for local development. As a Docker-based development environment, we can add a container with the required tools already installed and use that.
We'll be using the ddev/ddev-selenium-standalone-chrome DDEV service for running standalone Chrome. It can be used with any project type (not just a Drupal project).
Install the DDEV service
ddev get ddev/ddev-selenium-standalone-chrome
For more information, consult the ddev/ddev-selenium-standalone-chrome project README.
Restart DDEV
ddev restart
Ensure you have drupal/core-dev
installed
See Install Drupal Development Requirements with Composer for instructions.
Run a Drupal core FunctionalJavaScript test
To see this in action, run the following command from the root of your project
ddev exec -d /var/www/html/web "../vendor/bin/phpunit -v -c ./core/phpunit.xml.dist ./core/modules/system/tests/src/FunctionalJavascript/FrameworkTest.php"
Watch tests run in real time
- Run
ddev describe
and note theselenium-chrome
URL. It should start withhttps://
, then your DDEV site name, and end with:7900
, which is the port to access noVNC, for examplehttps://drupal.ddev.site:7900
. (The noVNC service allows you to view what's happening in this container.) - Copy the
selenium-chrome
URL and open it into your browser. (Don't forget the port.) - Enter the password
secret
. - Edit the file .ddev/config.selenium-standalone-chrome.yaml and on the line that sets the arguments for
MINK_DRIVER_ARGS_WEBDRIVER
, delete\"--headless\",
. This will launch the browser when the tests are run instead of keeping it in the background. - Run
ddev restart
. - Arrange your workspace, so you can see both your browser and Terminal. Run a FunctionalJavascript test again (see previous step) and watch it run in the browser.
Run functional tests in other Docker-based environments
A similar approach should work with any other Docker environment, the important steps are:
- Use a Docker image like
seleniarm/standalone-chromium
ordrupalci/chromedriver:production
with a WebDriver compatible browser. - Ensure the Docker container is accessible from the container that hosts your Drupal application.
-
Configure PHPUnit with the appropriate
SIMPLETEST_DB
,SIMPLETEST_BASE_URL
, andMINK_DRIVER_ARGS_WEBDRIVER
settings for your specific environment.
Install ChromeDriver on your host machine
You can also install and execute ChromeDriver on your host machine instead of in a container. You might find it easier to configure it more quickly via the phpunit.xml than via a container's YAML file, which often do override those environment variables.
Install ChromeDriver
Download and install the version of ChromeDriver that matches the version of the Chrome browser you have installed: https://chromedriver.chromium.org/downloads
Or, install it via the command line.
MacOS
brew install selenium-server-standalone
brew install chromedriver
Note: for macOS Catalina and later you may see a message about chromedriver being from an untrusted developer when you first try and launch it. Learn how to bypass this warning here.
Linux or Windows
npm install chromedriver -g
(optional) Install Selenium
If you want to be able to drive other browsers for testing, like Firefox. You can use Selenium with the WebDriver API.
MacOS
brew install selenium-server-standalone
Linux or Windows
npm install selenium-standalone@latest -g
selenium-standalone install
Start ChromeDriver / Selenium
In a new terminal window (you need to keep it running in the background) start ChromeDriver:
chromedriver
Note: the default ChromeDriver port is 9515.
Or Selenium:
selenium-server -port 4444
Start your webserver
Next, you'll need to start your webserver. If you're using something like MAMP, go ahead and start it if it's not already running.
Or, use PHP's built-in webserver and sqlLite.
In a new terminal window run the following command from the root of your Drupal codebase:
php -S 127.0.0.1:8000 .ht.router.php
# Related base url:
# http://127.0.0.1:8000
# Related database connection string:
# sqlite://localhost/sites/default/files/.ht.sqlite
The important thing here is that you have the server running, and you know the URL that someone would use to access the site, and the database connection string.
Set up testing environment variables
If you haven't already, copy core/phpunit.xml.dist to core/phpunit.xml.
cp core/phpunit.xml.dist core/phpunit.xml
Edit core/phpunit.xml and adjust the following lines, replacing http://localhost:8000
in the SIMPLETEST_BASE_URL
value with your local environment's URL. And the SIMPLETEST_DB
value with your database credentials. Your local setup might be different than the example below. See also Create a PHPUnit Config File for Your Project.
<env name="SIMPLETEST_BASE_URL" value="http://localhost:8000"></env>
<env name="SIMPLETEST_DB" value="sqlite://localhost/sites/default/files/.ht.sqlite"></env>
For ChromeDriver (add/remove --headless
depending on if you want the Chrome browser to open so you can see it):
<env name="MINK_DRIVER_ARGS_WEBDRIVER" value='["chrome", {"browserName":"chrome","chromeOptions":{"args":["--disable-gpu", "--no-sandbox", "--headless"]}}, "http://127.0.0.1:9515"]'/>
For Selenium:
<env name="MINK_DRIVER_ARGS_WEBDRIVER" value='["chrome", null, "http://localhost:4444/wd/hub"]'/>
Execute a test with PHPUnit
Run the following command to execute the JSWebAssertTest
functional JavaScript tests:
../vendor/bin/phpunit -v -c ./core core/tests/Drupal/FunctionalJavascriptTests/Tests/JSWebAssertTest.php
Expected result:
PHPUnit 9.5.28 by Sebastian Bergmann and contributors.
Runtime: PHP 8.1.13
Configuration: ./core/phpunit.xml.dist
Testing Drupal\FunctionalJavascriptTests\Tests\JSWebAssertTest
. 1 / 1 (100%)
Time: 00:10.633, Memory: 4.00 MB
OK (1 test, 24 assertions)
HTML output was generated
https://functional-js-testing.ddev.site/sites/simpletest/browser_output/Drupal_FunctionalJavascriptTests_Tests_JSWebAssertTest-1-81901143.html
https://functional-js-testing.ddev.site/sites/simpletest/browser_output/Drupal_FunctionalJavascriptTests_Tests_JSWebAssertTest-2-81901143.html
You should also see Chrome browser open, perform the tests, and close again.
Stop ChromeDriver / Selenium server
When you're done running tests you can stop the chromedriver
or selenium-server
process.
Recap
In this tutorial, we learned how to set up our local development environment to run functional JavaScript tests using WebDriver API and Chromedriver or Selenium. We walked through 2 different setups: one using DDEV and a DDEV service which installs a standalone Chromedriver container that we can access and use with a local DDEV site. And, a second setup where we installed Chromedriver or Selenium locally on our machine.
Further your understanding
- What types of behavior on your site might be worth writing functional JavaScript tests for?
- Learn about other Drupal testing frameworks: Frameworks for Testing in Drupal (Drupalize.Me).
- Read core/tests/README.md for additional/alternative methods and hints, including running legacy tests with PhantomJS.
Additional resources
- ChromeDriver (chromedriver.chromium.org)
- Selenium (selenium.dev)
- MDN web docs: WebDriver (developer.mozilla.org)
- Running Drupal's FunctionalJavascript tests on DDEV (glamanate.com)
- Debug any of Drupal's PHPUnit tests in PhpStorm with a DDEV Environment (Drupalize.Me)