Writing Tests
A Test Case
Put the tests in tests/TestCase/EndToEnd/ (app/Test/Case/EndToEnd/ in the legacy layout) and extend PantherTestCase (docs/examples/ArticlesTest.php):
<?php
declare(strict_types=1);
namespace App\Test\TestCase\EndToEnd;
use ClassRegistry;
use PieceOfCake2\Panther\TestSuite\PantherTestCase;
class ArticlesTest extends PantherTestCase
{
/**
* @var array<string>
*/
public $fixtures = ['app.article'];
/**
* @return void
*/
public function testIndexShowsTheFixtureRecords(): void
{
$client = static::createPantherClient();
$client->request('GET', '/articles');
$this->assertPageTitleContains('Articles');
$this->assertSelectorTextContains('table', 'First article');
}
/**
* @return void
*/
public function testAdd(): void
{
$client = static::createPantherClient();
$crawler = $client->request('GET', '/articles/add');
// FormHelper puts a hidden input before each checkbox: click the checkbox itself
$crawler->filter('#ArticlePublished')->click();
$client->submitForm('Submit', [
'data[Article][title]' => 'Written in the browser',
'data[Article][body]' => 'Hello',
]);
$this->assertSelectorTextContains('.message', 'The article has been saved.');
$this->assertSame(1, ClassRegistry::init('Article')->find('count', [
'conditions' => ['Article.title' => 'Written in the browser', 'Article.published' => true],
]));
}
}static::createPantherClient() returns the browser. The first call of the run starts the web server and Chrome; the following calls return the same browser.
Opening pages
$client = static::createPantherClient();
$crawler = $client->request('GET', '/articles?page=2'); // a path of the application
$crawler = $client->clickLink('Next');
$client->back();
$client->getCurrentURL(); // http://127.0.0.1:9080/articles?page=2
$crawler->filter('h1')->text(); // the visible text
$client->executeScript('return document.title;');
$client->takeScreenshot('tmp/articles.png');The client is a BrowserKit client and the crawler a DomCrawler crawler whose elements are WebDriver elements: see Panther's documentation.
Forms
submitForm() fills the fields of the form that contains the button, by their name, and submits it:
$client->submitForm('Log in', [
'data[User][email]' => 'user@example.com',
'data[User][password]' => 'secret',
]);Give the full CakePHP names, such as data[User][email]; nested arrays are not supported. For a checkbox of FormHelper, which puts a hidden input with the same name before it, click the checkbox before the submit: $crawler->filter('#UserRememberMe')->click(). SecurityComponent tokens work, because the browser submits the form.
Waiting for JavaScript
$crawler->filter('#load-more')->click();
$client->waitFor('.comment'); // in the DOM
$client->waitForVisibility('#dialog');
$client->waitForElementToContain('#total', '3 items');
// or as assertions
$this->assertSelectorWillContain('#total', '3 items');
$this->assertSelectorWillNotBeVisible('.spinner');Cookies and logging in
The browser is kept from one test to the next, with its cookies, as in Panther. A test that must start logged out clears them first:
$client = static::createPantherClient();
$client->request('GET', '/');
$client->getCookieJar()->clear();Log in through the login form in a helper of your base class, or keep a test logged in on purpose for the tests that follow.
Several browsers
static::createAdditionalPantherClient() opens another, isolated browser, for example a second user of a chat. The assertions check the browser created last.
Mobile viewport
Panther has no device presets, and headless Chrome does not make its window narrower than 500 pixels with --window-size. Emulate a phone with the DevTools protocol of php-webdriver, and reset it at the end because the browser is kept for the next test:
use Facebook\WebDriver\Chrome\ChromeDevToolsDriver;
$client = static::createPantherClient();
$devTools = new ChromeDevToolsDriver($client->getWebDriver());
$devTools->execute('Emulation.setDeviceMetricsOverride', ['width' => 390, 'height' => 844, 'deviceScaleFactor' => 3, 'mobile' => true]);
try {
$client->request('GET', '/');
$this->assertSelectorIsVisible('.menu-toggle');
} finally {
$devTools->execute('Emulation.clearDeviceMetricsOverride');
}Emulation.setUserAgentOverride and Emulation.setTouchEmulationEnabled change the User-Agent and the touch events the same way.
Assertions
PantherTestCase has the assertions of Symfony's and Panther's WebTestAssertionsTrait. They check the page of the browser created last. A locator is a CSS selector, or an XPath expression that starts with /.
| Assertion | Checks |
|---|---|
assertSelectorExists($selector), assertSelectorNotExists($selector) | An element matches |
assertSelectorCount($count, $selector) | The number of matching elements |
assertSelectorTextContains($selector, $text), assertSelectorTextNotContains(), assertSelectorTextSame() | The visible text of the first matching element |
assertPageTitleSame($title), assertPageTitleContains($title) | <title> |
assertInputValueSame($name, $value), assertInputValueNotSame() | The current value of a field, by its name, e.g. data[Article][title] |
assertCheckboxChecked($name), assertCheckboxNotChecked($name) | A checkbox, by its name |
assertBrowserHasCookie($name), assertBrowserNotHasCookie($name), assertBrowserCookieValueSame($name, $value) | A cookie of the browser |
assertSelectorIsVisible($locator), assertSelectorIsNotVisible() | Visibility of the first matching element |
assertSelectorIsEnabled($locator), assertSelectorIsDisabled() | Whether it is enabled |
assertSelectorAttributeContains($locator, $attribute, $text), assertSelectorAttributeNotContains() | An attribute; $text = null checks that the attribute is not set |
assertSelectorWillExist(), assertSelectorWillNotExist(), assertSelectorWillBeVisible(), assertSelectorWillNotBeVisible(), assertSelectorWillContain(), assertSelectorWillNotContain(), assertSelectorWillBeEnabled(), assertSelectorWillBeDisabled(), assertSelectorAttributeWillContain(), assertSelectorAttributeWillNotContain() | The same, after waiting for it (Panther's waitFor*()) |
The response assertions of Symfony (assertResponseIsSuccessful() ...) do not exist: WebDriver does not expose the status code. Use IntegrationTestTrait for them.
Error screenshots and the interactive mode
With PANTHER_ERROR_SCREENSHOT_DIR and the extension, a test that fails or errors after it created a browser saves a screenshot of each browser, named after the date, the kind of failure and the test:

With PANTHER_NO_HEADLESS=1 and PHPUnit's --debug, the run pauses after a failure until you press Enter, so you can look at the browser:
PANTHER_NO_HEADLESS=1 vendor/bin/phpunit --testsuite EndToEnd --debugAPI reference
PantherTestCase
PieceOfCake2\Panther\TestSuite\PantherTestCase extends CakeTestCase and uses Panther's PantherTestCaseTrait and this package's WebTestAssertionsTrait.
| Member | Description |
|---|---|
static createPantherClient(array $options = [], array $kernelOptions = [], array $managerOptions = []) | The browser; starts the web server and the browser at the first call (Panther) |
static createAdditionalPantherClient() | Another, isolated browser (Panther) |
static createHttpBrowserClient(array $options = []) | A client without JavaScript, through HTTP (Panther) |
static startWebServer(array $options = []) | Starts the web server with the CakePHP defaults, then as Panther does |
static stopWebServer(), static isWebServerStarted() | Panther |
static defaultWebServerDir(?string $projectRoot = null) | webroot/ or app/webroot/ of the project (protected) |
tearDown() | CakeTestCase::tearDown(), then the screenshot of a failed test |
ROUTER | Path of the CakePHP router script |
CHROME, FIREFOX, SELENIUM | Values of the browser option |
WebTestAssertionsTrait
PieceOfCake2\Panther\TestSuite\WebTestAssertionsTrait: the assertions. Use it in a class that uses PantherTestCaseTrait.
ServerExtension
PieceOfCake2\Panther\ServerExtension does what Panther's ServerExtension does: it keeps the web server and the browser until the last test, takes the screenshots of failed tests and pauses in the interactive mode, and it stops the web server and the browser of PantherTestCase after the last test. Register it instead of Panther's extension.
