-
Notifications
You must be signed in to change notification settings - Fork 92
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
Testing best practice guides? waitFor is inoptimal #176
Comments
@snewcomer This issue is kinda big problem for us. Mind having a look? |
@lolmaus Interesting ideas. On one hand, tests that invoke didEnterViewport usually result in some operation (whether sync/async) and I am able to assert the successful operation. So |
That's not the case for me. :( The problem is that the test resumes before So in our tests we have to do this: When I visit /feed
And pause for 2000 ms
Then there should be 1 Item in the Feed Items are fetched via
As a result, the test asserts for the number of items in the feed before PS Note that without the |
So something like this doesn't work?
Even |
It does, but it's an ugly hack. Instead of requiring an ad-hoc check to be crafted, unique for every case, the addon should provide a general way to wait for Apparently, |
I think we need to first avoid referring to these as "ugly hacks". They are definitely not as long as the test is deterministic. These are simply ways of asserting the logic as a result of entering the viewport is in the DOM. Now if we await for |
I have explained in the top post why I consider There are situations when using a tip of a knife is a reasonable way of unscrewing a screw. I mean, when it's urgent and you have no screwdriver around. But "sometimes reasonable" does not prevent it from being an ugly trick that many people would like to avoid as much as possible.
Well, why do I have to hardcode that there are two elements on the page? Tomorrow the number may change, and you'll have to update more than one source of truth. I could do What if the component had already rendered, queried and displayed the elements, and I'm triggering The
In order to await for a method:
Neither is true for I see two ways of solving the problem:
Before thinking about the implementation, we must decide on an API to use in integration and acceptance tests. Since there may be more than one in-viewport element on the page, we need to somehow reference which one we're waiting for. Something like: import { waitForElementToEnterViewport, waitForElementToLeaveViewport } from 'ember-in-viewport/test-support';
await waitForElementToEnterViewport('foo');
await waitForElementToLeaveViewport('bar'); Note that those test helpers should NOT be accepting CSS selectors. Using selectors in acceptance tests is an extremely bad practice. In a similar situation he ember-lifeline addon by @rwjblue is using a token shared between a component definition and a test: export const POLL_TOKEN = 'zee token';
export default Component.extend({
init() {
this._super(...arguments);
pollTask(this, 'updateTime', POLL_TOKEN);
},
}) import { POLL_TOKEN } from 'my-app/components/update-time';
module('updating-time', function(hooks) {
test('updating-time updates', async function(assert) {
await pollTaskFor(POLL_TOKEN);
});
}); @snewcomer, do you think we could do a similar thing with |
I think I have lost the motivation to put in the work here. If you would like to make a PR, I'd be happy to review and merge. |
OK, but before I proceed to coding, I would like to agree upon the design and resolve some questions. As I said above, I'm thinking about using custom tokens to reference components from tests. A token could be set on component like this: export const MY_JUMPY_COMPONENT_TOKEN = 'foo'; // Could also use `{}` or `Symbol()` as a unique token
export default class MyJumpyComponent extends Component {
viewportToken = MY_JUMPY_COMPONENT_TOKEN;
} Note: using the Then it can be used in tests like this: import { waitForElementToEnterViewport } from 'ember-in-viewport/test-support';
import { MY_JUMPY_COMPONENT_TOKEN } from 'my-app/components/my-jumpy-component';
module('foo', function(hooks) {
test('bar', async function(assert) {
await waitForElementToEnterViewport(MY_JUMPY_COMPONENT_TOKEN);
});
}); The above is all the new API surface area. Under the hood, I'm imagining the export async function waitForElementToEnterViewport(token) {
await new Promise((resolve) => {
SOME_KIND_OF_REGISTRY.waitForElementToEnterViewport('token', resolve);
});
return settled();
} As you can see, it passes the token and a promise The in-viewport mixin would trigger the callback on the registry during didEnterViewport() {
if (this.viewportToken !== undefined) {
SOME_KIND_OF_REGISTRY.triggerEnterViewport(this.viewportToken);
}
} Note that it will require users to add The registry could be implemented like this:
Alternatively, the registry could be implemented as a simple array, instantiated and exported on the root level of a ES module. In that case all the logic for manipulating callbacks would need to go into the mixin and/or service. Open questions:
|
@snewcomer Bump. |
Honestly I'm not sure if the juice is worth the squeeze. A lot of code to manage. I'll let you make that decision though. Otherwise not a whole lot to comment on other than thinking about what trade offs we are bringing onto this project. |
@lolmaus did you ever come up with a good solution here? Trying to write acceptance test for a client-side pagination feature and basically can't get it to work due to nature of scrolling/waiting in the test etc Although not ideal, I ended up finding that ember-concurrency's
https://github.com/machty/ember-concurrency/blob/master/addon/utils.js#L160 |
@erichonkanen Nah, I ended up doing this and I'm not happy about it: await waitFor('[data-test-loading]', {timeoutMessage: 'loading indicator should eventually appear'}); If there's a bug and the loading indicator would not appear, then the test would keep running until the testing suite times out. await timeout(50); This is also not good. One day it will take more than 50ms to do its thing, and your CI will crash with a false negative. Extra waiting time and false negatives are not a big deal. They are a minor nuisance and not a sacrifice of guarantees. So this feature request is not about fixing something that's broken in the addon. It's about getting rid of the sense of discomfort from using pessimal techniques in tests. Here's another suggestion of how it could look like from the dev's perspective. The addon could provide a import { find, findAll } from '@ember/test-helpers';
import { scrollTop } from 'ember-in-viewport/test-support';
// ...
assert.equal(findAll('[data-test-slot-item]').length, 10);
await scrollTop(find('[data-test-slots]'), 10000);
assert.equal(findAll('[data-test-slot-item]').length, 20); From the addon maintainer's perspective, it's a lot less code to manage than with my suggestion to use the |
@lolmaus I like that approach! I wonder how hard to implement? And definitely agree, I just had to get something working for the time being and so went with sub-optimal solution. A scrollTop helper would be really nice (possibly even better if it was part of ember-test-helpers) |
@lolmaus Something I forgot to ask a while back. Do you think a token that is shared between the service and test land that awaits for |
I am willing to explore this if you would like. However, I'm still curious if a token will tell us "done" state (that is asserting a list length of x items). Do you have any thoughts? |
Uhm, no idea. I guess it only looks at the root element of the component, if I understood the question correctly. Also, the @snewcomer Are you sure you want to go the path of a global registry and tokens? My last suggestion seems easier both for app developers and addon maintainers. Though I'm not sure how to exactly implement it. |
I'm not sure. The trouble I see is some helper still may not tell you if the next list is actually painted on the screen, thus a dev's next assertion may be too early. |
@snewcomer Sorry, I'm not following what you mean exactly. Can you please elaborate with an example? 🙏 |
Hi!
I've tried integration-testing my component that relies on
ember-in-viewport
and found the tests to be flakey: randomly failing or passing. After pulling my hair as I spent hours debugging in wrong directions, I've realized that it'sember-in-viewport
not playing along withawait settled()
.I've looked into
ember-in-viewport
own tests and found that they rely onwaitFor(selector)
. I refactored my tests to use that and it seemed to resolve the flakiness.But I find
waitFor(selector)
in tests to be a bad practice, for a number of reasons:didEnterViewport
/didExitViewport
is unknown or irrelevant (out of the current test's scope).didEnterViewport
/didExitViewport
don't cause anything to appear in DOM at all.didEnterViewport
/didExitViewport
should remove something from the page, and there's nowaitToDisappear
helper.Instead of relying on
waitFor(selector)
, there should be a direct way of waiting for thedidEnterViewport
/didExitViewport
to trigger.In other words, there should be a way to rewrite this to be reliable without relying on some third-party element appearing in the DOM:
Maybe
ember-in-viewport
could provide its own helper to be awaited. Though I struggle to come up with specifics how it could work. My only idea is to rely on some token or custom identifier similar to how ember-lifeline works:await pollTaskFor('my-task-name')
.CC @simonihmig
The text was updated successfully, but these errors were encountered: