Page Object Model is a design pattern that focuses on making a Test Automation Framework extensible, maintainable, and easy to understand. In this article, we will see how we can create a Page Object structure for Nightwatch JS.

Step 1: Create a folder named ‘pageObjects’ and then mention the folder path in the nightwatch.conf.js file:

1
page_objects_path: "pageObjects"

Nightwatch conf js with page object path property
 
Step 2: Let’s write a simple test and then implement Page Object for it.

1. Log in to the Orange CRM Website
2. Go to My Info Page, update the first and last name
3. Validate that the first and last names are updated correctly
4. Logout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
module.exports = {
    before: function (browser) {
        //Declaring Global Timeout
        browser
            .globals.waitForConditionTimeout = 7000
    },
    'Login': function (browser) {
        browser
            .url('https://opensource-demo.orangehrmlive.com/')
            .waitForElementVisible('#txtUsername')
            .waitForElementVisible('#txtPassword')
            .setValue('#txtUsername', 'Admin')
            .setValue('#txtPassword', 'admin123')
            .click('#btnLogin')
            .assert.urlContains('/index.php/dashboard')
    },
    'Check Personal Info': function (browser) {
        browser
            .waitForElementVisible('#menu_pim_viewMyDetails')
            .click('#menu_pim_viewMyDetails')
            .click('#btnSave')
            .clearValue('#personal_txtEmpFirstName')
            .setValue('#personal_txtEmpFirstName', 'Peter')
            .clearValue('#personal_txtEmpLastName')
            .setValue('#personal_txtEmpLastName', 'Sullivan')
            .click('#btnSave')
            .assert.value('#personal_txtEmpFirstName', 'Peter')
            .assert.value('#personal_txtEmpLastName', 'Sullivan')
    },
    'Logout': function (browser) {
        browser
            .waitForElementVisible('a[href*="dashboard"]')
            .click('a[href*="dashboard"]')
            .waitForElementVisible('#welcome')
            .click('#welcome')
            .waitForElementVisible('a[href*="logout"]')
            .click('a[href*="logout"]')
            .assert.urlContains('/index.php/auth/login')
    },

    after: function (browser) {
        browser.end();
    }
}

Nightwatch JS test
 
Step 3: Now, let’s move the locators from the test file and manage them from a single location. This is very helpful in case of maintenance.

Now, inside the pageObjects folder, we will create three js files, as in our test we would be working on three different pages. The idea is to create one js file per page.

Nightwatch JS Page Object structure
 
Step 4: Now we will move the locators of the corresponding pages to their respective JS files.

a) home.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module.exports = {
    url: 'https://opensource-demo.orangehrmlive.com/',
    elements: {
        username: {
            selector: '#txtUsername'
        },
        password: {
            selector: '#txtPassword',
        },
        loginBtn: {
            selector: '#btnLogin'
        }
    }
}

Nightwatch JS page object home js

b) myInfo.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module.exports = {
    elements: {
        myInfoTab: {
            selector: '#menu_pim_viewMyDetails'
        },
        editSaveBtn: {
            selector: '#btnSave',
        },
        firstName: {
            selector: '#personal_txtEmpFirstName'
        },
        lastName: {
            selector: '#personal_txtEmpLastName'
        }
    }
}

Nightwatch JS page object my info js

c) dashboard.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
module.exports = {
    elements: {
        dashboardTab: {
            selector: 'a[href*="dashboard"]'
        },
        welcomeText: {
            selector: '#welcome',
        },
        logoutBtn: {
            selector: 'a[href*="logout"]'
        }
    }
}

Nightwatch JS page object dashboard js
 
Step 5: Page objects are defined in modules and parsed into factory functions that create page object instances. These factories are accessible through the page reference within the command API (accessible through the “client” or “browser” object) using the name of the module that defines them. For eg.

1
var home = browser.page.home()

 
Next, we will access the locators of the specific page using their respective page object. For eg. If we want to input the text ‘Admin’ in the ‘username’ field on the home page, we can write something like:

