Skip to content

Commit

Permalink
Make the examples in the section on IDE support executable.
Browse files Browse the repository at this point in the history
  • Loading branch information
erdi committed Jun 24, 2015
1 parent 22d801e commit da51dd4
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 49 deletions.
6 changes: 5 additions & 1 deletion doc/new-manual/new-manual.gradle
Expand Up @@ -90,4 +90,8 @@ idea.module {
testSourceDirs += sourceSets.realBrowserTest.allSource.srcDirs
}

check.dependsOn realBrowserTest
check.dependsOn realBrowserTest

test {
maxParallelForks(Math.max(2, Runtime.runtime.availableProcessors().intdiv(2)))
}
85 changes: 38 additions & 47 deletions doc/new-manual/src/docs/asciidoc/130-ide-and-typing.adoc
@@ -1,74 +1,62 @@
= IDE support

Geb does not require any special plugins or configuration for use inside an IDE. However, there are some considerations that will be addressed in this chapter.
Geb does not require any special plugins or configuration for use inside an IDE.
However, there are some considerations that will be addressed in this chapter.

== Execution

Geb _scripts_ can be executed in an IDE if that IDE supports executing Groovy scripts. All IDEs that support Groovy typically support this. There are typically only two concerns in the configuration of this: getting the Geb classes on the classpath, and the `GebConfig` file.
Geb _scripts_ can be executed in an IDE if that IDE supports executing Groovy scripts.
All IDEs that support Groovy typically support this.
There are typically only two concerns in the configuration of this: getting the Geb classes on the classpath, and the `GebConfig.groovy` file.

Geb _tests_ can be executed in an IDE if that IDE supports Groovy scripts and the testing framework that you are using with Geb. If you are using JUnit or Spock (which is based on JUnit) this is trivial, as all modern Java IDEs support JUnit. As far as the IDE is concerned, the Geb test is simply a JUnit test and no special support is required. As with executing scripts, the IDE must put the Geb classes on the classpath for test execution and the `GebConfig` file must be accessible (typically putting this file at the root of the test source tree is sufficient).
Geb _tests_ can be executed in an IDE if that IDE supports Groovy scripts and the testing framework that you are using with Geb.
If you are using JUnit or Spock (which is based on JUnit) this is trivial, as all modern Java IDEs support JUnit.
As far as the IDE is concerned, the Geb test is simply a JUnit test and no special support is required.
As with executing scripts, the IDE must put the Geb classes on the classpath for test execution and the `GebConfig.groovy` file must be accessible (typically putting this file at the root of the
test source tree is sufficient).

In both cases, the simplest way to create such an IDE configuration is to use a build tool (such as Gradle or Maven) that supports IDE integration. This will take care of the classpath setup and other concerns.
In both cases, the simplest way to create such an IDE configuration is to use a build tool (such as Gradle or Maven) that supports IDE integration.
This will take care of the classpath setup and other concerns.

== Authoring assistance (autocomplete and navigation)

This section discusses what kind of authoring assistance can be provided by IDEs and usage patterns that enable better authoring support.

=== Dynamism and conciseness vs tooling support

Geb heavily embraces the dynamic typing offered by Groovy, to achieve conciseness for the purpose of readability. This immediately reduces the amount of authoring assistance that an IDE can provide when authoring Geb code. This is an intentional compromise. The primary cost in functional/acceptance testing is in the _maintenance_ of the test suite over time. Geb optimizes for this in several ways, one of which being the focus on intention revealing code (which is achieved through conciseness).
Geb heavily embraces the dynamic typing offered by Groovy, to achieve conciseness for the purpose of readability.
This immediately reduces the amount of authoring assistance that an IDE can provide when authoring Geb code.
This is an intentional compromise.
The primary cost in functional/acceptance testing is in the _maintenance_ of the test suite over time.
Geb optimizes for this in several ways, one of which being the focus on intention revealing code (which is achieved through conciseness).

That said, if authoring support is a concern for you, read on to learn for details on ways to forsake conciseness in order to improve authoring support.

[[strong-typing]]
=== Strong typing

In order to gain improved authoring support, you must include types in your tests and page objects. Additionally, you must explicitly access the browser and page objects instead of relying on dynamic dispatch.
In order to gain improved authoring support, you must include types in your tests and page objects.
Additionally, you must explicitly access the browser and page objects instead of relying on dynamic dispatch.

Here's an example of idiomatic (untyped) Geb code.

[source,groovy]
----
to HomePage
loginButton.click()
at LoginPage
username = "user1"
password = "password1"
loginButton.click()
at SecurePage
include::{test-dir}/ide/StrongTypingSpec.groovy[tag=untyped,indent=0]
----

The same code written with types would look like:

[source,groovy]
----
HomePage homePage = browser.to HomePage
homePage.loginButton.click()
LoginPage loginPage = browser.at LoginPage
SecurePage securePage = loginPage.login("user1", "password1")
include::{test-dir}/ide/StrongTypingSpec.groovy[tag=typed,indent=0]
----

Where the page objects are:

[source,groovy]
----
class HomePage extends Page {
Navigator getLoginButton() {
$("#loginButton")
}
}
class LoginPage extends Page {
static at = { title == "Login Page" }
SecurePage login(String username, String password) {
$("#username").value username
$("#password").value username
$("#loginButton").click()
browser.at SecurePage
}
}
include::{test-dir}/ide/StrongTypingSpec.groovy[tag=pages,indent=0]
----

