Using multiple browsers in a Geb functional test

When testing a web application that does realtime communication between browsers using something like WebSockets, it’d be great to be able to verify in an automated test that data submitted in one browser shows up in another. Normally you’d have tests running in a single browser instance when using a browser functional testing library like Geb, but what if you could drive separate browsers in the same test to verify cross-browser WebSocket communication?

Thanks

This code is based on code from Alex Anderson’s Geb multibrowser project on Github - thanks Alex!

Multi-browser test class

We’ll create a parent test class that encapsulates all the pieces we’ll need to use multiple browsers in the same Geb test. We’ll call this parent class MultiBrowserGebSpec. First, we’ll need to store the different browsers our test uses.

class MultiBrowserGebSpec extends GebReportingSpec {
    private Map<String, Browser> browserMap = [:]

    private Browser overrideBrowser = null
}

Next, we’ll need a way to control these different browsers during a test execution. To do this, we’ll create a new method withBrowserSession that allows a test to run a given set of Geb commands in a specific browser instance.

/**
 * Run Geb commands in a separate browser window, creating it if necessary.
 *
 * @param browserId user-specified identifier string to reference this browser instance. If a browser with this ID
 *                  was used previously in the same test, will re-use it. Otherwise, will create a new browser.
 * @param c Closure with the code you want to execute in the separate browser.
 */
void withBrowserSession(String browserId, Closure c) {
    if (!browserMap[browserId]) {
        Browser browser = createBrowser()
        // Disable browser caching so it will create another browser
        browser.config.cacheDriver = false

        browserMap[browserId] = browser
    }

    overrideBrowser = browserMap[browserId]

    c.call()

    overrideBrowser = null
}

And to have Geb use the overrideBrowser field, we simply need to override the getBrowser similar to this:

@Override
Browser getBrowser() {
    overrideBrowser ?: super.getBrowser()
}

Use different browsers in test

Now we can use our withBrowserSession method to execute to control different browser sessions in the same test. We just need to call that method with a browser identifier string and the closure with the code we want to execute in that browser.

class StandupMultiBrowserGebSpec extends MultiBrowserGebSpec {
    void 'when submitting status in a separate browser window should appear in both browsers'() {
        given:
        HomePage homePageFirstBrowser = to(HomePage)

        when:
        withBrowserSession('second') {
            HomePage homePageSecondBrowser = to(HomePage)
            homePageSecondBrowser.submitStatus('Second browser')
        }

        then:
        waitFor { homePageFirstBrowser.findStatusFor('Second browser') }

        when:
        homePageFirstBrowser.submitStatus('First browser')

        then:
        withBrowserSession('second') {
            HomePage homePageSecondBrowser = browser.page
            waitFor { homePageSecondBrowser.findStatusFor('First browser') }
        }
    }
}

Close all browsers

When the test completes Geb needs to close down the browser instances, and since we’re using multiple browsers we’ll need to ensure all our browsers are closed. To do that, we can override the resetBrowser method:

@Override
void resetBrowser() {
    super.resetBrowser()

    browserMap.each { browserId, browserFromMap ->
        if (browserFromMap?.config?.autoClearCookies) {
            browserFromMap.clearCookiesQuietly()
        }
        // Only cached browsers are automatically closed, so close all the non-cached browsers by hand
        browserFromMap.close()
    }

    browserMap.clear()
}

Screenshot/HTML reports

One of the handy features of GebReportingSpec is the automatic screenshot and HTML reports it creates. But the default Geb behavior only knows about its single browser instance, so we’ll override the report method to write out these reports for all the browsers our test is using.

@Override
void report(String label = "") {
    super.report(label)

    Class testClass = getClass()

    browserMap.each { browserId, browserFromMap ->
        browserFromMap.reportGroup(testClass)

        String reportLabel = (label) ? "${browserId}-${label}" : browserId

        // This report name generation will be simpler once there is a new Geb release with the changes from this pull request
        // https://github.com/geb/geb/pull/123
        String reportName = ReporterSupport.toTestReportLabel(1, 1, gebReportingSpecTestName.methodName, reportLabel)
        browserFromMap.report(reportName)
    }
}

Source code

The full source code for the WebSocket project is on Github at https://github.com/craigatk/ratpack-standup with the multi-browser Geb testing code in the https://github.com/craigatk/ratpack-standup/tree/master/src/test/groovy/standup/geb/multi folder.

Happy testing!