Cypress Guidebook
What is Cypress?
Cypress is an end-to-end (E2E) testing tool, designed for automated testing of modern web applications. Built with JavaScript, Cypress provides a developer-friendly testing environment with powerful features to help QA teams test web applications easily and efficiently. Cypress provides various functions and methods for carrying out end-to-end tests on an application.
Cypress allows you to set up, write, run and debug tests. The types of tests that you can write are end-to-end tests, component tests, integration tests and unit tests.
To guide the way, the Cypress team has created the Real World App (RWA), a full-stack example application that demonstrates testing with Cypress in practical and realistic scenarios.
Why We Use Cypress?
Tentunya ada alasan dibalik pemilihan cypress sebagai tool testing untuk project web base Arkademi, berikut detail dari alasan-alasan tersebut.
The Initial Goal
The main purpose of using Cypress is to ensure the entire application system works correctly from the user's perspective. From the several types of tests that can be performed using Cypress, for now, testing is only conducted on the E2E test and unit test type.
Why E2E Testing?
- E2E validates user interaction with the application from start until finish, covering all system components and workflows that the user might go through
- The initiation of E2E: Aim to explore and improve the quality of Arkademi's products such as avoiding bugs to serve the best performance.
- User's perspective: it requires a good understanding of how users operate the application.
- E2E will be used as a reference and standard in maintaining the Arkademi Website which will change in the future both in terms of features and functionality of the Arkademi website.
In detail, here are the purposes of using Cypress:
- Core Functionality Validation: E2E testing ensures that the core features of the application function as expected. For example, login or registration works properly, navigation buttons go to the correct page, or the search feature returns appropriate results.
- Workflow Testing: E2E testing tests the workflow that a user should go through from start to finish. This includes testing navigation between pages, logging in and out of accounts, and performing other actions that represent everyday user experience.
- Compatibility and Responsiveness: E2E testing also includes ensuring that the application functions properly across devices and browsers. This helps ensure that users from different platforms have a uniform and satisfying experience.
- System Resilience: E2E testing can also test the system's resilience to extreme situations or unexpected conditions, such as a slow internet connection or server failure.
- Data Safety: E2E testing can ensure that sensitive user data is properly enclosed and can only be accessed by authorized users. This includes validation of access permission settings and authentication security.
- Reduces the Risk of Bugs: By identifying and fixing bugs before an application is launched, E2E testing helps reduce the risk of bugs and problems that may occur in a production environment.
- Improving Product Quality: Overall, E2E testing aims to improve product quality by ensuring that the application functions as users expect and meets established quality standards.
The Final Goal
Setting feature development standards, in this case providing test cases at the beginning of feature development by the prototype provided by the relevant team. This test case will ensure that the features built have specifications that match the initial design.
How to
Install Cypress
Install Cypress via npm:
- Make sure node.js is installed.
node -v - Go to your project.
cd your/project/path - Install Cypress on the project.
npm install cypress --save-dev - Run the Cypress.
npx cypress open
Cypress GUI
The following are the steps for running cypress:
- Make sure it is on the project that will be tested.
- Make sure Cypress is installed and the testing code is available.
- Run command
npx cypress open, if the project is run locally, run it simultaneously. - A cypress popup window appears.
- Select E2E Testing.
- Select the browser for testing.
tip
Suggestion: Using Chrome
- Select Specs on the left menu.
- Select the E2E spec to run.
Developed Cypress Test
Engineers already developed some tests using Cypress, which are detailed below:
Folder Structure
Here is Arkademi's Cypress e2e folder structure:
| # | Name | Description |
|---|---|---|
| 1 | Downloads | Save all files downloaded via the Cypress browser. |
| 2 | E2E | Contains the main code used for testing. |
| 3 | Fixture | Stores data for use in testing, the data used is generally in .json format. |
| 4 | Screenshots | Save screenshots when testing is run (currently test cases run on Cypress Cloud). |
| 5 | Support | contains helper codes that are useful for separating unit test code, which can be combined into one larger test case. |
Developed E2E Testing
The following are the cypress E2E tests that have currently been implemented:
| # | Name | Project Type | Testing Type |
|---|---|---|---|
| 1 | Homepage | Regular | unit |
| 2 | Coursepage JRC | Regular | unit |
| 3 | Checkout Page | Regular | unit |
| 4 | Course Status | Regular | E2E |
Write Cypress
Start to Write
The code below describes the test spec, beforeEach function will be run before each part of the test function it is executed.
export default describe("example spec", () => {
beforeEach(() => {
// do something before test
});
it("passes", () => {
// do something
});
});
Visiting a Page
To visit a page, add the URL to cy.visit()
describe("example spec", () => {
it("Visits a Page", () => {
cy.visit("https://example-page");
});
});
Ensure that the destination URL matches the URL code command.
describe('example spec', () => {
it('Visits a Page', () => {
cy.visit('https://example-page')
cy.url().should(‘contain’, 'example-page')
})
})
Queries
Queries itself refer to the methods and commands used to select and interact with elements on a web page. These queries are fundamental for writing tests that verify the behavior and functionality of web applications. Cypress provides a robust set of query commands that allow you to locate elements in various ways, enabling you to simulate user interactions and assert expected outcomes. Below are commonly used queries in Arkademi cypress:
as
Provide another name/alias. Used to store elements, values, and so on.
.as(aliasName)
.as(aliasName, options) // options: query / static
In general, aliases are used directly without requiring additional options. Such as storing data on intercepts (spy & stub network requests and responses).
cy.intercept(`**/course/${course_id}`).as("courseDetail");
The the code above stores the data obtained from the API response as courseDetail and can be used in testing using the .wrap() command.
contain
Search for elements containing the same text in the DOM. can easily find elements but the bad side.
In the code example below it will call a DOM element that contains the word About, however, if there is more than one element in the DOM that contains the same text then both elements will be selected. Here are tips for using it.
cy.get("button").contains("Kirim").should("exist").click();
With the code below, only elements with About text that are inside elements with the .nav class will be called.
cy.get("button").contains("Kirim").should("exist").click();
// or
cy.get(.nav).within(() => {
cy.get("button").contains("Kirim").should("exist").click();
})
get
Get one or more DOM elements by selector or alias. A frequently used command can be used by calling a class or attribute element.
cy.get(selector);
cy.get(alias);
cy.get(selector, options);
cy.get(alias, options);
Can retrieve DOM elements in 4 ways. Examples of use below.
// with tag html
cy.get("ul[class=isi-kelas-course-tab]");
// with tag html & class like
cy.get("ul[class^=isi-kelas-course]");
// with class
cy.get(".isi-kelas-course-tab");
// with custom data-test / data-cy / data-testid
cy.get("ul[data-cy=”cy-isi-kelas-course-tab”]");
In the code example above, it is recommended to use the .get() command to use the data-cy custom attribute so that the test code does not fail when there are changes to the HTML class or tag. Using similar classes is also recommended only if there are many of the same classes in one parent element, for example:
// code
<ul class=”tab-wrapper”>
<li class=”course-tab-active”>tab 1</li>
<li class=”course-tab”>tab 2</li>
</ul>
// cypress
cy.get("ul[class=tab-wrapper]").within(() => {
cy.wrap(@data).each((data, index) => {
cy.get(li[class^=”course-tab”]).eq(index).should(“exist”)
})
})
The the code above checks whether the course-tab elements in UL match the amount of data. By using the code above, all course-tab* elements can be selected.
find
Has almost the same function as .get() but has a different context, find is used more specifically after the .get() command is executed.
cy.get(".form").find("input");
eq
It is indexing if more than one element is selected in the parent element. Generally used to check elements by looping data from response, fixture, or state. The following is an example code for using the .eq() command:
cy.wrap(data?.globalWatchCourse?.watchCourseLampiran).each(
(lampiran: any, index: number) => {
cy.get("li[class='isi-lampiran']")
.eq(index)
.within(() => {
cy.get("div[class='judul']")
.get("h6[class='nama-file']")
.should("have.text", he.decode(lampiran?.namaFile));
cy.get("a[class='tbl-download']").click();
});
}
);
In the code above there is a loop for course data that calls the .get()command to call the isi-lampiran element but according to the index the element is located.
first
Taking the first element in several elements with the same attribute or class is an example of use.
// html
<ul>
<li class=”example-class”>example 1</li>
<li class=”example-class”>example 2</li>
<li class=”example-class”>example 3</li>
<li class=”example-class”>example 4</li>
</ul>
// cypress
cy.get(“li[class=’example-class’]”).fist()
From the code above, the only element selected is the first element, namely <li class="example-class">example 1</li>.
last
Taking the last element in several elements with the same attribute or class is an example of use.
// html
<ul>
<li class=”example-class”>example 1</li>
<li class=”example-class”>example 2</li>
<li class=”example-class”>example 3</li>
<li class=”example-class”>example 4</li>
</ul>
// cypress
cy.get(“li[class=’example-class’]”).fist()
From the code above, the only element selected is the first element, namely <li class="example-class">example 4</li>.
invoke
In Cypress, the invoke() command is used to call a function on a previously yielded subject. This command is particularly useful when need to interact with or assert properties, methods, or values that are not directly accessible through Cypress commands. Berikut ini adalah beberapa contoh penggunaan invoke:
a. Access properties of a DOM element or a JavaScript object.
// Get the value of the 'textContent' property of an element
cy.get("h1").invoke("text");
b. Interact with and manipulate DOM elements
// example change width to greater than 100
cy.get(".box")
.invoke("width")
.then((width) => {
expect(width).to.be.greaterThan(100);
});
// example for change tag <a> attribute
cy.get("a[data-cy='button-review']")
.scrollIntoView()
.invoke("removeAttr", "target")
.should("be.visible")
.click();
In the code above, example 1 manipulates the DOM to change the width to more than 100 and example 2 removes the target attribute in the <a> tag. Invoke can also be used for complex assertions for example:
cy.get("input").invoke("val").should("equal", "expected value");
url()
Get the current URL of the page that is currently active.
// syntax
cy.url()
cy.url(options)
// usage
cy.url() // Yields the current URL as a string
// usage example visiting a page and make sure url is valid
cy.visit('https://example-page')
cy.url().should(‘contain’, 'example-page')
window()
Get the window object of the page that is currently active. Can be used in code testing to retrieve window objects such as store, localStorage and so on.
cy.window();
cy.window(options); // options: log | timeout
// use for calling window store
cy.window()
.its("store")
.invoke("getState")
.then((state) => {
return state;
});
Assertions
Assertions in Cypress are used to verify that the application is behaving as expected. They are statements that check whether a condition is true or false at a specific point. If an assertion fails, Cypress will stop the test and provide feedback on what went wrong, making it easier to identify and fix issues. Cypress offers a range of built-in assertions, which can be used to validate various aspects of your web application, such as element visibility, content, state, and more. Below are some assertions examples:
Content
contain
Asserts that an element contains specific text.
cy.get("h1").should("contain", "Welcome");
have.text
Asserts that an element has exact text.
cy.get("h1").should("have.text", "Welcome to Cypress");
Visibility
be.visible
Asserts that an element is visible.
cy.get("button").should("be.visible");
not.be.visible
Asserts that an element is not visible.
cy.get("button").should("not.be.visible");
Existence
exist
Asserts that an element exists in the DOM
cy.get(".nav-item").should("exist");
not.exist
Asserts that an element does not exist in the DOM.
cy.get(".nav-item").should("not.exist");
Attribute
have.attr
Asserts that an element has a specific attribute with a specific value.
cy.get("input").should("have.attr", "type", "text");