The below block successfully executes when tr is found, but sometimes it will be empty and no tr. How to handle the exception if tr not found?
cy.get('tbody.ant-table-tbody tr').then((rows) => {
// success
});
There is no catch block chained here.
cy.get() will always fail if it cannot find the element (unless you run cy.get('foo').should('not.exist'), but that can't be used in combination with if/else.)
You can instead use JQuery in combination with Cypress to check for an element's existence without failing a test. In this case, we'll yield the result from the parent element, and search it with JQuery.
cy.get('tbody.ant-table-tbody').then(($parent) => {
if ($parent.find('tr').length) { // check if the length is > 0
// Code to run if tr is found
} else {
// Code to run if tr is not found
}
});
All of that being said, I would push back on needing to use something like this. Tests should be deterministic, and you should know before the test runs if tr exists or not. Consider the ways that you can pre-determine before the test runs (maybe seeding a database, or intercepting a network request). Also consider, what happens if tr never exists -- that functionality is never being tested. Or, if tr always exists, then the functionality of when it doesn't exist is never being tested.
There is an add-on package cypress-if written by former lead engineer at Cypress that addressed this problem.
The syntax is simple, just chain .if() and eveything after it only runs if the element is found.
cy.get('tbody.ant-table-tbody tr')
.if() // checks for 4 seconds
.then((rows) => {
// success
})
.else()
.then(() => {
//no rows
})
.finally(() => {
// either way
})
It will suppress the error that usually occurs when the element is missing.
It will use Cypress retry for asynchronous element loading, will will not happen if you use the jQuery method.
Don't do this if rows are fetched asynchronously
cy.get('tbody.ant-table-tbody').then(($parent) => {
// This will evaluate immediately and fail if the row is still loading
if ($parent.find('tr').length) {
...
But cypress-if is broken in Cypress v12
Cypress v12 split cypress commands into "commands" and "queries" (cy.get() is a query) and blocked overwriting of queries. This breaks a whole lot of prior code, including cypress-if.
Expect the change to be reversed before long.
Please see issue Can we please overwrite query commands #25078
Related
I am testing a web app that renders some of its errors as HTML and gives no other indication that a problem occurred. After every click, I would like to see if a .error element exists.
I know I could manually add this after every click, but it's going to obfuscate my test and it will be easy to forget some instances. Is there a way to tell testcafe that a certain condition should make the test fail, even if I'm not explicitly checking for it?
I did something like this:
const scriptContent = `
window.addEventListener('click', function () {
if($('.error').length > 0) {
throw new Error($('.error').text());
}
});
`;
fixture`Main test`
.page`../../dist/index.html`.clientScripts(
{ content: scriptContent }
);
This injects a script onto the page I am testing. After every click, it uses jQuery to see if an error class exists. If it does, it throws an error on the page I'm testing. Testcafe reports the message of that error.
I'm hoping there's a better way.
Is there a way to determine if there is an empty event loop?
For example,
// Example 1
setInterval(() => console.log("hi"), 1000);
// event loop is not empty
// Example 2
console.log("hi");
// event loop is now empty
Based on your comment:
I want to exit a process once the event loop is empty
Node.js got you covered. Node process will exit automatically when the event-loop is empty.
If you opened this question because your tests (using Jest) were hanged for some reason, it means that the event loop was not empty at the end of all the afterEach and afterAll hooks, so if you are running a single test-file, Jest will usally hang. If you are running multiple tests files, jest will print an error which indicated that Jest forced to (not-gracefully) close the node-process of one of the test-files.
To fix that, make sure you don't forget to await to any promise, close all conections and clear all timeouts.
In the Meteor forums I read that it is suggested to put Meteor.logoutOtherClients inside Accounts.onLogin(). Although this works, there is a problem to it, and that is the Accounts.onLogin() gets called multiple times when there are multiple TABS (not browsers) opened. Is this the expected output?
Here is my code below:
Accounts.onLogin(() => {
console.log('onLogin called')
Meteor.logoutOtherClients((error) => {
if (error) {
console.log(`error: ${error.error}`)
}
})
// Some Meteor Method calls here
alert('Welcome User!')
})
Another problem is that I got method calls in the same Accounts.onLogin() block and it gets called every time.
meteor#1.4.2.6
accounts-base#1.2.17
Question
How should I prevent this infinite calls from happening?
If I can't prevent this, where should I dispatch method calls when user logs in? Because obviously if I put it inside this code block it causes the dispatches to get called infinitely and that alert gets fired infinitely.
You can also see the details reported here: https://github.com/meteor/meteor/issues/8669
This is a confirmed bug #8669. So my workaround is I created a manual token for the user instead of using the default from accounts-base. I also handled the checking manually so basically getting rid of "magic" Meteor offers.
I have to listen to an event and check that a particular value is set after this event. For example:
eventEmitter.on("event", () => {
expect(thing).to.equal(otherThing);
done();
});
This all works fine if thing equals otherThing which it should -- however, if there is something wrong and the test fails (the two are not equal) or there is an error in this callback (I wrote otherTing instead of otherThing for example) the error is swallowed and done is not called.
I understand that the issue here is that the it spec code and the event callback run in separate domains so the errors in the latter do not propagate to the former.
What is the proper way to handle this situation in tests?
I noticed that qUnit doesn't give any notice when an exception happens in a later part of the test. For example, running this in a test():
stop();
function myfun(ed) {
console.log('resumed');
start(); //Resume qunit
ok(1,'entered qunit again');
ok(ed.getContent()== 'expected content') // < causes exception, no getContent() yet.
}
R.tinymce.onAddEditor.add(myfun)
in an inner iframe on the page will cause an exception (TypeError: ed.getContent is not a function),
but nothing in Qunit status area tells this. I see 0 failures.
(R being the inner iframe, using technique here: http://www.mattevanoff.com/2011/01/unit-testing-jquery-w-qunit/) Would I be correct in assuming this isn't the best way to go for testing sequences of UI interaction that cause certain results? Is it always better to use something like selenium, even for some mostly-javascript oriented frontend web-app tests?
As a side note, the Firefox console shows the console.log below the exception here, even though it happened first... why?
If you look into qUnit source code, there are two mechanisms handling exceptions. One is controlled by config.notrycatch setting and will wrap test setup, execution and teardown in try..catch blocks. This approach won't help much with exceptions thrown by asynchronous tests however, qUnit isn't the caller there. This is why there is an additional window.onerror handler controlled by Test.ignoreGlobalErrors setting. Both settings are false by default so that both kinds of exceptions are caught. In fact, the following code (essentially same as yours but without TinyMCE-specific parts) produces the expected results for me:
test("foo", function()
{
stop();
function myfun(ed)
{
start();
ok(1, 'entered qunit again');
throw "bar";
}
setTimeout(myfun, 1000);
});
I first see a passed tests with the message "entered qunit again" and then a failed one with the message: "uncaught exception: bar." As to why this doesn't work for you, I can see the following options:
Your qUnit copy is more than two years old, before qUnit issue 134 was fixed and a global exception handler added.
Your code is changing Test.ignoreGlobalErrors setting (unlikely).
There is an existing window.onerror handler that returns true and thus tells qUnit that the error has been handled. I checked whether TinyMCE adds one by default but it doesn't look like it does.
TinyMCE catches errors in event handlers when calling them. This is the logical thing to do when dealing with multiple callbacks, the usual approach is something like this:
for (var i = 0; i < callbacks.length; i++)
{
try
{
callbacks[i]();
}
catch (e)
{
console.error(e);
}
}
By redirecting all exceptions to console.error this makes sure that exceptions are still reported while all callbacks will be called even if one of them throws an exception. However, since the exception is handled jQuery can no longer catch it. Again, I checked whether TinyMCE implements this pattern - it doesn't look like it.
Update: Turns out there is a fifth option that I didn't think of: the exception is fired inside a frame and qUnit didn't set up its global error handler there (already because tracking frame creation is non-trivial, a new frame can be created any time). This should be easily fixed by adding the following code to the frame:
window.onerror = function()
{
if (parent.onerror)
{
// Forward the call to the parent frame
return parent.onerror.apply(parent, arguments);
}
else
return false;
}
Concerning your side-note: the console object doesn't guarantee you any specific order in which messages appear. In fact, the code console.log("foo");throw "bar"; also shows the exception first, followed by the log message. This indicates that log messages are queued and handled delayed, probably for performance reasons. But you would need to look into the implementation of the console object in Firefox to be certain - this is an implementation detail.