1: <?php
2: namespace Azalea\Selenium\Toolkit;
3:
4: use Azalea\Selenium\Toolkit\Exception\ElementNotFoundException;
5: use PHPUnit_Extensions_Selenium2TestCase_Keys as Keys;
6:
7: class Dom
8: {
9: /** @var \Azalea\Selenium\Toolkit\TestCase $driver */
10: protected $driver = null;
11:
12: /** @var string $root */
13: protected $root = null;
14:
15: /**
16: * @param \Azalea\Selenium\Toolkit\TestCase $driver
17: * @throws \InvalidArgumentException
18: */
19: public function __construct($driver, $root = "body")
20: {
21: if (!$driver) {
22: throw new \InvalidArgumentException(get_class($this).": driver not defined");
23: }
24:
25: $this->driver = $driver;
26: $this->root = $root;
27: }
28:
29: /**
30: * Get the active web driver.
31: *
32: * @return \Azalea\Selenium\Toolkit\TestCase
33: */
34: public function getDriver()
35: {
36: return $this->driver;
37: }
38:
39: /**
40: * Sets the root element's selector.
41: *
42: * @param string $root
43: * @return $this
44: */
45: public function setRoot($root)
46: {
47: $this->root = ($root ? $root : "body");
48: return $this;
49: }
50:
51: /**
52: * Get the root element's selector.
53: *
54: * @return string
55: */
56: public function getRoot()
57: {
58: return $this->root;
59: }
60:
61: /**
62: * Get the active root element.
63: *
64: * @return \PHPUnit_Extensions_Selenium2TestCase_Element
65: */
66: public function root()
67: {
68: if ($this->root == "body") {
69: return $this->driver;
70: }
71: return $this->driver->byCssSelector($this->root);
72: }
73:
74: /**
75: * Filter down to a sub-element and returns a new dom instance.
76: *
77: * @param string $selector
78: * @return \Azalea\Selenium\Toolkit\Dom
79: */
80: public function filter($selector)
81: {
82: if ($this->root == "body") {
83: return new Dom($this->driver, $selector);
84: }
85: return new Dom($this->driver, $this->root." ".$selector);
86: }
87:
88: /*-------------------------------------------------------------------------
89: * Assertions
90: *-----------------------------------------------------------------------*/
91:
92: /**
93: * Asserts if an element matching the given selector does not exist.
94: *
95: * @param string $selector
96: * @return $this
97: */
98: public function assertElementExists($selector)
99: {
100: $this->driver->assertTrue($this->hasElement($selector));
101: return $this;
102: }
103:
104: /**
105: * Asserts if an element matching the given selector does exist.
106: *
107: * @param string $selector
108: * @return $this
109: */
110: public function assertElementNotExists($selector)
111: {
112: $this->driver->assertFalse($this->hasElement($selector));
113: return $this;
114: }
115:
116: /**
117: * Asserts if an element matching the given selector is not visible.
118: *
119: * @param string $selector
120: * @return $this
121: */
122: public function assertElementVisible($selector)
123: {
124: $this->driver->assertTrue($this->canSeeElement($selector));
125: return $this;
126: }
127:
128: /**
129: * Asserts if an element matching the given selector is visible.
130: *
131: * @param string $selector
132: * @return $this
133: */
134: public function assertElementNotVisible($selector)
135: {
136: $this->driver->assertFalse($this->canSeeElement($selector));
137: return $this;
138: }
139:
140: /**
141: * Asserts if an element matching the given selector does not contain
142: * the given text.
143: *
144: * @param string $selector
145: * @param string $matches
146: * @return $this
147: */
148: public function assertElementHasText($selector, $matches)
149: {
150: $text = $this->querySelector($selector)->text();
151: $this->driver->assertTrue(strpos($text, $matches) !== false, "Did not find text \"".$matches."\" in Element ".$selector);
152: return $this;
153: }
154:
155: /**
156: * Asserts if an element matching the given selector does not contain
157: * the given text - case insensitive.
158: *
159: * @param string $selector
160: * @param string $matches
161: * @return $this
162: */
163: public function assertElementHasTextNoCase($selector, $matches)
164: {
165: $text = $this->querySelector($selector)->text();
166: $this->driver->assertTrue(stripos($text, $matches) !== false, "Did not find text \"".$matches."\" in Element ".$selector);
167: return $this;
168: }
169:
170: /**
171: * Asserts if an element matching the given selector does contain
172: * the given text.
173: *
174: * @param string $selector
175: * @return $this
176: */
177: public function assertElementNotHasText($selector, $matches)
178: {
179: $text = $this->querySelector($selector)->text();
180: $this->driver->assertTrue(strpos($text, $matches) === false, "Found text \"".$matches."\" in Element ".$selector);
181: return $this;
182: }
183:
184: /**
185: * Asserts if an element matching the given selector contains any text.
186: *
187: * @param string $selector
188: * @param string $matches
189: * @return $this
190: */
191: public function assertElementEmpty($selector)
192: {
193: $text = $this->querySelector($selector)->text();
194: $this->driver->assertTrue(empty($text), "Element ".$selector." is not empty");
195: return $this;
196: }
197:
198: /**
199: * Asserts if an element matching the given selector does not contain
200: * any text.
201: *
202: * @param string $selector
203: * @param string $matches
204: * @return $this
205: */
206: public function assertNotElementEmpty($selector)
207: {
208: $text = $this->querySelector($selector)->text();
209: $this->driver->assertFalse(empty($text), "Element ".$selector." is empty");
210: return $this;
211: }
212:
213: /**
214: * Asserts if the given text is not visible anywhere in the page
215: * or scoped by the given selector.
216: *
217: * @param string $text
218: * @return $this
219: */
220: public function assertCanSee($text)
221: {
222: $this->driver->assertTrue($this->see($text));
223: return $this;
224: }
225:
226: /**
227: * Asserts if the given text is visible anywhere in the page
228: * or scoped by the given selector.
229: *
230: * @param string $text
231: * @return $this
232: */
233: public function assertCanNotSee($text)
234: {
235: $this->driver->assertFalse($this->see($text));
236: return $this;
237: }
238:
239: /**
240: * Asserts if the input element given by name attribute $name is not
241: * enabled (element.disabled = false).
242: *
243: * @param string $name
244: * @return $this
245: */
246: public function assertInputEnabled($name)
247: {
248: $element = $this->querySelector("[name=".$name."]");
249: $this->driver->assertTrue($element->enabled());
250: return $this;
251: }
252:
253: /**
254: * Asserts if the input element given by name attribute $name is not
255: * disabled (element.disabled = true).
256: *
257: * @param string $name
258: * @return $this
259: */
260: public function assertInputDisabled($name)
261: {
262: $element = $this->querySelector("[name=".$name."]");
263: $this->driver->assertFalse($element->enabled());
264: return $this;
265: }
266:
267: /**
268: * Asserts if the input element given by name attribute $name does not
269: * equal the given text (checkboxes and radio buttons will always assert).
270: * For <select> elements it will assert if the selection value does not
271: * equal the given text (element.value = "").
272: *
273: * @param string $name
274: * @return $this
275: */
276: public function assertInputValueEquals($name, $text)
277: {
278: $value = $this->getInput($name);
279: $this->driver->assertEquals($text, $value);
280: return $this;
281: }
282:
283: /**
284: * Asserts if the input element given by name attribute $name does not
285: * contain any text (checkboxes and radio buttons will always assert).
286: * For <select> elements it will assert if there is no selection
287: * (element.value = "").
288: *
289: * @param string $name
290: * @return $this
291: */
292: public function assertInputHasValue($name)
293: {
294: $value = $this->getInput($name);
295: $this->driver->assertTrue(!empty($value) && !is_bool($value));
296: return $this;
297: }
298:
299: /**
300: * Asserts if the input element given by name attribute $name
301: * contains any text (checkboxes and radio buttons will always assert).
302: * For <select> elements it will assert if there is a selection
303: * (element.value = "").
304: *
305: * @param string $name
306: * @return $this
307: */
308: public function assertNotInputHasValue($name)
309: {
310: $value = $this->getInput($name);
311: $this->driver->assertFalse(!empty($value) && !is_bool($value));
312: return $this;
313: }
314:
315: /*-------------------------------------------------------------------------
316: * DOM Selection
317: *-----------------------------------------------------------------------*/
318:
319: /**
320: * Returns the element matching the given Id.
321: *
322: * @param string $id
323: * @return \PHPUnit_Extensions_Selenium2TestCase_Element
324: */
325: public function getElementById($id)
326: {
327: return $this->root()->byId($id);
328: }
329:
330: /**
331: * Returns the element matching the given css selector.
332: *
333: * @param string $css
334: * @return \PHPUnit_Extensions_Selenium2TestCase_Element
335: */
336: public function querySelector($css)
337: {
338: return $this->root()->byCssSelector($css);
339: }
340:
341: /**
342: * Returns all elements that match the given css selector.
343: *
344: * @param string $css
345: * @return array
346: */
347: public function querySelectorAll($css)
348: {
349: $elements = $this->root()->elements(
350: $this->root()->using('css selector')->value($css)
351: );
352: return ($elements ? $elements : array());
353: }
354:
355: /**
356: * Returns the element matching the given xpath selector.
357: *
358: * @param string $css
359: * @return \PHPUnit_Extensions_Selenium2TestCase_Element
360: */
361: public function queryXPath($css)
362: {
363: return $this->driver->byXPath($css);
364: }
365:
366: /**
367: * Returns all elements that match the given xpath.
368: *
369: * @param string $xpath
370: * @return array
371: */
372: public function queryXPathAll($xpath)
373: {
374: $elements = $this->driver->elements(
375: $this->driver->using('xpath')->value($xpath)
376: );
377: return ($elements ? $elements : array());
378: }
379:
380: /**
381: * Finds all links with the given text.
382: *
383: * @param string $text
384: * @param string $scope
385: * @return array
386: */
387: public function getLinks($text)
388: {
389: $links = array();
390: $elements = $this->querySelectorAll("a");
391: foreach ($elements as $element) {
392: if ($element->displayed() && $element->text() == $text) {
393: $links[] = $element;
394: }
395: }
396: return $links;
397: // $xpath = "//*/a[text()='".addslashes($text)."']";
398: // return $this->queryXPathAll($xpath);
399: }
400:
401: /**
402: * Finds all buttons with the given text.
403: * Searches for <button>, <input type="button">, or <input type="submit">.
404: *
405: * @param string $text
406: * @param string $scope
407: * @return array
408: */
409: public function getButtons($text)
410: {
411: $buttons = array();
412: $elements = $this->querySelectorAll("button, input[type=button], input[type=submit]");
413: foreach ($elements as $element) {
414: try {
415: if ($element->displayed() && ($element->text() == $text || $element->value() == $text)) {
416: $buttons[] = $element;
417: }
418: } catch (\Exception $e) {
419: // keep on truckin'
420: }
421: }
422: return $buttons;
423: // $xpath = "//*/button[text()='".addslashes($text)."']";
424: // $buttons = $this->queryXPathAll($xpath);
425: // $xpath = "//*/input[@type='button' and @value='".addslashes($text)."']";
426: // $buttons = array_merge($buttons, $this->queryXPathAll($xpath));
427: // $xpath = "//*/input[@type='submit' and @value='".addslashes($text)."']";
428: // $buttons = array_merge($buttons, $this->queryXPathAll($xpath));
429: // return $buttons;
430: }
431:
432: /**
433: * Get the first visible element matching the given selector
434: *
435: * @param string $selector
436: * @throws \Azalea\Selenium\Toolkit\Exception\ElementNotFoundException
437: * @return \PHPUnit_Extensions_Selenium2TestCase_Element
438: */
439: public function getFirstVisible($selector)
440: {
441: $elements = $this->querySelectorAll($selector);
442: if (empty($elements)) {
443: throw new ElementNotFoundException("Could not find any elements matching ".$selector);
444: }
445:
446: foreach ($elements as $element) {
447: if ($element->displayed()) {
448: return $element;
449: }
450: }
451:
452: throw new ElementNotFoundException("Could not find any visible elements matching ".$selector);
453: }
454:
455: /*-------------------------------------------------------------------------
456: * DOM Tests
457: *-----------------------------------------------------------------------*/
458:
459: /**
460: * Returns true if elements matching the given css selectors
461: * exists.
462: *
463: * @param string $css
464: * @return boolean
465: */
466: public function hasElement($css)
467: {
468: try {
469: $this->querySelector($css);
470: } catch (\Exception $e) {
471: return false;
472: }
473: return true;
474: }
475:
476: /**
477: * Returns true if the given text is visible somewhere on the screen.
478: *
479: * @param string $text
480: * @param string $scope
481: * @return bool
482: */
483: public function see($text)
484: {
485: $selector = ($this->root == "body" ? "" : $this->root);
486: $textContents = $this->javascript('return document.querySelector("'.addslashes($selector).'").textContent;');
487: // var_dump($textContents);
488: // $element = $this->root();
489: // if ($this->root == "body") {
490: // $element = $this->driver->byCssSelector("body");
491: // }
492: // $textContents = str_replace("\n", " ", $element->text());
493: return strpos($textContents, $text) !== false;
494: }
495:
496: /**
497: * Returns true if an element matching the given css selector
498: * exists and is visually displayed (not display:none).
499: *
500: * @param string $css
501: * @return boolean
502: */
503: public function canSeeElement($css)
504: {
505: if (!$this->hasElement($css) || !$this->querySelector($css)->displayed()) {
506: return false;
507: }
508: return true;
509: }
510:
511: /*-------------------------------------------------------------------------
512: * DOM Interaction
513: *-----------------------------------------------------------------------*/
514:
515: /**
516: * Clicks a link with the given text.
517: *
518: * @param string $text
519: * @return $this
520: */
521: public function click($text)
522: {
523: $links = $this->getLinks($text);
524: if (count($links) == 0) {
525: throw new \RuntimeException("Could not find any links with text ".$text);
526: }
527: $links[0]->click();
528: return $this;
529: }
530:
531: /**
532: * Uses JavaScript to simulate a mouse click.
533: *
534: * This should only be used if there are issues with the selenium web
535: * driver's element click command.
536: *
537: * @param string $selector
538: *
539: * @return $this
540: */
541: public function clickElement($selector)
542: {
543: $script = '(function () {
544: var elem = document.querySelector("'.addslashes($selector).'");
545: var evt = document.createEvent("HTMLEvents");
546: if (elem) {
547: evt.initEvent("click", true, true);
548: elem.dispatchEvent(evt);
549: return;
550: }
551: throw Exception("Could not find element with CSS '.$selector.'");
552: })();';
553:
554: $this->javascript($script);
555:
556: return $this;
557: }
558:
559: /**
560: * Presses a <button>, <input type="button">, or <input type="submit">
561: * with the given text.
562: *
563: * @param string $text
564: * @return $this
565: */
566: public function press($text, $index = 0)
567: {
568: $buttons = $this->getButtons($text);
569: if (count($buttons) < ($index + 1)) {
570: throw new \RuntimeException("Could not find any buttons with text ".$text." at index ".$index);
571: }
572: $buttons[$index]->click();
573: return $this;
574: }
575:
576: /**
577: * Tabs away from the active element.
578: *
579: * @return $this
580: */
581: public function tab()
582: {
583: try {
584: $this->driver->keys(Keys::TAB);
585: return $this;
586: } catch (\PHPUnit_Extensions_Selenium2TestCase_WebDriverException $e) {
587: // webdriver doesn't support "sendKeysToActiveElement"
588: return $this;
589: }
590: }
591:
592: /*-------------------------------------------------------------------------
593: * Form Interaction
594: *-----------------------------------------------------------------------*/
595:
596: /**
597: * Type text into an input element with the given name attribute.
598: *
599: * @param string $name
600: * @param string $text
601: * @return $this
602: */
603: public function type($name, $text)
604: {
605: $css = '[name="'.$name.'"]';
606: $this->querySelector($css)
607: ->value($text);
608: return $this;
609: }
610:
611: /**
612: * Checks a checkbox / radio input with the given name attribute.
613: *
614: * @param string $name
615: * @return $this
616: */
617: public function check($name)
618: {
619: $css = '[name="'.$name.'"]';
620: $elem = $this->querySelector($css);
621: if (!$elem->selected()) {
622: $elem->click();
623: }
624: return $this;
625: }
626:
627: /**
628: * Checks a checkbox / radio input with the given name attribute.
629: *
630: * @param string $name
631: * @return $this
632: */
633: public function uncheck($name)
634: {
635: $css = '[name="'.$name.'"]';
636: $elem = $this->querySelector($css);
637: if ($elem->selected()) {
638: $elem->click();
639: }
640: return $this;
641: }
642:
643: /**
644: * Sets the value of a <select> element by name attribute.
645: *
646: * @param string $name
647: * @param string $value
648: * @return $this
649: */
650: public function select($name, $value)
651: {
652: $css = '[name="'.$name.'"] > option[value="'.$value.'"]';
653: $this->querySelector($css)->click();
654: return $this;
655: }
656:
657: /**
658: * Set the value of a radio group with the given name attribute.
659: *
660: * @param string $name
661: * @param string $value
662: * @return $this
663: */
664: public function radio($name, $value)
665: {
666: $elements = $this->querySelectorAll('[name="'.$name.'"]');
667: foreach ($elements as $radio) {
668: if ($radio->attribute("value") == $value) {
669: $radio->click();
670: break;
671: }
672: }
673: return $this;
674: }
675:
676: /**
677: * Retrieves an input's value by name attribute.
678: *
679: * If it is a checkbox or radio button then this will return
680: * a boolean true or false.
681: *
682: * @param string $name
683: * @return mixed
684: */
685: public function getInput($name)
686: {
687: $value = "";
688: $element = $this->querySelector("[name=".$name."]");
689: $tagName = $element->name();
690:
691: if ($tagName == "textarea") {
692: $value = $element->text();
693: } else if ($tagName == "select") {
694: $value = $element->value();
695: } else if ($tagName == "input") {
696: $type = strtolower($element->attribute("type"));
697: if ($type == "checkbox" || $type == "radio") {
698: $value = $element->selected();
699: } else {
700: $value = $element->value();
701: }
702: }
703: // else don't know what this is
704:
705: return $value;
706: }
707:
708: /**
709: * Retrieves an input element by name attribute.
710: *
711: * @param string $name
712: * @return \PHPUnit_Extensions_Selenium2TestCase_Element
713: */
714: public function getInputElement($name)
715: {
716: return $this->querySelector("[name=".$name."]");
717: }
718:
719: /**
720: * Retrieves the option text of the given select element for the
721: * given value.
722: *
723: * @param string $name
724: * @return \PHPUnit_Extensions_Selenium2TestCase_Element
725: */
726: public function getSelectOptionText($name, $value)
727: {
728: $css = '[name="'.$name.'"] > option[value="'.$value.'"]';
729: $element = $this->querySelector($css);
730: return $element->text();
731: }
732:
733: /**
734: * Set an input with the given name to the given value. Can accept
735: * an array of name => value pairs.
736: *
737: * Keys are the input's name attribute.
738: *
739: * For checkboxes and radio buttons use boolean true / false values.
740: *
741: * The inputs are also stored locally in the Form view so that you
742: * can reference them later without going back to the DOM.
743: *
744: * @param array $inputs
745: * @return $this
746: */
747: public function setInput($name, $value = null)
748: {
749: $inputs = array();
750:
751: if ($value === null) {
752: if (!is_array($name)) {
753: throw new \InvalidArgumentException("Expected an array.");
754: }
755: $inputs = $name;
756: } else {
757: $inputs[$name] = $value;
758: }
759:
760: try {
761: foreach ($inputs as $name => $value) {
762: $value = (string)$value;
763: $element = $this->querySelector("[name=".$name."]");
764:
765: if (!$element->enabled()) {
766: // this element is disabled and can't be modified
767: continue;
768: }
769:
770: $tagName = strtolower($element->name());
771: if ($tagName == "textarea") {
772: $element->clear();
773: // $this->driver->keys(Keys::CONTROL."a");
774: $element->value($value);
775: } else if ($tagName == "select") {
776: $element->byCssSelector('option[value="'.$value.'"]')->click();
777: } else if ($tagName == "input") {
778: $type = $element->attribute("type");
779: if ($type == "checkbox") {
780: if ($value === true) {
781: $this->check($name);
782: } else {
783: $this->uncheck($name);
784: }
785: } else if ($type == "radio") {
786: $this->radio($name, $value);
787: } else {
788: $element->clear();
789: // $this->driver->wait(5);
790: // $this->driver->keys(Keys::CONTROL."a");
791: $element->value($value);
792: }
793: }
794: // else don't know what this is
795: }
796: } catch (\Exception $e) {
797: throw new \RuntimeException("Could not set value of element ".$name.": ".$e->getMessage());
798: }
799:
800: return $this;
801: }
802:
803: /**
804: * Sets the value of a hidden input element by name attribute.
805: *
806: * @param string $name
807: * @param string $value
808: * @return $this
809: */
810: public function setHiddenInput($name, $value)
811: {
812: $this->javascript("document.querySelector('".$this->root." [name=".$name."]').setAttribute('value', '".$value."');");
813: return $this;
814: }
815:
816: /**
817: * Clears an input element by name attribute.
818: *
819: * @param string $name
820: * @return $this
821: */
822: public function clearInput($name)
823: {
824: $element = $this->querySelector("[name=".$name."]");
825: $element->clear();
826: $element->click();
827: return $this;
828: }
829:
830: /**
831: * Gives focus to the input element with the give name.
832: *
833: * @param string $name
834: * @return $this
835: */
836: public function focusInput($name)
837: {
838: $script = '(function () {
839: var elem = document.querySelector(\'[name="'.$name.'"]\');
840: if (elem) {
841: elem.focus();
842: return;
843: }
844: throw Exception(\'Could not find element with CSS [name="'.$name.'"]\');
845: })();';
846:
847: $this->javascript($script);
848:
849: return $this;
850: }
851:
852: /**
853: * Submits the form if an input[submit] or button[submit] element is found.
854: *
855: * @return \Azalea\Selenium\Toolkit\Components\Form
856: */
857: public function submit()
858: {
859: if ($this->hasElement('input[type="submit"]')) {
860: $this->querySelector('input[type="submit"]')->click();
861: } else if ($this->hasElement('button[type="submit"]')) {
862: $this->querySelector('button[type="submit"]')->click();
863: } else {
864: throw new \RuntimeException("Could not submit form. No submit button found.");
865: }
866:
867: return $this;
868: }
869:
870: /*-------------------------------------------------------------------------
871: * JavaScript
872: *-----------------------------------------------------------------------*/
873:
874: /**
875: * Executes JavaScript in the browser.
876: *
877: * @param string $script
878: * @param array $args
879: * @return mixed
880: */
881: public function javascript($script, $args = array())
882: {
883: try {
884: $result = $this->driver->execute(array(
885: 'script' => $script,
886: 'args' => $args
887: ));
888: } catch (\Exception $e) {
889: throw new \RuntimeException("Error executing JavaScript: ".$script, 1, $e);
890: }
891:
892: return $result;
893: }
894:
895: /*-------------------------------------------------------------------------
896: * Location Hashes
897: *-----------------------------------------------------------------------*/
898:
899: /**
900: * Gets the current window location's hash value.
901: * @return string
902: */
903: public function getCurrentHash()
904: {
905: return $this->javascript("return window.location.hash");
906: }
907:
908: /**
909: * Updates the hash fragment in the browser's url.
910: *
911: * @param string $hash
912: * @return $this
913: */
914: public function updateHash($hash)
915: {
916: $this->javascript("window.location.hash = '".$hash."'");
917: return $this;
918: }
919:
920: /*-------------------------------------------------------------------------
921: * Timing
922: *-----------------------------------------------------------------------*/
923:
924: /**
925: * Wait for an element with the given selector to appear in the dom, then
926: * return that element.
927: *
928: * @param string $selector
929: * @param int $timeout
930: * @return \PHPUnit_Extensions_Selenium2TestCase_Element
931: */
932: public function waitFor($selector, $timeout = 30)
933: {
934: $self = $this;
935: $this->driver->spinWait(function () use ($self, $selector) {
936: return $self->hasElement($selector);
937: }, $timeout);
938:
939: return $this->querySelector($selector);
940: }
941:
942: /**
943: * Wait for an element with the given selector to be visible in the dom, then
944: * return that element.
945: *
946: * @param string $selector
947: * @param int $timeout
948: * @return \PHPUnit_Extensions_Selenium2TestCase_Element
949: */
950: public function waitForElementVisible($selector, $timeout = 30)
951: {
952: $self = $this;
953: $this->driver->spinWait(function () use ($self, $selector) {
954: return $self->canSeeElement($selector);
955: }, $timeout);
956:
957: return $this->querySelector($selector);
958: }
959:
960: /**
961: * Wait for an element with the given selector to contain the given text,
962: * then return that element.
963: *
964: * @param string $selector
965: * @param int $timeout
966: * @return \PHPUnit_Extensions_Selenium2TestCase_Element
967: */
968: public function waitForElementText($selector, $text, $timeout = 30)
969: {
970: $self = $this;
971: $this->driver->spinWait(function () use ($self, $selector, $text) {
972: return (trim($self->querySelector($selector)->text()) == $text);
973: }, $timeout);
974:
975: return $this->querySelector($selector);
976: }
977: }
978:
979: ?>