-
Notifications
You must be signed in to change notification settings - Fork 153
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
Add retry-ability to the cypress adapters for dom-testing-library #78
Conversation
It seems to be that if you load a new page, the retry functionality that's baked in at the moment doesn't work.
There is a built in, but undocumented, promise loop that can be run so that functions can continue to be executed time and time ago, with this enabled the previously failing test now passes. To get it to work I have to trap the getBy exceptions when an item was not found. This means the previously useful error thrown is no longer as easily visible. I've made it accessible by clicking on the command. All getBy, getAllBy, queryBy, queryAllBy, etc, behaviours should all still work as expected. Another side effect of this was that Chai assertion error messages are not as helpful. I've hijacked the jQuery object selector because Cypress uses that in the error message for jQuery object messages. In the selector I've placed a description of the underlying function call, which gives pretty good feedback in the UI window. I had to adjust other tests due to message changes that came with retry-ability.
Super appreciate this PR @Jmaharman. It sounds like a great idea, and I'll try to pull it down and play around with it soon. I'd love it if other people watching this repo could do the same and provide feedback as well. Thanks! |
Looks like the build is failing due to linting issues. If you run |
I will see if I can get this PR working locally at my work notebook. This looks exciting. If I understand it correctly this would make it easier to detect appearing or disappearing elements without the need of wait() left and right? |
src/index.js
Outdated
} | ||
catch (err) { | ||
// Catch exceptions where it can't find the element, so we can retry | ||
if (/Unable to find an element with the text/.test(err.message)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this regex capture all of the errors thrown by DOM Testing Library queries? There are other ones like "Unable to find an element with the label text" etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was originally catching any exception to getBy working as expected, when testing other functionality I realised that the retry-ability stuff would try everything even when the failure is correct. For example getBy, findBy and queryBy all throw an error when matching multiple results, and that scenario is less likely something the user wants to retry, once it fails that is probably the correct result.
If there are other error messages for getByLabelText, getByDisplayValue, getByTestId, getByRole etc, then yes we definitely need to add those or create a specific error type in dom-testing-library and catch those.
I'll defer the decision to someone with greater knowledge of dom-testing-library for that one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've since seen that getByLabelText doesn't retry, due to the error message not being as you mention. We can add the relevant messages for each of those queries, but I wonder if there is a way to catch all of those in a more succinct and deliberate way?
@kentcdodds can we create a specific type of error message for get* exceptions in dom-testing-library? That way we can test on something more concrete than the error message text, which could change over time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if we add a property to the error object like error.isDTLError = true
? Would that work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something along those lines would be fine yes. What does DTL stand for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could also create an error class in DTL, and use instanceof
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The main thing we need to know is how many elements have been found. If it’s 0 then we know we need to re-run the function, if it’s more than that then we let it throw.
Either adding a property to that affect on said errors would work, whether you want to use a new class of error is up to you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(DTL is DOM Testing Library)
src/index.js
Outdated
|
||
// Overriding the selector of the jquery object because it's displayed in the long message of .should('exist') failure message | ||
// Hopefully it makes it clearer, because I find the normal response of "Expected to find element '', but never found it" confusing | ||
result.selector = queryName + '(' + findTextOrRegex(args) + ')'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice improvement
src/index.js
Outdated
}) | ||
} | ||
|
||
if (/queryBy|queryAllBy/.test(queryName)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this what makes getBy
different than queryBy
?
I kind of think we should just make findBy
and findAllBy
the default names in Cypress since they are async even in DTL, and just not implement the other styles.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes that's the only difference at the moment. We can't retry the something where it's valid to return a null or empty array, simply because the retry-ability will keep going until it finds something.
I simply run the function to get the dom element and return early, no retryability for these. The only other option would be to keep retrying until just before the original timeout, but to me that would slow down tests unnecessarily, the developer should check the page is ready through some other query before using query*
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kind of think we should just make findBy and findAllBy the default names in Cypress since they are async even in DTL, and just not implement the other styles.
I'm kinda thinking this is the right way to go. What do other's think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds like a reasonable change. I don't think we need to support all the same queries for Cypress if they don't make sense with the Cypress ecosystem
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There were no tests for find* so out of curiosity I've converted all of the get* tests to find* locally to see what happens, of which they all fail.
I've only ever used the get* and query* queries, and hadn't realised the implications of find*.
I've quickly tried to fix the find* queries, however I've found that when it can't find an element it doesn't show the spinner because it's still waiting for a response from the find* queries.
The try-ability code works on the premise that it will find the value, check to see if the next assertion passes and if not try again, until the timeout expires. Due to the fact it only tries once, we don't see the spinner.
I don't mind get* or find* are removed, but either way they both need to be implemented using the get* underlying functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To confirm, are you both saying you are happy for CTL to have the 4 queries?
- queryBy*
- queryAllBy*
- findBy*
- findAllBy*
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, and if we could implement getBy*
and getAllBy*
to throw an error with a descriptive message that would be good. For example:
You used
getByAllText
which has been removed from Cypress Testing Library because it does not make sense in this context. Please usefindByAllText
instead.
I think that would be great :)
By the way, thank you so much for working on this so tirelessly! I'm excited for this to be released and to have a library that works much better with Cypress than ever before.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not at all, I'm doing very little in the grand scheme of things. We started using this library at work, and I saw the issue and realised I could do something about it.
I need to get something else done, but should be able to do the work over the weekend / early next week.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Super excited to get this merged! I will see if I can use your repo at my work's project to improve the tests and let you know how it goes. Currently, I have a bunch of wait() to give the page time to render some elements.
My understanding is that with this PR I could avoid these changes. I also it sounds like it should use findByText etc instead of getByText.
How can I help getting this merged?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the delay guys, things have been hectic at work and at home. I will get onto it soon.
find* now used query* implementations, but hooked up via the Cypress retry-ability so that it remains async as per the main DTL API. get* queries are now redundant, because users expect the retry-ability from Cypress by default, we now throw an error asking the user to use find* instead. query* queries continue to work as they did before. I've created tests for all functions, and then the main behaviors associated to each type of query.
Here you go all, get that down and give it a test in your own projects. |
Thanks, I will give it a try tomorrow morning at work. I hope I can remove a bunch of wait() in my tests now 💃 |
I resolved some merge conflicts and published this as 5.0.0-alpha.0 (you can install it via |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is good to go IMO.
Just FYI to whoever merges this (can be me or another maintainer), make sure to use |
LGTM, tested with a few smoke tests and just needed to update the |
🎉 This PR is included in version 5.0.0 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
I've realised this morning that I'm not retrying when multiple matches are found, it fails early. I'll open another PR for it. |
@Jmaharman i installed the latest version of this library. but unfortunately i don't see the command is not waiting for element to exist. but after changing to cy.findByTestId('') doesn't wait and also doesn't fail even after passing wrong element to it. I am i missing something here ? |
@Jmaharman @kentcdodds can you please respond to the above comment |
Currently on holiday, I will be back online next week.
… On 14 Oct 2019, at 05:15, Kamran Athar ***@***.***> wrote:
@Jmaharman @kentcdodds can you please respond to the above comment
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or unsubscribe.
|
Ping |
@kathar1223 I've tried to replicate this issue in the Cypress testing suite for this project and found no issue. It waits for the cypress default of 4 seconds and then returns the below error: I assume there is something else occurring here for the issue to arise for you. Can you explain your scenario in full? Are any of the preceeding interactions modifying the page in someway, e.g. previous command clicks a new link, which loads a new page A reproducible example would be appreciated. |
What:
Replaced the waitForElement functionality with the built in cypress retry-ability helper to:
Why:
How:
I've had to update src/index.js pretty rapidly to make this possible and while it doesn't break the API it is drastic.
Checklist:
While I feel that the code is complete, as well as having more tests now, it is drastically different from what it was previously and so I think a key contributor / project owner will need to look over it closely.