Frappe uses Cypress for end-to-end browser tests.
These tests drive a real browser against a running site: they log in, visit
pages, click around, and assert on what they see. Use them to test Desk
behaviour, forms, and client scripts that you cannot cover from Python.
Specs live in cypress/integration/ and are plain JavaScript files. A spec looks
like this:
context("ToDo", () => {
before(() => {
cy.login();
cy.visit("/app/todo");
});
it("creates a new ToDo", () => {
cy.click_listview_primary_button("Add ToDo");
cy.fill_field("description", "Write docs", "Text Editor");
cy.click_doc_primary_button("Save");
cy.get(".title-text").should("contain", "Write docs");
});
});
Running UI tests
The site needs allow_tests enabled (see Overview). Start
the bench in one terminal:
bench start
Then run the tests for an app:
bench --site mysite run-ui-tests myapp
Useful options:
--headlessruns without opening the interactive Cypress window, which is what
CI uses.--browser chromepicks the browser (Chrome is the default).--spec <path>runs a single spec file. Only takes effect together with
--headless; without it,--specis silently ignored and the full Cypress
window opens with every spec.--parallelruns specs in parallel.--with-coveragegenerates a coverage report.
bench --site mysite run-ui-tests myapp --headless --spec cypress/integration/todo.js
The first run installs Cypress and its plugins if they are missing. Without
--headless the Cypress app opens so you can watch tests run and debug them
step by step.
Custom commands
Frappe registers helper commands on cy so specs read clearly. Common ones:
cy.login(email, password)logs in (defaults to the test user).cy.visit("/app/todo")opens a Desk route.cy.fill_field(fieldname, value, fieldtype)fills a form field.cy.get_field(fieldname)returns a field's control.cy.click_listview_primary_button(label)andcy.click_doc_primary_button(label)
click the primary action button matching that label (it matches on visible
text, not the doctype), for examplecy.click_listview_primary_button("Add ToDo").cy.insert_doc(doctype, args)andcy.remove_doc(doctype, name)create and
delete documents over the API for test setup.cy.clear_filters()resets list view filters so state does not bleed into the
next spec.
The full list is in cypress/support/. Configuration such as timeouts, the test
user, and the viewport size lives in cypress.config.js at the app root.
Setting up test data
For data your spec needs, call the whitelisted helpers in
frappe/tests/ui_test_helpers.py, for example create_if_not_exists, through
cy.call. These endpoints only work when the site runs in test mode, so they are
safe to leave in place.
Code coverage
Coverage tells you which lines of JavaScript ran during the tests. The source is
instrumented with Istanbul, and the
Cypress code-coverage plugin
merges the results from every spec into one report.
To generate a report locally:
-
Instrument the source with nyc. This
rewrites the files in place to add counters, so do it on a throwaway checkout.npx nyc instrument -x 'frappe/public/dist/**' -x 'frappe/public/js/lib/**' -x '**/*.bundle.js' --compact=false --in-place frappeThe
-xflag excludes paths; use-nto include specific paths instead. -
Run the tests with coverage enabled:
bench --site mysite run-ui-tests frappe --with-coverage -
Print the report:
npx nyc report --reporter=textSee alternative reporters
for other output formats. The HTML and clover reports are written to
.cypress-coverage/(set by thenycconfig inpackage.json).
Testing Library queries
You can use Testing Library queries inside specs.
They find elements the way a user would, by role, label, or visible text, instead
of by CSS selectors that break on refactors. See the
queries docs for the full list.
findByRole is the one to reach for first.
This table maps HTML elements
to their default roles.
| Query | Matches |
|---|---|
findByRole('button', {name: 'Save'}) |
a button whose accessible name is 'Save' |
findByRole('checkbox') |
input type=checkbox |
findByRole('textbox') |
input type=text, textarea |
findByRole('searchbox') |
input type=search |
findByRole('listbox') |
select, datalist |
Other queries target an element by a specific attribute or its text:
| Query | Matches |
|---|---|
findByLabelText('Optimize') |
element tied to the label 'Optimize' |
findByPlaceholderText('Name') |
element with placeholder='Name' |
findByText('example.json') |
element whose text content is 'example.json' |
findByDisplayValue('Option 1') |
input, textarea, or select with that value |
findByTitle('Open Link') |
element with title='Open Link' |