Skip to content

Commit

Permalink
Merge pull request #174 from open-sausages/pulls/4.0/selenium-upgrade
Browse files Browse the repository at this point in the history
API Update to use new facebook driver extension
  • Loading branch information
Damian Mooyman authored Apr 11, 2018
2 parents 04789ce + 6ef427f commit 55e9221
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 36 deletions.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,22 @@ This will start a Firefox browser by default. Other browsers and profiles can be

For example, if you want to start a Chrome Browser you can following the instructions provided [here](docs/chrome-behat.md).

### Running with stand-alone command

If running with `silverstripe/serve` and `chromedriver`, you can also use the following command
which will automatically start and stop these services for individual tests.

vendor/bin/behat-ss @framework

This automates:
- starting server
- starting chromedriver
- running behat
- shutting down chromedriver
- shutting down server

Make sure you set `SS_BASE_URL` to `http://localhost:8080` in `.env`

## Tutorials

* [Tutorial: Testing Form Submissions](docs/tutorial.md)
Expand Down
20 changes: 20 additions & 0 deletions bin/behat-ss
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/bin/sh
echo "setting up /artifacts"
mkdir -p artifacts

echo "starting chromedriver"
chromedriver &> artifacts/chromedriver.log 2> artifacts/chromedriver-error.log &
cd_pid=$!

echo "starting webserver"
vendor/bin/serve &> artifacts/serve.log 2> artifacts/serve-error.log &
ws_pid=$!

echo "starting behat"
vendor/bin/behat "$@"

echo "killing webserver (PID: $ws_pid)"
pkill -TERM -P $ws_pid &> /dev/null

echo "killing chromedriver (PID: $cd_pid)"
kill -9 $cd_pid &> /dev/null
9 changes: 6 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@
"behat/behat": "^3.2",
"behat/mink": "^1.7",
"behat/mink-extension": "^2.1",
"behat/mink-selenium2-driver": "^1.3",
"silverstripe/mink-facebook-web-driver": "^1",
"symfony/dom-crawler": "^3",
"silverstripe/testsession": "^2.0.0@alpha",
"silverstripe/framework": "^4@dev",
"silverstripe/testsession": "^2.1",
"silverstripe/framework": "^4",
"symfony/finder": "^3.2"
},
"autoload": {
Expand All @@ -47,6 +47,9 @@
"3.x-dev": "3.1.x-dev"
}
},
"bin": [
"bin/behat-ss"
],
"scripts": {
"lint": "phpcs --standard=PSR2 -n src/ tests/php/"
},
Expand Down
79 changes: 51 additions & 28 deletions src/Context/BasicContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,19 @@
use Behat\Behat\Hook\Scope\AfterStepScope;
use Behat\Behat\Hook\Scope\BeforeStepScope;
use Behat\Behat\Hook\Scope\StepScope;
use Behat\Mink\Driver\Selenium2Driver;
use Behat\Mink\Element\NodeElement;
use Behat\Mink\Session;
use Behat\Testwork\Tester\Result\TestResult;
use Exception;
use Facebook\WebDriver\Exception\WebDriverException;
use Facebook\WebDriver\WebDriver;
use Facebook\WebDriver\WebDriverAlert;
use Facebook\WebDriver\WebDriverExpectedCondition;
use InvalidArgumentException;
use SilverStripe\Assets\File;
use SilverStripe\Assets\Filesystem;
use SilverStripe\BehatExtension\Utility\StepHelper;
use WebDriver\Exception as WebDriverException;
use WebDriver\Session as WebDriverSession;
use SilverStripe\MinkFacebookWebDriver\FacebookWebDriver;

/**
* BasicContext
Expand Down Expand Up @@ -248,7 +251,7 @@ public function handleAjaxTimeout()

/**
* Take screenshot when step fails.
* Works only with Selenium2Driver.
* Works only with FacebookWebDriver.
*
* @AfterStep
* @param AfterStepScope $event
Expand Down Expand Up @@ -282,7 +285,7 @@ public function closeModalDialog(AfterScenarioScope $event)
try {
// Navigate away triggered by reloading the page
$this->getSession()->reload();
$this->getWebDriverSession()->accept_alert();
$this->getExpectedAlert()->accept();
} catch (WebDriverException $e) {
// no-op, alert might not be present
}
Expand Down Expand Up @@ -316,8 +319,8 @@ public function takeScreenshot(StepScope $event)
{
// Validate driver
$driver = $this->getSession()->getDriver();
if (!($driver instanceof Selenium2Driver)) {
file_put_contents('php://stdout', 'ScreenShots are only supported for Selenium2Driver: skipping');
if (!($driver instanceof FacebookWebDriver)) {
file_put_contents('php://stdout', 'ScreenShots are only supported for FacebookWebDriver: skipping');
return;
}

Expand Down Expand Up @@ -350,8 +353,8 @@ public function takeScreenshot(StepScope $event)
}

$path = sprintf('%s/%s_%d.png', $path, basename($feature->getFile()), $step->getLine());
$screenshot = $driver->getWebDriverSession()->screenshot();
file_put_contents($path, base64_decode($screenshot));
$screenshot = $driver->getScreenshot();
file_put_contents($path, $screenshot);
file_put_contents('php://stderr', sprintf('Saving screenshot into %s' . PHP_EOL, $path));
}

Expand Down Expand Up @@ -521,10 +524,7 @@ public function iClickInTheElementDismissingTheDialog($clickType, $text, $select
*/
public function iSeeTheDialogText($expected)
{
$session = $this->getSession();
/** @var Selenium2Driver $driver */
$driver = $session->getDriver();
$text = $driver->getWebDriverSession()->getAlert_text();
$text = $this->getExpectedAlert()->getText();
assertContains($expected, $text);
}

