Skip to content

Commit 94c46e4

Browse files
authored
fix: remove deprecated query* queries (#130)
BREAKING CHANGE: Remove `query*` queries. Throw an error instead. Fixing requires updating all `query*` to `find*` queries. In practice this means replacing `cy.query` with `cy.find` BREAKING CHANGE: Remove fallback that retries chaiined query that assumes no previous subject. In practice this means starting new chains if no previous subject is required. ```js // Before cy.findByText('Foo') .click() .findByText('Bar') // Element with 'Bar' text is not a child of an element with 'Foo' text .click() // After cy.findByText('Foo') .click() cy.findByText('Bar') .click() ```
1 parent 8e165b4 commit 94c46e4

File tree

5 files changed

+70
-291
lines changed

5 files changed

+70
-291
lines changed

README.md

+34-35
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ This allows you to use all the useful
5757
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
5858
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
5959

60-
6160
- [Installation](#installation)
6261
- [With TypeScript](#with-typescript)
6362
- [Usage](#usage)
@@ -79,7 +78,8 @@ npm install --save-dev @testing-library/cypress
7978

8079
### With TypeScript
8180

82-
Typings are defined in `@types/testing-library__cypress` at [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/testing-library__cypress),
81+
Typings are defined in `@types/testing-library__cypress` at
82+
[DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/testing-library__cypress),
8383
and should be added as follows in `tsconfig.json`:
8484

8585
```json
@@ -100,11 +100,12 @@ Add this line to your project's `cypress/support/commands.js`:
100100
import '@testing-library/cypress/add-commands'
101101
```
102102

103-
You can now use all of `DOM Testing Library`'s `findBy`, `findAllBy`, `queryBy`
104-
and `queryAllBy` commands.
103+
You can now use all of `DOM Testing Library`'s `findBy` and `findAllBy`
104+
commands.
105105
[See the `DOM Testing Library` docs for reference](https://testing-library.com)
106106

107-
You can find [all Library definitions here](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/testing-library__cypress/index.d.ts).
107+
You can find
108+
[all Library definitions here](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/testing-library__cypress/index.d.ts).
108109

109110
To configure DOM Testing Library, use the following custom command:
110111

@@ -113,46 +114,43 @@ cy.configureCypressTestingLibrary(config)
113114
```
114115

115116
To show some simple examples (from
116-
[cypress/integration/query.spec.js](cypress/integration/query.spec.js) or [cypress/integration/find.spec.js](cypress/integration/find.spec.js)):
117+
[cypress/integration/find.spec.js](cypress/integration/find.spec.js)):
117118

118119
```javascript
119-
cy.queryAllByText('Button Text').should('exist')
120-
cy.queryAllByText('Non-existing Button Text').should('not.exist')
121-
cy.queryAllByLabelText('Label text', {timeout: 7000}).should('exist')
122-
cy.findAllByText('Jackie Chan').click();
120+
cy.findAllByText('Button Text').should('exist')
121+
cy.findAllByText('Non-existing Button Text').should('not.exist')
122+
cy.findAllByLabelText('Label text', {timeout: 7000}).should('exist')
123+
cy.findAllByText('Jackie Chan').click()
123124

124125
// findAllByText _inside_ a form element
125-
cy.get('form').within(() => {
126-
cy.findAllByText('Button Text').should('exist')
127-
})
128-
cy.get('form').then(subject => {
129-
cy.findAllByText('Button Text', {container: subject}).should('exist')
130-
})
131-
cy.get('form').findAllByText('Button Text').should('exist')
126+
cy.get('form')
127+
.findAllByText('Button Text')
128+
.should('exist')
132129
```
133130

134131
### Differences from DOM Testing Library
135132

136133
`Cypress Testing Library` supports both jQuery elements and DOM nodes. This is
137134
necessary because Cypress uses jQuery elements, while `DOM Testing Library`
138-
expects DOM nodes. When you pass a jQuery element as `container`, it will get
139-
the first DOM node from the collection and use that as the `container` parameter
140-
for the `DOM Testing Library` functions.
141-
142-
`get*` queries are disabled. `find*` queries do not use the Promise API of
143-
`DOM Testing Library`, but instead forward to the `get*` queries and use Cypress'
144-
built-in retryability using error messages from `get*` APIs to forward as error
145-
messages if a query fails. `query*` also uses `get*` APIs, but disables retryability.
146-
147-
`findAll*` can select more than one element and is closer in functionality to how
148-
Cypress built-in commands work. `findAll*` is preferred to `find*` queries.
149-
`find*` commands will fail if more than one element is found that matches the criteria
150-
which is not how built-in Cypress commands work, but is provided for closer compatibility
151-
to other Testing Libraries.
152-
153-
Cypress handles actions when there is only one element found. For example, the following
154-
will work without having to limit to only 1 returned element. The `cy.click` will
155-
automatically fail if more than 1 element is returned by the `findAllByText`:
135+
expects DOM nodes. When you chain a query, it will get the first DOM node from
136+
`subject` of the collection and use that as the `container` parameter for the
137+
`DOM Testing Library` functions.
138+
139+
`get*` and `query*` queries are disabled. `find*` queries do not use the Promise
140+
API of `DOM Testing Library`, but instead forward to the `get*` queries and use
141+
Cypress' built-in retryability using error messages from `get*` APIs to forward
142+
as error messages if a query fails.
143+
144+
`findAll*` can select more than one element and is closer in functionality to
145+
how Cypress built-in commands work. `find*` commands will fail if more than one
146+
element is found that matches the criteria which is not how built-in Cypress
147+
commands work, but is provided for closer compatibility to other Testing
148+
Libraries.
149+
150+
Cypress handles actions when there is only one element found. For example, the
151+
following will work without having to limit to only 1 returned element. The
152+
`cy.click` will automatically fail if more than 1 element is returned by the
153+
`findAllByText`:
156154

157155
```javascript
158156
cy.findAllByText('Some Text').click()
@@ -223,6 +221,7 @@ Thanks goes to these people ([emoji key][emojis]):
223221

224222
<!-- markdownlint-enable -->
225223
<!-- prettier-ignore-end -->
224+
226225
<!-- ALL-CONTRIBUTORS-LIST:END -->
227226

228227
This project follows the [all-contributors][all-contributors] specification.

cypress/integration/configure.spec.js

-18
This file was deleted.

cypress/integration/find.spec.js

+1-21
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ describe('find* dom-testing-library commands', () => {
108108

109109
it('findByText with a previous subject', () => {
110110
cy.get('#nested')
111-
.findByText('Button Text 1', {fallbackRetryWithoutPreviousSubject: false})
111+
.findByText('Button Text 1')
112112
.should('not.exist')
113113
cy.get('#nested')
114114
.findByText('Button Text 2')
@@ -188,15 +188,6 @@ describe('find* dom-testing-library commands', () => {
188188
cy.findByText(/^Button Text/i, {timeout: 100})
189189
})
190190

191-
it('findByText should not break existing code', () => {
192-
cy.window()
193-
.findByText('Button Text 1')
194-
.should('exist')
195-
cy.location()
196-
.findByText('Button Text 1')
197-
.should('exist')
198-
})
199-
200191
it('findByText should show as a parent command if it starts a chain', () => {
201192
const assertLog = (attrs, log) => {
202193
if (log.get('name') === 'findByText') {
@@ -218,17 +209,6 @@ describe('find* dom-testing-library commands', () => {
218209
cy.on('log:added', assertLog)
219210
cy.get('body').findByText('Button Text 1')
220211
})
221-
222-
it('should chain findBy* with subject different of document, element or window', () => {
223-
cy.wrap(true)
224-
.should('be.true')
225-
.findByText('Error message')
226-
.findByLabelText(/Required/i)
227-
.type('something')
228-
.findByText('Submit')
229-
.queryByText('Error message')
230-
.should('not.be.visible')
231-
})
232212
})
233213

234214
/* global cy */

cypress/integration/query.spec.js

+28-175
Original file line numberDiff line numberDiff line change
@@ -1,190 +1,43 @@
1-
/// <reference types="cypress" />
2-
describe('query* dom-testing-library commands', () => {
1+
describe('get* queries should error', () => {
32
beforeEach(() => {
4-
cy.visit('cypress/fixtures/test-app/')
3+
cy.visit('cypress/fixtures/test-app/')
54
})
65

7-
// Test each of the types of queries: LabelText, PlaceholderText, Text, DisplayValue, AltText, Title, Role, TestId
6+
const queryPrefixes = ['By', 'AllBy']
7+
const queryTypes = ['LabelText', 'PlaceholderText', 'Text', 'DisplayValue', 'AltText', 'Title', 'Role', 'TestId']
88

9-
it('queryByLabelText', () => {
10-
cy.queryByLabelText('Label 1')
11-
.click()
12-
.type('Hello Input Labelled By Id')
13-
})
14-
15-
it('queryAllByLabelText', () => {
16-
cy.queryAllByLabelText(/^Label \d$/).should('have.length', 2)
17-
})
18-
19-
it('queryByPlaceholderText', () => {
20-
cy.queryByPlaceholderText('Input 1')
21-
.click()
22-
.type('Hello Placeholder')
23-
})
9+
queryPrefixes.forEach(queryPrefix => {
10+
queryTypes.forEach(queryType => {
11+
const obsoleteQueryName = `query${queryPrefix + queryType}`;
12+
const preferredQueryName = `find${queryPrefix + queryType}`;
13+
it(`${obsoleteQueryName} should error and suggest using ${preferredQueryName}`, () => {
2414

25-
it('queryAllByPlaceholderText', () => {
26-
cy.queryAllByPlaceholderText(/^Input \d$/).should('have.length', 2)
27-
})
15+
const errorMessage = `You used '${obsoleteQueryName}' which has been removed from Cypress Testing Library because it does not make sense in this context. Please use '${preferredQueryName}' instead.`
16+
cy.on('fail', err => {
17+
expect(err.message).to.eq(errorMessage)
18+
})
2819

29-
it('queryByText', () => {
30-
cy.queryByText('Button Text 1')
31-
.click()
32-
.should('contain', 'Button Clicked')
33-
})
20+
cy[`${obsoleteQueryName}`]('Irrelevant')
21+
})
3422

35-
it('queryAllByText', () => {
36-
cy.queryAllByText(/^Button Text \d$/)
37-
.should('have.length', 2)
38-
.click({ multiple: true })
39-
.should('contain', 'Button Clicked')
40-
})
23+
it(`${obsoleteQueryName} should not log more than once`, () => {
4124

42-
it('queryByDisplayValue', () => {
43-
cy.queryByDisplayValue('Display Value 1')
44-
.click()
45-
.clear()
46-
.type('Some new text')
47-
})
48-
49-
it('queryAllByDisplayValue', () => {
50-
cy.queryAllByDisplayValue(/^Display Value \d$/)
51-
.should('have.length', 2)
52-
})
53-
54-
it('queryByAltText', () => {
55-
cy.queryByAltText('Image Alt Text 1').click()
56-
})
57-
58-
it('queryAllByAltText', () => {
59-
cy.queryAllByAltText(/^Image Alt Text \d$/).should('have.length', 2)
60-
})
25+
let logCount = 0
26+
cy.on('log:added', (attrs, log) => {
27+
if (log.get('name') === obsoleteQueryName) {
28+
logCount = logCount + 1
29+
}
30+
})
6131

62-
it('queryByTitle', () => {
63-
cy.queryByTitle('Title 1').click()
64-
})
65-
66-
it('queryAllByTitle', () => {
67-
cy.queryAllByTitle(/^Title \d$/).should('have.length', 2)
68-
})
69-
70-
it('queryByRole', () => {
71-
cy.queryByRole('dialog').click()
72-
})
73-
74-
it('queryAllByRole', () => {
75-
cy.queryAllByRole(/^dialog/).should('have.length', 2)
76-
})
77-
78-
it('queryByTestId', () => {
79-
cy.queryByTestId('image-with-random-alt-tag-1').click()
80-
})
81-
82-
it('queryAllByTestId', () => {
83-
cy.queryAllByTestId(/^image-with-random-alt-tag-\d$/).should('have.length', 2)
84-
})
85-
86-
/* Test the behaviour around these queries */
87-
88-
it('queryByText with .should(\'not.exist\')', () => {
89-
cy.queryAllByText(/^Button Text \d$/).should('exist')
90-
cy.queryByText('Non-existing Button Text', {timeout: 100}).should('not.exist')
91-
})
32+
cy.on('fail', _ => {
33+
expect(logCount).to.equal(1)
34+
cy.removeAllListeners('log:added')
35+
})
9236

93-
it('queryByText within', () => {
94-
cy.get('#nested').within(() => {
95-
cy.queryByText('Button Text 2').click()
96-
})
97-
})
98-
99-
it('queryByText should set the Cypress element to the found element', (done) => {
100-
// This test is a little strange since snapshots show what element
101-
// is selected, but snapshots themselves don't give access to those
102-
// elements. I had to make the implementation specific so that the `$el`
103-
// is the `subject` when the log is added and the `$el` is the `value`
104-
// when the log is changed. It would be better to extract the `$el` from
105-
// each snapshot
106-
107-
cy.on('log:changed', (attrs, log) => {
108-
if (log.get('name') === 'queryByText') {
109-
expect(log.get('$el')).to.have.text('Button Text 1')
110-
done()
111-
}
112-
})
113-
114-
cy.queryByText('Button Text 1')
115-
})
116-
117-
it('query* will return immediately, and never retry', () => {
118-
cy.queryByText('Next Page').click()
119-
120-
const errorMessage = `Unable to find an element with the text: New Page Loaded.`
121-
cy.on('fail', err => {
122-
expect(err.message).to.contain(errorMessage)
123-
})
124-
125-
cy.queryByText('New Page Loaded', { timeout: 300 }).should('exist')
126-
})
127-
128-
it('query* in container', () => {
129-
return cy.get('#nested')
130-
.then(subject => {
131-
cy.queryByText(/^Button Text/, {container: subject}).click()
37+
cy[`${obsoleteQueryName}`]('Irrelevant')
38+
})
13239
})
13340
})
134-
135-
it('queryByText can return no result, and should not error', () => {
136-
const text = 'Supercalifragilistic'
137-
138-
cy.queryByText(text, {timeout: 100})
139-
.should('have.length', 0)
140-
.and('not.exist')
141-
})
142-
143-
it('queryAllByText can return no results message should not error', () => {
144-
const text = 'Supercalifragilistic'
145-
146-
cy.queryAllByText(text, {timeout: 100})
147-
.should('have.length', 0)
148-
.and('not.exist')
149-
})
150-
151-
it('queryAllByText should forward existence error message from @testing-library/dom', () => {
152-
const text = 'Supercalifragilistic'
153-
const errorMessage = `Unable to find an element with the text: Supercalifragilistic.`
154-
cy.on('fail', err => {
155-
expect(err.message).to.contain(errorMessage)
156-
})
157-
158-
cy.queryAllByText(text, {timeout: 100}).should('exist')
159-
})
160-
161-
it('queryByLabelText should forward useful error messages from @testing-library/dom', () => {
162-
const errorMessage = `Found a label with the text of: Label 3, however no form control was found associated to that label.`
163-
cy.on('fail', err => {
164-
expect(err.message).to.contain(errorMessage)
165-
})
166-
167-
cy.queryByLabelText('Label 3', {timeout: 100}).should('exist')
168-
})
169-
170-
it('queryAllByText should default to Cypress non-existence error message', () => {
171-
const errorMessage = `Expected <button> not to exist in the DOM, but it was continuously found.`
172-
cy.on('fail', err => {
173-
expect(err.message).to.contain(errorMessage)
174-
})
175-
176-
cy.queryAllByText('Button Text 1', {timeout: 100})
177-
.should('not.exist')
178-
})
179-
180-
it('queryByText finding multiple items should error', () => {
181-
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\`)).`
182-
cy.on('fail', err => {
183-
expect(err.message).to.contain(errorMessage)
184-
})
185-
186-
cy.queryByText(/^Button Text/i)
187-
})
18841
})
18942

19043
/* global cy */

0 commit comments

Comments
 (0)