From 5da8f5f052dc0b44245014ab6b82d46f222fa705 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Sun, 31 Jul 2016 20:21:11 +0200 Subject: [PATCH] fix selenium integration tests and introduce page object pattern --- scm-webapp/pom.xml | 29 +- .../scm/selenium/AuthenticationITCase.java | 24 +- ...RUDITCase.java => RepositoriesITCase.java} | 82 +++--- .../scm/selenium/SeleniumITCaseBase.java | 71 +++++ .../sonia/scm/selenium/SeleniumTestBase.java | 271 ------------------ .../sonia/scm/selenium/page/BasePage.java | 168 +++++++++++ .../sonia/scm/selenium/page/LoginPage.java | 89 ++++++ .../sonia/scm/selenium/page/MainPage.java | 95 ++++++ .../java/sonia/scm/selenium/page/Pages.java | 113 ++++++++ .../selenium/page/RepositoriesAddPage.java | 136 +++++++++ .../scm/selenium/page/RepositoriesPage.java | 103 +++++++ .../scm/selenium/page/RepositoryPage.java | 77 +++++ 12 files changed, 930 insertions(+), 328 deletions(-) rename scm-webapp/src/test/java/sonia/scm/selenium/{RepositoryCRUDITCase.java => RepositoriesITCase.java} (66%) create mode 100644 scm-webapp/src/test/java/sonia/scm/selenium/SeleniumITCaseBase.java delete mode 100644 scm-webapp/src/test/java/sonia/scm/selenium/SeleniumTestBase.java create mode 100644 scm-webapp/src/test/java/sonia/scm/selenium/page/BasePage.java create mode 100644 scm-webapp/src/test/java/sonia/scm/selenium/page/LoginPage.java create mode 100644 scm-webapp/src/test/java/sonia/scm/selenium/page/MainPage.java create mode 100644 scm-webapp/src/test/java/sonia/scm/selenium/page/Pages.java create mode 100644 scm-webapp/src/test/java/sonia/scm/selenium/page/RepositoriesAddPage.java create mode 100644 scm-webapp/src/test/java/sonia/scm/selenium/page/RepositoriesPage.java create mode 100644 scm-webapp/src/test/java/sonia/scm/selenium/page/RepositoryPage.java diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index b1b971af3e..1a629c71f9 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -326,13 +326,20 @@ ${selenium.version} test - + org.seleniumhq.selenium selenium-firefox-driver ${selenium.version} test + + + org.seleniumhq.selenium + htmlunit-driver + 2.21 + test + com.sun.jersey @@ -528,7 +535,7 @@ DEVELOPMENT target/scm-it default - 2.28.0 + 2.53.1 1.31 1.13.1 1.0 @@ -688,6 +695,24 @@ selenium + + + + org.apache.httpcomponents + httpclient + 4.3.2 + test + + + + xml-apis + xml-apis + 1.4.01 + test + + + + diff --git a/scm-webapp/src/test/java/sonia/scm/selenium/AuthenticationITCase.java b/scm-webapp/src/test/java/sonia/scm/selenium/AuthenticationITCase.java index a2b0d0fe6d..34013e7e76 100644 --- a/scm-webapp/src/test/java/sonia/scm/selenium/AuthenticationITCase.java +++ b/scm-webapp/src/test/java/sonia/scm/selenium/AuthenticationITCase.java @@ -34,25 +34,27 @@ package sonia.scm.selenium; //~--- non-JDK imports -------------------------------------------------------- +import sonia.scm.selenium.page.Pages; +import sonia.scm.selenium.page.MainPage; +import sonia.scm.selenium.page.LoginPage; +import static org.junit.Assert.*; import org.junit.Test; /** - * + * Authentication related selenium integration tests. + * * @author Sebastian Sdorra */ -public class AuthenticationITCase extends SeleniumTestBase -{ +public class AuthenticationITCase extends SeleniumITCaseBase { /** - * Method description - * - * - * @throws Exception + * Authenticates an user and call logout function. */ @Test - public void testAuthentication() throws Exception - { - login("scmadmin", "scmadmin"); - logout(); + public void testAuthentication() { + MainPage main = Pages.get(driver, LoginPage.class).login("scmadmin", "scmadmin"); + assertEquals("scmadmin", main.getUserInfo()); + main.logout(); } + } diff --git a/scm-webapp/src/test/java/sonia/scm/selenium/RepositoryCRUDITCase.java b/scm-webapp/src/test/java/sonia/scm/selenium/RepositoriesITCase.java similarity index 66% rename from scm-webapp/src/test/java/sonia/scm/selenium/RepositoryCRUDITCase.java rename to scm-webapp/src/test/java/sonia/scm/selenium/RepositoriesITCase.java index 64760ae774..05898f6529 100644 --- a/scm-webapp/src/test/java/sonia/scm/selenium/RepositoryCRUDITCase.java +++ b/scm-webapp/src/test/java/sonia/scm/selenium/RepositoriesITCase.java @@ -34,61 +34,55 @@ package sonia.scm.selenium; //~--- non-JDK imports -------------------------------------------------------- +import sonia.scm.selenium.page.Pages; +import sonia.scm.selenium.page.MainPage; +import sonia.scm.selenium.page.LoginPage; import org.junit.After; import org.junit.Before; import org.junit.Test; -import static org.junit.Assert.*; +import sonia.scm.repository.Repository; /** - * + * Repository related selenium integration tests. + * * @author Sebastian Sdorra */ -public class RepositoryCRUDITCase extends SeleniumTestBase -{ +public class RepositoriesITCase extends SeleniumITCaseBase { + private MainPage main; + /** - * Method description - * - */ - @After - public void after() - { - logout(); - } - - /** - * Method description - * - */ - @Test - public void createRepository() throws InterruptedException - { - waitAndClick("#repositoryAddButton"); - waitForPresence("input[name=name]").sendKeys("scm"); - select("#x-form-el-repositoryType img").click(); - waitAndClick("div.x-combo-list-item:nth-of-type(2)"); - type("input[name=contact]", "scmadmin@scm-manager.org"); - type("textarea[name=description]", "SCM-Manager"); - waitAndClick("div.x-panel-btns button:nth-of-type(1)"); - - String name = - waitForPresence( - "div.x-grid3-row-selected div.x-grid3-col-name").getText(); - - assertEquals("scm", name); - - waitAndClick("#repoRmButton button"); - waitAndClick("div.x-window button:nth-of-type(1)"); - } - - /** - * Method description - * + * Authenticates admin user, before each test. */ @Before - public void login() - { - login("scmadmin", "scmadmin"); + public void login() { + main = Pages.get(driver, LoginPage.class) + .login("scmadmin", "scmadmin"); + } + + /** + * Creates, select and removes a repository. + */ + @Test + public void createRepository() { + Repository repository = new Repository(); + repository.setName("scm"); + repository.setType("git"); + repository.setContact("scmadmin@scm-manager.org"); + repository.setDescription("SCM-Manager"); + + main.repositories() + .add(repository) + .select(repository.getName()) + .remove(); + } + + /** + * Logs the user out, after each test. + */ + @After + public void logout() { + main.logout(); } } diff --git a/scm-webapp/src/test/java/sonia/scm/selenium/SeleniumITCaseBase.java b/scm-webapp/src/test/java/sonia/scm/selenium/SeleniumITCaseBase.java new file mode 100644 index 0000000000..fde04d9cad --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/selenium/SeleniumITCaseBase.java @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ +package sonia.scm.selenium; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.firefox.FirefoxDriver; + +/** + * Base class for selenium integration tests. + * + * @author Sebastian Sdorra + */ +public class SeleniumITCaseBase { + + /** + * Selenium test driver. + */ + protected static WebDriver driver; + + /** + * Setup selenium test driver. + */ + @BeforeClass + public static void setUpDriver() { + // DesiredCapabilities capa = DesiredCapabilities.chrome(); + // capa.setBrowserName("firefox"); + // capa.setPlatform(Platform.ANY); + // RemoteWebDriver driver = new RemoteWebDriver(new URL("http://localhost:4444/wd/hub"), capa); + + driver = new FirefoxDriver(); + driver.get("http://localhost:8082/scm/index.html"); + } + + /** + * Closes the selenium test driver. + */ + @AfterClass + public static void tearDownDriver() { + driver.close(); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/selenium/SeleniumTestBase.java b/scm-webapp/src/test/java/sonia/scm/selenium/SeleniumTestBase.java deleted file mode 100644 index 1ae5e86b3d..0000000000 --- a/scm-webapp/src/test/java/sonia/scm/selenium/SeleniumTestBase.java +++ /dev/null @@ -1,271 +0,0 @@ -/** - * Copyright (c) 2010, Sebastian Sdorra - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. Neither the name of SCM-Manager; nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * http://bitbucket.org/sdorra/scm-manager - * - */ - - -package sonia.scm.selenium; - -//~--- non-JDK imports -------------------------------------------------------- - -import com.google.common.io.Files; - -import org.junit.After; -import org.junit.Before; - -import org.openqa.selenium.By; -import org.openqa.selenium.OutputType; -import org.openqa.selenium.TakesScreenshot; -import org.openqa.selenium.WebDriver; -import org.openqa.selenium.WebElement; -import org.openqa.selenium.firefox.FirefoxDriver; -import org.openqa.selenium.support.ui.ExpectedConditions; -import org.openqa.selenium.support.ui.WebDriverWait; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import static org.junit.Assert.*; - -//~--- JDK imports ------------------------------------------------------------ - -import java.io.File; -import java.io.IOException; - -import java.util.concurrent.TimeUnit; - -/** - * - * @author Sebastian Sdorra - */ -public class SeleniumTestBase -{ - - /** - * the logger for SeleniumTestBase - */ - private static final Logger logger = - LoggerFactory.getLogger(SeleniumTestBase.class); - - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @throws Exception - */ - @After - public void tearDown() throws Exception - { - driver.quit(); - } - - //~--- set methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @throws Exception - */ - @Before - public void setUp() throws Exception - { - driver = new FirefoxDriver(); - baseUrl = "http://localhost:8082/scm/"; - open("index.html"); - } - - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @param username - * @param password - */ - protected void login(String username, String password) - { - type("input[name=username]", username); - type("input[name=password]", password); - waitAndClick("#loginButton button"); - - String ue = waitForPresence("#scm-userinfo-tip").getText(); - - assertEquals(username, ue); - } - - /** - * Method description - * - */ - protected void logout() - { - waitAndClick("#navLogout a"); - } - - /** - * Method description - * - * - * @param url - */ - protected void open(String url) - { - driver.get(baseUrl + url); - pause(500, TimeUnit.MILLISECONDS); - } - - /** - * Method description - * - * - * @param value - * @param unit - */ - protected void pause(int value, TimeUnit unit) - { - driver.manage().timeouts().implicitlyWait(value, unit); - } - - /** - * Method description - * - * - * @param target - */ - protected void screenshot(String target) - { - screenshot(new File(target)); - } - - /** - * Method description - * - * - * @param target - */ - protected void screenshot(File target) - { - try - { - File scrFile = - ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE); - - Files.copy(scrFile, target); - } - catch (IOException ex) - { - logger.error("could not create screenshot", ex); - } - } - - /** - * Method description - * - * - * @param cssSelector - * - * @return - */ - protected WebElement select(String cssSelector) - { - WebElement element = driver.findElement(By.cssSelector(cssSelector)); - - assertNotNull(element); - - return element; - } - - /** - * Method description - * - * - * @param cssLocator - * @param value - */ - protected void type(String cssLocator, String value) - { - select(cssLocator).clear(); - select(cssLocator).sendKeys(value); - } - - /** - * Method description - * - * - * @param query - */ - protected void waitAndClick(String query) - { - waitToBeClickable(query).click(); - } - - /** - * Method description - * - * - * @param query - * - * @return - */ - protected WebElement waitForPresence(String query) - { - WebDriverWait wait = new WebDriverWait(driver, 5); - - return wait.until( - ExpectedConditions.presenceOfElementLocated(By.cssSelector(query))); - } - - /** - * Method description - * - * - * @param query - * - * @return - */ - protected WebElement waitToBeClickable(String query) - { - WebDriverWait wait = new WebDriverWait(driver, 5); - - return wait.until( - ExpectedConditions.elementToBeClickable(By.cssSelector(query))); - } - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - protected WebDriver driver; - - /** Field description */ - private String baseUrl; -} diff --git a/scm-webapp/src/test/java/sonia/scm/selenium/page/BasePage.java b/scm-webapp/src/test/java/sonia/scm/selenium/page/BasePage.java new file mode 100644 index 0000000000..28c27457a8 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/selenium/page/BasePage.java @@ -0,0 +1,168 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ +package sonia.scm.selenium.page; + +import com.google.common.base.Throwables; +import com.google.common.io.Files; +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.concurrent.TimeUnit; +import org.openqa.selenium.By; +import org.openqa.selenium.OutputType; +import org.openqa.selenium.TakesScreenshot; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.ui.ExpectedConditions; +import org.openqa.selenium.support.ui.WebDriverWait; + +/** + * Abstract selenium base page. + * + * @author Sebastian Sdorra + * + * @param

