Test Framework - support for multiple WebDriver instances. (#46982)

Signed-off-by: Lukas Hanusovsky <lhanusov@redhat.com>
This commit is contained in:
Lukas Hanusovsky 2026-03-16 12:54:19 +01:00 committed by GitHub
parent 0466955604
commit e351d5949b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 135 additions and 23 deletions

View file

@ -1,6 +1,7 @@
package org.keycloak.testframework.oauth;
import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.client.registration.ClientRegistration;
import org.keycloak.testframework.ui.page.LoginPage;
import org.keycloak.testframework.ui.webdriver.ManagedWebDriver;
@ -17,15 +18,21 @@ import org.openqa.selenium.support.PageFactory;
public class OAuthClient extends AbstractOAuthClient<OAuthClient> {
private final ManagedWebDriver managedWebDriver;
private final ClientResource clientResource;
public OAuthClient(String baseUrl, CloseableHttpClient httpClient, ManagedWebDriver managedWebDriver) {
public OAuthClient(String baseUrl, CloseableHttpClient httpClient, ManagedWebDriver managedWebDriver, ClientResource clientResource) {
super(baseUrl, httpClient, managedWebDriver.driver());
this.managedWebDriver = managedWebDriver;
this.clientResource = clientResource;
config = new OAuthClientConfig()
.responseType(OAuth2Constants.CODE);
}
public OAuthClient(String baseUrl, CloseableHttpClient httpClient, ManagedWebDriver managedWebDriver) {
this(baseUrl, httpClient, managedWebDriver, null);
}
@Override
public void fillLoginForm(String username, String password) {
LoginPage loginPage = new LoginPage(managedWebDriver);
@ -45,6 +52,9 @@ public class OAuthClient extends AbstractOAuthClient<OAuthClient> {
}
public void close() {
if (clientResource != null) {
clientResource.remove();
}
}
}

View file

@ -2,6 +2,7 @@ package org.keycloak.testframework.oauth;
import java.util.List;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.testframework.injection.DependenciesBuilder;
import org.keycloak.testframework.injection.Dependency;
@ -26,7 +27,7 @@ public class OAuthClientSupplier implements Supplier<OAuthClient, InjectOAuthCli
public List<Dependency> getDependencies(RequestedInstance<OAuthClient, InjectOAuthClient> instanceContext) {
return DependenciesBuilder.create(KeycloakUrls.class)
.add(HttpClient.class)
.add(ManagedWebDriver.class)
.add(ManagedWebDriver.class, instanceContext.getAnnotation().webDriverRef())
.add(TestApp.class)
.add(ManagedRealm.class, instanceContext.getAnnotation().realmRef()).build();
}
@ -37,7 +38,7 @@ public class OAuthClientSupplier implements Supplier<OAuthClient, InjectOAuthCli
KeycloakUrls keycloakUrls = instanceContext.getDependency(KeycloakUrls.class);
CloseableHttpClient httpClient = (CloseableHttpClient) instanceContext.getDependency(HttpClient.class);
ManagedWebDriver webDriver = instanceContext.getDependency(ManagedWebDriver.class);
ManagedWebDriver webDriver = instanceContext.getDependency(ManagedWebDriver.class, annotation.webDriverRef());
TestApp testApp = instanceContext.getDependency(TestApp.class);
ManagedRealm realm = instanceContext.getDependency(ManagedRealm.class, annotation.realmRef());
@ -53,12 +54,16 @@ public class OAuthClientSupplier implements Supplier<OAuthClient, InjectOAuthCli
testAppClient.setAdminUrl(testApp.getAdminUri());
}
String clientId = testAppClient.getClientId();
String clientId = testAppClient.getClientId();;
if (!annotation.ref().isEmpty()) {
clientId = clientId + "-" + annotation.ref();
testAppClient.setClientId(clientId);
}
String clientSecret = testAppClient.getSecret();
ApiUtil.getCreatedId(realm.admin().clients().create(testAppClient));
OAuthClient oAuthClient = new OAuthClient(keycloakUrls.getBase(), httpClient, webDriver);
String id = ApiUtil.getCreatedId(realm.admin().clients().create(testAppClient));
ClientResource clientResource = realm.admin().clients().get(id);
OAuthClient oAuthClient = new OAuthClient(keycloakUrls.getBase(), httpClient, webDriver, clientResource);
oAuthClient.config().realm(realm.getName()).client(clientId, clientSecret).redirectUri(redirectUri);
return oAuthClient;
}

View file

@ -24,6 +24,8 @@ public @interface InjectOAuthClient {
String realmRef() default "";
String webDriverRef() default "";
boolean kcAdmin() default false;
}

View file

@ -0,0 +1,81 @@
package org.keycloak.testframework.tests;
import org.keycloak.testframework.annotations.InjectEvents;
import org.keycloak.testframework.annotations.InjectRealm;
import org.keycloak.testframework.annotations.InjectUser;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.events.EventAssertion;
import org.keycloak.testframework.events.Events;
import org.keycloak.testframework.injection.LifeCycle;
import org.keycloak.testframework.oauth.OAuthClient;
import org.keycloak.testframework.oauth.annotations.InjectOAuthClient;
import org.keycloak.testframework.realm.ManagedRealm;
import org.keycloak.testframework.realm.ManagedUser;
import org.keycloak.testframework.realm.UserConfig;
import org.keycloak.testframework.realm.UserConfigBuilder;
import org.keycloak.testframework.ui.annotations.InjectPage;
import org.keycloak.testframework.ui.annotations.InjectWebDriver;
import org.keycloak.testframework.ui.page.LoginPage;
import org.keycloak.testframework.ui.webdriver.ManagedWebDriver;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@KeycloakIntegrationTest
public class ManagedWebDriverTest {
@InjectRealm
ManagedRealm managedRealm;
@InjectUser(ref = "user", config = UserForManagedWebDriver.class)
ManagedUser user;
@InjectWebDriver(ref = "webDriver1", lifecycle = LifeCycle.CLASS)
ManagedWebDriver webDriver1;
@InjectWebDriver(ref = "webDriver2", lifecycle = LifeCycle.CLASS)
ManagedWebDriver webDriver2;
@InjectOAuthClient(ref = "oauth1", webDriverRef = "webDriver1")
OAuthClient oauth1;
@InjectOAuthClient(ref = "oauth2", webDriverRef = "webDriver2")
OAuthClient oauth2;
@InjectEvents
Events events;
@InjectPage(ref = "loginPage1", webDriverRef = "webDriver1")
LoginPage loginPage1;
@InjectPage(ref = "loginPage2", webDriverRef = "webDriver2")
LoginPage loginPage2;
@Test
public void testMultipleWebDriverInstances() {
oauth1.openLoginForm();
loginPage1.fillLogin(user.getUsername(), user.getPassword());
loginPage1.submit();
Assertions.assertNotNull(oauth1.parseLoginResponse().getCode());
EventAssertion.assertSuccess(events.poll()).userId(user.getId());
oauth2.openLoginForm();
loginPage2.fillLogin(user.getUsername(), user.getPassword());
loginPage2.submit();
Assertions.assertNotNull(oauth2.parseLoginResponse().getCode());
EventAssertion.assertSuccess(events.poll()).userId(user.getId());
}
private static class UserForManagedWebDriver implements UserConfig {
@Override
public UserConfigBuilder configure(UserConfigBuilder user) {
return user.username("user").password("password").email("user@localhost").name("User", "Last");
}
}
}

View file

@ -12,4 +12,12 @@ import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface InjectPage {
/**
* A ref must be set if a test requires multiple instances
*/
String ref() default "";
String webDriverRef() default "";
}

View file

@ -5,10 +5,23 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.keycloak.testframework.injection.LifeCycle;
/**
* Inject a {@link org.keycloak.testframework.ui.webdriver.ManagedWebDriver} to interact directly with the web driver.
* When possible it is recommended to use pages instead of directly accessing the web driver.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface InjectWebDriver { }
public @interface InjectWebDriver {
/**
* A ref must be set if a test requires multiple instances
*/
String ref() default "";
/**
* Controls the lifecycle of the resource
*/
LifeCycle lifecycle() default LifeCycle.GLOBAL;
}

