Test fails because AngularJS has not initialized in time - angularjs

I'm trying out TestCafe for an AngularJS (v1.6) application.
I have a button then when clicked, opens a modal (from UI bootstrap). This works fine when I try myself in Chrome.
<button class="btn" ng-click="open()">Open</button>
Our application requires user authentication, and the login page is not Angular-based. That phase of my test works fine.
However, when the actual test runs, it "clicks" the button but nothing happens.
I suspect, but can't prove, that it's clicked before AngularJS has properly initialized on the page.
With some research, I found the testcafe-angular-selectors project and a waitForAngular method but that appears to apply only to Angular2+.
import { Role, Selector } from 'testcafe';
const regularAccUser = Role('http://127.0.0.1:8080', async t => {
await t
.typeText('[name=username]', 'abc')
.typeText('[name=password]', '123')
.click('.btn-primary');
});
fixture`Characters Modal`;
test('modal title', async t => {
await t
.useRole(regularAccUser)
.navigateTo('http://127.0.0.1:8080/fake/page')
.click('.btn')
.expect(Selector('.modal-title').innerText).eql('Insert Symbol');
});
Adding .wait(1000) before the click solves the issue. It's not waiting for Angular to load. I'd rather not have waits in every test - is there some other technique I can use?

You can use TestCafe assertions as a mechanism to wait until an element is ready before acting on it.
A typical waiting mechanism would be:
const button = Selector('button.btn')
.with({visibilityCheck: true});
await t
.expect(button.exists) // wait until component is mounted in DOM
.ok({timeout: 10000}) // wait enough time
.hover(button) // move TestCafe cursor over the component
.expect(button.hasAttribute('disabled'))
.notOk({timeout: 10000}) // wait until the button is enabled
.click(button); // now we are sure the button is there and is clickable
This article may also help you in managing all those waiting mechanisms.

As you correctly mentioned, the waitForAngular method is intended for Angular only, not for AngularJS.
I recommend you create your own waitForAngularJS function and call it on the beforeEach hook and after the role was initialized.
In the simplest case, it can be implemented as follows:
function waitForAngularJS (t) {
await t.wait(1000);
}
fixture `App tests`
.page('page with angularjs')
.beforeEach(async t => {
await waitForAngularJS(t);
});
However, the use of the wait method is not a solid solution. I recommend you find a way to detect if AngularJS is loaded on a page on the client side. If it is possible, you can implement the waitForAngularJS method using the TestCafe ClientFunctions mechanism.
This post can be usefulĀ as well: How to check if angular is loaded correctly

Related

Socket.io doesn't close connection on linking with reacter-router-dom

As I have understood, Socket.io automatically disconnects on page refresh. However, when using react-router-dom, a disconnect message is not sent to the backend, how would one implement this? I don't think code is needed, however feel free to ask!
Try to handle the disconnection on window unload event in this way:
window.addEventListener('beforeunload', function (e) {
e.preventDefault();
e.returnValue = ' ';
});
window.addEventListener('unload', function (e) {
socket.emit('disconnection message', {data to be sent});
});
In that case you will have to handle it manually in your component using socket.disconnect(). react-router provides a SPA functionality and in that context, the page doesn't actually reload, otherwise you lose the current state and everything starts from scratch.

Cypress async issue

I have a situation in my test where I click a button and it makes an apollo graphQL call in my reactJS side.
createProductCategory({
variables: {
restaurantID: props.restaurant.id,
name: name
}
});
This is how it looks like in the ReactJS side. This createProductCategory method returns a promise which we wait for and continue with the rest of the code.
This works perfectly fine when I click on the button manually. I can see the graphQL call being made in the network tab and it works perfectly fine. However, if I try to get cypress to click on the button, I can see the button gets clicked, but the graphQL call never gets made. I just cannot understand why it doesn't work and what I am doing wrong.
Can someone please help me out with this. I tried putting wait statements everywhere but it just doesnt "wait" for the promise in the reactJS side to be resolved.
cy.contains("Save")
.click()
.then(() => {
cy.wait(2000)
});
cy.wait(2000)
Thanks for the help in advance.
From what you are describing you should have code that surrounds the button click and waits for the graphql call to complete like so
cy.server()
cy.route('/graphql').as('graphql')
cy.get('buton').contains('abc').click()
cy.wait('#graphql')
More info: alias waits

