QA Tester Beginner Series: A beginners guide to JavaScript Automation with Cypress (PART II)- Locators And UI Web Elements in Cypress
Ok guys, not even going to lie this one took a lot out of me but i'm glad because making useful content for you guys is going to be worth it. I'm so passionate about this beginners series because everyone starts out as one and that feeling lingers even after you've been doing this for a while. I'm also aware that the present market can be unforgiving is not as beginner-friendly as we all would like it to be. It's OK. Keep your chin up; you'll get there. Over the course of writing/researching this post, I've been fortunate to re-learn a couple of things and i also have some new ideas with regards to content i think the community would appreciate going forward; so that's also a bonus. This is going to be a pretty long read but i promise if you stick with it, you'll get a lot out of it. I spent some time thinking of the right way to structure this segment of the Cypress series as there were multiple related concepts that i wanted to cover within this segment. This structure might not be ideal for everyone but please bear with me. We have a lot to get to so before i begin to ramble, let us begin.
Locator Elements in Cypress
Just like with Selenium, we can locate web elements with Cypress. But unlike selenium, Cypress only supports CSS selectors as locators. To locate elements using css selectors, we can use the get() method in the form "cy.get(selector)". Where the selector could be .class, #id, [attribute=value], .class[attribute=value].
Using locators
Whenever you parse a locator as as selector, you can parse the selector in double quote(" ") or single quote (' '). It is advisable to pick one and stick with it for the purpose of consistency.
.class: To find this selector, right click on the element that you want to inspect, click "inspect". The "class" is always the value of the class attribute. And to use this as a locator, it should be preceded with a "." symbol.
#id: To find this selector, right click on the element that you want to inspect, click "inspect". The "id" is always the value of the id attribute. And to use this as a locator, it should be preceded with a "#".
[attribute=value]: To find this selector, right click on the element that you want to inspect, click "inspect". To use this as a locator, a square bracket containing an attribute like "className" and the "value" of that element's class attribute.
.class[attribute=value]: Sometimes, certain locator types are repeated across multiple elements, it is difficult to use them because they are not unique. You can use a combination of the ".class" and [attribute=value] to create unique locators. You can find a complete guide to css selectors in the link below:
w3schools.com/cssref/css_selectors.asp.
If you don't want to have to write your css selector manually, you can automatically capture it for a particular element, there are a couple of ways you can do this;
1) Right Click on Element->click on inspect->copy->copy selector. I would recommend if you can't be bothered to mess around. However, if you do choose this method, you do not need to preceded it with a "." or a "#" as you would for class and "id" respectively.
2) Using a chrome extension like Chropath or SelectorsHub which automatically allows you generate and validate the css selector of a web element.
3)We can use the Cypress Test Runner window.
a) Go back to the VSCode terminal.
b) Start the Cypress Test Runner using the command: "./node_modules/.bin/cypress open"
c) Run the sample test case we created from our previous example on Test Runner.
d) After the web page is navigated to, navigate to the icon that loosely resembles a snipers scope with the label that says "open selectors playground".
e) Click on the web element whose selector you want to view.
f) The css selector is automatically generated in the toolbar below with cy.get().
Implementation
To implement this, we are going to use a couple of demo website that allows us to practice on a majority of these UI elements. Now i understand that this might be inconvenient for some of you but due to time constraints, it was challenging to find a single demo site that adequately covered all of the UI elements we were keen to explore.
NOTE: When you create a test file for this test case, name the file "testcasename.spec.js". This is the naming convention that we will adhere to for our testcases.
Test cases
1) Launch Browser & navigate to URL: demo.automationtesting.in/Register.html; Verify the URL.
2) SELECT CHECK-BOXES.
3) UNCHECK CHECK-BOXES.
4) SELECT MULTIPLE CHECK-BOXES IN A SINGLE STATEMENT.
5) SELECT DROP-DOWN ELEMENT BY USING ID.
Now if you do all of the above right, this code should flawlessly execute without the need for any waits which if you have used selenium would be unthinkable. Not to mention, cypress also automatically re-runs your tests after it detects saved changes to your code (provided you have test runner & cypress open) which is not a bad feature to contend with. However, i would recommend using some type of wait to observe how the pages load and view how elements are interacted with. Cypress can be flaky at times and all your the steps in your test case can appear to be running concurrently on your test runner even if they aren't; which isn't helpful if your keen to monitor these tests on the UI level. Not to mention if you're interested in how a user actually uses a piece of software, the user flow is somewhat unrealistic. It's ideal for non-functional testing but not necessarily for functional testing.
Handling UI Elements with Cypress
For this, we shall be using the classic phptravels web-page which can be found at "phptravels.net" which is a good site we'll come back to when we want to explore end-to-end testing. While there, we shall be looking to verify UI elements such as Input boxes, check boxes and Radio buttons, drop-downs etc. This is done in a variety of ways and a good example of this is checking to verify that the element "should" be "visible", and "enabled" before it can be interacted with; as illustrated in the snippet below for a Radio Button UI element:
Radio Button
cy.get("#one-way").should('be.visible').should('be.enabled').should('be.checked')
NOTE: If for whatever reason you're on a web-page and unable to perform actions such as a click action on the web-elements that you want, and keep getting an error message saying that the action could not be performed because the element you're trying to interact with is being covered by another element. This error could be as a result of a variety of reasons which i won't get into and it can get quite frustrating. Without going into too much detail, a quick way to fix this error would be to use the "click({force:true})" method on the element you want to click on. It is advised to only do this when you have validated the selector for that element. An example of this is illustrated in the snippet below:
cy.get(".form-control[placeholder=Password]")
.should('be.visible')
.should('be.enabled')
.click({force:true})
.type("testuser")
Check-Boxes
Say we wanted to do the same for a list of check-boxes, that we want to be "checked" on the demo-website: "demo.automationtesting.in/Register.html". We can do that with the snippet below.
cy.get('#checkbox1')
.check()
.should('be.checked')
.and('have.value','Cricket')
cy.get('#checkbox2')
.check()
.should('be.checked')
.and('have.value','Movies')
cy.get('#checkbox3')
.check()
.should('be.checked')
.and('have.value','Hockey')
The .check() method is the action method that performs the "check" action on the checkbox.
The .should('be.checked') method verifies that the action took place.
The .and('have.value', 'Cricket') method indicates further the value of the text that accompanies #checkbox1.
The same can be done if we want to "uncheck" them
cy.get('#checkbox1')
.uncheck()
.should('not.be.checked')
cy.get('#checkbox2')
.uncheck()
.should('not.be.checked')
cy.get('#checkbox3')
.uncheck()
.should('not.be.checked')
The .uncheck() method is the action method that performs the "uncheck" action on the checkbox.
The .should('not.be.checked') method verifies the action.
The .and('have.value','Apples') method indicates the value of text for the #checkbox1 element.
We can also select multiple check-boxes within a single statement. To do that, we need to use an attribute that all three of these check-boxes have in common. For the above illustration, that attribute will obviously be ('input[type=checkbox]'). Since this selector is pointing to multiple elements, we can define an array that will hold the individual values of all the elements. This gives us something similar to the snippet below which will select two out of the three check boxes;
cy.get('input[type=checkbox]')
.check(['Cricket', 'Movies'])
Drop-downs
How drop-downs are handled generally depends on the type of drop-down. For Static drop-downs, they have the select tag when you inspect the element and by using the ".select()" method, you can select an element from the drop-down. The snippet below illustrates as such.
it('handles static drop-downs'), function()
{
//SELECT DROP-DOWN ELEMENT BY USING ID
cy.get('#Skills').select('Select Skills').should('have.value','Select Skills')
}
For a multi-select drop-down that allows you to select multiple items at once, find a suitable selector for the drop-down, perform a click action with the .click() method and it will give you all the possible options you can select from. However, we cannot use the .select() method so we must capture all the options in the drop-down by using a common attribute. For this demo site, the attibute we use is the ".className" attribute which all the elements of the drop-down have in common when you collapse the "list item(li)" tag; then the ".contains()" method to define the content of the item to be selected and finally the ".click()" method to click on the item as illustrated by the snippet below.
it('handles multi-select drop-down/list-box'), function()
{
SELECT DROP-DOWN ELEMENT BY USING ID
cy.get('#msdd').click()
cy.get('.ui-corner-all').contains('English').click()
cy.get('.ui-corner-all').contains('German').click()
}
For drop-downs where you are required to perform multiple actions such as clicking on the drop-down, entering some text and then select/clicking an option corresponding to the text you entered by hitting "ENTER". Elements like these don't usually have a "select" tag; they usually have a span tag. As illustrated by the snippet below
it('handles searchable drop-down'), function()
{
//SELECT DROP-DOWN ELEMENT BY USING ID
cy.get('[role=combobox]').click()
cy.get('.select2-search__field').type('Den')
cy.get('.select2-search__field').type('{enter}')
}
Handling Alerts in Cypress
Cypress automatically handles alert windows without requiring you to write any script. However, if our aim is to validate a piece of text on the alert, then we can use Cypress to do that as illustrated by the snippet below. To demonstrate how to handle alerts in Cypress, we'll be using the "mail.rediff.com/cgi-bin/login.cgi" demo website which requires us to handle an alert when you click on the "go" button before we can proceed to login. It is important to note that the way you handle an alert is highly dependent on the type of alert you're trying to handle. For that reason lets take a brief look at what types of JavaScript alerts there are and how you can identify them.
Alert Box: Used to inform the user about an event. Usually only has one button named 'OK'.
cy.visit("mail.rediff.com/cgi-bin/login.cgi")
cy.get('input[type=submit]').click()
cy.on('window:alert',(str) =>
{
expect(str).to.equal('Please enter a valid user name')
})
'window:alert' ; captures the alert when it pops on screen before it is automatically handled by Cypress and saves it in a variable.
(str) ; the name of the variable where the text o the alert is saved.
=> ; JavaScript operator used to define a JavaScript function.
expect(str).to.equal('Please enter a valid user name') ; JavaScript function that expects the value of the str variable to equal the text "Please enter a valid user name". This piece of code is used to validate the alert message.
Confirmation Box: Used to provide the user with a choice about an event. Usually has two buttons named 'OK' and 'Cancel' and returns the value of 'true' or 'false' depending on which of the buttons is clicked.
cy.visit("https://testautomationpractice.blogspot.com/")
cy.wait(9000)
cy.get('#HTML9 > div.widget-content > button').click()
cy.on('window:confirm',(str2) =>
{
expect(str2).to.equal('Press a button!')
})
Alerts are a windows event type and there other types of events as well. A complete list and description of cypress events can be found in the Cypress documentation by following the link below; docs.cypress.io/api/events/catalog-of-events
Page Navigation in Cypress
You can navigate forwards and backwards from your current page by using the "cy.go()" command. To utilize this command, there are two types of parameters you can parse into the "go()" method. The first of these is the use of keywords such as "forward" and "backwards" to navigate your browser forward and backwards respectively.
cy.go('forward')
cy.go('back')
The second type of parameter is the use of the values (1) & (-1); where 1 is used to navigate forward and -1 does the opposite.
cy.go(1)
cy.go(-1)
BONUS: The command cy.reload() is used to refresh a web page.
//Refresh Page
cy.reload()
Web/HTML Tables in Cypress
A Web Table is a Web element used when information has to be displayed in a tabular format via rows and columns; and the information represented within them can be static or dynamic. A web table can displayed on a web-page as product specifications for an eCommerce platform. We will be using the demo site: "testautomationpractice.blogspot.com" to demonstrate this and while it is not exactly an eCommerce site, it gets the point across nicely as there are certain actions that can be performed on Web Tables which we can try out. There are certain actions that can be performed on Web Tables. These include but are not limited to;
1) Checking the presence of a value anywhere in the table.
2) Checking the presence of a value in specific row & column.
3) Checking the presence of a value based on a condition by iterating the rows in a table.
The first two scenarios are fairly straightforward and can be accomplished by the snippets below.
//1) CHECK THE PRESENCE OF A VALUE ANYWHERE IN A TABLE.
cy.get('table[name=BookTable]').contains('Learn Selenium').should('be.visible')
//2) VERIFY THE PRESENCE OF A VALUE IN A SPECIFIC ROW & COLUMN
cy.get('table[name=BookTable] > tbody > tr:nth-child(2) > td:nth-child(3)').contains('Selenium').should('be.visible')
The third scenario requires Checking the presence of a value based on a condition by iterating the rows in a table. To implement this, we need to involve some programming logic that allows us to iterate through items successfully.
To do this in Cypress, we use the .each() method which requires 3 parameters in the format.
.each(($e,index,$list)) => {}
The parameters/arguments are "value", "index" and "collection" respectively.
value($e): This is a variable that holds every object/value on the column we are iterating through at least once to verify if it is the value we are looking for. It holds these values one at a time unlike the list variable that holds multiple values at once.
index: This is the argument that counts over how many times the iteration has occurred. Think of the index as the only argument both of these (i.e $e and $list) values will share when they are equal. i.e when an item from $e equals an item on $list, they have the same index value.
list/collection($list): This is another variable that holds/saves the value from multiple items (in a single column) that are going to be iterated through. In this context, it is going to hold the "list" of items/objects assigned to the table element our css selector is pointing to. It holds these items all at once unlike the value variable that holds multiple items one at a time.
=>{}: In the function that follows the ".each()" method, we first define a variable using const or var keywords. We'll be using the const keyword. Followed by what you want to call the variable. Lets call it authName for the name of the author. And then we assign it to $e variable argument using the "=" operator. But then we also add the ".text()" method to the $e variable because our aim is to get the text value attached to it so we can compare it to the name of our author of choice. To carry out the comparison we introduce an if conditional statement.
If the condition in this statement is met, then we must verify that the BookName matches the same index value assigned to the author(i.e the author of our choice is on the same row as the BookName). In other words, the index value for the BookName column is equal to the index value for our author of choice. This is then followed by a ".then()" method which contains another function which contains an argument called "bname" whose ".text()" property will be saved to another variable called bookName. We then use the expect(bookName).to.equal('Master In Java'). which corresponds to the aforementioned index for our author of choice.
So in essence, we are going to consistently compare the values of $e & $list until we get a match. When we do get a match, the next step will be to get the BookName assigned to our matching values of $e and $list. This BookName is then saved to another variable which we will create and then we will verify the value of this variable to see if it matches our expectation.
The snippet below should illustrate that
//3) CHECKING THE PRESENCE OF A VALUE BASED ON A CONDITION BY ITERATING THE ROWS IN A TABLE
// SCENARIO: WE WANT TO VERIFY THE BOOK NAME "Master In Java" WHOSE AUTHOR IS Amod
//step 1: create a css selector that matches with all the author names column
cy.get('table[name=BookTable] > tbody > tr td:nth-child(2)').each(($e1, index, $list) => {
const authName = $e1.text()
if(authName.includes("Amod"))
{
cy.get('table[name=BookTable] > tbody > tr td:nth-child(1)').eq(index).then(function(bname)
{
const bookName = bname.text()
expect(bookName).to.equal("Master In Java")
})
}
})
NOTE: When committing to git on VSCode, git asks you for a commit message on a text editor like "Sublime Text", after you type in your commit message and save it, you cannot proceed until the file with the commit message is closed. Do not close the Sublime Text editor. Instead, navigate to file, click on "close file" and the VSCode terminal should acknowledge your commit message.
NOTE: If attempting to run your tests in cypress, you come across a uncaught exception (referenceerror):angular is not defined, this error is more likely to be as a result of the application code and not your test code or Cypress itself. When Cypress detects uncaught errors originating from your application it will automatically fail the current test. This behavior is configurable, and you can choose to turn this off by listening to the 'uncaught:exception' event.
That's a wrap for part 2 our multi-part cypress series. Hope you've enjoyed. If you found this post helpful, feel free to follow me on Twitter @4EVER1CONIC where i tweet some more stuff you might find useful. Part 3 of the series returns in early September but until then; remain calm, stay grateful and keep coding.