0

I have just started looking into switching to Serenity/JS and wanted to know if it's best practice to have base Questions/Tasks?

There are many times I will want to check if a field is blank or has an error, so I created a 'base Question' to achieve this:

Base Question

import { Is, See, Target, Task, Wait, Value, Attribute } from 'serenity-js/lib/screenplay-protractor';
import { equals, contains } from '../../../support/chai-wrapper';
import { blank } from '../../../data/blanks';

export class InputFieldQuestion {
    constructor(private inputField: Target) { }

    isBlank = () => Task.where(`{0} ensures the ${this.inputField} is blank`,
        Wait.until(this.inputField, Is.visible()),
        See.if(Value.of(this.inputField), equals(blank))
    )

    hasAnError = () => Task.where(`{0} ensures the ${this.inputField} has an error`,
        See.if(Attribute.of(this.inputField).called('class'), contains('ng-invalid'))
    )
}

I then create classes specific to the scenario but simply extend the base question:

import { LoginForm } from '../scenery/login_form';
import { InputFieldQuestion } from './common';

class CheckIfTheUsernameFieldQuestion extends InputFieldQuestion {
    constructor() { super(LoginForm.Username_Field) }
}

export let CheckIfTheUsernameField = new CheckIfTheUsernameFieldQuestion();

The beauty of node exports allow me to export an instantiated question class for use in my spec.

I just wanted to know if I am abusing the Serenity/JS framework or if this is okay? I want to establish a good framework and wanted to ensure I am doing everything to best practice. Any feedback is appreciated!

Tom Hudson
  • 75
  • 1
  • 9

1 Answers1

0

Sure, you could do it this way, although I'd personally favour composition over inheritance.

From what I can tell, you're designing standardised verification Tasks, which you want to use as follows:

actor.attemptsTo(
    CheckIfTheUsernameField.isBlank()
)

You could accomplish the same result with a slightly more flexible design where a task can be parametrised with the field to be checked:

actor.attemptsTo(
    EnsureBlank(LoginForm.Username_Field)
)

Where:

const EnsureBlank = (field: Target) => Task.where(`#actor ensures that the ${field} is blank`,
    Wait.until(field, Is.visible()),
    See.if(Value.of(field), equals(blank)),
);

Or, to follow the DSL you wanted:

const EnsureThat = (field: Target) => ({
    isBlank: () => Task.where(`#actor ensures that the ${field} is empty`,
        Wait.until(field, Is.visible()),
        See.if(Value.of(field), equals(blank)),
    ),
    hasError: () => Task.where(`#actor ensures that the ${field} has an error`,
       See.if(Attribute.of(this.inputField).called('class'), contains('ng-invalid')),
    ),
});

Which can be used as follows:

actor.attemptsTo(
    EnsureThat(LoginForm.Username_Field).isBlank()
);

Hope this helps!

Jan

Jan Molak
  • 4,426
  • 2
  • 36
  • 32