This is an automated email from the ASF dual-hosted git repository.
matrei pushed a commit to branch 7.0.x
in repository https://gitbox.apache.org/repos/asf/grails-core.git
The following commit(s) were added to refs/heads/7.0.x by this push:
new bcf4aa6f9c Geb 8 (#15067)
bcf4aa6f9c is described below
commit bcf4aa6f9cb14e3e372d40fab23a6bf06a09d694
Author: Mattias Reichel <[email protected]>
AuthorDate: Wed Sep 24 10:51:12 2025 +0200
Geb 8 (#15067)
* fix(deps): `geb` v. `7.0` -> `8.0.0`
* fix: adapt to changes in Geb 8
Adapt to changed driver creation logic in Geb 8.
* refactor(geb): simplifications
* docs(geb): improve comments and javadoc
* chore(geb): whitespace
* fix(geb): reset `webdriver.remote.server` after `RemoteWebDriver` init
Reinstate resetting the system property after driver construction.
* docs(geb): minor comment improvement
* fix(geb): prevent cross-thread leakage of `webdriver.remote.server`
Scope the `webdriver.remote.server` System property to the current thread
during `RemoteWebDriver` creation, then restore it. This avoids routing
later sessions to the wrong endpoint in parallel builds.
* fix(geb): allow testing with different browsers
This allows using different browsers in `GebConfig.groovy`
A `configuredBrowser` property has to be set that matches
the browser capabilities used to create the `RemoteWebDriver`
to start the correct container for the browser.
* chore(geb): formatting
* chore(geb): formatting
* refactor(geb): simplify system property override
* docs(geb): improve javadoc and code comments
* fix(geb): resolve trait method conflicts via explicit override
* refactor(geb): simplify
* chore(geb): formatting
* refactor(geb): simplify
* test(geb): update test label
* docs(geb): add `GebConfig` instructions to README
* fix(geb): make inner classes `static`
* feedback(geb): rename property to `containerBrowser`
* chore(geb): formatting
* fix(geb): add message for missing `containerBrowser`
Add a more helpful error message in the case where
a `containerBrowser` property is missing from `GebConfig.groovy`.
* test(geb): make tests more resilient in slow runners
* fix(feedback): skip `containerBrowser` validation
* refactor(geb): extract methods
* fix(geb): move `atCheckWaiting` to CI env variable
* fix(geb): feedback - remove container fallback
Throw exception when `driver` and `containerBrowser`
are mismatched in `GebConfig` instead of falling
back to chrome.
* fix(geb): system property setting of timeout values
* ci: update Geb `atCheckWaiting` system property
* chore(geb): formatting
* fix(geb): use correct `atCheckWaiting` system property
* fix(geb): feedback - use `BigDecimal` in `GrailsGebSettings`
* ci: feedback - set `atCheckWaiting` in build file
---
.github/workflows/gradle.yml | 1 +
DEVELOPMENT.md | 1 +
build.gradle | 1 +
dependencies.gradle | 4 +-
gradle/functional-test-config.gradle | 5 +
grails-doc/build.gradle | 2 +-
grails-geb/README.md | 40 +-
grails-geb/build.gradle | 4 +-
.../grails/plugin/geb/ContainerFileDetector.groovy | 3 +-
.../plugin/geb/ContainerGebConfiguration.groovy | 7 +-
.../grails/plugin/geb/ContainerGebSpec.groovy | 13 +-
.../plugin/geb/ContainerGebTestDescription.groovy | 5 +-
.../grails/plugin/geb/GebOnFailureReporter.groovy | 10 +-
.../plugin/geb/GebRecordingTestListener.groovy | 19 +-
.../plugin/geb/GrailsContainerGebExtension.groovy | 38 +-
.../grails/plugin/geb/GrailsGebSettings.groovy | 84 +++--
.../plugin/geb/WebDriverContainerHolder.groovy | 406 ++++++++++++++-------
grails-test-examples/geb-gebconfig/build.gradle | 1 +
.../groovy/org/demo/spock/GebConfigSpec.groovy | 4 +-
.../integration-test/resources/GebConfig.groovy | 39 +-
.../geb/grails-app/views/serverName/index.gsp | 9 +-
.../spock/ContainerFileDetectorDefaultSpec.groovy | 7 +-
.../spock/ContainerFileDetectorSpockSpec.groovy | 2 +-
.../org/demo/spock/DownloadSupportSpec.groovy | 4 +-
.../org/demo/spock/InheritedConfigSpec.groovy | 16 +-
.../groovy/org/demo/spock/PageDelegateSpec.groovy | 2 +-
.../org/demo/spock/PerTestRecordingSpec.groovy | 6 +-
.../groovy/org/demo/spock/RootPageSpec.groovy | 4 +-
.../org/demo/spock/ServerNameControllerSpec.groovy | 4 +-
.../groovy/org/demo/spock/UploadSpec.groovy | 12 +-
.../ServerNamePage.groovy} | 16 +-
.../UploadSuccessPage.groovy} | 15 +-
32 files changed, 508 insertions(+), 276 deletions(-)
diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml
index 1585dbd990..6a1fa73326 100644
--- a/.github/workflows/gradle.yml
+++ b/.github/workflows/gradle.yml
@@ -178,6 +178,7 @@ jobs:
--continue
--rerun-tasks
--stacktrace
+ -PgebAtCheckWaiting
-PonlyFunctionalTests
-PskipCodeStyle
-PskipHibernate5Tests
diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md
index 9399c7eaba..43541a0ce4 100644
--- a/DEVELOPMENT.md
+++ b/DEVELOPMENT.md
@@ -31,6 +31,7 @@ These can be set on the command line like so:
`./gradlew check -PskipCodeStyle`
+* `gebAtCheckWaiting` - enables Geb atCheckWaiting
* `onlyCoreTests` - runs tests that do not include mongo, hibernate, or
functional
* `onlyFunctionalTests` - runs only grails-test-examples/* tests
* `onlyHibernate5Tests` - runs only a hibernate5 related test
diff --git a/build.gradle b/build.gradle
index 4a96394b39..d1a9bae86c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -70,6 +70,7 @@ allprojects {
url = 'https://repository.apache.org/content/groups/staging'
content {
includeModuleByRegex('org[.]apache[.]grails[.]gradle',
'grails-publish')
+ includeModuleByRegex('org[.]apache[.]groovy[.]geb', 'geb-.*')
}
mavenContent {
releasesOnly()
diff --git a/dependencies.gradle b/dependencies.gradle
index 0dd451e4ce..cb50888b5a 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -76,7 +76,7 @@ ext {
'bootstrap-icons.version' : '1.13.1',
'bootstrap.version' : '5.3.7',
'commons-codec.version' : '1.18.0',
- 'geb-spock.version' : '7.0',
+ 'geb-spock.version' : '8.0.0',
'groovy.version' : '4.0.28',
'h2.version' : '2.3.232',
'jackson.version' : '2.19.1',
@@ -106,7 +106,7 @@ ext {
'bootstrap' :
"org.webjars.npm:bootstrap:${bomDependencyVersions['bootstrap.version']}",
'bootstrap-icons' :
"org.webjars.npm:bootstrap-icons:${bomDependencyVersions['bootstrap-icons.version']}",
'commons-codec' :
"commons-codec:commons-codec:${bomDependencyVersions['commons-codec.version']}",
- 'geb-spock' :
"org.gebish:geb-spock:${bomDependencyVersions['geb-spock.version']}",
+ 'geb-spock' :
"org.apache.groovy.geb:geb-spock:${bomDependencyVersions['geb-spock.version']}",
'h2' :
"com.h2database:h2:${bomDependencyVersions['h2.version']}",
'jquery' :
"org.webjars.npm:jquery:${bomDependencyVersions['jquery.version']}",
'liquibase-hibernate5' :
"org.liquibase:liquibase:${bomDependencyVersions['liquibase-hibernate5.version']}",
diff --git a/gradle/functional-test-config.gradle
b/gradle/functional-test-config.gradle
index 2ec6226495..2aa21d95a3 100644
--- a/gradle/functional-test-config.gradle
+++ b/gradle/functional-test-config.gradle
@@ -113,6 +113,11 @@ tasks.withType(Test).configureEach { Test task ->
if (System.getProperty('debug.tests')) {
task.jvmArgs += debugArguments
}
+
+ // Make Geb tests more resilient in slow CI environments
+ if (project.hasProperty('gebAtCheckWaiting')) {
+ systemProperty('grails.geb.atCheckWaiting.enabled', 'true')
+ }
}
tasks.named('groovydoc').configure {
diff --git a/grails-doc/build.gradle b/grails-doc/build.gradle
index ee8d7f1474..26d675dea9 100644
--- a/grails-doc/build.gradle
+++ b/grails-doc/build.gradle
@@ -53,7 +53,7 @@ dependencies {
implementation 'org.testcontainers:testcontainers'
implementation 'org.springframework:spring-core'
implementation 'org.springframework.boot:spring-boot'
- implementation 'org.gebish:geb-spock'
+ implementation 'org.apache.groovy.geb:geb-spock'
}
// this task needs to be here instead of the root since bom resolution only
occurs when the java / groovy plugins are applied
diff --git a/grails-geb/README.md b/grails-geb/README.md
index 29fe8c48bd..a6a1280d0a 100644
--- a/grails-geb/README.md
+++ b/grails-geb/README.md
@@ -135,18 +135,34 @@ the `container` from within your `ContainerGebSpec` to,
for example, call `.copy
An Example of this can be seen in [ContainerSupport#createFileInputSource
utility
method](./src/testFixtures/groovy/grails/plugin/geb/support/ContainerSupport.groovy).
#### Timeouts
-
+The following system properties exist to configure timeouts:
+
+* `grails.geb.atCheckWaiting.enabled`
+ * purpose: if `at` checks should wait for the page to be in the expected
state (uses configured waiting timeout values)
+ * type: boolean
+ * defaults to `false`
+* `grails.geb.timeouts.retryInterval`
+ * purpose: how often to retry waiting operations
+ * type: Number
+ * defaults to `0.1` seconds
+* `grails.geb.timeouts.waiting`
+ * purpose: amount of time to wait for waiting operations
+ * type: Number
+ * defaults to `5.0` seconds
* `grails.geb.timeouts.implicitlyWait`
* purpose: amount of time the driver should wait when searching for an
element if it is not immediately present.
+ * type: int
* defaults to `0` seconds, which means that if an element is not found, it
will immediately return an error.
* Warning: Do not mix implicit and explicit waits. Doing so can cause
unpredictable wait times.
Consult the
[Geb](https://groovy.apache.org/geb/manual/current/#implicit-assertions-waiting)
and/or [Selenium](https://www.selenium.dev/documentation/webdriver/waits/)
documentation for details.
* `grails.geb.timeouts.pageLoad`
* purpose: amount of time to wait for a page load to complete before
throwing an error.
+ * type: int
* defaults to `300` seconds
* `grails.geb.timeouts.script`
* purpose: amount of time to wait for an asynchronous script to finish
execution before throwing an error.
+ * type: int
* defaults to `30` seconds
#### Observability and Tracing
@@ -161,6 +177,28 @@ To enable tracing, set the following system property:
This allows you to opt in to tracing when an OpenTelemetry collector is
available.
+#### GebConfig.groovy and using non-default browser settings
+Provide a `GebConfig.groovy` on the test runtime classpath (commonly
`src/integration-test/resources`, but any location on the test classpath works)
to customize the browser.
+
+To make this work, ensure:
+1. The `driver` property in your `GebConfig` is a `Closure` that returns a
`RemoteWebDriver` instance.
+2. You set a custom `containerBrowser` property so that `ContainerGebSpec` can
start a matching container (e.g. "chrome", "edge", "firefox"). For a list of
supported browsers, see the [Testcontainers
documentation](https://java.testcontainers.org/modules/webdriver_containers/#other-browsers).
+3. Your `build.gradle` includes the driver dependency for the chosen browser.
+
+Example `GebConfig.groovy`:
+```groovy
+driver = {
+ new RemoteWebDriver(new FireFoxOptions())
+}
+containerBrowser = 'firefox'
+```
+Example `build.gradle`:
+```groovy
+dependencies {
+ integrationTestImplementation
'org.seleniumhq.selenium:selenium-firefox-driver'
+}
+```
+
### GebSpec
If you choose to extend `GebSpec`, you will need to have a [Selenium
WebDriver](https://www.selenium.dev/documentation/webdriver/browsers/)
installed that matches a browser you have installed on your system.
diff --git a/grails-geb/build.gradle b/grails-geb/build.gradle
index 857eb49b2c..eac82e74fc 100644
--- a/grails-geb/build.gradle
+++ b/grails-geb/build.gradle
@@ -49,7 +49,7 @@ dependencies {
testFixturesCompileOnly 'jakarta.servlet:jakarta.servlet-api'
testFixturesCompileOnly 'org.slf4j:slf4j-simple' // Remove compilation
warning about missing slf4j impl
- testFixturesApi 'org.gebish:geb-spock'
+ testFixturesApi 'org.apache.groovy.geb:geb-spock'
testFixturesApi project(':grails-testing-support-core')
testFixturesApi project(':grails-datamapping-core')
testFixturesApi "org.testcontainers:selenium"
@@ -59,7 +59,7 @@ dependencies {
testFixturesImplementation "org.seleniumhq.selenium:selenium-support"
// Added to be able to resolve the geb version from the BOM in the
resolveVersions task
- compileOnly 'org.gebish:geb-spock'
+ compileOnly 'org.apache.groovy.geb:geb-spock'
}
apply {
diff --git
a/grails-geb/src/testFixtures/groovy/grails/plugin/geb/ContainerFileDetector.groovy
b/grails-geb/src/testFixtures/groovy/grails/plugin/geb/ContainerFileDetector.groovy
index ca27a04278..86fca0df62 100644
---
a/grails-geb/src/testFixtures/groovy/grails/plugin/geb/ContainerFileDetector.groovy
+++
b/grails-geb/src/testFixtures/groovy/grails/plugin/geb/ContainerFileDetector.groovy
@@ -26,7 +26,8 @@ import org.openqa.selenium.remote.FileDetector
* An extension of {@link org.openqa.selenium.remote.FileDetector}
* that will get passed additional parameters from the webdriver container
holder.
* <p>
- * Implementations must provide a zero-argument constructor to ensure
compatibility with {@link java.util.ServiceLoader}.
+ * Implementations must provide a zero-argument constructor to ensure
compatibility
+ * with {@link java.util.ServiceLoader}.
*
* @see GebRecordingTestListener
*/
diff --git
a/grails-geb/src/testFixtures/groovy/grails/plugin/geb/ContainerGebConfiguration.groovy
b/grails-geb/src/testFixtures/groovy/grails/plugin/geb/ContainerGebConfiguration.groovy
index 1a083e6929..cd917fb966 100644
---
a/grails-geb/src/testFixtures/groovy/grails/plugin/geb/ContainerGebConfiguration.groovy
+++
b/grails-geb/src/testFixtures/groovy/grails/plugin/geb/ContainerGebConfiguration.groovy
@@ -26,7 +26,7 @@ import java.lang.annotation.Target
import org.testcontainers.containers.GenericContainer
/**
- * Can be used to configure the protocol and hostname that the container's
browser will use
+ * Can be used to configure the protocol and hostname that the container's
browser will use.
*
* @author James Daugherty
* @since 4.1
@@ -53,7 +53,8 @@ import org.testcontainers.containers.GenericContainer
String hostName() default DEFAULT_HOSTNAME_FROM_CONTAINER
/**
- * Whether reporting should be enabled for this test. Add a
`GebConfig.groovy` to customize the reporter configuration.
+ * Whether reporting should be enabled for this test.
+ * Add a `GebConfig.groovy` to customize the reporter configuration.
*/
boolean reporting() default false
@@ -70,7 +71,7 @@ import org.testcontainers.containers.GenericContainer
}
/**
- * Inheritable version of {@link ContainerGebConfiguration}
+ * Inheritable version of {@link ContainerGebConfiguration}.
*
* @since 4.2
*/
diff --git
a/grails-geb/src/testFixtures/groovy/grails/plugin/geb/ContainerGebSpec.groovy
b/grails-geb/src/testFixtures/groovy/grails/plugin/geb/ContainerGebSpec.groovy
index 2a1b258de9..2b6799ebc1 100644
---
a/grails-geb/src/testFixtures/groovy/grails/plugin/geb/ContainerGebSpec.groovy
+++
b/grails-geb/src/testFixtures/groovy/grails/plugin/geb/ContainerGebSpec.groovy
@@ -20,6 +20,7 @@ package grails.plugin.geb
import groovy.transform.CompileStatic
+import geb.Page
import geb.test.GebTestManager
import spock.lang.Shared
import spock.lang.Specification
@@ -32,12 +33,14 @@ import grails.plugin.geb.support.delegate.DriverDelegate
import grails.plugin.geb.support.delegate.PageDelegate
/**
- * A {@link geb.spock.GebSpec GebSpec} that leverages Testcontainers to run
the browser inside a container.
+ * A {@link geb.spock.GebSpec GebSpec} that leverages Testcontainers
+ * to run the browser inside a container.
*
* <p>Prerequisites:
* <ul>
* <li>
- * The test class must be annotated with {@link
grails.testing.mixin.integration.Integration @Integration}.
+ * The test class must be annotated with
+ * {@link grails.testing.mixin.integration.Integration @Integration}.
* </li>
* <li>
* A <a
href="https://java.testcontainers.org/supported_docker_environment/">compatible
container runtime</a>
@@ -61,4 +64,10 @@ abstract class ContainerGebSpec extends Specification
implements ContainerSuppor
static void setTestManager(GebTestManager testManager) {
this.testManager = testManager
}
+
+ @Override
+ Page getPage() {
+ // Be explicit which trait to use (PageDelegate vs BrowserDelegate)
+ PageDelegate.super.page
+ }
}
diff --git
a/grails-geb/src/testFixtures/groovy/grails/plugin/geb/ContainerGebTestDescription.groovy
b/grails-geb/src/testFixtures/groovy/grails/plugin/geb/ContainerGebTestDescription.groovy
index 237b8228ee..62b82d124a 100644
---
a/grails-geb/src/testFixtures/groovy/grails/plugin/geb/ContainerGebTestDescription.groovy
+++
b/grails-geb/src/testFixtures/groovy/grails/plugin/geb/ContainerGebTestDescription.groovy
@@ -24,7 +24,8 @@ import org.spockframework.runtime.model.IterationInfo
import org.testcontainers.lifecycle.TestDescription
/**
- * Implements {@link org.testcontainers.lifecycle.TestDescription} to
customize recording names.
+ * Implements {@link org.testcontainers.lifecycle.TestDescription}
+ * to customize recording names.
*
* @author James Daugherty
* @since 4.1
@@ -43,7 +44,7 @@ class ContainerGebTestDescription implements TestDescription {
testInfo.displayName != testInfo.feature.displayName ?
testInfo.iterationIndex : null
].findAll(/* Remove nulls */).join(' ')
- String safeName = testId.replaceAll('\\W+', '_')
+ def safeName = testId.replaceAll('\\W+', '_')
filesystemFriendlyName = safeName
}
}
diff --git
a/grails-geb/src/testFixtures/groovy/grails/plugin/geb/GebOnFailureReporter.groovy
b/grails-geb/src/testFixtures/groovy/grails/plugin/geb/GebOnFailureReporter.groovy
index e8f8daa65e..e7e71c885e 100644
---
a/grails-geb/src/testFixtures/groovy/grails/plugin/geb/GebOnFailureReporter.groovy
+++
b/grails-geb/src/testFixtures/groovy/grails/plugin/geb/GebOnFailureReporter.groovy
@@ -25,8 +25,8 @@ import org.spockframework.runtime.extension.IMethodInterceptor
import org.spockframework.runtime.extension.IMethodInvocation
/**
- * This class is a direct clone of {@link geb.spock.OnFailureReporter
OnFailureReporter}, except it works for the
- * {@link grails.plugin.geb.ContainerGebSpec ContainerGebSpec}.
+ * Adapts {@link geb.spock.OnFailureReporter} for use with
+ * {@link grails.plugin.geb.ContainerGebSpec}.
*/
@CompileStatic
class GebOnFailureReporter implements IMethodInterceptor {
@@ -37,13 +37,11 @@ class GebOnFailureReporter implements IMethodInterceptor {
} catch (IncompleteExecutionException notACauseForReporting) {
throw notACauseForReporting
} catch (Throwable throwable) {
- ContainerGebSpec spec = invocation.instance as ContainerGebSpec
+ def spec = invocation.instance as ContainerGebSpec
if (spec.testManager.reportingEnabled) {
try {
spec.testManager.reportFailure()
- } catch (ignored) {
- //ignore
- }
+ } catch (ignored) {}
}
throw throwable
}
diff --git
a/grails-geb/src/testFixtures/groovy/grails/plugin/geb/GebRecordingTestListener.groovy
b/grails-geb/src/testFixtures/groovy/grails/plugin/geb/GebRecordingTestListener.groovy
index 0b34b8424f..3deaac5822 100644
---
a/grails-geb/src/testFixtures/groovy/grails/plugin/geb/GebRecordingTestListener.groovy
+++
b/grails-geb/src/testFixtures/groovy/grails/plugin/geb/GebRecordingTestListener.groovy
@@ -27,8 +27,9 @@ import org.spockframework.runtime.model.ErrorInfo
import org.spockframework.runtime.model.IterationInfo
/**
- * A test listener that reports the test result to {@link
org.testcontainers.containers.BrowserWebDriverContainer} so
- * that recordings may be saved.
+ * A test listener that reports the test result to
+ * {@link org.testcontainers.containers.BrowserWebDriverContainer}
+ * so that recordings may be saved.
*
* @see org.testcontainers.containers.BrowserWebDriverContainer#afterTest
*
@@ -49,17 +50,19 @@ class GebRecordingTestListener extends AbstractRunListener {
@Override
void afterIteration(IterationInfo iteration) {
try {
- containerHolder.currentContainer.afterTest(
+ containerHolder.container.afterTest(
new ContainerGebTestDescription(iteration),
Optional.ofNullable(errorInfo?.exception)
)
} catch (NotFoundException e) {
- // Handle the case where VNC recording container doesn't have a
recording file
- // This can happen when per-test recording is enabled and a test
doesn't use the browser
- if
(containerHolder.grailsGebSettings.restartRecordingContainerPerTest &&
+ // Handle the case where VNC recording container doesn't have a
recording file.
+ // This can happen when per-test recording is enabled and a test
doesn't use the browser.
+ if (containerHolder.settings.restartRecordingContainerPerTest &&
e.message?.contains('/newScreen.mp4')) {
- log.debug("No VNC recording found for test '{}' - this is
expected for tests that don't use the browser",
- iteration.displayName)
+ log.debug(
+ 'No VNC recording found for test [{}] - this is
expected for tests that do not use a browser',
+ iteration.displayName
+ )
} else {
// Re-throw if it's a different type of NotFoundException
throw e
diff --git
a/grails-geb/src/testFixtures/groovy/grails/plugin/geb/GrailsContainerGebExtension.groovy
b/grails-geb/src/testFixtures/groovy/grails/plugin/geb/GrailsContainerGebExtension.groovy
index 4863101815..953f48228b 100644
---
a/grails-geb/src/testFixtures/groovy/grails/plugin/geb/GrailsContainerGebExtension.groovy
+++
b/grails-geb/src/testFixtures/groovy/grails/plugin/geb/GrailsContainerGebExtension.groovy
@@ -33,11 +33,14 @@ import grails.plugin.geb.support.LocalhostDownloadSupport
import grails.testing.mixin.integration.Integration
/**
- * A Spock Extension that manages the Testcontainers lifecycle for a {@link
grails.plugin.geb.ContainerGebSpec}
+ * A Spock Extension that manages the Testcontainers
+ * lifecycle for a {@link grails.plugin.geb.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. Instead, we
initialize the same interceptors
- * as the {@link geb.spock.GebExtension GebExtension} does.
+ * <p>
+ * {@link grails.plugin.geb.ContainerGebSpec} cannot be a
+ * {@link geb.test.ManagedGebTest} because it would cause the test
+ * manager to be initialized out of sequence of the container management.
+ * Instead, we initialize the same interceptors as the {@link
geb.spock.GebExtension} does.
*
* @author James Daugherty
* @since 4.1
@@ -70,6 +73,7 @@ class GrailsContainerGebExtension implements IGlobalExtension
{
@Override
void visitSpec(SpecInfo spec) {
if (isContainerGebSpec(spec) && validateContainerGebSpec(spec)) {
+
// Do not allow parallel execution since there's only 1 set of
containers in testcontainers
spec.addExclusiveResource(exclusiveResource)
@@ -78,10 +82,10 @@ class GrailsContainerGebExtension implements
IGlobalExtension {
holder.reinitialize(invocation)
ContainerGebSpec gebSpec = invocation.sharedInstance as
ContainerGebSpec
- gebSpec.container = holder.currentContainer
+ gebSpec.container = holder.container
gebSpec.testManager = holder.testManager
gebSpec.downloadSupport = new LocalhostDownloadSupport(
- holder.currentBrowser,
+ holder.browser,
holder.hostNameFromHost
)
@@ -93,7 +97,6 @@ class GrailsContainerGebExtension implements IGlobalExtension
{
spec.addSetupInterceptor { invocation ->
// Grails will be initialized by this point, so setup the
browser url correctly
holder.setupBrowserUrl(invocation)
-
invocation.proceed()
}
@@ -117,17 +120,14 @@ class GrailsContainerGebExtension implements
IGlobalExtension {
addGebExtensionOnFailureReporter(spec)
- GebRecordingTestListener recordingListener = new
GebRecordingTestListener(
- holder
- )
- spec.addListener(recordingListener)
+ spec.addListener(new GebRecordingTestListener(holder))
}
}
@TailRecursive
private boolean isContainerGebSpec(SpecInfo spec) {
- if (spec != null) {
- if (spec.filename.startsWith("${ContainerGebSpec.simpleName}." as
String)) {
+ if (spec) {
+ if (spec.filename.startsWith("${ContainerGebSpec.simpleName}.")) {
return true
}
return isContainerGebSpec(spec.superSpec)
@@ -136,18 +136,18 @@ class GrailsContainerGebExtension implements
IGlobalExtension {
}
private static boolean validateContainerGebSpec(SpecInfo specInfo) {
- if (!specInfo.annotations.find { it.annotationType() == Integration })
{
- throw new IllegalArgumentException('ContainerGebSpec classes must
be annotated with @Integration')
+ if (!specInfo.annotations.any { it.annotationType() == Integration }) {
+ throw new IllegalArgumentException(
+ 'ContainerGebSpec classes must be annotated with
@Integration'
+ )
}
-
return true
}
private static void addGebExtensionOnFailureReporter(SpecInfo spec) {
List<MethodInfo> methods = spec.allFeatures*.featureMethod +
spec.allFixtureMethods.toList()
- methods.each { MethodInfo method ->
- method.addInterceptor(new GebOnFailureReporter())
+ methods.each {
+ it.addInterceptor(new GebOnFailureReporter())
}
}
}
-
diff --git
a/grails-geb/src/testFixtures/groovy/grails/plugin/geb/GrailsGebSettings.groovy
b/grails-geb/src/testFixtures/groovy/grails/plugin/geb/GrailsGebSettings.groovy
index ee6db82106..632edc9491 100644
---
a/grails-geb/src/testFixtures/groovy/grails/plugin/geb/GrailsGebSettings.groovy
+++
b/grails-geb/src/testFixtures/groovy/grails/plugin/geb/GrailsGebSettings.groovy
@@ -25,11 +25,14 @@ import groovy.transform.CompileStatic
import groovy.transform.Memoized
import groovy.util.logging.Slf4j
+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
GrailsContainerGebExtension}
+ * Handles parsing various recording configuration
+ * used by {@link GrailsContainerGebExtension}.
*
* @author James Daugherty
* @since 4.1
@@ -38,13 +41,14 @@ import static
org.testcontainers.containers.VncRecordingContainer.VncRecordingFo
@CompileStatic
class GrailsGebSettings {
+ public static boolean DEFAULT_AT_CHECK_WAITING = false
private static VncRecordingMode DEFAULT_RECORDING_MODE =
VncRecordingMode.SKIP
private static VncRecordingFormat DEFAULT_RECORDING_FORMAT =
VncRecordingFormat.MP4
public static int DEFAULT_TIMEOUT_IMPLICITLY_WAIT = 0
public static int DEFAULT_TIMEOUT_PAGE_LOAD = 300
public static int DEFAULT_TIMEOUT_SCRIPT = 30
- String tracingEnabled
+ boolean tracingEnabled
String recordingDirectoryName
String reportingDirectoryName
boolean restartRecordingContainerPerTest
@@ -55,8 +59,12 @@ class GrailsGebSettings {
int pageLoadTimeout
int scriptTimeout
+ boolean atCheckWaiting
+ Number timeout
+ Number retryInterval
+
GrailsGebSettings(LocalDateTime startTime) {
- tracingEnabled = System.getProperty('grails.geb.tracing.enabled',
'false')
+ tracingEnabled = getBooleanProperty('grails.geb.tracing.enabled',
false)
recordingDirectoryName =
System.getProperty('grails.geb.recording.directory',
'build/gebContainer/recordings')
reportingDirectoryName =
System.getProperty('grails.geb.reporting.directory',
'build/gebContainer/reports')
recordingMode = VncRecordingMode.valueOf(
@@ -65,17 +73,48 @@ class GrailsGebSettings {
recordingFormat = VncRecordingFormat.valueOf(
System.getProperty('grails.geb.recording.format',
DEFAULT_RECORDING_FORMAT.name())
)
- restartRecordingContainerPerTest =
Boolean.parseBoolean(System.getProperty('grails.geb.recording.restartRecordingContainerPerTest',
'true'))
+ restartRecordingContainerPerTest = getBooleanProperty(
+ 'grails.geb.recording.restartRecordingContainerPerTest',
+ true
+ )
implicitlyWait = getIntProperty('grails.geb.timeouts.implicitlyWait',
DEFAULT_TIMEOUT_IMPLICITLY_WAIT)
pageLoadTimeout = getIntProperty('grails.geb.timeouts.pageLoad',
DEFAULT_TIMEOUT_PAGE_LOAD)
scriptTimeout = getIntProperty('grails.geb.timeouts.script',
DEFAULT_TIMEOUT_SCRIPT)
+ atCheckWaiting =
getBooleanProperty('grails.geb.atCheckWaiting.enabled',
DEFAULT_AT_CHECK_WAITING)
+ timeout = getNumberProperty('grails.geb.timeouts.timeout',
Wait.DEFAULT_TIMEOUT)
+ retryInterval = getNumberProperty('grails.geb.timeouts.retryInterval',
Wait.DEFAULT_RETRY_INTERVAL)
this.startTime = startTime
}
+ private static boolean getBooleanProperty(String propertyName, boolean
defaultValue) {
+ Boolean.parseBoolean(System.getProperty(propertyName,
defaultValue.toString()))
+ }
+
private static int getIntProperty(String propertyName, int defaultValue) {
Integer.getInteger(propertyName, defaultValue) ?: defaultValue
}
+ private static Number getNumberProperty(String propertyName, Number
defaultValue) {
+ def propValue = System.getProperty(propertyName)
+ if (propValue) {
+ try {
+ if (propValue.contains('.')) {
+ return new BigDecimal(propValue)
+ } else {
+ 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
+ }
+
boolean isRecordingEnabled() {
recordingMode != VncRecordingMode.SKIP
}
@@ -85,18 +124,7 @@ class GrailsGebSettings {
if (!recordingEnabled) {
return null
}
-
- File recordingDirectory = new
File("${recordingDirectoryName}${File.separator}${DateTimeFormatter.ofPattern('yyyyMMdd_HHmmss').format(startTime)}")
- if (!recordingDirectory.exists()) {
- if (!recordingDirectory.parentFile.exists()) {
- log.info('Could not find `{}` Directory for recording.
Creating...', recordingDirectoryName)
- }
- recordingDirectory.mkdirs()
- } else if (!recordingDirectory.isDirectory()) {
- throw new IllegalStateException("Configured recording directory
'${recordingDirectory}' is expected to be a directory, but found file instead.")
- }
-
- return recordingDirectory
+ createDirectory(recordingDirectoryName, 'recording')
}
@Memoized
@@ -104,17 +132,23 @@ class GrailsGebSettings {
if (!reportingDirectoryName) {
return null
}
+ createDirectory(reportingDirectoryName, 'reporting')
+ }
- File reportingDirectory = new
File("${reportingDirectoryName}${File.separator}${DateTimeFormatter.ofPattern('yyyyMMdd_HHmmss').format(startTime)}")
- if (!reportingDirectory.exists()) {
- if (!reportingDirectory.parentFile.exists()) {
- log.info('Could not find `{}` Directory for reporting.
Creating...', reportingDirectoryName)
+ private File createDirectory(String directoryName, String useCase) {
+ def dir = new File(
+
"$directoryName$File.separator${DateTimeFormatter.ofPattern('yyyyMMdd_HHmmss').format(startTime)}"
+ )
+ if (!dir.exists()) {
+ if (!dir.parentFile.exists()) {
+ log.info('Could not find [{}] directory for {}. Creating...',
directoryName, useCase)
}
- reportingDirectory.mkdirs()
- } else if (!reportingDirectory.isDirectory()) {
- throw new IllegalStateException("Configured reporting directory
'${reportingDirectory}' is expected to be a directory, but found file instead.")
+ dir.mkdirs()
+ } else if (!dir.isDirectory()) {
+ throw new IllegalStateException(
+ "Configured $useCase directory [$dir] is expected to be a
directory, but found file instead."
+ )
}
-
- return reportingDirectory
+ return dir
}
}
diff --git
a/grails-geb/src/testFixtures/groovy/grails/plugin/geb/WebDriverContainerHolder.groovy
b/grails-geb/src/testFixtures/groovy/grails/plugin/geb/WebDriverContainerHolder.groovy
index c1f21992e8..3baeee8385 100644
---
a/grails-geb/src/testFixtures/groovy/grails/plugin/geb/WebDriverContainerHolder.groovy
+++
b/grails-geb/src/testFixtures/groovy/grails/plugin/geb/WebDriverContainerHolder.groovy
@@ -18,7 +18,6 @@
*/
package grails.plugin.geb
-import java.lang.reflect.Field
import java.time.Duration
import java.time.temporal.ChronoUnit
import java.util.function.Supplier
@@ -27,6 +26,7 @@ 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
@@ -34,21 +34,31 @@ import geb.Configuration
import geb.ConfigurationLoader
import geb.spock.SpockGebTestManagerBuilder
import geb.test.GebTestManager
+import geb.waiting.Wait
+import org.openqa.selenium.SessionNotCreatedException
import org.openqa.selenium.chrome.ChromeOptions
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.BrowserWebDriverContainer
+import org.testcontainers.containers.ContainerFetchException
import org.testcontainers.containers.PortForwardingContainer
+import org.testcontainers.containers.SeleniumUtils
import org.testcontainers.containers.VncRecordingContainer
import org.testcontainers.images.PullPolicy
+import org.testcontainers.utility.DockerImageName
import grails.plugin.geb.serviceloader.ServiceRegistry
+import static GrailsGebSettings.DEFAULT_AT_CHECK_WAITING
+import static GrailsGebSettings.DEFAULT_TIMEOUT_IMPLICITLY_WAIT
+import static GrailsGebSettings.DEFAULT_TIMEOUT_PAGE_LOAD
+import static GrailsGebSettings.DEFAULT_TIMEOUT_SCRIPT
+
/**
- * Responsible for initializing a {@link
org.testcontainers.containers.BrowserWebDriverContainer
BrowserWebDriverContainer}
- * per the Spec's {@link grails.plugin.geb.ContainerGebConfiguration
ContainerGebConfiguration}. This class will try to
+ * Responsible for initializing a {@link
org.testcontainers.containers.BrowserWebDriverContainer}
+ * per the Spec's {@link grails.plugin.geb.ContainerGebConfiguration}. This
class will try to
* reuse the same container if the configuration matches the current container.
*
* @author James Daugherty
@@ -59,47 +69,55 @@ import grails.plugin.geb.serviceloader.ServiceRegistry
class WebDriverContainerHolder {
private static final String DEFAULT_HOSTNAME_FROM_HOST = 'localhost'
+ private static final String REMOTE_ADDRESS_PROPERTY =
'webdriver.remote.server'
+ private static final String DEFAULT_BROWSER = 'chrome'
- GrailsGebSettings grailsGebSettings
+ GrailsGebSettings settings
GebTestManager testManager
- Browser currentBrowser
- BrowserWebDriverContainer currentContainer
- WebDriverContainerConfiguration currentConfiguration
+ Browser browser
+ BrowserWebDriverContainer container
+ WebDriverContainerConfiguration containerConf
- WebDriverContainerHolder(GrailsGebSettings grailsGebSettings) {
- this.grailsGebSettings = grailsGebSettings
+ WebDriverContainerHolder(GrailsGebSettings settings) {
+ this.settings = settings
}
boolean isInitialized() {
- currentContainer != null
+ container != null
}
void stop() {
- currentContainer?.stop()
- currentContainer = null
- currentBrowser = null
+ container?.stop()
+ container = null
+ browser = null
testManager = null
- currentConfiguration = null
+ containerConf = null
}
- boolean
matchesCurrentContainerConfiguration(WebDriverContainerConfiguration
specConfiguration) {
- specConfiguration == currentConfiguration &&
grailsGebSettings.recordingMode ==
BrowserWebDriverContainer.VncRecordingMode.SKIP
+ boolean
matchesCurrentContainerConfiguration(WebDriverContainerConfiguration specConf) {
+ specConf == containerConf &&
+ settings.recordingMode ==
BrowserWebDriverContainer.VncRecordingMode.SKIP
}
- private static int getPort(IMethodInvocation invocation) {
+ private static int findServerPort(IMethodInvocation methodInvocation) {
try {
- return (int)
invocation.instance.metaClass.getProperty(invocation.instance, 'serverPort')
+ return (int) methodInvocation.instance.metaClass.getProperty(
+ methodInvocation.instance,
+ 'serverPort'
+ )
} catch (ignored) {
- throw new IllegalStateException('Test class must be annotated with
@Integration for serverPort to be injected')
+ throw new IllegalStateException(
+ 'Test class must be annotated with @Integration for
serverPort to be injected'
+ )
}
}
@PackageScope
- boolean reinitialize(IMethodInvocation invocation) {
- WebDriverContainerConfiguration specConfiguration = new
WebDriverContainerConfiguration(
- invocation.getSpec()
+ boolean reinitialize(IMethodInvocation methodInvocation) {
+ def specConf = new WebDriverContainerConfiguration(
+ methodInvocation.spec
)
- if (matchesCurrentContainerConfiguration(specConfiguration)) {
+ if (matchesCurrentContainerConfiguration(specConf)) {
return false
}
@@ -107,51 +125,73 @@ class WebDriverContainerHolder {
stop()
}
- currentConfiguration = specConfiguration
- currentContainer = new BrowserWebDriverContainer().withRecordingMode(
- grailsGebSettings.recordingMode,
- grailsGebSettings.recordingDirectory,
- grailsGebSettings.recordingFormat
+ def gebConf = new ConfigurationLoader().conf
+ def gebConfigExists = gebConf.rawConfig.size() != 0
+ def dockerImageName = createDockerImageName(DEFAULT_BROWSER)
+ def customBrowser = gebConf.rawConfig.containerBrowser as String
+
+ if (gebConfigExists) {
+ validateDriverConf(gebConf)
+ if (customBrowser) {
+ // Prepare for creating a container matching
+ // the GebConfig `containerBrowser` property.
+ dockerImageName = createDockerImageName(customBrowser)
+ } else {
+ log.info(
+ 'No \'containerBrowser\' property found in GebConfig.
' +
+ "Using default [$DEFAULT_BROWSER] container image."
+ )
+ }
+ }
+
+ containerConf = specConf
+ container = new
BrowserWebDriverContainer(dockerImageName).withRecordingMode(
+ settings.recordingMode,
+ settings.recordingDirectory,
+ settings.recordingFormat
)
- currentContainer.with {
- withEnv('SE_ENABLE_TRACING', grailsGebSettings.tracingEnabled)
+ container.with {
+ withEnv('SE_ENABLE_TRACING', settings.tracingEnabled.toString())
withAccessToHost(true)
withImagePullPolicy(PullPolicy.ageBased(Duration.of(1,
ChronoUnit.DAYS)))
- start()
}
+ startContainer(container, dockerImageName, customBrowser)
+
if (hostnameChanged) {
- currentContainer.execInContainer('/bin/sh', '-c', "echo
'$hostIp\t${currentConfiguration.hostName}' | sudo tee -a /etc/hosts")
+ container.execInContainer(
+ '/bin/sh', '-c',
+ "echo '$hostIp\t$containerConf.hostName' | sudo tee -a
/etc/hosts"
+ )
}
- // Create a Geb Configuration the same way as an empty Browser
constructor would do
- Configuration gebConfig = new ConfigurationLoader().conf
+ // 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
- // Ensure driver points to re-initialized container with correct host
- // Driver is explicitly quit by us in stop() method to fulfill our
resulting responsibility
- gebConfig.cacheDriver = false
+ // As we don't cache, this will have been defaulted to true. We
override to false.
+ gebConf.quitDriverOnBrowserReset = false
- // "If driver caching is disabled then this setting defaults to true"
- we override to false
- gebConfig.quitDriverOnBrowserReset = false
-
- gebConfig.baseUrl = currentContainer.seleniumAddress.toString()
- if (currentConfiguration.reporting) {
- gebConfig.reportsDir = grailsGebSettings.reportingDirectory
- gebConfig.reporter = (invocation.sharedInstance as
ContainerGebSpec).createReporter()
+ gebConf.baseUrl = container.seleniumAddress
+ if (containerConf.reporting) {
+ gebConf.reportsDir = settings.reportingDirectory
+ gebConf.reporter = (methodInvocation.sharedInstance as
ContainerGebSpec).createReporter()
}
- if (gebConfig.driverConf instanceof RemoteWebDriver) {
- // Similar to Browser#getDriverConf's Exception
- throw new IllegalStateException(
- "The 'driver' config value is an instance of
RemoteWebDriver. " +
- 'You need to wrap the driver instance in a
closure.'
+ 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
)
- }
- if (gebConfig.driverConf == null) {
- // If no driver was set in GebConfig.groovy, default to Chrome
- gebConfig.driverConf = { ->
- log.info('Using default Chrome RemoteWebDriver for {}',
currentContainer.seleniumAddress)
- new RemoteWebDriver(currentContainer.seleniumAddress, new
ChromeOptions().tap {
+ } else {
+ // If no driver was set in GebConfig, create a Chrome driver
+ gebConf.driverConf = { ->
+ log.info('Using default Chrome RemoteWebDriver for {}',
container.seleniumAddress)
+ new RemoteWebDriver(container.seleniumAddress, new
ChromeOptions().tap {
// See https://issues.chromium.org/issues/42323769
setExperimentalOption('prefs', [
'credentials_enable_service': false,
@@ -162,102 +202,159 @@ class WebDriverContainerHolder {
}
}
- // If `GebConfig` instantiates a `RemoteWebDriver` without using it's
`remoteAddress` constructor,
- // the `RemoteWebDriver` will be instantiated using the
`webdriver.remote.server` system property.
- String existingPropertyValue =
System.getProperty('webdriver.remote.server')
- System.setProperty('webdriver.remote.server',
currentContainer.seleniumAddress.toString())
- gebConfig.driver // This will implicitly call `createDriver()`
-
- // Restore the `webdriver.remote.server` system property
- if (existingPropertyValue == null) {
- System.clearProperty('webdriver.remote.server')
- } else {
- System.setProperty('webdriver.remote.server',
existingPropertyValue)
- }
-
- currentBrowser = new Browser(gebConfig)
+ browser = createBrowser(gebConf)
+ applyFileDetector(browser, containerConf)
+ applyTimeouts(browser, settings)
- if (currentConfiguration.fileDetector != NullContainerFileDetector) {
- ServiceRegistry.setInstance(ContainerFileDetector,
currentConfiguration.fileDetector)
- }
- ContainerFileDetector fileDetector =
ServiceRegistry.getInstance(ContainerFileDetector, DefaultContainerFileDetector)
- ((RemoteWebDriver) currentBrowser.driver).setFileDetector(fileDetector)
+ // 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).
+ browser.baseUrl = 'http://localhost'
- // Overwrite `GebConfig` timeouts with values explicitly set in
`GrailsGebSettings` (via system properties)
- if (grailsGebSettings.implicitlyWait !=
GrailsGebSettings.DEFAULT_TIMEOUT_IMPLICITLY_WAIT)
-
currentBrowser.driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(grailsGebSettings.implicitlyWait))
- if (grailsGebSettings.pageLoadTimeout !=
GrailsGebSettings.DEFAULT_TIMEOUT_PAGE_LOAD)
-
currentBrowser.driver.manage().timeouts().pageLoadTimeout(Duration.ofSeconds(grailsGebSettings.pageLoadTimeout))
- if (grailsGebSettings.scriptTimeout !=
GrailsGebSettings.DEFAULT_TIMEOUT_SCRIPT)
-
currentBrowser.driver.manage().timeouts().scriptTimeout(Duration.ofSeconds(grailsGebSettings.scriptTimeout))
+ testManager = createTestManager()
- // There's a bit of a chicken and egg problem here: the container &
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)
- // set the url to localhost, which the selenium server should respond
to (albeit with an error that will be ignored)
+ return true
+ }
- currentBrowser.baseUrl = 'http://localhost'
+ private static Browser createBrowser(Configuration gebConf) {
+ def browser = new Browser(gebConf)
+ try {
+ browser.driver
+ }
+ catch (SessionNotCreatedException e) {
+ throw new IllegalStateException(
+ 'Failed to create a remote browser session. ' +
+ 'Did you set a \'containerBrowser\' property ' +
+ 'corresponding to the \'driver\' in GebConfig?',
+ e
+ )
+ }
+ browser
+ }
- testManager = createTestManager()
+ 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
+ )
+ }
- return true
+ private static void applyTimeouts(Browser browser, GrailsGebSettings
settings) {
+ // Overwrite `GebConfig` timeouts with values explicitly set in
+ // `GrailsGebSettings` (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
}
- void setupBrowserUrl(IMethodInvocation invocation) {
- if (!currentBrowser) {
- return
+ private static void startContainer(BrowserWebDriverContainer container,
DockerImageName dockerImageName, String customBrowser) {
+ try {
+ container.start()
+ } catch (ContainerFetchException e) {
+ if (customBrowser) {
+ throw new IllegalStateException(
+ "Could not find the Docker image [$dockerImageName] " +
+ "with the browser name from the 'containerBrowser'
[$customBrowser] " +
+ 'property specified in GebConfig. ' +
+ 'See https://hub.docker.com/u/selenium for a list of
available images.',
+ e
+ )
+ }
+ throw e
}
- int port = getPort(invocation)
- Testcontainers.exposeHostPorts(port)
+ }
- currentBrowser.baseUrl =
"${currentConfiguration.protocol}://${currentConfiguration.hostName}:${port}"
+ void setupBrowserUrl(IMethodInvocation methodInvocation) {
+ if (!browser) return
+ int hostPort = findServerPort(methodInvocation)
+ Testcontainers.exposeHostPorts(hostPort)
+ browser.baseUrl =
"$containerConf.protocol://$containerConf.hostName:$hostPort"
}
private GebTestManager createTestManager() {
new SpockGebTestManagerBuilder()
- .withReportingEnabled(currentConfiguration.reporting)
+ .withReportingEnabled(containerConf.reporting)
.withBrowserCreator(
new Supplier<Browser>() {
@Override
Browser get() {
- currentBrowser
+ browser
}
}
)
.build()
}
- private boolean getHostnameChanged() {
- currentConfiguration.hostName !=
ContainerGebConfiguration.DEFAULT_HOSTNAME_FROM_CONTAINER
+ private boolean isHostnameChanged() {
+ containerConf.hostName !=
ContainerGebConfiguration.DEFAULT_HOSTNAME_FROM_CONTAINER
}
private static String getHostIp() {
try {
PortForwardingContainer.getDeclaredMethod('getNetwork').with {
accessible = true
- Optional<ContainerNetwork> network =
invoke(PortForwardingContainer.INSTANCE) as Optional<ContainerNetwork>
- return network.get().ipAddress
+ (invoke(PortForwardingContainer.INSTANCE) as
Optional<ContainerNetwork>)
+ .get()
+ .ipAddress
}
} catch (Exception e) {
- throw new RuntimeException('Could not access network from
PortForwardingContainer', e)
+ throw new RuntimeException(
+ 'Could not access network from PortForwardingContainer',
+ e
+ )
}
}
/**
* Returns the hostname that the server under test is available on from
the host.
- * <p>This is useful when using any of the {@code download*()} methods as
they will connect from the host,
- * and not from within the container.
+ * <p>This is useful when using any of the {@code download*()} methods as
they will
+ * 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 {@code webDriverContainer.getHost()}.
+ * is different from the default, this method will return the same value
same as
+ * {@code webDriverContainer.getHost()}.
*
* @return the hostname for accessing the server under test from the host
*/
String getHostNameFromHost() {
- return hostNameChanged ? currentContainer.host :
DEFAULT_HOSTNAME_FROM_HOST
+ hostNameChanged ? container.host : DEFAULT_HOSTNAME_FROM_HOST
}
private boolean isHostNameChanged() {
- return currentContainer.host !=
ContainerGebConfiguration.DEFAULT_HOSTNAME_FROM_CONTAINER
+ container.host !=
ContainerGebConfiguration.DEFAULT_HOSTNAME_FROM_CONTAINER
+ }
+
+ private static DockerImageName createDockerImageName(Object browserName) {
+ DockerImageName.parse(
+ "selenium/standalone-$browserName:$seleniumVersion"
+ )
+ }
+
+ private static void validateDriverConf(Configuration gebConf) {
+ if (gebConf.driverConf && !(gebConf.driverConf instanceof Closure)) {
+ throw new IllegalStateException(
+ 'The \'driver\' property of GebConfig must be a ' +
+ 'Closure that returns an instance of RemoteWebDriver.'
+ )
+ }
+ }
+
+ private static String getSeleniumVersion() {
+ SeleniumUtils.determineClasspathSeleniumVersion()
}
@CompileStatic
@@ -270,58 +367,117 @@ class WebDriverContainerHolder {
Class<? extends ContainerFileDetector> fileDetector
WebDriverContainerConfiguration(SpecInfo spec) {
- ContainerGebConfiguration configuration
+
+ ContainerGebConfiguration conf
// Check if the class implements the interface
if (IContainerGebConfiguration.isAssignableFrom(spec.reflection)) {
- configuration = spec.reflection.getConstructor().newInstance()
as ContainerGebConfiguration
+ conf = spec.reflection.getConstructor().newInstance() as
ContainerGebConfiguration
} else {
// Check for the annotation
- configuration = spec.annotations.find {
+ conf = spec.annotations.find {
it.annotationType() == ContainerGebConfiguration
} as ContainerGebConfiguration
}
- protocol = configuration?.protocol() ?:
ContainerGebConfiguration.DEFAULT_PROTOCOL
- hostName = configuration?.hostName() ?:
ContainerGebConfiguration.DEFAULT_HOSTNAME_FROM_CONTAINER
- reporting = configuration?.reporting() ?: false
- fileDetector = configuration?.fileDetector() ?:
ContainerGebConfiguration.DEFAULT_FILE_DETECTOR
+ protocol = conf?.protocol() ?:
ContainerGebConfiguration.DEFAULT_PROTOCOL
+ hostName = conf?.hostName() ?:
ContainerGebConfiguration.DEFAULT_HOSTNAME_FROM_CONTAINER
+ reporting = conf?.reporting() ?: false
+ fileDetector = conf?.fileDetector() ?:
ContainerGebConfiguration.DEFAULT_FILE_DETECTOR
}
}
/**
* Workaround for
https://github.com/testcontainers/testcontainers-java/issues/3998
- * 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.
+ * <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.
*/
void restartVncRecordingContainer() {
- if (!grailsGebSettings.recordingEnabled ||
!grailsGebSettings.restartRecordingContainerPerTest || !currentContainer) {
+ if (!settings.recordingEnabled ||
!settings.restartRecordingContainerPerTest || !container) {
return
}
try {
// Use reflection to access the VNC recording container field
- Field vncRecordingContainerField =
BrowserWebDriverContainer.getDeclaredField('vncRecordingContainer')
- vncRecordingContainerField.setAccessible(true)
-
- VncRecordingContainer vncContainer =
vncRecordingContainerField.get(currentContainer) as VncRecordingContainer
+ 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
- VncRecordingContainer newVncContainer = new
VncRecordingContainer(currentContainer)
+ def newVncContainer = new VncRecordingContainer(container)
.withVncPassword('secret')
.withVncPort(5900)
- .withVideoFormat(grailsGebSettings.recordingFormat)
- vncRecordingContainerField.set(currentContainer,
newVncContainer)
+ .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)
+ log.warn("Failed to restart VNC recording container: $e.message",
e)
// Don't throw the exception to avoid breaking the test execution
}
}
+
+ @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 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/grails-test-examples/geb-gebconfig/build.gradle
b/grails-test-examples/geb-gebconfig/build.gradle
index 19782d3db2..be441fb3c6 100644
--- a/grails-test-examples/geb-gebconfig/build.gradle
+++ b/grails-test-examples/geb-gebconfig/build.gradle
@@ -67,6 +67,7 @@ dependencies {
testImplementation 'org.spockframework:spock-core'
integrationTestImplementation testFixtures('org.apache.grails:grails-geb')
+ integrationTestImplementation
'org.seleniumhq.selenium:selenium-firefox-driver'
}
//tasks.withType(Test).configureEach {
diff --git
a/grails-test-examples/geb-gebconfig/src/integration-test/groovy/org/demo/spock/GebConfigSpec.groovy
b/grails-test-examples/geb-gebconfig/src/integration-test/groovy/org/demo/spock/GebConfigSpec.groovy
index 782bd9ccfe..830a716dea 100644
---
a/grails-test-examples/geb-gebconfig/src/integration-test/groovy/org/demo/spock/GebConfigSpec.groovy
+++
b/grails-test-examples/geb-gebconfig/src/integration-test/groovy/org/demo/spock/GebConfigSpec.groovy
@@ -42,8 +42,8 @@ class GebConfigSpec extends ContainerGebSpec {
then: 'our custom capability set in GebConfig is available'
capabilities.getCapability('grails:gebConfigUsed') == true
- and: 'the driver should have Chrome-specific capabilities'
- capabilities.browserName == 'chrome'
+ and: 'the driver should have Firefox-specific capabilities'
+ capabilities.browserName == 'firefox'
when: 'navigating to a page'
to(HomePage)
diff --git
a/grails-test-examples/geb-gebconfig/src/integration-test/resources/GebConfig.groovy
b/grails-test-examples/geb-gebconfig/src/integration-test/resources/GebConfig.groovy
index fe22ed89f8..963250f647 100644
---
a/grails-test-examples/geb-gebconfig/src/integration-test/resources/GebConfig.groovy
+++
b/grails-test-examples/geb-gebconfig/src/integration-test/resources/GebConfig.groovy
@@ -19,39 +19,32 @@
import geb.report.ReportState
import geb.report.Reporter
-import org.openqa.selenium.chrome.ChromeOptions
+import org.openqa.selenium.firefox.FirefoxOptions
import org.openqa.selenium.remote.RemoteWebDriver
import geb.report.ReportingListener
-// Configuration for container-based Geb testing
-// This driver configuration will be used by WebDriverContainerHolder
+// Configuration for container-based Geb testing.
+// This driver configuration will be used by WebDriverContainerHolder.
driver = {
- // Chrome preferences to disable password manager and credentials service
- def prefs = [
- 'credentials_enable_service': false,
- 'profile.password_manager_enabled': false,
- 'profile.password_manager_leak_detection': false
- ]
- def chromeOptions = new ChromeOptions()
- // TO DO: guest would be preferred, but this causes issues with downloads
- // see https://issues.chromium.org/issues/42323769
- // chromeOptions.addArguments('--guest')
- chromeOptions.setExperimentalOption('prefs', prefs)
-
- // Add a custom capability that we can test for to verify our
configuration is being used
- chromeOptions.setCapability('grails:gebConfigUsed', true)
-
- // The remote address will be set by WebDriverContainerHolder via system
property
- // webdriver.remote.server before this closure is called
- new RemoteWebDriver(chromeOptions)
+ // The remote address will be set by WebDriverContainerHolder via
+ // system property `webdriver.remote.server` before this closure is called.
+ new RemoteWebDriver(new FirefoxOptions().tap {
+ // Add a custom capability that we can test for
+ // to verify our configuration is being used.
+ setCapability('grails:gebConfigUsed', true)
+ })
}
-// Another proof that GebConfig.groovy is being utilized, next to GebConfigSpec
+// The `containerBrowser` property must match the configured
+// driver in order to start up a matching Selenium browser container.
+containerBrowser = 'firefox'
+
+// Another proof that GebConfig.groovy is being utilized, next to
GebConfigSpec.
reportingListener = new ReportingListener() {
void onReport(Reporter reporter, ReportState reportState, List<File>
reportFiles) {
reportFiles.each {
println "[[ATTACHMENT|$it.absolutePath]]"
}
}
-}
\ No newline at end of file
+}
diff --git a/grails-test-examples/geb/grails-app/views/serverName/index.gsp
b/grails-test-examples/geb/grails-app/views/serverName/index.gsp
index c90faadae3..f965987913 100644
--- a/grails-test-examples/geb/grails-app/views/serverName/index.gsp
+++ b/grails-test-examples/geb/grails-app/views/serverName/index.gsp
@@ -16,17 +16,10 @@
~ specific language governing permissions and limitations
~ under the License.
--%>
-<%--
- Created by IntelliJ IDEA.
- User: sbglasius
- Date: 25/11/2024
- Time: 08.09
---%>
-
<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<head>
- <title></title>
+ <title>Server name test</title>
</head>
<body>
diff --git
a/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/ContainerFileDetectorDefaultSpec.groovy
b/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/ContainerFileDetectorDefaultSpec.groovy
index 88586b5417..f6bf82e281 100644
---
a/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/ContainerFileDetectorDefaultSpec.groovy
+++
b/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/ContainerFileDetectorDefaultSpec.groovy
@@ -19,6 +19,8 @@
package org.demo.spock
+import org.demo.spock.pages.UploadSuccessPage
+
import grails.plugin.geb.ContainerGebSpec
import grails.testing.mixin.integration.Integration
import org.demo.spock.pages.UploadPage
@@ -33,7 +35,7 @@ class ContainerFileDetectorDefaultSpec extends
ContainerGebSpec {
void 'should be able to find and upload local files'() {
given:
- def uploadPage = to UploadPage
+ def uploadPage = to(UploadPage)
when:
uploadPage.fileInput.file = new
File('src/integration-test/resources/assets/upload-test.txt')
@@ -42,7 +44,6 @@ class ContainerFileDetectorDefaultSpec extends
ContainerGebSpec {
uploadPage.submitBtn.click()
then:
- title == 'File Uploaded'
- pageSource.contains('File uploaded successfully')
+ at(UploadSuccessPage)
}
}
diff --git
a/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/ContainerFileDetectorSpockSpec.groovy
b/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/ContainerFileDetectorSpockSpec.groovy
index 7754097cb1..7382c31753 100644
---
a/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/ContainerFileDetectorSpockSpec.groovy
+++
b/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/ContainerFileDetectorSpockSpec.groovy
@@ -45,7 +45,7 @@ class ContainerFileDetectorSpockSpec extends ContainerGebSpec
{
@PendingFeature(reason =
'https://github.com/apache/grails-geb/pull/146#issuecomment-2691433277')
void 'should fail to find file with fileDetector changed to
UselessContainerFileDetector in setupSpec'() {
given:
- def uploadPage = to UploadPage
+ def uploadPage = to(UploadPage)
when:
uploadPage.fileInput.file = new
File('src/integration-test/resources/assets/upload-test.txt')
diff --git
a/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/DownloadSupportSpec.groovy
b/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/DownloadSupportSpec.groovy
index 1c3d6da5bc..1e9241f080 100644
---
a/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/DownloadSupportSpec.groovy
+++
b/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/DownloadSupportSpec.groovy
@@ -19,6 +19,8 @@
package org.demo.spock
+import org.demo.spock.pages.HomePage
+
import grails.plugin.geb.ContainerGebSpec
import grails.testing.mixin.integration.Integration
@@ -27,7 +29,7 @@ class DownloadSupportSpec extends ContainerGebSpec {
void 'should be able to use download methods'() {
when:
- go('/')
+ to(HomePage)
then:
downloadText().contains('Welcome to Grails')
diff --git
a/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/InheritedConfigSpec.groovy
b/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/InheritedConfigSpec.groovy
index 970a6a6f9b..b90eec0a09 100644
---
a/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/InheritedConfigSpec.groovy
+++
b/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/InheritedConfigSpec.groovy
@@ -19,6 +19,8 @@
package org.demo.spock
+import org.demo.spock.pages.ServerNamePage
+
import grails.plugin.geb.ContainerGebConfiguration
import grails.plugin.geb.IContainerGebConfiguration
import grails.plugin.geb.ContainerGebSpec
@@ -31,7 +33,7 @@ import grails.testing.mixin.integration.Integration
class SuperSpec extends ContainerGebSpec implements IContainerGebConfiguration
{
@Override
String hostName() {
- return 'super.example.com'
+ 'super.example.com'
}
}
@@ -43,7 +45,7 @@ class NotSuperSpec extends ContainerGebSpec {}
class InheritedConfigSpec extends SuperSpec {
void 'should show the right server name when visiting /serverName'() {
when: 'visiting the server name controller'
- go('/serverName')
+ to(ServerNamePage)
then: 'the emitted hostname is correct'
pageSource.contains('Server name: super.example.com')
@@ -54,7 +56,7 @@ class InheritedConfigSpec extends SuperSpec {
class NotInheritedConfigSpec extends NotSuperSpec {
void 'should show the right server name when visiting /serverName'() {
when: 'visiting the server name controller'
- go('/serverName')
+ to(ServerNamePage)
then: 'the emitted hostname is correct'
!pageSource.contains('Server name: not.example.com')
@@ -65,12 +67,12 @@ class NotInheritedConfigSpec extends NotSuperSpec {
class ChildPreferenceInheritedConfigSpec extends SuperSpec {
@Override
String hostName() {
- return 'child.example.com'
+ 'child.example.com'
}
void 'should show the right server name when visiting /serverName'() {
when: 'visiting the server name controller'
- go('/serverName')
+ to(ServerNamePage)
then: 'the emitted hostname is correct'
pageSource.contains('Server name: child.example.com')
@@ -90,7 +92,7 @@ class ChildPreferenceInheritedConfigSpec extends SuperSpec {
class SuperSuperInheritedConfigSpec extends SuperSpec {
@Override
boolean reporting() {
- return true
+ true
}
}
@@ -98,7 +100,7 @@ class SuperSuperInheritedConfigSpec extends SuperSpec {
class MultipleInheritanceSpec extends SuperSuperInheritedConfigSpec {
void 'should show the right server name when visiting /serverName'() {
when: 'visiting the server name controller'
- go('/serverName')
+ to(ServerNamePage)
then: 'the emitted hostname is correct'
pageSource.contains('Server name: super.example.com')
diff --git
a/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/PageDelegateSpec.groovy
b/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/PageDelegateSpec.groovy
index 5a8eb3ec7d..ac48153be3 100644
---
a/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/PageDelegateSpec.groovy
+++
b/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/PageDelegateSpec.groovy
@@ -28,7 +28,7 @@ class PageDelegateSpec extends ContainerGebSpec {
void 'should delegate to page object'() {
given:
- to UploadPage
+ to(UploadPage)
when:
nop()
diff --git
a/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/PerTestRecordingSpec.groovy
b/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/PerTestRecordingSpec.groovy
index 8f82cdaafe..c60ab7a952 100644
---
a/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/PerTestRecordingSpec.groovy
+++
b/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/PerTestRecordingSpec.groovy
@@ -33,7 +33,7 @@ class PerTestRecordingSpec extends ContainerGebSpec {
void '(setup) running a test to create a recording'() {
when: 'visiting the home page'
- to HomePage
+ to(HomePage)
then: 'the page loads correctly'
title == 'Welcome to Grails'
@@ -41,7 +41,7 @@ class PerTestRecordingSpec extends ContainerGebSpec {
void '(setup) running a second test to create another recording'() {
when: 'visiting another page than the previous test'
- to UploadPage
+ to(UploadPage)
and: 'pausing to ensure the recorded file size is different'
Thread.sleep(1000)
@@ -98,6 +98,6 @@ class PerTestRecordingSpec extends ContainerGebSpec {
}
private static boolean isVideoFile(File file) {
- return file.isFile() && (file.name.endsWith('.mp4') ||
file.name.endsWith('.flv'))
+ file.isFile() && (file.name.endsWith('.mp4') ||
file.name.endsWith('.flv'))
}
}
diff --git
a/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/RootPageSpec.groovy
b/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/RootPageSpec.groovy
index 7c7cdc977a..e09cd02f22 100644
---
a/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/RootPageSpec.groovy
+++
b/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/RootPageSpec.groovy
@@ -22,6 +22,8 @@ package org.demo.spock
import geb.report.CompositeReporter
import geb.report.PageSourceReporter
import geb.report.Reporter
+import org.demo.spock.pages.HomePage
+
import grails.plugin.geb.ContainerGebConfiguration
import grails.plugin.geb.ContainerGebSpec
import grails.testing.mixin.integration.Integration
@@ -42,7 +44,7 @@ class RootPageSpec extends ContainerGebSpec {
void 'should display the correct title on the home page'() {
when: 'visiting the home page'
- go('/')
+ to(HomePage)
then: 'the page title is correct'
report('root page report')
diff --git
a/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/ServerNameControllerSpec.groovy
b/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/ServerNameControllerSpec.groovy
index 691a1a03f8..042637c011 100644
---
a/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/ServerNameControllerSpec.groovy
+++
b/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/ServerNameControllerSpec.groovy
@@ -19,6 +19,8 @@
package org.demo.spock
+import org.demo.spock.pages.ServerNamePage
+
import grails.plugin.geb.ContainerGebConfiguration
import grails.plugin.geb.ContainerGebSpec
import grails.testing.mixin.integration.Integration
@@ -33,7 +35,7 @@ class ServerNameControllerSpec extends ContainerGebSpec {
void 'should show the right server name when visiting /serverName'() {
when: 'visiting the server name controller'
- go('/serverName')
+ to(ServerNamePage)
then: 'the emitted hostname is correct'
$('p').text() == 'Server name: testing.example.com'
diff --git
a/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/UploadSpec.groovy
b/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/UploadSpec.groovy
index 982e52846c..41c61aa577 100644
---
a/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/UploadSpec.groovy
+++
b/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/UploadSpec.groovy
@@ -19,6 +19,8 @@
package org.demo.spock
+import org.demo.spock.pages.UploadSuccessPage
+
import grails.plugin.geb.ContainerGebSpec
import grails.testing.mixin.integration.Integration
import org.demo.spock.pages.UploadPage
@@ -31,7 +33,7 @@ class UploadSpec extends ContainerGebSpec {
@Requires({ os.windows })
void 'should be able to upload files on a Windows host'() {
given:
- def uploadPage = to UploadPage
+ def uploadPage = to(UploadPage)
when:
uploadPage.fileInput.file = createFileInputSource(
@@ -43,14 +45,13 @@ class UploadSpec extends ContainerGebSpec {
uploadPage.submitBtn.click()
then:
- title == 'File Uploaded'
- pageSource.contains('File uploaded successfully')
+ at(UploadSuccessPage)
}
@IgnoreIf({ os.windows })
void 'should be able to upload files on a non-Windows host'() {
given:
- def uploadPage = to UploadPage
+ def uploadPage = to(UploadPage)
when:
uploadPage.fileInput.file = createFileInputSource(
@@ -62,7 +63,6 @@ class UploadSpec extends ContainerGebSpec {
uploadPage.submitBtn.click()
then:
- title == 'File Uploaded'
- pageSource.contains('File uploaded successfully')
+ at(UploadSuccessPage)
}
}
\ No newline at end of file
diff --git
a/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/DownloadSupportSpec.groovy
b/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/pages/ServerNamePage.groovy
similarity index 70%
copy from
grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/DownloadSupportSpec.groovy
copy to
grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/pages/ServerNamePage.groovy
index 1c3d6da5bc..ed7c39f76f 100644
---
a/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/DownloadSupportSpec.groovy
+++
b/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/pages/ServerNamePage.groovy
@@ -17,19 +17,13 @@
* under the License.
*/
-package org.demo.spock
+package org.demo.spock.pages
-import grails.plugin.geb.ContainerGebSpec
-import grails.testing.mixin.integration.Integration
+import geb.Page
-@Integration
-class DownloadSupportSpec extends ContainerGebSpec {
+class ServerNamePage extends Page {
- void 'should be able to use download methods'() {
- when:
- go('/')
+ static url = '/serverName'
+ static at = { title == 'Server name test' }
- then:
- downloadText().contains('Welcome to Grails')
- }
}
\ No newline at end of file
diff --git
a/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/DownloadSupportSpec.groovy
b/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/pages/UploadSuccessPage.groovy
similarity index 70%
copy from
grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/DownloadSupportSpec.groovy
copy to
grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/pages/UploadSuccessPage.groovy
index 1c3d6da5bc..0217ff5e6e 100644
---
a/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/DownloadSupportSpec.groovy
+++
b/grails-test-examples/geb/src/integration-test/groovy/org/demo/spock/pages/UploadSuccessPage.groovy
@@ -17,19 +17,12 @@
* under the License.
*/
-package org.demo.spock
+package org.demo.spock.pages
-import grails.plugin.geb.ContainerGebSpec
-import grails.testing.mixin.integration.Integration
+import geb.Page
-@Integration
-class DownloadSupportSpec extends ContainerGebSpec {
+class UploadSuccessPage extends Page {
- void 'should be able to use download methods'() {
- when:
- go('/')
+ static at = { title == 'File Uploaded' }
- then:
- downloadText().contains('Welcome to Grails')
- }
}
\ No newline at end of file