Completed
Push — master ( cfd104...361933 )
by Ingo
11:14 queued 07:45
created

iClickInTheElementDismissingTheDialog()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
rs 10
cc 1
eloc 3
nc 1
nop 3
1
<?php
2
3
namespace SilverStripe\BehatExtension\Context;
4
5
use Behat\Behat\Context\BehatContext,
6
    Behat\Behat\Context\Step,
7
    Behat\Behat\Event\StepEvent,
8
    Behat\Behat\Event\ScenarioEvent;
9
10
use Behat\Mink\Driver\Selenium2Driver;
11
12
use Behat\Gherkin\Node\PyStringNode,
13
    Behat\Gherkin\Node\TableNode;
14
15
// PHPUnit
16
require_once 'PHPUnit/Autoload.php';
17
require_once 'PHPUnit/Framework/Assert/Functions.php';
18
19
/**
20
 * BasicContext
21
 *
22
 * Context used to define generic steps like following anchors or pressing buttons.
23
 * Handles timeouts.
24
 * Handles redirections.
25
 * Handles AJAX enabled links, buttons and forms - jQuery is assumed.
26
 */
27
class BasicContext extends BehatContext
28
{
29
    protected $context;
30
31
    /**
32
	 * Date format in date() syntax
33
	 * @var String
34
	 */
35
	protected $dateFormat = 'Y-m-d';
36
37
	/**
38
	 * Time format in date() syntax
39
	 * @var String
40
	 */
41
	protected $timeFormat = 'H:i:s';
42
43
	/**
44
	 * Date/time format in date() syntax
45
	 * @var String
46
	 */
47
	protected $datetimeFormat = 'Y-m-d H:i:s';
48
49
    /**
50
     * Initializes context.
51
     * Every scenario gets it's own context object.
52
     *
53
     * @param   array   $parameters     context parameters (set them up through behat.yml)
54
     */
55
	public function __construct(array $parameters) {
56
        // Initialize your context here
57
        $this->context = $parameters;
58
    }
59
60
	/**
61
	 * Get Mink session from MinkContext
62
	 *
63
	 * @return \Behat\Mink\Session
64
	 */
65
	public function getSession($name = null) {
66
		return $this->getMainContext()->getSession($name);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Behat\Behat\Context\ExtendedContextInterface as the method getSession() does only exist in the following implementations of said interface: Behat\MinkExtension\Context\MinkContext, Behat\MinkExtension\Context\RawMinkContext, SilverStripe\BehatExtension\Context\BasicContext, SilverStripe\BehatExtension\Context\EmailContext, SilverStripe\BehatExtension\Context\FixtureContext, SilverStripe\BehatExtension\Context\LoginContext, SilverStripe\BehatExtens...ext\SilverStripeContext.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
67
	}
68
69
    /**
70
     * @AfterStep ~@modal
71
     *
72
     * Excluding scenarios with @modal tag is required,
73
     * because modal dialogs stop any JS interaction
74
     */
75
	public function appendErrorHandlerBeforeStep(StepEvent $event) {
0 ignored issues
show
Unused Code introduced by
The parameter $event is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
76
		try{
77
			$javascript = <<<JS
78
window.onerror = function(message, file, line, column, error) {
79
    var body = document.getElementsByTagName('body')[0];
80
	var msg = message + " in " + file + ":" + line + ":" + column;
81
	if(error !== undefined && error.stack !== undefined) {
82
		msg += "\\nSTACKTRACE:\\n" + error.stack;
83
	}
84
	body.setAttribute('data-jserrors', '[captured JavaScript error] ' + msg);
85
}
86
if ('undefined' !== typeof window.jQuery) {
87
    window.jQuery('body').ajaxError(function(event, jqxhr, settings, exception) {
88
        if ('abort' === exception) return;
89
        window.onerror(event.type + ': ' + settings.type + ' ' + settings.url + ' ' + exception + ' ' + jqxhr.responseText);
90
    });
91
}
92
JS;
93
94
			$this->getSession()->executeScript($javascript);
95
        }catch(\WebDriver\Exception $e){
96
        	$this->logException($e);
97
        }
98
    }
99
100
    /**
101
     * @AfterStep ~@modal
102
     *
103
     * Excluding scenarios with @modal tag is required,
104
     * because modal dialogs stop any JS interaction
105
     */
106
	public function readErrorHandlerAfterStep(StepEvent $event) {
107
		try{
108
	        $page = $this->getSession()->getPage();
109
	
110
	        $jserrors = $page->find('xpath', '//body[@data-jserrors]');
111
	        if (null !== $jserrors) {
112
	            $this->takeScreenshot($event);
113
	            file_put_contents('php://stderr', $jserrors->getAttribute('data-jserrors') . PHP_EOL);
114
	        }
115
	
116
	        $javascript = <<<JS
117
if ('undefined' !== typeof window.jQuery) {
118
	window.jQuery(document).ready(function() {
119
		window.jQuery('body').removeAttr('data-jserrors');
120
	});
121
}
122
JS;
123
124
			$this->getSession()->executeScript($javascript);
125
		}catch(\WebDriver\Exception $e){
126
			$this->logException($e);
127
        }
128
    }
129
130
    /**
131
     * Hook into jQuery ajaxStart, ajaxSuccess and ajaxComplete events.
132
     * Prepare __ajaxStatus() functions and attach them to these handlers.
133
     * Event handlers are removed after one run.
134
     *
135
     * @BeforeStep
136
     */
137 View Code Duplication
	public function handleAjaxBeforeStep(StepEvent $event) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
138
		try{
139
			$ajaxEnabledSteps = $this->getMainContext()->getAjaxSteps();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Behat\Behat\Context\ExtendedContextInterface as the method getAjaxSteps() does only exist in the following implementations of said interface: SilverStripe\BehatExtens...ext\SilverStripeContext.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
140
			$ajaxEnabledSteps = implode('|', array_filter($ajaxEnabledSteps));
141
142
			if (empty($ajaxEnabledSteps) || !preg_match('/(' . $ajaxEnabledSteps . ')/i', $event->getStep()->getText())) {
143
				return;
144
			}
145
146
			$javascript = <<<JS
147
if ('undefined' !== typeof window.jQuery && 'undefined' !== typeof window.jQuery.fn.on) {
148
    window.jQuery(document).on('ajaxStart.ss.test.behaviour', function(){
149
        window.__ajaxStatus = function() {
150
            return 'waiting';
151
        };
152
    });
153
    window.jQuery(document).on('ajaxComplete.ss.test.behaviour', function(e, jqXHR){
154
        if (null === jqXHR.getResponseHeader('X-ControllerURL')) {
155
            window.__ajaxStatus = function() {
156
                return 'no ajax';
157
            };
158
        }
159
    });
160
    window.jQuery(document).on('ajaxSuccess.ss.test.behaviour', function(e, jqXHR){
161
        if (null === jqXHR.getResponseHeader('X-ControllerURL')) {
162
            window.__ajaxStatus = function() {
163
                return 'success';
164
            };
165
        }
166
    });
167
}
168
JS;
169
			$this->getSession()->wait(500); // give browser a chance to process and render response
170
			$this->getSession()->executeScript($javascript);
171
		}catch(\WebDriver\Exception $e){
172
			$this->logException($e);
173
        }
174
    }
