From 337e94d5a45397f2e835e23c3650fbfdd642d8fb Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 20 Feb 2026 11:17:09 +0100 Subject: [PATCH] Add JavaDoc for most important parts of the new test framework MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #46170 Signed-off-by: stianst Signed-off-by: Stian Thorgersen Co-authored-by: Šimon Vacek <86605314+vaceksimon@users.noreply.github.com> --- .../FatalTestClassException.java | 5 ++ .../testframework/TestFrameworkExtension.java | 18 +++++ .../annotations/InjectAdminClient.java | 20 +++++ .../annotations/InjectAdminClientFactory.java | 10 +++ .../annotations/InjectAdminEvents.java | 9 +++ .../annotations/InjectClient.java | 12 +++ .../annotations/InjectCryptoHelper.java | 3 + .../annotations/InjectDependency.java | 4 + .../annotations/InjectEvents.java | 9 +++ .../annotations/InjectHttpClient.java | 6 ++ .../annotations/InjectHttpServer.java | 5 ++ .../annotations/InjectInfinispanServer.java | 6 ++ .../annotations/InjectKeycloakUrls.java | 4 + .../annotations/InjectRealm.java | 12 +++ .../annotations/InjectSimpleHttp.java | 3 + .../annotations/InjectSysLogServer.java | 4 + .../annotations/InjectTestDatabase.java | 3 + .../testframework/annotations/InjectUser.java | 6 ++ .../annotations/KeycloakIntegrationTest.java | 6 ++ .../annotations/TestCleanup.java | 4 + .../testframework/annotations/TestSetup.java | 4 + .../conditions/DisabledForDatabases.java | 3 + .../conditions/DisabledForServers.java | 3 + .../database/DatabaseConfig.java | 5 ++ .../database/DatabaseConfigBuilder.java | 18 +++++ .../testframework/events/AbstractEvents.java | 22 +++++- .../events/AdminEventAssertion.java | 64 ++++++++++++++++ .../testframework/events/AdminEvents.java | 3 + .../testframework/events/EventAssertion.java | 67 +++++++++++++++++ .../testframework/events/EventMatchers.java | 21 +++++- .../https/CertificatesConfig.java | 3 + .../https/CertificatesConfigBuilder.java | 18 +++++ .../https/ManagedCertificates.java | 38 +++++++++- .../injection/ManagedTestResource.java | 4 + .../testframework/realm/ClientConfig.java | 3 + .../testframework/realm/ManagedClient.java | 27 +++++++ .../realm/ManagedClientCleanup.java | 6 ++ .../testframework/realm/ManagedRealm.java | 58 +++++++++++++++ .../realm/ManagedRealmCleanup.java | 6 ++ .../testframework/realm/RealmConfig.java | 3 + .../testframework/realm/UserConfig.java | 3 + .../server/KeycloakServerConfig.java | 3 + .../server/KeycloakServerConfigBuilder.java | 74 +++++++++++++++++++ .../testframework/server/KeycloakUrls.java | 46 ++++++++++++ .../keycloak/testframework/util/ApiUtil.java | 12 +++ .../testframework/mail/MailServer.java | 26 +++++++ .../mail/annotations/InjectMailServer.java | 3 + .../testframework/oauth/OAuthClient.java | 3 + .../oauth/OAuthIdentityProvider.java | 3 + .../keycloak/testframework/oauth/TestApp.java | 3 + .../oauth/annotations/InjectOAuthClient.java | 3 + .../InjectOAuthIdentityProvider.java | 4 + .../oauth/annotations/InjectTestApp.java | 3 + .../remote/runonserver/InjectRunOnServer.java | 4 + .../remote/runonserver/RunOnServerClient.java | 30 +++++++- .../remote/timeoffset/InjectTimeOffSet.java | 3 + .../remote/timeoffset/TimeOffSet.java | 11 +++ .../ui/annotations/InjectPage.java | 4 + .../ui/annotations/InjectWebDriver.java | 4 + 59 files changed, 762 insertions(+), 5 deletions(-) diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/FatalTestClassException.java b/test-framework/core/src/main/java/org/keycloak/testframework/FatalTestClassException.java index 5da917fa9dc..7ed9ef550da 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/FatalTestClassException.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/FatalTestClassException.java @@ -1,5 +1,10 @@ package org.keycloak.testframework; +/** + * FatalTestClassException is thrown when a test class contains invalid configuration, or there is a non-recoverable + * problem when setting up managed resources for a test, for example the server can not be started. When a + * FatalTestClassException is thrown subsequent test methods in a test class will be skipped. + */ public class FatalTestClassException extends RuntimeException { public FatalTestClassException(String message) { diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/TestFrameworkExtension.java b/test-framework/core/src/main/java/org/keycloak/testframework/TestFrameworkExtension.java index 3a793af0214..356c46f0186 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/TestFrameworkExtension.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/TestFrameworkExtension.java @@ -6,14 +6,32 @@ import java.util.Map; import org.keycloak.testframework.injection.Supplier; +/** + * Test framework extensions allows adding additional suppliers to the test framework + */ public interface TestFrameworkExtension { + /** + * List of suppliers provided by the extension + * @return supplier list + */ List> suppliers(); + /** + * List of value types that are always created when running tests. Extensions usually does not need to implement + * this method + * @return the list of value types that are always requested for tests + */ default List> alwaysEnabledValueTypes() { return Collections.emptyList(); } + /** + * List of aliases for value types. By default, {@code getSimpleName} is used as the name for a value type, implementing + * this method allows setting custom aliases for the value type. For example the core extension has the alias + * {@code server} for the value type {@code KeycloakServer} + * @return map where key is the value type and value is the alias + */ default Map, String> valueTypeAliases() { return Collections.emptyMap(); } diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectAdminClient.java b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectAdminClient.java index 4435f51066d..e7f7b393e73 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectAdminClient.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectAdminClient.java @@ -6,18 +6,38 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * Injects a {@link org.keycloak.admin.client.Keycloak} instance to access Keycloak Admin APIs + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface InjectAdminClient { + /** + * A ref must be set if a test requires multiple instances + */ String ref() default ""; + /** + * Set to attach to the non-default realm + */ String realmRef() default ""; + /** + * BOOTSTRAP attaches to the master realm and global test client, while MANAGED_REALM + * attaches to a managed realm using the specified client or user. When using MANAGED_REALM either + * client or user has to be set + */ Mode mode() default Mode.BOOTSTRAP; + /** + * The client to authenticate as + */ String client() default ""; + /** + * The user to authenticate as + */ String user() default ""; enum Mode { diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectAdminClientFactory.java b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectAdminClientFactory.java index 03b4a432faf..9f491f6164e 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectAdminClientFactory.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectAdminClientFactory.java @@ -8,11 +8,21 @@ import java.lang.annotation.Target; import org.keycloak.testframework.injection.LifeCycle; +/** + * Injects a {@link org.keycloak.testframework.admin.AdminClientFactory} instance that can be used to create + * {@link org.keycloak.admin.client.Keycloak} instances. + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface InjectAdminClientFactory { + /** + * A ref must be set if a test requires multiple instances + */ String ref() default ""; + /** + * Controls the lifecycle of the resource + */ LifeCycle lifecycle() default LifeCycle.CLASS; } diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectAdminEvents.java b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectAdminEvents.java index 831d6086013..b717f487ed6 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectAdminEvents.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectAdminEvents.java @@ -5,12 +5,21 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * Injects a {@link org.keycloak.testframework.events.AdminEvents} instance that can be used to poll admin events from Keycloak + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface InjectAdminEvents { + /** + * A ref must be set if a test requires multiple instances + */ String ref() default ""; + /** + * Set to attach to the non-default realm + */ String realmRef() default ""; } diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectClient.java b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectClient.java index 96d8386d99e..c6e0522cb70 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectClient.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectClient.java @@ -9,16 +9,28 @@ import org.keycloak.testframework.injection.LifeCycle; import org.keycloak.testframework.realm.ClientConfig; import org.keycloak.testframework.realm.DefaultClientConfig; +/** + * Injects a {@link org.keycloak.testframework.realm.ManagedClient} used to create a client within the realm + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface InjectClient { + /** + * Used to define a custom configuration for the client + */ Class config() default DefaultClientConfig.class; + /** + * Controls the lifecycle of the resource + */ LifeCycle lifecycle() default LifeCycle.CLASS; String ref() default ""; + /** + * Set to attach to the non-default realm + */ String realmRef() default ""; /** diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectCryptoHelper.java b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectCryptoHelper.java index fe902f72a2a..f57499b8610 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectCryptoHelper.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectCryptoHelper.java @@ -5,6 +5,9 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * Injects a {@link org.keycloak.testframework.crypto.CryptoHelper} with various crypto utilities + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface InjectCryptoHelper { diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectDependency.java b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectDependency.java index 1a5e35228fb..969cfc92512 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectDependency.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectDependency.java @@ -5,6 +5,10 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * Injects dependencies into configuration classes; for example if a {@link org.keycloak.testframework.realm.ClientConfig} + * needs to access the {@link org.keycloak.testframework.realm.ManagedRealm} to set the correct configuration. + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface InjectDependency { diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectEvents.java b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectEvents.java index 962d9730398..d1e35a7b87f 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectEvents.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectEvents.java @@ -5,12 +5,21 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * Injects a {@link org.keycloak.testframework.events.Events} instance that can be used to poll login events from Keycloak + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface InjectEvents { + /** + * A ref must be set if a test requires multiple instances + */ String ref() default ""; + /** + * Set to attach to the non-default realm + */ String realmRef() default ""; } diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectHttpClient.java b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectHttpClient.java index f49c767c09b..7de94b681d0 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectHttpClient.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectHttpClient.java @@ -5,8 +5,14 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * Injects a {@link org.apache.http.client.HttpClient} that can be used to do HTTP requests within tests. See + * {@link InjectSimpleHttp} as an alternative that provides a simpler API. + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface InjectHttpClient { + boolean followRedirects() default true; + } diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectHttpServer.java b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectHttpServer.java index 4ce8edd379f..237ca28ec08 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectHttpServer.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectHttpServer.java @@ -5,6 +5,11 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * Injects a {@link com.sun.net.httpserver.HttpServer} instance that can be used to register or unregister additional + * contexts to the Mock HTTP server used for tests. This should usually only be used by suppliers and not directly + * by test. + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface InjectHttpServer { diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectInfinispanServer.java b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectInfinispanServer.java index a68f1633181..3f240933d2d 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectInfinispanServer.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectInfinispanServer.java @@ -7,9 +7,15 @@ import java.lang.annotation.Target; import org.keycloak.testframework.injection.LifeCycle; +/** + * Injects a {@link org.keycloak.testframework.infinispan.InfinispanServer} that starts an external Infinispan server + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface InjectInfinispanServer { + /** + * Controls the lifecycle of the resource + */ LifeCycle lifecycle() default LifeCycle.GLOBAL; } diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectKeycloakUrls.java b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectKeycloakUrls.java index 4b1c6c82ae9..b4e4983725b 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectKeycloakUrls.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectKeycloakUrls.java @@ -5,6 +5,10 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * Injects a {@link org.keycloak.testframework.server.KeycloakUrls} instance that can be used to discover various + * endpoints offered by the Keycloak server + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface InjectKeycloakUrls { diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectRealm.java b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectRealm.java index c0872b04eaa..5a8809eb803 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectRealm.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectRealm.java @@ -13,12 +13,24 @@ import org.keycloak.testframework.realm.RealmConfig; @Target(ElementType.FIELD) public @interface InjectRealm { + /** + * Used to define a custom configuration for the realm + */ Class config() default DefaultRealmConfig.class; + /** + * Loads custom configuration from a json file on the classpath + */ String fromJson() default ""; + /** + * Controls the lifecycle of the resource + */ LifeCycle lifecycle() default LifeCycle.CLASS; + /** + * A ref must be set if a test requires multiple instances + */ String ref() default ""; /** diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectSimpleHttp.java b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectSimpleHttp.java index 5609931acf0..e52dd2baddd 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectSimpleHttp.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectSimpleHttp.java @@ -6,6 +6,9 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * Injects a {@link org.keycloak.http.simple.SimpleHttp} that can be used to do HTTP requests within tests. + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface InjectSimpleHttp { diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectSysLogServer.java b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectSysLogServer.java index 77768813aab..11ab3da895d 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectSysLogServer.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectSysLogServer.java @@ -5,6 +5,10 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * Injects a {@link org.keycloak.testframework.events.SysLogServer} that can be used to register listeners to obtain + * logging events from Keycloak over syslog. + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface InjectSysLogServer { diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectTestDatabase.java b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectTestDatabase.java index 119c4f105d8..18357ecefda 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectTestDatabase.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectTestDatabase.java @@ -13,6 +13,9 @@ import org.keycloak.testframework.injection.LifeCycle; @Target(ElementType.FIELD) public @interface InjectTestDatabase { + /** + * Controls the lifecycle of the resource + */ LifeCycle lifecycle() default LifeCycle.GLOBAL; Class config() default DefaultDatabaseConfig.class; diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectUser.java b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectUser.java index 40a785995d0..1768f3b123a 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectUser.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectUser.java @@ -15,8 +15,14 @@ public @interface InjectUser { Class config() default DefaultUserConfig.class; + /** + * Controls the lifecycle of the resource + */ LifeCycle lifecycle() default LifeCycle.CLASS; + /** + * A ref must be set if a test requires multiple instances + */ String ref() default ""; String realmRef() default ""; diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/KeycloakIntegrationTest.java b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/KeycloakIntegrationTest.java index ea89ecc9ba1..9ae6d149fef 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/KeycloakIntegrationTest.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/KeycloakIntegrationTest.java @@ -11,11 +11,17 @@ import org.keycloak.testframework.server.KeycloakServerConfig; import org.junit.jupiter.api.extension.ExtendWith; +/** + * Enables the test framework for tests + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @ExtendWith({KeycloakIntegrationTestExtension.class}) public @interface KeycloakIntegrationTest { + /** + * Used to define custom configuration for the Keycloak server + */ Class config() default DefaultKeycloakServerConfig.class; } diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/TestCleanup.java b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/TestCleanup.java index b43a556ba05..cf27242b2cf 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/TestCleanup.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/TestCleanup.java @@ -5,6 +5,10 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * Methods annotated with @TestCleanup are invoked by the test framework after all tests methods are + * completed + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface TestCleanup { diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/TestSetup.java b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/TestSetup.java index 0a5f55d69ea..35c5976d95b 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/TestSetup.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/TestSetup.java @@ -5,6 +5,10 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * Methods annotated with @TestSetup are invoked by the test framework after all managed resources are + * injected into the test and before any test methods are executed + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface TestSetup { diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/conditions/DisabledForDatabases.java b/test-framework/core/src/main/java/org/keycloak/testframework/conditions/DisabledForDatabases.java index 2b7f83ddc8e..f28cdf793a4 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/conditions/DisabledForDatabases.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/conditions/DisabledForDatabases.java @@ -8,6 +8,9 @@ import java.lang.annotation.Target; import org.junit.jupiter.api.extension.ExtendWith; +/** + * Tests annotated with @DisabledForDatabases will be skipped for the specified databases + */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/conditions/DisabledForServers.java b/test-framework/core/src/main/java/org/keycloak/testframework/conditions/DisabledForServers.java index cb76ba398dc..5aaf1c80bea 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/conditions/DisabledForServers.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/conditions/DisabledForServers.java @@ -8,6 +8,9 @@ import java.lang.annotation.Target; import org.junit.jupiter.api.extension.ExtendWith; +/** + * Tests annotated with @DisabledForServers will be skipped for the specified server modes + */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/database/DatabaseConfig.java b/test-framework/core/src/main/java/org/keycloak/testframework/database/DatabaseConfig.java index e369bc3dddc..bdc92ab233d 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/database/DatabaseConfig.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/database/DatabaseConfig.java @@ -1,5 +1,10 @@ package org.keycloak.testframework.database; +/** + * Declarative configuration for the managed database + */ public interface DatabaseConfig { + DatabaseConfigBuilder configure(DatabaseConfigBuilder database); + } diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/database/DatabaseConfigBuilder.java b/test-framework/core/src/main/java/org/keycloak/testframework/database/DatabaseConfigBuilder.java index 64d7b3a0868..9c2a2a7a8ab 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/database/DatabaseConfigBuilder.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/database/DatabaseConfigBuilder.java @@ -13,16 +13,34 @@ public class DatabaseConfigBuilder { return new DatabaseConfigBuilder(rep); } + /** + * Configure a script to initialise the database on startup + * + * @param initScript path to init script on the classpath + * @return + */ public DatabaseConfigBuilder initScript(String initScript) { rep.setInitScript(initScript); return this; } + /** + * Set the database name to use, defaults to keycloak + * + * @param database name of the database to use + * @return + */ public DatabaseConfigBuilder database(String database) { rep.setDatabase(database); return this; } + /** + * Prevent re-use of the database + * + * @param preventReuse set to true to prevent re-use of the database + * @return + */ public DatabaseConfigBuilder preventReuse(boolean preventReuse) { rep.setPreventReuse(preventReuse); return this; diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/events/AbstractEvents.java b/test-framework/core/src/main/java/org/keycloak/testframework/events/AbstractEvents.java index e4a56c459ef..2d741ab4af4 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/events/AbstractEvents.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/events/AbstractEvents.java @@ -29,6 +29,12 @@ public abstract class AbstractEvents { this.realm = realm; } + /** + * Returns the oldest event within the current window. The window is reset for each started test, which means + * any events triggered by previous tests are ignored + * + * @return the oldest event with the current window + */ public R poll() { long currentTimeOffset = getCurrentTimeOffset(); if (timeOffset != currentTimeOffset) { @@ -69,14 +75,25 @@ public abstract class AbstractEvents { return events.poll(); } + /** + * Skip the next event + */ public void skip() { skip(1); } + /** + * Skip the specified number of events + * + * @param events number of events to skip + */ public void skip(int events) { skip += events; } + /** + * Skip all current events + */ public void skipAll() { try { Thread.sleep(1); // Wait 1 ms to make sure time passes @@ -88,6 +105,9 @@ public abstract class AbstractEvents { events.clear(); } + /** + * Clear all events locally and remotely + */ public void clear() { events.clear(); clearServerEvents(); @@ -109,7 +129,7 @@ public abstract class AbstractEvents { protected abstract Logger getLogger(); - public long getCurrentTime() { + protected long getCurrentTime() { return System.currentTimeMillis(); } diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/events/AdminEventAssertion.java b/test-framework/core/src/main/java/org/keycloak/testframework/events/AdminEventAssertion.java index f2d6293f791..16767bdf960 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/events/AdminEventAssertion.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/events/AdminEventAssertion.java @@ -31,6 +31,12 @@ public class AdminEventAssertion { this.expectSuccess = expectSuccess; } + /** + * Assert an expected successfull event + * + * @param event the event to assert + * @return + */ public static AdminEventAssertion assertSuccess(AdminEventRepresentation event) { Assertions.assertFalse(event.getOperationType().endsWith("_ERROR"), "Expected successful event"); return new AdminEventAssertion(event, true) @@ -38,6 +44,12 @@ public class AdminEventAssertion { .assertValidOperationType(); } + /** + * Assert an expected error event + * + * @param event the event to assert + * @return + */ public static AdminEventAssertion assertError(AdminEventRepresentation event) { Assertions.assertTrue(event.getOperationType().endsWith("_ERROR"), "Expected error event"); return new AdminEventAssertion(event, false) @@ -45,6 +57,17 @@ public class AdminEventAssertion { .assertValidOperationType(); } + /** + * Assert an expected successfull event, with the additional expected parameters. This method should be avoided, + * use method chaining instead. + * + * @param event + * @param operationType + * @param resourcePath + * @param representation + * @param resourceType + * @return + */ public static AdminEventAssertion assertEvent(AdminEventRepresentation event, OperationType operationType, String resourcePath, Object representation, ResourceType resourceType) { return assertSuccess(event) .operationType(operationType) @@ -53,6 +76,16 @@ public class AdminEventAssertion { .resourceType(resourceType); } + /** + * Assert an expected successfull event, with the additional expected parameters. This method should be avoided, + * use method chaining instead. + * + * @param event + * @param operationType + * @param resourcePath + * @param resourceType + * @return + */ public static AdminEventAssertion assertEvent(AdminEventRepresentation event, OperationType operationType, String resourcePath, ResourceType resourceType) { return assertSuccess(event) .operationType(operationType) @@ -60,11 +93,24 @@ public class AdminEventAssertion { .resourceType(resourceType); } + /** + * Assert the operation type of the event + * @param operationType the expected operation type + * @return + */ public AdminEventAssertion operationType(OperationType operationType) { Assertions.assertEquals(operationType.name(), getOperationType()); return this; } + /** + * Assert the authentication details for the event + * + * @param expectedRealmId the expected authentication realmId + * @param expectedClientId the expected authentication clientId + * @param expectedUserId the expected authentication userId + * @return + */ public AdminEventAssertion auth(String expectedRealmId, String expectedClientId, String expectedUserId) { AuthDetailsRepresentation authDetails = event.getAuthDetails(); Assertions.assertEquals(expectedRealmId, authDetails.getRealmId()); @@ -73,16 +119,34 @@ public class AdminEventAssertion { return this; } + /** + * Assert the type of resource for the event + * + * @param expectedResourceType the expected resource type + * @return + */ public AdminEventAssertion resourceType(ResourceType expectedResourceType) { Assertions.assertEquals(expectedResourceType.name(), event.getResourceType()); return this; } + /** + * Assert the resource path for the event + * + * @param expectedResourcePath the expected resource path + * @return + */ public AdminEventAssertion resourcePath(String... expectedResourcePath) { Assertions.assertEquals(String.join("/", expectedResourcePath), event.getResourcePath()); return this; } + /** + * Assert the representation attached to the event + * + * @param expectedRep the expected representation + * @return + */ public AdminEventAssertion representation(Object expectedRep) { String actualRepresentation = event.getRepresentation(); if (expectedRep == null) { diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/events/AdminEvents.java b/test-framework/core/src/main/java/org/keycloak/testframework/events/AdminEvents.java index ee03f3615fc..3b59a1b8dcf 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/events/AdminEvents.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/events/AdminEvents.java @@ -7,6 +7,9 @@ import org.keycloak.testframework.realm.ManagedRealm; import org.jboss.logging.Logger; +/** + * Poll admin events from the Keycloak server + */ public class AdminEvents extends AbstractEvents { private static final Logger LOGGER = Logger.getLogger(AdminEvents.class); diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/events/EventAssertion.java b/test-framework/core/src/main/java/org/keycloak/testframework/events/EventAssertion.java index e62068bee26..21e34262726 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/events/EventAssertion.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/events/EventAssertion.java @@ -8,6 +8,9 @@ import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.jupiter.api.Assertions; +/** + * Helper to assert login events + */ public class EventAssertion { private final EventRepresentation event; @@ -18,51 +21,109 @@ public class EventAssertion { this.event = event; } + /** + * Assert an expected successfull event + * + * @param event the event to assert + * @return + */ public static EventAssertion assertSuccess(EventRepresentation event) { Assertions.assertFalse(event.getType().endsWith("_ERROR"), "Expected successful event"); return new EventAssertion(event); } + /** + * Assert an expected error event + * + * @param event the event to assert + * @return + */ public static EventAssertion assertError(EventRepresentation event) { Assertions.assertTrue(event.getType().endsWith("_ERROR"), "Expected error event"); return new EventAssertion(event); } + /** + * Assert the error message + * + * @param error the expected error message + * @return + */ public EventAssertion error(String error) { Assertions.assertEquals(error, event.getError()); return this; } + /** + * Assert the type of the event + * + * @param type the expected type of the event + * @return + */ public EventAssertion type(EventType type) { Assertions.assertEquals(type, EventType.valueOf(event.getType())); return this; } + /** + * Assert the event has a sessionId set + * + * @return + */ public EventAssertion hasSessionId() { MatcherAssert.assertThat(event.getSessionId(), EventMatchers.isSessionId()); return this; } + /** + * Assert the event has the code_id details set + * @return + */ public EventAssertion isCodeId() { MatcherAssert.assertThat(event.getDetails().get(Details.CODE_ID), EventMatchers.isCodeId()); return this; } + /** + * Assert the clientId for the event + * + * @param clientId the expected clientId + * @return + */ public EventAssertion clientId(String clientId) { Assertions.assertEquals(clientId, event.getClientId()); return this; } + /** + * Assert the sessionId for the event + * + * @param sessionId the expected sessionId + * @return + */ public EventAssertion sessionId(String sessionId) { Assertions.assertEquals(sessionId, event.getSessionId()); return this; } + /** + * Assert the userId (sub) of the event + * + * @param userId the expected userId + * @return + */ public EventAssertion userId(String userId) { Assertions.assertEquals(userId, event.getUserId()); return this; } + /** + * Assert the event has an entry in the details map with the specified key and value + * + * @param key the expected details key + * @param value the expected details value + * @return + */ public EventAssertion details(String key, String value) { if (value != null) { MatcherAssert.assertThat(event.getDetails(), Matchers.hasEntry(key, value)); @@ -72,6 +133,12 @@ public class EventAssertion { return this; } + /** + * Assert the event details map does not contain the specified keys + * + * @param keys the list of keys that are not expected in the details map + * @return + */ public EventAssertion withoutDetails(String... keys) { for (String key : keys) { MatcherAssert.assertThat(event.getDetails(), Matchers.not(Matchers.hasKey(key))); diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/events/EventMatchers.java b/test-framework/core/src/main/java/org/keycloak/testframework/events/EventMatchers.java index 4fb696505aa..13498d86357 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/events/EventMatchers.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/events/EventMatchers.java @@ -8,23 +8,40 @@ import org.hamcrest.Matcher; import org.hamcrest.Matchers; import org.hamcrest.TypeSafeMatcher; +/** + * Matchers to assert event properties + */ public class EventMatchers { + /** + * Check if value is a UUID + * @return + */ public static Matcher isUUID() { return new UUIDMatcher(); } + /** + * Check if value is a code_id + * + * @return + */ public static Matcher isCodeId() { // Make the tests pass with the old and the new encoding of code IDs return Matchers.anyOf(isBase64WithAtLeast128Bits(), isUUID()); } + /** + * Check if value is a session_id + * + * @return + */ public static Matcher isSessionId() { // Make the tests pass with the old and the new encoding of sessions return Matchers.anyOf(isBase64WithAtLeast128Bits(), isUUID()); } - public static Matcher isBase64WithAtLeast128Bits() { + private static Matcher isBase64WithAtLeast128Bits() { return new TypeSafeMatcher<>() { private static final Pattern BASE64 = Pattern.compile("[-A-Za-z0-9+/_]*"); @@ -43,7 +60,7 @@ public class EventMatchers { private EventMatchers() { } - public static class UUIDMatcher extends TypeSafeMatcher { + private static class UUIDMatcher extends TypeSafeMatcher { @Override protected boolean matchesSafely(String item) { diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/https/CertificatesConfig.java b/test-framework/core/src/main/java/org/keycloak/testframework/https/CertificatesConfig.java index 666b3529b19..17c9caa67ce 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/https/CertificatesConfig.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/https/CertificatesConfig.java @@ -1,5 +1,8 @@ package org.keycloak.testframework.https; +/** + * Declarative configuration for managed certificates + */ public interface CertificatesConfig { CertificatesConfigBuilder configure(CertificatesConfigBuilder config); diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/https/CertificatesConfigBuilder.java b/test-framework/core/src/main/java/org/keycloak/testframework/https/CertificatesConfigBuilder.java index 8fbec80ed6d..b66a71cecfb 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/https/CertificatesConfigBuilder.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/https/CertificatesConfigBuilder.java @@ -11,6 +11,12 @@ public class CertificatesConfigBuilder { public CertificatesConfigBuilder() { } + /** + * Use the specified keystore format + * + * @param keystoreFormat the keystore format to use + * @return + */ public CertificatesConfigBuilder keystoreFormat(KeystoreUtil.KeystoreFormat keystoreFormat) { this.keystoreFormat = keystoreFormat; return this; @@ -20,6 +26,12 @@ public class CertificatesConfigBuilder { return this.keystoreFormat; } + /** + * Enable TLS + * + * @param tlsEnabled true if tls should be enabled + * @return + */ public CertificatesConfigBuilder tlsEnabled(boolean tlsEnabled) { this.tlsEnabled = tlsEnabled; return this; @@ -29,6 +41,12 @@ public class CertificatesConfigBuilder { return tlsEnabled || mTlsEnabled; } + /** + * Enable mTLS authentication between Keycloak and clients + * + * @param mTlsEnabled true if mTLS should be enabled + * @return + */ public CertificatesConfigBuilder mTlsEnabled(boolean mTlsEnabled) { this.mTlsEnabled = mTlsEnabled; return this; diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/https/ManagedCertificates.java b/test-framework/core/src/main/java/org/keycloak/testframework/https/ManagedCertificates.java index 064dddc52ac..f659ec4762f 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/https/ManagedCertificates.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/https/ManagedCertificates.java @@ -22,6 +22,9 @@ import org.keycloak.crypto.def.DefaultCryptoProvider; import org.apache.http.conn.ssl.TrustAllStrategy; import org.apache.http.ssl.SSLContextBuilder; +/** + * Utilities for Keycloak server and clients to obtain keystore and truststores with the managed certificates + */ public class ManagedCertificates { private final static Path KEYSTORES_DIR = Path.of(System.getProperty("java.io.tmpdir")); @@ -72,35 +75,68 @@ public class ManagedCertificates { clientSslContext = tlsEnabled ? createClientSSLContext() : null; } + /** + * The path of the generated Keycloak server keystore containing the private certificate for the Keycloak server + * + * @return path to keystore + */ public String getServerKeyStorePath() { return tlsEnabled ? serverKeystorePath.toString() : null; } + /** + * The password used for the keystore + * + * @return keystore password + */ public String getServerKeyStorePassword() { return tlsEnabled ? STORE_PASSWORD : null; } + /** + * The path of the generated Keycloak server truststore containing public certificates for clients + * @return + */ public String getServerTrustStorePath() { return mTlsEnabled ? serverTruststorePath.toString() : null; } + /** + * The password used for the truststore + * + * @return truststore password + */ public String getServerTrustStorePassword() { return mTlsEnabled ? STORE_PASSWORD : null; } + /** + * Return the SSL context configured with the client truststore + * + * @return + */ public SSLContext getClientSSLContext() { return clientSslContext; } + /** + * Returns true if TLS is enabled + * + * @return true if TLS is enabled + */ public boolean isTlsEnabled() { return tlsEnabled; } + /** + * Returns true if mTLS is enabled + * + * @return true if mTLS is enabled + */ public boolean isMTlsEnabled() { return mTlsEnabled; } - private SSLContext createClientSSLContext() { try { SSLContextBuilder sslContextBuilder = SSLContextBuilder.create() diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/injection/ManagedTestResource.java b/test-framework/core/src/main/java/org/keycloak/testframework/injection/ManagedTestResource.java index 75048e1c359..f2b83f1ae85 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/injection/ManagedTestResource.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/injection/ManagedTestResource.java @@ -10,6 +10,10 @@ public abstract class ManagedTestResource { return dirty; } + /** + * Marking the resource as dirty will result in the test framework re-creating the resource after the test + * has executed + */ public void dirty() { this.dirty = true; } diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/realm/ClientConfig.java b/test-framework/core/src/main/java/org/keycloak/testframework/realm/ClientConfig.java index 9cfcc1c6522..d1e252ce08e 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/realm/ClientConfig.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/realm/ClientConfig.java @@ -1,5 +1,8 @@ package org.keycloak.testframework.realm; +/** + * Declarative configuration for managed clients + */ public interface ClientConfig { ClientConfigBuilder configure(ClientConfigBuilder client); diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/realm/ManagedClient.java b/test-framework/core/src/main/java/org/keycloak/testframework/realm/ManagedClient.java index 353d0786c68..ed3bf82e38a 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/realm/ManagedClient.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/realm/ManagedClient.java @@ -4,6 +4,9 @@ import org.keycloak.admin.client.resource.ClientResource; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.testframework.injection.ManagedTestResource; +/** + * Utilities to work with managed clients + */ public class ManagedClient extends ManagedTestResource { private final ClientRepresentation createdRepresentation; @@ -16,22 +19,46 @@ public class ManagedClient extends ManagedTestResource { this.clientResource = clientResource; } + /** + * The UUID of the client + * @return client UUID + */ public String getId() { return createdRepresentation.getId(); } + /** + * The clientId of the client + * @return client clientId + */ public String getClientId() { return createdRepresentation.getClientId(); } + /** + * The client secret if set + * @return client secret + */ public String getSecret() { return createdRepresentation.getSecret(); } + /** + * Admin client resource for the client to view or update the configuration of the client. Updates should in general + * not be done directly through the client resource as it will leave the client in a unexpected state for sub-sequent + * tests + * + * @return client resource + */ public ClientResource admin() { return clientResource; } + /** + * Update the client within a test with automatic reset to the original configuration after the test has completed + * + * @param updates the update to the client + */ public void updateWithCleanup(ManagedClient.ClientUpdate... updates) { ClientRepresentation rep = admin().toRepresentation(); diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/realm/ManagedClientCleanup.java b/test-framework/core/src/main/java/org/keycloak/testframework/realm/ManagedClientCleanup.java index 42b67479931..8d3d6ec400f 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/realm/ManagedClientCleanup.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/realm/ManagedClientCleanup.java @@ -10,6 +10,12 @@ public class ManagedClientCleanup { private final List cleanupTasks = new LinkedList<>(); + /** + * Add a cleanup to be done for the client after the test is completed + * + * @param clientCleanup the required cleanup + * @return + */ public ManagedClientCleanup add(ClientCleanup clientCleanup) { this.cleanupTasks.add(clientCleanup); return this; diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/realm/ManagedRealm.java b/test-framework/core/src/main/java/org/keycloak/testframework/realm/ManagedRealm.java index 09adfe19a2e..c41a475d795 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/realm/ManagedRealm.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/realm/ManagedRealm.java @@ -14,6 +14,9 @@ import org.keycloak.testframework.util.ApiUtil; import org.junit.jupiter.api.Assertions; +/** + * Utilities to work with managed realms + */ public class ManagedRealm extends ManagedTestResource { private final String baseUrl; @@ -29,10 +32,20 @@ public class ManagedRealm extends ManagedTestResource { this.realmResource = realmResource; } + /** + * The base URL of the realm (for example http://localhost:8080/realms/myrealm) + * + * @return the realm base URL + */ public String getBaseUrl() { return baseUrl; } + /** + * The UUID of the realm + * + * @return realm UUID + */ public String getId() { if (realmId == null && createdRepresentation.getId() != null) { realmId = createdRepresentation.getId(); @@ -42,18 +55,40 @@ public class ManagedRealm extends ManagedTestResource { return realmId; } + /** + * The name of the realm + * + * @return realm name + */ public String getName() { return createdRepresentation.getRealm(); } + /** + * Admin realm resource for the realm to view or update the configuration of the realm. Updates should in general + * not be done directly through the realm resource as it will leave the realm in an unexpected state for sub-sequent + * tests + * + * @return realm resource + */ public RealmResource admin() { return realmResource; } + /** + * The representation used to create the realm + * + * @return realm representation + */ public RealmRepresentation getCreatedRepresentation() { return createdRepresentation; } + /** + * Update the realm within a test with automatic reset to the original configuration after the test has completed + * + * @param updates the updates to the realm + */ public void updateWithCleanup(RealmUpdate... updates) { RealmRepresentation rep = admin().toRepresentation(); cleanup().resetToOriginalRepresentation(rep); @@ -66,12 +101,23 @@ public class ManagedRealm extends ManagedTestResource { admin().update(configBuilder.build()); } + /** + * Add a user to the realm, which is automatically removed once the test is completed + * + * @param user the user to add + */ public void addUser(UserConfigBuilder user) { UserRepresentation rep = user.build(); String id = ApiUtil.getCreatedId(realmResource.users().create(rep)); cleanup().add(r -> r.users().get(id).remove()); } + /** + * Update a user within the realm, which is automatically reset once the test is completed + * + * @param username the username of the user to update + * @param update the update to perform on the user + */ public void updateUser(String username, UserConfigBuilder.UserUpdate update) { List result = realmResource.users().search(username); Assertions.assertEquals(1, result.size()); @@ -84,6 +130,12 @@ public class ManagedRealm extends ManagedTestResource { cleanup().add(r -> r.users().get(original.getId()).update(original)); } + /** + * Update an identity provider within the realm, which is automatically reset once the test is completed + * + * @param alias the alias of the identity provider to update + * @param update the update to perform on the identity provider + */ public void updateIdentityProvider(String alias, IdentityProviderUpdate update) { IdentityProviderResource resource = realmResource.identityProviders().get(alias); @@ -95,6 +147,12 @@ public class ManagedRealm extends ManagedTestResource { cleanup().add(r -> r.identityProviders().get(alias).update(original)); } + /** + * Update a component within the realm, which is automatically reset once the test is completed + * + * @param id the id of the component to update + * @param update the update to perform on the component + */ public void updateComponent(String id, ComponentUpdate update) { ComponentResource componentResource = realmResource.components().component(id); diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/realm/ManagedRealmCleanup.java b/test-framework/core/src/main/java/org/keycloak/testframework/realm/ManagedRealmCleanup.java index 54c79109f13..3a126f048e8 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/realm/ManagedRealmCleanup.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/realm/ManagedRealmCleanup.java @@ -10,6 +10,12 @@ public class ManagedRealmCleanup { private final List cleanupTasks = new LinkedList<>(); + /** + * Add a cleanup task to perform on the realm once the test has completed + * + * @param realmCleanup the cleanup to perform on the realm + * @return + */ public ManagedRealmCleanup add(RealmCleanup realmCleanup) { this.cleanupTasks.add(realmCleanup); return this; diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/realm/RealmConfig.java b/test-framework/core/src/main/java/org/keycloak/testframework/realm/RealmConfig.java index 94525c868fa..63044dd7252 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/realm/RealmConfig.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/realm/RealmConfig.java @@ -1,5 +1,8 @@ package org.keycloak.testframework.realm; +/** + * Declarative configuration for managed realms + */ public interface RealmConfig { RealmConfigBuilder configure(RealmConfigBuilder realm); diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/realm/UserConfig.java b/test-framework/core/src/main/java/org/keycloak/testframework/realm/UserConfig.java index 62acfceed13..7f0016c3511 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/realm/UserConfig.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/realm/UserConfig.java @@ -1,5 +1,8 @@ package org.keycloak.testframework.realm; +/** + * Declarative configuration for managed users + */ public interface UserConfig { UserConfigBuilder configure(UserConfigBuilder user); diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/server/KeycloakServerConfig.java b/test-framework/core/src/main/java/org/keycloak/testframework/server/KeycloakServerConfig.java index 4caf343c996..69114729d73 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/server/KeycloakServerConfig.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/server/KeycloakServerConfig.java @@ -1,5 +1,8 @@ package org.keycloak.testframework.server; +/** + * Declarative configuration for the managed Keycloak server + */ public interface KeycloakServerConfig { KeycloakServerConfigBuilder configure(KeycloakServerConfigBuilder config); diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/server/KeycloakServerConfigBuilder.java b/test-framework/core/src/main/java/org/keycloak/testframework/server/KeycloakServerConfigBuilder.java index 53a085c92ef..d22ad46964d 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/server/KeycloakServerConfigBuilder.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/server/KeycloakServerConfigBuilder.java @@ -38,21 +38,48 @@ public class KeycloakServerConfigBuilder { return new KeycloakServerConfigBuilder("start-dev"); } + /** + * Set the client id and secret to use for bootstrapping configuration + * + * @param clientId the client id + * @param clientSecret the client secret + * @return + */ public KeycloakServerConfigBuilder bootstrapAdminClient(String clientId, String clientSecret) { return option("bootstrap-admin-client-id", clientId) .option("bootstrap-admin-client-secret", clientSecret); } + /** + * Set the username and password to use for bootstrapping configuration + * + * @param username the username + * @param password the secret + * @return + */ public KeycloakServerConfigBuilder bootstrapAdminUser(String username, String password) { return option("bootstrap-admin-username", username) .option("bootstrap-admin-password", password); } + /** + * Configure if local caches or clustered caches should be used. Using local caches results in a faster startup + * time + * + * @param cacheType + * @return + */ public KeycloakServerConfigBuilder cache(CacheType cacheType) { this.cacheType = cacheType; return this; } + /** + * Connect to a managed external Infinispan server + * + * @param enabled + * @return + */ public KeycloakServerConfigBuilder externalInfinispanEnabled(boolean enabled) { if (enabled) { this.externalInfinispan = true; @@ -68,35 +95,82 @@ public class KeycloakServerConfigBuilder { return this.externalInfinispan; } + /** + * Configure logging + * + * @return + */ public LogBuilder log() { return log; } + /** + * Enable the specified features. In most cases used to enable features that are not enabled by default + * + * @param features the features to enable + * @return + */ public KeycloakServerConfigBuilder features(Profile.Feature... features) { this.features.addAll(toFeatureStrings(features)); return this; } + /** + * Disable the specified features. In most cases used to disable features that are enabled by default + * + * @param features the features to disable + * @return + */ public KeycloakServerConfigBuilder featuresDisabled(Profile.Feature... features) { this.featuresDisabled.addAll(toFeatureStrings(features)); return this; } + /** + * Set multiple CLI options + * + * @param options + * @return + */ public KeycloakServerConfigBuilder options(Map options) { this.options.putAll(options); return this; } + /** + * Set the specified CLI option + * + * @param key the key of the option + * @param value the value of the option + * @return + */ public KeycloakServerConfigBuilder option(String key, String value) { options.put(key, value); return this; } + /** + * Set an SPI configuration option + * + * @param spi the name of the SPI + * @param provider the name of the provider + * @param key the name of the option + * @param value the value to set + * @return + */ public KeycloakServerConfigBuilder spiOption(String spi, String provider, String key, String value) { options.put(String.format(SPI_OPTION, spi, provider, key), value); return this; } + /** + * Deploy a dependency to the server by specifying the Maven groupId and artifactId. The version is resolved from + * the project pom files + * + * @param groupId the Maven groupId of the dependency + * @param artifactId the Maven artifactId of the dependency + * @return + */ public KeycloakServerConfigBuilder dependency(String groupId, String artifactId) { dependencies.add(new DependencyBuilder().setGroupId(groupId).setArtifactId(artifactId).build()); return this; diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/server/KeycloakUrls.java b/test-framework/core/src/main/java/org/keycloak/testframework/server/KeycloakUrls.java index 5825de1b921..231036a5c2f 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/server/KeycloakUrls.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/server/KeycloakUrls.java @@ -16,38 +16,84 @@ public class KeycloakUrls { this.managementBaseUrl = managementBaseUrl; } + /** + * The base string representation of the URL of the Keycloak server (for example http://localhost:8080) + * + * @return the server base URL as a string + */ public String getBase() { return baseUrl; } + + /** + * The URL of the Keycloak server (for example http://localhost:8080) + * + * @return the server base URL + */ public URL getBaseUrl() { return toUrl(getBase()); } + /** + * The string representation of the base URL of the master realm + * + * @return + */ public String getMasterRealm() { return baseUrl + "/realms/master"; } + /** + * The URL of the master realm + * + * @return master realm URL + */ public URL getMasterRealmUrl() { return toUrl(getMasterRealm()); } + /** + * The string representation of the URL of Admin endpoints + * + * @return admin URL as a string + */ public String getAdmin() { return baseUrl + "/admin"; } + /** + * The URL of Admin endpoints + * + * @return admin URL + */ public URL getAdminUrl() { return toUrl(getAdmin()); } + /** + * Builder to resolve paths from the Keycloak server base URL + * + * @return base URL builder + */ public KeycloakUriBuilder getBaseBuilder() { return toBuilder(getBase()); } + /** + * Builder to resolve paths from the admin URL + * + * @return admin URL builder + */ public KeycloakUriBuilder getAdminBuilder() { return toBuilder(getAdmin()); } + /** + * String representation of the URL of the metrics endpoint + * + * @return metrics endpoint + */ public String getMetric() { return managementBaseUrl + "/metrics"; } diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/util/ApiUtil.java b/test-framework/core/src/main/java/org/keycloak/testframework/util/ApiUtil.java index 2d9f26a7e7e..f924f32cbc5 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/util/ApiUtil.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/util/ApiUtil.java @@ -4,8 +4,20 @@ import jakarta.ws.rs.core.Response; import org.junit.jupiter.api.Assertions; +/** + * Utilities for the Keycloak Java Admin client + */ public class ApiUtil { + /** + * Several POST endpoints in Keycloak Admin API does not return the created resource in the response; but rather + * returns a location header instead, making it harder to get the generated ID of a newly created resource. This + * method parses the location header and returns the ID of the created resource, as well as closing the JAX-RS + * response. + * + * @param response the response from a POST request, for example creating a new user in a realm + * @return the ID of the created resource, for example the UUID of a new user + */ public static String getCreatedId(Response response) { try (response) { Assertions.assertEquals(201, response.getStatus()); diff --git a/test-framework/email-server/src/main/java/org/keycloak/testframework/mail/MailServer.java b/test-framework/email-server/src/main/java/org/keycloak/testframework/mail/MailServer.java index 3fd10a74141..0051f2d5551 100644 --- a/test-framework/email-server/src/main/java/org/keycloak/testframework/mail/MailServer.java +++ b/test-framework/email-server/src/main/java/org/keycloak/testframework/mail/MailServer.java @@ -10,6 +10,10 @@ import com.icegreen.greenmail.user.TokenValidator; import com.icegreen.greenmail.util.GreenMail; import com.icegreen.greenmail.util.ServerSetup; +/** + * Retrieve emails sent by the Keycloak server. Received emails are reset when a test is executed, which means + * only emails sent during a test was executed are returned. + */ public class MailServer extends ManagedTestResource { private final GreenMail greenMail; @@ -36,19 +40,41 @@ public class MailServer extends ManagedTestResource { ((com.icegreen.greenmail.user.UserImpl)user).setTokenValidator(validator); } + /** + * Retrieve all received emails + * + * @return list of received emails + */ public MimeMessage[] getReceivedMessages() { return greenMail.getReceivedMessages(); } + /** + * Retrieve the last received email + * + * @return the last received email + */ public MimeMessage getLastReceivedMessage() { MimeMessage[] receivedMessages = greenMail.getReceivedMessages(); return receivedMessages != null && receivedMessages.length > 0 ? receivedMessages[receivedMessages.length - 1] : null; } + /** + * Wait for the specified time to receive the specified number of emails + * + * @param timeout the time to wait for emails to be received + * @param emailCount the number of emails to wait for + * @return + */ public boolean waitForIncomingEmail(long timeout, int emailCount) { return greenMail.waitForIncomingEmail(timeout, emailCount); } + /** + * + * @param emailCount + * @return + */ public boolean waitForIncomingEmail(int emailCount) { return greenMail.waitForIncomingEmail(emailCount); } diff --git a/test-framework/email-server/src/main/java/org/keycloak/testframework/mail/annotations/InjectMailServer.java b/test-framework/email-server/src/main/java/org/keycloak/testframework/mail/annotations/InjectMailServer.java index d1d5a2a4c48..edce6bc86f0 100644 --- a/test-framework/email-server/src/main/java/org/keycloak/testframework/mail/annotations/InjectMailServer.java +++ b/test-framework/email-server/src/main/java/org/keycloak/testframework/mail/annotations/InjectMailServer.java @@ -5,6 +5,9 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * Injects a {@link org.keycloak.testframework.mail.MailServer} to receive emails sent by the Keycloak server + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface InjectMailServer { } diff --git a/test-framework/oauth/src/main/java/org/keycloak/testframework/oauth/OAuthClient.java b/test-framework/oauth/src/main/java/org/keycloak/testframework/oauth/OAuthClient.java index 7004130800b..2ee1c90443d 100644 --- a/test-framework/oauth/src/main/java/org/keycloak/testframework/oauth/OAuthClient.java +++ b/test-framework/oauth/src/main/java/org/keycloak/testframework/oauth/OAuthClient.java @@ -11,6 +11,9 @@ import org.keycloak.testsuite.util.oauth.OAuthClientConfig; import org.apache.http.impl.client.CloseableHttpClient; import org.openqa.selenium.support.PageFactory; +/** + * OAuth client to send OAuth request and handle callbacks + */ public class OAuthClient extends AbstractOAuthClient { private final ManagedWebDriver managedWebDriver; diff --git a/test-framework/oauth/src/main/java/org/keycloak/testframework/oauth/OAuthIdentityProvider.java b/test-framework/oauth/src/main/java/org/keycloak/testframework/oauth/OAuthIdentityProvider.java index d59c0b82e23..ed8f99590ee 100644 --- a/test-framework/oauth/src/main/java/org/keycloak/testframework/oauth/OAuthIdentityProvider.java +++ b/test-framework/oauth/src/main/java/org/keycloak/testframework/oauth/OAuthIdentityProvider.java @@ -27,6 +27,9 @@ import com.sun.net.httpserver.HttpServer; import static org.keycloak.common.crypto.CryptoConstants.EC_KEY_SECP256R1; +/** + * Mock identity provider that can be used to test various brokering flows + */ public class OAuthIdentityProvider { private final HttpServer httpServer; diff --git a/test-framework/oauth/src/main/java/org/keycloak/testframework/oauth/TestApp.java b/test-framework/oauth/src/main/java/org/keycloak/testframework/oauth/TestApp.java index 01f04247567..532e0f50d0b 100644 --- a/test-framework/oauth/src/main/java/org/keycloak/testframework/oauth/TestApp.java +++ b/test-framework/oauth/src/main/java/org/keycloak/testframework/oauth/TestApp.java @@ -2,6 +2,9 @@ package org.keycloak.testframework.oauth; import com.sun.net.httpserver.HttpServer; +/** + * Mock OAuth client exposed on an HTTP server so Keycloak can send callbacks to the client + */ public class TestApp { public static final String OAUTH_CALLBACK_PATH = "/callback/oauth"; diff --git a/test-framework/oauth/src/main/java/org/keycloak/testframework/oauth/annotations/InjectOAuthClient.java b/test-framework/oauth/src/main/java/org/keycloak/testframework/oauth/annotations/InjectOAuthClient.java index 2c3b417b8a3..cb989bf4aee 100644 --- a/test-framework/oauth/src/main/java/org/keycloak/testframework/oauth/annotations/InjectOAuthClient.java +++ b/test-framework/oauth/src/main/java/org/keycloak/testframework/oauth/annotations/InjectOAuthClient.java @@ -9,6 +9,9 @@ import org.keycloak.testframework.injection.LifeCycle; import org.keycloak.testframework.oauth.DefaultOAuthClientConfiguration; import org.keycloak.testframework.realm.ClientConfig; +/** + * Injects a {@link org.keycloak.testframework.oauth.OAuthClient} that can be used to send OAuth request to Keycloak + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface InjectOAuthClient { diff --git a/test-framework/oauth/src/main/java/org/keycloak/testframework/oauth/annotations/InjectOAuthIdentityProvider.java b/test-framework/oauth/src/main/java/org/keycloak/testframework/oauth/annotations/InjectOAuthIdentityProvider.java index cdf4c1b013b..c451b928559 100644 --- a/test-framework/oauth/src/main/java/org/keycloak/testframework/oauth/annotations/InjectOAuthIdentityProvider.java +++ b/test-framework/oauth/src/main/java/org/keycloak/testframework/oauth/annotations/InjectOAuthIdentityProvider.java @@ -9,6 +9,10 @@ import org.keycloak.testframework.injection.LifeCycle; import org.keycloak.testframework.oauth.DefaultOAuthIdentityProviderConfig; import org.keycloak.testframework.oauth.OAuthIdentityProviderConfig; +/** + * Injects a {@link org.keycloak.testframework.oauth.OAuthIdentityProvider} that can be used to mock an identity + * provider + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface InjectOAuthIdentityProvider { diff --git a/test-framework/oauth/src/main/java/org/keycloak/testframework/oauth/annotations/InjectTestApp.java b/test-framework/oauth/src/main/java/org/keycloak/testframework/oauth/annotations/InjectTestApp.java index 0499859d222..31d7722d775 100644 --- a/test-framework/oauth/src/main/java/org/keycloak/testframework/oauth/annotations/InjectTestApp.java +++ b/test-framework/oauth/src/main/java/org/keycloak/testframework/oauth/annotations/InjectTestApp.java @@ -5,6 +5,9 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * Injects a {@link org.keycloak.testframework.oauth.TestApp} that can be used to mock an OAuth client + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface InjectTestApp { diff --git a/test-framework/remote/src/main/java/org/keycloak/testframework/remote/runonserver/InjectRunOnServer.java b/test-framework/remote/src/main/java/org/keycloak/testframework/remote/runonserver/InjectRunOnServer.java index 72ba8a320a3..6bbda5085fb 100644 --- a/test-framework/remote/src/main/java/org/keycloak/testframework/remote/runonserver/InjectRunOnServer.java +++ b/test-framework/remote/src/main/java/org/keycloak/testframework/remote/runonserver/InjectRunOnServer.java @@ -7,6 +7,10 @@ import java.lang.annotation.Target; import org.keycloak.testframework.injection.LifeCycle; +/** + * Injects a {@link RunOnServerClient} to execute code within the Keycloak server. Classes are serialized and sent + * to the Keycloak server when needed + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface InjectRunOnServer { diff --git a/test-framework/remote/src/main/java/org/keycloak/testframework/remote/runonserver/RunOnServerClient.java b/test-framework/remote/src/main/java/org/keycloak/testframework/remote/runonserver/RunOnServerClient.java index ef0e99a04ed..b76ec1eaafb 100644 --- a/test-framework/remote/src/main/java/org/keycloak/testframework/remote/runonserver/RunOnServerClient.java +++ b/test-framework/remote/src/main/java/org/keycloak/testframework/remote/runonserver/RunOnServerClient.java @@ -31,10 +31,26 @@ public class RunOnServerClient { this.executionId = executionId; } + /** + * Retrieve some value from the Keycloak server using the specified wrapper + * + * @param wrapper the wrapper containing the code and return type + * @return the value + * @param the return type + * @throws RunOnServerException + */ public T fetch(FetchOnServerWrapper wrapper) throws RunOnServerException { return fetch(wrapper.getRunOnServer(), wrapper.getResultClass()); } + /** + * Retrieve some value from the Keycloak server using the specified function + * @param function the function to execute + * @param clazz the return type + * @return the value + * @param the return type + * @throws RunOnServerException + */ public T fetch(FetchOnServer function, Class clazz) throws RunOnServerException { try { String s = fetchString(function); @@ -44,6 +60,12 @@ public class RunOnServerClient { } } + /** + * Retrieve a string value from the Keycloak server using the specified function + * @param function the function to execute + * @return the value + * @throws RunOnServerException + */ public String fetchString(FetchOnServer function) throws RunOnServerException { String encoded = SerializationUtil.encode(function); @@ -60,6 +82,12 @@ public class RunOnServerClient { } } + /** + * Execute code on the Keycloak server, including assertions to verify values on the server side + * + * @param function the function to execute + * @throws RunOnServerException + */ public void run(RunOnServer function) throws RunOnServerException { String encoded = SerializationUtil.encode(function); @@ -74,7 +102,7 @@ public class RunOnServerClient { } } - public String runOnServer(String encoded) throws RunOnServerException { + private String runOnServer(String encoded) throws RunOnServerException { try { HttpPost request = new HttpPost(url + "?executionId=" + executionId); request.setHeader("Content-type", "text/plain;charset=utf-8"); diff --git a/test-framework/remote/src/main/java/org/keycloak/testframework/remote/timeoffset/InjectTimeOffSet.java b/test-framework/remote/src/main/java/org/keycloak/testframework/remote/timeoffset/InjectTimeOffSet.java index b64a9e1ddaa..9cadb90a81d 100644 --- a/test-framework/remote/src/main/java/org/keycloak/testframework/remote/timeoffset/InjectTimeOffSet.java +++ b/test-framework/remote/src/main/java/org/keycloak/testframework/remote/timeoffset/InjectTimeOffSet.java @@ -7,6 +7,9 @@ import java.lang.annotation.Target; import org.keycloak.testframework.injection.LifeCycle; +/** + * Injects a {@link TimeOffSet} to change the timeoffset on the Keycloak server + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface InjectTimeOffSet { diff --git a/test-framework/remote/src/main/java/org/keycloak/testframework/remote/timeoffset/TimeOffSet.java b/test-framework/remote/src/main/java/org/keycloak/testframework/remote/timeoffset/TimeOffSet.java index d75a9ff5ed5..4a3051a9807 100644 --- a/test-framework/remote/src/main/java/org/keycloak/testframework/remote/timeoffset/TimeOffSet.java +++ b/test-framework/remote/src/main/java/org/keycloak/testframework/remote/timeoffset/TimeOffSet.java @@ -30,6 +30,12 @@ public class TimeOffSet { currentOffset = initOffset; } + /** + * Set the timeoffset on the Keycloak server + * + * @param offset the timeoffset + * @throws RuntimeException + */ public void set(int offset) throws RuntimeException { currentOffset = offset; @@ -57,6 +63,11 @@ public class TimeOffSet { } + /** + * Retrive the current time offset + * + * @return the time offset + */ public int get() { return currentOffset; } diff --git a/test-framework/ui/src/main/java/org/keycloak/testframework/ui/annotations/InjectPage.java b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/annotations/InjectPage.java index e4e8ceba1e3..d98dcec3689 100644 --- a/test-framework/ui/src/main/java/org/keycloak/testframework/ui/annotations/InjectPage.java +++ b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/annotations/InjectPage.java @@ -5,6 +5,10 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * Injects an implementation of {@link org.keycloak.testframework.ui.page.AbstractPage} to interact with HTML pages + * published by the Keycloak server + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface InjectPage { diff --git a/test-framework/ui/src/main/java/org/keycloak/testframework/ui/annotations/InjectWebDriver.java b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/annotations/InjectWebDriver.java index 1c37e499f20..17d00ff32e1 100644 --- a/test-framework/ui/src/main/java/org/keycloak/testframework/ui/annotations/InjectWebDriver.java +++ b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/annotations/InjectWebDriver.java @@ -5,6 +5,10 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * 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 { }