Skip to content

Commit

Permalink
GEB-167 Add waiting, window closing and page switching options to wit…
Browse files Browse the repository at this point in the history
…hNewWindow()
  • Loading branch information
erdi committed Nov 7, 2012
1 parent eb23001 commit 5749221
Show file tree
Hide file tree
Showing 5 changed files with 203 additions and 22 deletions.
54 changes: 51 additions & 3 deletions doc/manual/src/chapters/020-browser.md
Expand Up @@ -259,6 +259,9 @@ Listeners cannot be registered twice. If an attempt is made to register a listen

When you're working with an application that opens new windows or tabs, for example when clicking on a link with a target attribute set, you can use `withWindow()` and `withNewWindow()` methods to execute code in the context of other windows.

If you really need to know the name of the current window or all the names of open windows use [`getCurrentWindow()`](api/geb/Browser.html#getCurrentWindow(\)) and [`getAvailableWindows()`](api/geb/Browser.html#getAvailableWindows(\)) methods but `withWindow()` and `withNewWindow()` are the preferred methods when it comes to dealing with multiple windows.

### Switching context to already opened windows
If you know the name of the window in which context you want to execute the code you can use [`withWindow(String windowName, Closure block)`](api/geb/Browser.html#withWindow(java.lang.String, groovy.lang.Closure\)). Given this html:

<a href="http://www.gebish.org" target="myWindow">Geb</a>
Expand All @@ -281,15 +284,60 @@ This code passes:
assert $('#slogan').text() == 'very groovy browser automation… web testing, screen scraping and more'
}

Finally if you want to execute code in a window that is newly opened by some of your actions use the [`withNewWindow(Closure windowOpeningBlock, Closure block)`](api/geb/Browser.html#withNewWindow(groovy.lang.Closure, groovy.lang.Closure\)) method. Given html as above the following will pass:
### Switching context to newly opened windows

If you wish to execute code in a window that is newly opened by some of your actions use the [`withNewWindow(Closure windowOpeningBlock, Closure block)`](api/geb/Browser.html#withNewWindow(groovy.lang.Closure, groovy.lang.Closure\)) method. Given html as above the following will pass:

withNewWindow({ $('a').click() }) {
assert $('title').text() == 'Geb - Very Groovy Browser Automation'
}

Note that if the first parameter opens none or more than one window then [`NoSuchWindowException`](http://selenium.googlecode.com/svn/trunk/docs/api/java/org/openqa/selenium/NoSuchWindowException.html) is thrown.
Note that if the first parameter opens none or more than one window then [`NoNewWindowException`](api/geb/error/NoNewWindowException.html) is thrown.

If you really need to know the name of the current window or all the names of open windows use [`getCurrentWindow()`](api/geb/Browser.html#getCurrentWindow(\)) and [`getAvailableWindows()`](api/geb/Browser.html#getAvailableWindows(\)) methods but `withWindow()` and `withNewWindow()` are the preferred methods when it comes to dealing with multiple windows.
#### Passing options when working with newly opened windows

There are several options that can be passed to a [`withNewWindow()`](api/geb/Browser.html#withNewWindow(java.util.Map, groovy.lang.Closure, groovy.lang.Closure\)) call which make working with newly opened windows even simpler. The general syntax is:

withNewWindow({ «window opening action» }, «option name»: «option value», ...) { «action executed within the context of the window» }

##### close

Default value: `true`

If you pass any truly value as `close` option then the newly opened window will be closed after the execution of the closure passed as the last argument to the `withNewWindow()` call.

##### page

Default value: `null`

If you pass a class that extends `Page` as `page` option then browser's page will be set to that value before executing the closure passed as the last argument and will be reverted to it's original value afterwards.

##### wait

Default value: `null`

You can specify `wait` option if the action defined in the window opening closure passed as the first argument is asynchronous and you need to wait for the new window to be opened. The possible values for the `wait` option are consistent with the ones for content definition and can be one of the following:

* **`true`** - wait for the content using the _default wait_ configuration
* **a string** - wait for the content using the _wait preset_ with this name from the configuration
* **a number** - wait for the content for this many seconds, using the _default retry interval_ from the configuration
* **a 2 element list of numbers** - wait for the content using element 0 as the timeout seconds value, and element 1 as the retry interval seconds value

Given the following html:

<a href="http://www.gebish.org" target="_blank" id="new-window-link">Geb</a>

the following will pass:

withNewWindow({
js.exec """
setTimeout(function() {
document.getElementById('new-window-link').click();
}, 200);
"""
}, wait: true) {
assert $('title').text() == 'Geb - Very Groovy Browser Automation'
}

## Quitting the browser

Expand Down
1 change: 1 addition & 0 deletions doc/manual/src/chapters/130-project.md
Expand Up @@ -38,6 +38,7 @@ This page lists the high level changes between versions of Geb.
* New `toAt()` method that changes the page on the browser and verifies the at checker of that page in one method call \[[GEB-1](http://jira.codehaus.org/browse/GEB-1)\].
* It is now possible to provide your own [`Navigator`][navigator-api] implementations by specifying a custom [`NavigatorFactory`](api/geb/navigator/factory/NavigatorFactory.html), see [this manual section](configuration.html#navigator_factory) for more information \[[GEB-96](http://jira.codehaus.org/browse/GEB-96)\].
* New variants of `withFrame()` method that allow to switch into frame context and change the page in one go and also automatically change it back to the original page after the call, see [switching pages and frames at once][switch-frame-and-page] in the manual \[[GEB-213](http://jira.codehaus.org/browse/GEB-213)\].
* `wait`, `page` and `close` options can be passed to `withNewWindow()` calls, see [this manual section](browser.html#passing_options_when_working_with_newly_opened_windows) for more information \[[GEB-167](http://jira.codehaus.org/browse/GEB-167)\].

#### Fixes

Expand Down
@@ -1,10 +1,10 @@
package geb

import geb.test.GebSpecWithServer
import geb.driver.CachingDriverFactory
import groovy.xml.MarkupBuilder
import spock.lang.Unroll
import geb.error.NoNewWindowException
import geb.test.GebSpecWithServer
import org.openqa.selenium.NoSuchWindowException
import spock.lang.Unroll

class WindowHandlingSpec extends GebSpecWithServer {

Expand All @@ -29,7 +29,7 @@ class WindowHandlingSpec extends GebSpecWithServer {
body {
[1, 2].each {
def label = "$page-$it"
a(target: "window-$label", href: "/$label")
a(id: label, target: "window-$label", href: "/$label")
}
}
}
Expand Down Expand Up @@ -136,7 +136,7 @@ class WindowHandlingSpec extends GebSpecWithServer {
withNewWindow(newWindowBlock) {}

then:
NoSuchWindowException e = thrown()
NoNewWindowException e = thrown()
e.message.startsWith(message)

where:
Expand Down Expand Up @@ -176,6 +176,78 @@ class WindowHandlingSpec extends GebSpecWithServer {
windowTitle(1) | 1
windowTitle(2) | 2
}

def "withNewWindow closes the new window if 'close' option is passed"() {
given:
go MAIN_PAGE_URL

when:
withNewWindow({ openWindow(1) }, close: true) {}

then:
availableWindows.size() == 1
inContextOfMainWindow

}

def "withNewWindow closes the new window if 'close' option is passed and block closure throws an exception"() {
given:
go MAIN_PAGE_URL

when:
withNewWindow({ openWindow(1) }, close: true) { throw new Exception() }

then:
thrown(Exception)
availableWindows.size() == 1
}

def "withNewWindow block closure is called in the context of the page passed as the 'page' option"() {
given:
go MAIN_PAGE_URL
page WindowHandlingSpecMainPage

when:
withNewWindow({ openWindow(1) }, page: WindowHandlingSpecNewWindowPage) {
assert page.getClass() == WindowHandlingSpecNewWindowPage
}

then:
page.getClass() == WindowHandlingSpecMainPage
}

def "page context is reverted after a withNewWindow call where block closure throws an exception and 'page' option is present"() {
given:
go MAIN_PAGE_URL
page WindowHandlingSpecMainPage

when:
withNewWindow({ openWindow(1) }, page: WindowHandlingSpecNewWindowPage) {
throw new Exception()
}

then:
thrown(Exception)
page.getClass() == WindowHandlingSpecMainPage
}

def "'wait' option can be used in withNewWindow call if the new window opens asynchronously"() {
given:
go MAIN_PAGE_URL
page WindowHandlingSpecMainPage

when:
withNewWindow({
js.exec """
setTimeout(function() {
document.getElementById('main-1').click();
}, 200);
"""
}, wait: true) {}

then:
notThrown(NoNewWindowException)
}

def "withWindow methods can be nested"() {
given:
Expand Down Expand Up @@ -222,3 +294,6 @@ class WindowHandlingSpec extends GebSpecWithServer {
}

}

class WindowHandlingSpecMainPage extends Page {}
class WindowHandlingSpecNewWindowPage extends Page {}
62 changes: 48 additions & 14 deletions module/geb-core/src/main/groovy/geb/Browser.groovy
Expand Up @@ -25,8 +25,7 @@ import org.openqa.selenium.WebDriver
import org.openqa.selenium.WebDriverException
import org.openqa.selenium.NoSuchWindowException
import geb.navigator.factory.NavigatorFactory
import geb.navigator.factory.BrowserBackedNavigatorFactory
import geb.navigator.factory.DefaultInnerNavigatorFactory
import geb.error.NoNewWindowException

/**
* The browser is the centre of Geb. It encapsulates a {@link org.openqa.selenium.WebDriver} implementation and references
Expand Down Expand Up @@ -574,32 +573,67 @@ class Browser {

/**
* Expects the first closure argument to open a new window and calls the second closure argument in the context
* of the newly opened window.
* of the newly opened window. A map of options can also be specified that allows to close the new window, switch to a
* different page for closure executed in the context of the new window and also to wait for the window opening if the
* window opening is asynchronous.
*
* @param options a map that can be used to pass additional options
* @param windowOpeningBlock a closure that should open a new window
* @param block closure to be executed in the new window context
* @return The return value of {@code block}
* @throws org.openqa.selenium.NoSuchWindowException if the window opening closure doesn't open one or opens more
* @throws geb.error.NoNewWindowException if the window opening closure doesn't open one or opens more
* than one new window
*/
def withNewWindow(Closure windowOpeningBlock, Closure block) {
def originalWindows = availableWindows
def withNewWindow(Map options, Closure windowOpeningBlock, Closure block) {
def originalWindow = currentWindow
def originalPage = page

windowOpeningBlock.call()
def newWindow = (availableWindows - originalWindows) as List

if (newWindow.size() != 1) {
def message = newWindow ? 'There has been more than one window opened' : 'No new window has been opened'
throw new NoSuchWindowException(message)
}
def newWindow = executeNewWindowOpening(windowOpeningBlock, options.wait)
try {
switchToWindow(newWindow.first())
switchToWindow(newWindow)
if (options.page) {
page(options.page)
}
block.call()
} finally {
if (options.close) {
driver.close()
}
switchToWindow(originalWindow)
page originalPage
}
}

private String executeNewWindowOpening(Closure windowOpeningBlock, wait) {
def originalWindows = availableWindows
windowOpeningBlock.call()

if (wait) {
config.getWaitForParam(wait).waitFor { (availableWindows - originalWindows).size() == 1 }
}

def newWindows = (availableWindows - originalWindows) as List

if (newWindows.size() != 1) {
def message = newWindows ? 'There has been more than one window opened' : 'No new window has been opened'
throw new NoNewWindowException(message)
}
newWindows.first()
}

/**
* Expects the first closure argument to open a new window and calls the second closure argument in the context
* of the newly opened window.
*
* @param windowOpeningBlock a closure that should open a new window
* @param block closure to be executed in the new window context
* @return The return value of {@code block}
* @throws geb.error.NoNewWindowException if the window opening closure doesn't open one or opens more
* than one new window
*/
def withNewWindow(Closure windowOpeningBlock, Closure block) {
withNewWindow([:], windowOpeningBlock, block)
}

/**
* Creates a new instance of the given page type and initialises it.
Expand Down
@@ -0,0 +1,23 @@
/*
* Copyright 2012 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.error;

public class NoNewWindowException extends GebException {
public NoNewWindowException(String message) {
super(message);
}
}

0 comments on commit 5749221

Please sign in to comment.