175
176
    /**
177
     * Wait for the __ajaxStatus()to return anything but 'waiting'.
178
     * Don't wait longer than 5 seconds.
179
     *
180
     * Don't unregister handler if we're dealing with modal windows
181
     *
182
     * @AfterStep ~@modal
183
     */
184 View Code Duplication
	public function handleAjaxAfterStep(StepEvent $event) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
185
		try{
186
			$ajaxEnabledSteps = $this->getMainContext()->getAjaxSteps();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Behat\Behat\Context\ExtendedContextInterface as the method getAjaxSteps() does only exist in the following implementations of said interface: SilverStripe\BehatExtens...ext\SilverStripeContext.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
187
			$ajaxEnabledSteps = implode('|', array_filter($ajaxEnabledSteps));
188
189
			if (empty($ajaxEnabledSteps) || !preg_match('/(' . $ajaxEnabledSteps . ')/i', $event->getStep()->getText())) {
190
				return;
191
			}
192
193
			$this->handleAjaxTimeout();
194
195
			$javascript = <<<JS
196
if ('undefined' !== typeof window.jQuery && 'undefined' !== typeof window.jQuery.fn.off) {
197
window.jQuery(document).off('ajaxStart.ss.test.behaviour');
198
window.jQuery(document).off('ajaxComplete.ss.test.behaviour');
199
window.jQuery(document).off('ajaxSuccess.ss.test.behaviour');
200
}
201
JS;
202
			$this->getSession()->executeScript($javascript);
203
		}catch(\WebDriver\Exception $e){
204
			$this->logException($e);
205
        }
206
    }
207
208
	public function handleAjaxTimeout() {
209
        $timeoutMs = $this->getMainContext()->getAjaxTimeout();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Behat\Behat\Context\ExtendedContextInterface as the method getAjaxTimeout() does only exist in the following implementations of said interface: SilverStripe\BehatExtens...ext\SilverStripeContext.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
210
211
        // Wait for an ajax request to complete, but only for a maximum of 5 seconds to avoid deadlocks
212
        $this->getSession()->wait($timeoutMs,
213
            "(typeof window.__ajaxStatus !== 'undefined' ? window.__ajaxStatus() : 'no ajax') !== 'waiting'"
214
        );
215
216
        // wait additional 100ms to allow DOM to update
217
        $this->getSession()->wait(100);
218
    }
219
220
    /**
221
     * Take screenshot when step fails.
222
     * Works only with Selenium2Driver.
223
     *
224
     * @AfterStep
225
     */
226
	public function takeScreenshotAfterFailedStep(StepEvent $event) {
227
		if (4 === $event->getResult()) {
228
			try{
229
				$this->takeScreenshot($event);
230
			}catch(\WebDriver\Exception $e){
231
				$this->logException($e);
232
			}
233
		}
234
	}
235
236
	/**
237
	 * Close modal dialog if test scenario fails on CMS page
238
	 *
239
	 * @AfterScenario
240
	 */
241
	public function closeModalDialog(ScenarioEvent $event) {
242
		try{
243
			// Only for failed tests on CMS page
244
			if (4 === $event->getResult()) {
245
				$cmsElement = $this->getSession()->getPage()->find('css', '.cms');
246
				if($cmsElement) {
247
					try {
248
						// Navigate away triggered by reloading the page
249
						$this->getSession()->reload();
250
						$this->getSession()->getDriver()->getWebDriverSession()->accept_alert();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Behat\Mink\Driver\DriverInterface as the method getWebDriverSession() does only exist in the following implementations of said interface: Behat\Mink\Driver\Selenium2Driver.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
251
					} catch(\WebDriver\Exception $e) {
252
						// no-op, alert might not be present
253
					}
254
				}
255
			}
256
		}catch(\WebDriver\Exception $e){
257
			$this->logException($e);
258
		}
259
	}
