2012-11-09 17:34:24 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace SilverStripe\BehatExtension\Context;
|
|
|
|
|
2018-12-19 21:37:13 +01:00
|
|
|
use Behat\Behat\Hook\Scope\AfterStepScope;
|
2014-08-02 08:30:27 +02:00
|
|
|
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
|
|
|
|
use Behat\Mink\Element\NodeElement;
|
2017-09-08 04:49:50 +02:00
|
|
|
use Behat\Mink\Exception\ElementNotFoundException;
|
|
|
|
use Behat\Mink\Exception\UnsupportedDriverActionException;
|
2014-08-02 08:30:27 +02:00
|
|
|
use Behat\Mink\Selector\Xpath\Escaper;
|
2012-11-09 17:34:24 +01:00
|
|
|
use Behat\MinkExtension\Context\MinkContext;
|
2016-09-01 06:22:47 +02:00
|
|
|
use InvalidArgumentException;
|
2017-10-16 06:09:41 +02:00
|
|
|
use SilverStripe\BehatExtension\Utility\TestMailer;
|
2014-08-02 08:30:27 +02:00
|
|
|
use SilverStripe\CMS\Model\SiteTree;
|
|
|
|
use SilverStripe\Core\ClassInfo;
|
2017-09-08 04:49:50 +02:00
|
|
|
use SilverStripe\Core\Convert;
|
2017-10-16 06:09:41 +02:00
|
|
|
use SilverStripe\Core\Environment;
|
2014-08-02 08:30:27 +02:00
|
|
|
use SilverStripe\Core\Resettable;
|
2017-09-08 04:49:50 +02:00
|
|
|
use SilverStripe\MinkFacebookWebDriver\FacebookWebDriver;
|
2014-08-02 08:30:27 +02:00
|
|
|
use SilverStripe\ORM\DataObject;
|
2017-04-26 06:24:20 +02:00
|
|
|
use SilverStripe\TestSession\TestSessionEnvironment;
|
2014-08-02 08:30:27 +02:00
|
|
|
use Symfony\Component\CssSelector\Exception\SyntaxErrorException;
|
2016-02-23 10:51:56 +01:00
|
|
|
|
2012-11-09 17:34:24 +01:00
|
|
|
/**
|
|
|
|
* SilverStripeContext
|
|
|
|
*
|
|
|
|
* Generic context wrapper used as a base for Behat FeatureContext.
|
2014-08-02 08:30:27 +02:00
|
|
|
*
|
|
|
|
* The default context for each module should extend this and be named `FeatureContext`
|
|
|
|
* under the standard module namespace.
|
|
|
|
*
|
|
|
|
* @link http://behat.org/en/latest/user_guide/context.html
|
2012-11-09 17:34:24 +01:00
|
|
|
*/
|
2014-08-02 08:30:27 +02:00
|
|
|
abstract class SilverStripeContext extends MinkContext implements SilverStripeAwareContext
|
2012-11-09 17:34:24 +01:00
|
|
|
{
|
2016-08-10 03:35:13 +02:00
|
|
|
protected $databaseName;
|
|
|
|
|
|
|
|
/**
|
2016-09-01 06:22:47 +02:00
|
|
|
* @var array Partial string match for step names
|
2016-08-10 03:35:13 +02:00
|
|
|
* that are considered to trigger Ajax request in the CMS,
|
|
|
|
* and hence need special timeout handling.
|
2014-08-02 08:30:27 +02:00
|
|
|
* @see \SilverStripe\BehatExtension\Context\BasicContextAwareTrait->handleAjaxBeforeStep().
|
2016-08-10 03:35:13 +02:00
|
|
|
*/
|
|
|
|
protected $ajaxSteps;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var Int Timeout in milliseconds, after which the interface assumes
|
|
|
|
* that an Ajax request has timed out, and continues with assertions.
|
|
|
|
*/
|
|
|
|
protected $ajaxTimeout;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var String Relative URL to the SilverStripe admin interface.
|
|
|
|
*/
|
|
|
|
protected $adminUrl;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var String Relative URL to the SilverStripe login form.
|
|
|
|
*/
|
|
|
|
protected $loginUrl;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var String Relative path to a writeable folder where screenshots can be stored.
|
|
|
|
* If set to NULL, no screenshots will be stored.
|
|
|
|
*/
|
|
|
|
protected $screenshotPath;
|
|
|
|
|
2014-08-02 08:30:27 +02:00
|
|
|
/**
|
|
|
|
* @var TestSessionEnvironment
|
|
|
|
*/
|
2016-08-10 03:35:13 +02:00
|
|
|
protected $testSessionEnvironment;
|
|
|
|
|
2014-08-02 08:30:27 +02:00
|
|
|
protected $regionMap;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* XPath escaper
|
|
|
|
*
|
|
|
|
* @var Escaper
|
|
|
|
*/
|
|
|
|
protected $xpathEscaper;
|
2016-08-10 03:35:13 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Initializes context.
|
|
|
|
* Every scenario gets it's own context object.
|
|
|
|
*
|
|
|
|
* @param array $parameters context parameters (set them up through behat.yml)
|
|
|
|
*/
|
2014-08-02 08:30:27 +02:00
|
|
|
public function __construct(array $parameters = null)
|
2016-08-10 03:35:13 +02:00
|
|
|
{
|
2014-08-02 08:30:27 +02:00
|
|
|
if (!preg_match('/\\FeatureContext$/', get_class($this))) {
|
|
|
|
throw new InvalidArgumentException(
|
|
|
|
'Subclasses of SilverStripeContext must be named FeatureContext. Found "' . get_class($this) . '""'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
// Initialize your context here
|
2014-08-02 08:30:27 +02:00
|
|
|
$this->xpathEscaper = new Escaper();
|
|
|
|
$this->testSessionEnvironment = TestSessionEnvironment::singleton();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get xpath escaper
|
|
|
|
*
|
|
|
|
* @return Escaper
|
|
|
|
*/
|
|
|
|
public function getXpathEscaper()
|
|
|
|
{
|
|
|
|
return $this->xpathEscaper;
|
2016-08-10 03:35:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public function setDatabase($databaseName)
|
|
|
|
{
|
|
|
|
$this->databaseName = $databaseName;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setAjaxSteps($ajaxSteps)
|
|
|
|
{
|
|
|
|
if ($ajaxSteps) {
|
|
|
|
$this->ajaxSteps = $ajaxSteps;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getAjaxSteps()
|
|
|
|
{
|
|
|
|
return $this->ajaxSteps;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setAjaxTimeout($ajaxTimeout)
|
|
|
|
{
|
|
|
|
$this->ajaxTimeout = $ajaxTimeout;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getAjaxTimeout()
|
|
|
|
{
|
|
|
|
return $this->ajaxTimeout;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setAdminUrl($adminUrl)
|
|
|
|
{
|
|
|
|
$this->adminUrl = $adminUrl;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getAdminUrl()
|
|
|
|
{
|
|
|
|
return $this->adminUrl;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setLoginUrl($loginUrl)
|
|
|
|
{
|
|
|
|
$this->loginUrl = $loginUrl;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getLoginUrl()
|
|
|
|
{
|
|
|
|
return $this->loginUrl;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setScreenshotPath($screenshotPath)
|
|
|
|
{
|
|
|
|
$this->screenshotPath = $screenshotPath;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getScreenshotPath()
|
|
|
|
{
|
|
|
|
return $this->screenshotPath;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getRegionMap()
|
|
|
|
{
|
|
|
|
return $this->regionMap;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setRegionMap($regionMap)
|
|
|
|
{
|
|
|
|
$this->regionMap = $regionMap;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2014-08-02 08:30:27 +02:00
|
|
|
* Returns NodeElement based off region defined in .yml file.
|
2016-08-10 03:35:13 +02:00
|
|
|
* Also supports direct CSS selectors and regions identified by a "data-title" attribute.
|
|
|
|
* When using the "data-title" attribute, ensure not to include double quotes.
|
|
|
|
*
|
2016-09-01 06:22:47 +02:00
|
|
|
* @param string $region Region name or CSS selector
|
2014-08-02 08:30:27 +02:00
|
|
|
* @return NodeElement
|
|
|
|
* @throws ElementNotFoundException
|
2016-08-10 03:35:13 +02:00
|
|
|
*/
|
|
|
|
public function getRegionObj($region)
|
|
|
|
{
|
|
|
|
// Try to find regions directly by CSS selector.
|
|
|
|
try {
|
|
|
|
$regionObj = $this->getSession()->getPage()->find(
|
|
|
|
'css',
|
|
|
|
// Escape CSS selector
|
2014-08-02 08:30:27 +02:00
|
|
|
(false !== strpos($region, "'")) ? str_replace("'", "\\'", $region) : $region
|
2016-08-10 03:35:13 +02:00
|
|
|
);
|
|
|
|
if ($regionObj) {
|
|
|
|
return $regionObj;
|
|
|
|
}
|
2014-08-02 08:30:27 +02:00
|
|
|
} catch (SyntaxErrorException $e) {
|
2016-08-10 03:35:13 +02:00
|
|
|
// fall through to next case
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fall back to region identified by data-title.
|
|
|
|
// Only apply if no double quotes exist in search string,
|
|
|
|
// which would break the CSS selector.
|
|
|
|
if (false === strpos($region, '"')) {
|
|
|
|
$regionObj = $this->getSession()->getPage()->find(
|
|
|
|
'css',
|
|
|
|
'[data-title="' . $region . '"]'
|
|
|
|
);
|
|
|
|
if ($regionObj) {
|
|
|
|
return $regionObj;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Look for named region
|
|
|
|
if (!$this->regionMap) {
|
|
|
|
throw new \LogicException("Cannot find 'region_map' in the behat.yml");
|
|
|
|
}
|
|
|
|
if (!array_key_exists($region, $this->regionMap)) {
|
|
|
|
throw new \LogicException("Cannot find the specified region in the behat.yml");
|
|
|
|
}
|
|
|
|
$regionObj = $this->getSession()->getPage()->find('css', $region);
|
|
|
|
if (!$regionObj) {
|
2014-08-02 08:30:27 +02:00
|
|
|
throw new ElementNotFoundException($this->getSession(), "Cannot find the specified region on the page");
|
2016-08-10 03:35:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return $regionObj;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @BeforeScenario
|
2014-08-02 08:30:27 +02:00
|
|
|
* @param BeforeScenarioScope $event
|
2016-08-10 03:35:13 +02:00
|
|
|
*/
|
2014-08-02 08:30:27 +02:00
|
|
|
public function before(BeforeScenarioScope $event)
|
2016-08-10 03:35:13 +02:00
|
|
|
{
|
|
|
|
if (!isset($this->databaseName)) {
|
|
|
|
throw new \LogicException(
|
|
|
|
'Context\'s $databaseName has to be set when implementing SilverStripeAwareContextInterface.'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
$state = $this->getTestSessionState();
|
|
|
|
$this->testSessionEnvironment->startTestSession($state);
|
|
|
|
|
|
|
|
// Optionally import database
|
|
|
|
if (!empty($state['importDatabasePath'])) {
|
|
|
|
$this->testSessionEnvironment->importDatabase(
|
|
|
|
$state['importDatabasePath'],
|
|
|
|
!empty($state['requireDefaultRecords']) ? $state['requireDefaultRecords'] : false
|
|
|
|
);
|
|
|
|
} elseif (!empty($state['requireDefaultRecords']) && $state['requireDefaultRecords']) {
|
|
|
|
$this->testSessionEnvironment->requireDefaultRecords();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fixtures
|
|
|
|
$fixtureFile = (!empty($state['fixture'])) ? $state['fixture'] : null;
|
|
|
|
if ($fixtureFile) {
|
|
|
|
$this->testSessionEnvironment->loadFixtureIntoDb($fixtureFile);
|
|
|
|
}
|
|
|
|
|
2017-10-16 06:09:41 +02:00
|
|
|
if ($screenSize = Environment::getEnv('BEHAT_SCREEN_SIZE')) {
|
2016-08-10 03:35:13 +02:00
|
|
|
list($screenWidth, $screenHeight) = explode('x', $screenSize);
|
|
|
|
$this->getSession()->resizeWindow((int)$screenWidth, (int)$screenHeight);
|
|
|
|
} else {
|
|
|
|
$this->getSession()->resizeWindow(1024, 768);
|
|
|
|
}
|
2014-08-02 08:30:27 +02:00
|
|
|
|
|
|
|
// Reset everything
|
|
|
|
foreach (ClassInfo::implementorsOf(Resettable::class) as $class) {
|
|
|
|
$class::reset();
|
|
|
|
}
|
|
|
|
DataObject::flush_and_destroy_cache();
|
|
|
|
DataObject::reset();
|
2017-05-05 01:20:53 +02:00
|
|
|
if (class_exists(SiteTree::class)) {
|
|
|
|
SiteTree::reset();
|
|
|
|
}
|
2016-08-10 03:35:13 +02:00
|
|
|
}
|
|
|
|
|
2018-12-19 21:37:13 +01:00
|
|
|
/**
|
|
|
|
* @AfterStep
|
|
|
|
*
|
|
|
|
* Wait for all requests to be handled after each step
|
|
|
|
*
|
|
|
|
* @param AfterStepScope $event
|
|
|
|
*/
|
|
|
|
public function waitResponsesAfterStep(AfterStepScope $event)
|
|
|
|
{
|
|
|
|
$success = $this->testSessionEnvironment->waitForPendingRequests();
|
|
|
|
if (!$success) {
|
|
|
|
echo (
|
|
|
|
'Warning! The timeout for waiting a response from the server has expired...'.PHP_EOL.
|
|
|
|
'I keep going on, but this test behaviour may be inconsistent.'.PHP_EOL.PHP_EOL.
|
|
|
|
'Your action required!'.PHP_EOL.PHP_EOL.
|
|
|
|
'You may want to investigate why the server is responding that slowly.'.PHP_EOL.
|
|
|
|
'Otherwise, you may need to increase the timeout.'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
/**
|
|
|
|
* Returns a parameter map of state to set within the test session.
|
|
|
|
* Takes TESTSESSION_PARAMS environment variable into account for run-specific configurations.
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getTestSessionState()
|
|
|
|
{
|
|
|
|
$extraParams = array();
|
2017-10-16 06:09:41 +02:00
|
|
|
parse_str(Environment::getEnv('TESTSESSION_PARAMS'), $extraParams);
|
2016-08-10 03:35:13 +02:00
|
|
|
return array_merge(
|
|
|
|
array(
|
|
|
|
'database' => $this->databaseName,
|
2017-10-16 06:09:41 +02:00
|
|
|
'mailer' => TestMailer::class,
|
2016-08-10 03:35:13 +02:00
|
|
|
),
|
|
|
|
$extraParams
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Parses given URL and returns its components
|
|
|
|
*
|
|
|
|
* @param $url
|
|
|
|
* @return array|mixed Parsed URL
|
|
|
|
*/
|
|
|
|
public function parseUrl($url)
|
|
|
|
{
|
|
|
|
$url = parse_url($url);
|
|
|
|
$url['vars'] = array();
|
|
|
|
if (!isset($url['fragment'])) {
|
|
|
|
$url['fragment'] = null;
|
|
|
|
}
|
|
|
|
if (isset($url['query'])) {
|
|
|
|
parse_str($url['query'], $url['vars']);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $url;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks whether current URL is close enough to the given URL.
|
|
|
|
* Unless specified in $url, get vars will be ignored
|
|
|
|
* Unless specified in $url, fragment identifiers will be ignored
|
|
|
|
*
|
|
|
|
* @param $url string URL to compare to current URL
|
|
|
|
* @return boolean Returns true if the current URL is close enough to the given URL, false otherwise.
|
|
|
|
*/
|
|
|
|
public function isCurrentUrlSimilarTo($url)
|
|
|
|
{
|
|
|
|
$current = $this->parseUrl($this->getSession()->getCurrentUrl());
|
|
|
|
$test = $this->parseUrl($url);
|
|
|
|
|
|
|
|
if ($current['path'] !== $test['path']) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isset($test['fragment']) && $current['fragment'] !== $test['fragment']) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($test['vars'] as $name => $value) {
|
|
|
|
if (!isset($current['vars'][$name]) || $current['vars'][$name] !== $value) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns base URL parameter set in MinkExtension.
|
|
|
|
* It simplifies configuration by allowing to specify this parameter
|
|
|
|
* once but makes code dependent on MinkExtension.
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getBaseUrl()
|
|
|
|
{
|
|
|
|
return $this->getMinkParameter('base_url') ?: '';
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Joins URL parts into an URL using forward slash.
|
|
|
|
* Forward slash usages are normalised to one between parts.
|
|
|
|
* This method takes variable number of parameters.
|
|
|
|
*
|
2014-08-02 08:30:27 +02:00
|
|
|
* @param string $part,...
|
2016-08-10 03:35:13 +02:00
|
|
|
* @return string
|
2014-08-02 08:30:27 +02:00
|
|
|
* @throws InvalidArgumentException
|
2016-08-10 03:35:13 +02:00
|
|
|
*/
|
2014-08-02 08:30:27 +02:00
|
|
|
public function joinUrlParts($part = null)
|
2016-08-10 03:35:13 +02:00
|
|
|
{
|
|
|
|
if (0 === func_num_args()) {
|
2014-08-02 08:30:27 +02:00
|
|
|
throw new InvalidArgumentException('Need at least one argument');
|
2016-08-10 03:35:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$parts = func_get_args();
|
|
|
|
$trimSlashes = function (&$part) {
|
|
|
|
$part = trim($part, '/');
|
|
|
|
};
|
|
|
|
array_walk($parts, $trimSlashes);
|
|
|
|
|
|
|
|
return implode('/', $parts);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function canIntercept()
|
|
|
|
{
|
|
|
|
$driver = $this->getSession()->getDriver();
|
2017-09-08 04:49:50 +02:00
|
|
|
if ($driver instanceof FacebookWebDriver) {
|
2014-08-02 08:30:27 +02:00
|
|
|
return false;
|
2016-08-10 03:35:13 +02:00
|
|
|
}
|
|
|
|
|
2014-08-02 08:30:27 +02:00
|
|
|
throw new UnsupportedDriverActionException(
|
|
|
|
'You need to tag the scenario with "@mink:symfony". Intercepting the redirections is not supported by %s',
|
|
|
|
get_class($driver)
|
|
|
|
);
|
2016-08-10 03:35:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Fills in form field with specified id|name|label|value.
|
|
|
|
* Overwritten to select the first *visible* element, see https://github.com/Behat/Mink/issues/311
|
2014-08-02 08:30:27 +02:00
|
|
|
*
|
|
|
|
* @param string $field
|
|
|
|
* @param string $value
|
|
|
|
* @throws ElementNotFoundException
|
2016-08-10 03:35:13 +02:00
|
|
|
*/
|
|
|
|
public function fillField($field, $value)
|
|
|
|
{
|
|
|
|
$value = $this->fixStepArgument($value);
|
2014-08-02 08:30:27 +02:00
|
|
|
$nodes = $this->getSession()->getPage()->findAll('named', array(
|
|
|
|
'field', $this->getXpathEscaper()->escapeLiteral($field)
|
2016-08-10 03:35:13 +02:00
|
|
|
));
|
2014-08-02 08:30:27 +02:00
|
|
|
if ($nodes) {
|
|
|
|
/** @var NodeElement $node */
|
|
|
|
foreach ($nodes as $node) {
|
|
|
|
if ($node->isVisible()) {
|
2017-09-08 04:49:50 +02:00
|
|
|
// 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);
|
|
|
|
}
|
2016-08-10 03:35:13 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new ElementNotFoundException(
|
|
|
|
$this->getSession(),
|
|
|
|
'form field',
|
|
|
|
'id|name|label|value',
|
|
|
|
$field
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Overwritten to click the first *visable* link the DOM.
|
2014-08-02 08:30:27 +02:00
|
|
|
*
|
|
|
|
* @param string $link
|
|
|
|
* @throws ElementNotFoundException
|
2016-08-10 03:35:13 +02:00
|
|
|
*/
|
|
|
|
public function clickLink($link)
|
|
|
|
{
|
|
|
|
$link = $this->fixStepArgument($link);
|
2014-08-02 08:30:27 +02:00
|
|
|
$nodes = $this->getSession()->getPage()->findAll('named', array(
|
|
|
|
'link', $this->getXpathEscaper()->escapeLiteral($link)
|
2016-08-10 03:35:13 +02:00
|
|
|
));
|
2014-08-02 08:30:27 +02:00
|
|
|
if ($nodes) {
|
|
|
|
/** @var NodeElement $node */
|
|
|
|
foreach ($nodes as $node) {
|
|
|
|
if ($node->isVisible()) {
|
|
|
|
$node->click();
|
2016-08-10 03:35:13 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
throw new ElementNotFoundException(
|
|
|
|
$this->getSession(),
|
|
|
|
'link',
|
|
|
|
'id|name|label|value',
|
|
|
|
$link
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the current date. Relies on the underlying functionality using
|
|
|
|
* {@link SS_Datetime::now()} rather than PHP's system time methods like date().
|
|
|
|
* Supports ISO fomat: Y-m-d
|
|
|
|
* Example: Given the current date is "2009-10-31"
|
|
|
|
*
|
|
|
|
* @Given /^the current date is "([^"]*)"$/
|
2014-08-02 08:30:27 +02:00
|
|
|
* @param string $date
|
2016-08-10 03:35:13 +02:00
|
|
|
*/
|
|
|
|
public function givenTheCurrentDateIs($date)
|
|
|
|
{
|
|
|
|
$newDatetime = \DateTime::createFromFormat('Y-m-d', $date);
|
|
|
|
if (!$newDatetime) {
|
|
|
|
throw new InvalidArgumentException(sprintf('Invalid date format: %s (requires "Y-m-d")', $date));
|
|
|
|
}
|
|
|
|
|
|
|
|
$state = $this->testSessionEnvironment->getState();
|
|
|
|
$oldDatetime = \DateTime::createFromFormat('Y-m-d H:i:s', isset($state->datetime) ? $state->datetime : null);
|
|
|
|
if ($oldDatetime) {
|
|
|
|
$newDatetime->setTime($oldDatetime->format('H'), $oldDatetime->format('i'), $oldDatetime->format('s'));
|
|
|
|
}
|
|
|
|
$state->datetime = $newDatetime->format('Y-m-d H:i:s');
|
|
|
|
$this->testSessionEnvironment->applyState($state);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the current time. Relies on the underlying functionality using
|
|
|
|
* {@link \SS_Datetime::now()} rather than PHP's system time methods like date().
|
|
|
|
* Supports ISO fomat: H:i:s
|
|
|
|
* Example: Given the current time is "20:31:50"
|
|
|
|
*
|
|
|
|
* @Given /^the current time is "([^"]*)"$/
|
2014-08-02 08:30:27 +02:00
|
|
|
* @param string $time
|
2016-08-10 03:35:13 +02:00
|
|
|
*/
|
|
|
|
public function givenTheCurrentTimeIs($time)
|
|
|
|
{
|
2016-12-12 14:53:02 +01:00
|
|
|
$newDatetime = \DateTime::createFromFormat('H:i:s', $time);
|
2016-08-10 03:35:13 +02:00
|
|
|
if (!$newDatetime) {
|
2016-12-12 14:53:02 +01:00
|
|
|
throw new InvalidArgumentException(sprintf('Invalid date format: %s (requires "H:i:s")', $time));
|
2016-08-10 03:35:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$state = $this->testSessionEnvironment->getState();
|
|
|
|
$oldDatetime = \DateTime::createFromFormat('Y-m-d H:i:s', isset($state->datetime) ? $state->datetime : null);
|
|
|
|
if ($oldDatetime) {
|
|
|
|
$newDatetime->setDate($oldDatetime->format('Y'), $oldDatetime->format('m'), $oldDatetime->format('d'));
|
|
|
|
}
|
|
|
|
$state->datetime = $newDatetime->format('Y-m-d H:i:s');
|
|
|
|
$this->testSessionEnvironment->applyState($state);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Selects option in select field with specified id|name|label|value.
|
|
|
|
*
|
|
|
|
* @override /^(?:|I )select "(?P<option>(?:[^"]|\\")*)" from "(?P<select>(?:[^"]|\\")*)"$/
|
2014-08-02 08:30:27 +02:00
|
|
|
* @param string $select
|
|
|
|
* @param string $option
|
2016-08-10 03:35:13 +02:00
|
|
|
*/
|
|
|
|
public function selectOption($select, $option)
|
|
|
|
{
|
|
|
|
// Find field
|
|
|
|
$field = $this
|
|
|
|
->getSession()
|
|
|
|
->getPage()
|
|
|
|
->findField($this->fixStepArgument($select));
|
|
|
|
|
|
|
|
// If field is visible then select it as per normal
|
|
|
|
if ($field && $field->isVisible()) {
|
|
|
|
parent::selectOption($select, $option);
|
|
|
|
} else {
|
|
|
|
$this->selectOptionWithJavascript($select, $option);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Selects option in select field with specified id|name|label|value using javascript
|
|
|
|
* This method uses javascript to allow selection of options that may be
|
|
|
|
* overridden by javascript libraries, and thus hide the element.
|
|
|
|
*
|
|
|
|
* @When /^(?:|I )select "(?P<option>(?:[^"]|\\")*)" from "(?P<select>(?:[^"]|\\")*)" with javascript$/
|
2014-08-02 08:30:27 +02:00
|
|
|
* @param string $select
|
|
|
|
* @param string $option
|
|
|
|
* @throws ElementNotFoundException
|
2016-08-10 03:35:13 +02:00
|
|
|
*/
|
|
|
|
public function selectOptionWithJavascript($select, $option)
|
|
|
|
{
|
|
|
|
$select = $this->fixStepArgument($select);
|
|
|
|
$option = $this->fixStepArgument($option);
|
|
|
|
$page = $this->getSession()->getPage();
|
|
|
|
|
|
|
|
// Find field
|
|
|
|
$field = $page->findField($select);
|
|
|
|
if (null === $field) {
|
|
|
|
throw new ElementNotFoundException($this->getSession(), 'form field', 'id|name|label|value', $select);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find option
|
|
|
|
$opt = $field->find('named', array(
|
2014-08-02 08:30:27 +02:00
|
|
|
'option', $this->getXpathEscaper()->escapeLiteral($option)
|
2016-08-10 03:35:13 +02:00
|
|
|
));
|
|
|
|
if (null === $opt) {
|
|
|
|
throw new ElementNotFoundException($this->getSession(), 'select option', 'value|text', $option);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Merge new option in with old handling both multiselect and single select
|
|
|
|
$value = $field->getValue();
|
|
|
|
$newValue = $opt->getAttribute('value');
|
|
|
|
if (is_array($value)) {
|
|
|
|
if (!in_array($newValue, $value)) {
|
|
|
|
$value[] = $newValue;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$value = $newValue;
|
|
|
|
}
|
|
|
|
$valueEncoded = json_encode($value);
|
|
|
|
|
|
|
|
// Inject this value via javascript
|
|
|
|
$fieldID = $field->getAttribute('ID');
|
|
|
|
$script = <<<EOS
|
2014-05-29 23:04:18 +02:00
|
|
|
(function($) {
|
|
|
|
$("#$fieldID")
|
|
|
|
.val($valueEncoded)
|
2014-06-19 07:14:06 +02:00
|
|
|
.change()
|
2014-05-29 23:04:18 +02:00
|
|
|
.trigger('liszt:updated')
|
|
|
|
.trigger('chosen:updated');
|
|
|
|
})(jQuery);
|
|
|
|
EOS;
|
2016-08-10 03:35:13 +02:00
|
|
|
$this->getSession()->getDriver()->executeScript($script);
|
|
|
|
}
|
2012-11-09 17:34:24 +01:00
|
|
|
}
|