How to refactor common Geb test sequences - grails

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.

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"() {
...
}
}

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")
}
}

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)
}
}

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.

Related

geb handing multiple tabs simultaneously

My testing environment is Geb+Spock.
I have one test case scenario in which I perform some operation(e.g.creating a user) in one tab[URL1] of chrome browser and after performing that operation I want to verify it on the other tab[URL2].
Both tab's URLs are different.
I have tried below approach but it didn't work:
class checkUserSpec {
def 'create new users'(){
given:'URL of creating user page'
browser.at(createUserPage)
when:'create user'
createUser()
then:'User should be created'
withNewWindow({js.exec("window.open(URL2', 'opennewtab', '')")} ,page:verifyUserPage, wait: true) {
at verifyUserPage
verifyUserPage.with{
verifyCreatedUser()
}
}
}
}
class createUserPage {
static URL = 'URL1'
static at ={
$(title: "Create User Page")
}
def createUser(){
......
......
}
}
class verifyUserPage {
static URL = 'URL2'
static at ={
$(title: "Verify User Page")
}
def verifyCreatedUser(){
......
......
}
}

handle successful login event with spring security

My Grails app uses the Spring Security plugin. Whenever a user successfully logs in I want to:
store something in the session
redirect them to a custom page (depending on their role)
I need to handle logout events similarly, which was pretty straightforward because the plugin provides a bean named logoutSuccessHandler that can be overriden. I was hoping to similarly find a bean named loginSuccessHandler, but no such luck.
I read the page in the plugin's docs about event handling, but neither of the event handling mechanisms appears to give me access to the current request or session.
If you want to do some stuff upon successful login. You can listen to InteractiveAuthenticationSuccessEvent
class AuthenticationSuccessEventListener implements
ApplicationListener<InteractiveAuthenticationSuccessEvent> {
#Override
public void onApplicationEvent(InteractiveAuthenticationSuccessEvent event) {
.......do some stuff here
}
}
And then register AuthenticationSuccessEventListener as a spring bean in resources.groovy
You can do whatever you want here, however you wont be able to do redirect from listener.
Here's another similar question
Add a config parameter:
grails.plugins.springsecurity.successHandler.defaultTargetUrl = '/myLogin/handleSuccessLogin'
Then add your custom login-handling in the action that handles this URL
class MyLoginController {
def springSecurityService
def handleSuccessLogin() {
session.foo = 'bar'
if (springSecurityService.currentUser.username == 'bob') {
redirect action: 'bobLogin'
} else {
redirect action: 'defaultLogin'
}
}
def bobLogin() {
// bob's login handler
}
def defaultLogin() {
// default login handler
}
}
I recently used this in a project for logging in. Its kind of a hack but works for me. I'm using version 1.2.7.3 of the plugin.
def auth() {
def config = SpringSecurityUtils.securityConfig
if (springSecurityService.isLoggedIn()) {
def user = User.get(principal.id)
def roles = user.getAuthorities()
def admin_role = Role.findByAuthority("ROLE_ADMIN")
//this user is not admin
if(!roles.contains(admin_role)){
//perform redirect to appropriate page
}
redirect uri: config.successHandler.defaultTargetUrl
//log.info(getPrincipal().username + "logged in at :"+new Date())
return
}
String view = 'auth'
String postUrl = "${request.contextPath}${config.apf.filterProcessesUrl}"
render view: view, model: [postUrl: postUrl,
rememberMeParameter: config.rememberMe.parameter]
}
For logging out I used a Logout controller, performed some action before redirecting to the logout handler:
class LogoutController {
/**
* Index action. Redirects to the Spring security logout uri.
*/
def index = {
// perform some action here
redirect uri: SpringSecurityUtils.securityConfig.logout.filterProcessesUrl
}
}

Unable to Populate select-option tag using g:select in Groovy

