Filipin.eu

Željko Filipin's blog.
Home Blog Tags License

View on GitHub
30 June 2021

Page object pattern

by Željko Filipin

Dynastes hercules ecuatorianus MHNT

Simplicity versus readability and maintainability.

There are two main purposes of the page object pattern.

Let’s describe the two purposes with code samples from MediaWiki Core (as of June 2021).

With page object pattern

The code is split into two files. user.js contains the test. createaccount.page.js contains the page object. Code is available at 702327.

Please notice how readable user.js is. You can immediately see that the point of User should be able to create account is to create an user account with username and password as arguments.

tests/selenium/specs/user.js

'use strict';

const assert = require( 'assert' );
const CreateAccountPage = require( '../pageobjects/createaccount.page' );
const Util = require( 'wdio-mediawiki/Util' );

describe( 'User', function () {
	it( 'should be able to create account', function () {
		const username = Util.getTestString( 'User-' );
		const password = Util.getTestString();

		CreateAccountPage.createAccount( username, password );

		assert.strictEqual( CreateAccountPage.heading.getText(), `Welcome, ${username}!` );
	} );
} );

Please notice that createaccount.page.js has two major sections. In the first section are elements on the page (username()…). The second section contains actions (createAccount()…).

tests/selenium/pageobjects/createaccount.page.js

'use strict';

const Page = require( 'wdio-mediawiki/Page' );

class CreateAccountPage extends Page {
	get username() { return $( '#wpName2' ); }
	get password() { return $( '#wpPassword2' ); }
	get confirmPassword() { return $( '#wpRetype' ); }
	get create() { return $( '#wpCreateaccount' ); }
	get heading() { return $( '#firstHeading' ); }

	open() {
		super.openTitle( 'Special:CreateAccount' );
	}

	createAccount( username, password ) {
		this.open();
		this.username.setValue( username );
		this.password.setValue( password );
		this.confirmPassword.setValue( password );
		this.create.click();
	}
}

module.exports = new CreateAccountPage();

Without page object pattern

Let’s compare that to a solution without page object pattern. Code is available at 702341.

tests/selenium/specs/user.js

'use strict';

const assert = require( 'assert' );
const Util = require( 'wdio-mediawiki/Util' );

describe( 'User', function () {
	it( 'should be able to create account', function () {
		const username = Util.getTestString( 'User-' );
		const password = Util.getTestString();

		browser.url( '/wiki/Special:CreateAccount' );
		$( '#wpName2' ).setValue( username );
		$( '#wpPassword2' ).setValue( password );
		$( '#wpRetype' ).setValue( password );
		$( '#wpCreateaccount' ).click();

		assert.strictEqual( $( '#firstHeading' ).getText(), `Welcome, ${username}!` );
	} );
} );

Conclusion

With page object pattern, the framework is more complex, there are more files (two instead of one) and there is more overall code, but the tests are more readable and more maintainable.

Without page object pattern, the framework is simpler, there are less files (one instead of two) and there is less overall code, but the tests are less readable and less maintainable.

To keep this blog post short, I’ve selected a simple example. If the test suite creates accounts a lot, without page object pattern, code for creating account would have to be copy/pasted, leading to duplication and harder maintenance.

With the page object pattern, creating an account is just a call to CreateAccountPage.createAccount() function. If any of the elements for creating the account changes, only CreateAccountPage needs to be updated. If the logic of creating and account changes (for example, not requiring password re-entry) only CreateAccountPage.createAccount() needs to be updated.

More information

tags: code - commons - photo