Skip to content

Commit

Permalink
Improve IDE experience for snippets #221
Browse files Browse the repository at this point in the history
  • Loading branch information
paulk-asert committed Dec 19, 2024
1 parent 6ff1a43 commit 6439553
Show file tree
Hide file tree
Showing 16 changed files with 141 additions and 65 deletions.
3 changes: 3 additions & 0 deletions doc/manual/manual.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ asciidoctorj {
toc : 'left',
encoding : 'utf-8',
'snippets-dir' : snippetsPath,
'rb-snippets-dir' : snippetsPath,
'cm-snippets-dir' : snippetsPath,
'ct-snippets-dir' : snippetsPath,
numbered : true,
sectlinks : true,
idprefix : '',
Expand Down
28 changes: 16 additions & 12 deletions doc/manual/src/docs/asciidoc/010-intro.adoc
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
ifndef::snippets-dir[]
:snippets-dir: ../../../../manual-snippets/src/test/groovy
:rb-snippets-dir: ../../../../manual-snippets/real-browser/src/test/groovy
endif::[]
= Introduction

Geb (pronounced "jeb" and originally a contraction of "Groovy web") is a developer focused tool for automating the interaction between web browsers and web content.
Expand All @@ -6,7 +10,7 @@ inspection and traversal API (for finding and interacting with content).

Geb was born out of a desire to make browser automation (originally for web testing) easier and more productive.
It aims to be a *developer tool* in that it allows and encourages the using of programming and language constructs instead of creating a restricted environment.
It uses Groovy's dynamism to remove the noise and boiler plate code in order to focus on what's important — the content and interaction.
It uses Groovy's dynamism to remove the noise and boilerplate code in order to focus on what's important — the content and interaction.

== The browser automation technology

Expand Down Expand Up @@ -87,9 +91,9 @@ Here's an example of using Geb in an inline (i.e. no page objects or predefined

[source,groovy]
----
include::{snippets-dir}/intro/ScriptingSpec.groovy[tags=imports,indent=0]
include::{rb-snippets-dir}/intro/ScriptingSpec.groovy[tags=imports,indent=0]
include::{snippets-dir}/intro/ScriptingSpec.groovy[tags=inline,indent=0]
include::{rb-snippets-dir}/intro/ScriptingSpec.groovy[tags=inline,indent=0]
----
<1> Check that we are at Geb's homepage.
<2> Click on the manual menu entry to open it.
Expand All @@ -103,14 +107,14 @@ This time let us define our content up front using the Page Object pattern…

[source,groovy]
----
include::{snippets-dir}/intro/module/ManualsMenuModule.groovy[tags=imports,indent=0]
include::{snippets-dir}/intro/page/GebHomePage.groovy[tags=imports,indent=0]
include::{rb-snippets-dir}/intro/module/ManualsMenuModule.groovy[tags=imports,indent=0]
include::{rb-snippets-dir}/intro/page/GebHomePage.groovy[tags=imports,indent=0]
include::{snippets-dir}/intro/module/ManualsMenuModule.groovy[tags=class,indent=0]
include::{rb-snippets-dir}/intro/module/ManualsMenuModule.groovy[tags=class,indent=0]
include::{snippets-dir}/intro/page/GebHomePage.groovy[tags=class,indent=0]
include::{rb-snippets-dir}/intro/page/GebHomePage.groovy[tags=class,indent=0]
include::{snippets-dir}/intro/page/TheBookOfGebPage.groovy[tags=class,indent=0]
include::{rb-snippets-dir}/intro/page/TheBookOfGebPage.groovy[tags=class,indent=0]
----
<1> Modules are reusable fragments that can be used across pages. Here we are using a module to model a dropdown menu.
<2> Content DSL.
Expand All @@ -124,9 +128,9 @@ Now our script again, using the above defined content…

[source,groovy]
----
include::{snippets-dir}/intro/ScriptingSpec.groovy[tags=imports,indent=0]
include::{rb-snippets-dir}/intro/ScriptingSpec.groovy[tags=imports,indent=0]
include::{snippets-dir}/intro/ScriptingSpec.groovy[tags=using_page_objects,indent=0]
include::{rb-snippets-dir}/intro/ScriptingSpec.groovy[tags=using_page_objects,indent=0]
----
<1> Go to the url defined by `GebHomePage` and also verify it's “at checker”.

Expand All @@ -140,9 +144,9 @@ Here is our Geb homepage case again, this time using Geb's {spock} integration

[source,groovy]
----
include::{snippets-dir}/intro/GebHomepageSpec.groovy[tags=imports,indent=0]
include::{rb-snippets-dir}/intro/GebHomepageSpec.groovy[tags=imports,indent=0]
include::{snippets-dir}/intro/GebHomepageSpec.groovy[tags=class,indent=0]
include::{rb-snippets-dir}/intro/GebHomepageSpec.groovy[tags=class,indent=0]
----

For more information on using Geb for web and functional testing, see the <<testing,testing chapter>>.
Expand Down
6 changes: 5 additions & 1 deletion doc/manual/src/docs/asciidoc/020-browser.adoc
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
ifndef::snippets-dir[]
:snippets-dir: ../../../../manual-snippets/src/test/groovy
:rb-snippets-dir: ../../../../manual-snippets/real-browser/src/test/groovy
endif::[]
[[browser]]
= The `Browser`

Expand Down Expand Up @@ -469,7 +473,7 @@ Both of them can be used to read and write values to the underlying storage as i

[source,groovy,indent=0]
----
include::{snippets-dir}/browser/WebStorageSpec.groovy[tag=write_and_read]
include::{rb-snippets-dir}/browser/WebStorageSpec.groovy[tag=write_and_read]
----

Both `localStorage` and `sessionStorage` properties are of `geb.webstorage.WebStorage` type - please refer to link:api/geb/webstorage/WebStorage.html[its javadoc] for information about other operations that are supported apart from reading and writing values.
Expand Down
4 changes: 4 additions & 0 deletions doc/manual/src/docs/asciidoc/021-driver.adoc
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
ifndef::snippets-dir[]
:snippets-dir: ../../../../manual-snippets/src/test/groovy
:rb-snippets-dir: ../../../../manual-snippets/real-browser/src/test/groovy
endif::[]
[[driver]]
= The WebDriver implementation

Expand Down
49 changes: 27 additions & 22 deletions doc/manual/src/docs/asciidoc/030-navigator.adoc
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
ifndef::snippets-dir[]
:snippets-dir: ../../../../manual-snippets/src/test/groovy
:rb-snippets-dir: ../../../../manual-snippets/real-browser/src/test/groovy
:cm-snippets-dir: ../../../../../module/geb-core/src/main/groovy
endif::[]
[[navigator]]
= Interacting with content

Expand Down Expand Up @@ -94,14 +99,14 @@ Let's consider a simple link:https://vuejs.org/[Vue.js] application which render

[source,html]
----
include::{snippets-dir}/navigator/DynamicModuleBaseSpec.groovy[tag=template,indent=0]
include::{rb-snippets-dir}/navigator/DynamicModuleBaseSpec.groovy[tag=template,indent=0]
----

…and the following javascript code…

[source,groovy]
----
include::{snippets-dir}/navigator/DynamicModuleBaseSpec.groovy[tag=vue_app,indent=0]
include::{rb-snippets-dir}/navigator/DynamicModuleBaseSpec.groovy[tag=vue_app,indent=0]
----

The important thing to note is that the `li` elements re re-rendered when they are reordered.
Expand All @@ -110,40 +115,40 @@ Let's model each item in the list using the following module…

[source,groovy]
----
include::{snippets-dir}/navigator/DynamicModuleBaseSpec.groovy[tag=module,indent=0]
include::{rb-snippets-dir}/navigator/DynamicModuleBaseSpec.groovy[tag=module,indent=0]
----

Given the following content definition…

[source,groovy]
----
include::{snippets-dir}/navigator/DynamicModuleBaseSpec.groovy[tag=static_navigator,indent=0]
include::{rb-snippets-dir}/navigator/DynamicModuleBaseSpec.groovy[tag=static_navigator,indent=0]
----

The code below will fail with `StaleElementReferenceException`…

[source,groovy]
----
include::{snippets-dir}/navigator/DynamicModuleBaseSpec.groovy[tag=exception,indent=0]
include::{rb-snippets-dir}/navigator/DynamicModuleBaseSpec.groovy[tag=exception,indent=0]
----

If we change the base navigator of the list element module to be dynamic as in the following page class…

[source,groovy]
----
include::{snippets-dir}/navigator/DynamicModuleBaseSpec.groovy[tag=dynamic_navigator,indent=0]
include::{rb-snippets-dir}/navigator/DynamicModuleBaseSpec.groovy[tag=dynamic_navigator,indent=0]
----

Then the following will pass…

[source,groovy]
----
include::{snippets-dir}/navigator/DynamicModuleBaseSpec.groovy[tag=no_exception,indent=0]
include::{rb-snippets-dir}/navigator/DynamicModuleBaseSpec.groovy[tag=no_exception,indent=0]
----

[WARNING]
====
While using dynamic navigators might seem like a way to completely avoid `{stale-element-reference-exception-api}` it is unfortunatelly not the case.
While using dynamic navigators might seem like a way to completely avoid `{stale-element-reference-exception-api}` it is unfortunately not the case.
That's because operations which access elements of a dynamic `Navigator` are not atomic.
For example, when calling `click()` on a dynamic `Navigator`, first a WebDriver protocol command will be executed to refresh the web elements which underpin that instance and then another command will be executed to click on the element that was found - if between the two commands the element is removed from the DOM the exception will still occur.
While dynamic navigators make `StaleElementReferenceException` less likely, it's still far from impossible to get one when using them, especially in highly asynchronous applications with frequent DOM redraws.
Expand Down Expand Up @@ -732,8 +737,8 @@ It is possible to send non-textual characters to content by using the WebDriver

[source,groovy]
----
include::{snippets-dir}/navigator/NonCharacterKeystrokesSpec.groovy[tag=import,indent=0]
include::{snippets-dir}/navigator/NonCharacterKeystrokesSpec.groovy[tag=keystrokes,indent=0]
include::{rb-snippets-dir}/navigator/NonCharacterKeystrokesSpec.groovy[tag=import,indent=0]
include::{rb-snippets-dir}/navigator/NonCharacterKeystrokesSpec.groovy[tag=keystrokes,indent=0]
----

Here we are sending a “control-c” to an input.
Expand Down Expand Up @@ -1013,8 +1018,8 @@ Which an also be used for non-character keys…

[source,groovy]
----
include::{snippets-dir}/navigator/BackspaceSpec.groovy[tag=import,indent=0]
include::{snippets-dir}/navigator/BackspaceSpec.groovy[tag=backspace,indent=0]
include::{rb-snippets-dir}/navigator/BackspaceSpec.groovy[tag=import,indent=0]
include::{rb-snippets-dir}/navigator/BackspaceSpec.groovy[tag=backspace,indent=0]
----

[WARNING]
Expand Down Expand Up @@ -1055,29 +1060,29 @@ It is possible to access the contained `WebElement` instances via the following

[source,groovy]
----
include::{snippets-dir}/geb/navigator/Navigator.groovy[tag=web_element_returning_methods,indent=0]
include::{cm-snippets-dir}/geb/navigator/Navigator.groovy[tag=web_element_returning_methods,indent=0]
----

By using the methods of the WebDriver `{actions-api}` class with `WebElement` instances, complex user gestures can be emulated.
First you will need to create an `Actions` instance after obtaining the WebDriver driver:

[source,groovy]
----
include::{snippets-dir}/navigator/InteractionsSpec.groovy[tag=actions_creation,indent=0]
include::{rb-snippets-dir}/navigator/InteractionsSpec.groovy[tag=actions_creation,indent=0]
----

Next, use methods of `Actions` to compose a series of UI actions, then call `build()` to create a concrete `Action`:

[source,groovy]
----
include::{snippets-dir}/navigator/InteractionsSpec.groovy[tag=action_definition,indent=0]
include::{rb-snippets-dir}/navigator/InteractionsSpec.groovy[tag=action_definition,indent=0]
----

Finally, call `perform()` to actually trigger the desired mouse or keyboard behavior:

[source,groovy]
----
include::{snippets-dir}/navigator/InteractionsSpec.groovy[tag=action_execution,indent=0]
include::{rb-snippets-dir}/navigator/InteractionsSpec.groovy[tag=action_execution,indent=0]
----

[[interact-closures]]
Expand All @@ -1093,14 +1098,14 @@ This `interact()` call performs the same work as the calls in the <<using-action

[source,groovy]
----
include::{snippets-dir}/navigator/InteractionsSpec.groovy[tag=interact,indent=0]
include::{rb-snippets-dir}/navigator/InteractionsSpec.groovy[tag=interact,indent=0]
----

While usually not needed, it's possible to directly access the `Actions` instance backing the `InteractDelegate`:

[source,groovy]
----
include::{snippets-dir}/navigator/InteractionsSpec.groovy[tag=actions_in_interact,indent=0]
include::{rb-snippets-dir}/navigator/InteractionsSpec.groovy[tag=actions_in_interact,indent=0]
----

For the full list of available methods that can be used for emulating user gestures see the documentation for the `{interact-delegate-api}` class.
Expand All @@ -1115,14 +1120,14 @@ Calls to `interact()` can be used to perform behaviors that are more complicated

[source,groovy]
----
include::{snippets-dir}/navigator/DragAndDropSpec.groovy[tag=verbose,indent=0]
include::{rb-snippets-dir}/navigator/DragAndDropSpec.groovy[tag=verbose,indent=0]
----

Drag-and-dropping can also be accomplished using the `dragAndDropBy()` convenience method from the Actions API:

[source,groovy]
----
include::{snippets-dir}/navigator/DragAndDropSpec.groovy[tag=convenience,indent=0]
include::{rb-snippets-dir}/navigator/DragAndDropSpec.groovy[tag=convenience,indent=0]
----

In this particular example, the element will be clicked then dragged 150 pixels to the right and 200 pixels downward before being released.
Expand All @@ -1138,6 +1143,6 @@ Control-clicking several elements, such as items in a list, is performed the sam

[source,groovy]
----
include::{snippets-dir}/navigator/ControlClickSpec.groovy[tag=import,indent=0]
include::{snippets-dir}/navigator/ControlClickSpec.groovy[tag=interact,indent=0]
include::{rb-snippets-dir}/navigator/ControlClickSpec.groovy[tag=import,indent=0]
include::{rb-snippets-dir}/navigator/ControlClickSpec.groovy[tag=interact,indent=0]
----
11 changes: 8 additions & 3 deletions doc/manual/src/docs/asciidoc/040-pages.adoc
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
ifndef::snippets-dir[]
:snippets-dir: ../../../../manual-snippets/src/test/groovy
:rb-snippets-dir: ../../../../manual-snippets/real-browser/src/test/groovy
:cm-snippets-dir: ../../../../../module/geb-core/src/main/groovy
endif::[]
[[pages]]
= Pages

Expand Down Expand Up @@ -442,13 +447,13 @@ This essentially means that verification of page transition (“at checking”)

[source,groovy]
----
include::{snippets-dir}/pages/ToWaitOptionSpec.groovy[tag=to_wait_page,indent=0]
include::{rb-snippets-dir}/pages/ToWaitOptionSpec.groovy[tag=to_wait_page,indent=0]
----
<1> Page change is asynchronous, e.g. an ajax call is involved.

[source,groovy]
----
include::{snippets-dir}/pages/ToWaitOptionSpec.groovy[tag=to_wait,indent=0]
include::{rb-snippets-dir}/pages/ToWaitOptionSpec.groovy[tag=to_wait,indent=0]
----

See the <<waiting, section on waiting>> for the semantics of the `waitFor()` method, that is used here internally.
Expand Down Expand Up @@ -763,7 +768,7 @@ The `{page-api}` implementation of this method looks like this…

[source,groovy]
----
include::{snippets-dir}/geb/Page.groovy[tag=convert_to_path,indent=0]
include::{cm-snippets-dir}/geb/Page.groovy[tag=convert_to_path,indent=0]
----

You can either overwrite this catchall method to control path conversion for all invocations or provide an overloaded version for a specific type signature.
Expand Down
Loading

0 comments on commit 6439553

Please sign in to comment.