How to assert run loop DOM changes inside an Ember test - testing

I am writing test for Ember app written in Ember 1.6.
Inside a controller I have a function executed upon promise success:
var me = this;
function onSuccess(result) {
printSuccessMessage();
Ember.RSVP.all(promises).then(function(value) {
Ember.run.later(this, function() {
clearMessages();
}, 5000);
});
}
Then, inside the test, I am trying to assert that the success message appears:
fillIn('#MyInputField', 'Some text');
click('#MyButton');
andThen(function() {
strictEqual(find('[data-output="info-message"]').text().trim().indexOf('Done!') >= 0, true, 'Expected success message!');
});
But the problem is, that after the click, andThen is waiting for a run loop to finish. So after this click, andThen waits 5 seconds and then executes assertions.
In that moment clearMessages() is already executed, the message div is cleared, and the test fails.
Any idea how to assert that this message has certain text?

If you are willing to have a condition in your code, that checks whether or not Ember is in testing mode, you can toggle the Ember.testing value in your test, and then clear or not clear the message in your controller, based on that value. Your tests can then assert that the message is cleared in one instance, and showing in the other.
In the controller's onSuccess call, observe the Ember.testing condition:
onSuccess(message) {
this.printSuccessMessage(message);
if (Ember.testing) { // <-- HERE
// during testing
return; // don't clear the message and assert that it's there
} else {
// during dev, live in production, or Ember.testing === false
this.clearMessages(); // clear the message, and assert that it's gone
}
},
In the acceptance test for setting the message, since Ember.testing is true by default, the controller will not clear the message, and the following test will succeed:
test('setting the message', function(assert) {
visit('/messages');
fillIn('input.text-input', 'Some text');
click('button.clicker');
// while Ember.testing is `true` (default), do not remove message
andThen(() => {
assert.equal(find('div.info-message').text(),
'Done!',
'The message was set properly.');
});
});
In the test that follows, observe the toggling of false for Ember.testing, which will "emulate" live dev or production conditions for the controller. The controller will clear the message, as normal, and this test will also succeed:
test('clearing the message', function(assert) {
visit('/messages');
fillIn('input.text-input', 'Some text');
andThen(() => {
Ember.testing = false;
});
click('button.clicker');
// while Ember.testing is `false`, remove message, as normal, as in dev or prod
andThen(() => {
assert.equal(find('div.info-message').text(),
'',
'The message has been cleared.');
});
// reset Ember.testing to its default
andThen(() => {
Ember.testing = true;
});
});
Please note, Ember.testing is reset to its default value of true as soon as the false condition is no longer needed. This is important because Ember run loop behavior is different during testing by design.
In this solution, some code has been refactored, to isolate concerns and make it easier to unit test. Here's an Ember Twiddle to demonstrate, which was inspired, in part, by this article on Medium.

Related

how to make cypress wait for a async search with multiple result complete without causing the test to fail

I have a typical search where the user types some text in an input, async work is done and a table with the results is properly updated.
I have tests that must wait for this search step and then assert business rules regarding the results, like if the table records are eligible for edit.
Every time I ran a complete test battery (something like 80 test files), one or two of the tests involving that search inevitably fail. But if immediately after that, I run the same test alone, the test passes. It's excruciating and makes e2e testing in CI/CD pointless for the project.
I've read the Cypress documentation about flaky tests and searched questions in StackOverflow and GitHub with only complete failure. It's a drama.
Here is one of the tests:
import { searchList } from '../helpers';
import { createFluxoIniciado, randomFluxoNome } from './common';
import { fluxoSelectors } from './selectors';
describe('fluxos finish', () => {
it('can manually finish a fluxo INICIADO', () => {
// feed data to be searched
const fluxoNome = randomFluxoNome();
createFluxoIniciado({ fluxoNome });
// search
searchList(fluxoNome);
// do something with search results
fluxoSelectors.fluxos.view().click();
fluxoSelectors.finish().click();
fluxoSelectors.confirm().click();
// serach again
searchList(fluxoNome);
cy.contains('FINALIZADO');
});
});
The code in searchList is where trouble emerge sometimes. It uses the callback strategy recommended here. The code attempts to cause retries if not all rows have the searched text.
export function searchList (text) {
cy.get('#searchText')
.scrollIntoView()
.type(text)
.blur();
cy.get('tbody tr').should($trs => {
$trs.each((i, $tr) => {
expect($tr).to.contain(text);
});
}, { timeout: 15000 });
}
Here is an example of a test failure inside a run all test execution:
The problem is obviously caused by the async fetch between .blur() and testing the rows.
You are correctly trying to use Cypress retry with .should(callback), but if the callback is complex or there are multiple steps it may not retry the element that is changing (the table rows).
Ideally you want to keep the cy.get(...).should(...) as simple as possible, and start by testing that the table loading has completed.
// wait for expected number of rows
cy.get('tbody tr', {timeout: 15000}).should('have.length', 5)
cy.get('tbody tr').each($tr => {
expect($tr).to.contain(text);
})
But you have a randomizer there, so maybe it's not possible to test explicitly the row count.
Another approach, test the whole table for text (.contains() checks child text also)
// wait for text to appear somewhere
cy.get('tbody tr', {timeout: 15000}).should('contain', text)
cy.get('tbody tr').each($tr => {
expect($tr).to.contain(text);
})
You can also add an intercept between start and end of api call
export function searchList (text) {
cy.intercept('search/api/endpoint').as('search')
cy.get('#searchText')
.scrollIntoView()
.type(text)
.blur();
cy.wait('#search') // wait for api response
cy.get('tbody tr', {timeout: 15000}).should('contain', text)
cy.get('tbody tr').each($tr => {
expect($tr).to.contain(text);
})
}
I just noticed you have the {timeout} option on .should(), but that's the wrong place,
see Timeouts
cy.get('input', { timeout: 10000 }).should('have.value', '10')
// timeout here will be passed down to the '.should()'
// and it will retry for up to 10 secs
This may be successful
cy.get('tbody tr', { timeout: 15000 })
.should($trs => {
$trs.each((i, $tr) => {
expect($tr).to.contain(text);
});
})

