1   package com.atlassian.pageobjects.framework.element;
2   
3   import com.atlassian.pageobjects.PageBinder;
4   import com.atlassian.pageobjects.binder.Init;
5   import com.atlassian.pageobjects.framework.timeout.TimeoutType;
6   import com.atlassian.pageobjects.framework.timeout.Timeouts;
7   import com.atlassian.webdriver.AtlassianWebDriver;
8   import com.atlassian.webdriver.utils.Check;
9   import com.atlassian.webdriver.utils.element.ElementLocated;
10  import org.openqa.selenium.By;
11  import org.openqa.selenium.RenderedWebElement;
12  import org.openqa.selenium.SearchContext;
13  import org.openqa.selenium.WebElement;
14  
15  import java.util.LinkedList;
16  import java.util.List;
17  import java.util.concurrent.TimeUnit;
18  import javax.inject.Inject;
19  
20  import static com.google.common.base.Preconditions.checkNotNull;
21  
22  /**
23   * Implementation of {@link com.atlassian.pageobjects.framework.element.Element} that waits for element to be
24   * present before executing each actions.
25   * 
26   */
27  public class WebDriverDelayedElement implements Element
28  {
29      @Inject
30      protected AtlassianWebDriver driver;
31  
32      @Inject
33      protected PageBinder pageBinder;
34  
35      @Inject
36      protected Timeouts timeouts;
37  
38      protected By locator;
39      protected SearchContext searchContext;
40  
41      private WebElementHolder webElementHolder;
42      private WebElement webElement;
43      private boolean webElementLocated = false;
44      private final TimeoutType defaultTimeout;
45  
46      /**
47       * Creates a WebDriverDelayedElement within the driver's search context and default timeout
48       * @param locator The locator mechanism to use.
49       */
50      public WebDriverDelayedElement(By locator)
51      {
52          this(locator, null, TimeoutType.DEFAULT);
53      }
54  
55      /**
56       * Creates a WebDriverDelayedElement within the driver's search context and default timeout.
57       * 
58       * @param locator The locator mechanism to use.
59       * @param defaultTimeout default timeout of this element
60       */
61      public WebDriverDelayedElement(By locator, TimeoutType defaultTimeout)
62      {
63          this(locator, null, defaultTimeout);
64      }
65  
66      /**
67       * Creates a WebDriverDelayedElement within a given search context and default timeout.
68       *
69       * @param locator The locator mechanism to use.
70       * @param searchContext The SearchContext to use.
71       */
72      public WebDriverDelayedElement(By locator, SearchContext searchContext)
73      {
74          this(locator, searchContext, TimeoutType.DEFAULT);
75      }
76  
77      /**
78       * Creates a WebDriverDelayedElement within a given search context and default timeout.
79       *
80       * @param locator The locator mechanism to use.
81       * @param searchContext The SearchContext to use.
82       * @param defaultTimeout default timeout of this element
83       */
84      public WebDriverDelayedElement(By locator, SearchContext searchContext, TimeoutType defaultTimeout)
85      {
86          this.locator = locator;
87          this.searchContext = searchContext;
88          this.defaultTimeout = checkNotNull(defaultTimeout);
89      }
90  
91      /**
92       * Creates a WebDriverDelayedElement with the given WebElement and default timeout.
93       *
94       * @param webElement The WebElement to wrap in a delayed element.
95       * @param defaultTimeout default timeout of this element
96       */
97      public WebDriverDelayedElement(WebElement webElement, TimeoutType defaultTimeout)
98      {
99          this.webElementHolder = new WebElementHolder(webElement);
100         this.defaultTimeout = checkNotNull(defaultTimeout);
101     }
102 
103 
104     /**
105      * Creates a WebDriverDelayedElement with the given WebElement.
106      *
107      * @param webElement The WebElement to wrap in a delayed element.
108      */
109     public WebDriverDelayedElement(WebElement webElement)
110     {
111         this(webElement, TimeoutType.DEFAULT);
112     }
113 
114     protected long timeout()
115     {
116         return timeouts.timeoutFor(defaultTimeout);
117     }
118 
119     protected int timeoutInSeconds()
120     {
121         // sucks sucks sucks sucks sucks....
122         return (int) TimeUnit.MILLISECONDS.toSeconds(timeout());
123     }
124 
125     @Init
126     public void initialize()
127     {
128         if(searchContext == null)
129         {
130             searchContext = driver;
131         }
132         if(this.webElementHolder != null)
133         {
134             this.webElement = this.webElementHolder.getWebElement();
135             this.webElementLocated = true;
136         }
137     }
138 
139     /**
140      * Class that holds a reference to a WebElement, used so that WebDriverDelayedElement can be instantiated with
141      * an exisiting instance of WebElment. Otherwise it would be overriden by the pageinjector.
142      */
143     private class WebElementHolder
144     {
145         private final WebElement webElement;
146 
147         public WebElementHolder(WebElement webElement)
148         {
149             this.webElement = webElement;
150         }
151 
152         public WebElement getWebElement()
153         {
154             return this.webElement;
155         }
156     }
157 
158     /**
159      * Waits until WebElement is present.
160      * 
161      * @return The WebElement or throws exception if not found.
162      */
163     protected WebElement waitForWebElement()
164     {
165         if(!webElementLocated)
166         {
167             if(!driver.elementExistsAt(locator, searchContext) && timeoutInSeconds() > 0)
168             {
169                 driver.waitUntil(new ElementLocated(locator, searchContext), timeoutInSeconds());
170             }
171 
172             webElement = searchContext.findElement(locator);
173             webElementLocated = true;
174         }
175         return webElement;
176     }
177     
178     public boolean isPresent()
179     {
180         // TODO: [Issue #SELENIUM-54] if the element was already located, isPresent() always returns true.
181         return webElementLocated || driver.elementExistsAt(locator, searchContext);
182     }
183 
184     public boolean isVisible()
185     {
186         WebElement element = waitForWebElement();
187         return ((RenderedWebElement) element).isDisplayed();
188     }
189 
190     public boolean hasClass(String className)
191     {
192         return Check.hasClass(className, waitForWebElement());
193     }
194 
195     public String attribute(String name)
196     {
197         return waitForWebElement().getAttribute(name);
198     }
199 
200     public String text()
201     {
202         return waitForWebElement().getText();
203     }
204 
205     public String value()
206     {
207         return waitForWebElement().getValue();
208     }
209 
210     public void click()
211     {
212         waitForWebElement().click();
213     }
214 
215     public void type(CharSequence... keysToSend)
216     {
217         waitForWebElement().sendKeys(keysToSend);
218     }
219 
220     public TimedElement timed()
221     {
222        return pageBinder.bind(WebDriverTimedElement.class, locator, defaultTimeout);
223     }
224 
225     public WebDriverMouseEvents mouseEvents()
226     {
227         return new WebDriverMouseEvents(driver, waitForWebElement());
228     }
229 
230     public List<Element> findAll(By locator)
231     {
232         List<Element> elements = new LinkedList<Element>();
233         List<WebElement> webElements = waitForWebElement().findElements(locator);
234         for(WebElement e: webElements)
235         {
236             elements.add(pageBinder.bind(WebDriverDelayedElement.class, e));
237         }
238         return elements;
239     }
240 
241     public Element find(By locator)
242     {
243         return pageBinder.bind(WebDriverDelayedElement.class, locator, waitForWebElement());
244     }
245 }