This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace SilverStripe\BehatExtension\Context; |
||
4 | |||
5 | use Behat\Behat\Context\BehatContext; |
||
6 | use Behat\Behat\Context\Step; |
||
7 | use Behat\Behat\Event\StepEvent; |
||
8 | use Behat\Behat\Event\ScenarioEvent; |
||
9 | |||
10 | use Behat\Mink\Driver\Selenium2Driver; |
||
11 | |||
12 | use Behat\Gherkin\Node\PyStringNode; |
||
13 | use Behat\Gherkin\Node\TableNode; |
||
14 | |||
15 | // PHPUnit |
||
16 | require_once BASE_PATH . '/vendor/phpunit/phpunit/src/Framework/Assert/Functions.php'; |
||
17 | |||
18 | /** |
||
19 | * BasicContext |
||
20 | * |
||
21 | * Context used to define generic steps like following anchors or pressing buttons. |
||
22 | * Handles timeouts. |
||
23 | * Handles redirections. |
||
24 | * Handles AJAX enabled links, buttons and forms - jQuery is assumed. |
||
25 | */ |
||
26 | class BasicContext extends BehatContext |
||
27 | { |
||
28 | protected $context; |
||
29 | |||
30 | /** |
||
31 | * Date format in date() syntax |
||
32 | * @var String |
||
33 | */ |
||
34 | protected $dateFormat = 'Y-m-d'; |
||
35 | |||
36 | /** |
||
37 | * Time format in date() syntax |
||
38 | * @var String |
||
39 | */ |
||
40 | protected $timeFormat = 'H:i:s'; |
||
41 | |||
42 | /** |
||
43 | * Date/time format in date() syntax |
||
44 | * @var String |
||
45 | */ |
||
46 | protected $datetimeFormat = 'Y-m-d H:i:s'; |
||
47 | |||
48 | /** |
||
49 | * Initializes context. |
||
50 | * Every scenario gets it's own context object. |
||
51 | * |
||
52 | * @param array $parameters context parameters (set them up through behat.yml) |
||
53 | */ |
||
54 | public function __construct(array $parameters) |
||
55 | { |
||
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 | { |
||
67 | return $this->getMainContext()->getSession($name); |
||
68 | } |
||
69 | |||
70 | /** |
||
71 | * @AfterStep ~@modal |
||
72 | * |
||
73 | * Excluding scenarios with @modal tag is required, |
||
74 | * because modal dialogs stop any JS interaction |
||
75 | */ |
||
76 | public function appendErrorHandlerBeforeStep(StepEvent $event) |
||
77 | { |
||
78 | try { |
||
79 | $javascript = <<<JS |
||
80 | window.onerror = function(message, file, line, column, error) { |
||
81 | var body = document.getElementsByTagName('body')[0]; |
||
82 | var msg = message + " in " + file + ":" + line + ":" + column; |
||
83 | if(error !== undefined && error.stack !== undefined) { |
||
84 | msg += "\\nSTACKTRACE:\\n" + error.stack; |
||
85 | } |
||
86 | body.setAttribute('data-jserrors', '[captured JavaScript error] ' + msg); |
||
87 | } |
||
88 | if ('undefined' !== typeof window.jQuery) { |
||
89 | window.jQuery('body').ajaxError(function(event, jqxhr, settings, exception) { |
||
90 | if ('abort' === exception) return; |
||
91 | window.onerror(event.type + ': ' + settings.type + ' ' + settings.url + ' ' + exception + ' ' + jqxhr.responseText); |
||
92 | }); |
||
93 | } |
||
94 | JS; |
||
95 | |||
96 | $this->getSession()->executeScript($javascript); |
||
97 | } catch (\WebDriver\Exception $e) { |
||
98 | $this->logException($e); |
||
99 | } |
||
100 | } |
||
101 | |||
102 | /** |
||
103 | * @AfterStep ~@modal |
||
104 | * |
||
105 | * Excluding scenarios with @modal tag is required, |
||
106 | * because modal dialogs stop any JS interaction |
||
107 | */ |
||
108 | public function readErrorHandlerAfterStep(StepEvent $event) |
||
109 | { |
||
110 | try { |
||
111 | $page = $this->getSession()->getPage(); |
||
112 | |||
113 | $jserrors = $page->find('xpath', '//body[@data-jserrors]'); |
||
114 | if (null !== $jserrors) { |
||
115 | $this->takeScreenshot($event); |
||
116 | file_put_contents('php://stderr', $jserrors->getAttribute('data-jserrors') . PHP_EOL); |
||
117 | } |
||
118 | |||
119 | $javascript = <<<JS |
||
120 | if ('undefined' !== typeof window.jQuery) { |
||
121 | window.jQuery(document).ready(function() { |
||
122 | window.jQuery('body').removeAttr('data-jserrors'); |
||
123 | }); |
||
124 | } |
||
125 | JS; |
||
126 | |||
127 | $this->getSession()->executeScript($javascript); |
||
128 | } catch (\WebDriver\Exception $e) { |
||
129 | $this->logException($e); |
||
130 | } |
||
131 | } |
||
132 | |||
133 | /** |
||
134 | * Hook into jQuery ajaxStart, ajaxSuccess and ajaxComplete events. |
||
135 | * Prepare __ajaxStatus() functions and attach them to these handlers. |
||
136 | * Event handlers are removed after one run. |
||
137 | * |
||
138 | * @BeforeStep |
||
139 | */ |
||
140 | View Code Duplication | public function handleAjaxBeforeStep(StepEvent $event) |
|
0 ignored issues
–
show
|
|||
141 | { |
||
142 | try { |
||
143 | $ajaxEnabledSteps = $this->getMainContext()->getAjaxSteps(); |
||
144 | $ajaxEnabledSteps = implode('|', array_filter($ajaxEnabledSteps)); |
||
145 | |||
146 | if (empty($ajaxEnabledSteps) || !preg_match('/(' . $ajaxEnabledSteps . ')/i', $event->getStep()->getText())) { |
||
147 | return; |
||
148 | } |
||
149 | |||
150 | $javascript = <<<JS |
||
151 | if ('undefined' !== typeof window.jQuery && 'undefined' !== typeof window.jQuery.fn.on) { |
||
152 | window.jQuery(document).on('ajaxStart.ss.test.behaviour', function(){ |
||
153 | window.__ajaxStatus = function() { |
||
154 | return 'waiting'; |
||
155 | }; |
||
156 | }); |
||
157 | window.jQuery(document).on('ajaxComplete.ss.test.behaviour', function(e, jqXHR){ |
||
158 | if (null === jqXHR.getResponseHeader('X-ControllerURL')) { |
||
159 | window.__ajaxStatus = function() { |
||
160 | return 'no ajax'; |
||
161 | }; |
||
162 | } |
||
163 | }); |
||
164 | window.jQuery(document).on('ajaxSuccess.ss.test.behaviour', function(e, jqXHR){ |
||
165 | if (null === jqXHR.getResponseHeader('X-ControllerURL')) { |
||
166 | window.__ajaxStatus = function() { |
||
167 | return 'success'; |
||
168 | }; |
||
169 | } |
||
170 | }); |
||
171 | } |
||
172 | JS; |
||
173 | $this->getSession()->wait(500); // give browser a chance to process and render response |
||
174 | $this->getSession()->executeScript($javascript); |
||
175 | } catch (\WebDriver\Exception $e) { |
||
176 | $this->logException($e); |
||
177 | } |
||
178 | } |
||
179 | |||
180 | /** |
||
181 | * Wait for the __ajaxStatus()to return anything but 'waiting'. |
||
182 | * Don't wait longer than 5 seconds. |
||
183 | * |
||
184 | * Don't unregister handler if we're dealing with modal windows |
||
185 | * |
||
186 | * @AfterStep ~@modal |
||
187 | */ |
||
188 | View Code Duplication | public function handleAjaxAfterStep(StepEvent $event) |
|
0 ignored issues
–
show
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...
|
|||
189 | { |
||
190 | try { |
||
191 | $ajaxEnabledSteps = $this->getMainContext()->getAjaxSteps(); |
||
192 | $ajaxEnabledSteps = implode('|', array_filter($ajaxEnabledSteps)); |
||
193 | |||
194 | if (empty($ajaxEnabledSteps) || !preg_match('/(' . $ajaxEnabledSteps . ')/i', $event->getStep()->getText())) { |
||
195 | return; |
||
196 | } |
||
197 | |||
198 | $this->handleAjaxTimeout(); |
||
199 | |||
200 | $javascript = <<<JS |
||
201 | if ('undefined' !== typeof window.jQuery && 'undefined' !== typeof window.jQuery.fn.off) { |
||
202 | window.jQuery(document).off('ajaxStart.ss.test.behaviour'); |
||
203 | window.jQuery(document).off('ajaxComplete.ss.test.behaviour'); |
||
204 | window.jQuery(document).off('ajaxSuccess.ss.test.behaviour'); |
||
205 | } |
||
206 | JS; |
||
207 | $this->getSession()->executeScript($javascript); |
||
208 | } catch (\WebDriver\Exception $e) { |
||
209 | $this->logException($e); |
||
210 | } |
||
211 | } |
||
212 | |||
213 | public function handleAjaxTimeout() |
||
214 | { |
||
215 | $timeoutMs = $this->getMainContext()->getAjaxTimeout(); |
||
216 | |||
217 | // Wait for an ajax request to complete, but only for a maximum of 5 seconds to avoid deadlocks |
||
218 | $this->getSession()->wait( |
||
219 | $timeoutMs, |
||
220 | "(typeof window.__ajaxStatus !== 'undefined' ? window.__ajaxStatus() : 'no ajax') !== 'waiting'" |
||
221 | ); |
||
222 | |||
223 | // wait additional 100ms to allow DOM to update |
||
224 | $this->getSession()->wait(100); |
||
225 | } |
||
226 | |||
227 | /** |
||
228 | * Take screenshot when step fails. |
||
229 | * Works only with Selenium2Driver. |
||
230 | * |
||
231 | * @AfterStep |
||
232 | */ |
||
233 | public function takeScreenshotAfterFailedStep(StepEvent $event) |
||
234 | { |
||
235 | if (4 === $event->getResult()) { |
||
236 | try { |
||
237 | $this->takeScreenshot($event); |
||
238 | } catch (\WebDriver\Exception $e) { |
||
239 | $this->logException($e); |
||
240 | } |
||
241 | } |
||
242 | } |
||
243 | |||
244 | /** |
||
245 | * Close modal dialog if test scenario fails on CMS page |
||
246 | * |
||
247 | * @AfterScenario |
||
248 | */ |
||
249 | public function closeModalDialog(ScenarioEvent $event) |
||
250 | { |
||
251 | try { |
||
252 | // Only for failed tests on CMS page |
||
253 | if (4 === $event->getResult()) { |
||
254 | $cmsElement = $this->getSession()->getPage()->find('css', '.cms'); |
||
255 | if ($cmsElement) { |
||
256 | try { |
||
257 | // Navigate away triggered by reloading the page |
||
258 | $this->getSession()->reload(); |
||
259 | $this->getSession()->getDriver()->getWebDriverSession()->accept_alert(); |
||
260 | } catch (\WebDriver\Exception $e) { |
||
261 | // no-op, alert might not be present |
||
262 | } |
||
263 | } |
||
264 | } |
||
265 | } catch (\WebDriver\Exception $e) { |
||
266 | $this->logException($e); |
||
267 | } |
||
268 | } |
||
269 | |||
270 | /** |
||
271 | * Delete any created files and folders from assets directory |
||
272 | * |
||
273 | * @AfterScenario @assets |
||
274 | */ |
||
275 | public function cleanAssetsAfterScenario(ScenarioEvent $event) |
||
276 | { |
||
277 | foreach (\File::get() as $file) { |
||
278 | $file->delete(); |
||
279 | } |
||
280 | \Filesystem::removeFolder(ASSETS_PATH, true); |
||
281 | } |
||
282 | |||
283 | public function takeScreenshot(StepEvent $event) |
||
284 | { |
||
285 | $driver = $this->getSession()->getDriver(); |
||
286 | // quit silently when unsupported |
||
287 | if (!($driver instanceof Selenium2Driver)) { |
||
288 | return; |
||
289 | } |
||
290 | |||
291 | $parent = $event->getLogicalParent(); |
||
292 | $feature = $parent->getFeature(); |
||
293 | $step = $event->getStep(); |
||
294 | $screenshotPath = null; |
||
295 | |||
296 | $path = $this->getMainContext()->getScreenshotPath(); |
||
297 | if (!$path) { |
||
298 | return; |
||
299 | } // quit silently when path is not set |
||
300 | |||
301 | \Filesystem::makeFolder($path); |
||
302 | $path = realpath($path); |
||
303 | |||
304 | View Code Duplication | if (!file_exists($path)) { |
|
0 ignored issues
–
show
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...
|
|||
305 | file_put_contents('php://stderr', sprintf('"%s" is not valid directory and failed to create it' . PHP_EOL, $path)); |
||
306 | return; |
||
307 | } |
||
308 | |||
309 | View Code Duplication | if (file_exists($path) && !is_dir($path)) { |
|
0 ignored issues
–
show
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...
|
|||
310 | file_put_contents('php://stderr', sprintf('"%s" is not valid directory' . PHP_EOL, $path)); |
||
311 | return; |
||
312 | } |
||
313 | View Code Duplication | if (file_exists($path) && !is_writable($path)) { |
|
0 ignored issues
–
show
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...
|
|||
314 | file_put_contents('php://stderr', sprintf('"%s" directory is not writable' . PHP_EOL, $path)); |
||
315 | return; |
||
316 | } |
||
317 | |||
318 | $path = sprintf('%s/%s_%d.png', $path, basename($feature->getFile()), $step->getLine()); |
||
319 | $screenshot = $driver->getWebDriverSession()->screenshot(); |
||
320 | file_put_contents($path, base64_decode($screenshot)); |
||
321 | file_put_contents('php://stderr', sprintf('Saving screenshot into %s' . PHP_EOL, $path)); |
||
322 | } |
||
323 | |||
324 | /** |
||
325 | * @Then /^I should be redirected to "([^"]+)"/ |
||
326 | */ |
||
327 | public function stepIShouldBeRedirectedTo($url) |
||
328 | { |
||
329 | if ($this->getMainContext()->canIntercept()) { |
||
330 | $client = $this->getSession()->getDriver()->getClient(); |
||
331 | $client->followRedirects(true); |
||
332 | $client->followRedirect(); |
||
333 | |||
334 | $url = $this->getMainContext()->joinUrlParts($this->context['base_url'], $url); |
||
335 | |||
336 | assertTrue($this->getMainContext()->isCurrentUrlSimilarTo($url), sprintf('Current URL is not %s', $url)); |
||
337 | } |
||
338 | } |
||
339 | |||
340 | /** |
||
341 | * @Given /^the page can't be found/ |
||
342 | */ |
||
343 | public function stepPageCantBeFound() |
||
344 | { |
||
345 | $page = $this->getSession()->getPage(); |
||
346 | assertTrue( |
||
347 | // Content from ErrorPage default record |
||
348 | $page->hasContent('Page not found') |
||
349 | // Generic ModelAsController message |
||
350 | || $page->hasContent('The requested page could not be found') |
||
351 | ); |
||
352 | } |
||
353 | |||
354 | /** |
||
355 | * @Given /^I wait (?:for )?([\d\.]+) second(?:s?)$/ |
||
356 | */ |
||
357 | public function stepIWaitFor($secs) |
||
358 | { |
||
359 | $this->getSession()->wait((float)$secs*1000); |
||
360 | } |
||
361 | |||
362 | /** |
||
363 | * @Given /^I press the "([^"]*)" button$/ |
||
364 | */ |
||
365 | public function stepIPressTheButton($button) |
||
366 | { |
||
367 | $page = $this->getSession()->getPage(); |
||
368 | $els = $page->findAll('named', array('link_or_button', "'$button'")); |
||
369 | $matchedEl = null; |
||
370 | foreach ($els as $el) { |
||
371 | if ($el->isVisible()) { |
||
372 | $matchedEl = $el; |
||
373 | } |
||
374 | } |
||
375 | assertNotNull($matchedEl, sprintf('%s button not found', $button)); |
||
376 | $matchedEl->click(); |
||
377 | } |
||
378 | |||
379 | /** |
||
380 | * Needs to be in single command to avoid "unexpected alert open" errors in Selenium. |
||
381 | * Example1: I press the "Remove current combo" button, confirming the dialog |
||
382 | * Example2: I follow the "Remove current combo" link, confirming the dialog |
||
383 | * |
||
384 | * @Given /^I (?:press|follow) the "([^"]*)" (?:button|link), confirming the dialog$/ |
||
385 | */ |
||
386 | public function stepIPressTheButtonConfirmingTheDialog($button) |
||
387 | { |
||
388 | $this->stepIPressTheButton($button); |
||
389 | $this->iConfirmTheDialog(); |
||
390 | } |
||
391 | |||
392 | /** |
||
393 | * Needs to be in single command to avoid "unexpected alert open" errors in Selenium. |
||
394 | * Example: I follow the "Remove current combo" link, dismissing the dialog |
||
395 | * |
||
396 | * @Given /^I (?:press|follow) the "([^"]*)" (?:button|link), dismissing the dialog$/ |
||
397 | */ |
||
398 | public function stepIPressTheButtonDismissingTheDialog($button) |
||
399 | { |
||
400 | $this->stepIPressTheButton($button); |
||
401 | $this->iDismissTheDialog(); |
||
402 | } |
||
403 | |||
404 | /** |
||
405 | * @Given /^I (click|double click) "([^"]*)" in the "([^"]*)" element$/ |
||
406 | */ |
||
407 | public function iClickInTheElement($clickType, $text, $selector) |
||
408 | { |
||
409 | $clickTypeMap = array( |
||
410 | "double click" => "doubleclick", |
||
411 | "click" => "click" |
||
412 | ); |
||
413 | $page = $this->getSession()->getPage(); |
||
414 | $parentElement = $page->find('css', $selector); |
||
415 | assertNotNull($parentElement, sprintf('"%s" element not found', $selector)); |
||
416 | $element = $parentElement->find('xpath', sprintf('//*[count(*)=0 and contains(.,"%s")]', $text)); |
||
417 | assertNotNull($element, sprintf('"%s" not found', $text)); |
||
418 | $clickTypeFn = $clickTypeMap[$clickType]; |
||
419 | $element->$clickTypeFn(); |
||
420 | } |
||
421 | |||
422 | /** |
||
423 | * Needs to be in single command to avoid "unexpected alert open" errors in Selenium. |
||
424 | * Example: I click "Delete" in the ".actions" element, confirming the dialog |
||
425 | * |
||
426 | * @Given /^I (click|double click) "([^"]*)" in the "([^"]*)" element, confirming the dialog$/ |
||
427 | */ |
||
428 | public function iClickInTheElementConfirmingTheDialog($clickType, $text, $selector) |
||
429 | { |
||
430 | $this->iClickInTheElement($clickType, $text, $selector); |
||
431 | $this->iConfirmTheDialog(); |
||
432 | } |
||
433 | /** |
||
434 | * Needs to be in single command to avoid "unexpected alert open" errors in Selenium. |
||
435 | * Example: I click "Delete" in the ".actions" element, dismissing the dialog |
||
436 | * |
||
437 | * @Given /^I (click|double click) "([^"]*)" in the "([^"]*)" element, dismissing the dialog$/ |
||
438 | */ |
||
439 | public function iClickInTheElementDismissingTheDialog($clickType, $text, $selector) |
||
440 | { |
||
441 | $this->iClickInTheElement($clickType, $text, $selector); |
||
442 | $this->iDismissTheDialog(); |
||
443 | } |
||
444 | |||
445 | /** |
||
446 | * @Given /^I type "([^"]*)" into the dialog$/ |
||
447 | */ |
||
448 | public function iTypeIntoTheDialog($data) |
||
449 | { |
||
450 | $data = array( |
||
451 | 'text' => $data, |
||
452 | ); |
||
453 | $this->getSession()->getDriver()->getWebDriverSession()->postAlert_text($data); |
||
454 | } |
||
455 | |||
456 | /** |
||
457 | * @Given /^I confirm the dialog$/ |
||
458 | */ |
||
459 | public function iConfirmTheDialog() |
||
460 | { |
||
461 | $this->getSession()->getDriver()->getWebDriverSession()->accept_alert(); |
||
462 | $this->handleAjaxTimeout(); |
||
463 | } |
||
464 | |||
465 | /** |
||
466 | * @Given /^I dismiss the dialog$/ |
||
467 | */ |
||
468 | public function iDismissTheDialog() |
||
469 | { |
||
470 | $this->getSession()->getDriver()->getWebDriverSession()->dismiss_alert(); |
||
471 | $this->handleAjaxTimeout(); |
||
472 | } |
||
473 | |||
474 | /** |
||
475 | * @Given /^(?:|I )attach the file "(?P<path>[^"]*)" to "(?P<field>(?:[^"]|\\")*)" with HTML5$/ |
||
476 | */ |
||
477 | public function iAttachTheFileTo($field, $path) |
||
478 | { |
||
479 | // Remove wrapped button styling to make input field accessible to Selenium |
||
480 | $js = <<<JS |
||
481 | var input = jQuery('[name="$field"]'); |
||
482 | if(input.closest('.ss-uploadfield-item-info').length) { |
||
483 | while(!input.parent().is('.ss-uploadfield-item-info')) input = input.unwrap(); |
||
484 | } |
||
485 | JS; |
||
486 | |||
487 | $this->getSession()->executeScript($js); |
||
488 | $this->getSession()->wait(1000); |
||
489 | |||
490 | return new Step\Given(sprintf('I attach the file "%s" to "%s"', $path, $field)); |
||
491 | } |
||
492 | |||
493 | /** |
||
494 | * Select an individual input from within a group, matched by the top-most label. |
||
495 | * |
||
496 | * @Given /^I select "([^"]*)" from "([^"]*)" input group$/ |
||
497 | */ |
||
498 | public function iSelectFromInputGroup($value, $labelText) |
||
499 | { |
||
500 | $page = $this->getSession()->getPage(); |
||
501 | $parent = null; |
||
502 | |||
503 | foreach ($page->findAll('css', 'label') as $label) { |
||
504 | if ($label->getText() == $labelText) { |
||
505 | $parent = $label->getParent(); |
||
506 | } |
||
507 | } |
||
508 | |||
509 | if (!$parent) { |
||
510 | throw new \InvalidArgumentException(sprintf('Input group with label "%s" cannot be found', $labelText)); |
||
511 | } |
||
512 | |||
513 | foreach ($parent->findAll('css', 'label') as $option) { |
||
514 | if ($option->getText() == $value) { |
||
515 | $input = null; |
||
516 | |||
517 | // First, look for inputs referenced by the "for" element on this label |
||
518 | $for = $option->getAttribute('for'); |
||
519 | if ($for) { |
||
520 | $input = $parent->findById($for); |
||
521 | } |
||
522 | |||
523 | // Otherwise look for inputs _inside_ the label |
||
524 | if (!$input) { |
||
525 | $input = $option->find('css', 'input'); |
||
526 | } |
||
527 | |||
528 | if (!$input) { |
||
529 | throw new \InvalidArgumentException(sprintf('Input "%s" cannot be found', $value)); |
||
530 | } |
||
531 | |||
532 | $this->getSession()->getDriver()->click($input->getXPath()); |
||
533 | } |
||
534 | } |
||
535 | } |
||
536 | |||
537 | /** |
||
538 | * Pauses the scenario until the user presses a key. Useful when debugging a scenario. |
||
539 | * |
||
540 | * @Then /^(?:|I )put a breakpoint$/ |
||
541 | */ |
||
542 | public function iPutABreakpoint() |
||
543 | { |
||
544 | fwrite(STDOUT, "\033[s \033[93m[Breakpoint] Press \033[1;93m[RETURN]\033[0;93m to continue...\033[0m"); |
||
545 | while (fgets(STDIN, 1024) == '') { |
||
546 | } |
||
547 | fwrite(STDOUT, "\033[u"); |
||
548 | |||
549 | return; |
||
550 | } |
||
551 | |||
552 | /** |
||
553 | * Transforms relative time statements compatible with strtotime(). |
||
554 | * Example: "time of 1 hour ago" might return "22:00:00" if its currently "23:00:00". |
||
555 | * Customize through {@link setTimeFormat()}. |
||
556 | * |
||
557 | * @Transform /^(?:(the|a)) time of (?<val>.*)$/ |
||
558 | */ |
||
559 | View Code Duplication | public function castRelativeToAbsoluteTime($prefix, $val) |
|
0 ignored issues
–
show
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...
|
|||
560 | { |
||
561 | $timestamp = strtotime($val); |
||
562 | if (!$timestamp) { |
||
563 | throw new \InvalidArgumentException(sprintf( |
||
564 | "Can't resolve '%s' into a valid datetime value", |
||
565 | $val |
||
566 | )); |
||
567 | } |
||
568 | return date($this->timeFormat, $timestamp); |
||
569 | } |
||
570 | |||
571 | /** |
||
572 | * Transforms relative date and time statements compatible with strtotime(). |
||
573 | * Example: "datetime of 2 days ago" might return "2013-10-10 22:00:00" if its currently |
||
574 | * the 12th of October 2013. Customize through {@link setDatetimeFormat()}. |
||
575 | * |
||
576 | * @Transform /^(?:(the|a)) datetime of (?<val>.*)$/ |
||
577 | */ |
||
578 | View Code Duplication | public function castRelativeToAbsoluteDatetime($prefix, $val) |
|
0 ignored issues
–
show
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...
|
|||
579 | { |
||
580 | $timestamp = strtotime($val); |
||
581 | if (!$timestamp) { |
||
582 | throw new \InvalidArgumentException(sprintf( |
||
583 | "Can't resolve '%s' into a valid datetime value", |
||
584 | $val |
||
585 | )); |
||
586 | } |
||
587 | return date($this->datetimeFormat, $timestamp); |
||
588 | } |
||
589 | |||
590 | /** |
||
591 | * Transforms relative date statements compatible with strtotime(). |
||
592 | * Example: "date 2 days ago" might return "2013-10-10" if its currently |
||
593 | * the 12th of October 2013. Customize through {@link setDateFormat()}. |
||
594 | * |
||
595 | * @Transform /^(?:(the|a)) date of (?<val>.*)$/ |
||
596 | */ |
||
597 | View Code Duplication | public function castRelativeToAbsoluteDate($prefix, $val) |
|
0 ignored issues
–
show
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...
|
|||
598 | { |
||
599 | $timestamp = strtotime($val); |
||
600 | if (!$timestamp) { |
||
601 | throw new \InvalidArgumentException(sprintf( |
||
602 | "Can't resolve '%s' into a valid datetime value", |
||
603 | $val |
||
604 | )); |
||
605 | } |
||
606 | return date($this->dateFormat, $timestamp); |
||
607 | } |
||
608 | |||
609 | public function getDateFormat() |
||
610 | { |
||
611 | return $this->dateFormat; |
||
612 | } |
||
613 | |||
614 | public function setDateFormat($format) |
||
615 | { |
||
616 | $this->dateFormat = $format; |
||
617 | } |
||
618 | |||
619 | public function getTimeFormat() |
||
620 | { |
||
621 | return $this->timeFormat; |
||
622 | } |
||
623 | |||
624 | public function setTimeFormat($format) |
||
625 | { |
||
626 | $this->timeFormat = $format; |
||
627 | } |
||
628 | |||
629 | public function getDatetimeFormat() |
||
630 | { |
||
631 | return $this->datetimeFormat; |
||
632 | } |
||
633 | |||
634 | public function setDatetimeFormat($format) |
||
635 | { |
||
636 | $this->datetimeFormat = $format; |
||
637 | } |
||
638 | |||
639 | /** |
||
640 | * Checks that field with specified in|name|label|value is disabled. |
||
641 | * Example: Then the field "Email" should be disabled |
||
642 | * Example: Then the "Email" field should be disabled |
||
643 | * |
||
644 | * @Then /^the "(?P<name>(?:[^"]|\\")*)" (?P<type>(?:(field|button))) should (?P<negate>(?:(not |)))be disabled/ |
||
645 | * @Then /^the (?P<type>(?:(field|button))) "(?P<name>(?:[^"]|\\")*)" should (?P<negate>(?:(not |)))be disabled/ |
||
646 | */ |
||
647 | public function stepFieldShouldBeDisabled($name, $type, $negate) |
||
648 | { |
||
649 | $page = $this->getSession()->getPage(); |
||
650 | if ($type == 'field') { |
||
651 | $element = $page->findField($name); |
||
652 | } else { |
||
653 | $element = $page->find('named', array( |
||
654 | 'button', $this->getSession()->getSelectorsHandler()->xpathLiteral($name) |
||
655 | )); |
||
656 | } |
||
657 | |||
658 | assertNotNull($element, sprintf("Element '%s' not found", $name)); |
||
659 | |||
660 | $disabledAttribute = $element->getAttribute('disabled'); |
||
661 | if (trim($negate)) { |
||
662 | assertNull($disabledAttribute, sprintf("Failed asserting element '%s' is not disabled", $name)); |
||
663 | } else { |
||
664 | assertNotNull($disabledAttribute, sprintf("Failed asserting element '%s' is disabled", $name)); |
||
665 | } |
||
666 | } |
||
667 | |||
668 | /** |
||
669 | * Checks that checkbox with specified in|name|label|value is enabled. |
||
670 | * Example: Then the field "Email" should be enabled |
||
671 | * Example: Then the "Email" field should be enabled |
||
672 | * |
||
673 | * @Then /^the "(?P<field>(?:[^"]|\\")*)" field should be enabled/ |
||
674 | * @Then /^the field "(?P<field>(?:[^"]|\\")*)" should be enabled/ |
||
675 | */ |
||
676 | public function stepFieldShouldBeEnabled($field) |
||
677 | { |
||
678 | $page = $this->getSession()->getPage(); |
||
679 | $fieldElement = $page->findField($field); |
||
680 | assertNotNull($fieldElement, sprintf("Field '%s' not found", $field)); |
||
681 | |||
682 | $disabledAttribute = $fieldElement->getAttribute('disabled'); |
||
683 | |||
684 | assertNull($disabledAttribute, sprintf("Failed asserting field '%s' is enabled", $field)); |
||
685 | } |
||
686 | |||
687 | /** |
||
688 | * Clicks a link in a specific region (an element identified by a CSS selector, a "data-title" attribute, |
||
689 | * or a named region mapped to a CSS selector via Behat configuration). |
||
690 | * |
||
691 | * Example: Given I follow "Select" in the "header .login-form" region |
||
692 | * Example: Given I follow "Select" in the "My Login Form" region |
||
693 | * |
||
694 | * @Given /^I (?:follow|click) "(?P<link>[^"]*)" in the "(?P<region>[^"]*)" region$/ |
||
695 | */ |
||
696 | View Code Duplication | public function iFollowInTheRegion($link, $region) |
|
0 ignored issues
–
show
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...
|
|||
697 | { |
||
698 | $context = $this->getMainContext(); |
||
699 | $regionObj = $context->getRegionObj($region); |
||
700 | assertNotNull($regionObj); |
||
701 | |||
702 | $linkObj = $regionObj->findLink($link); |
||
703 | if (empty($linkObj)) { |
||
704 | throw new \Exception(sprintf('The link "%s" was not found in the region "%s" |
||
705 | on the page %s', $link, $region, $this->getSession()->getCurrentUrl())); |
||
706 | } |
||
707 | |||
708 | $linkObj->click(); |
||
709 | } |
||
710 | |||
711 | /** |
||
712 | * Fills in a field in a specfic region similar to (@see iFollowInTheRegion or @see iSeeTextInRegion) |
||
713 | * |
||
714 | * Example: Given I fill in "Hello" with "World" |
||
715 | * |
||
716 | * @Given /^I fill in "(?P<field>[^"]*)" with "(?P<value>[^"]*)" in the "(?P<region>[^"]*)" region$/ |
||
717 | */ |
||
718 | View Code Duplication | public function iFillinTheRegion($field, $value, $region) |
|
0 ignored issues
–
show
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...
|
|||
719 | { |
||
720 | $context = $this->getMainContext(); |
||
721 | $regionObj = $context->getRegionObj($region); |
||
722 | assertNotNull($regionObj, "Region Object is null"); |
||
723 | |||
724 | $fieldObj = $regionObj->findField($field); |
||
725 | if (empty($fieldObj)) { |
||
726 | throw new \Exception(sprintf('The field "%s" was not found in the region "%s" |
||
727 | on the page %s', $field, $region, $this->getSession()->getCurrentUrl())); |
||
728 | } |
||
729 | |||
730 | $regionObj->fillField($field, $value); |
||
731 | } |
||
732 | |||
733 | |||
734 | /** |
||
735 | * Asserts text in a specific region (an element identified by a CSS selector, a "data-title" attribute, |
||
736 | * or a named region mapped to a CSS selector via Behat configuration). |
||
737 | * Supports regular expressions in text value. |
||
738 | * |
||
739 | * Example: Given I should see "My Text" in the "header .login-form" region |
||
740 | * Example: Given I should not see "My Text" in the "My Login Form" region |
||
741 | * |
||
742 | * @Given /^I should (?P<negate>(?:(not |)))see "(?P<text>[^"]*)" in the "(?P<region>[^"]*)" region$/ |
||
743 | */ |
||
744 | public function iSeeTextInRegion($negate, $text, $region) |
||
745 | { |
||
746 | $context = $this->getMainContext(); |
||
747 | $regionObj = $context->getRegionObj($region); |
||
748 | assertNotNull($regionObj); |
||
749 | |||
750 | $actual = $regionObj->getText(); |
||
751 | $actual = preg_replace('/\s+/u', ' ', $actual); |
||
752 | $regex = '/'.preg_quote($text, '/').'/ui'; |
||
753 | |||
754 | if (trim($negate)) { |
||
755 | View Code Duplication | if (preg_match($regex, $actual)) { |
|
0 ignored issues
–
show
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...
|
|||
756 | $message = sprintf( |
||
757 | 'The text "%s" was found in the text of the "%s" region on the page %s.', |
||
758 | $text, |
||
759 | $region, |
||
760 | $this->getSession()->getCurrentUrl() |
||
761 | ); |
||
762 | |||
763 | throw new \Exception($message); |
||
764 | } |
||
765 | View Code Duplication | } else { |
|
0 ignored issues
–
show
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...
|
|||
766 | if (!preg_match($regex, $actual)) { |
||
767 | $message = sprintf( |
||
768 | 'The text "%s" was not found anywhere in the text of the "%s" region on the page %s.', |
||
769 | $text, |
||
770 | $region, |
||
771 | $this->getSession()->getCurrentUrl() |
||
772 | ); |
||
773 | |||
774 | throw new \Exception($message); |
||
775 | } |
||
776 | } |
||
777 | } |
||
778 | |||
779 | /** |
||
780 | * Selects the specified radio button |
||
781 | * |
||
782 | * @Given /^I select the "([^"]*)" radio button$/ |
||
783 | */ |
||
784 | public function iSelectTheRadioButton($radioLabel) |
||
785 | { |
||
786 | $session = $this->getSession(); |
||
787 | $radioButton = $session->getPage()->find('named', array( |
||
788 | 'radio', $this->getSession()->getSelectorsHandler()->xpathLiteral($radioLabel) |
||
789 | )); |
||
790 | assertNotNull($radioButton); |
||
791 | $session->getDriver()->click($radioButton->getXPath()); |
||
792 | } |
||
793 | |||
794 | /** |
||
795 | * @Then /^the "([^"]*)" table should contain "([^"]*)"$/ |
||
796 | */ |
||
797 | View Code Duplication | public function theTableShouldContain($selector, $text) |
|
0 ignored issues
–
show
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...
|
|||
798 | { |
||
799 | $table = $this->getTable($selector); |
||
800 | |||
801 | $element = $table->find('named', array('content', "'$text'")); |
||
802 | assertNotNull($element, sprintf('Element containing `%s` not found in `%s` table', $text, $selector)); |
||
803 | } |
||
804 | |||
805 | /** |
||
806 | * @Then /^the "([^"]*)" table should not contain "([^"]*)"$/ |
||
807 | */ |
||
808 | View Code Duplication | public function theTableShouldNotContain($selector, $text) |
|
0 ignored issues
–
show
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...
|
|||
809 | { |
||
810 | $table = $this->getTable($selector); |
||
811 | |||
812 | $element = $table->find('named', array('content', "'$text'")); |
||
813 | assertNull($element, sprintf('Element containing `%s` not found in `%s` table', $text, $selector)); |
||
814 | } |
||
815 | |||
816 | /** |
||
817 | * @Given /^I click on "([^"]*)" in the "([^"]*)" table$/ |
||
818 | */ |
||
819 | View Code Duplication | public function iClickOnInTheTable($text, $selector) |
|
0 ignored issues
–
show
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...
|
|||
820 | { |
||
821 | $table = $this->getTable($selector); |
||
822 | |||
823 | $element = $table->find('xpath', sprintf('//*[count(*)=0 and contains(.,"%s")]', $text)); |
||
824 | assertNotNull($element, sprintf('Element containing `%s` not found', $text)); |
||
825 | $element->click(); |
||
826 | } |
||
827 | |||
828 | /** |
||
829 | * Finds the first visible table by various factors: |
||
830 | * - table[id] |
||
831 | * - table[title] |
||
832 | * - table *[class=title] |
||
833 | * - fieldset[data-name] table |
||
834 | * - table caption |
||
835 | * |
||
836 | * @return Behat\Mink\Element\NodeElement |
||
837 | */ |
||
838 | protected function getTable($selector) |
||
839 | { |
||
840 | $selector = $this->getSession()->getSelectorsHandler()->xpathLiteral($selector); |
||
841 | $page = $this->getSession()->getPage(); |
||
842 | $candidates = $page->findAll( |
||
843 | 'xpath', |
||
844 | $this->getSession()->getSelectorsHandler()->selectorToXpath( |
||
845 | "xpath", |
||
846 | ".//table[(./@id = $selector or contains(./@title, $selector))]" |
||
847 | ) |
||
848 | ); |
||
849 | |||
850 | // Find tables by a <caption> field |
||
851 | $candidates += $page->findAll('xpath', "//table//caption[contains(normalize-space(string(.)), |
||
852 | $selector)]/ancestor-or-self::table[1]"); |
||
853 | |||
854 | // Find tables by a .title node |
||
855 | $candidates += $page->findAll('xpath', "//table//*[contains(concat(' ',normalize-space(@class),' '), ' title ') and contains(normalize-space(string(.)), |
||
856 | $selector)]/ancestor-or-self::table[1]"); |
||
857 | |||
858 | // Some tables don't have a visible title, so look for a fieldset with data-name instead |
||
859 | $candidates += $page->findAll('xpath', "//fieldset[@data-name=$selector]//table"); |
||
860 | |||
861 | assertTrue((bool)$candidates, 'Could not find any table elements'); |
||
862 | |||
863 | $table = null; |
||
864 | foreach ($candidates as $candidate) { |
||
865 | if (!$table && $candidate->isVisible()) { |
||
866 | $table = $candidate; |
||
867 | } |
||
868 | } |
||
869 | |||
870 | assertTrue((bool)$table, 'Found table elements, but none are visible'); |
||
871 | |||
872 | return $table; |
||
873 | } |
||
874 | |||
875 | /** |
||
876 | * Checks the order of two texts. |
||
877 | * Assumptions: the two texts appear in their conjunct parent element once |
||
878 | * @Then /^I should see the text "(?P<textBefore>(?:[^"]|\\")*)" (before|after) the text "(?P<textAfter>(?:[^"]|\\")*)" in the "(?P<element>[^"]*)" element$/ |
||
879 | */ |
||
880 | public function theTextBeforeAfter($textBefore, $order, $textAfter, $element) |
||
881 | { |
||
882 | $ele = $this->getSession()->getPage()->find('css', $element); |
||
883 | assertNotNull($ele, sprintf('%s not found', $element)); |
||
884 | |||
885 | // Check both of the texts exist in the element |
||
886 | $text = $ele->getText(); |
||
887 | assertTrue(strpos($text, $textBefore) !== 'FALSE', sprintf('%s not found in the element %s', $textBefore, $element)); |
||
888 | assertTrue(strpos($text, $textAfter) !== 'FALSE', sprintf('%s not found in the element %s', $textAfter, $element)); |
||
889 | |||
890 | /// Use strpos to get the position of the first occurrence of the two texts (case-sensitive) |
||
891 | // and compare them with the given order (before or after) |
||
892 | if ($order === 'before') { |
||
893 | assertTrue(strpos($text, $textBefore) < strpos($text, $textAfter)); |
||
894 | } else { |
||
895 | assertTrue(strpos($text, $textBefore) > strpos($text, $textAfter)); |
||
896 | } |
||
897 | } |
||
898 | |||
899 | /** |
||
900 | * Wait until a certain amount of seconds till I see an element identified by a CSS selector. |
||
901 | * |
||
902 | * Example: Given I wait for 10 seconds until I see the ".css_element" element |
||
903 | * |
||
904 | * @Given /^I wait for (\d+) seconds until I see the "([^"]*)" element$/ |
||
905 | **/ |
||
906 | View Code Duplication | public function iWaitXUntilISee($wait, $selector) |
|
0 ignored issues
–
show
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...
|
|||
907 | { |
||
908 | $page = $this->getSession()->getPage(); |
||
909 | |||
910 | $this->spin(function ($page) use ($page, $selector) { |
||
911 | $element = $page->find('css', $selector); |
||
912 | |||
913 | if (empty($element)) { |
||
914 | return false; |
||
915 | } else { |
||
916 | return $element->isVisible(); |
||
917 | } |
||
918 | }); |
||
919 | } |
||
920 | |||
921 | /** |
||
922 | * Wait until a particular element is visible, using a CSS selector. Useful for content loaded via AJAX, or only |
||
923 | * populated after JS execution. |
||
924 | * |
||
925 | * Example: Given I wait until I see the "header .login-form" element |
||
926 | * |
||
927 | * @Given /^I wait until I see the "([^"]*)" element$/ |
||
928 | */ |
||
929 | View Code Duplication | public function iWaitUntilISee($selector) |
|
0 ignored issues
–
show
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...
|
|||
930 | { |
||
931 | $page = $this->getSession()->getPage(); |
||
932 | $this->spin(function ($page) use ($page, $selector) { |
||
933 | $element = $page->find('css', $selector); |
||
934 | if (empty($element)) { |
||
935 | return false; |
||
936 | } else { |
||
937 | return ($element->isVisible()); |
||
938 | } |
||
939 | }); |
||
940 | } |
||
941 | |||
942 | /** |
||
943 | * Wait until a particular string is found on the page. Useful for content loaded via AJAX, or only populated after |
||
944 | * JS execution. |
||
945 | * |
||
946 | * Example: Given I wait until I see the text "Welcome back, John!" |
||
947 | * |
||
948 | * @Given /^I wait until I see the text "([^"]*)"$/ |
||
949 | */ |
||
950 | public function iWaitUntilISeeText($text) |
||
951 | { |
||
952 | $page = $this->getSession()->getPage(); |
||
953 | $session = $this->getSession(); |
||
954 | $this->spin(function ($page) use ($page, $session, $text) { |
||
955 | $element = $page->find( |
||
956 | 'xpath', |
||
957 | $session->getSelectorsHandler()->selectorToXpath("xpath", ".//*[contains(text(), '$text')]") |
||
958 | ); |
||
959 | |||
960 | if (empty($element)) { |
||
961 | return false; |
||
962 | } else { |
||
963 | return ($element->isVisible()); |
||
964 | } |
||
965 | }); |
||
966 | } |
||
967 | |||
968 | /** |
||
969 | * @Given /^I scroll to the bottom$/ |
||
970 | */ |
||
971 | public function iScrollToBottom() |
||
972 | { |
||
973 | $javascript = 'window.scrollTo(0, Math.max(document.documentElement.scrollHeight, document.body.scrollHeight, document.documentElement.clientHeight));'; |
||
974 | $this->getSession()->executeScript($javascript); |
||
975 | } |
||
976 | |||
977 | /** |
||
978 | * @Given /^I scroll to the top$/ |
||
979 | */ |
||
980 | public function iScrollToTop() |
||
981 | { |
||
982 | $this->getSession()->executeScript('window.scrollTo(0,0);'); |
||
983 | } |
||
984 | |||
985 | /** |
||
986 | * Scroll to a certain element by label. |
||
987 | * Requires an "id" attribute to uniquely identify the element in the document. |
||
988 | * |
||
989 | * Example: Given I scroll to the "Submit" button |
||
990 | * Example: Given I scroll to the "My Date" field |
||
991 | * |
||
992 | * @Given /^I scroll to the "([^"]*)" (field|link|button)$/ |
||
993 | */ |
||
994 | public function iScrollToField($locator, $type) |
||
995 | { |
||
996 | $page = $this->getSession()->getPage(); |
||
997 | $el = $page->find('named', array($type, "'$locator'")); |
||
998 | assertNotNull($el, sprintf('%s element not found', $locator)); |
||
999 | |||
1000 | $id = $el->getAttribute('id'); |
||
1001 | if (empty($id)) { |
||
1002 | throw new \InvalidArgumentException('Element requires an "id" attribute'); |
||
1003 | } |
||
1004 | |||
1005 | $js = sprintf("document.getElementById('%s').scrollIntoView(true);", $id); |
||
1006 | $this->getSession()->executeScript($js); |
||
1007 | } |
||
1008 | |||
1009 | /** |
||
1010 | * Scroll to a certain element by CSS selector. |
||
1011 | * Requires an "id" attribute to uniquely identify the element in the document. |
||
1012 | * |
||
1013 | * Example: Given I scroll to the ".css_element" element |
||
1014 | * |
||
1015 | * @Given /^I scroll to the "(?P<locator>(?:[^"]|\\")*)" element$/ |
||
1016 | */ |
||
1017 | public function iScrollToElement($locator) |
||
1018 | { |
||
1019 | $el = $this->getSession()->getPage()->find('css', $locator); |
||
1020 | assertNotNull($el, sprintf('The element "%s" is not found', $locator)); |
||
1021 | |||
1022 | $id = $el->getAttribute('id'); |
||
1023 | if (empty($id)) { |
||
1024 | throw new \InvalidArgumentException('Element requires an "id" attribute'); |
||
1025 | } |
||
1026 | |||
1027 | $js = sprintf("document.getElementById('%s').scrollIntoView(true);", $id); |
||
1028 | $this->getSession()->executeScript($js); |
||
1029 | } |
||
1030 | |||
1031 | /** |
||
1032 | * Continuously poll the dom until callback returns true, code copied from |
||
1033 | * (@link http://docs.behat.org/cookbook/using_spin_functions.html) |
||
1034 | * If not found within a given wait period, timeout and throw error |
||
1035 | * |
||
1036 | * @param callback $lambda The function to run continuously |
||
1037 | * @param integer $wait Timeout in seconds |
||
1038 | * @return bool Returns true if the lambda returns successfully |
||
1039 | * @throws \Exception Thrown if the wait threshold is exceeded without the lambda successfully returning |
||
1040 | */ |
||
1041 | public function spin($lambda, $wait = 60) |
||
1042 | { |
||
1043 | for ($i = 0; $i < $wait; $i++) { |
||
1044 | try { |
||
1045 | if ($lambda($this)) { |
||
1046 | return true; |
||
1047 | } |
||
1048 | } catch (\Exception $e) { |
||
1049 | // do nothing |
||
1050 | } |
||
1051 | |||
1052 | sleep(1); |
||
1053 | } |
||
1054 | |||
1055 | $backtrace = debug_backtrace(); |
||
1056 | |||
1057 | throw new \Exception(sprintf( |
||
1058 | "Timeout thrown by %s::%s()\n.", |
||
1059 | $backtrace[1]['class'], |
||
1060 | $backtrace[1]['function'] |
||
1061 | )); |
||
1062 | } |
||
1063 | |||
1064 | |||
1065 | |||
1066 | /** |
||
1067 | * We have to catch exceptions and log somehow else otherwise behat falls over |
||
1068 | */ |
||
1069 | protected function logException($e) |
||
1070 | { |
||
1071 | file_put_contents('php://stderr', 'Exception caught: '.$e); |
||
1072 | } |
||
1073 | } |
||
1074 |
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.