In this workshop we'll introduce the participants to the Geb framework and how to use it for real groovy browser automation.
We will make use of the Spock framework for the tests but won't go into much detail on that.
In this workshop we will be using an instance of RealWorld example app which is dockerised based on a docker-compose.yaml file. We will be using the React + MobX version of the frontend which is available on docker hub based on this clone repository. For the backend, we will be using the SpringBoot version which is also available on docker hub and it's based on this clone repository.
- To import the project into IntelliJ simply run
./gradlew openIdea(orgradlew.bat openIdeaif you're using Windows) in the root of the repository - To start up the application run
docker-compose up- the application will be available on localhost at port 3000. - How does this project work (see GebConfig.groovy, build.gradle)
- Open
BasicSpecand implement the first feature. First use thego()method to navigate http://localhost:3000/. Then use the$()method the verify there is adivwith classhome-page. - In the second feature, check if the only
h1on the page has thetext"Sign Up". - In the third feature the
h1'stextshould be "Sign In". - Since we don't want to re-type that URL all the time, let's define it as
baseUrlfor the suite. Open GebConfig.groovy and set thebaseUrloutside of theenvironmentsblock. Implement the three outstanding features by using relative URLs and rely onbaseUrl.
So now we already visited three different pages and implemented checks to verify we are at these pages. Next we would want to interact with these pages, which would lead to a lot of duplicate code.
- Create
HomePage,RegistrationPageandLoginPageclasses all extendinggeb.Pageand located inorg.gebish.geb.workshop.objective02pagespackage. Set the pageurls and specify anatchecker on each of them. Implement features inAtCheckerSpecby usingto(). Use theat()method in your assertions even thoughto()automatically verifies the success of the navigation by callingat(). Also note thatto()returns an instance of the page object class passed to it.
Especially on single page applications, some content is loaded dynamically. This means that they will show up eventually, but we cannot tell when. When using such a page as a human, you might not even notice, but Geb will try to find the element immediately and will fail if it is not there, yet.
- Copy the
RegistrationPagefromobjective02pagespackage toobjective03waitingpackage. Implement the feature inWaitingSpec. Go toRegistrationPagefill out the email and password fields and submit the form. Check for an error message and verify it starts with "username can't be blank". Since the error message is loaded dynamically, you will need to wait for it to appear.
So far we've been using inline css selectors in specs. This can be very repetitive the more you interact with content in tests and that's why Geb has a concept of content definitions to remove the need for such duplication. It also means that if the selectors change for any reason then they have to be changed only in one place and not for each tests in which they are used.
- Copy the
HomePageandRegistrationPagefromobjective02pagespackage toobjective04contentpackage. Introduce a content definition for the selector used inHomePage'satchecker. Introduce content definitions for username, email and password form elements, the submit button and the list of error messages inRegistrationPageas well as a content definition for the selector used inatchecker. The implementation of the first feature inContentSpecshould be very similar to the implementation of the feature from the previous objective but it should use the newly introduced content definitions. Use the password, username and email values already provided in the spec. For the second feature, go toRegistrationPage, fill out the email and password as well as the username and check that you end up atHomePageafter submitting the form.
Sometimes web applications' pages consist of several interesting components. E.g. the page's header or navigation or a form with multiple inputs. We refer to these components in natural language. E.g. "Fill the registration form." Geb's modules are a way to achieve that. Modules are not only great for complex content, but also for repeating content.
- Copy the
HomePagefromobjective04contentpackage toobjective05modulespackage. Add anArticlePreviewclass extendinggeb.Modulewith a content definition for author username. Add a content definition toHomePagefor a list of article previews usingNavigator'smoduleList()method. Implement the feature inModulesSpecby going toHomePage, grouping previews by author and then asserting article counts for each of the authors. There is already some code inModulesSpecwhich deals with bootstrapping article data for you.
Pages and modules are not only useful for abstracting selectors out into content definitions. It's also useful to declare methods on pages and modules which contain common actions. Such methods can help to avoid duplication and make tests more readable.
- Copy
LoginPagefromobjective02pagespackage andHomePageas well asArticlePreviewmodule fromobjective05modulespackage toobjective06pagemethodspackage. Add alogin()method toLoginPagetaking a username and password which fills out the login form and submits it. Use that new method inPageMethodsSpecto login atLoginPageand verify that you end up atHomePageafterwards. Code creating a user is already provided in the spec.
When dealing with asynchronous applications waiting for asynchronous events to complete is very important otherwise the tests will be flakey. A useful technique is to wrap waiting in methods of pages and modules. Waiting should be added immediately after executing an action triggering an asynchronous event. This makes tests appear as if they were synchronous even when they are not. Another benefit is that all team members do not have to exactly understand which parts are asynchronous as long as they use such methods.
- [] Copy
LoginPage,HomePageandArticlePreviewfromobjective06pagemethodspackage toobjective07advancedwaitingpackage. Add a content definition toHomePageselecting the div with "No articles are here... yet." text which appears when there are no articles. Change the at checker to check if either some article previews or no article available message has been rendered. This will mean that the at checker forHomePagesucceeds only after the page is fully loaded even though article previews are resolved asynchronously. Add a check at the end of thelogin()method inLoginPageto ensure that we are atHomePage- that way calls tologin()will finish only after theHomePagehas been rendered even though it happens asynchronously. Actions in the first feature ofAdvancedWaitingSpecshould be the same as inModulesSpecbut there should not be any explicit waiting in the spec. Implementation of the second feature should be the same as the one inPageMethodsSpec.
- setting a
baseUrlviaGebConfig.groovyor system property - reporting, the
GebReportingSpec - defining
environments waitpresets
- avoid the
StaleElementException - combination with Testcontainers