How to refactor common Geb test sequences
Asked Answered
M

4

3

Suppose I have multiple Geb/Spock tests that beings with logging in. For example:

@Stepwise
Class AddNewPictureSpec extends GebSpec {
  def "User at login page"() {
    given: "User beings from login page"
    to LoginPage
  }
  def "User gets redirected to Main page"() {
    given: "User at Login page"
    at LoginPage

    when: "User signs in"
    signIn "username", "pw"
    to MainPage

    then:
    at MainPage

  def "other test sequences follow...."() {
  }    
}

And another test spec with the exact same start sequence:

@Stepwise
Class EditPictureSpec extends GebSpec {
      def "User at login page"() {
        given: "User beings from login page"
        to LoginPage
      }
      def "User gets redirected to Main page"() {
        given: "User at Login page"
        at LoginPage

        when: "User signs in"
        signIn "username", "pw"
        to MainPage

        then:
        at MainPage    

  def "other test sequences follow...."() {
      }
    }

How do I refactor/extract out the common login "steps" so that I do not have duplicate code? Or am I writing my tests wrongly? Thanks.

Mechanist answered 8/5, 2014 at 4:15 Comment(1)
Which is your preferred method. If you like any of the answers below accept one of the answers or comment why they didn't work for you.Tecla
R
6

I think the 'geb' way to do this is to use modules.

You can create a login module like this:

class LoginModule extends Module {  
    static content = {
        loginForm {$("form")}
        loginButton {$("input", value: "Sign in")}
    }

    void login(String username, String password = "Passw0rd!") {
        loginForm.j_username = username
        loginForm.j_password = password
        loginButton.click()
    }
}

Include it in your LoginPage:

class LoginPage extends Page {
    static url = "login/auth"   

    static at = {title == "My Grails Application"}

    static content = {
        loginModule { module LoginModule }
    }
}

Then in your test, you can reference your module's login method:

@Stepwise
class EditPictureSpec extends GebSpec {

    def setupSpec() {
        to LoginPage
        loginModule.login(loginUsername)
    }

    def "some test"() {
        ...
    }
}
Rafael answered 8/5, 2014 at 7:25 Comment(0)
O
3

You can create a login method and put it in a BaseSpec (that you would also create), which you would then extend in your tests. Eg:

class BaseSpec extends GebReportingSpec {

  def login(name, pw) {
    to LoginPage
    // login code here... 
  }

}

Since you're using @StepWise, I'm assuming you're logging in once per spec, so use setupSpec() thusly...

Class AddNewPictureSpec extends BaseSpec {
  def setupSpec() {
    login("username", "password")
  }
}
Octofoil answered 8/5, 2014 at 14:21 Comment(0)
I
2

One possibility is to have one Spec for verifying the actual login behavior (e.g. LoginSpec) that is completely written out as it is now. For other Specs that need to login before doing the actual test you can abstract the entire login process behind a method in the LoginPage. Like you do now with singIn.

When you have a lot of Specs that need to login before they can really start testing the functionality they intend to test, doing the login steps through the browser again and again can take a lot of time.

An alternative can be to create a specific controller that is only loaded in the dev/test environments and that offers a login action. So instead of going through all steps (go to page, enter name, enter password, ...) you can simply go to the URL /my-app/testLogin/auth?username=username.

Below an example how we do this in our Grails + Spring Security setup. We also bundle other utility methods in that controller that are used in the setup of multiple Specs and that would otherwise require several clicks in the browser, e.g. changing the interface language.

// Example TestLoginController when using the Spring Security plugin
class TestLoginController {

def auth = { String userName, String startPage = 'dashboard' ->

    // Block the dev login functionality in production environments
    // Can also be done with filter, ...
    Environment.executeForCurrentEnvironment {
        production {
            render(status: HttpServletResponse.SC_NOT_FOUND)
            return
        }
    }

    def endUser = getYourEndUserDataByUsername()
    if (endUser) {
        // Logout existing user
        new SecurityContextLogoutHandler().logout(request, null, null)

        // Authenticate the user
        UserDetails userDetails = new User(endUser)
        def authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, userDetails.password, userDetails.authorities)
        SecurityContextHolder.context.setAuthentication(authenticationToken)

        // Bind the security context to the (new) session
        session.SPRING_SECURITY_CONTEXT = SecurityContextHolder.context

        redirect(action: "index", controller: startPage)
    }
}
Isochronism answered 8/5, 2014 at 6:40 Comment(2)
I personally like using Groovy Remote Control for this because then I can have arbitrary code in my test without a need to create multiple test/fixture controllers. I described this technique here.Dieterich
Remote Control is indeed another option to achieve login in (and other fixture setup tasks) without clicking in the browser all the time. We use the Remote Control plugin to change configuration values, e.g. feature toggles, during test setup. But choose the dev/test controller approach for login, language change, etc. since we always want the page to reload anyway.Isochronism
S
1

The correct solution is to create methods on a geb Page which encapsulate common functionality:

class LoginPage extends Page {
    static url = "login/auth"   

    static at = {title == "Login"}

    static content = {
        username { $("#user") }
        password { $("#password") }
    }

    def login(String email, String passwd) {
        emailInput.value(email)
        passwordInput.value(passwd)
        passwordInput << Keys.ENTER
    }
}

Then your test looks like this:

@Stepwise
class ThingSpec extends GebSpec {

    def setupSpec() {
        to LoginPage
        page.login("user", "pass")
    }

    def "some test"() {
        ...
    }
}

From an OOP perspective this is the best solution as the login procedure is only applicable to the login page. It doesn't make sense to use a module because no other page has a login box (unless it does, then modules make sense.)

It also doesn't make sense to use inheritance, you'll end up with an unorganized pile of methods and class names like "BaseSpec", bleh.

Stereo answered 4/6, 2014 at 17:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.