#browser-automation
#cross-browser-testing
#node-js
## Purpose
[**cypress**](https://www.npmjs.com/package/cypress) is a tool specifically made for browser **test** automation. If compared with a common Selenium-using project, Cypress covers the parts that are needed to drive the browser (WebDriver), that are running the actual test code (e.g. Mocha) and that are mocking requests or stubbing functions (e.g. Sinon).
One of its selling proposition is that it is running right next to the application in the browser. This allows to inspect the internals of an app to a great extent.
## Documentation entry points
- [Cypress documentation](https://docs.cypress.io/)
- [Getting started](https://docs.cypress.io/guides/getting-started/installing-cypress)
- [Writing your first test](https://docs.cypress.io/guides/getting-started/writing-your-first-test)
- [Hooks](https://docs.cypress.io/guides/core-concepts/writing-and-organizing-tests#Hooks)
- [Assertion styles](https://docs.cypress.io/guides/core-concepts/writing-and-organizing-tests#Assertion-Styles)
- [Common assertions](https://docs.cypress.io/guides/references/assertions#Common-Assertions)
- How to tamper with [intercepted requests](https://docs.cypress.io/api/commands/intercept#Intercepted-requests)
- [Debugging guide](https://docs.cypress.io/guides/guides/debugging) in the Cypress docs
- [Retry-ability](https://docs.cypress.io/guides/core-concepts/retry-ability) explained in the Cypress docs
- [Command line](https://docs.cypress.io/guides/guides/command-line#Installation)
- [GitHub Actions](https://docs.cypress.io/guides/continuous-integration/github-actions)
- [Configuration](https://docs.cypress.io/guides/references/configuration)
- [Keep passwords secret in E2E tests](https://glebbahmutov.com/blog/keep-passwords-secret-in-e2e-tests/)
- [cypress-io/github-action](https://github.com/marketplace/actions/cypress-io)
## Cypress architecture
The Cypress architecture resembles that of [[+Selenium architecture|Selenium]] at version 1 (which dates back to ~2004). I tried to sketch out the architecture of Cypress, and arrived at this (open it in a new tab for best readability):
![[cypress-architecture.png]]
-- [Lucidchart edit view](https://lucid.app/lucidchart/36502e6b-8056-40eb-8322-672c1e9afa76/edit?page=HWEp-vi-RSFO#)
## Cross-browser support
- When running tests locally with `cypress run`, a headless Electron instance is used by default
- Other than that, Chrome, Chromium, Firefox and Edge can be controlled (headless or headful mode)
- No mobile device support
## Learnings and observations
- The onboarding is awesome, with nice code examples offered on the first run of `npx cypress open`
- Nice developer experience with [`cypress run`](https://docs.cypress.io/guides/guides/command-line#cypress-open): Tests are re-run when test files are changed/saved
- [No support for `async`/`await`](https://docs.cypress.io/faq/questions/using-cypress-faq#Can-I-use-the-new-ES7-async-await-syntax)
- I like to [switch video recordings off](https://docs.cypress.io/guides/references/configuration#Videos) when running tests via `cypress run`
- Geolocation emulation does not work out of the box with Cypress, because Cypress cannot handle/suppress the geolocation permission dialog. [cypress-io/cypress#2671](https://github.com/cypress-io/cypress/issues/2671) describes possible workarounds. The interesting thing is that geolocation emulation works when running tests via `cypress open`, but it does not when running tests via `cypress run`
- Not sure whether I like Cypress's implicit assertions. This makes test code less "expressive", because it lacks the documentation of the intent behind the test
## Things to keep in mind
- "the structure of your test should flow query -> query -> command or assertion(s). It's best practice not to chain anything after an action command" (https://docs.cypress.io/guides/end-to-end-testing/writing-your-first-end-to-end-test)
- Do not assign Cypress command results to variables. The reason: Cypress commands **do not return** their subjects, instead **they yield** their subjects. Cypress executes all commands asynchronously. This means: It queues up all commands up for execution. The actual execution happens only when the test function exits. If a test requires to interact with the result of a command, it needs to to that via `.then()` (which, confusingly, is a Cypress-specific method and not the generic JavaScript `.then()`; however, it works similarly)
- [Commands are Asynchronous](https://docs.cypress.io/guides/core-concepts/introduction-to-cypress#Commands-Are-Asynchronous)
- [The Cypress Command Queue](https://docs.cypress.io/guides/core-concepts/introduction-to-cypress#The-Cypress-Command-Queue)
- [Return Values](https://docs.cypress.io/guides/core-concepts/variables-and-aliases#Return-Values)
## "Flake-free"?
In [Why Cypress? > Features](https://docs.cypress.io/guides/overview/why-cypress#Features), the documentation says:
> **Consistent Results:** Our architecture doesn’t use Selenium or WebDriver. Say hello to fast, consistent and reliable tests that are flake-free.
So, Cypress tests are flake-free? Why then is there a [flaky test management](https://docs.cypress.io/guides/dashboard/flaky-test-management) feature?
## Cypress Dashboard
- Running tests in parallel is the selling proposition for the dashboard. It it really that hard to achieve with a DIY solution?
- „Load balancer“: test files are picked up and distributed across several workers for parallelization
- Metadata of test runs is collected and presented (screenshots and videos)
- Not clear to me how [[Sauce Labs]]'s „Sauce Labs Test Runner“ hurts Cypress's business model
## Repository for experiments
[https://github.com/systemboogie/explore-cypress](https://github.com/systemboogie/explore-cypress)
## Things to consider
- Add the [Cypress ESLint plugin](https://github.com/cypress-io/eslint-plugin-cypress) to your project to enforce rules in the test code.
## Cheat sheet
```js
// Find `a` element inside div
cy.get('div#some-id').find('a');
// Get the first element from an array of elements
cy.get('li').eq(0);
// Aliases
// Create an alias...
cy.get('table').find('tr').as('rows');
// ... and access it later on, even across scopes
cy.get('@rows');
// ... or access it via Mocha's context
this.rows
// Explicit waits
// First, set up an alias for the thing to wait upon...
cy.request('POST', '/users').as('signup');
// ... and wait for it at a later point, even across scopes
cy.wait('@signup');
// Debugging
// Print message to Cypress command log
cy.log('foo');
// Print message to browser console
console.log('foo');
// Assertion about API response
cy.request("POST", "http://localhost:8888/users/admin", { name: "Jane" }).then(
(response) => {
expect(response.body).to.have.property("name", "Jane")
}
)
```
## Useful resources
- https://github.com/cypress-io/cypress-documentation/issues/872
- https://docs.cypress.io/guides/guides/launching-browsers.html
- https://www.testim.io/blog/puppeteer-selenium-playwright-cypress-how-to-choose/
- https://glebbahmutov.com/blog/cypress-is/
- https://www.cypress.io/dashboard/
- https://docs.cypress.io/guides/guides/parallelization.html