Is it possible to have several pm.test() inside a pm.test()

Is it possible to have several pm.test() inside a pm.test()? In such a way that if any of the inner pm.test() fails, the outer pm.test() will also fail. And if no inner pm.test() fails, the outer pm.test() passes.
My requirement is similar to how pm.expect() works. But i want to use pm.test() instead so i can the status of each test in the Test Results tab.
My code is below.
var response = pm.response.json()
pm.test("Test scenario 1", ()=> {
pm.test("Checking response code", ()=> {
pm.expect(response.code).to.eql(200);
});
pm.test("Checking response message", ()=> {
pm.expect(response.message).to.eql('OK');
});
//more pm test
});
Thanks a lot.
pm.test("Test scenario 1", ()=> {
pm.test("Checking response code", ()=> {
pm.expect(pm.response.code).to.eql(200);
});
pm.test("Checking response message", ()=> {
pm.expect(pm.response.status).to.eql('5OK');
});
//more pm test
});
you can have but the output will show this as three tests only and result of internal test won't affect the out side abstraction
so in short you can but it won't give the behavior as you except
By implementing some counter I was able to achieve what I want. I incremented the counter at the end of the inner pm.test(). If the pm.expect() fails, the counter is not incremented.
When all inner pm.test() are done, I compare the counter+1 with pm.test.index(). If they are equal, I do nothing (which basically passes the outer pm.test()). If they are not equal, I throw pm.expect.fail().
Note: pm.test.index() returns the number of tests executed at that point of time.
pm.test("To test able to do that", () => {
let numberOfPassedTest = 0;
pm.test("This test will pass", () => {
//put assertion here
pm.expect(200).to.eql(200)
numberOfPassedTest += 1;
});
pm.test("This test will fail", () => {
pm.expect(200).to.eql(201)
numberOfPassedTest += 1; // this line is not executed because the previous line (pm.expect()) failed.
});
if(pm.test.index() === numberOfPassedTest+1){
//do nothing. meaning no test failed and the whole pm.test() will be passed
} else {
pm.expect.fail("At least one of the tests failed. So this test case is marked as failed.");
}
});
Sample result with failure:
enter image description here

It is so slow when a callback function be called repeatedly in react native app

It will have a callback function called repeatedly to when I start a download task. But It will be so slow that it can't give feedback when touch a button.
Fileio.downloadFile(downloadData[downloadFileIndex].uri, '1.jpg',this.progressFunc.bind(this)).then((DownloadResult)=> {
if (DownloadResult.statusCode == 200) {
let nextDownloadFileIndex = this.props.downloadFileIndex + 1;
this.props.dispatch(DOWNLOADED_A_FILE({
downloadFileIndex:nextDownloadFileIndex,
progressNum:0
}))
}
}).catch((error)=> {
console.log(error)
})
This is my code and the callback function are as be folllowed
progressFunc(DownloadBeginCallbackResult) {
let progressNum = DownloadBeginCallbackResult.bytesWritten / DownloadBeginCallbackResult.contentLength;
if(progressNum<=0.99){
this.props.dispatch(DOWNLOADING_A_FILE({
progressNum:progressNum,
jobId:this.props.jobId
}));
}else{
this.props.dispatch(DOWNLOADING_A_FILE({
progressNum:0,
jobId:this.props.jobId
}));
}
}
I mean I can't get feedback immediately when I touch button. I think that it is because I have a callback function called repeatedly. so js can't handle so many tasks;
It does sound like JS thread is busy doing the request and not able to communicate back to UI thread. One thing you can try is to wrap you on press handler in an InteractionManager.runAfterInteractions(() => ...)
See https://facebook.github.io/react-native/docs/interactionmanager.html

backbone view in router lost after creation

