KEYCLOAK-3212 Improve Console UI tests stability in FF, Chrome and IE

This commit is contained in:
Vaclav Muzikar 2016-07-12 11:39:27 +02:00
parent f97d0846ed
commit 7dbdb023a0
39 changed files with 382 additions and 221 deletions

View file

@ -42,7 +42,7 @@
<!--component versions--> <!--component versions-->
<arquillian-core.version>1.1.11.Final</arquillian-core.version> <arquillian-core.version>1.1.11.Final</arquillian-core.version>
<selenium.version>2.52.0</selenium.version> <selenium.version>2.53.0</selenium.version>
<arquillian-drone.version>2.0.0.Beta1</arquillian-drone.version> <arquillian-drone.version>2.0.0.Beta1</arquillian-drone.version>
<arquillian-graphene.version>2.1.0.Alpha3</arquillian-graphene.version> <arquillian-graphene.version>2.1.0.Alpha3</arquillian-graphene.version>
<arquillian-wildfly-container.version>2.0.0.Final</arquillian-wildfly-container.version> <arquillian-wildfly-container.version>2.0.0.Final</arquillian-wildfly-container.version>

View file

@ -20,6 +20,8 @@ package org.keycloak.testsuite.auth.page.account;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.page.Form; import org.keycloak.testsuite.page.Form;
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement; import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElementIsNotPresent;
import org.openqa.selenium.WebElement; import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.FindBy;
@ -69,7 +71,7 @@ public class AccountFields extends Form {
} }
public void waitForUsernameInputNotPresent() { public void waitForUsernameInputNotPresent() {
waitUntilElement(usernameInput).is().not().present(); waitUntilElementIsNotPresent(driver, usernameInput);
} }
} }

View file

@ -25,7 +25,7 @@ import org.keycloak.testsuite.page.AbstractAlert;
public class AccountManagementAlert extends AbstractAlert { public class AccountManagementAlert extends AbstractAlert {
public boolean isError() { public boolean isError() {
return getAttributeClass().contains("alert-error"); return checkAlertType("error");
} }
} }

View file

@ -23,6 +23,7 @@ import static org.keycloak.testsuite.admin.Users.getPasswordOf;
import org.keycloak.testsuite.auth.page.account.AccountFields; import org.keycloak.testsuite.auth.page.account.AccountFields;
import org.keycloak.testsuite.auth.page.account.PasswordFields; import org.keycloak.testsuite.auth.page.account.PasswordFields;
import static org.keycloak.testsuite.util.WaitUtils.*; import static org.keycloak.testsuite.util.WaitUtils.*;
import org.openqa.selenium.WebElement; import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.FindBy;
@ -51,6 +52,9 @@ public class LoginForm extends Form {
@FindBy(id = "rememberMe") @FindBy(id = "rememberMe")
private WebElement rememberMe; private WebElement rememberMe;
@FindBy(xpath = ".//label[@for='password']")
private WebElement labelPassword;
public void setUsername(String username) { public void setUsername(String username) {
accountFields.setUsername(username); accountFields.setUsername(username);
} }
@ -70,26 +74,28 @@ public class LoginForm extends Form {
} }
public void register() { public void register() {
waitForUsernameInputPresent();
waitUntilElement(registerLink).is().present();
registerLink.click(); registerLink.click();
waitForPageToLoad(driver);
} }
public void login() { public void login() {
waitUntilElement(loginButton).is().present(); labelPassword.click(); // This is a nasty trick for IE; As IE was "moving the cursor" towards the login button
// it opened the internationalization menu (when present) and then clicked
// one of the languages instead of the Login button
loginButton.click(); loginButton.click();
waitForPageToLoad(driver);
} }
public void forgotPassword() { public void forgotPassword() {
waitUntilElement(forgottenPassword).is().present();
forgottenPassword.click(); forgottenPassword.click();
waitForPageToLoad(driver);
} }
public void rememberMe(boolean value) { public void rememberMe(boolean value) {
waitForRememberMePresent();
boolean selected = rememberMe.isSelected(); boolean selected = rememberMe.isSelected();
if ((value && !selected) || !value && selected) { if ((value && !selected) || !value && selected) {
rememberMe.click(); rememberMe.click();
waitForPageToLoad(driver);
} }
} }
@ -103,7 +109,7 @@ public class LoginForm extends Form {
} }
public void waitForRegisterLinkNotPresent() { public void waitForRegisterLinkNotPresent() {
waitUntilElement(registerLink).is().not().present(); waitUntilElementIsNotPresent(driver, registerLink);
} }
public void waitForResetPasswordLinkNotPresent() { public void waitForResetPasswordLinkNotPresent() {
@ -115,7 +121,7 @@ public class LoginForm extends Form {
} }
public void waitForRememberMeNotPresent() { public void waitForRememberMeNotPresent() {
waitUntilElement(rememberMe).is().not().present(); waitUntilElementIsNotPresent(driver, rememberMe);
} }
public void waitForLoginButtonPresent() { public void waitForLoginButtonPresent() {
@ -150,6 +156,7 @@ public class LoginForm extends Form {
public void submit() { public void submit() {
submit.click(); submit.click();
waitForPageToLoad(driver);
} }
} }
} }

View file

