Skip to content

Commit

Permalink
Fix reporting support for ContainerGebSpec
Browse files Browse the repository at this point in the history
  • Loading branch information
jdaugherty committed Nov 28, 2024
1 parent 8824159 commit e53ff5f
Show file tree
Hide file tree
Showing 16 changed files with 424 additions and 265 deletions.
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,21 @@ This requires a [compatible container runtime](https://java.testcontainers.org/s
If you choose to use the `ContainerGebSpec` class, as long as you have a compatible container runtime installed, you don't need to do anything else.
Just run `./gradlew integrationTest` and a container will be started and configured to start a browser that can access your application under test.

#### Parallel Execution

Parallel execution of `ContainerGebSpec` specifications is not currently supported.

#### Custom Host Configuration

The annotation `ContainerGebConfiguration` exists to customize the connection the container will use to access the application under test. The annotation is not required and `ContainerGebSpec` will use the default values in this annotation if it's not present.
The annotation `ContainerGebConfiguration` exists to customize the connection the container will use to access the application under test. The annotation is not required and `ContainerGebSpec` will use the default values in this annotation if it's not present. A traditional `GebConfig.groovy` can be provided to configure non-container specific settings.

#### Reporting

To configure reporting, enable it using the `recording` property on the annotation `ContainerGebConfiguration`. The following system properties exist for reporting configuration:

* `grails.geb.reporting.directory`
* purpose: if the test enables reporting, the directory to save the reports relative to the project directory
* defaults to `build/gebContainer/reports`

#### Recording

Expand All @@ -74,7 +86,7 @@ By default, no test recording will be performed. Various system properties exis

* `grails.geb.recording.directory`
* purpose: the directory to save the recordings relative to the project directory
* defaults to `build/recordings`
* defaults to `build/gebContainer/recordings`


* `grails.geb.recording.format`
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ projectVersion=5.0.0-SNAPSHOT
grailsVersion=7.0.0-SNAPSHOT
grailsGradlePluginVersion=7.0.0-SNAPSHOT
seleniumVersion=4.25.0
testcontainersVersion=1.20.2
testcontainersVersion=1.20.4

# This prevents the Grails Gradle Plugin from unnecessarily excluding slf4j-simple in the generated POMs
# https://github.com/grails/grails-gradle-plugin/issues/222
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package org.demo.spock

import geb.report.CompositeReporter
import geb.report.PageSourceReporter
import geb.report.Reporter
import geb.report.ScreenshotReporter
import grails.plugin.geb.ContainerGebConfiguration
import grails.plugin.geb.ContainerGebSpec
import grails.testing.mixin.integration.Integration

Expand All @@ -8,13 +13,21 @@ import grails.testing.mixin.integration.Integration
* for more instructions on how to write functional tests with Grails and Geb.
*/
@Integration
@ContainerGebConfiguration(reporting = true)
class RootPageSpec extends ContainerGebSpec {

@Override
Reporter createReporter() {
// Override the default reporter to demonstrate how this can be customized
new CompositeReporter(new ScreenshotReporter(), new PageSourceReporter())
}

void 'should display the correct title on the home page'() {
when: 'visiting the home page'
go '/'

then: 'the page title is correct'
report('root page report')
title == 'Welcome to Grails'
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,10 @@
*/
package grails.plugin.geb

import geb.Browser
import geb.download.DefaultDownloadSupport
import geb.download.DownloadSupport
import groovy.transform.CompileStatic
import groovy.transform.SelfType

import java.util.regex.Pattern
import spock.lang.Shared

/**
* A custom implementation of {@link geb.download.DownloadSupport} for enabling the use of its {@code download*()} methods
Expand All @@ -42,34 +39,6 @@ import java.util.regex.Pattern
trait ContainerAwareDownloadSupport implements DownloadSupport {

@Delegate
private final DownloadSupport downloadSupport = new LocalhostDownloadSupport(browser, this)

abstract Browser getBrowser()

abstract String getHostNameFromHost()

private static class LocalhostDownloadSupport extends DefaultDownloadSupport {

private final static Pattern urlPattern = ~/(https?:\/\/)([^\/:]+)(:\d+\/.*)/

private final ContainerAwareDownloadSupport parent
private final Browser browser

LocalhostDownloadSupport(Browser browser, ContainerAwareDownloadSupport parent) {
super(browser)
this.browser = browser
this.parent = parent
}

@Override
HttpURLConnection download(Map options) {
return super.download([*: options, base: resolveBase(options)])
}

private String resolveBase(Map options) {
return options.base ?: browser.driver.currentUrl.replaceAll(urlPattern) { match, proto, host, rest ->
"${proto}${parent.hostNameFromHost}${rest}"
}
}
}
@Shared
DownloadSupport downloadSupport
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package grails.plugin.geb

import org.testcontainers.containers.GenericContainer

import java.lang.annotation.ElementType
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
Expand All @@ -30,7 +32,7 @@ import java.lang.annotation.Target
@Retention(RetentionPolicy.RUNTIME)
@interface ContainerGebConfiguration {

static final String DEFAULT_HOSTNAME_FROM_CONTAINER = 'host.testcontainers.internal'
static final String DEFAULT_HOSTNAME_FROM_CONTAINER = GenericContainer.INTERNAL_HOST_HOSTNAME
static final String DEFAULT_PROTOCOL = 'http'

/**
Expand All @@ -45,4 +47,9 @@ import java.lang.annotation.Target
* <p>This is useful when the server under test needs to be accessed with a certain hostname.
*/
String hostName() default DEFAULT_HOSTNAME_FROM_CONTAINER

/**
* Whether reporting should be enabled for this test. Add a `GebConfig.groovy` to customize the reporter configuration.
*/
boolean reporting() default false
}

This file was deleted.

43 changes: 12 additions & 31 deletions src/testFixtures/groovy/grails/plugin/geb/ContainerGebSpec.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
*/
package grails.plugin.geb

import geb.report.CompositeReporter
import geb.report.PageSourceReporter
import geb.report.Reporter
import geb.test.GebTestManager
import geb.test.ManagedGebTest
import geb.transform.DynamicallyDispatchesToBrowser
import org.testcontainers.containers.BrowserWebDriverContainer
import spock.lang.Shared
Expand Down Expand Up @@ -44,21 +46,12 @@ import spock.lang.Specification
* @since 5.0
*/
@DynamicallyDispatchesToBrowser
abstract class ContainerGebSpec extends Specification implements ManagedGebTest, ContainerAwareDownloadSupport {

private static final String DEFAULT_HOSTNAME_FROM_HOST = 'localhost'
boolean reportingEnabled = false

@Override
@Delegate(includes = ['getBrowser', 'report'])
GebTestManager getTestManager() {
return isReportingEnabled() ?
GebTestManagerProvider.getReportingInstance() :
GebTestManagerProvider.getInstance()
}
abstract class ContainerGebSpec extends Specification implements ContainerAwareDownloadSupport {

@Shared
BrowserWebDriverContainer webDriverContainer
@Delegate(includes = ['getBrowser', 'report'])
@SuppressWarnings('unused')
static GebTestManager testManager

/**
* Get access to container running the web-driver, for convenience to execInContainer, copyFileToContainer etc.
Expand All @@ -68,25 +61,13 @@ abstract class ContainerGebSpec extends Specification implements ManagedGebTest,
* @see org.testcontainers.containers.ContainerState#copyFileFromContainer(java.lang.String, java.lang.String)
* @see org.testcontainers.containers.ContainerState
*/
BrowserWebDriverContainer getContainer() {
return webDriverContainer
}
@Shared
static BrowserWebDriverContainer container

/**
* 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>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()}.
*
* @return the hostname for accessing the server under test from the host
* The reporter that GebShould use when reporting is enabled.
*/
@Override
String getHostNameFromHost() {
return hostNameChanged ? webDriverContainer.host : DEFAULT_HOSTNAME_FROM_HOST
}

private boolean isHostNameChanged() {
return webDriverContainer.host != ContainerGebConfiguration.DEFAULT_HOSTNAME_FROM_CONTAINER
Reporter createReporter() {
new CompositeReporter(new PageSourceReporter())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,9 @@ import groovy.transform.CompileStatic
import org.spockframework.runtime.model.IterationInfo
import org.testcontainers.lifecycle.TestDescription

import java.time.LocalDateTime
import java.time.format.DateTimeFormatter

/**
* Implements {@link org.testcontainers.lifecycle.TestDescription} to customize recording names.
*
* @see org.testcontainers.lifecycle.TestDescription
* @author James Daugherty
* @since 5.0
*/
Expand All @@ -35,9 +31,9 @@ class ContainerGebTestDescription implements TestDescription {
String testId
String filesystemFriendlyName

ContainerGebTestDescription(IterationInfo testInfo, LocalDateTime runDate) {
ContainerGebTestDescription(IterationInfo testInfo) {
testId = testInfo.displayName
String safeName = testId.replaceAll('\\W+', '_')
filesystemFriendlyName = "${DateTimeFormatter.ofPattern('yyyyMMdd_HHmmss').format(runDate)}_${safeName}"
filesystemFriendlyName = safeName
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed 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
*
* http://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 grails.plugin.geb

import groovy.transform.CompileStatic
import org.opentest4j.IncompleteExecutionException
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}.
*/
@CompileStatic
class GebOnFailureReporter implements IMethodInterceptor {
void intercept(IMethodInvocation invocation) throws Throwable {
try {
invocation.proceed()
} catch (IncompleteExecutionException notACauseForReporting) {
throw notACauseForReporting
} catch (Throwable throwable) {
ContainerGebSpec spec = invocation.instance as ContainerGebSpec
if (spec.testManager.reportingEnabled) {
try {
spec.testManager.reportFailure()
} catch (ignored) {
//ignore
}
}
throw throwable
}
}
}
Loading

0 comments on commit e53ff5f

Please sign in to comment.