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