Skip to content

WIP: getById retries #31

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

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 95 additions & 66 deletions cypress/fixtures/test-app/index.html
Original file line number Diff line number Diff line change
@@ -1,70 +1,99 @@
<!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>getByPlaceholderText</h2>
<input type="text" placeholder="Placeholder Text" />
</section>
<section>
<h2>getByText</h2>
<button onclick="this.innerText = 'Button Clicked'">Button Text</button>
<div id="nested">
<h3>getByText within</h3>
<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>getByPlaceholderText</h2>
<input type="text" placeholder="Placeholder Text" />
</section>
<section>
<h2>getByText</h2>
<button onclick="this.innerText = 'Button Clicked'">Button Text</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" />
</section>
<section>
<h2>getByAltText</h2>
<img
src="data:image/png;base64,"
alt="Image Alt Text"
onclick="this.style.border = '5px solid red'"
/>
</section>
<section>
<h2>getByTestId</h2>
<img
data-testid="image-with-random-alt-tag"
src="data:image/png;base64,"
onclick="this.style.border = '5px solid red'"
/>
</section>
<section>
<h2>getAllByText</h2>
<button onclick="this.innerText = 'Jackie Kicked'">Jackie Chan 1</button>
<button onclick="this.innerText = 'Jackie Kicked'">Jackie Chan 2</button>
</section>
<!-- Prettier unindents the script tag below -->
<script>
document
.querySelector('[data-testid="image-with-random-alt-tag"]')
.setAttribute('alt', 'Image Random Alt Text ' + Math.random())
</script>
</body>
<div id="nested">
<h3>getByText within</h3>
<button onclick="this.innerText = 'Button Clicked'">Button Text</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"
/>
</section>
<section>
<h2>getByAltText</h2>
<img
src="data:image/png;base64,"
alt="Image Alt Text"
onclick="this.style.border = '5px solid red'"
/>
</section>
<section>
<h2>getByTestId</h2>
<img
id="test-image-with-data-testid"
data-testid="image-with-random-alt-tag"
src="data:image/png;base64,"
onclick="this.style.border = '5px solid red'"
/>
<img
id="test-id-is-added-after-delay"
data-testid=""
src="data:image/png;base64,"
onclick="this.style.border = '5px solid blue'"
/>
</section>
<section>
<h2>getAllByText</h2>
<button onclick="this.innerText = 'Jackie Kicked'">Jackie Chan 1</button>
<button onclick="this.innerText = 'Jackie Kicked'">Jackie Chan 2</button>
</section>
<!-- Prettier unindents the script tag below -->
<script>
document
.querySelector('[data-testid="image-with-random-alt-tag"]')
.setAttribute('alt', 'Image Random Alt Text ' + Math.random())

setTimeout(() => {
console.log(
'adding data-testid attribute to image #test-id-is-added-after-delay',
)
document
.getElementById('test-id-is-added-after-delay')
.setAttribute('data-testid', 'dynamically-added-data-test-id-123')
}, 2000)

setTimeout(() => {
console.log(
'removing data test id attribute from image #test-image-with-data-testid',
)
document
.getElementById('test-image-with-data-testid')
.setAttribute('data-testid', '')
}, 3000)
</script>
</body>
</html>
28 changes: 23 additions & 5 deletions cypress/integration/commands.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,29 @@ describe('dom-testing-library commands', () => {
cy.getByAltText('Image Alt Text').click()
})

