I can't see a proper reason for a LitElement WebComponent to be rendered before having its props/attributes available. If the template needs a prop, you are forced to render a second time the component, after the first useless one. Worse than that, if your component looks like:
class MyComp extends LitElement
static get properties() {
return {
myBigProp: {
type: Object
}
render() {
return html`<p> ${this.myBigProp.nestedProp}</p>`
}
you get an error for accessing nestedProp when myBigProp is undefined. Is there a clean way to avoid rendering twice this component?
If you already are able to set the properties you can simply define them in the constructor. Then it should be accessible at the first render.
constructor() {
super();
this.myBigProp = {nestedProp:'nested value'}
}
However if you have to wait for them I usually make it clear that the component is loading visually by using an "initialized" property then handling it in the render function.
render() {
if (!this.initialized) {
return html`<div class='loading-reserved-zone'></div>`;
} else {
return html`<p>${this.myBigProp.nestedProp}</p>`
}
}
Otherwise, "Optional Chaining Operator" are coming up to javascript in august 2020 which could handle your second scenario or you could technically use this babel plugin if you want to use it earlier.
There is a lifecycle callback shouldUpdate. Return false until you want to render the component.
shouldUpdate(changedProps) {
return this.myBigProp != null;
}
I try to understand more about how Geb and Spock work internally to understand what is really happening in my tests.
I found that GebSpec which I extend to write my tests has a field Browser _browser.
I also found that GebSpec has a method getBrowser() which returns _browser, so _browser can be accessed over getBrowser() and get_browser(). But the interesting part is that while debugging in intelliJ expanding an instance of GebSpec shows no field _browser but only a field $spock_sharedField__browser.
A little example:
Debugging my Class: The instance of GebSpec has a field spock_sharedField__browser but no field _browser
How do they manage to hide the _browser field from me in the debugger and why do they do it?
Recall a field Browser _browser is declared in GebSpec and a field $spock_sharedField__browser is not.
There also is no method get$spock_sharedField__browser() but I still can access and manipulate $spock_sharedField__browser.
I tried to do it myself:
I wrote a class TestClass that declares _browser exactly analog to GebSpec, but if I debug here the field _browser is shown normally as one would expect
Can someone explain me what is going on?
Why hide _browser?
What is $spock_sharedField__browser good for?
UPDATE: I think the following code describes summarizes it pretty good:
import geb.spock.GebSpec
class GebHomeSpec extends GebSpec{
def "test Geb homepage"(){
when:
['get$spock_sharedField__browser', 'getBrowser', 'get_browser'].each {
try {
println this."${it}"()
} catch (MissingFieldException e) {
println e
}
}
['$spock_sharedField__browser', 'browser', '_browser'].each {
try {
println this.getMetaClass().getAttribute(this, it)
} catch (MissingFieldException e){
println e
}
}
then:
true
}
}
The result on the console is:
null
geb.Browser#352ff4da
geb.Browser#352ff4da
null
groovy.lang.MissingFieldException: No such field: browser for class: GebHomeSpec
groovy.lang.MissingFieldException: No such field: _browser for class: GebHomeSpec
My interpretation, considering the answer of kriegaex, is that in the Compilation during the Spock transformation the field $spock_sharedField__browser is declared and the field _browser is removed. The field browser never existed. But there are still getters for browser and _browser. I wonder where there get their data from (in this case geb.Browser#352ff4da) as none of the field exists anymore as the exceptions show. At least it matches with the debugging information (c.f. first picture/link) that shows the field $spock_sharedField__browser but neither a field _browser nor a field browser.
Finally I noticed (and I dont really know how to explain that) the getters for _browser and browser are outside of the class no longer available (see below). I thought the concept of private is not implemented in groovy and making getters private makes no sense to me anyways.
import geb.spock.GebSpec
class Main {
static void main(String[] args) {
GebSpec gebSpec = new GebSpec()
['get$spock_sharedField__browser', 'getBrowser', 'get_browser'].each {
try {
println gebSpec."${it}"()
} catch (MissingFieldException e) {
println e
}
}
['$spock_sharedField__browser', 'browser', '_browser'].each {
try {
println gebSpec.getMetaClass().getAttribute(gebSpec, it)
} catch (MissingFieldException e){
println e
}
}
}
}
This leads to
null
groovy.lang.MissingFieldException: No such field: $spock_sharedField__browser for class: org.codehaus.groovy.runtime.NullObject
groovy.lang.MissingFieldException: No such field: $spock_sharedField__browser for class: org.codehaus.groovy.runtime.NullObject
null
groovy.lang.MissingFieldException: No such field: browser for class: geb.spock.GebSpec
groovy.lang.MissingFieldException: No such field: _browser for class: geb.spock.GebSpec
All in all I find this rather confusing and I wonder what this is good for. Why introduce $spock_sharedField__browser and remove _browser?
If you use IntelliJ IDEA, you can just decompile the GebSpec class and will see something like this (this is what the Groovy compiler really produced when it compiled the library class):
public class GebSpec extends Specification implements GroovyObject {
// ...
#Shared
#FieldMetadata(
line = 29,
name = "_browser",
ordinal = 2
)
protected volatile Browser $spock_sharedField__browser;
// ...
public Browser createBrowser() {
CallSite[] var1 = $getCallSiteArray();
return !__$stMC && !BytecodeInterface8.disabledStandardMetaClass() ? (Browser)ScriptBytecodeAdapter.castToType(var1[8].callConstructor(Browser.class, this.createConf()), Browser.class) : (Browser)ScriptBytecodeAdapter.castToType(var1[6].callConstructor(Browser.class, var1[7].callCurrent(this)), Browser.class);
}
public Browser getBrowser() {
CallSite[] var1 = $getCallSiteArray();
if (BytecodeInterface8.isOrigZ() && !__$stMC && !BytecodeInterface8.disabledStandardMetaClass()) {
if (ScriptBytecodeAdapter.compareEqual(var1[11].callGroovyObjectGetProperty(this), (Object)null)) {
Browser var3 = this.createBrowser();
ScriptBytecodeAdapter.setGroovyObjectProperty(var3, GebSpec.class, this, (String)"_browser");
}
} else if (ScriptBytecodeAdapter.compareEqual(var1[9].callGroovyObjectGetProperty(this), (Object)null)) {
Object var2 = var1[10].callCurrent(this);
ScriptBytecodeAdapter.setGroovyObjectProperty((Browser)ScriptBytecodeAdapter.castToType(var2, Browser.class), GebSpec.class, this, (String)"_browser");
}
return (Browser)ScriptBytecodeAdapter.castToType(var1[12].callGroovyObjectGetProperty(this), Browser.class);
}
// ...
public Browser get$spock_sharedField__browser() {
return this.$spock_sharedField__browser;
}
public void set$spock_sharedField__browser(Browser var1) {
this.$spock_sharedField__browser = var1;
}
}
I think you have dived deep enough already to understand without further explanation.
Update: I forgot to mention: Your test class does not inherit GebSpec (which again inherits from Specification, i.e. the code will not be transformed by Spock/Geb because it has the wrong base class. If you do this, though:
package de.scrum_master.stackoverflow
import geb.spock.GebSpec
import spock.lang.Shared
class FooIT extends GebSpec {
#Shared
def myField
def test() {
expect:
true
}
}
Then the decompiled code will be:
package de.scrum_master.stackoverflow;
import geb.spock.GebSpec;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
import org.codehaus.groovy.runtime.callsite.CallSite;
import org.spockframework.runtime.ErrorCollector;
import org.spockframework.runtime.SpockRuntime;
import org.spockframework.runtime.ValueRecorder;
import org.spockframework.runtime.model.BlockKind;
import org.spockframework.runtime.model.BlockMetadata;
import org.spockframework.runtime.model.FeatureMetadata;
import org.spockframework.runtime.model.FieldMetadata;
import org.spockframework.runtime.model.SpecMetadata;
import spock.lang.Shared;
#SpecMetadata(
filename = "FooIT.groovy",
line = 6
)
public class FooIT extends GebSpec {
#Shared
#FieldMetadata(
line = 7,
name = "myField",
ordinal = 0
)
protected volatile Object $spock_sharedField_myField;
public FooIT() {
CallSite[] var1 = $getCallSiteArray();
}
#FeatureMetadata(
line = 10,
name = "test",
ordinal = 0,
blocks = {#BlockMetadata(
kind = BlockKind.EXPECT,
texts = {}
)},
parameterNames = {}
)
public void $spock_feature_1_0() {
CallSite[] var1 = $getCallSiteArray();
ErrorCollector $spock_errorCollector = (ErrorCollector)ScriptBytecodeAdapter.castToType(var1[2].callConstructor(ErrorCollector.class, false), ErrorCollector.class);
ValueRecorder $spock_valueRecorder = (ValueRecorder)ScriptBytecodeAdapter.castToType(var1[3].callConstructor(ValueRecorder.class), ValueRecorder.class);
Object var10000;
try {
try {
SpockRuntime.verifyCondition($spock_errorCollector, $spock_valueRecorder.reset(), "true", Integer.valueOf(12), Integer.valueOf(5), (Object)null, $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(0)), true));
var10000 = null;
} catch (Throwable var13) {
SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder, "true", Integer.valueOf(12), Integer.valueOf(5), (Object)null, var13);
var10000 = null;
} finally {
;
}
var1[4].call(var1[5].call(this.getSpecificationContext()));
} finally {
$spock_errorCollector.validateCollectedErrors();
var10000 = null;
}
}
public Object get$spock_sharedField_myField() {
return this.$spock_sharedField_myField;
}
public void set$spock_sharedField_myField(Object var1) {
this.$spock_sharedField_myField = var1;
}
}
Update 2:
As for your additional questions, I can only speculate about the answers, I am sure users like #erdi (Geb maintainer), #Szymon Stepniak, #Leonard Brünings (who seem to be Groovy cracks, which I am not) could say more about it, but OTOH this is not a discussion forum and the questions are not particularly well suited for SO. Anyway, I edited the question tags to include "groovy" so as to maybe raise their attention.
Why introduce $spock_sharedField__browser and remove _browser?
I think it is just the result of Spock's way of transforming the #Shared annotation into a member variable with named so as to very unlikely collide with any existing member names. You also see this happening in the decompiled version of my own Spock/Geb specification.
But there are still getters for browser and _browser.
Of course there is a getter for browser, as in the Geb DLS you usually don't look behind the scenes but just use the syntactic sugar browser to access the browser instance. This Groovy-ism will call getBrowser(), as you probably know. This particular getter is declared explicitly in the GebSpec class in order to make the member conveniently accessible (you also see some lazy browser instantiation logic here):
Browser getBrowser() {
if (_browser == null) {
_browser = createBrowser()
}
_browser
}
I wonder where there get their data from (in this case geb.Browser#352ff4da) as none of the field exists anymore as the exceptions show.
I do not know enough about Groovy's dynamic language features to answer that, but you can see the actual mechanics in my decompiled code snippets.
Accessing Spock-specific class members from outside a running specification obviously does not work and probably is not meant to be. But if you run this test, it works just fine:
package de.scrum_master.stackoverflow
import geb.spock.GebSpec
import spock.lang.Shared
class FooIT extends GebSpec {
#Shared
def myField = "foo"
def test() {
given:
println browser
println myField
expect:
true
}
}
Console log:
geb.Browser#1722011b
foo
I am new to writing geb specs for funcational testing and appreciate your help with this issue. I am also new to Groovy, and the "Not Initialized" error might be to do with basic inexperience in that. I am trying to build a module to log me in automatically, at which point I can proceed with further tests, but I receive a geb.error.ModuleInstanceNotInitializedException. Previously, not using a module, I was able to log in to my page using only a spec. I was refactoring this to a module approach based on the answer on rcgeorge23's response here:
How to refactor common Geb test sequences.
The spec:
import spock.lang.Unroll
class FlowSpecs extends GebReportingSpec {
#Unroll
def "setup Spec"() {
given:
to QUserPage
when:
authModule.signIn("mwalle","password")
then:
assert { $("span", "class":"login-text z-label")[3].text() == "mwalle" }
}
the page:
package pages.app
import geb.Page
import AuthModule
class QUserPage extends Page {
static url = "/qsystem/quser"
static at = { title == "QSystem" }
static content = {
authModule { module AuthModule }
}
}
and the module:
import geb.Module
class AuthModule extends Module {
def idUser = js."zk.Widget.\$('\$usr').uuid" + "-real"
def idPass = js."zk.Widget.\$('\$pwd').uuid"
static content = {
loginUser { $("input", id:idUser) }
loginPass { $("input", id:idPass) }
loginButton { $("button",class:"login-button z-button")[0] }
}
void signIn(String username = "mwalle", String password = "password") {
loginUser.value(user)
loginPass.value(pass)
loginButton.click()
}
}
The full error is:
geb.error.ModuleInstanceNotInitializedException: Instance of module class AuthModule has not been initialized. Please pass it to Navigable.module() or Navigator.module() before using it.
at geb.Module.uninitializedException(Module.groovy:757)
at geb.Module.getJs(Module.groovy:96)
at AuthModule.<init>(AuthModule.groovy:5)
at geb.navigator.AbstractNavigator.module(AbstractNavigator.groovy:356)
at geb.content.NavigableSupport.module(NavigableSupport.groovy:207)
at geb.content.PageContentTemplateFactoryDelegate.module(PageContentTemplateFactoryDelegate.groovy:31)
at pages.app.QUserPage._clinit__closure2$_closure3(QUserPage.groovy:12)
at pages.app.QUserPage._clinit__closure2$_closure3(QUserPage.groovy)
at geb.content.PageContentTemplate.invokeFactory(PageContentTemplate.groovy:97)
at geb.content.PageContentTemplate.create_closure1(PageContentTemplate.groovy:59)
at geb.content.PageContentTemplate.create_closure1(PageContentTemplate.groovy)
at geb.content.PageContentTemplate.create(PageContentTemplate.groovy:82)
at geb.content.PageContentTemplate.get(PageContentTemplate.groovy:54)
at geb.content.DefaultPageContentSupport.getContent(DefaultPageContentSupport.groovy:42)
at geb.content.PageContentSupport.propertyMissing(PageContentSupport.groovy:39)
at geb.Page.propertyMissing(Page.groovy:112)
at geb.Browser.propertyMissing(Browser.groovy:216)
at geb.spock.GebSpec.propertyMissing(GebSpec.groovy:60)
at FlowSpecs.setup Spec(FlowSpecs.groovy:23)
The problem are these lines
def idUser = js."zk.Widget.\$('\$usr').uuid" + "-real"
def idPass = js."zk.Widget.\$('\$pwd').uuid"
js needs an initialized navigator, as you can see with at geb.Module.getJs(Module.groovy:96)
I would strongly advise to use another selector (css or otherwise) if you can instead of using javascript to lookup the id. If you cannot then move the lookup code into the content block.
static content = {
loginUser { $("input", id: js."zk.Widget.\$('\$usr').uuid" + "-real") }
loginPass { $("input", id: js."zk.Widget.\$('\$pwd').uuid") }
loginButton { $("button",class:"login-button z-button")[0] }
}
I work with tests on geb and I have problem. I need to save/print the address of the current page (function SaveUrl()).
Spock Test:
class TestSpec extends GebReportingSpec {
def "Google"() {
given: "go to google.com"
to GooglePage
when: "we at Google home page"
at GooglePage
then: "Search Yahoo"
Search("Yahoo")
SaveUrl()
}
}
GooglePage:
class GooglePage extends Page {
static url = "http://www.google.by"
static at = { $("title").text() == "Google"}
static content = {
theModule { module SearchModule }
}
def Search(String arg0) {
theModule.field.value(arg0)
theModule.search.click()
}
def SaveUrl() {
// need implement
}
}
Modile:
class SearchModule extends Module {
static content = {
field { $("input", name: "q") }
search { $("input", name: "btnK") }
}
}
Please help save/print current URL.
Thank You!
You can use the current url getter on WebDriver class. A WebDriver instance is stored as driver property on Browser. So in a Geb Spock test it is as simple as saying:
driver.currentUrl
EDIT
Since Geb 0.9.3 there is also a current url getter available on Browser.
I have simple Groovy category class which adds method to String instances:
final class SampleCategory {
static String withBraces(String self) {
"($self)"
}
}
I want to use this category in my unit tests (for example). It looks like this:
class MyTest {
#Test
void shouldDoThis() {
use (SampleCategory) {
assert 'this'.withBraces() == '(this)'
}
}
#Test
void shouldDoThat() {
use (SampleCategory) {
assert 'that'.withBraces() == '(that)'
}
}
}
What I'd like to achieve, however, is ability to specify that category SampleCategory is used in scope of each and every instance method of MyTest so I don't have to specify use(SampleCategory) { ... } in every method.
Is it possible?
You can use mixin to apply the category directly to String's metaClass. Assign null to the metaClass to reset it to groovy defaults. For example:
#Before void setUp() {
String.mixin(SampleCategory)
}
#After void tearDown() {
String.metaClass = null
}
#Test
void shouldDoThat() {
assert 'that'.withBraces() == '(that)'
}
Now you have the option to use extension modules instead of categories:
http://mrhaki.blogspot.se/2013/01/groovy-goodness-adding-extra-methods.html
On the plus side Intellij will recognize the extensions. I've just noticed that it doesn't even need to be a separate module as suggested by the link, just add META-INF/services/org.codehaus.groovy.runtime.ExtensionModule to the project:
# File: src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule
moduleName = module
moduleVersion = 1.0
extensionClasses = SampleExtension
The extension class is pretty much defined like a normal category:
class SampleExtension {
static String withBraces(String self) {
"($self)"
}
}
Can be used like:
def "Sample extension"() {
expect: 'this'.withBraces() == '(this)'
}
If you are using Spock there is a #Use annotation that can be used on the specifications. The drawback with that is that Intellij will not recognize it.