checking (reading) messages to console in js - javascript

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.

Related

Checking console messages in Cypress

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");

Protractor Throws Error on Assigning Variable

var itemStatus = element(by.model('item.statusId')).getText();
This causes protractor to throw:
Uncaught exception: Error while waiting for Protractor to sync with
the page: "Angular could not be found on the window" Process exited
with error code 1.
Can someone explain why? This doesn't throw:
var itemStatus = element(by.model('item.statusId'))
Is it something to do with promises, it can't execute .getText() until the element is located?
I guess I have a weak understanding of the basics here.
Update:
var itemStatus = element(by.model('item.statusId'))
// var itemStatus = element(by.model('item.statusId')).getText(); //was throwing with this
And then used below, before the code was commented out, I simply did not run the ".getText()" in the expect.
it('Should check item status, verify it is Checked Out.', function(){
expect(itemStatus.getText()).toBe('Checked Out');
//expect(itemStatus).toBe('Checked Out'); //this is how it was during error
});
and the html:
<div class="form-control ng-binding ng-scope ng-isolate-scope ng-valid" ng-model="item.statusId" disabled="disabled">Checked In</div>
I want to note that even with all the expect statements commented out, the script would throw an error when I tried to getText() for var itemStatus.
Sorry, forgot config:
exports.config = {
seleniumAddress: 'http://localhost:4444/wd/hub',
specs: ['transfer_spec.js']
}
I think this has to do with where have you defined the itemStatus variable. If it's defined out of scope of describe/it, you may fall into a synchronization issue between protractor and angular - at the moment getText() is called, it's too early, protractor has not yet received the Angular "I'm ready" signal. Have you declarations inside the describe/it, or inside a Page Object:
var MyPage = function () {
this.status = element(by.model('item.statusId'));
};
module.exports = new MyPage();
Usage:
var myPage = require("MyPage.js")
describe("My test", function () {
it("Should check item status, verify it is Checked Out.", function () {
expect(myPage.status.getText()).toEqual("Checked Out");
});
});

How to read from Chrome's console in JavaScript

