geb.Configuration.groovy Maven / Gradle / Ivy
Show all versions of geb-core Show documentation
/* Copyright 2009 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 geb
import geb.buildadapter.SystemPropertiesBuildAdapter
import geb.driver.*
import geb.error.InvalidGebConfiguration
import geb.navigator.event.NavigatorEventListener
import geb.navigator.factory.*
import geb.report.*
import geb.waiting.Wait
import org.openqa.selenium.WebDriver
/**
* Represents a particular configuration of Geb.
*/
class Configuration {
private final static PageEventListener NOOP_PAGE_EVENT_LISTENER = new PageEventListenerSupport()
final ClassLoader classLoader
final ConfigObject rawConfig
final Properties properties
final BuildAdapter buildAdapter
private WebDriver driver
Configuration(Map rawConfig) {
this(toConfigObject(rawConfig), null, null, null)
}
Configuration(ConfigObject rawConfig = null, Properties properties = null, BuildAdapter buildAdapter = null, ClassLoader classLoader = null) {
this.classLoader = classLoader ?: new GroovyClassLoader()
this.properties = properties == null ? System.properties : properties
this.buildAdapter = buildAdapter ?: new SystemPropertiesBuildAdapter()
this.rawConfig = rawConfig ?: new ConfigObject()
}
/**
* Updates a {@code waiting.preset} config entry for a given preset name.
*/
void setWaitPreset(String name, Number presetTimeout, Number presetRetryInterval) {
rawConfig.waiting.presets[name].with {
timeout = presetTimeout
retryInterval = presetRetryInterval
}
}
Wait getWaitPreset(String name) {
def preset = rawConfig.waiting.presets[name]
def timeout = readValue(preset, 'timeout', getDefaultWaitTimeout())
def retryInterval = readValue(preset, 'retryInterval', getDefaultWaitRetryInterval())
new Wait(timeout, retryInterval, getIncludeCauseInWaitTimeoutExceptionMessage())
}
Wait getDefaultWait() {
new Wait(getDefaultWaitTimeout(), getDefaultWaitRetryInterval(), getIncludeCauseInWaitTimeoutExceptionMessage())
}
Wait getWait(Number timeout) {
new Wait(timeout, getDefaultWaitRetryInterval(), getIncludeCauseInWaitTimeoutExceptionMessage())
}
Wait getWaitForParam(waitingParam) {
if (waitingParam == true) {
defaultWait
} else if (waitingParam instanceof CharSequence) {
getWaitPreset(waitingParam.toString())
} else if (waitingParam instanceof Number && waitingParam > 0) {
getWait(waitingParam)
} else if (waitingParam instanceof Collection) {
if (waitingParam.size() == 2) {
def timeout = waitingParam[0]
def retryInterval = waitingParam[1]
if (timeout instanceof Number && retryInterval instanceof Number) {
new Wait(timeout, retryInterval, getIncludeCauseInWaitTimeoutExceptionMessage())
} else {
throw new IllegalArgumentException("'wait' param has illegal value '$waitingParam' (collection elements must be numbers)")
}
} else {
throw new IllegalArgumentException("'wait' param for content template ${this} has illegal value '$waitingParam' (collection must have 2 elements)")
}
} else {
null
}
}
/**
* Updates the {@code waiting.timeout} config entry.
*
* @see #getDefaultWaitTimeout()
*/
void setDefaultWaitTimeout(Number defaultWaitTimeout) {
rawConfig.waiting.timeout = defaultWaitTimeout
}
/**
* The default {@code timeout} value to use for waiting (i.e. if unspecified).
*
* Either the value at config path {@code waiting.timeout} or {@link geb.waiting.Wait#DEFAULT_TIMEOUT 5}.
*/
Number getDefaultWaitTimeout() {
readValue(rawConfig.waiting, 'timeout', Wait.DEFAULT_TIMEOUT) as Number
}
/**
* Returns Either the value at config path {@code waiting.includeCauseInMessage} or {@code false} if there is none.
*
* Determines if the message of {@link geb.waiting.WaitTimeoutException} should contain a string representation of its cause.
*/
boolean getIncludeCauseInWaitTimeoutExceptionMessage() {
readValue(rawConfig.waiting, 'includeCauseInMessage', false)
}
/**
* Updates the {@code waiting.includeCauseInMessage} config entry.
*
* @see #getIncludeCauseInWaitTimeoutExceptionMessage()
*/
void setIncludeCauseInWaitTimeoutExceptionMessage(boolean include) {
rawConfig.waiting.includeCauseInMessage = include
}
/**
* Updates the {@code waiting.retryInterval} config entry.
*
* @see #getDefaultWaitRetryInterval()
*/
void setDefaultWaitRetryInterval(Number defaultWaitRetryInterval) {
rawConfig.waiting.retryInterval = defaultWaitRetryInterval
}
/**
* The default {@code retryInterval} value to use for waiting (i.e. if unspecified).
*
* Either the value at config path {@code waiting.retryInterval} or {@link geb.waiting.Wait#DEFAULT_RETRY_INTERVAL 0.1}.
*/
Number getDefaultWaitRetryInterval() {
readValue(rawConfig.waiting, 'retryInterval', Wait.DEFAULT_RETRY_INTERVAL)
}
Wait getAtCheckWaiting() {
getWaitForParam(rawConfig.atCheckWaiting)
}
void setAtCheckWaiting(Object waitForParam) {
rawConfig.atCheckWaiting = waitForParam
}
Wait getBaseNavigatorWaiting() {
getWaitForParam(rawConfig.baseNavigatorWaiting)
}
void setBaseNavigatorWaiting(Object waitForParam) {
rawConfig.baseNavigatorWaiting = waitForParam
}
Collection> getUnexpectedPages() {
def unexpectedPages = rawConfig.unexpectedPages ?: []
def isCollectionContainingOnlyPageClasses = unexpectedPages instanceof Collection && unexpectedPages.every { it instanceof Class && Page.isAssignableFrom(it) }
if (!isCollectionContainingOnlyPageClasses) {
def message = "Unexpected pages configuration has to be a collection of classes that extend ${Page.name} but found \"$unexpectedPages\". " +
"Did you forget to include some imports in your config file?"
throw new InvalidGebConfiguration(message)
}
unexpectedPages
}
void setUnexpectedPages(Collection> pages) {
rawConfig.unexpectedPages = pages
}
/**
* Should the created driver be cached if there is no existing cached driver, of if there
* is a cached driver should it be used instead of creating a new one.
*
* The value is the config entry {@code cacheDriver}, which defaults to {@code true}.
*/
boolean isCacheDriver() {
readValue('cacheDriver', true)
}
/**
* Updates the {@code cacheDriver} config entry.
*
* @see #isCacheDriver()
*/
void setCacheDriver(boolean flag) {
rawConfig.cacheDriver = flag
}
/**
* If the driver is to be cached, this setting controls whether or not the driver is cached per thread or
* globally for all threads.
*
* The value is the config entry {@code cacheDriverPerThread}, which defaults to {@code false}.
*/
boolean isCacheDriverPerThread() {
readValue('cacheDriverPerThread', false)
}
/**
* Updates the {@code cacheDriverPerThread} config entry.
*
* @see #isCacheDriverPerThread()
*/
void setCacheDriverPerThread(boolean flag) {
rawConfig.cacheDriverPerThread = flag
}
/**
* If a cached driver is being used, should it be automatically quit when the JVM exits.
*
* The value is the config entry {@code quitCachedDriverOnShutdown}, which defaults to {@code true}.
*/
boolean isQuitCachedDriverOnShutdown() {
readValue('quitCachedDriverOnShutdown', true)
}
/**
* Sets whether or not the cached driver should be quit when the JVM shuts down.
*/
void setQuitCacheDriverOnShutdown(boolean flag) {
rawConfig.quitCachedDriverOnShutdown = flag
}
void setQuitDriverOnBrowserReset(boolean flag) {
rawConfig.quitDriverOnBrowserReset = flag
}
boolean isQuitDriverOnBrowserReset() {
readValue('quitDriverOnBrowserReset', !cacheDriver && !cacheDriverPerThread)
}
/**
* Sets the driver configuration value.
*
* This may be the class name of a driver implementation, a driver short name or a closure
* that when invoked with no arguments returns a driver implementation.
*
* @see #getDriver()
*/
void setDriverConf(value) {
rawConfig.driver = value
}
/**
* Returns the configuration value for the driver.
*
* This may be the class name of a driver implementation, a short name, or a closure
* that when invoked returns an actual driver.
*
* @see #getDriver()
*/
def getDriverConf() {
def value = properties.getProperty("geb.driver") ?: readValue("driver", null)
if (value instanceof WebDriver) {
throw new IllegalStateException(
"The 'driver' config value is an instance of WebDriver. " +
"You need to wrap the driver instance in a closure."
)
}
value
}
/**
* Returns the config value {@code baseUrl}, or {@link geb.BuildAdapter#getBaseUrl()}.
*/
String getBaseUrl() {
readValue("baseUrl", buildAdapter.baseUrl)
}
void setBaseUrl(baseUrl) {
rawConfig.baseUrl = baseUrl == null ? null : baseUrl.toString()
}
/**
* Returns the config value {@code reportsDir}, or {@link geb.BuildAdapter#getReportsDir()}.
*/
File getReportsDir() {
def reportsDir = readValue("reportsDir", buildAdapter.reportsDir)
if (reportsDir == null) {
null
} else if (reportsDir instanceof File) {
reportsDir
} else {
new File(reportsDir.toString())
}
}
def setReportOnTestFailureOnly(boolean value) {
rawConfig.reportOnTestFailureOnly = value
}
boolean isReportOnTestFailureOnly() {
readValue("reportOnTestFailureOnly", true)
}
void setReportsDir(File reportsDir) {
rawConfig.reportsDir = reportsDir
}
/**
* Returns the reporter implementation to use for taking snapshots of the browser's state.
*
* Returns the config value {@code reporter}, or reporter that records page source and screen shots if not explicitly set.
*/
Reporter getReporter() {
def reporter = readValue("reporter", null)
if (reporter == null) {
reporter = createDefaultReporter()
this.reporter = reporter
} else if (!(reporter instanceof Reporter)) {
throw new InvalidGebConfiguration("The specified reporter ($reporter) is not an implementation of ${Reporter.name}")
}
def typedReporter = reporter as Reporter
def reportingListener = getReportingListener()
if (reportingListener) {
// Adding is idempotent
typedReporter.addListener(reportingListener)
}
typedReporter
}
/**
* Updates the {@code reporter} config entry.
*
* @see #getReporter()
*/
void setReporter(Reporter reporter) {
rawConfig.reporter = reporter
}
void setReportingListener(ReportingListener reportingListener) {
rawConfig.reportingListener = reportingListener
}
ReportingListener getReportingListener() {
readValue("reportingListener", null)
}
NavigatorEventListener getNavigatorEventListener() {
readValue("navigatorEventListener", null)
}
void setNavigatorEventListener(NavigatorEventListener navigatorEventListener) {
rawConfig.navigatorEventListener = navigatorEventListener
}
PageEventListener getPageEventListener() {
readValue("pageEventListener", NOOP_PAGE_EVENT_LISTENER)
}
void setPageEventListener(PageEventListener pageEventListener) {
rawConfig.pageEventListener = pageEventListener
}
WebDriver getDriver() {
if (driver == null) {
driver = createDriver()
}
driver
}
void setDriver(WebDriver driver) {
this.driver = driver
}
/**
* Whether or not to automatically clear the browser's cookies.
*
* Different integrations inspect this property at different times.
*
* @return the config value for {@code autoClearCookies}, defaulting to {@code true} if not set.
*/
boolean isAutoClearCookies() {
readValue('autoClearCookies', true)
}
/**
* Sets the auto clear cookies flag explicitly, overwriting any value from the config script.
*/
void setAutoClearCookies(boolean flag) {
rawConfig.autoClearCookies = flag
}
/**
* Whether or not to automatically clear the browser's web storage, that is both local and session storage.
*
* Different integrations inspect this property at different times.
*
* @return the config value for {@code autoClearWebStorage}, defaulting to {@code false} if not set.
*/
boolean isAutoClearWebStorage() {
readValue('autoClearWebStorage', false)
}
/**
* Sets the auto clear web storage flag explicitly, overwriting any value from the config script.
*/
void setAutoClearWebStorage(boolean flag) {
rawConfig.autoClearWebStorage = flag
}
/**
* Creates the navigator factory to be used.
*
* Returns {@link BrowserBackedNavigatorFactory} by default.
*
* Override by setting the 'navigatorFactory' to a closure that takes a single {@link Browser} argument
* and returns an instance of {@link NavigatorFactory}
*
* @param browser The browser to use as the basis of the navigatory factory.
*/
NavigatorFactory createNavigatorFactory(Browser browser) {
def navigatorFactory = readValue("navigatorFactory", null)
if (navigatorFactory == null) {
new BrowserBackedNavigatorFactory(browser, getInnerNavigatorFactory())
} else {
if (navigatorFactory instanceof Closure) {
def result = navigatorFactory.call(browser)
if (result instanceof NavigatorFactory) {
return result
}
throw new InvalidGebConfiguration("navigatorFactory returned '${result}', it should be a NavigatorFactory implementation")
} else {
throw new InvalidGebConfiguration("navigatorFactory is '${navigatorFactory}', it should be a Closure that returns a NavigatorFactory implementation")
}
}
}
/**
* Returns the inner navigatory factory, that turns WebElements into Navigators.
*
* Returns {@link DefaultInnerNavigatorFactory} instances by default.
*
* To override, set 'innerNavigatorFactory' to:
*
* - An instance of {@link InnerNavigatorFactory}
*
- A Closure, that has the signature ({@link Browser}, List<{@link org.openqa.selenium.WebElement}>)
*
*
* @return The inner navigator factory.
*/
InnerNavigatorFactory getInnerNavigatorFactory() {
def innerNavigatorFactory = readValue("innerNavigatorFactory", null)
switch (innerNavigatorFactory) {
case null:
return new DefaultInnerNavigatorFactory()
case InnerNavigatorFactory:
return innerNavigatorFactory
case Closure:
return new ClosureInnerNavigatorFactory(innerNavigatorFactory)
default:
throw new InvalidGebConfiguration("innerNavigatorFactory is '${innerNavigatorFactory}', it should be a Closure or InnerNavigatorFactory implementation")
}
}
/**
* Sets the inner navigator factory.
*
* Only effectual before the browser calls {@link #createNavigatorFactory(Browser)} initially.
*/
void setInnerNavigatorFactory(InnerNavigatorFactory innerNavigatorFactory) {
this.rawConfig.innerNavigatorFactory = innerNavigatorFactory
}
/**
* Returns the default configuration closure to be applied before the user-
* supplied config closure when using the download support.
*/
@SuppressWarnings("ClosureAsLastMethodParameter")
Closure getDownloadConfig() {
readValue("defaultDownloadConfig", { HttpURLConnection con -> })
}
void setDownloadConfig(Closure config) {
rawConfig.defaultDownloadConfig = config
}
/**
* Updates the {@code templateOptions.cache} config entry.
*/
void setTemplateCacheOption(boolean cache) {
rawConfig.templateOptions.cache = cache
}
/**
* Updates the {@code templateOptions.wait} config entry.
*/
void setTemplateWaitOption(wait) {
rawConfig.templateOptions.wait = wait
}
/**
* Updates the {@code templateOptions.toWait} config entry.
*/
void setTemplateToWaitOption(toWait) {
rawConfig.templateOptions.toWait = toWait
}
/**
* Updates the {@code templateOptions.waitCondition} config entry.
*/
void setTemplateWaitConditionOption(Closure> waitCondition) {
rawConfig.templateOptions.waitCondition = waitCondition
}
/**
* Updates the {@code templateOptions.required} config entry.
*/
void setTemplateRequiredOption(boolean required) {
rawConfig.templateOptions.required = required
}
/**
* Updates the {@code templateOptions.min} config entry.
*/
void setTemplateMinOption(int min) {
rawConfig.templateOptions.min = min
}
/**
* Updates the {@code templateOptions.max} config entry.
*/
void setTemplateMaxOption(int max) {
rawConfig.templateOptions.max = max
}
/**
* Returns default values used for some of the content DSL template options.
*/
TemplateOptionsConfiguration getTemplateOptions() {
def raw = rawConfig.templateOptions
def configuration = TemplateOptionsConfiguration.builder()
.cache(raw.cache as boolean)
.wait(readValue(raw, 'wait', null))
.toWait(readValue(raw, 'toWait', null))
.waitCondition(extractWaitCondition(raw))
.required(readOptionalBooleanValue(raw, 'required'))
.min(readOptionalNonNegativeIntegerValue(raw, 'min', 'min template option'))
.max(readOptionalNonNegativeIntegerValue(raw, 'max', 'max template option'))
.build()
validate(configuration)
configuration
}
/**
* Updates the {@code withWindow.close} config entry.
*/
void setWithWindowCloseOption(boolean close) {
rawConfig.withWindow.close = close
}
WithWindowConfiguration getWithWindowConfig() {
WithWindowConfiguration.builder()
.close(rawConfig.withWindow.close as boolean)
.build()
}
/**
* Updates the {@code withNewWindow.close} config entry.
*/
void setWithNewWindowCloseOption(boolean close) {
rawConfig.withNewWindow.close = close
}
/**
* Updates the {@code withWindow.wait} config entry.
*/
void setWithNewWindowWaitOption(wait) {
rawConfig.withNewWindow.wait = wait
}
/**
* Sets the {@code requirePageAtCheckers} flag explicitly, overwriting any value from the config script.
*/
void setRequirePageAtCheckers(boolean requirePageAtCheckers) {
rawConfig.requirePageAtCheckers = requirePageAtCheckers
}
/**
* Whether or not to throw an exception when implicit "at checks" are being performed and the checked page does not define an "at check".
*
* @return the config value for {@code requirePageAtCheckers}, defaulting to {@code false} if not set.
*/
boolean getRequirePageAtCheckers() {
rawConfig.requirePageAtCheckers
}
WithNewWindowConfiguration getWithNewWindowConfig() {
def raw = rawConfig.withNewWindow
WithNewWindowConfiguration.builder()
.close(readOptionalBooleanValue(raw, 'close'))
.wait(raw.wait)
.build()
}
void validate(TemplateOptionsConfiguration configuration) {
def required = configuration.required
def min = configuration.min
def max = configuration.max
if (required.present) {
if (min.present) {
if ((required.get() && min.get() == 0) || (!required.get() && min.get() != 0)) {
boundsAndRequiredConflicting()
}
}
if (max.present && required.get() && max.get() == 0) {
boundsAndRequiredConflicting()
}
}
if (max.present && min.present && max.get() < min.get()) {
throw new InvalidGebConfiguration("Configuration contains 'max' template option that is lower than the 'min' template option")
}
}
Configuration merge(Configuration other) {
rawConfig.merge(other.rawConfig)
this
}
protected readValue(String name, defaultValue) {
readValue(rawConfig, name, defaultValue)
}
protected readValue(ConfigObject config, String name, defaultValue) {
if (config.containsKey(name)) {
config[name]
} else {
defaultValue
}
}
protected Optional readOptionalBooleanValue(ConfigObject config, String name) {
if (config.containsKey(name)) {
Optional.of(config[name] as boolean)
} else {
Optional.empty()
}
}
protected Optional readOptionalNonNegativeIntegerValue(ConfigObject config, String name, String errorName) {
if (config.containsKey(name)) {
def value = config[name]
if (value instanceof Integer && value >= 0) {
Optional.of(value)
} else {
throw new InvalidGebConfiguration("Configuration for $errorName should be a non-negative integer but found \"$value\"")
}
} else {
Optional.empty()
}
}
protected Reporter createDefaultReporter() {
new CompositeReporter(new PageSourceReporter(), new ScreenshotReporter())
}
private static toConfigObject(Map rawConfig) {
def configObject = new ConfigObject()
configObject.putAll(rawConfig)
configObject
}
private Closure> extractWaitCondition(ConfigObject config) {
def waitCondition = config.waitCondition
if (waitCondition) {
if (waitCondition instanceof Closure) {
waitCondition
} else {
throw new InvalidGebConfiguration("Configuration for waitCondition template option should be a closure but found \"$waitCondition\"")
}
}
}
private void boundsAndRequiredConflicting() {
throw new InvalidGebConfiguration("Configuration for bounds and 'required' template options is conflicting")
}
protected WebDriver createDriver() {
wrapDriverFactoryInCachingIfNeeded(getDriverFactory(getDriverConf())).driver
}
protected DriverFactory getDriverFactory(driverValue) {
switch (driverValue) {
case CharSequence:
return new NameBasedDriverFactory(classLoader, driverValue.toString())
case Closure:
return new CallbackDriverFactory(driverValue)
case null:
return new DefaultDriverFactory(classLoader)
default:
throw new DriverCreationException("Unable to determine factory for 'driver' config value '$driverValue'")
}
}
protected DriverFactory wrapDriverFactoryInCachingIfNeeded(DriverFactory factory) {
if (isCacheDriver()) {
isCacheDriverPerThread() ? CachingDriverFactory.perThread(factory, isQuitCachedDriverOnShutdown()) : CachingDriverFactory.global(factory, isQuitCachedDriverOnShutdown())
} else {
factory
}
}
}