Skip to content

Commit ab5263d

Browse files
Jmaharmanalexkrolick
authored andcommitted
Add retry-ability to the cypress adapters for dom-testing-library (#78)
- find* now used query* implementations, but hooked up via the Cypress retry-ability so that it remains async as per the main DTL API. - query* queries continue to work as they did before. BREAKING CHANGE: get* queries are now removed, because users expect the retry-ability from Cypress by default. We now throw an error asking the user to use find* instead. Refactors: - Changes made to variable scope to pass linting rules - Renamed findTextOrRegex to queryArgument so it's clearer what it is for - Replace Chai assertion error messages with better feedback about the failed command names
1 parent 3ada0f2 commit ab5263d

File tree

7 files changed

+509
-132
lines changed

7 files changed

+509
-132
lines changed

cypress/fixtures/test-app/index.html

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,47 +23,76 @@
2323
click <span title="Run All Tests"></span> Run All Tests.
2424
</blockquote>
2525
<section>
26-
<h2>getByPlaceholderText</h2>
27-
<input type="text" placeholder="Placeholder Text" />
26+
<h2>*ByLabel and *ByPlaceholder</h2>
27+
<label for="by-text-input">Label 1</label>
28+
<input type="text" placeholder="Input 1" id="by-text-input" />
29+
30+
<label for="by-text-input-2">Label 2</label>
31+
<input type="text" placeholder="Input 2" id="by-text-input-2" />
2832
</section>
2933
<section>
30-
<h2>getByText</h2>
31-
<button onclick="this.innerText = 'Button Clicked'">Button Text</button>
34+
<h2>*ByText</h2>
35+
<button onclick="this.innerText = 'Button Clicked'">Button Text 1</button>
3236
<div id="nested">
33-
<h3>getByText within</h3>
34-
<button onclick="this.innerText = 'Button Clicked'">Button Text</button>
37+
<h3>ByText within</h3>
38+
<button onclick="this.innerText = 'Button Clicked'">Button Text 2</button>
3539
</div>
3640
</section>
3741
<section>
38-
<h2>getByLabelText</h2>
39-
<label for="input-labelled-by-id">Label For Input Labelled By Id</label>
40-
<input type="text" placeholder="Input Labelled By Id" id="input-labelled-by-id" />
42+
<h2>*ByDisplayValue</h2>
43+
<input type="text" value="Display Value 1" />
44+
45+
<input type="text" value="Display Value 2" />
4146
</section>
4247
<section>
43-
<h2>getByAltText</h2>
48+
<h2>*ByAltText</h2>
4449
<img
4550
src="data:image/png;base64,"
46-
alt="Image Alt Text"
51+
alt="Image Alt Text 1"
4752
onclick="this.style.border = '5px solid red'"
4853
/>
54+
<img
55+
src="data:image/png;base64,"
56+
alt="Image Alt Text 2"
57+
onclick="this.style.border = '5px solid red'"
58+
/>
59+
</section>
60+
<section>
61+
<h2>*ByTitle</h2>
62+
<span title="Title 1">1</span>
63+
<span title="Title 2">2</span>
64+
</section>
65+
<section>
66+
<h2>*ByRole</h2>
67+
<div role="dialog">dialog 1</div>
68+
<div role="dialog-fake">dialog 2</div>
4969
</section>
5070
<section>
51-
<h2>getByTestId</h2>
71+
<h2>*ByTestId</h2>
5272
<img
53-
data-testid="image-with-random-alt-tag"
73+
data-testid="image-with-random-alt-tag-1"
74+
src="data:image/png;base64,"
75+
onclick="this.style.border = '5px solid red'"
76+
/>
77+
<img
78+
data-testid="image-with-random-alt-tag-2"
5479
src="data:image/png;base64,"
5580
onclick="this.style.border = '5px solid red'"
5681
/>
5782
</section>
5883
<section>
59-
<h2>getAllByText</h2>
84+
<h2>*AllByText</h2>
6085
<button onclick="this.innerText = 'Jackie Kicked'">Jackie Chan 1</button>
6186
<button onclick="this.innerText = 'Jackie Kicked'">Jackie Chan 2</button>
6287
</section>
88+
<section>
89+
<h2>*ByText on another page</h2>
90+
<a onclick='setTimeout(function() { window.location = "/next-page.html"; }, 100);'>Next Page</a>
91+
</section>
6392
<!-- Prettier unindents the script tag below -->
6493
<script>
6594
document
66-
.querySelector('[data-testid="image-with-random-alt-tag"]')
95+
.querySelector('[data-testid="image-with-random-alt-tag-1"]')
6796
.setAttribute('alt', 'Image Random Alt Text ' + Math.random())
6897
</script>
6998
</body>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<meta http-equiv="X-UA-Compatible" content="ie=edge">
7+
<title>cypress-testing-library</title>
8+
<style>
9+
blockquote {
10+
margin: 0;
11+
border-left: 4px solid grey;
12+
padding-left: 10px;
13+
color: grey;
14+
}
15+
section {
16+
padding: 10px;
17+
}
18+
</style>
19+
</head>
20+
<body>
21+
<blockquote>
22+
No auto-reload after changing this static HTML markup:
23+
click <span title="Run All Tests"></span> Run All Tests.
24+
</blockquote>
25+
<section>
26+
<h2>New Page Loaded</h2>
27+
</section>
28+
</body>
29+
</html>

cypress/integration/commands.spec.js

Lines changed: 0 additions & 69 deletions
This file was deleted.

cypress/integration/find.spec.js

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
describe('find* dom-testing-library commands', () => {
2+
beforeEach(() => {
3+
cy.visit('/')
4+
})
5+
6+
// Test each of the types of queries: LabelText, PlaceholderText, Text, DisplayValue, AltText, Title, Role, TestId
7+
8+
it('findByLabelText', () => {
9+
cy.findByLabelText('Label 1')
10+
.click()
11+
.type('Hello Input Labelled By Id')
12+
})
13+
14+
it('findAllByLabelText', () => {
15+
cy.findAllByLabelText(/^Label \d$/).should('have.length', 2)
16+
})
17+
18+
it('findByPlaceholderText', () => {
19+
cy.findByPlaceholderText('Input 1')
20+
.click()
21+
.type('Hello Placeholder')
22+
})
23+
24+
it('findAllByPlaceholderText', () => {
25+
cy.findAllByPlaceholderText(/^Input \d$/).should('have.length', 2)
26+
})
27+
28+
it('findByText', () => {
29+
cy.findByText('Button Text 1')
30+
.click()
31+
.should('contain', 'Button Clicked')
32+
})
33+
34+
it('findAllByText', () => {
35+
cy.findAllByText(/^Button Text \d$/)
36+
.should('have.length', 2)
37+
.click({ multiple: true })
38+
.should('contain', 'Button Clicked')
39+
})
40+
41+
it('findByDisplayValue', () => {
42+
cy.findByDisplayValue('Display Value 1')
43+
.click()
44+
.clear()
45+
.type('Some new text')
46+
})
47+
48+
it('findAllByDisplayValue', () => {
49+
cy.findAllByDisplayValue(/^Display Value \d$/)
50+
.should('have.length', 2)
51+
})
52+
53+
it('findByAltText', () => {
54+
cy.findByAltText('Image Alt Text 1').click()
55+
})
56+
57+
it('findAllByAltText', () => {
58+
cy.findAllByAltText(/^Image Alt Text \d$/).should('have.length', 2)
59+
})
60+
61+
it('findByTitle', () => {
62+
cy.findByTitle('Title 1').click()
63+
})
64+
65+
it('findAllByTitle', () => {
66+
cy.findAllByTitle(/^Title \d$/).should('have.length', 2)
67+
})
68+
69+
it('findByRole', () => {
70+
cy.findByRole('dialog').click()
71+
})
72+
73+
it('findAllByRole', () => {
74+
cy.findAllByRole(/^dialog/).should('have.length', 2)
75+
})
76+
77+
it('findByTestId', () => {
78+
cy.findByTestId('image-with-random-alt-tag-1').click()
79+
})
80+
81+
it('findAllByTestId', () => {
82+
cy.findAllByTestId(/^image-with-random-alt-tag-\d$/).should('have.length', 2)
83+
})
84+
85+
/* Test the behaviour around these queries */
86+
87+
it('findByText with should(\'not.exist\')', () => {
88+
cy.findAllByText(/^Button Text \d$/).should('exist')
89+
cy.findByText('Non-existing Button Text', {timeout: 100}).should('not.exist')
90+
})
91+
92+
it('findByText within', () => {
93+
cy.get('#nested').within(() => {
94+
cy.findByText('Button Text 2').click()
95+
})
96+
})
97+
98+
it('findByText in container', () => {
99+
return cy.get('#nested')
100+
.then(subject => {
101+
cy.findByText(/^Button Text/, {container: subject}).click()
102+
})
103+
})
104+
105+
it('findByText works when another page loads', () => {
106+
cy.findByText('Next Page').click()
107+
cy.findByText('New Page Loaded').should('exist')
108+
})
109+
110+
it('findByText should error if no elements are found', () => {
111+
const regex = /Supercalifragilistic/
112+
const errorMessage = `Timed out retrying: Expected to find element: 'findByText(${regex})', but never found it.`
113+
cy.on('fail', err => {
114+
expect(err.message).to.eq(errorMessage)
115+
})
116+
117+
cy.findByText(regex, {timeout: 100}) // Doesn't explicitly need .should('exist') if it's the last element?
118+
})
119+
120+
it('findByText finding multiple items should error', () => {
121+
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\`)).`
122+
cy.on('fail', err => {
123+
expect(err.message).to.eq(errorMessage)
124+
})
125+
126+
cy.findByText(/^Button Text/i)
127+
})
128+
})
129+
130+
/* global cy */

cypress/integration/get.spec.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
describe('get* queries should error', () => {
2+
beforeEach(() => {
3+
cy.visit('/')
4+
})
5+
6+
const queryPrefixes = ['By', 'AllBy']
7+
const queryTypes = ['LabelText', 'PlaceholderText', 'Text', 'DisplayValue', 'AltText', 'Title', 'Role', 'TestId']
8+
9+
queryPrefixes.forEach(queryPrefix => {
10+
queryTypes.forEach(queryType => {
11+
const obsoleteQueryName = `get${queryPrefix + queryType}`;
12+
const preferredQueryName = `find${queryPrefix + queryType}`;
13+
it(`${obsoleteQueryName} should error and suggest using ${preferredQueryName}`, () => {
14+
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+
})
19+
20+
cy[`${obsoleteQueryName}`]('Irrelevant')
21+
})
22+
})
23+
})
24+
})
25+
26+
/* global cy */

0 commit comments

Comments
 (0)