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 471539a44db514ec088186faef0060179fcabbaa
Author: Jonny Carter <[email protected]>
AuthorDate: Tue Mar 24 10:20:01 2026 -0500

    Refactor test containers to use base class pattern
---
 integration/geb-testcontainers/README.md           |   2 +-
 .../ContainerFileDetectorAnnotationSpec.groovy     |   8 +-
 .../ContainerFileDetectorSpockSpec.groovy          |  15 +-
 .../HostNameConfigurationSpec.groovy               |  10 +-
 .../geb/testcontainers/InheritedConfigSpec.groovy  |  18 +-
 .../geb/testcontainers/PerTestRecordingSpec.groovy |   2 +-
 .../groovy/geb/testcontainers/RootPageSpec.groovy  |  10 +-
 .../geb/testcontainers/TestFileServer.groovy       |   2 +-
 .../ContainerGebConfiguration.groovy               |  76 ++--
 .../testcontainers/ContainerGebExtension.groovy    |  89 ++---
 .../geb/testcontainers/ContainerGebSpec.groovy     |  77 +++-
 .../DefaultContainerFileDetector.groovy            |   8 -
 .../geb/testcontainers/GebContainerSettings.groovy |  78 +---
 .../geb/testcontainers/GebOnFailureReporter.groovy |   2 +-
 .../testcontainers/GebRecordingTestListener.groovy |   2 +-
 .../testcontainers/WebDriverContainerHolder.groovy | 288 ++++----------
 .../serviceloader/ServiceRegistry.groovy           |  96 -----
 .../geb/testcontainers/support/BrowserType.groovy  |  42 --
 .../testcontainers/support/ContainerSupport.groovy |  82 ----
 .../testcontainers/support/ReportingSupport.groovy |  50 ---
 .../support/delegate/BrowserDelegate.groovy        | 429 ---------------------
 .../delegate/DownloadSupportDelegate.groovy        | 173 ---------
 .../support/delegate/DriverDelegate.groovy         |  53 ---
 .../support/delegate/PageDelegate.groovy           | 415 --------------------
 ...ockframework.runtime.extension.IGlobalExtension |   2 +-
 25 files changed, 256 insertions(+), 1773 deletions(-)

diff --git a/integration/geb-testcontainers/README.md 
b/integration/geb-testcontainers/README.md
index aa5a3935..18dc8c6a 100644
--- a/integration/geb-testcontainers/README.md
+++ b/integration/geb-testcontainers/README.md
@@ -129,7 +129,7 @@ For this reason, this plugin will setup a Local File 
Detector by default.
 To customize the default, either:
 
 1. Create a class that implements 
[`ContainerFileDetector`](./src/testFixtures/groovy/grails/plugin/geb/ContainerFileDetector.groovy)
-   and specify its fully qualified class name in a 
`META-INF/services/grails.plugin.geb.ContainerFileDetector` file
+   and specify its fully qualified class name in a 
`META-INF/services/geb.testcontainers.ContainerFileDetector` file
    on the classpath (e.g., `src/integration-test/resources`).
 2. Use the `ContainerGebConfiguration` annotation and set its `fileDetector` 
property to your `ContainerFileDetector` implementation class.
 
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 040ef620..881e3c1b 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,10 +25,14 @@ import org.openqa.selenium.WebDriverException
  * Altered copy of {@link ContainerFileDetectorDefaultSpec}
  * that throws {@link org.openqa.selenium.InvalidArgumentException}
  */
