001 /*
002 * Copyright 2005-2013 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017 package edu.samplu.common;
018
019 import org.junit.rules.TestName;
020 import org.openqa.selenium.By;
021 import org.openqa.selenium.NoSuchFrameException;
022 import org.openqa.selenium.WebDriver;
023 import org.openqa.selenium.chrome.ChromeDriver;
024 import org.openqa.selenium.chrome.ChromeDriverService;
025 import org.openqa.selenium.firefox.FirefoxDriver;
026 import org.openqa.selenium.firefox.FirefoxProfile;
027 import org.openqa.selenium.remote.DesiredCapabilities;
028 import org.openqa.selenium.remote.RemoteWebDriver;
029 import org.openqa.selenium.safari.SafariDriver;
030
031 import com.thoughtworks.selenium.SeleneseTestBase;
032
033 import java.io.File;
034 import java.net.MalformedURLException;
035 import java.net.URL;
036 import java.util.HashMap;
037 import java.util.Iterator;
038 import java.util.Map;
039 import java.util.concurrent.TimeUnit;
040
041
042 /**
043 * The goal of the WebDriverUtil class is to invert the dependencies on WebDriver from WebDriverLegacyITBase for reuse
044 * without having to extend WebDriverLegacyITBase. For the first example see waitFor
045 *
046 * @see WebDriverLegacyITBase
047 * @author Kuali Rice Team (rice.collab@kuali.org)
048 */
049 public class WebDriverUtil {
050
051 /**
052 * TODO apparent dup WebDriverITBase.DEFAULT_WAIT_SEC
053 * TODO parametrize for JVM Arg
054 * 30 Seconds
055 */
056 public static int DEFAULT_IMPLICIT_WAIT_TIME = 30;
057
058 /**
059 * TODO introduce SHORT_IMPLICIT_WAIT_TIME with param in WebDriverITBase
060 * TODO parametrize for JVM Arg
061 * 1 Second
062 */
063 public static int SHORT_IMPLICIT_WAIT_TIME = 1;
064
065 /**
066 * Set -Dremote.driver.saucelabs for running on saucelabs
067 * @link https://wiki.kuali.org/display/KULRICE/How+To+Run+a+Selenium+Test for patch required
068 */
069 public static final String REMOTE_DRIVER_SAUCELABS_PROPERTY = "remote.driver.saucelabs";
070
071 /**
072 * Selenium's webdriver.chrome.driver parameter, you can set -Dwebdriver.chrome.driver= or Rice's REMOTE_PUBLIC_CHROME
073 */
074 public static final String WEBDRIVER_CHROME_DRIVER = "webdriver.chrome.driver";
075
076 /**
077 * Set -Dremote.public.chrome= or WEBDRIVER_CHROME_DRIVER
078 */
079 public static final String REMOTE_PUBLIC_CHROME = "remote.public.chrome";
080
081 /**
082 * Time to wait for the URL used in setup to load. Sometimes this is the first hit on the app and it needs a bit
083 * longer than any other. 120 Seconds.
084 * TODO parametrize for JVM Arg
085 */
086 public static final int SETUP_URL_LOAD_WAIT_SECONDS = 120;
087
088 /**
089 * https://jira.kuali.org/browse/
090 */
091 public static final String JIRA_BROWSE_URL = "https://jira.kuali.org/browse/";
092 static Map<String, String> jiraMatches;
093 static {
094 jiraMatches = new HashMap<String, String>();
095 jiraMatches.put("Error setting property values; nested exception is org.springframework.beans.NotWritablePropertyException: Invalid property 'refreshWhenChanged' of bean class [org.kuali.rice.krad.uif.element.Action]: Bean property 'refreshWhenChanged' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?",
096 "KULRICE-8137 Agenda Rule edit Incident report Invalid property 'refreshWhenChanged'");
097
098 jiraMatches.put("org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase.processAddCollectionLineBusinessRules(MaintenanceDocumentRuleBase.",
099 "KULRICE-8142 NPE in MaintenanceDocumentRuleBase.processAddCollectionLineBusinessRules");
100
101 jiraMatches.put("at org.kuali.rice.krad.rules.DocumentRuleBase.isDocumentOverviewValid(DocumentRuleBase.",
102 "KULRICE-8134 NPE in DocumentRuleBase.isDocumentOverviewValid(DocumentRuleBase");
103
104 jiraMatches.put("org.kuali.rice.krad.uif.layout.TableLayoutManager.buildLine(TableLayoutManager.",
105 "KULRICE-8160 NPE at TableLayoutManager.buildLine(TableLayoutManager");
106
107 jiraMatches.put("Bean property 'configFileLocations' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?",
108 "KULRICE-8173 Bean property 'configFileLocations' is not writable or has an invalid setter method");
109
110 jiraMatches.put("Bean property 'componentSecurity' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?",
111 "KULRICE-8182 JDK7 Bean property 'componentSecurity' is not readable...");
112
113 jiraMatches.put("java.sql.SQLSyntaxErrorException: ORA-00904: \"ROUTEHEADERID\": invalid identifier",
114 "KULRICE-8277 Several ITs fail with OJB operation; bad SQL grammar []; nested exception is java.sql.SQLException: ORA-00904: \"ROUTEHEADERID\": invalid identifier");
115
116 jiraMatches.put("By.xpath: //button[@data-loadingmessage='Adding Line...']",
117 "KULRICE-9044 KRAD \"stacked\" collection elements are not rendering add/delete buttons ");
118
119 jiraMatches.put("Error: on line 135, column 39 in krad/WEB-INF/ftl/lib/grid.ftl",
120 "KULRICE-9047 Term maintenance freemarker exception ");
121 }
122
123 /**
124 * Setup the WebDriver test, login, and load the given web page
125 *
126 * @param username
127 * @param url
128 * @return driver
129 * @throws Exception
130 */
131 public static WebDriver setUp(String username, String url) throws Exception {
132 return setUp(username, url, null, null);
133 }
134
135 /**
136 * Setup the WebDriver test, login, and load the given web page
137 *
138 * @param username
139 * @param url
140 * @param className
141 * @param testName
142 * @return driver
143 * @throws Exception
144 */
145 public static WebDriver setUp(String username, String url, String className, TestName testName) throws Exception {
146 WebDriver driver = null;
147 if (System.getProperty(REMOTE_DRIVER_SAUCELABS_PROPERTY) == null) {
148 driver = getWebDriver();
149 // } else {
150 // SauceLabsWebDriverHelper saucelabs = new SauceLabsWebDriverHelper();
151 // saucelabs.setUp(className, testName);
152 // driver = saucelabs.getDriver();
153 }
154 driver.manage().timeouts().implicitlyWait(SETUP_URL_LOAD_WAIT_SECONDS, TimeUnit.SECONDS);
155
156 // TODO Got into the situation where the first url doesn't expect server, but all others do. Readdress once
157 // the NavIT WDIT conversion has been completed.
158 if (!url.startsWith("http")) {
159 url = ITUtil.getBaseUrlString() + url;
160 }
161
162 driver.get(url);
163 driver.manage().timeouts().implicitlyWait(DEFAULT_IMPLICIT_WAIT_TIME, TimeUnit.SECONDS);
164 return driver;
165 }
166
167 /**
168 *
169 * @param passed
170 * @param sessionId
171 * @param testParam
172 * @param userParam
173 * @throws Exception
174 */
175 public static void tearDown(boolean passed, String sessionId, String testParam, String userParam) throws Exception {
176
177 // if (System.getProperty(SauceLabsWebDriverHelper.SAUCE_PROPERTY) != null) {
178 // SauceLabsWebDriverHelper.tearDown(passed, sessionId, System.getProperty(SauceLabsWebDriverHelper.SAUCE_USER_PROPERTY),
179 // System.getProperty(SauceLabsWebDriverHelper.SAUCE_KEY_PROPERTY));
180 // }
181
182 if (System.getProperty(WebDriverLegacyITBase.REMOTE_PUBLIC_USERPOOL_PROPERTY) != null) {
183 ITUtil.getHTML(ITUtil.prettyHttp(System.getProperty(WebDriverLegacyITBase.REMOTE_PUBLIC_USERPOOL_PROPERTY) + "?test="
184 + testParam + "&user=" + userParam));
185 }
186 }
187
188 /**
189 *
190 * @param testParam
191 * @return
192 */
193 public static String determineUser(String testParam) {
194 String user = null;
195
196 if (System.getProperty(WebDriverLegacyITBase.REMOTE_PUBLIC_USER_PROPERTY) != null) {
197 return System.getProperty(WebDriverLegacyITBase.REMOTE_PUBLIC_USER_PROPERTY);
198 } else if (System.getProperty(WebDriverLegacyITBase.REMOTE_PUBLIC_USERPOOL_PROPERTY) != null) { // deprecated
199 String userResponse = ITUtil.getHTML(ITUtil.prettyHttp(System.getProperty(
200 WebDriverLegacyITBase.REMOTE_PUBLIC_USERPOOL_PROPERTY) + "?test=" + testParam.trim()));
201 return userResponse.substring(userResponse.lastIndexOf(":") + 2, userResponse.lastIndexOf("\""));
202 }
203
204 return user;
205 }
206
207 /***
208 * @link ITUtil#checkForIncidentReport
209 * @param driver
210 * @param locator
211 * @param message
212 */
213 public static void checkForIncidentReport(WebDriver driver, String locator, Failable failable,
214 String message) {
215 ITUtil.checkForIncidentReport(driver.getPageSource(), locator, failable, message);
216 }
217
218 /**
219 * @link http://code.google.com/p/chromedriver/downloads/list
220 * @link #REMOTE_PUBLIC_CHROME
221 * @link #WEBDRIVER_CHROME_DRIVER
222 * @link ITUtil#HUB_DRIVER_PROPERTY
223 * @return chromeDriverService
224 */
225 public static ChromeDriverService chromeDriverCreateCheck() {
226 String driverParam = System.getProperty(ITUtil.HUB_DRIVER_PROPERTY);
227 // TODO can the saucelabs driver stuff be leveraged here?
228 if (driverParam != null && "chrome".equals(driverParam.toLowerCase())) {
229 if (System.getProperty(WEBDRIVER_CHROME_DRIVER) == null) {
230 if (System.getProperty(REMOTE_PUBLIC_CHROME) != null) {
231 System.setProperty(WEBDRIVER_CHROME_DRIVER, System.getProperty(REMOTE_PUBLIC_CHROME));
232 }
233 }
234 try {
235 ChromeDriverService chromeDriverService = new ChromeDriverService.Builder()
236 .usingChromeDriverExecutable(new File(System.getProperty(WEBDRIVER_CHROME_DRIVER)))
237 .usingAnyFreePort()
238 .build();
239 return chromeDriverService;
240 } catch (Throwable t) {
241 throw new RuntimeException("Exception starting chrome driver service, is chromedriver ( http://code.google.com/p/chromedriver/downloads/list ) installed? You can include the path to it using -Dremote.public.chrome", t) ;
242 }
243 }
244 return null;
245 }
246
247 /**
248 * remote.public.driver set to chrome or firefox (null assumes firefox)
249 * if remote.public.hub is set a RemoteWebDriver is created (Selenium Grid)
250 * @return WebDriver or null if unable to create
251 */
252 public static WebDriver getWebDriver() {
253 String driverParam = System.getProperty(ITUtil.HUB_DRIVER_PROPERTY);
254 String hubParam = System.getProperty(ITUtil.HUB_PROPERTY);
255 if (hubParam == null) {
256 if (driverParam == null || "firefox".equalsIgnoreCase(driverParam)) {
257 FirefoxProfile profile = new FirefoxProfile();
258 profile.setEnableNativeEvents(false);
259 return new FirefoxDriver(profile);
260 } else if ("chrome".equalsIgnoreCase(driverParam)) {
261 return new ChromeDriver();
262 } else if ("safari".equals(driverParam)) {
263 System.out.println("SafariDriver probably won't work, if it does please contact Erik M.");
264 return new SafariDriver();
265 }
266 } else {
267 try {
268 if (driverParam == null || "firefox".equalsIgnoreCase(driverParam)) {
269 return new RemoteWebDriver(new URL(ITUtil.getHubUrlString()), DesiredCapabilities.firefox());
270 } else if ("chrome".equalsIgnoreCase(driverParam)) {
271 return new RemoteWebDriver(new URL(ITUtil.getHubUrlString()), DesiredCapabilities.chrome());
272 }
273 } catch (MalformedURLException mue) {
274 System.out.println(ITUtil.getHubUrlString() + " " + mue.getMessage());
275 mue.printStackTrace();
276 }
277 }
278 return null;
279 }
280
281 /**
282 * If the JVM arg remote.autologin is set, auto login as admin will not be done.
283 * @param driver
284 * @param userName
285 * @param failable
286 * @throws InterruptedException
287 */
288 public static void login(WebDriver driver, String userName, Failable failable) throws InterruptedException {
289 if (System.getProperty(ITUtil.REMOTE_AUTOLOGIN_PROPERTY) == null) {
290 driver.findElement(By.name("__login_user")).clear();
291 driver.findElement(By.name("__login_user")).sendKeys(userName);
292 driver.findElement(By.cssSelector("input[type=\"submit\"]")).click();
293 Thread.sleep(1000);
294 String contents = driver.getPageSource();
295 ITUtil.failOnInvalidUserName(userName, contents, failable);
296 }
297 }
298
299 protected static void selectFrameSafe(WebDriver driver, String locator) {
300 try {
301 driver.switchTo().frame(locator);
302 } catch (NoSuchFrameException nsfe) {
303 // don't fail
304 }
305 }
306
307 /**
308 * Wait for the given amount of seconds, for the given by, using the given driver. The message is displayed if the
309 * by cannot be found. No action is performed on the by, so it is possible that the by found is not visible or enabled.
310 *
311 * @param driver WebDriver
312 * @param waitSeconds int
313 * @param by By
314 * @param message String
315 * @throws InterruptedException
316 */
317 public static void waitFor(WebDriver driver, int waitSeconds, By by, String message) throws InterruptedException {
318 driver.manage().timeouts().implicitlyWait(waitSeconds, TimeUnit.SECONDS);
319 Thread.sleep(1000);
320 driver.findElement(by); // NOTICE just the find, no action, so by is found, but might not be visible or enabled.
321 driver.manage().timeouts().implicitlyWait(1, TimeUnit.SECONDS);
322 }
323
324 public static void failOnMatchedJira(String contents) {
325 Iterator<String> iter = jiraMatches.keySet().iterator();
326 String key = null;
327
328 while (iter.hasNext()) {
329 key = iter.next();
330 if (contents.contains(key)) {
331 SeleneseTestBase.fail(JIRA_BROWSE_URL + jiraMatches.get(key));
332 }
333 }
334 }
335
336 private static void failWithReportInfoForKim(String contents, String linkLocator, String message) {
337 final String kimIncidentReport = extractIncidentReportKim(contents, linkLocator, message);
338 SeleneseTestBase.fail(kimIncidentReport);
339 }
340
341 private static String extractIncidentReportKim(String contents, String linkLocator, String message) {
342 String chunk = contents.substring(contents.indexOf("id=\"headerarea\""), contents.lastIndexOf("</div>") );
343 String docIdPre = "type=\"hidden\" value=\"";
344 String docId = chunk.substring(chunk.indexOf(docIdPre) + docIdPre.length(), chunk.indexOf("\" name=\"documentId\""));
345
346 String stackTrace = chunk.substring(chunk.lastIndexOf("name=\"displayMessage\""), chunk.length());
347 String stackTracePre = "value=\"";
348 stackTrace = stackTrace.substring(stackTrace.indexOf(stackTracePre) + stackTracePre.length(), stackTrace.indexOf("name=\"stackTrace\"") - 2);
349
350 return "\nIncident report "+ message+ " navigating to "+ linkLocator + " Doc Id: "+ docId.trim()+ "\nStackTrace: "+ stackTrace.trim();
351 }
352
353 private static void processIncidentReport(String contents, String linkLocator, String message) {
354 failOnMatchedJira(contents);
355
356 if (contents.indexOf("Incident Feedback") > -1) {
357 failWithReportInfo(contents, linkLocator, message);
358 }
359
360 if (contents.indexOf("Incident Report") > -1) { // KIM incident report
361 failWithReportInfoForKim(contents, linkLocator, message);
362 }
363
364 SeleneseTestBase.fail("\nIncident report detected " + message + "\n Unable to parse out details for the contents that triggered exception: " + deLinespace(
365 contents));
366 }
367
368 private static void failWithReportInfo(String contents, String linkLocator, String message) {
369 final String incidentReportInformation = extractIncidentReportInfo(contents, linkLocator, message);
370 SeleneseTestBase.fail(incidentReportInformation);
371 }
372
373 private static String extractIncidentReportInfo(String contents, String linkLocator, String message) {
374 String chunk = contents.substring(contents.indexOf("Incident Feedback"), contents.lastIndexOf("</div>") );
375 String docId = chunk.substring(chunk.lastIndexOf("Document Id"), chunk.indexOf("View Id"));
376 docId = docId.substring(0, docId.indexOf("</span>"));
377 docId = docId.substring(docId.lastIndexOf(">") + 2, docId.length());
378
379 String viewId = chunk.substring(chunk.lastIndexOf("View Id"), chunk.indexOf("Error Message"));
380 viewId = viewId.substring(0, viewId.indexOf("</span>"));
381 viewId = viewId.substring(viewId.lastIndexOf(">") + 2, viewId.length());
382
383 String stackTrace = chunk.substring(chunk.lastIndexOf("(only in dev mode)"), chunk.length());
384 stackTrace = stackTrace.substring(stackTrace.indexOf("<span id=\"") + 3, stackTrace.length());
385 stackTrace = stackTrace.substring(stackTrace.indexOf("\">") + 2, stackTrace.indexOf("</span>"));
386
387 return "\nIncident report "+ message+ " navigating to "+ linkLocator+ " : View Id: "+ viewId.trim()+ " Doc Id: "+ docId.trim()+ "\nStackTrace: "+ stackTrace.trim();
388 }
389
390 public static String deLinespace(String contents) {
391 while (contents.contains("\n\n")) {
392 contents = contents.replaceAll("\n\n", "\n");
393 }
394
395 return contents;
396 }
397 }