Jest testing with drivers / PageObjects
- Unit testing of components in general
- Enzyme and driver testing
- E2E testing and PageObjects
Plan
Unit testing of components in general
- Logic unit tests
- Snapshot unit tests
- Some interactivity tests
- Screenshot tests
Pyramid of tests
Enzyme and drivers - driver
...
import {
DriverBase,
wrapWithAngularRouter,
reduxStoreAsProp
} from "client/reactApp/utils/jest";
const WrappedComponent = reduxStoreAsProp(wrapWithAngularRouter(Organizations));
class OrganizationsDriver extends DriverBase {
constructor() {
super(WrappedComponent);
}
initElements() {
this.openModalButton = this.wrapper.find("Header").find("button");
}
clickOpenModalButton() {
this.openModalButton.simulate("click");
}
isAddMembersModalOpen() {
return this.membersContainerElement.prop("isModalOpen") === true;
}
}
...
Enzyme and drivers - init and mocking
...
import { flush, mockStore } from "client/reactApp/utils/jest";
import Driver from "./Organizations.driver";
jest.mock("./Members", () => {
return () => "Members container";
});
const getMockedStore = () =>
mockStore({
organizations: {
organizations: [{}]
}
});
describe("Containers::Organizations", () => {
let driver;
const angularRouter = { routerState: "" };
beforeEach(() => {
driver = new Driver();
driver.init({
angularRouter,
store: getMockedStore()
});
});
});
...
Enzyme and drivers - usage
...
it("tells Members container to open modal on action button click", async () => {
driver.init({
angularRouter: { ...angularRouter, routerState: "organizations.members" },
store: getMockedStore()
});
driver.clickOpenModalButton();
await flush();
driver.update();
expect(driver.isAddMembersModalOpen()).toBe(true);
});
...
yarn test:client
Enzyme and drivers - snapshots
...
it("tells Members container to open modal on action button click", async () => {
driver.init({
angularRouter: { ...angularRouter, routerState: "organizations.members" },
store: getMockedStore()
});
expect(driver.getMembersList()).toMatchSnapshot();
});
...
yarn test:client -u
yarn test:client
Enzyme and drivers - base methods and helpers
...
export class DriverBase {
constructor(Component, props) {
this.Component = Component;
}
init(props) {
this.wrapper = mount(<this.Component {...props} />);
...
}
}
...
export const wrapWithThemeProvider
...
export const wrapWithAngularRouter
...
export const wrapWithPermissionsProvider
...
export const reduxStoreAsProp
...
export const findReactComponentAndShallowRender
...
PageObject - idea
- Hide implementation details of component
- Hide interaction details of driver
- Simply hide complexity
Provides a way to interact with UI - a page or a complex component - from test
PageObject - example
import { PageBase } from "./page-base";
export class LoginPage extends PageBase {
async init(host) {
await this.goToUrl(concatUrl(host, "signin"));
this.emailInput = await this.waitForSelector("#input-email");
this.passInput = await this.waitForSelector("#input-password");
}
async login(login, password) {
await this.emailInput.setValue(login);
await this.passInput.setValue(password);
const signinButton = await this.waitForSelector(
"button:enabled[type=submit]"
);
await signinButton.click();
}
}
PageObject - usage
import { LoginPage } from "./e2e-test-helpers/login-page";
describe("smoke UI tests", () => {
let page;
beforeAll(async () => {
page = new LoginPage(global.browser);
});
beforeEach(async () => {
await liltApp.init();
});
it("can login", async () => {
await loginPage.login("foo", "bar");
});
});
yarn test-cli ui-e2e --target=beta-nonRbac [--debug]
PageObject - base abstractions
export class PageBase {
constructor(impl, useWdio) {
this.impl = impl;
this.useWdio = useWdio;
}
goToUrl(url) {
return !this.useWdio ? this.impl.goto(url) : this.impl.url(url);
}
async waitForSelector(selector, timeoutOverride = undefined) {
if (this.useWdio) {
const elem = await this.impl.$(selector);
await elem.waitForExist(timeoutOverride);
return new BaseElem(elem, this.useWdio);
}
const elem = await this.impl.waitForSelector(selector);
return new BaseElem(elem, this.useWdio);
}
}
Making tests stable
- Use specific ids for locating elements like [data-test-id="login-button"]
- Write abstractions like Driver or PageObject
- Always provide meaningful data, not empty objects and arrays
- Isolate environments
- Be careful with component interfaces (or use TypeScript)
- Strive for determinism
Recap
- Be mindful of testing levels
- Abstract interactions with Drivers and PageObjects
- Focus on tests not being flaky
Thanks! Questions?
driver-po-at-lilt
By Valeriy Kuzmin
driver-po-at-lilt
- 296