In summary:
Expand All @@ -78,24 +66,27 @@ In summary:
. Use methods on the `Page` classes instead of the `content {}` block and dynamic properties
. If you need to use content definition options like `required:` and `wait:` then you can still reference content elements defined using the DSL in methods on `Page` and `Module` classes as usual, e.g.:

static content = {
async(wait: true) { $(".async") }
}

String asyncText() {
async.text() // Wait here for the async definition to return a non-empty Navigator…
}
[source,groovy]
----
include::{test-dir}/ide/StrongTypingSpec.groovy[tag=types_with_content,indent=0]
----
<1> Wait here for the async definition to return a non-empty Navigator…

Using this “typed” style is not an all or nothing proposition. The typing options exist on a spectrum and can be used selectively where/when the cost of the extra “noise” is worth it to achieve better IDE support. For example, a mix of using the `content {}` DSL and methods can easily be used. The key enabler is to capture the result of the `to()` and `at()` methods in order to access the page object instance.
Using this “typed” style is not an all or nothing proposition.
The typing options exist on a spectrum and can be used selectively where/when the cost of the extra “noise” is worth it to achieve better IDE support.
For example, a mix of using the `content {}` DSL and methods can easily be used.
The key enabler is to capture the result of the `to()` and `at()` methods in order to access the page object instance.

==== IntelliJ IDEA support

IntelliJ IDEA (since version 12) has special support for authoring Geb code. This is built in to the Groovy support; no additional installations are required.
IntelliJ IDEA (since version 12) has special support for authoring Geb code.
This is built in to the Groovy support; no additional installations are required.

The support provides:

* Understanding of implicit browser methods (e.g. `to()`, `at()`) in test classes (e.g. `extends GebSpec`)
* Understanding of content defined via the Content DSL (within `Page` and `Module` classes only)
* Completion in `at {}` and `content {}` blocks

This effectively enables more authoring support with less explicit type information. The Geb development team would like to thank the good folks at JetBrains for adding this explicit support for Geb to IDEA.
This effectively enables more authoring support with less explicit type information.
The Geb development team would like to thank the good folks at JetBrains for adding this explicit support for Geb to IDEA.
139 changes: 139 additions & 0 deletions doc/new-manual/src/test/groovy/ide/StrongTypingSpec.groovy
@@ -0,0 +1,139 @@
/*
* Copyright 2015 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 ide

import geb.Page
import geb.navigator.Navigator
import geb.test.GebSpecWithCallbackServer

import javax.servlet.http.HttpServletRequest

class StrongTypingSpec extends GebSpecWithCallbackServer {

def setup() {
html { HttpServletRequest request ->
switch(request.requestURI) {
case ~'/loginPage$':
head {
title "Login Page"
}
form(action: "/login") {
input(type: "text", name: "username")
input(type: "password", name: "password")
input(type: "submit", value: "login")
}
break
case ~'/login$':
head {
title "Secure Page"
}
break
default:
a(href: "/loginPage", id: "loginLink", "Login")
script(type: "text/javascript", """
setTimeout(function() {
var div = document.createElement("div");
div.innerHTML = "added!";
div.setAttribute("id", "async");
document.body.appendChild(div);
}, 100)
""")
}
}
}

def "untyped"() {
when:
// tag::untyped[]
to HomePage
loginPageLink.click()

at LoginPage
username = "user1"
password = "password1"
loginButton.click()

// end::untyped[]

then:
// tag::untyped[]
at SecurePage
// end::untyped[]
}

def "typed"() {
when:
// tag::typed[]
HomePage homePage = browser.to HomePage
homePage.loginPageLink.click()

LoginPage loginPage = browser.at LoginPage
// end::typed[]

then:
// tag::typed[]
SecurePage securePage = loginPage.login("user1", "password1")
// end::typed[]
}

def "typed content definitions"() {
when:
to AsyncPage

then:
asyncText() == "added!"
}
}

// tag::pages[]
class HomePage extends Page {
Navigator getLoginPageLink() {
$("#loginLink")
}
}

class LoginPage extends Page {

static at = { title == "Login Page" }

Navigator getLoginButton() {
$("input", type: "submit")
}

SecurePage login(String username, String password) {
$(name: "username").value username
$(name: "password").value username
loginButton.click()
browser.at SecurePage
}
}

class SecurePage extends Page {
static at = { title == "Secure Page" }
}
// end::pages[]

class AsyncPage extends Page {
//tag::types_with_content[]
static content = {
async(wait: true) { $("#async") }
}

String asyncText() {
async.text() //<1>
}
//end::types_with_content[]
}
2 changes: 1 addition & 1 deletion gradle/codenarc/rulesets.groovy
Expand Up @@ -111,7 +111,7 @@ ruleset {
enabled = false
}
UnnecessarySemicolon {
doNotApplyToFileNames = 'PageOrientedSpec.groovy'
doNotApplyToFileNames = 'PageOrientedSpec.groovy, StrongTypingSpec.groovy'
}
}
ruleset('rulesets/unused.xml')
Expand Down

0 comments on commit da51dd4

Please sign in to comment.