Strategy pattern in UI automation

Design patterns are great because they provide us with the solutions for common problems, which means that not all problems are unique and some of them (and quite a lot actually) can be solved by applying already existing approaches a.k.a. patterns. One more benefit of patterns is that they give engineers common vocabulary.

Speaking of UI automation, the most popular pattern would probably be "Page Object pattern". But are there any other patterns really applicable in UI tests? Yes, there other patterns in UI test automation like in any other software. And as example we will consider Strategy pattern.

A few words before we start. For the sake of laconism we will use naive implementation of strategy, we will use only method to encapsulate logic but not classes implementing strategy interface. So, it is important to remember about this simplification and not to confuse next examples with template method! We will use appium and Wortschatz android application for writing our tests. Wortschatz is a very simple mobile application designed for people learning German language. We'll use simple test where we check if new German words can be added/deleted into application database. Sample test code can be found on github.

Enough talk, let's get started. Imagine the situation when we have two simple tests in our UI automation:

    @Test
    public void testSearchSingleWord() {
        var someAdjective = getSomeAdjective();
        givenAdjectiveIsAddedViaEditor(someAdjective);
        givenManagerScreenIsOpened();
        whenSearchForWord(someAdjective.getWord());
        thenSearchReturnedNumberOfWords(1);
    }

    @Test
    public void testDeleteWord() {
        var someVerb = getSomeVerb();
        givenVerbIsAddedViaEditor(someVerb);
        whenWordDeletedAfterSearch(someVerb);
        thenSearchReturnedNumberOfWords(0);
    }

As you can see, there are two very similar parts -- setup actions "givenAdjectiveIsAddedViaEditor(someAdjective)" and "givenVerbIsAddedViaEditor(someVerb)". Implementations for them would be:

    private void givenAdjectiveIsAddedViaEditor(Word adjective){
        this.editorScreen = startScreen.openEditor(driver);
        this.editorScreen.clickSpinner();
        this.adjectivePageObject = (AdjectivePageObject) this.editorScreen.selectSpinnerValue(driver, WordType.ADJECTIVE);
        this.adjectivePageObject.enterWord(adjective.getWord());
        this.adjectivePageObject.enterTranslation(adjective.getTranslation());
        this.adjectivePageObject.saveWord();
    }

    private void givenVerbIsAddedViaEditor(Verb verb){
        this.editorScreen = this.startScreen.openEditor(driver);
        this.editorScreen.clickSpinner();
        this.verbPageObject = (VerbPageObject) this.editorScreen.selectSpinnerValue(driver, WordType.VERB);
        this.verbPageObject.enterWord(verb.getWord());
        this.verbPageObject.enterTranslation(verb.getTranslation());
        this.verbPageObject.enterPartizip(verb.getPartizip());
        this.verbPageObject.selectAuxVerb(verb.getAuxverb());
        this.verbPageObject.saveWord();
    }

Look very similar, don't they? It would be good to create sort of generic mechanism for adding words via UI and encapsulate related logic in one place. Well, following the one of definitions of Strategy design pattern "it enables selecting an algorithm at runtime", we can create a method adding specific word depending on a word type. For example:

    private void wordIsAddedViaEditor(WordType type) {
        switch (type) {
            case ADJECTIVE:
                someAdjective = getSomeAdjective();
                addAdjectiveViaEditor(someAdjective);
                break;
            case VERB:
                someVerb = getSomeVerb();
                addVerbViaEditor(someVerb);
                break;
        }
    }

Where "addAdjectiveViaEditor(someAdjective)" and "addVerbViaEditor(someVerb)" hold all actions related to entering correspondent word details. One can argue that we added more lines of code. While it can be a valid point, look at our tests now

    @Test
    public void testSearchSingleWord() {
        wordIsAddedViaEditor(WordType.ADJECTIVE);
        givenManagerScreenIsOpened();
        whenSearchForWord(someAdjective.getWord());
        thenSearchReturnedNumberOfWords(1);
    }

    @Test
    public void testDeleteWord() {
        wordIsAddedViaEditor(WordType.VERB);
        whenWordDeletedAfterSearch(someVerb);
        thenSearchReturnedNumberOfWords(0);
    }

We use single setup method "wordIsAddedViaEditor()" and more importantly we made our two tests shorter and as a result intention of test is clearer now.

One more possible implementation for those who don't like switch-statement is to use Java's functional interfaces. Namely, we need an interface accepting no arguments and returning no value. We can use existing interface Runnable or better create our own with some fancy name


    @FunctionalInterface
    public interface Action {
        void execute();
    }

And having our new interface, switch-statement becomes a simple map:

    private void wordIsAddedViaEditor(WordType type) {
        Map<WordType, Action> wordTypeTostrategy = Map.of(
                WordType.ADJECTIVE, this::addAdjectiveViaEditor,
                WordType.VERB, this::addVerbViaEditor
        );
        wordTypeTostrategy.get(type).execute();
    }

Looks a bit fancier now, don't you think so?

For full sample project look at github.

Comments