question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[Documentation] Add a guide for using Page Objects Pattern with Jest

See original GitHub issue

🚀 Feature Proposal

Page Objects Pattern has makes it much easier for developers to write tests that make assertions against the DOM. With a good Page Objects implementation, developers can create convenient interfaces between the DOM and their test suite. By abstracting references to specific DOM selectors, developers maintaining large test suites can make changes to selectors in one place instead of having to update every test for a component that changed.

Page Objects are especially useful when components are distributed as a collection of common components and meant to be consumed to build specialized components. For common component libraries, Page Objects allow the original developers to provide Page Objects to be used in tests of components that consume their components. By providing an interface to components’ DOM, the creators of the common components get the freedom to change the structure or selectors used to access DOM elements without necessarily breaking the tests that rely on these components.

Motivation

The motivation for this change it is to inform Jest users of a pattern that they can use to make their test suite less fragile, faster to write, more expressive and easier to maintain.

Example

No specific change is necessary support Page Objects in tests. It’s purely a documentation feature meant to inform developers of a pattern that they can use with Jest. I will show how Page Objects can be created with @bigtest/interactor package. There are other libraries available for this purpose but @bigtest/interactor is a great match for Jest because composability of these Page Objects is well suited for component libraries.

To understand how Page Objects work, let’s create a simple button component that will be with other components. We’ll start by creating the button component and a corresponding Page Object.

import React from 'react';

export default function Button({ children, classNames, onClick }) {
  return <button className={['common-button', ...classNames]} onClick={onClick}>{children}</button>
}

To create a Page Object, we’ll create a module that will contain our page object. This file can be included in the component module, but this is a purely stylistic decision.

import { interactor, text, clickable } from '@bigtest/interactor'

export default interactor(
  class ButtonInteractor {
    static defaultScope = '.common-button' // this provides default selector for this button, it can be overwritten for specific tests
    text = text() // provides text of the interactor
    click = clickable() // allows to click the element
  }
)

To use this interactor in a test, it’d look something like this. I’m going to use @bigtest/react to render the actual component, but it’s not necessary. You can use any mechanism as long as your component is rendered before you start using the page object.

import Button from './button';
import ButtonInteractor from './button-interactor';
import { mount } from '@bigtest/react';

describe('<Button />', () => {
  let button = new ButtonInteractor();
  it('is rendered', async () => {
        await mount(() => <Button />)
        
        // interactors provide some built in properties that are provided for free because you're using the interactor
        expect(button.isPresent).toBe(true);
  });

  it('renders provided text', async () => {
    await mount(() => <Button>hello world</Button>)

    expect(button.text).toBe('hello world');
  });

  it('calls onClick handler', async () => {
    const handler = jest.fn();
    
    await mount(() => <Button onClick={handler}>hello world</Button>)

    await button.click();
    
    expect(handler).toBeCalled();
  });
})

When this component is used in another component, the developer using the provided component can import the page object and compose it with their own to test their component. Let’s create a component that uses the button and test it.

import { Button } from 'common-components';

export default function ContactForm({ onSubmit, onChangeName, name }) {
  return (
    <form class="contact-form" onSubmit={onSubmit}>
      <input classNames="first-name" value={name} onChange={e => onChangeName(e.target.value) } />
      <Button classNames="submit-button" type="submit">Submit</button>
    </div>
  )
}

To test this form component,

import { Button, ButtonIteractor } from 'common-components';
import { mount } from '@bigtest/react';
import { interactor, fillable, scoped } from '@bigtest/interactor';

const ContactFormInteractor = interactor(
  class {
    static defaultScope = 'contact-form';
    enterFirstName = fillable('input'); // this will be scoped to the form's default scope
    submitButton = scoped('.submit-button', ButtonInteractor) // mount composed interactor
  }
)
 
describe('<ContactForm />', () => {
  const form = new ContactFormInteractor();

  it('allows user to input text', async () => {
    const handler = jest.fn();

    await mount(() => <ContactForm onChangeName={handler} />);

    await form.enterFirstName('Bob');

    expect(handler).toBeCalledWith('Bob');
  });

  it('calls submit when button is pressed', () => {
    const handler = jest.fn();

    await mount(() => <ContactForm onSubmit={handler} />)

    await form.submitButton.click();

    expect(handler).toBeCalled();
  });
});

This is a very simple example, but you can see how the button page object is composed into the form page object. The test is very clear about what is happening and the developer doesn’t need to work directly with DOM elements.

For an example of a robust component library using this pattern with @bigtest/interactor you can checkout Folio Stripes component library used to build future platform for libraries around the world.

Pitch

Without making any changes to Jest, users of Jest can start using a pattern that is proven to scale to very large projects. This pattern makes writing DOM tests a joy.

Additional Resources

I’ll be happy to write this documentation if this addition is welcomed.

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Comments:7 (2 by maintainers)

github_iconTop GitHub Comments

1reaction
rickhanloniicommented, Dec 14, 2018

Hey @taras, thanks for submitting! This looks like a really cool way to test components

What would you say to someone who would think that this guide would be used as just a marketing channel to funnel people to bigtestjs/interactor?

0reactions
github-actions[bot]commented, Apr 30, 2022

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. Please note this issue tracker is not a help forum. We recommend using StackOverflow or our discord channel for questions.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Using Page Object Model (POM) Pattern In Selenium JavaScript
In this blog, you will learn how to implement the Page Object Model pattern in JavaScript with the help of the WebdriverIO framework....
Read more >
Page Object Pattern - WebdriverIO
The goal of using page objects is to abstract any page information away from the actual tests. Ideally, you should store all selectors...
Read more >
Page Object Model | Developer Guide - Nightwatch.js
The Page Objects methodology is a popular pattern to write end-to-end tests by wrapping the pages or page fragments of a web app...
Read more >
Stop using Page Objects and Start using App Actions - Cypress
Page objects are hard to maintain and take away time from actual application development. I have never seen PageObjects documented well enough ...
Read more >
Configuring Jest
You can use --config flag to pass an explicit path to the file. note. Keep in mind that the resulting configuration object must...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found