Expand All @@ -534,15 +534,37 @@ public function iSeeTheDialogText($expected)
*/
public function iTypeIntoTheDialog($data)
{
$this->getWebDriverSession()->postAlert_text([ 'text' => $data ]);
$this->getExpectedAlert()
->sendKeys($data)
->accept();
}

/**
* Wait for alert to appear, and return handle
*
* @return WebDriverAlert
*/
protected function getExpectedAlert()
{
$session = $this->getWebDriverSession();
$session->wait()->until(
WebDriverExpectedCondition::alertIsPresent(),
"Alert is expected"
);
return $session->switchTo()->alert();
}

/**
* @Given /^I confirm the dialog$/
*/
public function iConfirmTheDialog()
{
$this->getWebDriverSession()->accept_alert();
$session = $this->getWebDriverSession();
$session->wait()->until(
WebDriverExpectedCondition::alertIsPresent(),
"Alert is expected"
);
$session->switchTo()->alert()->accept();
$this->handleAjaxTimeout();
}

Expand All @@ -551,23 +573,23 @@ public function iConfirmTheDialog()
*/
public function iDismissTheDialog()
{
$this->getWebDriverSession()->dismiss_alert();
$this->getExpectedAlert()->dismiss();
$this->handleAjaxTimeout();
}

/**
* Get Selenium webdriver session.
* Note: Will fail if current driver isn't Selenium2Driver
* Note: Will fail if current driver isn't FacebookWebDriver
*
* @return WebDriverSession
* @return WebDriver
*/
protected function getWebDriverSession()
{
$driver = $this->getSession()->getDriver();
if (! $driver instanceof Selenium2Driver) {
throw new \InvalidArgumentException("Not supported for non-selenium2 drivers");
if (! $driver instanceof FacebookWebDriver) {
throw new InvalidArgumentException("Only supported for FacebookWebDriver");
}
return $driver->getWebDriverSession();
return $driver->getWebDriver();
}

/**
Expand Down Expand Up @@ -612,7 +634,7 @@ public function iSelectFromInputGroup($value, $labelText)
}

if (!$parent) {
throw new \InvalidArgumentException(sprintf('Input group with label "%s" cannot be found', $labelText));
throw new InvalidArgumentException(sprintf('Input group with label "%s" cannot be found', $labelText));
}

/** @var NodeElement $option */
Expand All @@ -632,7 +654,7 @@ public function iSelectFromInputGroup($value, $labelText)
}

if (!$input) {
throw new \InvalidArgumentException(sprintf('Input "%s" cannot be found', $value));
throw new InvalidArgumentException(sprintf('Input "%s" cannot be found', $value));
}

