Checking console messages in Cypress - javascript

I have local file where I can check if correct messages have been sent through console.
I can see the messages through cypress and count them but it seems that I can't check the actual message.
Code Example:
it(`Checking Error Text in:`, () => {
cy.visit('../testmessages.html', {
onBeforeLoad(win) {
cy.stub(win.console, 'log').as('consoleLog')},
})
cy.get('button').click()
cy.get("#consoleLog").should('be.calledThrice')
//here I would like to confirm that message contains "Id" etc
})})})

Technically, if you use cy.stub() you can't see the message in the console any more, because it blocks the call.
A cy.spy() would be better, then any console.log() you add for debugging aren't swallowed up by the stub.
If you save the result of the cy.spy(), you can use methods on it to extract the call data collected.
let spy;
Cypress.on('window:before:load', (win) => {
spy = cy.spy(win.console, 'log');
});
cy.visit('../testmessages.html')
cy.get('button').click() // console.log('hello', 'world')
cy.then(() => {
const calls = spy.getCalls();
expect(calls.length).to.eq(3)
expect(calls[1].args).to.deep.eq(['hello', 'world'])
})

With a stub, you can check what was passed to the function with something like this
cy.get(#consoleLog).should("have.been.calledWith", "Expected console message");

Related

Cypress intercept blocks the request when it's called several times in a test run

I've created some tests in Cypress to add and duplicate article in our Angular application. The code for test ArticlesTest.js
describe('Shop articles single station', () => {
const productManagementPage = new ProductManagementPage()
const shopArticlesPage = new ShopArticlesPage()
before(() => {
var credentials =
{
"username": "someusername#user.name",
"password": "strongPassword!"
}
cy.navigateToProductManagement(credentials)
})
beforeEach(() => {
productManagementPage.shopArticlesProgrammingButton().click()
shopArticlesPage.WaitUntilPageLoaded('/productmanagement/api/v1/articles/get', 'GetArticles')
})
it('Add article', () => {
var randomNumber = RandomDataGenerator.GenerateRandomInt(1000,4999)
var randomName = RandomDataGenerator.GenerateRandomString(20)
var randomPrice = RandomDataGenerator.GenerateRandomDecimal(1,99)
shopArticlesPage.newArticleButton().click()
shopArticlesPage.saveButton().should('be.disabled')
shopArticlesPage.undoButton().should('be.disabled')
shopArticlesPage.deleteButton().should('be.disabled')
shopArticlesPage.articlesList().should('not.exist')
shopArticlesPage.articleNumberTextBox().should('be.enabled')
shopArticlesPage.articleNumberTextBox().type(randomNumber)
shopArticlesPage.articleNameTextBox().type(randomName)
shopArticlesPage.articleUnitPriceTextBox().type(randomPrice)
shopArticlesPage.undoButton().should('be.enabled')
shopArticlesPage.saveButton().click()
shopArticlesPage.newArticleButton().should('exist')
shopArticlesPage.articlesList().should('exist')
shopArticlesPage.saveButton().should('be.disabled')
shopArticlesPage.undoButton().should('be.disabled')
})
it('Duplicate article', () => {
var articleNumber = RandomDataGenerator.GenerateRandomInt(51,65)
var newArticleNumber = RandomDataGenerator.GenerateRandomInt(1000, 4999)
var newArticleName = RandomDataGenerator.GenerateRandomString(20)
shopArticlesPage.articlesList().selectFromList(articleNumber)
const articleUnitPrice = shopArticlesPage.articleUnitPriceTextBox().invoke('text')
const vatCodeValue = shopArticlesPage.vatCodeDropDown().invoke('text')
const cardCodeValue = shopArticlesPage.cardCodeDropDown().invoke('text')
shopArticlesPage.duplicateArticleButton().click()
shopArticlesPage.WaitUntilPageLoaded()
shopArticlesPage.articleNumberTextBox().type(newArticleNumber)
shopArticlesPage.articleNameTextBox().type(newArticleName)
shopArticlesPage.saveButton().click()
shopArticlesPage.newArticleButton().should('be.enabled')
})
WaitUntilPageLoaded() method code is:
WaitUntilPageLoaded(path, alias) {
return cy.waitForRequestToComplete(path, alias)
}
which, in turn, is custom Cypress command:
Cypress.Commands.add('waitForRequestToComplete', (path, alias) => {
cy.intercept('POST', path).as(alias)
cy.wait('#' + alias).its('response.statusCode').should('be.ok')
})
In 1st beforeEach() run, there's no problem with intercepting GetArticles and waiting for it to complete.
The problem starts in 2nd test, as it looks like GetArticles is not intercepted, it's not called at all, though it's supposed to be. The problem doesn't exist when clicking through the application manually, and /articles/get is always invoked.
The test ends up with error message
Timed out retrying after 30000ms: cy.wait() timed out waiting 30000ms for the 1st request to the route: GetArticles. No request ever occurred.
I've also tried using other endpoint e.g. vatcodes/get, and it works perfectly. The problem occurs only for articles/get, but I don't see any trail that would tell my why this happens for articles endpoint.
What is the problem? Why Cypress "blocks" 2nd call to this endpoint? What's more interesting, the problem doesn't exist for GetFeatures alias, which is created in an identical way.
Make sure the network intercept is registered before the application makes the call.
it('is registered too late', () => {
cy.intercept('/todos').as('todos')
cy.visit('/')
cy.wait('#todos')
})
In our case, we need to register the intercept before visiting the page. Once the page is loaded, the application fetches the todo items, and everything is working as expected.
you can see this link: https://glebbahmutov.com/blog/cypress-intercept-problems/
If I'm reading the situation correctly, the last log image is the failing test.
There is no (xhr) 200 /productmanagement/api/v1/articles/get showing there.
It goes straight from api/v1/subscriptionfeatures/get to api/v1/vatcodes/get, but in the first test the api/v1/articles/get was between those two calls.
If it occurs later in the screenshot, add an increased timeout to catch it (the same intercept can use the longer timeout in both tests, but it won't delay the first test).
This may mean you have found a bug in the app - it seems that a "Duplicate" action should have the same POSTs as an "Add" action.
Have you resolved this?
I'm using this config:
Given('a GraphQL service error is thrown', () => {
cy.intercept({ method: 'POST', url: '/uat/graphql', times: 1 }, { forceNetworkError: true });
});
With times: 1. But the interception does not block the request now.
I found times in the docs.

how to present a pop-up message based on Observable property?

I have a pop-up message that I want to present when my observable is delivered, its a string observable.
this is my function that returns string observable:
public sendUpdate() {
this._mtService.sendCurrentUpdate(this.myList).subscribe(
res => this.messageToDisplay = res,
error => this.messageToDisplay = error
);
}
this is my function that I present the pop up with the relevant message:
public showMessageDialog(message) {
let config = new MdDialogConfig()
.title('Message:')
.textContent(message)
.ok('Got it');
this.dialog.open(MdDialogBasic, this.element, config);
}
now, I want to know where and how should I call this message to present messageToDisplay when the observable is ready.
I t would be even better if you can tell me how I can show some loader while the observable is waiting to receive the string and then when its there present it...
I tried to do this:
public sendUpdate() {
this._mtService.sendCurrentUpdate(this.myList).subscribe(
res => this.messageToDisplay = res,
error => this.messageToDisplay = error
);
this.showMessageDialog(this.messageToDisplay);
}
but what happens here is that the first time i click on update i see an empty pop-up and if I click on it again I see the pop-up with the message, its obvious that it happens because the string didnt came back yet, but how do I get over it?
thanks!
The functions you pass to subscribe() will be called later/asynchronously, hence you need to call showMessageDialog() later as well:
public sendUpdate() {
this.showLoader();
this._mtService.sendCurrentUpdate(this.myList).subscribe(
res => { this.stopLoader(); this.showMessageDialog(res); },
error => { this.stopLoader(); this.showMessageDialog(error); }
);
}

Cannot get MockBackend to return an error response

I have an issue when I try to unit test using the Angular 2 MockBackend
My tests for backend success responses works fine, but when I try to mock an error response from the backend, it always goes in the success case.
I bind the following to two different buttons where one passes true and the other passes false to the function. No matter what button i press we always get to the success block in the subscribtion. What am I missing?
callHttp(success) {
let connection;
mockBackend.connections.subscribe(c => connection = c);
this.http.get("/test")
.subscribe(
res => {
let msg = "Success status ("+res.status+")";
console.log(msg);
this.latestMessage = msg;
}),
err => {
// We never get here...
let msg = "Error status ("+res.status+")";
console.log(msg);
this.latestMessage = msg;
});
connection.mockRespond(new Response(new BaseResponseOptions().merge({
status: success ? 200 : 401,
body: JSON.stringify("Response")
})));
}
I have reproduced the issue in the following plunk: http://plnkr.co/edit/Rsl8Zcj6iEAIoD8KDDly
When I run the code with the actual backend, it works as expected. My problem is merely to mock the error responses, when I write tests.
The MockConnection object has a mockError(err?: Error) function, which you should call instead of mockRespond.

checking (reading) messages to console in js

I'm creating unit tests using Qunit. I want to test that, for a non-fatal error, a warning message is sent to console. (Yes, I know we shouldn't be writing to console in production code. Let it go.)
So, I've got this popup utility that accepts a config object:
popup.js:
showPopup = function(cfg){
if !(cfg.message){
utils.log('A message is required!');
}
};
(utils.log function will handle whether or not the browser actually supports console)
And then my tests file does its thing.
popup.tests.js:
showPopup({stuff: 'stuff', message: 'I am a popup!'});
QUnit.test('A warning message is logged to console', function (assert) {
// want to know a message was sent
}
How can I confirm that the message was sent?
What I would do is override console.log in your unit test:
window.console = (function(old_logger) {
var previous_message;
var log = function(msg) {
previous_message = msg;
old_logger.log(msg);
}
var previous = function() {
return previous_message;
}
return { log: log, previous: previous }
})(window.console);
And then you can do:
showPopup({stuff: 'stuff'});
assert.equal('A message is required!', console.previous());
Apparently, the "correct" answer for us is to use sinon stubs.

Is there a way in JavaScript to listen console events?

I'm trying to write handler for uncaught exceptions and browser warnings in Javascript. All errors and warnings should be sent to server for later review.
Handled exceptions can be caught and easily logged with
console.error("Error: ...");
or
console.warn("Warning: ...");
So they are not problem if they are called from javascript code, even more, unhandled exceptions could be caught with this peace of code:
window.onerror = function(){
// add to errors Stack trace etc.
});
}
so exceptions are pretty covered but I've stuck with warnings which browser sends to console. For instance security or html validation warnings. Example below is taken from Google Chrome console
The page at https://domainname.com/ ran insecure content from
http://domainname.com/javascripts/codex/MANIFEST.js.
It would be great if there is some event like window.onerror but for warnings. Any thoughts?
You could just wrap the console methods yourself. For example, to record each call in an array:
var logOfConsole = [];
var _log = console.log,
_warn = console.warn,
_error = console.error;
console.log = function() {
logOfConsole.push({method: 'log', arguments: arguments});
return _log.apply(console, arguments);
};
console.warn = function() {
logOfConsole.push({method: 'warn', arguments: arguments});
return _warn.apply(console, arguments);
};
console.error = function() {
logOfConsole.push({method: 'error', arguments: arguments});
return _error.apply(console, arguments);
};
More Succint Way:
// this method will proxy your custom method with the original one
function proxy(context, method, message) {
return function() {
method.apply(context, [message].concat(Array.prototype.slice.apply(arguments)))
}
}
// let's do the actual proxying over originals
console.log = proxy(console, console.log, 'Log:')
console.error = proxy(console, console.error, 'Error:')
console.warn = proxy(console, console.warn, 'Warning:')
// let's test
console.log('im from console.log', 1, 2, 3);
console.error('im from console.error', 1, 2, 3);
console.warn('im from console.warn', 1, 2, 3);
I know it's an old post but it can be useful anyway as others solution are not compatible with older browsers.
You can redefine the behavior of each function of the console (and for all browsers) like this:
// define a new console
var console = (function(oldCons){
return {
log: function(text){
oldCons.log(text);
// Your code
},
info: function (text) {
oldCons.info(text);
// Your code
},
warn: function (text) {
oldCons.warn(text);
// Your code
},
error: function (text) {
oldCons.error(text);
// Your code
}
};
}(window.console));
//Then redefine the old console
window.console = console;
I needed to debug console output on mobile devices so I built this drop-in library to capture console output and category and dump it to the page. Check the source code, it's quite straightforward.
https://github.com/samsonradu/Consolify
In the same function that you are using to do console.log(), simply post the same message to a web service that you are recording the logs on.
You're going about this backwards. Instead of intercepting when an error is logged, trigger an event as part of the error handling mechanism and log it as one of the event listeners:
try
{
//might throw an exception
foo();
}
catch (e)
{
$(document).trigger('customerror', e);
}
function customErrorHandler(event, ex)
{
console.error(ex)
}
function customErrorHandler2(event, ex)
{
$.post(url, ex);
}
this code uses jQuery and is oversimplified strictly for use as an example.

Categories