Skip to content

Commit

Permalink
Add retry-ability to the cypress adapters for dom-testing-library (#78)
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
Jmaharman authored and alexkrolick committed Sep 4, 2019
1 parent 3ada0f2 commit ab5263d
Show file tree
Hide file tree
Showing 7 changed files with 509 additions and 132 deletions.
59 changes: 44 additions & 15 deletions cypress/fixtures/test-app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,47 +23,76 @@
click <span title="Run All Tests"></span> Run All Tests.
</blockquote>
<section>
<h2>getByPlaceholderText</h2>
<input type="text" placeholder="Placeholder Text" />
<h2>*ByLabel and *ByPlaceholder</h2>
<label for="by-text-input">Label 1</label>
<input type="text" placeholder="Input 1" id="by-text-input" />

<label for="by-text-input-2">Label 2</label>
<input type="text" placeholder="Input 2" id="by-text-input-2" />
</section>
<section>
<h2>getByText</h2>
<button onclick="this.innerText = 'Button Clicked'">Button Text</button>
<h2>*ByText</h2>
<button onclick="this.innerText = 'Button Clicked'">Button Text 1</button>
<div id="nested">
<h3>getByText within</h3>
<button onclick="this.innerText = 'Button Clicked'">Button Text</button>
<h3>ByText within</h3>
<button onclick="this.innerText = 'Button Clicked'">Button Text 2</button>
</div>
</section>
<section>
<h2>getByLabelText</h2>
<label for="input-labelled-by-id">Label For Input Labelled By Id</label>
<input type="text" placeholder="Input Labelled By Id" id="input-labelled-by-id" />
<h2>*ByDisplayValue</h2>
<input type="text" value="Display Value 1" />

<input type="text" value="Display Value 2" />
</section>
<section>
<h2>getByAltText</h2>
<h2>*ByAltText</h2>
<img
src="data:image/png;base64,"
alt="Image Alt Text"
alt="Image Alt Text 1"
onclick="this.style.border = '5px solid red'"
/>
<img
src="data:image/png;base64,"
alt="Image Alt Text 2"
onclick="this.style.border = '5px solid red'"
/>
</section>
<section>
<h2>*ByTitle</h2>
<span title="Title 1">1</span>
<span title="Title 2">2</span>
</section>
<section>
<h2>*ByRole</h2>
<div role="dialog">dialog 1</div>
<div role="dialog-fake">dialog 2</div>
</section>
<section>
<h2>getByTestId</h2>
<h2>*ByTestId</h2>
<img
data-testid="image-with-random-alt-tag"
data-testid="image-with-random-alt-tag-1"
src="data:image/png;base64,"
onclick="this.style.border = '5px solid red'"
/>
<img
data-testid="image-with-random-alt-tag-2"
src="data:image/png;base64,"
onclick="this.style.border = '5px solid red'"
/>
</section>
<section>
<h2>getAllByText</h2>
<h2>*AllByText</h2>
<button onclick="this.innerText = 'Jackie Kicked'">Jackie Chan 1</button>
<button onclick="this.innerText = 'Jackie Kicked'">Jackie Chan 2</button>
</section>
<section>
<h2>*ByText on another page</h2>
<a onclick='setTimeout(function() { window.location = "/next-page.html"; }, 100);'>Next Page</a>
</section>
<!-- Prettier unindents the script tag below -->
<script>
document
.querySelector('[data-testid="image-with-random-alt-tag"]')
.querySelector('[data-testid="image-with-random-alt-tag-1"]')
.setAttribute('alt', 'Image Random Alt Text ' + Math.random())
</script>
</body>
Expand Down
29 changes: 29 additions & 0 deletions cypress/fixtures/test-app/next-page.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>cypress-testing-library</title>
<style>
blockquote {
margin: 0;
border-left: 4px solid grey;
padding-left: 10px;
color: grey;
}
section {
padding: 10px;
}
</style>
</head>
<body>
<blockquote>
No auto-reload after changing this static HTML markup:
click <span title="Run All Tests"></span> Run All Tests.
</blockquote>
<section>
<h2>New Page Loaded</h2>
</section>
</body>
</html>
69 changes: 0 additions & 69 deletions cypress/integration/commands.spec.js

This file was deleted.

130 changes: 130 additions & 0 deletions cypress/integration/find.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
describe('find* dom-testing-library commands', () => {
beforeEach(() => {
cy.visit('/')
})

// Test each of the types of queries: LabelText, PlaceholderText, Text, DisplayValue, AltText, Title, Role, TestId

it('findByLabelText', () => {
cy.findByLabelText('Label 1')
.click()
.type('Hello Input Labelled By Id')
})

it('findAllByLabelText', () => {
cy.findAllByLabelText(/^Label \d$/).should('have.length', 2)
})

it('findByPlaceholderText', () => {
cy.findByPlaceholderText('Input 1')
.click()
.type('Hello Placeholder')
})

it('findAllByPlaceholderText', () => {
cy.findAllByPlaceholderText(/^Input \d$/).should('have.length', 2)
})

it('findByText', () => {
cy.findByText('Button Text 1')
.click()
.should('contain', 'Button Clicked')
})

it('findAllByText', () => {
cy.findAllByText(/^Button Text \d$/)
.should('have.length', 2)
.click({ multiple: true })
.should('contain', 'Button Clicked')
})

it('findByDisplayValue', () => {
cy.findByDisplayValue('Display Value 1')
.click()
.clear()
.type('Some new text')
})

it('findAllByDisplayValue', () => {
cy.findAllByDisplayValue(/^Display Value \d$/)
.should('have.length', 2)
})

it('findByAltText', () => {
cy.findByAltText('Image Alt Text 1').click()
})

it('findAllByAltText', () => {
cy.findAllByAltText(/^Image Alt Text \d$/).should('have.length', 2)
})

it('findByTitle', () => {
cy.findByTitle('Title 1').click()
})

it('findAllByTitle', () => {
cy.findAllByTitle(/^Title \d$/).should('have.length', 2)
})

it('findByRole', () => {
cy.findByRole('dialog').click()
})

it('findAllByRole', () => {
cy.findAllByRole(/^dialog/).should('have.length', 2)
})

it('findByTestId', () => {
cy.findByTestId('image-with-random-alt-tag-1').click()
})

it('findAllByTestId', () => {
cy.findAllByTestId(/^image-with-random-alt-tag-\d$/).should('have.length', 2)
})

/* Test the behaviour around these queries */

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 within', () => {
cy.get('#nested').within(() => {
cy.findByText('Button Text 2').click()
})
})

it('findByText in container', () => {
return cy.get('#nested')
.then(subject => {
cy.findByText(/^Button Text/, {container: subject}).click()
})
})

it('findByText works when another page loads', () => {
cy.findByText('Next Page').click()
cy.findByText('New Page Loaded').should('exist')
})

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.`
cy.on('fail', err => {
expect(err.message).to.eq(errorMessage)
})

cy.findByText(regex, {timeout: 100}) // Doesn't explicitly need .should('exist') if it's the last element?
})

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)
})

cy.findByText(/^Button Text/i)
})
})

/* global cy */
26 changes: 26 additions & 0 deletions cypress/integration/get.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
describe('get* queries should error', () => {
beforeEach(() => {
cy.visit('/')
})

const queryPrefixes = ['By', 'AllBy']
const queryTypes = ['LabelText', 'PlaceholderText', 'Text', 'DisplayValue', 'AltText', 'Title', 'Role', 'TestId']

queryPrefixes.forEach(queryPrefix => {
queryTypes.forEach(queryType => {
const obsoleteQueryName = `get${queryPrefix + queryType}`;
const preferredQueryName = `find${queryPrefix + queryType}`;
it(`${obsoleteQueryName} should error and suggest using ${preferredQueryName}`, () => {

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.`
cy.on('fail', err => {
expect(err.message).to.eq(errorMessage)
})

cy[`${obsoleteQueryName}`]('Irrelevant')
})
})
})
})

/* global cy */
Loading

0 comments on commit ab5263d

Please sign in to comment.