concrete page implementation + */ +public abstract class BasePage

{ + + /** + * Selenium test driver. + */ + protected final WebDriver driver; + + /** + * Constructs a new base page. + * + * @param driver selenium test driver + */ + protected BasePage(WebDriver driver) { + this.driver = driver; + } + + /** + * Performs a {@link Thread#sleep(long)} for the given timeout. + * + * @param time timeout + * @param unit time unit of timeout + */ + protected void sleep(long time, TimeUnit unit) { + try { + unit.sleep(time); + } catch (InterruptedException ex) { + throw Throwables.propagate(ex); + } + } + + /** + * Wait for the element until it is clickable. + * + * @param by element selector + * + * @return web element + */ + protected WebElement waitToBeClickable(By by){ + return waitToBeClickable(driver.findElement(by)); + } + + /** + * Waits for the element until it is clickable. + * + * @param element web element + * + * @return web element + */ + protected WebElement waitToBeClickable(WebElement element) { + WebDriverWait wait = new WebDriverWait(driver, 5); + + return wait.until(ExpectedConditions.elementToBeClickable(element)); + } + + /** + * Waits until the element is present. + * + * @param by element locator + * + * @return web element + */ + protected WebElement waitFor(By by){ + WebDriverWait wait = new WebDriverWait(driver, 1); + return wait.until(ExpectedConditions.presenceOfElementLocated(by)); + } + + /** + * Waits until the elements are present. + * + * @param by element selector + * + * @return list of web elements + */ + protected List waitForAll(By by){ + WebDriverWait wait = new WebDriverWait(driver, 1); + return wait.until(ExpectedConditions.presenceOfAllElementsLocatedBy(by)); + } + + /** + * Creates a screenshot of the current browser content and stores it at the given path. + * + * @param target target file path + * + * @return {@code this} + */ + public P screenshot(String target) { + return screenshot(new File(target)); + } + + /** + * Creates a screenshot of the current browser content and stores it at the file. + * + * @param target target file + * + * @return {@code this} + */ + public P screenshot(File target) { + try { + File scrFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE); + + Files.copy(scrFile, target); + } catch (IOException ex) { + throw Throwables.propagate(ex); + } + return self(); + } + + /** + * Returns {@code this}. + * + * @return {@code this} + */ + protected abstract P self(); + +} diff --git a/scm-webapp/src/test/java/sonia/scm/selenium/page/LoginPage.java b/scm-webapp/src/test/java/sonia/scm/selenium/page/LoginPage.java new file mode 100644 index 0000000000..cf9d231510 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/selenium/page/LoginPage.java @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ +package sonia.scm.selenium.page; + +import java.util.concurrent.TimeUnit; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.FindBy; + +/** + * Page object for the scm-manager login page. + * + * @author Sebastian Sdorra + */ +public class LoginPage extends BasePage { + + @FindBy(css = "input[name=username]") + private WebElement usernameInput; + + @FindBy(css = "input[name=password]") + private WebElement passwordInput; + + @FindBy(css = "#loginButton button") + private WebElement loginButton; + + /** + * Constructs a new page. This constructor should only be called from {@link Pages}. + * + * @param driver selenium test driver + */ + LoginPage(WebDriver driver) { + super(driver); + } + + @Override + protected LoginPage self() { + return this; + } + + /** + * Authenticates the user and returns the {@link MainPage}. + * + * @param username username + * @param password password + * + * @return {@link MainPage} after successful authentication + */ + public MainPage login(String username, String password) { + usernameInput.clear(); + usernameInput.sendKeys(username); + + passwordInput.clear(); + passwordInput.sendKeys(password); + + sleep(250, TimeUnit.MILLISECONDS); + + waitToBeClickable(loginButton).click(); + + return Pages.get(driver, MainPage.class); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/selenium/page/MainPage.java b/scm-webapp/src/test/java/sonia/scm/selenium/page/MainPage.java new file mode 100644 index 0000000000..42fc807e67 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/selenium/page/MainPage.java @@ -0,0 +1,95 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ +package sonia.scm.selenium.page; + +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.FindBy; + +/** + * Page object for scm-manager's main page. + * + * @author Sebastian Sdorra + */ +public class MainPage extends BasePage { + + @FindBy(css = "#navLogout a") + private WebElement logoutLink; + + @FindBy(linkText = "Repositories") + private WebElement repositoriesLink; + + @FindBy(css = "#scm-userinfo-tip") + private WebElement userInfoTip; + + /** + * Constructs a new page. This constructor should only be called from {@link Pages}. + * + * @param driver selenium test driver + */ + MainPage(WebDriver driver) { + super(driver); + } + + @Override + protected MainPage self() { + return this; + } + + /** + * Returns the name of the current authenticated user from the user info tip. + * + * @return name of the current authenticated user + */ + public String getUserInfo(){ + return userInfoTip.getText(); + } + + /** + * Navigates to the repositories page and returns the page object for this page. + * + * @return page object for repositories page + */ + public RepositoriesPage repositories(){ + repositoriesLink.click(); + return Pages.get(driver, RepositoriesPage.class); + } + + /** + * Logs the current user out. + * + * @return page object for the login + */ + public LoginPage logout(){ + waitToBeClickable(logoutLink).click(); + return Pages.get(driver, LoginPage.class); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/selenium/page/Pages.java b/scm-webapp/src/test/java/sonia/scm/selenium/page/Pages.java new file mode 100644 index 0000000000..b8d85ea08b --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/selenium/page/Pages.java @@ -0,0 +1,113 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ +package sonia.scm.selenium.page; + +import com.google.common.base.Throwables; +import com.google.common.collect.Lists; +import java.lang.reflect.Constructor; +import java.util.Arrays; +import java.util.List; +import org.openqa.selenium.By; +import org.openqa.selenium.SearchContext; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.PageFactory; +import org.openqa.selenium.support.pagefactory.DefaultElementLocatorFactory; +import org.openqa.selenium.support.ui.ExpectedConditions; +import org.openqa.selenium.support.ui.WebDriverWait; + +/** + * Helper class for selenium page objects. + * + * @author Sebastian Sdorra + */ +public final class Pages { + + private Pages() { + } + + /** + * Creates an instance of the given page object. + * + * @param page object type + * @param driver selenium driver + * @param clazz page object type + * @param otherArguments other constructor arguments + * + * @return instance of page object + */ + public static T get(WebDriver driver, Class clazz, Object... otherArguments) + { + T page = null; + try { + List> argumentTypes = Lists.newArrayList(); + argumentTypes.add(WebDriver.class); + for (Object argument : otherArguments) { + argumentTypes.add(argument.getClass()); + } + + List arguments = Lists.newArrayList(); + arguments.add(driver); + arguments.addAll(Arrays.asList(otherArguments)); + + Constructor constructor = clazz.getDeclaredConstructor( + argumentTypes.toArray(new Class[argumentTypes.size()]) + ); + page = constructor.newInstance(arguments.toArray(new Object[arguments.size()])); + + PageFactory.initElements(new DefaultElementLocatorFactory(new WaitingSearchContext(driver)), page); + } catch (Exception ex) { + throw Throwables.propagate(ex); + } + return page; + } + + private static class WaitingSearchContext implements SearchContext { + + private final WebDriver driver; + private final WebDriverWait wait; + + private WaitingSearchContext(WebDriver driver) { + this.driver = driver; + this.wait = new WebDriverWait(driver, 1); + } + + @Override + public List findElements(By by) { + return wait.until(ExpectedConditions.presenceOfAllElementsLocatedBy(by)); + } + + @Override + public WebElement findElement(By by) { + return wait.until(ExpectedConditions.presenceOfElementLocated(by)); + } + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/selenium/page/RepositoriesAddPage.java b/scm-webapp/src/test/java/sonia/scm/selenium/page/RepositoriesAddPage.java new file mode 100644 index 0000000000..f9f00056ae --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/selenium/page/RepositoriesAddPage.java @@ -0,0 +1,136 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ +package sonia.scm.selenium.page; + +import com.google.common.base.Objects; +import java.util.List; +import org.openqa.selenium.By; +import org.openqa.selenium.JavascriptExecutor; +import org.openqa.selenium.NotFoundException; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.FindBy; +import org.openqa.selenium.support.ui.WebDriverWait; +import sonia.scm.repository.Repository; + +/** + * Page object for scm-manager's repository creation page. + * + * @author Sebastian Sdorra + */ +public class RepositoriesAddPage extends BasePage { + + @FindBy(css = "input[name=name]") + private WebElement nameInput; + + @FindBy(css = "input[name=contact]") + private WebElement contactInput; + + @FindBy(css = "#x-form-el-repositoryType img") + private WebElement typeInput; + + @FindBy(css = "textarea[name=description]") + private WebElement descriptionInput; + + @FindBy(css = "div.x-panel-btns button:nth-of-type(1)") + private WebElement okButton; + + private final RepositoriesPage repositoriesPage; + + /** + * Constructs a new page. This constructor should only be called from {@link Pages}. + * + * @param driver selenium test driver + * @param repositoriesPage repositories page object + */ + RepositoriesAddPage(WebDriver driver, RepositoriesPage repositoriesPage) { + super(driver); + this.repositoriesPage = repositoriesPage; + } + + @Override + protected RepositoriesAddPage self() { + return this; + } + + /** + * Creates a new {@link Repository}. + * + * @param repository repository for creation + * + * @return repositories overview page + */ + public RepositoriesPage add(Repository repository) { + nameInput.sendKeys(repository.getName()); + + selectType(repository.getType()); + + contactInput.sendKeys(repository.getContact()); + descriptionInput.sendKeys(repository.getDescription()); + + waitToBeClickable(okButton).click(); + + return repositoriesPage; + } + + private void selectType(String type) { + typeInput.click(); + + String displayName = findDisplayName(type); + + WebDriverWait wait = new WebDriverWait(driver, 1); + List elements = waitForAll(By.className("x-combo-list-item")); + WebElement typeElement = null; + for (WebElement te : elements){ + if (te.getText().trim().equalsIgnoreCase(displayName)){ + typeElement = te; + break; + } + } + + if (typeElement == null){ + throw new NotFoundException("could not find type element with type " + displayName); + } + + typeElement.click(); + } + + private String findDisplayName(String type) { + String displayName = null; + if (driver instanceof JavascriptExecutor) { + // TODO seams not to work + String script = "Sonia.repository.getTypeByName('" + type + "').displayName;"; + displayName = (String) ((JavascriptExecutor)driver).executeScript(script); + } + return Objects.firstNonNull(displayName, type); + } + +} diff --git a/scm-webapp/src/test/java/sonia/scm/selenium/page/RepositoriesPage.java b/scm-webapp/src/test/java/sonia/scm/selenium/page/RepositoriesPage.java new file mode 100644 index 0000000000..fef75683ff --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/selenium/page/RepositoriesPage.java @@ -0,0 +1,103 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ +package sonia.scm.selenium.page; + +import java.util.List; +import java.util.Locale; +import org.openqa.selenium.By; +import org.openqa.selenium.NotFoundException; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.FindBy; +import sonia.scm.repository.Repository; + +/** + * Page object for scm-manager's repositories overview page. + * + * @author Sebastian Sdorra + */ +public class RepositoriesPage extends BasePage { + + @FindBy(id = "repositoryAddButton") + private WebElement addButton; + + /** + * Constructs a new page. This constructor should only be called from {@link Pages}. + * + * @param driver selenium test driver + */ + RepositoriesPage(WebDriver driver) { + super(driver); + } + + @Override + protected RepositoriesPage self() { + return this; + } + + /** + * Creates a new {@link Repository}. + * + * @param repository repository for creation + * + * @return {@link this} + */ + public RepositoriesPage add(Repository repository){ + addButton.click(); + RepositoriesAddPage addPage = Pages.get(driver, RepositoriesAddPage.class, this); + return addPage.add(repository); + } + + /** + * Selects the repository with the given name and returns the detail page object for the selected repository. + * + * @param repositoryName name of the repository + * + * @return page object for selected repository + */ + public RepositoryPage select(String repositoryName){ + WebElement repositoryNameColumn = null; + + List elements = waitForAll(By.className("x-grid3-col-name")); + for (WebElement element : elements){ + if (element.getText().trim().toLowerCase(Locale.ENGLISH).equals(repositoryName)){ + repositoryNameColumn = element; + break; + } + } + + if ( repositoryNameColumn == null ) { + throw new NotFoundException("could not find repository " + repositoryName); + } + + return Pages.get(driver, RepositoryPage.class, this); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/selenium/page/RepositoryPage.java b/scm-webapp/src/test/java/sonia/scm/selenium/page/RepositoryPage.java new file mode 100644 index 0000000000..d09a0debb5 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/selenium/page/RepositoryPage.java @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ +package sonia.scm.selenium.page; + +import org.openqa.selenium.By; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.FindBy; + +/** + * Page object for scm-manager's repository detail page. + * + * @author Sebastian Sdorra + */ +public class RepositoryPage extends BasePage { + + @FindBy(css = "#repoRmButton button") + private WebElement removeButton; + + private final RepositoriesPage repositoriesPage; + + /** + * Constructs a new page. This constructor should only be called from {@link Pages}. + * + * @param driver selenium test driver + * @param repositoriesPage repositories page object + */ + RepositoryPage(WebDriver driver, RepositoriesPage repositoriesPage) { + super(driver); + this.repositoriesPage = repositoriesPage; + } + + @Override + protected RepositoryPage self() { + return this; + } + + /** + * Removes the selected repository. + * + * @return repositories overview page object + */ + public RepositoriesPage remove(){ + removeButton.click(); + waitToBeClickable(By.cssSelector("div.x-window button:nth-of-type(1)")).click(); + return repositoriesPage; + } + +}