I would like to put a button in my app, if you press it it will get the contents of everything that was written to the console and email it to me (for reporting bugs). I know I can keep a variable around and every time I do a console.log also append the message to that variable but I am trying to keep the memory consumption of the app low so it would be much more efficient just to grab it from the console.
Is there a way to retrieve the console messages from javascript?
You can't. What's in the console can't be read from JavaScript.
What you can do is hook the console.log function so that you store when it logs :
console.stdlog = console.log.bind(console);
console.logs = [];
console.log = function(){
console.logs.push(Array.from(arguments));
console.stdlog.apply(console, arguments);
}
console.logs contains all what was logged. You can clean it at any time by doing console.logs.length = 0;.
You can still do a standard, non storing, log by calling console.stdlog.
get all console data
how to read browser console error in js?
How to read from Chrome's console in JavaScript
https://www.quora.com/How-do-I-read-console-window-errors-from-Chrome-using-JavaScript
logs
console.defaultLog = console.log.bind(console);
console.logs = [];
console.log = function(){
// default & console.log()
console.defaultLog.apply(console, arguments);
// new & array data
console.logs.push(Array.from(arguments));
}
error
console.defaultError = console.error.bind(console);
console.errors = [];
console.error = function(){
// default & console.error()
console.defaultError.apply(console, arguments);
// new & array data
console.errors.push(Array.from(arguments));
}
warn
console.defaultWarn = console.warn.bind(console);
console.warns = [];
console.warn = function(){
// default & console.warn()
console.defaultWarn.apply(console, arguments);
// new & array data
console.warns.push(Array.from(arguments));
}
debug
console.defaultDebug = console.debug.bind(console);
console.debugs = [];
console.debug = function(){
// default & console.debug()
console.defaultDebug.apply(console, arguments);
// new & array data
console.debugs.push(Array.from(arguments));
}
I have used this code in the past to capture all console activity and store it with types and timestamps in console.everything for sending back to the server for diagnosing form data entry issues. I run this code as early as possible in the <head> element.
if (console.everything === undefined)
{
console.everything = [];
console.defaultLog = console.log.bind(console);
console.log = function(){
console.everything.push({"type":"log", "datetime":Date().toLocaleString(), "value":Array.from(arguments)});
console.defaultLog.apply(console, arguments);
}
console.defaultError = console.error.bind(console);
console.error = function(){
console.everything.push({"type":"error", "datetime":Date().toLocaleString(), "value":Array.from(arguments)});
console.defaultError.apply(console, arguments);
}
console.defaultWarn = console.warn.bind(console);
console.warn = function(){
console.everything.push({"type":"warn", "datetime":Date().toLocaleString(), "value":Array.from(arguments)});
console.defaultWarn.apply(console, arguments);
}
console.defaultDebug = console.debug.bind(console);
console.debug = function(){
console.everything.push({"type":"debug", "datetime":Date().toLocaleString(), "value":Array.from(arguments)});
console.defaultDebug.apply(console, arguments);
}
}
QA Collective's solution is very nice but has a lot of repeated code and doesn't capture errors that are not printed via the console.log, console.error, etc.
Here's the DRY and extended version of his solution that captures more error messages that show up in the console:
if (console.everything === undefined) {
console.everything = [];
function TS(){
return (new Date).toLocaleString("sv", { timeZone: 'UTC' }) + "Z"
}
window.onerror = function (error, url, line) {
console.everything.push({
type: "exception",
timeStamp: TS(),
value: { error, url, line }
})
return false;
}
window.onunhandledrejection = function (e) {
console.everything.push({
type: "promiseRejection",
timeStamp: TS(),
value: e.reason
})
}
function hookLogType(logType) {
const original= console[logType].bind(console)
return function(){
console.everything.push({
type: logType,
timeStamp: TS(),
value: Array.from(arguments)
})
original.apply(console, arguments)
}
}
['log', 'error', 'warn', 'debug'].forEach(logType=>{
console[logType] = hookLogType(logType)
})
}
I also changed the timestamp format to use the ISO format in UTC timezone, to be able to compare time stamps in different time zones more easily.
If you're working on vue.js, you can actually do this:
data () {
return {
data: []
}
},
created () {
let current_log = console.log;
console.log = msg => {
if (msg !== undefined) this.data.push(msg);
current_log.apply(null, arguments);
}
}
All logs from console will be captured and stored in data
If you just want to catch windows errors (Browser's developer tool), you just need to use the window.onerror listener. and the most important thing is to keep returning it false because If you return true in your callback, then the propagation of the error will stop and won't be log in the console anymore .
window.onerror = function myErrorHandler(err, url, line) {
//Do some stuff
console.log(err) // Uncaught SyntaxError: Invalid or unexpected token at Line no:- 1
return false; // so you still log errors into console
}

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.

Showing console errors and alerts in a div inside the page

I'm building a debugging tool for my web app and I need to show console errors in a div. I know I can use my own made console like object and use it, but for future use I need to send all console errors to window. Actually I want to catch console events.
To keep the console working:
if (typeof console != "undefined")
if (typeof console.log != 'undefined')
console.olog = console.log;
else
console.olog = function() {};
console.log = function(message) {
console.olog(message);
$('#debugDiv').append('<p>' + message + '</p>');
};
console.error = console.debug = console.info = console.log
Here's a way using closure, containing the old console log function in the scope of the new one.
console.log = (function (old_function, div_log) {
return function (text) {
old_function(text);
div_log.value += text;
};
} (console.log.bind(console), document.getElementById("error-log")));
None of the answers here consider console messages that get passed multiple parameters. E.g. console.log("Error:", "error details")).
The function that replaces the default log function better regards all function arguments (e.g. by using the arguments object). Here is an example:
console.log = function() {
log.textContent += Array.prototype.slice.call(arguments).join(' ');
}
(The Array.prototype.slice.call(...) simply converts the arguments object to an array, so it can be concatenated easily with join().)
When the original log should be kept working as well:
console.log = (function (old_log, log) {
return function () {
log.textContent += Array.prototype.slice.call(arguments).join(' ');
old_log.apply(console, arguments);
};
} (console.log.bind(console), document.querySelector('#log')));
A complete solution:
var log = document.querySelector('#log');
['log','debug','info','warn','error'].forEach(function (verb) {
console[verb] = (function (method, verb, log) {
return function () {
method.apply(console, arguments);
var msg = document.createElement('div');
msg.classList.add(verb);
msg.textContent = verb + ': ' + Array.prototype.slice.call(arguments).join(' ');
log.appendChild(msg);
};
})(console[verb], verb, log);
});
(An example of a framework that emits messages with multiple parameters is Video.js. But there is certainly many others.)
Edit: Another use of multiple parameters is the formatting capabilities of the console (e.g. console.log("Status code: %d", code).
About errors that are not shown
(Update Dec. 2021)
If any code crashes with an uncaught error, in might not show up in the div. One solution could be, if possible, to wrap all code in a try block to catch such errors and log them manually to the div.
try {
// Code that might throw errors...
} catch(err) {
// Pass the error to the overridden error log handler
console.error(err);
}
Else, if you were concerned at keeping log, warn and error separate from one another, you could do something like this (adapted from MST's answer):
var log = document.querySelector('#log');
['log','warn','error'].forEach(function (verb) {
console[verb] = (function (method, verb, log) {
return function (text) {
method(text);
// handle distinguishing between methods any way you'd like
var msg = document.createElement('code');
msg.classList.add(verb);
msg.textContent = verb + ': ' + text;
log.appendChild(msg);
};
})(console[verb].bind(console), verb, log);
});
where #log is your HTML element. The variable verb is one of 'log', 'warn', or 'error'. You can then use CSS to style the text in a distinguishable way. Note that a lot of this code isn't compatible with old versions of IE.
How about something as simple as:
console.log = function(message) {$('#debugDiv').append('<p>' + message + '</p>');};
console.error = console.debug = console.info = console.log
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
</head>
<body>
<div id="logger" class="web_console"></div>
<script type="text/javascript">
// Overriding console object
var console = {};
// Getting div to insert logs
var logger = document.getElementById("logger");
// Adding log method from our console object
console.log = function(text)
{
var element = document.createElement("div");
var txt = document.createTextNode(text);
element.appendChild(txt);
logger.appendChild(element);
}
// testing
console.log("Hello World...");
console.log("WOW");
/**
console.log prints the message in the page instead browser console, useful to programming and debugging JS using a Android phone
*/
</script>
</body>
</html>
I created a zero-dependency npm module for this case: console-events (surely if you're okay to use nodejs :P)
You can add event listener like that:
const { console } = require('console-events');
console.addEventListener('log', (e) => {
e.preventDefault(); //if you need to prevent normal behaviour e.g. output to devtools console
$('#debugDiv').append('<p>' + message + '</p>');
})

Categories