Merge pull request #174 from open-sausages/pulls/4.0/selenium-upgrade

API Update to use new facebook driver extension
This commit is contained in:
Damian Mooyman 2018-04-12 11:53:09 +12:00 committed by GitHub
commit 55e9221dcd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 128 additions and 36 deletions

View File

@ -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). 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 ## Tutorials
* [Tutorial: Testing Form Submissions](docs/tutorial.md) * [Tutorial: Testing Form Submissions](docs/tutorial.md)

20
bin/behat-ss Executable file
View File

@ -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

View File

@ -26,10 +26,10 @@
"behat/behat": "^3.2", "behat/behat": "^3.2",
"behat/mink": "^1.7", "behat/mink": "^1.7",
"behat/mink-extension": "^2.1", "behat/mink-extension": "^2.1",
"behat/mink-selenium2-driver": "^1.3", "silverstripe/mink-facebook-web-driver": "^1",
"symfony/dom-crawler": "^3", "symfony/dom-crawler": "^3",
"silverstripe/testsession": "^2.0.0@alpha", "silverstripe/testsession": "^2.1",
"silverstripe/framework": "^4@dev", "silverstripe/framework": "^4",
"symfony/finder": "^3.2" "symfony/finder": "^3.2"
}, },
"autoload": { "autoload": {
@ -47,6 +47,9 @@
"3.x-dev": "3.1.x-dev" "3.x-dev": "3.1.x-dev"
} }
}, },
"bin": [
"bin/behat-ss"
],
"scripts": { "scripts": {
"lint": "phpcs --standard=PSR2 -n src/ tests/php/" "lint": "phpcs --standard=PSR2 -n src/ tests/php/"
}, },

View File

