package com.codeborne.selenide; import com.codeborne.selenide.ex.DialogTextMismatch; import com.codeborne.selenide.ex.JavaScriptErrorsFound; import com.codeborne.selenide.impl.*; import org.openqa.selenium.*; import org.openqa.selenium.interactions.Actions; import org.openqa.selenium.logging.LogEntry; import org.openqa.selenium.support.PageFactory; import org.openqa.selenium.support.ui.FluentWait; import java.lang.reflect.Constructor; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import static com.codeborne.selenide.Configuration.captureJavascriptErrors; import static com.codeborne.selenide.Configuration.dismissModalDialogs; import static com.codeborne.selenide.Configuration.timeout; import static com.codeborne.selenide.WebDriverRunner.*; import static com.codeborne.selenide.impl.WebElementWrapper.wrap; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.openqa.selenium.support.ui.ExpectedConditions.alertIsPresent; /** * The main starting point of Selenide. * * You start with methods {@link #open(String)} for opening the tested application page and * {@link #$(String)} for searching web elements. */ public class Selenide { private static final Logger log = Logger.getLogger(Selenide.class.getName()); public static Navigator navigator = new Navigator(); /** * The main starting point in your tests. * Open a browser window with given URL. * * If browser window was already opened before, it will be reused. * * Don't bother about closing the browser - it will be closed automatically when all your tests are done. * * @param relativeOrAbsoluteUrl * If not starting with "http://" or "https://" or "file://", it's considered to be relative URL. * In this case, it's prepended by baseUrl */ public static void open(String relativeOrAbsoluteUrl) { open(relativeOrAbsoluteUrl, "", "" , ""); } /** * @see Selenide#open(String) */ public static void open(URL absoluteUrl) { open(absoluteUrl, "", "" , ""); } /** * The main starting point in your tests. * Open a browser window with given URL and credentials for basic authentication * * If browser window was already opened before, it will be reused. * * Don't bother about closing the browser - it will be closed automatically when all your tests are done. * * @param relativeOrAbsoluteUrl * @param domain * @param login * @param password * If not starting with "http://" or "https://" or "file://", it's considered to be relative URL. * In this case, it's prepended by baseUrl */ public static void open(String relativeOrAbsoluteUrl, String domain, String login, String password) { navigator.open(relativeOrAbsoluteUrl, domain, login, password); mockModalDialogs(); } /** * @see Selenide#open(URL, String, String, String) */ public static void open(URL absoluteUrl, String domain, String login, String password) { navigator.open(absoluteUrl, domain, login, password); mockModalDialogs(); } /** * Update the hash of the window location. * Useful to navigate in ajax apps without reloading the page, since open(url) makes a full page reload. * * @param hash value for window.location.hash - Accept either "#hash" or "hash". */ public static void updateHash(String hash) { String localHash = (hash.charAt(0) == '#') ? hash.substring(1) : hash; executeJavaScript("window.location.hash='" + localHash + "'"); } private static boolean doDismissModalDialogs() { return !supportsModalDialogs() || dismissModalDialogs; } private static void mockModalDialogs() { if (doDismissModalDialogs()) { String jsCode = " window._selenide_modalDialogReturnValue = true;\n" + " window.alert = function(message) {};\n" + " window.confirm = function(message) {\n" + " return window._selenide_modalDialogReturnValue;\n" + " };"; try { executeJavaScript(jsCode); } catch (UnsupportedOperationException cannotExecuteJsAgainstPlainTextPage) { log.warning(cannotExecuteJsAgainstPlainTextPage.toString()); } } } /** * Open a web page and create PageObject for it. * @return PageObject of given class */ public static <PageObjectClass> PageObjectClass open(String relativeOrAbsoluteUrl, Class<PageObjectClass> pageObjectClassClass) { return open(relativeOrAbsoluteUrl, "", "", "", pageObjectClassClass); } /** * Open a web page and create PageObject for it. * @return PageObject of given class */ public static <PageObjectClass> PageObjectClass open(URL absoluteUrl, Class<PageObjectClass> pageObjectClassClass) { return open(absoluteUrl, "", "", "", pageObjectClassClass); } /** * Open a web page using Basic Auth credentials and create PageObject for it. * @return PageObject of given class */ public static <PageObjectClass> PageObjectClass open(String relativeOrAbsoluteUrl, String domain, String login, String password, Class<PageObjectClass> pageObjectClassClass) { open(relativeOrAbsoluteUrl, domain, login, password); return page(pageObjectClassClass); } /** * Open a web page using Basic Auth credentials and create PageObject for it. * @return PageObject of given class */ public static <PageObjectClass> PageObjectClass open(URL absoluteUrl, String domain, String login, String password, Class<PageObjectClass> pageObjectClassClass) { open(absoluteUrl, domain, login, password); return page(pageObjectClassClass); } /** * Close the browser if it's open */ public static void close() { closeWebDriver(); } /** * Reload current page */ public static void refresh() { navigator.open(url()); } /** * Navigate browser back to previous page */ public static void back() { navigator.back(); } /** * Navigate browser forward to next page */ public static void forward() { navigator.forward(); } /** * * @return title of the page */ public static String title() { return getWebDriver().getTitle(); } /** * Not recommended. Test should not sleep, but should wait for some condition instead. * @param milliseconds Time to sleep in milliseconds */ public static void sleep(long milliseconds) { try { Thread.sleep(milliseconds); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } } /** * Take the screenshot of current page and save to file fileName.html and fileName.png * @param fileName Name of file (without extension) to save HTML and PNG to * @return The name of resulting file */ public static String screenshot(String fileName) { return Screenshots.takeScreenShot(fileName); } /** * Wrap standard Selenium WebElement into SelenideElement * to use additional methods like shouldHave(), selectOption() etc. * * @param webElement standard Selenium WebElement * @return given WebElement wrapped into SelenideElement */ public static SelenideElement $(WebElement webElement) { return wrap(webElement); } /** * Locates the first element matching given CSS selector * ATTENTION! This method doesn't start any search yet! * @param cssSelector any CSS selector like "input[name='first_name']" or "#messages .new_message" * @return SelenideElement */ public static SelenideElement $(String cssSelector) { return getElement(By.cssSelector(cssSelector)); } /** * Locates the first element matching given XPATH expression * ATTENTION! This method doesn't start any search yet! * @param xpathExpression any XPATH expression //*[@id='value'] //E[contains(@A, 'value')] * @return SelenideElement which locates elements via XPath */ public static SelenideElement $x(String xpathExpression) { return getElement(By.xpath(xpathExpression)); } /** * Locates the first element matching given CSS selector * ATTENTION! This method doesn't start any search yet! * @param seleniumSelector any Selenium selector like By.id(), By.name() etc. * @return SelenideElement */ public static SelenideElement $(By seleniumSelector) { return getElement(seleniumSelector); } /** * @see #getElement(By, int) */ public static SelenideElement $(By seleniumSelector, int index) { return getElement(seleniumSelector, index); } /** * @deprecated please use $(parent).$(String) which is the same * (method will not be removed until 4.x or later) * @see #$(String) * * Locates the first element matching given CSS selector * ATTENTION! This method doesn't start any search yet! * @param parent the WebElement to search elements in * @param cssSelector any CSS selector like "input[name='first_name']" or "#messages .new_message" * @return SelenideElement */ @Deprecated public static SelenideElement $(WebElement parent, String cssSelector) { return ElementFinder.wrap($(parent), By.cssSelector(cssSelector), 0); } /** * Locates the Nth element matching given criteria * ATTENTION! This method doesn't start any search yet! * @param cssSelector any CSS selector like "input[name='first_name']" or "#messages .new_message" * @param index 0..N * @return SelenideElement */ public static SelenideElement $(String cssSelector, int index) { return ElementFinder.wrap(null, By.cssSelector(cssSelector), index); } /** * @deprecated please use $(parent).$(String, int) which is the same * (method will not be removed until 4.x or later) * @see #$(String, int) * * Locates the Nth element matching given criteria * ATTENTION! This method doesn't start any search yet! * @param parent the WebElement to search elements in * @param cssSelector any CSS selector like "input[name='first_name']" or "#messages .new_message" * @param index 0..N * @return SelenideElement */ @Deprecated public static SelenideElement $(WebElement parent, String cssSelector, int index) { return ElementFinder.wrap($(parent), By.cssSelector(cssSelector), index); } /** * @deprecated please use $(parent).$(By) which is the same * (method will not be removed until 4.x or later) * @see #$(By) * * Locates the first element matching given criteria * ATTENTION! This method doesn't start any search yet! * @param parent the WebElement to search elements in * @param seleniumSelector any Selenium selector like By.id(), By.name() etc. * @return SelenideElement */ @Deprecated public static SelenideElement $(WebElement parent, By seleniumSelector) { return ElementFinder.wrap($(parent), seleniumSelector, 0); } /** * @deprecated please use $(parent).$(By, int) which is the same * (method will not be removed until 4.x or later) * @see #$(By, int) * * Locates the Nth element matching given criteria * ATTENTION! This method doesn't start any search yet! * @param parent the WebElement to search elements in * @param seleniumSelector any Selenium selector like By.id(), By.name() etc. * @param index 0..N * @return SelenideElement */ @Deprecated public static SelenideElement $(WebElement parent, By seleniumSelector, int index) { return ElementFinder.wrap($(parent), seleniumSelector, index); } /** * Initialize collection with Elements * @param elements * @return */ public static ElementsCollection $$(Collection<? extends WebElement> elements) { return new ElementsCollection(new WebElementsCollectionWrapper(elements)); } /** * Locates all elements matching given CSS selector. * ATTENTION! This method doesn't start any search yet! * Methods returns an ElementsCollection which is a list of WebElement objects that can be iterated, * and at the same time is implementation of WebElement interface, * meaning that you can call methods .sendKeys(), click() etc. on it. * * @param cssSelector any CSS selector like "input[name='first_name']" or "#messages .new_message" * @return empty list if element was no found */ public static ElementsCollection $$(String cssSelector) { return new ElementsCollection(new BySelectorCollection(By.cssSelector(cssSelector))); } /** * Locates all elements matching given XPATH expression. * ATTENTION! This method doesn't start any search yet! * Methods returns an ElementsCollection which is a list of WebElement objects that can be iterated, * and at the same time is implementation of WebElement interface, * meaning that you can call methods .sendKeys(), click() etc. on it. * @param xpathExpression any XPATH expression //*[@id='value'] //E[contains(@A, 'value')] * @return ElementsCollection which locates elements via XPath */ public static ElementsCollection $$x(String xpathExpression) { return new ElementsCollection(new BySelectorCollection(By.xpath(xpathExpression))); } /** * Locates all elements matching given CSS selector. * ATTENTION! This method doesn't start any search yet! * Methods returns an ElementsCollection which is a list of WebElement objects that can be iterated, * and at the same time is implementation of WebElement interface, * meaning that you can call methods .sendKeys(), click() etc. on it. * * @param seleniumSelector any Selenium selector like By.id(), By.name() etc. * @return empty list if element was no found */ public static ElementsCollection $$(By seleniumSelector) { return new ElementsCollection(new BySelectorCollection(seleniumSelector)); } /** * @deprecated please use $(parent).$$(String) which is the same * (method will not be removed until 4.x or later) * @see #$$(String) * * Locates all elements matching given CSS selector inside given parent element * ATTENTION! This method doesn't start any search yet! * Methods returns an ElementsCollection which is a list of WebElement objects that can be iterated, * and at the same time is implementation of WebElement interface, * meaning that you can call methods .sendKeys(), click() etc. on it. * * @param parent the WebElement to search elements in * @param cssSelector any CSS selector like "input[name='first_name']" or "#messages .new_message" * @return empty list if element was no found */ @Deprecated public static ElementsCollection $$(WebElement parent, String cssSelector) { return new ElementsCollection(new BySelectorCollection(parent, By.cssSelector(cssSelector))); } /** * @deprecated please use $(parent).$$(By) which is the same * (method will not be removed until 4.x or later) * @see #$$(By) * * Locates all elements matching given criteria inside given parent element * ATTENTION! This method doesn't start any search yet! * @see Selenide#$$(WebElement, String) */ @Deprecated public static ElementsCollection $$(WebElement parent, By seleniumSelector) { return new ElementsCollection(new BySelectorCollection(parent, seleniumSelector)); } /** * Locates the first element matching given criteria * ATTENTION! This method doesn't start any search yet! * @param criteria instance of By: By.id(), By.className() etc. * @return SelenideElement */ public static SelenideElement getElement(By criteria) { return ElementFinder.wrap(null, criteria, 0); } /** * Locates the Nth element matching given criteria * ATTENTION! This method doesn't start any search yet! * @param criteria instance of By: By.id(), By.className() etc. * @param index 0..N * @return SelenideElement */ public static SelenideElement getElement(By criteria, int index) { return ElementFinder.wrap(null, criteria, index); } /** * Locates all elements matching given CSS selector * ATTENTION! This method doesn't start any search yet! * @param criteria instance of By: By.id(), By.className() etc. * @return empty list if element was no found */ public static ElementsCollection getElements(By criteria) { return $$(criteria); } /** * Executes JavaScript */ @SuppressWarnings("unchecked") public static <T> T executeJavaScript(String jsCode, Object... arguments) { return (T) ((JavascriptExecutor) getWebDriver()).executeScript(jsCode, arguments); } /** * @deprecated Not recommended. Use method {@code $(radioField).selectRadio(value);} instead * * Select radio field by value * @param radioField any By selector for finding radio field * @param value value to select (should match an attribute "value") * @return the selected radio field */ @Deprecated public static SelenideElement selectRadio(By radioField, String value) { return $(radioField).selectRadio(value); } /** * Returns selected element in radio group * @param radioField * @return null, if nothing selected */ public static SelenideElement getSelectedRadio(By radioField) { for (WebElement radio : $$(radioField)) { if (radio.getAttribute("checked") != null) { return wrap(radio); } } return null; } /** * Mock confirm dialog that return given value * @param confirmReturnValue true = OK, false = CANCEL */ public static void onConfirmReturn(boolean confirmReturnValue) { if (doDismissModalDialogs()) { executeJavaScript("window._selenide_modalDialogReturnValue = " + confirmReturnValue + ';'); } } /** * Accept (Click "Yes" or "Ok") in the confirmation dialog (javascript 'alert' or 'confirm'). * @return actual dialog text */ public static String confirm() { return confirm(null); } /** * Accept (Click "Yes" or "Ok") in the confirmation dialog (javascript 'alert' or 'confirm'). * Method does nothing in case of HtmlUnit browser (since HtmlUnit does not support alerts). * * @param expectedDialogText if not null, check that confirmation dialog displays this message (case-sensitive) * @throws DialogTextMismatch if confirmation message differs from expected message * @return actual dialog text */ public static String confirm(String expectedDialogText) { if (!doDismissModalDialogs()) { Alert alert = Wait().until(alertIsPresent()); String actualDialogText = alert.getText(); alert.accept(); checkDialogText(expectedDialogText, actualDialogText); return actualDialogText; } return null; } /** * Dismiss (click "No" or "Cancel") in the confirmation dialog (javascript 'alert' or 'confirm'). * @return actual dialog text */ public static String dismiss() { return dismiss(null); } /** * Dismiss (click "No" or "Cancel") in the confirmation dialog (javascript 'alert' or 'confirm'). * Method does nothing in case of HtmlUnit browser (since HtmlUnit does not support alerts). * * @param expectedDialogText if not null, check that confirmation dialog displays this message (case-sensitive) * @throws DialogTextMismatch if confirmation message differs from expected message * @return actual dialog text */ public static String dismiss(String expectedDialogText) { if (!doDismissModalDialogs()) { Alert alert = Wait().until(alertIsPresent()); String actualDialogText = alert.getText(); alert.dismiss(); checkDialogText(expectedDialogText, actualDialogText); return actualDialogText; } return null; } private static void checkDialogText(String expectedDialogText, String actualDialogText) { if (expectedDialogText != null && !expectedDialogText.equals(actualDialogText)) { Screenshots.takeScreenShot(Selenide.class.getName(), Thread.currentThread().getName()); throw new DialogTextMismatch(actualDialogText, expectedDialogText); } } /** * Switch to window/tab/frame/parentFrame/innerFrame/alert. * Allows switching to window by title, index, name etc. * * Similar to org.openqa.selenium.WebDriver#switchTo(), but all methods wait until frame/window/alert * appears if it's not visible yet (like other Selenide methods). * * @return SelenideTargetLocator */ public static SelenideTargetLocator switchTo() { return new SelenideTargetLocator(getWebDriver().switchTo()); } /** * * @return WebElement, not SelenideElement! which has focus on it */ public static WebElement getFocusedElement() { return (WebElement) executeJavaScript("return document.activeElement"); } /** * Create a Page Object instance. * @see PageFactory#initElements(WebDriver, Class) */ public static <PageObjectClass> PageObjectClass page(Class<PageObjectClass> pageObjectClass) { try { Constructor<PageObjectClass> constructor = pageObjectClass.getDeclaredConstructor(); constructor.setAccessible(true); return page(constructor.newInstance()); } catch (Exception e) { throw new RuntimeException("Failed to create new instance of " + pageObjectClass, e); } } /** * Create a Page Object instance. * @see PageFactory#initElements(WebDriver, Class) */ public static <PageObjectClass, T extends PageObjectClass> PageObjectClass page(T pageObject) { SelenidePageFactory.initElements(new SelenideFieldDecorator(getWebDriver()), pageObject); return pageObject; } /** * Create a org.openqa.selenium.support.ui.FluentWait instance with Selenide timeout/polling. * * Sample usage: * {@code * Wait().until(invisibilityOfElementLocated(By.id("magic-id"))); * } * * @return instance of org.openqa.selenium.support.ui.FluentWait */ public static FluentWait<WebDriver> Wait() { return new FluentWait<>(getWebDriver()) .withTimeout(timeout, MILLISECONDS) .pollingEvery(Configuration.pollingInterval, MILLISECONDS); } /** * With this method you can use Selenium Actions like described in the * <a href="http://code.google.com/p/selenium/wiki/AdvancedUserInteractions">AdvancedUserInteractions</a> page. * * <pre> * actions() * .sendKeys($(By.name("rememberMe")), "John") * .click($(#rememberMe")) * .click($(byText("Login"))) * .build() * .perform(); * </pre> */ public static Actions actions() { return new Actions(getWebDriver()); } /** * Get JavaScript errors that happened on this page. * * Format can differ from browser to browser: * - Uncaught ReferenceError: $ is not defined at http://localhost:35070/page_with_js_errors.html:8 * - ReferenceError: Can't find variable: $ at http://localhost:8815/page_with_js_errors.html:8 * * Function returns nothing if the page has its own "window.onerror" handler. * * @return list of error messages. Returns empty list if webdriver is not started properly. */ public static List<String> getJavascriptErrors() { if (!captureJavascriptErrors) { return emptyList(); } else if (!hasWebDriverStarted()) { return emptyList(); } else if (!supportsJavascript()) { return emptyList(); } try { Object errors = executeJavaScript("return window._selenide_jsErrors"); if (errors == null) { return emptyList(); } else if (errors instanceof List) { return errorsFromList((List<Object>) errors); } else if (errors instanceof Map) { return errorsFromMap((Map<Object, Object>) errors); } else { return asList(errors.toString()); } } catch (WebDriverException | UnsupportedOperationException cannotExecuteJs) { log.warning(cannotExecuteJs.toString()); return emptyList(); } } private static List<String> errorsFromList(List<Object> errors) { if (errors.isEmpty()) { return emptyList(); } List<String> result = new ArrayList<>(errors.size()); for (Object error : errors) { result.add(error.toString()); } return result; } private static List<String> errorsFromMap(Map<Object, Object> errors) { if (errors.isEmpty()) { return emptyList(); } List<String> result = new ArrayList<>(errors.size()); for (Map.Entry error : errors.entrySet()) { result.add(error.getKey() + ": " + error.getValue()); } return result; } /** * Check if there is not JS errors on the page * @throws JavaScriptErrorsFound */ public static void assertNoJavascriptErrors() throws JavaScriptErrorsFound { List<String> jsErrors = getJavascriptErrors(); if (jsErrors != null && !jsErrors.isEmpty()) { throw new JavaScriptErrorsFound(jsErrors); } } /** * Zoom current page (in or out). * @param factor e.g. 1.1 or 2.0 or 0.5 */ public static void zoom(double factor) { executeJavaScript( "document.body.style.transform = 'scale(' + arguments[0] + ')';" + "document.body.style.transformOrigin = '0 0';", factor ); } /** * Same as com.codeborne.selenide.Selenide#getWebDriverLogs(java.lang.String, java.util.logging.Level) */ public static List<String> getWebDriverLogs(String logType) { return getWebDriverLogs(logType, Level.ALL); } /** * Getting and filtering of the WebDriver logs for specified LogType by specified logging level * <br /> * For example to get WebDriver Browser's console output (including JS info, warnings, errors, etc. messages) * you can use: * <br /> * <pre> * {@code * for(String logEntry : getWebDriverLogs(LogType.BROWSER, Level.ALL)) { * Reporter.log(logEntry + "<br />"); * } * } * </pre> * <br /> * Be aware that currently "manage().logs()" is in the Beta stage, but it is beta-then-nothing :) * <br /> * List of the unsupported browsers and issues: * <br /> * http://bit.ly/RZcmrM * <br /> * http://bit.ly/1nZTaqu * <br /> * * @param logType WebDriver supported log types * @param logLevel logging level that will be used to control logging output * @return list of log entries * @see org.openqa.selenium.logging.LogType, * @see java.util.logging.Level */ public static List<String> getWebDriverLogs(String logType, Level logLevel) { return listToString(getLogEntries(logType, logLevel)); } /** * Clear browser cookies. * * In case if you are trying to avoid restarting browser * */ public static void clearBrowserCookies() { getWebDriver().manage().deleteAllCookies(); } /** * Clear browser local storage. * * In case if you need to be sure that browser's localStorage is empty */ public static void clearBrowserLocalStorage() { executeJavaScript("localStorage.clear();"); } private static List<LogEntry> getLogEntries(String logType, Level logLevel) { try { return getWebDriver().manage().logs().get(logType).filter(logLevel); } catch (UnsupportedOperationException ignore) { return emptyList(); } } private static <T> List<String> listToString(List<T> objects) { if (objects == null || objects.isEmpty()) { return emptyList(); } List<String> result = new ArrayList<>(objects.size()); for (T object : objects) { result.add(object.toString()); } return result; } }