When I try to associate my router's public variable this.currentView to a newly created view, the view gets lost, the public variable is null instead of containing the newly created view.
var self=this;
var watchListsCollection = new WatchlistCollection;
watchListsCollection.url = "watchlists";
user.fetch().done(function() {
watchListsCollection.fetch().done(function () {
loggedUser.fetch().done(function () {
self.currentView = new UserView(user, watchListsCollection,loggedUser);
});
});
});
alert(this.currentView); //null
The fetch() calls you do are firing asynchronous AJAX requests, meaning the code in your done handlers are not going to be executed untill the server calls return. Once you've executed user.fetch() the browser will fire off a request and then continue running your program and alert this.currentView without waiting for the requests to finish.
The sequence of events is basically going to be
call user.fetch()
alert this.currentView
call watchListsCollection.fetch()
call loggedUser.fetch()
set the value of self.currentView
You will not be able to see the value of your currentView before the last server request have completed.
If you change your code to
var self=this;
var watchListsCollection = new WatchlistCollection;
watchListsCollection.url = "watchlists";
user.fetch().done(function() {
watchListsCollection.fetch().done(function () {
loggedUser.fetch().done(function () {
self.currentView = new UserView(user, watchListsCollection,loggedUser);
alertCurrentView();
});
});
});
function alertCurrentView() {
alert(this.currentView);
}
You should see the correct value displayed. Now, depending on what you intend to use your this.currentView for that might or might not let you fix whatever issue you have, but there's no way you're not going to have to wait for all the requests to complete before it's available. If you need to do something with it straight away you should create your UserView immediately and move the fetch() calls into that view's initialize().
fetch() is asynchronous, but you check your variable right after you've started your task. Probably these tasks, as they supposed to be just reads, should be run in parallel. And forget making a copy of this, try _.bind instead according to the Airbnb styleguide: https://github.com/airbnb/javascript
var tasks = [];
tasks.push(user.fetch());
tasks.push(watchListsCollection.fetch());
tasks.push(loggedUser.fetch());
Promise.all(tasks).then(_.bind(function() {
this.currentView = new UserView(user, watchListsCollection, loggedUser);
}, this));
or using ES6 generators:
function* () {
var tasks = [];
tasks.push(user.fetch());
tasks.push(watchListsCollection.fetch());
tasks.push(loggedUser.fetch());
yield Promise.all(tasks);
this.currentView = new UserView(user, watchListsCollection, loggedUser);
}

Casperjs Unsafe JavaScript attempt to access frame with URL about:blank from frame with URL

casper.on("page.initialized", function(casp){
this.echo("Initialized...");
casper.evaluate(function(){ window.sessionStorage.setItem('authorization','xxxxxxx');
window.sessionStorage.setItem('_USER','xxxxx');
window.sessionStorage.setItem('USERNAME','xxxxx');
window.sessionStorage.setItem('INTERNAL','xxxx');
});
};
casper.test.begin('1: Asserting casperjs is working', 2, function suite(test) {
test.assertEquals(true, true);
test.assert(true);
test.done();
});
casper.test.begin('starting at /', 5, function suite(test
var starting = Date.now();
casper.start();
casper.open("xxxxx",function() {
test.assertHttpStatus(200);
var session = this.evaluate(function(){
return JSON.stringify(window.sessionStorage);
});
this.echo("SESSION: ", session);
});
casper.then(function(){
this.wait(10000, function(){this.echo("WAITED!");});
this.echo(this.getHTML('body'));
try{
casper.waitFor(function check() {
return this.evaluate(function() {
var quotes = document.getElementById('quoteBody').children.length > 1;
this.echo("QUOTES", quotes);
return true;
});
}, function then() {
// ending timer
var ending = Date.now();
var totalt = (ending - start) / 3600;
test.assertFalsy(function(){
return totalt >= 10.0;
}, "loading asserting total time passed");
this.echo("total time: ", totalt);
// tests
test.assertEval(function(){
return document.querySelectorAll('#quotesBody tr').length > 1;
});
//this.echo(this.getHTML('body'));
this.assertTitle("xxxxx", "title match");
});
} catch (e){
this.echo(e);
}
});
casper.run(function() {
test.done();
});
});
I've tried wrapping the test.done() in a timeout but I can't get passed the endless Unsafe JavaScript attempt to access frame with URL about:blank from frame with URL... warnings. The document.getElementById('quoteBody').children.length > 1; works in the browser, but is not returning true and triggering the waitTimeout event. It is after this event is emitted that I get the warnings. I tried waiting much longer than 10000 for loading to no avail. I was able to get some good advice from #Artjom B to get my script off the ground, but this seemingly perennial phantom bug is adding noise to my logs and the waitFor if returned falsy should not present any Unsafe errors.
edit
I think I can fix my script if the warnings weren't completely polluting my output. I've tried web-security=no flags -- didn't work. downgrade to phantom 1.9.2 from 1.9.8? I understand 1.9.8 is a little buggy and many people are having this issue. I'm much more concerned about how to get rid of the noise for now.
still looking for solution
"phantomjs": "^1.9.9"
for casperJs
casperjs --ssl-protocol=tlsv1 test run.js