PHPUnit Javascript testing tutorial

Last updated on
24 March 2017

This documentation is incomplete. Add more information.

As part of 8.1.x we introduced a way to test Javascript code in a end-to-end testing way, this means, the entire Drupal site, (exactly like in WebTestBase / BrowserTestBase) is set up.

Execute tests

In order to run it, you need to install Phantomjs, and then execute the following command to start phantomjs:

phantomjs --ssl-protocol=any --ignore-ssl-errors=true ./vendor/jcalderonzumba/gastonjs/src/Client/main.js 8510 1024 768 2>&1 >> /dev/null &

The following options can be added right after -ignore-ssl-errors=true.

Option Description
--cookies-file=/tmp/cookies.txt Enables settings cookies in PhantomJS
--debug=true Makes the phanromJS more verbose. Only usefull if you don't add 2>&1 >> /dev/null & at the end.

The next step is to setup some environment variables. Therefore copy the core phpunit.xml.dist file to phpunit.xml and set the following two lines:

<env name="SIMPLETEST_DB" value="sqlite://localhost/sites/default/files/.ht.sqlite"></env>
<env name="SIMPLETEST_BASE_URL" value="http://drupal8.dev"></env>

Now you can execute tests using phpunit (_www is the user of the webserver, needed for permission issues)

sudo -u _www ./vendor/bin/phpunit -c core core/modules/toolbar/tests/src/FunctionalJavascript/ToolbarIntegrationTest.php

An alternative way to run the test is using run-tests.sh, this is more like the Drupal test-bot runs the tests but is much less verbose. If your tests passes using phpunit but fails on drupal.org, try running your test using the following method:

sudo -u _www php ./core/scripts/run-tests.sh --verbose --sqlite [Valid path, eg: /tmp/test.sqlite] --url [Valid URL, eg: http://drupal.dev] --class "[Class including namespace to test]"

Write tests

The main difference from other tests is that the Javascript function tests are executed in a real browser, javascript behaviours are executed meaning the tests will have the same experience AND requirements that you have in a normal browser.

Setup

In order to write your own tests, you add a FunctionalJavascript folder inside yourmodule/tests/src and create a FooTest.php file.
In there ensure to extend \Drupal\FunctionalJavascriptTests\JavascriptTestBase. The namespace for your test will be \Drupal\Tests\[MODULENAME]\FunctionalJavascript.

Navigation

In there you can do ordinary HTTP requests using drupalGet, but also click on links / press buttons. When pressing a submit button (that is not ajaxified) the page will submit and load the resulting page. After the page is loaded the test will continue.

NOTE: The test continues on the load event. If AJAX behaviors are triggered on load (for example in behaviors) you should add a $this->assertSession()->assertWaitOnAjaxRequest() after the load.

Finding and using elements

After loading a page you can use various methods of finding elements on the page. The most common methods are for this are (there are other methods but these are the most used):

$page = $this->getSession()->getPage();

// Find the submit button labeled 'Save'
$button = $page->findButton('Save');

// Find the field with the name 'test'
$field = $page->findField('field_test[0][value]');

// Find links
$link = $page->findLink('Link text');

// Find using css
$element = $page->find('css', 'css selector');

Before using an element you should always check if it was found:

$this->assertNotEmpty($field);

After this you can start changing / using the element. For example: You can now check the visibility of an element, see the following code examples:

$page = $this->getSession()->getPage();
$content = $page->findLink('Content');
$this->assertTrue($content->isVisible());

Ajax form interactions are also supported. The following will run the Ajax behaviours if there are any attached:

$this->getSession()->getPage()->find('css', '#somebutton')->click();

Waiting

When clicking a link/button with Ajax behaviour attached, you need to keep in mind that the underlying browser might need a while to deliver changes to the HTML. The preferred method for this is to wait untill an expected piece of UI is available. There are various methods available for this:

  • ::waitForElement()
  • ::waitForElementVisible()
  • ::waitForButton()
  • ::waitForLink()
  • ::waitForField()
  • ::waitForId()

The methods work similar to the find...() methods in that they return an element when found.

There are edge cases where you have to directly wait for the Ajax request to finish. Use $this->assertSession()->assertWaitOnAjaxRequest() to wait for that.

As a last resort you can use a custom javascript snippet code that needs to pass:

$this->assertJsCondition('Javascript condition that should equal TRUE');

You should always avoid waiting for a specific number of time. This would cause random failures in tests as sometimes the test will run longer than you'd expect.

Debug tests

Screenshots

If you want to see a screenshot to work out what is going on you can do this! (introduced in 8.1.9)

$this->createScreenshot('PATH/TO/screenshot.jpg');

Don't put the screenshot in a path inside the test environment like public://test.jpg as this will be cleaned up at the end of the test.

Read more about the context of these changes at #2807237: PHPUnit initiative.

Screenshots on Drupal CI

If you want to see a screenshot to work out what is going when the Drupal CI testbot runs your tests, you can also do this! To achieve this, you have to write the screenshot to a directory which will be in the build artifacts. The folder sites/default/files/simpletest is such a folder.

Example code:

$this->createScreenshot(\Drupal::root() . '/sites/default/files/simpletest/screen.png');

You can then inspect your screenshot by clicking your way to this file:

  • Click the test result on the issue
  • Click View results on dispatcher
  • Expand the following path under Build Artifacts: simpletest.js -> phpunit-xml

HTML

Sometimes you'll want to inspect the HTML of the page or a specific element. There is no official method for this yet but the following works:

$this->assertEquals('', $this->getSession()->getPage()->getHTML());

Your test will now fail with the HTML in the assert error message.