I am trying to run the sample Collab-Todo application as in book Beginning Groovy and Grails,page no.123 (Author:Christopher M. Judd,Joseph Faisal Nusairat,and James Shingler Publication:Apress and Edition:2008). Here is my User.groovy file:
package collab.todo
class User {
String userName
String firstName
String lastName
static hasMany = [todos: Todo, categories: Category]
static constraints = {
userName(blank:false,unique:true)
firstName(blank:false)
lastName(blank:false)
}
String toString () {
"$lastName, $firstName"
}
}
The UserController.groovy is as Follows:
package collab.todo
class UserController {
def scaffold = User
def login = {
}
def handleLogin = {
def user = User.findByUserName(params.userName)
if (!user) {
flash.message = "User not found for userName: ${params.userName}"
redirect(action:'login')
}
session.user = user
redirect(controller:'todo')
}
def logout = {
if(session.user) {
session.user = null
redirect(action:'login')
}
}
}
I am able to create,read,update or delete the User table as usual.Here is a sample screenshot of my User view:
In the scaffolding view, I am trying to show the list of all the users in a drop-down(as per the book) using following snippet inside the user/login.gsp:
<g:select name='userName' from="${User?.list()}"
optionKey="userName" optionValue="userName"></g:select>
But what I am getting in the login page is a dropdown with no values populated:
Here is the screenshot of the login page:
In case I change
from="${User?.list()}"
to
from="${User.list()}"
I am getting a NullPointerException. So any clues what is going on?
It looks like the User class could not be found from your view.
Try one of the following:
Add the import statement to your view.
<%# page import="collab.todo.User" %>
Or use the fully qualified name within from attribute.
from="${collab.todo.User.list()}"
The best practice would be to pass the list of users from the controller:
def login = {
[users: User.list()]
}
And use the collection within your view
from="${users}"

Spring security core plugin: Different home page for user depending upon its Role

I have integrated Spring security core plugin in my Grails application.
grails.plugins.springsecurity.successHandler.defaultTargetUrl = "/user/home"
This is what I have done to set default home page after successful login. But I would like to have different home page depending upon user roles
Currently I have 2 user roles
1)"ROLE_ADMIN"
2)"ROLE_USER"
How would I implement this?
One quick way would be to do the logic in the controller action. For example, the home action could render a different view based on role, e.g.:
import grails.plugin.springsecurity.annotation.Secured
class UserController {
def home() {
String view
if (SpringSecurityUtils.ifAllGranted('ROLE_ADMIN')) {
view = 'admin'
}
else if (SpringSecurityUtils.ifAllGranted('ROLE_USER')) {
view = 'user'
}
else {
// ???
}
render view: view, model: [...]
}
}
If you want to distribute the logic among different controllers, you could redirect based on role:
import org.codehaus.groovy.grails.plugins.springsecurity.SpringSecurityUtils
class UserController {
def home() {
if (SpringSecurityUtils.ifAllGranted('ROLE_ADMIN')) {
redirect controller: '...', action: '...'
return
}
if (SpringSecurityUtils.ifAllGranted('ROLE_USER')) {
redirect controller: '...', action: '...'
return
}
// ???
}
}
You can configure an authentication success handler too which will redirect users to specific controllers based on the roles.
class CustomAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
LinkGenerator linkGenerator
private static final ADMIN_ROLE = 'ROLE_Admin'
#Override
protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response) {
if(SpringSecurityUtils.ifAllGranted(ADMIN_ROLE)) {
return linkGenerator.link(controller: 'admin', action: "index")
}
return super.determineTargetUrl(request, response);
}
}
See Spring Security Core : Redirect users to different screen based on role

How to authenticate user against spring Security in unit tests

I am using spring security core plugin (1.2.7) with grails 2.0
Let's say that I have controller with a method that uses #Secured annotation.
class ArticleController {
def springSecurityService
#Secured(['ROLE_PREMIUM_USER'])
def listPremium() {
render 'premium content'
}
}
in my unit test I would like to test if a user with role 'ROLE_PREMIUM_USER' can see content of listPremium method. How can I do this?
I know that it should start as follows:
#TestFor(ArticleController)
#Mock([SpringSecurityService])
class ArticleControllerTests {
void testListPremium() {
defineBeans {
springSecurityService(SpringSecurityService)
}
//but how to login the user here in order to see premium content?
controller.listPremium()
assert response.text() == 'premium content'
}
}
I am not sure how can I authenticate user or mock action that checks ROLE_PREMIUM_USER. Any help?
You may be able to use
SpringSecurityUtils.reauthenticate username, null
We created our custom AuthenticationHelper:
public final class AuthenticationHelper {
public static Authentication authenticate(UserDetailsService userDetailsServiceImpl, String userName) {
UserDetails userDetails = userDetailsServiceImpl.loadUserByUsername(userName);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword());
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(userDetails, token.getCredentials(), userDetails.getAuthorities());
result.setDetails(token.getDetails());
Authentication auth = result;
SecurityContextHolder.getContext().setAuthentication(auth);
auth = SecurityContextHolder.getContext().getAuthentication();
Assert.assertTrue(auth.isAuthenticated());
return auth;
}
}
The important part is:
SecurityContextHolder.getContext().setAuthentication(auth);

Resources