001/* 002 * (C) Copyright 2013 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl-2.1.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * <a href="mailto:grenard@nuxeo.com">Guillaume</a> 016 */ 017package org.nuxeo.functionaltests.forms; 018 019import java.util.List; 020import java.util.concurrent.TimeUnit; 021 022import org.apache.commons.logging.Log; 023import org.apache.commons.logging.LogFactory; 024import org.nuxeo.functionaltests.AbstractTest; 025import org.nuxeo.functionaltests.AjaxRequestManager; 026import org.nuxeo.functionaltests.Locator; 027import org.nuxeo.functionaltests.fragment.WebFragmentImpl; 028import org.nuxeo.functionaltests.pages.search.SearchPage; 029import org.openqa.selenium.By; 030import org.openqa.selenium.Keys; 031import org.openqa.selenium.NoSuchElementException; 032import org.openqa.selenium.StaleElementReferenceException; 033import org.openqa.selenium.TimeoutException; 034import org.openqa.selenium.WebDriver; 035import org.openqa.selenium.WebElement; 036import org.openqa.selenium.support.ui.FluentWait; 037import org.openqa.selenium.support.ui.Wait; 038 039import com.gargoylesoftware.htmlunit.ElementNotFoundException; 040import com.google.common.base.Function; 041 042/** 043 * Convenient class to handle a select2Widget. 044 * 045 * @since 5.7.3 046 */ 047public class Select2WidgetElement extends WebFragmentImpl { 048 049 private static class Select2Wait implements Function<WebElement, Boolean> { 050 051 @Override 052 public Boolean apply(WebElement element) { 053 boolean result = !element.getAttribute("class").contains(S2_CSS_ACTIVE_CLASS); 054 return result; 055 } 056 } 057 058 private static final Log log = LogFactory.getLog(Select2WidgetElement.class); 059 060 private static final String S2_CSS_ACTIVE_CLASS = "select2-active"; 061 062 private static final String S2_MULTIPLE_CURRENT_SELECTION_XPATH = "ul[@class='select2-choices']/li[@class='select2-search-choice']"; 063 064 private final static String S2_MULTIPLE_INPUT_XPATH = "ul/li/input"; 065 066 private static final String S2_SINGLE_CURRENT_SELECTION_XPATH = "a[@class='select2-choice']/span[@class='select2-chosen']"; 067 068 private final static String S2_SINGLE_INPUT_XPATH = "//*[@id='select2-drop']/div/input"; 069 070 private static final String S2_SUGGEST_RESULT_XPATH = "//*[@id='select2-drop']//li[contains(@class,'select2-result-selectable')]/div"; 071 072 /** 073 * Select2 loading timeout in seconds. 074 */ 075 private static final int SELECT2_LOADING_TIMEOUT = 20; 076 077 protected boolean mutliple = false; 078 079 /** 080 * Constructor. 081 * 082 * @param driver the driver 083 * @param id the id of the widget 084 * @since 7.1 085 */ 086 public Select2WidgetElement(WebDriver driver, String id) { 087 this(driver, driver.findElement(By.id(id))); 088 } 089 090 /** 091 * Constructor. 092 * 093 * @param driver the driver 094 * @param by the by locator of the widget 095 */ 096 public Select2WidgetElement(WebDriver driver, WebElement element) { 097 super(driver, element); 098 } 099 100 /** 101 * Constructor. 102 * 103 * @param driver the driver 104 * @param by the by locator of the widget 105 * @param multiple whether the widget can have multiple values 106 */ 107 public Select2WidgetElement(final WebDriver driver, WebElement element, final boolean multiple) { 108 this(driver, element); 109 mutliple = multiple; 110 } 111 112 /** 113 * @since 5.9.3 114 */ 115 public WebElement getSelectedValue() { 116 if (mutliple) { 117 throw new UnsupportedOperationException("The select2 is multiple and has multiple selected values"); 118 } 119 return element.findElement(By.xpath(S2_SINGLE_CURRENT_SELECTION_XPATH)); 120 } 121 122 /** 123 * @since 5.9.3 124 */ 125 public List<WebElement> getSelectedValues() { 126 if (!mutliple) { 127 throw new UnsupportedOperationException( 128 "The select2 is not multiple and can't have multiple selected values"); 129 } 130 return element.findElements(By.xpath(S2_MULTIPLE_CURRENT_SELECTION_XPATH)); 131 } 132 133 /** 134 * @since 5.9.3 135 */ 136 protected String getSubmittedValue() { 137 String eltId = element.getAttribute("id"); 138 String submittedEltId = element.getAttribute("id").substring("s2id_".length(), eltId.length()); 139 return driver.findElement(By.id(submittedEltId)).getAttribute("value"); 140 } 141 142 public List<WebElement> getSuggestedEntries() { 143 try { 144 return driver.findElements(By.xpath(S2_SUGGEST_RESULT_XPATH)); 145 } catch (NoSuchElementException e) { 146 return null; 147 } 148 } 149 150 /** 151 * @since 5.9.3 152 */ 153 public void removeFromSelection(final String displayedText) { 154 if (!mutliple) { 155 throw new UnsupportedOperationException("The select2 is not multiple and you can't remove a specific value"); 156 } 157 final String submittedValueBefore = getSubmittedValue(); 158 boolean found = false; 159 for (WebElement el : getSelectedValues()) { 160 if (el.getText().equals(displayedText)) { 161 el.findElement(By.xpath("a[@class='select2-search-choice-close']")).click(); 162 found = true; 163 } 164 } 165 if (found) { 166 Locator.waitUntilGivenFunction(new Function<WebDriver, Boolean>() { 167 @Override 168 public Boolean apply(WebDriver driver) { 169 return !submittedValueBefore.equals(getSubmittedValue()); 170 } 171 }); 172 } else { 173 throw new ElementNotFoundException("remove link for select2 '" + displayedText + "' item", "", ""); 174 } 175 } 176 177 /** 178 * Select a single value. 179 * 180 * @param value the value to be selected 181 * @since 5.7.3 182 */ 183 public void selectValue(final String value) { 184 selectValue(value, false); 185 } 186 187 /** 188 * @since 7.1 189 */ 190 public void selectValue(final String value, final boolean wait4A4J) { 191 clickOnSelect2Field(); 192 193 WebElement suggestInput = getSuggestInput(); 194 195 char c; 196 for (int i = 0; i < value.length(); i++) { 197 c = value.charAt(i); 198 suggestInput.sendKeys(c + ""); 199 } 200 201 waitSelect2(); 202 203 if (getSuggestedEntries() != null && getSuggestedEntries().size() > 1) { 204 log.warn("Suggestion for element " + element.getAttribute("id") 205 + " returned more than 1 result, the first suggestion will be selected : " 206 + getSuggestedEntries().get(0).getText()); 207 } 208 209 WebElement suggestion = driver.findElement(By.xpath(S2_SUGGEST_RESULT_XPATH)); 210 AjaxRequestManager arm = new AjaxRequestManager(driver);; 211 if (wait4A4J) { 212 arm.watchAjaxRequests(); 213 } 214 try { 215 suggestion.click(); 216 } catch (StaleElementReferenceException e) { 217 suggestion = driver.findElement(By.xpath(S2_SUGGEST_RESULT_XPATH)); 218 suggestion.click(); 219 } 220 if (wait4A4J) { 221 arm.waitForAjaxRequests(); 222 } 223 } 224 225 /** 226 * Select multiple values. 227 * 228 * @param values the values 229 * @since 5.7.3 230 */ 231 public void selectValues(final String[] values) { 232 for (String value : values) { 233 selectValue(value); 234 } 235 } 236 237 /** 238 * Type a value in the select2 and return the suggested entries. 239 * 240 * @param value The value to type in the select2. 241 * @return The suggested values for the parameter. 242 * @since 6.0 243 */ 244 public List<WebElement> typeAndGetResult(final String value) { 245 246 clickOnSelect2Field(); 247 248 WebElement suggestInput = getSuggestInput(); 249 250 suggestInput.sendKeys(value); 251 try { 252 waitSelect2(); 253 } catch (TimeoutException e) { 254 log.warn("Suggestion timed out with input : " + value + ". Let's try with next letter."); 255 } 256 257 return getSuggestedEntries(); 258 } 259 260 /** 261 * Click on the select2 field. 262 * 263 * @since 6.0 264 */ 265 protected void clickOnSelect2Field() { 266 WebElement select2Field = null; 267 if (mutliple) { 268 select2Field = element; 269 } else { 270 select2Field = element.findElement(By.xpath("a[contains(@class,'select2-choice')]")); 271 } 272 select2Field.click(); 273 } 274 275 /** 276 * @return The suggest input element. 277 * @since 6.0 278 */ 279 private WebElement getSuggestInput() { 280 WebElement suggestInput = null; 281 if (mutliple) { 282 suggestInput = element.findElement(By.xpath("ul/li[@class='select2-search-field']/input")); 283 } else { 284 suggestInput = driver.findElement(By.xpath(S2_SINGLE_INPUT_XPATH)); 285 } 286 287 return suggestInput; 288 } 289 290 /** 291 * Do a wait on the select2 field. 292 * 293 * @throws TimeoutException 294 * @since 6.0 295 */ 296 private void waitSelect2() throws TimeoutException { 297 Wait<WebElement> wait = new FluentWait<WebElement>( 298 !mutliple ? driver.findElement(By.xpath(S2_SINGLE_INPUT_XPATH)) 299 : element.findElement(By.xpath(S2_MULTIPLE_INPUT_XPATH))).withTimeout(SELECT2_LOADING_TIMEOUT, 300 TimeUnit.SECONDS).pollingEvery(100, TimeUnit.MILLISECONDS).ignoring(NoSuchElementException.class); 301 Function<WebElement, Boolean> s2WaitFunction = new Select2Wait(); 302 wait.until(s2WaitFunction); 303 } 304 305 /** 306 * Clear the input of the select2. 307 * 308 * @since 6.0 309 */ 310 public void clearSuggestInput() { 311 WebElement suggestInput = null; 312 if (mutliple) { 313 suggestInput = driver.findElement(By.xpath("//ul/li[@class='select2-search-field']/input")); 314 } else { 315 suggestInput = driver.findElement(By.xpath(S2_SINGLE_INPUT_XPATH)); 316 } 317 318 if (suggestInput != null) { 319 suggestInput.clear(); 320 } 321 } 322 323 /** 324 * Click on the select2 element. 325 * 326 * @since 6.0 327 */ 328 public void clickSelect2Field() { 329 WebElement select2Field = null; 330 if (mutliple) { 331 select2Field = element; 332 } else { 333 select2Field = element.findElement(By.xpath("a[contains(@class,'select2-choice select2-default')]")); 334 } 335 select2Field.click(); 336 } 337 338 /** 339 * Type a value in the select2 and then simulate the enter key. 340 * 341 * @since 6.0 342 */ 343 public SearchPage typeValueAndTypeEnter(String value) { 344 clickOnSelect2Field(); 345 346 WebElement suggestInput = getSuggestInput(); 347 348 suggestInput.sendKeys(value); 349 try { 350 waitSelect2(); 351 } catch (TimeoutException e) { 352 log.warn("Suggestion timed out with input : " + value + ". Let's try with next letter."); 353 } 354 suggestInput.sendKeys(Keys.RETURN); 355 356 return AbstractTest.asPage(SearchPage.class); 357 } 358 359}