Checking Blaze template has rendered in Cypress


#1

After a bit of investigation I decided to try create some end-to-end tests with Cypress. I’ve been pretty impressed although it’s taken me a while to get some basics working. A shout out to @florianbienefelt @fgm whose thread helped a lot.

As a basic starting point my intention was to simply walk through different parts of the app and see if there were any uncaught exceptions. A familiar problem here is that Meteor swallows all the exceptions in different places. I tried allowing some Blaze ones through and also patching Meteor._debug but there’s a few places inside Tracker that are going to be hard to patch without creating a local copy. I read also that Cypress wasn’t fool-proof in this regard anyway so thought I’d try something else.

My current thinking is to add a function to each template’s onRendered that does something like this:

    if (!window._templatesRendered) {
        window._templatesRendered = {};
    }
    window._templatesRendered[nameOfTemplate] = true;

And then in my test I can check to see if the template correctly rendered without having to test for the existence of a specific element on each page/form.

Cypress.Commands.add("getRendered", templateName => {
    cy.window().then(win => {
        return win._templatesRendered ? win._templatesRendered[templateName] : false;
    });
});

Allowing me to test

        cy.get("#general-save-button").click();
        cy.wait(1000); //would like to remove this
        cy.getRendered("myTemplate").then(result => {
            cy.expect(result).to.equal(true);
        });

My issue is that this only works if I add an arbitrary wait. The previous command isn’t a visit, but instead I’ve just submitted a form.

Are there more elegant ways around this ? I believe the command can be written in a retryable form but didn’t have any luck doing that.


#2

In the end we got something better working modifying the DOM from the Meteor end and checking for it in Cypress. So in our client startup we added

Meteor.startup(function() {
    if (Environment.isTesting()) {
        _.forOwn(Template, (value, key) => {
            if (value && Blaze.isTemplate(value)) {
                const name = value.viewName.substring(9);
                if (!name.startsWith("af") && !name.startsWith("_")) {
                    const divId = `${name}Rendered`;
                    const body = document.getElementsByTagName("body")[0];
                    value.onRendered(function() {
                        let el = document.getElementById(divId);
                        if (!el) {
                            el = document.createElement("div");
                            el.setAttribute("id", divId);
                            //el.setAttribute("data-test", true);
                            body.appendChild(el);
                        }
                    });
                    value.onDestroyed(function() {
                        const el = document.getElementById(divId);
                        if (el) {
                            body.removeChild(el);
                        }
                    });
                }
            }
        });
    }
});

Note: We set our own testing flag as we don’t want to run Meteor in test mode for these tests.

Then in cypress you can simply do

cy.get("div#myTemplateNameRendered");

#3

Pardon my ignorance, I’ve never used Cypress, but I thought it had it’s own “wait for stuff to happen” mechanism, where it would wait for a reasonable amount of time for the expected things to appear or happen? Is this sort of sorcery really necessary? Does it apply only to Blaze for some reason?


#4

Yes, it does, but they are mainly for waiting for things to appear in the DOM, or a route to change or a response to a http request. You can write custom commands that can wait on arbitrary conditions but I’ve not reached that level of familiarity yet and my first idea was as much of a hack as the second anyway :laughing: .
If we had a way to better control exceptions on the client this would all be moot as the tests would fail as soon as an exception fires. It’s not just Blaze catching them, but Tracker and also Meteor._debug. (I’ve had success switching off everything except Tracker). So the above hack will basically add a div to the DOM saying a particular template has rendered. I’m guessing it’s pretty specific to Blaze, depending on how the Meteor integrations work with React and Vue. I’ve not used them so can’t really say.
I’d recommend having a quick read of the docs on Cypress, they’re very well written and I’ve enjoyed the ride so far.


#5

I wouldn’t guess at why you would want to wait for a specific template to render. In my mind, the whole point of cypress-like testing tool is to test from the perspective of the user, who doesn’t know about templates. Cypress allows you to describe the user journey, and interact with elements as if you were a user. In our tests, we try our best to not rely on implementation specifics, but on the actual content that shows up, and let cypress intelligently do the correct waiting.

So if I am waiting for a certain item to show up in a list, I only navigate to the page, and “expect” an item with the desired description to appear. Cypress will then wait for it to happen, and move on from there.


#6

You’re right, and we also want those kind of tests but they take a lot more time to write. I just completed a pretty extensive refactor and I wanted to quickly fly through the app and see that everything loads ok. Ideally blaze etc would not catch all the exceptions and I could see via that that there was a problem, but the next best thing is to check we’ve made it to Blaze.onRendered. I could go through and check for a specific element in each one like you say but that also takes a bit more time. Currently with an array of path/template elements I can dynamically generate a test for each one. It’s far from ideal but it’s a nice quick start.


#7

Hmm my concern with that would be that even though the template “technically” rendered, what you wanted might still not have shown up on the screen. The only way to check for that is to check for the existence of the desired object. Hacking around to get to the inner workings of blaze would seem to me more time consuming than just doing cy.contains("Text I am looking for on the screen"). My tests are just a series of cy.click on buttons and looking for text, which was really easy to write. But I am not here to question whatever you are doing, but I am a bit curious though.


#8

My main motivation is speed :slight_smile: My idea is to add better and more thorough tests later but you can catch quite a few things by seeing if a template renders and after a bit of learning about Cypress I’ve written 60+ tests with very few lines of code. The Blaze part was quick - the only hacky bit was getting all the templates, there really should be an api call for that.

This is a simplified version of what we have for various sections of our project.

describe("AreaName", () => {
    sections.forEach(section => {
        it(`check ${section.name}`, function() {
            cy.visit(section.url).get(`div#${section.template}Rendered`);
        });
    });
});

Also, I would be careful looking for text in your tests, that’s a little brittle, probably better to check for ids or data- elements.