diff --git a/doc/new-manual/new-manual.gradle b/doc/new-manual/new-manual.gradle index c4c4bc8a2..09f4d0186 100644 --- a/doc/new-manual/new-manual.gradle +++ b/doc/new-manual/new-manual.gradle @@ -90,4 +90,8 @@ idea.module { testSourceDirs += sourceSets.realBrowserTest.allSource.srcDirs } -check.dependsOn realBrowserTest \ No newline at end of file +check.dependsOn realBrowserTest + +test { + maxParallelForks(Math.max(2, Runtime.runtime.availableProcessors().intdiv(2))) +} \ No newline at end of file diff --git a/doc/new-manual/src/docs/asciidoc/130-ide-and-typing.adoc b/doc/new-manual/src/docs/asciidoc/130-ide-and-typing.adoc index 4cdc9f6c4..7cbf58f03 100644 --- a/doc/new-manual/src/docs/asciidoc/130-ide-and-typing.adoc +++ b/doc/new-manual/src/docs/asciidoc/130-ide-and-typing.adoc @@ -1,14 +1,22 @@ = 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) @@ -16,59 +24,39 @@ This section discusses what kind of authoring assistance can be provided by IDEs === 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: @@ -78,19 +66,21 @@ 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: @@ -98,4 +88,5 @@ The support provides: * 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. \ No newline at end of file +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. \ No newline at end of file diff --git a/doc/new-manual/src/test/groovy/ide/StrongTypingSpec.groovy b/doc/new-manual/src/test/groovy/ide/StrongTypingSpec.groovy new file mode 100644 index 000000000..44fe69d46 --- /dev/null +++ b/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[] +} \ No newline at end of file diff --git a/gradle/codenarc/rulesets.groovy b/gradle/codenarc/rulesets.groovy index ccc5c69b6..1df0f3976 100644 --- a/gradle/codenarc/rulesets.groovy +++ b/gradle/codenarc/rulesets.groovy @@ -111,7 +111,7 @@ ruleset { enabled = false } UnnecessarySemicolon { - doNotApplyToFileNames = 'PageOrientedSpec.groovy' + doNotApplyToFileNames = 'PageOrientedSpec.groovy, StrongTypingSpec.groovy' } } ruleset('rulesets/unused.xml')