Howto define a complex Task involving the actor #2326
-
I come from serenity-BDD (java), and I was tired of using the wrong language to fidget with the UI elements, so I'm trying to switch over to serenity-js. Back in serenity-bdd, I had a complex task that was able to Read an email on a mailcatcher, extract some registration link from it, and save it to the notebook. Here the body of my custom task, inclusive a custom question that check if the mails arrived yet. @Override
public <T extends Actor> void performAs(T aActor) {
aActor.whoCan(CallAnApi.at(itsHost))
.wasAbleTo(
Wait.until(EmailAmount.forThemselves(), greaterThan(0))
.forNoMoreThan(Duration.of(5, ChronoUnit.SECONDS))
);
List<String> mailIds = SerenityRest.lastResponse()
.prettyPeek()
.path("messages.ID");
aActor.wasAbleTo(
Ensure.that(mailIds).isNotEmpty(),
Open.url(itsHost + "/view/" + mailIds.getFirst()),
Switch.toFrame(PageElement.located(By.id("preview-html")).resolveFor(aActor))
);
aActor.remember(itsKey, Attribute.of(Link.startingWith("Registrierung"), "href"));
}
private static class EmailAmount {
public static Question<Integer> forThemselves() {
return actor -> {
actor.attemptsTo(
Get.resource("/api/v1/search").with(
req -> req.queryParam("query", actor.usingAbilityTo(Authenticate.class).getUsername())
.queryParam("limit", 5)));
return ((List<?>) SerenityRest.lastResponse().path("messages")).size();
};
}
} I'm having hard time converting that to serenity-js:
Anyway, here is my attempt: export class ReadMail {
private saveAs: string;
private toSelf: boolean;
static toSelf = () => new ReadMail();
andSaveRegistrationLinkAs = (key: string) =>
Task.where(`#actor reads its Mail`,
Wait.upTo(Duration.ofSeconds(5))
.until(new EmailAmount(), isGreaterThan(0)),
Navigate.to(URLForEmail()),
Switch.to(PageElement.located(By.id('preview-html'))),
notes().set(key, Attribute.called('href')
.of(PageElement.located(By.cssContainingText('a', 'Registrierung')))),
);
}
class EmailAmount extends Question<Promise<number>> {
private subject: string;
public async answeredBy(actor: AnswersQuestions & UsesAbilities): Promise<number> {
let username = await actor.answer(Authenticate.username());
await Send.a(GetRequest.to(`${catcherURL}/api/v1/search?query=${username}&limit=5`)).performAs(actor);
return LastResponse.body<SearchResult>().messages.length.answeredBy(actor);
}
...
}
const URLForEmail = () => Question.about('the page of the first mail from the search result', async actor =>
LastResponse.body<SearchResult>().messages[ 0 ].ID.answeredBy(actor)
.then(id => catcherURL + '/view/' + id));
|
Beta Was this translation helpful? Give feedback.
Replies: 1 comment
-
Serenity/JS offers a much stronger separation between tasks, interactions and questions than our original implementation in Serenity BDD, as well as a stronger preference towards composition over inheritance:
You're, of course, free to implement your tasks using the original inheritance style, or the composition style, so let me show you some examples. InheritanceYou can implement a custom task by inheriting from the base import { Task, PerformsActivities, UsesAbilities, AnswersQuestions } from '@serenity-js/core'
export class GetRegistrationLink extends Task {
constructor(private readonly someParameter: string) {
super(`#actor gets a registration link`);
}
async performAs(actor: PerformsActivities & UsesAbilities & AnswersQuestions): Promise<void> {
return actor.attemptsTo(
// ...
);
}
} Several tasks in the Serenity/JS code base follow this pattern. Composition
Your implementation looks good. You could simplify it, however, by accessing the ability to import { Task, UsesAbilities, AnswersQuestions } from '@serenity-js/core'
export class RememberRegistrationLink {
static as = (key: string) =>
Task.where(`#actor remembers the registration link`,
Wait.upTo(Duration.ofSeconds(5))
.until(Email.unread(), isGreaterThan(0)),
Navigate.to(URLForEmail()),
Switch.to(PageElement.located(By.id('preview-html'))).and(
notes().set(key, Attribute.called('href')
.of(PageElement.located(By.cssContainingText('a', 'Registrierung')))),
),
);
}
class Email {
static unread = () =>
Question.about('number of unread emails', (actor: AnswersQuestions & UsesAbilities => {
const username = await actor.answer(Authenticate.username());
const response = await CallAnApi.as(actor).request({
url: `${catcherURL}/api/v1/search?query=${username}&limit=5`
})
return response.data.messages.length;
})
} |
Beta Was this translation helpful? Give feedback.
Serenity/JS offers a much stronger separation between tasks, interactions and questions than our original implementation in Serenity BDD, as well as a stronger preference towards composition over inheritance:
You're, of course, free to implement your tasks using the original inh…