260
	
261
    /**
262
     * Delete any created files and folders from assets directory
263
     *
264
     * @AfterScenario @assets
265
     */
266
	public function cleanAssetsAfterScenario(ScenarioEvent $event) {
0 ignored issues
show
Unused Code introduced by
The parameter $event is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
267
        foreach(\File::get() as $file) {
268
            $file->delete();
269
        }
270
		\Filesystem::removeFolder(ASSETS_PATH, true);
271
    }
272
273
    public function takeScreenshot(StepEvent $event) {
274
        $driver = $this->getSession()->getDriver();
275
        // quit silently when unsupported
276
        if (!($driver instanceof Selenium2Driver)) {
277
            return;
278
        }
279
280
        $parent = $event->getLogicalParent();
281
        $feature = $parent->getFeature();
282
        $step = $event->getStep();
283
        $screenshotPath = null;
0 ignored issues
show
Unused Code introduced by
$screenshotPath is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
284
285
        $path = $this->getMainContext()->getScreenshotPath();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Behat\Behat\Context\ExtendedContextInterface as the method getScreenshotPath() does only exist in the following implementations of said interface: SilverStripe\BehatExtens...ext\SilverStripeContext.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
286
        if(!$path) return; // quit silently when path is not set
287
288
        \Filesystem::makeFolder($path);
289
        $path = realpath($path);
290
291 View Code Duplication
        if (!file_exists($path)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
292
            file_put_contents('php://stderr', sprintf('"%s" is not valid directory and failed to create it' . PHP_EOL, $path));
293
            return;
294
        }
295
296 View Code Duplication
        if (file_exists($path) && !is_dir($path)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
297
            file_put_contents('php://stderr', sprintf('"%s" is not valid directory' . PHP_EOL, $path));
298
            return;
299
        }
300 View Code Duplication
        if (file_exists($path) && !is_writable($path)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
301
            file_put_contents('php://stderr', sprintf('"%s" directory is not writable' . PHP_EOL, $path));
302
            return;
303
        }
304
305
        $path = sprintf('%s/%s_%d.png', $path, basename($feature->getFile()), $step->getLine());
306
        $screenshot = $driver->getWebDriverSession()->screenshot();
307
        file_put_contents($path, base64_decode($screenshot));
308
        file_put_contents('php://stderr', sprintf('Saving screenshot into %s' . PHP_EOL, $path));
309
    }
310
311
    /**
312
     * @Then /^I should be redirected to "([^"]+)"/
313
     */
314
	public function stepIShouldBeRedirectedTo($url) {
315
        if ($this->getMainContext()->canIntercept()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Behat\Behat\Context\ExtendedContextInterface as the method canIntercept() does only exist in the following implementations of said interface: SilverStripe\BehatExtens...ext\SilverStripeContext.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
316
            $client = $this->getSession()->getDriver()->getClient();
0 ignored issues
show
Bug introduced by
The method getClient() does not seem to exist on object<Behat\Mink\Driver\DriverInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
317
            $client->followRedirects(true);
318
            $client->followRedirect();
319
320
            $url = $this->getMainContext()->joinUrlParts($this->context['base_url'], $url);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Behat\Behat\Context\ExtendedContextInterface as the method joinUrlParts() does only exist in the following implementations of said interface: SilverStripe\BehatExtens...ext\SilverStripeContext.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
321
322
            assertTrue($this->getMainContext()->isCurrentUrlSimilarTo($url), sprintf('Current URL is not %s', $url));
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Behat\Behat\Context\ExtendedContextInterface as the method isCurrentUrlSimilarTo() does only exist in the following implementations of said interface: SilverStripe\BehatExtens...ext\SilverStripeContext.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
323
        }
324
    }
325
326
    /**
327
     * @Given /^the page can't be found/
328
     */
329
	public function stepPageCantBeFound() {
330
        $page = $this->getSession()->getPage();
331
        assertTrue(
332
            // Content from ErrorPage default record
333
            $page->hasContent('Page not found')
334
            // Generic ModelAsController message
335
            || $page->hasContent('The requested page could not be found')
336
        );
337
    }
338
339
    /**
340
     * @Given /^I wait (?:for )?([\d\.]+) second(?:s?)$/
341
     */
342
	public function stepIWaitFor($secs) {
343
        $this->getSession()->wait((float)$secs*1000);
344
    }
345
346
    /**
347
     * @Given /^I press the "([^"]*)" button$/
348
     */
349
	public function stepIPressTheButton($button) {
350
        $page = $this->getSession()->getPage();
351
        $els = $page->findAll('named', array('link_or_button', "'$button'"));
352
        $matchedEl = null;
353
        foreach($els as $el) {
354
            if($el->isVisible()) $matchedEl = $el;
355
        }
356
        assertNotNull($matchedEl, sprintf('%s button not found', $button));
357
        $matchedEl->click();
358
    }
359
360
    /**
361
     * Needs to be in single command to avoid "unexpected alert open" errors in Selenium.
362
     * Example1: I press the "Remove current combo" button, confirming the dialog
363
     * Example2: I follow the "Remove current combo" link, confirming the dialog
364
     *
365
     * @Given /^I (?:press|follow) the "([^"]*)" (?:button|link), confirming the dialog$/
366
     */
