1 package com.atlassian.pageobjects.elements.search;
2
3 import com.atlassian.annotations.PublicApi;
4
5 import javax.annotation.Nonnull;
6
7 /**
8 * <h3>Search Overview</h3>
9 * Access point to advanced search within the context of the whole page, or a particular
10 * {@link com.atlassian.pageobjects.elements.PageElement DOM element}. The search query object provided by this method
11 * allows for issuing a sophisticated, multi-level search across the DOM and returning the result in a desired form.
12 * The following paragraphs discuss the search query API.
13 *
14 * <h3>Search query</h3>
15 * The {@code SearchQuery} object is essentially a sequence of search/transform/filter steps that are applied
16 * sequentially, starting with the root context (either the entire page, or a specific DOM element, depending on what
17 * this {@code PageElementSearch} refers to). Each single step will be applied to <i>all parents</i> that were found
18 * by the previous steps, resulting in a new list of elements that will be roots for the next step, and so on until the
19 * result is requested.
20 *
21 * <p/>
22 * Each search query starts off as an instance of {@link PageElementQuery} - with element type
23 * {@link com.atlassian.pageobjects.elements.PageElement}, however they may be converted into a more generic
24 * {@link AnyQuery} by transforming/mapping the results, which limits the applicable operations to only those
25 * applicable to any object (see {@link AnyQuery} and {@link SearchQuery}).
26 *
27 * <h4>Search Steps</h4>
28 * Each search step <i>must</i> be specified using a {@link org.openqa.selenium.By by locator}, using one of the
29 * {@code by()} methods on {@link PageElementQuery}. The search steps are only applicable if the element type of the
30 * query is {@link com.atlassian.pageobjects.elements.PageElement}.
31 *
32 * <h4>Filter Steps</h4>
33 * Each query supports {@code filters} to further narrow down the results. Filters can be specified by
34 * {@link com.google.common.base.Predicate Guava predicates} - the
35 * {@link com.atlassian.pageobjects.elements.PageElements} class contains a collection of common predicates to use.
36 * Clients can also use {@link com.atlassian.webdriver.testing.matcher.MatcherPredicate} to convert {@code Hamcrest}
37 * matchers into {@code Guava} predicates. The filter steps are applicable to any object, however filter steps preserve
38 * the element type of the query.
39 *
40 * <h4>Mapping Steps</h4>
41 * Each query supports {@code mappers} to map or flat map query results into some other objects (or collections of
42 * thereof). Map steps are applicable to any object, however they do not preserve element type, and therefore can turn
43 * {@link PageElementQuery} into a more generic {@link AnyQuery}. Some specialized page-element specific mapping steps
44 * preserve the element type (see {@link PageElementQuery}).
45 *
46 * <h3>Query results</h3>
47 * The search query object offers multiple methods to obtain the results, depending on what the client wants to achieve:
48 * <ul>
49 * <li>{@link SearchQuery#get()} - get the list of resulting objects now. Note that search query thus implements a
50 * specific Guava supplier, which makes it usable in all contexts where supplier can be used</li>
51 * <li>{@link SearchQuery#now()}} - synonym to {@link SearchQuery#get()}</li>
52 * <li>{@link SearchQuery#first()} - obtain the first element of the result, or {@code null} if the results is empty
53 * <li>{@link SearchQuery#hasResult()} - obtain a {@link com.atlassian.pageobjects.elements.query.TimedCondition}
54 * that will periodically execute the search and allow the client to wait until there is any result/there is no
55 * result matching the query, with a desired timeout - using
56 * {@link com.atlassian.pageobjects.elements.query.Poller}</li>
57 * <li>{@link SearchQuery#timed()} - obtain a {@link com.atlassian.pageobjects.elements.query.TimedQuery} that will
58 * periodically execute the search and allow the client to wait until the result matches its expectations, with a
59 * desired timeout - using {@link com.atlassian.pageobjects.elements.query.Poller}. This is <b>the most recommended
60 * way</b> of obtaining the search query results</li>
61 * </ul>
62 *
63 * <h4>Example</h4>
64 * The following method (presumably in a page object) searches for all the {@code span} elements in a drop-down that
65 * represent users, and subsequently retrieves the username value from those elements, returning them as a
66 * {@link com.atlassian.pageobjects.elements.query.TimedQuery}. The subsequent call to {@code Poller} (presumably in a
67 * test) executes timed assertion that validates that the list contains 3 usernames ("alice", "bob" and "charlie")
68 * within the timeout of an AJAX load (expecting the hypothetical drop-down to issue an AJAX call to populate its
69 * contents).
70 * <p/>
71 * <pre>
72 * {@code
73 * public TimedQuery<String> getUsernames() {
74 * return mySearch.search()
75 * .by(By.id("user-dropdown"))
76 * .by(By.tagName("li")) // can also use Elements.TAG_LI here
77 * .filter(isVisible()) // from PageElements
78 * .filter(hasClass("user-element")) // from PageElements
79 * .by(By.tagName("span"), hasDataAttribute("username")) // from PageElements, check for "data-username" attribute
80 * .withTimeout(TimeoutType.AJAX_ACTION)
81 * .transform(getDataAttribute("username")) // from PageElements
82 * .timed()
83 * }
84 *
85 * // ...
86 * Poller.waitUntil(myDropdown.getUsernames(), Matchers.contains("alice", "bob", "charlie"));
87 * }
88 * </pre>
89 *
90 * @since 2.3
91 *
92 * @see SearchQuery
93 * @see AnyQuery
94 * @see PageElementQuery
95 * @see DefaultQuery
96 */
97 @PublicApi
98 public interface PageElementSearch
99 {
100 /**
101 * @return a new search query object that allows execute the search in the current context
102 */
103 @Nonnull
104 DefaultQuery search();
105 }