FunctionalJavascript tests
FunctionalJavascript tests drive a real browser through WebDriver. By default they use the Google Chrome or Chromium on your machine, so you don't need Docker. Follow this page to run them with either backend. It also covers how each project gets its WebDriver port and how projects share a machine.
To run the tests with your local Chrome, run the following commands:
- Make
- Ahoy
- No wrapper
make start
make provision
make test-functional-javascript
make browser-stop
ahoy start
ahoy provision
ahoy test-functional-javascript
ahoy browser-stop
vendor/bin/eddy-start
vendor/bin/eddy-provision
vendor/bin/eddy-browser-start
export WEBDRIVER_PORT="$(vendor/bin/eddy-info webdriver-port)"
export WEBDRIVER_BACKEND="$(vendor/bin/eddy-info webdriver-backend)"
export WEBDRIVER_HOST="$(vendor/bin/eddy-info webdriver-host)"
export WEBSERVER_PORT="$(vendor/bin/eddy-info webserver-port)"
export SIMPLETEST_BASE_URL="http://localhost:${WEBSERVER_PORT}"
cd build
php -d pcov.directory=.. vendor/bin/phpunit --testsuite functional-javascript
cd ..
vendor/bin/eddy-browser-stop
With Make or Ahoy, test and test-functional-javascript start the browser before the tests and leave it running, so the next run reuses it. When the browser can't start, those commands stop before any test runs. The last command in each list stops the browser.
The tests need a chromedriver that matches the major version of your Chrome. browser-start, the step that starts the browser, uses the one on your PATH when it matches. Otherwise it fetches a matching one into .chromedriver with npx @puppeteer/browsers, which needs Node.js. Tests that extend the generated base class run Chrome headless, so they work without a display, such as on a CI runner.
Run the browser in Docker
To run the browser in a Docker Selenium container instead, set WEBDRIVER_BACKEND=selenium. The container can't reach the host's localhost, so start the webserver on all interfaces:
- Make
- Ahoy
- No wrapper
WEBSERVER_HOST=0.0.0.0 make start
make provision
WEBDRIVER_BACKEND=selenium make test-functional-javascript
make browser-stop
WEBSERVER_HOST=0.0.0.0 ahoy start
ahoy provision
WEBDRIVER_BACKEND=selenium ahoy test-functional-javascript
ahoy browser-stop
WEBSERVER_HOST=0.0.0.0 vendor/bin/eddy-start
vendor/bin/eddy-provision
WEBDRIVER_BACKEND=selenium vendor/bin/eddy-browser-start
export WEBDRIVER_PORT="$(vendor/bin/eddy-info webdriver-port)"
export WEBDRIVER_HOST="$(vendor/bin/eddy-info webdriver-host)"
export WEBSERVER_PORT="$(vendor/bin/eddy-info webserver-port)"
export SIMPLETEST_BASE_URL="http://localhost:${WEBSERVER_PORT}"
cd build
WEBDRIVER_BACKEND=selenium php -d pcov.directory=.. vendor/bin/phpunit \
--testsuite functional-javascript
cd ..
vendor/bin/eddy-browser-stop
The browser reaches the webserver at localhost with the default backend, and at host.docker.internal (macOS) or 172.17.0.1 (other systems) from the Selenium container. To use a different address, set WEBDRIVER_HOST.
While WEBDRIVER_HOST is unset, vendor/bin/eddy-info webdriver-host prints an empty line, so the no-wrapper commands export an empty value. The base class of your tests then picks the address for the backend that the test command names. Before each test, the base class builds SIMPLETEST_BASE_URL from that address and WEBSERVER_PORT. The SIMPLETEST_BASE_URL that the no-wrapper commands export reaches only tests that don't extend the base class.
You can switch WEBDRIVER_BACKEND between runs without a browser-stop first, because browser-start replaces the project's own browser of the other backend (see Projects that share a machine).
WebDriver ports
Each project records its own WebDriver port, whichever backend it uses. browser-start claims the first free port from 4444 and saves it in .env, so several projects can run these tests at the same time. 2 projects can still record the same port, such as when one claims it while the other's browser is stopped. See Projects that share a machine for what happens then.
To pin a specific port, set WEBDRIVER_PORT in .env, such as WEBDRIVER_PORT=4450. A value in your shell wins over the one in .env. While either one sets the port, browser-start skips the search for a free port.
Tests reach the claimed port through the base class that the installer generates, so extend YourExtensionFunctionalJavascriptTestBase in place of WebDriverTestBase. The installer names the class after your extension's machine name. For my_extension, it's MyExtensionFunctionalJavascriptTestBase in tests/src/FunctionalJavascript, in the Drupal\Tests\my_extension\FunctionalJavascript namespace. The base class also points the browser at the webserver address for the backend, and runs Chrome headless with the default backend. A test that extends WebDriverTestBase directly keeps the default endpoint from phpunit.xml, unless it exports its own MINK_DRIVER_ARGS_WEBDRIVER.
The base class reads the backend, host and port from the environment, and make and Ahoy load them from .env. Without a command wrapper, the commands at the top of this page export the values that eddy-info resolves from your shell and .env. So a value set in .env reaches the tests in every setup. The no-wrapper Selenium commands in the preceding section pass WEBDRIVER_BACKEND to the 2 commands that need it, which selects the backend for that run only.
Projects that share a machine
When something already holds the WebDriver port, browser-start acts as follows:
- A browser of the selected backend that already accepts WebDriver sessions on the port gets reused, whichever project started it, since every session is independent.
- Any other browser that belongs to the project gets replaced, whichever backend it uses.
- Any other holder, such as another project's browser of the other backend or another process, makes
browser-startfail and name the holder.
browser-stop and reset stop only the project's own browser, so another project's browser on the same port keeps running. A browser belongs to a project in these cases:
- A chromedriver belongs to the project whose root is its working directory, which is where
browser-startstarts it. A chromedriver whose working directory can't be read counts as another project's. - A Selenium container belongs to the project whose root its
eddy.projectlabel holds, whichbrowser-startsets when it starts the container. A container without the label counts as the project's own.
A stopped Selenium container holds no port, so it never blocks browser-start. The selenium backend replaces a stopped container, whichever project started it.