367
	public function stepIPressTheButtonConfirmingTheDialog($button) {
368
        $this->stepIPressTheButton($button);
369
        $this->iConfirmTheDialog();
370
    }
371
372
    /**
373
     * Needs to be in single command to avoid "unexpected alert open" errors in Selenium.
374
     * Example: I follow the "Remove current combo" link, dismissing the dialog
375
     *
376
     * @Given /^I (?:press|follow) the "([^"]*)" (?:button|link), dismissing the dialog$/
377
     */
378
	public function stepIPressTheButtonDismissingTheDialog($button) {
379
		$this->stepIPressTheButton($button);
380
		$this->iDismissTheDialog();
381
    }
382
383
    /**
384
     * @Given /^I (click|double click) "([^"]*)" in the "([^"]*)" element$/
385
     */
386
    public function iClickInTheElement($clickType, $text, $selector) {
387
        $clickTypeMap = array(
388
            "double click" => "doubleclick",
389
            "click" => "click"
390
        );
391
        $page = $this->getSession()->getPage();
392
        $parentElement = $page->find('css', $selector);
393
        assertNotNull($parentElement, sprintf('"%s" element not found', $selector));
394
        $element = $parentElement->find('xpath', sprintf('//*[count(*)=0 and contains(.,"%s")]', $text));
395
        assertNotNull($element, sprintf('"%s" not found', $text));
396
        $clickTypeFn = $clickTypeMap[$clickType];
397
        $element->$clickTypeFn();
398
    }
399
    
400
    /**
401
    * Needs to be in single command to avoid "unexpected alert open" errors in Selenium.
402
    * Example: I click "Delete" in the ".actions" element, confirming the dialog
403
    *
404
    * @Given /^I (click|double click) "([^"]*)" in the "([^"]*)" element, confirming the dialog$/
405
    */
406
   public function iClickInTheElementConfirmingTheDialog($clickType, $text, $selector) {
407
       $this->iClickInTheElement($clickType, $text, $selector);
408
       $this->iConfirmTheDialog();
409
   }
410
   /**
411
    * Needs to be in single command to avoid "unexpected alert open" errors in Selenium.
412
    * Example: I click "Delete" in the ".actions" element, dismissing the dialog
413
    *
414
    * @Given /^I (click|double click) "([^"]*)" in the "([^"]*)" element, dismissing the dialog$/
415
    */
416
   public function iClickInTheElementDismissingTheDialog($clickType, $text, $selector) {
417
       $this->iClickInTheElement($clickType, $text, $selector);
418
       $this->iDismissTheDialog();
419
   }
420
421
    /**
422
     * @Given /^I type "([^"]*)" into the dialog$/
423
     */
424
	public function iTypeIntoTheDialog($data) {
425
        $data = array(
426
            'text' => $data,
427
        );
428
        $this->getSession()->getDriver()->getWebDriverSession()->postAlert_text($data);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Behat\Mink\Driver\DriverInterface as the method getWebDriverSession() does only exist in the following implementations of said interface: Behat\Mink\Driver\Selenium2Driver.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
429
    }
430
431
    /**
432
     * @Given /^I confirm the dialog$/
433
     */
434
	public function iConfirmTheDialog() {
435
        $this->getSession()->getDriver()->getWebDriverSession()->accept_alert();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Behat\Mink\Driver\DriverInterface as the method getWebDriverSession() does only exist in the following implementations of said interface: Behat\Mink\Driver\Selenium2Driver.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
436
        $this->handleAjaxTimeout();
437
    }
438
439
    /**
440
     * @Given /^I dismiss the dialog$/
441
     */
442
	public function iDismissTheDialog() {
443
        $this->getSession()->getDriver()->getWebDriverSession()->dismiss_alert();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Behat\Mink\Driver\DriverInterface as the method getWebDriverSession() does only exist in the following implementations of said interface: Behat\Mink\Driver\Selenium2Driver.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
444
        $this->handleAjaxTimeout();
445
    }
446
447
    /**
448
     * @Given /^(?:|I )attach the file "(?P<path>[^"]*)" to "(?P<field>(?:[^"]|\\")*)" with HTML5$/
449
     */
450
	public function iAttachTheFileTo($field, $path) {
451
        // Remove wrapped button styling to make input field accessible to Selenium
452
        $js = <<<JS
453
var input = jQuery('[name="$field"]');
454
if(input.closest('.ss-uploadfield-item-info').length) {
455
    while(!input.parent().is('.ss-uploadfield-item-info')) input = input.unwrap();
456
}
457
JS;
458
459
        $this->getSession()->executeScript($js);
460
        $this->getSession()->wait(1000);
461
462
        return new Step\Given(sprintf('I attach the file "%s" to "%s"', $path, $field));
463
    }
464
465
	/**
466
	 * Select an individual input from within a group, matched by the top-most label.
467
	 *
468
	 * @Given /^I select "([^"]*)" from "([^"]*)" input group$/
469
	 */
