One thing I have to repeat again and again to everyone willing to liste, is that Cypress architecture is fundamentally different from Selenium or WebDriver. Cypress runs right inside the browser next to your web app. And Cypress is just JavaScript, like your web app (after maybe transpiling the source code). And if you know how to build a web application, you can change how Cypress looks and behaves because Cypress user interface is a web application itself. Because Cypress is also a Node application, from the tests you can jump to the operating system and do everything you might want. Let's see how it all comes together.
Cypress is just JavaScript
Cypress tests are written usually in JavaScript, CoffeeScript or TypeScript. Ultimately everything gets transpiled to JavaScript, and runs in the spec iframe in the browser. Modern browsers understand modern JavaScript (and missing features can be polyfilled for your tests), so your tests can take advantage of it.
Take ES6 proxies for example. We can use a proxy to intercept calls to the global cy
object and create convenient methods for finding elements by test id attribute. The following code snippet comes from cypress-get-it.
1 | global.cy = new Proxy(global.cy, { |
So when we call cy.visit('...')
from now on, it goes to the "real" cy.visit
. But if we call any method that starts with cy.get...
then we convert the method name like cy.getFooBarBaz("value")
to the data attribute selector and call the existing method cy.get('[foo-bar-baz="value"])
.
For page that looks like this:
1 | <div data-test-id="foo">foo</div> |
we can write method names that express actual data attributes and are easy to read
1 | cy.getDataTestId('foo').should('have.text', 'foo') |
Should you use cypress-get-it? Probably not. You better use small utility functions without any magic.
1 | const ti = s => `[data-test-id="${s}]` |
You can even overwrite cy.get
using custom Cypress command and invent your own syntax (in addition to the built-in jQuery selectors)
1 | // all selectors that start with "=" are going to become "data-test-id" selectors |
You have a choice, because it is just JavaScript.
Cypress runs in the browser
Cypress is controlling a real browser when it runs your tests. In the browser window, there are 2 iframes: app iframe and spec iframe. The app iframe is holding the web application. The spec iframe loads the bundled test code.
The spec iframe has no width or height, since it has no visual elements. Instead it sends all events that happen during a test to the top window where Cypress web application is drawing the Command Log. You can open DevTools and inspec the iframes yourself.
The most immediate result of this architecture besides being able to control application directly via app actions, is that the test code can modify the Cypress user interface. Literally, your spec code can even use JSX right away because Cypress UI is a React application and our browserify bundler transpiles JSX.
1 | const ReactDOM = require('react-dom') |
Once you realize that the spec JavaScript code can control the web application it is running in, the world is your oyster. For example, you will no longer need to wait for the Cypress dev team to add color theme support. You can just do it in user space.
Find the source code and two dark color themes in cypress-dark.
Cypress has Node backend
Cypress tests are running in the browser, but can call the backend code that runs on Node using cy.task
command. Anything you might want to do on the host system can be done from Node. Read and write files, work with a database, send smoke signals - anything Node can do, your tests can do too.
Let's put everything we just saw together. Running a single test, or skipping a test from the Cypress UI has been a common feature request. But do we need to change the core of the test runner to be able to do it? Can we do it ourselves (in a hacky way)? We want:
- when all tests have finished, put a button "Skip" next to each test name in the Command Log
- when a user clicks on "Skip" button, we can read the spec file and change it by adding
it.skip
for that test - save the changed file on disk, and Cypress will pick up changes, rerunning the tests
So let's do this. You can find the solution in cypress-skip-and-only-ui repo. Drawing buttons after all tests have finished is somewhat tricky because we have to compute the full test title from UI elements by walking through the DOM.
1 | after(() => { |
Find the rest of the code in src/support.tsx. The final result looks like this:
When you click on a button, like "skip" for example, it sends a message using cy.task
to the Node backend. The message includes the spec filename (provided by Cypress) and the full test title.
1 | const onClickSkip = () => { |
The Node handles the skipTests
command by loading the spec, creating an abstract syntax tree, walking it to find CallExpression
with Identifier = "it"
and then rewriting that particular node. Hint: use module called falafel for this, it is great. Find the code to do this in src/task-utils.ts. Note: I am transpiling TSX to plain React.createElement
using TypeScript before publishing cypress-skip-and-only-ui
to NPM, because Cypress bundler does NOT transpile node_modules
. The final result: Cypress UI with my buttons that modify the specs on the fly, and Cypress rerunning the tests on chance.
Beautiful.
Communicate yourself
You can create your own link between Cypress backend and the web app, for example by using web sockets. Then you can perform operations on the server, like watch files. Whenever a file changes, send a message to the Cypress web application via web socket, and the web app can react. This is the idea behind cypress-watch-and-reload - watch arbitrary source files and rerun tests on changes.
You know how it reruns the tests? By clicking on the UI "Rerun" button of course 😁
1 | window.top.document.querySelector('.reporter .restart').click() |
Conclusion
If you know how to make a web application using JavaScript, HTML and CSS, you can:
- Write good end-to-end tests using Cypress.io test runner.
- Customize how Cypress looks and behaves because Cypress is just a JavaScript code running inside a web app and on Node backend.
And if you are just beginning your web development journey, take a look at Cypress testing tutorials. They will help you get better at both testing and at web development in general.