Skip to content

First stab at adding support for tests timeout #80

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

Open
wants to merge 12 commits into
base: main
Choose a base branch
from

Conversation

DmitrySharabin
Copy link
Collaborator

No description provided.

Copy link

netlify bot commented Feb 11, 2025

Deploy Preview for h-test ready!

Name Link
🔨 Latest commit bd83528
🔍 Latest deploy log https://app.netlify.com/sites/h-test/deploys/67b73bc7e73d960008801e15
😎 Deploy Preview https://deploy-preview-80--h-test.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

@DmitrySharabin DmitrySharabin force-pushed the tests-timeout branch 2 times, most recently from d7a7d12 to 0b0123d Compare February 11, 2025 21:43
Copy link
Collaborator

@LeaVerou LeaVerou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait why are we adding another option instead of fixing maxTime?

@DmitrySharabin
Copy link
Collaborator Author

Wait why are we adding another option instead of fixing maxTime?

After thinking about this a bit more, I agree that we probably don't need another global option.

I see some use cases that should be considered, though.

  • If a test has expect or throws (i.e., it's not a time-based test), we can use maxTime as a timeout time (since it doesn't have any other meaning in this case).
  • However, if we have a time-based test, we might interpret maxTime/maxTimeAsync wrong: it shouldn't be considered a timeout time. The test might take more than maxTime/maxTimeAsync to run. So, it should be considered failing but not because it timed out. That means we need another way to set a timeout time for that case.

The more I think about it, the more I lean towards not re-purposing maxTime to set timeout time, and we need a separate property for it. What do you think?

@LeaVerou
Copy link
Collaborator

Wait why are we adding another option instead of fixing maxTime?

After thinking about this a bit more, I agree that we probably don't need another global option.

I see some use cases that should be considered, though.

  • If a test has expect or throws (i.e., it's not a time-based test), we can use maxTime as a timeout time (since it doesn't have any other meaning in this case).
  • However, if we have a time-based test, we might interpret maxTime/maxTimeAsync wrong: it shouldn't be considered a timeout time. The test might take more than maxTime/maxTimeAsync to run. So, it should be considered failing but not because it timed out. That means we need another way to set a timeout time for that case.

The more I think about it, the more I lean towards not re-purposing maxTime to set timeout time, and we need a separate property for it. What do you think?

I don't understand. I would expect that if a test has maxTime and no throws or expect that it would fail if it exceeds maxTime. What actually happens?

@DmitrySharabin
Copy link
Collaborator Author

I don't understand. I would expect that if a test has maxTime and no throws or expect that it would fail if it exceeds maxTime. What actually happens?

For now, if a test has maxTime/maxTimeAsync, it would fail if it exceeds the specified time if only the test has actually finished running. But if something prevents it from being finished (e.g., we await some promise that will never be fulfilled), the test won't be evaluated at all because this line will never be reached:

this.evaluate();

Now I see the actual bug that should be fixed and why it didn't work when I tried limiting the time for test execution. Hm. Let me update the code to address it.

@DmitrySharabin
Copy link
Collaborator Author

It turned out that setting a timeout via setTimeout is not enough: when the specified time runs out, we don't break the test execution, but should. After some research, I thought that abortable API is our go-to solution. I implemented it by making the interceptConsole() function abortable (we execute tests in the body of this function).

This is my first time working with AbortSignal, so I might have missed something or made it wrong. I'm open to any feedback.

@LeaVerou
Copy link
Collaborator

It turned out that setting a timeout via setTimeout is not enough: when the specified time runs out, we don't break the test execution, but should. After some research, I thought that abortable API is our go-to solution. I implemented it by making the interceptConsole() function abortable (we execute tests in the body of this function).

This is my first time working with AbortSignal, so I might have missed something or made it wrong. I'm open to any feedback.

At first glance, this seems like a very complicated solution to this problem. If the issue is that we need to externally reject the promise, we can perhaps wrap the promise and do something like this or use Promise.withResolvers() (though the former is more well supported, and not much more code)

@DmitrySharabin
Copy link
Collaborator Author

we can perhaps wrap the promise and do something like this

Oh, that's a very nice idea. Let me try to use it. By the way, I started to recognize this pattern in our projects. It's time to internalize it. 🙂

All tests should be result-based
We need to be able to abort (for real) the test execution on time out.
We just need to ensure that only one promise is resolved by the required time and the properties are set.
@DmitrySharabin DmitrySharabin force-pushed the tests-timeout branch 2 times, most recently from 0cb67fa to bd83528 Compare February 20, 2025 14:27
@DmitrySharabin
Copy link
Collaborator Author

After some more trials and errors, I think I could find exactly where set setTimeout should be used to support the logic of aborting test execution after time out (and setting all the required properties)—no need to externally resolve/reject a promise and (moreover) in using AbortSignal.

Could you please give this PR another look?

@DmitrySharabin
Copy link
Collaborator Author

After some more trials and errors, I think I could find exactly where set setTimeout should be used to support the logic of aborting test execution after time out (and setting all the required properties)—no need to externally resolve/reject a promise and (moreover) in using AbortSignal.

It's still not enough. 🤦‍♂️ Even though a promise resolved on timeout might have precedence, the promise running the test is not aborted. When it is, it updates some properties of the test that should not be updated, such as this.timeTaken. We can see this in the summary after all the tests have run.

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants