KEYCLOAK-6736 Base UI tests for mobile and desktop browsers
This commit is contained in:
parent
72750b9882
commit
65f51b7b83
118 changed files with 2801 additions and 782 deletions
|
@ -287,10 +287,10 @@ This will start latest Keycloak and import the realm JSON file, which was previo
|
|||
-Dmigrated.auth.server.version=1.9.8.Final
|
||||
|
||||
|
||||
## UI tests
|
||||
## Admin Console UI tests
|
||||
The UI tests are real-life, UI focused integration tests. Hence they do not support the default HtmlUnit browser. Only the following real-life browsers are supported: Mozilla Firefox, Google Chrome and Internet Explorer. For details on how to run the tests with these browsers, please refer to [Different Browsers](#different-browsers) chapter.
|
||||
|
||||
The UI tests are focused on the Admin Console as well as on some login scenarios. They are placed in the `console` module and are disabled by default.
|
||||
The UI tests are focused on the Admin Console. They are placed in the `console` module and are disabled by default.
|
||||
|
||||
The tests also use some constants placed in [test-constants.properties](tests/base/src/test/resources/test-constants.properties). A different file can be specified by `-Dtestsuite.constants=path/to/different-test-constants.properties`
|
||||
|
||||
|
@ -304,6 +304,17 @@ mvn -f testsuite/integration-arquillian/tests/other/console/pom.xml \
|
|||
-Dfirefox_binary=/opt/firefox-45.1.1esr/firefox
|
||||
```
|
||||
|
||||
## Base UI tests
|
||||
Similarly to Admin Console tests, these tests are focused on UI, specifically on the parts of the server that are accessed by an end user (like Login page, or Account Console).
|
||||
They are designed to work with mobile browsers (alongside the standard desktop browsers). For details on the supported browsers and their configuration please refer to [Different Browsers chapter](#different-browsers).
|
||||
#### Execution example
|
||||
```
|
||||
mvn -f testsuite/integration-arquillian/tests/other/base-ui/pom.xml \
|
||||
clean test \
|
||||
-Pandroid \
|
||||
-Dappium.avd=Nexus_5X_API_27
|
||||
```
|
||||
|
||||
## Welcome Page tests
|
||||
The Welcome Page tests need to be run on WildFly/EAP. So that they are disabled by default and are meant to be run separately.
|
||||
|
||||
|
@ -366,31 +377,70 @@ To run the tests run:
|
|||
To run individual social provider test only you can use option like `-Dtest=SocialLoginTest#linkedinLogin`
|
||||
|
||||
## Different Browsers
|
||||
You can use many different real-world browsers to run the integration tests.
|
||||
Although technically they can be run with almost every test in the testsuite, they can fail with some of them as the tests often require specific optimizations for given browser. Therefore, only some of the test modules have support to be run with specific browsers.
|
||||
|
||||
#### Mozilla Firefox
|
||||
* **Supported version:** [latest ESR](https://www.mozilla.org/en-US/firefox/organizations/) (Extended Support Release)
|
||||
* **Driver download required:** no (using the old legacy Firefox driver)
|
||||
* **Run with:** `-Dbrowser=firefox`; optionally you can specify `-Dfirefox_binary=path/to/firefox/binary`
|
||||
#### Mozilla Firefox with legacy driver
|
||||
* **Supported test modules:** `console`
|
||||
* **Supported version:** [52 ESR](https://www.mozilla.org/en-US/firefox/organizations/) (Extended Support Release)
|
||||
* **Driver download required:** no
|
||||
* **Run with:** `-Dbrowser=firefox -DfirefoxLegacyDriver=true`; optionally you can specify `-Dfirefox_binary=path/to/firefox/binary`
|
||||
|
||||
#### Mozilla Firefox with GeckoDriver
|
||||
You can also use Firefox automation with modern Marionette protocol and GeckoDriver. However, this is **highly experimental** and the testsuite may not work as expected.
|
||||
* **Supported test modules:** `base-ui`
|
||||
* **Supported version:** as latest as possible (Firefox has better support for Marionette with each version released)
|
||||
* **Driver download required:** [GeckoDriver](https://github.com/mozilla/geckodriver/releases)
|
||||
* **Run with:** `-Dbrowser=firefox -DfirefoxLegacyDriver=false -Dwebdriver.gecko.driver=path/to/geckodriver`
|
||||
|
||||
#### Google Chrome
|
||||
* **Supported test modules:** `console`, `base-ui`
|
||||
* **Supported version:** latest stable
|
||||
* **Driver download required:** [ChromeDriver](https://sites.google.com/a/chromium.org/chromedriver/) which corresponds with your version of the browser
|
||||
* **Driver download required:** [ChromeDriver](https://sites.google.com/a/chromium.org/chromedriver/) that corresponds with your version of the browser
|
||||
* **Run with:** `-Dbrowser=chrome -Dwebdriver.chrome.driver=path/to/chromedriver`
|
||||
|
||||
#### Internet Explorer
|
||||
* **Supported test modules:** `console`, `base-ui`
|
||||
* **Supported version:** 11
|
||||
* **Driver download required:** [Internet Explorer Driver Server](http://www.seleniumhq.org/download/); recommended version [3.5.1 32-bit](http://selenium-release.storage.googleapis.com/3.5/IEDriverServer_Win32_3.5.1.zip)
|
||||
* **Run with:** `-Dbrowser=internetExplorer -Dwebdriver.ie.driver=path/to/IEDriverServer.exe`
|
||||
|
||||
#### Apple Safari
|
||||
* **Supported test modules:** `base-ui`
|
||||
* **Supported version:** latest stable
|
||||
* **Driver download required:** no (the driver is bundled with macOS)
|
||||
* **Run with:** `-Dbrowser=safari`
|
||||
|
||||
#### Automatic driver downloads
|
||||
You can rely on automatic driver downloads which is provided by [Arquillian Drone](http://arquillian.org/arquillian-extension-drone/#_automatic_download). To do so just omit the `-Dwebdriver.{browser}.driver` CLI argument when running the tests.
|
||||
|
||||
#### Mobile browsers
|
||||
The support for testing with the mobile browsers is implemented using the [Appium](http://appium.io/) project.
|
||||
This means the tests can be run with a real mobile browser in a real mobile OS. However, only emulators/simulators of mobile devices are supported at the moment (no physical devices) in our testsuite.
|
||||
|
||||
First, you need to install the Appium server. If you have Node.js and npm installed on your machine, you can do that with: `npm install -g appium`. For further details and requirements please refer to the [official Appium documentation](http://appium.io/docs/en/about-appium/intro/).
|
||||
The tests will try to start the Appium server automatically but you can do it manually as well (just by executing `appium`).
|
||||
|
||||
To use a mobile browser you need to create a virtual device. The most convenient way to do so is to install the desired platform's IDE - either [Android Studio](https://developer.android.com/studio/) (for Android devices) or [Xcode](https://developer.apple.com/xcode/) (for iOS devices) - then you can create a device (smartphone/tablet) there. For details please refer to documentation of those IDEs.
|
||||
|
||||
#### Google Chrome on Android
|
||||
* **Supported test modules:** `base-ui`
|
||||
* **Supported host OS:** Windows, Linux, macOS
|
||||
* **Supported browser version:** latest stable
|
||||
* **Supported mobile OS version:** Android 7.x, 8.x
|
||||
* **Run with:** `mvn clean test -Pandroid -Dappium.avd=name_of_the_AVD` where AVD is the name of your Android Virtual Device (e.g. `Nexus_5X_API_27`)
|
||||
|
||||
**Tips & tricks:**
|
||||
* If the AVD name contains any spaces, you need to replace them with underscores when specifying the `-Dappium.avd=...`.
|
||||
* It's probable that a freshly created device will contain an outdated Chrome version. To update to the latest version (without using the Play Store) you need to download an `.apk` for Chrome and install it with `adb install -r path/to/chrome.apk`.
|
||||
* Chrome on Android uses ChromeDriver similarly to regular desktop Chrome. The ChromeDriver is bundled with the Appium server. To use a newer ChromeDriver please follow the [Appium documentation](http://appium.io/docs/en/writing-running-appium/web/chromedriver/).
|
||||
|
||||
#### Apple Safari on iOS
|
||||
* **Supported test modules:** `base-ui`
|
||||
* **Supported host OS:** macOS
|
||||
* **Supported browser version:** _depends on the mobile OS version_
|
||||
* **Supported mobile OS version:** iOS 11.x
|
||||
* **Run with:** `mvn clean test -Pios -Dappium.deviceName=device_name` where the device name is your device identification (e.g. `iPhone X`)
|
||||
|
||||
## Run X.509 tests
|
||||
|
||||
To run the X.509 client certificate authentication tests:
|
||||
|
|
|
@ -84,7 +84,7 @@ and the URL hierarchy is modeled by the class inheritance hierarchy (subclasses/
|
|||
|
||||
The default browser for UI testing is `htmlunit` which is used for fast "headless" testing.
|
||||
Other browsers can be selected with the `-Dbrowser` property, for example `firefox`.
|
||||
See [HOW-TO-RUN.md](HOW-TO-RUN.md) and Arquillian Graphene documentation for more details.
|
||||
See [HOW-TO-RUN.md](HOW-TO-RUN.md) and Arquillian Drone documentation for more details.
|
||||
|
||||
### Utils classes
|
||||
UI testing is sometimes very tricky due to different demands and behaviours of different browsers and their drivers. So there are some very useful Utils classes which are already dealing with some common stability issues while testing. See `UIUtils`, `URLUtils` and `WaitUtils` classes in the Base Testsuite.
|
||||
|
@ -97,6 +97,15 @@ UI testing is sometimes very tricky due to different demands and behaviours of d
|
|||
The base testsuite contains custom Arquillian extensions and most functional tests.
|
||||
The other test modules depend on this module.
|
||||
|
||||
### Base UI Testsuite
|
||||
Contains most of the UI-focused tests that don't cover Admin Console, i.e. all the parts of the server that are intended to be accessed by an end user.
|
||||
The tests placed here are exclusively covering the UI functionality of the server, i.e. checking if all the page elements are visible, links clickable etc., and are focused on simplicity and stability.
|
||||
This differs them from other integration tests and Admin Console UI tests.
|
||||
|
||||
They are designed to work with most of the desktop browsers (HtmlUnit included) as well as mobile browsers (Chrome on Android and Safari on iOS). Please see [HOW-TO-RUN.md](HOW-TO-RUN.md) for details on supported browsers.
|
||||
|
||||
The tests are place in a separate module (`tests/other/base-ui`) and are run with HtmlUnit by default.
|
||||
|
||||
### Admin Console UI Tests
|
||||
|
||||
Tests for Keycloak Admin Console are located in a separate module `tests/other/console`
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
<arquillian-core.version>1.2.1.Final</arquillian-core.version>
|
||||
<!--the version of shrinkwrap_resolver should align with the version in arquillian-bom-->
|
||||
<shrinkwrap-resolver.version>2.2.6</shrinkwrap-resolver.version>
|
||||
<selenium.version>3.11.0</selenium.version>
|
||||
<selenium.version>3.13.0</selenium.version>
|
||||
<arquillian-drone.version>2.5.1</arquillian-drone.version>
|
||||
<arquillian-graphene.version>2.3.2</arquillian-graphene.version>
|
||||
<arquillian-wildfly-container.version>2.1.0.Final</arquillian-wildfly-container.version>
|
||||
|
@ -61,6 +61,7 @@
|
|||
<undertow-embedded.version>1.0.0.Alpha2</undertow-embedded.version>
|
||||
<version.org.wildfly.extras.creaper>1.6.1</version.org.wildfly.extras.creaper>
|
||||
<testcontainers.version>1.5.1</testcontainers.version>
|
||||
<appium.client.version>6.1.0</appium.client.version>
|
||||
|
||||
<!--migration properties-->
|
||||
<migration.70.version>1.9.8.Final</migration.70.version>
|
||||
|
|
|
@ -186,7 +186,7 @@
|
|||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-arquillian-xml-and-password-blacklists</id>
|
||||
<id>copy-common-dependencies</id>
|
||||
<phase>generate-resources</phase>
|
||||
<goals>
|
||||
<goal>copy-resources</goal>
|
||||
|
@ -199,6 +199,7 @@
|
|||
<includes>
|
||||
<include>arquillian.xml</include>
|
||||
<include>password-blacklists/**</include>
|
||||
<include>log4j.properties</include>
|
||||
</includes>
|
||||
<!--<filtering>true</filtering>-->
|
||||
</resource>
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
package org.keycloak.testsuite.adapter.page;
|
||||
|
||||
import org.jboss.arquillian.test.api.ArquillianResource;
|
||||
import org.keycloak.testsuite.arquillian.annotation.AppServerContext;
|
||||
import org.keycloak.testsuite.arquillian.annotation.AppServerBrowserContext;
|
||||
import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl;
|
||||
|
||||
import java.net.URL;
|
||||
|
@ -30,7 +30,7 @@ import java.net.URL;
|
|||
public class AppServerContextRoot extends AbstractPageWithInjectedUrl {
|
||||
|
||||
@ArquillianResource
|
||||
@AppServerContext
|
||||
@AppServerBrowserContext
|
||||
private URL appServerContextRoot;
|
||||
|
||||
@Override
|
||||
|
|
|
@ -22,9 +22,9 @@ import org.jboss.arquillian.graphene.page.Page;
|
|||
import org.jboss.arquillian.test.api.ArquillianResource;
|
||||
import org.keycloak.testsuite.auth.page.login.OIDCLogin;
|
||||
import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl;
|
||||
import org.keycloak.testsuite.page.Form;
|
||||
import org.keycloak.testsuite.pages.ConsentPage;
|
||||
import org.keycloak.testsuite.util.JavascriptBrowser;
|
||||
import org.keycloak.testsuite.util.UIUtils;
|
||||
import org.keycloak.testsuite.util.URLUtils;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
@ -35,6 +35,7 @@ import java.net.URL;
|
|||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.keycloak.testsuite.util.UIUtils.clickLink;
|
||||
import static org.keycloak.testsuite.util.WaitUtils.pause;
|
||||
import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
|
||||
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
|
||||
|
@ -104,8 +105,8 @@ public class PhotozClientAuthzTestApp extends AbstractPageWithInjectedUrl {
|
|||
createAlbum.click();
|
||||
WebElement albumNameInput = driver.findElement(By.id("album.name"));
|
||||
waitUntilElement(albumNameInput).is().present();
|
||||
Form.setInputValue(albumNameInput, name);
|
||||
waitUntilElement(albumNameInput).attribute(Form.VALUE).contains(name);
|
||||
UIUtils.setTextInputValue(albumNameInput, name);
|
||||
waitUntilElement(albumNameInput).attribute(UIUtils.VALUE_ATTR_NAME).contains(name);
|
||||
WebElement button = driver.findElement(By.id(buttonId));
|
||||
waitUntilElement(button).is().clickable();
|
||||
button.click();
|
||||
|
@ -142,7 +143,7 @@ public class PhotozClientAuthzTestApp extends AbstractPageWithInjectedUrl {
|
|||
|
||||
public void navigateToAdminAlbum(boolean shouldBeDenied) {
|
||||
log.debug("Navigating to Admin Album");
|
||||
URLUtils.navigateToUri(toString() + "/#/admin/album", true);
|
||||
URLUtils.navigateToUri(toString() + "/#/admin/album");
|
||||
|
||||
driver.navigate().refresh(); // This is sometimes necessary for loading the new policy settings
|
||||
waitForPageToLoad();
|
||||
|
@ -157,8 +158,7 @@ public class PhotozClientAuthzTestApp extends AbstractPageWithInjectedUrl {
|
|||
public void logOut() {
|
||||
navigateTo();
|
||||
waitUntilElement(signOutButton).is().clickable(); // Sometimes doesn't work in PhantomJS!
|
||||
signOutButton.click();
|
||||
this.loginPage.form().waitForLoginButtonPresent();
|
||||
clickLink(signOutButton);
|
||||
}
|
||||
|
||||
public void requestEntitlement() {
|
||||
|
@ -210,7 +210,7 @@ public class PhotozClientAuthzTestApp extends AbstractPageWithInjectedUrl {
|
|||
|
||||
urlWithScopeParam.append(scopesValue);
|
||||
|
||||
URLUtils.navigateToUri(urlWithScopeParam.toString(), true);
|
||||
URLUtils.navigateToUri(urlWithScopeParam.toString());
|
||||
}
|
||||
|
||||
this.loginPage.form().login(username, password);
|
||||
|
@ -315,9 +315,9 @@ public class PhotozClientAuthzTestApp extends AbstractPageWithInjectedUrl {
|
|||
public void accountShareResource(String name, String user) {
|
||||
accountMyResource(name);
|
||||
WebElement userIdInput = driver.findElement(By.id("user_id"));
|
||||
Form.setInputValue(userIdInput, user);
|
||||
UIUtils.setTextInputValue(userIdInput, user);
|
||||
pause(200); // We need to wait a bit for the form to "accept" the input (otherwise it registers the input as empty)
|
||||
waitUntilElement(userIdInput).attribute(Form.VALUE).contains(user);
|
||||
waitUntilElement(userIdInput).attribute(UIUtils.VALUE_ATTR_NAME).contains(user);
|
||||
|
||||
WebElement shareButton = driver.findElement(By.id("share-button"));
|
||||
waitUntilElement(shareButton).is().clickable();
|
||||
|
@ -330,9 +330,9 @@ public class PhotozClientAuthzTestApp extends AbstractPageWithInjectedUrl {
|
|||
accountMyResource(name);
|
||||
|
||||
WebElement userIdInput = driver.findElement(By.id("user_id"));
|
||||
Form.setInputValue(userIdInput, user);
|
||||
UIUtils.setTextInputValue(userIdInput, user);
|
||||
pause(200); // We need to wait a bit for the form to "accept" the input (otherwise it registers the input as empty)
|
||||
waitUntilElement(userIdInput).attribute(Form.VALUE).contains(user);
|
||||
waitUntilElement(userIdInput).attribute(UIUtils.VALUE_ATTR_NAME).contains(user);
|
||||
|
||||
WebElement shareRemoveScope = driver.findElement(By.id("share-remove-scope-" + name + "-" + scope));
|
||||
waitUntilElement(shareRemoveScope).is().clickable();
|
||||
|
@ -379,13 +379,13 @@ public class PhotozClientAuthzTestApp extends AbstractPageWithInjectedUrl {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void navigateTo(boolean waitForMatch) {
|
||||
super.navigateTo(waitForMatch);
|
||||
public void navigateTo() {
|
||||
super.navigateTo();
|
||||
pause(WAIT_AFTER_OPERATION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCurrent() {
|
||||
return URLUtils.currentUrlStartWith(toString());
|
||||
return URLUtils.currentUrlStartsWith(toString());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.keycloak.testsuite.arquillian;
|
|||
|
||||
import java.io.File;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.jboss.arquillian.container.test.api.ContainerController;
|
||||
import org.jboss.arquillian.core.api.Instance;
|
||||
import org.jboss.arquillian.core.api.annotation.Inject;
|
||||
|
@ -97,6 +98,18 @@ public class AppServerTestEnricher {
|
|||
}
|
||||
}
|
||||
|
||||
public static String getAppServerBrowserContextRoot() throws MalformedURLException {
|
||||
return getAppServerBrowserContextRoot(new URL(getAuthServerContextRoot()));
|
||||
}
|
||||
|
||||
public static String getAppServerBrowserContextRoot(URL contextRoot) {
|
||||
String browserHost = System.getProperty("app.server.browserHost");
|
||||
if (StringUtils.isEmpty(browserHost)) {
|
||||
browserHost = contextRoot.getHost();
|
||||
}
|
||||
return String.format("%s://%s:%s", contextRoot.getProtocol(), browserHost, contextRoot.getPort());
|
||||
}
|
||||
|
||||
public void updateTestContextWithAppServerInfo(@Observes(precedence = 1) BeforeClass event) {
|
||||
testContext = testContextInstance.get();
|
||||
|
||||
|
@ -140,11 +153,12 @@ public class AppServerTestEnricher {
|
|||
private ContainerInfo updateWithAppServerInfo(ContainerInfo appServerInfo, int clusterPortOffset) {
|
||||
try {
|
||||
|
||||
String appServerContextRootStr = isRelative()
|
||||
URL appServerContextRoot = new URL(isRelative()
|
||||
? getAuthServerContextRoot(clusterPortOffset)
|
||||
: getAppServerContextRoot(clusterPortOffset);
|
||||
: getAppServerContextRoot(clusterPortOffset));
|
||||
|
||||
appServerInfo.setContextRoot(new URL(appServerContextRootStr));
|
||||
appServerInfo.setContextRoot(appServerContextRoot);
|
||||
appServerInfo.setBrowserContextRoot(new URL(getAppServerBrowserContextRoot(appServerContextRoot)));
|
||||
|
||||
} catch (MalformedURLException ex) {
|
||||
throw new IllegalArgumentException(ex);
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
package org.keycloak.testsuite.arquillian;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.jboss.arquillian.container.spi.ContainerRegistry;
|
||||
import org.jboss.arquillian.container.spi.event.StartContainer;
|
||||
import org.jboss.arquillian.container.spi.event.StartSuiteContainers;
|
||||
|
@ -120,6 +121,18 @@ public class AuthServerTestEnricher {
|
|||
return String.format("%s://%s:%s", scheme, host, port + clusterPortOffset);
|
||||
}
|
||||
|
||||
public static String getAuthServerBrowserContextRoot() throws MalformedURLException {
|
||||
return getAuthServerBrowserContextRoot(new URL(getAuthServerContextRoot()));
|
||||
}
|
||||
|
||||
public static String getAuthServerBrowserContextRoot(URL contextRoot) {
|
||||
String browserHost = System.getProperty("auth.server.browserHost");
|
||||
if (StringUtils.isEmpty(browserHost)) {
|
||||
browserHost = contextRoot.getHost();
|
||||
}
|
||||
return String.format("%s://%s:%s", contextRoot.getProtocol(), browserHost, contextRoot.getPort());
|
||||
}
|
||||
|
||||
public static OnlineManagementClient getManagementClient() {
|
||||
try {
|
||||
return ManagementClient.online(OnlineOptions
|
||||
|
@ -259,7 +272,10 @@ public class AuthServerTestEnricher {
|
|||
|
||||
private ContainerInfo updateWithAuthServerInfo(ContainerInfo authServerInfo, int clusterPortOffset) {
|
||||
try {
|
||||
authServerInfo.setContextRoot(new URL(getAuthServerContextRoot(clusterPortOffset)));
|
||||
URL contextRoot = new URL(getAuthServerContextRoot(clusterPortOffset));
|
||||
|
||||
authServerInfo.setContextRoot(contextRoot);
|
||||
authServerInfo.setBrowserContextRoot(new URL(getAuthServerBrowserContextRoot(contextRoot)));
|
||||
} catch (MalformedURLException ex) {
|
||||
throw new IllegalArgumentException(ex);
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import java.util.Objects;
|
|||
public class ContainerInfo implements Comparable<ContainerInfo> {
|
||||
|
||||
private URL contextRoot;
|
||||
private URL browserContextRoot;
|
||||
private Container arquillianContainer;
|
||||
|
||||
public ContainerInfo(Container arquillianContainer) {
|
||||
|
@ -53,6 +54,14 @@ public class ContainerInfo implements Comparable<ContainerInfo> {
|
|||
this.contextRoot = contextRoot;
|
||||
}
|
||||
|
||||
public void setBrowserContextRoot(URL browserContextRoot) {
|
||||
this.browserContextRoot = browserContextRoot;
|
||||
}
|
||||
|
||||
public URL getBrowserContextRoot() {
|
||||
return browserContextRoot;
|
||||
}
|
||||
|
||||
public boolean isUndertow() {
|
||||
return getQualifier().toLowerCase().contains("undertow");
|
||||
}
|
||||
|
|
|
@ -79,7 +79,7 @@ public class KeycloakArquillianExtension implements LoadableExtension {
|
|||
.override(ResourceProvider.class, ContainerCustomizableURLResourceProvider.class, URLProvider.class);
|
||||
|
||||
builder
|
||||
.override(Configurator.class, WebDriverFactory.class, KeycloakWebDriverConfigurator.class)
|
||||
.observer(KeycloakWebDriverConfigurator.class)
|
||||
.observer(HtmlUnitScreenshots.class)
|
||||
.observer(KeycloakDronePostSetup.class);
|
||||
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright 2017 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.testsuite.arquillian.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RUNTIME)
|
||||
@Target({ElementType.FIELD})
|
||||
public @interface AppServerBrowserContext
|
||||
{
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright 2017 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.testsuite.arquillian.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RUNTIME)
|
||||
@Target({ElementType.FIELD})
|
||||
public @interface AuthServerBrowserContext
|
||||
{
|
||||
}
|
|
@ -26,7 +26,9 @@ import org.jboss.logging.Logger;
|
|||
import org.jboss.logging.Logger.Level;
|
||||
import org.keycloak.testsuite.arquillian.SuiteContext;
|
||||
import org.keycloak.testsuite.arquillian.TestContext;
|
||||
import org.keycloak.testsuite.arquillian.annotation.AppServerBrowserContext;
|
||||
import org.keycloak.testsuite.arquillian.annotation.AppServerContext;
|
||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerBrowserContext;
|
||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContext;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
@ -108,6 +110,12 @@ public class URLProvider extends URLResourceProvider {
|
|||
|
||||
return appServerBackendsInfo.get(0).getContextRoot();
|
||||
}
|
||||
if (AuthServerBrowserContext.class.isAssignableFrom(a.annotationType())) {
|
||||
return suiteContext.get().getAuthServerInfo().getBrowserContextRoot();
|
||||
}
|
||||
if (AppServerBrowserContext.class.isAssignableFrom(a.annotationType())) {
|
||||
return testContext.get().getAppServerInfo().getBrowserContextRoot();
|
||||
}
|
||||
}
|
||||
|
||||
return url;
|
||||
|
|
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.testsuite.auth.page;
|
||||
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.util.UIUtils;
|
||||
import org.openqa.selenium.NoSuchElementException;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
||||
import static org.keycloak.testsuite.util.UIUtils.getTextFromElement;
|
||||
|
||||
/**
|
||||
* @author tkyjovsk
|
||||
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
||||
*/
|
||||
public class AccountFields extends FieldsBase {
|
||||
|
||||
@FindBy(id = "username")
|
||||
private WebElement usernameInput;
|
||||
@FindBy(xpath = "//label[@for='username']")
|
||||
private WebElement usernameLabel;
|
||||
|
||||
@FindBy(id = "email")
|
||||
private WebElement emailInput;
|
||||
@FindBy(xpath = "//label[@for='email']")
|
||||
private WebElement emailLabel;
|
||||
|
||||
@FindBy(id = "firstName")
|
||||
private WebElement firstNameInput;
|
||||
@FindBy(xpath = "//label[@for='firstName']")
|
||||
private WebElement firstNameLabel;
|
||||
|
||||
@FindBy(id = "lastName")
|
||||
private WebElement lastNameInput;
|
||||
@FindBy(xpath = "//label[@for='lastName']")
|
||||
private WebElement lastNameLabel;
|
||||
|
||||
public void setUsername(String username) {
|
||||
UIUtils.setTextInputValue(usernameInput, username);
|
||||
}
|
||||
|
||||
public AccountFields setEmail(String email) {
|
||||
UIUtils.setTextInputValue(emailInput, email);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccountFields setFirstName(String firstName) {
|
||||
UIUtils.setTextInputValue(firstNameInput, firstName);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccountFields setLastName(String lastName) {
|
||||
UIUtils.setTextInputValue(lastNameInput, lastName);
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return UIUtils.getTextInputValue(usernameInput);
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return UIUtils.getTextInputValue(emailInput);
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return UIUtils.getTextInputValue(firstNameInput);
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return UIUtils.getTextInputValue(lastNameInput);
|
||||
}
|
||||
|
||||
public void setValues(UserRepresentation user) {
|
||||
setUsername(user.getUsername());
|
||||
setEmail(user.getEmail());
|
||||
setFirstName(user.getFirstName());
|
||||
setLastName(user.getLastName());
|
||||
}
|
||||
|
||||
public boolean isUsernamePresent() {
|
||||
try {
|
||||
return usernameInput.isDisplayed();
|
||||
}
|
||||
catch (NoSuchElementException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String getUsernameLabel() {
|
||||
return getTextFromElement(usernameLabel);
|
||||
}
|
||||
|
||||
public String getEmailLabel() {
|
||||
return getTextFromElement(emailLabel);
|
||||
}
|
||||
|
||||
public String getFirstNameLabel() {
|
||||
return getTextFromElement(firstNameLabel);
|
||||
}
|
||||
|
||||
public String getLastNameLabel() {
|
||||
return getTextFromElement(lastNameLabel);
|
||||
}
|
||||
|
||||
public boolean hasUsernameError() {
|
||||
return hasFieldError(usernameInput);
|
||||
}
|
||||
|
||||
public boolean hasEmailError() {
|
||||
return hasFieldError(emailInput);
|
||||
}
|
||||
|
||||
public boolean hasFirstNameError() {
|
||||
return hasFieldError(firstNameInput);
|
||||
}
|
||||
|
||||
public boolean hasLastNameError() {
|
||||
return hasFieldError(lastNameInput);
|
||||
}
|
||||
|
||||
}
|
|
@ -18,7 +18,7 @@
|
|||
package org.keycloak.testsuite.auth.page;
|
||||
|
||||
import org.jboss.arquillian.test.api.ArquillianResource;
|
||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContext;
|
||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerBrowserContext;
|
||||
import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl;
|
||||
|
||||
import java.net.URL;
|
||||
|
@ -33,7 +33,7 @@ import java.net.URL;
|
|||
public class AuthServerContextRoot extends AbstractPageWithInjectedUrl {
|
||||
|
||||
@ArquillianResource
|
||||
@AuthServerContext
|
||||
@AuthServerBrowserContext
|
||||
private URL authServerContextRoot;
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
@ -15,27 +15,17 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.testsuite.auth.page.account;
|
||||
package org.keycloak.testsuite.auth.page;
|
||||
|
||||
import org.keycloak.testsuite.page.Form;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author tkyjovsk
|
||||
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
||||
*/
|
||||
public class ContactInfoFields extends Form {
|
||||
|
||||
@FindBy(id = "user.attributes.street")
|
||||
private WebElement streetInput;
|
||||
@FindBy(id = "user.attributes.locality")
|
||||
private WebElement localityInput;
|
||||
@FindBy(id = "user.attributes.region")
|
||||
private WebElement regionInput;
|
||||
@FindBy(id = "user.attributes.postal_code")
|
||||
private WebElement postalCodeInput;
|
||||
@FindBy(id = "user.attributes.country")
|
||||
private WebElement counryInput;
|
||||
|
||||
public class FieldsBase extends Form {
|
||||
protected boolean hasFieldError(WebElement field) {
|
||||
return field.findElement(By.xpath("../..")).getAttribute("class").contains("has-error");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.testsuite.auth.page;
|
||||
|
||||
import org.keycloak.testsuite.util.UIUtils;
|
||||
import org.openqa.selenium.NoSuchElementException;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
||||
import static org.keycloak.testsuite.util.UIUtils.getTextFromElement;
|
||||
|
||||
/**
|
||||
* @author tkyjovsk
|
||||
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
||||
*/
|
||||
public class PasswordFields extends FieldsBase {
|
||||
|
||||
@FindBy(id = "password")
|
||||
private WebElement passwordInput;
|
||||
@FindBy(xpath = "//label[@for='password']")
|
||||
private WebElement passwordLabel;
|
||||
|
||||
@FindBy(id = "password-new")
|
||||
private WebElement newPasswordInput;
|
||||
@FindBy(xpath = "//label[@for='password-new']")
|
||||
private WebElement newPasswordLabel;
|
||||
|
||||
@FindBy(id = "password-confirm")
|
||||
private WebElement confirmPasswordInput;
|
||||
@FindBy(xpath = "//label[@for='password-confirm']")
|
||||
private WebElement confirmPasswordLabel;
|
||||
|
||||
public void setPassword(String password) {
|
||||
UIUtils.setTextInputValue(passwordInput, password);
|
||||
}
|
||||
|
||||
public void setNewPassword(String newPassword) {
|
||||
UIUtils.setTextInputValue(newPasswordInput, newPassword);
|
||||
}
|
||||
|
||||
public void setConfirmPassword(String confirmPassword) {
|
||||
UIUtils.setTextInputValue(confirmPasswordInput, confirmPassword);
|
||||
}
|
||||
|
||||
public void setPasswords(String password, String newPassword, String confirmPassword) {
|
||||
setPassword(password);
|
||||
setNewPassword(newPassword);
|
||||
setConfirmPassword(confirmPassword);
|
||||
}
|
||||
|
||||
public boolean isConfirmPasswordPresent() {
|
||||
try {
|
||||
return confirmPasswordInput.isDisplayed();
|
||||
}
|
||||
catch (NoSuchElementException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String getPasswordLabel() {
|
||||
return getTextFromElement(passwordLabel);
|
||||
}
|
||||
|
||||
public String getNewPasswordLabel() {
|
||||
return getTextFromElement(newPasswordLabel);
|
||||
}
|
||||
|
||||
public String getConfirmPasswordLabel() {
|
||||
return getTextFromElement(confirmPasswordLabel);
|
||||
}
|
||||
|
||||
public boolean hasPasswordError() {
|
||||
return hasFieldError(passwordInput);
|
||||
}
|
||||
|
||||
public boolean hasNewPasswordError() {
|
||||
return hasFieldError(newPasswordInput);
|
||||
}
|
||||
|
||||
public boolean hasConfirmPasswordError() {
|
||||
return hasFieldError(confirmPasswordInput);
|
||||
}
|
||||
}
|
|
@ -21,7 +21,7 @@ import org.openqa.selenium.By;
|
|||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
||||
import static org.keycloak.testsuite.page.Form.setInputValue;
|
||||
import static org.keycloak.testsuite.util.UIUtils.setTextInputValue;
|
||||
import static org.keycloak.testsuite.util.UIUtils.clickLink;
|
||||
|
||||
/**
|
||||
|
@ -47,9 +47,9 @@ public class WelcomePage extends AuthServer {
|
|||
}
|
||||
|
||||
public void setPassword(String username, String password) {
|
||||
setInputValue(usernameInput, username);
|
||||
setInputValue(passwordInput, password);
|
||||
setInputValue(passwordConfirmationInput, password);
|
||||
setTextInputValue(usernameInput, username);
|
||||
setTextInputValue(passwordInput, password);
|
||||
setTextInputValue(passwordConfirmationInput, password);
|
||||
|
||||
clickLink(createButton);
|
||||
|
||||
|
|
|
@ -79,7 +79,7 @@ public class Account extends AccountManagement {
|
|||
}
|
||||
|
||||
public boolean isCurrent() {
|
||||
return URLUtils.currentUrlStartWith(toString()); // Sometimes after login the URL ends with /# or similar
|
||||
return URLUtils.currentUrlStartsWith(toString()); // Sometimes after login the URL ends with /# or similar
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,89 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.testsuite.auth.page.account;
|
||||
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.page.Form;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
||||
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
|
||||
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElementIsNotPresent;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author tkyjovsk
|
||||
*/
|
||||
public class AccountFields extends Form {
|
||||
|
||||
@FindBy(id = "username")
|
||||
private WebElement usernameInput;
|
||||
@FindBy(id = "email")
|
||||
private WebElement emailInput;
|
||||
@FindBy(id = "firstName")
|
||||
private WebElement firstNameInput;
|
||||
@FindBy(id = "lastName")
|
||||
private WebElement lastNameInput;
|
||||
|
||||
public void setUsername(String username) {
|
||||
Form.setInputValue(usernameInput, username);
|
||||
}
|
||||
|
||||
public AccountFields setEmail(String email) {
|
||||
Form.setInputValue(emailInput, email);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccountFields setFirstName(String firstName) {
|
||||
Form.setInputValue(firstNameInput, firstName);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccountFields setLastName(String lastName) {
|
||||
Form.setInputValue(lastNameInput, lastName);
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return Form.getInputValue(emailInput);
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return Form.getInputValue(firstNameInput);
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return Form.getInputValue(lastNameInput);
|
||||
}
|
||||
|
||||
public void setValues(UserRepresentation user) {
|
||||
setUsername(user.getUsername());
|
||||
setEmail(user.getEmail());
|
||||
setFirstName(user.getFirstName());
|
||||
setLastName(user.getLastName());
|
||||
}
|
||||
|
||||
public void waitForUsernameInputPresent() {
|
||||
waitUntilElement(usernameInput).is().present();
|
||||
}
|
||||
|
||||
public void waitForUsernameInputNotPresent() {
|
||||
waitUntilElementIsNotPresent(usernameInput);
|
||||
}
|
||||
|
||||
}
|
|
@ -17,6 +17,7 @@
|
|||
package org.keycloak.testsuite.auth.page.account;
|
||||
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.keycloak.testsuite.auth.page.PasswordFields;
|
||||
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.testsuite.auth.page.account;
|
||||
|
||||
import org.keycloak.testsuite.page.Form;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
||||
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author tkyjovsk
|
||||
*/
|
||||
public class PasswordFields extends Form {
|
||||
|
||||
@FindBy(id = "password")
|
||||
private WebElement passwordInput;
|
||||
@FindBy(id = "password-new")
|
||||
private WebElement newPasswordInput;
|
||||
@FindBy(id = "password-confirm")
|
||||
private WebElement confirmPasswordInput;
|
||||
|
||||
public void setPassword(String password) {
|
||||
setInputValue(passwordInput, password);
|
||||
}
|
||||
|
||||
public void setNewPassword(String newPassword) {
|
||||
setInputValue(newPasswordInput, newPassword);
|
||||
}
|
||||
|
||||
public void setConfirmPassword(String confirmPassword) {
|
||||
setInputValue(confirmPasswordInput, confirmPassword);
|
||||
}
|
||||
|
||||
public void setPasswords(String password, String newPassword, String confirmPassword) {
|
||||
setPassword(password);
|
||||
setNewPassword(newPassword);
|
||||
setConfirmPassword(confirmPassword);
|
||||
}
|
||||
|
||||
public void waitForConfirmPasswordInputPresent() {
|
||||
waitUntilElement(confirmPasswordInput).is().present();
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
@ -17,8 +17,6 @@
|
|||
|
||||
package org.keycloak.testsuite.auth.page.login;
|
||||
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
|
||||
/**
|
||||
|
@ -26,17 +24,8 @@ import javax.ws.rs.core.UriBuilder;
|
|||
* @author tkyjovsk
|
||||
*/
|
||||
public abstract class Authenticate extends LoginActions {
|
||||
|
||||
@Override
|
||||
public UriBuilder createUriBuilder() {
|
||||
return super.createUriBuilder().path("authenticate");
|
||||
}
|
||||
|
||||
@Page
|
||||
private LoginForm login;
|
||||
|
||||
public LoginForm loginForm() {
|
||||
return login;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.testsuite.auth.page.login;
|
||||
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.NoSuchElementException;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.keycloak.testsuite.util.UIUtils.getTextFromElement;
|
||||
|
||||
/**
|
||||
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
||||
*/
|
||||
public class FeedbackMessage {
|
||||
@FindBy(css = ".alert")
|
||||
private WebElement alertRoot;
|
||||
|
||||
public boolean isPresent() {
|
||||
try {
|
||||
return alertRoot.isDisplayed();
|
||||
}
|
||||
catch (NoSuchElementException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return getTextFromElement(alertRoot.findElement(By.className("kc-feedback-text")));
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
String cssClass = alertRoot.getAttribute("class");
|
||||
Matcher classMatcher = Pattern.compile("alert-(.+)").matcher(cssClass);
|
||||
if (!classMatcher.find()) {
|
||||
throw new RuntimeException("Failed to identify feedback message type");
|
||||
}
|
||||
return classMatcher.group(1);
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return getType().equals("success");
|
||||
}
|
||||
|
||||
public boolean isWarning() {
|
||||
return getType().equals("warning");
|
||||
}
|
||||
|
||||
public boolean isError() {
|
||||
return getType().equals("error");
|
||||
}
|
||||
|
||||
public boolean isInfo() {
|
||||
return getType().equals("info");
|
||||
}
|
||||
}
|
|
@ -17,25 +17,20 @@
|
|||
package org.keycloak.testsuite.auth.page.login;
|
||||
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.keycloak.testsuite.auth.page.AuthRealm;
|
||||
import org.openqa.selenium.By;
|
||||
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
|
||||
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Petr Mensik
|
||||
* @author tkyjovsk
|
||||
*/
|
||||
public abstract class Login extends AuthRealm {
|
||||
public abstract class Login extends LoginBase {
|
||||
|
||||
public static final String PROTOCOL = "protocol";
|
||||
public static final String OIDC = "openid-connect";
|
||||
public static final String SAML = "saml";
|
||||
public static final String LOGIN_ACTION = "login-action";
|
||||
private String keycloakThemeCssName;
|
||||
|
||||
@Override
|
||||
public UriBuilder createUriBuilder() {
|
||||
|
@ -58,23 +53,4 @@ public abstract class Login extends AuthRealm {
|
|||
return form;
|
||||
}
|
||||
|
||||
public void setKeycloakThemeCssName(String name) {
|
||||
keycloakThemeCssName = name;
|
||||
}
|
||||
|
||||
protected By getKeycloakThemeLocator() {
|
||||
if (keycloakThemeCssName == null) {
|
||||
throw new IllegalStateException("keycloakThemeCssName property must be set");
|
||||
}
|
||||
return By.cssSelector("link[href*='login/" + keycloakThemeCssName + "/css/login.css']");
|
||||
}
|
||||
|
||||
public void waitForKeycloakThemeNotPresent() {
|
||||
waitUntilElement(getKeycloakThemeLocator()).is().not().present();
|
||||
}
|
||||
|
||||
public void waitForKeycloakThemePresent() {
|
||||
waitUntilElement(getKeycloakThemeLocator()).is().present();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,22 +16,19 @@
|
|||
*/
|
||||
package org.keycloak.testsuite.auth.page.login;
|
||||
|
||||
import org.keycloak.testsuite.auth.page.AuthRealm;
|
||||
import org.keycloak.testsuite.util.URLUtils;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
|
||||
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
|
||||
import static org.keycloak.testsuite.util.UIUtils.clickLink;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author tkyjovsk
|
||||
*/
|
||||
public class LoginActions extends AuthRealm {
|
||||
|
||||
@FindBy(id = "kc-page-title")
|
||||
protected WebElement heading;
|
||||
public class LoginActions extends LoginBase {
|
||||
|
||||
@Override
|
||||
public UriBuilder createUriBuilder() {
|
||||
|
@ -42,23 +39,12 @@ public class LoginActions extends AuthRealm {
|
|||
@FindBy(css = "input[type='submit']")
|
||||
private WebElement submitButton;
|
||||
|
||||
@FindBy(css = "div[id='kc-form-options'] span a")
|
||||
private WebElement backToLoginForm;
|
||||
|
||||
@FindBy(xpath = "//span[@class='kc-feedback-text' and string-length(text())>1]")
|
||||
private WebElement feedbackText;
|
||||
|
||||
public String getFeedbackText() {
|
||||
waitUntilElement(feedbackText, "Feedback message should be present").is().visible();
|
||||
return feedbackText.getText();
|
||||
}
|
||||
|
||||
public void backToLoginPage() {
|
||||
backToLoginForm.click();
|
||||
}
|
||||
|
||||
public void submit() {
|
||||
submitButton.click();
|
||||
clickLink(submitButton);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCurrent() {
|
||||
return URLUtils.currentUrlStartsWith(toString() + "?"); // ignore the query string
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.testsuite.auth.page.login;
|
||||
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.keycloak.testsuite.auth.page.AuthRealm;
|
||||
import org.keycloak.testsuite.console.page.fragment.LocaleDropdown;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
||||
import static org.keycloak.testsuite.util.UIUtils.getTextFromElement;
|
||||
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
|
||||
|
||||
/**
|
||||
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
||||
*/
|
||||
public abstract class LoginBase extends AuthRealm {
|
||||
@Page
|
||||
protected FeedbackMessage feedbackMessage;
|
||||
|
||||
@FindBy(id = "kc-page-title")
|
||||
protected WebElement title;
|
||||
|
||||
@FindBy(id = "kc-header-wrapper")
|
||||
protected WebElement header;
|
||||
|
||||
@FindBy(id = "kc-locale-dropdown")
|
||||
private LocaleDropdown localeDropdown;
|
||||
|
||||
protected String keycloakThemeCssName;
|
||||
|
||||
public FeedbackMessage feedbackMessage() {
|
||||
return feedbackMessage;
|
||||
}
|
||||
|
||||
public String getTitleText() {
|
||||
return getTextFromElement(title);
|
||||
}
|
||||
|
||||
public String getHeaderText() {
|
||||
return getTextFromElement(header);
|
||||
}
|
||||
|
||||
protected By getKeycloakThemeLocator() {
|
||||
if (keycloakThemeCssName == null) {
|
||||
throw new IllegalStateException("keycloakThemeCssName property must be set");
|
||||
}
|
||||
return By.cssSelector("link[href*='login/" + keycloakThemeCssName + "/css/login.css']");
|
||||
}
|
||||
|
||||
public void waitForKeycloakThemeNotPresent() {
|
||||
waitUntilElement(getKeycloakThemeLocator()).is().not().present();
|
||||
}
|
||||
|
||||
public void waitForKeycloakThemePresent() {
|
||||
waitUntilElement(getKeycloakThemeLocator()).is().present();
|
||||
}
|
||||
|
||||
public void setKeycloakThemeCssName(String name) {
|
||||
keycloakThemeCssName = name;
|
||||
}
|
||||
|
||||
public LocaleDropdown localeDropdown() {
|
||||
return localeDropdown;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.testsuite.auth.page.login;
|
||||
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
||||
import static org.keycloak.testsuite.util.UIUtils.clickLink;
|
||||
import static org.keycloak.testsuite.util.UIUtils.getTextFromElement;
|
||||
|
||||
/**
|
||||
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
||||
*/
|
||||
public class LoginError extends LoginBase {
|
||||
@FindBy(xpath = "//div[@id='kc-error-message']/p[@class='instruction']")
|
||||
private WebElement errorMessage;
|
||||
|
||||
@FindBy(id = "backToApplication")
|
||||
private WebElement backToApplicationLink;
|
||||
|
||||
public String getErrorMessage() {
|
||||
return getTextFromElement(errorMessage);
|
||||
}
|
||||
|
||||
public void backToApplication() {
|
||||
clickLink(backToApplicationLink);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCurrent() {
|
||||
return getTitleText().equals("We're sorry...");
|
||||
}
|
||||
}
|
|
@ -18,16 +18,17 @@ package org.keycloak.testsuite.auth.page.login;
|
|||
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.auth.page.account.AccountFields;
|
||||
import org.keycloak.testsuite.auth.page.account.PasswordFields;
|
||||
import org.keycloak.testsuite.auth.page.AccountFields;
|
||||
import org.keycloak.testsuite.auth.page.PasswordFields;
|
||||
import org.keycloak.testsuite.page.Form;
|
||||
import org.keycloak.testsuite.util.UIUtils;
|
||||
import org.openqa.selenium.NoSuchElementException;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
||||
import static org.keycloak.testsuite.admin.Users.getPasswordOf;
|
||||
import static org.keycloak.testsuite.util.UIUtils.clickLink;
|
||||
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
|
||||
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElementIsNotPresent;
|
||||
import static org.keycloak.testsuite.util.UIUtils.getTextFromElement;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -44,18 +45,16 @@ public class LoginForm extends Form {
|
|||
|
||||
@FindBy(name = "login")
|
||||
private WebElement loginButton;
|
||||
// @FindBy(name = "cancel")
|
||||
// private WebElement cancelButton;
|
||||
|
||||
@FindBy(xpath = "//div[@id='kc-registration']/span/a")
|
||||
private WebElement registerLink;
|
||||
@FindBy(linkText = "Forgot Password?")
|
||||
private WebElement forgottenPassword;
|
||||
|
||||
@FindBy(id = "rememberMe")
|
||||
private WebElement rememberMe;
|
||||
|
||||
@FindBy(xpath = ".//label[@for='password']")
|
||||
private WebElement labelPassword;
|
||||
@FindBy(xpath = "//input[@id='rememberMe']/parent::label")
|
||||
private WebElement rememberMeLabel;
|
||||
|
||||
public void setUsername(String username) {
|
||||
accountFields.setUsername(username);
|
||||
|
@ -94,39 +93,66 @@ public class LoginForm extends Form {
|
|||
}
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public void cancel() {
|
||||
// waitUntilElement(cancelButton).is().present();
|
||||
// cancelButton.click();
|
||||
// }
|
||||
public void waitForUsernameInputPresent() {
|
||||
accountFields.waitForUsernameInputPresent();
|
||||
public boolean isRememberMe() {
|
||||
return rememberMe.isSelected();
|
||||
}
|
||||
|
||||
public void waitForRegisterLinkNotPresent() {
|
||||
waitUntilElementIsNotPresent(registerLink);
|
||||
public boolean isUsernamePresent() {
|
||||
return accountFields.isUsernamePresent();
|
||||
}
|
||||
|
||||
public void waitForResetPasswordLinkNotPresent() {
|
||||
waitUntilElement(forgottenPassword).is().not().present();
|
||||
public boolean isRegisterLinkPresent() {
|
||||
try {
|
||||
return registerLink.isDisplayed();
|
||||
}
|
||||
catch (NoSuchElementException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void waitForRememberMePresent() {
|
||||
waitUntilElement(rememberMe).is().present();
|
||||
public boolean isForgotPasswordLinkPresent() {
|
||||
try {
|
||||
return forgottenPassword.isDisplayed();
|
||||
}
|
||||
catch (NoSuchElementException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void waitForRememberMeNotPresent() {
|
||||
waitUntilElementIsNotPresent(rememberMe);
|
||||
public boolean isRememberMePresent() {
|
||||
try {
|
||||
return rememberMe.isDisplayed();
|
||||
}
|
||||
catch (NoSuchElementException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void waitForLoginButtonPresent() {
|
||||
waitUntilElement(loginButton).is().present();
|
||||
public boolean isLoginButtonPresent() {
|
||||
try {
|
||||
return loginButton.isDisplayed();
|
||||
}
|
||||
catch (NoSuchElementException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public TotpSetupForm totpForm() {
|
||||
return totpForm;
|
||||
}
|
||||
|
||||
public String getUsernameLabel() {
|
||||
return accountFields.getUsernameLabel();
|
||||
}
|
||||
|
||||
public String getPasswordLabel() {
|
||||
return passwordFields.getPasswordLabel();
|
||||
}
|
||||
|
||||
public String getRememberMeLabel() {
|
||||
return getTextFromElement(rememberMeLabel);
|
||||
}
|
||||
|
||||
public class TotpSetupForm extends Form {
|
||||
@FindBy(id = "totp")
|
||||
private WebElement totpInputField;
|
||||
|
@ -137,16 +163,12 @@ public class LoginForm extends Form {
|
|||
@FindBy(xpath = ".//input[@value='Submit']")
|
||||
private WebElement submit;
|
||||
|
||||
public void waitForTotpInputFieldPresent() {
|
||||
waitUntilElement(totpInputField).is().present();
|
||||
}
|
||||
|
||||
public void setTotp(String value) {
|
||||
setInputValue(totpInputField, value);
|
||||
UIUtils.setTextInputValue(totpInputField, value);
|
||||
}
|
||||
|
||||
public String getTotpSecret() {
|
||||
return totpSecret.getAttribute(VALUE);
|
||||
return totpSecret.getAttribute(UIUtils.VALUE_ATTR_NAME);
|
||||
}
|
||||
|
||||
public void submit() {
|
||||
|
|
|
@ -16,40 +16,56 @@
|
|||
*/
|
||||
package org.keycloak.testsuite.auth.page.login;
|
||||
|
||||
import org.keycloak.common.util.CollectionUtil;
|
||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||
import org.keycloak.testsuite.util.DroneUtils;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
||||
import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.keycloak.testsuite.util.UIUtils.clickLink;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
||||
*/
|
||||
public class OAuthGrant extends LoginActions {
|
||||
|
||||
public class OAuthGrant extends RequiredActions {
|
||||
@FindBy(css = "input[name=\"accept\"]")
|
||||
private WebElement acceptButton;
|
||||
|
||||
@FindBy(css = "input[name=\"cancel\"]")
|
||||
private WebElement cancelButton;
|
||||
|
||||
@FindBy(xpath = "//div[@id='kc-oauth']/ul/li/span")
|
||||
private List<WebElement> scopesToApprove;
|
||||
|
||||
@Override
|
||||
public String getActionId() {
|
||||
return AuthenticatedClientSessionModel.Action.OAUTH_GRANT.name();
|
||||
}
|
||||
|
||||
public void accept() {
|
||||
acceptButton.click();
|
||||
clickLink(acceptButton);
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
cancelButton.click();
|
||||
clickLink(cancelButton);
|
||||
}
|
||||
|
||||
|
||||
public boolean isCurrent(WebDriver driver1) {
|
||||
if (driver1 == null) driver1 = driver;
|
||||
waitForPageToLoad();
|
||||
return driver1.getPageSource().contains("Do you grant these access privileges");
|
||||
DroneUtils.addWebDriver(driver1);
|
||||
boolean ret = super.isCurrent();
|
||||
DroneUtils.removeWebDriver();
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCurrent() {
|
||||
return isCurrent(null);
|
||||
public void assertClientScopes(List<String> expectedScopes) {
|
||||
List<String> actualScopes = scopesToApprove.stream().map(WebElement::getText).collect(Collectors.toList());
|
||||
assertTrue("Expected and actual Client Scopes to approve don't match",
|
||||
CollectionUtil.collectionEquals(expectedScopes, actualScopes)); // order of scopes doesn't matter
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.testsuite.auth.page.login;
|
||||
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.openqa.selenium.NoSuchElementException;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
||||
import static org.keycloak.testsuite.util.UIUtils.clickLink;
|
||||
|
||||
/**
|
||||
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
||||
*/
|
||||
public class OTPSetup extends RequiredActions {
|
||||
@Page
|
||||
private LoginForm.TotpSetupForm form;
|
||||
|
||||
@FindBy(id = "kc-totp-secret-qr-code")
|
||||
private WebElement barcodeImg;
|
||||
|
||||
@FindBy(id = "kc-totp-secret-key")
|
||||
private WebElement secretKey;
|
||||
|
||||
@FindBy(id = "mode-manual")
|
||||
private WebElement manualModeLink;
|
||||
|
||||
@FindBy(id = "mode-barcode")
|
||||
private WebElement barcodeModeLink;
|
||||
|
||||
@FindBy(id = "kc-totp-type")
|
||||
private WebElement otpType;
|
||||
|
||||
@FindBy(id = "kc-totp-algorithm")
|
||||
private WebElement otpAlgorithm;
|
||||
|
||||
@FindBy(id = "kc-totp-digits")
|
||||
private WebElement otpDigits;
|
||||
|
||||
@FindBy(id = "kc-totp-period")
|
||||
private WebElement otpPeriod;
|
||||
|
||||
@FindBy(id = "kc-totp-counter")
|
||||
private WebElement otpCounter;
|
||||
|
||||
public void setTotp(String value) {
|
||||
form.setTotp(value);
|
||||
}
|
||||
|
||||
public boolean isBarcodePresent() {
|
||||
try {
|
||||
return barcodeImg.isDisplayed();
|
||||
}
|
||||
catch (NoSuchElementException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String getSecretKey() {
|
||||
return secretKey.getText().replace(" ", "");
|
||||
}
|
||||
|
||||
public boolean isSecretKeyPresent() {
|
||||
try {
|
||||
return secretKey.isDisplayed();
|
||||
}
|
||||
catch (NoSuchElementException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void clickManualMode() {
|
||||
clickLink(manualModeLink);
|
||||
}
|
||||
|
||||
public void clickBarcodeMode() {
|
||||
clickLink(barcodeModeLink);
|
||||
}
|
||||
|
||||
public String getOtpType() {
|
||||
return otpType.getText();
|
||||
}
|
||||
|
||||
public String getOtpAlgorithm() {
|
||||
return otpAlgorithm.getText();
|
||||
}
|
||||
|
||||
public String getOtpDigits() {
|
||||
return otpDigits.getText();
|
||||
}
|
||||
|
||||
public String getOtpPeriod() {
|
||||
return otpPeriod.getText();
|
||||
}
|
||||
|
||||
public String getOtpCounter() {
|
||||
return otpCounter.getText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getActionId() {
|
||||
return UserModel.RequiredAction.CONFIGURE_TOTP.name();
|
||||
}
|
||||
}
|
|
@ -17,18 +17,47 @@
|
|||
package org.keycloak.testsuite.auth.page.login;
|
||||
|
||||
import org.keycloak.testsuite.auth.page.login.LoginForm.TotpSetupForm;
|
||||
import org.openqa.selenium.NoSuchElementException;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
||||
import static org.keycloak.testsuite.util.UIUtils.getTextFromElement;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author <a href="mailto:vramik@redhat.com">Vlastislav Ramik</a>
|
||||
*/
|
||||
public class OneTimeCode extends Authenticate {
|
||||
|
||||
@FindBy(id = "kc-totp-login-form")
|
||||
private TotpSetupForm form;
|
||||
|
||||
@FindBy(xpath = ".//label[@for='totp']")
|
||||
private WebElement totpInputLabel;
|
||||
|
||||
public TotpSetupForm form() {
|
||||
return form;
|
||||
}
|
||||
|
||||
public String getTotpLabel() {
|
||||
return getTextFromElement(totpInputLabel);
|
||||
}
|
||||
|
||||
public boolean isTotpLabelPresent() {
|
||||
try {
|
||||
return totpInputLabel.isDisplayed();
|
||||
}
|
||||
catch (NoSuchElementException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void sendCode(String code) {
|
||||
form.setTotp(code);
|
||||
submit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCurrent() {
|
||||
return super.isCurrent() && isTotpLabelPresent();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,17 +18,19 @@ package org.keycloak.testsuite.auth.page.login;
|
|||
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.auth.page.account.AccountFields;
|
||||
import org.keycloak.testsuite.auth.page.account.ContactInfoFields;
|
||||
import org.keycloak.testsuite.auth.page.account.PasswordFields;
|
||||
import org.keycloak.testsuite.auth.page.AccountFields;
|
||||
import org.keycloak.testsuite.auth.page.PasswordFields;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
|
||||
import static org.keycloak.testsuite.admin.Users.getPasswordOf;
|
||||
import static org.keycloak.testsuite.util.UIUtils.clickLink;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Filip Kiss
|
||||
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
||||
*/
|
||||
public class Registration extends LoginActions {
|
||||
|
||||
|
@ -44,8 +46,8 @@ public class Registration extends LoginActions {
|
|||
@Page
|
||||
private PasswordFields passwordFields;
|
||||
|
||||
@Page
|
||||
private ContactInfoFields contactInfoFields;
|
||||
@FindBy(xpath = "//a[contains(., 'Back to Login')]")
|
||||
private WebElement backToLoginLink;
|
||||
|
||||
public void register(UserRepresentation user) {
|
||||
setValues(user);
|
||||
|
@ -62,16 +64,23 @@ public class Registration extends LoginActions {
|
|||
passwordFields.setConfirmPassword(confirmPassword);
|
||||
}
|
||||
|
||||
public void waitForUsernameInputPresent() {
|
||||
accountFields.waitForUsernameInputPresent();
|
||||
public boolean isUsernamePresent() {
|
||||
return accountFields.isUsernamePresent();
|
||||
}
|
||||
|
||||
public void waitForUsernameInputNotPresent() {
|
||||
accountFields.waitForUsernameInputNotPresent();
|
||||
public boolean isConfirmPasswordPresent() {
|
||||
return passwordFields.isConfirmPasswordPresent();
|
||||
}
|
||||
|
||||
public void waitForConfirmPasswordInputPresent() {
|
||||
passwordFields.waitForConfirmPasswordInputPresent();
|
||||
public AccountFields accountFields() {
|
||||
return accountFields;
|
||||
}
|
||||
|
||||
public PasswordFields passwordFields() {
|
||||
return passwordFields;
|
||||
}
|
||||
|
||||
public void backToLogin() {
|
||||
clickLink(backToLoginLink);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.testsuite.auth.page.login;
|
||||
|
||||
import org.keycloak.testsuite.util.URLUtils;
|
||||
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
|
||||
/**
|
||||
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
||||
*/
|
||||
public abstract class RequiredActions extends LoginActions {
|
||||
|
||||
@Override
|
||||
public UriBuilder createUriBuilder() {
|
||||
return super.createUriBuilder().path("required-action");
|
||||
}
|
||||
|
||||
public abstract String getActionId();
|
||||
|
||||
@Override
|
||||
public boolean isCurrent() {
|
||||
return URLUtils.currentUrlWithQueryEquals(toString(), "execution=" + getActionId());
|
||||
}
|
||||
}
|
|
@ -16,20 +16,19 @@
|
|||
*/
|
||||
package org.keycloak.testsuite.auth.page.login;
|
||||
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.keycloak.testsuite.auth.page.account.AccountFields;
|
||||
import org.keycloak.testsuite.auth.page.account.PasswordFields;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
|
||||
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
|
||||
import static org.keycloak.testsuite.util.UIUtils.clickLink;
|
||||
import static org.keycloak.testsuite.util.UIUtils.setTextInputValue;
|
||||
import static org.keycloak.testsuite.util.UIUtils.getTextFromElement;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author vramik
|
||||
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
||||
*/
|
||||
public class ResetCredentials extends LoginActions {
|
||||
|
||||
|
@ -38,27 +37,25 @@ public class ResetCredentials extends LoginActions {
|
|||
return super.createUriBuilder().path("reset-credentials");
|
||||
}
|
||||
|
||||
@Page
|
||||
private AccountFields accountFields;
|
||||
@Page
|
||||
private PasswordFields passwordFields;
|
||||
@FindBy(id = "username")
|
||||
private WebElement usernameOrEmailInput;
|
||||
|
||||
@FindBy(xpath = "//a[contains(., 'Back to Login')]")
|
||||
private WebElement backToLoginLink;
|
||||
|
||||
@FindBy(id = "kc-info")
|
||||
private WebElement info;
|
||||
|
||||
public void resetCredentials(String value) {
|
||||
accountFields.setUsername(value);
|
||||
public void resetCredentials(String usernameOrEmail) {
|
||||
setTextInputValue(usernameOrEmailInput, usernameOrEmail);
|
||||
submit();
|
||||
}
|
||||
|
||||
public void updatePassword(String password) {
|
||||
passwordFields.setNewPassword(password);
|
||||
passwordFields.setConfirmPassword(password);
|
||||
submit();
|
||||
public void backToLogin() {
|
||||
clickLink(backToLoginLink);
|
||||
}
|
||||
|
||||
public String getInfoMessage() {
|
||||
waitUntilElement(info, "Info message should be visible").is().present();
|
||||
return info.getText();
|
||||
return getTextFromElement(info);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,14 +16,16 @@
|
|||
*/
|
||||
package org.keycloak.testsuite.auth.page.login;
|
||||
|
||||
import org.keycloak.testsuite.util.WaitUtils;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
||||
import static org.keycloak.testsuite.util.UIUtils.clickLink;
|
||||
import static org.keycloak.testsuite.util.UIUtils.getTextFromElement;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class TermsAndConditions extends LoginActions {
|
||||
public class TermsAndConditions extends RequiredActions {
|
||||
|
||||
@FindBy(id = "kc-accept")
|
||||
private WebElement acceptButton;
|
||||
|
@ -35,17 +37,15 @@ public class TermsAndConditions extends LoginActions {
|
|||
private WebElement textElem;
|
||||
|
||||
@Override
|
||||
public boolean isCurrent() {
|
||||
return heading.getText().equals("Terms and Conditions");
|
||||
public String getActionId() {
|
||||
return "terms_and_conditions";
|
||||
}
|
||||
|
||||
public void acceptTerms() {
|
||||
acceptButton.click();
|
||||
WaitUtils.waitForPageToLoad();
|
||||
clickLink(acceptButton);
|
||||
}
|
||||
public void declineTerms() {
|
||||
declineButton.click();
|
||||
WaitUtils.waitForPageToLoad();
|
||||
clickLink(declineButton);
|
||||
}
|
||||
|
||||
public String getAcceptButtonText() {
|
||||
|
@ -57,7 +57,7 @@ public class TermsAndConditions extends LoginActions {
|
|||
}
|
||||
|
||||
public String getText() {
|
||||
return textElem.getText();
|
||||
return getTextFromElement(textElem);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,18 +18,24 @@
|
|||
package org.keycloak.testsuite.auth.page.login;
|
||||
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.auth.page.account.AccountFields;
|
||||
import org.keycloak.testsuite.auth.page.AccountFields;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author tkyjovsk
|
||||
*/
|
||||
public class UpdateAccount extends Authenticate {
|
||||
public class UpdateAccount extends RequiredActions {
|
||||
|
||||
@Page
|
||||
private AccountFields accountFields;
|
||||
|
||||
@Override
|
||||
public String getActionId() {
|
||||
return UserModel.RequiredAction.UPDATE_PROFILE.name();
|
||||
}
|
||||
|
||||
public void updateAccount(UserRepresentation user) {
|
||||
updateAccount(user.getEmail(), user.getFirstName(), user.getLastName());
|
||||
}
|
||||
|
|
|
@ -18,21 +18,31 @@
|
|||
package org.keycloak.testsuite.auth.page.login;
|
||||
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.keycloak.testsuite.auth.page.account.PasswordFields;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.testsuite.auth.page.PasswordFields;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author tkyjovsk
|
||||
*/
|
||||
public class UpdatePassword extends Authenticate {
|
||||
public class UpdatePassword extends RequiredActions {
|
||||
|
||||
@Page
|
||||
private PasswordFields passwordFields;
|
||||
|
||||
@Override
|
||||
public String getActionId() {
|
||||
return UserModel.RequiredAction.UPDATE_PASSWORD.name();
|
||||
}
|
||||
|
||||
public void updatePasswords(String newPassword, String confirmPassword) {
|
||||
passwordFields.setNewPassword(newPassword);
|
||||
passwordFields.setConfirmPassword(confirmPassword);
|
||||
submit();
|
||||
}
|
||||
|
||||
public PasswordFields fields() {
|
||||
return passwordFields;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,26 +16,35 @@
|
|||
*/
|
||||
package org.keycloak.testsuite.auth.page.login;
|
||||
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
||||
import static org.keycloak.testsuite.util.UIUtils.clickLink;
|
||||
import static org.keycloak.testsuite.util.UIUtils.getTextFromElement;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author <a href="mailto:vramik@redhat.com">Vlastislav Ramik</a>
|
||||
*/
|
||||
public class VerifyEmail extends Authenticate {
|
||||
public class VerifyEmail extends RequiredActions {
|
||||
|
||||
@FindBy(xpath = "//div[@id='kc-content-wrapper']/p[contains(@class, 'instruction')][1]")
|
||||
private WebElement instruction;
|
||||
|
||||
@FindBy(id = "kc-error-message")
|
||||
private WebElement error;
|
||||
@FindBy(xpath = "//div[@id='kc-content-wrapper']/p[contains(@class, 'instruction')][2]/a[text()='Click here']")
|
||||
private WebElement resendLink;
|
||||
|
||||
@Override
|
||||
public String getActionId() {
|
||||
return UserModel.RequiredAction.VERIFY_EMAIL.name();
|
||||
}
|
||||
|
||||
public String getInstructionMessage() {
|
||||
return instruction.getText();
|
||||
return getTextFromElement(instruction);
|
||||
}
|
||||
|
||||
public String getErrorMessage() {
|
||||
return error.getText();
|
||||
public void clickResend() {
|
||||
clickLink(resendLink);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.keycloak.testsuite.console.page.events;
|
|||
|
||||
import org.keycloak.testsuite.console.page.fragment.DataTable;
|
||||
import org.keycloak.testsuite.page.Form;
|
||||
import org.keycloak.testsuite.util.UIUtils;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
@ -98,23 +99,23 @@ public class AdminEvents extends Events {
|
|||
}
|
||||
|
||||
public void setResourcePathInput(String value) {
|
||||
setInputValue(resourcePathInput, value);
|
||||
UIUtils.setTextInputValue(resourcePathInput, value);
|
||||
}
|
||||
|
||||
public void setRealmInput(String value) {
|
||||
setInputValue(realmInput, value);
|
||||
UIUtils.setTextInputValue(realmInput, value);
|
||||
}
|
||||
|
||||
public void setClientInput(String value) {
|
||||
setInputValue(clientInput, value);
|
||||
UIUtils.setTextInputValue(clientInput, value);
|
||||
}
|
||||
|
||||
public void setUserInput(String value) {
|
||||
setInputValue(userInput, value);
|
||||
UIUtils.setTextInputValue(userInput, value);
|
||||
}
|
||||
|
||||
public void setIpAddressInput(String value) {
|
||||
setInputValue(ipAddressInput, value);
|
||||
UIUtils.setTextInputValue(ipAddressInput, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.keycloak.testsuite.console.page.events;
|
|||
|
||||
import org.keycloak.testsuite.console.page.fragment.OnOffSwitch;
|
||||
import org.keycloak.testsuite.page.Form;
|
||||
import org.keycloak.testsuite.util.UIUtils;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
@ -106,7 +107,7 @@ public class Config extends Events {
|
|||
|
||||
public void setExpiration(String value, String unit) {
|
||||
expirationUnitSelect.selectByVisibleText(unit);
|
||||
Form.setInputValue(expirationInput, value);
|
||||
UIUtils.setTextInputValue(expirationInput, value);
|
||||
}
|
||||
|
||||
public void setSaveAdminEvents(boolean value) {
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.keycloak.testsuite.console.page.events;
|
|||
|
||||
import org.keycloak.testsuite.console.page.fragment.DataTable;
|
||||
import org.keycloak.testsuite.page.Form;
|
||||
import org.keycloak.testsuite.util.UIUtils;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
@ -89,11 +90,11 @@ public class LoginEvents extends Events {
|
|||
}
|
||||
|
||||
public void setClientInput(String value) {
|
||||
setInputValue(clientInput, value);
|
||||
UIUtils.setTextInputValue(clientInput, value);
|
||||
}
|
||||
|
||||
public void setUserInput(String value) {
|
||||
setInputValue(userInput, value);
|
||||
UIUtils.setTextInputValue(userInput, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
package org.keycloak.testsuite.console.page.fragment;
|
||||
|
||||
import org.jboss.arquillian.graphene.fragment.Root;
|
||||
import org.jboss.arquillian.test.api.ArquillianResource;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
||||
import static org.keycloak.testsuite.util.UIUtils.clickLink;
|
||||
|
||||
/**
|
||||
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
||||
*/
|
||||
public class Dropdown {
|
||||
@Root
|
||||
private WebElement dropDownRoot; // MUST be .kc-dropdown
|
||||
|
||||
@FindBy(id = "kc-current-locale-link")
|
||||
private WebElement localeLink;
|
||||
|
||||
@ArquillianResource
|
||||
private WebDriver driver;
|
||||
|
||||
public String getSelected() {
|
||||
return localeLink.getText();
|
||||
}
|
||||
|
||||
public void selectByText(String text) {
|
||||
localeLink.click();
|
||||
clickLink(dropDownRoot.findElement(By.xpath("./ul/li/a[text()='" + text + "']")));
|
||||
}
|
||||
}
|
|
@ -23,7 +23,7 @@ import org.openqa.selenium.support.FindBy;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.keycloak.testsuite.page.Form.getInputValue;
|
||||
import static org.keycloak.testsuite.util.UIUtils.getTextInputValue;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -37,7 +37,7 @@ public class InputList {
|
|||
public List<String> getValues() {
|
||||
List<String> values = new ArrayList<>();
|
||||
for (WebElement input: inputs) {
|
||||
values.add(getInputValue(input));
|
||||
values.add(getTextInputValue(input));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
package org.keycloak.testsuite.console.page.fragment;
|
||||
|
||||
import io.appium.java_client.ios.IOSDriver;
|
||||
import org.jboss.arquillian.graphene.fragment.Root;
|
||||
import org.jboss.arquillian.test.api.ArquillianResource;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.firefox.FirefoxDriver;
|
||||
import org.openqa.selenium.interactions.Actions;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.keycloak.testsuite.util.UIUtils.clickLink;
|
||||
import static org.keycloak.testsuite.util.UIUtils.getTextFromElement;
|
||||
import static org.keycloak.testsuite.util.WaitUtils.pause;
|
||||
|
||||
/**
|
||||
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
||||
*/
|
||||
public class LocaleDropdown {
|
||||
@Root
|
||||
private WebElement root;
|
||||
|
||||
@FindBy(tagName = "ul")
|
||||
private WebElement dropDownMenu;
|
||||
|
||||
@FindBy(id = "kc-current-locale-link")
|
||||
private WebElement currentLocaleLink;
|
||||
|
||||
@ArquillianResource
|
||||
private WebDriver driver;
|
||||
|
||||
public String getSelected() {
|
||||
return getTextFromElement(currentLocaleLink);
|
||||
}
|
||||
|
||||
public void selectByText(String text) {
|
||||
// open the menu
|
||||
if (driver instanceof FirefoxDriver) { // GeckoDriver hack
|
||||
Actions actions = new Actions(driver);
|
||||
actions.moveToElement(root).perform();
|
||||
pause(500);
|
||||
}
|
||||
else if (driver instanceof IOSDriver) { // TODO: Fix this! It's a very, very, ... very nasty hack for Safari on iOS - see KEYCLOAK-7947
|
||||
((IOSDriver) driver).executeScript("arguments[0].setAttribute('style', 'display: block')", dropDownMenu);
|
||||
}
|
||||
else {
|
||||
root.click();
|
||||
}
|
||||
|
||||
// click desired locale
|
||||
clickLink(dropDownMenu.findElement(By.xpath("./li/a[text()='" + text + "']")));
|
||||
}
|
||||
|
||||
public void selectAndAssert(String text) {
|
||||
assertNotEquals(text, getSelected());
|
||||
selectByText(text);
|
||||
assertEquals(text, getSelected());
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@ package org.keycloak.testsuite.drone;
|
|||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.appium.java_client.AppiumDriver;
|
||||
import org.jboss.arquillian.core.api.annotation.Observes;
|
||||
import org.jboss.arquillian.drone.spi.DroneContext;
|
||||
import org.jboss.arquillian.drone.spi.DronePoint;
|
||||
|
@ -44,11 +45,11 @@ public class KeycloakDronePostSetup {
|
|||
Object drone = droneContext.get(dronePoint).getInstance();
|
||||
|
||||
|
||||
if (drone instanceof WebDriver) {
|
||||
if (drone instanceof WebDriver && !(drone instanceof AppiumDriver)) {
|
||||
WebDriver webDriver = (WebDriver) drone;
|
||||
configureDriverSettings(webDriver);
|
||||
} else {
|
||||
log.warn("Drone is not instanceof WebDriver! Drone is " + drone);
|
||||
log.warn("Drone is not instanceof WebDriver for a desktop browser! Drone is " + drone);
|
||||
}
|
||||
|
||||
if (drone instanceof GrapheneProxyInstance) {
|
||||
|
|
|
@ -17,107 +17,109 @@
|
|||
|
||||
package org.keycloak.testsuite.drone;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import org.jboss.arquillian.core.api.Instance;
|
||||
import org.jboss.arquillian.core.api.annotation.Inject;
|
||||
import org.jboss.arquillian.core.api.annotation.Observes;
|
||||
import org.jboss.arquillian.drone.spi.DroneContext;
|
||||
import org.jboss.arquillian.drone.spi.event.BeforeDroneInstantiated;
|
||||
import org.jboss.arquillian.drone.webdriver.configuration.WebDriverConfiguration;
|
||||
import org.jboss.arquillian.drone.webdriver.spi.BrowserCapabilities;
|
||||
import org.jboss.arquillian.drone.webdriver.spi.BrowserCapabilitiesRegistry;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.openqa.selenium.Capabilities;
|
||||
import org.openqa.selenium.phantomjs.PhantomJSDriverService;
|
||||
import org.openqa.selenium.remote.DesiredCapabilities;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jboss.arquillian.config.descriptor.api.ArquillianDescriptor;
|
||||
import org.jboss.arquillian.drone.spi.Configurator;
|
||||
import org.jboss.arquillian.drone.spi.DronePoint;
|
||||
import org.jboss.arquillian.drone.webdriver.configuration.WebDriverConfiguration;
|
||||
import org.jboss.arquillian.drone.webdriver.factory.BrowserCapabilitiesList;
|
||||
import org.jboss.arquillian.drone.webdriver.factory.BrowserCapabilitiesList.PhantomJS;
|
||||
import org.jboss.arquillian.drone.webdriver.factory.WebDriverFactory;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.openqa.selenium.phantomjs.PhantomJSDriverService;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
||||
*/
|
||||
public class KeycloakWebDriverConfigurator extends WebDriverFactory implements Configurator<WebDriver, WebDriverConfiguration> {
|
||||
public class KeycloakWebDriverConfigurator {
|
||||
|
||||
protected final Logger log = Logger.getLogger(KeycloakWebDriverConfigurator.class);
|
||||
|
||||
@Override
|
||||
public int getPrecedence() {
|
||||
return 1;
|
||||
@Inject
|
||||
private Instance<BrowserCapabilitiesRegistry> registryInstance;
|
||||
|
||||
public void createConfiguration(@Observes BeforeDroneInstantiated event, DroneContext droneContext) {
|
||||
WebDriverConfiguration webDriverCfg = droneContext.get(event.getDronePoint()).getConfigurationAs(WebDriverConfiguration.class);
|
||||
|
||||
DesiredCapabilities capabilitiesToAdd = new DesiredCapabilities();
|
||||
updateCapabilityKeys("htmlUnit", webDriverCfg, capabilitiesToAdd);
|
||||
updateCapabilityKeys("appium", webDriverCfg, capabilitiesToAdd);
|
||||
configurePhantomJSDriver(webDriverCfg, capabilitiesToAdd);
|
||||
|
||||
BrowserCapabilities browserCap = registryInstance.get().getEntryFor(webDriverCfg.getBrowser());
|
||||
webDriverCfg.setBrowserInternal(new KcBrowserCapabilities(capabilitiesToAdd, browserCap));
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebDriverConfiguration createConfiguration(ArquillianDescriptor descriptor, DronePoint<WebDriver> dronePoint) {
|
||||
WebDriverConfiguration webDriverCfg = super.createConfiguration(descriptor, dronePoint);
|
||||
|
||||
if (webDriverCfg.getBrowser().equals("htmlUnit")) {
|
||||
updateCapabilities(webDriverCfg);
|
||||
} else if (webDriverCfg.getBrowser().equals("phantomjs")) {
|
||||
configurePhantomJSDriver(webDriverCfg);
|
||||
private void configurePhantomJSDriver(WebDriverConfiguration webDriverCfg, DesiredCapabilities capabilitiesToAdd) {
|
||||
if (!webDriverCfg.getBrowser().equals("phantomjs")) {
|
||||
return;
|
||||
}
|
||||
|
||||
return webDriverCfg;
|
||||
String cliArgs = System.getProperty("keycloak.phantomjs.cli.args");
|
||||
|
||||
if (cliArgs == null) {
|
||||
cliArgs = "--ignore-ssl-errors=true --web-security=false";
|
||||
}
|
||||
|
||||
private void configurePhantomJSDriver(WebDriverConfiguration webDriverCfg) {
|
||||
webDriverCfg.setBrowserInternal(new PhantomJS() {
|
||||
@Override
|
||||
public Map<String, ?> getRawCapabilities() {
|
||||
List<String> cliArgs = new ArrayList<>();
|
||||
String cliArgsProperty = System.getProperty("keycloak.phantomjs.cli.args");
|
||||
|
||||
if (cliArgsProperty != null) {
|
||||
cliArgs = Arrays.asList(cliArgsProperty.split(" "));
|
||||
} else {
|
||||
cliArgs.add("--ignore-ssl-errors=true");
|
||||
cliArgs.add("--web-security=false");
|
||||
}
|
||||
|
||||
Map<String, Object> mergedCapabilities = new HashMap<>(super.getRawCapabilities());
|
||||
|
||||
mergedCapabilities.put(PhantomJSDriverService.PHANTOMJS_CLI_ARGS, cliArgs.toArray(new String[cliArgs.size()]));
|
||||
|
||||
return mergedCapabilities;
|
||||
}
|
||||
});
|
||||
capabilitiesToAdd.setCapability(PhantomJSDriverService.PHANTOMJS_CLI_ARGS, cliArgs);
|
||||
}
|
||||
|
||||
|
||||
// This is to ensure that default value of capabilities like "version" will be used just for the HtmlUnitDriver, but not for other drivers.
|
||||
// Hence in configs we have "htmlUnit.version" instead of "version"
|
||||
protected void updateCapabilities(WebDriverConfiguration configuration) {
|
||||
Map<String, Object> newCapabilities = new HashMap<>();
|
||||
private void updateCapabilityKeys(String browser, WebDriverConfiguration webDriverCfg, DesiredCapabilities capabilitiesToAdd, String... exclude) {
|
||||
if (!webDriverCfg.getBrowser().toLowerCase().equals(browser.toLowerCase())) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Map.Entry<String, ?> capability : configuration.getCapabilities().asMap().entrySet()) {
|
||||
if (capability.getKey().startsWith("htmlUnit.")) {
|
||||
newCapabilities.put(capability.getKey().substring(9), capability.getValue());
|
||||
List excludeList = Arrays.asList(exclude);
|
||||
|
||||
String key = browser + ".";
|
||||
int keyLength = key.length();
|
||||
for (Map.Entry<String, ?> capability : webDriverCfg.getCapabilities().asMap().entrySet()) {
|
||||
if (!excludeList.contains(capability.getKey()) && capability.getKey().startsWith(key)) {
|
||||
capabilitiesToAdd.setCapability(capability.getKey().substring(keyLength), capability.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("Adding new capabilities for HtmlUnitDriver: " + newCapabilities);
|
||||
public class KcBrowserCapabilities implements BrowserCapabilities {
|
||||
private Capabilities capabilitiesToAdd;
|
||||
private BrowserCapabilities origBrowserCapabilities;
|
||||
|
||||
KcHtmlUnitCapabilities mergedBrowser = new KcHtmlUnitCapabilities(newCapabilities);
|
||||
configuration.setBrowserInternal(mergedBrowser);
|
||||
public KcBrowserCapabilities(Capabilities capabilitiesToAdd, BrowserCapabilities origBrowserCapabilities) {
|
||||
this.capabilitiesToAdd = capabilitiesToAdd;
|
||||
this.origBrowserCapabilities = origBrowserCapabilities;
|
||||
}
|
||||
|
||||
|
||||
private static class KcHtmlUnitCapabilities extends BrowserCapabilitiesList.HtmlUnit {
|
||||
|
||||
private final Map<String, Object> newCapabilities;
|
||||
|
||||
public KcHtmlUnitCapabilities(Map<String, Object> newCapabilities) {
|
||||
this.newCapabilities = newCapabilities;
|
||||
@Override
|
||||
public String getImplementationClassName() {
|
||||
return origBrowserCapabilities.getImplementationClassName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ?> getRawCapabilities() {
|
||||
Map<String, ?> parent = super.getRawCapabilities();
|
||||
|
||||
Map<String, Object> merged = new HashMap<>(parent);
|
||||
merged.putAll(newCapabilities);
|
||||
|
||||
return merged;
|
||||
Map<String, Object> ret = new HashMap<>(origBrowserCapabilities.getRawCapabilities());
|
||||
ret.putAll(capabilitiesToAdd.asMap());
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReadableName() {
|
||||
return origBrowserCapabilities.getReadableName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPrecedence() {
|
||||
return origBrowserCapabilities.getPrecedence();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,15 +92,11 @@ public abstract class AbstractPage {
|
|||
}
|
||||
|
||||
public void navigateTo() {
|
||||
navigateTo(true);
|
||||
}
|
||||
|
||||
public void navigateTo(boolean waitForMatch) {
|
||||
URLUtils.navigateToUri(buildUri().toASCIIString(), waitForMatch);
|
||||
URLUtils.navigateToUri(buildUri().toASCIIString());
|
||||
}
|
||||
|
||||
public boolean isCurrent() {
|
||||
return URLUtils.currentUrlEqual(toString());
|
||||
return URLUtils.currentUrlEquals(toString());
|
||||
}
|
||||
|
||||
public void assertCurrent() {
|
||||
|
|
|
@ -19,7 +19,6 @@ package org.keycloak.testsuite.page;
|
|||
|
||||
import org.jboss.arquillian.drone.api.annotation.Drone;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.testsuite.util.WaitUtils;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
@ -53,22 +52,6 @@ public class Form {
|
|||
guardAjax(cancel).click();
|
||||
}
|
||||
|
||||
public static String getInputValue(WebElement input) {
|
||||
return input.getAttribute(VALUE);
|
||||
}
|
||||
|
||||
public static final String VALUE = "value";
|
||||
|
||||
public static void setInputValue(WebElement input, String value) {
|
||||
if (input.isEnabled()) {
|
||||
input.clear();
|
||||
if (value != null) {
|
||||
input.sendKeys(value);
|
||||
}
|
||||
} else {
|
||||
// TODO log warning
|
||||
}
|
||||
}
|
||||
public WebElement saveBtn() {
|
||||
return save;
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ public abstract class AbstractPage {
|
|||
|
||||
protected URI getAuthServerRoot() {
|
||||
try {
|
||||
return KeycloakUriBuilder.fromUri(suiteContext.getAuthServerInfo().getContextRoot().toURI()).path("/auth/").build();
|
||||
return KeycloakUriBuilder.fromUri(suiteContext.getAuthServerInfo().getBrowserContextRoot().toURI()).path("/auth/").build();
|
||||
} catch (URISyntaxException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
package org.keycloak.testsuite.pages;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
|
||||
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
|
@ -51,12 +52,11 @@ public class AppServerWelcomePage extends AppServerContextRoot {
|
|||
}
|
||||
|
||||
public void navigateToConsole() {
|
||||
URLUtils.navigateToUri(getInjectedUrl().toString() + "/console", true);
|
||||
loginPage.form().waitForLoginButtonPresent();
|
||||
URLUtils.navigateToUri(getInjectedUrl().toString() + "/console");
|
||||
}
|
||||
|
||||
public void login(String username, String password) {
|
||||
loginPage.form().waitForLoginButtonPresent();
|
||||
assertTrue(loginPage.form().isLoginButtonPresent());
|
||||
loginPage.form().login(username, password);
|
||||
waitForPageToLoad();
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ public class GitHubLoginPage extends AbstractSocialLoginPage {
|
|||
@Override
|
||||
public void logout() {
|
||||
log.info("performing logout from GitHub");
|
||||
URLUtils.navigateToUri("https://github.com/logout", true);
|
||||
URLUtils.navigateToUri("https://github.com/logout");
|
||||
UIUtils.clickLink(logoutButton);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
package org.keycloak.testsuite.util;
|
||||
|
||||
import io.appium.java_client.android.AndroidDriver;
|
||||
import org.openqa.selenium.JavascriptExecutor;
|
||||
import org.openqa.selenium.TimeoutException;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.safari.SafariDriver;
|
||||
import org.openqa.selenium.support.ui.ExpectedConditions;
|
||||
import org.openqa.selenium.support.ui.Select;
|
||||
import org.openqa.selenium.support.ui.WebDriverWait;
|
||||
|
@ -15,6 +18,8 @@ import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
|
|||
*/
|
||||
public final class UIUtils {
|
||||
|
||||
public static final String VALUE_ATTR_NAME = "value";
|
||||
|
||||
public static boolean selectContainsOption(Select select, String optionText) {
|
||||
for (WebElement option : select.getOptions()) {
|
||||
if (option.getText().equals(optionText)) {
|
||||
|
@ -58,7 +63,7 @@ public final class UIUtils {
|
|||
* @param element
|
||||
*/
|
||||
public static void navigateToLink(WebElement element) {
|
||||
URLUtils.navigateToUri(element.getAttribute("href"), true);
|
||||
URLUtils.navigateToUri(element.getAttribute("href"));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -80,4 +85,36 @@ public final class UIUtils {
|
|||
element.sendKeys(keys);
|
||||
jsExecutor.executeScript("arguments[0].setAttribute('style', '" + styleBckp + "');", element);
|
||||
}
|
||||
|
||||
public static String getTextInputValue(WebElement input) {
|
||||
return input.getAttribute(VALUE_ATTR_NAME);
|
||||
}
|
||||
|
||||
public static void setTextInputValue(WebElement input, String value) {
|
||||
input.click();
|
||||
input.clear();
|
||||
if (value != null) {
|
||||
input.sendKeys(value);
|
||||
}
|
||||
|
||||
WebDriver driver = getCurrentDriver();
|
||||
if (driver instanceof AndroidDriver) {
|
||||
AndroidDriver androidDriver = (AndroidDriver) driver;
|
||||
androidDriver.hideKeyboard(); // stability improvement
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains some browser-specific tweaks for getting an element text.
|
||||
*
|
||||
* @param element
|
||||
* @return
|
||||
*/
|
||||
public static String getTextFromElement(WebElement element) {
|
||||
String text = element.getText();
|
||||
if (getCurrentDriver() instanceof SafariDriver) {
|
||||
return text.trim(); // Safari on macOS sometimes for no obvious reason surrounds the text with spaces
|
||||
}
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ import org.openqa.selenium.ie.InternetExplorerDriver;
|
|||
import org.openqa.selenium.support.ui.ExpectedCondition;
|
||||
import org.openqa.selenium.support.ui.WebDriverWait;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.keycloak.testsuite.util.DroneUtils.getCurrentDriver;
|
||||
|
@ -23,12 +25,11 @@ public final class URLUtils {
|
|||
|
||||
private static Logger log = Logger.getLogger(URLUtils.class);
|
||||
|
||||
public static void navigateToUri(String uri, boolean waitForMatch) {
|
||||
navigateToUri(uri, waitForMatch, true);
|
||||
public static void navigateToUri(String uri) {
|
||||
navigateToUri(uri, true);
|
||||
}
|
||||
|
||||
// TODO: remove waitForMatch
|
||||
private static void navigateToUri(String uri, boolean waitForMatch, boolean enableIEWorkaround) {
|
||||
private static void navigateToUri(String uri, boolean enableIEWorkaround) {
|
||||
WebDriver driver = getCurrentDriver();
|
||||
|
||||
log.info("starting navigation");
|
||||
|
@ -62,14 +63,14 @@ public final class URLUtils {
|
|||
&& (driver.getCurrentUrl().matches("^[^#]+/#state=[^#/&]+&code=[^#/&]+$")
|
||||
|| driver.getCurrentUrl().matches("^.+/auth/admin/[^/]+/console/$"))) {
|
||||
log.info("IE workaround: reloading the page after deleting the cookies...");
|
||||
navigateToUri(uri, waitForMatch, false);
|
||||
navigateToUri(uri, false);
|
||||
}
|
||||
else {
|
||||
log.info("navigation complete");
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean currentUrlEqual(String url) {
|
||||
public static boolean currentUrlEquals(String url) {
|
||||
return urlCheck(urlToBe(url));
|
||||
}
|
||||
|
||||
|
@ -77,12 +78,32 @@ public final class URLUtils {
|
|||
return urlCheck(not(urlToBe(url)));
|
||||
}
|
||||
|
||||
public static boolean currentUrlStartWith(String url) {
|
||||
return urlCheck(urlMatches("^" + Pattern.quote(url) + ".*$"));
|
||||
public static boolean currentUrlWithQueryEquals(String expectedUrl, String... expectedQuery) {
|
||||
List<String> expectedQueryList = Arrays.asList(expectedQuery);
|
||||
|
||||
ExpectedCondition<Boolean> condition = (WebDriver driver) -> {
|
||||
String[] urlParts = driver.getCurrentUrl().split("\\?", 2);
|
||||
if (urlParts.length != 2) {
|
||||
throw new RuntimeException("Current URL doesn't contain query string");
|
||||
}
|
||||
List<String> queryParts = Arrays.asList(urlParts[1].split("&"));
|
||||
|
||||
return urlParts[0].equals(expectedUrl) && queryParts.containsAll(expectedQueryList);
|
||||
};
|
||||
|
||||
return urlCheck(condition);
|
||||
}
|
||||
|
||||
public static boolean currentUrlStartsWith(String url) {
|
||||
return currentUrlMatches("^" + Pattern.quote(url) + ".*$");
|
||||
}
|
||||
|
||||
public static boolean currentUrlDoesntStartWith(String url) {
|
||||
return urlCheck(urlMatches("^(?!" + Pattern.quote(url) + ").+$"));
|
||||
return currentUrlMatches("^(?!" + Pattern.quote(url) + ").+$");
|
||||
}
|
||||
|
||||
public static boolean currentUrlMatches(String regex) {
|
||||
return urlCheck(urlMatches(regex));
|
||||
}
|
||||
|
||||
private static boolean urlCheck(ExpectedCondition condition) {
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.keycloak.testsuite;
|
|||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Before;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.admin.client.resource.UserResource;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
|
@ -87,19 +88,25 @@ public abstract class AbstractAuthTest extends AbstractKeycloakTest {
|
|||
bburkeUser = createUserRepresentation("bburke", "bburke@redhat.com", "Bill", "Burke", true);
|
||||
setPasswordFor(bburkeUser, PASSWORD);
|
||||
|
||||
deleteAllCookiesForTestRealm();
|
||||
resetTestRealmSession();
|
||||
}
|
||||
|
||||
|
||||
public void createTestUserWithAdminClient() {
|
||||
createTestUserWithAdminClient(true);
|
||||
}
|
||||
|
||||
public void createTestUserWithAdminClient(boolean setRealmRoles) {
|
||||
ApiUtil.removeUserByUsername(testRealmResource(), "test");
|
||||
|
||||
log.debug("creating test user");
|
||||
String id = createUserAndResetPasswordWithAdminClient(testRealmResource(), testUser, PASSWORD);
|
||||
testUser.setId(id);
|
||||
|
||||
if (setRealmRoles) {
|
||||
assignClientRoles(testRealmResource(), id, "realm-management", "view-realm");
|
||||
}
|
||||
}
|
||||
|
||||
public static UserRepresentation createUserRepresentation(String username, String email, String firstName, String lastName, boolean enabled) {
|
||||
UserRepresentation user = new UserRepresentation();
|
||||
|
@ -111,10 +118,29 @@ public abstract class AbstractAuthTest extends AbstractKeycloakTest {
|
|||
return user;
|
||||
}
|
||||
|
||||
public void deleteAllCookiesForTestRealm() {
|
||||
public static UserRepresentation createUserRepresentation(String username, String email, String firstName, String lastName, boolean enabled, String password) {
|
||||
UserRepresentation user = createUserRepresentation(username, email, firstName, lastName, enabled);
|
||||
setPasswordFor(user, password);
|
||||
return user;
|
||||
}
|
||||
|
||||
public static UserRepresentation createUserRepresentation(String username, String password) {
|
||||
UserRepresentation user = createUserRepresentation(username, null, null, null, true, password);
|
||||
return user;
|
||||
}
|
||||
|
||||
protected void deleteAllCookiesForTestRealm() {
|
||||
deleteAllCookiesForRealm(testRealmAccountPage);
|
||||
}
|
||||
|
||||
protected void deleteAllSessionsInTestRealm() {
|
||||
deleteAllSessionsInRealm(testRealmAccountPage.getAuthRealm());
|
||||
}
|
||||
|
||||
private void resetTestRealmSession() {
|
||||
resetRealmSession(testRealmAccountPage.getAuthRealm());
|
||||
}
|
||||
|
||||
public void listCookies() {
|
||||
log.info("LIST OF COOKIES: ");
|
||||
for (Cookie c : driver.manage().getCookies()) {
|
||||
|
@ -127,4 +153,8 @@ public abstract class AbstractAuthTest extends AbstractKeycloakTest {
|
|||
return adminClient.realm(testRealmPage.getAuthRealm());
|
||||
}
|
||||
|
||||
protected UserResource testUserResource() {
|
||||
return testRealmResource().users().get(testUser.getId());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
package org.keycloak.testsuite;
|
||||
|
||||
import io.appium.java_client.AppiumDriver;
|
||||
import org.apache.commons.configuration.ConfigurationException;
|
||||
import org.apache.commons.configuration.PropertiesConfiguration;
|
||||
import org.jboss.arquillian.container.test.api.RunAsClient;
|
||||
|
@ -29,6 +30,7 @@ import org.junit.BeforeClass;
|
|||
import org.junit.runner.RunWith;
|
||||
import org.keycloak.admin.client.Keycloak;
|
||||
import org.keycloak.admin.client.resource.AuthenticationManagementResource;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.admin.client.resource.RealmsResource;
|
||||
import org.keycloak.admin.client.resource.UserResource;
|
||||
import org.keycloak.admin.client.resource.UsersResource;
|
||||
|
@ -83,6 +85,7 @@ import java.util.concurrent.TimeoutException;
|
|||
import static org.keycloak.testsuite.admin.Users.setPasswordFor;
|
||||
import static org.keycloak.testsuite.auth.page.AuthRealm.ADMIN;
|
||||
import static org.keycloak.testsuite.auth.page.AuthRealm.MASTER;
|
||||
import static org.keycloak.testsuite.util.URLUtils.navigateToUri;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -94,6 +97,8 @@ public abstract class AbstractKeycloakTest {
|
|||
|
||||
protected static final boolean AUTH_SERVER_SSL_REQUIRED = Boolean.parseBoolean(System.getProperty("auth.server.ssl.required", "false"));
|
||||
|
||||
protected static final String ENGLISH_LOCALE_NAME = "English";
|
||||
|
||||
protected Logger log = Logger.getLogger(this.getClass());
|
||||
|
||||
@ArquillianResource
|
||||
|
@ -173,6 +178,8 @@ public abstract class AbstractKeycloakTest {
|
|||
if (!isImportAfterEachMethod()) {
|
||||
testContext.setTestRealmReps(testRealmReps);
|
||||
}
|
||||
|
||||
afterAbstractKeycloakTestRealmImport();
|
||||
}
|
||||
|
||||
oauth.init(adminClient, driver);
|
||||
|
@ -194,6 +201,8 @@ public abstract class AbstractKeycloakTest {
|
|||
protected void postAfterAbstractKeycloak() {
|
||||
}
|
||||
|
||||
protected void afterAbstractKeycloakTestRealmImport() {}
|
||||
|
||||
@After
|
||||
public void afterAbstractKeycloakTest() {
|
||||
if (resetTimeOffset) {
|
||||
|
@ -269,11 +278,49 @@ public abstract class AbstractKeycloakTest {
|
|||
|
||||
protected void deleteAllCookiesForRealm(String realmName) {
|
||||
// masterRealmPage.navigateTo();
|
||||
driver.navigate().to(OAuthClient.AUTH_SERVER_ROOT + "/realms/" + realmName + "/account"); // Because IE webdriver freezes when loading a JSON page (realm page), we need to use this alternative
|
||||
navigateToUri(accountPage.getAuthRoot() + "/realms/" + realmName + "/account"); // Because IE webdriver freezes when loading a JSON page (realm page), we need to use this alternative
|
||||
log.info("deleting cookies in '" + realmName + "' realm");
|
||||
driver.manage().deleteAllCookies();
|
||||
}
|
||||
|
||||
// this is useful mainly for smartphones as cookies deletion doesn't work there
|
||||
protected void deleteAllSessionsInRealm(String realmName) {
|
||||
log.info("removing all sessions from '" + realmName + "' realm...");
|
||||
try {
|
||||
adminClient.realm(realmName).logoutAll();
|
||||
log.info("sessions successfully deleted");
|
||||
}
|
||||
catch (NotFoundException e) {
|
||||
log.warn("realm not found");
|
||||
}
|
||||
}
|
||||
|
||||
protected void resetRealmSession(String realmName) {
|
||||
deleteAllCookiesForRealm(realmName);
|
||||
|
||||
if (driver instanceof AppiumDriver) { // smartphone drivers don't support cookies deletion
|
||||
try {
|
||||
log.info("resetting realm session");
|
||||
|
||||
final RealmRepresentation realmRep = adminClient.realm(realmName).toRepresentation();
|
||||
|
||||
deleteAllSessionsInRealm(realmName); // logout users
|
||||
|
||||
if (realmRep.isInternationalizationEnabled()) { // reset the locale
|
||||
String locale = getDefaultLocaleName(realmRep.getRealm());
|
||||
loginPage.localeDropdown().selectByText(locale);
|
||||
log.info("locale reset to " + locale);
|
||||
}
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("realm not found");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected String getDefaultLocaleName(String realmName) {
|
||||
return ENGLISH_LOCALE_NAME;
|
||||
}
|
||||
|
||||
public void setDefaultPageUriParameters() {
|
||||
masterRealmPage.setAuthRealm(MASTER);
|
||||
loginPage.setAuthRealm(MASTER);
|
||||
|
|
|
@ -91,7 +91,6 @@ public class CustomAuthFlowOTPTest extends AbstractCustomAccountManagementTest {
|
|||
//configure OTP for test user
|
||||
testRealmAccountManagementPage.navigateTo();
|
||||
testRealmLoginPage.form().login(testUser);
|
||||
testRealmLoginPage.form().totpForm().waitForTotpInputFieldPresent();
|
||||
String totpSecret = testRealmLoginPage.form().totpForm().getTotpSecret();
|
||||
testRealmLoginPage.form().totpForm().setTotp(totp.generateTOTP(totpSecret));
|
||||
testRealmLoginPage.form().totpForm().submit();
|
||||
|
@ -117,7 +116,6 @@ public class CustomAuthFlowOTPTest extends AbstractCustomAccountManagementTest {
|
|||
|
||||
configureOTP();
|
||||
testRealmLoginPage.form().login(testUser);
|
||||
testRealmLoginPage.form().totpForm().waitForTotpInputFieldPresent();
|
||||
|
||||
//verify that the page is login page, not totp setup
|
||||
assertCurrentUrlStartsWith(testLoginOneTimeCodePage);
|
||||
|
@ -134,7 +132,6 @@ public class CustomAuthFlowOTPTest extends AbstractCustomAccountManagementTest {
|
|||
//test OTP is required
|
||||
testRealmAccountManagementPage.navigateTo();
|
||||
testRealmLoginPage.form().login(testUser);
|
||||
testRealmLoginPage.form().totpForm().waitForTotpInputFieldPresent();
|
||||
|
||||
//verify that the page is login page, not totp setup
|
||||
assertCurrentUrlStartsWith(testLoginOneTimeCodePage);
|
||||
|
@ -170,7 +167,6 @@ public class CustomAuthFlowOTPTest extends AbstractCustomAccountManagementTest {
|
|||
|
||||
configureOTP();
|
||||
testRealmLoginPage.form().login(testUser);
|
||||
testRealmLoginPage.form().totpForm().waitForTotpInputFieldPresent();
|
||||
|
||||
//verify that the page is login page, not totp setup
|
||||
assertCurrentUrlStartsWith(testLoginOneTimeCodePage);
|
||||
|
@ -192,7 +188,6 @@ public class CustomAuthFlowOTPTest extends AbstractCustomAccountManagementTest {
|
|||
//test OTP is required
|
||||
testRealmAccountManagementPage.navigateTo();
|
||||
testRealmLoginPage.form().login(testUser);
|
||||
testRealmLoginPage.form().totpForm().waitForTotpInputFieldPresent();
|
||||
|
||||
//verify that the page is login page, not totp setup
|
||||
assertCurrentUrlStartsWith(testLoginOneTimeCodePage);
|
||||
|
@ -238,7 +233,6 @@ public class CustomAuthFlowOTPTest extends AbstractCustomAccountManagementTest {
|
|||
|
||||
configureOTP();
|
||||
testRealmLoginPage.form().login(testUser);
|
||||
testRealmLoginPage.form().totpForm().waitForTotpInputFieldPresent();
|
||||
|
||||
//verify that the page is login page, not totp setup
|
||||
assertCurrentUrlStartsWith(testLoginOneTimeCodePage);
|
||||
|
@ -285,7 +279,6 @@ public class CustomAuthFlowOTPTest extends AbstractCustomAccountManagementTest {
|
|||
|
||||
configureOTP();
|
||||
testRealmLoginPage.form().login(testUser);
|
||||
testRealmLoginPage.form().totpForm().waitForTotpInputFieldPresent();
|
||||
|
||||
//verify that the page is login page, not totp setup
|
||||
assertCurrentUrlStartsWith(testLoginOneTimeCodePage);
|
||||
|
@ -339,7 +332,6 @@ public class CustomAuthFlowOTPTest extends AbstractCustomAccountManagementTest {
|
|||
|
||||
configureOTP();
|
||||
testRealmLoginPage.form().login(testUser);
|
||||
testRealmLoginPage.form().totpForm().waitForTotpInputFieldPresent();
|
||||
|
||||
//verify that the page is login page, not totp setup
|
||||
assertCurrentUrlStartsWith(testLoginOneTimeCodePage);
|
||||
|
@ -389,7 +381,6 @@ public class CustomAuthFlowOTPTest extends AbstractCustomAccountManagementTest {
|
|||
|
||||
configureOTP();
|
||||
testRealmLoginPage.form().login(testUser);
|
||||
testRealmLoginPage.form().totpForm().waitForTotpInputFieldPresent();
|
||||
|
||||
//verify that the page is login page, not totp setup
|
||||
assertCurrentUrlStartsWith(testLoginOneTimeCodePage);
|
||||
|
|
|
@ -83,6 +83,7 @@ import static org.hamcrest.Matchers.not;
|
|||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWithLoginUrlOf;
|
||||
import static org.keycloak.testsuite.utils.io.IOUtil.loadJson;
|
||||
import static org.keycloak.testsuite.utils.io.IOUtil.loadRealm;
|
||||
import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
|
||||
|
@ -738,7 +739,7 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
|
|||
|
||||
clientPage.navigateTo();
|
||||
// Check for correct logout
|
||||
this.jsDriverTestRealmLoginPage.form().waitForLoginButtonPresent();
|
||||
assertCurrentUrlStartsWithLoginUrlOf(jsDriverTestRealmLoginPage);
|
||||
} else {
|
||||
throw ex;
|
||||
}
|
||||
|
|
|
@ -87,7 +87,6 @@ import org.keycloak.testsuite.adapter.page.SecurePortal;
|
|||
import org.keycloak.testsuite.adapter.page.SecurePortalWithCustomSessionConfig;
|
||||
import org.keycloak.testsuite.adapter.page.TokenMinTTLPage;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.arquillian.AppServerTestEnricher;
|
||||
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
|
||||
import org.keycloak.testsuite.arquillian.containers.ContainerConstants;
|
||||
import org.keycloak.testsuite.auth.page.account.Applications;
|
||||
|
@ -261,6 +260,7 @@ public class DemoServletsAdapterTest extends AbstractServletsAdapterTest {
|
|||
loginEventsPage.setConsoleRealm(DEMO);
|
||||
applicationsPage.setAuthRealm(DEMO);
|
||||
loginEventsPage.setConsoleRealm(DEMO);
|
||||
oAuthGrantPage.setAuthRealm(DEMO);
|
||||
}
|
||||
|
||||
@Before
|
||||
|
@ -433,7 +433,7 @@ public class DemoServletsAdapterTest extends AbstractServletsAdapterTest {
|
|||
public void testLoginSSOAndLogout() {
|
||||
// test login to customer-portal which does a bearer request to customer-db
|
||||
customerPortal.navigateTo();
|
||||
testRealmLoginPage.form().waitForUsernameInputPresent();
|
||||
assertTrue(testRealmLoginPage.form().isUsernamePresent());
|
||||
assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
|
||||
testRealmLoginPage.form().login("bburke@redhat.com", "password");
|
||||
assertCurrentUrlEquals(customerPortal);
|
||||
|
@ -508,7 +508,7 @@ public class DemoServletsAdapterTest extends AbstractServletsAdapterTest {
|
|||
public void testLoginSSOIdle() {
|
||||
// test login to customer-portal which does a bearer request to customer-db
|
||||
customerPortal.navigateTo();
|
||||
testRealmLoginPage.form().waitForUsernameInputPresent();
|
||||
assertTrue(testRealmLoginPage.form().isUsernamePresent());
|
||||
assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
|
||||
testRealmLoginPage.form().login("bburke@redhat.com", "password");
|
||||
assertCurrentUrlEquals(customerPortal);
|
||||
|
@ -534,7 +534,7 @@ public class DemoServletsAdapterTest extends AbstractServletsAdapterTest {
|
|||
// test login to customer-portal which does a bearer request to customer-db
|
||||
customerPortal.navigateTo();
|
||||
log.info("Current url: " + driver.getCurrentUrl());
|
||||
testRealmLoginPage.form().waitForUsernameInputPresent();
|
||||
assertTrue(testRealmLoginPage.form().isUsernamePresent());
|
||||
assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
|
||||
testRealmLoginPage.form().login("bburke@redhat.com", "password");
|
||||
log.info("Current url: " + driver.getCurrentUrl());
|
||||
|
@ -566,7 +566,7 @@ public class DemoServletsAdapterTest extends AbstractServletsAdapterTest {
|
|||
|
||||
// test login to customer-portal which does a bearer request to customer-db
|
||||
customerPortal.navigateTo();
|
||||
testRealmLoginPage.form().waitForUsernameInputPresent();
|
||||
assertTrue(testRealmLoginPage.form().isUsernamePresent());
|
||||
assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
|
||||
testRealmLoginPage.form().login("bburke@redhat.com", "password");
|
||||
assertCurrentUrlEquals(customerPortal);
|
||||
|
@ -723,7 +723,7 @@ public class DemoServletsAdapterTest extends AbstractServletsAdapterTest {
|
|||
public void testTokenMinTTL() {
|
||||
// Login
|
||||
tokenMinTTLPage.navigateTo();
|
||||
testRealmLoginPage.form().waitForUsernameInputPresent();
|
||||
assertTrue(testRealmLoginPage.form().isUsernamePresent());
|
||||
assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
|
||||
testRealmLoginPage.form().login("bburke@redhat.com", "password");
|
||||
assertCurrentUrlEquals(tokenMinTTLPage);
|
||||
|
@ -767,7 +767,7 @@ public class DemoServletsAdapterTest extends AbstractServletsAdapterTest {
|
|||
|
||||
// Test I need to reauthenticate with prompt=login
|
||||
String appUri = tokenMinTTLPage.getUriBuilder().queryParam(OIDCLoginProtocol.PROMPT_PARAM, OIDCLoginProtocol.PROMPT_VALUE_LOGIN).build().toString();
|
||||
URLUtils.navigateToUri(appUri, true);
|
||||
URLUtils.navigateToUri(appUri);
|
||||
assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
|
||||
testRealmLoginPage.form().login("bburke@redhat.com", "password");
|
||||
AccessToken token = tokenMinTTLPage.getAccessToken();
|
||||
|
@ -805,14 +805,14 @@ public class DemoServletsAdapterTest extends AbstractServletsAdapterTest {
|
|||
String portalUri = securePortal.getUriBuilder().build().toString();
|
||||
UriBuilder uriBuilder = securePortal.getUriBuilder();
|
||||
String appUri = uriBuilder.clone().queryParam(OAuth2Constants.UI_LOCALES_PARAM, "de en").build().toString();
|
||||
URLUtils.navigateToUri(appUri, true);
|
||||
URLUtils.navigateToUri(appUri);
|
||||
assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
|
||||
// check the ui_locales param is there
|
||||
Map<String, String> parameters = getQueryFromUrl(driver.getCurrentUrl());
|
||||
assertThat(parameters.get(OAuth2Constants.UI_LOCALES_PARAM), allOf(containsString("de"), containsString("en")));
|
||||
|
||||
String appUriDe = uriBuilder.clone().queryParam(OAuth2Constants.UI_LOCALES_PARAM, "de").build().toString();
|
||||
URLUtils.navigateToUri(appUriDe, true);
|
||||
URLUtils.navigateToUri(appUriDe);
|
||||
assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
|
||||
|
||||
// check that the page is in german
|
||||
|
|
|
@ -136,7 +136,7 @@ public class OIDCPublicKeyRotationAdapterTest extends AbstractServletsAdapterTes
|
|||
|
||||
// Try to login again. It should fail now because not yet allowed to download new keys
|
||||
tokenMinTTLPage.navigateTo();
|
||||
testRealmLoginPage.form().waitForUsernameInputPresent();
|
||||
assertTrue(testRealmLoginPage.form().isUsernamePresent());
|
||||
assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
|
||||
testRealmLoginPage.form().login("bburke@redhat.com", "password");
|
||||
URLAssert.assertCurrentUrlStartsWith(tokenMinTTLPage.getInjectedUrl().toString());
|
||||
|
@ -281,7 +281,7 @@ public class OIDCPublicKeyRotationAdapterTest extends AbstractServletsAdapterTes
|
|||
|
||||
private void loginToTokenMinTtlApp() {
|
||||
tokenMinTTLPage.navigateTo();
|
||||
testRealmLoginPage.form().waitForUsernameInputPresent();
|
||||
assertTrue(testRealmLoginPage.form().isUsernamePresent());
|
||||
assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
|
||||
testRealmLoginPage.form().login("bburke@redhat.com", "password");
|
||||
assertCurrentUrlEquals(tokenMinTTLPage);
|
||||
|
|
|
@ -369,7 +369,7 @@ public class SocialLoginTest extends AbstractKeycloakTest {
|
|||
}
|
||||
|
||||
private void assertAccount() {
|
||||
assertTrue(URLUtils.currentUrlStartWith(accountPage.toString())); // Sometimes after login the URL ends with /# or similar
|
||||
assertTrue(URLUtils.currentUrlStartsWith(accountPage.toString())); // Sometimes after login the URL ends with /# or similar
|
||||
|
||||
assertEquals(getConfig("profile.firstName"), accountPage.getFirstName());
|
||||
assertEquals(getConfig("profile.lastName"), accountPage.getLastName());
|
||||
|
|
|
@ -22,7 +22,6 @@ import org.junit.Test;
|
|||
import org.keycloak.models.AccountRoles;
|
||||
import org.keycloak.models.AdminRoles;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.AuthenticationSessionManager;
|
||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||
import org.keycloak.testsuite.ActionURIUtils;
|
||||
|
@ -89,7 +88,7 @@ public class CookiesPathTest extends AbstractKeycloakTest {
|
|||
cookies.stream().forEach(cookie -> Assert.assertThat(cookie.getPath(), Matchers.endsWith("/auth/realms/foobar/")));
|
||||
|
||||
// lets back to "/realms/foo/account" to test the cookies for "foo" realm are still there and haven't been (correctly) sent to "foobar"
|
||||
URLUtils.navigateToUri( oauth.AUTH_SERVER_ROOT + "/realms/foo/account", true);
|
||||
URLUtils.navigateToUri( oauth.AUTH_SERVER_ROOT + "/realms/foo/account");
|
||||
|
||||
cookies = driver.manage().getCookies();
|
||||
Assert.assertTrue("There should be cookies sent!", cookies.size() > 0);
|
||||
|
|
|
@ -105,7 +105,7 @@ public class TrustStoreEmailTest extends AbstractTestRealmKeycloakTest {
|
|||
String mailCodeId = sendEvent.getDetails().get(Details.CODE_ID);
|
||||
|
||||
assertEquals("You need to verify your email address to activate your account.",
|
||||
testRealmVerifyEmailPage.getFeedbackText());
|
||||
testRealmVerifyEmailPage.feedbackMessage().getText());
|
||||
|
||||
String verifyEmailUrl = assertEmailAndGetUrl(MailServerConfiguration.FROM, user.getEmail(),
|
||||
"Someone has created a Test account with this email address.", true);
|
||||
|
@ -159,6 +159,6 @@ public class TrustStoreEmailTest extends AbstractTestRealmKeycloakTest {
|
|||
|
||||
// Email wasn't send, but we won't notify end user about that. Admin is aware due to the error in the logs and the SEND_VERIFY_EMAIL_ERROR event.
|
||||
assertEquals("You need to verify your email address to activate your account.",
|
||||
testRealmVerifyEmailPage.getFeedbackText());
|
||||
testRealmVerifyEmailPage.feedbackMessage().getText());
|
||||
}
|
||||
}
|
|
@ -37,8 +37,8 @@ import java.nio.charset.Charset;
|
|||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.keycloak.testsuite.util.URLUtils.currentUrlDoesntStartWith;
|
||||
import static org.keycloak.testsuite.util.URLUtils.currentUrlEqual;
|
||||
import static org.keycloak.testsuite.util.URLUtils.currentUrlStartWith;
|
||||
import static org.keycloak.testsuite.util.URLUtils.currentUrlEquals;
|
||||
import static org.keycloak.testsuite.util.URLUtils.currentUrlStartsWith;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -62,7 +62,7 @@ public class URLAssert {
|
|||
|
||||
public static void assertCurrentUrlEquals(final String url) {
|
||||
assertTrue("Expected URL: " + url + "; actual: " + DroneUtils.getCurrentDriver().getCurrentUrl(),
|
||||
currentUrlEqual(url));
|
||||
currentUrlEquals(url));
|
||||
}
|
||||
|
||||
|
||||
|
@ -82,7 +82,7 @@ public class URLAssert {
|
|||
|
||||
public static void assertCurrentUrlStartsWith(final String url){
|
||||
assertTrue("URL expected to begin with:" + url + "; actual URL: " + DroneUtils.getCurrentDriver().getCurrentUrl(),
|
||||
currentUrlStartWith(url));
|
||||
currentUrlStartsWith(url));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -27,20 +27,32 @@
|
|||
<property name="downloadBinaries">${webdriverDownloadBinaries}</property>
|
||||
<property name="githubUsername">${github.username}</property>
|
||||
<property name="githubToken">${github.secretToken}</property>
|
||||
<property name="ieDriverArch">${ieDriverArch}</property>
|
||||
|
||||
<!-- htmlunit -->
|
||||
<property name="htmlUnit.version">${htmlUnitBrowserVersion}</property>
|
||||
<property name="htmlUnitWebClientOptions">cssEnabled=false;historyPageCacheLimit=1</property>
|
||||
|
||||
<!-- firefox -->
|
||||
<property name="firefox_binary">${firefox_binary}</property>
|
||||
<property name="firefoxBinary">${firefox_binary}</property> <!-- we need to use 'firefoxBinary' instead of 'firefox_binary' due to some weird conflict with Appium -->
|
||||
<property name="firefoxLogLevel">OFF</property>
|
||||
<property name="firefoxLegacy">${firefoxLegacyDriver}</property>
|
||||
|
||||
<!-- chrome -->
|
||||
<property name="chromeBinary">${chromeBinary}</property>
|
||||
<property name="chromeArguments">${chromeArguments}</property>
|
||||
|
||||
<!-- internet explorer -->
|
||||
<property name="ieDriverArch">${ieDriverArch}</property>
|
||||
|
||||
<!-- appium -->
|
||||
<property name="appium.platformName">${appium.platformName}</property>
|
||||
<property name="appium.deviceName">${appium.deviceName}</property>
|
||||
<property name="appium.browserName">${appium.browserName}</property>
|
||||
<property name="appium.avd">${appium.avd}</property>
|
||||
<property name="appium.automationName">${appium.automationName}</property>
|
||||
<property name="appium.noReset">${appium.noReset}</property>
|
||||
<property name="appium.fullReset">${appium.fullReset}</property>
|
||||
<property name="appium.nativeWebScreenshot">true</property> <!-- there's some issue when taking screenshot using the chromedriver therefore we need to take screenshots of the whole screen (using adb) instead -->
|
||||
</extension>
|
||||
|
||||
<extension qualifier="drone">
|
||||
|
@ -66,7 +78,7 @@
|
|||
<property name="htmlUnitWebClientOptions">cssEnabled=false;historyPageCacheLimit=1</property>
|
||||
|
||||
<!-- firefox -->
|
||||
<property name="firefox_binary">${firefox_binary}</property>
|
||||
<property name="firefoxBinary">${firefox_binary}</property>
|
||||
<property name="firefoxLogLevel">OFF</property>
|
||||
<property name="firefoxLegacy">${firefoxLegacyDriver}</property>
|
||||
|
||||
|
@ -77,7 +89,7 @@
|
|||
|
||||
<extension qualifier="graphene-secondbrowser">
|
||||
<property name="browser">${browser}</property>
|
||||
<property name="firefox_binary">${firefox_binary}</property>
|
||||
<property name="firefoxBinary">${firefox_binary}</property>
|
||||
</extension>
|
||||
|
||||
<engine>
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
<app.server.mode>manual</app.server.mode>
|
||||
|
||||
<app.server.host>localhost</app.server.host>
|
||||
<app.server.browserHost/> <!-- if set, this host will be used by the browser instead of app.server.host -->
|
||||
<app.server.port.offset>200</app.server.port.offset>
|
||||
<app.server.http.port>8280</app.server.http.port>
|
||||
<app.server.https.port>8643</app.server.https.port>
|
||||
|
@ -202,6 +203,7 @@
|
|||
<app.server.mode>${app.server.mode}</app.server.mode>
|
||||
|
||||
<app.server.host>${app.server.host}</app.server.host>
|
||||
<app.server.browserHost>${app.server.browserHost}</app.server.browserHost> <!-- if set, this host will be used by the browser -->
|
||||
<app.server.port.offset>${app.server.port.offset}</app.server.port.offset>
|
||||
<app.server.http.port>${app.server.http.port}</app.server.http.port>
|
||||
<app.server.https.port>${app.server.https.port}</app.server.https.port>
|
||||
|
|
175
testsuite/integration-arquillian/tests/other/base-ui/pom.xml
Normal file
175
testsuite/integration-arquillian/tests/other/base-ui/pom.xml
Normal file
|
@ -0,0 +1,175 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
~ and other contributors as indicated by the @author tags.
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>integration-arquillian-tests-other</artifactId>
|
||||
<groupId>org.keycloak.testsuite</groupId>
|
||||
<version>4.3.0.Final-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>integration-arquillian-tests-base-ui</artifactId>
|
||||
<name>Base UI TestSuite</name>
|
||||
|
||||
<properties>
|
||||
<droneInstantiationTimeoutInSeconds>600</droneInstantiationTimeoutInSeconds>
|
||||
<firefoxLegacyDriver>false</firefoxLegacyDriver> <!-- using the new GeckoDriver -->
|
||||
<keycloak.theme.dir>${auth.server.home}/themes</keycloak.theme.dir>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>properties-maven-plugin</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>process-resources</phase>
|
||||
<goals>
|
||||
<goal>read-project-properties</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<files>
|
||||
<file>${testsuite.constants}</file>
|
||||
</files>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-theme-files</id>
|
||||
<phase>process-resources</phase>
|
||||
<goals>
|
||||
<goal>copy-resources</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${keycloak.theme.dir}</outputDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources/themes</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<systemProperties>
|
||||
<keycloak.theme.dir>${keycloak.theme.dir}</keycloak.theme.dir>
|
||||
</systemProperties>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.arquillian.extension</groupId>
|
||||
<artifactId>arquillian-drone-appium-extension</artifactId>
|
||||
<version>${arquillian-drone.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<profiles>
|
||||
|
||||
<profile>
|
||||
<id>android</id>
|
||||
<properties>
|
||||
<browser>appium</browser>
|
||||
<appium.platformName>android</appium.platformName>
|
||||
<appium.browserName>chrome</appium.browserName>
|
||||
<appium.deviceName>doesn't matter</appium.deviceName> <!-- ignored but required by AndroidDriver -->
|
||||
<appium.automationName>Appium</appium.automationName>
|
||||
<auth.server.browserHost>10.0.2.2</auth.server.browserHost>
|
||||
</properties>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-enforcer-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>enforce</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<rules>
|
||||
<requireProperty>
|
||||
<property>appium.avd</property>
|
||||
<regex>\S+.*</regex>
|
||||
</requireProperty>
|
||||
</rules>
|
||||
<fail>true</fail>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>ios</id>
|
||||
<properties>
|
||||
<browser>appium</browser>
|
||||
<appium.platformName>ios</appium.platformName>
|
||||
<appium.browserName>safari</appium.browserName>
|
||||
<appium.automationName>XCUITest</appium.automationName>
|
||||
<appium.noReset>true</appium.noReset>
|
||||
</properties>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-enforcer-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>enforce</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<rules>
|
||||
<requireProperty>
|
||||
<property>appium.deviceName</property>
|
||||
<regex>\S+.*</regex>
|
||||
</requireProperty>
|
||||
</rules>
|
||||
<fail>true</fail>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
|
||||
</profiles>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,2 @@
|
|||
#encoding: utf-8
|
||||
locale_test=Přísný jazyk
|
|
@ -0,0 +1,2 @@
|
|||
#encoding: utf-8
|
||||
locale_test=Přísný jazyk
|
|
@ -0,0 +1,2 @@
|
|||
parent=${theme-default-name}
|
||||
locales=en,lang01,lang02,lang03,lang04,lang05,test,lang06,lang07,lang08,lang09,lang10
|
|
@ -0,0 +1,2 @@
|
|||
#encoding: utf-8
|
||||
locale_test=Přísný jazyk
|
|
@ -0,0 +1,2 @@
|
|||
#encoding: utf-8
|
||||
locale_test=Přísný jazyk
|
|
@ -0,0 +1,2 @@
|
|||
parent=${theme-default-name}
|
||||
locales=en,lang01,lang02,lang03,lang04,lang05,test,lang06,lang07,lang08,lang09,lang10
|
|
@ -0,0 +1,2 @@
|
|||
#encoding: utf-8
|
||||
locale_test=Přísný jazyk
|
|
@ -0,0 +1,2 @@
|
|||
#encoding: utf-8
|
||||
locale_test=Přísný jazyk
|
|
@ -0,0 +1,2 @@
|
|||
parent=${theme-default-name}
|
||||
locales=en,lang01,lang02,lang03,lang04,lang05,test,lang06,lang07,lang08,lang09,lang10
|
|
@ -0,0 +1,2 @@
|
|||
#encoding: utf-8
|
||||
locale_test=Přísný jazyk
|
|
@ -0,0 +1,12 @@
|
|||
#encoding: utf-8
|
||||
locale_test=Přísný jazyk
|
||||
termsText=[TEST LOCALE] souhlas s podmínkami
|
||||
notMatchPasswordMessage=[TEST LOCALE] hesla se neshodují
|
||||
firstName=[TEST LOCALE] křestní jméno
|
||||
updateProfileMessage=[TEST LOCALE] aktualizovat profil
|
||||
verifyEmailMessage=[TEST LOCALE] je třeba ověřit emailovou adresu
|
||||
invalidTotpMessage=[TEST LOCALE] vložen chybný kód
|
||||
oauthGrantTitle=[TEST LOCALE] Udělit přístup {0}
|
||||
rememberMe=[TEST LOCALE] Zapamatuj si mě
|
||||
invalidUserMessage=[TEST LOCALE] Chybné jméno nebo heslo
|
||||
emailForgotTitle=[TEST LOCALE] Zapomenuté heslo
|
|
@ -0,0 +1,2 @@
|
|||
parent=${theme-default-name}
|
||||
locales=en,lang01,lang02,lang03,lang04,lang05,test,lang06,lang07,lang08,lang09,lang10
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.testsuite.ui;
|
||||
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.testsuite.AbstractAuthTest;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.keycloak.testsuite.auth.page.AuthRealm.TEST;
|
||||
|
||||
/**
|
||||
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
||||
*/
|
||||
public abstract class AbstractUiTest extends AbstractAuthTest {
|
||||
public static final String LOCALIZED_THEME = "localized-theme";
|
||||
public static final String CUSTOM_LOCALE_NAME = "Přísný jazyk";
|
||||
|
||||
@Override
|
||||
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
||||
RealmRepresentation testRealmRep = new RealmRepresentation();
|
||||
testRealmRep.setId(TEST);
|
||||
testRealmRep.setRealm(TEST);
|
||||
testRealmRep.setEnabled(true);
|
||||
configureInternationalizationForRealm(testRealmRep);
|
||||
testRealms.add(testRealmRep);
|
||||
}
|
||||
|
||||
protected void configureInternationalizationForRealm(RealmRepresentation realm) {
|
||||
// fetch the supported locales for the special test theme that includes some fake test locales
|
||||
Set<String> supportedLocales = adminClient.serverInfo().getInfo().getThemes().get("login").stream()
|
||||
.filter(x -> x.getName().equals(LOCALIZED_THEME))
|
||||
.flatMap(x -> Arrays.stream(x.getLocales()))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
realm.setInternationalizationEnabled(true);
|
||||
realm.setSupportedLocales(supportedLocales);
|
||||
realm.setLoginTheme(LOCALIZED_THEME);
|
||||
realm.setAdminTheme(LOCALIZED_THEME);
|
||||
realm.setAccountTheme(LOCALIZED_THEME);
|
||||
realm.setEmailTheme(LOCALIZED_THEME);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.testsuite.ui.login;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.keycloak.testsuite.ui.AbstractUiTest;
|
||||
|
||||
import static junit.framework.TestCase.assertEquals;
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlDoesntStartWith;
|
||||
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
|
||||
|
||||
/**
|
||||
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
||||
*/
|
||||
public abstract class AbstractLoginTest extends AbstractUiTest {
|
||||
@Before
|
||||
public void addTestUser() {
|
||||
createTestUserWithAdminClient(false);
|
||||
}
|
||||
|
||||
protected void assertLoginFailed(String message) {
|
||||
assertCurrentUrlDoesntStartWith(testRealmAccountPage);
|
||||
assertTrue("Feedback message should be an error", loginPage.feedbackMessage().isError());
|
||||
assertEquals(message, loginPage.feedbackMessage().getText());
|
||||
}
|
||||
|
||||
protected void assertLoginSuccessful() {
|
||||
assertCurrentUrlStartsWith(testRealmAccountPage);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,302 @@
|
|||
/*
|
||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.testsuite.ui.login;
|
||||
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.auth.page.login.Registration;
|
||||
import org.keycloak.testsuite.auth.page.login.ResetCredentials;
|
||||
import org.keycloak.testsuite.auth.page.login.UpdateAccount;
|
||||
import org.keycloak.testsuite.auth.page.login.UpdatePassword;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.keycloak.testsuite.auth.page.AuthRealm.TEST;
|
||||
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWithLoginUrlOf;
|
||||
|
||||
/**
|
||||
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
||||
*/
|
||||
public class LoginPageTest extends AbstractLoginTest {
|
||||
@Page
|
||||
private UpdateAccount updateAccountPage;
|
||||
|
||||
@Page
|
||||
private UpdatePassword updatePasswordPage;
|
||||
|
||||
@Page
|
||||
private Registration registrationPage;
|
||||
|
||||
@Page
|
||||
private ResetCredentials resetCredentialsPage;
|
||||
|
||||
@Override
|
||||
public void setDefaultPageUriParameters() {
|
||||
super.setDefaultPageUriParameters();
|
||||
updateAccountPage.setAuthRealm(TEST);
|
||||
updatePasswordPage.setAuthRealm(TEST);
|
||||
registrationPage.setAuthRealm(TEST);
|
||||
resetCredentialsPage.setAuthRealm(TEST);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
||||
super.addTestRealms(testRealms);
|
||||
RealmRepresentation testRealmRep = testRealms.get(0);
|
||||
testRealmRep.setDisplayNameHtml("Test realm <b>HTML</b>");
|
||||
testRealmRep.setRememberMe(true);
|
||||
testRealmRep.setResetPasswordAllowed(true);
|
||||
testRealmRep.setRegistrationAllowed(true);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void beforeLoginTest() {
|
||||
deleteAllCookiesForTestRealm();
|
||||
testRealmAccountPage.navigateTo();
|
||||
assertCurrentUrlStartsWithLoginUrlOf(testRealmAccountPage);
|
||||
assertFalse(testRealmLoginPage.feedbackMessage().isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void wrongCredentials() {
|
||||
assertFalse(testRealmLoginPage.form().isRememberMe());
|
||||
testRealmLoginPage.form().rememberMe(true);
|
||||
assertTrue(testRealmLoginPage.form().isRememberMe());
|
||||
testRealmLoginPage.form().login("some-user", "badPwd");
|
||||
assertTrue(testRealmLoginPage.form().isRememberMe());
|
||||
|
||||
assertLoginFailed("Invalid username or password.");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disabledUser() {
|
||||
testUser.setEnabled(false);
|
||||
testUserResource().update(testUser);
|
||||
|
||||
testRealmLoginPage.form().login(testUser);
|
||||
|
||||
assertLoginFailed("Account is disabled, contact admin.");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void labelsTest() {
|
||||
assertEquals("test realm html", testRealmLoginPage.getHeaderText().toLowerCase()); // we need to convert to lower case as Safari handles getText() differently
|
||||
assertEquals("Username or email", testRealmLoginPage.form().getUsernameLabel());
|
||||
assertEquals("Password", testRealmLoginPage.form().getPasswordLabel());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loginSuccessful() {
|
||||
testRealmLoginPage.form().login(testUser);
|
||||
assertLoginSuccessful();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void internationalizationTest() {
|
||||
final String rememberMeLabel = "[TEST LOCALE] Zapamatuj si mě";
|
||||
|
||||
// required action set up
|
||||
testUser.setRequiredActions(Arrays.asList(updatePasswordPage.getActionId(), updateAccountPage.getActionId()));
|
||||
testUserResource().update(testUser);
|
||||
|
||||
assertEquals("Remember me", testRealmLoginPage.form().getRememberMeLabel());
|
||||
testRealmLoginPage.localeDropdown().selectByText(CUSTOM_LOCALE_NAME);
|
||||
assertEquals(rememberMeLabel, testRealmLoginPage.form().getRememberMeLabel());
|
||||
|
||||
testRealmLoginPage.form().login();
|
||||
assertLoginFailed("[TEST LOCALE] Chybné jméno nebo heslo");
|
||||
assertEquals(rememberMeLabel, testRealmLoginPage.form().getRememberMeLabel());
|
||||
testRealmLoginPage.form().login(testUser);
|
||||
|
||||
if (updatePasswordPage.isCurrent()) {
|
||||
updatePassword();
|
||||
updateProfile();
|
||||
}
|
||||
else {
|
||||
updateProfile();
|
||||
updatePassword();
|
||||
}
|
||||
|
||||
assertLoginSuccessful();
|
||||
}
|
||||
|
||||
private void updateProfile() {
|
||||
assertEquals("[TEST LOCALE] aktualizovat profil", updateAccountPage.feedbackMessage().getText());
|
||||
updateAccountPage.submit(); // should be pre-filled
|
||||
}
|
||||
|
||||
private void updatePassword() {
|
||||
updatePasswordPage.updatePasswords("some wrong", "password");
|
||||
assertEquals("[TEST LOCALE] hesla se neshodují", updatePasswordPage.feedbackMessage().getText());
|
||||
updatePasswordPage.updatePasswords("matchingPassword", "matchingPassword");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void registerTest() {
|
||||
testRealmLoginPage.form().register();
|
||||
|
||||
registrationPage.assertCurrent();
|
||||
|
||||
registrationPage.localeDropdown().selectByText(CUSTOM_LOCALE_NAME);
|
||||
registrationPage.submit();
|
||||
|
||||
assertTrue(registrationPage.feedbackMessage().isError());
|
||||
assertEquals("[TEST LOCALE] křestní jméno", registrationPage.accountFields().getFirstNameLabel());
|
||||
|
||||
registrationPage.backToLogin();
|
||||
testRealmLoginPage.form().register();
|
||||
|
||||
registrationPage.localeDropdown().selectByText(ENGLISH_LOCALE_NAME);
|
||||
|
||||
final String username = "vmuzikar";
|
||||
final String email = "vmuzikar@redhat.com";
|
||||
final String firstName = "Vaclav";
|
||||
final String lastName = "Muzikar";
|
||||
final UserRepresentation newUser = createUserRepresentation(username, email, firstName, lastName, true, "password");
|
||||
|
||||
// empty form
|
||||
registrationPage.submit();
|
||||
assertRegistrationFields(null, null, null, null, false, true);
|
||||
|
||||
// email filled in
|
||||
registrationPage.accountFields().setEmail(email);
|
||||
registrationPage.submit();
|
||||
assertRegistrationFields(null, null, email, null, false, true);
|
||||
|
||||
// first name filled in
|
||||
registrationPage.accountFields().setEmail(null);
|
||||
registrationPage.accountFields().setFirstName(firstName);
|
||||
registrationPage.submit();
|
||||
assertRegistrationFields(firstName, null, null, null, false, true);
|
||||
|
||||
// last name filled in
|
||||
registrationPage.accountFields().setFirstName(null);
|
||||
registrationPage.accountFields().setLastName(lastName);
|
||||
registrationPage.submit();
|
||||
assertRegistrationFields(null, lastName, null, null, false, true);
|
||||
|
||||
// username filled in
|
||||
registrationPage.accountFields().setLastName(null);
|
||||
registrationPage.accountFields().setUsername(username);
|
||||
registrationPage.submit();
|
||||
assertRegistrationFields(null, null, null, username, false, true);
|
||||
|
||||
// password mismatch
|
||||
registrationPage.accountFields().setValues(newUser);
|
||||
registrationPage.passwordFields().setPassword("wrong");
|
||||
registrationPage.passwordFields().setConfirmPassword("password");
|
||||
registrationPage.submit();
|
||||
assertRegistrationFields(firstName, lastName, email, username, true, false);
|
||||
|
||||
// success
|
||||
registrationPage.register(newUser);
|
||||
assertLoginSuccessful();
|
||||
}
|
||||
|
||||
private void assertRegistrationFields(String firstName, String lastName, String email, String username, boolean password, boolean passwordConfirm) {
|
||||
assertTrue(registrationPage.feedbackMessage().isError());
|
||||
final String errorMsg = registrationPage.feedbackMessage().getText();
|
||||
|
||||
if (firstName != null) {
|
||||
assertEquals(firstName, registrationPage.accountFields().getFirstName());
|
||||
assertFalse(registrationPage.accountFields().hasFirstNameError());
|
||||
assertFalse(errorMsg.contains("first name"));
|
||||
}
|
||||
else {
|
||||
assertTrue(registrationPage.accountFields().hasFirstNameError());
|
||||
assertTrue(errorMsg.contains("first name"));
|
||||
}
|
||||
|
||||
if (lastName != null) {
|
||||
assertEquals(lastName, registrationPage.accountFields().getLastName());
|
||||
assertFalse(registrationPage.accountFields().hasLastNameError());
|
||||
assertFalse(errorMsg.contains("last name"));
|
||||
}
|
||||
else {
|
||||
assertTrue(registrationPage.accountFields().hasLastNameError());
|
||||
assertTrue(errorMsg.contains("last name"));
|
||||
}
|
||||
|
||||
if (email != null) {
|
||||
assertEquals(email, registrationPage.accountFields().getEmail());
|
||||
assertFalse(registrationPage.accountFields().hasEmailError());
|
||||
assertFalse(errorMsg.contains("email"));
|
||||
}
|
||||
else {
|
||||
assertTrue(registrationPage.accountFields().hasEmailError());
|
||||
assertTrue(errorMsg.contains("email"));
|
||||
}
|
||||
|
||||
if (username != null) {
|
||||
assertEquals(username, registrationPage.accountFields().getUsername());
|
||||
assertFalse(registrationPage.accountFields().hasUsernameError());
|
||||
assertFalse(errorMsg.contains("username"));
|
||||
}
|
||||
else {
|
||||
assertTrue(registrationPage.accountFields().hasUsernameError());
|
||||
assertTrue(errorMsg.contains("username"));
|
||||
}
|
||||
|
||||
if (password) {
|
||||
assertFalse(registrationPage.passwordFields().hasPasswordError());
|
||||
assertFalse(errorMsg.contains("Please specify password."));
|
||||
}
|
||||
else {
|
||||
assertTrue(registrationPage.passwordFields().hasPasswordError());
|
||||
assertTrue(errorMsg.contains("Please specify password."));
|
||||
}
|
||||
|
||||
if (passwordConfirm) {
|
||||
assertFalse(registrationPage.passwordFields().hasConfirmPasswordError());
|
||||
assertFalse(registrationPage.feedbackMessage().getText().contains("Password confirmation doesn't match."));
|
||||
}
|
||||
else {
|
||||
assertTrue(registrationPage.passwordFields().hasConfirmPasswordError());
|
||||
assertTrue(registrationPage.feedbackMessage().getText().contains("Password confirmation doesn't match."));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resetCredentialsTest() {
|
||||
testRealmLoginPage.form().forgotPassword();
|
||||
resetCredentialsPage.localeDropdown().selectByText(CUSTOM_LOCALE_NAME);
|
||||
resetCredentialsPage.assertCurrent();
|
||||
resetCredentialsPage.backToLogin();
|
||||
|
||||
testRealmLoginPage.form().forgotPassword();
|
||||
assertEquals("[TEST LOCALE] Zapomenuté heslo", resetCredentialsPage.getTitleText());
|
||||
|
||||
// empty form
|
||||
assertFalse(resetCredentialsPage.feedbackMessage().isPresent());
|
||||
resetCredentialsPage.submit();
|
||||
resetCredentialsPage.assertCurrent();
|
||||
assertTrue(resetCredentialsPage.feedbackMessage().isPresent());
|
||||
assertTrue(resetCredentialsPage.feedbackMessage().isError());
|
||||
|
||||
// non-empty form
|
||||
resetCredentialsPage.resetCredentials(testUser.getUsername());
|
||||
// there will be probably an error sending email, so no further action here
|
||||
}
|
||||
}
|
|
@ -0,0 +1,524 @@
|
|||
/*
|
||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.testsuite.ui.login;
|
||||
|
||||
import com.google.zxing.BinaryBitmap;
|
||||
import com.google.zxing.Result;
|
||||
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
|
||||
import com.google.zxing.common.HybridBinarizer;
|
||||
import com.google.zxing.qrcode.QRCodeReader;
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.models.utils.Base32;
|
||||
import org.keycloak.models.utils.HmacOTP;
|
||||
import org.keycloak.models.utils.TimeBasedOTP;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.auth.page.login.LoginError;
|
||||
import org.keycloak.testsuite.auth.page.login.OAuthGrant;
|
||||
import org.keycloak.testsuite.auth.page.login.OTPSetup;
|
||||
import org.keycloak.testsuite.auth.page.login.OneTimeCode;
|
||||
import org.keycloak.testsuite.auth.page.login.RequiredActions;
|
||||
import org.keycloak.testsuite.auth.page.login.TermsAndConditions;
|
||||
import org.keycloak.testsuite.auth.page.login.UpdateAccount;
|
||||
import org.keycloak.testsuite.auth.page.login.UpdatePassword;
|
||||
import org.keycloak.testsuite.auth.page.login.VerifyEmail;
|
||||
import org.openqa.selenium.OutputType;
|
||||
import org.openqa.selenium.TakesScreenshot;
|
||||
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.google.zxing.BarcodeFormat.QR_CODE;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assume.assumeFalse;
|
||||
import static org.keycloak.models.ClientScopeModel.CONSENT_SCREEN_TEXT;
|
||||
import static org.keycloak.models.ClientScopeModel.DISPLAY_ON_CONSENT_SCREEN;
|
||||
import static org.keycloak.models.Constants.ACCOUNT_MANAGEMENT_CLIENT_ID;
|
||||
import static org.keycloak.representations.idm.CredentialRepresentation.PASSWORD;
|
||||
import static org.keycloak.testsuite.admin.ApiUtil.createUserAndResetPasswordWithAdminClient;
|
||||
import static org.keycloak.testsuite.auth.page.AuthRealm.TEST;
|
||||
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWithLoginUrlOf;
|
||||
|
||||
/**
|
||||
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
||||
*/
|
||||
public class RequiredActionsTest extends AbstractLoginTest {
|
||||
public static final String GRANT_REALM = "grant-realm";
|
||||
public static final String CONSENT_TEXT = "Příliš žluťoučký kůň úpěl ďábelské ódy";
|
||||
|
||||
private UserRepresentation grantRealmUser = createUserRepresentation("test", PASSWORD);
|
||||
|
||||
public static final String TOTP = "totp";
|
||||
public static final String HOTP = "hotp";
|
||||
|
||||
@Page
|
||||
private TermsAndConditions termsAndConditionsPage;
|
||||
|
||||
@Page
|
||||
private UpdatePassword updatePasswordPage;
|
||||
|
||||
@Page
|
||||
private UpdateAccount updateAccountPage;
|
||||
|
||||
@Page
|
||||
private VerifyEmail verifyEmailPage;
|
||||
|
||||
@Page
|
||||
private OTPSetup otpSetupPage;
|
||||
|
||||
@Page
|
||||
private OneTimeCode oneTimeCodePage;
|
||||
|
||||
@Page
|
||||
private OAuthGrant oAuthGrantPage;
|
||||
|
||||
@Page
|
||||
private LoginError loginErrorPage;
|
||||
|
||||
private TimeBasedOTP otpGenerator = new TimeBasedOTP();
|
||||
|
||||
@Override
|
||||
public void setDefaultPageUriParameters() {
|
||||
super.setDefaultPageUriParameters();
|
||||
termsAndConditionsPage.setAuthRealm(TEST);
|
||||
updatePasswordPage.setAuthRealm(TEST);
|
||||
updateAccountPage.setAuthRealm(TEST);
|
||||
verifyEmailPage.setAuthRealm(TEST);
|
||||
otpSetupPage.setAuthRealm(TEST);
|
||||
oneTimeCodePage.setAuthRealm(TEST);
|
||||
oAuthGrantPage.setAuthRealm(GRANT_REALM);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
||||
super.addTestRealms(testRealms);
|
||||
|
||||
RealmRepresentation testRealmRep = new RealmRepresentation();
|
||||
testRealmRep.setId(GRANT_REALM);
|
||||
testRealmRep.setRealm(GRANT_REALM);
|
||||
configureInternationalizationForRealm(testRealmRep);
|
||||
testRealmRep.setEnabled(true);
|
||||
|
||||
testRealms.add(testRealmRep);
|
||||
}
|
||||
|
||||
// Some actions we need to do after the realm is created and configured
|
||||
@Override
|
||||
protected void afterAbstractKeycloakTestRealmImport() {
|
||||
super.afterAbstractKeycloakTestRealmImport();
|
||||
|
||||
// create test user
|
||||
createUserAndResetPasswordWithAdminClient(adminClient.realm(GRANT_REALM), grantRealmUser, PASSWORD);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void termsAndConditions() {
|
||||
RequiredActionProviderRepresentation termsAndCondRep = testRealmResource().flows().getRequiredAction(termsAndConditionsPage.getActionId());
|
||||
termsAndCondRep.setEnabled(true);
|
||||
testRealmResource().flows().updateRequiredAction(termsAndConditionsPage.getActionId(), termsAndCondRep);
|
||||
|
||||
initiateRequiredAction(termsAndConditionsPage);
|
||||
|
||||
termsAndConditionsPage.localeDropdown().selectAndAssert(CUSTOM_LOCALE_NAME);
|
||||
|
||||
termsAndConditionsPage.acceptTerms();
|
||||
assertLoginSuccessful();
|
||||
|
||||
deleteAllSessionsInTestRealm();
|
||||
initiateRequiredAction(termsAndConditionsPage);
|
||||
assertEquals("[TEST LOCALE] souhlas s podmínkami", termsAndConditionsPage.getText());
|
||||
termsAndConditionsPage.declineTerms();
|
||||
loginErrorPage.assertCurrent();
|
||||
assertNoAccess();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updatePassword() {
|
||||
initiateRequiredAction(updatePasswordPage);
|
||||
|
||||
updatePasswordPage.localeDropdown().selectAndAssert(CUSTOM_LOCALE_NAME);
|
||||
assertTrue(updatePasswordPage.feedbackMessage().isWarning());
|
||||
assertEquals("You need to change your password to activate your account.", updatePasswordPage.feedbackMessage().getText());
|
||||
assertEquals("New Password", updatePasswordPage.fields().getNewPasswordLabel());
|
||||
assertEquals("Confirm password", updatePasswordPage.fields().getConfirmPasswordLabel());
|
||||
|
||||
updatePasswordPage.updatePasswords("some wrong", "password");
|
||||
assertTrue(updatePasswordPage.feedbackMessage().isError());
|
||||
assertEquals("[TEST LOCALE] hesla se neshodují", updatePasswordPage.feedbackMessage().getText());
|
||||
|
||||
updatePasswordPage.localeDropdown().selectAndAssert(ENGLISH_LOCALE_NAME);
|
||||
updatePasswordPage.updatePasswords("matchingPassword", "matchingPassword");
|
||||
assertLoginSuccessful();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateProfile() {
|
||||
initiateRequiredAction(updateAccountPage);
|
||||
|
||||
// prefilled profile
|
||||
assertTrue(updateAccountPage.feedbackMessage().isWarning());
|
||||
updateAccountPage.localeDropdown().selectAndAssert(CUSTOM_LOCALE_NAME);
|
||||
assertEquals("[TEST LOCALE] aktualizovat profil", updateAccountPage.feedbackMessage().getText());
|
||||
updateAccountPage.localeDropdown().selectAndAssert(ENGLISH_LOCALE_NAME);
|
||||
assertFalse(updateAccountPage.fields().isUsernamePresent());
|
||||
assertEquals("Email", updateAccountPage.fields().getEmailLabel());
|
||||
assertEquals("First name", updateAccountPage.fields().getFirstNameLabel());
|
||||
assertEquals("Last name", updateAccountPage.fields().getLastNameLabel());
|
||||
assertFalse(updateAccountPage.fields().hasEmailError());
|
||||
assertFalse(updateAccountPage.fields().hasFirstNameError());
|
||||
assertFalse(updateAccountPage.fields().hasLastNameError());
|
||||
assertEquals(testUser.getEmail(), updateAccountPage.fields().getEmail());
|
||||
assertEquals(testUser.getFirstName(), updateAccountPage.fields().getFirstName());
|
||||
assertEquals(testUser.getLastName(), updateAccountPage.fields().getLastName());
|
||||
updateAccountPage.localeDropdown().selectAndAssert(CUSTOM_LOCALE_NAME);
|
||||
|
||||
// empty form
|
||||
updateAccountPage.updateAccount(null, null, null);
|
||||
assertTrue(updateAccountPage.feedbackMessage().isError());
|
||||
String errorMsg = updateAccountPage.feedbackMessage().getText();
|
||||
assertTrue(errorMsg.contains("first name") && errorMsg.contains("last name") && errorMsg.contains("email"));
|
||||
assertTrue(updateAccountPage.fields().hasEmailError());
|
||||
assertTrue(updateAccountPage.fields().hasFirstNameError());
|
||||
assertTrue(updateAccountPage.fields().hasLastNameError());
|
||||
|
||||
final String email = "vmuzikar@redhat.com";
|
||||
final String firstName = "Vaclav";
|
||||
final String lastName = "Muzikar";
|
||||
|
||||
// email filled in
|
||||
updateAccountPage.fields().setEmail(email);
|
||||
updateAccountPage.submit();
|
||||
assertTrue(updateAccountPage.feedbackMessage().isError());
|
||||
errorMsg = updateAccountPage.feedbackMessage().getText();
|
||||
assertTrue(errorMsg.contains("first name") && errorMsg.contains("last name") && !errorMsg.contains("email"));
|
||||
assertFalse(updateAccountPage.fields().hasEmailError());
|
||||
assertTrue(updateAccountPage.fields().hasFirstNameError());
|
||||
assertTrue(updateAccountPage.fields().hasLastNameError());
|
||||
assertEquals(email, updateAccountPage.fields().getEmail());
|
||||
|
||||
// first name filled in
|
||||
updateAccountPage.fields().setFirstName(firstName);
|
||||
updateAccountPage.submit();
|
||||
assertTrue(updateAccountPage.feedbackMessage().isError());
|
||||
errorMsg = updateAccountPage.feedbackMessage().getText();
|
||||
assertTrue(!errorMsg.contains("first name") && errorMsg.contains("last name") && !errorMsg.contains("email"));
|
||||
assertFalse(updateAccountPage.fields().hasEmailError());
|
||||
assertFalse(updateAccountPage.fields().hasFirstNameError());
|
||||
assertTrue(updateAccountPage.fields().hasLastNameError());
|
||||
assertEquals(email, updateAccountPage.fields().getEmail());
|
||||
assertEquals(firstName, updateAccountPage.fields().getFirstName());
|
||||
|
||||
// last name filled in
|
||||
updateAccountPage.fields().setFirstName(null);
|
||||
updateAccountPage.fields().setLastName(lastName);
|
||||
updateAccountPage.submit();
|
||||
assertTrue(updateAccountPage.feedbackMessage().isError());
|
||||
errorMsg = updateAccountPage.feedbackMessage().getText();
|
||||
assertTrue(errorMsg.contains("first name") && !errorMsg.contains("last name") && !errorMsg.contains("email"));
|
||||
assertFalse(updateAccountPage.fields().hasEmailError());
|
||||
assertTrue(updateAccountPage.fields().hasFirstNameError());
|
||||
assertFalse(updateAccountPage.fields().hasLastNameError());
|
||||
assertEquals(email, updateAccountPage.fields().getEmail());
|
||||
assertEquals(lastName, updateAccountPage.fields().getLastName());
|
||||
|
||||
// success
|
||||
assertEquals("[TEST LOCALE] křestní jméno", updateAccountPage.fields().getFirstNameLabel());
|
||||
updateAccountPage.updateAccount(email, firstName, lastName);
|
||||
assertLoginSuccessful();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyEmail() {
|
||||
initiateRequiredAction(verifyEmailPage);
|
||||
|
||||
verifyEmailPage.localeDropdown().selectAndAssert(CUSTOM_LOCALE_NAME);
|
||||
|
||||
boolean firstAttempt = true;
|
||||
while (true) {
|
||||
assertTrue(verifyEmailPage.feedbackMessage().isWarning());
|
||||
assertEquals("[TEST LOCALE] je třeba ověřit emailovou adresu", verifyEmailPage.feedbackMessage().getText());
|
||||
assertEquals("An email with instructions to verify your email address has been sent to you.", verifyEmailPage.getInstructionMessage());
|
||||
|
||||
if (firstAttempt) {
|
||||
verifyEmailPage.clickResend();
|
||||
firstAttempt = false;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureManualTotp() {
|
||||
setRealmOtpType(TOTP);
|
||||
testManualOtp();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureManualHotp() {
|
||||
setRealmOtpType(HOTP);
|
||||
testManualOtp();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureBarcodeTotp() throws Exception {
|
||||
setRealmOtpType(TOTP);
|
||||
testBarcodeOtp();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureBarcodeHotp() throws Exception {
|
||||
setRealmOtpType(HOTP);
|
||||
testBarcodeOtp();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clientConsent() {
|
||||
testRealmPage.setAuthRealm(GRANT_REALM);
|
||||
testRealmAccountPage.setAuthRealm(GRANT_REALM);
|
||||
testRealmLoginPage.setAuthRealm(GRANT_REALM);
|
||||
|
||||
final List<String> defaultClientScopesToApprove = Arrays.asList("Email address", "User profile");
|
||||
|
||||
// custom consent text
|
||||
initiateClientScopesConsent(true, CONSENT_TEXT);
|
||||
oAuthGrantPage.localeDropdown().selectAndAssert(CUSTOM_LOCALE_NAME);
|
||||
List<String> clientScopesToApprove = new LinkedList<>(defaultClientScopesToApprove);
|
||||
clientScopesToApprove.add(CONSENT_TEXT);
|
||||
oAuthGrantPage.assertClientScopes(clientScopesToApprove);
|
||||
|
||||
// default consent text
|
||||
initiateClientScopesConsent(true, null);
|
||||
clientScopesToApprove = new LinkedList<>(defaultClientScopesToApprove);
|
||||
clientScopesToApprove.add("Account");
|
||||
oAuthGrantPage.assertClientScopes(clientScopesToApprove);
|
||||
|
||||
// consent with missing client
|
||||
initiateClientScopesConsent(false, CONSENT_TEXT);
|
||||
oAuthGrantPage.assertClientScopes(defaultClientScopesToApprove);
|
||||
|
||||
// test buttons
|
||||
oAuthGrantPage.cancel();
|
||||
assertNoAccess();
|
||||
testRealmLoginPage.form().login(grantRealmUser);
|
||||
assertEquals("[TEST LOCALE] Udělit přístup Account", oAuthGrantPage.getTitleText());
|
||||
oAuthGrantPage.accept();
|
||||
assertLoginSuccessful();
|
||||
}
|
||||
|
||||
private void testManualOtp() {
|
||||
initiateRequiredAction(otpSetupPage);
|
||||
|
||||
otpSetupPage.localeDropdown().selectAndAssert(CUSTOM_LOCALE_NAME);
|
||||
|
||||
otpSetupPage.clickManualMode();
|
||||
assertFalse(otpSetupPage.isBarcodePresent());
|
||||
assertTrue(otpSetupPage.feedbackMessage().isWarning());
|
||||
assertEquals("You need to set up Mobile Authenticator to activate your account.", otpSetupPage.feedbackMessage().getText());
|
||||
|
||||
// empty input
|
||||
otpSetupPage.submit();
|
||||
assertTrue(otpSetupPage.feedbackMessage().isError());
|
||||
assertEquals("Please specify authenticator code.", otpSetupPage.feedbackMessage().getText());
|
||||
|
||||
// TODO: remove this once is KEYCLOAK-7081 fixed
|
||||
otpSetupPage.clickManualMode();
|
||||
otpSetupPage.clickManualMode();
|
||||
|
||||
final String replacePattern = "^.+: ";
|
||||
|
||||
// extract data
|
||||
String type = otpSetupPage.getOtpType().replaceAll(replacePattern, "");
|
||||
if (type.equals("Time-based")) type = TOTP;
|
||||
else if (type.equals("Counter-based")) type = HOTP;
|
||||
String secret = otpSetupPage.getSecretKey();
|
||||
int digits = Integer.parseInt(otpSetupPage.getOtpDigits().replaceAll(replacePattern, ""));
|
||||
String algorithm = otpSetupPage.getOtpAlgorithm().replaceAll(replacePattern, "");
|
||||
Integer period = type.equals(TOTP) ? Integer.parseInt(otpSetupPage.getOtpPeriod().replaceAll(replacePattern, "")) : null;
|
||||
Integer counter = type.equals(HOTP) ? Integer.parseInt(otpSetupPage.getOtpCounter().replaceAll(replacePattern, "")) : null;
|
||||
|
||||
// the actual test
|
||||
testOtp(type, algorithm, digits, period, counter, secret);
|
||||
}
|
||||
|
||||
private void testBarcodeOtp() throws Exception {
|
||||
assumeFalse(driver instanceof HtmlUnitDriver); // HtmlUnit browser cannot take screenshots
|
||||
TakesScreenshot screenshotDriver = (TakesScreenshot) driver;
|
||||
QRCodeReader qrCodeReader = new QRCodeReader();
|
||||
|
||||
initiateRequiredAction(otpSetupPage);
|
||||
|
||||
otpSetupPage.localeDropdown().selectAndAssert(CUSTOM_LOCALE_NAME);
|
||||
|
||||
otpSetupPage.clickManualMode();
|
||||
otpSetupPage.clickBarcodeMode();
|
||||
|
||||
assertTrue(otpSetupPage.isBarcodePresent());
|
||||
assertFalse(otpSetupPage.isSecretKeyPresent());
|
||||
assertTrue(otpSetupPage.feedbackMessage().isWarning());
|
||||
assertEquals("You need to set up Mobile Authenticator to activate your account.", otpSetupPage.feedbackMessage().getText());
|
||||
|
||||
// empty input
|
||||
otpSetupPage.submit();
|
||||
assertTrue(otpSetupPage.feedbackMessage().isError());
|
||||
assertEquals("Please specify authenticator code.", otpSetupPage.feedbackMessage().getText());
|
||||
|
||||
// take a screenshot of the QR code
|
||||
byte[] screenshot = screenshotDriver.getScreenshotAs(OutputType.BYTES);
|
||||
BufferedImage screenshotImg = ImageIO.read(new ByteArrayInputStream(screenshot));
|
||||
BinaryBitmap screenshotBinaryBitmap = new BinaryBitmap(new HybridBinarizer(new BufferedImageLuminanceSource(screenshotImg)));
|
||||
Result qrCode = qrCodeReader.decode(screenshotBinaryBitmap);
|
||||
|
||||
// parse the QR code string
|
||||
Pattern qrUriPattern = Pattern.compile("^otpauth:\\/\\/(?<type>.+)\\/(?<realm>.+):(?<user>.+)\\?secret=(?<secret>.+)&digits=(?<digits>.+)&algorithm=(?<algorithm>.+)&issuer=(?<issuer>.+)&(?:period=(?<period>.+)|counter=(?<counter>.+))$");
|
||||
Matcher qrUriMatcher = qrUriPattern.matcher(qrCode.getText());
|
||||
assertTrue(qrUriMatcher.find());
|
||||
|
||||
// extract data
|
||||
String type = qrUriMatcher.group("type");
|
||||
String realm = qrUriMatcher.group("realm");
|
||||
String user = qrUriMatcher.group("user");
|
||||
String secret = qrUriMatcher.group("secret");
|
||||
int digits = Integer.parseInt(qrUriMatcher.group("digits"));
|
||||
String algorithm = qrUriMatcher.group("algorithm");
|
||||
String issuer = qrUriMatcher.group("issuer");
|
||||
Integer period = type.equals(TOTP) ? Integer.parseInt(qrUriMatcher.group("period")) : null;
|
||||
Integer counter = type.equals(HOTP) ? Integer.parseInt(qrUriMatcher.group("counter")) : null;
|
||||
|
||||
RealmRepresentation realmRep = testRealmResource().toRepresentation();
|
||||
String expectedRealmName = realmRep.getDisplayName() != null && !realmRep.getDisplayName().isEmpty() ? realmRep.getDisplayName() : realmRep.getRealm();
|
||||
|
||||
// basic assertations
|
||||
assertEquals(QR_CODE, qrCode.getBarcodeFormat());
|
||||
assertEquals(expectedRealmName, realm);
|
||||
assertEquals(expectedRealmName, issuer);
|
||||
assertEquals(testUser.getUsername(), user);
|
||||
|
||||
// the actual test
|
||||
testOtp(type, algorithm, digits, period, counter, secret);
|
||||
}
|
||||
|
||||
private void testOtp(String type, String algorithm, int digits, Integer period, Integer counter, String secret) {
|
||||
switch (algorithm) {
|
||||
case "SHA1":
|
||||
algorithm = TimeBasedOTP.HMAC_SHA1;
|
||||
break;
|
||||
case "SHA256":
|
||||
algorithm = TimeBasedOTP.HMAC_SHA256;
|
||||
break;
|
||||
case "SHA512":
|
||||
algorithm = TimeBasedOTP.HMAC_SHA512;
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Wrong algorithm type");
|
||||
}
|
||||
|
||||
HmacOTP otpGenerator;
|
||||
String secretDecoded = new String(Base32.decode(secret));
|
||||
String code;
|
||||
|
||||
switch (type) {
|
||||
case TOTP:
|
||||
otpGenerator = new TimeBasedOTP(algorithm, digits, period, 0);
|
||||
code = ((TimeBasedOTP) otpGenerator).generateTOTP(secretDecoded);
|
||||
break;
|
||||
case HOTP:
|
||||
otpGenerator = new HmacOTP(digits, algorithm, 0);
|
||||
code = otpGenerator.generateHOTP(secretDecoded, counter);
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Wrong OTP type");
|
||||
}
|
||||
|
||||
// fill in the form
|
||||
otpSetupPage.setTotp(code);
|
||||
otpSetupPage.submit();
|
||||
assertLoginSuccessful();
|
||||
|
||||
// try the code is working
|
||||
deleteAllSessionsInTestRealm();
|
||||
testRealmAccountPage.navigateTo();
|
||||
testRealmLoginPage.form().login(testUser);
|
||||
oneTimeCodePage.assertCurrent();
|
||||
assertEquals("One-time code", oneTimeCodePage.getTotpLabel());
|
||||
|
||||
// bad attempt
|
||||
oneTimeCodePage.submit();
|
||||
assertTrue(oneTimeCodePage.feedbackMessage().isError());
|
||||
assertEquals("[TEST LOCALE] vložen chybný kód", oneTimeCodePage.feedbackMessage().getText());
|
||||
oneTimeCodePage.sendCode("XXXXXX");
|
||||
assertTrue(oneTimeCodePage.feedbackMessage().isError());
|
||||
assertEquals("[TEST LOCALE] vložen chybný kód", oneTimeCodePage.feedbackMessage().getText());
|
||||
|
||||
// generate new code
|
||||
code = type.equals(TOTP) ? ((TimeBasedOTP) otpGenerator).generateTOTP(secretDecoded) : otpGenerator.generateHOTP(secretDecoded, ++counter);
|
||||
oneTimeCodePage.sendCode(code);
|
||||
assertLoginSuccessful();
|
||||
}
|
||||
|
||||
private void setRealmOtpType(String otpType) {
|
||||
RealmRepresentation realmRep = testRealmResource().toRepresentation();
|
||||
realmRep.setOtpPolicyType(otpType);
|
||||
testRealmResource().update(realmRep);
|
||||
}
|
||||
|
||||
private void initiateRequiredAction(RequiredActions requiredActionPage) {
|
||||
testUser.setRequiredActions(Collections.singletonList(requiredActionPage.getActionId()));
|
||||
testUserResource().update(testUser);
|
||||
|
||||
testRealmAccountPage.navigateTo();
|
||||
assertCurrentUrlStartsWithLoginUrlOf(testRealmAccountPage);
|
||||
|
||||
testRealmLoginPage.form().login(testUser);
|
||||
requiredActionPage.assertCurrent();
|
||||
}
|
||||
|
||||
private void initiateClientScopesConsent(boolean displayOnConsentScreen, String consentScreenText) {
|
||||
ClientRepresentation accountClientRep = testRealmResource().clients().findByClientId(ACCOUNT_MANAGEMENT_CLIENT_ID).get(0);
|
||||
ClientResource accountClient = testRealmResource().clients().get(accountClientRep.getId());
|
||||
accountClientRep.setConsentRequired(true);
|
||||
accountClientRep.getAttributes().put(DISPLAY_ON_CONSENT_SCREEN, String.valueOf(displayOnConsentScreen));
|
||||
accountClientRep.getAttributes().put(CONSENT_SCREEN_TEXT, consentScreenText);
|
||||
accountClient.update(accountClientRep);
|
||||
|
||||
testRealmAccountPage.navigateTo();
|
||||
testRealmLoginPage.form().login(grantRealmUser);
|
||||
oAuthGrantPage.assertCurrent();
|
||||
}
|
||||
|
||||
private void assertNoAccess() {
|
||||
assertEquals("No access", loginErrorPage.getErrorMessage());
|
||||
loginErrorPage.backToApplication();
|
||||
assertCurrentUrlStartsWithLoginUrlOf(testRealmLoginPage);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
package org.keycloak.testsuite.console.page.authentication;
|
||||
|
||||
import org.keycloak.testsuite.page.Form;
|
||||
import org.keycloak.testsuite.util.UIUtils;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
@ -70,7 +70,7 @@ public class PasswordPolicy extends Authentication {
|
|||
|
||||
private void setPolicyValue(Type policy, String value) {
|
||||
WebElement input = getPolicyRow(policy).findElement(By.tagName("input"));
|
||||
Form.setInputValue(input, value);
|
||||
UIUtils.setTextInputValue(input, value);
|
||||
}
|
||||
|
||||
private WebElement getPolicyRow(Type policy) {
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
package org.keycloak.testsuite.console.page.authentication.flows;
|
||||
|
||||
import org.keycloak.testsuite.page.Form;
|
||||
import org.keycloak.testsuite.util.UIUtils;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
import org.openqa.selenium.support.ui.Select;
|
||||
|
@ -59,8 +60,8 @@ public class CreateFlowForm extends Form {
|
|||
}
|
||||
|
||||
public void setValues(String alias, String description, FlowType flowType) {
|
||||
setInputValue(aliasInput, alias);
|
||||
setInputValue(descriptionTextarea, description);
|
||||
UIUtils.setTextInputValue(aliasInput, alias);
|
||||
UIUtils.setTextInputValue(descriptionTextarea, description);
|
||||
flowTypeSelect.selectByVisibleText(flowType.getName());
|
||||
save();
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
package org.keycloak.testsuite.console.page.authentication.otppolicy;
|
||||
|
||||
import org.keycloak.testsuite.page.Form;
|
||||
import org.keycloak.testsuite.util.UIUtils;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
import org.openqa.selenium.support.ui.Select;
|
||||
|
@ -55,14 +56,14 @@ public class OTPPolicyForm extends Form {
|
|||
this.otpHashAlg.selectByValue(otpHashAlg.getName());
|
||||
this.digits.selectByVisibleText("" + digits.getName());
|
||||
|
||||
setInputValue(this.lookAhead, lookAhead);
|
||||
UIUtils.setTextInputValue(this.lookAhead, lookAhead);
|
||||
|
||||
switch (otpType) {
|
||||
case TIME_BASED:
|
||||
setInputValue(period, periodOrCounter);
|
||||
UIUtils.setTextInputValue(period, periodOrCounter);
|
||||
break;
|
||||
case COUNTER_BASED:
|
||||
setInputValue(counter, periodOrCounter);
|
||||
UIUtils.setTextInputValue(counter, periodOrCounter);
|
||||
break;
|
||||
}
|
||||
save();
|
||||
|
|
|
@ -3,11 +3,12 @@ package org.keycloak.testsuite.console.page.clients;
|
|||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.testsuite.page.Form;
|
||||
import org.keycloak.testsuite.util.Timer;
|
||||
import org.keycloak.testsuite.util.UIUtils;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
import org.openqa.selenium.support.ui.Select;
|
||||
|
||||
import static org.keycloak.testsuite.page.Form.getInputValue;
|
||||
import static org.keycloak.testsuite.util.UIUtils.getTextInputValue;
|
||||
import static org.keycloak.testsuite.util.WaitUtils.*;
|
||||
|
||||
/**
|
||||
|
@ -30,11 +31,11 @@ public class CreateClientForm extends Form {
|
|||
}
|
||||
|
||||
public String getClientId() {
|
||||
return getInputValue(clientIdInput);
|
||||
return getTextInputValue(clientIdInput);
|
||||
}
|
||||
|
||||
public void setClientId(String clientId) {
|
||||
setInputValue(clientIdInput, clientId);
|
||||
UIUtils.setTextInputValue(clientIdInput, clientId);
|
||||
}
|
||||
|
||||
public String getProtocol() {
|
||||
|
|
|
@ -41,6 +41,7 @@ import org.keycloak.testsuite.console.page.fragment.ModalDialog;
|
|||
import org.keycloak.testsuite.console.page.fragment.MultipleStringSelect2;
|
||||
import org.keycloak.testsuite.console.page.fragment.OnOffSwitch;
|
||||
import org.keycloak.testsuite.page.Form;
|
||||
import org.keycloak.testsuite.util.UIUtils;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
import org.openqa.selenium.support.ui.Select;
|
||||
|
@ -102,14 +103,14 @@ public class ResourcePermissionForm extends Form {
|
|||
private GroupPolicy groupPolicy;
|
||||
|
||||
public void populate(ResourcePermissionRepresentation expected, boolean save) {
|
||||
setInputValue(name, expected.getName());
|
||||
setInputValue(description, expected.getDescription());
|
||||
UIUtils.setTextInputValue(name, expected.getName());
|
||||
UIUtils.setTextInputValue(description, expected.getDescription());
|
||||
decisionStrategy.selectByValue(expected.getDecisionStrategy().name());
|
||||
|
||||
resourceTypeSwitch.setOn(expected.getResourceType() != null);
|
||||
|
||||
if (expected.getResourceType() != null) {
|
||||
setInputValue(resourceType, expected.getResourceType());
|
||||
UIUtils.setTextInputValue(resourceType, expected.getResourceType());
|
||||
} else {
|
||||
resourceTypeSwitch.setOn(false);
|
||||
resourceSelect.update(expected.getResources());
|
||||
|
@ -132,11 +133,11 @@ public class ResourcePermissionForm extends Form {
|
|||
public ResourcePermissionRepresentation toRepresentation() {
|
||||
ResourcePermissionRepresentation representation = new ResourcePermissionRepresentation();
|
||||
|
||||
representation.setName(getInputValue(name));
|
||||
representation.setDescription(getInputValue(description));
|
||||
representation.setName(UIUtils.getTextInputValue(name));
|
||||
representation.setDescription(UIUtils.getTextInputValue(description));
|
||||
representation.setDecisionStrategy(DecisionStrategy.valueOf(decisionStrategy.getFirstSelectedOption().getText().toUpperCase()));
|
||||
representation.setPolicies(policySelect.getSelected());
|
||||
String inputValue = getInputValue(resourceType);
|
||||
String inputValue = UIUtils.getTextInputValue(resourceType);
|
||||
|
||||
if (!"".equals(inputValue)) {
|
||||
representation.setResourceType(inputValue);
|
||||
|
|
|
@ -44,6 +44,7 @@ import org.keycloak.testsuite.console.page.fragment.ModalDialog;
|
|||
import org.keycloak.testsuite.console.page.fragment.MultipleStringSelect2;
|
||||
import org.keycloak.testsuite.console.page.fragment.SingleStringSelect2;
|
||||
import org.keycloak.testsuite.page.Form;
|
||||
import org.keycloak.testsuite.util.UIUtils;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
import org.openqa.selenium.support.ui.Select;
|
||||
|
@ -108,8 +109,8 @@ public class ScopePermissionForm extends Form {
|
|||
private GroupPolicy groupPolicy;
|
||||
|
||||
public void populate(ScopePermissionRepresentation expected, boolean save) {
|
||||
setInputValue(name, expected.getName());
|
||||
setInputValue(description, expected.getDescription());
|
||||
UIUtils.setTextInputValue(name, expected.getName());
|
||||
UIUtils.setTextInputValue(description, expected.getDescription());
|
||||
decisionStrategy.selectByValue(expected.getDecisionStrategy().name());
|
||||
|
||||
Set<String> resources = expected.getResources();
|
||||
|
@ -141,8 +142,8 @@ public class ScopePermissionForm extends Form {
|
|||
public ScopePermissionRepresentation toRepresentation() {
|
||||
ScopePermissionRepresentation representation = new ScopePermissionRepresentation();
|
||||
|
||||
representation.setName(getInputValue(name));
|
||||
representation.setDescription(getInputValue(description));
|
||||
representation.setName(UIUtils.getTextInputValue(name));
|
||||
representation.setDescription(UIUtils.getTextInputValue(description));
|
||||
representation.setDecisionStrategy(DecisionStrategy.valueOf(decisionStrategy.getFirstSelectedOption().getText().toUpperCase()));
|
||||
representation.setPolicies(policySelect.getSelected());
|
||||
representation.setResources(resourceSelect.getSelected());
|
||||
|
|
|
@ -33,6 +33,7 @@ import org.keycloak.representations.idm.authorization.TimePolicyRepresentation;
|
|||
import org.keycloak.representations.idm.authorization.UserPolicyRepresentation;
|
||||
import org.keycloak.testsuite.console.page.fragment.ModalDialog;
|
||||
import org.keycloak.testsuite.page.Form;
|
||||
import org.keycloak.testsuite.util.UIUtils;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
import org.openqa.selenium.support.ui.Select;
|
||||
|
@ -85,8 +86,8 @@ public class AggregatePolicyForm extends Form {
|
|||
private GroupPolicy groupPolicy;
|
||||
|
||||
public void populate(AggregatePolicyRepresentation expected, boolean save) {
|
||||
setInputValue(name, expected.getName());
|
||||
setInputValue(description, expected.getDescription());
|
||||
UIUtils.setTextInputValue(name, expected.getName());
|
||||
UIUtils.setTextInputValue(description, expected.getDescription());
|
||||
logic.selectByValue(expected.getLogic().name());
|
||||
|
||||
Set<String> selectedPolicies = policySelect.getSelected();
|
||||
|
@ -128,8 +129,8 @@ public class AggregatePolicyForm extends Form {
|
|||
public AggregatePolicyRepresentation toRepresentation() {
|
||||
AggregatePolicyRepresentation representation = new AggregatePolicyRepresentation();
|
||||
|
||||
representation.setName(getInputValue(name));
|
||||
representation.setDescription(getInputValue(description));
|
||||
representation.setName(UIUtils.getTextInputValue(name));
|
||||
representation.setDescription(UIUtils.getTextInputValue(description));
|
||||
representation.setLogic(Logic.valueOf(logic.getFirstSelectedOption().getText().toUpperCase()));
|
||||
representation.setPolicies(policySelect.getSelected());
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ import org.keycloak.representations.idm.authorization.Logic;
|
|||
import org.keycloak.testsuite.console.page.fragment.ModalDialog;
|
||||
import org.keycloak.testsuite.console.page.fragment.MultipleStringSelect2;
|
||||
import org.keycloak.testsuite.page.Form;
|
||||
import org.keycloak.testsuite.util.UIUtils;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
@ -57,8 +58,8 @@ public class ClientPolicyForm extends Form {
|
|||
protected ModalDialog modalDialog;
|
||||
|
||||
public void populate(ClientPolicyRepresentation expected, boolean save) {
|
||||
setInputValue(name, expected.getName());
|
||||
setInputValue(description, expected.getDescription());
|
||||
UIUtils.setTextInputValue(name, expected.getName());
|
||||
UIUtils.setTextInputValue(description, expected.getDescription());
|
||||
logic.selectByValue(expected.getLogic().name());
|
||||
|
||||
clientsInput.update(expected.getClients());
|
||||
|
@ -76,8 +77,8 @@ public class ClientPolicyForm extends Form {
|
|||
public ClientPolicyRepresentation toRepresentation() {
|
||||
ClientPolicyRepresentation representation = new ClientPolicyRepresentation();
|
||||
|
||||
representation.setName(getInputValue(name));
|
||||
representation.setDescription(getInputValue(description));
|
||||
representation.setName(UIUtils.getTextInputValue(name));
|
||||
representation.setDescription(UIUtils.getTextInputValue(description));
|
||||
representation.setLogic(Logic.valueOf(logic.getFirstSelectedOption().getText().toUpperCase()));
|
||||
representation.setClients(clientsInput.getSelected());
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ import org.keycloak.representations.idm.authorization.GroupPolicyRepresentation;
|
|||
import org.keycloak.representations.idm.authorization.Logic;
|
||||
import org.keycloak.testsuite.console.page.fragment.ModalDialog;
|
||||
import org.keycloak.testsuite.page.Form;
|
||||
import org.keycloak.testsuite.util.UIUtils;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.openqa.selenium.WebElement;
|
||||
|
@ -64,9 +65,9 @@ public class GroupPolicyForm extends Form {
|
|||
private WebDriver driver;
|
||||
|
||||
public void populate(GroupPolicyRepresentation expected, boolean save) {
|
||||
setInputValue(name, expected.getName());
|
||||
setInputValue(description, expected.getDescription());
|
||||
setInputValue(groupsClaim, expected.getGroupsClaim());
|
||||
UIUtils.setTextInputValue(name, expected.getName());
|
||||
UIUtils.setTextInputValue(description, expected.getDescription());
|
||||
UIUtils.setTextInputValue(groupsClaim, expected.getGroupsClaim());
|
||||
logic.selectByValue(expected.getLogic().name());
|
||||
|
||||
|
||||
|
@ -135,10 +136,10 @@ public class GroupPolicyForm extends Form {
|
|||
public GroupPolicyRepresentation toRepresentation() {
|
||||
GroupPolicyRepresentation representation = new GroupPolicyRepresentation();
|
||||
|
||||
representation.setName(getInputValue(name));
|
||||
representation.setDescription(getInputValue(description));
|
||||
representation.setName(UIUtils.getTextInputValue(name));
|
||||
representation.setDescription(UIUtils.getTextInputValue(description));
|
||||
|
||||
String groupsClaimValue = getInputValue(groupsClaim);
|
||||
String groupsClaimValue = UIUtils.getTextInputValue(groupsClaim);
|
||||
|
||||
representation.setGroupsClaim(groupsClaim == null || "".equals(groupsClaimValue.trim()) ? null : groupsClaimValue);
|
||||
representation.setLogic(Logic.valueOf(logic.getFirstSelectedOption().getText().toUpperCase()));
|
||||
|
|
|
@ -20,6 +20,7 @@ import org.keycloak.representations.idm.authorization.JSPolicyRepresentation;
|
|||
import org.keycloak.representations.idm.authorization.Logic;
|
||||
import org.keycloak.testsuite.console.page.fragment.ModalDialog;
|
||||
import org.keycloak.testsuite.page.Form;
|
||||
import org.keycloak.testsuite.util.UIUtils;
|
||||
import org.openqa.selenium.JavascriptExecutor;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
@ -46,8 +47,8 @@ public class JSPolicyForm extends Form {
|
|||
protected ModalDialog modalDialog;
|
||||
|
||||
public void populate(JSPolicyRepresentation expected, boolean save) {
|
||||
setInputValue(name, expected.getName());
|
||||
setInputValue(description, expected.getDescription());
|
||||
UIUtils.setTextInputValue(name, expected.getName());
|
||||
UIUtils.setTextInputValue(description, expected.getDescription());
|
||||
logic.selectByValue(expected.getLogic().name());
|
||||
|
||||
JavascriptExecutor scriptExecutor = (JavascriptExecutor) driver;
|
||||
|
@ -67,8 +68,8 @@ public class JSPolicyForm extends Form {
|
|||
public JSPolicyRepresentation toRepresentation() {
|
||||
JSPolicyRepresentation representation = new JSPolicyRepresentation();
|
||||
|
||||
representation.setName(getInputValue(name));
|
||||
representation.setDescription(getInputValue(description));
|
||||
representation.setName(UIUtils.getTextInputValue(name));
|
||||
representation.setDescription(UIUtils.getTextInputValue(description));
|
||||
representation.setLogic(Logic.valueOf(logic.getFirstSelectedOption().getText().toUpperCase()));
|
||||
|
||||
JavascriptExecutor scriptExecutor = (JavascriptExecutor) driver;
|
||||
|
|
|
@ -30,6 +30,7 @@ import org.keycloak.representations.idm.authorization.RolePolicyRepresentation;
|
|||
import org.keycloak.testsuite.console.page.fragment.AbstractMultipleSelect2;
|
||||
import org.keycloak.testsuite.console.page.fragment.ModalDialog;
|
||||
import org.keycloak.testsuite.page.Form;
|
||||
import org.keycloak.testsuite.util.UIUtils;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
@ -65,8 +66,8 @@ public class RolePolicyForm extends Form {
|
|||
protected ModalDialog modalDialog;
|
||||
|
||||
public void populate(RolePolicyRepresentation expected, boolean save) {
|
||||
setInputValue(name, expected.getName());
|
||||
setInputValue(description, expected.getDescription());
|
||||
UIUtils.setTextInputValue(name, expected.getName());
|
||||
UIUtils.setTextInputValue(description, expected.getDescription());
|
||||
logic.selectByValue(expected.getLogic().name());
|
||||
|
||||
Set<RolePolicyRepresentation.RoleDefinition> roles = expected.getRoles();
|
||||
|
@ -124,8 +125,8 @@ public class RolePolicyForm extends Form {
|
|||
public RolePolicyRepresentation toRepresentation() {
|
||||
RolePolicyRepresentation representation = new RolePolicyRepresentation();
|
||||
|
||||
representation.setName(getInputValue(name));
|
||||
representation.setDescription(getInputValue(description));
|
||||
representation.setName(UIUtils.getTextInputValue(name));
|
||||
representation.setDescription(UIUtils.getTextInputValue(description));
|
||||
representation.setLogic(Logic.valueOf(logic.getFirstSelectedOption().getText().toUpperCase()));
|
||||
|
||||
Set<RolePolicyRepresentation.RoleDefinition> roles = realmRoleSelect.getSelected();
|
||||
|
|
|
@ -20,6 +20,7 @@ import org.keycloak.representations.idm.authorization.Logic;
|
|||
import org.keycloak.representations.idm.authorization.RulePolicyRepresentation;
|
||||
import org.keycloak.testsuite.console.page.fragment.ModalDialog;
|
||||
import org.keycloak.testsuite.page.Form;
|
||||
import org.keycloak.testsuite.util.UIUtils;
|
||||
import org.keycloak.testsuite.util.WaitUtils;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
@ -76,11 +77,11 @@ public class RulePolicyForm extends Form {
|
|||
private WebElement resolveModuleButton;
|
||||
|
||||
public void populate(RulePolicyRepresentation expected, boolean save) {
|
||||
setInputValue(name, expected.getName());
|
||||
setInputValue(description, expected.getDescription());
|
||||
setInputValue(artifactGroupId, expected.getArtifactGroupId());
|
||||
setInputValue(artifactId, expected.getArtifactId());
|
||||
setInputValue(artifactVersion, expected.getArtifactVersion());
|
||||
UIUtils.setTextInputValue(name, expected.getName());
|
||||
UIUtils.setTextInputValue(description, expected.getDescription());
|
||||
UIUtils.setTextInputValue(artifactGroupId, expected.getArtifactGroupId());
|
||||
UIUtils.setTextInputValue(artifactId, expected.getArtifactId());
|
||||
UIUtils.setTextInputValue(artifactVersion, expected.getArtifactVersion());
|
||||
|
||||
clickLink(resolveModuleButton);
|
||||
waitGui().withTimeout(150, TimeUnit.SECONDS).until().element(id("moduleName")).is().enabled(); // The module load time could be long at some conditions
|
||||
|
@ -90,7 +91,7 @@ public class RulePolicyForm extends Form {
|
|||
|
||||
sessionName.selectByVisibleText(expected.getSessionName());
|
||||
|
||||
setInputValue(scannerPeriod, expected.getScannerPeriod());
|
||||
UIUtils.setTextInputValue(scannerPeriod, expected.getScannerPeriod());
|
||||
scannerPeriodUnit.selectByVisibleText(expected.getScannerPeriodUnit());
|
||||
logic.selectByValue(expected.getLogic().name());
|
||||
|
||||
|
@ -107,15 +108,15 @@ public class RulePolicyForm extends Form {
|
|||
public RulePolicyRepresentation toRepresentation() {
|
||||
RulePolicyRepresentation representation = new RulePolicyRepresentation();
|
||||
|
||||
representation.setName(getInputValue(name));
|
||||
representation.setDescription(getInputValue(description));
|
||||
representation.setName(UIUtils.getTextInputValue(name));
|
||||
representation.setDescription(UIUtils.getTextInputValue(description));
|
||||
representation.setLogic(Logic.valueOf(logic.getFirstSelectedOption().getText().toUpperCase()));
|
||||
representation.setArtifactGroupId(getInputValue(artifactGroupId));
|
||||
representation.setArtifactId(getInputValue(artifactId));
|
||||
representation.setArtifactVersion(getInputValue(artifactVersion));
|
||||
representation.setArtifactGroupId(UIUtils.getTextInputValue(artifactGroupId));
|
||||
representation.setArtifactId(UIUtils.getTextInputValue(artifactId));
|
||||
representation.setArtifactVersion(UIUtils.getTextInputValue(artifactVersion));
|
||||
representation.setModuleName(moduleName.getFirstSelectedOption().getText());
|
||||
representation.setSessionName(sessionName.getFirstSelectedOption().getText());
|
||||
representation.setScannerPeriod(getInputValue(scannerPeriod));
|
||||
representation.setScannerPeriod(UIUtils.getTextInputValue(scannerPeriod));
|
||||
representation.setScannerPeriodUnit(scannerPeriodUnit.getFirstSelectedOption().getText());
|
||||
|
||||
return representation;
|
||||
|
|
|
@ -20,6 +20,7 @@ import org.keycloak.representations.idm.authorization.TimePolicyRepresentation;
|
|||
import org.keycloak.representations.idm.authorization.Logic;
|
||||
import org.keycloak.testsuite.console.page.fragment.ModalDialog;
|
||||
import org.keycloak.testsuite.page.Form;
|
||||
import org.keycloak.testsuite.util.UIUtils;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
import org.openqa.selenium.support.ui.Select;
|
||||
|
@ -82,21 +83,21 @@ public class TimePolicyForm extends Form {
|
|||
protected ModalDialog modalDialog;
|
||||
|
||||
public void populate(TimePolicyRepresentation expected, boolean save) {
|
||||
setInputValue(name, expected.getName());
|
||||
setInputValue(description, expected.getDescription());
|
||||
UIUtils.setTextInputValue(name, expected.getName());
|
||||
UIUtils.setTextInputValue(description, expected.getDescription());
|
||||
logic.selectByValue(expected.getLogic().name());
|
||||
setInputValue(notBefore, expected.getNotBefore());
|
||||
setInputValue(notOnOrAfter, expected.getNotOnOrAfter());
|
||||
setInputValue(dayMonth, expected.getDayMonth());
|
||||
setInputValue(dayMonthEnd, expected.getDayMonthEnd());
|
||||
setInputValue(month, expected.getMonth());
|
||||
setInputValue(monthEnd, expected.getMonthEnd());
|
||||
setInputValue(year, expected.getYear());
|
||||
setInputValue(yearEnd, expected.getYearEnd());
|
||||
setInputValue(hour, expected.getHour());
|
||||
setInputValue(hourEnd, expected.getHourEnd());
|
||||
setInputValue(minute, expected.getMinute());
|
||||
setInputValue(minuteEnd, expected.getMinuteEnd());
|
||||
UIUtils.setTextInputValue(notBefore, expected.getNotBefore());
|
||||
UIUtils.setTextInputValue(notOnOrAfter, expected.getNotOnOrAfter());
|
||||
UIUtils.setTextInputValue(dayMonth, expected.getDayMonth());
|
||||
UIUtils.setTextInputValue(dayMonthEnd, expected.getDayMonthEnd());
|
||||
UIUtils.setTextInputValue(month, expected.getMonth());
|
||||
UIUtils.setTextInputValue(monthEnd, expected.getMonthEnd());
|
||||
UIUtils.setTextInputValue(year, expected.getYear());
|
||||
UIUtils.setTextInputValue(yearEnd, expected.getYearEnd());
|
||||
UIUtils.setTextInputValue(hour, expected.getHour());
|
||||
UIUtils.setTextInputValue(hourEnd, expected.getHourEnd());
|
||||
UIUtils.setTextInputValue(minute, expected.getMinute());
|
||||
UIUtils.setTextInputValue(minuteEnd, expected.getMinuteEnd());
|
||||
|
||||
if (save) {
|
||||
save();
|
||||
|
@ -111,21 +112,21 @@ public class TimePolicyForm extends Form {
|
|||
public TimePolicyRepresentation toRepresentation() {
|
||||
TimePolicyRepresentation representation = new TimePolicyRepresentation();
|
||||
|
||||
representation.setName(getInputValue(name));
|
||||
representation.setDescription(getInputValue(description));
|
||||
representation.setName(UIUtils.getTextInputValue(name));
|
||||
representation.setDescription(UIUtils.getTextInputValue(description));
|
||||
representation.setLogic(Logic.valueOf(logic.getFirstSelectedOption().getText().toUpperCase()));
|
||||
representation.setDayMonth(getInputValue(dayMonth));
|
||||
representation.setDayMonthEnd(getInputValue(dayMonthEnd));
|
||||
representation.setMonth(getInputValue(month));
|
||||
representation.setMonthEnd(getInputValue(monthEnd));
|
||||
representation.setYear(getInputValue(year));
|
||||
representation.setYearEnd(getInputValue(yearEnd));
|
||||
representation.setHour(getInputValue(hour));
|
||||
representation.setHourEnd(getInputValue(hourEnd));
|
||||
representation.setMinute(getInputValue(minute));
|
||||
representation.setMinuteEnd(getInputValue(minuteEnd));
|
||||
representation.setNotBefore(getInputValue(notBefore));
|
||||
representation.setNotOnOrAfter(getInputValue(notOnOrAfter));
|
||||
representation.setDayMonth(UIUtils.getTextInputValue(dayMonth));
|
||||
representation.setDayMonthEnd(UIUtils.getTextInputValue(dayMonthEnd));
|
||||
representation.setMonth(UIUtils.getTextInputValue(month));
|
||||
representation.setMonthEnd(UIUtils.getTextInputValue(monthEnd));
|
||||
representation.setYear(UIUtils.getTextInputValue(year));
|
||||
representation.setYearEnd(UIUtils.getTextInputValue(yearEnd));
|
||||
representation.setHour(UIUtils.getTextInputValue(hour));
|
||||
representation.setHourEnd(UIUtils.getTextInputValue(hourEnd));
|
||||
representation.setMinute(UIUtils.getTextInputValue(minute));
|
||||
representation.setMinuteEnd(UIUtils.getTextInputValue(minuteEnd));
|
||||
representation.setNotBefore(UIUtils.getTextInputValue(notBefore));
|
||||
representation.setNotOnOrAfter(UIUtils.getTextInputValue(notOnOrAfter));
|
||||
|
||||
return representation;
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ import org.keycloak.representations.idm.authorization.UserPolicyRepresentation;
|
|||
import org.keycloak.testsuite.console.page.fragment.ModalDialog;
|
||||
import org.keycloak.testsuite.console.page.fragment.MultipleStringSelect2;
|
||||
import org.keycloak.testsuite.page.Form;
|
||||
import org.keycloak.testsuite.util.UIUtils;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
@ -57,8 +58,8 @@ public class UserPolicyForm extends Form {
|
|||
protected ModalDialog modalDialog;
|
||||
|
||||
public void populate(UserPolicyRepresentation expected, boolean save) {
|
||||
setInputValue(name, expected.getName());
|
||||
setInputValue(description, expected.getDescription());
|
||||
UIUtils.setTextInputValue(name, expected.getName());
|
||||
UIUtils.setTextInputValue(description, expected.getDescription());
|
||||
logic.selectByValue(expected.getLogic().name());
|
||||
|
||||
usersInput.update(expected.getUsers());
|
||||
|
@ -76,8 +77,8 @@ public class UserPolicyForm extends Form {
|
|||
public UserPolicyRepresentation toRepresentation() {
|
||||
UserPolicyRepresentation representation = new UserPolicyRepresentation();
|
||||
|
||||
representation.setName(getInputValue(name));
|
||||
representation.setDescription(getInputValue(description));
|
||||
representation.setName(UIUtils.getTextInputValue(name));
|
||||
representation.setDescription(UIUtils.getTextInputValue(description));
|
||||
representation.setLogic(Logic.valueOf(logic.getFirstSelectedOption().getText().toUpperCase()));
|
||||
representation.setUsers(usersInput.getSelected());
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.keycloak.representations.idm.authorization.ResourceRepresentation;
|
|||
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
|
||||
import org.keycloak.testsuite.console.page.fragment.ModalDialog;
|
||||
import org.keycloak.testsuite.page.Form;
|
||||
import org.keycloak.testsuite.util.UIUtils;
|
||||
import org.keycloak.testsuite.util.WaitUtils;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.JavascriptExecutor;
|
||||
|
@ -78,16 +79,16 @@ public class ResourceForm extends Form {
|
|||
}
|
||||
}
|
||||
|
||||
setInputValue(name, expected.getName());
|
||||
setInputValue(displayName, expected.getDisplayName());
|
||||
setInputValue(type, expected.getType());
|
||||
UIUtils.setTextInputValue(name, expected.getName());
|
||||
UIUtils.setTextInputValue(displayName, expected.getDisplayName());
|
||||
UIUtils.setTextInputValue(type, expected.getType());
|
||||
|
||||
for (String uri : expected.getUris()) {
|
||||
setInputValue(newUri, uri);
|
||||
UIUtils.setTextInputValue(newUri, uri);
|
||||
addUriButton.click();
|
||||
}
|
||||
|
||||
setInputValue(iconUri, expected.getIconUri());
|
||||
UIUtils.setTextInputValue(iconUri, expected.getIconUri());
|
||||
|
||||
Set<ScopeRepresentation> scopes = expected.getScopes();
|
||||
|
||||
|
@ -123,15 +124,15 @@ public class ResourceForm extends Form {
|
|||
public ResourceRepresentation toRepresentation() {
|
||||
ResourceRepresentation representation = new ResourceRepresentation();
|
||||
|
||||
representation.setName(getInputValue(name));
|
||||
representation.setDisplayName(getInputValue(displayName));
|
||||
representation.setType(getInputValue(type));
|
||||
representation.setName(UIUtils.getTextInputValue(name));
|
||||
representation.setDisplayName(UIUtils.getTextInputValue(displayName));
|
||||
representation.setType(UIUtils.getTextInputValue(type));
|
||||
|
||||
for (WebElement uriInput : driver.findElements(By.xpath("//input[@ng-model='resource.uris[i]']"))) {
|
||||
representation.addUri(getInputValue(uriInput));
|
||||
representation.addUri(UIUtils.getTextInputValue(uriInput));
|
||||
}
|
||||
|
||||
representation.setIconUri(getInputValue(iconUri));
|
||||
representation.setIconUri(UIUtils.getTextInputValue(iconUri));
|
||||
representation.setScopes(scopesInput.getSelected());
|
||||
|
||||
return representation;
|
||||
|
@ -152,7 +153,7 @@ public class ResourceForm extends Form {
|
|||
private List<WebElement> selection;
|
||||
|
||||
public void select(String name) {
|
||||
setInputValue(search, name);
|
||||
UIUtils.setTextInputValue(search, name);
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.keycloak.testsuite.console.page.clients.authorization.scope;
|
|||
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
|
||||
import org.keycloak.testsuite.console.page.fragment.ModalDialog;
|
||||
import org.keycloak.testsuite.page.Form;
|
||||
import org.keycloak.testsuite.util.UIUtils;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
||||
|
@ -43,9 +44,9 @@ public class ScopeForm extends Form {
|
|||
protected ModalDialog modalDialog;
|
||||
|
||||
public void populate(ScopeRepresentation expected) {
|
||||
setInputValue(name, expected.getName());
|
||||
setInputValue(displayName, expected.getDisplayName());
|
||||
setInputValue(iconUri, expected.getIconUri());
|
||||
UIUtils.setTextInputValue(name, expected.getName());
|
||||
UIUtils.setTextInputValue(displayName, expected.getDisplayName());
|
||||
UIUtils.setTextInputValue(iconUri, expected.getIconUri());
|
||||
save();
|
||||
}
|
||||
|
||||
|
@ -57,9 +58,9 @@ public class ScopeForm extends Form {
|
|||
public ScopeRepresentation toRepresentation() {
|
||||
ScopeRepresentation representation = new ScopeRepresentation();
|
||||
|
||||
representation.setName(getInputValue(name));
|
||||
representation.setDisplayName(getInputValue(displayName));
|
||||
representation.setIconUri(getInputValue(iconUri));
|
||||
representation.setName(UIUtils.getTextInputValue(name));
|
||||
representation.setDisplayName(UIUtils.getTextInputValue(displayName));
|
||||
representation.setIconUri(UIUtils.getTextInputValue(iconUri));
|
||||
|
||||
return representation;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
package org.keycloak.testsuite.console.page.clients.clustering;
|
||||
|
||||
import org.keycloak.testsuite.page.Form;
|
||||
import org.keycloak.testsuite.util.UIUtils;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
import org.openqa.selenium.support.ui.Select;
|
||||
|
@ -45,7 +46,7 @@ public class ClientClusteringForm extends Form {
|
|||
private WebElement hostNameInput;
|
||||
|
||||
private void setNodeReRegistrationTimeout(String value) {
|
||||
setInputValue(nodeReRegistrationTimeoutInput, value);
|
||||
UIUtils.setTextInputValue(nodeReRegistrationTimeoutInput, value);
|
||||
}
|
||||
|
||||
private void setNodeReRegistrationTimeoutUnit(String value) {
|
||||
|
@ -60,7 +61,7 @@ public class ClientClusteringForm extends Form {
|
|||
public void addNode(String hostName) {
|
||||
registerNodeManuallyLink.click();
|
||||
// waitforElement(hostNameInput);
|
||||
setInputValue(hostNameInput, hostName);
|
||||
UIUtils.setTextInputValue(hostNameInput, hostName);
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue