Recently I’ve attended BKK.JS at Siam Paragon and there is session about Javascript Testing by P’Somkiat and I also forgot that I’ve done some proof-of-concept on end-to-end testing (which is ready for lightning talk stuff). I guess I should share here.
Problems on E2E Testing
-
Messy test code
- Current codebase pattern e.g. page object pattern is unintuitive, duplicate
- Explicit waits
- It’s so free-form that
-
Test code cannot be documentated or maintained easily
- From (1), onboarding application is hard for developers and maintainers.
- This is even harder on knowing as-is requirements/features of a project
What if it is not page object pattern.
Screenplay pattern proposed test involved around these components.
- Actor
This is a person (or some system) interacting with the application play as specific role e.g. Bob as an admin or Alice as a user.
- Ability
Actor has “abilities” that can do some interaction with interfaces of application e.g. use a browser
- Interaction
Abilities enable user to do some interactions e.g. navigating to page or click a some buttons
- Question
Actor can answer “questions”, asserting if it is correct or not. This is the same as expect
.
- Task
From all those component above, it can be formed as sequences of activities, describing business requirement of what we can do in a test in a normal human readable form.
Serenity.js
This library is for implementing tests with screenplay pattern, including all business logic of screenplay pattern on top of your favorite end-to-end testing library e.g.
- Playwright — this is my preference
- Protractor
- WebdriverIO
Easy setup, just click and go along.
Let’s take a look at code, here is some example of my first attempt and it is “perfection”.
import { describe, it } from "@serenity-js/playwright-test";
import { Navigate, PageElements, By } from "@serenity-js/web";
import { Ensure, equals } from "@serenity-js/assertions";
import { displayItemCounts } from "../app/questions";
import { useAppActor } from "../fixtures/actors";
describe("Todo List", () => {
useAppActor();
it("list should be empty", async ({ actorCalled }) => {
// Actor doing stuff in test.
await actorCalled("Alice").attemptsTo(
// Task.
Navigate.to("https://todo-app.serenity-js.org/"),
// Question.
Ensure.that(displayItemCounts(), equals(0))
);
});
});
From the code, everything component can be seperated as functions or classes and combined as a test. Let’s take a look at task and question in this test case.
import { By, PageElements } from "@serenity-js/web";
// Interaction to page.
export const displayedItems = (): PageElements =>
PageElements.located(By.css(".todo-list li")).describedAs("displayed items");
export const displayItemCounts = () => displayedItems().count();
For full code of example test, please checkout at this repository.