Cannot register Service Worker in React script

I am making a basic web app with ReactJS to create push notifications.
I want to let the user the possibility to register manually the service worker by clicking on a button. But nothings happens (no message of success in the console). Did I make a mistake somewhere?
In a standard js file, the code to register the service worker works (without the button though) , but not in the react file.
the code to register the service worker :
and the event listener on the button :
Thanks in advance !
The main issue I see is that you attach an EventListener to window that would execute on 'load'. Since this event is most likely already triggered since you can click on a button, the SW is not getting loaded.
Try removing the eventhandler part to directly call the register function.
It would then look something like this:
function registerSW() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('sw.js')
//...
}
}
Another issue I see is that you try to set state by directly assigning it. In react you shouldn't access state like that but rather call this.setState like this:
// ...
.then(swReg => {
console.log('SW registered');
this.setState({ swReg: swReg });
})
// ...

React Create Portal event listening

So I am trying to migrate an existing Portal implementation from the old unstable_renderSubtreeIntoContainer to the new portal implementation.
I have an issue though, the relevant code has the following functionality:
unstable_renderSubtreeIntoContainer(
this,
this.props.children,
this.portalElement,
() => {
if (this.props.isOpen) {
this.props.onRender(this.portalElement,
this.getTargetElement());
}
callback(); //runs this.props.open() if the update ran open
},
);
Some of the open/close logic could be simplified by wrapping the component to be rendered inside an object and the appropriate callbacks could be called from there. But it seems createPortal has no callback to allow you specify when a render has or hasn't taken place. Is there anyway to synchronously or asynchronously on a createPortal call has finished rendering?

Preventing page navigation inside a Backbone-driven SPA

The justification
In my BB app, I allow rapid input from users which gets queued & sent off periodically in the background to the server. The problem I currently have is if a user leaves the page they effectively discard any pending changes sitting in the queue.
So basically what I want to do is inform the user before they leave to give them the opportunity to wait for the changes to be saved rather than just exiting & discarding.
The nitty gritty
So for the general cases where the user refreshes or attempts to navigate to an external URL we can handle the onbeforeunload event. Where it becomes slightly tricky is when we are in the context of an SPA whereby switching between pages does not cause a page refresh.
My immediate thought was to use a global click event handler for all anchors and validate whether or not I want to allow the click, which would work for in-site link navigation. However, where this falls over is navigating via the browsers Back/Forward buttons.
I also had a look at Backbone.routefilter, which at first glance appeared to do exactly what I needed. However, using the simple case as described in the docs, the route was still being executed.
The question
How do we intercept navigation for all scenarios within a Backbone SPA?
Direct link navigation
Use a global event handler to capture all click events
$(document).on('click', 'a[href^="/"]', function (e) {
var href = $(e.currentTarget).attr('href');
e.preventDefault();
if (doSomeValidation()) {
router.navigate(href, { trigger: true });
}
});
Page refreshing / external URL navigation
Handle the onbeforeunload event on the window
$(window).on('beforeunload', function (e) {
if (!doSomeValidation()) {
return 'Leaving now will may result in data loss';
}
});
Browser back/forward button navigation
Behind the scenes Backbone.Router uses the Backbone.history which ultimately leverages the HTML5 pushstate API. Depending on what options you pass to Backbone.history.start, and what your browser is capable of, the API will hook into either the onhashchange event or the onpopstate event.
Delving into the source for Backbone.history.start it becomes apparent that regardless of whether you are using push state or not, the same event handler is used i.e. checkUrl.
if (this._hasPushState) {
addEventListener('popstate', this.checkUrl, false);
} else if (this._wantsHashChange && this._hasHashChange && !this.iframe) {
addEventListener('hashchange', this.checkUrl, false);
} else if (this._wantsHashChange) {
this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
}
Therefore, we can override this method & perform our validation in there
var originalCheckUrl = Backbone.history.checkUrl;
Backbone.history.checkUrl = function (e) {
if (doSomeValidation()) {
return originalCheckUrl.call(this, e);
} else {
// re-push the current page into the history (at this stage it's been popped)
window.history.pushState({}, document.title, Backbone.history.fragment);
// cancel the original event
return false;
}
};

Resources