-@ContainerGebConfiguration(fileDetector = UselessContainerFileDetector)
 class ContainerFileDetectorAnnotationSpec extends ContainerGebSpecWithServer {
 
-    def "should fail to find file with fileDetector changed to 
UselessContainerFileDetector via annotation"() {
+    @Override
+    Class<? extends ContainerFileDetector> fileDetector() {
+        UselessContainerFileDetector
+    }
+
+    def "should fail to find file with fileDetector changed to 
UselessContainerFileDetector via interface"() {
         given:
         def uploadPage = to UploadPage
 
diff --git 
a/integration/geb-testcontainers/src/integration-test/groovy/geb/testcontainers/ContainerFileDetectorSpockSpec.groovy
 
b/integration/geb-testcontainers/src/integration-test/groovy/geb/testcontainers/ContainerFileDetectorSpockSpec.groovy
index 02267980..9212d12e 100644
--- 
a/integration/geb-testcontainers/src/integration-test/groovy/geb/testcontainers/ContainerFileDetectorSpockSpec.groovy
+++ 
b/integration/geb-testcontainers/src/integration-test/groovy/geb/testcontainers/ContainerFileDetectorSpockSpec.groovy
@@ -18,26 +18,21 @@
  */
 package geb.testcontainers
 
-import geb.testcontainers.serviceloader.ServiceRegistry
 import geb.testcontainers.pages.UploadPage
 import org.openqa.selenium.WebDriverException
-import spock.lang.PendingFeature
 
 /**
  * Altered copy of {@link ContainerFileDetectorAnnotationSpec}
+ * that configures the file detector via the interface.
  */
 class ContainerFileDetectorSpockSpec extends ContainerGebSpecWithServer {
 
-    def setupSpec() {
-        ServiceRegistry.setInstance(ContainerFileDetector, new 
UselessContainerFileDetector())
+    @Override
+    Class<? extends ContainerFileDetector> fileDetector() {
+        UselessContainerFileDetector
     }
 
-    def cleanupSpec() {
-        ServiceRegistry.setInstance(ContainerFileDetector, null)
-    }
-
-    @PendingFeature(reason = 
'https://github.com/apache/grails-geb/pull/146#issuecomment-2691433277')
-    def "should fail to find file with fileDetector changed to 
UselessContainerFileDetector in setupSpec"() {
+    def "should fail to find file with fileDetector changed to 
UselessContainerFileDetector via interface"() {
         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 1b3b7a7b..9e6fae98 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
@@ -22,13 +22,17 @@ import geb.testcontainers.pages.HomePage
 import spock.lang.Title
 
 /**
- * See 
https://grails.apache.org/docs/latest/guide/testing.html#functionalTesting and 
https://groovy.apache.org/geb/manual/current/
- * for more instructions on how to write functional tests with Grails and Geb.
+ * See https://groovy.apache.org/geb/manual/current/
+ * 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/InheritedConfigSpec.groovy
 
b/integration/geb-testcontainers/src/integration-test/groovy/geb/testcontainers/InheritedConfigSpec.groovy
index 764f3457..cd73cb78 100644
--- 
a/integration/geb-testcontainers/src/integration-test/groovy/geb/testcontainers/InheritedConfigSpec.groovy
+++ 
b/integration/geb-testcontainers/src/integration-test/groovy/geb/testcontainers/InheritedConfigSpec.groovy
@@ -23,15 +23,19 @@ import geb.testcontainers.pages.HomePage
 /**
  * Adaptation of {@link HostNameConfigurationSpec}
  */
-class SuperSpec extends ContainerGebSpecWithServer implements 
IContainerGebConfiguration {
+class SuperSpec extends ContainerGebSpecWithServer {
     @Override
     String hostName() {
         'super.example.com'
     }
 }
 
-@ContainerGebConfiguration(hostName = 'not.example.com')
-class NotSuperSpec extends ContainerGebSpecWithServer {}
+class NotSuperSpec extends ContainerGebSpecWithServer {
+    @Override
+    String hostName() {
+        'not.example.com'
+    }
+}
 
 class InheritedConfigSpec extends SuperSpec {
     void 'should show the right server name when visiting /serverName'() {
@@ -39,7 +43,6 @@ class InheritedConfigSpec extends SuperSpec {
         to(HomePage)
 
         then: 'the emitted hostname is correct'
-        // pageSource.contains('Server name: super.example.com')
         currentUrl == "http://super.example.com:8090/";
     }
 
@@ -54,9 +57,8 @@ class NotInheritedConfigSpec extends NotSuperSpec {
         when: 'visiting the server name controller'
         to(HomePage)
 
-        then: 'the emitted hostname is correct'
-        // !pageSource.contains('Server name: not.example.com')
-        currentUrl != "http://not.example.com:8090/";
+        then: 'the emitted hostname is inherited via interface'
+        currentUrl == "http://not.example.com:8090/";
     }
 
     def cleanup() {
@@ -75,7 +77,6 @@ class ChildPreferenceInheritedConfigSpec extends SuperSpec {
         to(HomePage)
 
         then: 'the emitted hostname is correct'
-        // pageSource.contains('Server name: child.example.com')
         currentUrl == "http://child.example.com:8090/";
 
         when:
@@ -106,7 +107,6 @@ class MultipleInheritanceSpec extends 
SuperSuperInheritedConfigSpec {
         to(HomePage)
 
         then: 'the emitted hostname is correct'
-        // pageSource.contains('Server name: super.example.com')
         currentUrl == "http://super.example.com:8090/";
         report('multi inheritance report')
     }
diff --git 
a/integration/geb-testcontainers/src/integration-test/groovy/geb/testcontainers/PerTestRecordingSpec.groovy
 
b/integration/geb-testcontainers/src/integration-test/groovy/geb/testcontainers/PerTestRecordingSpec.groovy
index 2245adfd..292ea23b 100644
--- 
a/integration/geb-testcontainers/src/integration-test/groovy/geb/testcontainers/PerTestRecordingSpec.groovy
+++ 
b/integration/geb-testcontainers/src/integration-test/groovy/geb/testcontainers/PerTestRecordingSpec.groovy
@@ -95,4 +95,4 @@ class PerTestRecordingSpec extends ContainerGebSpec {
         return file.isFile() && (file.name.endsWith('.mp4') || 
file.name.endsWith('.flv'))
     }
 
-}
\ No newline at end of file
+}
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 371d5144..9789242a 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
@@ -24,12 +24,16 @@ import geb.report.Reporter
 import geb.testcontainers.pages.HomePage
 
 /**
- * See 
https://grails.apache.org/docs/latest/guide/testing.html#functionalTesting and 
https://groovy.apache.org/geb/manual/current/
- * for more instructions on how to write functional tests with Grails and Geb.
+ * 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/integration-test/groovy/geb/testcontainers/TestFileServer.groovy
 
b/integration/geb-testcontainers/src/integration-test/groovy/geb/testcontainers/TestFileServer.groovy
index 38f01bb4..fee1d509 100644
--- 
a/integration/geb-testcontainers/src/integration-test/groovy/geb/testcontainers/TestFileServer.groovy
+++ 
b/integration/geb-testcontainers/src/integration-test/groovy/geb/testcontainers/TestFileServer.groovy
@@ -45,7 +45,7 @@ class TestFileServer {
         // Use JDK's built-in SimpleFileServer to serve the static content
         server = SimpleFileServer.createFileServer(addr, staticDirPath, 
OutputLevel.INFO)
         server.start()
-        println "TestFileServer started on port 8080"
+        println "TestFileServer started on port $port"
     }
 
     void stop() {
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 a0b72c4f..b5f6b985 100644
--- 
a/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/ContainerGebConfiguration.groovy
+++ 
b/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/ContainerGebConfiguration.groovy
@@ -20,76 +20,62 @@ package geb.testcontainers
 
 import org.testcontainers.containers.GenericContainer
 
-import java.lang.annotation.ElementType
-import java.lang.annotation.Retention
-import java.lang.annotation.RetentionPolicy
-import java.lang.annotation.Target
-
 /**
- * Can be used to configure the protocol and hostname that the container's 
browser will use.
+ * Implement this interface on a {@link ContainerGebSpec} subclass to 
configure the
+ * protocol, hostname, reporting, and file detector for container-based 
browser tests.
+ *
+ * <p>Configuration is inherited by subclasses, and can be overridden by 
re-implementing
+ * the desired methods.
+ *
+ * <p>Example:
+ * <pre><code>
+ * class MySpec extends ContainerGebSpec {
+ *     &#64;Override
+ *     String hostName() { 'testing.example.com' }
+ *
+ *     &#64;Override
+ *     boolean reporting() { true }
+ * }
+ * </code></pre>
  *
  * @author James Daugherty
  * @since 4.1
  */
-@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'
-    static final Class<? extends ContainerFileDetector> DEFAULT_FILE_DETECTOR 
= DefaultContainerFileDetector
+interface ContainerGebConfiguration {
 
     /**
      * The protocol that the container's browser will use to access the server 
under test.
      * <p>Defaults to {@code http}.
      */
-    String protocol() default DEFAULT_PROTOCOL
+    default String protocol() {
+        'http'
+    }
 
     /**
      * 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.
      */
-    String hostName() default DEFAULT_HOSTNAME_FROM_CONTAINER
+    default String hostName() {
+        GenericContainer.INTERNAL_HOST_HOSTNAME
+    }
 
     /**
      * Whether reporting should be enabled for this test.
-     * Add a `GebConfig.groovy` to customize the reporter configuration.
+     * Add a {@code GebConfig.groovy} to customize the reporter configuration.
      */
-    boolean reporting() default false
+    default boolean reporting() {
+        false
+    }
 
     /**
-     * The {@link org.openqa.selenium.remote.FileDetector} implementation to 
use for this class.
-     * <p> {@link NullContainerFileDetector} results in the
-     *     {@link geb.testcontainers.serviceloader.ServiceRegistry last set} 
instance being used.
+     * The {@link ContainerFileDetector} implementation to use for this class.
      *
      * @since 4.2
-     * @see DefaultContainerFileDetector DefaultContainerFileDetector
-     * @see UselessContainerFileDetector UselessContainerFileDetector
+     * @see DefaultContainerFileDetector
+     * @see UselessContainerFileDetector
      */
-    Class<? extends ContainerFileDetector> fileDetector() default 
DefaultContainerFileDetector
-}
-
-/**
- * Inheritable version of {@link ContainerGebConfiguration}.
- *
- * @since 4.2
- */
-interface IContainerGebConfiguration {
-
-    default String protocol() {
-        ContainerGebConfiguration.DEFAULT_PROTOCOL
-    }
-
-    default String hostName() {
-        ContainerGebConfiguration.DEFAULT_HOSTNAME_FROM_CONTAINER
-    }
-
-    default boolean reporting() {
-        false
-    }
-
     default Class<? extends ContainerFileDetector> fileDetector() {
-        ContainerGebConfiguration.DEFAULT_FILE_DETECTOR
+        DefaultContainerFileDetector
     }
 }
diff --git 
a/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/ContainerGebExtension.groovy
 
b/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/ContainerGebExtension.groovy
index aac4636f..a543f17e 100644
--- 
a/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/ContainerGebExtension.groovy
+++ 
b/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/ContainerGebExtension.groovy
@@ -18,7 +18,6 @@
  */
 package geb.testcontainers
 
-import geb.testcontainers.support.LocalhostDownloadSupport
 import groovy.transform.CompileStatic
 import groovy.transform.TailRecursive
 import groovy.util.logging.Slf4j
@@ -35,8 +34,8 @@ import java.time.LocalDateTime
  * lifecycle for a {@link ContainerGebSpec}.
  *
  * <p> ContainerGebSpec cannot be a
- * {@link geb.test.ManagedGebTest ManagedGebTest} because it would cause the 
test
- * manager to be initialized out of sequence of the container management.
+ * {@link geb.test.ManagedGebTest ManagedGebTest} with a static test manager 
because
+ * the test manager needs to be rebuilt when the container is (re)initialized.
  * Instead, we initialize the same interceptors as the {@link 
geb.spock.GebExtension GebExtension} does.
  *
  * @author James Daugherty
@@ -70,59 +69,54 @@ class ContainerGebExtension implements IGlobalExtension {
 
     @Override
     void visitSpec(SpecInfo spec) {
-        if (isContainerGebSpec(spec)) {
-            // Do not allow parallel execution since there's only 1 set of 
containers in testcontainers
-            spec.addExclusiveResource(exclusiveResource)
-
-            // Always initialize the container requirements first so the 
GebTestManager can properly configure the browser
-            spec.addSharedInitializerInterceptor { invocation ->
-                holder.reinitialize(invocation)
-
-                ContainerGebSpec gebSpec = invocation.sharedInstance as 
ContainerGebSpec
-                gebSpec.container = holder.container
-                gebSpec.testManager = holder.testManager
-                gebSpec.downloadSupport = new LocalhostDownloadSupport(
-                        holder.browser,
-                        holder.hostNameFromHost
-                )
-
-                // code below here is from the geb.spock.GebExtension since 
there can only be 1 shared initializer per extension
-                holder.testManager.beforeTestClass(invocation.spec.reflection)
-                invocation.proceed()
-            }
+        if (!isContainerGebSpec(spec)) {
+            return
+        }
 
-            spec.addSetupInterceptor { invocation ->
-                // Grails will be initialized by this point, so setup the 
browser url correctly
-                holder.setupBrowserUrl(invocation)
-                invocation.proceed()
-            }
+        // Do not allow parallel execution since there's only 1 set of 
containers in testcontainers
+        spec.addExclusiveResource(exclusiveResource)
 
-            spec.addInterceptor { invocation ->
-                try {
-                    invocation.proceed()
-                } finally {
-                    holder.testManager.afterTestClass()
-                }
-            }
+        // Always initialize the container requirements first so the 
GebTestManager can properly configure the browser
+        spec.addSharedInitializerInterceptor { invocation ->
+            holder.reinitialize(invocation)
 
-            spec.allFeatures*.addIterationInterceptor { invocation ->
-                holder.restartVncRecordingContainer()
+            ContainerGebSpec gebSpec = invocation.sharedInstance as 
ContainerGebSpec
+            gebSpec.holder = holder
 
-                holder.testManager.beforeTest(invocation.instance.getClass(), 
invocation.iteration.displayName)
-                try {
-                    invocation.proceed()
-                } finally {
-                    holder.testManager.afterTest()
-                }
-            }
+            // Code below here is from the geb.spock.GebExtension since there 
can only be 1 shared initializer per extension
+            holder.testManager.beforeTestClass(invocation.spec.reflection)
+            invocation.proceed()
+        }
 
-            addGebExtensionOnFailureReporter(spec)
+        spec.addSetupInterceptor { invocation ->
+            // By this point, any server under test should be running, so 
setup the browser url correctly
+            holder.setupBrowserUrl(invocation)
+            invocation.proceed()
+        }
 
-            spec.addListener(new GebRecordingTestListener(holder))
+        spec.addInterceptor { invocation ->
+            try {
+                invocation.proceed()
+            } finally {
+                holder.testManager.afterTestClass()
+            }
+        }
+
+        spec.allFeatures*.addIterationInterceptor { invocation ->
+            holder.restartVncRecordingContainer()
+            holder.testManager.beforeTest(invocation.instance.getClass(), 
invocation.iteration.displayName)
+            try {
+                invocation.proceed()
+            } finally {
+                holder.testManager.afterTest()
+            }
         }
+
+        addOnFailureReporter(spec)
+        spec.addListener(new GebRecordingTestListener(holder))
     }
 
-    private static void addGebExtensionOnFailureReporter(SpecInfo spec) {
+    private static void addOnFailureReporter(SpecInfo spec) {
         List<MethodInfo> methods = spec.allFeatures*.featureMethod + 
spec.allFixtureMethods.toList()
         methods.each {
             it.addInterceptor(new GebOnFailureReporter())
@@ -140,4 +134,3 @@ class ContainerGebExtension implements IGlobalExtension {
         return false
     }
 }
-
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 4b8ef27b..79e0759a 100644
--- 
a/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/ContainerGebSpec.groovy
+++ 
b/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/ContainerGebSpec.groovy
@@ -18,20 +18,21 @@
  */
 package geb.testcontainers
 
-import geb.Page
+import geb.download.DownloadSupport
+import geb.report.CompositeReporter
+import geb.report.PageSourceReporter
+import geb.report.Reporter
+import geb.report.ScreenshotReporter
 import geb.test.GebTestManager
-import geb.testcontainers.support.ContainerSupport
-import geb.testcontainers.support.ReportingSupport
-import geb.testcontainers.support.delegate.BrowserDelegate
-import geb.testcontainers.support.delegate.DownloadSupportDelegate
-import geb.testcontainers.support.delegate.DriverDelegate
-import geb.testcontainers.support.delegate.PageDelegate
-import groovy.transform.CompileStatic
+import geb.transform.DynamicallyDispatchesToBrowser
+import geb.testcontainers.support.ContainerGebFileInputSource
+import org.testcontainers.containers.BrowserWebDriverContainer
+import org.testcontainers.images.builder.Transferable
 import spock.lang.Shared
 import spock.lang.Specification
 
 /**
- * A {@link geb.spock.GebSpec GebSpec} that leverages Testcontainers
+ * A {@link geb.spock.GebSpec GebSpec} equivalent that leverages Testcontainers
  * to run the browser inside a container.
  *
  * <p>Prerequisites:
@@ -49,19 +50,57 @@ import spock.lang.Specification
  * @author James Daugherty
  * @since 4.1
  */
-@CompileStatic
-abstract class ContainerGebSpec extends Specification implements 
ContainerSupport, ReportingSupport, BrowserDelegate, PageDelegate, 
DriverDelegate, DownloadSupportDelegate {
+@DynamicallyDispatchesToBrowser
+abstract class ContainerGebSpec extends Specification implements 
ContainerGebConfiguration {
 
     @Shared
-    static GebTestManager testManager
+    WebDriverContainerHolder holder
 
-    static void setTestManager(GebTestManager testManager) {
-        this.testManager = testManager
+    @Delegate(includes = ['getBrowser'])
+    GebTestManager getTestManager() {
+        holder?.testManager
     }
 
-    @Override
-    Page getPage() {
-        // Be explicit which trait to use (PageDelegate vs BrowserDelegate)
-        PageDelegate.super.page
+    /**
+     * Access the container running the web-driver, for convenience to 
execInContainer, copyFileToContainer etc.
+     */
+    BrowserWebDriverContainer getContainer() {
+        holder?.container
     }
-}
\ No newline at end of file
+
+    /**
+     * Returns the download support configured for localhost-aware downloads 
from containers.
+     */
+    @Delegate(interfaces = false)
+    DownloadSupport getDownloadSupport() {
+        holder?.downloadSupport
+    }
+
+    /**
+     * Reports the current state of the browser under the given label.
+     */
+    void report(String message = '') {
+        testManager?.report(message)
+    }
+
+    /**
+     * The reporter that Geb should use when reporting is enabled.
+     */
+    Reporter createReporter() {
+        new CompositeReporter(new PageSourceReporter(), new 
ScreenshotReporter())
+    }
+
+    /**
+     * 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
+     * @return the file object to assign to the FileInput module
+     * @since 4.2
+     */
+    File createFileInputSource(String hostPath, String containerPath) {
+        container.copyFileToContainer(Transferable.of(new 
File(hostPath).bytes), containerPath)
+        new ContainerGebFileInputSource(containerPath)
+    }
+}
diff --git 
a/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/DefaultContainerFileDetector.groovy
 
b/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/DefaultContainerFileDetector.groovy
index 040a8277..cccf4f2e 100644
--- 
a/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/DefaultContainerFileDetector.groovy
+++ 
b/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/DefaultContainerFileDetector.groovy
@@ -36,11 +36,3 @@ class DefaultContainerFileDetector extends LocalFileDetector 
implements Containe
  */
 class UselessContainerFileDetector extends UselessFileDetector implements 
ContainerFileDetector {
 }
-
-/**
- * Used by {@link ContainerGebConfiguration#fileDetector()} interface to 
represent a null value.
- *
- * @since 4.2
- */
-class NullContainerFileDetector extends DefaultContainerFileDetector {
-}
\ No newline at end of file
diff --git 
a/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/GebContainerSettings.groovy
 
b/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/GebContainerSettings.groovy
index 795e683b..e753566d 100644
--- 
a/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/GebContainerSettings.groovy
+++ 
b/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/GebContainerSettings.groovy
@@ -25,14 +25,14 @@ import groovy.util.logging.Slf4j
 import java.time.LocalDateTime
 import java.time.format.DateTimeFormatter
 
-import geb.waiting.Wait
-
 import static 
org.testcontainers.containers.BrowserWebDriverContainer.VncRecordingMode
 import static 
org.testcontainers.containers.VncRecordingContainer.VncRecordingFormat
 
 /**
- * Handles parsing various recording configuration
- * used by {@link ContainerGebExtension}.
+ * Container-specific settings parsed from system properties.
+ * <p>
+ * Timeout and waiting configuration should be set in {@code GebConfig.groovy}
+ * using standard Geb mechanisms.
  *
  * @author James Daugherty
  * @since 4.1
@@ -41,51 +41,36 @@ import static 
org.testcontainers.containers.VncRecordingContainer.VncRecordingFo
 @CompileStatic
 class GebContainerSettings {
 
-    public static final boolean DEFAULT_AT_CHECK_WAITING = false
     private static final VncRecordingMode DEFAULT_RECORDING_MODE = 
VncRecordingMode.SKIP
     private static final VncRecordingFormat DEFAULT_RECORDING_FORMAT = 
VncRecordingFormat.MP4
-    public static final int DEFAULT_TIMEOUT_IMPLICITLY_WAIT = 0
-    public static final int DEFAULT_TIMEOUT_PAGE_LOAD = 300
-    public static final int DEFAULT_TIMEOUT_SCRIPT = 30
 
     boolean tracingEnabled
     String recordingDirectoryName
     String reportingDirectoryName
-    String browserType
     boolean restartRecordingContainerPerTest
     VncRecordingMode recordingMode
     VncRecordingFormat recordingFormat
     LocalDateTime startTime
-    int implicitlyWait
-    int pageLoadTimeout
-    int scriptTimeout
-
-    boolean atCheckWaiting
-    Number timeout
-    Number retryInterval
 
     GebContainerSettings(LocalDateTime startTime) {
-        tracingEnabled = getBooleanProperty('geb.container.tracing.enabled', 
false)
-        recordingDirectoryName = 
System.getProperty('geb.container.recording.directory', 
'build/gebContainer/recordings')
-        reportingDirectoryName = 
System.getProperty('geb.container.reporting.directory', 
'build/gebContainer/reports')
-        // browserType = System.getProperty('geb.container.browser.type', 
DEFAULT_BROWSER_TYPE)
-        // browserType = System.getProperty('geb.env', DEFAULT_BROWSER_TYPE)
+        tracingEnabled = Boolean.parseBoolean(
+            System.getProperty('geb.container.tracing.enabled', 'false')
+        )
+        recordingDirectoryName = System.getProperty(
+            'geb.container.recording.directory', 
'build/gebContainer/recordings'
+        )
+        reportingDirectoryName = System.getProperty(
+            'geb.container.reporting.directory', 'build/gebContainer/reports'
+        )
         recordingMode = VncRecordingMode.valueOf(
-                System.getProperty('geb.container.recording.mode', 
DEFAULT_RECORDING_MODE.name())
+            System.getProperty('geb.container.recording.mode', 
DEFAULT_RECORDING_MODE.name())
         )
         recordingFormat = VncRecordingFormat.valueOf(
-                System.getProperty('geb.container.recording.format', 
DEFAULT_RECORDING_FORMAT.name())
+            System.getProperty('geb.container.recording.format', 
DEFAULT_RECORDING_FORMAT.name())
         )
-        restartRecordingContainerPerTest = getBooleanProperty(
-                'geb.container.recording.restartRecordingContainerPerTest',
-                true
+        restartRecordingContainerPerTest = Boolean.parseBoolean(
+            
System.getProperty('geb.container.recording.restartRecordingContainerPerTest', 
'true')
         )
-        implicitlyWait = 
getIntProperty('geb.container.timeouts.implicitlyWait', 
DEFAULT_TIMEOUT_IMPLICITLY_WAIT)
-        pageLoadTimeout = getIntProperty('geb.container.timeouts.pageLoad', 
DEFAULT_TIMEOUT_PAGE_LOAD)
-        scriptTimeout = getIntProperty('geb.container.timeouts.script', 
DEFAULT_TIMEOUT_SCRIPT)
-        atCheckWaiting = 
getBooleanProperty('geb.container.atCheckWaiting.enabled', 
DEFAULT_AT_CHECK_WAITING)
-        timeout = getNumberProperty('geb.container.timeouts.timeout', 
Wait.DEFAULT_TIMEOUT)
-        retryInterval = 
getNumberProperty('geb.container.timeouts.retryInterval', 
Wait.DEFAULT_RETRY_INTERVAL)
         this.startTime = startTime
     }
 
@@ -109,34 +94,6 @@ class GebContainerSettings {
         createDirectory(reportingDirectoryName, 'reporting')
     }
 
-    private static boolean getBooleanProperty(String propertyName, boolean 
defaultValue) {
-        Boolean.parseBoolean(System.getProperty(propertyName, 
defaultValue.toString()))
-    }
-
-    private static int getIntProperty(String propertyName, int defaultValue) {
-        System.getProperty(propertyName)?.toInteger() ?: defaultValue
-    }
-
-    private static Number getNumberProperty(String propertyName, Number 
defaultValue) {
-        def propValue = System.getProperty(propertyName)
-        if (propValue) {
-            try {
-                if (propValue.contains('.')) {
-                    return new BigDecimal(propValue)
-                }
-                return Integer.parseInt(propValue)
-            } catch (NumberFormatException ignored) {
-                log.warn(
-                        'Could not parse property [{}] with value [{}] as a 
Number. Using default value [{}] instead.',
-                        propertyName,
-                        propValue,
-                        defaultValue
-                )
-            }
-        }
-        return defaultValue
-    }
-
     private File createDirectory(String directoryName, String useCase) {
         def dir = new File(
                 
"$directoryName$File.separator${DateTimeFormatter.ofPattern('yyyyMMdd_HHmmss').format(startTime)}"
@@ -151,7 +108,6 @@ class GebContainerSettings {
                     "Configured $useCase directory [$dir] is expected to be a 
directory, but found file instead."
             )
         }
-
         return dir
     }
 }
diff --git 
a/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/GebOnFailureReporter.groovy
 
b/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/GebOnFailureReporter.groovy
index fd278692..4900b620 100644
--- 
a/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/GebOnFailureReporter.groovy
+++ 
b/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/GebOnFailureReporter.groovy
@@ -36,7 +36,7 @@ class GebOnFailureReporter implements IMethodInterceptor {
             throw notACauseForReporting
         } catch (Throwable throwable) {
             def spec = invocation.instance as ContainerGebSpec
-            if (spec.testManager.reportingEnabled) {
+            if (spec.testManager?.reportingEnabled) {
                 try {
                     spec.testManager.reportFailure()
                 } catch (ignored) {}
diff --git 
a/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/GebRecordingTestListener.groovy
 
b/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/GebRecordingTestListener.groovy
index 1ba98ddf..dfe2b1c7 100644
--- 
a/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/GebRecordingTestListener.groovy
+++ 
b/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/GebRecordingTestListener.groovy
@@ -74,4 +74,4 @@ class GebRecordingTestListener extends AbstractRunListener {
     void error(ErrorInfo error) {
         errorInfo = error
     }
-}
\ No newline at end of file
+}
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 fab7f18e..34335c06 100644
--- 
a/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/WebDriverContainerHolder.groovy
+++ 
b/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/WebDriverContainerHolder.groovy
@@ -18,30 +18,25 @@
  */
 package geb.testcontainers
 
-import org.openqa.selenium.firefox.FirefoxOptions
-
-import java.time.Duration
-import java.time.temporal.ChronoUnit
-import java.util.function.Supplier
-
-import groovy.transform.CompileStatic
-import groovy.transform.EqualsAndHashCode
-import groovy.transform.PackageScope
-import groovy.util.logging.Slf4j
-import org.codehaus.groovy.runtime.InvokerHelper
-
 import com.github.dockerjava.api.model.ContainerNetwork
 import geb.Browser
 import geb.Configuration
 import geb.ConfigurationLoader
+import geb.download.DownloadSupport
 import geb.spock.SpockGebTestManagerBuilder
 import geb.test.GebTestManager
-import geb.waiting.Wait
+import geb.testcontainers.support.LocalhostDownloadSupport
+import groovy.transform.CompileStatic
+import groovy.transform.EqualsAndHashCode
+import groovy.transform.PackageScope
+import groovy.util.logging.Slf4j
 import org.openqa.selenium.SessionNotCreatedException
+import org.openqa.selenium.firefox.FirefoxOptions
 import org.openqa.selenium.remote.RemoteWebDriver
 import org.spockframework.runtime.extension.IMethodInvocation
 import org.spockframework.runtime.model.SpecInfo
 import org.testcontainers.Testcontainers
+import org.testcontainers.containers.GenericContainer
 import org.testcontainers.containers.BrowserWebDriverContainer
 import org.testcontainers.containers.ContainerFetchException
 import org.testcontainers.containers.PortForwardingContainer
@@ -50,15 +45,12 @@ import org.testcontainers.containers.VncRecordingContainer
 import org.testcontainers.images.PullPolicy
 import org.testcontainers.utility.DockerImageName
 
-import geb.testcontainers.serviceloader.ServiceRegistry
-
-import static GebContainerSettings.DEFAULT_AT_CHECK_WAITING
-import static GebContainerSettings.DEFAULT_TIMEOUT_IMPLICITLY_WAIT
-import static GebContainerSettings.DEFAULT_TIMEOUT_PAGE_LOAD
-import static GebContainerSettings.DEFAULT_TIMEOUT_SCRIPT
+import java.time.Duration
+import java.time.temporal.ChronoUnit
+import java.util.function.Supplier
 
 /**
- * Responsible for initializing a {@link 
org.testcontainers.containers.BrowserWebDriverContainer}
+ * Responsible for initializing a {@link BrowserWebDriverContainer}
  * per the Spec's {@link ContainerGebConfiguration}. This class will try to
  * reuse the same container if the configuration matches the current container.
  *
@@ -69,10 +61,6 @@ import static GebContainerSettings.DEFAULT_TIMEOUT_SCRIPT
 @CompileStatic
 class WebDriverContainerHolder {
 
-    /* Driver defaults to Firefox due to availability on AMD64 and ARM64 
platforms.
-    * {@link 
https://github.com/SeleniumHQ/docker-selenium?tab=readme-ov-file#experimental-multi-arch-amd64aarch64armhf-images
 Selenium Browser Archetectures}
-    */
-
     private static final String DEFAULT_HOSTNAME_FROM_HOST = 'localhost'
     private static final String REMOTE_ADDRESS_PROPERTY = 
'webdriver.remote.server'
     private static final String HOST_PORT_PROPERTY = 'hostPort'
@@ -85,7 +73,8 @@ class WebDriverContainerHolder {
     GebTestManager testManager
     Browser browser
     BrowserWebDriverContainer container
-    WebDriverContainerConfiguration containerConf
+    DownloadSupport downloadSupport
+    SpecContainerConfiguration containerConf
 
     WebDriverContainerHolder(GebContainerSettings settings) {
         this.settings = settings
@@ -96,23 +85,24 @@ class WebDriverContainerHolder {
     }
 
     void stop() {
+        System.clearProperty(REMOTE_ADDRESS_PROPERTY)
         container?.stop()
         container = null
         browser = null
         testManager = null
+        downloadSupport = null
         containerConf = null
     }
 
-    boolean 
matchesCurrentContainerConfiguration(WebDriverContainerConfiguration specConf) {
+    boolean matchesCurrentContainerConfiguration(SpecContainerConfiguration 
specConf) {
         specConf == containerConf &&
             settings.recordingMode == 
BrowserWebDriverContainer.VncRecordingMode.SKIP
     }
 
     @PackageScope
     boolean reinitialize(IMethodInvocation methodInvocation) {
-        def specConf = new WebDriverContainerConfiguration(
-            methodInvocation.spec
-        )
+        def specConf = new SpecContainerConfiguration(methodInvocation.spec)
+
         if (matchesCurrentContainerConfiguration(specConf)) {
             return false
         }
@@ -127,15 +117,14 @@ class WebDriverContainerHolder {
         def customBrowser = gebConf.rawConfig.containerBrowser as String
 
         if (gebConfigExists) {
-            log.info("A Geb configuration exists...")
+            log.info('A Geb configuration exists...')
             validateDriverConf(gebConf)
+
             if (customBrowser) {
                 log.info(
                     'A \'containerBrowser\' property was found in GebConfig. ' 
+
                         "Using [$customBrowser] container image."
                 )
-                // Prepare for creating a container matching
-                // the GebConfig `containerBrowser` property.
                 dockerImageName = createDockerImageName(customBrowser)
             } else {
                 log.info(
@@ -154,16 +143,12 @@ class WebDriverContainerHolder {
 
         container.with {
             withEnv('SE_ENABLE_TRACING', settings.tracingEnabled.toString())
-            // Disable withAccessToHost when running in a container (CI 
environment)
-            // as SSH port forwarding doesn't work well in 
container-in-container setups
             if (!System.getenv(CI_PROPERTY)) {
                 withAccessToHost(true)
             } else {
-                // Increase startup timeout for CI environments 
(container-in-container is slower)
                 withStartupTimeout(Duration.of(2, ChronoUnit.MINUTES))
             }
             withImagePullPolicy(PullPolicy.ageBased(Duration.of(1, 
ChronoUnit.DAYS)))
-            // start()  // without Capabilities this is starting chrome
         }
 
         startContainer(container, dockerImageName, customBrowser)
@@ -176,29 +161,21 @@ class WebDriverContainerHolder {
         }
 
         // Ensure that the driver points to the re-initialized container with 
the correct host.
-        // The driver is explicitly quit by us in stop() method, to fulfill 
our resulting responsibility.
         gebConf.cacheDriver = false
-
-        // As we don't cache, this will have been defaulted to true. We 
override to false.
         gebConf.quitDriverOnBrowserReset = false
-
         gebConf.baseUrl = container.seleniumAddress
+
         if (containerConf.reporting) {
             gebConf.reportsDir = settings.reportingDirectory
             gebConf.reporter = (methodInvocation.sharedInstance as 
ContainerGebSpec).createReporter()
         }
 
-        if (gebConf.driverConf) {
-            // As a custom `GebConfig` cannot know the `remoteAddress` of the 
container beforehand,
-            // the `RemoteWebDriver` will be instantiated using the 
`webdriver.remote.server`
-            // system property. We set that property to inform the driver of 
the container address.
-            gebConf.driverConf = ClosureDecorators.withSystemProperty(
-                gebConf.driverConf as Closure,
-                REMOTE_ADDRESS_PROPERTY,
-                container.seleniumAddress
-            )
-        } else {
-            // If no driver was set in GebConfig, create a Firefox driver
+        // Set the selenium address as a system property so that 
RemoteWebDriver
+        // constructors (without explicit URL) can find it. Tests run serially
+        // (enforced by ExclusiveResource) so this is safe.
+        System.setProperty(REMOTE_ADDRESS_PROPERTY, 
container.seleniumAddress.toString())
+
+        if (!gebConf.driverConf) {
             gebConf.driverConf = { ->
                 log.info('Using default Firefox RemoteWebDriver for {}', 
container.seleniumAddress)
                 new RemoteWebDriver(container.seleniumAddress, new 
FirefoxOptions())
@@ -207,17 +184,15 @@ class WebDriverContainerHolder {
 
         browser = createBrowser(gebConf)
         applyFileDetector(browser, containerConf)
-        applyTimeouts(browser, settings)
 
         // There's a bit of a chicken and egg problem here: the container and 
browser are initialized
-        // when the static/shared fields are initialized, which is before the 
grails server has started
-        // so the real url cannot be set (it will be checked as part of the 
geb test manager startup in
-        // reporting mode). We set the url to localhost, which the selenium 
server should respond to
-        // (albeit with an error that will be ignored).
+        // when the shared fields are initialized, which is before the server 
under test has started,
+        // so the real url cannot be set yet. We set the url to localhost, 
which the selenium server
+        // should respond to (albeit with an error that will be ignored).
         browser.baseUrl = 'http://localhost'
 
+        downloadSupport = new LocalhostDownloadSupport(browser, 
hostNameFromHost)
         testManager = createTestManager()
-
         return true
     }
 
@@ -226,9 +201,8 @@ class WebDriverContainerHolder {
             return
         }
 
-        // use a configured baseUrl if found, otherwise use localhost and 
hostPort
         def baseUrl = findBaseUrl(methodInvocation)
-        if (baseUrl != "") {
+        if (baseUrl) {
             browser.baseUrl = baseUrl
         } else {
             int hostPort = findServerPort(methodInvocation)
@@ -243,10 +217,8 @@ class WebDriverContainerHolder {
      * connect from the host, and not from within the container.
      *
      * <p>Defaults to {@code localhost}. If the value returned by {@code 
webDriverContainer.getHost()}
-     * is different from the default, this method will return the same value 
same as
+     * is different from the default, this method will return the same value as
      * {@code webDriverContainer.getHost()}.
-     *
-     * @return the hostname for accessing the server under test from the host
      */
     String getHostNameFromHost() {
         hostNameChanged ? container.host : DEFAULT_HOSTNAME_FROM_HOST
@@ -256,60 +228,46 @@ class WebDriverContainerHolder {
      * Workaround for 
https://github.com/testcontainers/testcontainers-java/issues/3998
      * <p>
      * Restarts the VNC recording container to enable separate recording files 
for each
-     * test method. This method uses reflection to access the VNC recording 
container
-     * field in BrowserWebDriverContainer. Should be called BEFORE each test 
starts.
+     * test method. Uses reflection to access the VNC recording container 
field in
+     * BrowserWebDriverContainer. Should be called BEFORE each test starts.
      */
     void restartVncRecordingContainer() {
         if (!settings.recordingEnabled || 
!settings.restartRecordingContainerPerTest || !container) {
             return
         }
+
         try {
-            // Use reflection to access the VNC recording container field
             def field = 
BrowserWebDriverContainer.getDeclaredField('vncRecordingContainer').tap {
                 accessible = true
             }
-
             def vncContainer = field.get(container) as VncRecordingContainer
             if (vncContainer) {
-                // Stop the current VNC recording container
                 vncContainer.stop()
-                // Create and start a new VNC recording container for the next 
test
                 def newVncContainer = new VncRecordingContainer(container)
                     .withVncPassword('secret')
                     .withVncPort(5900)
                     .withVideoFormat(settings.recordingFormat)
                 field.set(container, newVncContainer)
                 newVncContainer.start()
-
                 log.debug('Successfully restarted VNC recording container')
             }
         } catch (Exception e) {
             log.warn("Failed to restart VNC recording container: $e.message", 
e)
-            // Don't throw the exception to avoid breaking the test execution
         }
     }
 
-    /**
-     * Returns a host port to use for the container in this order:
-     * Class field 'hostPort' if found in the invocation (Spec).
-     * A 'hostPort' property in the GebConfig configuration.
-     * @param methodInvocation The method invocation.
-     * @return int The server port.
-     */
     private static int findServerPort(IMethodInvocation methodInvocation) {
-        // use the test class hostPort if specified
+        // Use getMetaProperty to check for a real declared property, not 
propertyMissing
+        // (which @DynamicallyDispatchesToBrowser would intercept)
         try {
-            return (int) methodInvocation.instance.metaClass.getProperty(
-                    methodInvocation.instance,
-                    HOST_PORT_PROPERTY
-            )
+            if 
(methodInvocation.instance.metaClass.getMetaProperty(HOST_PORT_PROPERTY)) {
+                return (int) methodInvocation.instance[HOST_PORT_PROPERTY]
+            }
         } catch (ignored) {
-            // Grails throws an IllegalStateException about their annotation 
here.  We do not.
             log.info("no ${HOST_PORT_PROPERTY} from methodInvocation")
         }
 
         try {
-            // use geb config setting or default 8080
             Configuration gebConfig = new ConfigurationLoader().conf
             return (int) gebConfig.rawConfig.getProperty(HOST_PORT_PROPERTY)
         } catch (ignored) {
@@ -320,34 +278,25 @@ class WebDriverContainerHolder {
         return 8080
     }
 
-    /**
-     * Returns a baseUrl from a setting if found in this order:
-     * A class field 'baseUrl' if found in the invocation (Spec).
-     * A 'baseUrl' property in the GebConfig configuration.
-     * Or an empty String if not found.
-     * @param methodInvocation The method invocation.
-     * @return String The base URL.
-     */
     private static String findBaseUrl(IMethodInvocation methodInvocation) {
-        // use the test class baseUrl if specified
+        // Use getMetaProperty to check for a real declared property, not 
propertyMissing
+        // (which @DynamicallyDispatchesToBrowser would intercept)
         try {
-            String baseUrl =  methodInvocation.instance.metaClass.getProperty(
-                    methodInvocation.instance,
-                    BASE_URL_PROPERTY
-            )
-            if (baseUrl) {
-                log.info("using ${BASE_URL_PROPERTY}: ${baseUrl} from method 
invocation.")
-                return baseUrl
+            if 
(methodInvocation.instance.metaClass.getMetaProperty(BASE_URL_PROPERTY)) {
+                String baseUrl = methodInvocation.instance[BASE_URL_PROPERTY]
+                if (baseUrl) {
+                    log.info("using ${BASE_URL_PROPERTY}: ${baseUrl} from 
method invocation.")
+                    return baseUrl
+                }
             }
         } catch (ignored) {
             log.info("no ${BASE_URL_PROPERTY} from methodInvocation")
         }
 
         try {
-            // use geb config setting or default
             Configuration gebConfig = new ConfigurationLoader().conf
-            String baseUrl = gebConfig.getProperty(BASE_URL_PROPERTY) ?: ""
-            if (baseUrl != "") {
+            String baseUrl = gebConfig.getProperty(BASE_URL_PROPERTY) ?: ''
+            if (baseUrl) {
                 log.info("using ${BASE_URL_PROPERTY}: ${baseUrl} from 
configuration.")
                 return baseUrl
             }
@@ -356,7 +305,7 @@ class WebDriverContainerHolder {
         }
 
         log.info("no configured ${BASE_URL_PROPERTY} found.")
-        return ""
+        return ''
     }
 
     private static Browser createBrowser(Configuration gebConf) {
@@ -375,37 +324,8 @@ class WebDriverContainerHolder {
         browser
     }
 
-    private static void applyFileDetector(Browser browser, 
WebDriverContainerConfiguration conf) {
-        if (conf.fileDetector != NullContainerFileDetector) {
-            ServiceRegistry.setInstance(ContainerFileDetector, 
conf.fileDetector)
-        }
-        ((RemoteWebDriver) browser.driver).fileDetector = 
ServiceRegistry.getInstance(
-                ContainerFileDetector,
-                DefaultContainerFileDetector
-        )
-    }
-
-    private static void applyTimeouts(Browser browser, GebContainerSettings 
settings) {
-        // Overwrite `GebConfig` timeouts with values explicitly set in
-        // `GebContainerSettings` (via system properties)
-        if (settings.implicitlyWait != DEFAULT_TIMEOUT_IMPLICITLY_WAIT) {
-            
browser.driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(settings.implicitlyWait))
-        }
-        if (settings.pageLoadTimeout != DEFAULT_TIMEOUT_PAGE_LOAD) {
-            
browser.driver.manage().timeouts().pageLoadTimeout(Duration.ofSeconds(settings.pageLoadTimeout))
-        }
-        if (settings.scriptTimeout != DEFAULT_TIMEOUT_SCRIPT) {
-            
browser.driver.manage().timeouts().scriptTimeout(Duration.ofSeconds(settings.scriptTimeout))
-        }
-        if (settings.atCheckWaiting != DEFAULT_AT_CHECK_WAITING) {
-            browser.config.atCheckWaiting = settings.atCheckWaiting
-        }
-        if (settings.timeout != Wait.DEFAULT_TIMEOUT) {
-            (browser.config.rawConfig.waiting as ConfigObject).timeout = 
settings.timeout
-        }
-        if (settings.retryInterval != Wait.DEFAULT_RETRY_INTERVAL) {
-            (browser.config.rawConfig.waiting as ConfigObject).retryInterval = 
settings.retryInterval
-        }
+    private static void applyFileDetector(Browser browser, 
SpecContainerConfiguration conf) {
+        ((RemoteWebDriver) browser.driver).fileDetector = 
conf.fileDetector.getDeclaredConstructor().newInstance()
     }
 
     private static void startContainer(BrowserWebDriverContainer container, 
DockerImageName dockerImageName, String customBrowser) {
@@ -426,12 +346,8 @@ class WebDriverContainerHolder {
     }
 
     private static String getHostIp() {
-        // In CI environments (container-in-container),
-        // withAccessToHost is disabled and PortForwardingContainer is not 
used.
-        // Get the actual IP address of this container so other containers can 
reach it.
         if (System.getenv(CI_PROPERTY)) {
             try {
-                // Execute hostname -I to get the container's IP address
                 def process = 'hostname -I'.execute()
                 process.waitFor()
                 def ip = process.text.trim().split(/\s+/)[0]
@@ -441,7 +357,6 @@ class WebDriverContainerHolder {
             } catch (Exception e) {
                 log.warn('Failed to get container IP via hostname -I', e)
             }
-            // Fallback to localhost
             return LOCALHOST_IP
         }
 
@@ -494,103 +409,38 @@ class WebDriverContainerHolder {
     }
 
     private boolean isHostnameChanged() {
-        containerConf.hostName != 
ContainerGebConfiguration.DEFAULT_HOSTNAME_FROM_CONTAINER
+        containerConf.hostName != SpecContainerConfiguration.DEFAULT_HOSTNAME
     }
 
     private boolean isHostNameChanged() {
-        container.host != 
ContainerGebConfiguration.DEFAULT_HOSTNAME_FROM_CONTAINER
+        container.host != SpecContainerConfiguration.DEFAULT_HOSTNAME
     }
 
     @CompileStatic
     @EqualsAndHashCode
-    private static class WebDriverContainerConfiguration {
+    @PackageScope
+    static class SpecContainerConfiguration {
+
+        static final String DEFAULT_PROTOCOL = 'http'
+        static final String DEFAULT_HOSTNAME = 
GenericContainer.INTERNAL_HOST_HOSTNAME
+        static final Class<? extends ContainerFileDetector> 
DEFAULT_FILE_DETECTOR = DefaultContainerFileDetector
 
         String protocol
         String hostName
         boolean reporting
         Class<? extends ContainerFileDetector> fileDetector
 
-        WebDriverContainerConfiguration(SpecInfo spec) {
-            ContainerGebConfiguration conf
+        SpecContainerConfiguration(SpecInfo spec) {
+            ContainerGebConfiguration conf = null
 
-            // Check if the class implements the interface
-            if (IContainerGebConfiguration.isAssignableFrom(spec.reflection)) {
+            if (ContainerGebConfiguration.isAssignableFrom(spec.reflection)) {
                 conf = spec.reflection.getConstructor().newInstance() as 
ContainerGebConfiguration
-            } else {
-                // Check for the annotation
-                conf = spec.annotations.find {
-                    it.annotationType() == ContainerGebConfiguration
-                } as ContainerGebConfiguration
             }
 
-            protocol = conf?.protocol() ?: 
ContainerGebConfiguration.DEFAULT_PROTOCOL
-            hostName = conf?.hostName() ?: 
ContainerGebConfiguration.DEFAULT_HOSTNAME_FROM_CONTAINER
+            protocol = conf?.protocol() ?: DEFAULT_PROTOCOL
+            hostName = conf?.hostName() ?: DEFAULT_HOSTNAME
             reporting = conf?.reporting() ?: false
-            fileDetector = conf?.fileDetector() ?: 
ContainerGebConfiguration.DEFAULT_FILE_DETECTOR
+            fileDetector = conf?.fileDetector() ?: DEFAULT_FILE_DETECTOR
         }
     }
-
-    @CompileStatic
-    private static class ClosureDecorators {
-
-        /**
-         * Wraps a closure so that during its execution, 
System.getProperty(key)
-         * returns a custom value instead of what is actually in the system 
properties.
-         */
-        static Closure withSystemProperty(Closure target, String key, Object 
value) {
-            Closure wrapped = { Object... args ->
-                SysPropScope.withProperty(key, value.toString()) {
-                    InvokerHelper.invokeClosure(target, args)
-                }
-            }
-
-            // keep original closure semantics
-            wrapped.rehydrate(target.delegate, target.owner, 
target.thisObject).tap {
-                resolveStrategy = target.resolveStrategy
-            }
-        }
-
-        @CompileStatic
-        private static class SysPropScope {
-
-            private static final ThreadLocal<Map<String,String>> 
OVERRIDDEN_SYSTEM_PROPERTIES =
-                ThreadLocal.withInitial { [:] } as ThreadLocal<Map<String, 
String>>
-
-            @Lazy // Thread-safe wrapping of system properties
-            private static Properties propertiesWrappedOnFirstAccess = {
-                new InterceptingProperties().tap {
-                    putAll(System.getProperties())
-                    System.setProperties(it)
-                }
-            }()
-
-            static <T> T withProperty(String key, String value, Closure<T> 
body) {
-                propertiesWrappedOnFirstAccess // Access property to trigger 
property wrapping
-                def map = OVERRIDDEN_SYSTEM_PROPERTIES.get()
-                def prev = map.put(key, value)
-                try {
-                    return body.call()
-                } finally {
-                    if (prev == null) {
-                        map.remove(key)
-                    } else {
-                        map[key] = prev
-                    }
-                    if (map.isEmpty()) {
-                        OVERRIDDEN_SYSTEM_PROPERTIES.remove()
-                    }
-                }
-            }
-
-            @CompileStatic
-            private static class InterceptingProperties extends Properties {
-                @Override
-                String getProperty(String key) {
-                    def v = OVERRIDDEN_SYSTEM_PROPERTIES.get().get(key)
-                    v != null ? v : super.getProperty(key)
-                }
-            }
-        }
-    }
-
 }
diff --git 
a/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/serviceloader/ServiceRegistry.groovy
 
b/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/serviceloader/ServiceRegistry.groovy
deleted file mode 100644
index 6abb58ab..00000000
--- 
a/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/serviceloader/ServiceRegistry.groovy
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    https://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- */
-package geb.testcontainers.serviceloader
-
-import groovy.transform.CompileStatic
-
-/**
- * A service registry that loads and caches different service types using the 
Java
- * {@link java.util.ServiceLoader}, while allowing overriding which instance 
to return.
- * <p>
- * This class provides thread-safe service loading and caching, supporting 
parallel test execution.
- * It provides both automatic service discovery through {@link 
java.util.ServiceLoader}
- * and explicit replacement of the returned service instance for customization.
- * </p>
- * <p>
- * Usage example:
- * <pre><code>
- * MyService service = ServiceRegistry.getInstance(MyService, DefaultMyService)
- * </code></pre>
- * </p>
- *
- * @since 4.2
- * @author Mattias Reichel
- */
-@CompileStatic
-class ServiceRegistry {
-
-    private static final ThreadLocal<HashMap<Class<?>, Object>> INSTANCES = 
ThreadLocal.withInitial {
-        [:]
-    } as ThreadLocal<HashMap<Class<?>, Object>>
-
-    /**
-     * Returns the service instance of the given service type, loading it using
-     * {@link java.util.ServiceLoader} if not already loaded or an instance
-     * of the default implementation type if no service implementation is found
-     * by the {@link java.util.ServiceLoader}.
-     *
-     * If an instance has been set using {@link #setInstance(Class, Object)} or
-     * {@link #setInstance(Class, Class)}, that instance will be returned 
instead.
-     *
-     * @param serviceType The service type
-     * @param defaultType The service implementation type to use if no service
-     * implementation is found (Must have a zero-argument constructor)
-     * @return An instance of the service type
-     */
-    static <T> T getInstance(Class<T> serviceType, Class<? extends T> 
defaultType) {
-        (T) INSTANCES.get().computeIfAbsent(serviceType) {
-            ServiceLoader.load(serviceType)
-                    .findFirst()
-                    .orElseGet { 
defaultType.getDeclaredConstructor().newInstance() }
-        }
-    }
-
-    /**
-     * Sets the instance to be returned for the given service type, bypassing 
instance
-     * loading from {@link java.util.ServiceLoader}.
-     * <p>
-     * Setting the instance to {@code null} will revert to loading the the 
service instance
-     * via the {@link java.util.ServiceLoader}.
-     * </p>
-     * @param serviceType The service type for which the instance should be set
-     * @param instance The instance to return for the given service type, or
-     * {@code null} for default service loading
-     */
-    static <T> void setInstance(Class<T> serviceType, T instance) {
-        INSTANCES.get().put(serviceType, instance)
-    }
-
-    /**
-     * Sets the implementation type to return for the given service type, 
bypassing instance loading
-     * from {@link java.util.ServiceLoader}.
-     *
-     * @param serviceType The service type for which the instance should be set
-     * @param instanceType The type of the instance to return for the given 
service type.
-     * (Must have a zero-argument constructor).
-     */
-    static <T> void setInstance(Class<T> serviceType, Class<? extends T> 
instanceType) {
-        setInstance(serviceType, 
instanceType.getDeclaredConstructor().newInstance())
-    }
-}
diff --git 
a/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/support/BrowserType.groovy
 
b/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/support/BrowserType.groovy
deleted file mode 100644
index 4ab4de73..00000000
--- 
a/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/support/BrowserType.groovy
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    https://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- */
-package geb.testcontainers.support
-
-/**
- * Represents the supported browser types.
- *
- * <p>This enum defines the available browser options that can be used.</p>
- */
-enum BrowserType {
-
-    /**
-     * Google Chrome browser.
-     */
-
-    CHROME,
-    /**
-     * Mozilla Firefox browser.
-     */
-    FIREFOX,
-
-    /**
-     * Microsoft Edge browser.
-     */
-    EDGE
-}
\ No newline at end of file
diff --git 
a/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/support/ContainerSupport.groovy
 
b/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/support/ContainerSupport.groovy
deleted file mode 100644
index 82d53194..00000000
--- 
a/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/support/ContainerSupport.groovy
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    https://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- */
-package geb.testcontainers.support
-
-import geb.download.DownloadSupport
-import geb.testcontainers.ContainerGebSpec
-import groovy.transform.CompileStatic
-import groovy.transform.SelfType
-import org.testcontainers.containers.BrowserWebDriverContainer
-import org.testcontainers.images.builder.Transferable
-import spock.lang.Shared
-
-/**
- * Features for supporting Geb tests running in a container.
- *
- * @author Mattias Reichel
- * @since 4.2
- */
-@CompileStatic
-@SelfType(ContainerGebSpec)
-trait ContainerSupport implements DownloadSupport {
-
-    /**
-     * Get access to container running the web-driver, for convenience to 
execInContainer, copyFileToContainer etc.
-     *
-     * @see 
org.testcontainers.containers.ContainerState#execInContainer(java.lang.String 
...)
-     * @see 
org.testcontainers.containers.ContainerState#copyFileToContainer(org.testcontainers.utility.MountableFile,
 java.lang.String)
-     * @see 
org.testcontainers.containers.ContainerState#copyFileFromContainer(java.lang.String,
 java.lang.String)
-     * @see org.testcontainers.containers.ContainerState
-     */
-    @Shared
-    static BrowserWebDriverContainer container
-
-    static void setContainer(BrowserWebDriverContainer container) {
-        this.container = container
-    }
-
-    @Shared
-    static DownloadSupport downloadSupport
-
-    /**
-     * Sets the {@link DownloadSupport} instance to use for file downloads.
-     * This allows for setting a custom implementation of {@code 
DownloadSupport}
-     * when downloading from a container.
-     *
-     * @param downloadSupport the {@code DownloadSupport} instance to use
-     * @since 4.1
-     */
-    static void setDownloadSupport(DownloadSupport downloadSupport) {
-        this.downloadSupport = downloadSupport
-    }
-
-    /**
-     * 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
-     * @return the file object to assign to the FileInput module
-     * @since 4.2
-     */
-    File createFileInputSource(String hostPath, String containerPath) {
-        container.copyFileToContainer(Transferable.of(new 
File(hostPath).bytes), containerPath)
-        return new ContainerGebFileInputSource(containerPath)
-    }
-}
\ No newline at end of file
diff --git 
a/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/support/ReportingSupport.groovy
 
b/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/support/ReportingSupport.groovy
deleted file mode 100644
index 98267bff..00000000
--- 
a/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/support/ReportingSupport.groovy
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    https://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- */
-package geb.testcontainers.support
-
-import geb.report.CompositeReporter
-import geb.report.PageSourceReporter
-import geb.report.Reporter
-import geb.report.ScreenshotReporter
-import geb.testcontainers.ContainerGebSpec
-import groovy.transform.CompileStatic
-import groovy.transform.SelfType
-
-/**
- * Enables reporting support for ContainerGebSpec.
- *
- * @author James Daugherty
- * @author Mattias Reichel
- * @since 4.2
- */
-@CompileStatic
-@SelfType(ContainerGebSpec)
-trait ReportingSupport {
-
-    void report(String message) {
-        ContainerGebSpec.testManager.report(message)
-    }
-
-    /**
-     * The reporter that Geb should use when reporting is enabled.
-     */
-    Reporter createReporter() {
-        return new CompositeReporter(new PageSourceReporter(), new 
ScreenshotReporter())
-    }
-}
diff --git 
a/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/support/delegate/BrowserDelegate.groovy
 
b/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/support/delegate/BrowserDelegate.groovy
deleted file mode 100644
index db4893e0..00000000
--- 
a/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/support/delegate/BrowserDelegate.groovy
+++ /dev/null
@@ -1,429 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    https://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- */
-package geb.testcontainers.support.delegate
-
-import geb.Browser
-import geb.Page
-import geb.js.JavascriptInterface
-import geb.url.UrlFragment
-import geb.webstorage.WebStorage
-import geb.testcontainers.ContainerGebSpec
-import groovy.transform.CompileDynamic
-import groovy.transform.CompileStatic
-import groovy.transform.SelfType
-import org.openqa.selenium.WebDriver
-
-import java.time.Duration
-
-import static groovy.lang.Closure.DELEGATE_FIRST
-
-/**
- * Handles delegation to the browser instance so that the Geb API can be used 
directly in the test.
- * <p>
- * As method parameter names are not available in the Geb artifacts we are 
delegating manually,
- * instead of using @Delegate AST transform, to get the best possible end user 
IDE support and user experience.
- *
- * @author Mattias Reichel
- * @since 4.2
- */
-@CompileStatic
-@SelfType(ContainerGebSpec)
-trait BrowserDelegate {
-
-    /**
-     * Accessor to the Geb {@link geb.Browser} instance.
-     */
-    Browser getBrowser() {
-        ContainerGebSpec.testManager.browser
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#getDriver()}.
-     */
-    WebDriver getDriver() {
-        browser.driver
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#getCurrentUrl()}.
-     */
-    String getCurrentUrl() {
-        browser.currentUrl
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#getPage()}.
-     */
-    Page getPage() {
-        browser.page
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#at(Class)}.
-     */
-    <T extends Page> T at(Class<T> pageType) {
-        browser.at(pageType)
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#at(Class, Closure)}.
-     */
-    <T extends Page, R> R at(Class<T> pageType, @DelegatesTo(strategy = 
DELEGATE_FIRST, genericTypeIndex = 0) Closure<R> assertions) {
-        browser.at(at(pageType), assertions)
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#at(Page)}.
-     */
-    <T extends Page> T at(T page) {
-        browser.at(page)
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#isAt(Class, boolean)}.
-     */
-    boolean isAt(Class<? extends Page> pageType, boolean 
honourGlobalAtCheckWaiting = true) {
-        browser.isAt(pageType, honourGlobalAtCheckWaiting)
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#isAt(Page, boolean)}.
-     */
-    boolean isAt(Page page, boolean honourGlobalAtCheckWaiting = true) {
-        browser.isAt(page, honourGlobalAtCheckWaiting)
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#checkIfAtAnUnexpectedPage(Class[])}.
-     */
-    void checkIfAtAnUnexpectedPage(Class<? extends Page>[] expectedPages) {
-        browser.checkIfAtAnUnexpectedPage(expectedPages)
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#checkIfAtAnUnexpectedPage(Page[])}.
-     */
-    void checkIfAtAnUnexpectedPage(Page[] expectedPages) {
-        browser.checkIfAtAnUnexpectedPage(expectedPages)
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#go(String)}.
-     */
-    void go(String url) {
-        browser.go(url)
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#go(String, UrlFragment)}.
-     */
-    void go(String url, UrlFragment fragment) {
-        browser.go(url, fragment)
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#go(Map, UrlFragment)}.
-     */
-    void go(Map params = [:], UrlFragment fragment) {
-        browser.go(params, fragment)
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#go(Map, String, UrlFragment)}.
-     */
-    void go(Map params = [:], String url = null, UrlFragment fragment = null) {
-        browser.go(params, url, fragment)
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#to(Map, Class, Object[])}.
-     */
-    <T extends Page> T to(Map params = [:], Class<T> pageType, Object[] args) {
-        browser.to(params, pageType, args)
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#to(Map, Class, UrlFragment, Object[])}.
-     */
-    <T extends Page> T to(Map params = [:], Class<T> pageType, UrlFragment 
fragment, Object[] args) {
-        browser.to(params, pageType, fragment, args)
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#to(Map, Page, Object[])}.
-     */
-    <T extends Page> T to(Map params = [:], T page, Object[] args) {
-        browser.to(params, page, args)
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#to(Map, Page, UrlFragment, Object[])}.
-     */
-    <T extends Page> T to(Map params = [:], T page, UrlFragment fragment, 
Object[] args) {
-        browser.to(params, page, fragment, args)
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#via(Map, Class, Object[])}.
-     */
-    <T extends Page> T via(Map params = [:], Class<T> pageType, Object[] args) 
{
-        browser.via(params, pageType, args)
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#via(Map, Class, UrlFragment, Object[])}.
-     */
-    <T extends Page> T via(Map params = [:], Class<T> pageType, UrlFragment 
fragment, Object[] args) {
-        browser.via(params, pageType, fragment, args)
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#via(Map, Page, Object[])}.
-     */
-    <T extends Page> T via(Map params = [:], T page, Object[] args) {
-        browser.via(params, page, args)
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#via(Map, Page, UrlFragment, Object[])}.
-     */
-    <T extends Page> T via(Map params = [:], T page, UrlFragment fragment, 
Object[] args) {
-        browser.via(params, page, fragment, args)
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#clearCookies(String[])}.
-     */
-    void clearCookies(String... additionalUrls) {
-        browser.clearCookies(additionalUrls)
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#clearCookies()}.
-     */
-    void clearCookies() {
-        browser.clearCookies()
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#clearCookiesQuietly()}.
-     */
-    void clearCookiesQuietly() {
-        browser.clearCookiesQuietly()
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#clearWebStorage()}.
-     */
-    void clearWebStorage() {
-        browser.clearWebStorage()
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#clearWebStorageQuietly()}.
-     */
-    void clearWebStorageQuietly() {
-        browser.clearWebStorageQuietly()
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#quit()}.
-     */
-    void quit() {
-        browser.quit()
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#close()}.
-     */
-    void close() {
-        browser.close()
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#getAvailableWindows()}.
-     */
-    Set<String> getAvailableWindows() {
-        browser.getAvailableWindows()
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#withWindow(String, Closure)}.
-     */
-    <T> T withWindow(String window, @DelegatesTo(value = Browser, strategy = 
DELEGATE_FIRST) Closure<T> block) {
-        browser.withWindow(window, block)
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#withWindow(Closure, Closure)}.
-     */
-    <T> List<T> withWindow(Closure specification, @DelegatesTo(value = 
Browser, strategy = DELEGATE_FIRST) Closure<T> block) {
-        browser.withWindow(specification, block)
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#withWindow(Map, Closure, Closure)}.
-     */
-    <T> List<T> withWindow(Map options, Closure specification, 
@DelegatesTo(value = Browser, strategy = DELEGATE_FIRST) Closure<T> block) {
-        browser.withWindow(options, specification, block)
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#withWindow(Map, String, Closure)}.
-     */
-    <T> T withWindow(Map options, String window, @DelegatesTo(value = Browser, 
strategy = DELEGATE_FIRST) Closure<T> block) {
-        browser.withWindow(options, window, block)
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#withWindow(Map, Closure, Closure)}.
-     */
-    <T> T withNewWindow(Map options, Closure windowOpeningBlock, 
@DelegatesTo(value = Browser, strategy = DELEGATE_FIRST) Closure<T> block) {
-        browser.withNewWindow(options, windowOpeningBlock, block)
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#withNewWindow(Closure, Closure)}.
-     */
-    <T> T withNewWindow(Closure windowOpeningBlock, @DelegatesTo(value = 
Browser, strategy = DELEGATE_FIRST) Closure<T> block) {
-        browser.withNewWindow(windowOpeningBlock, block)
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#createPage(Class)}.
-     */
-    <T extends Page> T createPage(Class<T> pageType) {
-        browser.createPage(pageType)
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#getJs()}.
-     */
-    JavascriptInterface getJs() {
-        browser.js
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#getReportGroupDir()}.
-     */
-    File getReportGroupDir() {
-        browser.reportGroupDir
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#reportGroup(String)}.
-     */
-    void reportGroup(String path) {
-        browser.reportGroup(path)
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#reportGroup(Class)}.
-     */
-    void reportGroup(Class clazz) {
-        browser.reportGroup(clazz)
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#cleanReportGroupDir()}.
-     */
-    void cleanReportGroupDir() {
-        browser.cleanReportGroupDir()
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#pause()}.
-     */
-    void pause() {
-        browser.pause()
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#getLocalStorage()}.
-     */
-    WebStorage getLocalStorage() {
-        browser.localStorage
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#getSessionStorage()}.
-     */
-    WebStorage getSessionStorage() {
-        browser.sessionStorage
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#verifyAtImplicitly(Class)}.
-     */
-    void verifyAtImplicitly(Class<? extends Page> targetPage) {
-        browser.verifyAtImplicitly(targetPage)
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#verifyAtImplicitly(Page)}.
-     */
-    void verifyAtImplicitly(Page targetPage) {
-        browser.verifyAtImplicitly(targetPage)
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#setNetworkLatency(Duration)}.
-     */
-    void setNetworkLatency(Duration duration) {
-        browser.networkLatency = duration
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#resetNetworkLatency()}.
-     */
-    void resetNetworkLatency() {
-        browser.resetNetworkLatency()
-    }
-
-    /**
-     * Delegates to {@link geb.Browser#driverAs(Class)}.
-     */
-    <T> Optional<T> driverAs(Class<T> castType) {
-        browser.driverAs(castType)
-    }
-
-    /**
-     * Delegates missing method calls to the current {@link geb.Page} instance.
-     */
-    @CompileDynamic
-    def methodMissing(String name, args) {
-        browser.page."$name"(*args)
-    }
-
-    /**
-     * Delegates missing property accesses to the current {@link geb.Page} 
instance.
-     */
-    @CompileDynamic
-    def propertyMissing(String name) {
-        browser.page."$name"
-    }
-
-    /**
-     * Delegates missing property mutations to the current {@link geb.Page} 
instance.
-     */
-    @CompileDynamic
-    def propertyMissing(String name, value) {
-        browser.page."$name" = value
-    }
-}
\ No newline at end of file
diff --git 
a/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/support/delegate/DownloadSupportDelegate.groovy
 
b/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/support/delegate/DownloadSupportDelegate.groovy
deleted file mode 100644
index d7740377..00000000
--- 
a/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/support/delegate/DownloadSupportDelegate.groovy
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    https://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- */
-package geb.testcontainers.support.delegate
-
-import geb.download.DownloadSupport
-import geb.testcontainers.ContainerGebSpec
-import groovy.transform.CompileStatic
-import groovy.transform.SelfType
-
-/**
- * Handles delegation to the DownloadSupport instance so that the Geb API can 
be used directly in the test.
- * <p>
- * As method parameter names are not available in the Geb artifacts we are 
delegating manually to
- * get the best possible IDE support and user experience.
- *
- * @author Mattias Reichel
- * @since 4.2
- */
-@CompileStatic
-@SelfType(ContainerGebSpec)
-trait DownloadSupportDelegate implements DownloadSupport {
-
-    @Override
-    HttpURLConnection download() {
-        ContainerGebSpec.downloadSupport.download()
-    }
-
-    @Override
-    HttpURLConnection download(Map options) {
-        ContainerGebSpec.downloadSupport.download(options)
-    }
-
-    @Override
-    HttpURLConnection download(String uri) {
-        ContainerGebSpec.downloadSupport.download(uri)
-    }
-
-    @Override
-    InputStream downloadStream() {
-        ContainerGebSpec.downloadSupport.downloadStream()
-    }
-
-    @Override
-    InputStream downloadStream(Map options) {
-        ContainerGebSpec.downloadSupport.downloadStream(options)
-    }
-
-    @Override
-    InputStream downloadStream(Map options, Closure connectionConfig) {
-        ContainerGebSpec.downloadSupport.downloadStream(options, 
connectionConfig)
-    }
-
-    @Override
-    InputStream downloadStream(String uri) {
-        ContainerGebSpec.downloadSupport.downloadStream(uri)
-    }
-
-    @Override
-    InputStream downloadStream(String uri, Closure connectionConfig) {
-        ContainerGebSpec.downloadSupport.downloadStream(uri, connectionConfig)
-    }
-
-    @Override
-    InputStream downloadStream(Closure connectionConfig) {
-        ContainerGebSpec.downloadSupport.downloadStream(connectionConfig)
-    }
-
-    @Override
-    String downloadText() {
-        ContainerGebSpec.downloadSupport.downloadText()
-    }
-
-    @Override
-    String downloadText(Map options) {
-        ContainerGebSpec.downloadSupport.downloadText(options)
-    }
-
-    @Override
-    String downloadText(Map options, Closure connectionConfig) {
-        ContainerGebSpec.downloadSupport.downloadText(options, 
connectionConfig)
-    }
-
-    @Override
-    String downloadText(String uri) {
-        ContainerGebSpec.downloadSupport.downloadText(uri)
-    }
-
-    @Override
-    String downloadText(String uri, Closure connectionConfig) {
-        ContainerGebSpec.downloadSupport.downloadText(uri, connectionConfig)
-    }
-
-    @Override
-    String downloadText(Closure connectionConfig) {
-        ContainerGebSpec.downloadSupport.downloadText(connectionConfig)
-    }
-
-    @Override
-    byte[] downloadBytes() {
-        ContainerGebSpec.downloadSupport.downloadBytes()
-    }
-
-    @Override
-    byte[] downloadBytes(Map options) {
-        ContainerGebSpec.downloadSupport.downloadBytes(options)
-    }
-
-    @Override
-    byte[] downloadBytes(Map options, Closure connectionConfig) {
-        ContainerGebSpec.downloadSupport.downloadBytes(options, 
connectionConfig)
-    }
-
-    @Override
-    byte[] downloadBytes(Closure connectionConfig) {
-        ContainerGebSpec.downloadSupport.downloadBytes(connectionConfig)
-    }
-
-    @Override
-    byte[] downloadBytes(String uri) {
-        ContainerGebSpec.downloadSupport.downloadBytes(uri)
-    }
-
-    @Override
-    byte[] downloadBytes(String uri, Closure connectionConfig) {
-        ContainerGebSpec.downloadSupport.downloadBytes(uri, connectionConfig)
-    }
-
-    @Override
-    Object downloadContent() {
-        ContainerGebSpec.downloadSupport.downloadContent()
-    }
-
-    @Override
-    Object downloadContent(Map options) {
-        ContainerGebSpec.downloadSupport.downloadContent(options)
-    }
-
-    @Override
-    Object downloadContent(Map options, Closure connectionConfig) {
-        ContainerGebSpec.downloadSupport.downloadContent(options, 
connectionConfig)
-    }
-
-    @Override
-    Object downloadContent(String uri) {
-        ContainerGebSpec.downloadSupport.downloadContent(uri)
-    }
-
-    @Override
-    Object downloadContent(String uri, Closure connectionConfig) {
-        ContainerGebSpec.downloadSupport.downloadContent(uri, connectionConfig)
-    }
-
-    @Override
-    Object downloadContent(Closure connectionConfig) {
-        ContainerGebSpec.downloadSupport.downloadContent(connectionConfig)
-    }
-}
\ No newline at end of file
diff --git 
a/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/support/delegate/DriverDelegate.groovy
 
b/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/support/delegate/DriverDelegate.groovy
deleted file mode 100644
index 05af54c0..00000000
--- 
a/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/support/delegate/DriverDelegate.groovy
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    https://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- */
-package geb.testcontainers.support.delegate
-
-import geb.testcontainers.ContainerGebSpec
-import groovy.transform.CompileStatic
-import groovy.transform.SelfType
-
-/**
- * Handles delegation to select methods of the driver instance for end user 
convenience.
- *
- * @author Mattias Reichel
- * @since 4.2
- */
-@CompileStatic
-@SelfType(ContainerGebSpec)
-trait DriverDelegate {
-
-    /**
-     * Get the source of the last loaded page. If the page has been modified 
after loading (for
-     * example, by Javascript) there is no guarantee that the returned text is 
that of the modified
-     * page. Please consult the documentation of the particular driver being 
used to determine whether
-     * the returned text reflects the current state of the page or the text 
last sent by the web
-     * server. The page source returned is a representation of the underlying 
DOM: do not expect it to
-     * be formatted or escaped in the same way as the response sent from the 
web server. Think of it
-     * as an artist's impression.
-     *
-     * <p>See <a href="https://w3c.github.io/webdriver/#get-page-source";>W3C 
WebDriver
-     * specification</a> for more details.
-     *
-     * @return The source of the current page
-     */
-    String getPageSource() {
-        ContainerGebSpec.testManager.browser.driver.pageSource
-    }
-
-}
\ No newline at end of file
diff --git 
a/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/support/delegate/PageDelegate.groovy
 
b/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/support/delegate/PageDelegate.groovy
deleted file mode 100644
index bafba5cf..00000000
--- 
a/integration/geb-testcontainers/src/main/groovy/geb/testcontainers/support/delegate/PageDelegate.groovy
+++ /dev/null
@@ -1,415 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    https://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- */
-package geb.testcontainers.support.delegate
-
-import geb.Module
-import geb.Page
-import geb.content.Navigable
-import geb.content.TemplateDerivedPageContent
-import geb.frame.FrameSupport
-import geb.interaction.InteractDelegate
-import geb.interaction.InteractionsSupport
-import geb.js.AlertAndConfirmSupport
-import geb.navigator.Navigator
-import geb.textmatching.TextMatchingSupport
-import geb.url.UrlFragment
-import geb.waiting.WaitingSupport
-import geb.testcontainers.ContainerGebSpec
-import groovy.transform.CompileStatic
-import groovy.transform.SelfType
-import org.openqa.selenium.By
-import org.openqa.selenium.WebElement
-
-/**
- * Handles delegation to the page instance so that the Geb API can be used 
directly in the test.
- * <p>
- * As method parameter names are not available in the Geb artifacts we are 
delegating manually to
- * get the best possible IDE support and user experience.
- *
- * @author Mattias Reichel
- * @since 4.2
- */
-@CompileStatic
-@SelfType(ContainerGebSpec)
-trait PageDelegate implements Navigable, AlertAndConfirmSupport, 
WaitingSupport, FrameSupport, InteractionsSupport {
-
-    @Delegate
-    private final TextMatchingSupport textMatchingSupport = new 
TextMatchingSupport()
-
-    Page getPage() {
-        ContainerGebSpec.testManager.browser.page
-    }
-
-    void to(Map params, UrlFragment fragment = null, Object[] args) {
-        page.to(params, fragment, args)
-    }
-
-    UrlFragment getPageFragment() {
-        page.pageFragment
-    }
-
-    String getPageUrl(String path) {
-        page.getPageUrl(path)
-    }
-
-    String getTitle() {
-        page.title
-    }
-
-    <T> T refreshWaitFor(Map params = [:], Closure<T> block) {
-        page.refreshWaitFor(params, block)
-    }
-
-    <T> T refreshWaitFor(Map params = [:], String waitPreset, Closure<T> 
block) {
-        page.refreshWaitFor(params, waitPreset, block)
-    }
-
-    <T> T refreshWaitFor(Map params = [:], Number timeoutSeconds, Closure<T> 
block) {
-        page.refreshWaitFor(params, timeoutSeconds, block)
-    }
-
-    <T> T refreshWaitFor(Map params = [:], Number timeoutSeconds, Number 
intervalSeconds, Closure<T> block) {
-        page.refreshWaitFor(params, timeoutSeconds, intervalSeconds, block)
-    }
-
-    @Override
-    Navigator find() {
-        page.find()
-    }
-
-    @Override
-    Navigator $() {
-        page.$()
-    }
-
-    @Override
-    Navigator find(int index) {
-        page.find(index)
-    }
-
-    @Override
-    Navigator find(Range<Integer> range) {
-        page.find(range)
-    }
-
-    @Override
-    Navigator $(int index) {
-        page.$(index)
-    }
-
-    @Override
-    Navigator $(Range<Integer> range) {
-        page.$(range)
-    }
-
-    @Override
-    Navigator $(Navigator... navigators) {
-        page.$(navigators)
-    }
-
-    @Override
-    Navigator $(WebElement... elements) {
-        page.$(elements)
-    }
-
-    @Override
-    Navigator focused() {
-        page.focused()
-    }
-
-    @Override
-    <T extends Module> T module(Class<T> moduleClass) {
-        page.module(moduleClass)
-    }
-
-    @Override
-    <T extends Module> T module(T module) {
-        page.module(module)
-    }
-
-    @Override
-    Navigator find(String selector) {
-        page.find(selector)
-    }
-
-    @Override
-    Navigator $(String selector) {
-        page.$(selector)
-    }
-
-    @Override
-    Navigator find(Map<String, Object> attributes) {
-        page.find(attributes)
-    }
-
-    @Override
-    Navigator find(String selector, int index) {
-        page.find(selector, index)
-    }
-
-    @Override
-    Navigator find(String selector, Range<Integer> range) {
-        page.find(selector, range)
-    }
-
-    @Override
-    Navigator $(String selector, int index) {
-        page.$(selector, index)
-    }
-
-    @Override
-    Navigator $(String selector, Range<Integer> range) {
-        page.$(selector, range)
-    }
-
-    @Override
-    Navigator $(Map<String, Object> attributes, By bySelector) {
-        page.$(attributes, bySelector)
-    }
-
-    @Override
-    Navigator $(Map<String, Object> attributes, By bySelector, int index) {
-        page.$(attributes, bySelector, index)
-    }
-
-    @Override
-    Navigator $(Map<String, Object> attributes, By bySelector, Range<Integer> 
range) {
-        page.$(attributes, bySelector, range)
-    }
-
-    @Override
-    Navigator $(By bySelector) {
-        page.$(bySelector)
-    }
-
-    @Override
-    Navigator $(By bySelector, int index) {
-        page.$(bySelector, index)
-    }
-
-    @Override
-    Navigator $(By bySelector, Range<Integer> range) {
-        page.$(bySelector, range)
-    }
-
-    @Override
-    Navigator find(By bySelector, Range<Integer> range) {
-        page.find(bySelector, range)
-    }
-
-    @Override
-    Navigator $(Map<String, Object> attributes) {
-        page.$(attributes)
-    }
-
-    @Override
-    Navigator $(Map<String, Object> attributes, int index) {
-        page.$(attributes, index)
-    }
-
-    @Override
-    Navigator $(Map<String, Object> attributes, Range<Integer> range) {
-        page.$(attributes, range)
-    }
-
-    @Override
-    Navigator $(Map<String, Object> attributes, String selector) {
-        page.$(attributes, selector)
-    }
-
-    @Override
-    Navigator $(Map<String, Object> attributes, String selector, int index) {
-        page.$(attributes, selector, index)
-    }
-
-    @Override
-    Navigator $(Map<String, Object> attributes, String selector, 
Range<Integer> range) {
-        page.$(attributes, selector, range)
-    }
-
-    @Override
-    Navigator find(By bySelector) {
-        page.find(bySelector)
-    }
-
-    @Override
-    Navigator find(By bySelector, int index) {
-        page.find(bySelector, index)
-    }
-
-    @Override
-    Navigator find(Map<String, Object> attributes, By bySelector) {
-        page.find(attributes, bySelector)
-    }
-
-    @Override
-    Navigator find(Map<String, Object> attributes, int index) {
-        page.find(attributes, index)
-    }
-
-    @Override
-    Navigator find(Map<String, Object> attributes, Range<Integer> range) {
-        page.find(attributes, range)
-    }
-
-    @Override
-    Navigator find(Map<String, Object> attributes, By bySelector, int index) {
-        page.find(attributes, bySelector, index)
-    }
-
-    @Override
-    Navigator find(Map<String, Object> attributes, By bySelector, 
Range<Integer> range) {
-        page.find(attributes, bySelector, range)
-    }
-
-    @Override
-    Navigator find(Map<String, Object> attributes, String selector) {
-        page.find(attributes, selector)
-    }
-
-    @Override
-    Navigator find(Map<String, Object> attributes, String selector, int index) 
{
-        page.find(attributes, selector, index)
-    }
-
-    @Override
-    Navigator find(Map<String, Object> attributes, String selector, 
Range<Integer> range) {
-        page.find(attributes, selector, range)
-    }
-
-    @Override
-    Object withAlert(Closure actions) {
-        page.withAlert(actions)
-    }
-
-    @Override
-    Object withAlert(Map params, Closure actions) {
-         page.withAlert(params, actions)
-    }
-
-    @Override
-    void withNoAlert(Closure actions) {
-        page.withNoAlert(actions)
-    }
-
-    @Override
-    Object withConfirm(boolean ok, Closure actions) {
-        page.withConfirm(ok, actions)
-    }
-
-    @Override
-    Object withConfirm(Closure actions) {
-        page.withConfirm(actions)
-    }
-
-    @Override
-    Object withConfirm(Map attributes, Closure actions) {
-        page.withConfirm(attributes, actions)
-    }
-
-    @Override
-    Object withConfirm(Map attributes, boolean ok, Closure actions) {
-        page.withConfirm(attributes, ok, actions)
-    }
-
-    @Override
-    void withNoConfirm(Closure actions) {
-        page.withNoConfirm(actions)
-    }
-
-    @Override
-    <T> T waitFor(String waitPreset, Closure<T> block) {
-        page.waitFor(waitPreset, block)
-    }
-
-    @Override
-    <T> T waitFor(Map params, String waitPreset, Closure<T> block) {
-        page.waitFor(params, waitPreset, block)
-    }
-
-    @Override
-    <T> T waitFor(Closure<T> block) {
-        page.waitFor(block)
-    }
-
-    @Override
-    <T> T waitFor(Map params, Closure<T> block) {
-        page.waitFor(params, block)
-    }
-
-    @Override
-    <T> T waitFor(Number timeoutSeconds, Closure<T> block) {
-        page.waitFor(timeoutSeconds, block)
-    }
-
-    @Override
-    <T> T waitFor(Map params, Number timeoutSeconds, Closure<T> block) {
-        page.waitFor(params, timeoutSeconds, block)
-    }
-
-    @Override
-    <T> T waitFor(Number timeoutSeconds, Number intervalSeconds, Closure<T> 
block) {
-        page.waitFor(timeoutSeconds, intervalSeconds, block)
-    }
-
-    @Override
-    <T> T waitFor(Map params, Number timeoutSeconds, Number intervalSeconds, 
Closure<T> block) {
-        page.waitFor(params, timeoutSeconds, intervalSeconds, block)
-    }
-
-    @Override
-    <T> T withFrame(Object frame, Closure<T> block) {
-        page.withFrame(frame, block)
-    }
-
-    @Override
-    <P extends Page, T> T withFrame(Object frame, @DelegatesTo.Target Class<P> 
page, @DelegatesTo(strategy = 1, genericTypeIndex = 0) Closure<T> block) {
-        this.page.withFrame(frame, page, block)
-    }
-
-    @Override
-    <P extends Page, T> T withFrame(Object frame, @DelegatesTo.Target P page, 
@DelegatesTo(strategy = 1) Closure<T> block) {
-        this.page.withFrame(frame, page, block)
-    }
-
-    @Override
-    <P extends Page, T> T withFrame(Navigator frame, @DelegatesTo.Target 
Class<P> page, @DelegatesTo(strategy = 1, genericTypeIndex = 0) Closure<T> 
block) {
-        this.page.withFrame(frame, page, block)
-    }
-
-    @Override
-    <P extends Page, T> T withFrame(Navigator frame, @DelegatesTo.Target P 
page, @DelegatesTo(strategy = 1) Closure<T> block) {
-        this.page.withFrame(frame, page, block)
-    }
-
-    @Override
-    <T> T withFrame(Navigator frame, Closure<T> block) {
-        page.withFrame(frame, block)
-    }
-
-    @Override
-    <T> T withFrame(TemplateDerivedPageContent frame, Closure<T> block) {
-        page.withFrame(frame, block)
-    }
-
-    @Override
-    void interact(@DelegatesTo(strategy = 1, value = InteractDelegate) Closure 
interactionClosure) {
-        page.interact(interactionClosure)
-    }
-}
\ No newline at end of file
diff --git 
a/integration/geb-testcontainers/src/main/resources/META-INF/services/org.spockframework.runtime.extension.IGlobalExtension
 
b/integration/geb-testcontainers/src/main/resources/META-INF/services/org.spockframework.runtime.extension.IGlobalExtension
index beedcb82..da620020 100644
--- 
a/integration/geb-testcontainers/src/main/resources/META-INF/services/org.spockframework.runtime.extension.IGlobalExtension
+++ 
b/integration/geb-testcontainers/src/main/resources/META-INF/services/org.spockframework.runtime.extension.IGlobalExtension
@@ -1 +1 @@
-geb.testcontainers.ContainerGebExtension
\ No newline at end of file
+geb.testcontainers.ContainerGebExtension

Reply via email to