2012-11-09 17:34:24 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace SilverStripe\BehatExtension\Context;
|
|
|
|
|
2021-11-08 02:29:05 +01:00
|
|
|
use Exception;
|
|
|
|
use InvalidArgumentException;
|
2014-08-02 08:30:27 +02:00
|
|
|
use Behat\Behat\Context\Context;
|
2021-11-08 02:29:05 +01:00
|
|
|
use Behat\Behat\Context\Environment\InitializedContextEnvironment;
|
2014-08-02 08:30:27 +02:00
|
|
|
use Behat\Behat\Definition\Call;
|
|
|
|
use Behat\Behat\Hook\Scope\AfterScenarioScope;
|
|
|
|
use Behat\Behat\Hook\Scope\AfterStepScope;
|
2021-11-08 02:29:05 +01:00
|
|
|
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
|
2014-08-02 08:30:27 +02:00
|
|
|
use Behat\Behat\Hook\Scope\BeforeStepScope;
|
2023-12-22 01:20:03 +01:00
|
|
|
use Behat\Mink\Driver\Selenium2Driver;
|
2017-01-10 02:36:18 +01:00
|
|
|
use Behat\Mink\Element\NodeElement;
|
2021-11-08 02:29:05 +01:00
|
|
|
use Behat\Mink\Exception\ElementNotFoundException;
|
2017-01-12 03:12:29 +01:00
|
|
|
use Behat\Mink\Session;
|
2014-08-02 08:30:27 +02:00
|
|
|
use Behat\Testwork\Tester\Result\TestResult;
|
2017-09-08 04:49:50 +02:00
|
|
|
use Facebook\WebDriver\Exception\WebDriverException;
|
|
|
|
use Facebook\WebDriver\WebDriver;
|
|
|
|
use Facebook\WebDriver\WebDriverAlert;
|
|
|
|
use Facebook\WebDriver\WebDriverExpectedCondition;
|
2021-11-08 02:29:05 +01:00
|
|
|
use Facebook\WebDriver\WebDriverKeys;
|
2021-10-27 06:14:44 +02:00
|
|
|
use PHPUnit\Framework\Assert;
|
2023-01-20 02:28:38 +01:00
|
|
|
use PHPUnit\Framework\ExpectationFailedException;
|
2016-09-01 06:22:47 +02:00
|
|
|
use SilverStripe\Assets\File;
|
|
|
|
use SilverStripe\Assets\Filesystem;
|
2017-04-22 06:27:44 +02:00
|
|
|
use SilverStripe\BehatExtension\Utility\StepHelper;
|
2021-11-08 02:29:05 +01:00
|
|
|
use SilverStripe\BehatExtension\Utility\DebugTools;
|
2017-09-08 04:49:50 +02:00
|
|
|
use SilverStripe\MinkFacebookWebDriver\FacebookWebDriver;
|
2012-11-09 17:34:24 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* BasicContext
|
|
|
|
*
|
|
|
|
* Context used to define generic steps like following anchors or pressing buttons.
|
|
|
|
* Handles timeouts.
|
|
|
|
* Handles redirections.
|
|
|
|
* Handles AJAX enabled links, buttons and forms - jQuery is assumed.
|
|
|
|
*/
|
2014-08-02 08:30:27 +02:00
|
|
|
class BasicContext implements Context
|
2012-11-09 17:34:24 +01:00
|
|
|
{
|
2014-08-02 08:30:27 +02:00
|
|
|
use MainContextAwareTrait;
|
2017-04-22 06:27:44 +02:00
|
|
|
use StepHelper;
|
2021-11-08 02:29:05 +01:00
|
|
|
use DebugTools;
|
2012-11-09 17:34:24 +01:00
|
|
|
|
2014-10-03 06:30:55 +02:00
|
|
|
/**
|
2016-08-10 03:35:13 +02:00
|
|
|
* Date format in date() syntax
|
2014-08-02 08:30:27 +02:00
|
|
|
*
|
|
|
|
* @var string
|
2016-08-10 03:35:13 +02:00
|
|
|
*/
|
|
|
|
protected $dateFormat = 'Y-m-d';
|
2013-11-15 14:05:28 +01:00
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
/**
|
|
|
|
* Time format in date() syntax
|
|
|
|
* @var String
|
|
|
|
*/
|
|
|
|
protected $timeFormat = 'H:i:s';
|
2013-11-15 14:05:28 +01:00
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
/**
|
|
|
|
* Date/time format in date() syntax
|
|
|
|
* @var String
|
|
|
|
*/
|
|
|
|
protected $datetimeFormat = 'Y-m-d H:i:s';
|
2013-11-15 14:05:28 +01:00
|
|
|
|
2021-11-08 02:29:05 +01:00
|
|
|
/**
|
|
|
|
* @var FixtureContext
|
|
|
|
*/
|
|
|
|
protected $fixtureContext = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the fixture context of the current module
|
|
|
|
*
|
|
|
|
* @BeforeScenario
|
|
|
|
*/
|
|
|
|
public function gatherContexts(BeforeScenarioScope $scope): void
|
|
|
|
{
|
|
|
|
/** @var InitializedContextEnvironment $environment */
|
|
|
|
$environment = $scope->getEnvironment();
|
|
|
|
|
|
|
|
// Find the FixtureContext defined in behat.yml
|
|
|
|
$subClasses = $this->getSubclassesOf(FixtureContext::class);
|
|
|
|
foreach ($subClasses as $class) {
|
|
|
|
if (!$environment->hasContextClass($class)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
$this->fixtureContext = $environment->getContext($class);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// Fallback to base FixtureClass
|
|
|
|
if (!$this->fixtureContext && $environment->hasContextClass(FixtureContext::class)) {
|
|
|
|
$this->fixtureContext = $environment->getContext(FixtureContext::class);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the subclasses of a class
|
|
|
|
*/
|
|
|
|
private function getSubclassesOf($parent): array
|
|
|
|
{
|
|
|
|
$result = [];
|
|
|
|
foreach (get_declared_classes() as $class) {
|
2022-04-13 07:37:24 +02:00
|
|
|
if (is_subclass_of($class, $parent ?? '')) {
|
2021-11-08 02:29:05 +01:00
|
|
|
$result[] = $class;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
/**
|
|
|
|
* Get Mink session from MinkContext
|
|
|
|
*
|
2016-09-01 06:22:47 +02:00
|
|
|
* @param string $name
|
2017-01-12 03:12:29 +01:00
|
|
|
* @return Session
|
2016-08-10 03:35:13 +02:00
|
|
|
*/
|
|
|
|
public function getSession($name = null)
|
|
|
|
{
|
2017-01-12 03:12:29 +01:00
|
|
|
/** @var SilverStripeContext $context */
|
|
|
|
$context = $this->getMainContext();
|
|
|
|
return $context->getSession($name);
|
2016-08-10 03:35:13 +02:00
|
|
|
}
|
2012-11-09 17:34:24 +01:00
|
|
|
|
2014-08-02 08:30:27 +02:00
|
|
|
/**
|
|
|
|
* @AfterStep
|
2014-10-03 06:30:55 +02:00
|
|
|
*
|
|
|
|
* Excluding scenarios with @modal tag is required,
|
|
|
|
* because modal dialogs stop any JS interaction
|
2014-08-02 08:30:27 +02:00
|
|
|
*
|
|
|
|
* @param AfterStepScope $event
|
2014-10-03 06:30:55 +02:00
|
|
|
*/
|
2014-08-02 08:30:27 +02:00
|
|
|
public function appendErrorHandlerBeforeStep(AfterStepScope $event)
|
2016-08-10 03:35:13 +02:00
|
|
|
{
|
2014-08-02 08:30:27 +02:00
|
|
|
// Manually exclude @modal
|
|
|
|
if ($this->stepHasTag($event, 'modal')) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
try {
|
|
|
|
$javascript = <<<JS
|
2014-08-08 06:09:13 +02:00
|
|
|
window.onerror = function(message, file, line, column, error) {
|
2014-10-03 06:30:55 +02:00
|
|
|
var body = document.getElementsByTagName('body')[0];
|
2014-08-08 06:09:13 +02:00
|
|
|
var msg = message + " in " + file + ":" + line + ":" + column;
|
|
|
|
if(error !== undefined && error.stack !== undefined) {
|
|
|
|
msg += "\\nSTACKTRACE:\\n" + error.stack;
|
|
|
|
}
|
2014-08-02 08:30:27 +02:00
|
|
|
body.setAttribute('data-jserrors', '[captured JavaScript error] ' + msg);
|
|
|
|
};
|
2012-11-09 17:34:24 +01:00
|
|
|
if ('undefined' !== typeof window.jQuery) {
|
2014-10-03 06:30:55 +02:00
|
|
|
window.jQuery('body').ajaxError(function(event, jqxhr, settings, exception) {
|
2014-08-02 08:30:27 +02:00
|
|
|
if ('abort' === exception) {
|
|
|
|
return;
|
|
|
|
}
|
2014-10-03 06:30:55 +02:00
|
|
|
window.onerror(event.type + ': ' + settings.type + ' ' + settings.url + ' ' + exception + ' ' + jqxhr.responseText);
|
|
|
|
});
|
2012-11-09 17:34:24 +01:00
|
|
|
}
|
|
|
|
JS;
|
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
$this->getSession()->executeScript($javascript);
|
2014-08-02 08:30:27 +02:00
|
|
|
} catch (WebDriverException $e) {
|
2016-08-10 03:35:13 +02:00
|
|
|
$this->logException($e);
|
2015-09-10 04:32:09 +02:00
|
|
|
}
|
2014-10-03 06:30:55 +02:00
|
|
|
}
|
2014-09-30 15:20:40 +02:00
|
|
|
|
2014-10-03 06:30:55 +02:00
|
|
|
/**
|
2014-08-02 08:30:27 +02:00
|
|
|
* @AfterStep
|
2014-10-03 06:30:55 +02:00
|
|
|
*
|
|
|
|
* Excluding scenarios with @modal tag is required,
|
|
|
|
* because modal dialogs stop any JS interaction
|
2014-08-02 08:30:27 +02:00
|
|
|
*
|
|
|
|
* @param AfterStepScope $event
|
2014-10-03 06:30:55 +02:00
|
|
|
*/
|
2014-08-02 08:30:27 +02:00
|
|
|
public function readErrorHandlerAfterStep(AfterStepScope $event)
|
2016-08-10 03:35:13 +02:00
|
|
|
{
|
2014-08-02 08:30:27 +02:00
|
|
|
// Manually exclude @modal
|
|
|
|
if ($this->stepHasTag($event, 'modal')) {
|
|
|
|
return;
|
|
|
|
}
|
2016-08-10 03:35:13 +02:00
|
|
|
try {
|
|
|
|
$page = $this->getSession()->getPage();
|
|
|
|
|
|
|
|
$jserrors = $page->find('xpath', '//body[@data-jserrors]');
|
|
|
|
if (null !== $jserrors) {
|
|
|
|
$this->takeScreenshot($event);
|
2021-11-08 02:29:05 +01:00
|
|
|
$this->logMessage($jserrors->getAttribute('data-jserrors'));
|
2016-08-10 03:35:13 +02:00
|
|
|
}
|
2016-08-03 08:36:44 +02:00
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
$javascript = <<<JS
|
2014-09-04 03:01:39 +02:00
|
|
|
if ('undefined' !== typeof window.jQuery) {
|
2014-09-04 03:01:39 +02:00
|
|
|
window.jQuery(document).ready(function() {
|
|
|
|
window.jQuery('body').removeAttr('data-jserrors');
|
2014-09-04 03:01:39 +02:00
|
|
|
});
|
|
|
|
}
|
2012-11-09 17:34:24 +01:00
|
|
|
JS;
|
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
$this->getSession()->executeScript($javascript);
|
2014-08-02 08:30:27 +02:00
|
|
|
} catch (WebDriverException $e) {
|
2016-08-10 03:35:13 +02:00
|
|
|
$this->logException($e);
|
2015-09-10 04:32:09 +02:00
|
|
|
}
|
2014-10-03 06:30:55 +02:00
|
|
|
}
|
2014-09-30 15:20:40 +02:00
|
|
|
|
2014-10-03 06:30:55 +02:00
|
|
|
/**
|
|
|
|
* Hook into jQuery ajaxStart, ajaxSuccess and ajaxComplete events.
|
|
|
|
* Prepare __ajaxStatus() functions and attach them to these handlers.
|
|
|
|
* Event handlers are removed after one run.
|
|
|
|
*
|
|
|
|
* @BeforeStep
|
2014-08-02 08:30:27 +02:00
|
|
|
* @param BeforeStepScope $event
|
2014-10-03 06:30:55 +02:00
|
|
|
*/
|
2014-08-02 08:30:27 +02:00
|
|
|
public function handleAjaxBeforeStep(BeforeStepScope $event)
|
2016-08-10 03:35:13 +02:00
|
|
|
{
|
2014-08-02 08:30:27 +02:00
|
|
|
// Manually exclude @modal
|
|
|
|
if ($this->stepHasTag($event, 'modal')) {
|
|
|
|
return;
|
|
|
|
}
|
2016-08-10 03:35:13 +02:00
|
|
|
try {
|
|
|
|
$ajaxEnabledSteps = $this->getMainContext()->getAjaxSteps();
|
2022-04-13 07:37:24 +02:00
|
|
|
$ajaxEnabledSteps = implode('|', array_filter($ajaxEnabledSteps ?? []));
|
2016-08-10 03:35:13 +02:00
|
|
|
|
2022-04-13 07:37:24 +02:00
|
|
|
if (empty($ajaxEnabledSteps) || !preg_match('/(' . $ajaxEnabledSteps . ')/i', $event->getStep()->getText() ?? '')) {
|
2016-08-10 03:35:13 +02:00
|
|
|
return;
|
|
|
|
}
|
2014-09-30 15:20:40 +02:00
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
$javascript = <<<JS
|
2014-05-07 00:46:59 +02:00
|
|
|
if ('undefined' !== typeof window.jQuery && 'undefined' !== typeof window.jQuery.fn.on) {
|
2014-10-03 06:30:55 +02:00
|
|
|
window.jQuery(document).on('ajaxStart.ss.test.behaviour', function(){
|
|
|
|
window.__ajaxStatus = function() {
|
|
|
|
return 'waiting';
|
|
|
|
};
|
|
|
|
});
|
|
|
|
window.jQuery(document).on('ajaxComplete.ss.test.behaviour', function(e, jqXHR){
|
|
|
|
if (null === jqXHR.getResponseHeader('X-ControllerURL')) {
|
|
|
|
window.__ajaxStatus = function() {
|
|
|
|
return 'no ajax';
|
|
|
|
};
|
|
|
|
}
|
|
|
|
});
|
|
|
|
window.jQuery(document).on('ajaxSuccess.ss.test.behaviour', function(e, jqXHR){
|
|
|
|
if (null === jqXHR.getResponseHeader('X-ControllerURL')) {
|
|
|
|
window.__ajaxStatus = function() {
|
|
|
|
return 'success';
|
|
|
|
};
|
|
|
|
}
|
|
|
|
});
|
2012-11-09 17:34:24 +01:00
|
|
|
}
|
|
|
|
JS;
|
2016-08-10 03:35:13 +02:00
|
|
|
$this->getSession()->wait(500); // give browser a chance to process and render response
|
|
|
|
$this->getSession()->executeScript($javascript);
|
2014-08-02 08:30:27 +02:00
|
|
|
} catch (WebDriverException $e) {
|
2016-08-10 03:35:13 +02:00
|
|
|
$this->logException($e);
|
2015-09-10 04:32:09 +02:00
|
|
|
}
|
2014-10-03 06:30:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Wait for the __ajaxStatus()to return anything but 'waiting'.
|
|
|
|
* Don't wait longer than 5 seconds.
|
|
|
|
*
|
|
|
|
* Don't unregister handler if we're dealing with modal windows
|
|
|
|
*
|
2014-08-02 08:30:27 +02:00
|
|
|
* @AfterStep
|
|
|
|
* @param AfterStepScope $event
|
2014-10-03 06:30:55 +02:00
|
|
|
*/
|
2014-08-02 08:30:27 +02:00
|
|
|
public function handleAjaxAfterStep(AfterStepScope $event)
|
2016-08-10 03:35:13 +02:00
|
|
|
{
|
2014-08-02 08:30:27 +02:00
|
|
|
// Manually exclude @modal
|
|
|
|
if ($this->stepHasTag($event, 'modal')) {
|
|
|
|
return;
|
|
|
|
}
|
2016-08-10 03:35:13 +02:00
|
|
|
try {
|
|
|
|
$ajaxEnabledSteps = $this->getMainContext()->getAjaxSteps();
|
2022-04-13 07:37:24 +02:00
|
|
|
$ajaxEnabledSteps = implode('|', array_filter($ajaxEnabledSteps ?? []));
|
2016-08-10 03:35:13 +02:00
|
|
|
|
2022-04-13 07:37:24 +02:00
|
|
|
if (empty($ajaxEnabledSteps) || !preg_match('/(' . $ajaxEnabledSteps . ')/i', $event->getStep()->getText() ?? '')) {
|
2016-08-10 03:35:13 +02:00
|
|
|
return;
|
|
|
|
}
|
2014-09-30 15:20:40 +02:00
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
$this->handleAjaxTimeout();
|
2014-09-30 15:20:40 +02:00
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
$javascript = <<<JS
|
2014-05-07 00:46:59 +02:00
|
|
|
if ('undefined' !== typeof window.jQuery && 'undefined' !== typeof window.jQuery.fn.off) {
|
2012-11-09 17:34:24 +01:00
|
|
|
window.jQuery(document).off('ajaxStart.ss.test.behaviour');
|
|
|
|
window.jQuery(document).off('ajaxComplete.ss.test.behaviour');
|
|
|
|
window.jQuery(document).off('ajaxSuccess.ss.test.behaviour');
|
|
|
|
}
|
|
|
|
JS;
|
2016-08-10 03:35:13 +02:00
|
|
|
$this->getSession()->executeScript($javascript);
|
2014-08-02 08:30:27 +02:00
|
|
|
} catch (WebDriverException $e) {
|
2016-08-10 03:35:13 +02:00
|
|
|
$this->logException($e);
|
2015-09-10 04:32:09 +02:00
|
|
|
}
|
2014-10-03 06:30:55 +02:00
|
|
|
}
|
2014-09-30 15:20:40 +02:00
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
public function handleAjaxTimeout()
|
|
|
|
{
|
2014-10-03 06:30:55 +02:00
|
|
|
$timeoutMs = $this->getMainContext()->getAjaxTimeout();
|
|
|
|
|
|
|
|
// Wait for an ajax request to complete, but only for a maximum of 5 seconds to avoid deadlocks
|
2016-08-10 03:35:13 +02:00
|
|
|
$this->getSession()->wait(
|
|
|
|
$timeoutMs,
|
2014-10-03 06:30:55 +02:00
|
|
|
"(typeof window.__ajaxStatus !== 'undefined' ? window.__ajaxStatus() : 'no ajax') !== 'waiting'"
|
|
|
|
);
|
|
|
|
|
|
|
|
// wait additional 100ms to allow DOM to update
|
|
|
|
$this->getSession()->wait(100);
|
|
|
|
}
|
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
/**
|
|
|
|
* Close modal dialog if test scenario fails on CMS page
|
|
|
|
*
|
|
|
|
* @AfterScenario
|
2014-08-02 08:30:27 +02:00
|
|
|
* @param AfterScenarioScope $event
|
2016-08-10 03:35:13 +02:00
|
|
|
*/
|
2014-08-02 08:30:27 +02:00
|
|
|
public function closeModalDialog(AfterScenarioScope $event)
|
2016-08-10 03:35:13 +02:00
|
|
|
{
|
2018-10-15 00:26:34 +02:00
|
|
|
$expectsUnsavedChangesModal = $this->stepHasTag($event, 'unsavedChanges');
|
2021-04-15 02:13:29 +02:00
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
try {
|
|
|
|
// Only for failed tests on CMS page
|
2018-10-15 00:26:34 +02:00
|
|
|
if ($expectsUnsavedChangesModal || $event->getTestResult()->getResultCode() === TestResult::FAILED) {
|
2016-08-10 03:35:13 +02:00
|
|
|
$cmsElement = $this->getSession()->getPage()->find('css', '.cms');
|
|
|
|
if ($cmsElement) {
|
|
|
|
try {
|
|
|
|
// Navigate away triggered by reloading the page
|
|
|
|
$this->getSession()->reload();
|
2017-09-08 04:49:50 +02:00
|
|
|
$this->getExpectedAlert()->accept();
|
2014-08-02 08:30:27 +02:00
|
|
|
} catch (WebDriverException $e) {
|
2016-08-10 03:35:13 +02:00
|
|
|
// no-op, alert might not be present
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-08-02 08:30:27 +02:00
|
|
|
} catch (WebDriverException $e) {
|
2016-08-10 03:35:13 +02:00
|
|
|
$this->logException($e);
|
|
|
|
}
|
|
|
|
}
|
2016-08-03 08:36:44 +02:00
|
|
|
|
2014-10-03 06:30:55 +02:00
|
|
|
/**
|
|
|
|
* Delete any created files and folders from assets directory
|
|
|
|
*
|
|
|
|
* @AfterScenario @assets
|
2014-08-02 08:30:27 +02:00
|
|
|
* @param AfterScenarioScope $event
|
2014-10-03 06:30:55 +02:00
|
|
|
*/
|
2014-08-02 08:30:27 +02:00
|
|
|
public function cleanAssetsAfterScenario(AfterScenarioScope $event)
|
2016-08-10 03:35:13 +02:00
|
|
|
{
|
2016-09-01 06:22:47 +02:00
|
|
|
foreach (File::get() as $file) {
|
2015-10-15 23:53:37 +02:00
|
|
|
$file->delete();
|
2014-10-03 06:30:55 +02:00
|
|
|
}
|
2016-09-01 06:22:47 +02:00
|
|
|
Filesystem::removeFolder(ASSETS_PATH, true);
|
2014-10-03 06:30:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @Given /^the page can't be found/
|
|
|
|
*/
|
2016-08-10 03:35:13 +02:00
|
|
|
public function stepPageCantBeFound()
|
|
|
|
{
|
2014-10-03 06:30:55 +02:00
|
|
|
$page = $this->getSession()->getPage();
|
2021-10-27 06:14:44 +02:00
|
|
|
Assert::assertTrue(
|
2014-10-03 06:30:55 +02:00
|
|
|
// Content from ErrorPage default record
|
|
|
|
$page->hasContent('Page not found')
|
2021-11-08 02:29:05 +01:00
|
|
|
// Generic ModelAsController message
|
|
|
|
|| $page->hasContent('The requested page could not be found')
|
2014-10-03 06:30:55 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @Given /^I wait (?:for )?([\d\.]+) second(?:s?)$/
|
2014-08-02 08:30:27 +02:00
|
|
|
*
|
|
|
|
* @param float $secs
|
2014-10-03 06:30:55 +02:00
|
|
|
*/
|
2016-08-10 03:35:13 +02:00
|
|
|
public function stepIWaitFor($secs)
|
|
|
|
{
|
2021-11-08 02:29:05 +01:00
|
|
|
$this->getSession()->wait((float)$secs * 1000);
|
2014-10-03 06:30:55 +02:00
|
|
|
}
|
2014-09-30 15:20:40 +02:00
|
|
|
|
2014-10-03 06:30:55 +02:00
|
|
|
/**
|
2017-01-10 02:36:18 +01:00
|
|
|
* Find visible button with the given text.
|
|
|
|
* Supports data-text-alternate property.
|
|
|
|
*
|
2014-08-02 08:30:27 +02:00
|
|
|
* @param string $title
|
2017-01-10 02:36:18 +01:00
|
|
|
* @return NodeElement|null
|
2014-10-03 06:30:55 +02:00
|
|
|
*/
|
2017-01-10 02:36:18 +01:00
|
|
|
protected function findNamedButton($title)
|
2016-08-10 03:35:13 +02:00
|
|
|
{
|
2014-10-03 06:30:55 +02:00
|
|
|
$page = $this->getSession()->getPage();
|
2017-01-10 02:36:18 +01:00
|
|
|
// See https://mathiasbynens.be/notes/css-escapes
|
2022-04-13 07:37:24 +02:00
|
|
|
$escapedTitle = addcslashes($title ?? '', '!"#$%&\'()*+,-./:;<=>?@[\]^`{|}~');
|
2014-10-03 06:30:55 +02:00
|
|
|
$matchedEl = null;
|
2017-01-10 02:36:18 +01:00
|
|
|
$searches = [
|
|
|
|
['named', ['link_or_button', "'{$title}'"]],
|
|
|
|
['css', "button[data-text-alternate='{$escapedTitle}']"],
|
|
|
|
];
|
|
|
|
foreach ($searches as list($type, $arg)) {
|
|
|
|
$buttons = $page->findAll($type, $arg);
|
2014-08-02 08:30:27 +02:00
|
|
|
/** @var NodeElement $button */
|
|
|
|
foreach ($buttons as $button) {
|
|
|
|
if ($button->isVisible()) {
|
|
|
|
return $button;
|
2017-01-10 02:36:18 +01:00
|
|
|
}
|
2016-08-10 03:35:13 +02:00
|
|
|
}
|
2014-10-03 06:30:55 +02:00
|
|
|
}
|
2017-01-10 02:36:18 +01:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Example: I should see a "Submit" button
|
|
|
|
* Example: I should not see a "Delete" button
|
|
|
|
*
|
|
|
|
* @Given /^I should( not? |\s*)see (?:a|an|the) "([^"]*)" button$/
|
2014-08-02 08:30:27 +02:00
|
|
|
* @param string $negative
|
|
|
|
* @param string $text
|
2017-01-10 02:36:18 +01:00
|
|
|
*/
|
|
|
|
public function iShouldSeeAButton($negative, $text)
|
|
|
|
{
|
2017-04-22 06:27:44 +02:00
|
|
|
$button = $this->findNamedButton($text);
|
2022-04-13 07:37:24 +02:00
|
|
|
if (trim($negative ?? '')) {
|
2021-10-27 06:14:44 +02:00
|
|
|
Assert::assertNull($button, sprintf('%s button found', $text));
|
2017-01-10 02:36:18 +01:00
|
|
|
} else {
|
2021-10-27 06:14:44 +02:00
|
|
|
Assert::assertNotNull($button, sprintf('%s button not found', $text));
|
2017-01-10 02:36:18 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @Given /^I press the "([^"]*)" button$/
|
2014-08-02 08:30:27 +02:00
|
|
|
* @param string $text
|
2017-01-10 02:36:18 +01:00
|
|
|
*/
|
|
|
|
public function stepIPressTheButton($text)
|
|
|
|
{
|
|
|
|
$button = $this->findNamedButton($text);
|
2021-10-27 06:14:44 +02:00
|
|
|
Assert::assertNotNull($button, "{$text} button not found");
|
2017-01-10 02:36:18 +01:00
|
|
|
$button->click();
|
2014-10-03 06:30:55 +02:00
|
|
|
}
|
|
|
|
|
2019-09-24 03:58:15 +02:00
|
|
|
/**
|
2019-09-24 05:29:11 +02:00
|
|
|
* @Given /^I press the "([^"]*)" buttons$/
|
2019-09-24 03:58:15 +02:00
|
|
|
* @param string $text A list of button names can be provided by seperating the entries with the | character.
|
|
|
|
*/
|
2019-09-24 05:29:11 +02:00
|
|
|
public function stepIPressTheButtons($text)
|
2019-09-24 03:58:15 +02:00
|
|
|
{
|
2022-04-13 07:37:24 +02:00
|
|
|
$buttonNames = explode('|', $text ?? '');
|
2019-09-24 03:58:15 +02:00
|
|
|
foreach ($buttonNames as $name) {
|
2022-04-13 07:37:24 +02:00
|
|
|
$button = $this->findNamedButton(trim($name ?? ''));
|
2019-09-24 03:58:15 +02:00
|
|
|
if ($button) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-27 06:14:44 +02:00
|
|
|
Assert::assertNotNull($button, "{$text} button not found");
|
2019-09-24 03:58:15 +02:00
|
|
|
$button->click();
|
|
|
|
}
|
|
|
|
|
2014-10-03 06:30:55 +02:00
|
|
|
/**
|
|
|
|
* Needs to be in single command to avoid "unexpected alert open" errors in Selenium.
|
|
|
|
* Example1: I press the "Remove current combo" button, confirming the dialog
|
|
|
|
* Example2: I follow the "Remove current combo" link, confirming the dialog
|
|
|
|
*
|
|
|
|
* @Given /^I (?:press|follow) the "([^"]*)" (?:button|link), confirming the dialog$/
|
2014-08-02 08:30:27 +02:00
|
|
|
* @param string $button
|
2014-10-03 06:30:55 +02:00
|
|
|
*/
|
2016-08-10 03:35:13 +02:00
|
|
|
public function stepIPressTheButtonConfirmingTheDialog($button)
|
|
|
|
{
|
2014-10-03 06:30:55 +02:00
|
|
|
$this->stepIPressTheButton($button);
|
|
|
|
$this->iConfirmTheDialog();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Needs to be in single command to avoid "unexpected alert open" errors in Selenium.
|
|
|
|
* Example: I follow the "Remove current combo" link, dismissing the dialog
|
|
|
|
*
|
|
|
|
* @Given /^I (?:press|follow) the "([^"]*)" (?:button|link), dismissing the dialog$/
|
2014-08-02 08:30:27 +02:00
|
|
|
* @param string $button
|
2014-10-03 06:30:55 +02:00
|
|
|
*/
|
2016-08-10 03:35:13 +02:00
|
|
|
public function stepIPressTheButtonDismissingTheDialog($button)
|
|
|
|
{
|
|
|
|
$this->stepIPressTheButton($button);
|
|
|
|
$this->iDismissTheDialog();
|
2014-10-03 06:30:55 +02:00
|
|
|
}
|
2014-09-30 15:20:40 +02:00
|
|
|
|
2021-04-15 02:13:29 +02:00
|
|
|
/**
|
|
|
|
* @Given /^I click on the "([^"]+)" element$/
|
|
|
|
* @param string $selector
|
|
|
|
*/
|
|
|
|
public function iClickOnTheElement($selector)
|
|
|
|
{
|
|
|
|
$page = $this->getMainContext()->getSession()->getPage();
|
|
|
|
$element = $page->find('css', $selector);
|
2021-10-27 06:14:44 +02:00
|
|
|
Assert::assertNotNull($element, sprintf('Element %s not found', $selector));
|
2021-04-15 02:13:29 +02:00
|
|
|
$element->click();
|
|
|
|
}
|
|
|
|
|
2021-04-15 03:37:09 +02:00
|
|
|
/**
|
|
|
|
* Needs to be in single command to avoid "unexpected alert open" errors in Selenium.
|
|
|
|
*
|
|
|
|
* @When /^I click on the "([^"]+)" element, confirming the dialog$/
|
|
|
|
* @param $selector
|
|
|
|
*/
|
|
|
|
public function iClickOnTheElementConfirmingTheDialog($selector)
|
|
|
|
{
|
|
|
|
$this->iClickOnTheElement($selector);
|
|
|
|
$this->iConfirmTheDialog();
|
|
|
|
}
|
|
|
|
|
2014-10-03 06:30:55 +02:00
|
|
|
/**
|
2015-10-22 05:13:47 +02:00
|
|
|
* @Given /^I (click|double click) "([^"]*)" in the "([^"]*)" element$/
|
2014-08-02 08:30:27 +02:00
|
|
|
* @param string $clickType
|
|
|
|
* @param string $text
|
|
|
|
* @param string $selector
|
2014-10-03 06:30:55 +02:00
|
|
|
*/
|
2016-08-10 03:35:13 +02:00
|
|
|
public function iClickInTheElement($clickType, $text, $selector)
|
|
|
|
{
|
2015-10-22 05:13:47 +02:00
|
|
|
$clickTypeMap = array(
|
|
|
|
"double click" => "doubleclick",
|
|
|
|
"click" => "click"
|
|
|
|
);
|
2014-10-03 06:30:55 +02:00
|
|
|
$page = $this->getSession()->getPage();
|
|
|
|
$parentElement = $page->find('css', $selector);
|
2021-10-27 06:14:44 +02:00
|
|
|
Assert::assertNotNull($parentElement, sprintf('"%s" element not found', $selector));
|
2014-10-03 06:30:55 +02:00
|
|
|
$element = $parentElement->find('xpath', sprintf('//*[count(*)=0 and contains(.,"%s")]', $text));
|
2021-10-27 06:14:44 +02:00
|
|
|
Assert::assertNotNull($element, sprintf('"%s" not found', $text));
|
2015-10-22 05:13:47 +02:00
|
|
|
$clickTypeFn = $clickTypeMap[$clickType];
|
|
|
|
$element->$clickTypeFn();
|
2014-10-03 06:30:55 +02:00
|
|
|
}
|
2016-08-03 08:36:44 +02:00
|
|
|
|
2015-10-22 05:27:48 +02:00
|
|
|
/**
|
2014-08-02 08:30:27 +02:00
|
|
|
* Needs to be in single command to avoid "unexpected alert open" errors in Selenium.
|
|
|
|
* Example: I click "Delete" in the ".actions" element, confirming the dialog
|
|
|
|
*
|
|
|
|
* @Given /^I (click|double click) "([^"]*)" in the "([^"]*)" element, confirming the dialog$/
|
|
|
|
* @param string $clickType
|
|
|
|
* @param string $text
|
|
|
|
* @param string $selector
|
|
|
|
*/
|
2016-08-10 03:35:13 +02:00
|
|
|
public function iClickInTheElementConfirmingTheDialog($clickType, $text, $selector)
|
|
|
|
{
|
|
|
|
$this->iClickInTheElement($clickType, $text, $selector);
|
|
|
|
$this->iConfirmTheDialog();
|
|
|
|
}
|
2014-08-02 08:30:27 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Needs to be in single command to avoid "unexpected alert open" errors in Selenium.
|
|
|
|
* Example: I click "Delete" in the ".actions" element, dismissing the dialog
|
|
|
|
*
|
|
|
|
* @Given /^I (click|double click) "([^"]*)" in the "([^"]*)" element, dismissing the dialog$/
|
|
|
|
* @param string $clickType
|
|
|
|
* @param string $text
|
|
|
|
* @param string $selector
|
|
|
|
*/
|
2016-08-10 03:35:13 +02:00
|
|
|
public function iClickInTheElementDismissingTheDialog($clickType, $text, $selector)
|
|
|
|
{
|
|
|
|
$this->iClickInTheElement($clickType, $text, $selector);
|
|
|
|
$this->iDismissTheDialog();
|
|
|
|
}
|
2014-09-30 15:20:40 +02:00
|
|
|
|
2023-09-12 03:03:49 +02:00
|
|
|
/**
|
|
|
|
* @Then /^the "([^"]+)" element "([^"]+)" attribute should be "([^"]*)"$/
|
|
|
|
*/
|
|
|
|
public function theElementAttributeShouldBe($selector, $attribute, $value)
|
|
|
|
{
|
|
|
|
$page = $this->getSession()->getPage();
|
|
|
|
$element = $page->find('css', $selector);
|
|
|
|
Assert::assertNotNull($element, sprintf('Element %s not found', $selector));
|
|
|
|
Assert::assertEquals($value, $element->getAttribute($attribute));
|
|
|
|
}
|
|
|
|
|
2017-01-12 03:12:29 +01:00
|
|
|
/**
|
|
|
|
* @Given /^I see the text "([^"]+)" in the alert$/
|
2014-08-02 08:30:27 +02:00
|
|
|
* @param string $expected
|
2017-01-12 03:12:29 +01:00
|
|
|
*/
|
|
|
|
public function iSeeTheDialogText($expected)
|
|
|
|
{
|
2023-12-22 01:20:03 +01:00
|
|
|
$driver = $this->getSession()->getDriver();
|
|
|
|
if ($driver instanceof Selenium2Driver) {
|
|
|
|
$text = $driver->getWebDriverSession()->getAlert_text();
|
|
|
|
} else {
|
|
|
|
$text = $this->getExpectedAlert()->getText();
|
|
|
|
}
|
2021-10-27 06:14:44 +02:00
|
|
|
Assert::assertStringContainsString($expected, $text);
|
2017-01-12 03:12:29 +01:00
|
|
|
}
|
|
|
|
|
2014-10-03 06:30:55 +02:00
|
|
|
/**
|
|
|
|
* @Given /^I type "([^"]*)" into the dialog$/
|
2014-08-02 08:30:27 +02:00
|
|
|
* @param string $data
|
2014-10-03 06:30:55 +02:00
|
|
|
*/
|
2016-08-10 03:35:13 +02:00
|
|
|
public function iTypeIntoTheDialog($data)
|
|
|
|
{
|
2017-09-08 04:49:50 +02:00
|
|
|
$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();
|
2014-10-03 06:30:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @Given /^I confirm the dialog$/
|
|
|
|
*/
|
2016-08-10 03:35:13 +02:00
|
|
|
public function iConfirmTheDialog()
|
|
|
|
{
|
2023-12-22 01:20:03 +01:00
|
|
|
$driver = $this->getSession()->getDriver();
|
|
|
|
if ($driver instanceof Selenium2Driver) {
|
|
|
|
$driver->getWebDriverSession()->accept_alert();
|
|
|
|
} else {
|
|
|
|
$session = $this->getWebDriverSession();
|
|
|
|
$session->wait()->until(
|
|
|
|
WebDriverExpectedCondition::alertIsPresent(),
|
|
|
|
"Alert is expected"
|
|
|
|
);
|
|
|
|
$session->switchTo()->alert()->accept();
|
|
|
|
}
|
2014-10-03 06:30:55 +02:00
|
|
|
$this->handleAjaxTimeout();
|
|
|
|
}
|
2014-09-30 15:20:40 +02:00
|
|
|
|
2014-10-03 06:30:55 +02:00
|
|
|
/**
|
|
|
|
* @Given /^I dismiss the dialog$/
|
|
|
|
*/
|
2016-08-10 03:35:13 +02:00
|
|
|
public function iDismissTheDialog()
|
|
|
|
{
|
2023-12-22 01:20:03 +01:00
|
|
|
$driver = $this->getSession()->getDriver();
|
|
|
|
if ($driver instanceof Selenium2Driver) {
|
|
|
|
$driver->getWebDriverSession()->dismiss_alert();
|
|
|
|
} else {
|
|
|
|
$this->getExpectedAlert()->dismiss();
|
|
|
|
}
|
2014-10-03 06:30:55 +02:00
|
|
|
$this->handleAjaxTimeout();
|
|
|
|
}
|
2014-09-30 15:20:40 +02:00
|
|
|
|
2014-08-02 08:30:27 +02:00
|
|
|
/**
|
|
|
|
* Get Selenium webdriver session.
|
2017-09-08 04:49:50 +02:00
|
|
|
* Note: Will fail if current driver isn't FacebookWebDriver
|
2014-08-02 08:30:27 +02:00
|
|
|
*
|
2017-09-08 04:49:50 +02:00
|
|
|
* @return WebDriver
|
2014-08-02 08:30:27 +02:00
|
|
|
*/
|
|
|
|
protected function getWebDriverSession()
|
|
|
|
{
|
|
|
|
$driver = $this->getSession()->getDriver();
|
2021-11-08 02:29:05 +01:00
|
|
|
if (!$driver instanceof FacebookWebDriver) {
|
2017-09-08 04:49:50 +02:00
|
|
|
throw new InvalidArgumentException("Only supported for FacebookWebDriver");
|
2014-08-02 08:30:27 +02:00
|
|
|
}
|
2017-09-08 04:49:50 +02:00
|
|
|
return $driver->getWebDriver();
|
2014-08-02 08:30:27 +02:00
|
|
|
}
|
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
/**
|
|
|
|
* Select an individual input from within a group, matched by the top-most label.
|
|
|
|
*
|
|
|
|
* @Given /^I select "([^"]*)" from "([^"]*)" input group$/
|
2014-08-02 08:30:27 +02:00
|
|
|
* @param string $value
|
|
|
|
* @param string $labelText
|
2016-08-10 03:35:13 +02:00
|
|
|
*/
|
|
|
|
public function iSelectFromInputGroup($value, $labelText)
|
|
|
|
{
|
|
|
|
$page = $this->getSession()->getPage();
|
|
|
|
$parent = null;
|
2013-11-28 11:03:38 +01:00
|
|
|
|
2014-08-02 08:30:27 +02:00
|
|
|
/** @var NodeElement $label */
|
2016-08-10 03:35:13 +02:00
|
|
|
foreach ($page->findAll('css', 'label') as $label) {
|
|
|
|
if ($label->getText() == $labelText) {
|
|
|
|
$parent = $label->getParent();
|
|
|
|
}
|
|
|
|
}
|
2013-11-28 11:03:38 +01:00
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
if (!$parent) {
|
2017-09-08 04:49:50 +02:00
|
|
|
throw new InvalidArgumentException(sprintf('Input group with label "%s" cannot be found', $labelText));
|
2016-08-10 03:35:13 +02:00
|
|
|
}
|
2013-11-28 11:03:38 +01:00
|
|
|
|
2014-08-02 08:30:27 +02:00
|
|
|
/** @var NodeElement $option */
|
2016-08-10 03:35:13 +02:00
|
|
|
foreach ($parent->findAll('css', 'label') as $option) {
|
|
|
|
if ($option->getText() == $value) {
|
|
|
|
$input = null;
|
2016-07-28 06:51:33 +02:00
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
// First, look for inputs referenced by the "for" element on this label
|
|
|
|
$for = $option->getAttribute('for');
|
|
|
|
if ($for) {
|
|
|
|
$input = $parent->findById($for);
|
|
|
|
}
|
2016-07-28 06:51:33 +02:00
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
// Otherwise look for inputs _inside_ the label
|
|
|
|
if (!$input) {
|
|
|
|
$input = $option->find('css', 'input');
|
|
|
|
}
|
2013-11-28 11:03:38 +01:00
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
if (!$input) {
|
2017-09-08 04:49:50 +02:00
|
|
|
throw new InvalidArgumentException(sprintf('Input "%s" cannot be found', $value));
|
2016-08-10 03:35:13 +02:00
|
|
|
}
|
2013-11-28 11:03:38 +01:00
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
$this->getSession()->getDriver()->click($input->getXPath());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-11-28 11:03:38 +01:00
|
|
|
|
2014-10-03 06:30:55 +02:00
|
|
|
/**
|
|
|
|
* Pauses the scenario until the user presses a key. Useful when debugging a scenario.
|
|
|
|
*
|
|
|
|
* @Then /^(?:|I )put a breakpoint$/
|
|
|
|
*/
|
2016-08-10 03:35:13 +02:00
|
|
|
public function iPutABreakpoint()
|
|
|
|
{
|
2014-10-03 06:30:55 +02:00
|
|
|
fwrite(STDOUT, "\033[s \033[93m[Breakpoint] Press \033[1;93m[RETURN]\033[0;93m to continue...\033[0m");
|
2016-08-10 03:35:13 +02:00
|
|
|
while (fgets(STDIN, 1024) == '') {
|
2017-09-08 04:49:50 +02:00
|
|
|
// noop
|
2016-08-10 03:35:13 +02:00
|
|
|
}
|
2014-10-03 06:30:55 +02:00
|
|
|
fwrite(STDOUT, "\033[u");
|
2014-02-12 03:09:19 +01:00
|
|
|
|
2014-10-03 06:30:55 +02:00
|
|
|
return;
|
|
|
|
}
|
2014-02-12 03:09:19 +01:00
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
/**
|
|
|
|
* Transforms relative time statements compatible with strtotime().
|
|
|
|
* Example: "time of 1 hour ago" might return "22:00:00" if its currently "23:00:00".
|
|
|
|
* Customize through {@link setTimeFormat()}.
|
|
|
|
*
|
|
|
|
* @Transform /^(?:(the|a)) time of (?<val>.*)$/
|
2014-08-02 08:30:27 +02:00
|
|
|
* @param string $prefix
|
|
|
|
* @param string $val
|
|
|
|
* @return false|string
|
2016-08-10 03:35:13 +02:00
|
|
|
*/
|
|
|
|
public function castRelativeToAbsoluteTime($prefix, $val)
|
|
|
|
{
|
2022-04-13 07:37:24 +02:00
|
|
|
$timestamp = strtotime($val ?? '');
|
2016-08-10 03:35:13 +02:00
|
|
|
if (!$timestamp) {
|
2017-09-08 04:49:50 +02:00
|
|
|
throw new InvalidArgumentException(sprintf(
|
2016-08-10 03:35:13 +02:00
|
|
|
"Can't resolve '%s' into a valid datetime value",
|
|
|
|
$val
|
|
|
|
));
|
|
|
|
}
|
2022-04-13 07:37:24 +02:00
|
|
|
return date($this->timeFormat ?? '', $timestamp);
|
2016-08-10 03:35:13 +02:00
|
|
|
}
|
2013-11-15 14:05:28 +01:00
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
/**
|
|
|
|
* Transforms relative date and time statements compatible with strtotime().
|
|
|
|
* Example: "datetime of 2 days ago" might return "2013-10-10 22:00:00" if its currently
|
|
|
|
* the 12th of October 2013. Customize through {@link setDatetimeFormat()}.
|
|
|
|
*
|
|
|
|
* @Transform /^(?:(the|a)) datetime of (?<val>.*)$/
|
2014-08-02 08:30:27 +02:00
|
|
|
* @param string $prefix
|
|
|
|
* @param string $val
|
|
|
|
* @return false|string
|
2016-08-10 03:35:13 +02:00
|
|
|
*/
|
|
|
|
public function castRelativeToAbsoluteDatetime($prefix, $val)
|
|
|
|
{
|
2022-04-13 07:37:24 +02:00
|
|
|
$timestamp = strtotime($val ?? '');
|
2016-08-10 03:35:13 +02:00
|
|
|
if (!$timestamp) {
|
2017-09-08 04:49:50 +02:00
|
|
|
throw new InvalidArgumentException(sprintf(
|
2016-08-10 03:35:13 +02:00
|
|
|
"Can't resolve '%s' into a valid datetime value",
|
|
|
|
$val
|
|
|
|
));
|
|
|
|
}
|
2022-04-13 07:37:24 +02:00
|
|
|
return date($this->datetimeFormat ?? '', $timestamp);
|
2016-08-10 03:35:13 +02:00
|
|
|
}
|
2013-11-15 14:05:28 +01:00
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
/**
|
|
|
|
* Transforms relative date statements compatible with strtotime().
|
|
|
|
* Example: "date 2 days ago" might return "2013-10-10" if its currently
|
|
|
|
* the 12th of October 2013. Customize through {@link setDateFormat()}.
|
|
|
|
*
|
|
|
|
* @Transform /^(?:(the|a)) date of (?<val>.*)$/
|
2014-08-02 08:30:27 +02:00
|
|
|
* @param string $prefix
|
|
|
|
* @param string $val
|
|
|
|
* @return false|string
|
2016-08-10 03:35:13 +02:00
|
|
|
*/
|
|
|
|
public function castRelativeToAbsoluteDate($prefix, $val)
|
|
|
|
{
|
2022-04-13 07:37:24 +02:00
|
|
|
$timestamp = strtotime($val ?? '');
|
2016-08-10 03:35:13 +02:00
|
|
|
if (!$timestamp) {
|
2017-09-08 04:49:50 +02:00
|
|
|
throw new InvalidArgumentException(sprintf(
|
2016-08-10 03:35:13 +02:00
|
|
|
"Can't resolve '%s' into a valid datetime value",
|
|
|
|
$val
|
|
|
|
));
|
|
|
|
}
|
2022-04-13 07:37:24 +02:00
|
|
|
return date($this->dateFormat ?? '', $timestamp);
|
2016-08-10 03:35:13 +02:00
|
|
|
}
|
2013-11-15 14:05:28 +01:00
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
public function getDateFormat()
|
|
|
|
{
|
|
|
|
return $this->dateFormat;
|
|
|
|
}
|
2013-11-15 14:05:28 +01:00
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
public function setDateFormat($format)
|
|
|
|
{
|
|
|
|
$this->dateFormat = $format;
|
|
|
|
}
|
2013-11-15 14:05:28 +01:00
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
public function getTimeFormat()
|
|
|
|
{
|
|
|
|
return $this->timeFormat;
|
|
|
|
}
|
2013-11-15 14:05:28 +01:00
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
public function setTimeFormat($format)
|
|
|
|
{
|
|
|
|
$this->timeFormat = $format;
|
|
|
|
}
|
2013-11-15 14:05:28 +01:00
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
public function getDatetimeFormat()
|
|
|
|
{
|
|
|
|
return $this->datetimeFormat;
|
|
|
|
}
|
2013-11-15 14:05:28 +01:00
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
public function setDatetimeFormat($format)
|
|
|
|
{
|
|
|
|
$this->datetimeFormat = $format;
|
|
|
|
}
|
2014-02-07 02:43:26 +01:00
|
|
|
|
2014-10-03 06:30:55 +02:00
|
|
|
/**
|
|
|
|
* Checks that field with specified in|name|label|value is disabled.
|
|
|
|
* Example: Then the field "Email" should be disabled
|
|
|
|
* Example: Then the "Email" field should be disabled
|
|
|
|
*
|
|
|
|
* @Then /^the "(?P<name>(?:[^"]|\\")*)" (?P<type>(?:(field|button))) should (?P<negate>(?:(not |)))be disabled/
|
|
|
|
* @Then /^the (?P<type>(?:(field|button))) "(?P<name>(?:[^"]|\\")*)" should (?P<negate>(?:(not |)))be disabled/
|
2014-08-02 08:30:27 +02:00
|
|
|
* @param string $name
|
|
|
|
* @param string $type
|
|
|
|
* @param string $negate
|
2014-10-03 06:30:55 +02:00
|
|
|
*/
|
2016-08-10 03:35:13 +02:00
|
|
|
public function stepFieldShouldBeDisabled($name, $type, $negate)
|
|
|
|
{
|
2014-10-03 06:30:55 +02:00
|
|
|
$page = $this->getSession()->getPage();
|
2016-08-10 03:35:13 +02:00
|
|
|
if ($type == 'field') {
|
2014-10-03 06:30:55 +02:00
|
|
|
$element = $page->findField($name);
|
|
|
|
} else {
|
|
|
|
$element = $page->find('named', array(
|
2014-08-02 08:30:27 +02:00
|
|
|
'button',
|
|
|
|
$this->getMainContext()->getXpathEscaper()->escapeLiteral($name)
|
2014-10-03 06:30:55 +02:00
|
|
|
));
|
|
|
|
}
|
|
|
|
|
2021-10-27 06:14:44 +02:00
|
|
|
Assert::assertNotNull($element, sprintf("Element '%s' not found", $name));
|
2014-10-03 06:30:55 +02:00
|
|
|
|
|
|
|
$disabledAttribute = $element->getAttribute('disabled');
|
2022-04-13 07:37:24 +02:00
|
|
|
if (trim($negate ?? '')) {
|
2021-10-27 06:14:44 +02:00
|
|
|
Assert::assertNull($disabledAttribute, sprintf("Failed asserting element '%s' is not disabled", $name));
|
2014-10-03 06:30:55 +02:00
|
|
|
} else {
|
2021-10-27 06:14:44 +02:00
|
|
|
Assert::assertNotNull($disabledAttribute, sprintf("Failed asserting element '%s' is disabled", $name));
|
2014-10-03 06:30:55 +02:00
|
|
|
}
|
|
|
|
}
|
2014-02-07 02:43:26 +01:00
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
/**
|
|
|
|
* Checks that checkbox with specified in|name|label|value is enabled.
|
|
|
|
* Example: Then the field "Email" should be enabled
|
|
|
|
* Example: Then the "Email" field should be enabled
|
|
|
|
*
|
|
|
|
* @Then /^the "(?P<field>(?:[^"]|\\")*)" field should be enabled/
|
|
|
|
* @Then /^the field "(?P<field>(?:[^"]|\\")*)" should be enabled/
|
2014-08-02 08:30:27 +02:00
|
|
|
* @param string $field
|
2016-08-10 03:35:13 +02:00
|
|
|
*/
|
|
|
|
public function stepFieldShouldBeEnabled($field)
|
|
|
|
{
|
|
|
|
$page = $this->getSession()->getPage();
|
|
|
|
$fieldElement = $page->findField($field);
|
2021-10-27 06:14:44 +02:00
|
|
|
Assert::assertNotNull($fieldElement, sprintf("Field '%s' not found", $field));
|
2014-02-07 02:43:26 +01:00
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
$disabledAttribute = $fieldElement->getAttribute('disabled');
|
2014-02-07 02:43:26 +01:00
|
|
|
|
2021-10-27 06:14:44 +02:00
|
|
|
Assert::assertNull($disabledAttribute, sprintf("Failed asserting field '%s' is enabled", $field));
|
2016-08-10 03:35:13 +02:00
|
|
|
}
|
2014-02-07 02:43:26 +01:00
|
|
|
|
2014-10-03 06:30:55 +02:00
|
|
|
/**
|
|
|
|
* Clicks a link in a specific region (an element identified by a CSS selector, a "data-title" attribute,
|
|
|
|
* or a named region mapped to a CSS selector via Behat configuration).
|
|
|
|
*
|
|
|
|
* Example: Given I follow "Select" in the "header .login-form" region
|
|
|
|
* Example: Given I follow "Select" in the "My Login Form" region
|
|
|
|
*
|
|
|
|
* @Given /^I (?:follow|click) "(?P<link>[^"]*)" in the "(?P<region>[^"]*)" region$/
|
2014-08-02 08:30:27 +02:00
|
|
|
* @param string $link
|
|
|
|
* @param string $region
|
|
|
|
* @throws \Exception
|
2014-10-03 06:30:55 +02:00
|
|
|
*/
|
2016-08-10 03:35:13 +02:00
|
|
|
public function iFollowInTheRegion($link, $region)
|
|
|
|
{
|
2014-10-03 06:30:55 +02:00
|
|
|
$context = $this->getMainContext();
|
|
|
|
$regionObj = $context->getRegionObj($region);
|
2021-10-27 06:14:44 +02:00
|
|
|
Assert::assertNotNull($regionObj);
|
2014-10-03 06:30:55 +02:00
|
|
|
|
|
|
|
$linkObj = $regionObj->findLink($link);
|
|
|
|
if (empty($linkObj)) {
|
2021-04-15 02:13:29 +02:00
|
|
|
throw new \Exception(sprintf('The link "%s" was not found in the region "%s"
|
2014-09-30 15:20:40 +02:00
|
|
|
on the page %s', $link, $region, $this->getSession()->getCurrentUrl()));
|
2014-10-03 06:30:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$linkObj->click();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Fills in a field in a specfic region similar to (@see iFollowInTheRegion or @see iSeeTextInRegion)
|
|
|
|
*
|
|
|
|
* Example: Given I fill in "Hello" with "World"
|
|
|
|
*
|
|
|
|
* @Given /^I fill in "(?P<field>[^"]*)" with "(?P<value>[^"]*)" in the "(?P<region>[^"]*)" region$/
|
2014-08-02 08:30:27 +02:00
|
|
|
* @param string $field
|
|
|
|
* @param string $value
|
|
|
|
* @param string $region
|
|
|
|
* @throws \Exception
|
2014-10-03 06:30:55 +02:00
|
|
|
*/
|
2016-08-10 03:35:13 +02:00
|
|
|
public function iFillinTheRegion($field, $value, $region)
|
|
|
|
{
|
2014-10-03 06:30:55 +02:00
|
|
|
$context = $this->getMainContext();
|
|
|
|
$regionObj = $context->getRegionObj($region);
|
2021-10-27 06:14:44 +02:00
|
|
|
Assert::assertNotNull($regionObj, "Region Object is null");
|
2014-10-03 06:30:55 +02:00
|
|
|
|
|
|
|
$fieldObj = $regionObj->findField($field);
|
|
|
|
if (empty($fieldObj)) {
|
2021-04-15 02:13:29 +02:00
|
|
|
throw new \Exception(sprintf('The field "%s" was not found in the region "%s"
|
2014-09-30 15:20:40 +02:00
|
|
|
on the page %s', $field, $region, $this->getSession()->getCurrentUrl()));
|
2014-10-03 06:30:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$regionObj->fillField($field, $value);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Asserts text in a specific region (an element identified by a CSS selector, a "data-title" attribute,
|
|
|
|
* or a named region mapped to a CSS selector via Behat configuration).
|
|
|
|
* Supports regular expressions in text value.
|
|
|
|
*
|
|
|
|
* Example: Given I should see "My Text" in the "header .login-form" region
|
|
|
|
* Example: Given I should not see "My Text" in the "My Login Form" region
|
|
|
|
*
|
|
|
|
* @Given /^I should (?P<negate>(?:(not |)))see "(?P<text>[^"]*)" in the "(?P<region>[^"]*)" region$/
|
2014-08-02 08:30:27 +02:00
|
|
|
* @param string $negate
|
|
|
|
* @param string $text
|
|
|
|
* @param string $region
|
|
|
|
* @throws \Exception
|
2014-10-03 06:30:55 +02:00
|
|
|
*/
|
2016-08-10 03:35:13 +02:00
|
|
|
public function iSeeTextInRegion($negate, $text, $region)
|
|
|
|
{
|
2014-10-03 06:30:55 +02:00
|
|
|
$context = $this->getMainContext();
|
|
|
|
$regionObj = $context->getRegionObj($region);
|
2021-10-27 06:14:44 +02:00
|
|
|
Assert::assertNotNull($regionObj);
|
2014-10-03 06:30:55 +02:00
|
|
|
|
|
|
|
$actual = $regionObj->getText();
|
2022-04-13 07:37:24 +02:00
|
|
|
$actual = preg_replace('/\s+/u', ' ', $actual ?? '');
|
|
|
|
$regex = '/' . preg_quote($text ?? '', '/') . '/ui';
|
2014-10-03 06:30:55 +02:00
|
|
|
|
2022-04-13 07:37:24 +02:00
|
|
|
if (trim($negate ?? '')) {
|
|
|
|
if (preg_match($regex ?? '', $actual ?? '')) {
|
2014-10-03 06:30:55 +02:00
|
|
|
$message = sprintf(
|
|
|
|
'The text "%s" was found in the text of the "%s" region on the page %s.',
|
|
|
|
$text,
|
|
|
|
$region,
|
|
|
|
$this->getSession()->getCurrentUrl()
|
|
|
|
);
|
|
|
|
|
|
|
|
throw new \Exception($message);
|
|
|
|
}
|
|
|
|
} else {
|
2022-04-13 07:37:24 +02:00
|
|
|
if (!preg_match($regex ?? '', $actual ?? '')) {
|
2014-10-03 06:30:55 +02:00
|
|
|
$message = sprintf(
|
|
|
|
'The text "%s" was not found anywhere in the text of the "%s" region on the page %s.',
|
|
|
|
$text,
|
|
|
|
$region,
|
|
|
|
$this->getSession()->getCurrentUrl()
|
|
|
|
);
|
|
|
|
|
|
|
|
throw new \Exception($message);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-04-09 00:31:41 +02:00
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
/**
|
|
|
|
* Selects the specified radio button
|
|
|
|
*
|
|
|
|
* @Given /^I select the "([^"]*)" radio button$/
|
2014-08-02 08:30:27 +02:00
|
|
|
* @param string $radioLabel
|
2016-08-10 03:35:13 +02:00
|
|
|
*/
|
|
|
|
public function iSelectTheRadioButton($radioLabel)
|
|
|
|
{
|
|
|
|
$session = $this->getSession();
|
2014-08-02 08:30:27 +02:00
|
|
|
$radioButton = $session->getPage()->find('named', [
|
|
|
|
'radio',
|
|
|
|
$this->getMainContext()->getXpathEscaper()->escapeLiteral($radioLabel)
|
|
|
|
]);
|
2021-10-27 06:14:44 +02:00
|
|
|
Assert::assertNotNull($radioButton);
|
2016-08-10 03:35:13 +02:00
|
|
|
$session->getDriver()->click($radioButton->getXPath());
|
|
|
|
}
|
2014-05-05 02:32:47 +02:00
|
|
|
|
2014-10-03 06:30:55 +02:00
|
|
|
/**
|
|
|
|
* @Then /^the "([^"]*)" table should contain "([^"]*)"$/
|
2014-08-02 08:30:27 +02:00
|
|
|
* @param string $selector
|
|
|
|
* @param string $text
|
2014-10-03 06:30:55 +02:00
|
|
|
*/
|
2016-08-10 03:35:13 +02:00
|
|
|
public function theTableShouldContain($selector, $text)
|
|
|
|
{
|
2014-10-03 06:30:55 +02:00
|
|
|
$table = $this->getTable($selector);
|
|
|
|
|
|
|
|
$element = $table->find('named', array('content', "'$text'"));
|
2021-10-27 06:14:44 +02:00
|
|
|
Assert::assertNotNull($element, sprintf('Element containing `%s` not found in `%s` table', $text, $selector));
|
2014-10-03 06:30:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @Then /^the "([^"]*)" table should not contain "([^"]*)"$/
|
2014-08-02 08:30:27 +02:00
|
|
|
* @param string $selector
|
|
|
|
* @param string $text
|
2014-10-03 06:30:55 +02:00
|
|
|
*/
|
2016-08-10 03:35:13 +02:00
|
|
|
public function theTableShouldNotContain($selector, $text)
|
|
|
|
{
|
2014-10-03 06:30:55 +02:00
|
|
|
$table = $this->getTable($selector);
|
|
|
|
|
|
|
|
$element = $table->find('named', array('content', "'$text'"));
|
2021-10-27 06:14:44 +02:00
|
|
|
Assert::assertNull($element, sprintf('Element containing `%s` not found in `%s` table', $text, $selector));
|
2014-10-03 06:30:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @Given /^I click on "([^"]*)" in the "([^"]*)" table$/
|
2014-08-02 08:30:27 +02:00
|
|
|
* @param string $text
|
|
|
|
* @param string $selector
|
2014-10-03 06:30:55 +02:00
|
|
|
*/
|
2016-08-10 03:35:13 +02:00
|
|
|
public function iClickOnInTheTable($text, $selector)
|
|
|
|
{
|
2014-10-03 06:30:55 +02:00
|
|
|
$table = $this->getTable($selector);
|
|
|
|
|
|
|
|
$element = $table->find('xpath', sprintf('//*[count(*)=0 and contains(.,"%s")]', $text));
|
2021-10-27 06:14:44 +02:00
|
|
|
Assert::assertNotNull($element, sprintf('Element containing `%s` not found', $text));
|
2014-10-03 06:30:55 +02:00
|
|
|
$element->click();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Finds the first visible table by various factors:
|
|
|
|
* - table[id]
|
|
|
|
* - table[title]
|
|
|
|
* - table *[class=title]
|
|
|
|
* - fieldset[data-name] table
|
|
|
|
* - table caption
|
|
|
|
*
|
2014-08-02 08:30:27 +02:00
|
|
|
* @param string $selector
|
|
|
|
* @return NodeElement
|
2014-10-03 06:30:55 +02:00
|
|
|
*/
|
2016-08-10 03:35:13 +02:00
|
|
|
protected function getTable($selector)
|
|
|
|
{
|
2014-08-02 08:30:27 +02:00
|
|
|
$selector = $this->getMainContext()->getXpathEscaper()->escapeLiteral($selector);
|
2014-10-03 06:30:55 +02:00
|
|
|
$page = $this->getSession()->getPage();
|
|
|
|
$candidates = $page->findAll(
|
|
|
|
'xpath',
|
|
|
|
$this->getSession()->getSelectorsHandler()->selectorToXpath(
|
2016-08-10 03:35:13 +02:00
|
|
|
"xpath",
|
|
|
|
".//table[(./@id = $selector or contains(./@title, $selector))]"
|
2014-10-03 06:30:55 +02:00
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
// Find tables by a <caption> field
|
2021-04-15 02:13:29 +02:00
|
|
|
$candidates += $page->findAll('xpath', "//table//caption[contains(normalize-space(string(.)),
|
2014-09-30 15:20:40 +02:00
|
|
|
$selector)]/ancestor-or-self::table[1]");
|
|
|
|
|
2014-10-03 06:30:55 +02:00
|
|
|
// Find tables by a .title node
|
2021-04-15 02:13:29 +02:00
|
|
|
$candidates += $page->findAll('xpath', "//table//*[contains(concat(' ',normalize-space(@class),' '), ' title ') and contains(normalize-space(string(.)),
|
2014-09-30 15:20:40 +02:00
|
|
|
$selector)]/ancestor-or-self::table[1]");
|
|
|
|
|
2014-10-03 06:30:55 +02:00
|
|
|
// Some tables don't have a visible title, so look for a fieldset with data-name instead
|
|
|
|
$candidates += $page->findAll('xpath', "//fieldset[@data-name=$selector]//table");
|
2014-09-30 15:20:40 +02:00
|
|
|
|
2021-10-27 06:14:44 +02:00
|
|
|
Assert::assertTrue((bool)$candidates, 'Could not find any table elements');
|
2014-09-30 15:20:40 +02:00
|
|
|
|
2014-10-03 06:30:55 +02:00
|
|
|
$table = null;
|
2014-08-02 08:30:27 +02:00
|
|
|
/** @var NodeElement $candidate */
|
2016-08-10 03:35:13 +02:00
|
|
|
foreach ($candidates as $candidate) {
|
|
|
|
if (!$table && $candidate->isVisible()) {
|
2014-10-03 06:30:55 +02:00
|
|
|
$table = $candidate;
|
|
|
|
}
|
|
|
|
}
|
2014-09-30 15:20:40 +02:00
|
|
|
|
2021-10-27 06:14:44 +02:00
|
|
|
Assert::assertTrue((bool)$table, 'Found table elements, but none are visible');
|
2014-09-30 15:20:40 +02:00
|
|
|
|
2014-10-03 06:30:55 +02:00
|
|
|
return $table;
|
|
|
|
}
|
2014-06-13 05:05:32 +02:00
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
/**
|
|
|
|
* Checks the order of two texts.
|
|
|
|
* Assumptions: the two texts appear in their conjunct parent element once
|
2023-06-01 03:42:47 +02:00
|
|
|
* @Then /^I should see the text "(?P<textBefore>(?:[^"]|\\")*)" (?P<order>(before|after)) the text "(?P<textAfter>(?:[^"]|\\")*)" in the "(?P<element>[^"]*)" element$/
|
2014-08-02 08:30:27 +02:00
|
|
|
* @param string $textBefore
|
|
|
|
* @param string $order
|
|
|
|
* @param string $textAfter
|
|
|
|
* @param string $element
|
2016-08-10 03:35:13 +02:00
|
|
|
*/
|
|
|
|
public function theTextBeforeAfter($textBefore, $order, $textAfter, $element)
|
|
|
|
{
|
|
|
|
$ele = $this->getSession()->getPage()->find('css', $element);
|
2021-10-27 06:14:44 +02:00
|
|
|
Assert::assertNotNull($ele, sprintf('%s not found', $element));
|
2016-08-10 03:35:13 +02:00
|
|
|
|
|
|
|
// Check both of the texts exist in the element
|
|
|
|
$text = $ele->getText();
|
2022-04-13 07:37:24 +02:00
|
|
|
Assert::assertTrue(strpos($text ?? '', $textBefore ?? '') !== 'FALSE', sprintf('%s not found in the element %s', $textBefore, $element));
|
|
|
|
Assert::assertTrue(strpos($text ?? '', $textAfter ?? '') !== 'FALSE', sprintf('%s not found in the element %s', $textAfter, $element));
|
2016-08-10 03:35:13 +02:00
|
|
|
|
|
|
|
/// Use strpos to get the position of the first occurrence of the two texts (case-sensitive)
|
|
|
|
// and compare them with the given order (before or after)
|
|
|
|
if ($order === 'before') {
|
2022-04-13 07:37:24 +02:00
|
|
|
Assert::assertTrue(strpos($text ?? '', $textBefore ?? '') < strpos($text ?? '', $textAfter ?? ''));
|
2016-08-10 03:35:13 +02:00
|
|
|
} else {
|
2022-04-13 07:37:24 +02:00
|
|
|
Assert::assertTrue(strpos($text ?? '', $textBefore ?? '') > strpos($text ?? '', $textAfter ?? ''));
|
2016-08-10 03:35:13 +02:00
|
|
|
}
|
|
|
|
}
|
2014-08-14 20:56:46 +02:00
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
/**
|
2014-08-02 08:30:27 +02:00
|
|
|
* Wait until a certain amount of seconds till I see an element identified by a CSS selector.
|
|
|
|
*
|
|
|
|
* Example: Given I wait for 10 seconds until I see the ".css_element" element
|
|
|
|
*
|
|
|
|
* @Given /^I wait for (\d+) seconds until I see the "([^"]*)" element$/
|
|
|
|
* @param int $wait
|
|
|
|
* @param string $selector
|
|
|
|
*/
|
2016-08-10 03:35:13 +02:00
|
|
|
public function iWaitXUntilISee($wait, $selector)
|
|
|
|
{
|
|
|
|
$page = $this->getSession()->getPage();
|
|
|
|
|
2014-08-02 08:30:27 +02:00
|
|
|
$this->spin(function () use ($page, $selector) {
|
2016-08-10 03:35:13 +02:00
|
|
|
$element = $page->find('css', $selector);
|
|
|
|
|
|
|
|
if (empty($element)) {
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
return $element->isVisible();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2014-08-14 20:56:46 +02:00
|
|
|
|
2015-04-21 05:24:51 +02:00
|
|
|
/**
|
|
|
|
* Wait until a particular element is visible, using a CSS selector. Useful for content loaded via AJAX, or only
|
|
|
|
* populated after JS execution.
|
|
|
|
*
|
|
|
|
* Example: Given I wait until I see the "header .login-form" element
|
|
|
|
*
|
|
|
|
* @Given /^I wait until I see the "([^"]*)" element$/
|
2014-08-02 08:30:27 +02:00
|
|
|
* @param string $selector
|
2015-04-21 05:24:51 +02:00
|
|
|
*/
|
2016-08-10 03:35:13 +02:00
|
|
|
public function iWaitUntilISee($selector)
|
|
|
|
{
|
2015-04-21 05:24:51 +02:00
|
|
|
$page = $this->getSession()->getPage();
|
2014-08-02 08:30:27 +02:00
|
|
|
$this->spin(function () use ($page, $selector) {
|
2015-04-21 05:24:51 +02:00
|
|
|
$element = $page->find('css', $selector);
|
2016-08-10 03:35:13 +02:00
|
|
|
if (empty($element)) {
|
2015-04-21 05:24:51 +02:00
|
|
|
return false;
|
2016-08-10 03:35:13 +02:00
|
|
|
} else {
|
2015-04-21 05:24:51 +02:00
|
|
|
return ($element->isVisible());
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Wait until a particular string is found on the page. Useful for content loaded via AJAX, or only populated after
|
|
|
|
* JS execution.
|
|
|
|
*
|
|
|
|
* Example: Given I wait until I see the text "Welcome back, John!"
|
|
|
|
*
|
|
|
|
* @Given /^I wait until I see the text "([^"]*)"$/
|
2014-08-02 08:30:27 +02:00
|
|
|
* @param string $text
|
2015-04-21 05:24:51 +02:00
|
|
|
*/
|
2016-08-10 03:35:13 +02:00
|
|
|
public function iWaitUntilISeeText($text)
|
|
|
|
{
|
2015-04-21 05:24:51 +02:00
|
|
|
$page = $this->getSession()->getPage();
|
|
|
|
$session = $this->getSession();
|
2014-08-02 08:30:27 +02:00
|
|
|
$this->spin(function () use ($page, $session, $text) {
|
2022-03-29 03:10:29 +02:00
|
|
|
$elements = $page->findAll(
|
2015-04-21 05:24:51 +02:00
|
|
|
'xpath',
|
|
|
|
$session->getSelectorsHandler()->selectorToXpath("xpath", ".//*[contains(text(), '$text')]")
|
|
|
|
);
|
2022-03-29 03:10:29 +02:00
|
|
|
foreach ($elements as $element) {
|
|
|
|
if (empty($element)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!$element->isVisible()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
return true;
|
2015-04-21 05:24:51 +02:00
|
|
|
}
|
2022-03-29 03:10:29 +02:00
|
|
|
return false;
|
2015-04-21 05:24:51 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
/**
|
|
|
|
* @Given /^I scroll to the bottom$/
|
|
|
|
*/
|
|
|
|
public function iScrollToBottom()
|
|
|
|
{
|
|
|
|
$javascript = 'window.scrollTo(0, Math.max(document.documentElement.scrollHeight, document.body.scrollHeight, document.documentElement.clientHeight));';
|
|
|
|
$this->getSession()->executeScript($javascript);
|
|
|
|
}
|
2014-08-14 20:56:46 +02:00
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
/**
|
|
|
|
* @Given /^I scroll to the top$/
|
|
|
|
*/
|
|
|
|
public function iScrollToTop()
|
|
|
|
{
|
|
|
|
$this->getSession()->executeScript('window.scrollTo(0,0);');
|
|
|
|
}
|
2014-08-14 20:56:46 +02:00
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
/**
|
|
|
|
* Scroll to a certain element by label.
|
|
|
|
* Requires an "id" attribute to uniquely identify the element in the document.
|
|
|
|
*
|
|
|
|
* Example: Given I scroll to the "Submit" button
|
|
|
|
* Example: Given I scroll to the "My Date" field
|
|
|
|
*
|
|
|
|
* @Given /^I scroll to the "([^"]*)" (field|link|button)$/
|
2014-08-02 08:30:27 +02:00
|
|
|
* @param string $locator
|
|
|
|
* @param string $type
|
2016-08-10 03:35:13 +02:00
|
|
|
*/
|
|
|
|
public function iScrollToField($locator, $type)
|
|
|
|
{
|
|
|
|
$page = $this->getSession()->getPage();
|
2014-10-03 06:30:55 +02:00
|
|
|
$el = $page->find('named', array($type, "'$locator'"));
|
2021-10-27 06:14:44 +02:00
|
|
|
Assert::assertNotNull($el, sprintf('%s element not found', $locator));
|
2014-08-14 20:56:46 +02:00
|
|
|
|
2014-10-03 06:30:55 +02:00
|
|
|
$id = $el->getAttribute('id');
|
2016-08-10 03:35:13 +02:00
|
|
|
if (empty($id)) {
|
2017-09-08 04:49:50 +02:00
|
|
|
throw new InvalidArgumentException('Element requires an "id" attribute');
|
2016-08-10 03:35:13 +02:00
|
|
|
}
|
2014-08-14 20:56:46 +02:00
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
$js = sprintf("document.getElementById('%s').scrollIntoView(true);", $id);
|
|
|
|
$this->getSession()->executeScript($js);
|
|
|
|
}
|
2014-08-14 20:56:46 +02:00
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
/**
|
|
|
|
* Scroll to a certain element by CSS selector.
|
|
|
|
* Requires an "id" attribute to uniquely identify the element in the document.
|
|
|
|
*
|
|
|
|
* Example: Given I scroll to the ".css_element" element
|
|
|
|
*
|
|
|
|
* @Given /^I scroll to the "(?P<locator>(?:[^"]|\\")*)" element$/
|
2014-08-02 08:30:27 +02:00
|
|
|
* @param string $locator
|
2016-08-10 03:35:13 +02:00
|
|
|
*/
|
|
|
|
public function iScrollToElement($locator)
|
|
|
|
{
|
|
|
|
$el = $this->getSession()->getPage()->find('css', $locator);
|
2021-10-27 06:14:44 +02:00
|
|
|
Assert::assertNotNull($el, sprintf('The element "%s" is not found', $locator));
|
2016-08-10 03:35:13 +02:00
|
|
|
|
|
|
|
$id = $el->getAttribute('id');
|
|
|
|
if (empty($id)) {
|
2017-09-08 04:49:50 +02:00
|
|
|
throw new InvalidArgumentException('Element requires an "id" attribute');
|
2016-08-10 03:35:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$js = sprintf("document.getElementById('%s').scrollIntoView(true);", $id);
|
|
|
|
$this->getSession()->executeScript($js);
|
|
|
|
}
|
2015-04-21 05:24:51 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Continuously poll the dom until callback returns true, code copied from
|
|
|
|
* (@link http://docs.behat.org/cookbook/using_spin_functions.html)
|
|
|
|
* If not found within a given wait period, timeout and throw error
|
|
|
|
*
|
|
|
|
* @param callback $lambda The function to run continuously
|
|
|
|
* @param integer $wait Timeout in seconds
|
|
|
|
* @return bool Returns true if the lambda returns successfully
|
|
|
|
* @throws \Exception Thrown if the wait threshold is exceeded without the lambda successfully returning
|
|
|
|
*/
|
2016-08-10 03:35:13 +02:00
|
|
|
public function spin($lambda, $wait = 60)
|
|
|
|
{
|
2015-04-21 05:24:51 +02:00
|
|
|
for ($i = 0; $i < $wait; $i++) {
|
|
|
|
try {
|
2016-08-10 03:35:13 +02:00
|
|
|
if ($lambda($this)) {
|
2015-04-21 05:24:51 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
// do nothing
|
|
|
|
}
|
|
|
|
|
|
|
|
sleep(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
$backtrace = debug_backtrace();
|
|
|
|
|
|
|
|
throw new \Exception(sprintf(
|
|
|
|
"Timeout thrown by %s::%s()\n.",
|
|
|
|
$backtrace[1]['class'],
|
|
|
|
$backtrace[1]['function']
|
|
|
|
));
|
|
|
|
}
|
2016-08-03 08:36:44 +02:00
|
|
|
|
2021-11-08 02:29:05 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Log a message
|
|
|
|
*/
|
|
|
|
protected function logMessage(string $message)
|
|
|
|
{
|
|
|
|
file_put_contents('php://stderr', $message . PHP_EOL);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-08-10 03:35:13 +02:00
|
|
|
/**
|
|
|
|
* We have to catch exceptions and log somehow else otherwise behat falls over
|
2014-08-02 08:30:27 +02:00
|
|
|
*
|
|
|
|
* @param Exception $exception
|
2016-08-10 03:35:13 +02:00
|
|
|
*/
|
2014-08-02 08:30:27 +02:00
|
|
|
protected function logException(Exception $exception)
|
2016-08-10 03:35:13 +02:00
|
|
|
{
|
2021-11-08 02:29:05 +01:00
|
|
|
$this->logMessage('Exception caught: ' . $exception->getMessage());
|
2014-08-02 08:30:27 +02:00
|
|
|
}
|
2021-07-29 12:05:50 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Detect element with javascript, rather than having the selector converted to xpath
|
|
|
|
* There's already an xpath based function 'I see the "" element' iSeeTheElement() in silverstripe/cms
|
|
|
|
* There's also an 'I should see "" element' in MinkContext which also converts the css selector to xpath
|
|
|
|
*
|
2021-11-08 02:29:05 +01:00
|
|
|
* @When /^I should(| not) see the "([^"]+)" element/
|
2021-07-29 12:05:50 +02:00
|
|
|
* @param $selector
|
|
|
|
*/
|
2021-11-08 02:29:05 +01:00
|
|
|
public function iShouldSeeTheElement($not, $cssSelector = '')
|
2021-07-29 12:05:50 +02:00
|
|
|
{
|
2021-11-08 02:29:05 +01:00
|
|
|
// backwards compatibility for when function signature was just ($cssSelector)
|
|
|
|
if (!in_array($not, ['', ' not'])) {
|
|
|
|
$not = '';
|
|
|
|
$cssSelector = $not;
|
|
|
|
}
|
2022-04-13 07:37:24 +02:00
|
|
|
$sel = str_replace('"', '\\"', $cssSelector ?? '');
|
2021-07-29 12:05:50 +02:00
|
|
|
$js = <<<JS
|
|
|
|
return document.querySelector("$sel");
|
|
|
|
JS;
|
|
|
|
$element = $this->getSession()->evaluateScript($js);
|
2021-11-08 02:29:05 +01:00
|
|
|
if ($not) {
|
|
|
|
Assert::assertNull($element, sprintf('Element %s was found when it should not have been', $cssSelector));
|
|
|
|
} else {
|
|
|
|
Assert::assertNotNull($element, sprintf('Element %s not found', $cssSelector));
|
|
|
|
}
|
2021-07-29 12:05:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-11-08 02:29:05 +01:00
|
|
|
* Selects the option in select field with specified id|name|label|value
|
|
|
|
* Also accepts CSS selectors
|
2021-07-29 12:05:50 +02:00
|
|
|
*
|
2021-11-08 02:29:05 +01:00
|
|
|
* @When /^I select "([^"]+)" from the "([^"]+)" field(| with javascript)$/
|
2021-07-29 12:05:50 +02:00
|
|
|
* @param string $value
|
2021-11-08 02:29:05 +01:00
|
|
|
* @param string $locator - select id, name, label or element
|
|
|
|
* @param string $withJavascript - use javascript if having trouble selecting an option e.g. visibility
|
2021-07-29 12:05:50 +02:00
|
|
|
*/
|
2021-11-08 02:29:05 +01:00
|
|
|
public function iSelectFromTheField($value, $locator, $withJavascript)
|
2021-07-29 12:05:50 +02:00
|
|
|
{
|
2021-11-08 02:29:05 +01:00
|
|
|
$field = $this->getElement($locator);
|
|
|
|
if (!$withJavascript) {
|
|
|
|
$field->selectOption($value);
|
|
|
|
} else {
|
|
|
|
$xpath = $field->getXpath();
|
2022-04-13 07:37:24 +02:00
|
|
|
$xpath = str_replace(['"', "\n"], ['\"', ''], $xpath ?? '');
|
|
|
|
$value = str_replace('"', '\"', $value ?? '');
|
2021-11-08 02:29:05 +01:00
|
|
|
$js = <<<JS
|
|
|
|
return (function() {
|
|
|
|
let select = document.evaluate("{$xpath}", document).iterateNext();
|
|
|
|
let options = select.getElementsByTagName('option');
|
|
|
|
for (let i = 0; i < options.length; i++) {
|
|
|
|
let option = options[i];
|
|
|
|
if (option.value != "{$value}" && option.innerHTML.trim() != "{$value}") {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
select.value = option.value;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
})();
|
|
|
|
JS;
|
|
|
|
$result = $this->getSession()->evaluateScript($js);
|
|
|
|
Assert::assertEquals(1, $result, "Unable to select value {$value} from {$locator} with javascript");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @Then /^the rendered HTML should(| not) contain "(.+)"$/
|
|
|
|
* @param string $not
|
|
|
|
* @param string $htmlFragment
|
|
|
|
*/
|
|
|
|
public function theRenderedHtmlShouldContain($not, $htmlFragment)
|
|
|
|
{
|
|
|
|
$html = $this->getSession()->getPage()->getOuterHtml();
|
2022-04-13 07:37:24 +02:00
|
|
|
$htmlFragment = str_replace('\"', '"', $htmlFragment ?? '');
|
|
|
|
$contains = strpos($html ?? '', $htmlFragment ?? '') !== false;
|
2021-11-08 02:29:05 +01:00
|
|
|
if ($not) {
|
|
|
|
Assert::assertFalse($contains, "HTML fragment {$htmlFragment} was in rendered HTML when it should not have been");
|
|
|
|
} else {
|
|
|
|
Assert::assertTrue($contains, "HTML fragment {$htmlFragment} not found in rendered HTML");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add tag values to the react TagField component which uses react-select
|
|
|
|
*
|
|
|
|
* @Then /^I add "([^"]+)" to the "([^"]+)" tag field$/
|
|
|
|
* @param string $value
|
2023-01-20 02:28:38 +01:00
|
|
|
* @param string $selector
|
2021-11-08 02:29:05 +01:00
|
|
|
*/
|
2023-01-20 02:28:38 +01:00
|
|
|
public function iAddToTheTagField($value, $selector)
|
2021-11-08 02:29:05 +01:00
|
|
|
{
|
2023-01-20 02:28:38 +01:00
|
|
|
$page = $this->getSession()->getPage();
|
|
|
|
/** @var NodeElement $parentElement */
|
|
|
|
$parentElement = null;
|
|
|
|
$dropdown = null;
|
|
|
|
$this->retryThrowable(function () use (&$parentElement, &$page, $selector) {
|
|
|
|
$parentElement = $page->find('css', $selector);
|
|
|
|
Assert::assertNotNull($parentElement, sprintf('"%s" element not found', $selector));
|
|
|
|
$page = $this->getSession()->getPage();
|
|
|
|
});
|
|
|
|
|
|
|
|
$this->retryThrowable(function () use (&$dropdown, $parentElement, $selector) {
|
|
|
|
$dropdown = $parentElement->find('css', '.ss-tag-field__dropdown-indicator');
|
|
|
|
Assert::assertNotNull($dropdown, sprintf('Unable to find the dropdown in "%s"', $selector));
|
|
|
|
$dropdown->click();
|
|
|
|
});
|
|
|
|
|
|
|
|
$inputField = null;
|
|
|
|
try {
|
|
|
|
// Try setting to a value already in the dropdown
|
|
|
|
$this->retryThrowable(function () use ($value, $parentElement, $selector) {
|
|
|
|
$element = $parentElement->find('xpath', sprintf('//*[count(*)=0 and .="%s"]', $value));
|
|
|
|
Assert::assertNotNull($element, sprintf('"%s" not found in "%s"', $value, $selector));
|
|
|
|
$element->click();
|
|
|
|
});
|
|
|
|
} catch (ExpectationFailedException $e) {
|
|
|
|
// Try creating a new value
|
|
|
|
$this->retryThrowable(function () use (&$inputField, $value, $parentElement, $selector) {
|
|
|
|
/** @var NodeElement $parentElement */
|
|
|
|
$inputField = $parentElement->find('css', '.ss-tag-field__input');
|
|
|
|
Assert::assertNotNull($inputField, sprintf('Could not create "%s" in "%s"', $value, $selector));
|
|
|
|
// We need to type the value in - react won't accept us just setting the value via js
|
|
|
|
$inputField->focus();
|
|
|
|
/** @var FacebookWebDriver $driver */
|
|
|
|
$driver = $this->getSession()->getDriver();
|
|
|
|
$keyboard = $driver->getWebDriver()->getKeyboard();
|
|
|
|
$keyboard->sendKeys($value);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Try selecting the 'Create "$value"' option
|
|
|
|
$this->retryThrowable(function () use ($value, $parentElement, $selector) {
|
|
|
|
$createOption = 'Create "' . $value . '"';
|
|
|
|
$element = $parentElement->find('xpath', sprintf('//*[count(*)=0 and .=\'%s\']', $createOption));
|
|
|
|
Assert::assertNotNull($element, sprintf('"%s" not found in "%s"', $createOption, $selector));
|
|
|
|
$element->click();
|
|
|
|
});
|
|
|
|
}
|
2021-11-08 02:29:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @Then /^the "([^"]+)" field should have the value "([^"]+)"$/
|
|
|
|
* @param string $locator
|
|
|
|
* @param string $value
|
|
|
|
*/
|
|
|
|
public function theFieldShouldHaveTheValue($locator, $value)
|
|
|
|
{
|
|
|
|
Assert::assertEquals($value, $this->getElement($locator)->getValue());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Will first attempt to find a field based on $locator
|
|
|
|
* Will fall back to finding an element based on css selector
|
|
|
|
*
|
|
|
|
* @param string $locator
|
|
|
|
* @return null|NodeElement
|
|
|
|
*/
|
|
|
|
private function getElement($locator): ?NodeElement
|
|
|
|
{
|
|
|
|
$page = $this->getSession()->getPage();
|
|
|
|
try {
|
|
|
|
$element = $page->findField($locator);
|
|
|
|
} catch (ElementNotFoundException $e) {
|
|
|
|
// noop
|
|
|
|
}
|
|
|
|
if (!$element) {
|
|
|
|
$element = $page->find('css', $locator);
|
|
|
|
}
|
|
|
|
Assert::assertNotNull($element, "Field {$locator} was not found");
|
|
|
|
return $element;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @When /^I drag the "([^"]+)" element to the "([^"]+)" element$/
|
|
|
|
* @param string $locatorA
|
|
|
|
* @param string $locatorB
|
|
|
|
*/
|
|
|
|
public function iDragTheElementToTheElement($locatorA, $locatorB)
|
|
|
|
{
|
|
|
|
$elementA = $this->getElement($locatorA);
|
|
|
|
$elementB = $this->getElement($locatorB);
|
|
|
|
$elementA->dragTo($elementB);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This doesn't seem to work quite right in practice
|
|
|
|
* iDragTheElementToTheElement is much more reliable
|
|
|
|
*
|
|
|
|
* @When /^I drag the "([^"]+)" element by "(\-?[0-9]+),(\-?[0-9]+)"$/
|
|
|
|
* @param string $locatorA
|
|
|
|
* @param string $xOffset
|
|
|
|
* @param string $yOffset
|
|
|
|
*/
|
|
|
|
public function iDragTheElementBy($locatorA, $xOffset, $yOffset)
|
|
|
|
{
|
|
|
|
/** @var FacebookWebDrvier $driver */
|
|
|
|
$driver = $this->getSession()->getDriver();
|
|
|
|
if (!($driver instanceof FacebookWebDriver)) {
|
|
|
|
$this->logMessage('Drag and drop by offset is only supported for FacebookWebDriver: skipping');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
$elementA = $this->getElement($locatorA);
|
|
|
|
$driver->dragBy($elementA->getXpath(), (int) $xOffset, (int) $yOffset);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Globally press the key i.e. not type into an input
|
|
|
|
*
|
|
|
|
* @When /^I press the "([^"]+)" key globally$/
|
|
|
|
* @param string $keyCombo - e.g. tab / shift-tab / ctrl-c / alt-f4
|
|
|
|
*/
|
|
|
|
public function iPressTheKeyGlobally($keyCombo)
|
|
|
|
{
|
|
|
|
/** @var FacebookWebDrvier $driver */
|
|
|
|
$driver = $this->getSession()->getDriver();
|
|
|
|
if (!($driver instanceof FacebookWebDriver)) {
|
|
|
|
$this->logMessage('Pressing keys globally is only supported for FacebookWebDriver: skipping');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
$modifier = null;
|
2022-04-13 07:37:24 +02:00
|
|
|
$pos = strpos($keyCombo ?? '', '-');
|
2021-11-08 02:29:05 +01:00
|
|
|
if ($pos !== false && $pos !== 0) {
|
2022-04-13 07:37:24 +02:00
|
|
|
list($modifier, $char) = explode('-', $keyCombo ?? '');
|
2021-11-08 02:29:05 +01:00
|
|
|
} else {
|
|
|
|
$char = $keyCombo;
|
|
|
|
}
|
|
|
|
// handle special chars e.g. "space"
|
2022-04-13 07:37:24 +02:00
|
|
|
if (defined(WebDriverKeys::class . '::' . strtoupper($char ?? ''))) {
|
|
|
|
$char = constant(WebDriverKeys::class . '::' . strtoupper($char ?? ''));
|
2021-11-08 02:29:05 +01:00
|
|
|
}
|
|
|
|
if ($modifier) {
|
2022-04-13 07:37:24 +02:00
|
|
|
$modifier = strtoupper($modifier ?? '');
|
2021-11-08 02:29:05 +01:00
|
|
|
if (defined(WebDriverKeys::class . '::' . $modifier)) {
|
|
|
|
$modifier = constant(WebDriverKeys::class . '::' . $modifier);
|
|
|
|
} else {
|
|
|
|
$modifier = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$driver->globalKeyPress($char, $modifier);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Use upload fields
|
|
|
|
*
|
|
|
|
* @Then /^I attach the file "([^"]+)" to the "([^"]+)" field$/
|
|
|
|
* @param $filename
|
|
|
|
* @param $locator
|
|
|
|
*/
|
|
|
|
public function iAttachTheFileToTheField($filename, $locator)
|
|
|
|
{
|
|
|
|
Assert::assertNotNull($this->fixtureContext, 'FixtureContext was not found so cannot know location of fixture files');
|
|
|
|
$path = $this->fixtureContext->getFilesPath() . '/' . $filename;
|
2022-04-13 07:37:24 +02:00
|
|
|
$path = str_replace('//', '/', $path ?? '');
|
2021-11-08 02:29:05 +01:00
|
|
|
Assert::assertNotEmpty($path, 'Fixture files path is empty');
|
|
|
|
$field = $this->getElement($locator);
|
|
|
|
$filesPath = $this->fixtureContext->getFilesPath();
|
|
|
|
if ($filesPath) {
|
2022-04-13 07:37:24 +02:00
|
|
|
$fullPath = rtrim(realpath($filesPath ?? '') ?? '', DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $path;
|
|
|
|
if (is_file($fullPath ?? '')) {
|
2021-11-08 02:29:05 +01:00
|
|
|
$path = $fullPath;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Assert::assertFileExists($path, "{$path} does not exist");
|
|
|
|
$field->attachFile($path);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Use this to follow hyperlinks with target="_blank"
|
|
|
|
* Behat won't switch to the new tab
|
|
|
|
* Also allows use of css selectors
|
|
|
|
*
|
|
|
|
* @When /^I follow "([^"]+)" with javascript$/
|
|
|
|
* @param string $locator
|
|
|
|
*/
|
|
|
|
public function iFollowWithJavascript($locator)
|
|
|
|
{
|
|
|
|
$page = $this->getSession()->getPage();
|
|
|
|
$link = $page->find('named', ['link', $locator]);
|
|
|
|
if (!$link) {
|
|
|
|
$link = $page->find('css', $locator);
|
|
|
|
}
|
|
|
|
Assert::assertNotNull($link, "Link {$locator} was not found");
|
|
|
|
$html = $link->getOuterHtml();
|
2022-04-13 07:37:24 +02:00
|
|
|
preg_match('#href=([\'"])#', $html ?? '', $m);
|
2021-11-08 02:29:05 +01:00
|
|
|
$q = $m[1];
|
2022-04-13 07:37:24 +02:00
|
|
|
preg_match("#href={$q}(.+?){$q}#", $html ?? '', $m);
|
|
|
|
$href = str_replace("'", "\\'", $m[1] ?? '');
|
|
|
|
if (strpos($href ?? '', 'http') !== 0) {
|
|
|
|
$href = rtrim($href ?? '', '/');
|
2021-11-08 02:29:05 +01:00
|
|
|
$href = "/{$href}";
|
|
|
|
}
|
|
|
|
$this->getSession()->executeScript("document.location.href = '{$href}';");
|
2021-07-29 12:05:50 +02:00
|
|
|
}
|
2012-11-09 17:34:24 +01:00
|
|
|
}
|