This is an automated email from the ASF dual-hosted git repository.

jonnybot pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy-geb.git

commit d1a35ca7048dc5bd35b647a39cc01215dc6d4390
Author: Jonny Carter <[email protected]>
AuthorDate: Tue Mar 24 14:31:05 2026 -0500

    Use annotation-driven configuration
---
 .../ContainerFileDetectorAnnotationSpec.groovy     |  8 +---
 .../HostNameConfigurationSpec.groovy               |  6 +--
 .../groovy/geb/testcontainers/RootPageSpec.groovy  |  6 +--
 .../ContainerGebConfiguration.groovy               | 51 ++++++++++++----------
 .../geb/testcontainers/ContainerGebSpec.groovy     | 50 +++++++++++++++++++--
 .../testcontainers/WebDriverContainerHolder.groovy | 25 +++++++----
 6 files changed, 94 insertions(+), 52 deletions(-)

diff --git 
a/integration/geb-testcontainers/src/integration-test/groovy/geb/testcontainers/ContainerFileDetectorAnnotationSpec.groovy
 
b/integration/geb-testcontainers/src/integration-test/groovy/geb/testcontainers/ContainerFileDetectorAnnotationSpec.groovy
index 881e3c1b..040ef620 100644
--- 
a/integration/geb-testcontainers/src/integration-test/groovy/geb/testcontainers/ContainerFileDetectorAnnotationSpec.groovy
+++ 
b/integration/geb-testcontainers/src/integration-test/groovy/geb/testcontainers/ContainerFileDetectorAnnotationSpec.groovy
@@ -25,14 +25,10 @@ import org.openqa.selenium.WebDriverException
  * Altered copy of {@link ContainerFileDetectorDefaultSpec}
  * that throws {@link org.openqa.selenium.InvalidArgumentException}
  */