470
	public function iSelectFromInputGroup($value, $labelText) {
471
		$page = $this->getSession()->getPage();
472
		$parent = null;
473
474
		foreach($page->findAll('css', 'label') as $label) {
475
			if($label->getText() == $labelText) {
476
				$parent = $label->getParent();
477
			}
478
		}
479
480
		if(!$parent) throw new \InvalidArgumentException(sprintf('Input group with label "%s" cannot be found', $labelText));
481
482
		foreach($parent->findAll('css', 'label') as $option) {
483
			if($option->getText() == $value) {
484
				$for = $option->getAttribute('for');
485
				$input = $parent->findById($for);
486
487
				if(!$input) throw new \InvalidArgumentException(sprintf('Input "%s" cannot be found', $value));
488
489
				$this->getSession()->getDriver()->click($input->getXPath());
490
			}
491
		}
492
	}
493
494
    /**
495
     * Pauses the scenario until the user presses a key. Useful when debugging a scenario.
496
     *
497
     * @Then /^(?:|I )put a breakpoint$/
498
     */
499
	public function iPutABreakpoint() {
500
        fwrite(STDOUT, "\033[s    \033[93m[Breakpoint] Press \033[1;93m[RETURN]\033[0;93m to continue...\033[0m");
501
        while (fgets(STDIN, 1024) == '') {}
502
        fwrite(STDOUT, "\033[u");
503
504
        return;
505
    }
506
507
	/**
508
	 * Transforms relative time statements compatible with strtotime().
509
	 * Example: "time of 1 hour ago" might return "22:00:00" if its currently "23:00:00".
510
	 * Customize through {@link setTimeFormat()}.
511
	 *
512
	 * @Transform /^(?:(the|a)) time of (?<val>.*)$/
513
	 */
514 View Code Duplication
	public function castRelativeToAbsoluteTime($prefix, $val) {
0 ignored issues
show
Unused Code introduced by
The parameter $prefix is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
515
		$timestamp = strtotime($val);
516
		if(!$timestamp) {
517
			throw new \InvalidArgumentException(sprintf(
518
				"Can't resolve '%s' into a valid datetime value",
519
				$val
520
			));
521
		}
522
		return date($this->timeFormat, $timestamp);
523
	}
524
525
	/**
526
	 * Transforms relative date and time statements compatible with strtotime().
527
	 * Example: "datetime of 2 days ago" might return "2013-10-10 22:00:00" if its currently
528
	 * the 12th of October 2013. Customize through {@link setDatetimeFormat()}.
529
	 *
530
	 * @Transform /^(?:(the|a)) datetime of (?<val>.*)$/
531
	 */
532 View Code Duplication
	public function castRelativeToAbsoluteDatetime($prefix, $val) {
0 ignored issues
show
Unused Code introduced by
The parameter $prefix is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
533
		$timestamp = strtotime($val);
534
		if(!$timestamp) {
535
			throw new \InvalidArgumentException(sprintf(
536
				"Can't resolve '%s' into a valid datetime value",
537
				$val
538
			));
539
		}
540
		return date($this->datetimeFormat, $timestamp);
541
	}
542
543
	/**
544
	 * Transforms relative date statements compatible with strtotime().
545
	 * Example: "date 2 days ago" might return "2013-10-10" if its currently
546
	 * the 12th of October 2013. Customize through {@link setDateFormat()}.
547
	 *
548
	 * @Transform /^(?:(the|a)) date of (?<val>.*)$/
549
	 */
550 View Code Duplication
	public function castRelativeToAbsoluteDate($prefix, $val) {
0 ignored issues
show
Unused Code introduced by
The parameter $prefix is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
551
		$timestamp = strtotime($val);
552
		if(!$timestamp) {
553
			throw new \InvalidArgumentException(sprintf(
554
				"Can't resolve '%s' into a valid datetime value",
555
				$val
556
			));
557
		}
558
		return date($this->dateFormat, $timestamp);
559
	}
560
561
	public function getDateFormat() {
562
		return $this->dateFormat;
563
	}
564
565
	public function setDateFormat($format) {
566
		$this->dateFormat = $format;
567
	}
568
569
	public function getTimeFormat() {
570
		return $this->timeFormat;
571
	}
572
573
	public function setTimeFormat($format) {
574
		$this->timeFormat = $format;
575
	}
576
577
	public function getDatetimeFormat() {
578
		return $this->datetimeFormat;
579
	}
580
581
	public function setDatetimeFormat($format) {
582
		$this->datetimeFormat = $format;
583
	}
584
585
    /**
586
     * Checks that field with specified in|name|label|value is disabled.
587
     * Example: Then the field "Email" should be disabled
588
     * Example: Then the "Email" field should be disabled
589
     *
590
     * @Then /^the "(?P<name>(?:[^"]|\\")*)" (?P<type>(?:(field|button))) should (?P<negate>(?:(not |)))be disabled/
591
     * @Then /^the (?P<type>(?:(field|button))) "(?P<name>(?:[^"]|\\")*)" should (?P<negate>(?:(not |)))be disabled/
592
     */
593
    public function stepFieldShouldBeDisabled($name, $type, $negate) {
594
        $page = $this->getSession()->getPage();
595
        if($type == 'field') {
596
            $element = $page->findField($name);
597
        } else {
598
            $element = $page->find('named', array(
599
                'button', $this->getSession()->getSelectorsHandler()->xpathLiteral($name)
600
            ));
601
        }
602
603
        assertNotNull($element, sprintf("Element '%s' not found", $name));
604
605
        $disabledAttribute = $element->getAttribute('disabled');
606
        if(trim($negate)) {
607
            assertNull($disabledAttribute, sprintf("Failed asserting element '%s' is not disabled", $name));
608
        } else {
609
            assertNotNull($disabledAttribute, sprintf("Failed asserting element '%s' is disabled", $name));
610
        }
611
    }
