View Javadoc

1   
2   
3   package com.atlassian.selenium;
4   
5   import com.atlassian.selenium.keyboard.KeyEvent;
6   import com.atlassian.selenium.keyboard.KeyEventSequence;
7   import com.atlassian.webtest.ui.keys.KeyEventType;
8   import com.google.gson.Gson;
9   import com.google.gson.GsonBuilder;
10  import com.google.gson.reflect.TypeToken;
11  import com.thoughtworks.selenium.DefaultSelenium;
12  
13  import java.io.BufferedReader;
14  import java.io.IOException;
15  import java.io.InputStream;
16  import java.io.InputStreamReader;
17  import java.lang.reflect.Type;
18  import java.util.Collection;
19  import java.util.EnumSet;
20  import java.util.HashMap;
21  import java.util.Map;
22  import java.util.Set;
23  
24  /**
25   * Extends the {@link DefaultSelenium} client to provide a more sensible implementation
26   * as well some extra utility methods such as keypress.
27   */
28  public class SingleBrowserSeleniumClient extends DefaultSelenium implements SeleniumClient
29  {
30  
31      private Browser browser;
32  
33      private Map<String, KeyEventSequence> characterKeySequenceMap = new HashMap<String, KeyEventSequence>();
34  
35      private static final Set<KeyEventType> ALL_EVENTS = EnumSet.allOf(KeyEventType.class);
36  
37      /**
38       * The maximum page load wait time used by Selenium. This value is set with
39       * {@link SeleniumConfiguration#getPageLoadWait()}.
40       */
41      protected final long PAGE_LOAD_WAIT;
42  
43      /**
44       * The maximum wait time for actions that don't require page loads. This value is set with
45       * {@link SeleniumConfiguration#getActionWait()}.
46       */
47      protected final long ACTION_WAIT;
48  
49      public SingleBrowserSeleniumClient(SeleniumConfiguration config)
50      {
51          super(new HtmlDumpingHttpCommandProcessor(config.getServerLocation(), config.getServerPort(), config.getBrowserStartString(), config.getBaseUrl()));
52  
53          this.PAGE_LOAD_WAIT = config.getPageLoadWait();
54          this.ACTION_WAIT = config.getActionWait();
55  
56          browser = Browser.typeOf(config.getBrowserStartString());
57      }
58  
59      public Browser getBrowser()
60      {
61          return browser;
62      }
63  
64      public void seleniumKeyPress(String locator, String key) {
65          super.keyPress(locator, key);
66      }
67  
68      /**
69       * Unlike {@link DefaultSelenium#open}, this opens the provided URL relative to the application context path.
70       * It also waits for the page to load -- a maximum of {@link #PAGE_LOAD_WAIT} before returning.
71       */
72      public void open(String url)
73      {
74          open(url, PAGE_LOAD_WAIT);
75      }
76  
77      /**
78       * Wait for page to load doesn't work the case of non-HTML based resources (like images).
79       * So sometimes you really do want to open a url without waiting.
80       * @param url
81       */
82      public void openNoWait(String url)
83      {
84          super.open(url);
85      }
86  
87      /**
88       * Opens the given URL and waits a maximum of timeoutMillis for the page to load completely.
89       */
90      public void open(String url, long timeoutMillis)
91      {
92          super.open(url);
93          super.waitForPageToLoad(String.valueOf(timeoutMillis));
94      }
95  
96      /**
97       * Overloads {@link #waitForPageToLoad(String)} to take in a long.
98       */
99      public void waitForPageToLoad(long timeoutMillis)
100     {
101         super.waitForPageToLoad(String.valueOf(timeoutMillis));
102     }
103 
104     /**
105      * Waits for the page to load with the default timeout configured in {@link SeleniumConfiguration}.
106      */
107     public void waitForPageToLoad()
108     {
109         waitForPageToLoad(PAGE_LOAD_WAIT);
110     }
111 
112     /**
113      * Executes the given Javascript in the context of the text page and waits for it to evaluate to true
114      * for a maximum of {@link #ACTION_WAIT} milliseconds.
115      * @see #waitForCondition(String, long) if you would like to specify your own timeout.
116      */
117     public void waitForCondition(String javascript)
118     {
119         waitForCondition(javascript, ACTION_WAIT);
120     }
121 
122     /**
123      * Executes the given Javascript in the context of the text page and waits for it to evaluate to true
124      * for a maximum of timeoutMillis.
125      */
126     public void waitForCondition(String javascript, long timeoutMillis)
127     {
128         waitForCondition(javascript, Long.toString(timeoutMillis));
129     }
130 
131     /**
132      * Waits for the page to finish loading ajax calls, and returns if there are no more ajax calls currently running.
133      * The method will check for a maximum of {@link #ACTION_WAIT} milliseconds
134      * @see #waitForAjaxWithJquery(long) if you would like to specify your own timeout.
135      */
136     public void waitForAjaxWithJquery()
137     {
138         waitForAjaxWithJquery(ACTION_WAIT);
139     }
140 
141     /**
142      * Waits for the page to finish loading ajax calls, and returns if there are no more ajax calls currently running.
143      * The method will check for a maximum of timeoutMillis
144      */
145     public void waitForAjaxWithJquery(long timeoutMillis)
146     {
147         if (!hasJquery())
148         {
149             throw new UnsupportedOperationException("This operation requires jQuery.");
150         }
151         waitForCondition("selenium.browserbot.getCurrentWindow().jQuery.active == 0;", Long.toString(timeoutMillis));
152     }
153 
154     /**
155      * Click the element with the given locator and optionally wait for the page to load, using {@link #PAGE_LOAD_WAIT}.
156      *
157      * @param locator the element to click, specified using Selenium selector syntax
158      * @param waitForPageToLoad whether to wait for the page to reload. Don't use this unless the page is completely
159      * reloaded.
160      * @see #click(String, long) if you would like to specify your own timeout.
161      */
162     public void click(String locator, boolean waitForPageToLoad)
163     {
164         super.click(locator);
165         if (waitForPageToLoad)
166             super.waitForPageToLoad(String.valueOf(PAGE_LOAD_WAIT));
167     }
168 
169     /**
170      * Submit the named form locator and optionally wait for the page to load, using {@link #PAGE_LOAD_WAIT}.
171      *
172      * @param form to click, specified using Selenium selector syntax
173      * @param waitForPageToLoad whether to wait for the page to reload. Don't use this unless the page is completely
174      * reloaded.
175      * @see #submit(String, long) if you would like to specify your own timeout.
176      */
177     public void submit(String form, boolean waitForPageToLoad)
178     {
179         super.submit(form);
180         if (waitForPageToLoad)
181             super.waitForPageToLoad(String.valueOf(PAGE_LOAD_WAIT));
182     }
183 
184     /**
185      * Click the element with the given locator and wait for the page to load, for a maximum of timeoutMillis.
186      * <p/>
187      * Do not use this method if the page does not reload.
188      *
189      * @param locator the element to click, specified using Selenium selector syntax
190      * @param timeoutMillis the maximum number of milliseconds to wait for the page to load. Polling takes place
191      * more frequently.
192      * @see #click(String, boolean) if you would like to use the default timeout
193      */
194     public void click(String locator, long timeoutMillis)
195     {
196         super.click(locator);
197         super.waitForPageToLoad(Long.toString(timeoutMillis));
198     }
199 
200     /**
201      * Click the element with the given locator and wait for the ajax call to finish.
202      *
203      * @param locator the element to click, specified using Selenium selector syntax
204      */
205     public void clickAndWaitForAjaxWithJquery(String locator)
206     {
207         super.click(locator);
208         waitForAjaxWithJquery();
209     }
210 
211     /**
212      * Click the element with the given locator and wait for the ajax call to finish.
213      *
214      * @param locator the element to click, specified using Selenium selector syntax
215      * @param timeoutMillis the maximum number of milliseconds to wait for the ajax calls to finish.
216      * @see #clickAndWaitForAjaxWithJquery(String) if you would like to use the default timeout
217      */
218     public void clickAndWaitForAjaxWithJquery(String locator, long timeoutMillis)
219     {
220         super.click(locator);
221         waitForAjaxWithJquery(timeoutMillis);
222     }
223 
224     /**
225      * Submit the given form and wait for the page to load, for a maximum of timeoutMillis.
226      * <p/>
227      * Do not use this method if the page does not reload.
228      *
229      * @param form the form to submit
230      * @param timeoutMillis the maximum number of milliseconds to wait for the page to load. Polling takes place
231      * more frequently.
232      * @see #click(String, boolean) if you would like to use the default timeout
233      */
234     public void submit(String form, long timeoutMillis)
235     {
236         super.submit(form);
237         super.waitForPageToLoad(Long.toString(timeoutMillis));
238     }
239 
240     /**
241      * This will type into a field by sending key down / key press / key up events.
242      * @param locator Uses the Selenium locator syntax
243      * @param key The key to be pressed
244      */
245     public void keyPress(String locator, String key)
246     {
247         super.keyDown(locator, key);
248         super.keyPress(locator, key);
249         super.keyUp(locator, key);
250     }
251 
252     /**
253      * This will type into a field by first blanking it out and then sending key down / key press / key up
254      * events.
255      *
256      * @param locator the Selenium locator
257      * @param string  the string to type
258      * @param reset   Should the field be reset first?
259      */
260     public void typeWithFullKeyEvents(String locator, String string, boolean reset)
261     {
262         new SeleniumKeyHandler(this, locator, ALL_EVENTS, reset).typeWithFullKeyEvents(string);
263     }
264 
265     /**
266      * This will type into a field by first blanking it out and then sending key down / key press / key up
267      * events. This really only calls {@link #typeWithFullKeyEvents(String,String,boolean)})}
268      *
269      * @param locator - the usual Selenium locator
270      * @param string  the string to type into a field
271      */
272     public void typeWithFullKeyEvents(String locator, String string)
273     {
274         typeWithFullKeyEvents(locator, string, true);
275     }
276 
277     public void simulateKeyPressForCharacter(final String locator, final Character character)
278     {
279         simulateKeyPressForCharacter(locator,character, ALL_EVENTS);
280     }
281 
282     public void simulateKeyPressForCharacter(final String locator, final Character character, Collection<KeyEventType> eventsToFire)
283     {
284         simulateKeyPressForIdentifier(locator,character.toString(), eventsToFire);
285     }
286 
287     public void simulateKeyPressForSpecialKey(final String locator, final int keyCode)
288     {
289         simulateKeyPressForSpecialKey(locator,keyCode, ALL_EVENTS);
290     }
291 
292     public void simulateKeyPressForSpecialKey(final String locator, final int keyCode, Collection<KeyEventType> eventsToFire)
293     {
294         simulateKeyPressForIdentifier(locator,String.format("0x%x",keyCode), eventsToFire);
295     }
296 
297     public void generateKeyEvent(final String locator, final KeyEventType eventType, final int keyCode, final int characterCode,
298                                  final boolean shiftKey, final boolean altKey, final boolean controlKey, final boolean metaKey)
299     {
300         getEval("selenium.generateKeyEvent(\""+locator+"\",'"
301                 +eventType.getEventString()+"',"
302                 +Integer.toString(keyCode)+","
303                 +Integer.toString(characterCode)+","
304                 +Boolean.TRUE.toString()+","
305                 +Boolean.toString(shiftKey)+","
306                 +Boolean.toString(altKey)+","
307                 +Boolean.toString(controlKey)+","
308                 +Boolean.toString(metaKey)+")");
309     }
310 
311     private void simulateKeyPressForIdentifier(final String locator, final String identifier, Collection<KeyEventType> eventsToFire)
312     {
313         if (characterKeySequenceMap.containsKey(identifier))
314         {
315             for (KeyEvent ke : characterKeySequenceMap.get(identifier).getKeyEvents())
316             {
317                 if (!ke.getBrowsers().contains(this.browser))
318                 {
319                     continue;
320                 }
321                 if (!eventsToFire.contains(ke.getEventType()))
322                 {
323                     continue;
324                 }
325                 int keyCode = 0;
326                 int characterCode = 0;
327                 if (ke.isToCharacterCode())
328                 {
329                     characterCode = ke.getCode();
330                 }
331                 if (ke.isToKeyCode())
332                 {
333                    keyCode = ke.getCode();
334                 }
335                 generateKeyEvent(locator,ke.getEventType(),keyCode,characterCode, ke.isCtrlKeyDown(),ke.isAltKeyDown(),
336                         ke.isShiftKeyDown(),ke.isMetaKey());
337             }
338         }
339     }
340 
341     private void toggleShiftKey(boolean keyIsPressed)
342     {
343         if (keyIsPressed)
344         {
345             super.shiftKeyDown();
346         }
347         else
348         {
349             super.shiftKeyUp();
350         }
351     }
352 
353     private void toggleAltKey(boolean keyIsPressed)
354     {
355         if (keyIsPressed)
356         {
357             super.altKeyDown();
358         }
359         else
360         {
361             super.altKeyUp();
362         }
363     }
364 
365     private void toggleControlKey(boolean keyIsPressed)
366     {
367         if (keyIsPressed)
368         {
369             super.controlKeyDown();
370         }
371         else
372         {
373             super.controlKeyUp();
374         }
375     }
376 
377     private void toggleMetaKey(boolean keyIsPressed)
378     {
379         if (keyIsPressed)
380         {
381             super.metaKeyDown();
382         }
383         else
384         {
385             super.metaKeyUp();
386         }
387 
388     }
389 
390     public void toggleToKeyCode(boolean toKeyCode)
391     {
392         super.getEval("this.browserbot.toKeyCode = "+toKeyCode+";");
393     }
394 
395     public void toggleToCharacterCode(boolean toCharacterCode)
396     {
397         super.getEval("this.browserbot.toCharacterCode = "+toCharacterCode+";");        
398     }
399 
400     /**
401      * This will select an option from a {@code select} field.
402      *
403      * @param selectName the select field name
404      * @param label the label to select
405      */
406     public void selectOption(String selectName, String label)
407     {
408         // In some browsers (i.e. Safari) the select items have funny padding
409         // so we need to use this funny method to find how the select item is
410         // padded so that it can be matched
411         String[] options = super.getSelectOptions(selectName);
412         int i = 0;
413         for(; i < options.length; i++)
414         {
415             if(options[i].trim().equals(label))
416             {
417                 break;
418             }
419         }
420         if(i < options.length)
421         {
422             super.select(selectName, options[i]);
423         }
424     }
425 
426     /**
427      * This will select an option from a {@code select} field. If the field calls executes an ajax call onchange of
428      * the value, this method will wait for that ajax method to finish.
429      *
430      * @param selectName the select field name
431      * @param label the label to select
432      */
433     public void selectOptionAndWaitForAjaxWithJquery(String selectName, String label)
434     {
435         this.selectOption(selectName, label);
436         this.waitForAjaxWithJquery();
437     }
438 
439     /**
440      * Checks a checkbox given a name and value.
441      */
442     public void check(String name, String value)
443     {
444         check("name=" + name + " value=" + value);
445     }
446 
447     public void clickLinkWithText(String text, boolean waitForPageToLoad)
448     {
449         super.click("link=" + text);
450         if (waitForPageToLoad) waitForPageToLoad();
451     }
452 
453     public void clickButton(String buttonText, boolean waitForPageToLoad)
454     {
455         clickElementWithXpath("//input[@value = '" + buttonText + "']");
456         if (waitForPageToLoad) waitForPageToLoad();
457     }
458 
459     public void clickButtonAndWaitForAjaxWithJquery(String buttonText)
460     {
461         this.clickButton(buttonText, false);
462         waitForAjaxWithJquery();
463     }
464 
465     public void clickButtonWithName(String buttonName, boolean waitForPageToLoad)
466     {
467         clickElementWithXpath("//input[@name = '" + buttonName + "']");
468         if (waitForPageToLoad) waitForPageToLoad();
469     }
470 
471     public void clickButtonWithNameAndWaitForAjaxWithJquery(String buttonName)
472     {
473         this.clickButtonWithName(buttonName, false);
474         waitForAjaxWithJquery();
475     }
476 
477     public void clickElementWithTitle(String title)
478     {
479         super.click("xpath=//*[@title='" + title + "']");
480     }
481 
482     public void clickElementWithTitleAndWaitForAjaxWithJquery(String title)
483     {
484         this.clickElementWithTitle(title);
485         waitForAjaxWithJquery();
486     }
487 
488     public void clickElementWithClass(String className)
489     {
490         super.click("css=." + className);
491     }
492 
493     public void clickElementWithClassAndWaitForAjaxWithJquery(String className)
494     {
495         this.clickElementWithClass(className);
496         waitForAjaxWithJquery();
497     }
498 
499     public void clickElementWithCss(String cssSelector)
500     {
501         super.click("css=" + cssSelector);
502     }
503 
504     public void clickElementWithCssAndWaitForAjaxWithJquery(String cssSelector)
505     {
506         this.clickElementWithCss(cssSelector);
507         waitForAjaxWithJquery();
508     }
509 
510     public void clickElementWithXpath(String xpath)
511     {
512         super.click("xpath=" + xpath);
513     }
514 
515     public void clickElementWithXpathAndWaitForAjaxWithJquery(String xpath)
516     {
517         this.clickElementWithXpath(xpath);
518         waitForAjaxWithJquery();
519     }
520 
521     public void typeInElementWithName(String elementName, String text)
522     {
523         super.type("name=" + elementName, text);
524     }
525 
526     public void typeInElementWithCss(String cssSelector, String text)
527     {
528         super.type("css=" + cssSelector, text);
529     }
530 
531     public boolean hasJquery()
532     {
533         String evalJquery = getEval("selenium.browserbot.getCurrentWindow().jQuery");
534         return evalJquery != null && !"null".equals(evalJquery) && !"undefined".equals(evalJquery);
535     }
536 
537     private void addJqueryLocator() throws IOException
538     {
539         String jqueryImpl = readFile("jquery-1.4.2.min.js");
540         String jqueryLocStrategy = readFile("jquery-locationStrategy.js");
541         //client.setExtensionJs(jqueryImpl);
542         addScript(jqueryImpl, "jquery");
543         addLocationStrategy("jquery", jqueryLocStrategy );
544     }
545 
546     private void addKeyPressJSCorrections() throws IOException
547     {
548         String keyPressCorrectionsImpl = readFile("KeyPressCorrected.js");
549         String generateKeyPressImpl = readFile("generateKeyEvent.js");
550         addScript(keyPressCorrectionsImpl, "KeyPressCorrected");        
551         addScript(generateKeyPressImpl, "generateKeyEvent");
552     }
553 
554     private void loadCharacterKeyEventMap() throws IOException
555     {
556         Gson gson = new GsonBuilder().create();
557         String characterArrayJSON = readFile("charactersKeySequences.json");
558 
559         Type collectionType = new TypeToken<HashMap<String, KeyEventSequence>>(){}.getType();
560         characterKeySequenceMap = gson.fromJson(characterArrayJSON,collectionType);
561         characterKeySequenceMap.put(" ",characterKeySequenceMap.get(String.format("0x%x",java.awt.event.KeyEvent.VK_SPACE)));
562         characterKeySequenceMap.put("\n",characterKeySequenceMap.get(String.format("0x%x",java.awt.event.KeyEvent.VK_ENTER)));
563     }
564 
565     private void addTagNameScripts() throws IOException
566     {
567         String tagNames = readFile("tag-name.js");
568         addScript(tagNames, "seleniumTagNames");
569     }
570 
571     private static String readFile(String file) throws IOException
572     {
573         InputStream stream = SingleBrowserSeleniumClient.class.getClassLoader().getResourceAsStream(file);
574         BufferedReader reader =  new BufferedReader(new InputStreamReader(stream));
575 
576         String line = reader.readLine();
577         StringBuffer contents = new StringBuffer();
578 
579         while(line != null)
580         {
581             contents.append(line).append("\n");
582             line = reader.readLine();
583         }
584 
585         return contents.toString();
586     }
587 
588     public void start()
589     {
590         super.start();
591         try
592         {
593             addJqueryLocator();
594             loadCharacterKeyEventMap();
595             addKeyPressJSCorrections();
596             addTagNameScripts();
597         }
598         catch (IOException ioe)
599         {
600             System.err.println("Unable to load JQuery locator strategy: " + ioe);
601             ioe.printStackTrace(System.err);
602         }
603 
604     }
605 
606     public void evaluate (String command)
607     {
608         // For compatibility with VisualComparableClient
609         this.getEval(command);
610     }
611 
612     public void captureEntirePageScreenshot (String filePath)
613     {
614         // For compatibility with VisualComparableClient
615         this.captureEntirePageScreenshot(filePath, "");
616     }
617 
618     public void refreshAndWait ()
619     {
620         // For compatibility with VisualComparableClient
621         this.refresh();
622         this.waitForPageToLoad();
623     }
624 
625     public boolean waitForJQuery (long waitTimeMillis)
626     {
627         // For compatibility with VisualComparableClient
628         this.waitForCondition("selenium.browserbot.getCurrentWindow().jQuery.active == 0;", Long.toString(400));
629         try
630         {
631             Thread.sleep(waitTimeMillis);
632         }
633         catch (InterruptedException e)
634         {
635             return false;
636         }
637         return true;
638     }
639 }