it('getByTestId', () => {
cy.getByTestId('image-with-random-alt-tag').click()
context.only('getByTestId', () => {
it('it finds image', () => {
cy.getByTestId('image-with-random-alt-tag').click()
})

it('retries finding by test id attribute', () => {
cy.getByTestId('dynamically-added-data-test-id-123')
.should('exist')
.click()
})

it('retries should not exist assertion', () => {
// initially the image is there
cy.getByTestId('image-with-random-alt-tag')
.should('exist')
.should('have.id', 'test-image-with-data-testid')
// then its attribute is removed
cy.getByTestId('image-with-random-alt-tag').should('not.exist')
})
})

it('getAllByText', () => {
cy.getAllByText(/^Jackie Chan/).click({multiple: true})
cy.getAllByText(/^Jackie Chan/).click({ multiple: true })
})

it('queryByText', () => {
Expand All @@ -43,11 +60,12 @@ describe('dom-testing-library commands', () => {

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

it('getByTestId only throws the error message', () => {
// commands now properly retry
it.skip('getByTestId only throws the error message', () => {
const testId = 'Some random id'
const errorMessage = `Unable to find an element by: [data-testid="${testId}"]`
cy.on('fail', err => {
Expand Down
164 changes: 97 additions & 67 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,80 +1,110 @@
import {queries, waitForElement} from 'dom-testing-library'
import {getContainer} from './utils'
/// <reference types="cypress" />
import { queries, waitForElement } from 'dom-testing-library'
import { getContainer } from './utils'

const getDefaultCommandOptions = () => {
return {
timeout: Cypress.config().defaultCommandTimeout,
timeout: Cypress.config().defaultCommandTimeout
}
}

const commands = Object.keys(queries).map(queryName => {
return {
name: queryName,
command: (...args) => {
const lastArg = args[args.length - 1]
const defaults = getDefaultCommandOptions()
const waitOptions =
typeof lastArg === 'object'
? Object.assign({}, defaults, lastArg)
: defaults
const commands = Object.keys(queries)
.filter(name => name === 'getByTestId')
.map(queryName => {
console.log('query name', queryName)

const queryImpl = queries[queryName]
const baseCommandImpl = doc => {
const container = getContainer(waitOptions.container || doc)
return waitForElement(
() => queryImpl(container, ...args),
Object.assign({}, waitOptions, {
container,
}),
)
}
let commandImpl
if (
queryName.startsWith('queryBy') ||
queryName.startsWith('queryAllBy')
) {
commandImpl = doc =>
baseCommandImpl(doc).catch(_ =>
doc.querySelector('.___cypressNotExistingSelector'),
return {
name: queryName,
command: (...args) => {
const lastArg = args[args.length - 1]
const defaults = getDefaultCommandOptions()
const waitOptions =
typeof lastArg === 'object'
? Object.assign({}, defaults, lastArg)
: defaults

const queryImpl = queries[queryName]
const baseCommandImpl = doc => {
const container = getContainer(waitOptions.container || doc)
return waitForElement(
() => queryImpl(container, ...args),
Object.assign({}, waitOptions, {
container,
timeout: 10
})
)
} else {
commandImpl = doc => baseCommandImpl(doc)
}
const thenHandler = new Function(
'commandImpl',
`
return function Command__${queryName}(thenArgs) {
return commandImpl(thenArgs.document)
}
`,
)(commandImpl)
return cy
.window({log: false})
.then({timeout: waitOptions.timeout + 100}, thenHandler)
.then(subject => {
Cypress.log({
$el: subject,
name: queryName,
message: args.filter(value => {
if (Array.isArray(value) && value.length === 0) {
return false
}
if (
typeof value === 'object' &&
Object.keys(value).length === 0
) {
return false
}
return Boolean(value)
}),
}
let commandImpl
if (
queryName.startsWith('queryBy') ||
queryName.startsWith('queryAllBy')
) {
commandImpl = doc =>
baseCommandImpl(doc).catch(_ =>
doc.querySelector('.___cypressNotExistingSelector')
)
} else {
commandImpl = () => {
const doc = cy.state('window').document
// console.log('calling', baseCommandImpl.name)
// debugger
return baseCommandImpl(doc).catch(e => {
// do nothing?
})
}
}
// const thenHandler = new Function(
// 'commandImpl',
// `
// return function Command__${queryName}(thenArgs) {
// return commandImpl(thenArgs)
// }
// `
// )(commandImpl)

const resolveValue = () => {
return Cypress.Promise.try(commandImpl).then(value => {
// console.log('value', value)
return cy.verifyUpcomingAssertions(Cypress.$(value), waitOptions, {
onRetry: resolveValue
})
})
return subject
})
},
}
})
}

return (
// cy
// .window({ log: false })
// .then({ log: false, timeout: waitOptions.timeout + 100 }, w => {
// return resolveValue(w.document)()
// })
resolveValue()
// .then({ timeout: waitOptions.timeout + 100 }, thenHandler)
// .then({ timeout: waitOptions.timeout + 100 }, resolveValue())
.then(subject => {
Cypress.log({
$el: subject,
name: queryName,
message: args.filter(value => {
if (Array.isArray(value) && value.length === 0) {
return false
}
if (
typeof value === 'object' &&
Object.keys(value).length === 0
) {
return false
}
return Boolean(value)
})
})
return subject
})
)
}
}
})

export {commands}
export { commands }

/* eslint no-new-func:0 */
/* globals Cypress, cy */