View file

@ -14,18 +14,18 @@ public class PageSupplier implements Supplier<AbstractPage, InjectPage> {
@Override
public List<Dependency> getDependencies(RequestedInstance<AbstractPage, InjectPage> instanceContext) {
return DependenciesBuilder.create(ManagedWebDriver.class).build();
return DependenciesBuilder.create(ManagedWebDriver.class, instanceContext.getAnnotation().webDriverRef()).build();
}
@Override
public AbstractPage getValue(InstanceContext<AbstractPage, InjectPage> instanceContext) {
ManagedWebDriver webDriver = instanceContext.getDependency(ManagedWebDriver.class);
ManagedWebDriver webDriver = instanceContext.getDependency(ManagedWebDriver.class, instanceContext.getAnnotation().webDriverRef());
return webDriver.page().createPage(instanceContext.getRequestedValueType());
}
@Override
public boolean compatible(InstanceContext<AbstractPage, InjectPage> a, RequestedInstance<AbstractPage, InjectPage> b) {
return true;
return a.getAnnotation().ref().equals(b.getAnnotation().ref());
}
}

View file

@ -2,7 +2,6 @@ package org.keycloak.testframework.ui.webdriver;
import org.keycloak.testframework.injection.InstanceContext;
import org.keycloak.testframework.injection.LifeCycle;
import org.keycloak.testframework.injection.RequestedInstance;
import org.keycloak.testframework.injection.Supplier;
import org.keycloak.testframework.ui.annotations.InjectWebDriver;
@ -18,12 +17,7 @@ public abstract class AbstractWebDriverSupplier implements Supplier<ManagedWebDr
@Override
public boolean compatible(InstanceContext<ManagedWebDriver, InjectWebDriver> a, RequestedInstance<ManagedWebDriver, InjectWebDriver> b) {
return true;
}
@Override
public LifeCycle getDefaultLifecycle() {
return LifeCycle.GLOBAL;
return a.getAnnotation().ref().equals(b.getAnnotation().ref());
}
@Override

