Enhance Console Logging with a Custom Global Log Wrapper in WebDriverIO
Add Color-Coded Logs, Handle Promises, and Improve Debugging in WebDriverIO
Step-by-Step Guide to Creating a Custom Log Wrapper in WebDriverIO
In this guide, we’ll enhance console logging in WebDriverIO by creating a global log wrapper with custom features like color-coding and promise handling. Logging is an essential part of debugging and understanding what your code is doing under the hood. While console.log()
works well, enhancing it with custom formatting and color coding can make your logs more readable and informative. In this blog, we’ll walk through how to create a global wrapper around console.log()
in your WebDriverIO configuration, adding flexibility and clarity to your log messages.
Why Use a Global Log Wrapper in WebDriverIO?
One of the key challenges in Automation Frameworks is distinguishing your custom log messages from framework or Node.js logs. A simple console output can easily get buried, making it harder to track essential messages. We need a way to make our logs stand out while adding some useful features like skipping empty logs, handling unresolved promises, and color-coding for different log levels.
Why global.d.ts file?
global.d.ts File
/**
* Logs a message to the console
* @param text - The text or object to log
*/
declare function log(text: any, level = 'info'): void;
This code is a TypeScript declaration for a global function named log
.Let's examine its components:
Function Declaration:
The
declare
keyword is used to tell TypeScript that this function exists somewhere in the global scope, but its implementation is not provided here.This is commonly used in definition files (.d.ts) to describe the shape of existing JavaScript code for TypeScript.
Function Name and Parameters:
The function is named
log
It takes two parameters:
text
: of typeany
, meaning it can accept any type of input (string, number, object, etc.).level
: which has a default value of'info'
. This parameter is optional due to the default value.
Return Type:
- The function is declared to return
void
, meaning it doesn't return any value.
- The function is declared to return
JSDoc Comments:
The code includes a JSDoc comment block that provides a brief description of the function and its parameters.
This is useful for generating documentation and providing inline help in IDEs.
Usage: This declaration allows you to use the
log
function anywhere in your TypeScript code without having to import it. For example:global.log("Hello, world!"); global.log({ name: "John", age: 30 }); global.log("Warning!", "warn");
Purpose:
This function is likely used for logging messages or objects to the console with different severity levels.
The actual implementation (which is not shown here) would determine how these logs are formatted and displayed.
This declaration is particularly useful in a TypeScript project where you want to use a global logging function across different files without needing to import it everywhere. It provides type checking and autocompletion support for the log
function in TypeScript-aware editors.
Step 1: Creating a Simple log()
Wrapper
The console.log()
method is great for outputting information to the console, but we can enhance and shorten it with a custom log()
wrapper. Add this snippet at the end of your wdio.conf.ts
file:
/**
* log wrapper
* @param text to be output to the console window
*/
global.log = (text) => {
console.log(`---> ${text}`);
}
This new global.log()
works like console.log()
, but with a little extra formatting to help your logs stand out. Let’s see it in action:
Before:
console.log('Entering text');
Output:
[0-0] Entering text
After:
await global.log('Entering text');
Output:
[0-0] ---> Entering text
By adding this wrapper, your custom messages are clearly separated from the ones generated by the framework or Node.js.
Step 2: How to Handle Promises and Enhancing the log()
Function
Let’s take this a step further. What if we want to skip empty strings and null values in our logs? Or detect when a Promise is passed to the log? Here’s an improved version:
global.log = (text) => {
if (text) { // Truthy value check
if (text === Promise) {
console.log('---> WARN: Log was passed a Promise object');
console.trace();
} else {
console.log(`---> ${text}`);
}
}
}
This version gives you more control. It:
Ignores empty and null values.
Warns when a Promise object is passed.
Prints a console trace showing where the unresolved Promise originated.
Step 3: How to Add Color-Coded Logs
To make your logs even more distinguishable, we can add color coding using ANSI escape codes. Here’s how:
global.log = (text, level = 'info') => {
if (text === undefined || text === null) {
console.log('\x1b[33m%s\x1b[0m', '---> No log content (undefined or null)'); // Yellow for empty values
return;
}
let colorCode;
switch (level.toLowerCase()) {
case 'info':
colorCode = '\x1b[32m'; // Green for info
break;
case 'warn':
colorCode = '\x1b[33m'; // Yellow for warnings
break;
case 'error':
colorCode = '\x1b[31m'; // Red for errors
break;
default:
colorCode = '\x1b[37m'; // White for others
}
if (text instanceof Promise) {
console.log('\x1b[33m%s\x1b[0m', '---> WARN: Log was passed a Promise object'); // Yellow warning
console.trace();
} else if (typeof text === 'object') {
console.log(`${colorCode}%s\x1b[0m`, `---> ${level.toUpperCase()}: ${JSON.stringify(text, null, 2)}`);
} else {
console.log(`${colorCode}%s\x1b[0m`, `---> ${level.toUpperCase()}: ${text}`);
}
};
Function Definition
Here's an updated version of global.log
function with color codes:
Info logs will be green.
Warn logs will be yellow.
Error logs will be red.
The global.log
function is defined globally, meaning it can be accessed from anywhere in the application.
It takes two parameters:
text
: The message or object to be loggedlevel
: The severity level of the log (default is 'info')
Handling Different Types of Input:
If the input is undefined or null, it logs a yellow message saying "No log content".
If the input is a Promise, it warns that a Promise was passed and shows a stack trace.
If the input is an object, it's converted to a formatted JSON string.
For other types (like strings), it's logged as-is.
Color Code Explanation:
\x1b[32m: Green color for info logs.
\x1b[33m: Yellow color for warn logs.
\x1b[31m: Red color for error logs.
\x1b[37m: Default white for other log levels.
\x1b[0m: Resets the color back to normal after printing the log.
Output Format:
Each log message starts with "--->" followed by the uppercase log level and then the message.
For objects, it uses
JSON.stringify
with indentation for better readability.
Example Usage
Now, you can use this function throughout your framework:
global.log('This is an info log'); // Green text
global.log('This is a warning', 'warn'); // Yellow text
global.log('This is an error!', 'error'); // Red text
global.log({ key: 'value' }, 'info'); // Green formatted object
Output:
[0-0] ---> INFO: This is an info log
[0-0] ---> WARN: This is a warning
[0-0] ---> ERROR: This is an error!
[0-0] ---> INFO: {
"key": "value"
}
Conclusion
By creating a custom global.log()
function, we’ve improved how logs are handled in our framework. From adding color-coding to ignoring empty logs, this wrapper makes debugging much easier, while ensuring the framework’s logs are separated from ours.
Have you tried creating your own log wrapper in WebDriverIO? Share your experience in the comments below, and don't forget to subscribe for more WebDriverIO tips and tricks!
Happy logging!
Important Links:
GitHub repositories related to logging ( https://github.com/hardikchotaliya/WebdriverIO-TS-Cucumber-e2e )
Official WebDriverIO documentation ( https://webdriver.io/docs/gettingstarted )