DEV Community

Cover image for E2E Testing with TestCafe | Part 4

E2E Testing with TestCafe | Part 4

chrisvasqm profile image Christian Vasquez ・3 min read

On Part 3 we learned how to run not only multiple browsers in parallel, but also multiple instances of those same browsers.

So what can we do to improve our TestCafe project?

If you followed this guide since we started, your devto.js should look like this:

import { Selector } from 'testcafe'

fixture('DEV Tests')

test("Check founder's names", async(t) => {
    const aboutLink = Selector('a').withText('About');
    const firstFounder = Selector('b').withText('Ben Halpern');
    const secondFounder = Selector('b').withText('Jess Lee');
    const thirdFounder = Selector('b').withText('Peter Frank');

    await t

test("Search for posts about testing", async (t) => {
    const discussTag = Selector('span').withText('#discuss');
    const discussTitle = Selector('h1').withText('Discussion');

    await t

What can we possibly refactor?

It works just fine, right?

Correct, it does works. But you know what? We can take it to the next level. So let's go!


The first thing we will need is to find a better way to use our Selectors. Not by using a different method or syntax, but actually abstracting them from our test functions.

Our tests know too much right now.


So, how can we do that?

Introducing... Page Object Model

This design pattern consists of making classes that represent a given page, based on the functionality they are used for.

For example


This page should hold the Selectors that are needed to make a search. That means that any filter and articles that may show up, should be part of it.

This makes our code go from being:

const aboutLink = Selector('a').withText('About');


import DevHomePage from '../page/devhomepage.js';

const devHomePage = new DevHomePage();

// ...

await t

Now there's no doubt about which "About Link" we are using in our tests, while also making our Selector be available for other tests that might need it, also known as reusability.

So, our final result should look like this:

// project/page/devhomepage.js

import { Selector } from 'testcafe';

export default class DevHomePage {

    constructor() {
        this.aboutLink = Selector('a').withText('About');
        this.discussTag = Selector('span').withText('#discuss');

// project/page/devaboutpage.js

import { Selector } from 'testcafe';

export default class DevAboutPage {

    constructor() {
        this.firstFounder = Selector('b').withText('Ben Halpern');
        this.secondFounder = Selector('b').withText('Jess Lee');
        this.thirdFounder = Selector('b').withText('Peter Frank');


import { Selector } from 'testcafe';

export default class DevDiscussTagPage {

    constructor() {
        this.discussTitle = Selector('h1').withText('Discussion');


And now we can use all of our Pages in our tests like this:


import DevHomePage from "../page/devhomepage";
import DevAboutPage from "../page/devaboutpage";
import DevDiscussTagPage from "../page/devdiscusstagpage";

const devHomePage = new DevHomePage();
const devAboutPage = new DevAboutPage();
const devDiscussTagPage = new DevDiscussTagPage();

fixture('DEV Tests')

test("Check founder's names", async(t) => {
    await t

test("Filter articles by discuss tag", async (t) => {
    await t

But as always, never trust random post on the internet and try it out for yourself!

If you see:

Using locally installed version of TestCafe.
 Running tests in:
 - Chrome 63.0.3239 / Mac OS X 10.13.2

 DEV Tests
 ✓ Check founder's names
 ✓ Search for posts about testing

 2 passed (6s)

It means my job here is done 😎

You can probably continue your journey without me babysitting you.

So go eat the official documentation and

Learn all the things

Discussion (0)

Editor guide