@ -16,6 +16,7 @@
*/ */
package org.keycloak.testsuite.auth.page.login; package org.keycloak.testsuite.auth.page.login;
import org.keycloak.testsuite.util.UIUtils;
import org.openqa.selenium.WebElement; import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.FindBy;
@ -35,7 +36,7 @@ public class TermsAndConditions extends LoginActions {
@Override @Override
public boolean isCurrent() { public boolean isCurrent() {
return driver.getTitle().equals("Terms and Conditions"); return UIUtils.currentTitleEquals(driver, "Terms and Conditions");
} }
public void acceptTerms() { public void acceptTerms() {

View file

@ -50,17 +50,14 @@ public class AdminEvents extends Events {
private AdminEventsTableFilterForm filterForm; private AdminEventsTableFilterForm filterForm;
public void update() { public void update() {
waitForBody();
clickHeaderButton("Update"); clickHeaderButton("Update");
} }
public void reset() { public void reset() {
waitForBody();
clickHeaderButton("Reset"); clickHeaderButton("Reset");
} }
public void filter() { public void filter() {
waitForBody();
filterButton.click(); filterButton.click();
} }

View file

@ -50,17 +50,14 @@ public class LoginEvents extends Events {
private LoginEventsTableFilterForm filterForm; private LoginEventsTableFilterForm filterForm;
public void update() { public void update() {
waitForBody();
clickHeaderButton("Update"); clickHeaderButton("Update");
} }
public void reset() { public void reset() {
waitForBody();
clickHeaderButton("Reset"); clickHeaderButton("Reset");
} }
public void filter() { public void filter() {
waitForBody();
filterButton.click(); filterButton.click();
} }

View file

@ -27,19 +27,19 @@ import org.openqa.selenium.support.FindBy;
*/ */
public class AdminConsoleAlert extends AbstractAlert { public class AdminConsoleAlert extends AbstractAlert {
@FindBy(xpath = "//button[@class='close']") @FindBy(xpath = ".//button[@class='close']")
protected WebElement closeButton; protected WebElement closeButton;
public boolean isInfo() { public boolean isInfo() {
return getAttributeClass().contains("alert-info"); return checkAlertType("info");
} }
public boolean isWarning() { public boolean isWarning() {
return getAttributeClass().contains("alert-warning"); return checkAlertType("waring");
} }
public boolean isDanger() { public boolean isDanger() {
return getAttributeClass().contains("alert-danger"); return checkAlertType("danger");
} }
public void close() { public void close() {

View file

@ -17,12 +17,14 @@
package org.keycloak.testsuite.console.page.fragment; package org.keycloak.testsuite.console.page.fragment;
import org.jboss.arquillian.drone.api.annotation.Drone;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement; import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.FindBy;
import java.util.List; import java.util.List;
import static org.keycloak.testsuite.util.WaitUtils.pause; import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement; import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
import org.openqa.selenium.By; import org.openqa.selenium.By;
import static org.openqa.selenium.By.xpath; import static org.openqa.selenium.By.xpath;
@ -33,6 +35,9 @@ import static org.openqa.selenium.By.xpath;
*/ */
public class DataTable { public class DataTable {
@Drone
protected WebDriver driver;
@FindBy(css = "input[class*='search']") @FindBy(css = "input[class*='search']")
private WebElement searchInput; private WebElement searchInput;
@FindBy(css = "div[class='input-group-addon'] i") @FindBy(css = "div[class='input-group-addon'] i")
@ -49,38 +54,30 @@ public class DataTable {
private WebElement infoRow; private WebElement infoRow;
public void search(String pattern) { public void search(String pattern) {
waitForBody();
searchInput.sendKeys(pattern); searchInput.sendKeys(pattern);
searchButton.click(); searchButton.click();
} }
public void clickHeaderButton(String buttonText) { public void clickHeaderButton(String buttonText) {
waitForBody();
header.findElement(By.xpath(".//button[text()='" + buttonText + "']")).click(); header.findElement(By.xpath(".//button[text()='" + buttonText + "']")).click();
waitForPageToLoad(driver);
} }
public void clickHeaderLink(String linkText) { public void clickHeaderLink(String linkText) {
waitForBody();
header.findElement(By.linkText(linkText)).click(); header.findElement(By.linkText(linkText)).click();
waitForPageToLoad(driver);
} }
public WebElement body() { public WebElement body() {
return body; return body;
} }
public void waitForBody() {
waitUntilElement(body).is().present();
}
public List<WebElement> rows() { public List<WebElement> rows() {
waitForBody();
pause(250);
return rows; return rows;
} }
public WebElement getRowByLinkText(String text) { public WebElement getRowByLinkText(String text) {
WebElement row = body.findElement(By.xpath(".//tr[./td/a[text()='" + text + "']]")); WebElement row = body.findElement(By.xpath(".//tr[./td/a[text()='" + text + "']]"));
waitUntilElement(row).is().present();
return row; return row;
} }

View file

@ -17,12 +17,12 @@
package org.keycloak.testsuite.console.page.fragment; package org.keycloak.testsuite.console.page.fragment;
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement; import org.jboss.arquillian.drone.api.annotation.Drone;
import org.jboss.arquillian.graphene.fragment.Root; import org.jboss.arquillian.graphene.fragment.Root;
import static org.keycloak.testsuite.util.WaitUtils.pause; import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement; import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.FindBy;
import static org.keycloak.testsuite.util.WaitUtils.*;
/** /**
* *
@ -33,6 +33,9 @@ public class ModalDialog {
@Root @Root
private WebElement root; private WebElement root;
@Drone
private WebDriver driver;
@FindBy(xpath = ".//button[text()='Cancel']") @FindBy(xpath = ".//button[text()='Cancel']")
private WebElement cancelButton; private WebElement cancelButton;
@FindBy(xpath = ".//button[text()='Delete']") @FindBy(xpath = ".//button[text()='Delete']")
@ -44,26 +47,24 @@ public class ModalDialog {
private WebElement nameInput; private WebElement nameInput;
public void ok() { public void ok() {
waitUntilElement(okButton).is().present(); waitForModalFadeIn(driver);
okButton.click(); okButton.click();
waitUntilElement(root).is().not().present(); waitForModalFadeOut(driver);
} }
public void confirmDeletion() { public void confirmDeletion() {
waitUntilElement(deleteButton).is().present(); waitForModalFadeIn(driver);
deleteButton.click(); deleteButton.click();
waitUntilElement(root).is().not().present(); waitForModalFadeOut(driver);
pause(200);
} }
public void cancel() { public void cancel() {
waitUntilElement(cancelButton).is().present(); waitForModalFadeIn(driver);
cancelButton.click(); cancelButton.click();
waitUntilElement(root).is().not().present(); waitForModalFadeOut(driver);
} }
public void setName(String name) { public void setName(String name) {
waitUntilElement(nameInput).is().present();
nameInput.clear(); nameInput.clear();
nameInput.sendKeys(name); nameInput.sendKeys(name);
} }

View file

@ -17,14 +17,16 @@
package org.keycloak.testsuite.page; package org.keycloak.testsuite.page;
import com.google.common.base.Predicate; import org.jboss.arquillian.drone.api.annotation.Drone;
import java.util.Arrays;
import static org.jboss.arquillian.graphene.Graphene.waitModel;
import org.jboss.arquillian.graphene.fragment.Root; import org.jboss.arquillian.graphene.fragment.Root;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
import org.keycloak.testsuite.util.WaitUtils;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement; import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
/** /**
* *
@ -37,33 +39,27 @@ public abstract class AbstractAlert {
@Root @Root
protected WebElement root; protected WebElement root;
public void waitUntilPresent() { @Drone
waitUntilElement(root, "Flash message should be present.").is().present(); protected WebDriver driver;
}
public void waitUntilPresentAndClassSet() {
waitUntilPresent();
waitModel().until(new Predicate<WebDriver>() {
@Override
public boolean apply(WebDriver input) {
return !Arrays.asList(getAttributeClass().split(" ")).contains("alert-");
}
});
}
public String getText() { public String getText() {
return root.getText(); return root.getText();
} }
public String getAttributeClass() {
String attrClass = root.getAttribute("class");
log.debug("Alert @class = '" + attrClass + "'");
return attrClass;
}
public boolean isSuccess() { public boolean isSuccess() {
log.debug("Alert.isSuccess()"); log.debug("Alert.isSuccess()");
return getAttributeClass().contains("alert-success"); return checkAlertType("success");
}
protected boolean checkAlertType(String type) {
WaitUtils.waitForPageToLoad(driver);
try {
(new WebDriverWait(driver, 1)).until(ExpectedConditions.attributeContains(root, "class", "alert-" + type));
}
catch (TimeoutException e) {
return false;
}
return true;
} }
} }

View file

@ -23,10 +23,8 @@ import java.util.Map;
import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriBuilder;
import org.jboss.arquillian.drone.api.annotation.Drone; import org.jboss.arquillian.drone.api.annotation.Drone;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import static org.keycloak.testsuite.util.WaitUtils.pause;
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
import org.openqa.selenium.By; import org.keycloak.testsuite.util.URLUtils;
import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriver;
/** /**
@ -88,17 +86,15 @@ public abstract class AbstractPage {
} }
public void navigateTo() { public void navigateTo() {
String uri = buildUri().toASCIIString(); navigateTo(true);
log.debug("current URL: " + driver.getCurrentUrl()); }
log.info("navigating to " + uri);
driver.navigate().to(uri); public void navigateTo(boolean waitForMatch) {
pause(300); // this is needed for FF for some reason URLUtils.navigateToUri(driver, buildUri().toASCIIString(), waitForMatch);
waitUntilElement(By.tagName("body")).is().visible();
log.info("current URL: " + driver.getCurrentUrl());
} }
public boolean isCurrent() { public boolean isCurrent() {
return driver.getCurrentUrl().equals(toString()); return URLUtils.currentUrlEqual(driver, toString());
} }
} }

View file

@ -21,6 +21,8 @@ import org.jboss.arquillian.drone.api.annotation.Drone;
import static org.jboss.arquillian.graphene.Graphene.guardAjax; import static org.jboss.arquillian.graphene.Graphene.guardAjax;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement; import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
import org.keycloak.testsuite.util.WaitUtils;
import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement; import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.FindBy;
@ -45,8 +47,8 @@ public class Form {
public void save() { public void save() {
// guardAjax(save).click(); // guardAjax(save).click();
waitUntilElement(save).is().present();
save.click(); save.click();
WaitUtils.waitForPageToLoad(driver);
} }
public void cancel() { public void cancel() {
@ -54,14 +56,12 @@ public class Form {
} }
public static String getInputValue(WebElement input) { public static String getInputValue(WebElement input) {
waitUntilElement(input).is().present();
return input.getAttribute(VALUE); return input.getAttribute(VALUE);
} }
public static final String VALUE = "value"; public static final String VALUE = "value";
public static void setInputValue(WebElement input, String value) { public static void setInputValue(WebElement input, String value) {
waitUntilElement(input).is().present();
if (input.isEnabled()) { if (input.isEnabled()) {
input.clear(); input.clear();
if (value != null) { if (value != null) {

View file

@ -0,0 +1,33 @@
package org.keycloak.testsuite.util;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.Select;
import org.openqa.selenium.support.ui.WebDriverWait;
/**
* @author Vaclav Muzikar <vmuzikar@redhat.com>
*/
public final class UIUtils {
public static boolean selectContainsOption(Select select, String optionText) {
for (WebElement option : select.getOptions()) {
if (option.getText().equals(optionText)) {
return true;
}
}
return false;
}
public static boolean currentTitleEquals(WebDriver driver, String url) {
try {
(new WebDriverWait(driver, 5)).until(ExpectedConditions.titleIs(url));
}
catch (TimeoutException e) {
return false;
}
return true;
}
}

View file

@ -0,0 +1,108 @@
package org.keycloak.testsuite.util;
import org.jboss.logging.Logger;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.ie.InternetExplorerDriver;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.util.regex.Pattern;
import static org.openqa.selenium.support.ui.ExpectedConditions.*;
/**
* @author Vaclav Muzikar <vmuzikar@redhat.com>
*/
public final class URLUtils {
public static void navigateToUri(WebDriver driver, String uri, boolean waitForMatch) {
navigateToUri(driver, uri, waitForMatch, true);
}
private static void navigateToUri(WebDriver driver, String uri, boolean waitForMatch, boolean enableIEWorkaround) {
Logger log = Logger.getLogger(URLUtils.class);
log.info("starting navigation");
// In IE, sometime the current URL is not correct; one of the indicators is that the target URL
// equals the current URL
if (driver instanceof InternetExplorerDriver && driver.getCurrentUrl().equals(uri)) {
log.info("IE workaround: target URL equals current URL - refreshing the page");
driver.navigate().refresh();
}
WaitUtils.waitForPageToLoad(driver);
log.info("current URL: " + driver.getCurrentUrl());
log.info("navigating to " + uri);
driver.navigate().to(uri);
if (waitForMatch) {
try {
(new WebDriverWait(driver, 3)).until(urlMatches("^" + Pattern.quote(uri) + ".*$"));
} catch (TimeoutException e) {
log.info("new current URL doesn't start with desired URL");
}
}
WaitUtils.waitForPageToLoad(driver);
log.info("new current URL: " + driver.getCurrentUrl());
// In IE, after deleting the cookies for test realm, the first loaded page in master's admin console
// contains invalid URL (misses #/realms/[realm] or contains state and code fragments), although the
// address bar states the correct URL; seemingly this is another bug in IE WebDriver)
if (enableIEWorkaround && driver instanceof InternetExplorerDriver
&& (driver.getCurrentUrl().matches("^[^#]+/#state=[^#/&]+&code=[^#/&]+$")
|| driver.getCurrentUrl().matches("^.+/auth/admin/[^/]+/console/$"))) {
log.info("IE workaround: reloading the page after deleting the cookies...");
navigateToUri(driver, uri, waitForMatch, false);
}
else {
log.info("navigation complete");
}
}
public static boolean currentUrlEqual(WebDriver driver, String url) {
return urlCheck(driver, urlToBe(url));
}
public static boolean currentUrlDoesntEqual(WebDriver driver, String url) {
return urlCheck(driver, not(urlToBe(url)));
}
public static boolean currentUrlStartWith(WebDriver driver, String url) {
return urlCheck(driver, urlMatches("^" + Pattern.quote(url) + ".*$"));
}
public static boolean currentUrlDoesntStartWith(WebDriver driver, String url) {
return urlCheck(driver, urlMatches("^(?!" + Pattern.quote(url) + ").+$"));
}
private static boolean urlCheck(WebDriver driver, ExpectedCondition condition) {
return urlCheck(driver, condition, false);
}
private static boolean urlCheck(WebDriver driver, ExpectedCondition condition, boolean secondTry) {
Logger log = Logger.getLogger(URLUtils.class);
try {
(new WebDriverWait(driver, 1, 100)).until(condition);
}
catch (TimeoutException e) {
if (driver instanceof InternetExplorerDriver && !secondTry) {
// IE WebDriver has sometimes invalid current URL
log.info("IE workaround: checking URL failed at first attempt - refreshing the page and trying one more time...");
driver.navigate().refresh();
urlCheck(driver, condition, true);
}
else {
return false;
}
}
return true;
}
}

View file

@ -16,36 +16,60 @@
*/ */
package org.keycloak.testsuite.util; package org.keycloak.testsuite.util;
import java.util.Collections;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import static org.jboss.arquillian.graphene.Graphene.waitGui; import static org.jboss.arquillian.graphene.Graphene.waitGui;
import org.jboss.arquillian.graphene.wait.ElementBuilder; import org.jboss.arquillian.graphene.wait.ElementBuilder;
import org.openqa.selenium.By; import org.openqa.selenium.By;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement; import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.WebDriverWait;
import static org.jboss.arquillian.graphene.Graphene.waitModel;
import static org.openqa.selenium.support.ui.ExpectedConditions.*;
/** /**
* *
* @author Petr Mensik * @author Petr Mensik
* @author tkyjovsk * @author tkyjovsk
* @author Vaclav Muzikar <vmuzikar@redhat.com>
*/ */
public final class WaitUtils { public final class WaitUtils {
protected final static org.jboss.logging.Logger log = org.jboss.logging.Logger.getLogger(WaitUtils.class);
public static final String PAGELOAD_TIMEOUT_PROP = "pageload.timeout"; public static final String PAGELOAD_TIMEOUT_PROP = "pageload.timeout";
public static final Integer PAGELOAD_TIMEOUT = Integer.parseInt(System.getProperty(PAGELOAD_TIMEOUT_PROP, "60000")); public static final Integer PAGELOAD_TIMEOUT_MILLIS = Integer.parseInt(System.getProperty(PAGELOAD_TIMEOUT_PROP, "10000"));
public static final int IMPLICIT_ELEMENT_WAIT_MILLIS = 750;
// Should be no longer necessary for finding elements since we have implicit wait
public static ElementBuilder<Void> waitUntilElement(By by) { public static ElementBuilder<Void> waitUntilElement(By by) {
return waitGui().until().element(by); return waitGui().until().element(by);
} }
// Should be no longer necessary for finding elements since we have implicit wait
public static ElementBuilder<Void> waitUntilElement(WebElement element) { public static ElementBuilder<Void> waitUntilElement(WebElement element) {
return waitGui().until().element(element); return waitGui().until().element(element);
} }
// Should be no longer necessary for finding elements since we have implicit wait
public static ElementBuilder<Void> waitUntilElement(WebElement element, String failMessage) { public static ElementBuilder<Void> waitUntilElement(WebElement element, String failMessage) {
return waitGui().until(failMessage).element(element); return waitGui().until(failMessage).element(element);
} }
public static void waitUntilElementIsNotPresent(WebDriver driver, By locator) {
waitUntilElementIsNotPresent(driver, driver.findElement(locator));
}
public static void waitUntilElementIsNotPresent(WebDriver driver, WebElement element) {
(new WebDriverWait(driver, IMPLICIT_ELEMENT_WAIT_MILLIS))
.until(invisibilityOfAllElements(Collections.singletonList(element)));
}
public static void pause(long millis) { public static void pause(long millis) {
try { try {
Thread.sleep(millis); Thread.sleep(millis);
@ -55,4 +79,40 @@ public final class WaitUtils {
} }
} }
/**
* Waits for page to finish any pending redirects, REST API requests etc.
* Because Keycloak's Admin Console is a single-page application, we need to take extra steps to ensure
* the page is fully loaded
*
* @param driver
*/
public static void waitForPageToLoad(WebDriver driver) {
WebDriverWait wait = new WebDriverWait(driver, PAGELOAD_TIMEOUT_MILLIS / 1000);
try {
wait.until(not(urlContains("redirect_fragment")));
// Checks if the document is ready and asks AngularJS, if present, whether there are any REST API requests
// in progress
wait.until(javaScriptThrowsNoExceptions(
"if (document.readyState !== 'complete' " +
"|| (typeof angular !== 'undefined' && angular.element(document.body).injector().get('$http').pendingRequests.length !== 0)) {" +
"throw \"Not ready\";" +
"}"));
}
catch (TimeoutException e) {
// Sometimes, for no obvious reason, the browser/JS doesn't set document.readyState to 'complete' correctly
// but that's no reason to let the test fail; after the timeout the page is surely fully loaded
log.warn("waitForPageToLoad time exceeded!");
}
}
public static void waitForModalFadeIn(WebDriver driver) {
pause(500); // TODO: Find out how to do in more 'elegant' way, e.g. like in the waitForModalFadeOut
}
public static void waitForModalFadeOut(WebDriver driver) {
waitUntilElementIsNotPresent(driver, By.className("modal-backdrop"));
}
} }

View file

@ -22,6 +22,7 @@ import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.auth.page.AuthRealm; import org.keycloak.testsuite.auth.page.AuthRealm;
import org.keycloak.testsuite.auth.page.account.Account;
import org.keycloak.testsuite.auth.page.login.OIDCLogin; import org.keycloak.testsuite.auth.page.login.OIDCLogin;
import org.keycloak.testsuite.auth.page.login.SAMLPostLogin; import org.keycloak.testsuite.auth.page.login.SAMLPostLogin;
import org.keycloak.testsuite.auth.page.login.SAMLRedirectLogin; import org.keycloak.testsuite.auth.page.login.SAMLRedirectLogin;
@ -46,6 +47,8 @@ public abstract class AbstractAuthTest extends AbstractKeycloakTest {
protected AuthRealm testRealmPage; protected AuthRealm testRealmPage;
@Page @Page
protected OIDCLogin testRealmLoginPage; protected OIDCLogin testRealmLoginPage;
@Page
protected Account testRealmAccountPage;
@Page @Page
protected SAMLPostLogin testRealmSAMLPostLoginPage; protected SAMLPostLogin testRealmSAMLPostLoginPage;
@ -68,6 +71,7 @@ public abstract class AbstractAuthTest extends AbstractKeycloakTest {
@Before @Before
public void beforeAuthTest() { public void beforeAuthTest() {
testRealmLoginPage.setAuthRealm(testRealmPage); testRealmLoginPage.setAuthRealm(testRealmPage);
testRealmAccountPage.setAuthRealm(testRealmPage);
testUser = createUserRepresentation("test", "test@email.test", "test", "user", true); testUser = createUserRepresentation("test", "test@email.test", "test", "user", true);
setPasswordFor(testUser, PASSWORD); setPasswordFor(testUser, PASSWORD);
@ -97,7 +101,8 @@ public abstract class AbstractAuthTest extends AbstractKeycloakTest {
} }
public void deleteAllCookiesForTestRealm() { public void deleteAllCookiesForTestRealm() {
testRealmPage.navigateTo(); // testRealmPage.navigateTo();
testRealmAccountPage.navigateTo(); // Because IE webdriver freezes when loading a JSON page (realm page), we need to use this alternative
log.debug("deleting cookies in test realm"); log.debug("deleting cookies in test realm");
driver.manage().deleteAllCookies(); driver.manage().deleteAllCookies();
} }

View file

@ -169,13 +169,15 @@ public abstract class AbstractKeycloakTest {
} }
public void deleteAllCookiesForMasterRealm() { public void deleteAllCookiesForMasterRealm() {
masterRealmPage.navigateTo(); // masterRealmPage.navigateTo();
accountPage.navigateTo(); // Because IE webdriver freezes when loading a JSON page (realm page), we need to use this alternative
log.debug("deleting cookies in master realm"); log.debug("deleting cookies in master realm");
driver.manage().deleteAllCookies(); driver.manage().deleteAllCookies();
} }
protected void driverSettings() { protected void driverSettings() {
driver.manage().timeouts().pageLoadTimeout(WaitUtils.PAGELOAD_TIMEOUT, TimeUnit.MILLISECONDS); driver.manage().timeouts().implicitlyWait(WaitUtils.IMPLICIT_ELEMENT_WAIT_MILLIS, TimeUnit.MILLISECONDS);
driver.manage().timeouts().pageLoadTimeout(WaitUtils.PAGELOAD_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
driver.manage().window().maximize(); driver.manage().window().maximize();
} }

View file

@ -52,12 +52,10 @@ public abstract class AbstractAccountManagementTest extends AbstractAuthTest {
} }
public void assertAlertSuccess() { public void assertAlertSuccess() {
alert.waitUntilPresentAndClassSet();
assertTrue(alert.isSuccess()); assertTrue(alert.isSuccess());
} }
public void assertAlertError() { public void assertAlertError() {
alert.waitUntilPresentAndClassSet();
assertTrue(alert.isError()); assertTrue(alert.isError());
} }

View file

@ -17,8 +17,6 @@
package org.keycloak.testsuite.util; package org.keycloak.testsuite.util;
import javax.ws.rs.core.UriBuilder;
import org.apache.http.Header; import org.apache.http.Header;
import org.apache.http.HttpEntity; import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.CloseableHttpResponse;
@ -27,12 +25,12 @@ import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.client.HttpClients;
import org.junit.Assert; import org.junit.Assert;
import org.keycloak.testsuite.page.AbstractPage; import org.keycloak.testsuite.page.AbstractPage;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import org.keycloak.testsuite.auth.page.login.PageWithLoginUrl; import org.keycloak.testsuite.auth.page.login.PageWithLoginUrl;
import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriver;
import static org.keycloak.testsuite.util.URLUtils.*;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.Reader; import java.io.Reader;
@ -51,16 +49,9 @@ public class URLAssert {
} }
public static void assertCurrentUrlEquals(WebDriver driver, final AbstractPage page) { public static void assertCurrentUrlEquals(WebDriver driver, final AbstractPage page) {
// WebDriverWait wait = new WebDriverWait(driver, 1); String expected = page.toString();
// ExpectedCondition<Boolean> urlStartsWith = new ExpectedCondition<Boolean>() { assertTrue("Expected URL: " + expected + "; actual: " + driver.getCurrentUrl(),
// currentUrlEqual(driver, page.toString()));
// @Override
// public Boolean apply(WebDriver wd) {
// return startsWithNormalized(wd.getCurrentUrl(), page.toString());
// }
// };
// wait.until(urlStartsWith);
assertEqualsNormalized(page.toString(), driver.getCurrentUrl());
} }
public static void assertCurrentUrlStartsWith(AbstractPage page) { public static void assertCurrentUrlStartsWith(AbstractPage page) {
@ -68,16 +59,8 @@ public class URLAssert {
} }
public static void assertCurrentUrlStartsWith(WebDriver driver, final String url) { public static void assertCurrentUrlStartsWith(WebDriver driver, final String url) {
// WebDriverWait wait = new WebDriverWait(driver, 1); assertTrue("URL expected to begin with:" + url + "; actual URL: " + driver.getCurrentUrl(),
// ExpectedCondition<Boolean> urlStartsWith = new ExpectedCondition<Boolean>() { currentUrlStartWith(driver, url));
//
// @Override
// public Boolean apply(WebDriver wd) {
// return startsWithNormalized(wd.getCurrentUrl(), url);
// }
// };
// wait.until(urlStartsWith);
assertTrue("'" + driver.getCurrentUrl() + " does not start with '" + url + "'", startsWithNormalized(driver.getCurrentUrl(), url));
} }
public static void assertCurrentUrlDoesntStartWith(AbstractPage page) { public static void assertCurrentUrlDoesntStartWith(AbstractPage page) {
@ -85,35 +68,10 @@ public class URLAssert {
} }
public static void assertCurrentUrlDoesntStartWith(WebDriver driver, final String url) { public static void assertCurrentUrlDoesntStartWith(WebDriver driver, final String url) {
// WebDriverWait wait = new WebDriverWait(driver, 1, 250); assertTrue("URL expected NOT to begin with:" + url + "; actual URL: " + driver.getCurrentUrl(),
// ExpectedCondition<Boolean> urlDoesntStartWith = new ExpectedCondition<Boolean>() { currentUrlDoesntStartWith(driver, url));
//
// @Override
// public Boolean apply(WebDriver wd) {
// return !startsWithNormalized(wd.getCurrentUrl(), url);
// }
// };
// wait.until(urlDoesntStartWith);
assertFalse(startsWithNormalized(driver.getCurrentUrl(), url));
} }
// this normalization is needed because of slash-encoding in uri fragment (the part after #)
public static String normalizeUri(String uri) {
return UriBuilder.fromUri(uri).build().toASCIIString();
}
public static boolean startsWithNormalized(String str1, String str2) {
String uri1 = normalizeUri(str1);
String uri2 = normalizeUri(str2);
return uri1.startsWith(uri2);
}
public static void assertEqualsNormalized(String str1, String str2) {
assertEquals(normalizeUri(str1), normalizeUri(str2));
}
public static void assertCurrentUrlStartsWithLoginUrlOf(PageWithLoginUrl page) { public static void assertCurrentUrlStartsWithLoginUrlOf(PageWithLoginUrl page) {
assertCurrentUrlStartsWithLoginUrlOf(page.getDriver(), page); assertCurrentUrlStartsWithLoginUrlOf(page.getDriver(), page);
} }

View file

@ -31,6 +31,10 @@
<name>Admin Console UI Tests</name> <name>Admin Console UI Tests</name>
<properties>
<keycloak.theme.dir>${auth.server.home}/themes</keycloak.theme.dir>
</properties>
<build> <build>
<pluginManagement> <pluginManagement>
<plugins> <plugins>
@ -54,6 +58,14 @@
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<systemProperties>
<keycloak.theme.dir>${keycloak.theme.dir}</keycloak.theme.dir>
</systemProperties>
</configuration>
</plugin>
</plugins> </plugins>
</pluginManagement> </pluginManagement>
</build> </build>

View file

@ -52,7 +52,9 @@ public class PasswordPolicy extends Authentication {
public void removePolicy(Type policy) { public void removePolicy(Type policy) {
getPolicyRow(policy).findElement(By.cssSelector("td.kc-action-cell")).click(); getPolicyRow(policy).findElement(By.cssSelector("td.kc-action-cell")).click();
primaryButton.click(); if (!primaryButton.isDisplayed()) {
primaryButton.click();
}
} }
public void editPolicy(Type policy, int value) { public void editPolicy(Type policy, int value) {

View file

@ -62,32 +62,26 @@ public class Clients extends AdminConsoleRealm {
} }
public void createClient() { public void createClient() {
waitForBody();
clickHeaderLink(CREATE); clickHeaderLink(CREATE);
} }
public void importClient() { public void importClient() {
waitForBody();
clickHeaderLink(IMPORT); clickHeaderLink(IMPORT);
} }
public void clickClient(ClientRepresentation client) { public void clickClient(ClientRepresentation client) {
waitForBody();
clickClient(client.getClientId()); clickClient(client.getClientId());
} }
public void clickClient(String clientId) { public void clickClient(String clientId) {
waitForBody();
body().findElement(linkText(clientId)).click(); body().findElement(linkText(clientId)).click();
} }
public void editClient(String clientId) { public void editClient(String clientId) {
waitForBody();
clickRowActionButton(getRowByLinkText(clientId), EDIT); clickRowActionButton(getRowByLinkText(clientId), EDIT);
} }
public void deleteClient(String clientId) { public void deleteClient(String clientId) {
waitForBody();
clickRowActionButton(getRowByLinkText(clientId), DELETE); clickRowActionButton(getRowByLinkText(clientId), DELETE);
} }

View file

@ -56,15 +56,18 @@ public class ClientCredentialsForm extends Form {
public void regenerateSecret() { public void regenerateSecret() {
waitUntilElement(regenerateSecretButton).is().visible(); waitUntilElement(regenerateSecretButton).is().visible();
regenerateSecretButton.click(); regenerateSecretButton.click();
waitForPageToLoad(driver);
} }
public void regenerateRegistrationAccessToken() { public void regenerateRegistrationAccessToken() {
waitUntilElement(regenerateRegistrationAccessTokenButton).is().visible(); waitUntilElement(regenerateRegistrationAccessTokenButton).is().visible();
regenerateRegistrationAccessTokenButton.click(); regenerateRegistrationAccessTokenButton.click();
waitForPageToLoad(driver);
} }
public void generateNewKeysAndCert() { public void generateNewKeysAndCert() {
waitUntilElement(generateNewKeysAndCert).is().visible(); waitUntilElement(generateNewKeysAndCert).is().visible();
generateNewKeysAndCert.click(); generateNewKeysAndCert.click();
waitForPageToLoad(driver);
} }
} }

View file

@ -22,6 +22,7 @@
package org.keycloak.testsuite.console.page.clients.installation; package org.keycloak.testsuite.console.page.clients.installation;
import org.keycloak.testsuite.page.Form; import org.keycloak.testsuite.page.Form;
import org.keycloak.testsuite.util.WaitUtils;
import org.openqa.selenium.WebElement; import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.ui.Select; import org.openqa.selenium.support.ui.Select;
@ -40,6 +41,7 @@ public class ClientInstallationForm extends Form {
public void setConfigFormat(String value) { public void setConfigFormat(String value) {
configFormatsSelect.selectByVisibleText(value); configFormatsSelect.selectByVisibleText(value);
WaitUtils.waitForPageToLoad(driver);
} }
public String getTextareaContent() { public String getTextareaContent() {

View file

@ -42,17 +42,14 @@ public class ClientMappers extends Client {
} }
public void createMapper() { public void createMapper() {
waitForBody();
clickHeaderLink(CREATE); clickHeaderLink(CREATE);
} }
public void addBuiltin() { public void addBuiltin() {
waitForBody();
clickHeaderLink(ADD_BUILTIN); clickHeaderLink(ADD_BUILTIN);
} }
public void clickMapper(String mapperName) { public void clickMapper(String mapperName) {
waitForBody();
body().findElement(By.linkText(mapperName)).click(); body().findElement(By.linkText(mapperName)).click();
} }
@ -61,7 +58,6 @@ public class ClientMappers extends Client {
} }
private void clickMapperActionButton(String mapperName, String buttonText) { private void clickMapperActionButton(String mapperName, String buttonText) {
waitForBody();
clickRowActionButton(getRowByLinkText(mapperName), buttonText); clickRowActionButton(getRowByLinkText(mapperName), buttonText);
} }

View file

@ -1,8 +1,11 @@
package org.keycloak.testsuite.console.page.clients.mappers; package org.keycloak.testsuite.console.page.clients.mappers;
import org.jboss.arquillian.drone.api.annotation.Drone;
import org.keycloak.testsuite.console.page.fragment.OnOffSwitch; import org.keycloak.testsuite.console.page.fragment.OnOffSwitch;
import org.keycloak.testsuite.page.Form; import org.keycloak.testsuite.page.Form;
import static org.keycloak.testsuite.util.WaitUtils.pause;
import org.keycloak.testsuite.util.WaitUtils;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement; import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.ui.Select; import org.openqa.selenium.support.ui.Select;
@ -44,40 +47,40 @@ public class CreateClientMappersForm extends Form {
@FindBy(id = "mapperTypeCreate") @FindBy(id = "mapperTypeCreate")
private Select mapperTypeSelect; private Select mapperTypeSelect;
@FindBy(xpath = ".//div[@properties='model.mapperType.properties']//label[text()='Property']//following-sibling::node()//input[@type='text']") @FindBy(xpath = ".//div[@properties='model.mapperType.properties']//label[contains(text(),'Property')]//following-sibling::node()//input[@type='text']")
private WebElement propertyInput; private WebElement propertyInput;
@FindBy(xpath = ".//div[@properties='model.mapperType.properties']//label[text()='User Attribute']//following-sibling::node()//input[@type='text']") @FindBy(xpath = ".//div[@properties='model.mapperType.properties']//label[contains(text(),'User Attribute')]//following-sibling::node()//input[@type='text']")
private WebElement userAttributeInput; private WebElement userAttributeInput;
@FindBy(xpath = ".//div[@properties='model.mapperType.properties']//label[text()='User Session Note']//following-sibling::node()//input[@type='text']") @FindBy(xpath = ".//div[@properties='model.mapperType.properties']//label[contains(text(),'User Session Note')]//following-sibling::node()//input[@type='text']")
private WebElement userSessionNoteInput; private WebElement userSessionNoteInput;
@FindBy(xpath = ".//div[@properties='model.mapperType.properties']//label[text()='Multivalued']//following-sibling::node()//div[@class='onoffswitch']") @FindBy(xpath = ".//div[@properties='model.mapperType.properties']//label[contains(text(),'Multivalued')]//following-sibling::node()//div[@class='onoffswitch']")
private OnOffSwitch multivaluedInput; private OnOffSwitch multivaluedInput;
@FindBy(xpath = ".//button[text() = 'Select Role']/../..//input") @FindBy(xpath = ".//button[text() = 'Select Role']/../..//input")
private WebElement roleInput; private WebElement roleInput;
@FindBy(xpath = ".//div[@properties='model.mapperType.properties']//label[text()='New Role Name']//following-sibling::node()//input[@type='text']") @FindBy(xpath = ".//div[@properties='model.mapperType.properties']//label[contains(text(),'New Role Name')]//following-sibling::node()//input[@type='text']")
private WebElement newRoleInput; private WebElement newRoleInput;
@FindBy(xpath = ".//div[@properties='model.mapperType.properties']//label[text()='Token Claim Name']//following-sibling::node()//input[@type='text']") @FindBy(xpath = ".//div[@properties='model.mapperType.properties']//label[contains(text(),'Token Claim Name')]//following-sibling::node()//input[@type='text']")
private WebElement tokenClaimNameInput; private WebElement tokenClaimNameInput;
@FindBy(xpath = ".//div[@properties='model.mapperType.properties']//label[text()='Claim value']//following-sibling::node()//input[@type='text']") @FindBy(xpath = ".//div[@properties='model.mapperType.properties']//label[contains(text(),'Claim value')]//following-sibling::node()//input[@type='text']")
private WebElement tokenClaimValueInput; private WebElement tokenClaimValueInput;
@FindBy(xpath = ".//div[@properties='model.mapperType.properties']//label[text()='Claim JSON Type']//following-sibling::node()//select") @FindBy(xpath = ".//div[@properties='model.mapperType.properties']//label[contains(text(),'Claim JSON Type')]//following-sibling::node()//select")
private Select claimJSONTypeInput; private Select claimJSONTypeInput;
@FindBy(xpath = ".//div[@properties='model.mapperType.properties']//label[text()='Add to ID token']//following-sibling::node()//div[@class='onoffswitch']") @FindBy(xpath = ".//div[@properties='model.mapperType.properties']//label[contains(text(),'Add to ID token')]//following-sibling::node()//div[@class='onoffswitch']")
private OnOffSwitch addToIDTokenInput; private OnOffSwitch addToIDTokenInput;
@FindBy(xpath = ".//div[@properties='model.mapperType.properties']//label[text()='Add to access token']//following-sibling::node()//div[@class='onoffswitch']") @FindBy(xpath = ".//div[@properties='model.mapperType.properties']//label[contains(text(),'Add to access token')]//following-sibling::node()//div[@class='onoffswitch']")
private OnOffSwitch addToAccessTokenInput; private OnOffSwitch addToAccessTokenInput;
@FindBy(xpath = ".//div[@properties='model.mapperType.properties']//label[text()='Full group path']//following-sibling::node()//div[@class='onoffswitch']") @FindBy(xpath = ".//div[@properties='model.mapperType.properties']//label[contains(text(),'Full group path')]//following-sibling::node()//div[@class='onoffswitch']")
private OnOffSwitch fullGroupPath; private OnOffSwitch fullGroupPath;
@FindBy(xpath = ".//button[text() = 'Select Role']") @FindBy(xpath = ".//button[text() = 'Select Role']")
@ -87,16 +90,19 @@ public class CreateClientMappersForm extends Form {
private RoleSelectorModalDialog roleSelectorModalDialog; private RoleSelectorModalDialog roleSelectorModalDialog;
public class RoleSelectorModalDialog { public class RoleSelectorModalDialog {
@Drone
private WebDriver driver;
@FindBy(id = "available") @FindBy(id = "available")
private Select realmAvailable; private Select realmAvailable;
@FindBy(xpath = ".//button[@tooltip='Select realm role']") @FindBy(xpath = ".//button[@tooltip='Select realm role' and not(@disabled)]")
private WebElement selectRealmRoleButton; private WebElement selectRealmRoleButton;
@FindBy(id = "available-client") @FindBy(id = "available-client")
private Select clientAvailable; private Select clientAvailable;
@FindBy(id = "clients") @FindBy(id = "clients")
private Select clientSelect; private Select clientSelect;
@FindBy(xpath = ".//button[@tooltip='Select client role']") @FindBy(xpath = ".//button[@tooltip='Select client role' and not(@disabled)]")
private WebElement selectClientRoleButton; private WebElement selectClientRoleButton;
@FindBy(xpath = ".//button[@class='close']") @FindBy(xpath = ".//button[@class='close']")
private WebElement closeButton; private WebElement closeButton;
@ -109,8 +115,9 @@ public class CreateClientMappersForm extends Form {
if (roleName != null) { if (roleName != null) {
realmAvailable.selectByVisibleText(roleName); realmAvailable.selectByVisibleText(roleName);
} }
WaitUtils.pause(1000);
selectRealmRoleButton.click(); selectRealmRoleButton.click();
pause(500); // wait for the modal dialog to fade out WaitUtils.waitForModalFadeOut(driver);
} }
public void selectClientRole(String clientName, String roleName) { public void selectClientRole(String clientName, String roleName) {
@ -118,8 +125,9 @@ public class CreateClientMappersForm extends Form {
clientSelect.selectByVisibleText(clientName); clientSelect.selectByVisibleText(clientName);
clientAvailable.selectByVisibleText(roleName); clientAvailable.selectByVisibleText(roleName);
} }
WaitUtils.pause(1000);
selectClientRoleButton.click(); selectClientRoleButton.click();
pause(500); // wait for the modal dialog to fade out WaitUtils.waitForModalFadeOut(driver);
} }
} }
@ -263,25 +271,25 @@ public class CreateClientMappersForm extends Form {
} }
//SAML //SAML
@FindBy(xpath = ".//div[@properties='model.mapperType.properties']//label[text()='Role attribute name']//following-sibling::node()//input[@type='text']") @FindBy(xpath = ".//div[@properties='model.mapperType.properties']//label[contains(text(),'Role attribute name')]//following-sibling::node()//input[@type='text']")
private WebElement roleAttributeNameInput; private WebElement roleAttributeNameInput;
@FindBy(xpath = ".//div[@properties='model.mapperType.properties']//label[text()='Friendly Name']//following-sibling::node()//input[@type='text']") @FindBy(xpath = ".//div[@properties='model.mapperType.properties']//label[contains(text(),'Friendly Name')]//following-sibling::node()//input[@type='text']")
private WebElement friendlyNameInput; private WebElement friendlyNameInput;
@FindBy(xpath = ".//div[@properties='model.mapperType.properties']//label[text()='SAML Attribute NameFormat']//following-sibling::node()//select") @FindBy(xpath = ".//div[@properties='model.mapperType.properties']//label[contains(text(),'SAML Attribute NameFormat')]//following-sibling::node()//select")
private Select samlAttributeNameFormatSelect; private Select samlAttributeNameFormatSelect;
@FindBy(xpath = ".//div[@properties='model.mapperType.properties']//label[text()='Single Role Attribute']//following-sibling::node()//div[@class='onoffswitch']") @FindBy(xpath = ".//div[@properties='model.mapperType.properties']//label[contains(text(),'Single Role Attribute')]//following-sibling::node()//div[@class='onoffswitch']")
private OnOffSwitch singleRoleAttributeSwitch; private OnOffSwitch singleRoleAttributeSwitch;
@FindBy(xpath = ".//div[@properties='model.mapperType.properties']//label[text()='Attribute value']//following-sibling::node()//input[@type='text']") @FindBy(xpath = ".//div[@properties='model.mapperType.properties']//label[contains(text(),'Attribute value')]//following-sibling::node()//input[@type='text']")
private WebElement attributeValueInput; private WebElement attributeValueInput;
@FindBy(xpath = ".//div[@properties='model.mapperType.properties']//label[text()='Group attribute name']//following-sibling::node()//input[@type='text']") @FindBy(xpath = ".//div[@properties='model.mapperType.properties']//label[contains(text(),'Group attribute name')]//following-sibling::node()//input[@type='text']")
private WebElement groupAttributeNameInput; private WebElement groupAttributeNameInput;
@FindBy(xpath = ".//div[@properties='model.mapperType.properties']//label[text()='Single Group Attribute']//following-sibling::node()//div[@class='onoffswitch']") @FindBy(xpath = ".//div[@properties='model.mapperType.properties']//label[contains(text(),'Single Group Attribute')]//following-sibling::node()//div[@class='onoffswitch']")
private OnOffSwitch singleGroupAttributeSwitch; private OnOffSwitch singleGroupAttributeSwitch;
public void setRoleAttributeName(String value) { public void setRoleAttributeName(String value) {

View file

@ -51,7 +51,6 @@ public class ThemeSettings extends RealmSettings {
private OnOffSwitch internatEnabledSwitch; private OnOffSwitch internatEnabledSwitch;
public void changeLoginTheme(String themeName) { public void changeLoginTheme(String themeName) {
waitUntilElement(By.id("loginTheme")).is().present();
loginThemeSelect.selectByVisibleText(themeName); loginThemeSelect.selectByVisibleText(themeName);
} }

View file

@ -3,7 +3,6 @@ package org.keycloak.testsuite.console.page.roles;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -11,12 +10,12 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import org.keycloak.representations.idm.RoleRepresentation.Composites; import org.keycloak.representations.idm.RoleRepresentation.Composites;
import org.keycloak.testsuite.page.Form; import org.keycloak.testsuite.page.Form;
import static org.keycloak.testsuite.util.WaitUtils.pause;
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement; import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
import org.keycloak.testsuite.util.UIUtils;
import org.openqa.selenium.By; import org.openqa.selenium.By;
import org.openqa.selenium.WebElement; import org.openqa.selenium.WebElement;
import org.openqa.selenium.logging.LogEntries;
import org.openqa.selenium.logging.LogEntry;
import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.ui.Select; import org.openqa.selenium.support.ui.Select;
@ -156,22 +155,12 @@ public class RoleCompositeRoles extends Form {
public boolean isAssignedRole(String role) { public boolean isAssignedRole(String role) {
waitUntilElement(By.id("assigned")).is().present(); waitUntilElement(By.id("assigned")).is().present();
try { return UIUtils.selectContainsOption(assignedRealmRolesSelect, role);
assignedRealmRolesSelect.selectByVisibleText(role);
} catch (Exception ex) {
return false;
}
return true;
} }
public boolean isAssignedClientRole(String role) { public boolean isAssignedClientRole(String role) {
waitUntilElement(By.id("assigned")).is().present(); waitUntilElement(By.id("assigned")).is().present();
try { return UIUtils.selectContainsOption(assignedClientRolesSelect, role);
assignedClientRolesSelect.selectByVisibleText(role);
} catch (Exception ex) {
return false;
}
return true;
} }
public void selectClientRole(String client) { public void selectClientRole(String client) {

View file

@ -28,7 +28,6 @@ public class RolesTable extends DataTable {
} }
public void clickRole(String name) { public void clickRole(String name) {
waitForBody();
clickRowByLinkText(name); clickRowByLinkText(name);
} }

View file

@ -42,7 +42,6 @@ public class UserCredentials extends User {
} }
public void clickResetPasswordAndConfirm() { public void clickResetPasswordAndConfirm() {
waitUntilElement(resetPasswordButton);
resetPasswordButton.click(); resetPasswordButton.click();
modalDialog.ok(); modalDialog.ok();
} }

View file

@ -17,7 +17,10 @@
*/ */
package org.keycloak.testsuite.console.page.users; package org.keycloak.testsuite.console.page.users;
import org.jboss.arquillian.drone.api.annotation.Drone;
import org.keycloak.testsuite.util.URLUtils;
import org.openqa.selenium.By; import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement; import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.FindBy;
@ -27,6 +30,8 @@ import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.console.page.AdminConsoleRealm; import org.keycloak.testsuite.console.page.AdminConsoleRealm;
import org.keycloak.testsuite.console.page.fragment.DataTable; import org.keycloak.testsuite.console.page.fragment.DataTable;
import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement; import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
import static org.openqa.selenium.By.*; import static org.openqa.selenium.By.*;
@ -59,6 +64,9 @@ public class Users extends AdminConsoleRealm {
public class UsersTable extends DataTable { public class UsersTable extends DataTable {
@Drone
private WebDriver driver;
public List<UserRepresentation> searchUsers(String searchPattern) { public List<UserRepresentation> searchUsers(String searchPattern) {
search(searchPattern); search(searchPattern);
return getUsersFromTableRows(); return getUsersFromTableRows();
@ -73,19 +81,18 @@ public class Users extends AdminConsoleRealm {
} }
public void clickUser(String username) { public void clickUser(String username) {
waitUntilElement(body()).is().present(); URLUtils.navigateToUri(driver, getRowByUsername(username).findElement(By.xpath("./td[position()=1]/a")).getAttribute("href"), true);
WebElement link = body().findElement( waitForPageToLoad(driver);
By.xpath(".//tr/td[./following::td[text()='" + username + "']]/a")
);
link.click();
} }
public void editUser(String username) { public void editUser(String username) {
clickRowActionButton(getRowByUsername(username), EDIT); clickRowActionButton(getRowByUsername(username), EDIT);
waitForPageToLoad(driver);
} }
public void impersonateUser(String username) { public void impersonateUser(String username) {
clickRowActionButton(getRowByUsername(username), IMPERSONATE); clickRowActionButton(getRowByUsername(username), IMPERSONATE);
waitForPageToLoad(driver);
} }
public void deleteUser(String username) { public void deleteUser(String username) {
@ -137,11 +144,9 @@ public class Users extends AdminConsoleRealm {
} }
protected WebElement getRowByUsername(String userName) { protected WebElement getRowByUsername(String userName) {
WebElement row = body().findElement( return body().findElement(
By.xpath(".//tr[./td/following::td[text()='" + userName + "']]") By.xpath(".//tr[./td[position()=2 and text()='" + userName + "']]")
); );
waitUntilElement(row).is().present();
return row;
} }
} }

View file

@ -106,14 +106,12 @@ public abstract class AbstractConsoleTest extends AbstractAuthTest {
} }
public void assertAlertSuccess() { public void assertAlertSuccess() {
alert.waitUntilPresentAndClassSet(); assertTrue(alert.isSuccess());
assertTrue("Is not success; @class=" + alert.getAttributeClass(), alert.isSuccess());
alert.close(); alert.close();
} }
public void assertAlertDanger() { public void assertAlertDanger() {
alert.waitUntilPresentAndClassSet(); assertTrue(alert.isDanger());
assertTrue("Is not danger; @class=" + alert.getAttributeClass(), alert.isDanger());
alert.close(); alert.close();
} }

View file

@ -25,6 +25,7 @@ import org.keycloak.testsuite.console.AbstractConsoleTest;
import org.keycloak.testsuite.console.page.authentication.PasswordPolicy; import org.keycloak.testsuite.console.page.authentication.PasswordPolicy;
import org.keycloak.testsuite.console.page.users.UserCredentials; import org.keycloak.testsuite.console.page.users.UserCredentials;
import static org.keycloak.testsuite.console.page.authentication.PasswordPolicy.Type.DIGITS;
import static org.keycloak.testsuite.console.page.authentication.PasswordPolicy.Type.HASH_ITERATIONS; import static org.keycloak.testsuite.console.page.authentication.PasswordPolicy.Type.HASH_ITERATIONS;
import static org.keycloak.testsuite.console.page.authentication.PasswordPolicy.Type.REGEX_PATTERN; import static org.keycloak.testsuite.console.page.authentication.PasswordPolicy.Type.REGEX_PATTERN;
@ -48,17 +49,17 @@ public class PasswordPolicyTest extends AbstractConsoleTest {
@Test @Test
public void testAddAndRemovePolicy() { public void testAddAndRemovePolicy() {
passwordPolicyPage.navigateTo(); passwordPolicyPage.navigateTo();
passwordPolicyPage.addPolicy(HASH_ITERATIONS, 5); passwordPolicyPage.addPolicy(DIGITS, 5);
passwordPolicyPage.removePolicy(HASH_ITERATIONS); passwordPolicyPage.removePolicy(DIGITS);
assertAlertSuccess(); assertAlertSuccess();
} }
@Test @Test
public void testInvalidPolicyValues() { public void testInvalidPolicyValues() {
passwordPolicyPage.navigateTo(); passwordPolicyPage.navigateTo();
passwordPolicyPage.addPolicy(HASH_ITERATIONS, "asd"); passwordPolicyPage.addPolicy(DIGITS, "asd");
assertAlertDanger(); assertAlertDanger();
passwordPolicyPage.removePolicy(HASH_ITERATIONS); passwordPolicyPage.removePolicy(DIGITS);
passwordPolicyPage.addPolicy(REGEX_PATTERN, "(["); passwordPolicyPage.addPolicy(REGEX_PATTERN, "([");
assertAlertDanger(); assertAlertDanger();

View file

@ -53,7 +53,6 @@ public abstract class AbstractClientTest extends AbstractConsoleTest {
} }
public void createClient(ClientRepresentation client) { public void createClient(ClientRepresentation client) {
WaitUtils.waitUntilElement(By.tagName("body")).is().present();
assertCurrentUrlEquals(clientsPage); assertCurrentUrlEquals(clientsPage);
clientsPage.table().createClient(); clientsPage.table().createClient();
createClientPage.form().setValues(client); createClientPage.form().setValues(client);

View file

@ -25,7 +25,9 @@ public class ConfigTest extends AbstractConsoleTest {
@Test @Test
public void configLoginEventsTest() { public void configLoginEventsTest() {
configPage.form().setSaveEvents(true); configPage.form().setSaveEvents(true);
configPage.form().addSaveType("REGISTER_NODE"); // IE webdriver has problem with clicking not visible (scrolling is needed) items in the menu,
// so we need to select some type from the beginning of the menu
configPage.form().addSaveType("CLIENT_INFO");
//after removeSavedType method stay input focused -> in phantomjs drop menu doesn't appear after first click //after removeSavedType method stay input focused -> in phantomjs drop menu doesn't appear after first click
configPage.form().removeSaveType("LOGIN"); configPage.form().removeSaveType("LOGIN");
configPage.form().setExpiration("50", "Days"); configPage.form().setExpiration("50", "Days");
@ -35,7 +37,7 @@ public class ConfigTest extends AbstractConsoleTest {
RealmRepresentation realm = testRealmResource().toRepresentation(); RealmRepresentation realm = testRealmResource().toRepresentation();
assertTrue(realm.isEventsEnabled()); assertTrue(realm.isEventsEnabled());
assertFalse(realm.getEnabledEventTypes().contains("LOGIN")); assertFalse(realm.getEnabledEventTypes().contains("LOGIN"));
assertTrue(realm.getEnabledEventTypes().contains("REGISTER_NODE")); assertTrue(realm.getEnabledEventTypes().contains("CLIENT_INFO"));
assertEquals(4320000L, realm.getEventsExpiration().longValue()); assertEquals(4320000L, realm.getEventsExpiration().longValue());
} }

View file

@ -40,6 +40,7 @@ public class LoginEventsTest extends AbstractConsoleTest {
@Test @Test
public void userAccessEventsTest() { public void userAccessEventsTest() {
deleteAllCookiesForTestRealm();
testRealmAdminConsolePage.navigateTo(); testRealmAdminConsolePage.navigateTo();
Users.setPasswordFor(testUser, "Wrong_password"); Users.setPasswordFor(testUser, "Wrong_password");
testRealmLoginPage.form().login(testUser); testRealmLoginPage.form().login(testUser);

View file

@ -11,7 +11,6 @@ import org.openqa.selenium.By;
import org.openqa.selenium.WebElement; import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.FindBy;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import static org.keycloak.testsuite.util.WaitUtils.*;
import static org.keycloak.testsuite.util.URLAssert.*; import static org.keycloak.testsuite.util.URLAssert.*;
/** /**
@ -32,7 +31,6 @@ public class InternationalizationTest extends AbstractRealmTest {
themeSettingsPage.saveTheme(); themeSettingsPage.saveTheme();
assertAlertSuccess(); assertAlertSuccess();
realmSettingsPage.setAdminRealm(AuthRealm.TEST); realmSettingsPage.setAdminRealm(AuthRealm.TEST);
accountPage.setAuthRealm(testRealmPage);
deleteAllCookiesForTestRealm(); deleteAllCookiesForTestRealm();
deleteAllCookiesForMasterRealm(); deleteAllCookiesForMasterRealm();
} }
@ -55,7 +53,7 @@ public class InternationalizationTest extends AbstractRealmTest {
loginToTestRealmConsoleAs(testUser); loginToTestRealmConsoleAs(testUser);
assertConsoleLocale("Temas"); assertConsoleLocale("Temas");
accountPage.navigateTo(); testRealmAccountPage.navigateTo();
assertAccountLocale("Cuenta"); assertAccountLocale("Cuenta");
} }
@ -64,11 +62,11 @@ public class InternationalizationTest extends AbstractRealmTest {
*/ */
@Test @Test
public void accountInternationalization() { public void accountInternationalization() {
accountPage.navigateTo(); testRealmAccountPage.navigateTo();
loginPage.form().login(testUser); loginPage.form().login(testUser);
localeDropdown.selectByText("Français"); localeDropdown.selectByText("Français");
accountPage.navigateTo(); testRealmAccountPage.navigateTo();
assertAccountLocale("Compte"); assertAccountLocale("Compte");
deleteAllCookiesForTestRealm(); deleteAllCookiesForTestRealm();
@ -78,14 +76,12 @@ public class InternationalizationTest extends AbstractRealmTest {
} }
private void assertConsoleLocale(String expected) { private void assertConsoleLocale(String expected) {
pause(500);
assertCurrentUrlEquals(realmSettingsPage); assertCurrentUrlEquals(realmSettingsPage);
assertLocale(".//a[contains(@href,'/theme-settings')]", expected); // Themes assertLocale(".//a[contains(@href,'/theme-settings')]", expected); // Themes
} }
private void assertAccountLocale(String expected) { private void assertAccountLocale(String expected) {
pause(500); assertCurrentUrlEquals(testRealmAccountPage);
assertCurrentUrlEquals(accountPage);
assertLocale(".//div[contains(@class,'bs-sidebar')]/ul/li", expected); // Account assertLocale(".//div[contains(@class,'bs-sidebar')]/ul/li", expected); // Account
} }
@ -95,7 +91,6 @@ public class InternationalizationTest extends AbstractRealmTest {
} }
private void assertLocale(WebElement element, String expected) { private void assertLocale(WebElement element, String expected) {
waitUntilElement(element);
assertEquals(expected, element.getText()); assertEquals(expected, element.getText());
} }
} }

View file

@ -75,7 +75,6 @@ public class LoginSettingsTest extends AbstractRealmTest {
public void beforeLoginSettingsTest() { public void beforeLoginSettingsTest() {
// tabs().login(); // tabs().login();
loginSettingsPage.navigateTo(); loginSettingsPage.navigateTo();
assertCurrentUrlEquals(loginSettingsPage);
} }
@Test @Test