1
home.setValue('@username', 'Admin')

 
Now keeping these two things in mind, we will create three page objects for our three pages and update our test like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
module.exports = {
    before: function (browser) {
        //Declaring Global Timeout
        browser
            .globals.waitForConditionTimeout = 7000
    },
    'Login': function (browser) {
        var home = browser.page.home()
        home
            .navigate()
            .waitForElementVisible('@username')
            .waitForElementVisible('@password')
            .setValue('@username', 'Admin')
            .setValue('@password', 'admin123')
            .click('@loginBtn')
            .assert.urlContains('/index.php/dashboard')
    },
    'Check Personal Info': function (browser) {
        var myInfo = browser.page.myInfo()
        myInfo
            .waitForElementVisible('@myInfoTab')
            .click('@myInfoTab')
            .click('@editSaveBtn')
            .clearValue('@firstName')
            .setValue('@firstName', 'Peter')
            .clearValue('@lastName')
            .setValue('@lastName', 'Sullivan')
            .click('@editSaveBtn')
            .assert.value('@firstName', 'Peter')
            .assert.value('@lastName', 'Sullivan')
    },
    'Logout': function (browser) {
        var dashboard = browser.page.dashboard()
        dashboard
            .waitForElementVisible('@dashboardTab')
            .click('@dashboardTab')
            .waitForElementVisible('@welcomeText')
            .click('@welcomeText')
            .waitForElementVisible('@logoutBtn')
            .click('@logoutBtn')
            .assert.urlContains('/index.php/auth/login')
    },
    after: function (browser) {
        browser.end();
    }
}

Nightwatch JS test with page object locators
 
Step 6: We can further condense the code by writing page specific custom commands. This approach is also helpful in preventing repetitive code. In our test we are performing login on the homepage and logout on the dashboard page. So we will create custom commands for both in their respective pages.

Now, home.js will look something like this. We have added all the lines of code that are responsible for login under one function and in the tests we will only use login().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var homeCommands = {
    login: function () {
        return this.navigate()
            .waitForElementVisible('@username')
            .waitForElementVisible('@password')
            .setValue('@username', 'Admin')
            .setValue('@password', 'admin123')
            .click('@loginBtn')
    }
}

module.exports = {
    url: 'https://opensource-demo.orangehrmlive.com/',
    commands: [homeCommands],
    elements: {
        username: {
            selector: '#txtUsername'
        },
        password: {
            selector: '#txtPassword',
        },
        loginBtn: {
            selector: '#btnLogin'
        }
    }
}

Nightwatch js home js with page specific custom commands

Similarly, for dashboard.js we will add all the lines of code responsible for logout under one function and in the tests we will only use logout().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var dashboardCommands = {
    logout: function () {
        return this.waitForElementVisible('@dashboardTab')
            .click('@dashboardTab')
            .waitForElementVisible('@welcomeText')
            .click('@welcomeText')
            .waitForElementVisible('@logoutBtn')
            .click('@logoutBtn')
    }
}

module.exports = {
    commands: [dashboardCommands],
    elements: {
        dashboardTab: {
            selector: 'a[href*="dashboard"]'
        },
        welcomeText: {
            selector: '#welcome',
        },
        logoutBtn: {
            selector: 'a[href*="logout"]'
        }
    }
}

Nightwatch js dashboard js with page specific custom commands

Now, we will create a custom command for updating the first name and last name. Here we will create a parameterized function where we will pass the first and the last name from the test itself.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
var myInfoCommands = {
    updateMyInfo: function (firstName, lastName) {
        return this.waitForElementVisible('@myInfoTab')
        .click('@myInfoTab')
        .click('@editSaveBtn')
        .clearValue('@firstName')
        .setValue('@firstName', firstName)
        .clearValue('@lastName')
        .setValue('@lastName', lastName)
        .click('@editSaveBtn')
    }
}

module.exports = {
    commands: [myInfoCommands],
    elements: {
        myInfoTab: {
            selector: '#menu_pim_viewMyDetails'
        },
        editSaveBtn: {
            selector: '#btnSave',
        },
        firstName: {
            selector: '#personal_txtEmpFirstName'
        },
        lastName: {
            selector: '#personal_txtEmpLastName'
        }
    }
}

Nightwatch js my info js with page specific custom commands

So in the test, we will write:

1
.updateMyInfo('Peter', 'Sullivan')

 
Step 7: After making all the changes our test will look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
module.exports = {
    before: function (browser) {
        //Declaring Global Timeout
        browser
            .globals.waitForConditionTimeout = 7000
    },

    'Login': function (browser) {
        var home = browser.page.home()
        home
            .login()
            .assert.urlContains('/index.php/dashboard')
    },

    'Check Personal Info': function (browser) {
        var myInfo = browser.page.myInfo()
        myInfo
            .updateMyInfo('Peter', 'Sullivan')
            .assert.value('@firstName', 'Peter')
            .assert.value('@lastName', 'Sullivan')
    },

    'Logout': function (browser) {
        var dashboard = browser.page.dashboard()
        dashboard
            .logout()
            .assert.urlContains('/index.php/auth/login')
    },
   
    after: function (browser) {
        browser.end();
    }
}

Nightwatch JS page Object implemented test

After Test Execution:

cli test execution

Do check out 🙂

Github: https://github.com/alapanme/NightwatchJS
All Nightwatch JS Articles: https://testersdock.com/nightwatch-js-tutorial/