Skip to content

Commit c8069e3

Browse files
antsmartianKent C. Dodds
authored and
Kent C. Dodds
committed
feat(debug): Adding debug log when a get call fails (#3)
* feat(debug_helper): Adding debug helper when a get call fails * fix(review): Few minor changes * moving pretty-format to the dep * fix(review_comments): Making the fixes mentioned in the review comments * Update README.md * Update element-queries.js
1 parent d45b449 commit c8069e3

File tree

6 files changed

+177
-34
lines changed

6 files changed

+177
-34
lines changed

Diff for: README.md

+37
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ when a real user uses it.
8787
* [Custom Jest Matchers - Typescript](#custom-jest-matchers---typescript)
8888
* [`TextMatch`](#textmatch)
8989
* [`query` APIs](#query-apis)
90+
* [Debugging](#debugging)
9091
* [Implementations](#implementations)
9192
* [FAQ](#faq)
9293
* [Other Solutions](#other-solutions)
@@ -540,6 +541,42 @@ expect(submitButton).toBeNull() // it doesn't exist
540541
expect(submitButton).not.toBeInTheDOM()
541542
```
542543

544+
## Debugging
545+
546+
When you use any `get` calls in your test cases, the current state of the `container`
547+
(DOM) gets printed on the console. For example:
548+
549+
```javascript
550+
// <div>Hello world</div>
551+
getByText(container, 'Goodbye world') // will fail by throwing error
552+
```
553+
554+
The above test case will fail, however it prints the state of your DOM under test,
555+
so you will get to see:
556+
557+
```
558+
Unable to find an element with the text: Goodbye world. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.
559+
Here is the state of your container:
560+
<div>
561+
<div>
562+
Hello World!
563+
</div>
564+
</div>
565+
```
566+
567+
Note: Since the DOM size can get really large, you can set the limit of DOM content
568+
to be printed via environment variable `DEBUG_PRINT_LIMIT`. The default value is
569+
`7000`. You will see `...` in the console, when the DOM content is stripped off,
570+
because of the length you have set or due to default size limit. Here's how you
571+
might increase this limit when running tests:
572+
573+
```
574+
DEBUG_PRINT_LIMIT=10000 npm test
575+
```
576+
577+
This works on macOS/linux, you'll need to do something else for windows. If you'd
578+
like a solution that works for both, see [`cross-env`](https://www.npmjs.com/package/cross-env)
579+
543580
## Implementations
544581

545582
This library was not built to be used on its own. The original implementation

Diff for: package.json

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
],
3636
"dependencies": {
3737
"jest-matcher-utils": "^22.4.3",
38+
"pretty-format": "^22.4.3",
3839
"mutationobserver-shim": "^0.3.2",
3940
"wait-for-expect": "^0.4.0"
4041
},

Diff for: src/__tests__/__snapshots__/element-queries.js.snap

+51-7
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,59 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3-
exports[`get throws a useful error message 1`] = `"Unable to find a label with the text of: LucyRicardo"`;
3+
exports[`get throws a useful error message 1`] = `
4+
"Unable to find a label with the text of: LucyRicardo
45
5-
exports[`get throws a useful error message 2`] = `"Unable to find an element with the placeholder text of: LucyRicardo"`;
6+
<div>
7+
<div />
8+
</div>"
9+
`;
610

7-
exports[`get throws a useful error message 3`] = `"Unable to find an element with the text: LucyRicardo. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible."`;
11+
exports[`get throws a useful error message 2`] = `
12+
"Unable to find an element with the placeholder text of: LucyRicardo
813
9-
exports[`get throws a useful error message 4`] = `"Unable to find an element by: [data-testid=\\"LucyRicardo\\"]"`;
14+
<div>
15+
<div />
16+
</div>"
17+
`;
1018

11-
exports[`get throws a useful error message 5`] = `"Unable to find an element with the alt text: LucyRicardo"`;
19+
exports[`get throws a useful error message 3`] = `
20+
"Unable to find an element with the text: LucyRicardo. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.
1221
13-
exports[`label with no form control 1`] = `"Found a label with the text of: alone, however no form control was found associated to that label. Make sure you're using the \\"for\\" attribute or \\"aria-labelledby\\" attribute correctly."`;
22+
<div>
23+
<div />
24+
</div>"
25+
`;
1426

15-
exports[`totally empty label 1`] = `"Found a label with the text of: , however no form control was found associated to that label. Make sure you're using the \\"for\\" attribute or \\"aria-labelledby\\" attribute correctly."`;
27+
exports[`get throws a useful error message 4`] = `
28+
"Unable to find an element by: [data-testid=\\"LucyRicardo\\"]
29+
30+
<div>
31+
<div />
32+
</div>"
33+
`;
34+
35+
exports[`get throws a useful error message 5`] = `
36+
"Unable to find an element with the alt text: LucyRicardo
37+
38+
<div>
39+
<div />
40+
</div>"
41+
`;
42+
43+
exports[`label with no form control 1`] = `
44+
"Found a label with the text of: alone, however no form control was found associated to that label. Make sure you're using the \\"for\\" attribute or \\"aria-labelledby\\" attribute correctly.
45+
46+
<div>
47+
<label>
48+
All alone
49+
</label>
50+
</div>"
51+
`;
52+
53+
exports[`totally empty label 1`] = `
54+
"Found a label with the text of: , however no form control was found associated to that label. Make sure you're using the \\"for\\" attribute or \\"aria-labelledby\\" attribute correctly.
55+
56+
<div>
57+
<label />
58+
</div>"
59+
`;

Diff for: src/__tests__/__snapshots__/wait-for-element.js.snap

+17-21
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,44 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3-
exports[
4-
`it returns immediately if the callback returns the value before any mutations 1`
5-
] = `
3+
exports[`it returns immediately if the callback returns the value before any mutations 1`] = `
64
<div
75
data-test-attribute="something changed once"
86
>
97
<div
108
data-testid="initial-element"
119
/>
1210
</div>
13-
`
11+
`;
1412

1513
exports[`it throws if timeout is exceeded 1`] = `
1614
Array [
1715
[Error: Timed out in waitForElement.],
1816
]
19-
`
17+
`;
2018

2119
exports[`it throws if timeout is exceeded 2`] = `
2220
<div
2321
data-test-attribute="something changed twice"
2422
/>
25-
`
23+
`;
2624

27-
exports[
28-
`it throws the same error that the callback has thrown if timeout is exceeded 1`
29-
] = `
25+
exports[`it throws the same error that the callback has thrown if timeout is exceeded 1`] = `
3026
Array [
31-
[Error: Unable to find an element by: [data-testid="test"]],
27+
[Error: Unable to find an element by: [data-testid="test"]
28+
29+
<div
30+
data-test-attribute="something changed twice"
31+
/>],
3232
]
33-
`
33+
`;
3434

35-
exports[
36-
`it throws the same error that the callback has thrown if timeout is exceeded 2`
37-
] = `
35+
exports[`it throws the same error that the callback has thrown if timeout is exceeded 2`] = `
3836
<div
3937
data-test-attribute="something changed twice"
4038
/>
41-
`
39+
`;
4240

43-
exports[
44-
`it waits for the callback to return a value and only reacts to DOM mutations 1`
45-
] = `
41+
exports[`it waits for the callback to return a value and only reacts to DOM mutations 1`] = `
4642
<div>
4743
<div
4844
data-testid="initial-element"
@@ -66,16 +62,16 @@ exports[
6662
data-testid="the-element-we-are-looking-for"
6763
/>
6864
</div>
69-
`
65+
`;
7066

7167
exports[`it waits for the next DOM mutation with default callback 1`] = `
7268
<body>
7369
<div />
7470
</body>
75-
`
71+
`;
7672

7773
exports[`it waits for the next DOM mutation with default callback 2`] = `
7874
<body>
7975
<div />
8076
</body>
81-
`
77+
`;

Diff for: src/__tests__/element-queries.js

+32
Original file line numberDiff line numberDiff line change
@@ -193,4 +193,36 @@ test('using jest helpers to check element class names', () => {
193193
).toThrowError()
194194
})
195195

196+
test('test the debug helper prints the dom state here', () => {
197+
const originalDebugPrintLimit = process.env.DEBUG_PRINT_LIMIT
198+
const Large = `<div>
199+
${Array.from({length: 7000}, (v, key) => key).map(() => {
200+
return `<div data-testid="debugging" data-otherid="debugging">
201+
Hello World!
202+
</div>`
203+
})}
204+
</div>`
205+
206+
const {getByText} = render(Large) // render large DOM which exceeds 7000 limit
207+
expect(() => expect(getByText('not present')).toBeInTheDOM()).toThrowError()
208+
209+
const Hello = `<div data-testid="debugging" data-otherid="debugging">
210+
Hello World!
211+
</div>`
212+
const {getByTestId} = render(Hello)
213+
process.env.DEBUG_PRINT_LIMIT = 5 // user should see `...`
214+
expect(() => expect(getByTestId('not present')).toBeInTheDOM()).toThrowError(
215+
/\.\.\./,
216+
)
217+
218+
const {getByLabelText} = render(Hello)
219+
process.env.DEBUG_PRINT_LIMIT = 10000 // user shouldn't see `...`
220+
expect(() =>
221+
expect(getByLabelText('not present')).toBeInTheDOM(/^((?!\.\.\.).)*$/),
222+
).toThrowError()
223+
224+
//all good replacing it with old value
225+
process.env.DEBUG_PRINT_LIMIT = originalDebugPrintLimit
226+
})
227+
196228
/* eslint jsx-a11y/label-has-for:0 */

Diff for: src/queries.js

+39-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import prettyFormat from 'pretty-format'
12
import {matches} from './matches'
23

4+
const {DOMElement, DOMCollection} = prettyFormat.plugins
5+
36
// Here are the queries for the library.
47
// The queries here should only be things that are accessible to both users who are using a screen reader
58
// and those who are not using a screen reader (with the exception of the data-testid attribute query).
@@ -78,7 +81,11 @@ function getText(node) {
7881
function getByTestId(container, id, ...rest) {
7982
const el = queryByTestId(container, id, ...rest)
8083
if (!el) {
81-
throw new Error(`Unable to find an element by: [data-testid="${id}"]`)
84+
throw new Error(
85+
`Unable to find an element by: [data-testid="${id}"] \n\n${htmlElementToDisplay(
86+
container,
87+
)}`,
88+
)
8289
}
8390
return el
8491
}
@@ -87,7 +94,9 @@ function getByPlaceholderText(container, text, ...rest) {
8794
const el = queryByPlaceholderText(container, text, ...rest)
8895
if (!el) {
8996
throw new Error(
90-
`Unable to find an element with the placeholder text of: ${text}`,
97+
`Unable to find an element with the placeholder text of: ${text} \n\n${htmlElementToDisplay(
98+
container,
99+
)}`,
91100
)
92101
}
93102
return el
@@ -99,10 +108,16 @@ function getByLabelText(container, text, ...rest) {
99108
const label = queryLabelByText(container, text)
100109
if (label) {
101110
throw new Error(
102-
`Found a label with the text of: ${text}, however no form control was found associated to that label. Make sure you're using the "for" attribute or "aria-labelledby" attribute correctly.`,
111+
`Found a label with the text of: ${text}, however no form control was found associated to that label. Make sure you're using the "for" attribute or "aria-labelledby" attribute correctly. \n\n${htmlElementToDisplay(
112+
container,
113+
)}`,
103114
)
104115
} else {
105-
throw new Error(`Unable to find a label with the text of: ${text}`)
116+
throw new Error(
117+
`Unable to find a label with the text of: ${text} \n\n${htmlElementToDisplay(
118+
container,
119+
)}`,
120+
)
106121
}
107122
}
108123
return el
@@ -112,7 +127,9 @@ function getByText(container, text, ...rest) {
112127
const el = queryByText(container, text, ...rest)
113128
if (!el) {
114129
throw new Error(
115-
`Unable to find an element with the text: ${text}. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.`,
130+
`Unable to find an element with the text: ${text}. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible. \n\n${htmlElementToDisplay(
131+
container,
132+
)}`,
116133
)
117134
}
118135
return el
@@ -129,11 +146,27 @@ function queryByAltText(container, alt) {
129146
function getByAltText(container, alt) {
130147
const el = queryByAltText(container, alt)
131148
if (!el) {
132-
throw new Error(`Unable to find an element with the alt text: ${alt}`)
149+
throw new Error(
150+
`Unable to find an element with the alt text: ${alt} \n\n${htmlElementToDisplay(
151+
container,
152+
)}`,
153+
)
133154
}
134155
return el
135156
}
136157

158+
function htmlElementToDisplay(htmlElement) {
159+
const debugContent = prettyFormat(htmlElement, {
160+
plugins: [DOMElement, DOMCollection],
161+
printFunctionName: false,
162+
highlight: true,
163+
})
164+
const maxLength = process.env.DEBUG_PRINT_LIMIT || 7000
165+
return htmlElement.outerHTML.length > maxLength
166+
? `${debugContent.slice(0, maxLength)}...`
167+
: debugContent
168+
}
169+
137170
export {
138171
queryByPlaceholderText,
139172
getByPlaceholderText,

0 commit comments

Comments
 (0)