+namespace Your\Namespace\Tests\Features;
+use Facebook\WebDriver\WebDriverBy;
+use Facebook\WebDriver\WebDriverExpectedCondition;
+use Facebook\WebDriver\Remote\DesiredCapabilities;
+use Facebook\WebDriver\Remote\RemoteWebDriver;
+use Facebook\WebDriver\Remote\RemoteWebElement;
+use Facebook\WebDriver\Exception\StaleElementReferenceException;
+abstract class AbstractBaseFeatureTest extends \PHPUnit_Framework_TestCase
+ * Set this in phpunit.xml
+ protected $url = WEBDRIVER_BASE_SITE_URL;
+ * @var DesiredCapabilities
+ protected $desiredCapabilities;
+ * Sets the desiredCapabilities field based on the WEBDRIVER_BROWSER_NAME constant defined in phpunit.xml. Currently
+ * supports chrome and firefox, other values will result in an exception.
+ public function __construct()
+ switch (WEBDRIVER_BROWSER_NAME) {
+ $this->desiredCapabilities = DesiredCapabilities::chrome();
+ $this->desiredCapabilities = DesiredCapabilities::firefox();
+ throw new \Exception('Running tests on ' . ucfirst(WEBDRIVER_BROWSER_NAME) . ' is not supported.');
+ protected function setUp()
+ $this->webDriver = RemoteWebDriver::create(
+ $this->desiredCapabilities
+ protected function tearDown()
+ $this->webDriver->quit();
+ * Loads the page at $url. Returns control to the caller after the page has finished loading.
+ protected function getPageAndWaitForLoad($url)
+ $this->webDriver->get($url);
+ $this->webDriver->wait(10, 100)->until(function () {
+ return $this->webDriver->executeScript('return document.readyState') == 'complete';
+ * Finds the element identified by $condition and passes it to clickThroughToNewPageByElement. Returns control to
+ * the caller after the new page finishes loading.
+ * @param WebDriverBy $condition
+ protected function clickThroughToNewPageBy(WebDriverBy $condition)
+ $element = $this->webDriver->findElement($condition);
+ $this->clickThroughToNewPageByElement($element);
+ * Syntax sugar for calling clickThroughToNewPageBy with a link's text.
+ * @param string $linkText
+ protected function clickThroughToNewPageByLinkText($linkText)
+ $this->clickThroughToNewPageBy(WebDriverBy::linkText($linkText));
+ * Syntax sugar for calling clickThroughToNewPageBy with a CSS selector.
+ * @param string $cssSelector
+ protected function clickThroughToNewPageByCssSelector($cssSelector)
+ $this->clickThroughToNewPageBy(WebDriverBy::cssSelector($cssSelector));
+ * Syntax sugar for calling clickThroughToNewPageBy with an ID.
+ protected function clickThroughToNewPageById($id)
+ $this->clickThroughToNewPageBy(WebDriverBy::id($id));
+ * Syntax sugar for calling clickThroughToNewPageBy with an XPath selector.
+ protected function clickThroughToNewPageByXPath($xPath)
+ $this->clickThroughToNewPageBy(WebDriverBy::xpath($xPath));
+ * Clicks on the given element and waits for a new page to load.
+ * @param RemoteWebElement $element
+ protected function clickThroughToNewPageByElement(RemoteWebElement $element)
+ $this->webDriver->wait(10, 100)->until(WebDriverExpectedCondition::stalenessOf($element));
+ * Gets an element using xpath but waits until it's present by a given condition.
+ * @param string $xpath The xpath for the element - Defaults to presenceOfElementLocated.
+ * @param string $condition A WebDriverExpectedCondition
+ * @return WebDriverRemoteElement
+ protected function getElementByXpath($xpath, $condition = "presenceOfElementLocated")
+ $this->webDriver->wait(10, 100)->until(
+ WebDriverExpectedCondition::$condition(
+ WebDriverBy::xpath($xpath)
+ return $this->webDriver->findElement(WebDriverBy::xpath($xpath));
+ * Gets elements using xpath
+ * @param string $xpath The xpath for the elements.
+ protected function getElementsByXpath($xpath)
+ return $this->webDriver->findElements(WebDriverBy::xpath($xpath));
+ * Click 'okay' on an alert.
+ * @return AbstractBaseFeatureTest
+ protected function acceptAlert()
+ $this->webDriver->wait()->until(
+ WebDriverExpectedCondition::alertIsPresent()
+ $this->webDriver->switchTo()->alert()->accept();
+ * Click 'cancel' on an alert.
+ * @return AbstractBaseFeatureTest
+ protected function dismissAlert()
+ $this->webDriver->wait()->until(
+ WebDriverExpectedCondition::alertIsPresent()
+ $this->webDriver->switchTo()->alert()->dismiss();
+ * Output text to the console during a test.
+ * @param string $value Text to output.
+ * @param string $colour Which colour to make the text.
+ protected function output($value, $colour = null)
+ $colour = $this->getOutputColour($colour);
+ fwrite(STDOUT, $colour . "{$value} \033[0m \n");
+ * Get a colour code for outputting text.
+ * @return string The code for the given colour.
+ protected function getOutputColour($colour)
+ return "\033[{$colours[$colour]}m";
+ * Tries clicking an element. This repeated attempting pattern catches stale
+ * element exceptions which, until I wrote this method, were the bane of my existence.\
+ * This method isn't essential for every click, but it helps a lot with cases
+ * where JavaScript manages the DOM after a previous click occurred. For clicks
+ * before Javascript needs to run, this is probably overkill.
+ * @param RemoteWebElement $element The element to try clicking.
+ * @param string $elementXpath Path to the element to try re-referencing it.
+ * @param int $numAttempts How many times to try clicking.
+ * @param int $wait How long to wait between clicks in milliseconds.
+ * @return AbstractBaseFeatureTest
+ protected function tryClicking(RemoteWebElement $element, $elementXpath = null, $numAttempts = 5, $wait = 10)
+ while ($attempts < $numAttempts) {
+ } catch (StaleElementReferenceException $exception) {
+ "Re-referencing element due to stale element reference...",
+ $element = $this->getElementByXpath($elementXpath, "elementToBeClickable");
+ $remaining = $numAttempts - $attempts;
+ "Caught StaleElementReferenceException; waiting {$wait}ms then clicking again ({$remaining} tries remaining)...",
+ $this->webDriver->manage()->timeouts()->implicitlyWait($wait);
+ // Looks like we made it here, so we should let the exception happen.