612
613
	/**
614
	 * Checks that checkbox with specified in|name|label|value is enabled.
615
	 * Example: Then the field "Email" should be enabled
616
	 * Example: Then the "Email" field should be enabled
617
	 *
618
	 * @Then /^the "(?P<field>(?:[^"]|\\")*)" field should be enabled/
619
	 * @Then /^the field "(?P<field>(?:[^"]|\\")*)" should be enabled/
620
	 */
621
	public function stepFieldShouldBeEnabled($field) {
622
		$page = $this->getSession()->getPage();
623
		$fieldElement = $page->findField($field);
624
		assertNotNull($fieldElement, sprintf("Field '%s' not found", $field));
625
626
		$disabledAttribute = $fieldElement->getAttribute('disabled');
627
628
		assertNull($disabledAttribute, sprintf("Failed asserting field '%s' is enabled", $field));
629
	}
630
631
    /**
632
     * Clicks a link in a specific region (an element identified by a CSS selector, a "data-title" attribute,
633
     * or a named region mapped to a CSS selector via Behat configuration).
634
     *
635
     * Example: Given I follow "Select" in the "header .login-form" region
636
     * Example: Given I follow "Select" in the "My Login Form" region
637
     *
638
     * @Given /^I (?:follow|click) "(?P<link>[^"]*)" in the "(?P<region>[^"]*)" region$/
639
     */
640 View Code Duplication
    public function iFollowInTheRegion($link, $region) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
641
        $context = $this->getMainContext();
642
        $regionObj = $context->getRegionObj($region);
643
        assertNotNull($regionObj);
644
645
        $linkObj = $regionObj->findLink($link);
646
        if (empty($linkObj)) {
647
			throw new \Exception(sprintf('The link "%s" was not found in the region "%s" 
648
				on the page %s', $link, $region, $this->getSession()->getCurrentUrl()));
649
        }
650
651
        $linkObj->click();
652
    }
653
654
    /**
655
     * Fills in a field in a specfic region similar to (@see iFollowInTheRegion or @see iSeeTextInRegion)
656
     *
657
     * Example: Given I fill in "Hello" with "World"
658
     *
659
     * @Given /^I fill in "(?P<field>[^"]*)" with "(?P<value>[^"]*)" in the "(?P<region>[^"]*)" region$/
660
     */
661 View Code Duplication
    public function iFillinTheRegion($field, $value, $region){
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
662
        $context = $this->getMainContext();
663
        $regionObj = $context->getRegionObj($region);
664
        assertNotNull($regionObj, "Region Object is null");
665
666
        $fieldObj = $regionObj->findField($field);
667
        if (empty($fieldObj)) {
668
			throw new \Exception(sprintf('The field "%s" was not found in the region "%s" 
669
				on the page %s', $field, $region, $this->getSession()->getCurrentUrl()));
670
        }
671
672
        $regionObj->fillField($field, $value);
673
    }
674
675
676
    /**
677
     * Asserts text in a specific region (an element identified by a CSS selector, a "data-title" attribute,
678
     * or a named region mapped to a CSS selector via Behat configuration).
679
     * Supports regular expressions in text value.
680
     *
681
     * Example: Given I should see "My Text" in the "header .login-form" region
682
     * Example: Given I should not see "My Text" in the "My Login Form" region
683
     *
684
     * @Given /^I should (?P<negate>(?:(not |)))see "(?P<text>[^"]*)" in the "(?P<region>[^"]*)" region$/
685
     */
686
    public function iSeeTextInRegion($negate, $text, $region) {
687
        $context = $this->getMainContext();
688
        $regionObj = $context->getRegionObj($region);
689
        assertNotNull($regionObj);
690
691
        $actual = $regionObj->getText();
692
        $actual = preg_replace('/\s+/u', ' ', $actual);
693
        $regex  = '/'.preg_quote($text, '/').'/ui';
694
695
        if(trim($negate)) {
696 View Code Duplication
            if (preg_match($regex, $actual)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
697
                $message = sprintf(
698
                    'The text "%s" was found in the text of the "%s" region on the page %s.',
699
                    $text,
700
                    $region,
701
                    $this->getSession()->getCurrentUrl()
702
                );
703
704
                throw new \Exception($message);
705
            }
706 View Code Duplication
        } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
707
            if (!preg_match($regex, $actual)) {
708
                $message = sprintf(
709
                    'The text "%s" was not found anywhere in the text of the "%s" region on the page %s.',
710
                    $text,
711
                    $region,
712
                    $this->getSession()->getCurrentUrl()
713
                );
714
715
                throw new \Exception($message);
716
            }
717
        }
718
719
    }
720
721
	/**
722
	 * Selects the specified radio button
723
	 *
724
	 * @Given /^I select the "([^"]*)" radio button$/
725
	 */
726
	public function iSelectTheRadioButton($radioLabel) {
727
		$session = $this->getSession();
728
		$radioButton = $session->getPage()->find('named', array(
729
                      'radio', $this->getSession()->getSelectorsHandler()->xpathLiteral($radioLabel)
730
                  ));
731
		assertNotNull($radioButton);
732
		$session->getDriver()->click($radioButton->getXPath());
733
	}
734
735
    /**
736
     * @Then /^the "([^"]*)" table should contain "([^"]*)"$/
737
     */
738
    public function theTableShouldContain($selector, $text) {
739
        $table = $this->getTable($selector);
740
741
        $element = $table->find('named', array('content', "'$text'"));
742
        assertNotNull($element, sprintf('Element containing `%s` not found in `%s` table', $text, $selector));
743
    }
744
745
    /**
746
     * @Then /^the "([^"]*)" table should not contain "([^"]*)"$/
747
     */
748
    public function theTableShouldNotContain($selector, $text) {
749
        $table = $this->getTable($selector);
750
751
        $element = $table->find('named', array('content', "'$text'"));
752
        assertNull($element, sprintf('Element containing `%s` not found in `%s` table', $text, $selector));
753
    }
754
755
    /**
756
     * @Given /^I click on "([^"]*)" in the "([^"]*)" table$/
757
     */
758
    public function iClickOnInTheTable($text, $selector) {
759
        $table = $this->getTable($selector);
760
761
        $element = $table->find('xpath', sprintf('//*[count(*)=0 and contains(.,"%s")]', $text));
762
        assertNotNull($element, sprintf('Element containing `%s` not found', $text));
763
        $element->click();
764
    }
765
766
    /**
767
     * Finds the first visible table by various factors:
768
     * - table[id]
769
     * - table[title]
770
     * - table *[class=title]
771
     * - fieldset[data-name] table
772
     * - table caption
773
     *
774
     * @return Behat\Mink\Element\NodeElement
775
     */
776
    protected function getTable($selector) {
777
        $selector = $this->getSession()->getSelectorsHandler()->xpathLiteral($selector);
778
        $page = $this->getSession()->getPage();
779
        $candidates = $page->findAll(
780
            'xpath',
781
            $this->getSession()->getSelectorsHandler()->selectorToXpath(
782
                "xpath", ".//table[(./@id = $selector or  contains(./@title, $selector))]"
783
            )
784
        );
785
786
        // Find tables by a <caption> field
787
		$candidates += $page->findAll('xpath', "//table//caption[contains(normalize-space(string(.)), 
788
			$selector)]/ancestor-or-self::table[1]");
789
790
        // Find tables by a .title node
791
		$candidates += $page->findAll('xpath', "//table//*[@class='title' and contains(normalize-space(string(.)), 
792
			$selector)]/ancestor-or-self::table[1]");
793
794
        // Some tables don't have a visible title, so look for a fieldset with data-name instead
795
        $candidates += $page->findAll('xpath', "//fieldset[@data-name=$selector]//table");
796
797
        assertTrue((bool)$candidates, 'Could not find any table elements');
798
799
        $table = null;
800
        foreach($candidates as $candidate) {
801
            if(!$table && $candidate->isVisible()) {
802
                $table = $candidate;
803
            }
804
        }
805
806
        assertTrue((bool)$table, 'Found table elements, but none are visible');
807
808
        return $table;
809
    }