+@ContainerGebConfiguration(fileDetector = UselessContainerFileDetector)
 class ContainerFileDetectorAnnotationSpec extends ContainerGebSpecWithServer {
 
-    @Override
-    Class<? extends ContainerFileDetector> fileDetector() {
-        UselessContainerFileDetector
-    }
-
-    def "should fail to find file with fileDetector changed to 
UselessContainerFileDetector via interface"() {
+    def "should fail to find file with fileDetector changed to 
UselessContainerFileDetector via annotation"() {
         given:
         def uploadPage = to UploadPage
 
diff --git 
a/integration/geb-testcontainers/src/integration-test/groovy/geb/testcontainers/HostNameConfigurationSpec.groovy
 
b/integration/geb-testcontainers/src/integration-test/groovy/geb/testcontainers/HostNameConfigurationSpec.groovy
index 9e6fae98..a2029a4b 100644
--- 
a/integration/geb-testcontainers/src/integration-test/groovy/geb/testcontainers/HostNameConfigurationSpec.groovy
+++ 
b/integration/geb-testcontainers/src/integration-test/groovy/geb/testcontainers/HostNameConfigurationSpec.groovy
@@ -26,13 +26,9 @@ import spock.lang.Title
  * for more instructions on how to write functional tests with Geb.
  */
 @Title("host name configuration test")
+@ContainerGebConfiguration(hostName = 'testing.example.com')
 class HostNameConfigurationSpec extends ContainerGebSpecWithServer {
 
-    @Override
-    String hostName() {
-        'testing.example.com'
-    }
-
     def "should show the right server name when visiting home page"() {
         when: "visiting the hpme page with a configured host name"
         to(HomePage)
diff --git 
a/integration/geb-testcontainers/src/integration-test/groovy/geb/testcontainers/RootPageSpec.groovy
 
b/integration/geb-testcontainers/src/integration-test/groovy/geb/testcontainers/RootPageSpec.groovy
index 9789242a..aa599443 100644
--- 
a/integration/geb-testcontainers/src/integration-test/groovy/geb/testcontainers/RootPageSpec.groovy
+++ 
b/integration/geb-testcontainers/src/integration-test/groovy/geb/testcontainers/RootPageSpec.groovy
@@ -27,13 +27,9 @@ import geb.testcontainers.pages.HomePage
  * See https://groovy.apache.org/geb/manual/current/
  * for more instructions on how to write functional tests with Geb.
  */
+@ContainerGebConfiguration(reporting = true)
 class RootPageSpec extends ContainerGebSpecWithServer {
 
-    @Override
-    boolean reporting() {
-        true
-    }
-
     @Override
     Reporter createReporter() {
         // Override the default reporter to demonstrate how this can be 
customized
diff --git 
a/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/ContainerGebConfiguration.groovy
 
b/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/ContainerGebConfiguration.groovy
index b5f6b985..ac19b2af 100644
--- 
a/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/ContainerGebConfiguration.groovy
+++ 
b/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/ContainerGebConfiguration.groovy
@@ -20,53 +20,58 @@ package geb.testcontainers
 
 import org.testcontainers.containers.GenericContainer
 
+import java.lang.annotation.ElementType
+import java.lang.annotation.Inherited
+import java.lang.annotation.Retention
+import java.lang.annotation.RetentionPolicy
+import java.lang.annotation.Target
+
 /**
- * Implement this interface on a {@link ContainerGebSpec} subclass to 
configure the
- * protocol, hostname, reporting, and file detector for container-based 
browser tests.
+ * Annotation to configure the protocol, hostname, reporting, and file detector
+ * for container-based browser tests. Apply to a {@link ContainerGebSpec} 
subclass.
+ *
+ * <p>Values set here override the defaults from {@link ContainerGebSpec}.
+ * This annotation is {@link Inherited}, so subclasses inherit their parent's 
configuration.
+ * Subclasses can re-apply the annotation to override specific values.
  *
- * <p>Configuration is inherited by subclasses, and can be overridden by 
re-implementing
- * the desired methods.
+ * <p>For configuration that requires dynamic values or complex logic, override
+ * the corresponding methods on {@link ContainerGebSpec} directly instead.
  *
  * <p>Example:
  * <pre><code>
+ * &#64;ContainerGebConfiguration(hostName = 'testing.example.com', reporting 
= true)
  * class MySpec extends ContainerGebSpec {
- *     &#64;Override
- *     String hostName() { 'testing.example.com' }
- *
- *     &#64;Override
- *     boolean reporting() { true }
+ *     // ...
  * }
  * </code></pre>
  *
  * @author James Daugherty
  * @since 4.1
  */
-interface ContainerGebConfiguration {
+@Inherited
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@interface ContainerGebConfiguration {
+
+    static final String DEFAULT_HOSTNAME_FROM_CONTAINER = 
GenericContainer.INTERNAL_HOST_HOSTNAME
+    static final String DEFAULT_PROTOCOL = 'http'
 
     /**
      * The protocol that the container's browser will use to access the server 
under test.
      * <p>Defaults to {@code http}.
      */
-    default String protocol() {
-        'http'
-    }
+    String protocol() default DEFAULT_PROTOCOL
 
     /**
      * The hostname that the container's browser will use to access the server 
under test.
      * <p>Defaults to {@code host.testcontainers.internal}.
-     * <p>This is useful when the server under test needs to be accessed with 
a certain hostname.
      */
-    default String hostName() {
-        GenericContainer.INTERNAL_HOST_HOSTNAME
-    }
+    String hostName() default DEFAULT_HOSTNAME_FROM_CONTAINER
 
     /**
      * Whether reporting should be enabled for this test.
-     * Add a {@code GebConfig.groovy} to customize the reporter configuration.
      */
-    default boolean reporting() {
-        false
-    }
+    boolean reporting() default false
 
     /**
      * The {@link ContainerFileDetector} implementation to use for this class.
@@ -75,7 +80,5 @@ interface ContainerGebConfiguration {
      * @see DefaultContainerFileDetector
      * @see UselessContainerFileDetector
      */
-    default Class<? extends ContainerFileDetector> fileDetector() {
-        DefaultContainerFileDetector
-    }
+    Class<? extends ContainerFileDetector> fileDetector() default 
DefaultContainerFileDetector
 }
diff --git 
a/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/ContainerGebSpec.groovy
 
b/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/ContainerGebSpec.groovy
index 79e0759a..68d6b638 100644
--- 
a/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/ContainerGebSpec.groovy
+++ 
b/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/ContainerGebSpec.groovy
@@ -27,6 +27,7 @@ import geb.test.GebTestManager
 import geb.transform.DynamicallyDispatchesToBrowser
 import geb.testcontainers.support.ContainerGebFileInputSource
 import org.testcontainers.containers.BrowserWebDriverContainer
+import org.testcontainers.containers.GenericContainer
 import org.testcontainers.images.builder.Transferable
 import spock.lang.Shared
 import spock.lang.Specification
@@ -43,7 +44,14 @@ import spock.lang.Specification
  *   </li>
  * </ul>
  *
- * @see ContainerGebConfiguration for how to customize the container's 
connection information
+ * <p>Configuration can be customized in two ways:
+ * <ul>
+ *   <li>Apply {@link ContainerGebConfiguration @ContainerGebConfiguration} to 
set values declaratively.</li>
+ *   <li>Override methods ({@link #protocol()}, {@link #hostName()}, {@link 
#reporting()},
+ *       {@link #fileDetector()}) for dynamic or inheritable 
configuration.</li>
+ * </ul>
+ *
+ * @see ContainerGebConfiguration
  *
  * @author Søren Berg Glasius
  * @author Mattias Reichel
@@ -51,7 +59,7 @@ import spock.lang.Specification
  * @since 4.1
  */
 @DynamicallyDispatchesToBrowser
-abstract class ContainerGebSpec extends Specification implements 
ContainerGebConfiguration {
+abstract class ContainerGebSpec extends Specification {
 
     @Shared
     WebDriverContainerHolder holder
@@ -92,7 +100,6 @@ abstract class ContainerGebSpec extends Specification 
implements ContainerGebCon
 
     /**
      * Copies a file from the host to the container for assignment to a Geb 
FileInput module.
-     * This method is useful when you need to upload a file to a form in a Geb 
test and will work cross-platform.
      *
      * @param hostPath relative path to the file on the host
      * @param containerPath absolute path to where to put the file in the 
container
@@ -103,4 +110,41 @@ abstract class ContainerGebSpec extends Specification 
implements ContainerGebCon
         container.copyFileToContainer(Transferable.of(new 
File(hostPath).bytes), containerPath)
         new ContainerGebFileInputSource(containerPath)
     }
+
+    // --- Configuration defaults (overridable by subclasses or 
@ContainerGebConfiguration) ---
+
+    /**
+     * The protocol that the container's browser will use to access the server 
under test.
+     * <p>Defaults to {@code http}. Can also be set via {@link 
ContainerGebConfiguration#protocol()}.
+     */
+    String protocol() {
+        ContainerGebConfiguration.DEFAULT_PROTOCOL
+    }
+
+    /**
+     * The hostname that the container's browser will use to access the server 
under test.
+     * <p>Defaults to {@code host.testcontainers.internal}.
+     * Can also be set via {@link ContainerGebConfiguration#hostName()}.
+     */
+    String hostName() {
+        ContainerGebConfiguration.DEFAULT_HOSTNAME_FROM_CONTAINER
+    }
+
+    /**
+     * Whether reporting should be enabled for this test.
+     * Can also be set via {@link ContainerGebConfiguration#reporting()}.
+     */
+    boolean reporting() {
+        false
+    }
+
+    /**
+     * The {@link ContainerFileDetector} implementation to use for this class.
+     * Can also be set via {@link ContainerGebConfiguration#fileDetector()}.
+     *
+     * @since 4.2
+     */
+    Class<? extends ContainerFileDetector> fileDetector() {
+        DefaultContainerFileDetector
+    }
 }
diff --git 
a/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/WebDriverContainerHolder.groovy
 
b/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/WebDriverContainerHolder.groovy
index 34335c06..42b5fb1b 100644
--- 
a/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/WebDriverContainerHolder.groovy
+++ 
b/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/WebDriverContainerHolder.groovy
@@ -431,16 +431,23 @@ class WebDriverContainerHolder {
         Class<? extends ContainerFileDetector> fileDetector
 
         SpecContainerConfiguration(SpecInfo spec) {
-            ContainerGebConfiguration conf = null
-
-            if (ContainerGebConfiguration.isAssignableFrom(spec.reflection)) {
-                conf = spec.reflection.getConstructor().newInstance() as 
ContainerGebConfiguration
+            // Check for @ContainerGebConfiguration annotation first 
(including inherited)
+            def annotation = 
spec.reflection.getAnnotation(ContainerGebConfiguration)
+
+            if (annotation) {
+                // Annotation values take priority
+                protocol = annotation.protocol()
+                hostName = annotation.hostName()
+                reporting = annotation.reporting()
+                fileDetector = annotation.fileDetector()
+            } else {
+                // Fall back to instance methods on the spec (overridable 
defaults from ContainerGebSpec)
+                def instance = spec.reflection.getConstructor().newInstance() 
as ContainerGebSpec
+                protocol = instance.protocol()
+                hostName = instance.hostName()
+                reporting = instance.reporting()
+                fileDetector = instance.fileDetector()
             }
-
-            protocol = conf?.protocol() ?: DEFAULT_PROTOCOL
-            hostName = conf?.hostName() ?: DEFAULT_HOSTNAME
-            reporting = conf?.reporting() ?: false
-            fileDetector = conf?.fileDetector() ?: DEFAULT_FILE_DETECTOR
         }
     }
 }

Reply via email to