End to end testing is one of the testing methodologies which is supposed to check whether if an application works as expected or not, by testing the so called user flow.
Well let's write couple of e2e tests for one of the React applications that I've made: csstox, a simple utility with which you can convert CSS snippets to React Native/JSS stylesheet objects with ease. Please read through the behind story here.
Getting started
As evident from the title we would be making use of Cypress as the testing framework. First up we need to have cypress installed as a devDependency.
yarn add -D cypress
The following command creates couple of files and directories as needed by Cypress.
./node_modules/.bin/cypress open
Alternatively use the shortcut npm bin
$(npm bin)/cypress open
After couple of tweaks the directory structure for the test setup looks like the one below:
tests
└── e2e
├── integration
│ ├── basic.spec.js
│ └── behavior.spec.js
└── screenshots
Next up we need to configure Cypress based on the changes made, and we've got cypress.json
file for this purpose.
Let's make Cypress aware that it has to search for the intended files within tests/e2e/integration
directory:
"integrationFolder": "tests/e2e/integration",
A final version would look like the one below:
// cypress.json
{
"baseUrl": "http://localhost:3000",
"integrationFolder": "tests/e2e/integration",
"screenshotsFolder": "tests/e2e/screenshots",
"supportFile": false,
"pluginsFile": false,
"video": false
}
Also, Cypress would require our application to be up and running prior to start executing the tests. Let's install a utility that would do this for us.
yarn add -D start-server-and-test
Let's go ahead and add the following scripts to package.json
"cy:run": "cypress open",
"test:e2e": "start-server-and-test :3000 cy:run"
With that we can launch the test setup with yarn run test:e2e
.
start-server-and-test by default looks for a start script, luckily this is the case for us. Or else we're required to provide the associated script name as the very first argument followed by the local server URL and test script. And we're all set to start writing tests for our app.
As you might have noticed from the directory structure above, there are two test suites:
- Basic Workflow - This one's kinda like smoke-tests, it ensures that things are ready to carry out further test cases.
- Behavior - It includes test cases that ensures the end to end behavior of the application.
Basic workflow
- First up we need to ensure if our app is up and running.
it("renders without crashing", () => {
cy.visit("/");
});
- We've a select box as part of the UI which defaults to the value - 'React Native'. Cypress provides various commands to interact with the DOM as a real user would do. Here, we need a utility that would pick up the select box and makes sure that it defaults to the value, 'React Native'.
it("expects to find the select box defaulting to React Native", () => {
cy.visit("/")
.get("[data-testid=selectbox]")
.should("have.value", "React Native");
});
As you might have noticed an attribute (data-testid
) selector is used instead of a class selector, you might be wondering why. There're couple of best practices listed in the Cypress docs website and you can find selecting elements to be one among them. CSS classes are subjected to change anytime causing the test case to fail which would not be the case with data
attributes. As expected we've refactored the respective component to have a data-testid
attribute.
Cypress comes up with a handful of assertions to choose from which are made available from assertion libraries such as chai
, sinon
etc. One can create an assertion with should()
, and now we've have a better picture.
Behavior
Hurray, we've just finished writing test cases for the first test suite. And now we're off to write tests that describe the behavior of the app in detail.
- We've two
textarea
elements which serves different purpose. The one on the left is supposed to allow the user paste a CSS snippet while the other one should be displaying the React Native/JSS equivalent of the same. This calls for the need to type some input CSS snippet to the respectivetextarea
element. Luckily we've atype()
command as provided by Cypress for the purpose.
it("is possible to enter text to the `textarea` intended to receive input CSS snippet", () => {
const cssSnippet = "padding: 10px;";
cy.visit("/")
.get("[data-testid=input]")
.type(cssSnippet)
.should("have.value", cssSnippet);
});
- As said before both the
textarea
elements perform different roles. The one on the right is supposed to display the React Native/JSS equivalent which should be made not editable by the user. How're we gonna write a test case for this scenario? Well, it's pretty simple. Just make sure that the respectivetextarea
element has got areadonly
property.
it("expects to find readonly attribute associated with the textarea intended to display the result", () => {
cy.visit("/").get("[data-testid=output]").should("have.attr", "readonly");
});
- And now we need to write a test case to ensure if the application serves it's purpose, i,e if an input CSS snippet is being converted to the respective equivalent.
it("converts an input CSS snippet to the React Native equivalent", () => {
const inputCSSRule = "transform: translate(10px, 5px) scale(5);";
const result = {
transform: [{ scale: 5 }, { translateY: 5 }, { translateX: 10 }],
};
cy.visit("/")
.get("[data-testid=input]")
.type(inputCSSRule)
.get("[data-testid=output]")
.should("have.value", JSON.stringify(result, null, 2));
});
- Here comes the JSS counterpart presenting before us a new challenge. The select box defaults to the value - 'React Native', we're required to change the value to JSS and Cypress comes to the rescue with
select()
.
it("converts an input CSS snippet to the JSS equivalent", () => {
const inputCSSRule = "margin: 5px 7px 2px;";
const result = {
margin: "5px 7px 2px",
};
cy.visit("/")
.get("[data-testid=selectbox]")
.select("JSS")
.get("[data-testid=input]")
.type(inputCSSRule)
.get("[data-testid=output]")
.should("have.value", JSON.stringify(result, null, 2));
});
- We've validations in place to ensure submitting an invalid CSS rule results in an appropriate warning being displayed in the output
textarea
element. Well, let's write a test case for it.
it("shows an error message for invalid CSS snippet", () => {
const inputCSSRule = "margin: 5";
const result = `Error translating CSS`;
cy.visit("/")
.get("[data-testid=input")
.type(inputCSSRule)
.get("[data-testid=output]")
.should((el) => {
expect(el).to.contain(result);
});
});
- If the input
textarea
element is left blank we've a placeholder in place and the equivalent version is displayed on the outputtextarea
element.
it("generates the React Native equivalent of default CSS rule available as placeholder", () => {
const result = {
fontSize: 18,
lineHeight: 24,
color: "red",
};
cy.visit("/")
.get("[data-testid=output]")
.should((el) => {
expect(el).to.contain.text(JSON.stringify(result, null, 2));
});
});
- And the JSS counterpart.
it("generates the JSS equivalent of default CSS rule available as placeholder", () => {
const result = {
fontSize: "18px",
lineHeight: "24px",
color: "red",
};
cy.visit("/")
.get("[data-testid=selectbox]")
.select("JSS")
.get("[data-testid=output]")
.should((el) => {
expect(el).to.contain.text(JSON.stringify(result, null, 2));
});
});
And that's pretty much it. We have gone through only a few things that Cypress offers, please get to know more from the official docs. Thanks for reading through.
If you wish to catch up with my work, follow me on twitter.
Top comments (7)
Cool.
How can I run those tests on Internet Explorer, Safari and mobile browsers?
As of now Cypress has support for chrome-family browsers and beta support for Firefox browsers. Get to know more here. And regarding mobile browsers, Cypress will never be able to run on a native mobile app. But one can test the responsiveness of a website with the
cypress.viewport()
command.Thank you for your response.
Help me understand, what's the point of having e2e tests if I can't run them on Internet Explorer, Safari and mobile browsers?
Not all users are using Chrome and Firefox.
Even Selenium works on more browsers.
We're currently using Endtest, since it supports all major browsers.
Choosing a testing framework depends on one's use case. Hopefully Cypress would be supporting more browsers in the near future.
github.com/cypress-io/cypress/issu...
If you really want to do cross browser testing, browserstack is a good alternative. browserstack.com/automate
Sorry, but BrowserStack and Sauce Labs are just services that offer VMs with browsers.
You still have to write your own Selenium framework, you still have to store your tests in a repository, you still have to do all the work.
We use Endtest because it offers an easy way to create and execute tests and a cross-browser cloud infrastructure.
Endtest looks good. I never had a chance to try it. I had used browserstack for some of my projects. They have an open-source grants program that lets you use browerstack for free for your open source project.