I have recently been reading through the book of Geb and trying to get to grips with it as it seems like a great tool. I feel like I'm getting there but there's still some really core concepts that I can't seem to grasp.
As an example, take the website:-
http://www.escapistmagazine.com/videos/view/zero-punctuation
Now if I want to test that the first video listed links to the correct page my first thought was to do the following
class ZeroPunctuationIndexPage extends Page {
static url = "http://www.escapistmagazine.com/videos/view/zero-punctuation"
static at = {title == "Zero Punctuation Video Gallery | The Escapist"}
static content = {
selectFirstVideo {$("a", 0, class: "filmstrip_video")}
firstVideoTitle {$("i", 0, class: "filmstrip_video")}
}
}
class ZeroPunctuationIndexSpec extends GebReportingSpec {
def "Click the latest video and play it"(){
given:
to ZeroPunctuationIndexPage
when:
selectFirstVideo.click()
then:
title.endsWith(firstVideoTitle)
}
}
Essentially I thought if I pick the first class that contains the video (filmstrip_video) and then pick the link inside it then I could click that, and compare the video title from the link to the new page title.
Looking at it again I'm not surprised it didn't work, but I'm not sure what to do.
If anyone could give me a quick solution i'd appreciate it. I'm not doing anything in particular with the escapist page, I just picked somewhere with an index of videos to try out writing a test.
Thank you!
It was not getting the link and it was failing, so I fixed that and double checked. Now, it's working fine and it passes. Also, when you are selecting the first element, you don't need to write 0 explicitly as by default it will choose the 0th element.
class ZeroPunctuationIndexPage extends Page {
static url = "http://www.escapistmagazine.com/videos/view/zero-punctuation"
static at = {title == "Zero Punctuation Video Gallery | The Escapist"}
static content = {
selectFirstVideo {$("div.filmstrip_video").find('a')}
firstVideoTitle {$("div.filmstrip_video").find('i')}
}
}
class ZeroPunctuationIndexSpec extends GebReportingSpec {
def "Click the latest video and play it"(){
given:
to ZeroPunctuationIndexPage
when:
waitFor { selectFirstVideo.click() }
then:
title.endsWith(firstVideoTitle.text())
}
}
Cheers!
Try:
title.endsWith(firstVideoTitle.text())
I'm on my iPad, so I can't test it, but it should work.
Related
Was EphemeralKeyRing omitted from GitHub for Security Reasons?
This one is a brain teaser. I've spent quite a bit of time lately reading and and absorbing the hierarchy of classes that relate to asp.net core session storage and asp.net core data protection. In those journeys, I have come across a reference to a EphemeralKeyRing class. However, the code for this class does not seem to be in the Asp.Net Core source code repository on GitHub. Equally odd, when doing a google search on this class name, I can find no references anywhere on the internet that are about this asp.net core class other than the one GitHub source code file that uses it.
Here is the class that news up a EphemeralKeyRing object: https://github.com/aspnet/DataProtection/blob/rel/1.1.0/src/Microsoft.AspNetCore.DataProtection/EphemeralDataProtectionProvider.cs
Here's the results of a GitHub search for the EphemeralKeyRing class in the Asp.Net Core repository:
:
And here is the an amazingly sparse set of google results when searching for EphemeralKeyRing. Note the first entry is the code file on GitHub that I mentioned above which uses the object and the other results are unrelated to this asp.net core class.
So my question is this: Was the source code for the EphemeralKeyRing class omitted from GitHub purposely for security reasons? Or is it there and I'm just searching wrong?
Here is the link:
https://github.com/aspnet/DataProtection/blob/master/src/Microsoft.AspNetCore.DataProtection/EphemeralDataProtectionProvider.cs
which I see you found and clicked on it already. If you go to the bottom of the page youll see the class you are looking for, I'll paste the code just in case:
private sealed class EphemeralKeyRing<T> : IKeyRing, IKeyRingProvider
where T : IInternalAuthenticatedEncryptionSettings, new()
{
// Currently hardcoded to a 512-bit KDK.
private const int NUM_BYTES_IN_KDK = 512 / 8;
public IAuthenticatedEncryptor DefaultAuthenticatedEncryptor { get; } = new T().ToConfiguration(services: null).CreateNewDescriptor().CreateEncryptorInstance();
public Guid DefaultKeyId { get; } = default(Guid);
public IAuthenticatedEncryptor GetAuthenticatedEncryptorByKeyId(Guid keyId, out bool isRevoked)
{
isRevoked = false;
return (keyId == default(Guid)) ? DefaultAuthenticatedEncryptor : null;
}
public IKeyRing GetCurrentKeyRing()
{
return this;
}
}
We use Geb to run our Frontend Tests and we have some quite complex pages in our application.
Some of the pages have forms with a lot of different buttons, checkboxes and some multiselects.
I love the feature of geb/groovy that i just have to define the form in the Page Object and then can access all its elements in it.
static content = {
form { $("#form")}
}
But for them to be clickable and to query if they are readonly and more they need to be at least of type FormElement which does not happen with the above method. So I have to mention all these FormElements separately:
static content = {
form { $("#form")}
button1 { $("#button1").module(FormElement)}
button2 { $("#button2").module(FormElement)}
checkbox{ $("#checkbox").module(Checkbox)}
...
}
All those buttons, checkboxes... are already in the form variable, but cannot be clicked or checked if they are selected and so on. It's also not possible to apply the the module afterwards like this:
def "test something"() {
when:
form.button1.module(FormElement).click() //error
then:
...
}
Is there no way to automatically assign each input, checkbox, radiobutton, button,... the correct Module based on their type without the need of doing it by hand?
If someone could also point me in the right direction to understand how this "form { $("#form")}" works, that i can access all sub elements by its name by just suppying the form, that would be nice!
For your example of creating a module based on a form control you need to obtain a navigator for the control and not it's value. It's done by calling a method named the same as the control you're trying to access (it's explained in this section of The Book of Geb):
form.button1().module(FormElement).click()
If you want to automatically create modules based on the element type then you could create a Module for the form and override method missing:
class FormModule extends Module {
Object methodMissing(String name, Object args) {
def result = super.methodMissing(name, args)
if (result instanceof Navigator && result.tag() == "input") {
switch (result.#type) {
case "checkbox":
result = result.module(Checkbox)
break
default:
result = result.module(FormElement)
}
}
result
}
}
then you would use it like:
static content = {
form { $("#form").module(FormModule) }
}
form.button1().click()
I'm new to Groovy and testing a website with reasonably complex workflows using Geb/Cucumber.
I have a set of product pages represented by page classes Prod1Page, Prod2Page and Prod3Page. They share certain characteristics that can be tested by the same code.
I want to write a ProductPage page class along the lines of:
class ProductPage extends Page {
...
static at = {...}
...
}
so that
Given(~/.../) {
...
at ProductPage
...
}
would pass if any of the following were true:
at Prod1Page
at Prod2Page
at Prod3Page
In case it's not clear my goal is to avoid duplicating the details of Prod[1-3]Page classes somewhere else.
Thanks for any insights,
Nick
In the ProductPage's at checker just assert that the common HTML elements between all Prod#Pages are available. For example, if all ProdPages have a <div id="productContainer"> element then you can put the following in your ProductPage class:
class ProductPage extends Page{
static at = { $("#productContainer").displayed }
...
}
Now in your test you can call at ProductPage and it will assert true if that common element is currently available on the WebDriver's DOM; and you will know that you are on one of the ProdPages.
When I click a button I have to wait for some dynamic content to be rendered. When I put the waitFor closure in the test it works correctly. However, I wanted to put the waitFor in a method inside the Page object so I do not have to always call the waitFor after every click, but when I do that it fails stating it cannot find the property.
This does not work:
class LandingPage extends Page {
static content = {
resultsBtn(to: ResultsPage) { $("button", id: "showresults") }
}
void getResults() {
resultsBtn.click()
waitFor { ResultsPage.results.displayed }
}
}
class ResultsPage extends Page {
static content = {
results { $("div", id: "listresults") }
}
}
class ShowResults extends GebReportingTest {
#Test
public void displayResults() {
to LandingPage
getResults()
}
}
The error states something like "No such property: results for class ResultsPage".
Is it possible to put references to content from other Page Objects inside other Page Object methods?
EDIT: I feel like this is more of a Groovy specific thing rather than Geb. I'm not sure if it's even possible to access bindings within the content closure. But it also seems like creating a getVariable() function inside the Page Object doesn't help much either.
First you shouldn't assign closures in content blocks (there's unnecessary = in ResultPage) but pass them to an implicit method, you should have:
static content = {
results { $("div", id: "listresults") }
}
The other question is why do you want to model this as two pages? As far as I understand clicking the button doesn't cause a page reload but there's an ajax call to retrieve the results. I would simply put both results and resultsBtn as contents of one page and your problem would be gone.
EDIT:
It turns out that a page change is involved in your case. Assuming that you always want to wait for these results to appear you can either:
put your waitFor condition inside of static at = {} block for ResultsPage - at checks are executed implicitly whenever you use to() which means that it will wait wherever you go to that page
put a waitFor in a page change listener
access current page via the browser property on a page, in LandingPage: waitFor { browser.page.results.displayed } but this seems like a dirty solution to me - reaching from one page to another...
According to the "Book of Geb" I started to map our portal's web pages. I prefer to use variables defined within static content closure block and accessing them afterwards in page methods:
static content = {
buttonSend { $("input", type: "submit", nicetitle: "Senden") }
}
def sendLetter() {
waitFor { buttonSend.isDisplayed() }
buttonSend.click()
}
Unfortunately, sometimes I get an Geb waiting timeout exception (after 60 secs) or even worse I receive the well known "StaleElementReferenceException".
I could avoid the wait timeout when using "isEnabled" instead of "isDisplayed" but for the "StaleElementReferenceException" I could only apply the below solution:
def sendLetter() {
waitFor { buttonSend.isEnabled() }
try {
buttonSend.click()
} catch (StaleElementReferenceException e) {
log.info(e.getMessage())
buttonSend.click()
}
}
I guess, this solution is not really nice but I could not apply an explicitly wait as described in another article. Thus, I have some general questions:
Should I avoid to use static content definitions when pages are dynamically?
At what time or event Geb is refreshing its DOM? How can I trigger the DOM refreshment?
Why I still get a "StaleElementReferenceException" when using CSS selectors?
I would appreciate every hint which helps to understand or to solve this issue. The best would be to have a simple code example since I'm still a beginner. Thank you!
If you defined an at check on your page class the page would first verify that condition and wait for the first n seconds. Which is assigned in your gebConfig file. The default is 30 seconds.
static at = {
waitFor { buttonSend.isDisplayed() }
}
Thus once you call your pages 'to' method with a test or whatever you are using it for the page will wait and then perform your page manipulations.
to MyPage
buttonSend.click()
Should I avoid to use static content definitions when pages are dynamically?
No. Actually, the static definitions are of closures. So what is
actually happening is each time you make use of that Pages static
components you are calling a closure which is run dynamically on the
current page(collection of webElements). Understanding this is key to
using Geb and discovering the problems you will run into.
At what time or event Geb is refreshing its DOM? How can I trigger the DOM refreshment?
When you call: to, go, at, click ,withFrame(frame, page), withWindow
and browser drive methods it will refresh the current set of
WebElements. Geb has a nice collection of utiliities to make switching
between pages and waiting for page manipulations easy. Note: Geb is
actually built on WebDriver WebElements.
Why I still get a "StaleElementReferenceException" when using CSS selectors?
It is possible the page hasn't finished loading, has been manipulated
with ajax calls or has been refreshed in some other way. Sometimes an
'at' PAGE method call can fix these issues. They are for me most
common when using frames as Geb seems to become confused between pages
and frames a little. There are workarounds.
In short if you use the page pattern you can easily switch expected pages using the Page class you have defined with a static content, at, and url closure using the below:
to(Page)
at(Page)
Navigator.click(Page)
withFrame(frame, Page) { }
In addition to twinj's answer, I would like to point out a couple of other workarounds in case you encounter a StaleElementReferenceException.
Often times I find it is better to write out your selector manually rather than rely on the contents as defined in the page. Even though your page contents should not be cached by default, they still manage to slip away from me at times. This is particularly prevalent when dealing with dynamic content or iterations.
Ex: Let's say we want to click an element from a dynamically created dropdown.
Typically you might want to do something like...
static content = {
dropdown { $("#parentDiv").find("ul") }
}
void clickDesiredElement(String elementName) {
dropdown.click()
def desiredElement = dropdown.find("li", text:elementName)
waitFor { desiredElement.displayed }
desiredElement.click()
}
If this doesn't work, try getting rid of the contents altogether, and writing out the selector manually...
void clickDesiredElement(String elementName) {
$("#parentDiv").find("ul").click()
def desiredElement = $("#parentDiv").find("ul").find("li", text:elementName)
waitFor { desiredElement.displayed }
desiredElement.click()
}
In really nasty cases, you may have to use a manual timer, as pointed out in this answer, and your code may look like this...
void clickDesiredElement(String elementName) {
$("#parentDiv").find("ul").click()
sleepForNSeconds(2)
def desiredElement = $("#parentDiv").find("ul").find("li", text:elementName)
waitFor { desiredElement.displayed }
desiredElement.click()
}
Keep in mind this is a workaround :)
For large iterations and convenient closure methods, such as each{} or collect{}, you may want to add a waitFor{} in each iteration.
Ex: Let's say we want to get all rows of a large table
Typically you might want to do something like...
def rows = $("#table1").find("tr").collect {
[
name: it.find("td",0),
email: it.find("td",1)
]
}
Sometimes I find myself having to do this iteratively, along with a waitFor{} between each iteration in order to avoid a StaleElementReferentException. It might look something like this...
def rows = []
int numRows = $("#table1").find("tr").size()
int i
for(i=0; i < numRows; i++) {
waitFor {
def row = $("#table1").find("tr",i)
rows << [
name: row.find("td",0),
email: row.find("td",1)
]
}
}
I have figured that it is the navigator which get lost when you load dynamically.
I've solve the issue locally by reinit the page or module with below code:
void waitForDynamically(Double timeout = 20, Closure closure) {
closure.resolveStrategy = Closure.DELEGATE_FIRST
switch (this) {
case Module:
init(browser, browser.navigatorFactory)
break
case Page:
init(browser)
break
default:
throw new UnsupportedOperationException()
}
waitFor {
closure()
}
}