$this->getSession()->getDriver()->click($input->getXPath());
Expand All @@ -649,6 +671,7 @@ public function iPutABreakpoint()
{
fwrite(STDOUT, "\033[s \033[93m[Breakpoint] Press \033[1;93m[RETURN]\033[0;93m to continue...\033[0m");
while (fgets(STDIN, 1024) == '') {
// noop
}
fwrite(STDOUT, "\033[u");

Expand All @@ -669,7 +692,7 @@ public function castRelativeToAbsoluteTime($prefix, $val)
{
$timestamp = strtotime($val);
if (!$timestamp) {
throw new \InvalidArgumentException(sprintf(
throw new InvalidArgumentException(sprintf(
"Can't resolve '%s' into a valid datetime value",
$val
));
Expand All @@ -691,7 +714,7 @@ public function castRelativeToAbsoluteDatetime($prefix, $val)
{
$timestamp = strtotime($val);
if (!$timestamp) {
throw new \InvalidArgumentException(sprintf(
throw new InvalidArgumentException(sprintf(
"Can't resolve '%s' into a valid datetime value",
$val
));
Expand All @@ -713,7 +736,7 @@ public function castRelativeToAbsoluteDate($prefix, $val)
{
$timestamp = strtotime($val);
if (!$timestamp) {
throw new \InvalidArgumentException(sprintf(
throw new InvalidArgumentException(sprintf(
"Can't resolve '%s' into a valid datetime value",
$val
));
Expand Down Expand Up @@ -1150,7 +1173,7 @@ public function iScrollToField($locator, $type)

$id = $el->getAttribute('id');
if (empty($id)) {
throw new \InvalidArgumentException('Element requires an "id" attribute');
throw new InvalidArgumentException('Element requires an "id" attribute');
}

$js = sprintf("document.getElementById('%s').scrollIntoView(true);", $id);
Expand All @@ -1173,7 +1196,7 @@ public function iScrollToElement($locator)

$id = $el->getAttribute('id');
if (empty($id)) {
throw new \InvalidArgumentException('Element requires an "id" attribute');
throw new InvalidArgumentException('Element requires an "id" attribute');
}

$js = sprintf("document.getElementById('%s').scrollIntoView(true);", $id);
Expand Down
11 changes: 11 additions & 0 deletions src/Context/LoginContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,17 @@ public function stepILogInWith($email, $password)
$emailField->setValue($email);
$passwordField->setValue($password);
$submitButton->press();

// Wait 100 ms
$this->getMainContext()->getSession()->wait(100);

// In case of login error, throw exception
// E.g. 'Your session has expired. Please re-submit the form.'
// This will allow @retry
$page = $this->getMainContext()->getSession()->getPage();
$message = $page->find('css', '.message.error');
$error = $message ? $message->getText() : null;
assertNull($message, 'Could not log in with user ' . $email . '. Error: "' . $error. '""');
}

/**
Expand Down
22 changes: 17 additions & 5 deletions src/Context/SilverStripeContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@

use Behat\Behat\Hook\Scope\BeforeScenarioScope;
use Behat\Mink\Element\NodeElement;
use Behat\Mink\Exception\ElementNotFoundException;
use Behat\Mink\Exception\UnsupportedDriverActionException;
use Behat\Mink\Selector\Xpath\Escaper;
use Behat\MinkExtension\Context\MinkContext;
use Behat\Mink\Driver\Selenium2Driver;
use Behat\Mink\Exception\UnsupportedDriverActionException;
use Behat\Mink\Exception\ElementNotFoundException;
use InvalidArgumentException;
use SilverStripe\BehatExtension\Utility\TestMailer;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Convert;
use SilverStripe\Core\Environment;
use SilverStripe\Core\Resettable;
use SilverStripe\MinkFacebookWebDriver\FacebookWebDriver;
use SilverStripe\ORM\DataObject;
use SilverStripe\TestSession\TestSessionEnvironment;
use Symfony\Component\CssSelector\Exception\SyntaxErrorException;
Expand Down Expand Up @@ -383,7 +384,7 @@ public function joinUrlParts($part = null)
public function canIntercept()
{
$driver = $this->getSession()->getDriver();
if ($driver instanceof Selenium2Driver) {
if ($driver instanceof FacebookWebDriver) {
return false;
}

Expand Down Expand Up @@ -411,7 +412,18 @@ public function fillField($field, $value)
/** @var NodeElement $node */
foreach ($nodes as $node) {
if ($node->isVisible()) {
$node->setValue($value);
// Work around for https://github.com/FluentLenium/FluentLenium/issues/129
// Otherwise "Element must be user-editable in order to clear it"
$type = $node->getAttribute('type');
$id = $node->getAttribute('id');
if ($type === 'date' && $id) {
$jsValue = Convert::raw2js($value);
$this->getSession()->getDriver()->executeScript(
"document.getElementById(\"{$id}\").value = \"{$jsValue}\";"
);
} else {
$node->setValue($value);
}
return;
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/MinkExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Behat\MinkExtension\ServiceContainer\MinkExtension as BaseMinkExtension;
use SilverStripe\BehatExtension\Compiler\MinkExtensionBaseUrlPass;
use SilverStripe\MinkFacebookWebDriver\FacebookFactory;
use Symfony\Component\DependencyInjection\ContainerBuilder;

/**
Expand All @@ -14,6 +15,12 @@
*/
class MinkExtension extends BaseMinkExtension
{
public function __construct()
{
parent::__construct();
$this->registerDriverFactory(new FacebookFactory());
}

public function process(ContainerBuilder $container)
{
parent::process($container);
Expand Down

0 comments on commit 55e9221

Please sign in to comment.