-
Notifications
You must be signed in to change notification settings - Fork 153
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add more helpful debugging information to queries #108
Changes from all commits
c67414d
c9a25b6
b58f747
1f14ce1
217a289
baf066d
aa76c50
a5bfc6f
d85d918
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -61,6 +61,7 @@ This allows you to use all the useful | |
- [Installation](#installation) | ||
- [With TypeScript](#with-typescript) | ||
- [Usage](#usage) | ||
- [Differences from DOM Testing Library](#differences-from-dom-testing-library) | ||
- [Other Solutions](#other-solutions) | ||
- [Contributors](#contributors) | ||
- [LICENSE](#license) | ||
|
@@ -95,7 +96,7 @@ and should be added as follows in `tsconfig.json`: | |
|
||
Add this line to your project's `cypress/support/commands.js`: | ||
|
||
``` | ||
```javascript | ||
import '@testing-library/cypress/add-commands' | ||
``` | ||
|
||
|
@@ -105,28 +106,66 @@ and `queryAllBy` commands. | |
|
||
You can find [all Library definitions here](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/testing-library__cypress/index.d.ts). | ||
|
||
To configure DOM Testing Library, use the following custom command: | ||
|
||
```javascript | ||
cy.configureCypressTestingLibrary(config) | ||
``` | ||
|
||
To show some simple examples (from | ||
[cypress/integration/query.spec.js](cypress/integration/query.spec.js) or [cypress/integration/find.spec.js](cypress/integration/find.spec.js)): | ||
|
||
```javascript | ||
cy.queryByText('Button Text').should('exist') | ||
cy.queryByText('Non-existing Button Text').should('not.exist') | ||
cy.queryByLabelText('Label text', {timeout: 7000}).should('exist') | ||
cy.findAllByText('Jackie Chan').eq(0).click(); | ||
cy.queryAllByText('Button Text').should('exist') | ||
cy.queryAllByText('Non-existing Button Text').should('not.exist') | ||
cy.queryAllByLabelText('Label text', {timeout: 7000}).should('exist') | ||
cy.findAllByText('Jackie Chan').click(); | ||
|
||
// findAllByText _inside_ a form element | ||
cy.get('form').within(() => { | ||
cy.findByText('Button Text').should('exist') | ||
cy.findAllByText('Button Text').should('exist') | ||
}) | ||
cy.get('form').then(subject => { | ||
cy.findByText('Button Text', {container: subject}).should('exist') | ||
cy.findAllByText('Button Text', {container: subject}).should('exist') | ||
}) | ||
cy.get('form').findAllByText('Button Text').should('exist') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I felt this example was missing. It was one of the reasons I overlooked this library and the I found out #100 was merged just recently. This is more idiomatic for Cypress. |
||
``` | ||
|
||
### Differences from DOM Testing Library | ||
|
||
`Cypress Testing Library` supports both jQuery elements and DOM nodes. This is | ||
necessary because Cypress uses jQuery elements, while `DOM Testing Library` | ||
expects DOM nodes. When you pass a jQuery element as `container`, it will get | ||
the first DOM node from the collection and use that as the `container` parameter | ||
for the `DOM Testing Library` functions. | ||
|
||
`get*` queries are disabled. `find*` queries do not use the Promise API of | ||
NicholasBoll marked this conversation as resolved.
Show resolved
Hide resolved
|
||
`DOM Testing Library`, but instead forward to the `get*` queries and use Cypress' | ||
built-in retryability using error messages from `get*` APIs to forward as error | ||
messages if a query fails. `query*` also uses `get*` APIs, but disables retryability. | ||
|
||
`findAll*` can select more than one element and is closer in functionality to how | ||
Cypress built-in commands work. `findAll*` is preferred to `find*` queries. | ||
`find*` commands will fail if more than one element is found that matches the criteria | ||
which is not how built-in Cypress commands work, but is provided for closer compatibility | ||
to other Testing Libraries. | ||
|
||
Cypress handles actions when there is only one element found. For example, the following | ||
will work without having to limit to only 1 returned element. The `cy.click` will | ||
automatically fail if more than 1 element is returned by the `findAllByText`: | ||
|
||
```javascript | ||
cy.findAllByText('Some Text').click() | ||
``` | ||
|
||
If you intend to enforce only 1 element is returned by a selector, the following | ||
examples will both fail if more than one element is found. | ||
|
||
```javascript | ||
cy.findAllByText('Some Text').should('have.length', 1) | ||
cy.findByText('Some Text').should('exist') | ||
Comment on lines
+142
to
+166
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here's the updated documentation about |
||
``` | ||
|
||
## Other Solutions | ||
|
||
I'm not aware of any, if you are please [make a pull request][prs] and add it | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"rules": { | ||
"max-lines-per-function": "off", | ||
"jest/valid-expect-in-promise": "off" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
/// <reference types="cypress" /> | ||
describe('configuring fallback globally', () => { | ||
beforeEach(() => { | ||
cy.visit('cypress/fixtures/test-app/') | ||
cy.configureCypressTestingLibrary({ fallbackRetryWithoutPreviousSubject: false }) | ||
}) | ||
|
||
it('findByText with a previous subject', () => { | ||
cy.get('#nested') | ||
.findByText('Button Text 1') | ||
.should('not.exist') | ||
cy.get('#nested') | ||
.findByText('Button Text 2') | ||
.should('exist') | ||
}) | ||
}) | ||
|
||
/* global cy */ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
/// <reference types="cypress" /> | ||
describe('find* dom-testing-library commands', () => { | ||
beforeEach(() => { | ||
cy.visit('cypress/fixtures/test-app/') | ||
|
@@ -86,22 +87,48 @@ describe('find* dom-testing-library commands', () => { | |
|
||
/* Test the behaviour around these queries */ | ||
|
||
it('findByText should handle non-existence', () => { | ||
cy.findByText('Does Not Exist') | ||
.should('not.exist') | ||
}) | ||
|
||
it('findByText should handle eventual existence', () => { | ||
cy.findByText('Eventually Exists') | ||
.should('exist') | ||
}) | ||
|
||
it('findByText should handle eventual non-existence', () => { | ||
cy.findByText('Eventually Not exists') | ||
.should('not.exist') | ||
}) | ||
|
||
it("findByText with should('not.exist')", () => { | ||
cy.findAllByText(/^Button Text \d$/).should('exist') | ||
cy.findByText('Non-existing Button Text', {timeout: 100}).should( | ||
'not.exist', | ||
) | ||
}) | ||
|
||
it('findByText with a previous subject', () => { | ||
cy.get('#nested') | ||
.findByText('Button Text 1', { fallbackRetryWithoutPreviousSubject: false }) | ||
.should('not.exist') | ||
cy.get('#nested') | ||
.findByText('Button Text 2') | ||
.should('exist') | ||
}) | ||
|
||
it('findByText within', () => { | ||
cy.get('#nested').within(() => { | ||
cy.findByText('Button Text 2').click() | ||
cy.findByText('Button Text 1').should('not.exist') | ||
cy.findByText('Button Text 2').should('exist') | ||
}) | ||
}) | ||
|
||
it('findByText in container', () => { | ||
return cy.get('#nested').then(subject => { | ||
cy.findByText(/^Button Text/, {container: subject}).click() | ||
cy.get('#nested').then(subject => { | ||
cy.findByText('Button Text 1', {container: subject}).should('not.exist') | ||
cy.findByText('Button Text 2', {container: subject}).should('exist') | ||
}) | ||
}) | ||
|
||
|
@@ -110,23 +137,87 @@ describe('find* dom-testing-library commands', () => { | |
cy.findByText('New Page Loaded').should('exist') | ||
}) | ||
|
||
it('findByText should set the Cypress element to the found element', () => { | ||
// This test is a little strange since snapshots show what element | ||
// is selected, but snapshots themselves don't give access to those | ||
// elements. I had to make the implementation specific so that the `$el` | ||
// is the `subject` when the log is added and the `$el` is the `value` | ||
// when the log is changed. It would be better to extract the `$el` from | ||
// each snapshot | ||
Comment on lines
+141
to
+146
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
|
||
cy.on('log:changed', (attrs, log) => { | ||
if (log.get('name') === 'findByText') { | ||
expect(log.get('$el')).to.have.text('Button Text 1') | ||
} | ||
}) | ||
|
||
cy.findByText('Button Text 1') | ||
}) | ||
|
||
it('findByText should error if no elements are found', () => { | ||
const regex = /Supercalifragilistic/ | ||
const errorMessage = `Timed out retrying: Expected to find element: 'findByText(${regex})', but never found it.` | ||
const errorMessage = `Unable to find an element with the text: /Supercalifragilistic/` | ||
cy.on('fail', err => { | ||
expect(err.message).to.eq(errorMessage) | ||
expect(err.message).to.contain(errorMessage) | ||
}) | ||
|
||
cy.findByText(regex, {timeout: 100}) // Doesn't explicitly need .should('exist') if it's the last element? | ||
cy.findByText(regex, {timeout: 100}) | ||
}) | ||
|
||
it('findByText should default to Cypress non-existence error message', () => { | ||
const errorMessage = `Expected <button> not to exist in the DOM, but it was continuously found.` | ||
cy.on('fail', err => { | ||
expect(err.message).to.contain(errorMessage) | ||
}) | ||
|
||
cy.findByText('Button Text 1', {timeout: 100}) | ||
.should('not.exist') | ||
}) | ||
|
||
it('findByLabelText should forward useful error messages from @testing-library/dom', () => { | ||
const errorMessage = `Found a label with the text of: Label 3, however no form control was found associated to that label.` | ||
cy.on('fail', err => { | ||
expect(err.message).to.contain(errorMessage) | ||
NicholasBoll marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}) | ||
|
||
cy.findByLabelText('Label 3', {timeout: 100}) | ||
}) | ||
|
||
it('findByText finding multiple items should error', () => { | ||
const errorMessage = `Found multiple elements with the text: /^Button Text/i\n\n(If this is intentional, then use the \`*AllBy*\` variant of the query (like \`queryAllByText\`, \`getAllByText\`, or \`findAllByText\`)).` | ||
cy.on('fail', err => { | ||
expect(err.message).to.eq(errorMessage) | ||
expect(err.message).to.contain(errorMessage) | ||
}) | ||
|
||
cy.findByText(/^Button Text/i) | ||
cy.findByText(/^Button Text/i, {timeout: 100}) | ||
}) | ||
|
||
it('findByText should not break existing code', () => { | ||
cy.window() | ||
.findByText('Button Text 1') | ||
.should('exist') | ||
}) | ||
Comment on lines
+195
to
+199
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test verifies non-breaking changes for #110. |
||
|
||
it('findByText should show as a parent command if it starts a chain', () => { | ||
const assertLog = (attrs, log) => { | ||
if(log.get('name') === 'findByText') { | ||
expect(log.get('type')).to.equal('parent') | ||
cy.off('log:added', assertLog) | ||
} | ||
} | ||
cy.on('log:added', assertLog) | ||
cy.findByText('Button Text 1') | ||
}) | ||
|
||
it('findByText should show as a child command if it continues a chain', () => { | ||
const assertLog = (attrs, log) => { | ||
if(log.get('name') === 'findByText') { | ||
expect(log.get('type')).to.equal('child') | ||
cy.off('log:added', assertLog) | ||
} | ||
} | ||
cy.on('log:added', assertLog) | ||
cy.get('body').findByText('Button Text 1') | ||
Comment on lines
+201
to
+220
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
}) | ||
}) | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I have a good reason for changing these. It is part of the questions of the pull request. I think
*By
and*AllBy
make sense for@test-library/dom
,@testing-library/react
, and company, but I'm not sure it does for Cypress.Personally, I recommend always using the
*ByAll
since that's what native Cypress commands do now, but I'm welcoming other opinions