Which one is better? Selenium with POM or Cypress without POM

Which one is better? Selenium with POM or Cypress without POM
Photo by Pietro Mattia / Unsplash

One of the best things about trying various frameworks and designs is that you have the possibility to directly see how it "feels" and have a better understanding of what suits best for your present or future projects.

I have done the Different Angle Challenge and I have created the exact same tests but in two different software testing frameworks. First I have done the tests in Cypress with Javascript and then I have wrote the same tests in Python with Selenium (including Page Object Model).

I have chosen only one of the tests to showcase in this article. The main flow of the test is buying a product. You start from home page, choose a product and perform all the necessary steps for a proper end to end scenario until checkout.

We begin with the setup. The necessary code written to have the setup part of a test

picture 1

On our left side we have Cypress test setup example, notice that we are importing the selectors and then destructuring them in our test file to later be used with Cypress builtin methods to find our elements and interact with them. There is no custom commands created by us, there is no helper functions or page methods. We use faker library just for fun here to generate random test data. We could have easily had just a json or simple object with the same test data.

As for the right side on Selenium with Python test setup example, we are importing our driver with which we are going to create objects that will be used to perform our actions. Since our test will go thru various pages, and we have POM in place, we will import each page object that corresponds with each page we will visit.

We now move to adding an item to cart and proceeding to shipping details

picture 2

On the left side in Javascript with Cypress we are using our selectors to find the elements and interact with them. So the selectors are organized in just one file in Cypress framework. See a part of the file as example below:

picture 3

We import and destructure this file to make our test easy readable.

In comparison to this, in our right side at picture 2 for Python with Selenium using Page Object Model our selectors are stored at each page object, and the interaction with the elements is mainly a logic that is kept outside of the test. Keeping them outside of the test you will have tests that are really clear to understand, but in order for you to have this in place you must write these action methods in your page objects. See an example below

picture 4

Notice also in picture 2 we use cy.intercept to listen on network calls. Meaning that our calls can be spied and we can listen to their responses from the server and assert values sent/returned at a backend level. These responses can also be mocked which gives us the super-power to mock data in real time. A feature that is not available in Selenium.

Cypress has built in feature to wait for an element to be interactable, Selenium gives you the ability to create explicit waits for elements.

When you have a flow that goes via multiple pages, in Selenium you will use different ways to simulate waiting for the new pages to load, before you interact with the items. You can use things like presence of an element in DOM as confirmation that the page is loaded. So for each page, you have most of the time different elements that you use (as "guards") to wait for them. You don't need to do this in Cypress, because it will recognize that you go to a different page and it will wait for the page to load by default before proceeding.

If you would have multiple options to choose for the same product. Like in our situation here, for example if you have product with color orange or color blue, and you want to test both colors, in POM you store its selectors in that page object and use a method that takes in a string with the name of the color, this makes the test readable. While in Cypress without POM you can use the built in methods and just give it the selector, and you have the test again as readable.

Now let us move forward with our test, and fill in the necessary info using test data for shipping details

picture 6

You may have situations where a loader comes into place that your web application will generate and animate for you while it updates the data on your page after you interact with it. So this spinner, this "loading..." animated element needs to be handled in a way. In Cypress you just have to give it its locator and use should('not.exist'). This magical assertion will wait for an interval of time for an element to dissappear from DOM. This, can be similar achieved with a method in Python with Selenium

picture 7

Moving forward with our test flow and now we have to verify that our details that we have inserted are correctly stored by our app and later displayed before checkout

picture 8

Both have the same simple logic where you extract the values you find inside of a component and then check one by one with the values in your test data. The small difference is that fetching the data from the component in Cypress can be done in test while still be easy to understand, while Selenium has to be a method created in our page object.

picture 9

Up until now, things are simple, however, this is just one small test.

What happens when things scale up ? when you have a lot of tests

Organize selectors for Cypress with Javascript

Lets say for example a situation where you have hundreds of tests, how do you handle selectors? I have already shown a good example on how to organize your selectors, in this article when it comes to Cypress.

Maintainability is very important here, for example if one web element is changed on our web-app, what do you do?

You group your selectors based on what suits best for your project. There is no single best approach, but more of a situational thing. Here are some examples that can be mixed.

  • You may have web elements that repeat throughout the whole app, you can call them base selectors, for example a cancel button, a save button
  • You may have elements that are part of a component that is reused in multiple pages, such as card with details of a product, this component can be on home page, can be part of promotional page, can be part of search page. Name the group by its name, in this example "product card".
  • You may have elements part of a feature, for example all options in a navigation bar, about, contact, home. you can call them navbar selectors
  • You may have dynamic selectors that take arguments. Your selectors are just part of an object, so feel free to create methods that do exactly what you need, and group that as you see fit in the same single file of selectors.
  • Almost anything you have seen in page objects, related to selectors can be handled in a file as an object.

See below an example of such file

picture 10

Organize selectors for Page Object Model design pattern in Selenium with Python

In this case, you have something which is highly effective and is called a page object, meaning that there is a file per each page of your web-app. That file will include its selectors under property of a class. So any change performed to an element you can update on that page object. But if the element can be found in multiple pages you have to either adapt each page objects selectors or have a separate page object that will be reused either by import or inheritance.

See below an example of a page object with its selectors and some methods

picture 11

Speaking of methods and scaling up in our framework.

For bigger projects how do you handle multiple steps that repeat?

For example, steps to login. Or a form that needs to be filled. Or any steps that are as a group performing an action that may repeat in various places in our app and/or tests.

For Selenium with Python and POM

You have the same helpful to work with files called page objects. These files contain all the necessary methods that you need to interact with your app. And you also have the base page file, which has methods and selectors that repeat on almost all pages.

Here is an example of a base page file

picture 12

Now the cool part about this file is that you can use inheritance principle from OOP and you can have other page objects inherit these methods. So now you avoid repeating code.

Also a benefit is that methods of the same class can be combined and create actions that may require multiple steps and in the end in your test you will only use a method that will be easy to understand (for example product.proceed_to_checkout()). These give you the power to have less code in tests and more complex logic in page objects.

For Cypress with Javascript

You have commands.js file where you can create your own custom commands that you can later use in your tests. Its similar to methods in page object but they do not need to be part of a class. They are just async functions. Even more you can have folders and files with different names, grouped as you see fit for your project. And in the end you just import all of them in commands.js . If at selectors you have one file where you group the selectors , here at methods you can have folders and files grouped as you see fit. And once you need one of these functions/commands to use in your test, you don't even need to handle the import because Cypress does that for you and all you have to write in test is cy.nameOfYourMagicCommand() .

These commands can also take arguments, for example if you want to pass in test data.

Because of so many useful and easy to read built-in command of Cypress, I rarely need custom commands, but I will soon write an article only about cypress commands so you can see as examples.

And this is it. Direct comparison between Cypress with Javascript and Selenium with Python (including Page Object Model as design). I think the best way to look at it is with an open mind, and try and experiment what works best for you and your project.

Hit the clap button if you found this useful. Or even buy me a coffee if you want to motivate me even more.