Test refactoring - App Actions vs Page Object Model
As some of you already have pointed in comments the quality of code tests is
low. So today I will try to improve it by doing process of refactoring. First
what comes in my mind in this situation is usage of Page Object Model pattern,
to make our tests more logical and to highlight business layer. However in
documentation I’ve found such entry: Stop using Page Objects and Start using App
It suggests to try tackle it in different way - so I will use both solutions
As I am curious of this new approach, I will begin with App Actions. I am creating
new file named:
Now I copy content of LoginTests.spec.js. It seems that authors have extended
API of Cypress tool:
There is dedicated place for creating this type of functions - the file in the directory:
I am adding here a part of code, which I use in process of login by form on website:
Then I switch to modify file LoginTests.AppActions.spec changing way of
handling event of user login in test Successfull login:
I am also wrapping to separate functions the API queries of others actions cy.request:
My test presents much simpler:
I follow this pattern during refactor and I update next cases. It turns out
that I can use in several places function cy.login and cy.createNewUserAPI.
During my work I figure out that I need another function: cy.deleteUser:
Furthermore use of this approach allows for chaining
of functions. A practical usage of App Actions causes that test Successfull login
looks much cleaner.
I think we can also hide technical part of test assertion putting it in
I can do same for validation of displayed error message and validation of URL:
After applying the above practices we end with tests as below:
It turns out that Empty fields have finished with failure because of error:
CypressError: cy.type() cannot accept an empty String. You need to actually
type something. It means that sending string with value '’ to function
cy.type is not allowed Personally I prefer Selenium approach, where it is not
a problem. However it seems that authors of Cypress have different opinion and
it is not a defect: GitHub Issue - .type() will not accept an empty
string. I prefer freely
parametrize function which I’ve created, so I modify code of function
cy.login to be able skip interaction with login and password fields, allowing
passing empty string to function:
After above changes it turns out that tests are more readable, but quite a few
bad practices was moved to file commands.js. I will not improve most of them at
moment. I am going for now sort logically files and separate logical blocks. I
am creating new directory named login in path cypress/support/. In this
directory I am creating file named loginCommands.js and copy from file
commands.js implementation of function cy.login. Subsequently I am
creating another file loginAssertionsCommands.js and I am copy implementation
of functions cy.shouldBeLoggedIn, cy.shouldErrorMessageBeValid(arg) and
cy.shouldErrorMessagesBeValid(arg, arg). I do similar operation for
functions cy.createNewUserAPI, cy.deleteUserAPI and cy.shouldUrlContain.
Finally to have access to those functions, in newly created files I need to
modify file index.js in directory cypress/support/ and add imports
After those changes it will be easier find function, because in my opinion they
are much better placed. Benefits of this refactor will be visible during
implementing a new test cases. Question is: why does it work? Those functions
should not be visible in tests. This mechanism works because all dependencies
from file: cypress/support/index.js are automatically loaded before each
specification by Cypress.
App Actions - commit
You can find all changes in code about App Actions
Do you like my blog?
Subscribe to mail list, to be first which will be notified about new posts and informations:
I will add you to mailing list. You can unsubscribe at any time (one click).
Page Object Model
Applying Page Object Model patter is also possible. I am creating a new file:
Then I copy to it content of file LoginTests.spec.js. Next step is to create a
new class which will represent object of login page. I create it here: cypress/pageobjects/LoginPage.js
and I prototype usage of class in test code as below:
I notice also that I will need HomePage class which will represent home page of
application. I am creating it in this place cypress/pageobjects/HomePage.js
and I proceed with implementation of LoginPage.login function.
After test execution I received error: ReferenceError: LoginPage is not defined
which remind me to import newly created class. I am doing it this manner:
I am continuing prototyping the assertion
Now I know how my interface should look like (for now it only a class, but I
already think about it as future interface), so I move to implementation:
Then I perform same operation for API requests. I am creating a new class
which responsibility will be manage state of users. I am prototyping the test:
Once I know what I want to achieve, I go to the implementation of the class:
cypress / requests / User.js:
Now that I have my first test that looks as expected, I continue refactoring the next scenarios.
It turns out that for the test: Incorrect password the created LoginPage.login (arg, arg) function is not
sufficient because it returns an object of the HomePage type, and we know that after
incorrect logging in, we should stay on PageObject of the LoginPage type.
Therefore, a change of this class is required.
LoginPage class after refactoringwill look like this:
After refactoring all the tests using the pattern, I get the following code:
I was able to successfully implement the Page Object Model pattern. Same,
for App Actions, there is still a lot of room for improvement, but for this one
I focused on the basic implementation of the pattern assumptions.
Page Object Model - commit
You can find all the changes of the Page Object Model here.
What I liked was the possibility of deep integration with the tool, which
doesn’t require, at least at this stage of test implementation, to create
another one framework, because we have it ready and we manage it freely,
extending it functions. Moreover, tests written with this approach are very
legible. I expected however something more, it seems to me that with the
development of test the managing these functions will become cumbersome and too
less organized. At the same time I think that adding more scenarios will allow
me to analyze deeper the problem and it will force me to discover the potential
of it approach. I am glad that we have a choice, we can easily implement the
Page Object pattern and stick to old habits, if we care about it.
You can find all the changes in my repo on the branch, here: