1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package org.xwiki.test.wysiwyg.framework

File AbstractWysiwygTestCase.java

 

Coverage histogram

../../../../../img/srcFileCovDistChart10.png
0% of files have more coverage

Code metrics

18
212
114
1
1,075
629
124
0.58
1.86
114
1.09

Classes

Class Line # Actions
AbstractWysiwygTestCase 43 212 0% 124 29
0.915697791.6%
 

Contributing tests

This file is covered by 283 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package org.xwiki.test.wysiwyg.framework;
21   
22    import java.io.UnsupportedEncodingException;
23    import java.net.URLEncoder;
24    import java.util.Arrays;
25   
26    import org.openqa.selenium.By;
27    import org.openqa.selenium.JavascriptExecutor;
28    import org.openqa.selenium.Keys;
29    import org.openqa.selenium.WebDriver;
30    import org.openqa.selenium.WebElement;
31    import org.openqa.selenium.support.ui.ExpectedCondition;
32    import org.xwiki.test.selenium.framework.AbstractXWikiTestCase;
33   
34    import com.thoughtworks.selenium.Wait;
35   
36    import static org.junit.Assert.*;
37   
38    /**
39    * All XWiki WYSIWYG tests must extend this class.
40    *
41    * @version $Id: 7c45b7c27fc3b6043b2e5fa929db21c03dd29bc1 $
42    */
 