@ -8,16 +8,19 @@ use Behat\Behat\Hook\Scope\AfterScenarioScope;
use Behat\Behat\Hook\Scope\AfterStepScope; use Behat\Behat\Hook\Scope\AfterStepScope;
use Behat\Behat\Hook\Scope\BeforeStepScope; use Behat\Behat\Hook\Scope\BeforeStepScope;
use Behat\Behat\Hook\Scope\StepScope; use Behat\Behat\Hook\Scope\StepScope;
use Behat\Mink\Driver\Selenium2Driver;
use Behat\Mink\Element\NodeElement; use Behat\Mink\Element\NodeElement;
use Behat\Mink\Session; use Behat\Mink\Session;
use Behat\Testwork\Tester\Result\TestResult; use Behat\Testwork\Tester\Result\TestResult;
use Exception; 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\File;
use SilverStripe\Assets\Filesystem; use SilverStripe\Assets\Filesystem;
use SilverStripe\BehatExtension\Utility\StepHelper; use SilverStripe\BehatExtension\Utility\StepHelper;
use WebDriver\Exception as WebDriverException; use SilverStripe\MinkFacebookWebDriver\FacebookWebDriver;
use WebDriver\Session as WebDriverSession;
/** /**
* BasicContext * BasicContext
@ -248,7 +251,7 @@ JS;
/** /**
* Take screenshot when step fails. * Take screenshot when step fails.
* Works only with Selenium2Driver. * Works only with FacebookWebDriver.
* *
* @AfterStep * @AfterStep
* @param AfterStepScope $event * @param AfterStepScope $event
@ -282,7 +285,7 @@ JS;
try { try {
// Navigate away triggered by reloading the page // Navigate away triggered by reloading the page
$this->getSession()->reload(); $this->getSession()->reload();
$this->getWebDriverSession()->accept_alert(); $this->getExpectedAlert()->accept();
} catch (WebDriverException $e) { } catch (WebDriverException $e) {
// no-op, alert might not be present // no-op, alert might not be present
} }
@ -316,8 +319,8 @@ JS;
{ {
// Validate driver // Validate driver
$driver = $this->getSession()->getDriver(); $driver = $this->getSession()->getDriver();
if (!($driver instanceof Selenium2Driver)) { if (!($driver instanceof FacebookWebDriver)) {
file_put_contents('php://stdout', 'ScreenShots are only supported for Selenium2Driver: skipping'); file_put_contents('php://stdout', 'ScreenShots are only supported for FacebookWebDriver: skipping');
return; return;
} }
@ -350,8 +353,8 @@ JS;
} }
$path = sprintf('%s/%s_%d.png', $path, basename($feature->getFile()), $step->getLine()); $path = sprintf('%s/%s_%d.png', $path, basename($feature->getFile()), $step->getLine());
$screenshot = $driver->getWebDriverSession()->screenshot(); $screenshot = $driver->getScreenshot();
file_put_contents($path, base64_decode($screenshot)); file_put_contents($path, $screenshot);
file_put_contents('php://stderr', sprintf('Saving screenshot into %s' . PHP_EOL, $path)); file_put_contents('php://stderr', sprintf('Saving screenshot into %s' . PHP_EOL, $path));
} }
@ -521,10 +524,7 @@ JS;
*/ */
public function iSeeTheDialogText($expected) public function iSeeTheDialogText($expected)
{ {
$session = $this->getSession(); $text = $this->getExpectedAlert()->getText();
/** @var Selenium2Driver $driver */
$driver = $session->getDriver();
$text = $driver->getWebDriverSession()->getAlert_text();
assertContains($expected, $text); assertContains($expected, $text);
} }
@ -534,7 +534,24 @@ JS;
*/ */
public function iTypeIntoTheDialog($data) 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();
} }
/** /**
@ -542,7 +559,12 @@ JS;
*/ */
public function iConfirmTheDialog() public function iConfirmTheDialog()
{ {
$this->getWebDriverSession()->accept_alert(); $session = $this->getWebDriverSession();
$session->wait()->until(
WebDriverExpectedCondition::alertIsPresent(),
"Alert is expected"
);
$session->switchTo()->alert()->accept();
$this->handleAjaxTimeout(); $this->handleAjaxTimeout();
} }
@ -551,23 +573,23 @@ JS;
*/ */
public function iDismissTheDialog() public function iDismissTheDialog()
{ {
$this->getWebDriverSession()->dismiss_alert(); $this->getExpectedAlert()->dismiss();
$this->handleAjaxTimeout(); $this->handleAjaxTimeout();
} }
/** /**
* Get Selenium webdriver session. * 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() protected function getWebDriverSession()
{ {
$driver = $this->getSession()->getDriver(); $driver = $this->getSession()->getDriver();
if (! $driver instanceof Selenium2Driver) { if (! $driver instanceof FacebookWebDriver) {
throw new \InvalidArgumentException("Not supported for non-selenium2 drivers"); throw new InvalidArgumentException("Only supported for FacebookWebDriver");
} }
return $driver->getWebDriverSession(); return $driver->getWebDriver();
} }
/** /**
@ -612,7 +634,7 @@ JS;
} }
if (!$parent) { 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 */ /** @var NodeElement $option */
@ -632,7 +654,7 @@ JS;
} }
if (!$input) { 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()); $this->getSession()->getDriver()->click($input->getXPath());
@ -649,6 +671,7 @@ JS;
{ {
fwrite(STDOUT, "\033[s \033[93m[Breakpoint] Press \033[1;93m[RETURN]\033[0;93m to continue...\033[0m"); fwrite(STDOUT, "\033[s \033[93m[Breakpoint] Press \033[1;93m[RETURN]\033[0;93m to continue...\033[0m");
while (fgets(STDIN, 1024) == '') { while (fgets(STDIN, 1024) == '') {
// noop
} }
fwrite(STDOUT, "\033[u"); fwrite(STDOUT, "\033[u");
@ -669,7 +692,7 @@ JS;
{ {
$timestamp = strtotime($val); $timestamp = strtotime($val);
if (!$timestamp) { if (!$timestamp) {
throw new \InvalidArgumentException(sprintf( throw new InvalidArgumentException(sprintf(
"Can't resolve '%s' into a valid datetime value", "Can't resolve '%s' into a valid datetime value",
$val $val
)); ));
@ -691,7 +714,7 @@ JS;
{ {
$timestamp = strtotime($val); $timestamp = strtotime($val);
if (!$timestamp) { if (!$timestamp) {
throw new \InvalidArgumentException(sprintf( throw new InvalidArgumentException(sprintf(
"Can't resolve '%s' into a valid datetime value", "Can't resolve '%s' into a valid datetime value",
$val $val
)); ));
@ -713,7 +736,7 @@ JS;
{ {
$timestamp = strtotime($val); $timestamp = strtotime($val);
if (!$timestamp) { if (!$timestamp) {
throw new \InvalidArgumentException(sprintf( throw new InvalidArgumentException(sprintf(
"Can't resolve '%s' into a valid datetime value", "Can't resolve '%s' into a valid datetime value",
$val $val
)); ));
@ -1150,7 +1173,7 @@ JS;
$id = $el->getAttribute('id'); $id = $el->getAttribute('id');
if (empty($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); $js = sprintf("document.getElementById('%s').scrollIntoView(true);", $id);
@ -1173,7 +1196,7 @@ JS;
$id = $el->getAttribute('id'); $id = $el->getAttribute('id');
if (empty($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); $js = sprintf("document.getElementById('%s').scrollIntoView(true);", $id);

View File

@ -109,6 +109,17 @@ class LoginContext implements Context
$emailField->setValue($email); $emailField->setValue($email);
$passwordField->setValue($password); $passwordField->setValue($password);
$submitButton->press(); $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. '""');
} }
/** /**

View File

@ -4,17 +4,18 @@ namespace SilverStripe\BehatExtension\Context;
use Behat\Behat\Hook\Scope\BeforeScenarioScope; use Behat\Behat\Hook\Scope\BeforeScenarioScope;
use Behat\Mink\Element\NodeElement; use Behat\Mink\Element\NodeElement;
use Behat\Mink\Exception\ElementNotFoundException;
use Behat\Mink\Exception\UnsupportedDriverActionException;
use Behat\Mink\Selector\Xpath\Escaper; use Behat\Mink\Selector\Xpath\Escaper;
use Behat\MinkExtension\Context\MinkContext; use Behat\MinkExtension\Context\MinkContext;
use Behat\Mink\Driver\Selenium2Driver;
use Behat\Mink\Exception\UnsupportedDriverActionException;
use Behat\Mink\Exception\ElementNotFoundException;
use InvalidArgumentException; use InvalidArgumentException;
use SilverStripe\BehatExtension\Utility\TestMailer; use SilverStripe\BehatExtension\Utility\TestMailer;
use SilverStripe\CMS\Model\SiteTree; use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Core\ClassInfo; use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Convert;
use SilverStripe\Core\Environment; use SilverStripe\Core\Environment;
use SilverStripe\Core\Resettable; use SilverStripe\Core\Resettable;
use SilverStripe\MinkFacebookWebDriver\FacebookWebDriver;
use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObject;
use SilverStripe\TestSession\TestSessionEnvironment; use SilverStripe\TestSession\TestSessionEnvironment;
use Symfony\Component\CssSelector\Exception\SyntaxErrorException; use Symfony\Component\CssSelector\Exception\SyntaxErrorException;
@ -383,7 +384,7 @@ abstract class SilverStripeContext extends MinkContext implements SilverStripeAw
public function canIntercept() public function canIntercept()
{ {
$driver = $this->getSession()->getDriver(); $driver = $this->getSession()->getDriver();
if ($driver instanceof Selenium2Driver) { if ($driver instanceof FacebookWebDriver) {
return false; return false;
} }
@ -411,7 +412,18 @@ abstract class SilverStripeContext extends MinkContext implements SilverStripeAw
/** @var NodeElement $node */ /** @var NodeElement $node */
foreach ($nodes as $node) { foreach ($nodes as $node) {
if ($node->isVisible()) { 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; return;
} }
} }

View File

@ -4,6 +4,7 @@ namespace SilverStripe\BehatExtension;
use Behat\MinkExtension\ServiceContainer\MinkExtension as BaseMinkExtension; use Behat\MinkExtension\ServiceContainer\MinkExtension as BaseMinkExtension;
use SilverStripe\BehatExtension\Compiler\MinkExtensionBaseUrlPass; use SilverStripe\BehatExtension\Compiler\MinkExtensionBaseUrlPass;
use SilverStripe\MinkFacebookWebDriver\FacebookFactory;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
/** /**
@ -14,6 +15,12 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
*/ */
class MinkExtension extends BaseMinkExtension class MinkExtension extends BaseMinkExtension
{ {
public function __construct()
{
parent::__construct();
$this->registerDriverFactory(new FacebookFactory());
}
public function process(ContainerBuilder $container) public function process(ContainerBuilder $container)
{ {
parent::process($container); parent::process($container);