I have a JSF/ADF page which has a "button" which starts of as
<a id="pt1:r1:0:proBut" class="xfe p_AFDisabled" style="text-decoration:none;">
<span id="pt1:r1:0:pgl13" class="x26j x1a">Proceed</span>
</a>
Another button is then pressed which changes this button to enabled.
<a id="pt1:r1:0:proBut" class="xfe" href="#" onclick="return false;" style="text-decoration:none;">
<span id="pt1:r1:0:pgl13" class="x26j x1a">Proceed</span>
</a>
You can see there is a different class for the a element. When the element is disabled its class is "xfe p_AFDisabled", when it is active it changes to "xfe".
I have a Geb Page which contains a method which will wait until the button becomes enabled and looks like this
class CustomerSelection extends Page {
static at = { waitFor(100) {$("div",id:"pt1:pt_pgl10").$("div").$("span").text() == "Customer Selection"} }
static content = {
customers { $("div",id:"pt1:r1:0:pc1:tt1::db").$("table").$("tbody")}
proceedButton(to: Dashboard) { $("a",id: "pt1:r1:0:proBut",class: "xfe")}
}
void selectCustomer(int position) {
customers.$("tr",position).click()
println "selected customer!"
waitFor {proceedButton.present}
println "proceedButton present " + proceedButton.#class
}
void proceed() {
proceedButton.click()
}
}
However when I run this test via a SpockTest
package xx;
import geb.spock.GebReportingSpec
class LoginSpec extends GebReportingSpec {
def "login"() {
when:
to Login
report "login screen"
and:
login(username,password)
and:
at CustomerSelection
and:
selectCustomer(0)
and:
proceed()
then:
at Dashboard
where:
username | password
"x" | "x"
}
}
The output of println is
selected customer!
proceedButton present xfe p_AFDisabled
Which shows that the class is still xfe p_AFDisabled and has not finished processing.
It seems like the
waitFor {proceedButton.present}
is not working correctly?
EDIT ---
I have altered the waitFor to
waitFor {proceedButton.#class == "xfe"}
and the proceedButton definition to
proceedButton(to: Dashboard) { $("a",id: "pt1:r1:0:proBut")}
hence removing the class attribute
Which works, however I'm not happy with the solution as there is now 2 places with DOM specific tasks. I would prefer the class attribute to be moved back into the button definition?
EDIT 2----
I have added a new element to the Geb page which looks like this:
proceedButtonActive(wait: true, required: false) {proceedButton.#class == "xfe"}
I can then just call this element in the selectCustomer method and it works. However there are still 2 elements required for this logic. 1 is the ideal.
You need to change your selector to filter out elements that have p_AFDisabled class on them:
proceedButton(to: Dashboard, required: false) {
$("a", id: "pt1:r1:0:proBut").not(class: "p_AFDisabled")
}
Technically you don't even need to call isPresent() on processButton because if it's not present then that definition will return a "falsey" value and waitFor {} will not return. So selectCustomer() becomes:
void selectCustomer(int position) {
customers.$("tr",position).click()
waitFor { proceedButton }
}
Related
This is the page object.
package myapp.pages
import geb.Page
class LoginPage extends Page {
static url = "http://localhost:8080/login/auth"
//static at = {title.contains("Login")}
static at = {
waitFor {title.contains("Login")} // Add waitFor here to verify on page
}
static content = {
loginForm { $( 'form') }
usernameField { $('form').userName }
passwordField { $('form').password }
submitButton { $('input#submit' )}
}
void loginSubmit(String email, String password) {
usernameField = "email#something.com"
assert $('form').username == "email#something.com"
passwordField = "secret"
assert $('form').password == "secret"
submitButton.click()
}
}
And this is the LoginSpec test file
package myapp.login
import geb.spock.GebSpec
import grails.testing.mixin.integration.Integration
import grails.transaction.*
import myapp.pages.LoginPage
#Integration
#Rollback
class LoginSpec extends GebSpec {
def setup() {
}
def cleanup() {
}
void "user successfully logs in, is redirected to homepage"() {
given:
to LoginPage
when:
LoginPage.loginSubmit("email#something.com", "secret")
then:
title.contains("Dashboard")
}
}
When i run this test, I get the following error:
groovy.lang.MissingMethodException: No signature of method: static myapp.pages.LoginPage.loginSubmit() is applicable for argument types: (java.lang.String, java.lang.String) values: [email#something.com.com, secret]
I basically get the same error when I hardcode the username and password into the login page loginsubmit function. The selectors are fine, when I use the same selectors directly in the LoginSpec test to set the username and password, the test passes. The issue only occurs when I try to use the page object.
Instead of this:
when:
LoginPage.loginSubmit("email#something.com", "secret")
Use this:
when:
loginSubmit("email#something.com", "secret")
The issue isn't really a Geb one. The JVM doesn't allow you to invoke an instance method on a class reference as the context necessary to carry out that invocation wouldn't exist. loginSubmit is an instance method, not a static method.
I hope that helps.
Geb remembers the current page and automatically dispatches method calls to the page, so you do not need to include the page class name: loginSubmit("email#something.com", "secret") in the test will call the method on the page.
To be more explicit, the child component creates a property which is dependent on the argument passed by the parent component. I am not using the parent argument directly in the child template (this case works just fine).
Coming from a React background, my mental model suggests new arguments passed to a component will trigger re-render. But I am aware Glimmer does things differently with its #tracked decorator.
Okay, here is the contrived example. For a demo, head to the Glimmer Playground.
// parent-template.hbs
<button onclick={{action doubleNumber}}>Double Number</button>
<div>
Parent - {{number}}
</div>
<Child #number={{number}} />
// parent-component.ts
import Component, { tracked } from '#glimmer/component';
export default class extends Component {
#tracked number = 2;
doubleNumber() {
this.number = this.number * 2;
}
}
// child-template.ts
<div>
Child will render double of parent {{doubleOfParent}}
</div>
// child-component.ts
import Component, { tracked } from "#glimmer/component";
export default class extends Component {
args: {
number: number;
}
get doubleOfParent () {
return 2 * this.args.number;
}
};
Here the parent displays the doubled number on every click of the button. But the child never re-renders?
My question is do we always need to have the tracked variable inside the template. In this case number. And express the child template like this
<div>
Child will render double of parent {{double #number}}
</div>
Here double is helper which doubles the number.
If it is so what is the reason behind having the tracked properties/argument in the template?
It looks like your doubleOfParent() method is missing a #tracked annotation since its output depends on the args property:
import Component, { tracked } from "#glimmer/component";
export default class extends Component {
args: {
number: number;
}
#tracked('args')
get doubleOfParent() {
return 2 * this.args.number;
}
};
you can find more information on this topic at https://glimmerjs.com/guides/tracked-properties
So i am trying to implement an improved version of the available form tags so I am extending FormTagLib. I am tryign to do a simple test with teh textField tag but I can't seem to even figure out which method is getting called on the tag. I have override every available textField method available but none of them are getting hit
class TestTagLib extends FormTagLib {
static namespace = "test"
#Override
Object textField(Map attrs) {
return super.textField(attrs)
}
#Override
Object textField() {
return super.textField()
}
#Override
Object textField(Map attrs, CharSequence body) {
return super.textField(attrs, body)
}
#Override
Object textField(Closure body) {
return super.textField(body)
}
#Override
Object textField(Map attrs, Closure body) {
return super.textField(attrs, body)
}
}
I have tried putting breakpoints, console outputs for each method but nothing happens. The input fields are being generated just fine, but it doesn't seem to be using my code to do it. Heck i have even tried completely removing the call the super class and everything still works.
<test:textField name="test"/>
<input type="text" name="test" value="" id="test" />
What am I missing here and how do I intercept the creation of the textfield so I can make my modifications?
Have you taken a look at how the FormTagLib is implemented?
I think most tags are defined as Closures, like textField = { … }. This causes the implementation of the textField method to be replaced with the code between the {}.
I believe your example is a demonstration of the risks of extension. I think delegation is usually a better solution. Not sure if the tagLibs are spring beans, but you could try something like this (not tested):
class TestTagLib {
def formTagLib
def textField(args) {
formTagLib.textField(args)
}
}
I'm trying to use the Grails shopping cart plugin found here: http://grails.org/plugin/shopping-cart
I was able to successfully install the plugin in my application, as well as inject the service in my Controller:
class TestController {
def shoppingCartService
def index() {
def s = new DomainObj(name: "A Plain Ole Domain Object")
s.addToShoppingCart()
}
}
This appears to be adding the product to my shopping cart, as I expected. However, the problem I'm encountering now is actually listing the items out from the cart. According to the debugger, after running the above code, the shopping cart does indeed have an item (s) in it, as it returns:
com.metasieve.shoppingcart.ShoppingItem : 1
The item is properly being added to the cart, but now I would like to actually list the name of the item out again, so in this case, I want to display the name A Plain Ole Domain Object. How do I do this?
I'm unsure of the syntax for getting the actual objects back from the cart. The documentation doesn't describe how to do this clearly, as it merely states that the following should work:
def checkedOutItems = shoppingCartService.checkOut()
checkedOutItems.each {
println it['item']
println it['qty']
}
But that outputs
com.metasieve.shoppingcart.ShoppingItem : 1 , which is only a reference to some arbitrary item in the cart. I want to get back the actual name of my item.
Thanks in advance.
EDIT:
My domain class (DomainObj) is defined as follows:
class DomainObj extends com.metasieve.shoppingcart.Shoppable {
String name
static constraints = {
name blank: false
}
}
EDIT #2:
def index() {
def s = new DomainObj(name: "A Plain Ole Domain Object")
s.addToShoppingCart()
def r = new DomainObj(name: "Second Plain Ole Domain Object")
r.addToShoppingCart()
def checkedOutItems = shoppingCartService.checkOut()
println currentItems
println "-----"
checkedOutItems.each {
println it['item']
println it['qty']
}
}
The output of this is:
[com.metasieve.shoppingcart.ShoppingItem : 1, com.metasieve.shoppingcart.ShoppingItem : 2]
com.metasieve.shoppingcart.ShoppingItem : 2
1
com.metasieve.shoppingcart.ShoppingItem : 1
1
According to the documentation it["item"] gives you back the entity of a domain class that extends Shoppable. So in this case when you are printing it out it's calling the toString() method of that domain class. If you want that to return the value of the name property you need to implement your own toString(). Here is such an example
#Override
String toString() {
return name
}
EDIT:
Well as it's not clear from the documentation it['item'] is a pointer to the Shoppable instance which you can then use to query for the actual product in your cart like this:
com.metasieve.shoppingcart.Shoppable.findByShoppingItem(it['item'])
Thus the following will print out the toString() value of your products
checkedOutItems.each {
println com.metasieve.shoppingcart.Shoppable.findByShoppingItem(it['item'])
println it['qty']
}
For testing I created the following domain and controller.
Domain:
package com.test
class MyProduct extends com.metasieve.shoppingcart.Shoppable {
String name
static constraints = {
name(blank: false)
}
#Override
String toString() {
return name
}
}
Controller:
package com.test
class MyProductController {
def shoppingCartService
def index() {
def p1 = new MyProduct(name: 'one')
p1.save(flush: true, failOnError: true)
p1.addToShoppingCart()
def p2 = new MyProduct(name: 'two')
p2.save(flush: true, failOnError: true)
p2.addToShoppingCart()
def checkedOutItems = shoppingCartService.checkOut()
checkedOutItems.each {
println com.metasieve.shoppingcart.Shoppable.findByShoppingItem(it['item'])
println it['qty']
}
}
}
How do I write options so I can generate it into an HTML select? The problem with this is "options" needs a set, not an array
Here is everything I have. I know the naming convention is bad, and I will fix that, but for right now I've been on this issue for days now.
Controller Class
import org.springframework.dao.DataIntegrityViolationException
import grails.plugin.mail.*
class EmailServiceController {
static defaultAction = "contactService"
def contactService() {
def options = new ArrayList()
options.push("Qestions about service")
options.push("Feedback on performed service")
options.push("Other")
options.push("Why am I doing this")
options
}
def send() {
sendMail(){
to "mygroovytest#gmail.com"
from params.email
subject params.subject
body params.information
}
}
}
Domain class
class EmailService {
static constraints = {
}
}
g:select call from the gsp
<g:select name = "subject" from = "${options}" noSelection="Topic"/>
also tried the following with "${selectOptions}" instead of "${options}" with no luck
def selectOptions() {
def options = new ArrayList()
options.push("Qestions about service": "QAS")
options.push("Feedback on performed service":"FoPS")
options.push("Other":"Other")
options.push("Why am I doing this":"WHY")
return options
}
Ok, I think I might know what is going on here. The missing piece to the question is what gsp is being called. Here is the appropriate way:
class EmailServiceController {
def contactService() {
def options = ["Qestions about service", "Feedback on performed service", "Other"]
// assumes you are going to render contactService.gsp
// you have to push the options to the view in the request
[options:options]
}
}
And then in contactService.gsp you would have:
<g:select name="subject" from="${options}" noSelection="['Topic': 'Topic']"/>
Your options is neither an array nor map. There is a syntax error. That's why you have only one option in your select. You need to enter either a real list or a map, like that:
def selectOptions() {
def options = [:]
options["Qestions about service"] = "QAS"
options["Feedback on performed service"] = "FoPS"
[options:options]
}
Using a map you can use it in the view like this:
<g:select name="subject" from="${options.entrySet()}"
optionValue="key" optionKey="value"
noSelection="['Topic': 'Topic']"/>
You need to use double quotes in your tags, not single quotes. With single quotes, you're just passing a String that looks like '${options}' instead of passing a GString with the value of options.
<g:select name="subject" from="${options}" noSelection="Topic"/>
In addition, assuming you're calling the contactService action, you need to return options instead of returning options.push("Other"). push() returns a boolean, which means the implicit return of contactService is the boolean result of push() instead of options.