43    public class AbstractWysiwygTestCase extends AbstractXWikiTestCase
44    {
45    private static final String WYSIWYG_LOCATOR_FOR_WYSIWYG_TAB = "//div[@role='tab'][@tabIndex=0]/div[.='WYSIWYG']";
46   
47    private static final String WYSIWYG_LOCATOR_FOR_SOURCE_TAB = "//div[@role='tab'][@tabIndex=0]/div[.='Source']";
48   
49    /**
50    * The title of the indent tool bar button. This title is used in XPath locators to access the indent button.
51    */
52    public static final String TOOLBAR_BUTTON_INDENT_TITLE = "Increase Indent";
53   
54    /**
55    * The title of the outdent tool bar button. This title is used in XPath locators to access the outdent button.
56    */
57    public static final String TOOLBAR_BUTTON_OUTDENT_TITLE = "Decrease Indent";
58   
59    /**
60    * The title of the undo tool bar button.
61    */
62    public static final String TOOLBAR_BUTTON_UNDO_TITLE = "Undo (Ctrl+Z)";
63   
64    /**
65    * The title of the redo tool bar button.
66    */
67    public static final String TOOLBAR_BUTTON_REDO_TITLE = "Redo (Ctrl+Y)";
68   
69    /**
70    * The locator for the tool bar list box used to change the style of the current selection.
71    */
72    public static final String TOOLBAR_SELECT_STYLE = "//select[@title=\"Apply Style\"]";
73   
74    /**
75    * Locates a menu item by its label.
76    */
77    public static final String MENU_ITEM_BY_LABEL =
78    "//td[contains(@class, 'gwt-MenuItem')]/div[@class = 'gwt-MenuItemLabel' and . = '%s']";
79   
80    /**
81    * Use this small interval when the operation you are waiting for doesn't execute instantly but pretty fast anyway.
82    */
83    public static final long SMALL_WAIT_INTERVAL = 50L;
84   
 
85  283 toggle @Override
86    public void setUp()
87    {
88  283 super.setUp();
89   
90  283 login();
91  283 open(this.getClass().getSimpleName(), getTestMethodName(), "edit", "editor=wysiwyg");
92  283 waitForEditorToLoad();
93    }
94   
95    /**
96    * Logs in with the default user for this test case.
97    */
 
98  280 toggle protected void login()
99    {
100    // Nothing here. Use the default login in the WYSIWYG test setup.
101    }
102   
103    /**
104    * @return the rich text area element
105    */
 
106  968 toggle protected RichTextAreaElement getRichTextArea()
107    {
108  968 WebDriver driver = getDriver();
109  968 return new RichTextAreaElement(driver, driver.findElement(By.className("gwt-RichTextArea")));
110    }
111   
112    /**
113    * @return the source text area
114    */
 
115  1167 toggle protected WebElement getSourceTextArea()
116    {
117  1167 return getDriver().findElement(By.className("xPlainTextEditor"));
118    }
119   
120    /**
121    * Sets the content of the rich text area.
122    *
123    * @param html the new content of the rich text area
124    */
 
125  20 toggle public void setContent(String html)
126    {
127  20 getRichTextArea().setContent(html);
128    }
129   
130    /**
131    * Resets the content of the rich text area by selecting all the text like CTRL+A and deleting it using Backspace.
132    */
 
133  3 toggle public void resetContent()
134    {
135    // We try to mimic as much as possible the user behavior.
136    // First, we select all the content.
137  3 selectAllContent();
138    // Delete the selected content.
139  3 typeBackspace();
140    // We select again all the content. In Firefox, the selection will include the annoying br tag. Further typing
141    // will overwrite it. See XWIKI-2732.
142  3 selectAllContent();
143    }
144   
 
145  34 toggle public void selectAllContent()
146    {
147  34 getRichTextArea().sendKeys(Keys.chord(Keys.CONTROL, "a"));
148    }
149   
 
150  172 toggle public void typeText(String text)
151    {
152  172 getRichTextArea().sendKeys(text);
153    }
154   
 
155  19 toggle public void typeTextThenEnter(String text)
156    {
157  19 getRichTextArea().sendKeys(text, Keys.RETURN);
158    }
159   
160    /**
161    * Presses the specified key for the given number of times in WYSIWYG rich text editor.
162    *
163    * @param key the key to be pressed
164    * @param count the number of times to press the specified key
165    * @param hold {@code false} if the key should be released after each key press, {@code true} if it should be hold
166    * down and released just at the end
167    */
 
168  101 toggle public void sendKey(Keys key, int count, boolean hold)
169    {
170  101 Keys[] sequence = new Keys[count];
171  101 Arrays.fill(sequence, key);
172  101 if (hold) {
173  2 getRichTextArea().sendKeys(Keys.chord(sequence));
174    } else {
175  99 getRichTextArea().sendKeys(sequence);
176    }
177    }
178   
 
179  34 toggle public void typeEnter()
180    {
181  34 typeEnter(1);
182    }
183   
 
184  38 toggle public void typeEnter(int nb)
185    {
186  38 sendKey(Keys.RETURN, nb, false);
187    }
188   
 
189  15 toggle public void typeShiftEnter()
190    {
191  15 getRichTextArea().sendKeys(Keys.chord(Keys.SHIFT, Keys.RETURN));
192    }
193   
 
194  1 toggle public void typeControlEnter()
195    {
196  1 getRichTextArea().sendKeys(Keys.chord(Keys.CONTROL, Keys.RETURN));
197    }
198   
 
199  0 toggle public void typeMetaEnter()
200    {
201  0 getRichTextArea().sendKeys(Keys.chord(Keys.META, Keys.RETURN));
202    }
203   
 
204  27 toggle public void typeBackspace()
205    {
206  27 typeBackspace(1);
207    }
208   
 
209  33 toggle public void typeBackspace(int count)
210    {
211  33 typeBackspace(count, false);
212    }
213   
 
214  34 toggle public void typeBackspace(int count, boolean hold)
215    {
216  34 sendKey(Keys.BACK_SPACE, count, hold);
217    }
218   
 
219  0 toggle public void typeLeftArrow()
220    {
221  0 getRichTextArea().sendKeys(Keys.ARROW_LEFT);
222    }
223   
 
224  0 toggle public void typeUpArrow()
225    {
226  0 getRichTextArea().sendKeys(Keys.ARROW_UP);
227    }
228   
 
229  0 toggle public void typeRightArrow()
230    {
231  0 getRichTextArea().sendKeys(Keys.ARROW_RIGHT);
232    }
233   
 
234  0 toggle public void typeDownArrow()
235    {
236  0 getRichTextArea().sendKeys(Keys.ARROW_DOWN);
237    }
238   
 
239  19 toggle public void typeDelete()
240    {
241  19 typeDelete(1);
242    }
243   
 
244  20 toggle public void typeDelete(int count)
245    {
246  20 typeDelete(count, false);
247    }
248   
 
249  21 toggle public void typeDelete(int count, boolean hold)
250    {
251  21 sendKey(Keys.DELETE, count, hold);
252    }
253   
 
254  7 toggle public void typeTab()
255    {
256  7 typeTab(1);
257    }
258   
 
259  8 toggle public void typeTab(int count)
260    {
261  8 sendKey(Keys.TAB, count, false);
262    }
263   
 
264  10 toggle public void typeShiftTab()
265    {
266  10 getRichTextArea().sendKeys(Keys.chord(Keys.SHIFT, Keys.TAB));
267    }
268   
 
269  1 toggle public void typeShiftTab(int count)
270    {
271  5 for (int i = 0; i < count; i++) {
272  4 typeShiftTab();
273    }
274    }
275   
 
276  13 toggle public void clickUnorderedListButton()
277    {
278  13 pushToolBarButton("Bullets On/Off");
279    }
280   
 
281  3 toggle public void clickOrderedListButton()
282    {
283  3 pushToolBarButton("Numbering On/Off");
284    }
285   
 
286  8 toggle public void clickIndentButton()
287    {
288  8 pushToolBarButton(TOOLBAR_BUTTON_INDENT_TITLE);
289    }
290   
 
291  0 toggle public boolean isIndentButtonEnabled()
292    {
293  0 return isPushButtonEnabled(TOOLBAR_BUTTON_INDENT_TITLE);
294    }
295   
 
296  7 toggle public void clickOutdentButton()
297    {
298  7 pushToolBarButton(TOOLBAR_BUTTON_OUTDENT_TITLE);
299    }
300   
 
301  0 toggle public boolean isOutdentButtonEnabled()
302    {
303  0 return isPushButtonEnabled(TOOLBAR_BUTTON_OUTDENT_TITLE);
304    }
305   
 
306  10 toggle public void clickBoldButton()
307    {
308  10 pushToolBarButton("Bold (Ctrl+B)");
309    }
310   
 
311  2 toggle public void clickItalicsButton()
312    {
313  2 pushToolBarButton("Italic (Ctrl+I)");
314    }
315   
 
316  2 toggle public void clickUnderlineButton()
317    {
318  2 pushToolBarButton("Underline (Ctrl+U)");
319    }
320   
 
321  1 toggle public void clickStrikethroughButton()
322    {
323  1 pushToolBarButton("Strikethrough");
324    }
325   
 
326  6 toggle public void clickHRButton()
327    {
328  6 pushToolBarButton("Insert Horizontal Ruler");
329    }
330   
 
331  1 toggle public void clickSubscriptButton()
332    {
333  1 pushToolBarButton("Subscript");
334    }
335   
 
336  1 toggle public void clickSuperscriptButton()
337    {
338  1 pushToolBarButton("Superscript");
339    }
340   
 
341  15 toggle public void clickUndoButton()
342    {
343  15 pushToolBarButton(TOOLBAR_BUTTON_UNDO_TITLE);
344    }
345   
 
346  5 toggle public void clickUndoButton(int count)
347    {
348  18 for (int i = 0; i < count; i++) {
349  13 clickUndoButton();
350    }
351    }
352   
 
353  9 toggle public void clickRedoButton()
354    {
355  9 pushToolBarButton(TOOLBAR_BUTTON_REDO_TITLE);
356    }
357   
 
358  1 toggle public void clickRedoButton(int count)
359    {
360  8 for (int i = 0; i < count; i++) {
361  7 clickRedoButton();
362    }
363    }
364   
 
365  7 toggle public void clickSymbolButton()
366    {
367  7 pushToolBarButton("Insert Custom Character");
368    }
369   
 
370  0 toggle public void clickOfficeImporterButton()
371    {
372  0 pushToolBarButton("Import Office Content");
373    }
374   
 
375  2 toggle public void clickBackToEdit()
376    {
377  2 submit("//input[@type = 'submit' and @value = 'Back To Edit']");
378  2 waitForEditorToLoad();
379    }
380   
 
381  45 toggle public void applyStyle(final String style)
382    {
383    // Wait until the given style is not selected (because the tool bar might not be updated).
384  45 new Wait()
385    {
 
386  45 toggle public boolean until()
387    {
388  45 return getSelenium().isEditable(TOOLBAR_SELECT_STYLE)
389    && (!getSelenium().isSomethingSelected(TOOLBAR_SELECT_STYLE) || !style.equals(getSelenium()
390    .getSelectedLabel(TOOLBAR_SELECT_STYLE)));
391    }
392    }.wait("The specified style, '" + style + "', is already applied!");
393  45 getSelenium().select(TOOLBAR_SELECT_STYLE, style);
394    }
395   
396    /**
397    * Waits for the specified style to be detected.
398    *
399    * @param style the expected style
400    */
 
401  2 toggle public void waitForStyleDetected(final String style)
402    {
403  2 new Wait()
404    {
 
405  3 toggle public boolean until()
406    {
407  3 return getSelenium().isSomethingSelected(TOOLBAR_SELECT_STYLE)
408    && style.equals(getSelenium().getSelectedLabel(TOOLBAR_SELECT_STYLE));
409    }
410    }.wait("The specified style, '" + style + "', wasn't detected!");
411    }
412   
 
413  7 toggle public void applyStylePlainText()
414    {
415  7 applyStyle("Plain text");
416    }
417   
 
418  21 toggle public void applyStyleTitle1()
419    {
420  21 applyStyle("Title 1");
421    }
422   
 
423  3 toggle public void applyStyleTitle2()
424    {
425  3 applyStyle("Title 2");
426    }
427   
 
428  3 toggle public void applyStyleTitle3()
429    {
430  3 applyStyle("Title 3");
431    }
432   
 
433  2 toggle public void applyStyleTitle4()
434    {
435  2 applyStyle("Title 4");
436    }
437   
 
438  8 toggle public void applyStyleTitle5()
439    {
440  8 applyStyle("Title 5");
441    }
442   
 
443  1 toggle public void applyStyleTitle6()
444    {
445  1 applyStyle("Title 6");
446    }
447   
 
448  129 toggle public void pushButton(String locator)
449    {
450    // Can't use : selenium.click(locator);
451    // A GWT PushButton is not a standard HTML <input type="submit" ...> or a <button ...>
452    // rather it is a styled button constructed from DIV and other HTML tags.
453    // Source :
454    // http://www.blackpepper.co.uk/black-pepper-blog/Simulating-clicks-on-GWT-push-buttons-with-Selenium-RC.html
455  129 getSelenium().mouseOver(locator);
456  129 getSelenium().mouseDown(locator);
457  129 getSelenium().mouseUp(locator);
458  129 getSelenium().mouseOut(locator);
459    }
460   
461    /**
462    * Pushes the tool bar button with the specified title.
463    *
464    * @param title the title of the tool bar button to be pushed
465    */
 
466  129 toggle public void pushToolBarButton(String title)
467    {
468  129 pushButton("//div[@title='" + title + "']");
469    }
470   
 
471  187 toggle public void clickButtonWithText(String buttonText)
472    {
473  187 getSelenium().click("//button[. = \"" + buttonText + "\"]");
474    }
475   
476    /**
477    * Clicks on the menu item with the specified label.
478    *
479    * @param menuLabel a {@link String} representing the label of a menu item
480    */
 
481  353 toggle public void clickMenu(String menuLabel)
482    {
483  353 String selector = String.format(MENU_ITEM_BY_LABEL, menuLabel);
484    // We select the menu item first.
485  353 getSelenium().mouseOver(selector);
486    // And then we click on it.
487  353 getSelenium().click(selector);
488    }
489   
490    /**
491    * Waits for the specified menu to be present.
492    *
493    * @param menuLabel the menu label
494    */
 
495  0 toggle public void waitForMenu(String menuLabel)
496    {
497  0 waitForElement(String.format(MENU_ITEM_BY_LABEL, menuLabel));
498    }
499   
500    /**
501    * Closes the menu containing the specified menu item by pressing the escape key.
502    *
503    * @param menuLabel a menu item from the menu to be closed
504    */
 
505  4 toggle public void closeMenuContaining(String menuLabel)
506    {
507  4 getSelenium().typeKeys(String.format(MENU_ITEM_BY_LABEL, menuLabel), "\\27");
508    }
509   
510    /**
511    * Switch the WYSIWYG editor by clicking on the "WYSIWYG" tab item and waits for the rich text area to be
512    * initialized.
513    */
 
514  188 toggle public void switchToWysiwyg()
515    {
516  188 switchToWysiwyg(true);
517    }
518   
519    /**
520    * Switch the WYSIWYG editor by clicking on the "WYSIWYG" tab item.
521    *
522    * @param wait {@code true} to wait for the rich text area to be initialized, {@code false} otherwise
523    */
 
524  194 toggle public void switchToWysiwyg(boolean wait)
525    {
526  194 ensureElementIsNotCoveredByFloatingMenu(By.xpath(WYSIWYG_LOCATOR_FOR_WYSIWYG_TAB));
527  194 getSelenium().click(WYSIWYG_LOCATOR_FOR_WYSIWYG_TAB);
528  194 if (wait) {
529  188 final String enabledToolBarButtonXPath =
530    "//div[contains(@class, 'gwt-ToggleButton') and not(contains(@class, '-disabled'))]";
531  188 getDriver().waitUntilCondition(new ExpectedCondition<Boolean>()
532    {
 
533  386 toggle @Override
534    public Boolean apply(WebDriver input)
535    {
536    // When switching between tabs, it sometimes takes longer for toggle buttons to become enabled. We
537    // need to wait for that before we can properly use the WYSIWYG.
538  386 return !getSourceTextArea().isEnabled()
539    && getDriver().hasElementWithoutWaiting(By.xpath(enabledToolBarButtonXPath));
540    }
541    });
542    }
543    }
544   
545    /**
546    * Switch the Source editor by clicking on the "Source" tab item and waits for the plain text area to be
547    * initialized.
548    */
 
549  369 toggle public void switchToSource()
550    {
551  369 switchToSource(true);
552    }
553   
554    /**
555    * Switch the Source editor by clicking on the "Source" tab item.
556    *
557    * @param wait {@code true} to wait for the plain text area to be initialized, {@code false} otherwise
558    */
 
559  376 toggle public void switchToSource(boolean wait)
560    {
561  376 ensureElementIsNotCoveredByFloatingMenu(By.xpath(WYSIWYG_LOCATOR_FOR_SOURCE_TAB));
562  376 getSelenium().click(WYSIWYG_LOCATOR_FOR_SOURCE_TAB);
563  376 if (wait) {
564  369 getDriver().waitUntilCondition(new ExpectedCondition<Boolean>()
565    {
 
566  370 toggle @Override
567    public Boolean apply(WebDriver input)
568    {
569  370 return getDriver().findElementWithoutWaiting(By.className("xPlainTextEditor")).isEnabled();
570    }
571    });
572    // Focus the source text area.
573  369 getSourceTextArea().sendKeys("");
574    }
575    }
576   
577    /**
578    * Types the specified text in the input specified by its title.
579    *
580    * @param inputTitle the {@code title} attribute of the {@code} input element to type in
581    * @param text the text to type in the input
582    */
 
583  57 toggle public void typeInInput(String inputTitle, String text)
584    {
585  57 getSelenium().type("//input[@title=\"" + inputTitle + "\"]", text);
586    }
587   
588    /**
589    * @param inputTitle the title of the input whose value to return.
590    * @return the value of an input specified by its title.
591    */
 
592  12 toggle public String getInputValue(String inputTitle)
593    {
594  12 return getSelenium().getValue("//input[@title=\"" + inputTitle + "\"]");
595    }
596   
 
597  33 toggle public boolean isPushButtonEnabled(String pushButtonTitle)
598    {
599  33 return getDriver().hasElementWithoutWaiting(
600    By.xpath("//div[@title='" + pushButtonTitle + "' and @class='gwt-PushButton gwt-PushButton-up']"));
601    }
602   
603    /**
604    * @param toggleButtonTitle the tool tip of a toggle button from the WYSIWYG tool bar
605    * @return {@code true} if the specified toggle button is enabled, {@code false} otherwise
606    */
 
607  3 toggle public boolean isToggleButtonEnabled(String toggleButtonTitle)
608    {
609  3 return getDriver().hasElementWithoutWaiting(
610    By.xpath("//div[@title='" + toggleButtonTitle
611    + "' and contains(@class, 'gwt-ToggleButton') and not(contains(@class, '-disabled'))]"));
612    }
613   
614    /**
615    * Waits for the specified push button to have the specified state i.e. enabled or disabled.
616    *
617    * @param pushButtonTitle identifies the button to wait for
618    * @param enabled {@code true} to wait for the specified button to become enabled, {@code false} to wait for it to
619    * become disabled
620    */
 
621  25 toggle public void waitForPushButton(final String pushButtonTitle, final boolean enabled)
622    {
623  25 new Wait()
624    {
 
625  33 toggle public boolean until()
626    {
627  33 return enabled == isPushButtonEnabled(pushButtonTitle);
628    }
629  25 }.wait(pushButtonTitle + " button is not " + (enabled ? "enabled" : "disabled") + "!");
630    }
631   
632    /**
633    * Waits for the specified toggle button be enabled or disabled, based on the given state parameter.
634    *
635    * @param toggleButtonTitle identifies the button to wait for
636    * @param enabled {@code true} to wait for the specified toggle button to become enabled, {@code false} to wait for
637    * it to become disabled
638    */
 
639  3 toggle public void waitForToggleButton(final String toggleButtonTitle, final boolean enabled)
640    {
641  3 new Wait()
642    {
 
643  3 toggle public boolean until()
644    {
645  3 return enabled == isToggleButtonEnabled(toggleButtonTitle);
646    }
647  3 }.wait(toggleButtonTitle + " button is not " + (enabled ? "enabled" : "disabled") + "!");
648    }
649   
650    /**
651    * Waits until the specified toggle button has the given state. This method is useful to wait until a toggle button
652    * from the tool bar is updated.
653    *
654    * @param toggleButtonTitle the tool tip of a toggle button
655    * @param down {@code true} to wait until the specified toggle button is down, {@code false} to wait until it is up
656    */
 
657  51 toggle public void waitForToggleButtonState(final String toggleButtonTitle, final boolean down)
658    {
659  51 new Wait()
660    {
 
661  60 toggle public boolean until()
662    {
663  60 return down == isToggleButtonDown(toggleButtonTitle);
664    }
665    }.wait("The state of the '" + toggleButtonTitle + "' toggle button didn't change!");
666    }
667   
668    /**
669    * @param toggleButtonTitle the tool tip of a toggle button
670    * @return {@code true} if the specified toggle button is down, {@code false} otherwise
671    */
 
672  61 toggle public boolean isToggleButtonDown(String toggleButtonTitle)
673    {
674  61 return getDriver().hasElementWithoutWaiting(
675    By.xpath("//div[@title='" + toggleButtonTitle + "' and @class='gwt-ToggleButton gwt-ToggleButton-down']"));
676    }
677   
678    /**
679    * Checks if a menu item is enabled or disabled. Menu items have {@code gwt-MenuItem} CSS class. Disabled menu items
680    * have and additional {@code gwt-MenuItem-disabled} CSS class.
681    *
682    * @param menuLabel a {@link String} representing the label of a menu item
683    * @return {@code true} if the menu with the specified label is enabled, {@code false} otherwise
684    */
 
685  183 toggle public boolean isMenuEnabled(String menuLabel)
686    {
687  183 return getDriver().hasElementWithoutWaiting(
688    By.xpath("//td[contains(@class, 'gwt-MenuItem') and not(contains(@class, 'gwt-MenuItem-disabled'))]"
689    + "/div[@class = 'gwt-MenuItemLabel' and . = '" + menuLabel + "']"));
690    }
691   
692    /**
693    * Asserts that the rich text area has the expected inner HTML.
694    *
695    * @param expectedHTML the expected inner HTML of the rich text area
696    */
 
697  78 toggle public void assertContent(String expectedHTML)
698    {
699  78 assertEquals(expectedHTML, getRichTextArea().getContent());
700    }
701   
702    /**
703    * Places the caret in the specified container, at the specified offset.
704    *
705    * @param containerJSLocator the JavaScript code used to access the container node
706    * @param offset the offset within the container node
707    */
 
708  104 toggle public void moveCaret(String containerJSLocator, int offset)
709    {
710  104 StringBuilder script = new StringBuilder();
711  104 script.append("var range = document.createRange();\n");
712  104 script.append("range.setStart(");
713  104 script.append(containerJSLocator);
714  104 script.append(", ");
715  104 script.append(offset);
716  104 script.append(");\n");
717  104 script.append("range.collapse(true);\n");
718  104 script.append("window.getSelection().removeAllRanges();\n");
719  104 script.append("window.getSelection().addRange(range);");
720  104 getRichTextArea().executeScript(script.toString());
721  104 triggerToolbarUpdate();
722    }
723   
724    /**
725    * Selects the content between the specified points in the DOM tree.
726    *
727    * @param startContainerJSLocator the node containing the start of the selection
728    * @param startOffset the offset within the start container where the selection starts
729    * @param endContainerJSLocator the node containing the end of the selection
730    * @param endOffset the offset within the end container where the selection ends
731    */
 
732  46 toggle public void select(String startContainerJSLocator, int startOffset, String endContainerJSLocator, int endOffset)
733    {
734  46 StringBuilder script = new StringBuilder();
735  46 script.append("var range = document.createRange();\n");
736  46 script.append("range.setStart(");
737  46 script.append(startContainerJSLocator);
738  46 script.append(", ");
739  46 script.append(startOffset);
740  46 script.append(");\n");
741  46 script.append("range.setEnd(");
742  46 script.append(endContainerJSLocator);
743  46 script.append(", ");
744  46 script.append(endOffset);
745  46 script.append(");\n");
746  46 script.append("window.getSelection().removeAllRanges();\n");
747  46 script.append("window.getSelection().addRange(range);\n");
748  46 getRichTextArea().executeScript(script.toString());
749  46 triggerToolbarUpdate();
750    }
751   
752    /**
753    * Selects the specified DOM node.
754    *
755    * @param jsLocator a JavaScript locator for the node to be selected
756    */
 
757  22 toggle public void selectNode(String jsLocator)
758    {
759  22 StringBuilder script = new StringBuilder();
760  22 script.append("var range = document.createRange();\n");
761  22 script.append("range.selectNode(");
762  22 script.append(jsLocator);
763  22 script.append(");\n");
764  22 script.append("window.getSelection().removeAllRanges();\n");
765  22 script.append("window.getSelection().addRange(range);\n");
766  22 getRichTextArea().executeScript(script.toString());
767  22 triggerToolbarUpdate();
768    }
769   
770    /**
771    * Selects the contents of the specified DOM node.
772    *
773    * @param jsLocator a JavaScript locator for the node whose content are to be selected
774    */
 
775  38 toggle public void selectNodeContents(String jsLocator)
776    {
777  38 StringBuilder script = new StringBuilder();
778  38 script.append("var range = document.createRange();\n");
779  38 script.append("range.selectNodeContents(");
780  38 script.append(jsLocator);
781  38 script.append(");\n");
782  38 script.append("window.getSelection().removeAllRanges();\n");
783  38 script.append("window.getSelection().addRange(range);\n");
784  38 getRichTextArea().executeScript(script.toString());
785  38 triggerToolbarUpdate();
786    }
787   
788    /**
789    * Triggers the wysiwyg toolbar update by typing a key. To be used after programatically setting the selection (with
790    * {@link AbstractWysiwygTestCase#select(String, int, String, int)} or
791    * {@link AbstractWysiwygTestCase#moveCaret(String, int)}): it will not influence the selection but it will cause
792    * the toolbar to update according to the new selection.
793    */
 
794  245 toggle protected void triggerToolbarUpdate()
795    {
796  245 getRichTextArea().sendKeys(Keys.SHIFT);
797    }
798   
799    /**
800    * Wait for a WYSIWYG dialog to close. The test checks for a {@code div} element with {@code xDialogBox} value of
801    * {@code class} to not be present.
802    */
 
803  114 toggle public void waitForDialogToClose()
804    {
805  114 getDriver().waitUntilElementDisappears(By.className("xDialogBox"));
806    }
807   
808    /**
809    * Waits until a WYSIWYG modal dialog is fully loaded. While loading, the body of the dialog has the {@code loading}
810    * CSS class besides the {@code xDialogBody} one.
811    */
 
812  172 toggle public void waitForDialogToLoad()
813    {
814  172 getDriver().waitUntilElementIsVisible(
815    By.xpath("//div[contains(@class, 'xDialogBody') and not(contains(@class, 'loading'))]"));
816    }
817   
818    /**
819    * Close the dialog by clicking the close icon in the top right.
820    */
 
821  42 toggle public void closeDialog()
822    {
823  42 getSelenium().click("//img[contains(@class, \"gwt-Image\") and contains(@class, \"xDialogCloseIcon\")]");
824  42 waitForDialogToClose();
825    }
826   
827    /**
828    * Waits until the WYSIWYG editor detects the bold style on the current selection. The bold style is detected when
829    * the associated tool bar button is updated. The update is delayed to increase the typing speed.
830    */
 
831  6 toggle public void waitForBoldDetected(boolean down)
832    {
833  6 waitForToggleButtonState("Bold (Ctrl+B)", down);
834    }
835   
836    /**
837    * Waits until the WYSIWYG editor detects the underline style on the current selection. The underline style is
838    * detected when the associated tool bar button is updated. The update is delayed to increase the typing speed.
839    */
 
840  4 toggle public void waitForUnderlineDetected(boolean down)
841    {
842  4 waitForToggleButtonState("Underline (Ctrl+U)", down);
843    }
844   
845    /**
846    * Asserts that the specified error message exists, and the element passed through its XPath locator is marked as in
847    * error.
848    *
849    * @param errorMessage the expected error message
850    * @param fieldXPathLocator the XPath locator of the field which is in error
851    */
 
852  34 toggle public void assertFieldErrorIsPresent(String errorMessage, String fieldXPathLocator)
853    {
854    // test that the error field is present through this method because the isVisible stops at first encouter of the
855    // matching element and fails if it's not visible. However, multiple matching elements might exist and we're
856    // interested in at least one of them visible
857  34 assertTrue(getSelenium().getXpathCount(
858    "//*[contains(@class, \"xErrorMsg\") and . = '" + errorMessage + "' and @style='']").intValue() > 0);
859  34 assertTrue(getDriver().hasElementWithoutWaiting(
860    By.xpath(fieldXPathLocator + "[contains(@class, 'xErrorField')]")));
861    }
862   
863    /**
864    * Asserts that the specified error message does not exist and that the field passed through the XPath locator is
865    * not in error. Note that this function checks that the passed field is present, but without an error marker.
866    *
867    * @param errorMessage the error message
868    * @param fieldXPathLocator the XPath locator of the field to check that it's not in error
869    */
 
870  2 toggle public void assertFieldErrorIsNotPresent(String errorMessage, String fieldXPathLocator)
871    {
872  2 assertFalse(getSelenium().isVisible("//*[contains(@class, \"xErrorMsg\") and . = \"" + errorMessage + "\"]"));
873  2 assertTrue(getDriver().hasElementWithoutWaiting(
874    By.xpath(fieldXPathLocator + "[not(contains(@class, 'xFieldError'))]")));
875    }
876   
877    /**
878    * Asserts that no error message or field marked as in error is present.
879    */
 
880  20 toggle public void assertFieldErrorIsNotPresent()
881    {
882    // no error is visible
883  20 assertFalse(getSelenium().isVisible("//*[contains(@class, \"xErrorMsg\")]"));
884    // no field with error markers should be present
885  20 assertFalse(getDriver().hasElementWithoutWaiting(By.className("xFieldError")));
886    }
887   
888    /**
889    * Focuses the rich text area.
890    * <p>
891    * NOTE: The initial range CAN differ when the browser window is focused from when it isn't! Make sure you place the
892    * caret where you want it to be at the beginning of you test and after switching back to WYSIWYG editor.
893    */
 
894  8 toggle protected void focusRichTextArea()
895    {
896  8 getRichTextArea().sendKeys("");
897    }
898   
899    /**
900    * Simulates a blur event on the rich text area. We don't use the blur method because it fails to notify our
901    * listeners when the browser window is not focused, preventing us from running the tests in background.
902    */
 
903  3 toggle protected void blurRichTextArea()
904    {
905  3 getDriver().findElement(By.id("xwikidoctitleinput")).sendKeys("");
906    }
907   
908    /**
909    * Inserts a table in place of the current selection or at the caret position, using the default table settings.
910    */
 
911  4 toggle protected void insertTable()
912    {
913  4 openInsertTableDialog();
914  4 getSelenium().click("//button[text()=\"Insert Table\"]");
915    }
916   
917    /**
918    * Opens the insert table dialog.
919    */
 
920  9 toggle protected void openInsertTableDialog()
921    {
922  9 clickMenu("Table");
923  9 clickMenu("Insert Table...");
924  9 waitForDialogToLoad();
925    }
926   
927    /**
928    * @return the text from the source text area
929    */
 
930  224 toggle protected String getSourceText()
931    {
932  224 return getSourceTextArea().getAttribute("value");
933    }
934   
935    /**
936    * Sets the value of the source text area.
937    *
938    * @param sourceText the new value for the source text area
939    */
 
940  162 toggle protected void setSourceText(String sourceText)
941    {
942  162 ((JavascriptExecutor) getDriver()).executeScript("arguments[0].value = arguments[1]", getSourceTextArea(),
943    sourceText);
944    }
945   
946    /**
947    * Asserts that the source text area has the given value.
948    *
949    * @param expectedSourceText the expected value of the source text area
950    */
 
951  220 toggle protected void assertSourceText(String expectedSourceText)
952    {
953  220 assertEquals(expectedSourceText, getSourceText());
954    }
955   
956    /**
957    * Waits for the WYSIWYG editor to load.
958    */
 
959  361 toggle protected void waitForEditorToLoad()
960    {
961  361 final String sourceTabSelected = "//div[@class = 'gwt-TabBarItem gwt-TabBarItem-selected']/div[. = 'Source']";
962  361 final String richTextAreaLoader = "//div[@class = 'xRichTextEditor']//div[@class = 'loading']";
963  361 getDriver().waitUntilCondition(new ExpectedCondition<Boolean>()
964    {
 
965  648 toggle @Override
966    public Boolean apply(WebDriver input)
967    {
968    // Either the source tab is present and selected and the plain text area can be edited or the rich text
969    // area is not loading (with or without tabs).
970  648 return (getDriver().hasElementWithoutWaiting(By.xpath(sourceTabSelected)) && getSourceTextArea()
971    .isEnabled())
972    || (getDriver().hasElementWithoutWaiting(By.className("xRichTextEditor")) && !getDriver()
973    .hasElementWithoutWaiting(By.xpath(richTextAreaLoader)));
974    }
975    });
976    }
977   
978    /**
979    * Switches to full screen editing mode.
980    */
 
981  3 toggle protected void clickEditInFullScreen()
982    {
983  3 getSelenium().click("//img[@title = 'Maximize']");
984  3 waitForElement("//div[@class = 'fullScreenWrapper']");
985    }
986   
987    /**
988    * Exists full screen editing mode.
989    */
 
990  2 toggle protected void clickExitFullScreen()
991    {
992  2 getDriver().findElementByXPath("//input[@value = 'Exit Full Screen']").click();
993  2 getDriver().waitUntilElementDisappears(By.className("fullScreenWrapper"));
994    }
995   
996    /**
997    * Creates a new space with the specified name.
998    *
999    * @param spaceName the name of the new space to create
1000    */
 
1001  0 toggle public void createSpace(String spaceName)
1002    {
1003  0 getSelenium().runScript("window.scrollTo(0, 0)");
1004  0 getSelenium().click("//div[@id='tmCreate']//button[contains(@class, 'dropdown-toggle')]");
1005  0 clickLinkWithLocator("tmCreateSpace");
1006  0 getSelenium().type("name", spaceName);
1007  0 clickLinkWithLocator("//input[@value='Create']");
1008  0 clickEditSaveAndView();
1009    }
1010   
1011    /**
1012    * Begin creating a page in the specified space, with the specified name.
1013    * <p>
1014    * NOTE: We don't use the save action URL because it requires special characters in space and page name to be
1015    * escaped. We use instead the create action URL.
1016    *
1017    * @param spaceName the name of the space where to create the page
1018    * @param pageName the name of the page to create
1019    * @see #createPage(String, String, String)
1020    */
 
1021  2 toggle public void startCreatePage(String spaceName, String pageName)
1022    {
1023  2 String queryString = "templateprovider=";
1024  2 try {
1025  2 queryString += "&space=" + URLEncoder.encode(spaceName, "UTF-8");
1026  2 queryString += "&page=" + URLEncoder.encode(pageName, "UTF-8");
1027    } catch (UnsupportedEncodingException e) {
1028    // Shouldn't happen.
1029    }
1030  2 getSelenium().open(this.getUrl("Main", "WebHome", "create", queryString));
1031    }
1032   
1033    /**
1034    * Creates a page in the specified space, with the specified name.
1035    * <p>
1036    * NOTE: We overwrite the method from the base class because it creates the new page using the save action URL which
1037    * requires special characters in space and page name to be escaped. We use instead the create action URL.
1038    *
1039    * @param spaceName the name of the space where to create the page
1040    * @param pageName the name of the page to create
1041    * @param content the content of the new page
1042    * @see AbstractXWikiTestCase#createPage(String, String, String)
1043    */
 
1044  1 toggle public void createPage(String spaceName, String pageName, String content)
1045    {
1046  1 startCreatePage(spaceName, pageName);
1047   
1048  1 String location = getSelenium().getLocation();
1049  1 if (location.endsWith("?xpage=docalreadyexists")) {
1050  0 open(location.substring(0, location.length() - 23));
1051  0 clickEditPageInWysiwyg();
1052    }
1053  1 waitForEditorToLoad();
1054  1 switchToSource();
1055  1 setSourceText(content);
1056  1 clickEditSaveAndView();
1057    }
1058   
1059    /**
1060    * Selects the rich text area frame. Selectors are relative to the edited document after calling this method.
1061    */
 
1062  32 toggle public void selectRichTextAreaFrame()
1063    {
1064  32 WebDriver driver = getDriver();
1065  32 driver.switchTo().frame(driver.findElement(By.className("gwt-RichTextArea"))).switchTo().activeElement();
1066    }
1067   
1068    /**
1069    * Selects the top frame.
1070    */
 
1071  32 toggle public void selectTopFrame()
1072    {
1073  32 getSelenium().selectFrame("relative=top");
1074    }
1075    }