Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

VNC recording with BrowserWebdriverContainer in Spock-Extension #2548

Merged
merged 13 commits into from Apr 12, 2020
Merged
2 changes: 2 additions & 0 deletions modules/spock/build.gradle
Expand Up @@ -8,8 +8,10 @@ dependencies {
compile project(':testcontainers')
compile 'org.spockframework:spock-core:1.3-groovy-2.5'

testCompile project(':selenium')
testCompile project(':mysql')
testCompile project(':postgresql')

testCompile 'com.zaxxer:HikariCP:3.4.2'
testCompile 'org.apache.httpcomponents:httpclient:4.5.12'

Expand Down
@@ -0,0 +1,36 @@
package org.testcontainers.spock

import groovy.transform.PackageScope
import org.spockframework.runtime.extension.IMethodInvocation
import org.testcontainers.lifecycle.TestDescription

/**
* Spock specific implementation of a Testcontainers TestDescription.
*
* Filesystem friendly name is based on Specification and Feature.
*/
@PackageScope
class SpockTestDescription implements TestDescription {

String specName
String featureName

static SpockTestDescription fromTestDescription(IMethodInvocation invocation) {
return new SpockTestDescription([
specName: invocation.spec.name,
featureName: invocation.feature.name
bsideup marked this conversation as resolved.
Show resolved Hide resolved
])
}

@Override
String getTestId() {
return null
bsideup marked this conversation as resolved.
Show resolved Hide resolved
}

@Override
String getFilesystemFriendlyName() {
return [specName, featureName].collect {
kiview marked this conversation as resolved.
Show resolved Hide resolved
URLEncoder.encode(it, 'UTF-8')
}.join('-')
}
}
@@ -1,17 +1,32 @@
package org.testcontainers.spock

import org.spockframework.runtime.AbstractRunListener
import org.spockframework.runtime.extension.AbstractAnnotationDrivenExtension
import org.spockframework.runtime.model.ErrorInfo
import org.spockframework.runtime.model.SpecInfo

class TestcontainersExtension extends AbstractAnnotationDrivenExtension<Testcontainers> {

@Override
void visitSpecAnnotation(Testcontainers annotation, SpecInfo spec) {
def interceptor = new TestcontainersMethodInterceptor(spec)
def listener = new ErrorListener()
def interceptor = new TestcontainersMethodInterceptor(spec, listener)
spec.addSetupSpecInterceptor(interceptor)
spec.addCleanupSpecInterceptor(interceptor)
spec.addSetupInterceptor(interceptor)
spec.addCleanupInterceptor(interceptor)

spec.addListener(listener)

}

private class ErrorListener extends AbstractRunListener {
List<ErrorInfo> errors = []

@Override
void error(ErrorInfo error) {
errors.add(error)
}
}

}
Expand Up @@ -6,13 +6,17 @@ import org.spockframework.runtime.model.FieldInfo
import org.spockframework.runtime.model.SpecInfo
import org.testcontainers.containers.DockerComposeContainer
import org.testcontainers.containers.GenericContainer
import org.testcontainers.lifecycle.TestLifecycleAware
import org.testcontainers.spock.TestcontainersExtension.ErrorListener

class TestcontainersMethodInterceptor extends AbstractMethodInterceptor {

private final SpecInfo spec
private final ErrorListener errorListener

TestcontainersMethodInterceptor(SpecInfo spec) {
TestcontainersMethodInterceptor(SpecInfo spec, ErrorListener errorListener) {
this.spec = spec
this.errorListener = errorListener
}

@Override
Expand Down Expand Up @@ -50,6 +54,13 @@ class TestcontainersMethodInterceptor extends AbstractMethodInterceptor {

@Override
void interceptCleanupMethod(IMethodInvocation invocation) throws Throwable {
findAllTestLifecycleAwareContainers(invocation).each {
// we assume first error is the one we want
def maybeException = Optional.ofNullable(errorListener.errors[0]?.exception)
def testDescription = SpockTestDescription.fromTestDescription(invocation)
it.afterTest(testDescription, maybeException)
}

def containers = findAllContainers(false)
stopContainers(containers, invocation)

Expand All @@ -65,6 +76,14 @@ class TestcontainersMethodInterceptor extends AbstractMethodInterceptor {
}
}

private List<TestLifecycleAware> findAllTestLifecycleAwareContainers(IMethodInvocation invocation) {
spec.allFields.findAll { FieldInfo f ->
TestLifecycleAware.isAssignableFrom(f.type)
}.collect {
it.readValue(invocation.instance) as TestLifecycleAware
}
}

private List<FieldInfo> findAllComposeContainers(boolean shared) {
spec.allFields.findAll { FieldInfo f ->
DockerComposeContainer.isAssignableFrom(f.type) && f.shared == shared
Expand All @@ -90,14 +109,14 @@ class TestcontainersMethodInterceptor extends AbstractMethodInterceptor {
private static void startComposeContainers(List<FieldInfo> compose, IMethodInvocation invocation) {
compose.each { FieldInfo f ->
DockerComposeContainer c = f.readValue(invocation.instance) as DockerComposeContainer
c.starting(null)
c.start()
}
}

private static void stopComposeContainers(List<FieldInfo> compose, IMethodInvocation invocation) {
compose.each { FieldInfo f ->
DockerComposeContainer c = f.readValue(invocation.instance) as DockerComposeContainer
c.finished(null)
c.stop()
}
}

Expand Down
@@ -0,0 +1,176 @@
package org.testcontainers.spock

import org.intellij.lang.annotations.Language
import org.junit.Rule
import org.junit.rules.TemporaryFolder
import spock.lang.Specification
import spock.util.EmbeddedSpecRunner

class BrowserVncRecordingIT extends Specification {

@Rule
TemporaryFolder temp

String recordingDir

def setup() {
recordingDir = temp.getRoot().getAbsolutePath()
}

def "retains all recordings for RECORD_ALL if successful"() {
bsideup marked this conversation as resolved.
Show resolved Hide resolved
given:

//noinspection GrPackage
@Language("groovy")
kiview marked this conversation as resolved.
Show resolved Hide resolved
String myTest = """
package org.testcontainers.spock

import org.openqa.selenium.chrome.ChromeOptions
import org.openqa.selenium.remote.RemoteWebDriver
import org.testcontainers.containers.BrowserWebDriverContainer
import spock.lang.Specification

import java.util.concurrent.TimeUnit

import static org.testcontainers.containers.BrowserWebDriverContainer.VncRecordingMode.RECORD_ALL

@Testcontainers
class BrowserWebdriverContainerIT extends Specification {

BrowserWebDriverContainer browserContainer = new BrowserWebDriverContainer()
.withCapabilities(new ChromeOptions())
.withRecordingMode(RECORD_ALL, new File("$recordingDir"))

RemoteWebDriver driver

def setup() {
driver = browserContainer.getWebDriver()
driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS)
}

def "should record"() {
when:
driver.get("http://en.wikipedia.org/wiki/Randomness")

then:
driver.findElementByPartialLinkText("pattern").isDisplayed()
}

}


"""

when:
EmbeddedSpecRunner runner = new EmbeddedSpecRunner(throwFailure: true)
runner.run(myTest)

then:
temp.getRoot().list().find { it.contains("BrowserWebdriverContainerIT-should+record") }
}

def "records nothing if RECORD_FAILING and not failing"() {
given:

//noinspection GrPackage
@Language("groovy")
String myTest = """
package org.testcontainers.spock

import org.openqa.selenium.chrome.ChromeOptions
import org.openqa.selenium.remote.RemoteWebDriver
import org.testcontainers.containers.BrowserWebDriverContainer
import spock.lang.Specification

import java.util.concurrent.TimeUnit

import static org.testcontainers.containers.BrowserWebDriverContainer.VncRecordingMode.RECORD_FAILING

@Testcontainers
class BrowserWebdriverContainerIT extends Specification {

BrowserWebDriverContainer browserContainer = new BrowserWebDriverContainer()
.withCapabilities(new ChromeOptions())
.withRecordingMode(RECORD_FAILING, new File("$recordingDir"))

RemoteWebDriver driver

def setup() {
driver = browserContainer.getWebDriver()
driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS)
}

def "should record"() {
when:
driver.get("http://en.wikipedia.org/wiki/Randomness")

then:
driver.findElementByPartialLinkText("pattern").isDisplayed()
}

}


"""

when:
EmbeddedSpecRunner runner = new EmbeddedSpecRunner(throwFailure: false)
runner.run(myTest)

then:
temp.getRoot().list().length == 0
}

def "records file if RECORD_FAILING and failing"() {
given:

//noinspection GrPackage
@Language("groovy")
String myTest = """
package org.testcontainers.spock

import org.openqa.selenium.chrome.ChromeOptions
import org.openqa.selenium.remote.RemoteWebDriver
import org.testcontainers.containers.BrowserWebDriverContainer
import spock.lang.Specification

import java.util.concurrent.TimeUnit

import static org.testcontainers.containers.BrowserWebDriverContainer.VncRecordingMode.RECORD_FAILING

@Testcontainers
class BrowserWebdriverContainerIT extends Specification {

BrowserWebDriverContainer browserContainer = new BrowserWebDriverContainer()
.withCapabilities(new ChromeOptions())
.withRecordingMode(RECORD_FAILING, new File("$recordingDir"))

RemoteWebDriver driver

def setup() {
driver = browserContainer.getWebDriver()
driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS)
}

def "should record"() {
when:
driver.get("http://en.wikipedia.org/wiki/Randomness")

then:
!driver.findElementByPartialLinkText("pattern").isDisplayed()
}

}


"""

when:
EmbeddedSpecRunner runner = new EmbeddedSpecRunner(throwFailure: false)
runner.run(myTest)

then:
temp.getRoot().list().find { it.contains("BrowserWebdriverContainerIT-should+record") }
}

}