1 | <?php |
||||||
2 | |||||||
3 | namespace SilverStripe\Framework\Tests\Behaviour; |
||||||
4 | |||||||
5 | use BadMethodCallException; |
||||||
6 | use Behat\Behat\Context\Context; |
||||||
7 | use Behat\Mink\Exception\ElementHtmlException; |
||||||
8 | use Behat\Gherkin\Node\TableNode; |
||||||
9 | use SilverStripe\BehatExtension\Context\MainContextAwareTrait; |
||||||
10 | use SilverStripe\BehatExtension\Utility\StepHelper; |
||||||
11 | use Symfony\Component\DomCrawler\Crawler; |
||||||
12 | use Behat\Mink\Element\NodeElement; |
||||||
13 | use SilverStripe\SiteConfig\SiteConfig; |
||||||
14 | |||||||
15 | /** |
||||||
16 | * CmsFormsContext |
||||||
17 | * |
||||||
18 | * Context used to define steps related to forms inside CMS. |
||||||
19 | */ |
||||||
20 | class CmsFormsContext implements Context |
||||||
21 | { |
||||||
22 | use MainContextAwareTrait; |
||||||
23 | use StepHelper; |
||||||
24 | |||||||
25 | /** |
||||||
26 | * Get Mink session from MinkContext |
||||||
27 | */ |
||||||
28 | public function getSession($name = null) |
||||||
29 | { |
||||||
30 | return $this->getMainContext()->getSession($name); |
||||||
31 | } |
||||||
32 | |||||||
33 | /** |
||||||
34 | * Returns fixed step argument (with \\" replaced back to "). |
||||||
35 | * Copied from {@see MinkContext} |
||||||
36 | * |
||||||
37 | * @param string $argument |
||||||
38 | * @return string |
||||||
39 | */ |
||||||
40 | protected function fixStepArgument($argument) |
||||||
41 | { |
||||||
42 | return str_replace('\\"', '"', $argument); |
||||||
43 | } |
||||||
44 | |||||||
45 | /** |
||||||
46 | * @Then /^I should( not? |\s*)see an edit page form$/ |
||||||
47 | */ |
||||||
48 | public function stepIShouldSeeAnEditPageForm($negative) |
||||||
49 | { |
||||||
50 | $page = $this->getSession()->getPage(); |
||||||
51 | |||||||
52 | $form = $page->find('css', '#Form_EditForm'); |
||||||
53 | if (trim($negative)) { |
||||||
54 | assertNull($form, 'I should not see an edit page form'); |
||||||
55 | } else { |
||||||
56 | assertNotNull($form, 'I should see an edit page form'); |
||||||
57 | } |
||||||
58 | } |
||||||
59 | |||||||
60 | /** |
||||||
61 | * @When /^I fill in the "(?P<field>(?:[^"]|\\")*)" HTML field with "(?P<value>(?:[^"]|\\")*)"$/ |
||||||
62 | * @When /^I fill in "(?P<value>(?:[^"]|\\")*)" for the "(?P<field>(?:[^"]|\\")*)" HTML field$/ |
||||||
63 | */ |
||||||
64 | public function stepIFillInTheHtmlFieldWith($field, $value) |
||||||
65 | { |
||||||
66 | $inputField = $this->getHtmlField($field); |
||||||
67 | $value = $this->fixStepArgument($value); |
||||||
68 | |||||||
69 | $this->getSession()->evaluateScript(sprintf( |
||||||
70 | "jQuery('#%s').entwine('ss').getEditor().setContent('%s')", |
||||||
71 | $inputField->getAttribute('id'), |
||||||
72 | addcslashes($value, "'") |
||||||
73 | )); |
||||||
74 | } |
||||||
75 | |||||||
76 | /** |
||||||
77 | * @When /^I append "(?P<value>(?:[^"]|\\")*)" to the "(?P<field>(?:[^"]|\\")*)" HTML field$/ |
||||||
78 | */ |
||||||
79 | public function stepIAppendTotheHtmlField($field, $value) |
||||||
80 | { |
||||||
81 | $inputField = $this->getHtmlField($field); |
||||||
82 | $value = $this->fixStepArgument($value); |
||||||
83 | |||||||
84 | $this->getSession()->evaluateScript(sprintf( |
||||||
85 | "jQuery('#%s').entwine('ss').getEditor().insertContent('%s')", |
||||||
86 | $inputField->getAttribute('id'), |
||||||
87 | addcslashes($value, "'") |
||||||
88 | )); |
||||||
89 | } |
||||||
90 | |||||||
91 | /** |
||||||
92 | * @Then /^the "(?P<locator>(?:[^"]|\\")*)" HTML field should(?P<negative> not? |\s*)contain "(?P<html>.*)"$/ |
||||||
93 | */ |
||||||
94 | public function theHtmlFieldShouldContain($locator, $negative, $html) |
||||||
95 | { |
||||||
96 | $element = $this->getHtmlField($locator); |
||||||
97 | $actual = $element->getValue(); |
||||||
98 | $regex = '/' . preg_quote($html, '/') . '/ui'; |
||||||
99 | $failed = false; |
||||||
100 | |||||||
101 | if (trim($negative)) { |
||||||
102 | if (preg_match($regex, $actual)) { |
||||||
103 | $failed = true; |
||||||
104 | } |
||||||
105 | } else { |
||||||
106 | if (!preg_match($regex, $actual)) { |
||||||
107 | $failed = true; |
||||||
108 | } |
||||||
109 | } |
||||||
110 | |||||||
111 | if ($failed) { |
||||||
112 | $message = sprintf( |
||||||
113 | 'The string "%s" should%sbe found in the HTML of the element matching name "%s". Actual content: "%s"', |
||||||
114 | $html, |
||||||
115 | $negative, |
||||||
116 | $locator, |
||||||
117 | $actual |
||||||
118 | ); |
||||||
119 | throw new ElementHtmlException($message, $this->getSession(), $element); |
||||||
120 | } |
||||||
121 | } |
||||||
122 | |||||||
123 | // @codingStandardsIgnoreStart |
||||||
124 | /** |
||||||
125 | * Checks formatting in the HTML field, by analyzing the HTML node surrounding |
||||||
126 | * the text for certain properties. |
||||||
127 | * |
||||||
128 | * Example: Given "my text" in the "Content" HTML field should be right aligned |
||||||
129 | * Example: Given "my text" in the "Content" HTML field should not be bold |
||||||
130 | * |
||||||
131 | * @todo Use an actual DOM parser for more accurate assertions |
||||||
132 | * |
||||||
133 | * @Given /^"(?P<text>([^"]*))" in the "(?P<field>(?:[^"]|\\")*)" HTML field should(?P<negate>(?: not)?) be (?P<formatting>(.*))$/ |
||||||
134 | */ |
||||||
135 | public function stepContentInHtmlFieldShouldHaveFormatting($text, $field, $negate, $formatting) { |
||||||
136 | $inputField = $this->getHtmlField($field); |
||||||
137 | |||||||
138 | $crawler = new Crawler($inputField->getValue()); |
||||||
139 | $matchedNode = null; |
||||||
140 | foreach($crawler->filterXPath('//*') as $node) { |
||||||
141 | if( |
||||||
142 | $node->firstChild |
||||||
143 | && $node->firstChild->nodeType == XML_TEXT_NODE |
||||||
144 | && stripos($node->firstChild->nodeValue, $text) !== FALSE |
||||||
145 | ) { |
||||||
146 | $matchedNode = $node; |
||||||
147 | } |
||||||
148 | } |
||||||
149 | assertNotNull($matchedNode); |
||||||
150 | |||||||
151 | $assertFn = $negate ? 'assertNotEquals' : 'assertEquals'; |
||||||
152 | if($formatting == 'bold') { |
||||||
153 | call_user_func($assertFn, 'strong', $matchedNode->nodeName); |
||||||
154 | } else if($formatting == 'left aligned') { |
||||||
155 | if($matchedNode->getAttribute('style')) { |
||||||
156 | call_user_func($assertFn, 'text-align: left;', $matchedNode->getAttribute('style')); |
||||||
157 | } |
||||||
158 | } else if($formatting == 'right aligned') { |
||||||
159 | call_user_func($assertFn, 'text-align: right;', $matchedNode->getAttribute('style')); |
||||||
160 | } |
||||||
161 | } |
||||||
162 | // @codingStandardsIgnoreEnd |
||||||
163 | |||||||
164 | /** |
||||||
165 | * Selects the first textual match in the HTML editor. Does not support |
||||||
166 | * selection across DOM node boundaries. |
||||||
167 | * |
||||||
168 | * @When /^I select "(?P<text>([^"]*))" in the "(?P<field>(?:[^"]|\\")*)" HTML field$/ |
||||||
169 | */ |
||||||
170 | public function stepIHighlightTextInHtmlField($text, $field) |
||||||
171 | { |
||||||
172 | $inputField = $this->getHtmlField($field); |
||||||
173 | $inputFieldId = $inputField->getAttribute('id'); |
||||||
174 | $text = addcslashes($text, "'"); |
||||||
175 | |||||||
176 | $js = <<<JS |
||||||
177 | // TODO <IE9 support |
||||||
178 | // TODO Allow text matches across nodes |
||||||
179 | var editor = jQuery('#$inputFieldId').entwine('ss').getEditor(), |
||||||
180 | doc = editor.getInstance().getDoc(), |
||||||
181 | sel = editor.getInstance().selection, |
||||||
182 | rng = document.createRange(), |
||||||
183 | matched = false; |
||||||
184 | |||||||
185 | editor.getInstance().focus(); |
||||||
186 | jQuery(doc).find('body *').each(function() { |
||||||
187 | if(!matched) { |
||||||
188 | for(var i=0;i<this.childNodes.length;i++) { |
||||||
189 | if(!matched && this.childNodes[i].nodeValue && this.childNodes[i].nodeValue.match('$text')) { |
||||||
190 | rng.setStart(this.childNodes[i], this.childNodes[i].nodeValue.indexOf('$text')); |
||||||
191 | rng.setEnd(this.childNodes[i], this.childNodes[i].nodeValue.indexOf('$text') + '$text'.length); |
||||||
192 | sel.setRng(rng); |
||||||
193 | editor.getInstance().nodeChanged(); |
||||||
194 | matched = true; |
||||||
195 | break; |
||||||
196 | } |
||||||
197 | } |
||||||
198 | } |
||||||
199 | }); |
||||||
200 | JS; |
||||||
201 | |||||||
202 | $this->getSession()->executeScript($js); |
||||||
203 | } |
||||||
204 | |||||||
205 | /** |
||||||
206 | * @Given /^I should( not? |\s*)see a "([^"]*)" field$/ |
||||||
207 | */ |
||||||
208 | public function iShouldSeeAField($negative, $text) |
||||||
209 | { |
||||||
210 | $page = $this->getSession()->getPage(); |
||||||
211 | $els = $page->findAll('named', array('field', "'$text'")); |
||||||
212 | $matchedEl = null; |
||||||
213 | foreach ($els as $el) { |
||||||
214 | if ($el->isVisible()) { |
||||||
215 | $matchedEl = $el; |
||||||
216 | } |
||||||
217 | } |
||||||
218 | |||||||
219 | if (trim($negative)) { |
||||||
220 | assertNull($matchedEl); |
||||||
221 | } else { |
||||||
222 | assertNotNull($matchedEl); |
||||||
223 | } |
||||||
224 | } |
||||||
225 | |||||||
226 | /** |
||||||
227 | * Click on the element with the provided CSS Selector |
||||||
228 | * |
||||||
229 | * @When /^I press the "([^"]*)" HTML field button$/ |
||||||
230 | */ |
||||||
231 | public function iClickOnTheHtmlFieldButton($button) |
||||||
232 | { |
||||||
233 | $xpath = "//*[@aria-label='" . $button . "']"; |
||||||
234 | $session = $this->getSession(); |
||||||
235 | $element = $session->getPage()->find('xpath', $xpath); |
||||||
236 | if (null === $element) { |
||||||
237 | throw new \InvalidArgumentException(sprintf('Could not find element with xpath %s', $xpath)); |
||||||
238 | } |
||||||
239 | |||||||
240 | $element->click(); |
||||||
241 | } |
||||||
242 | |||||||
243 | /* |
||||||
244 | * @example Given the CMS settings has the following data |
||||||
245 | * | Title | My site title | |
||||||
246 | * | Theme | My site theme | |
||||||
247 | * @Given /^the CMS settings have the following data$/ |
||||||
248 | */ |
||||||
249 | public function theCmsSettingsHasData(TableNode $fieldsTable) |
||||||
250 | { |
||||||
251 | $fields = $fieldsTable->getRowsHash(); |
||||||
252 | $siteConfig = SiteConfig::get()->first(); |
||||||
253 | foreach ($fields as $field => $value) { |
||||||
254 | $siteConfig->$field = $value; |
||||||
255 | } |
||||||
256 | $siteConfig->write(); |
||||||
257 | $siteConfig->flushCache(); |
||||||
258 | } |
||||||
259 | |||||||
260 | /** |
||||||
261 | * Select a value in the tree dropdown field |
||||||
262 | * |
||||||
263 | * NOTE: This is react specific, may need to move to its own react section later |
||||||
264 | * |
||||||
265 | * @When /^I select "([^"]*)" in the "([^"]*)" tree dropdown$/ |
||||||
266 | */ |
||||||
267 | public function iSelectValueInTreeDropdown($text, $selector) |
||||||
268 | { |
||||||
269 | $page = $this->getSession()->getPage(); |
||||||
270 | $parentElement = null; |
||||||
271 | $this->retryThrowable(function () use (&$parentElement, &$page, $selector) { |
||||||
272 | $parentElement = $page->find('css', $selector); |
||||||
273 | assertNotNull($parentElement, sprintf('"%s" element not found', $selector)); |
||||||
274 | $page = $this->getSession()->getPage(); |
||||||
275 | }); |
||||||
276 | |||||||
277 | $this->retryThrowable(function () use ($parentElement, $selector) { |
||||||
278 | $dropdown = $parentElement->find('css', '.Select-arrow'); |
||||||
279 | assertNotNull($dropdown, sprintf('Unable to find the dropdown in "%s"', $selector)); |
||||||
280 | $dropdown->click(); |
||||||
281 | }); |
||||||
282 | |||||||
283 | $this->retryThrowable(function () use ($text, $parentElement, $selector) { |
||||||
284 | $element = $parentElement->find('xpath', sprintf('//*[count(*)=0 and contains(.,"%s")]', $text)); |
||||||
285 | assertNotNull($element, sprintf('"%s" not found in "%s"', $text, $selector)); |
||||||
286 | $element->click(); |
||||||
287 | }); |
||||||
288 | } |
||||||
289 | |||||||
290 | /** |
||||||
291 | * Locate an HTML editor field |
||||||
292 | * |
||||||
293 | * @param string $locator Raw html field identifier as passed from |
||||||
294 | * @return NodeElement |
||||||
295 | */ |
||||||
296 | protected function getHtmlField($locator) |
||||||
297 | { |
||||||
298 | $locator = $this->fixStepArgument($locator); |
||||||
299 | $page = $this->getSession()->getPage(); |
||||||
300 | $element = $page->find('css', 'textarea.htmleditor[name=\'' . $locator . '\']'); |
||||||
301 | assertNotNull($element, sprintf('HTML field "%s" not found', $locator)); |
||||||
302 | return $element; |
||||||
303 | } |
||||||
304 | |||||||
305 | /** |
||||||
306 | * @Given /^the "([^"]*)" field ((?:does not have)|(?:has)) property "([^"]*)"$/ |
||||||
307 | */ |
||||||
308 | public function assertTheFieldHasProperty($name, $cond, $property) |
||||||
309 | { |
||||||
310 | $name = $this->fixStepArgument($name); |
||||||
311 | $property = $this->fixStepArgument($property); |
||||||
312 | |||||||
313 | $context = $this->getMainContext(); |
||||||
314 | $fieldObj = $context->assertSession()->fieldExists($name); |
||||||
315 | |||||||
316 | // Check property |
||||||
317 | $hasProperty = $fieldObj->hasAttribute($property); |
||||||
318 | switch ($cond) { |
||||||
319 | case 'has': |
||||||
320 | assert($hasProperty, "Field $name does not have property $property"); |
||||||
321 | break; |
||||||
322 | case 'does not have': |
||||||
323 | assert(!$hasProperty, "Field $name should not have property $property"); |
||||||
324 | break; |
||||||
325 | default: |
||||||
326 | throw new BadMethodCallException("Invalid condition"); |
||||||
327 | } |
||||||
328 | } |
||||||
329 | |||||||
330 | /** |
||||||
331 | * @When /^I switch to the "([^"]*)" iframe$/ |
||||||
332 | * @param string $id iframe id property |
||||||
333 | */ |
||||||
334 | public function stepSwitchToTheFrame($id) |
||||||
335 | { |
||||||
336 | $this->getMainContext()->getSession()->getDriver()->switchToIFrame($id); |
||||||
337 | } |
||||||
338 | |||||||
339 | /** |
||||||
340 | * @When /^I am not in an iframe$/ |
||||||
341 | */ |
||||||
342 | public function stepSwitchToParentFrame() |
||||||
343 | { |
||||||
344 | $this->getMainContext()->getSession()->getDriver()->switchToIFrame(null); |
||||||
345 | } |
||||||
346 | |||||||
347 | /** |
||||||
348 | * @When /^my session expires$/ |
||||||
349 | */ |
||||||
350 | public function stepMySessionExpires() |
||||||
351 | { |
||||||
352 | // Destroy cookie to detach session |
||||||
353 | $this->getMainContext()->getSession()->setCookie('PHPSESSID', null); |
||||||
354 | } |
||||||
355 | |||||||
356 | /** |
||||||
357 | * @When /^I should see the "([^"]*)" button in the "([^"]*)" gridfield for the "([^"]*)" row$/ |
||||||
358 | * @param string $buttonLabel |
||||||
359 | * @param string $gridFieldName |
||||||
360 | * @param string $rowName |
||||||
361 | */ |
||||||
362 | public function assertIShouldSeeTheGridFieldButtonForRow($buttonLabel, $gridFieldName, $rowName) |
||||||
363 | { |
||||||
364 | $button = $this->getGridFieldButton($gridFieldName, $rowName, $buttonLabel); |
||||||
365 | assertNotNull($button, sprintf('Button "%s" not found', $buttonLabel)); |
||||||
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||||||
366 | } |
||||||
367 | |||||||
368 | /** |
||||||
369 | * @When /^I should not see the "([^"]*)" button in the "([^"]*)" gridfield for the "([^"]*)" row$/ |
||||||
370 | * @param string $buttonLabel |
||||||
371 | * @param string $gridFieldName |
||||||
372 | * @param string $rowName |
||||||
373 | */ |
||||||
374 | public function assertIShouldNotSeeTheGridFieldButtonForRow($buttonLabel, $gridFieldName, $rowName) |
||||||
375 | { |
||||||
376 | $button = $this->getGridFieldButton($gridFieldName, $rowName, $buttonLabel); |
||||||
377 | assertNull($button, sprintf('Button "%s" found', $buttonLabel)); |
||||||
378 | } |
||||||
379 | |||||||
380 | /** |
||||||
381 | * @When /^I click the "([^"]*)" button in the "([^"]*)" gridfield for the "([^"]*)" row$/ |
||||||
382 | * @param string $buttonLabel |
||||||
383 | * @param string $gridFieldName |
||||||
384 | * @param string $rowName |
||||||
385 | */ |
||||||
386 | public function stepIClickTheGridFieldButtonForRow($buttonLabel, $gridFieldName, $rowName) |
||||||
387 | { |
||||||
388 | $button = $this->getGridFieldButton($gridFieldName, $rowName, $buttonLabel); |
||||||
389 | assertNotNull($button, sprintf('Button "%s" not found', $buttonLabel)); |
||||||
0 ignored issues
–
show
The function
assertNotNull was not found. Maybe you did not declare it correctly or list all dependencies?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||
390 | |||||||
391 | $button->click(); |
||||||
392 | } |
||||||
393 | |||||||
394 | /** |
||||||
395 | * Finds a button in the gridfield row |
||||||
396 | * |
||||||
397 | * @param $gridFieldName |
||||||
398 | * @param $rowName |
||||||
399 | * @param $buttonLabel |
||||||
400 | * @return $button |
||||||
401 | */ |
||||||
402 | protected function getGridFieldButton($gridFieldName, $rowName, $buttonLabel) |
||||||
403 | { |
||||||
404 | $page = $this->getSession()->getPage(); |
||||||
405 | $gridField = $page->find('xpath', sprintf('//*[@data-name="%s"]', $gridFieldName)); |
||||||
406 | assertNotNull($gridField, sprintf('Gridfield "%s" not found', $gridFieldName)); |
||||||
0 ignored issues
–
show
The function
assertNotNull was not found. Maybe you did not declare it correctly or list all dependencies?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||
407 | |||||||
408 | $name = $gridField->find('xpath', sprintf('//*[count(*)=0 and contains(.,"%s")]', $rowName)); |
||||||
409 | if (!$name) { |
||||||
410 | return null; |
||||||
411 | } |
||||||
412 | |||||||
413 | $button = $name->getParent()->find('xpath', sprintf('//*[@aria-label="%s"]', $buttonLabel)); |
||||||
414 | |||||||
415 | return $button; |
||||||
416 | } |
||||||
417 | |||||||
418 | /** |
||||||
419 | * @When /^I click the "([^"]*)" option in the "([^"]*)" listbox$/ |
||||||
420 | * @param $optionLabel |
||||||
421 | * @param $fieldName |
||||||
422 | */ |
||||||
423 | public function stepIClickTheListBoxOption($optionLabel, $fieldName) |
||||||
424 | { |
||||||
425 | $page = $this->getSession()->getPage(); |
||||||
426 | $listBox = $page->find('xpath', sprintf('//*[@name="%s[]"]', $fieldName)); |
||||||
427 | assertNotNull($listBox, sprintf('The listbox %s is not found', $fieldName)); |
||||||
0 ignored issues
–
show
The function
assertNotNull was not found. Maybe you did not declare it correctly or list all dependencies?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||
428 | |||||||
429 | $option = $listBox->getParent() |
||||||
430 | ->find('css', '.chosen-choices') |
||||||
431 | ->find('xpath', sprintf('//*[count(*)=0 and contains(.,"%s")]', $optionLabel)); |
||||||
432 | assertNotNull($option, sprintf('Option %s is not found', $optionLabel)); |
||||||
433 | |||||||
434 | $button = $option->getParent()->find('css', 'a'); |
||||||
435 | |||||||
436 | $button->click(); |
||||||
437 | } |
||||||
438 | } |
||||||
439 |