810
811
	/**
812
	 * Checks the order of two texts.
813
	 * Assumptions: the two texts appear in their conjunct parent element once
814
	 * @Then /^I should see the text "(?P<textBefore>(?:[^"]|\\")*)" (before|after) the text "(?P<textAfter>(?:[^"]|\\")*)" in the "(?P<element>[^"]*)" element$/
815
	 */
816
	public function theTextBeforeAfter($textBefore, $order, $textAfter, $element) {
817
		$ele = $this->getSession()->getPage()->find('css', $element);
818
		assertNotNull($ele, sprintf('%s not found', $element));
819
820
		// Check both of the texts exist in the element
821
		$text = $ele->getText();
822
		assertTrue(strpos($text, $textBefore) !== 'FALSE', sprintf('%s not found in the element %s', $textBefore, $element));
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of strpos($text, $textBefore) (integer) and 'FALSE' (string) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
823
		assertTrue(strpos($text, $textAfter) !== 'FALSE', sprintf('%s not found in the element %s', $textAfter, $element));
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of strpos($text, $textAfter) (integer) and 'FALSE' (string) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
824
825
		/// Use strpos to get the position of the first occurrence of the two texts (case-sensitive)
826
		// and compare them with the given order (before or after)
827
		if($order === 'before') {
828
			assertTrue(strpos($text, $textBefore) < strpos($text, $textAfter));
829
		} else {
830
			assertTrue(strpos($text, $textBefore) > strpos($text, $textAfter));
831
		}
832
	}
833
834
	/**
835
	* Wait until a certain amount of seconds till I see an element  identified by a CSS selector.
836
	*
837
	* Example: Given I wait for 10 seconds until I see the ".css_element" element
838
	*
839
	* @Given /^I wait for (\d+) seconds until I see the "([^"]*)" element$/
840
	**/
841 View Code Duplication
	public function iWaitXUntilISee($wait, $selector) {
0 ignored issues
show
Unused Code introduced by
The parameter $wait is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
842
		$page = $this->getSession()->getPage();
843
844
		$this->spin(function($page) use ($page, $selector){
0 ignored issues
show
Bug Best Practice introduced by
The parameter name $page conflicts with one of the imported variables.
Loading history...
845
			$element = $page->find('css', $selector);
846
847
			if(empty($element)) {
848
				return false;
849
			} else {
850
				return $element->isVisible();
851
			}
852
		});
853
	}
