Search code examples
grailsgroovyspockgeb

Using NonEmptyNavigator.metaClass.invokeMethod { ... } to introduce short pause after field set


In my application, some of the Geb tests are a bit flaky since we're firing off an ajax validation http request after each form field changes. If the ajax call doesn't return fast enough, the test blows up.

I wanted to test a simple solution for this, which (rightly or wrongly, let's not get into that debate here...) is to introduce a short 100ms or so pause after each field is set, so I started looking at how & where I could make this happen.

It looks like I need to add a Thread.sleep after the NonEmptyNavigator.setInputValue and NonEmptyNavigator.setSelectValue methods are invoked. I created a subclass of GebSpec, into which I added a static initializer block:

static {
    NonEmptyNavigator.metaClass.invokeMethod = { String name, args ->
        def m = delegate.metaClass.getMetaMethod(name, *args)
        def result = (m ? m.invoke(delegate, *args) : delegate.metaClass.invokeMissingMethod(delegate, name, args))
        if ("setInputValue".equals(name) || "setSelectValue".equals(name)) {
            Thread.sleep(100)
        }
        return result
    }
}

However I added some debug logging, and I noticed that when I execute my spec I never hit this code. What am I doing wrong here...?


Solution

  • I know you asked not to get into a debate about putting sleeps whenever you set a form element value but I just want to assure you that this is really something you don't want to do. There are two reasons:

    • it will make your tests significantly slower and this will be painful in the long run as browser tests are slow in general
    • there will be situations (slow CI for instance) where that 100ms will not be enough, so in essence you are not removing the flakiness you are just somehow limiting it

    If you really insist on doing it this way then Geb allows you to use custom Navigator implementations. Your custom non empty Navigator implementation would look like this:

    class ValueSettingWaitingNonEmptyNavigator extends NonEmptyNavigator {
        Navigator value(value) {
            super.value(value)
            Thread.sleep(100)
            this
        }
    }
    

    This way there's no need to monkey-patch NonEmptyNavigator and you will avoid any strange problems that might cause.

    A proper solution would be to have a custom Module implementation that would override Navigator value(value) method and use waitFor() to check if the validation has completed. Finally you would wrap all your validated form elements in this module in your pages and modules content blocks. This would mean that you only wait where it's necessary and as little as possible. I don't know how big your suite is but as it will grow these 100ms will turn into minutes and you will be upset about how slow your tests are. Believe me, I've been there.