View Javadoc
1   /**
2    * Copyright 2005-2015 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.demo.uif.library.collections;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.apache.commons.lang.math.RandomUtils;
20  import org.junit.After;
21  import org.junit.Before;
22  import org.junit.Test;
23  import org.kuali.rice.krad.demo.uif.library.LibraryBase;
24  import org.kuali.rice.krad.uif.UifConstants;
25  import org.kuali.rice.testtools.selenium.WebDriverUtils;
26  import org.openqa.selenium.By;
27  import org.openqa.selenium.WebElement;
28  
29  import java.util.List;
30  import java.util.concurrent.TimeUnit;
31  
32  /**
33   * @author Kuali Rice Team (rice.collab@kuali.org)
34   */
35  public class LibraryCollectionFeaturesEditLineAft extends LibraryBase {
36  
37      /**
38       * /kr-krad/kradsampleapp?viewId=Demo-CollectionEditLineView
39       */
40      public static final String BOOKMARK_URL = "/kr-krad/kradsampleapp?viewId=Demo-CollectionEditLineView";
41      public static final int FIVE_SECOND_WAIT_TIME = 5;
42      public static final int THREE_SECOND_WAIT_TIME = 3000;
43      public static final String LIBRARY_MENU_CATEGORY_NAME = "Collection Features";
44      public static final String DEMO_ITEM_NAME = "Edit Line";
45  
46      public enum SubCollectionType {
47          ROW_DETAILS, SUB_COLLECTION, CUSTOM_SUB_COLLECTION, NO_SUB_COLLECTION
48      }
49  
50      public static final String UIF_DIALOG_MODAL_SELECTOR = "#Uif-Dialogs .modal[aria-hidden='false']";
51      public static final String ADD_BUTTON_CSS_SELECTOR =
52              " .uif-collectionAddItem  .uif-collection-column-action button";
53      public static final String ADD_FIELDS_CSS_SELECTOR = " .uif-collectionAddItem input:not([type='hidden'])";
54      public static final String EDIT_BUTTON_CSS_SELECTOR =
55              " tr:not(.uif-collectionAddItem) .uif-collection-column-action button[data-onclick^='"
56                      + UifConstants.JsFunctions.SHOW_EDIT_LINE_DIALOG + "']";
57      public static final String ADD_FIELDS_IN_DIALOG_CSS_SELECTOR =
58              UIF_DIALOG_MODAL_SELECTOR + " .modal-body .uif-inputField input:not([type='hidden'])";
59      public static final String ADD_LINE_ACTIONS_IN_DIALOG_CSS_SELECTOR =
60              UIF_DIALOG_MODAL_SELECTOR + " .modal-footer button[id$='_add']";
61      public static final String EDIT_DIALOG_BUTTONS_CSS_SELECTOR =
62              UIF_DIALOG_MODAL_SELECTOR + " .modal-footer button";
63      public static final String EDIT_DIALOG_CLOSE_BUTTON_CSS_SELECTOR =
64              UIF_DIALOG_MODAL_SELECTOR + " .modal-header button.close";
65      public static final String EDIT_DIALOG_INPUT_FIELDS_CSS_SELECTOR =
66              UIF_DIALOG_MODAL_SELECTOR + " .modal-body input:not([type='hidden'])";
67      public static final String EDIT_DIALOG_LOOKUP_CSS_SELECTOR =
68              UIF_DIALOG_MODAL_SELECTOR + " .modal-body .input-group-btn > button";
69      public static final String EDIT_DIALOG_CSS_SELECTOR =
70              UIF_DIALOG_MODAL_SELECTOR + " .modal-body";
71      public static final String ROW_DETAILS_LINK_CSS_SELECTOR =
72              " table tbody tr:not(.uif-collectionAddItem) a.uif-detailsAction";
73      public static final String TABLE_ROWS_CSS_SELECTOR = " table tbody tr:not(.uif-collectionAddItem)";
74      public static final String SUB_COLLECTION_CSS_SELECTOR =
75              " table tbody > tr:not(.uif-collectionAddItem) > td > div.uif-subCollection";
76  
77      @Before
78      @Override
79      public void testSetUp() {
80          super.testSetUp();
81          getDriver().manage().timeouts().implicitlyWait(1, TimeUnit.SECONDS);
82      }
83  
84      @After
85      @Override
86      public void tearDown() {
87          getDriver().manage().timeouts().implicitlyWait(WebDriverUtils.configuredImplicityWait(), TimeUnit.SECONDS);
88          super.tearDown();
89      }
90  
91      /**
92       * URL to the edit line demo page
93       */
94      @Override
95      protected String getBookmarkUrl() {
96          return BOOKMARK_URL;
97      }
98  
99      /**
100      * Navigation path to the edit line demo
101      */
102     @Override
103     protected void navigate() throws Exception {
104         navigateToLibraryDemo(LibraryCollectionFeaturesEditLineAft.LIBRARY_MENU_CATEGORY_NAME,
105                 LibraryCollectionFeaturesEditLineAft.DEMO_ITEM_NAME);
106     }
107 
108     /**
109      * Method to check default edit line edit.
110      */
111     protected void testDefaultEditLine() throws Exception {
112         testAllFeatures("Demo-CollectionEditLine-Example1", 1, 1, false, false, false,
113                 SubCollectionType.NO_SUB_COLLECTION, -1);
114     }
115 
116     /**
117      * Method for custom dialog edit.
118      */
119     protected void testCustomDialogEditLine1() throws Exception {
120         waitAndSelectByName("exampleShown", "Custom Dialog Edit Line 1");
121         testAllFeatures("Demo-CollectionEditLine-Example2", 1, 1, false, false, false,
122                 SubCollectionType.NO_SUB_COLLECTION, -1);
123     }
124 
125     /**
126      * Method for custom dialog edit.
127      */
128     protected void testCustomDialogEditLine2() throws Exception {
129         waitAndSelectByName("exampleShown", "Custom Dialog Edit Line 2");
130         testAllFeatures("Demo-CollectionEditLine-Example3", 2, 1, false, false, false,
131                 SubCollectionType.NO_SUB_COLLECTION, -1);
132     }
133 
134     /**
135      * Method too check custom edit line action edit.
136      */
137     protected void testCustomDialogLineActionEditLine() throws Exception {
138         waitAndSelectByName("exampleShown", "Custom Dialog Line Action Edit Line");
139         testAllFeatures("Demo-CollectionEditLine-Example4", 2, 2, false, false, false,
140                 SubCollectionType.NO_SUB_COLLECTION, -1);
141     }
142 
143     /**
144      * Method to check custom save action edit.
145      */
146     protected void testCustomDialogSaveActionEditLine() throws Exception {
147         waitAndSelectByName("exampleShown", "Custom Dialog Save Action Edit Line");
148         testAllFeatures("Demo-CollectionEditLine-Example5", 2, 2, true, false, false,
149                 SubCollectionType.NO_SUB_COLLECTION, -1);
150     }
151 
152     /**
153      * Method to check readOnly fields edit.
154      */
155     protected void testReadOnlyEditLine() throws Exception {
156         waitAndSelectByName("exampleShown", "ReadOnly Edit Line");
157         testAllFeatures("Demo-CollectionEditLine-Example6", 2, 1, false, true, false,
158                 SubCollectionType.NO_SUB_COLLECTION, -1);
159     }
160 
161     /**
162      * Method to check the readOnly collection edit.
163      */
164     protected void testEditAuthorizationEditLineCollectionReadOnly() throws Exception {
165         waitAndSelectByName("exampleShown", "Edit Authorization Edit Line (collection readOnly)");
166         verifyCollectionRowFieldsAreNonEditable("Demo-CollectionEditLine-Example7");
167     }
168 
169     /**
170      * Method to checck the edit line authorization edit.
171      */
172     protected void testEditAuthorizationEditLineLineAuthorization() throws Exception {
173         waitAndSelectByName("exampleShown", "Edit Authorization Edit Line (line authorization)");
174         testAllFeatures("Demo-CollectionEditLine-Example8", 1, 1, false, false, true,
175                 SubCollectionType.NO_SUB_COLLECTION, -1);
176     }
177 
178     /**
179      * Method to check edit line's lookup from edit dialog.
180      */
181     protected void testLookup() throws Exception {
182         waitAndSelectByName("exampleShown", "Lookup Collection Edit Line");
183         testAllFeatures("Demo-CollectionEditLine-Example9", 2, 2, false, false, false,
184                 SubCollectionType.NO_SUB_COLLECTION, 1);
185     }
186 
187     /**
188      * Method to test the row details group edit.
189      */
190     protected void testRowDetailsGroup() throws Exception {
191         waitAndSelectByName("exampleShown", "Row Details Edit Line");
192         testAllFeatures("Demo-CollectionEditLine-Example10", 1, 1, false, false, false, SubCollectionType.ROW_DETAILS,
193                 -1);
194     }
195 
196     /**
197      * Method to test the row details group edit.
198      */
199     protected void testSubCollection() throws Exception {
200         waitAndSelectByName("exampleShown", "SubCollection Edit Line");
201         testAllFeatures("Demo-CollectionEditLine-Example11", 1, 1, false, false, false,
202                 SubCollectionType.SUB_COLLECTION, -1);
203     }
204 
205     /**
206      * Method to test various examples of the edit line
207      */
208     protected void testCollectionFeaturesEditLine() throws Exception {
209         jGrowl("Calling testReadOnlyEditLine");
210         testReadOnlyEditLine();
211         jGrowl("Calling testEditAuthorizationEditLineCollectionReadOnly");
212         testEditAuthorizationEditLineCollectionReadOnly();
213         jGrowl("Calling testEditAuthorizationEditLineLineAuthorization");
214         testEditAuthorizationEditLineLineAuthorization();
215         jGrowl("Calling testLookup");
216         testLookup();
217         //        testRowDetailsGroup(); // tested separately
218         //        testSubCollection(); // tested separately
219     }
220 
221     /**
222      * Method to test all the features for the edit line in modal.
223      *
224      * @param exampleId the id of the example the collection is in
225      * @param lineFieldToEditIndex the index of the line field to edit
226      * @param dialogFieldToEditIndex the index of the dialog field to edit
227      * @param custom where it is a custom dialog
228      * @param readOnly whether we are doing a readOnly
229      * @param auth whether it is an authorization check
230      * @param subCollectionType the type of sub-collection
231      * @param lookupFieldToEditIndex the index of the lookup field
232      */
233     protected void testAllFeatures(String exampleId, int lineFieldToEditIndex, int dialogFieldToEditIndex,
234             boolean custom, boolean readOnly, boolean auth, SubCollectionType subCollectionType,
235             int lookupFieldToEditIndex) throws Exception {
236         waitFor(By.cssSelector("jQuery('#" + exampleId + "')"));
237         verifyCollectionRowFieldsAreNonEditable(exampleId);
238         verifyAddLineAndDeleteLineStillWork(exampleId, readOnly, subCollectionType);
239         verifyEditInDialogWorks(exampleId, lineFieldToEditIndex, dialogFieldToEditIndex, custom, auth,
240                 subCollectionType, readOnly, lookupFieldToEditIndex);
241     }
242 
243     /**
244      * Helper method that randomly picks a row to edit and verifies that editing the row fields work as expected.
245      *
246      * @param exampleId the id of the example the collection is in
247      * @param lineFieldToEditIndex the index of the line field to edit
248      * @param dialogFieldToEditIndex the index of the dialog field to edit
249      * @param custom whether it is a custom dialog
250      * @param auth whether it is an authorization check
251      * @param subCollectionType the type of sub-collection
252      * @param readOnly whether we are doing a readOnly
253      * @param lookupFieldToEditIndex the index of the lookup field
254      */
255     protected void verifyEditInDialogWorks(String exampleId, int lineFieldToEditIndex, int dialogFieldToEditIndex,
256             boolean custom, boolean auth, SubCollectionType subCollectionType, boolean readOnly,
257             int lookupFieldToEditIndex) throws Exception {
258         int rowElementsSize = 0;
259 
260         // sub-collection rows don't have edit buttons
261         if (subCollectionType != SubCollectionType.SUB_COLLECTION
262                 && subCollectionType != SubCollectionType.CUSTOM_SUB_COLLECTION) {
263             List<WebElement> rowElements = findVisibleElements(By.cssSelector(
264                     "#" + exampleId + TABLE_ROWS_CSS_SELECTOR));
265             rowElementsSize = rowElements.size();
266         } else {
267             List<WebElement> rowElements = findVisibleElements(By.cssSelector(
268                     "#" + exampleId + " table:nth-child(1) tbody > tr:not(.uif-collectionAddItem)"));
269             List<WebElement> subCollectionElements = findVisibleElements(By.cssSelector(
270                     "#" + exampleId + SUB_COLLECTION_CSS_SELECTOR));
271             rowElementsSize = rowElements.size() - subCollectionElements.size();
272         }
273         verifyCollectionRowsHaveEditButtons(exampleId, rowElementsSize);
274 
275         if (auth) {
276             return;
277         }
278 
279         // pick a random line to edit
280         int index = RandomUtils.nextInt(rowElementsSize) + 1; // for 1-based index
281 
282         // open the row detils cause we want to see it all
283         if (subCollectionType == SubCollectionType.ROW_DETAILS) {
284             // open up the row details
285             WebElement rowDetailsAchorElement = findElements(By.cssSelector(
286                     "#" + exampleId + ROW_DETAILS_LINK_CSS_SELECTOR)).get(index - 1);
287             rowDetailsAchorElement.click();
288         }
289 
290         if (subCollectionType == SubCollectionType.NO_SUB_COLLECTION) {
291             // verify dialog fields match line fields on save
292             verifyDialogEdit(exampleId, lineFieldToEditIndex, dialogFieldToEditIndex, true, false, custom, index,
293                     readOnly, lookupFieldToEditIndex);
294 
295             // verify dialog fields match line fields on don't save
296             verifyDialogEdit(exampleId, lineFieldToEditIndex, dialogFieldToEditIndex, false, false, custom, index,
297                     readOnly, lookupFieldToEditIndex);
298 
299             // verify dialog fields match line fields on close
300             verifyDialogEdit(exampleId, lineFieldToEditIndex, dialogFieldToEditIndex, false, true, custom, index,
301                     readOnly, lookupFieldToEditIndex);
302         } else {
303             // verify dialog fields and dialog sub-collection fields match those of line fields and line
304             // sub-collection fields on save
305             String dialogCssSelector = EDIT_DIALOG_CSS_SELECTOR;
306             String tableRowsCssSelector = "#" + exampleId + TABLE_ROWS_CSS_SELECTOR;
307 
308             // these have to be processed separately since there are essential differences in each
309             if (subCollectionType == SubCollectionType.ROW_DETAILS) {
310                 verifyRowDetails(exampleId, dialogCssSelector, tableRowsCssSelector, index);
311             } else if (subCollectionType == SubCollectionType.SUB_COLLECTION) {
312                 verifyRowSubCollections(exampleId, dialogCssSelector, tableRowsCssSelector, index);
313             }
314         }
315     }
316 
317     /**
318      * Helper method to process and verify sub-collections in row details group.
319      *
320      * @param exampleId the id of the example the collection is in
321      * @param dialogCssSelector the css selector of the selected row's dialog
322      * @param tableRowsCssSelector the css selector of the collection's rows
323      * @param index the index of the selected row
324      */
325     protected void verifyRowDetails(String exampleId, String dialogCssSelector, String tableRowsCssSelector, int index)
326             throws Exception {
327         String subCollectionFieldCssSelector =
328                 "#" + exampleId + " table tbody tr.detailsRow td.uif-rowDetails > div > div.uif-inputField > span";
329         String dialogSubCollectionFieldCssSelector =
330                 dialogCssSelector + " > div > div.uif-inputField > input:not([type='hidden'])";
331         String subCollectionRowsCssSelector =
332                 tableRowsCssSelector + " td.uif-rowDetails section " + TABLE_ROWS_CSS_SELECTOR;
333         String dialogSubCollectionRowsCssSelector = dialogCssSelector + " section " + TABLE_ROWS_CSS_SELECTOR;
334         String dialogSubCollectionAddLineButtonCssSelector = dialogCssSelector + " section " + ADD_BUTTON_CSS_SELECTOR;
335         String dialogSubCollectionAddLineFieldsCssSelector = dialogCssSelector + " section " + ADD_FIELDS_CSS_SELECTOR;
336         String dialogSubCollectionAddedLineFieldsCssSelector =
337                 dialogCssSelector + " section table tr:not(.uif-collectionAddItem)";
338         String dialogSubCollectionDeleteLineButtonCssSelector = dialogCssSelector
339                 + " section table tr:not(.uif-collectionAddItem) .uif-collection-column-action button";
340         int dialogSubCollectionFieldIndex = 2;
341 
342         verifySubCollections(exampleId, index, subCollectionFieldCssSelector, dialogSubCollectionFieldCssSelector,
343                 subCollectionRowsCssSelector, dialogSubCollectionRowsCssSelector,
344                 dialogSubCollectionAddLineButtonCssSelector, dialogSubCollectionAddLineFieldsCssSelector,
345                 dialogSubCollectionAddedLineFieldsCssSelector, dialogSubCollectionDeleteLineButtonCssSelector,
346                 dialogCssSelector, dialogSubCollectionFieldIndex);
347     }
348 
349     /**
350      * Helper method to process and verify sub-collections in row's sub-collection.
351      *
352      * @param exampleId the id of the example the collection is in
353      * @param dialogCssSelector the css selector of the selected row's dialog
354      * @param tableRowsCssSelector the css selector of the collection's rows
355      * @param index the index of the selected row
356      */
357     protected void verifyRowSubCollections(String exampleId, String dialogCssSelector, String tableRowsCssSelector,
358             int index) throws Exception {
359         int dialogSubCollectionFieldIndex = 0;
360         String subCollectionRowsCssSelector =
361                 "#" + exampleId + " table > tbody > tr:not(.uif-collectionAddItem):nth-child(" + Integer.toString(
362                         index * 2) + ") > td > div.uif-subCollection section " + TABLE_ROWS_CSS_SELECTOR;
363         String dialogSubCollectionRowsCssSelector =
364                 dialogCssSelector + " > div > div.uif-subCollection section " + TABLE_ROWS_CSS_SELECTOR;
365         String dialogSubCollectionAddLineButtonCssSelector =
366                 dialogCssSelector + " > div > div.uif-subCollection section " + ADD_BUTTON_CSS_SELECTOR;
367         String dialogSubCollectionAddLineFieldsCssSelector =
368                 dialogCssSelector + " > div > div.uif-subCollection section " + ADD_FIELDS_CSS_SELECTOR;
369         String dialogSubCollectionAddedLineFieldsCssSelector =
370                 dialogCssSelector + " > div > div.uif-subCollection section table tr:not(.uif-collectionAddItem)";
371         String dialogSubCollectionDeleteLineButtonCssSelector = dialogCssSelector
372                 + " > div > div.uif-subCollection section table tr:not(.uif-collectionAddItem) .uif-collection-column-action button";
373 
374         verifySubCollections(exampleId, index, null, null, subCollectionRowsCssSelector,
375                 dialogSubCollectionRowsCssSelector, dialogSubCollectionAddLineButtonCssSelector,
376                 dialogSubCollectionAddLineFieldsCssSelector, dialogSubCollectionAddedLineFieldsCssSelector,
377                 dialogSubCollectionDeleteLineButtonCssSelector, dialogCssSelector, dialogSubCollectionFieldIndex);
378     }
379 
380     /**
381      * Helper method to verify the action behaviors of all sub-collections wherein the location of the sub-collections
382      * might be different (row details or row sub-collection) and are identified by the provided css selectors.
383      *
384      * @param exampleId the id of the example the collection is in
385      * @param rowIndex the index of the row to edit
386      * @param subCollectionFieldCssSelector the css selector to the row field with the sub-collection
387      * @param dialogSubCollectionFieldCssSelector the css selector to the dialog field with the sub-collection
388      * @param subCollectionRowsCssSelector the collection's sub-collection's rows css selector
389      * @param dialogSubCollectionRowsCssSelector the dialog's sub-collection's rows css selector
390      * @param dialogSubCollectionAddLineButtonCssSelector the css selector to the sub-collections add line button
391      * @param dialogSubCollectionAddLineFieldsCssSelector the css selector to the sub-collections add line fields
392      * @param dialogSubCollectionAddedLineFieldsCssSelector the css selector to the sub-collections added line fields
393      * @param dialogSubCollectionDeleteLineButtonCssSelector the css selector to the sub-collections delete line button
394      * @param dialogCssSelector the css selector of the selected row's dialog
395      * @param dialogSubCollectionFieldIndex the index of the field in the dialog with the sub-collection
396      */
397     protected void verifySubCollections(String exampleId, int rowIndex, String subCollectionFieldCssSelector,
398             String dialogSubCollectionFieldCssSelector, String subCollectionRowsCssSelector,
399             String dialogSubCollectionRowsCssSelector, String dialogSubCollectionAddLineButtonCssSelector,
400             String dialogSubCollectionAddLineFieldsCssSelector, String dialogSubCollectionAddedLineFieldsCssSelector,
401             String dialogSubCollectionDeleteLineButtonCssSelector, String dialogCssSelector,
402             int dialogSubCollectionFieldIndex) throws Exception {
403 
404         String subCollectionFieldValue = null;
405         if (subCollectionFieldCssSelector != null) {
406             // get the field the sub-collection belongs to
407             WebElement subCollectionRowField = findElement(By.cssSelector(subCollectionFieldCssSelector));
408             subCollectionFieldValue = subCollectionRowField.getText();
409         }
410 
411         // open the edit line dialog
412         openEditLineDialog(exampleId, rowIndex);
413 
414         WebElement dialogSubCollectionField = null;
415         String dialogSubCollectionFieldValue = null;
416         if (dialogSubCollectionFieldCssSelector != null) {
417             // get the field in the dialog that the sub-collection belongs to
418             List<WebElement> dialogFields = findElements(By.cssSelector(dialogSubCollectionFieldCssSelector));
419             dialogSubCollectionField = dialogFields.get(dialogSubCollectionFieldIndex);
420             dialogSubCollectionFieldValue = dialogSubCollectionField.getAttribute("value");
421         }
422 
423         if (subCollectionFieldValue != null && dialogSubCollectionFieldValue != null) {
424             assertEquals("Dialog field the sub-collection is in does not match that of the line.",
425                     subCollectionFieldValue, dialogSubCollectionFieldValue);
426         }
427 
428         // modify the value of the sub-collection's parent field
429         if (dialogSubCollectionField != null) {
430             dialogSubCollectionField.sendKeys("sub");
431         }
432 
433         // get the dialog's sub-collection add line fields and type values into them
434         List<WebElement> dialogSubCollectionAddLineRowFields = findElements(By.cssSelector(
435                 dialogSubCollectionAddLineFieldsCssSelector));
436         int index = 1;
437         for (WebElement element : dialogSubCollectionAddLineRowFields) {
438             element.sendKeys("add" + Integer.toString(index));
439             index++;
440         }
441 
442         // click the add line button
443         WebElement subCollectionAddLineButton = findElement(By.cssSelector(
444                 dialogSubCollectionAddLineButtonCssSelector));
445         subCollectionAddLineButton.click();
446         waitForProgress("Adding Line...", WebDriverUtils.configuredImplicityWait() * 15);
447         waitFor(By.cssSelector(dialogCssSelector));
448 
449         // get the added line fields and make sure the values match the add line
450         List<WebElement> dialogSubCollectionAddedLineRows = findElements(By.cssSelector(
451                 dialogSubCollectionAddedLineFieldsCssSelector));
452         assertTrue("Dialog rows count is 0", dialogSubCollectionAddedLineRows.size() > 0);
453         List<WebElement> dialogSubCollectionAddedLineRowFields = dialogSubCollectionAddedLineRows.get(0).
454                 findElements(By.cssSelector(" td > div.uif-inputField > input:not([type='hidden'])"));
455         index = 1;
456         for (WebElement element : dialogSubCollectionAddedLineRowFields) {
457             assertEquals("Added line fields in the sub-collection do not match.", "add" + Integer.toString(index),
458                     element.getText());
459             index++;
460         }
461 
462         // verify the sub-collection field and dialog sub-collection field values
463         if (subCollectionFieldCssSelector != null && dialogSubCollectionFieldCssSelector != null) {
464             // make sure the underlying fields are untouched
465             WebElement subCollectionRowField = findElement(By.cssSelector(
466                     "#" + exampleId + subCollectionFieldCssSelector));
467             subCollectionFieldValue = subCollectionRowField.getText();
468             List<WebElement> dialogFields = findElements(By.cssSelector(dialogSubCollectionFieldCssSelector));
469             dialogSubCollectionField = dialogFields.get(dialogSubCollectionFieldIndex);
470             dialogSubCollectionFieldValue = dialogSubCollectionField.getAttribute("value");
471             assertEquals(
472                     "Changing the dialog' sub-collection field value also changed the value of that of the collection's.",
473                     subCollectionFieldValue + "sub", dialogSubCollectionFieldValue);
474         }
475 
476         // make sure the collection row's sub-collection do have have the changes of the add line of the
477         // sub-collection in the dialog
478         List<WebElement> subCollectionRows = findElements(By.cssSelector(subCollectionRowsCssSelector));
479         List<WebElement> dialogSubCollectionRows = findElements(By.cssSelector(dialogSubCollectionRowsCssSelector));
480         assertEquals("Dialog sub-collection rows size after add line match those of the line.",
481                 subCollectionRows.size() + 1, dialogSubCollectionRows.size());
482 
483         // get the buttons in the dialog
484         List<WebElement> buttonElements = findVisibleElements(By.cssSelector(EDIT_DIALOG_BUTTONS_CSS_SELECTOR));
485         WebElement saveButton = buttonElements.get(0);
486 
487         // click the save button and verify changes are saved
488         saveButton.click();
489         waitForProgress("Editing Line...", WebDriverUtils.configuredImplicityWait() * 15);
490 
491         // verify the save was processed correctly and the sub-collection field values match with that of the dialog's
492         if (subCollectionFieldCssSelector != null && dialogSubCollectionFieldCssSelector != null) {
493             WebElement subCollectionRowField = findElement(By.cssSelector(
494                     "#" + exampleId + subCollectionFieldCssSelector));
495             subCollectionFieldValue = subCollectionRowField.getText();
496             assertEquals("Saving the dialog' sub-collection field's new value did not change that of the collection's.",
497                     subCollectionFieldValue, dialogSubCollectionFieldValue);
498         }
499 
500         subCollectionRows = findElements(By.cssSelector(subCollectionRowsCssSelector));
501         assertEquals("Dialog sub-collection rows size do not match those of the line after saving changes.",
502                 subCollectionRows.size(), dialogSubCollectionRows.size());
503 
504         // delete the line added to the sub-collection but hit the no button and verify no changes
505         verifySubCollectionActionMakesNoChanges(exampleId, rowIndex, dialogSubCollectionDeleteLineButtonCssSelector,
506                 subCollectionRowsCssSelector, dialogSubCollectionRowsCssSelector, false);
507 
508         // delete the line added to the sub-collection but hit the close button and verify no changes
509         verifySubCollectionActionMakesNoChanges(exampleId, rowIndex, dialogSubCollectionDeleteLineButtonCssSelector,
510                 subCollectionRowsCssSelector, dialogSubCollectionRowsCssSelector, true);
511     }
512 
513     /**
514      * Helper method to perform no/close actions of the dialog and verify that, while changes were made to the
515      * dialog's sub-collection, the underlying collection's sub-collection have no changes.
516      *
517      * @param exampleId the id of the example the collection is in
518      * @param rowIndex the index of the row to edit
519      * @param dialogSubCollectionDeleteLineButtonCssSelector dialog's sub-collection's delete line button css selector
520      * @param subCollectionRowsCssSelector the collection's sub-collection's rows css selector
521      * @param dialogSubCollectionRowsCssSelector the dialog's sub-collection's rows css selector
522      * @param close whether this is a close action
523      */
524     protected void verifySubCollectionActionMakesNoChanges(String exampleId, int rowIndex,
525             String dialogSubCollectionDeleteLineButtonCssSelector, String subCollectionRowsCssSelector,
526             String dialogSubCollectionRowsCssSelector, boolean close) throws Exception {
527         // open the edit line dialog
528         openEditLineDialog(exampleId, rowIndex);
529 
530         // delete the line added to the sub-collection but hit the action button and verify no changes
531         WebElement subCollectionDeleteButton = waitAndGetElementsFor(By.cssSelector(
532                 dialogSubCollectionDeleteLineButtonCssSelector), "Could not find delete button.").get(0);
533         subCollectionDeleteButton.click();
534         waitForProgress("Editing Line...", WebDriverUtils.configuredImplicityWait() * 15);
535         waitFor(By.cssSelector(EDIT_DIALOG_CSS_SELECTOR));
536 
537         // get the dialog's sub-collection's contents for comparing
538         List<WebElement> dialogSubCollectionRows = waitAndGetElementsFor(By.cssSelector(
539                 dialogSubCollectionRowsCssSelector), "Could not get sub-collection rows.");
540 
541         // click either the close button or the button to not save changes
542         WebElement button = null;
543         if (close) {
544             button = waitFor(By.cssSelector(EDIT_DIALOG_CLOSE_BUTTON_CSS_SELECTOR));
545         } else {
546             List<WebElement> buttons = waitAndGetElementsFor(By.cssSelector(
547                     EDIT_DIALOG_BUTTONS_CSS_SELECTOR), "Could not find buttons.");
548             button = buttons.get(1);
549         }
550         button.click();
551         waitForProgressLoading();
552 
553         // get the underlying line field's sub-collection rows to compare to that of the dialog's
554         List<WebElement> subCollectionRows = waitAndGetElementsFor(By.cssSelector(subCollectionRowsCssSelector),
555                 "Cannot see elements.");
556         assertEquals("Dialog sub-collection rows size after delete line match those of the line.",
557                 subCollectionRows.size() - 1, dialogSubCollectionRows.size());
558     }
559 
560     /**
561      * Helper method that opens the dialog, edits the value, and checks results based on save or don't save.
562      *
563      * @param exampleId the id of the example the collection is in
564      * @param lineFieldToEditIndex the index of the line field to edit
565      * @param dialogFieldToEditIndex the index of the dialog field to edit
566      * @param save whether this is a save action or not
567      * @param close whether this is a close action
568      * @param custom where it is a custom dialog
569      * @param rowIndex the index of the row to edit
570      * @param readOnly whether we are doing a readOnly
571      * @param lookupFieldToEditIndex the index of the lookup field (this can be -1 which means there is no lookup
572      * configured on any field)
573      */
574     protected void verifyDialogEdit(String exampleId, int lineFieldToEditIndex, int dialogFieldToEditIndex,
575             boolean save, boolean close, boolean custom, int rowIndex, boolean readOnly, int lookupFieldToEditIndex)
576             throws Exception {
577         String tableRowsCssSelector = "#" + exampleId + TABLE_ROWS_CSS_SELECTOR;
578 
579         // get the values of the line fields in the given row
580         Thread.sleep(THREE_SECOND_WAIT_TIME); // avoid cache change by going to quick
581         List<WebElement> spanElements = findVisibleElements(By.cssSelector(tableRowsCssSelector)).get(rowIndex - 1).
582                 findElements(By.cssSelector(" div.uif-inputField > span"));
583         String field1Value = spanElements.get(0).getText();
584         String field2Value = spanElements.get(1).getText();
585         String fieldValue = spanElements.get(lineFieldToEditIndex - 1).getText();
586 
587         // open the edit line dialog
588         Thread.sleep(THREE_SECOND_WAIT_TIME); // avoid cache change by going to quick
589         openEditLineDialog(exampleId, rowIndex);
590 
591         // get the original values of the input fields
592         List<WebElement> inputElements = findElements(By.cssSelector(EDIT_DIALOG_INPUT_FIELDS_CSS_SELECTOR));
593 
594         if (inputElements.isEmpty()) {
595             jiraAwareFail("inputElements should not be empty for example " + exampleId);
596         }
597 
598         if (custom && !save) {
599             fieldValue = inputElements.get(dialogFieldToEditIndex - 1).getAttribute("value");
600         }
601 
602         // check if there is a lookup first
603         if (lookupFieldToEditIndex >= 0) {
604             WebElement lookup = waitFor(By.cssSelector(EDIT_DIALOG_LOOKUP_CSS_SELECTOR));
605             lookup.click();
606 
607             // switch to the lookup dialog iframe
608             WebElement lookupDialogFrame = waitFor(By.cssSelector("#Uif-DialogGroup-Lookup iframe"));
609             getDriver().switchTo().frame(lookupDialogFrame);
610 
611             List<WebElement> buttons = findElements(By.cssSelector(".uif-footer button"));
612 
613             WebElement searchButton = buttons.get(2);
614             searchButton.click();
615             waitForProgressLoading();
616 
617             List<WebElement> searchResults = findElements(By.cssSelector(
618                     ".uif-lookupPage section table > tbody > tr > td.uif-collection-column-action a"));
619 
620             // pick a random result row (except the last 2 cause they are garbage)
621             int searchResultIndex = RandomUtils.nextInt(searchResults.size());
622 
623             List<WebElement> searchResultValues = findElements(By.cssSelector(
624                     ".uif-lookupPage section table > tbody > tr > td.sorting_1 a"));
625 
626             WebElement searchResultPickedAnchor = searchResults.get(searchResultIndex);
627             WebElement searchResultPicked = searchResultValues.get(searchResultIndex);
628 
629             String resultValue = searchResultPicked.getText();
630             searchResultPickedAnchor.click();
631 
632             // switch back to the parent frame
633             getDriver().switchTo().defaultContent();
634 
635             // wait for the edit line dialog to re-appear
636             waitFor(By.cssSelector(EDIT_DIALOG_CSS_SELECTOR));
637             inputElements = findElements(By.cssSelector(EDIT_DIALOG_INPUT_FIELDS_CSS_SELECTOR));
638             verifyDialogField(inputElements.get(lookupFieldToEditIndex - 1), "", resultValue);
639             if (save) {
640                 if (lookupFieldToEditIndex == 1) {
641                     field1Value = resultValue;
642                 } else if (lookupFieldToEditIndex == 2) {
643                     field2Value = resultValue;
644                 }
645             }
646         }
647 
648         // verify and/or edit the fields individually
649         String editString = "edit";
650         verifyDialogField(inputElements.get(dialogFieldToEditIndex - 1), editString, fieldValue);
651 
652         // if there are other dialog fields, then check those (this is necessary in the case where user provides the
653         // list of dialog fields which is a subset of the list of line fields)
654         if (!readOnly && lineFieldToEditIndex % 2 < inputElements.size() && spanElements.size() != inputElements
655                 .size()) {
656             verifyDialogField(inputElements.get(dialogFieldToEditIndex % 2), null, spanElements.get(
657                     lineFieldToEditIndex % 2).getText());
658         }
659 
660         // get the buttons in the dialog
661         List<WebElement> buttonElements = waitAndGetElementsFor(By.cssSelector(
662                 EDIT_DIALOG_BUTTONS_CSS_SELECTOR), "Could not find buttons.");
663         WebElement saveButton = buttonElements.get(0);
664         WebElement noButton = buttonElements.get(1);
665 
666         // if its a close, then we point the no button to the close button
667         if (close) {
668             noButton = findElement(By.cssSelector(EDIT_DIALOG_CLOSE_BUTTON_CSS_SELECTOR));
669         }
670 
671         if (save) {
672             // click the save button
673             saveButton.click();
674         } else {
675             // click the no button
676             noButton.click();
677         }
678         waitForProgress("Editing Line...", WebDriverUtils.configuredImplicityWait() * 15);
679 
680         // get the new values of the line fields
681         spanElements = findVisibleElements(By.cssSelector(tableRowsCssSelector)).get(rowIndex - 1).
682                 findElements(By.cssSelector(" div.uif-inputField > span"));
683         String newField1Value = spanElements.get(0).getText();
684         String newField2Value = spanElements.get(1).getText();
685 
686         // verify changed values or not changed
687         if (save) {
688             if (lineFieldToEditIndex == 1) {
689                 assertEquals("Saving changes did not edit the value.", field1Value + editString, newField1Value);
690                 assertEquals("Saving changes did not edit the value.", field2Value, newField2Value);
691             } else {
692                 if (custom) {
693                     assertEquals("Saving changes did not edit the value.", "Custom Edit Line " + rowIndex,
694                             newField1Value);
695                 } else {
696                     assertEquals("Saving changes did not edit the value.", field1Value, newField1Value);
697                 }
698                 if (custom) {
699                     assertEquals("Saving changes did not edit the value.", field2Value, newField2Value);
700                 } else {
701                     assertEquals("Saving changes did not edit the value.", field2Value + editString, newField2Value);
702                 }
703             }
704         } else {
705             assertEquals("Cancelling the edit line still edits the value.", field1Value, newField1Value);
706             assertEquals("Cancelling the edit line still edits the value.", field2Value, newField2Value);
707         }
708     }
709 
710     /**
711      * Helper method to edit a field's value.
712      *
713      * <p>This method has performs the following functions:<ul>
714      * <li>Verifies that the values of the original dialog fields match those of the line fields.</li>
715      * <li>Types the string provided, if provided, into the provided element, thereby editing the value of
716      * the field.</li>
717      * <li>If value of the field is edited, then the new values are checked for correctness.</li>
718      * </ul></p>
719      *
720      * @param element the element in the dialog we are editing
721      * @param randomString the string to edit with
722      * @param fieldValue the value of the original field
723      */
724     protected void verifyDialogField(WebElement element, String randomString, String fieldValue) throws Exception {
725         // get the original values of the input fields
726         String inputFieldValue = element.getAttribute("value");
727 
728         // make sure the input fields in the dialog match the line fields
729         assertEquals("Value of input field in dialog does not match that of the line field's value.", fieldValue,
730                 inputFieldValue);
731 
732         if (StringUtils.isNotEmpty(randomString)) {
733             // type the random string in the given input field
734             element.sendKeys(randomString);
735 
736             // get the new values after typing
737             String newInputFieldValue = element.getAttribute("value");
738 
739             // the first should have the original and the random string appended, while the second should be the same
740             assertEquals(inputFieldValue + randomString, newInputFieldValue);
741         }
742     }
743 
744     /**
745      * Helper method to open the modal dialog.
746      *
747      * @param exampleId the id of the example the collection is in
748      * @param index the index of the row
749      */
750     protected void openEditLineDialog(String exampleId, int index) throws Exception {
751         List<WebElement> elements = findVisibleElements(By.cssSelector("#" + exampleId + EDIT_BUTTON_CSS_SELECTOR));
752         elements.get(index - 1).click();
753 
754         // verify that the dialog is visible soon
755         waitFor(By.cssSelector(EDIT_DIALOG_CSS_SELECTOR));
756     }
757 
758     /**
759      * Helper method to verify that add and delete line still work.
760      *
761      * <p>The line that is added is also the line that is deleted returning the contents of the list back to its
762      * original state.</p>
763      *
764      * @param exampleId the id of the example the collection is in
765      * @param readOnly whether we are doing a readOnly
766      * @param subCollectionType the type of sub-collection
767      */
768     protected void verifyAddLineAndDeleteLineStillWork(String exampleId, boolean readOnly,
769             SubCollectionType subCollectionType) throws Exception {
770         if (readOnly) {
771             return;
772         }
773 
774         String randomString = "add";
775 
776         verifyAddLineStillWorks(exampleId, randomString, subCollectionType);
777         verifyDeleteLineStillWorks(exampleId);
778     }
779 
780     /**
781      * Helper method to verify that adding a line in a collection works.
782      *
783      * @param exampleId the id of the example the collection is in
784      * @param randomString the string field that is part of the line to delete
785      * @param subCollectionType the type of sub-collection
786      */
787     protected void verifyAddLineStillWorks(String exampleId, String randomString, SubCollectionType subCollectionType)
788             throws Exception {
789         String addFieldsCssSelector = null;
790         String addLineActionCssSelector = null;
791 
792         if (subCollectionType != SubCollectionType.NO_SUB_COLLECTION) {
793             String addInDialogButtonCssSelector = "#" + exampleId + " button:first-of-type";
794             WebElement addInDialogButton = findElement(By.cssSelector(addInDialogButtonCssSelector));
795             jGrowl("Click add in dialog button");
796             addInDialogButton.click();
797             addFieldsCssSelector = ADD_FIELDS_IN_DIALOG_CSS_SELECTOR;
798             addLineActionCssSelector = ADD_LINE_ACTIONS_IN_DIALOG_CSS_SELECTOR;
799         } else {
800             addFieldsCssSelector = "#" + exampleId + ADD_FIELDS_CSS_SELECTOR;
801             addLineActionCssSelector = "#" + exampleId +  ADD_BUTTON_CSS_SELECTOR;
802         }
803 
804         List<WebElement> addFields = findElements(By.cssSelector(addFieldsCssSelector));
805 
806         int index = 1;
807         for (WebElement addField : addFields) {
808             jGrowl("Type in " + addField.getAttribute("name") + " " + randomString + index);
809             addField.sendKeys(randomString + index);
810             index++;
811         }
812 
813         WebElement buttonElement = findElement(By.cssSelector(addLineActionCssSelector));
814         jGrowl("Click add line");
815         buttonElement.click();
816         waitForProgress("Adding Line...", WebDriverUtils.configuredImplicityWait() * 15);
817         waitFor(By.cssSelector("#" + exampleId + " .uif-newCollectionItem"));
818         List<WebElement> fields = null;
819 
820         // if its row details then the first column has the details group so we want to look at the second
821         // and third columns only since there could be other input fields in row details
822         if (subCollectionType == SubCollectionType.NO_SUB_COLLECTION) {
823             fields = findVisibleElements(By.cssSelector("#" + exampleId +
824                     " .uif-newCollectionItem .uif-inputField span"));
825             assertEquals(addFields.size(), fields.size());
826         } else if (subCollectionType == SubCollectionType.ROW_DETAILS) {
827             fields = findVisibleElements(By.cssSelector("#" + exampleId +
828                     " .uif-newCollectionItem td:nth-of-type(2) .uif-inputField span"));
829             fields.addAll(findVisibleElements(By.cssSelector("#" + exampleId +
830                     " .uif-newCollectionItem td:nth-of-type(3) .uif-inputField span")));
831             assertEquals(addFields.size(), fields.size() + 1);
832         } else {
833             fields = findVisibleElements(By.cssSelector("#" + exampleId +
834                     " .uif-newCollectionItem td:nth-of-type(1) .uif-inputField span"));
835             fields.addAll(findVisibleElements(By.cssSelector("#" + exampleId +
836                     " .uif-newCollectionItem td:nth-of-type(2) .uif-inputField span")));
837             assertEquals(addFields.size(), fields.size() + 1);
838         }
839 
840         index = 1;
841         for (WebElement field : fields) {
842             assertEquals("Added field does not match what is in the collection.", randomString + index,
843                     field.getText());
844             index++;
845         }
846     }
847 
848     /**
849      * Helper method to verify that deleting a line in a collection works.
850      *
851      * @param exampleId the id of the example the collection is in
852      */
853     protected void verifyDeleteLineStillWorks(String exampleId) throws Exception {
854         WebElement deleteAction = findElement(By.cssSelector(
855                 "#" + exampleId + " .uif-newCollectionItem .uif-collection-column-action button"));
856         deleteAction.click();
857 
858         waitForProgress("Deleting Line...", WebDriverUtils.configuredImplicityWait() * 15);
859 
860         assertIsNotVisible(By.cssSelector("#" + exampleId + " .uif-newCollectionItem"));
861     }
862 
863     /**
864      * Helper method to make sure that all rows in a collection have a button to edit the line in a dialog (with the
865      * exception of the add line row).
866      *
867      * @param exampleId the id of the example the collection is in
868      * @param rowsSize the number of rows of the collection on the page
869      */
870     protected void verifyCollectionRowsHaveEditButtons(String exampleId, int rowsSize) {
871         List<WebElement> buttonElements = findVisibleElements(By.cssSelector(
872                 "#" + exampleId + EDIT_BUTTON_CSS_SELECTOR));
873         int buttonsSize = buttonElements.size();
874         if (rowsSize != buttonsSize) {
875             buttonsSize += 2;
876         }
877         assertEquals("The number of rows do not match the number of edit line buttons.", rowsSize, buttonsSize);
878     }
879 
880     /**
881      * Helper method to check that all row fields in a collection are not editable (with the exception of the
882      * add line row).
883      *
884      * @param exampleId the id of the example the collection is in
885      */
886     protected void verifyCollectionRowFieldsAreNonEditable(String exampleId) throws Exception {
887         List<WebElement> inputFields = findElements(By.cssSelector(
888                 "#" + exampleId + " tr:not(.uif-collectionAddItem) input:not([type='hidden'])"));
889         assertTrue(inputFields.isEmpty());
890     }
891 
892     @Test
893     public void testDefaultEditLineBookmark() throws Exception {
894         this.waitSeconds = FIVE_SECOND_WAIT_TIME;
895         testDefaultEditLine();
896         passed();
897     }
898 
899     @Test
900     public void testDefaultEditLineNav() throws Exception {
901         this.waitSeconds = FIVE_SECOND_WAIT_TIME;
902         testDefaultEditLine();
903         passed();
904     }
905 
906     @Test
907     public void testCustomDialogEditLine1Bookmark() throws Exception {
908         this.waitSeconds = FIVE_SECOND_WAIT_TIME;
909         testCustomDialogEditLine1();
910         passed();
911     }
912 
913     @Test
914     public void testCustomDialogEditLine1Nav() throws Exception {
915         this.waitSeconds = FIVE_SECOND_WAIT_TIME;
916         testCustomDialogEditLine1();
917         passed();
918     }
919 
920     @Test
921     public void testCustomDialogEditLine2Bookmark() throws Exception {
922         this.waitSeconds = FIVE_SECOND_WAIT_TIME;
923         testCustomDialogEditLine2();
924         passed();
925     }
926 
927     @Test
928     public void testCustomDialogEditLine2Nav() throws Exception {
929         this.waitSeconds = FIVE_SECOND_WAIT_TIME;
930         testCustomDialogEditLine2();
931         passed();
932     }
933 
934     @Test
935     public void testCustomDialogLineActionEditLineBookmark() throws Exception {
936         this.waitSeconds = FIVE_SECOND_WAIT_TIME;
937         testCustomDialogLineActionEditLine();
938         passed();
939     }
940 
941     @Test
942     public void testCustomDialogLineActionEditLineNav() throws Exception {
943         this.waitSeconds = FIVE_SECOND_WAIT_TIME;
944         testCustomDialogLineActionEditLine();
945         passed();
946     }
947 
948     @Test
949     public void testCustomDialogSaveActionEditLineBookmark() throws Exception {
950         this.waitSeconds = FIVE_SECOND_WAIT_TIME;
951         testCustomDialogSaveActionEditLine();
952         passed();
953     }
954 
955     @Test
956     public void testCustomDialogSaveActionEditLineNav() throws Exception {
957         this.waitSeconds = FIVE_SECOND_WAIT_TIME;
958         testCustomDialogSaveActionEditLine();
959         passed();
960     }
961 
962     @Test
963     public void testCollectionFeaturesEditLineBookmark() throws Exception {
964         this.waitSeconds = FIVE_SECOND_WAIT_TIME;
965         testCollectionFeaturesEditLine();
966         passed();
967     }
968 
969     @Test
970     public void testCollectionFeaturesEditLineNav() throws Exception {
971         this.waitSeconds = FIVE_SECOND_WAIT_TIME;
972         testCollectionFeaturesEditLine();
973         passed();
974     }
975 
976     @Test
977     public void testRowDetailsGroupBookmark() throws Exception {
978         this.waitSeconds = FIVE_SECOND_WAIT_TIME;
979         testRowDetailsGroup();
980         passed();
981     }
982 
983     @Test
984     public void testRowDetailsGroupNav() throws Exception {
985         this.waitSeconds = FIVE_SECOND_WAIT_TIME;
986         testRowDetailsGroup();
987         passed();
988     }
989 
990     @Test
991     public void testSubCollectionBookmark() throws Exception {
992         this.waitSeconds = FIVE_SECOND_WAIT_TIME;
993         testSubCollection();
994         passed();
995     }
996 
997     @Test
998     public void testSubCollectionNav() throws Exception {
999         this.waitSeconds = FIVE_SECOND_WAIT_TIME;
1000         testSubCollection();
1001         passed();
1002     }
1003 }