1<?php
2
3namespace Facebook\WebDriver;
4
5use Facebook\WebDriver\Exception\NoSuchAlertException;
6use Facebook\WebDriver\Exception\NoSuchElementException;
7use Facebook\WebDriver\Exception\NoSuchFrameException;
8use Facebook\WebDriver\Exception\StaleElementReferenceException;
9
10/**
11 * Canned ExpectedConditions which are generally useful within webdriver tests.
12 *
13 * @see WebDriverWait
14 */
15class WebDriverExpectedCondition
16{
17    /**
18     * A callable function to be executed by WebDriverWait. It should return
19     * a truthy value, mostly boolean or a WebDriverElement, on success.
20     * @var callable
21     */
22    private $apply;
23
24    protected function __construct(callable $apply)
25    {
26        $this->apply = $apply;
27    }
28
29    /**
30     * @return callable A callable function to be executed by WebDriverWait
31     */
32    public function getApply()
33    {
34        return $this->apply;
35    }
36
37    /**
38     * An expectation for checking the title of a page.
39     *
40     * @param string $title The expected title, which must be an exact match.
41     * @return static Condition returns whether current page title equals given string.
42     */
43    public static function titleIs($title)
44    {
45        return new static(
46            function (WebDriver $driver) use ($title) {
47                return $title === $driver->getTitle();
48            }
49        );
50    }
51
52    /**
53     * An expectation for checking substring of a page Title.
54     *
55     * @param string $title The expected substring of Title.
56     * @return static Condition returns whether current page title contains given string.
57     */
58    public static function titleContains($title)
59    {
60        return new static(
61            function (WebDriver $driver) use ($title) {
62                return mb_strpos($driver->getTitle(), $title) !== false;
63            }
64        );
65    }
66
67    /**
68     * An expectation for checking current page title matches the given regular expression.
69     *
70     * @param string $titleRegexp The regular expression to test against.
71     * @return static Condition returns whether current page title matches the regular expression.
72     */
73    public static function titleMatches($titleRegexp)
74    {
75        return new static(
76            function (WebDriver $driver) use ($titleRegexp) {
77                return (bool) preg_match($titleRegexp, $driver->getTitle());
78            }
79        );
80    }
81
82    /**
83     * An expectation for checking the URL of a page.
84     *
85     * @param string $url The expected URL, which must be an exact match.
86     * @return static Condition returns whether current URL equals given one.
87     */
88    public static function urlIs($url)
89    {
90        return new static(
91            function (WebDriver $driver) use ($url) {
92                return $url === $driver->getCurrentURL();
93            }
94        );
95    }
96
97    /**
98     * An expectation for checking substring of the URL of a page.
99     *
100     * @param string $url The expected substring of the URL
101     * @return static Condition returns whether current URL contains given string.
102     */
103    public static function urlContains($url)
104    {
105        return new static(
106            function (WebDriver $driver) use ($url) {
107                return mb_strpos($driver->getCurrentURL(), $url) !== false;
108            }
109        );
110    }
111
112    /**
113     * An expectation for checking current page URL matches the given regular expression.
114     *
115     * @param string $urlRegexp The regular expression to test against.
116     * @return static Condition returns whether current URL matches the regular expression.
117     */
118    public static function urlMatches($urlRegexp)
119    {
120        return new static(
121            function (WebDriver $driver) use ($urlRegexp) {
122                return (bool) preg_match($urlRegexp, $driver->getCurrentURL());
123            }
124        );
125    }
126
127    /**
128     * An expectation for checking that an element is present on the DOM of a page.
129     * This does not necessarily mean that the element is visible.
130     *
131     * @param WebDriverBy $by The locator used to find the element.
132     * @return static Condition returns the WebDriverElement which is located.
133     */
134    public static function presenceOfElementLocated(WebDriverBy $by)
135    {
136        return new static(
137            function (WebDriver $driver) use ($by) {
138                try {
139                    return $driver->findElement($by);
140                } catch (NoSuchElementException $e) {
141                    return false;
142                }
143            }
144        );
145    }
146
147    /**
148     * An expectation for checking that there is at least one element present on a web page.
149     *
150     * @param WebDriverBy $by The locator used to find the element.
151     * @return static Condition return an array of WebDriverElement once they are located.
152     */
153    public static function presenceOfAllElementsLocatedBy(WebDriverBy $by)
154    {
155        return new static(
156            function (WebDriver $driver) use ($by) {
157                $elements = $driver->findElements($by);
158
159                return count($elements) > 0 ? $elements : null;
160            }
161        );
162    }
163
164    /**
165     * An expectation for checking that an element is present on the DOM of a page and visible.
166     * Visibility means that the element is not only displayed but also has a height and width that is greater than 0.
167     *
168     * @param WebDriverBy $by The locator used to find the element.
169     * @return static Condition returns the WebDriverElement which is located and visible.
170     */
171    public static function visibilityOfElementLocated(WebDriverBy $by)
172    {
173        return new static(
174            function (WebDriver $driver) use ($by) {
175                try {
176                    $element = $driver->findElement($by);
177
178                    return $element->isDisplayed() ? $element : null;
179                } catch (StaleElementReferenceException $e) {
180                    return null;
181                }
182            }
183        );
184    }
185
186    /**
187     * An expectation for checking than at least one element in an array of elements is present on the
188     * DOM of a page and visible.
189     * Visibility means that the element is not only displayed but also has a height and width that is greater than 0.
190     *
191     * @param WebDriverBy $by The located used to find the element.
192     * @return static Condition returns the array of WebDriverElement that are located and visible.
193     */
194    public static function visibilityOfAnyElementLocated(WebDriverBy $by)
195    {
196        return new static(
197            function (WebDriver $driver) use ($by) {
198                $elements = $driver->findElements($by);
199                $visibleElements = [];
200
201                foreach ($elements as $element) {
202                    try {
203                        if ($element->isDisplayed()) {
204                            $visibleElements[] = $element;
205                        }
206                    } catch (StaleElementReferenceException $e) {
207                    }
208                }
209
210                return count($visibleElements) > 0 ? $visibleElements : null;
211            }
212        );
213    }
214
215    /**
216     * An expectation for checking that an element, known to be present on the DOM of a page, is visible.
217     * Visibility means that the element is not only displayed but also has a height and width that is greater than 0.
218     *
219     * @param WebDriverElement $element The element to be checked.
220     * @return static Condition returns the same WebDriverElement once it is visible.
221     */
222    public static function visibilityOf(WebDriverElement $element)
223    {
224        return new static(
225            function () use ($element) {
226                return $element->isDisplayed() ? $element : null;
227            }
228        );
229    }
230
231    /**
232     * An expectation for checking if the given text is present in the specified element.
233     * To check exact text match use elementTextIs() condition.
234     *
235     * @codeCoverageIgnore
236     * @deprecated Use WebDriverExpectedCondition::elementTextContains() instead
237     * @param WebDriverBy $by The locator used to find the element.
238     * @param string $text The text to be presented in the element.
239     * @return static Condition returns whether the text is present in the element.
240     */
241    public static function textToBePresentInElement(WebDriverBy $by, $text)
242    {
243        return self::elementTextContains($by, $text);
244    }
245
246    /**
247     * An expectation for checking if the given text is present in the specified element.
248     * To check exact text match use elementTextIs() condition.
249     *
250     * @param WebDriverBy $by The locator used to find the element.
251     * @param string $text The text to be presented in the element.
252     * @return static Condition returns whether the partial text is present in the element.
253     */
254    public static function elementTextContains(WebDriverBy $by, $text)
255    {
256        return new static(
257            function (WebDriver $driver) use ($by, $text) {
258                try {
259                    $element_text = $driver->findElement($by)->getText();
260
261                    return mb_strpos($element_text, $text) !== false;
262                } catch (StaleElementReferenceException $e) {
263                    return null;
264                }
265            }
266        );
267    }
268
269    /**
270     * An expectation for checking if the given text exactly equals the text in specified element.
271     * To check only partial substring of the text use elementTextContains() condition.
272     *
273     * @param WebDriverBy $by The locator used to find the element.
274     * @param string $text The expected text of the element.
275     * @return static Condition returns whether the element has text value equal to given one.
276     */
277    public static function elementTextIs(WebDriverBy $by, $text)
278    {
279        return new static(
280            function (WebDriver $driver) use ($by, $text) {
281                try {
282                    return $driver->findElement($by)->getText() == $text;
283                } catch (StaleElementReferenceException $e) {
284                    return null;
285                }
286            }
287        );
288    }
289
290    /**
291     * An expectation for checking if the given regular expression matches the text in specified element.
292     *
293     * @param WebDriverBy $by The locator used to find the element.
294     * @param string $regexp The regular expression to test against.
295     * @return static Condition returns whether the element has text value equal to given one.
296     */
297    public static function elementTextMatches(WebDriverBy $by, $regexp)
298    {
299        return new static(
300            function (WebDriver $driver) use ($by, $regexp) {
301                try {
302                    return (bool) preg_match($regexp, $driver->findElement($by)->getText());
303                } catch (StaleElementReferenceException $e) {
304                    return null;
305                }
306            }
307        );
308    }
309
310    /**
311     * An expectation for checking if the given text is present in the specified elements value attribute.
312     *
313     * @codeCoverageIgnore
314     * @deprecated Use WebDriverExpectedCondition::elementValueContains() instead
315     * @param WebDriverBy $by The locator used to find the element.
316     * @param string $text The text to be presented in the element value.
317     * @return static Condition returns whether the text is present in value attribute.
318     */
319    public static function textToBePresentInElementValue(WebDriverBy $by, $text)
320    {
321        return self::elementValueContains($by, $text);
322    }
323
324    /**
325     * An expectation for checking if the given text is present in the specified elements value attribute.
326     *
327     * @param WebDriverBy $by The locator used to find the element.
328     * @param string $text The text to be presented in the element value.
329     * @return static Condition returns whether the text is present in value attribute.
330     */
331    public static function elementValueContains(WebDriverBy $by, $text)
332    {
333        return new static(
334            function (WebDriver $driver) use ($by, $text) {
335                try {
336                    $element_text = $driver->findElement($by)->getAttribute('value');
337
338                    return mb_strpos($element_text, $text) !== false;
339                } catch (StaleElementReferenceException $e) {
340                    return null;
341                }
342            }
343        );
344    }
345
346    /**
347     * Expectation for checking if iFrame exists. If iFrame exists switches driver's focus to the iFrame.
348     *
349     * @param string $frame_locator The locator used to find the iFrame
350     *   expected to be either the id or name value of the i/frame
351     * @return static Condition returns object focused on new frame when frame is found, false otherwise.
352     */
353    public static function frameToBeAvailableAndSwitchToIt($frame_locator)
354    {
355        return new static(
356            function (WebDriver $driver) use ($frame_locator) {
357                try {
358                    return $driver->switchTo()->frame($frame_locator);
359                } catch (NoSuchFrameException $e) {
360                    return false;
361                }
362            }
363        );
364    }
365
366    /**
367     * An expectation for checking that an element is either invisible or not present on the DOM.
368     *
369     * @param WebDriverBy $by The locator used to find the element.
370     * @return static Condition returns whether no visible element located.
371     */
372    public static function invisibilityOfElementLocated(WebDriverBy $by)
373    {
374        return new static(
375            function (WebDriver $driver) use ($by) {
376                try {
377                    return !$driver->findElement($by)->isDisplayed();
378                } catch (NoSuchElementException $e) {
379                    return true;
380                } catch (StaleElementReferenceException $e) {
381                    return true;
382                }
383            }
384        );
385    }
386
387    /**
388     * An expectation for checking that an element with text is either invisible or not present on the DOM.
389     *
390     * @param WebDriverBy $by The locator used to find the element.
391     * @param string $text The text of the element.
392     * @return static Condition returns whether the text is found in the element located.
393     */
394    public static function invisibilityOfElementWithText(WebDriverBy $by, $text)
395    {
396        return new static(
397            function (WebDriver $driver) use ($by, $text) {
398                try {
399                    return !($driver->findElement($by)->getText() === $text);
400                } catch (NoSuchElementException $e) {
401                    return true;
402                } catch (StaleElementReferenceException $e) {
403                    return true;
404                }
405            }
406        );
407    }
408
409    /**
410     * An expectation for checking an element is visible and enabled such that you can click it.
411     *
412     * @param WebDriverBy $by The locator used to find the element
413     * @return static Condition return the WebDriverElement once it is located, visible and clickable.
414     */
415    public static function elementToBeClickable(WebDriverBy $by)
416    {
417        $visibility_of_element_located = self::visibilityOfElementLocated($by);
418
419        return new static(
420            function (WebDriver $driver) use ($visibility_of_element_located) {
421                $element = call_user_func(
422                    $visibility_of_element_located->getApply(),
423                    $driver
424                );
425
426                try {
427                    if ($element !== null && $element->isEnabled()) {
428                        return $element;
429                    }
430
431                    return null;
432                } catch (StaleElementReferenceException $e) {
433                    return null;
434                }
435            }
436        );
437    }
438
439    /**
440     * Wait until an element is no longer attached to the DOM.
441     *
442     * @param WebDriverElement $element The element to wait for.
443     * @return static Condition returns whether the element is still attached to the DOM.
444     */
445    public static function stalenessOf(WebDriverElement $element)
446    {
447        return new static(
448            function () use ($element) {
449                try {
450                    $element->isEnabled();
451
452                    return false;
453                } catch (StaleElementReferenceException $e) {
454                    return true;
455                }
456            }
457        );
458    }
459
460    /**
461     * Wrapper for a condition, which allows for elements to update by redrawing.
462     *
463     * This works around the problem of conditions which have two parts: find an element and then check for some
464     * condition on it. For these conditions it is possible that an element is located and then subsequently it is
465     * redrawn on the client. When this happens a StaleElementReferenceException is thrown when the second part of
466     * the condition is checked.
467     *
468     * @param WebDriverExpectedCondition $condition The condition wrapped.
469     * @return static Condition returns the return value of the getApply() of the given condition.
470     */
471    public static function refreshed(self $condition)
472    {
473        return new static(
474            function (WebDriver $driver) use ($condition) {
475                try {
476                    return call_user_func($condition->getApply(), $driver);
477                } catch (StaleElementReferenceException $e) {
478                    return null;
479                }
480            }
481        );
482    }
483
484    /**
485     * An expectation for checking if the given element is selected.
486     *
487     * @param mixed $element_or_by Either the element or the locator.
488     * @return static Condition returns whether the element is selected.
489     */
490    public static function elementToBeSelected($element_or_by)
491    {
492        return self::elementSelectionStateToBe(
493            $element_or_by,
494            true
495        );
496    }
497
498    /**
499     * An expectation for checking if the given element is selected.
500     *
501     * @param mixed $element_or_by Either the element or the locator.
502     * @param bool $selected The required state.
503     * @return static Condition returns whether the element is selected.
504     */
505    public static function elementSelectionStateToBe($element_or_by, $selected)
506    {
507        if ($element_or_by instanceof WebDriverElement) {
508            return new static(
509                function () use ($element_or_by, $selected) {
510                    return $element_or_by->isSelected() === $selected;
511                }
512            );
513        }
514
515        if ($element_or_by instanceof WebDriverBy) {
516            return new static(
517                function (WebDriver $driver) use ($element_or_by, $selected) {
518                    try {
519                        $element = $driver->findElement($element_or_by);
520
521                        return $element->isSelected() === $selected;
522                    } catch (StaleElementReferenceException $e) {
523                        return null;
524                    }
525                }
526            );
527        }
528
529        throw new \InvalidArgumentException('Instance of either WebDriverElement or WebDriverBy must be given');
530    }
531
532    /**
533     * An expectation for whether an alert() box is present.
534     *
535     * @return static Condition returns WebDriverAlert if alert() is present, null otherwise.
536     */
537    public static function alertIsPresent()
538    {
539        return new static(
540            function (WebDriver $driver) {
541                try {
542                    // Unlike the Java code, we get a WebDriverAlert object regardless
543                    // of whether there is an alert.  Calling getText() will throw
544                    // an exception if it is not really there.
545                    $alert = $driver->switchTo()->alert();
546                    $alert->getText();
547
548                    return $alert;
549                } catch (NoSuchAlertException $e) {
550                    return null;
551                }
552            }
553        );
554    }
555
556    /**
557     * An expectation checking the number of opened windows.
558     *
559     * @param int $expectedNumberOfWindows
560     * @return static
561     */
562    public static function numberOfWindowsToBe($expectedNumberOfWindows)
563    {
564        return new static(
565            function (WebDriver $driver) use ($expectedNumberOfWindows) {
566                return count($driver->getWindowHandles()) == $expectedNumberOfWindows;
567            }
568        );
569    }
570
571    /**
572     * An expectation with the logical opposite condition of the given condition.
573     *
574     * @param WebDriverExpectedCondition $condition The condition to be negated.
575     * @return mixed The negation of the result of the given condition.
576     */
577    public static function not(self $condition)
578    {
579        return new static(
580            function (WebDriver $driver) use ($condition) {
581                $result = call_user_func($condition->getApply(), $driver);
582
583                return !$result;
584            }
585        );
586    }
587}
588