From 51fa1383e4b732d61ce4ff428b452a9fddccc06b Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 2 Aug 2022 23:26:50 +0200 Subject: [PATCH 1/4] feat: add console logs on test failure --- src/setup-page.ts | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/setup-page.ts b/src/setup-page.ts index e4e30cc5..f22c6d07 100644 --- a/src/setup-page.ts +++ b/src/setup-page.ts @@ -56,20 +56,22 @@ export const setupPage = async (page) => { } return input; } - + class StorybookTestRunnerError extends Error { - constructor(storyId, errorMessage) { + constructor(storyId, errorMessage, logs) { super(errorMessage); this.name = 'StorybookTestRunnerError'; const storyUrl = \`${referenceURL || targetURL}?path=/story/\${storyId}\`; const finalStoryUrl = \`\${storyUrl}&addonPanel=storybook/interactions/panel\`; + const separator = '\\n\\n--------------------------------------------------'; + const extraLogs = logs.length > 0 ? separator + "\\n\\nBrowser logs:\\n\\n"+ logs.join('\\n') : ''; - this.message = \`\nAn error occurred in the following story. Access the link for full output:\n\${finalStoryUrl}\n\nMessage:\n \${truncate(errorMessage,${debugPrintLimit})}\`; + this.message = \`\nAn error occurred in the following story. Access the link for full output:\n\${finalStoryUrl}\n\nMessage:\n \${truncate(errorMessage,${debugPrintLimit})}\n\${extraLogs}\`; } } - async function __throwError(storyId, errorMessage) { - throw new StorybookTestRunnerError(storyId, errorMessage); + async function __throwError(storyId, errorMessage, logs) { + throw new StorybookTestRunnerError(storyId, errorMessage, logs); } async function __waitForElement(selector) { @@ -119,17 +121,30 @@ export const setupPage = async (page) => { ); } + let logs = []; + + const spyOnConsole = (originalFn) => { + return function() { + const [txt, ...args] = arguments; + logs.push(originalFn.name + ": " + txt + (args.length > 0 ? " " + JSON.stringify(args) : "")); + originalFn.apply(console, arguments); + } + } + console.log = spyOnConsole(console.log); + console.warn = spyOnConsole(console.warn); + console.error = spyOnConsole(console.error); + return new Promise((resolve, reject) => { channel.on('${renderedEvent}', () => resolve(document.getElementById('root'))); channel.on('storyUnchanged', () => resolve(document.getElementById('root'))); channel.on('storyErrored', ({ description }) => reject( - new StorybookTestRunnerError(storyId, description)) + new StorybookTestRunnerError(storyId, description, logs)) ); channel.on('storyThrewException', (error) => reject( - new StorybookTestRunnerError(storyId, error.message)) + new StorybookTestRunnerError(storyId, error.message, logs)) ); channel.on('storyMissing', (id) => id === storyId && reject( - new StorybookTestRunnerError(storyId, 'The story was missing when trying to access it.')) + new StorybookTestRunnerError(storyId, 'The story was missing when trying to access it.', logs)) ); channel.emit('setCurrentStory', { storyId, viewMode: '${viewMode}' }); From 4f0151cfb8d446bdc91e1cf546c86d1bc8ae1f9e Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 5 Aug 2022 16:09:14 +0200 Subject: [PATCH 2/4] improve logging and remove possible circular dependencies --- src/setup-page.ts | 66 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 10 deletions(-) diff --git a/src/setup-page.ts b/src/setup-page.ts index f22c6d07..862b4dba 100644 --- a/src/setup-page.ts +++ b/src/setup-page.ts @@ -50,6 +50,48 @@ export const setupPage = async (page) => { await page.addScriptTag({ content: ` + // colorizes the console output + const bold = (message) => \`\\u001b[1m\${message}\\u001b[22m\`; + const magenta = (message) => \`\\u001b[35m\${message}\\u001b[39m\`; + const blue = (message) => \`\\u001b[34m\${message}\\u001b[39m\`; + const red = (message) => \`\\u001b[31m\${message}\\u001b[39m\`; + const yellow = (message) => \`\\u001b[33m\${message}\\u001b[39m\`; + + // removes circular references from the object + function serializer(replacer, cycleReplacer) { + let stack = [], + keys = []; + + if (cycleReplacer == null) + cycleReplacer = function (_key, value) { + if (stack[0] === value) return '[Circular]'; + return '[Circular ~.' + keys.slice(0, stack.indexOf(value)).join('.') + ']'; + }; + + return function (key, value) { + if (stack.length > 0) { + let thisPos = stack.indexOf(this); + ~thisPos ? stack.splice(thisPos + 1) : stack.push(this); + ~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key); + if (~stack.indexOf(value)) value = cycleReplacer.call(this, key, value); + } else { + stack.push(value); + } + + return replacer == null ? value : replacer.call(this, key, value); + }; + } + + function safeStringify(obj, replacer, spaces, cycleReplacer) { + return JSON.stringify(obj, serializer(replacer, cycleReplacer), spaces); + } + + function composeMessage(args) { + if (typeof args === 'undefined') return "undefined"; + if (typeof args === 'string') return args; + return safeStringify(args); + } + function truncate(input, limit) { if (input.length > limit) { return input.substring(0, limit) + '…'; @@ -120,19 +162,23 @@ export const setupPage = async (page) => { 'The test runner could not access the Storybook channel. Are you sure the Storybook is running correctly in that URL?' ); } - + + // collect logs to show upon test error let logs = []; - const spyOnConsole = (originalFn) => { - return function() { - const [txt, ...args] = arguments; - logs.push(originalFn.name + ": " + txt + (args.length > 0 ? " " + JSON.stringify(args) : "")); + const spyOnConsole = (originalFn, name) => { + return function () { + const message = [...arguments].map(composeMessage).join(', '); + const prefix = \`\${bold(name)}: \`; + logs.push(prefix + message); originalFn.apply(console, arguments); - } - } - console.log = spyOnConsole(console.log); - console.warn = spyOnConsole(console.warn); - console.error = spyOnConsole(console.error); + }; + }; + + console.log = spyOnConsole(console.log, blue('log')); + console.warn = spyOnConsole(console.warn, yellow('warn')); + console.error = spyOnConsole(console.error, red('error')); + console.trace = spyOnConsole(console.trace, magenta('trace')); return new Promise((resolve, reject) => { channel.on('${renderedEvent}', () => resolve(document.getElementById('root'))); From 045ea48e2d4f1836b8c5eceab4a521af434aa488 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 5 Aug 2022 16:23:11 +0200 Subject: [PATCH 3/4] add extra space --- src/setup-page.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/setup-page.ts b/src/setup-page.ts index 862b4dba..17f67440 100644 --- a/src/setup-page.ts +++ b/src/setup-page.ts @@ -106,7 +106,7 @@ export const setupPage = async (page) => { const storyUrl = \`${referenceURL || targetURL}?path=/story/\${storyId}\`; const finalStoryUrl = \`\${storyUrl}&addonPanel=storybook/interactions/panel\`; const separator = '\\n\\n--------------------------------------------------'; - const extraLogs = logs.length > 0 ? separator + "\\n\\nBrowser logs:\\n\\n"+ logs.join('\\n') : ''; + const extraLogs = logs.length > 0 ? separator + "\\n\\nBrowser logs:\\n\\n"+ logs.join('\\n\\n') : ''; this.message = \`\nAn error occurred in the following story. Access the link for full output:\n\${finalStoryUrl}\n\nMessage:\n \${truncate(errorMessage,${debugPrintLimit})}\n\${extraLogs}\`; } From 66af1199487ca6f0766d08144cb854dcfd9522db Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 5 Aug 2022 16:54:32 +0200 Subject: [PATCH 4/4] support more logging methods --- src/setup-page.ts | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/setup-page.ts b/src/setup-page.ts index 17f67440..1d951a46 100644 --- a/src/setup-page.ts +++ b/src/setup-page.ts @@ -166,7 +166,8 @@ export const setupPage = async (page) => { // collect logs to show upon test error let logs = []; - const spyOnConsole = (originalFn, name) => { + const spyOnConsole = (method, name) => { + const originalFn = console[method]; return function () { const message = [...arguments].map(composeMessage).join(', '); const prefix = \`\${bold(name)}: \`; @@ -175,10 +176,19 @@ export const setupPage = async (page) => { }; }; - console.log = spyOnConsole(console.log, blue('log')); - console.warn = spyOnConsole(console.warn, yellow('warn')); - console.error = spyOnConsole(console.error, red('error')); - console.trace = spyOnConsole(console.trace, magenta('trace')); + // console methods + color function for their prefix + const spiedMethods = { + log: blue, + warn: yellow, + error: red, + trace: magenta, + group: magenta, + groupCollapsed: magenta, + } + + Object.entries(spiedMethods).forEach(([method, color]) => { + console[method] = spyOnConsole(method, color(method)) + }) return new Promise((resolve, reject) => { channel.on('${renderedEvent}', () => resolve(document.getElementById('root')));