854
855
    /**
856
     * Wait until a particular element is visible, using a CSS selector. Useful for content loaded via AJAX, or only
857
     * populated after JS execution.
858
     *
859
     * Example: Given I wait until I see the "header .login-form" element
860
     *
861
     * @Given /^I wait until I see the "([^"]*)" element$/
862
     */
863 View Code Duplication
    public function iWaitUntilISee($selector) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
864
        $page = $this->getSession()->getPage();
865
        $this->spin(function($page) use ($page, $selector){
0 ignored issues
show
Bug Best Practice introduced by
The parameter name $page conflicts with one of the imported variables.
Loading history...
866
            $element = $page->find('css', $selector);
867
            if(empty($element)){
868
                return false;
869
            } else{
870
                return ($element->isVisible());
871
            }
872
        });
873
    }
874
875
    /**
876
     * Wait until a particular string is found on the page. Useful for content loaded via AJAX, or only populated after
877
     * JS execution.
878
     *
879
     * Example: Given I wait until I see the text "Welcome back, John!"
880
     *
881
     * @Given /^I wait until I see the text "([^"]*)"$/
882
     */
883
    public function iWaitUntilISeeText($text){
884
        $page = $this->getSession()->getPage();
885
        $session = $this->getSession();
886
        $this->spin(function($page) use ($page, $session, $text) {
0 ignored issues
show
Bug Best Practice introduced by
The parameter name $page conflicts with one of the imported variables.
Loading history...
887
            $element = $page->find(
888
                'xpath',
889
                $session->getSelectorsHandler()->selectorToXpath("xpath", ".//*[contains(text(), '$text')]")
890
            );
891
892
            if(empty($element)) {
893
                return false;
894
            } else {
895
                return ($element->isVisible());
896
            }
897
        });
898
    }
899
900
	/**
901
	 * @Given /^I scroll to the bottom$/
902
	 */
903
	public function iScrollToBottom() {
904
		$javascript = 'window.scrollTo(0, Math.max(document.documentElement.scrollHeight, document.body.scrollHeight, document.documentElement.clientHeight));';
905
		$this->getSession()->executeScript($javascript);
906
	}
907
908
	/**
909
	 * @Given /^I scroll to the top$/
910
	 */
911
	public function iScrollToTop() {
912
		$this->getSession()->executeScript('window.scrollTo(0,0);');
913
	}
914
915
	/**
916
	 * Scroll to a certain element by label.
917
	 * Requires an "id" attribute to uniquely identify the element in the document.
918
	 *
919
	 * Example: Given I scroll to the "Submit" button
920
	 * Example: Given I scroll to the "My Date" field
921
	 *
922
	 * @Given /^I scroll to the "([^"]*)" (field|link|button)$/
923
	 */
924
	public function iScrollToField($locator, $type) {
925
		$page = $this->getSession()->getPage();
926
        $el = $page->find('named', array($type, "'$locator'"));
927
        assertNotNull($el, sprintf('%s element not found', $locator));
928
929
        $id = $el->getAttribute('id');
930
		if(empty($id)) {
931
			throw new \InvalidArgumentException('Element requires an "id" attribute');
932
		}
933
934
		$js = sprintf("document.getElementById('%s').scrollIntoView(true);", $id);
935
		$this->getSession()->executeScript($js);
936
	}
937
938
	/**
939
	 * Scroll to a certain element by CSS selector.
940
	 * Requires an "id" attribute to uniquely identify the element in the document.
941
	 *
942
	 * Example: Given I scroll to the ".css_element" element
943
	 *
944
	 * @Given /^I scroll to the "(?P<locator>(?:[^"]|\\")*)" element$/
945
	 */
946
	public function iScrollToElement($locator) {
947
		$el = $this->getSession()->getPage()->find('css', $locator);
948
		assertNotNull($el, sprintf('The element "%s" is not found', $locator));
949
950
		$id = $el->getAttribute('id');
951
		if(empty($id)) {
952
			throw new \InvalidArgumentException('Element requires an "id" attribute');
953
		}
954
955
		$js = sprintf("document.getElementById('%s').scrollIntoView(true);", $id);
956
		$this->getSession()->executeScript($js);
957
	}
958
959
    /**
960
     * Continuously poll the dom until callback returns true, code copied from
961
     * (@link http://docs.behat.org/cookbook/using_spin_functions.html)
962
     * If not found within a given wait period, timeout and throw error
963
     *
964
     * @param callback $lambda The function to run continuously
965
     * @param integer $wait Timeout in seconds
966
     * @return bool Returns true if the lambda returns successfully
967
     * @throws \Exception Thrown if the wait threshold is exceeded without the lambda successfully returning
968
     */
969
    public function spin($lambda, $wait = 60) {
970
        for ($i = 0; $i < $wait; $i++) {
971
            try {
972
                if($lambda($this)) {
973
                    return true;
974
                }
975
            } catch (\Exception $e) {
976
                // do nothing
977
            }
978
979
            sleep(1);
980
        }
981
982
        $backtrace = debug_backtrace();
983
984
        throw new \Exception(sprintf(
985
            "Timeout thrown by %s::%s()\n.",
986
            $backtrace[1]['class'],
987
            $backtrace[1]['function']
988
        ));
989
    }
990
	
991
	
992
	
993
	/**
994
	 * We have to catch exceptions and log somehow else otherwise behat falls over
995
	 */
996
	protected function logException($e){
997
		file_put_contents('php://stderr', 'Exception caught: '.$e);
998
	}
999
	
1000
}
1001