View file

@ -211,9 +211,8 @@ public class ConsentsTest {
RealmRepresentation providerRealmRep = providerRealm.admin().toRepresentation();
providerRealmRep.setAccountTheme("keycloak");
providerRealm.admin().update(providerRealmRep);
providerRealm.admin().clients().create(ClientConfigBuilder.create().clientId("test-app").redirectUris("*").publicClient(true).webOrigins("*").build());
ClientRepresentation providerAccountRep = providerRealm.admin().clients().findByClientId("test-app").get(0);
ClientRepresentation providerAccountRep = providerRealm.admin().clients().findByClientId("test-app-provider").get(0);
// add offline_scope to default account-console client scope
ClientScopeRepresentation offlineAccessScope = providerRealm.admin().getDefaultOptionalClientScopes().stream()
@ -261,7 +260,7 @@ public class ConsentsTest {
@Test
public void testConsentCancel() {
// setup account client to require consent
ClientResource accountClient = findClientByClientId(providerRealm.admin(), "test-app");
ClientResource accountClient = findClientByClientId(providerRealm.admin(), "test-app-provider");
ClientRepresentation clientRepresentation = accountClient.toRepresentation();
clientRepresentation.setConsentRequired(true);
@ -308,7 +307,7 @@ public class ConsentsTest {
userRealm.updateWithCleanup(r -> r.enabledEventTypes("REFRESH_TOKEN_ERROR"));
String sessionId = loginEvent.getSessionId();
ClientRepresentation clientRepresentation = userRealm.admin().clients().findByClientId("test-app").get(0);
ClientRepresentation clientRepresentation = userRealm.admin().clients().findByClientId("test-app-user").get(0);
try {
clientRepresentation.setConsentRequired(true);
userRealm.admin().clients().get(clientRepresentation.getId()).update(clientRepresentation);
@ -339,7 +338,7 @@ public class ConsentsTest {
@Test
public void testConsentWithAdditionalClientAttributes() {
// setup account client to require consent
ClientResource accountClient = findClientByClientId(providerRealm.admin(), "test-app");
ClientResource accountClient = findClientByClientId(providerRealm.admin(), "test-app-provider");
ClientRepresentation clientRepresentation = accountClient.toRepresentation();
clientRepresentation.setConsentRequired(true);