I have problems to understand the syntax with which pages are described (defined) in Geb. The following is an example page class that works fine but I don't understand how it works.
import geb.Page
class GebHomePage extends Page{
static url = "http://gebish.org"
static at = { title == "Geb - Very Groovy Browser Automation" }
static content = {
seeGuideButton { $('div', class:'ui huge primary button') }
uiHeader { $('h1', class:'ui header', 0)}
}
}
I am interested in the syntax inside the closure referred to by content. Geb describes the syntax for the content DSL as
«name»(«options map») { «definition» }
I don't understand this syntax. The name part looks like a variable, but it is not declared (I miss a def or something similar) and the last part looks like a closure. But where is the connection between the name and the closure. There is no equal sign connecting the two. In a standard groovy class the syntax
«name» { «definition» }
does not compile. What happens in one line of the content DSL? What precisely are uiHeader and seeGuideButton and how are they related to the following closures? How does this syntax work exactly?
Behind the scenes, Geb looks for a method with the signature:
uiHeader(Map, Closure)
In groovy, you can catch when a method is missing (see methodMissing) so internally Geb will catch this and know how to resolve any future requests for that variable
Also in groovy, a Closure as the last parameter can be outside the brackets, which is why you can do
uiHeader(required: false) { $('h1') }
Related
What is the use of the static initialization block in Groovy. Why does Geb use it? If the use of it is the same as in Java, then how can you initialize non-declared fields in a situtation like this?
class ManualsMenuModule extends Module {
static content = {
toggle { $("div.menu a.manuals") }
linksContainer { $("#manuals-menu") }
links { linksContainer.find("a") }
}
void open() {
toggle.click()
waitFor { !linksContainer.hasClass("animating") }
}
}
Some answers to your questions are provided in the section about the content DSL of the Geb manual.
The DSL is implemented using Groovy's methodMissing() mechanism and modification of the delegate of the closure assigned to the static content field. If you are interested in digging deeper then you can always look at the implementation in PageContentTemplateBuilder.
I'm not expert on Geb, I can only explain the groovy meaning of the code.
1st off, it's not the static initialization block like in java. In those lines static content = {...} you are assigning to a static variable a Closure instance which is evaluated and executed LATER (hence, lazy).
The closure represents a (part of) a Geb's Groovy Builder which is called by Geb framework to register/perform some tasks.
There's no Java counterpart to achieve the same, and that's the reason why groovy-based frameworks are so nice to use for testing purposes and they follow the general rule of thumb:
test code should be more abstract then the code under test
UPDATE:
This line:
toggle { $("div.menu a.manuals") }
can be rewritten like
toggle( { $("div.menu a.manuals") } )
or
def a = { $("div.menu a.manuals") }
toggle a
so it's a method invocation and not an assignment. In groovy you can omit the brackets in some cases.
Actually I'm experimenting writing a DSL with groovy. So far ...
There are some things unclear to be regarding delegation and intercepting unwanted (Closure) structures:
first of all: How can I throw a (type of?) Exception to point to the correct line of code in the DSL that fails?
assuming
abstract MyScript extends Script {
def type(#DelegateTo(MyType) Closure cl) {
cl.delegate = new MyType()
cl()
this
}
}
under
new GroovyShell(this.class.classLoader, new CompilerConfiguration(scriptBaseClass: MyScript.name)).evaluate(…)
the passed DSL / closure
type {
foo: "bar"
}
passes silently.
I'm aware of, that foo: is just a POJ label but I'm not that sure what that defined Closure is interpreted as?
Neither did I found anything regarding the AST metaprogramming to get in touch of any defined labels to use them?
giving in
type {
foo = "bar"
}
it's clear that he will try to set the property foo, but do I really have to intercept unwanted fields/props by
class MyType {
def propertyMissing(String name) {
… // where I'm unable to println name since this leads to field access 'name' ...
}
}
while user is still allowed to pass
type {
foo "bar"
}
which leads to method not defined .. so I have to write additionally some metaClass.methodMissing or metaClass.invokeMethod stuff ..
meanwhile I tend to dismiss any closures in my dsl only working with simple
def type(Map vars) {
store << new MyType(vars)
// where in the constructor I was forced to write metaClass stuff to validate that only fields are given in the map that are defined in the class
}
that works, but both drafts are not what I expected to do when reading "groovy is so great for making DSLs" ...
I would experiment with the different options and then settle for one.
To guide your users you should give feedback similar to that of the regular compiler (i.e. line-number and column, maybe the expression).
Enforcing the correctness of the input can be non-trivial -- depending on your DSL.
For example:
type {
foo: "bar"
}
Is just a closure that returns the String bar. Is that something your user is supposed to do? The label will be part of the AST, AFAIK in org.codehaus.groovy.ast.stmt.Statement.statementLabels. If you want this syntax to assign something to foo then you'll need to rewrite the AST. The Expression could become a Declaration for the local Variable foo or could become an assignment for the Field foo. That's really up to you, however, Groovy gives you some capabilities that make creating a DSL easier:
You already used #DelegateTo(MyType) so you could just add a Field foo to MyType:
class MyType {
String foo
}
And then either use #CompileStatic or #TypeChecked to verify your script. Note that #CompileStatic will deactivate Run-time Metaprogramming (i.e. propertyMissing etc. won't be called anymore.) while #TypeChecked does not. This, however, will only verify Type-Correctness. That is: assigning to anything but a declared Field will fail and assigning an incompatible Type will fail. It does not verify that something has been assigned to foo at all. If this is required you can verify the contents of the delegate after calling the Closure.
What does this syntax mean in Groovy?
class CreateMessagePage extends Page {
static at = { assert title == 'Messages : Create'; true }
static url = 'messages/form'
static content = {
submit { $('input[type=submit]') }
MyVeryStrangeForm { $('form') }
errors(required:false) { $('label.error, .alert-error')?.text() }
}
}
(taken from Spring MVC Test HtmlUnit manual)
The question is about Groovy and I would like know the answer in Groovy terms.
What is content? Is it a static variable? Is its name is random or predefined by base class of Page?
What is = (equal sign) after it? Is it an assignment operator?
What is at the right hand side of =? Is this a closure? Or if this an anonymous class? Or if these are the same?
What is submit inside curly braces?
Is this a variable? Why there is no assignment operator after it then?
Is this a function definition? Can I define functions in arbitrary places in Groovy? If this is a function definition, then what is errors then?
Is submit is a function call, receiving { $('input[type=submit]') } as a parameter? If yes, then where is this function can be defined? For example, where is MyVeryStrangeForm defined (is nowhere)?
If this was function call, then it won't work since it's undefined...
Quick answer to all questions: it's a block of code, like anonymous function, called closure in Groovy.
See http://www.groovy-lang.org/closures.html
In Groovy you can reference/pass/set such closure, as in any Functional Language.
So this:
static at = { assert title == 'Messages : Create'; true }
means that class field at will be set to this closure (notice, not result of closure execution, but closure itself, as block of code). Type of at is omitted there, but it could be static def at or static Object at, or static Closure at
This code could be executed anytime later, in different context, with title defined, etc.
This:
submit { $('input[type=submit]') }
means calling a function submit with closure as argument.
If you want to write own function like this, it should be something like:
def submit(Closure code) {
code.call()
}
Brackets could be omitted, so it could be written as submit({$('input[type=submit]')}). Same for other function as well, it could be println 'hello world!' instead of println('hello world').
There's also a common practice to define closure as last argument, like:
def errors(Map opts, Closure code) {
....
}
at this case you could pass first arguments as usual, wrapped in brackets, and closure outside:
errors(required:false) { ...... }
same to:
errors([required: false], { ..... })
I have a lot of code using and expecting java.util.Date which I would like to migrate to org.joda.time.LocalDate. The problem is that most of the code is dynamically typed.
So I wonder if there is any groovy way to intercept the ClassCastException, do the conversion at runtime (instead of letting the exception bubble up) and log the operation (so I could fix the code).
Example:
import org.joda.time.LocalDate
def someMethod(date) {
println date.year()
}
// this call is ok
someMethod(new LocalDate())
// this call raises an exception
someMethod(new Date())
I don't want to modify the code above, like surrounding the second call with a try-catch and recalling with the right type. I wanted a way to do this globally.
Idea 1:
You can benefit from Groovy multimethods: you write a method overload for the JodaTime's and let the java.util.Date method be chosen when it is a dynamic method parameter:
def someMethod(date) {
println date.year()
}
My advice is to write in this class a overload of these methods doing the conversion:
def someMethod(LocalDate date) {
someMethod date.convertToJavaDate()
}
If you don't have access to that code, you can use metaprogramming.
Idea 2:
If you have a huge library, my guess is that you are better mixing JodaTime class. You will get a power benefit from duck typing. You will need to know which members from java.util.Date are called and reroute them to Joda's own methods:
class LocalDateMixin {
def year() { this[Calendar.YEAR] }
}
LocalDate.metaClass.mixin LocalDateMixin
You can also apply the metaprogramming using extension methods.
As new to groovy...
I'm trying to replace the java idiom for event listeners, filters, etc.
My working code in groovy is the following:
def find() {
ODB odb = ODBFactory.open(files.nodupes); // data nucleus object database
Objects<Prospect> src = odb.getObjects(new QProspect());
src.each { println it };
odb.close();
}
class QProspect extends SimpleNativeQuery {
public boolean match(Prospect p) {
if (p.url) {
return p.url.endsWith(".biz");
}
return false;
}
}
Now, this is far from what I'm used to in java, where the implementation of the Query interface is done right inside the odb.getObjects() method. If I where to code "java" I'd probably do something like the following, yet it's not working:
Objects<Prospect> src = odb.getObjects( {
boolean match(p) {
if (p.url) {
return p.url.endsWith(".biz");
}
return false;
}
} as SimpleNativeQuery);
Or better, I'd like it to be like this:
Objects<Prospect> src = odb.getObjects(
{ it.url.endsWith(".biz") } as SimpleNativeQuery
);
However, what groovy does it to associate the "match" method with the outer script context and fail me.
I find groovy... groovy anyways so I'll stick to learning more about it. Thanks.
What I should've asked was how do we do the "anonymous" class in groovy. Here's the java idiom:
void defReadAFile() {
File[] files = new File(".").listFiles(new FileFilter() {
public boolean accept(File file) {
return file.getPath().endsWith(".biz");
}
});
}
Can groovy be as concise with no additional class declaration?
I think it would have helped you to get answers if you'd abstracted the problem so that it didn't rely on the Neodatis DB interface -- that threw me for a loop, as I've never used it. What I've written below about it is based on a very cursory analysis.
For that matter, I've never used Groovy either, though I like what I've seen of it. But seeing as no one else has answered yet, you're stuck with me :-)
I think the problem (or at least part of it) may be that you're expecting too much of the SimpleNativeQuery class from Neodatis. It doesn't look like it even tries to filter the objects before it adds them to the returned collection. I think instead you want to use org.neodatis.odb.impl.core.query.criteria.CriteriaQuery. (Note the "impl" in the package path. This has me a bit nervous, as I don't know for sure if this class is meant to be used by callers. But I don't see any other classes in Neodatis that allow for query criteria to be specified.)
But instead of using CriteriaQuery directly, I think you'd rather wrap it inside of a Groovy class so that you can use it with closures. So, I think a Groovy version of your code with closures might look something like this:
// Create a class that wraps CriteriaQuery and allows you
// to pass closures. This is wordy too, but at least it's
// reusable.
import org.neodatis.odb.impl.core.query.criteria;
class GroovyCriteriaQuery extends CriteriaQuery {
private final c;
QProspect(theClosure) {
// I prefer to check for null here, instead of in match()
if (theClosure == null) {
throw new InvalidArgumentException("theClosure can't be null!");
}
c = theClosure;
}
public boolean match(AbstractObjectInfo aoi){
//!! I'm assuming here that 'aoi' can be used as the actual
//!! object instance (or at least as proxy for it.)
//!! (You may have to extract the actual object from aoi before calling c.)
return c(aoi);
}
}
// Now use the query class in some random code.
Objects<Prospect> src = odb.getObjects(
new GroovyCriteriaQuery(
{ it.url.endsWith(".biz") }
)
)
I hope this helps!
I believe your real question is "Can I use closures instead of anonymous classes when calling Java APIs that do not use closures". And the answer is a definite "yes". This:
Objects<Prospect> src = odb.getObjects(
{ it.url.endsWith(".biz") } as SimpleNativeQuery
);
should work. You write "However, what groovy does it to associate the "match" method with the outer script context and fail me". How exactly does it fail? It seems to me like you're having a simple technical problem to get the solution that is both "the groovy way" and exactly what you desire to work.
Yep, thanks y'all, it works.
I also found out why SimpleNativeQuery does not work (per Dan Breslau).
I tried the following and it worked wonderfully. So the idiom does work as expected.
new File("c:\\temp").listFiles({ it.path.endsWith(".html") } as FileFilter);
This next one does not work because of the neodatis interface. The interface does not enforce a match() method! It only mentions it in the documentation yet it's not present in the class file:
public class SimpleNativeQuery extends AbstactQuery{
}
Objects<Prospect> src = odb.getObjects(
{ it.url.endsWith(".biz") } as SimpleNativeQuery
);
In the above, as the SimpleNativeQuery does not have a match() method, it makes it impossible for the groovy compiler to identify which method in the SimpleNativeQuery should the closure be attached to; it then defaults to the outer groovy script.
It's my third day with groovy and I'm loving it.
Both books are great:
- Groovy Recipes (Scott Davis)
- Programming Groovy (Venkat Subramaniam)