How to have geb static content recognized form test script - spock

The example:
Page Class
Page TestPage extends Page{
static at = {blah blah....}
static content = {
someVar {$(By.id("someId"))}
}
}
Script:
class Test extends GebReportingSpec{
def "some Feature Methods"(){
when:
def page1 = at TestPage
page1.someVar.click() //In intellij "someVar" shows as unrecognized
}
}
According to what I have read and researched, in the example above, someVar should autocomplete and be recognized, but it is not. the code still runs and works correctly but someVar still comes up as unrecognized by the autocompiler in Intellij.
The only way I can have the variable be recognized is by creating a getter method for someVar
When I see the answer to this question: Geb authoring support within Intellij-IDEA?
I assume that what I am attempting to do should work but it does not. is there a way to have the static content autocomplete without creating getter methods?
Edit:
Also wanted to add that in my case I am getting the page objects from a jar file. Both the binaries and the sources jars are downloaded and recognized.

Related

Grails 4 how to get an handle to artifacts in custom command

I need to build a custom command in a Grails 4 application (https://docs.grails.org/4.0.11/guide/single.html#creatingCustomCommands), and I need to get an handle to some Grails Services and Domain classes which I will query as needed.
The custom command skeleton is quite simple:
import grails.dev.commands.*
import org.apache.maven.artifact.Artifact
class HelloWorldCommand implements GrailsApplicationCommand {
boolean handle() {
return true
}
}
While the documentation says that a custom command has access to the whole application context, I haven't found any examples on how to get an handle of that and start accessing the various application artifacts.
Any hints?
EDIT: to add context and clarify the goal of the custom command in order for further recommendation/best practices/etc.: the command reads data from a file in a custom format, persist the data, and writes reports in another custom format.
Will eventually be replaced by a recurrent job, once the data will be available on demand from a third party REST API.
See the project at github.com/jeffbrown/marco-vittorini-orgeas-artifacts-cli.
grails-app/services/marco/vittorini/orgeas/artifacts/cli/GreetingService.groovy
package marco.vittorini.orgeas.artifacts.cli
class GreetingService {
String greeting = 'Hello World'
}
grails-app/commands/marco/vittorini/orgeas/artifacts/cli/HelloCommand.groovy
package marco.vittorini.orgeas.artifacts.cli
import grails.dev.commands.*
class HelloCommand implements GrailsApplicationCommand {
GreetingService greetingService
boolean handle() {
println greetingService.greeting
return true
}
}
EDIT:
I have added a commit at github.com/jeffbrown/marco-vittorini-orgeas-artifacts-cli/commit/49a846e3902073f8ea0539fcde550f6d002b9d89 which demonstrates accessing a domain class, which was part of the question I overlooked when writing the initial answer.

Add custom information to Spock Global Extension

I have configured Spock Global Extension and static class ErrorListener inside it. Works fine for test errors when I want to catch feature title and errors if they happen. But how can I add some custom information to the listener?
For example I have test that calls some API. In case it fails I want to add request/response body to the listener (and report it later). Obviously I have request/response inside the feature or I can get it. How can I pass this information to the Listener and read later in the handling code?
package org.example
import groovy.json.JsonSlurper
import org.spockframework.runtime.AbstractRunListener
import org.spockframework.runtime.extension.AbstractGlobalExtension
import org.spockframework.runtime.model.ErrorInfo
import org.spockframework.runtime.model.IterationInfo
import org.spockframework.runtime.model.SpecInfo
import spock.lang.Specification
class OpenBrewerySpec extends Specification{
def getBreweryTest(){
def breweryText = new URL('https://api.openbrewerydb.org/breweries/1').text
def breweryJson = new JsonSlurper().parseText(breweryText)
//TODO catch breweryText for test result reporting if it is possible
expect:
breweryJson.country == 'United States'
}
def cleanup() {
specificationContext.currentSpec.listeners
.findAll { it instanceof TestResultExtension.ErrorListener }
.each {
def errorInfo = (it as TestResultExtension.ErrorListener).errorInfo
if (errorInfo)
println "Test failure in feature '${specificationContext.currentIteration.name}', " +
"exception class ${errorInfo.exception.class.simpleName}"
else
println "Test passed in feature '${specificationContext.currentIteration.name}'"
}
}
}
class TestResultExtension extends AbstractGlobalExtension {
#Override
void visitSpec(SpecInfo spec) {
spec.addListener(new ErrorListener())
}
static class ErrorListener extends AbstractRunListener {
ErrorInfo errorInfo
#Override
void beforeIteration(IterationInfo iteration) {
errorInfo = null
}
#Override
void error(ErrorInfo error) {
errorInfo = error
}
}
}
Create file src/test/resources/META-INF/services/org.spockframework.runtime.extension.IGlobalExtension
and place string "org.example.TestResultExtension" there to enable extension.
I am pretty sure you found my solution here. Then you also know that it is designed to know in a cleanup() methods if the test succeeded or failed because otherwise Spock does not make the information available. I do not understand why deliberately omitted that information and posted a fragment instead of the whole method or at least mentioned where your code snippet gets executed. That is not a helpful way of asking a question. Nobody would know except for me because I am the author of this global extension.
So now after having established that you are inside a cleanup() method, I can tell you: The information does not belong into the global extension because in the cleanup() method you have access to information from the test such as fields. Why don't you design your test in such a way that whatever information cleanup() needs it stored in a field as you would normally do without using any global extensions? The latter is only meant to help you establish the error status (passed vs. failed) as such.
BTW, I even doubt if you need additional information in the cleanup() method at all because its purpose it cleaning up, not reporting or logging anything. For that Spock has a reporting system which you can also write extensions for.
Sorry for not being more specific in my answer, but your question is equally unspecific. It is an instance of the XY problem, explaining how you think you should do something instead of explaining what you want to achieve. Your sample code omits important details, e.g. the core test code as such.

Import groovy class in a pipeline Jenkinsfile

I need to be able to create classes and use them within a Jenkins pipeline.
Let's say I have a very simple groovy class, declared in a groovy script, looking as this:
class MyClass {
#Override
public String toString() {
return "toto";
}
}
return MyClass();
This class is located in the folder: Project\Buildfiles\Jenkins\com\external
Then in my Jenkinsfile I would do:
node('mynode') {
toto = load 'Project\Buildfiles\Jenkins\com\external\MyClass.groovy'
echo toto.toString()
}
And this actually works
However this do pose a certain numbers of issues with my IDE which does not understand what is happening. Also, this prevents me to have several constructor in my custom class.
What I have been trying to do, and for which I need help, is the following. In a file named ExternalClasses.groovy:
class Toto{
#Override
public String toString() {
return "toto";
}
}
class Tata{
#Override
public String toString() {
return "tata";
}
}
return this;
In the JenkinsFile:
node('mynode') {
external= load 'Project\Buildfiles\Jenkins\com\external\ExternalClasses.groovy'
toto = new Toto();
tata = new Tata();
}
And this fails
I have tried several approaches, used packages names, used the Toto.new() syntax, but none worked.
Any ideas ?
Edit about Shared Libraries:
I actually have a Shared library, it is used by several teams and contains very specific data which should be own by the teams and not by the library.
We need to be able to put out of the library things which does not belong to it. The purpose of this work is to alleviate the said library of non generic code.
You could use the Shared Library Feature. Upload your scripts into a VCS like Github/Bitbucket and use Jenkins-Jobs to execute them. They are available for all projects/jobs.

How do I create a "getter" for my modules (How do you instantiate a Module Object using a Navigator?)

I am trying to make it so that all of my page and module references can autocomplete in intellij.
Due to some sort of bug I am unable to do this like one normally would. (see here for more details: How to have geb static content recognized form test script )
In order to work around the above mentioned bug. I opted to create "getters" for all of my static content.
for example:
The Page:
class MyPage extends Page{
static content = {
tab {$(By.xpath("somexpath")}
}
Navigator tab(){
return tab
}
}
The Script:
//imagine we are in the middle of a feature method here
def test = at MyPage
test.tab().click()
So all of the above code works as I expect it to, and I want to redo my pages like this so that I can have autocomplete from the script side. Problems occur when I try to use this same technique for modules.
For example:
class MyPage extends Page{
static content = {
mod {module(new MyModule())}
}
MyModule mod(){
return mod
}
}
If I try and access mod from the script like so
//imagine we are in the middle of a feature method here
def test = at MyPage
test.mod().someModContentMaybe().click()
I get the following error:
org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object 'MyPage' -> mod: 'MyModule' with class 'geb.content.TemplateDerivedPageContent' to class 'MyModule'
If I try to do the following in the page object:
class MyPage extends Page{
static content = {
mod {module(new MyModule())}
}
MyModule mod(){
return new MyModule()
}
}
I get the following error when attempting to access the module from the script:
geb.error.ModuleInstanceNotInitializedException: Instance of module class MyModule has not been initialized. Please pass it to Navigable.module() or Navigator.module() before using it.
I guess it wants me to take an instantiated Navigator Object and and to call module(MyModule) but I am not sure how this works or how one would decide which Navigator Object to call module from.
All in all, I just want to be able to autocomplete module Names and static content from my scripts.
The Book of Geb's section about modules answers your question. You should not manually call the module's constructor, but instead instead use the syntax described right at the beginning of the chapter. This solution gets rid of the exception and also solves the code completion problem for me:
static content = {
mod { module MyModule }
}
Now that the exception is gone here is how to add the getter you asked for:
def myModule() { mod }
You're getting a GroovyCastException when returning content that contains a module from a method whose return type is a class which extends geb.Module because navigators and modules returned from content definitions get wrapped in geb.content.TemplateDerivedPageContent.
You can unwrap them using the as keyword as explained in the manual section about unwrapping modules returned from the content DSL. So, for one of your examples it would look like this:
MyModule mod(){
mod as MyModule
}
I think the problem is you content block. Modules are defined via Navigators' module method:
static content = {
mod { $("div.module").module(MyModule)
}
So no constructor calling required.

Beginner Groovy

I'm following the code examples in 'The Definitive Guide to Grails' by Graeme Keith Rocher, and have come across a rather unusual stumbling block.
Essentially, 2 domain classes exist - Bookmark & Tag.
Bookmark:
class Bookmark {
static hasMany = [tags:Tag]
URL url
String title
String notes
Date dateCreated = new Date()
}
Tag:
class Tag{
static belongsTo= Bookmark
Bookmark bookmark
String name
}
I'm instructed to launch the Grails Console (is this the same as the groovy console)and create a new object as follows.
def b = new Bookmark(url: new URL('http://grails.org/'), title:'Grails', notes:'Groovy')
This results in:
Result: Bookmark : null
According to the book, GORM automatically provides an implementation of an addTag method. So I code...
b.addTag( new Tag(name: 'grails'))
Only to get whammed with the error message:
Exception thrown: No such property: b for class: ConsoleScript1
groovy.lang.MissingPropertyException: No such property: b for class: ConsoleScript1 at ConsoleScript1.run(ConsoleScript1:2)
The author hasn't accounted for this in the book. I was wondering if anyone could help me out?
Thanks.
Are you reading the 1st edition of the book? If so it's quite outdated. The add* methods have been deprecated since 0.5. It was replaced by addTo* so do this instead:
b.addToTags( new Tag(name: 'grails'))
Assuming your code example shouldn't have Bookmarks defined twice (copy and paste error?) and Tag might look like this:
class Tag {
String name
}
The groovy console is not the same as the grails console. To access the grails console, type grails console in your application directory - you should get a Java GUI app. It's possible that the example will work then because grails add some stuff to the standard Groovy.
Also, your problem isn't the addTag method, but the item b that you defined which cannot be found. Try entering the whole script into the console at once and executing it, instead of executing it line by line.

Resources