You are viewing a plain text version of this content. The canonical link for it is here.
Posted to geb-notifications@groovy.apache.org by jo...@apache.org on 2026/04/10 18:54:03 UTC
(groovy-geb) 21/31: Refactor test containers to use base class pattern
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 <jc...@adaptavist.com>
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 {
+ * @Override
+ * String hostName() { 'testing.example.com' }
+ *
+ * @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