diff --git a/packages/browser/src/tracekit.ts b/packages/browser/src/tracekit.ts index 1b163b3927e3..e91dc37ddc58 100644 --- a/packages/browser/src/tracekit.ts +++ b/packages/browser/src/tracekit.ts @@ -18,7 +18,6 @@ export interface StackFrame { url: string; func: string; - args: string[]; line: number | null; column: number | null; } @@ -43,7 +42,7 @@ const UNKNOWN_FUNCTION = '?'; // Chromium based browsers: Chrome, Brave, new Opera, new Edge const chrome = - /^\s*at (?:(.*?) ?\()?((?:file|https?|blob|chrome-extension|address|native|eval|webpack||[-a-z]+:|.*bundle|\/).*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i; + /^\s*at (?:(.*?) ?\((?:address at )?)?((?:file|https?|blob|chrome-extension|address|native|eval|webpack||[-a-z]+:|.*bundle|\/).*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i; // gecko regex: `(?:bundle|\d+\.js)`: `bundle` is for react native, `\d+\.js` also but specifically for ram bundles because it // generates filenames without a prefix like `file://` the filenames in the stacktrace are just 42.js // We need this specific case for now because we want no other regex to match. @@ -113,9 +112,8 @@ function computeStackTraceFromStackProp(ex: any): StackTrace | null { let parts; let element; - for (let i = 0; i < lines.length; ++i) { - if ((parts = chrome.exec(lines[i]))) { - const isNative = parts[2] && parts[2].indexOf('native') === 0; // start of line + for (const line of lines) { + if ((parts = chrome.exec(line))) { isEval = parts[2] && parts[2].indexOf('eval') === 0; // start of line if (isEval && (submatch = chromeEval.exec(parts[2]))) { // throw out eval line/column and use top-most line/column number @@ -124,30 +122,24 @@ function computeStackTraceFromStackProp(ex: any): StackTrace | null { parts[4] = submatch[3]; // column } - // Arpad: Working with the regexp above is super painful. it is quite a hack, but just stripping the `address at ` - // prefix here seems like the quickest solution for now. - let url = parts[2] && parts[2].indexOf('address at ') === 0 ? parts[2].substr('address at '.length) : parts[2]; // Kamil: One more hack won't hurt us right? Understanding and adding more rules on top of these regexps right now // would be way too time consuming. (TODO: Rewrite whole RegExp to be more readable) - let func = parts[1] || UNKNOWN_FUNCTION; - [func, url] = extractSafariExtensionDetails(func, url); + const [func, url] = extractSafariExtensionDetails(parts[1] || UNKNOWN_FUNCTION, parts[2]); element = { url, func, - args: isNative ? [parts[2]] : [], line: parts[3] ? +parts[3] : null, column: parts[4] ? +parts[4] : null, }; - } else if ((parts = winjs.exec(lines[i]))) { + } else if ((parts = winjs.exec(line))) { element = { url: parts[2], func: parts[1] || UNKNOWN_FUNCTION, - args: [], line: +parts[3], column: parts[4] ? +parts[4] : null, }; - } else if ((parts = gecko.exec(lines[i]))) { + } else if ((parts = gecko.exec(line))) { isEval = parts[3] && parts[3].indexOf(' > eval') > -1; if (isEval && (submatch = geckoEval.exec(parts[3]))) { // throw out eval line/column and use top-most line number @@ -155,12 +147,6 @@ function computeStackTraceFromStackProp(ex: any): StackTrace | null { parts[3] = submatch[1]; parts[4] = submatch[2]; parts[5] = ''; // no column when eval - } else if (i === 0 && !parts[5] && ex.columnNumber !== void 0) { - // FireFox uses this awesome columnNumber property for its top frame - // Also note, Firefox's column number is 0-based and everything else expects 1-based, - // so adding 1 - // NOTE: this hack doesn't work if top-most frame is eval - stack[0].column = (ex.columnNumber as number) + 1; } let url = parts[3]; @@ -170,7 +156,6 @@ function computeStackTraceFromStackProp(ex: any): StackTrace | null { element = { url, func, - args: parts[2] ? parts[2].split(',') : [], line: parts[4] ? +parts[4] : null, column: parts[5] ? +parts[5] : null, }; @@ -178,10 +163,6 @@ function computeStackTraceFromStackProp(ex: any): StackTrace | null { continue; } - if (!element.func && element.line) { - element.func = UNKNOWN_FUNCTION; - } - stack.push(element); } @@ -208,7 +189,7 @@ function computeStackTraceFromStacktraceProp(ex: any): StackTrace | null { const stacktrace = ex.stacktrace; const opera10Regex = / line (\d+).*script (?:in )?(\S+)(?:: in function (\S+))?$/i; const opera11Regex = - / line (\d+), column (\d+)\s*(?:in (?:]+)>|([^)]+))\((.*)\))? in (.*):\s*$/i; + / line (\d+), column (\d+)\s*(?:in (?:]+)>|([^)]+))\(.*\))? in (.*):\s*$/i; const lines = stacktrace.split('\n'); const stack = []; let parts; @@ -219,15 +200,13 @@ function computeStackTraceFromStacktraceProp(ex: any): StackTrace | null { element = { url: parts[2], func: parts[3], - args: [], line: +parts[1], column: null, }; } else if ((parts = opera11Regex.exec(lines[line]))) { element = { - url: parts[6], + url: parts[5], func: parts[3] || parts[4], - args: parts[5] ? parts[5].split(',') : [], line: +parts[1], column: +parts[2], }; diff --git a/packages/browser/test/unit/tracekit/chromium.test.ts b/packages/browser/test/unit/tracekit/chromium.test.ts new file mode 100644 index 000000000000..65555c4656b6 --- /dev/null +++ b/packages/browser/test/unit/tracekit/chromium.test.ts @@ -0,0 +1,357 @@ +import { computeStackTrace } from '../../../src/tracekit'; + +describe('Tracekit - Chrome Tests', () => { + it('should parse Chrome error with no location', () => { + const NO_LOCATION = { message: 'foo', name: 'bar', stack: 'error\n at Array.forEach (native)' }; + const stackFrames = computeStackTrace(NO_LOCATION); + + expect(stackFrames).toEqual({ + message: 'foo', + name: 'bar', + stack: [{ url: 'native', func: 'Array.forEach', line: null, column: null }], + }); + }); + + it('should parse Chrome 15 error', () => { + const CHROME_15 = { + name: 'foo', + arguments: ['undef'], + message: "Object # has no method 'undef'", + stack: + "TypeError: Object # has no method 'undef'\n" + + ' at bar (http://path/to/file.js:13:17)\n' + + ' at bar (http://path/to/file.js:16:5)\n' + + ' at foo (http://path/to/file.js:20:5)\n' + + ' at http://path/to/file.js:24:4', + }; + + const stackFrames = computeStackTrace(CHROME_15); + + expect(stackFrames).toEqual({ + message: "Object # has no method 'undef'", + name: 'foo', + stack: [ + { url: 'http://path/to/file.js', func: 'bar', line: 13, column: 17 }, + { url: 'http://path/to/file.js', func: 'bar', line: 16, column: 5 }, + { url: 'http://path/to/file.js', func: 'foo', line: 20, column: 5 }, + { url: 'http://path/to/file.js', func: '?', line: 24, column: 4 }, + ], + }); + }); + + it('should parse Chrome 36 error with port numbers', () => { + const CHROME_36 = { + message: 'Default error', + name: 'Error', + stack: + 'Error: Default error\n' + + ' at dumpExceptionError (http://localhost:8080/file.js:41:27)\n' + + ' at HTMLButtonElement.onclick (http://localhost:8080/file.js:107:146)\n' + + ' at I.e.fn.(anonymous function) [as index] (http://localhost:8080/file.js:10:3651)', + }; + + const stackFrames = computeStackTrace(CHROME_36); + + expect(stackFrames).toEqual({ + message: 'Default error', + name: 'Error', + stack: [ + { url: 'http://localhost:8080/file.js', func: 'dumpExceptionError', line: 41, column: 27 }, + { url: 'http://localhost:8080/file.js', func: 'HTMLButtonElement.onclick', line: 107, column: 146 }, + { + url: 'http://localhost:8080/file.js', + func: 'I.e.fn.(anonymous function) [as index]', + line: 10, + column: 3651, + }, + ], + }); + }); + + it('should parse Chrome error with webpack URLs', () => { + // can be generated when Webpack is built with { devtool: eval } + const CHROME_XX_WEBPACK = { + message: "Cannot read property 'error' of undefined", + name: 'TypeError', + stack: + "TypeError: Cannot read property 'error' of undefined\n" + + ' at TESTTESTTEST.eval(webpack:///./src/components/test/test.jsx?:295:108)\n' + + ' at TESTTESTTEST.render(webpack:///./src/components/test/test.jsx?:272:32)\n' + + ' at TESTTESTTEST.tryRender(webpack:///./~/react-transform-catch-errors/lib/index.js?:34:31)\n' + + ' at TESTTESTTEST.proxiedMethod(webpack:///./~/react-proxy/modules/createPrototypeProxy.js?:44:30)', + }; + + const stackFrames = computeStackTrace(CHROME_XX_WEBPACK); + + expect(stackFrames).toEqual({ + message: "Cannot read property 'error' of undefined", + name: 'TypeError', + stack: [ + { url: 'webpack:///./src/components/test/test.jsx?', func: 'TESTTESTTEST.eval', line: 295, column: 108 }, + { url: 'webpack:///./src/components/test/test.jsx?', func: 'TESTTESTTEST.render', line: 272, column: 32 }, + { + url: 'webpack:///./~/react-transform-catch-errors/lib/index.js?', + func: 'TESTTESTTEST.tryRender', + line: 34, + column: 31, + }, + { + url: 'webpack:///./~/react-proxy/modules/createPrototypeProxy.js?', + func: 'TESTTESTTEST.proxiedMethod', + line: 44, + column: 30, + }, + ], + }); + }); + + it('should parse nested eval() from Chrome', () => { + const CHROME_48_EVAL = { + message: 'message string', + name: 'Error', + stack: + 'Error: message string\n' + + 'at baz (eval at foo (eval at speak (http://localhost:8080/file.js:21:17)), :1:30)\n' + + 'at foo (eval at speak (http://localhost:8080/file.js:21:17), :2:96)\n' + + 'at eval (eval at speak (http://localhost:8080/file.js:21:17), :4:18)\n' + + 'at Object.speak (http://localhost:8080/file.js:21:17)\n' + + 'at http://localhost:8080/file.js:31:13\n', + }; + + const stackFrames = computeStackTrace(CHROME_48_EVAL); + + expect(stackFrames).toEqual({ + message: 'message string', + name: 'Error', + stack: [ + { url: 'http://localhost:8080/file.js', func: 'baz', line: 21, column: 17 }, + { url: 'http://localhost:8080/file.js', func: 'foo', line: 21, column: 17 }, + { url: 'http://localhost:8080/file.js', func: 'eval', line: 21, column: 17 }, + { url: 'http://localhost:8080/file.js', func: 'Object.speak', line: 21, column: 17 }, + { url: 'http://localhost:8080/file.js', func: '?', line: 31, column: 13 }, + ], + }); + }); + + it('should parse Chrome error with blob URLs', () => { + const CHROME_48_BLOB = { + message: 'Error: test', + name: 'Error', + stack: + 'Error: test\n' + + ' at Error (native)\n' + + ' at s (blob:http%3A//localhost%3A8080/abfc40e9-4742-44ed-9dcd-af8f99a29379:31:29146)\n' + + ' at Object.d [as add] (blob:http%3A//localhost%3A8080/abfc40e9-4742-44ed-9dcd-af8f99a29379:31:30039)\n' + + ' at blob:http%3A//localhost%3A8080/d4eefe0f-361a-4682-b217-76587d9f712a:15:10978\n' + + ' at blob:http%3A//localhost%3A8080/abfc40e9-4742-44ed-9dcd-af8f99a29379:1:6911\n' + + ' at n.fire (blob:http%3A//localhost%3A8080/abfc40e9-4742-44ed-9dcd-af8f99a29379:7:3019)\n' + + ' at n.handle (blob:http%3A//localhost%3A8080/abfc40e9-4742-44ed-9dcd-af8f99a29379:7:2863)', + }; + + const stackFrames = computeStackTrace(CHROME_48_BLOB); + + expect(stackFrames).toEqual({ + message: 'Error: test', + name: 'Error', + stack: [ + { url: 'native', func: 'Error', line: null, column: null }, + { + url: 'blob:http%3A//localhost%3A8080/abfc40e9-4742-44ed-9dcd-af8f99a29379', + func: 's', + line: 31, + column: 29146, + }, + { + url: 'blob:http%3A//localhost%3A8080/abfc40e9-4742-44ed-9dcd-af8f99a29379', + func: 'Object.d [as add]', + line: 31, + column: 30039, + }, + { + url: 'blob:http%3A//localhost%3A8080/d4eefe0f-361a-4682-b217-76587d9f712a', + func: '?', + line: 15, + column: 10978, + }, + { + url: 'blob:http%3A//localhost%3A8080/abfc40e9-4742-44ed-9dcd-af8f99a29379', + func: '?', + line: 1, + column: 6911, + }, + { + url: 'blob:http%3A//localhost%3A8080/abfc40e9-4742-44ed-9dcd-af8f99a29379', + func: 'n.fire', + line: 7, + column: 3019, + }, + { + url: 'blob:http%3A//localhost%3A8080/abfc40e9-4742-44ed-9dcd-af8f99a29379', + func: 'n.handle', + line: 7, + column: 2863, + }, + ], + }); + }); + + it('should parse errors with custom schemes', () => { + const CHROMIUM_EMBEDDED_FRAMEWORK_CUSTOM_SCHEME = { + message: 'message string', + name: 'Error', + stack: `Error: message string + at examplescheme://examplehost/cd351f7250857e22ceaa.worker.js:70179:15`, + }; + + const stacktrace = computeStackTrace(CHROMIUM_EMBEDDED_FRAMEWORK_CUSTOM_SCHEME); + + expect(stacktrace).toEqual({ + message: 'message string', + name: 'Error', + stack: [ + { url: 'examplescheme://examplehost/cd351f7250857e22ceaa.worker.js', func: '?', line: 70179, column: 15 }, + ], + }); + }); + + it('should parse Chrome 73 with native code frames', () => { + const CHROME73_NATIVE_CODE_EXCEPTION = { + message: 'test', + name: 'Error', + stack: `Error: test + at fooIterator (http://localhost:5000/test:20:17) + at Array.map () + at foo (http://localhost:5000/test:19:19) + at http://localhost:5000/test:24:7`, + }; + + const stacktrace = computeStackTrace(CHROME73_NATIVE_CODE_EXCEPTION); + + expect(stacktrace).toEqual({ + message: 'test', + name: 'Error', + stack: [ + { url: 'http://localhost:5000/test', func: 'fooIterator', line: 20, column: 17 }, + { url: '', func: 'Array.map', line: null, column: null }, + { url: 'http://localhost:5000/test', func: 'foo', line: 19, column: 19 }, + { url: 'http://localhost:5000/test', func: '?', line: 24, column: 7 }, + ], + }); + }); + + it('should parse exceptions with eval frames in Chrome 73', () => { + const CHROME73_EVAL_EXCEPTION = { + message: 'bad', + name: 'Error', + stack: `Error: bad + at Object.aha (http://localhost:5000/:19:13) + at callAnotherThing (http://localhost:5000/:20:16) + at Object.callback (http://localhost:5000/:25:7) + at http://localhost:5000/:34:17 + at Array.map () + at test (http://localhost:5000/:33:23) + at eval (eval at aha (http://localhost:5000/:37:5), :1:1) + at aha (http://localhost:5000/:39:5) + at Foo.testMethod (http://localhost:5000/:44:7) + at http://localhost:5000/:50:19`, + }; + + const stacktrace = computeStackTrace(CHROME73_EVAL_EXCEPTION); + + expect(stacktrace).toEqual({ + message: 'bad', + name: 'Error', + stack: [ + { url: 'http://localhost:5000/', func: 'Object.aha', line: 19, column: 13 }, + { url: 'http://localhost:5000/', func: 'callAnotherThing', line: 20, column: 16 }, + { url: 'http://localhost:5000/', func: 'Object.callback', line: 25, column: 7 }, + { url: 'http://localhost:5000/', func: '?', line: 34, column: 17 }, + { url: '', func: 'Array.map', line: null, column: null }, + { url: 'http://localhost:5000/', func: 'test', line: 33, column: 23 }, + { url: 'http://localhost:5000/', func: 'eval', line: 37, column: 5 }, + { url: 'http://localhost:5000/', func: 'aha', line: 39, column: 5 }, + { url: 'http://localhost:5000/', func: 'Foo.testMethod', line: 44, column: 7 }, + { url: 'http://localhost:5000/', func: '?', line: 50, column: 19 }, + ], + }); + }); + + it('should parse exceptions with native code frames in Edge 44', () => { + const EDGE44_NATIVE_CODE_EXCEPTION = { + message: 'test', + name: 'Error', + stack: `Error: test + at fooIterator (http://localhost:5000/test:20:11) + at Array.prototype.map (native code) + at foo (http://localhost:5000/test:19:9) + at Global code (http://localhost:5000/test:24:7)`, + }; + + const stacktrace = computeStackTrace(EDGE44_NATIVE_CODE_EXCEPTION); + + expect(stacktrace).toEqual({ + message: 'test', + name: 'Error', + stack: [ + { url: 'http://localhost:5000/test', func: 'fooIterator', line: 20, column: 11 }, + { url: 'native code', func: 'Array.prototype.map', line: null, column: null }, + { url: 'http://localhost:5000/test', func: 'foo', line: 19, column: 9 }, + { url: 'http://localhost:5000/test', func: 'Global code', line: 24, column: 7 }, + ], + }); + }); + + it('should parse exceptions with eval frames in Edge 44', () => { + const EDGE44_EVAL_EXCEPTION = { + message: 'aha', + name: 'Error', + stack: `Error: bad + at aha (http://localhost:5000/:19:7) + at callAnotherThing (http://localhost:5000/:18:6) + at callback (http://localhost:5000/:25:7) + at Anonymous function (http://localhost:5000/:34:7) + at Array.prototype.map (native code) + at test (http://localhost:5000/:33:5) + at eval code (eval code:1:1) + at aha (http://localhost:5000/:39:5) + at Foo.prototype.testMethod (http://localhost:5000/:44:7) + at Anonymous function (http://localhost:5000/:50:8)`, + }; + + const stacktrace = computeStackTrace(EDGE44_EVAL_EXCEPTION); + + expect(stacktrace).toEqual({ + message: 'aha', + name: 'Error', + stack: [ + { url: 'http://localhost:5000/', func: 'aha', line: 19, column: 7 }, + { url: 'http://localhost:5000/', func: 'callAnotherThing', line: 18, column: 6 }, + { url: 'http://localhost:5000/', func: 'callback', line: 25, column: 7 }, + { url: 'http://localhost:5000/', func: 'Anonymous function', line: 34, column: 7 }, + { url: 'native code', func: 'Array.prototype.map', line: null, column: null }, + { url: 'http://localhost:5000/', func: 'test', line: 33, column: 5 }, + { url: 'eval code', func: 'eval code', line: 1, column: 1 }, + { url: 'http://localhost:5000/', func: 'aha', line: 39, column: 5 }, + { url: 'http://localhost:5000/', func: 'Foo.prototype.testMethod', line: 44, column: 7 }, + { url: 'http://localhost:5000/', func: 'Anonymous function', line: 50, column: 8 }, + ], + }); + }); + + it('should parse exceptions called within an iframe in Electron Renderer', () => { + const CHROME_ELECTRON_RENDERER = { + message: "Cannot read property 'error' of undefined", + name: 'TypeError', + stack: `TypeError: Cannot read property 'error' of undefined + at TESTTESTTEST.someMethod (C:\\Users\\user\\path\\to\\file.js:295:108)`, + }; + + const stacktrace = computeStackTrace(CHROME_ELECTRON_RENDERER); + + expect(stacktrace).toEqual({ + message: "Cannot read property 'error' of undefined", + name: 'TypeError', + stack: [{ url: 'C:\\Users\\user\\path\\to\\file.js', func: 'TESTTESTTEST.someMethod', line: 295, column: 108 }], + }); + }); +}); diff --git a/packages/browser/test/unit/tracekit/custom.test.ts b/packages/browser/test/unit/tracekit/custom.test.ts deleted file mode 100644 index 44c0ca490239..000000000000 --- a/packages/browser/test/unit/tracekit/custom.test.ts +++ /dev/null @@ -1,623 +0,0 @@ -import { computeStackTrace } from '../../../src/tracekit'; - -describe('Tracekit - Custom Tests', () => { - it('should parse errors with custom schemes', () => { - const CHROMIUM_EMBEDDED_FRAMEWORK_CUSTOM_SCHEME = { - message: 'message string', - name: 'Error', - stack: `Error: message string - at examplescheme://examplehost/cd351f7250857e22ceaa.worker.js:70179:15`, - }; - - const stacktrace = computeStackTrace(CHROMIUM_EMBEDDED_FRAMEWORK_CUSTOM_SCHEME); - - expect(stacktrace.stack).toEqual([ - { - args: [], - column: 15, - func: '?', - line: 70179, - url: 'examplescheme://examplehost/cd351f7250857e22ceaa.worker.js', - }, - ]); - }); - - describe('Safari extensions', () => { - it('should parse exceptions for safari-extension', () => { - const SAFARI_EXTENSION_EXCEPTION = { - message: 'wat', - name: 'Error', - stack: `Error: wat - at ClipperError@safari-extension:(//3284871F-A480-4FFC-8BC4-3F362C752446/2665fee0/commons.js:223036:10) - at safari-extension:(//3284871F-A480-4FFC-8BC4-3F362C752446/2665fee0/topee-content.js:3313:26)`, - }; - const stacktrace = computeStackTrace(SAFARI_EXTENSION_EXCEPTION); - expect(stacktrace.stack).toEqual([ - { - url: 'safari-extension://3284871F-A480-4FFC-8BC4-3F362C752446/2665fee0/commons.js', - func: 'ClipperError', - args: [], - line: 223036, - column: 10, - }, - { - url: 'safari-extension://3284871F-A480-4FFC-8BC4-3F362C752446/2665fee0/topee-content.js', - func: '?', - args: [], - line: 3313, - column: 26, - }, - ]); - }); - - it('should parse exceptions for safari-extension with frames-only stack', () => { - const SAFARI_EXTENSION_EXCEPTION = { - message: `undefined is not an object (evaluating 'e.groups.includes')`, - name: `TypeError`, - stack: `isClaimed@safari-extension://com.grammarly.safari.extension.ext2-W8F64X92K3/ee7759dd/Grammarly.js:2:929865 - safari-extension://com.grammarly.safari.extension.ext2-W8F64X92K3/ee7759dd/Grammarly.js:2:1588410 - promiseReactionJob@[native code]`, - }; - const stacktrace = computeStackTrace(SAFARI_EXTENSION_EXCEPTION); - - expect(stacktrace.stack).toEqual([ - { - url: 'safari-extension://com.grammarly.safari.extension.ext2-W8F64X92K3/ee7759dd/Grammarly.js', - func: 'isClaimed', - args: [], - line: 2, - column: 929865, - }, - { - url: 'safari-extension://com.grammarly.safari.extension.ext2-W8F64X92K3/ee7759dd/Grammarly.js', - func: '?', - args: [], - line: 2, - column: 1588410, - }, - { - url: '[native code]', - func: 'promiseReactionJob', - args: [], - line: null, - column: null, - }, - ]); - }); - - it('should parse exceptions for safari-web-extension', () => { - const SAFARI_WEB_EXTENSION_EXCEPTION = { - message: 'wat', - name: 'Error', - stack: `Error: wat - at ClipperError@safari-web-extension:(//3284871F-A480-4FFC-8BC4-3F362C752446/2665fee0/commons.js:223036:10) - at safari-web-extension:(//3284871F-A480-4FFC-8BC4-3F362C752446/2665fee0/topee-content.js:3313:26)`, - }; - const stacktrace = computeStackTrace(SAFARI_WEB_EXTENSION_EXCEPTION); - expect(stacktrace.stack).toEqual([ - { - url: 'safari-web-extension://3284871F-A480-4FFC-8BC4-3F362C752446/2665fee0/commons.js', - func: 'ClipperError', - args: [], - line: 223036, - column: 10, - }, - { - url: 'safari-web-extension://3284871F-A480-4FFC-8BC4-3F362C752446/2665fee0/topee-content.js', - func: '?', - args: [], - line: 3313, - column: 26, - }, - ]); - }); - - it('should parse exceptions for safari-web-extension with frames-only stack', () => { - const SAFARI_EXTENSION_EXCEPTION = { - message: `undefined is not an object (evaluating 'e.groups.includes')`, - name: `TypeError`, - stack: `p_@safari-web-extension://46434E60-F5BD-48A4-80C8-A422C5D16897/scripts/content-script.js:29:33314 - safari-web-extension://46434E60-F5BD-48A4-80C8-A422C5D16897/scripts/content-script.js:29:56027 - promiseReactionJob@[native code]`, - }; - const stacktrace = computeStackTrace(SAFARI_EXTENSION_EXCEPTION); - - expect(stacktrace.stack).toEqual([ - { - url: 'safari-web-extension://46434E60-F5BD-48A4-80C8-A422C5D16897/scripts/content-script.js', - func: 'p_', - args: [], - line: 29, - column: 33314, - }, - { - url: 'safari-web-extension://46434E60-F5BD-48A4-80C8-A422C5D16897/scripts/content-script.js', - func: '?', - args: [], - line: 29, - column: 56027, - }, - { - url: '[native code]', - func: 'promiseReactionJob', - args: [], - line: null, - column: null, - }, - ]); - }); - }); - - it('should parse exceptions for react-native-v8', () => { - const REACT_NATIVE_V8_EXCEPTION = { - message: 'Manually triggered crash to test Sentry reporting', - name: 'Error', - stack: `Error: Manually triggered crash to test Sentry reporting - at Object.onPress(index.android.bundle:2342:3773) - at s.touchableHandlePress(index.android.bundle:214:2048) - at s._performSideEffectsForTransition(index.android.bundle:198:9608) - at s._receiveSignal(index.android.bundle:198:8309) - at s.touchableHandleResponderRelease(index.android.bundle:198:5615) - at Object.y(index.android.bundle:93:571) - at P(index.android.bundle:93:714)`, - }; - const stacktrace = computeStackTrace(REACT_NATIVE_V8_EXCEPTION); - expect(stacktrace.stack).toEqual([ - { url: 'index.android.bundle', func: 'Object.onPress', args: [], line: 2342, column: 3773 }, - { url: 'index.android.bundle', func: 's.touchableHandlePress', args: [], line: 214, column: 2048 }, - { url: 'index.android.bundle', func: 's._performSideEffectsForTransition', args: [], line: 198, column: 9608 }, - { url: 'index.android.bundle', func: 's._receiveSignal', args: [], line: 198, column: 8309 }, - { url: 'index.android.bundle', func: 's.touchableHandleResponderRelease', args: [], line: 198, column: 5615 }, - { url: 'index.android.bundle', func: 'Object.y', args: [], line: 93, column: 571 }, - { url: 'index.android.bundle', func: 'P', args: [], line: 93, column: 714 }, - ]); - }); - - it('should parse exceptions for react-native Expo bundles', () => { - const REACT_NATIVE_EXPO_EXCEPTION = { - message: 'Test Error Expo', - name: 'Error', - stack: `onPress@/data/user/0/com.sentrytest/files/.expo-internal/bundle-613EDD44F3305B9D75D4679663900F2BCDDDC326F247CA3202A3A4219FD412D3:595:658 - value@/data/user/0/com.sentrytest/files/.expo-internal/bundle-613EDD44F3305B9D75D4679663900F2BCDDDC326F247CA3202A3A4219FD412D3:221:7656 - onResponderRelease@/data/user/0/com.sentrytest/files/.expo-internal/bundle-613EDD44F3305B9D75D4679663900F2BCDDDC326F247CA3202A3A4219FD412D3:221:5666 - p@/data/user/0/com.sentrytest/files/.expo-internal/bundle-613EDD44F3305B9D75D4679663900F2BCDDDC326F247CA3202A3A4219FD412D3:96:385 - forEach@[native code]`, - }; - const stacktrace = computeStackTrace(REACT_NATIVE_EXPO_EXCEPTION); - expect(stacktrace.stack).toEqual([ - { - url: '/data/user/0/com.sentrytest/files/.expo-internal/bundle-613EDD44F3305B9D75D4679663900F2BCDDDC326F247CA3202A3A4219FD412D3', - func: 'onPress', - args: [], - line: 595, - column: 658, - }, - { - url: '/data/user/0/com.sentrytest/files/.expo-internal/bundle-613EDD44F3305B9D75D4679663900F2BCDDDC326F247CA3202A3A4219FD412D3', - func: 'value', - args: [], - line: 221, - column: 7656, - }, - { - url: '/data/user/0/com.sentrytest/files/.expo-internal/bundle-613EDD44F3305B9D75D4679663900F2BCDDDC326F247CA3202A3A4219FD412D3', - func: 'onResponderRelease', - args: [], - line: 221, - column: 5666, - }, - { - url: '/data/user/0/com.sentrytest/files/.expo-internal/bundle-613EDD44F3305B9D75D4679663900F2BCDDDC326F247CA3202A3A4219FD412D3', - func: 'p', - args: [], - line: 96, - column: 385, - }, - { - url: '[native code]', - func: 'forEach', - args: [], - line: null, - column: null, - }, - ]); - }); - - describe('should parse exceptions with native code frames', () => { - it('in Chrome 73', () => { - const CHROME73_NATIVE_CODE_EXCEPTION = { - message: 'test', - name: 'Error', - stack: `Error: test - at fooIterator (http://localhost:5000/test:20:17) - at Array.map () - at foo (http://localhost:5000/test:19:19) - at http://localhost:5000/test:24:7`, - }; - - const stacktrace = computeStackTrace(CHROME73_NATIVE_CODE_EXCEPTION); - - expect(stacktrace.stack).toEqual([ - { args: [], column: 17, func: 'fooIterator', line: 20, url: 'http://localhost:5000/test' }, - { args: [], column: null, func: 'Array.map', line: null, url: '' }, - { args: [], column: 19, func: 'foo', line: 19, url: 'http://localhost:5000/test' }, - { args: [], column: 7, func: '?', line: 24, url: 'http://localhost:5000/test' }, - ]); - }); - - it('in Firefox 66', () => { - const FIREFOX66_NATIVE_CODE_EXCEPTION = { - message: 'test', - name: 'Error', - stack: `fooIterator@http://localhost:5000/test:20:17 - foo@http://localhost:5000/test:19:19 - @http://localhost:5000/test:24:7`, - }; - - const stacktrace = computeStackTrace(FIREFOX66_NATIVE_CODE_EXCEPTION); - - expect(stacktrace.stack).toEqual([ - { args: [], column: 17, func: 'fooIterator', line: 20, url: 'http://localhost:5000/test' }, - { args: [], column: 19, func: 'foo', line: 19, url: 'http://localhost:5000/test' }, - { args: [], column: 7, func: '?', line: 24, url: 'http://localhost:5000/test' }, - ]); - }); - - it('in Safari 12', () => { - const SAFARI12_NATIVE_CODE_EXCEPTION = { - message: 'test', - name: 'Error', - stack: `fooIterator@http://localhost:5000/test:20:26 - map@[native code] - foo@http://localhost:5000/test:19:22 - global code@http://localhost:5000/test:24:10`, - }; - - const stacktrace = computeStackTrace(SAFARI12_NATIVE_CODE_EXCEPTION); - - expect(stacktrace.stack).toEqual([ - { args: [], column: 26, func: 'fooIterator', line: 20, url: 'http://localhost:5000/test' }, - { args: [], column: null, func: 'map', line: null, url: '[native code]' }, - { args: [], column: 22, func: 'foo', line: 19, url: 'http://localhost:5000/test' }, - { args: [], column: 10, func: 'global code', line: 24, url: 'http://localhost:5000/test' }, - ]); - }); - - it('in Edge 44', () => { - const EDGE44_NATIVE_CODE_EXCEPTION = { - message: 'test', - name: 'Error', - stack: `Error: test - at fooIterator (http://localhost:5000/test:20:11) - at Array.prototype.map (native code) - at foo (http://localhost:5000/test:19:9) - at Global code (http://localhost:5000/test:24:7)`, - }; - - const stacktrace = computeStackTrace(EDGE44_NATIVE_CODE_EXCEPTION); - - expect(stacktrace.stack).toEqual([ - { args: [], column: 11, func: 'fooIterator', line: 20, url: 'http://localhost:5000/test' }, - { - args: ['native code'], - column: null, - func: 'Array.prototype.map', - line: null, - url: 'native code', - }, - { args: [], column: 9, func: 'foo', line: 19, url: 'http://localhost:5000/test' }, - { args: [], column: 7, func: 'Global code', line: 24, url: 'http://localhost:5000/test' }, - ]); - }); - }); - - describe('should parse exceptions with eval frames', () => { - it('in Chrome 73', () => { - const CHROME73_EVAL_EXCEPTION = { - message: 'bad', - name: 'Error', - stack: `Error: bad - at Object.aha (http://localhost:5000/:19:13) - at callAnotherThing (http://localhost:5000/:20:16) - at Object.callback (http://localhost:5000/:25:7) - at http://localhost:5000/:34:17 - at Array.map () - at test (http://localhost:5000/:33:23) - at eval (eval at aha (http://localhost:5000/:37:5), :1:1) - at aha (http://localhost:5000/:39:5) - at Foo.testMethod (http://localhost:5000/:44:7) - at http://localhost:5000/:50:19`, - }; - - const stacktrace = computeStackTrace(CHROME73_EVAL_EXCEPTION); - - expect(stacktrace.stack).toEqual([ - { column: 13, url: 'http://localhost:5000/', func: 'Object.aha', line: 19, args: [] }, - { column: 16, url: 'http://localhost:5000/', func: 'callAnotherThing', line: 20, args: [] }, - { column: 7, url: 'http://localhost:5000/', func: 'Object.callback', line: 25, args: [] }, - { column: 17, url: 'http://localhost:5000/', func: '?', line: 34, args: [] }, - { column: null, url: '', func: 'Array.map', line: null, args: [] }, - { column: 23, url: 'http://localhost:5000/', func: 'test', line: 33, args: [] }, - { column: 5, url: 'http://localhost:5000/', func: 'eval', line: 37, args: [] }, - { column: 5, url: 'http://localhost:5000/', func: 'aha', line: 39, args: [] }, - { column: 7, url: 'http://localhost:5000/', func: 'Foo.testMethod', line: 44, args: [] }, - { column: 19, url: 'http://localhost:5000/', func: '?', line: 50, args: [] }, - ]); - }); - - it('in Firefox 66', () => { - const FIREFOX66_EVAL_EXCEPTION = { - message: 'aha', - name: 'Error', - stack: `aha@http://localhost:5000/:19:13 - callAnotherThing@http://localhost:5000/:20:15 - callback@http://localhost:5000/:25:7 - test/<@http://localhost:5000/:34:7 - test@http://localhost:5000/:33:23 - @http://localhost:5000/ line 39 > eval:1:1 - aha@http://localhost:5000/:39:5 - testMethod@http://localhost:5000/:44:7 - @http://localhost:5000/:50:19`, - }; - - const stacktrace = computeStackTrace(FIREFOX66_EVAL_EXCEPTION); - - expect(stacktrace.stack).toEqual([ - { column: 13, url: 'http://localhost:5000/', func: 'aha', line: 19, args: [] }, - { column: 15, url: 'http://localhost:5000/', func: 'callAnotherThing', line: 20, args: [] }, - { column: 7, url: 'http://localhost:5000/', func: 'callback', line: 25, args: [] }, - { column: 7, url: 'http://localhost:5000/', func: 'test/<', line: 34, args: [] }, - { column: 23, url: 'http://localhost:5000/', func: 'test', line: 33, args: [] }, - { column: null, url: 'http://localhost:5000/', func: 'eval', line: 39, args: [] }, - { column: 5, url: 'http://localhost:5000/', func: 'aha', line: 39, args: [] }, - { column: 7, url: 'http://localhost:5000/', func: 'testMethod', line: 44, args: [] }, - { column: 19, url: 'http://localhost:5000/', func: '?', line: 50, args: [] }, - ]); - }); - - it('in Safari 12', () => { - const SAFARI12_EVAL_EXCEPTION = { - message: 'aha', - name: 'Error', - stack: `aha@http://localhost:5000/:19:22 - aha@[native code] - callAnotherThing@http://localhost:5000/:20:16 - callback@http://localhost:5000/:25:23 - http://localhost:5000/:34:25 - map@[native code] - test@http://localhost:5000/:33:26 - eval code - eval@[native code] - aha@http://localhost:5000/:39:9 - testMethod@http://localhost:5000/:44:10 - http://localhost:5000/:50:29`, - }; - - const stacktrace = computeStackTrace(SAFARI12_EVAL_EXCEPTION); - - expect(stacktrace.stack).toEqual([ - { column: 22, url: 'http://localhost:5000/', func: 'aha', line: 19, args: [] }, - { column: null, url: '[native code]', func: 'aha', line: null, args: [] }, - { column: 16, url: 'http://localhost:5000/', func: 'callAnotherThing', line: 20, args: [] }, - { column: 23, url: 'http://localhost:5000/', func: 'callback', line: 25, args: [] }, - { column: 25, url: 'http://localhost:5000/', func: '?', line: 34, args: [] }, - { column: null, url: '[native code]', func: 'map', line: null, args: [] }, - { column: 26, url: 'http://localhost:5000/', func: 'test', line: 33, args: [] }, - { column: null, url: '[native code]', func: 'eval', line: null, args: [] }, - { column: 9, url: 'http://localhost:5000/', func: 'aha', line: 39, args: [] }, - { column: 10, url: 'http://localhost:5000/', func: 'testMethod', line: 44, args: [] }, - { column: 29, url: 'http://localhost:5000/', func: '?', line: 50, args: [] }, - ]); - }); - - it('in Edge 44', () => { - const EDGE44_EVAL_EXCEPTION = { - message: 'aha', - name: 'Error', - stack: `Error: bad - at aha (http://localhost:5000/:19:7) - at callAnotherThing (http://localhost:5000/:18:6) - at callback (http://localhost:5000/:25:7) - at Anonymous function (http://localhost:5000/:34:7) - at Array.prototype.map (native code) - at test (http://localhost:5000/:33:5) - at eval code (eval code:1:1) - at aha (http://localhost:5000/:39:5) - at Foo.prototype.testMethod (http://localhost:5000/:44:7) - at Anonymous function (http://localhost:5000/:50:8)`, - }; - - const stacktrace = computeStackTrace(EDGE44_EVAL_EXCEPTION); - - expect(stacktrace.stack).toEqual([ - { column: 7, url: 'http://localhost:5000/', func: 'aha', line: 19, args: [] }, - { column: 6, url: 'http://localhost:5000/', func: 'callAnotherThing', line: 18, args: [] }, - { column: 7, url: 'http://localhost:5000/', func: 'callback', line: 25, args: [] }, - { column: 7, url: 'http://localhost:5000/', func: 'Anonymous function', line: 34, args: [] }, - { - args: ['native code'], - column: null, - func: 'Array.prototype.map', - line: null, - url: 'native code', - }, - { column: 5, url: 'http://localhost:5000/', func: 'test', line: 33, args: [] }, - { column: 1, url: 'eval code', func: 'eval code', line: 1, args: [] }, - { column: 5, url: 'http://localhost:5000/', func: 'aha', line: 39, args: [] }, - { - args: [], - column: 7, - func: 'Foo.prototype.testMethod', - line: 44, - url: 'http://localhost:5000/', - }, - { column: 8, url: 'http://localhost:5000/', func: 'Anonymous function', line: 50, args: [] }, - ]); - }); - }); - - describe('should parse exceptions called within an iframe', () => { - it('in Electron Renderer', () => { - const CHROME_ELECTRON_RENDERER = { - message: "Cannot read property 'error' of undefined", - name: 'TypeError', - stack: `TypeError: Cannot read property 'error' of undefined - at TESTTESTTEST.someMethod (C:\\Users\\user\\path\\to\\file.js:295:108)`, - }; - - const stacktrace = computeStackTrace(CHROME_ELECTRON_RENDERER); - - expect(stacktrace.stack).toEqual([ - { - args: [], - column: 108, - func: 'TESTTESTTEST.someMethod', - line: 295, - url: 'C:\\Users\\user\\path\\to\\file.js', - }, - ]); - }); - }); - - describe('React', () => { - it('should correctly parse Invariant Violation errors and use framesToPop to drop info message', () => { - const REACT_INVARIANT_VIOLATION_EXCEPTION = { - framesToPop: 1, - message: - 'Minified React error #31; visit https://reactjs.org/docs/error-decoder.html?invariant=31&args[]=object%20with%20keys%20%7B%7D&args[]= for the full message or use the non-minified dev environment for full errors and additional helpful warnings. ', - name: 'Invariant Violation', - stack: `Invariant Violation: Minified React error #31; visit https://reactjs.org/docs/error-decoder.html?invariant=31&args[]=object%20with%20keys%20%7B%7D&args[]= for the full message or use the non-minified dev environment for full errors and additional helpful warnings. - at http://localhost:5000/static/js/foo.chunk.js:1:21738 - at a (http://localhost:5000/static/js/foo.chunk.js:1:21841) - at ho (http://localhost:5000/static/js/foo.chunk.js:1:68735) - at f (http://localhost:5000/:1:980)`, - }; - - const stacktrace = computeStackTrace(REACT_INVARIANT_VIOLATION_EXCEPTION); - - expect(stacktrace.stack).toEqual([ - { - args: [], - column: 21738, - func: '?', - line: 1, - url: 'http://localhost:5000/static/js/foo.chunk.js', - }, - { - args: [], - column: 21841, - func: 'a', - line: 1, - url: 'http://localhost:5000/static/js/foo.chunk.js', - }, - { - args: [], - column: 68735, - func: 'ho', - line: 1, - url: 'http://localhost:5000/static/js/foo.chunk.js', - }, - { - args: [], - column: 980, - func: 'f', - line: 1, - url: 'http://localhost:5000/', - }, - ]); - }); - - it('should correctly parse production errors and drop initial frame if its not relevant', () => { - const REACT_PRODUCTION_ERROR = { - message: - 'Minified React error #200; visit https://reactjs.org/docs/error-decoder.html?invariant=200 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.', - name: 'Error', - stack: `Error: Minified React error #200; visit https://reactjs.org/docs/error-decoder.html?invariant=200 for the full message or use the non-minified dev environment for full errors and additional helpful warnings. - at http://localhost:5000/static/js/foo.chunk.js:1:21738 - at a (http://localhost:5000/static/js/foo.chunk.js:1:21841) - at ho (http://localhost:5000/static/js/foo.chunk.js:1:68735) - at f (http://localhost:5000/:1:980)`, - }; - - const stacktrace = computeStackTrace(REACT_PRODUCTION_ERROR); - - expect(stacktrace.stack).toEqual([ - { - args: [], - column: 21738, - func: '?', - line: 1, - url: 'http://localhost:5000/static/js/foo.chunk.js', - }, - { - args: [], - column: 21841, - func: 'a', - line: 1, - url: 'http://localhost:5000/static/js/foo.chunk.js', - }, - { - args: [], - column: 68735, - func: 'ho', - line: 1, - url: 'http://localhost:5000/static/js/foo.chunk.js', - }, - { - args: [], - column: 980, - func: 'f', - line: 1, - url: 'http://localhost:5000/', - }, - ]); - }); - - it('should not drop additional frame for production errors if framesToPop is still there', () => { - const REACT_PRODUCTION_ERROR = { - framesToPop: 1, - message: - 'Minified React error #200; visit https://reactjs.org/docs/error-decoder.html?invariant=200 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.', - name: 'Error', - stack: `Error: Minified React error #200; visit https://reactjs.org/docs/error-decoder.html?invariant=200 for the full message or use the non-minified dev environment for full errors and additional helpful warnings. - at http://localhost:5000/static/js/foo.chunk.js:1:21738 - at a (http://localhost:5000/static/js/foo.chunk.js:1:21841) - at ho (http://localhost:5000/static/js/foo.chunk.js:1:68735) - at f (http://localhost:5000/:1:980)`, - }; - - const stacktrace = computeStackTrace(REACT_PRODUCTION_ERROR); - - expect(stacktrace.stack).toEqual([ - { - args: [], - column: 21738, - func: '?', - line: 1, - url: 'http://localhost:5000/static/js/foo.chunk.js', - }, - { - args: [], - column: 21841, - func: 'a', - line: 1, - url: 'http://localhost:5000/static/js/foo.chunk.js', - }, - { - args: [], - column: 68735, - func: 'ho', - line: 1, - url: 'http://localhost:5000/static/js/foo.chunk.js', - }, - { - args: [], - column: 980, - func: 'f', - line: 1, - url: 'http://localhost:5000/', - }, - ]); - }); - }); -}); diff --git a/packages/browser/test/unit/tracekit/firefox.test.ts b/packages/browser/test/unit/tracekit/firefox.test.ts new file mode 100644 index 000000000000..03414c66da3a --- /dev/null +++ b/packages/browser/test/unit/tracekit/firefox.test.ts @@ -0,0 +1,270 @@ +import { computeStackTrace } from '../../../src/tracekit'; + +describe('Tracekit - Firefox Tests', () => { + it('should parse Firefox 3 error', () => { + const FIREFOX_3 = { + fileName: 'http://127.0.0.1:8000/js/stacktrace.js', + lineNumber: 44, + message: 'this.undef is not a function', + name: 'TypeError', + stack: + '()@http://127.0.0.1:8000/js/stacktrace.js:44\n' + + '(null)@http://127.0.0.1:8000/js/stacktrace.js:31\n' + + 'printStackTrace()@http://127.0.0.1:8000/js/stacktrace.js:18\n' + + 'bar(1)@http://127.0.0.1:8000/js/file.js:13\n' + + 'bar(2)@http://127.0.0.1:8000/js/file.js:16\n' + + 'foo()@http://127.0.0.1:8000/js/file.js:20\n' + + '@http://127.0.0.1:8000/js/file.js:24\n' + + '', + }; + + const stackFrames = computeStackTrace(FIREFOX_3); + + expect(stackFrames).toEqual({ + message: 'this.undef is not a function', + name: 'TypeError', + stack: [ + { url: 'http://127.0.0.1:8000/js/stacktrace.js', func: '?', line: 44, column: null }, + { url: 'http://127.0.0.1:8000/js/stacktrace.js', func: '?', line: 31, column: null }, + { url: 'http://127.0.0.1:8000/js/stacktrace.js', func: 'printStackTrace', line: 18, column: null }, + { url: 'http://127.0.0.1:8000/js/file.js', func: 'bar', line: 13, column: null }, + { url: 'http://127.0.0.1:8000/js/file.js', func: 'bar', line: 16, column: null }, + { url: 'http://127.0.0.1:8000/js/file.js', func: 'foo', line: 20, column: null }, + { url: 'http://127.0.0.1:8000/js/file.js', func: '?', line: 24, column: null }, + ], + }); + }); + + it('should parse Firefox 7 error', () => { + const FIREFOX_7 = { + name: 'foo', + message: 'bar', + fileName: 'file:///G:/js/stacktrace.js', + lineNumber: 44, + stack: + '()@file:///G:/js/stacktrace.js:44\n' + + '(null)@file:///G:/js/stacktrace.js:31\n' + + 'printStackTrace()@file:///G:/js/stacktrace.js:18\n' + + 'bar(1)@file:///G:/js/file.js:13\n' + + 'bar(2)@file:///G:/js/file.js:16\n' + + 'foo()@file:///G:/js/file.js:20\n' + + '@file:///G:/js/file.js:24\n' + + '', + }; + + const stackFrames = computeStackTrace(FIREFOX_7); + + expect(stackFrames).toEqual({ + message: 'bar', + name: 'foo', + stack: [ + { url: 'file:///G:/js/stacktrace.js', func: '?', line: 44, column: null }, + { url: 'file:///G:/js/stacktrace.js', func: '?', line: 31, column: null }, + { url: 'file:///G:/js/stacktrace.js', func: 'printStackTrace', line: 18, column: null }, + { url: 'file:///G:/js/file.js', func: 'bar', line: 13, column: null }, + { url: 'file:///G:/js/file.js', func: 'bar', line: 16, column: null }, + { url: 'file:///G:/js/file.js', func: 'foo', line: 20, column: null }, + { url: 'file:///G:/js/file.js', func: '?', line: 24, column: null }, + ], + }); + }); + + it('should parse Firefox 14 error', () => { + const FIREFOX_14 = { + name: 'foo', + message: 'x is null', + stack: + '@http://path/to/file.js:48\n' + + 'dumpException3@http://path/to/file.js:52\n' + + 'onclick@http://path/to/file.js:1\n' + + '', + fileName: 'http://path/to/file.js', + lineNumber: 48, + }; + + const stackFrames = computeStackTrace(FIREFOX_14); + + expect(stackFrames).toEqual({ + message: 'x is null', + name: 'foo', + stack: [ + { url: 'http://path/to/file.js', func: '?', line: 48, column: null }, + { url: 'http://path/to/file.js', func: 'dumpException3', line: 52, column: null }, + { url: 'http://path/to/file.js', func: 'onclick', line: 1, column: null }, + ], + }); + }); + + it('should parse Firefox 31 error', () => { + const FIREFOX_31 = { + message: 'Default error', + name: 'Error', + stack: + 'foo@http://path/to/file.js:41:13\n' + + 'bar@http://path/to/file.js:1:1\n' + + '.plugin/e.fn[c]/<@http://path/to/file.js:1:1\n' + + '', + fileName: 'http://path/to/file.js', + lineNumber: 41, + columnNumber: 12, + }; + + const stackFrames = computeStackTrace(FIREFOX_31); + + expect(stackFrames).toEqual({ + message: 'Default error', + name: 'Error', + stack: [ + { url: 'http://path/to/file.js', func: 'foo', line: 41, column: 13 }, + { url: 'http://path/to/file.js', func: 'bar', line: 1, column: 1 }, + { url: 'http://path/to/file.js', func: '.plugin/e.fn[c]/<', line: 1, column: 1 }, + ], + }); + }); + + it('should parse Firefox 44 ns exceptions', () => { + // Internal errors sometimes thrown by Firefox + // More here: https://developer.mozilla.org/en-US/docs/Mozilla/Errors + // + // Note that such errors are instanceof "Exception", not "Error" + const FIREFOX_44_NS_EXCEPTION = { + message: '', + name: 'NS_ERROR_FAILURE', + stack: + '[2] tag + '', + fileName: 'http://path/to/file.js', + columnNumber: 0, + lineNumber: 703, + result: 2147500037, + }; + + const stackFrames = computeStackTrace(FIREFOX_44_NS_EXCEPTION); + + expect(stackFrames).toEqual({ + message: 'No error message', + name: 'NS_ERROR_FAILURE', + stack: [ + { url: 'http://path/to/file.js', func: '[2] { + const FIREFOX_50_RESOURCE_URL = { + stack: + 'render@resource://path/data/content/bundle.js:5529:16\n' + + 'dispatchEvent@resource://path/data/content/vendor.bundle.js:18:23028\n' + + 'wrapped@resource://path/data/content/bundle.js:7270:25', + fileName: 'resource://path/data/content/bundle.js', + lineNumber: 5529, + columnNumber: 16, + message: 'this.props.raw[this.state.dataSource].rows is undefined', + name: 'TypeError', + }; + + const stackFrames = computeStackTrace(FIREFOX_50_RESOURCE_URL); + + expect(stackFrames).toEqual({ + message: 'this.props.raw[this.state.dataSource].rows is undefined', + name: 'TypeError', + stack: [ + { url: 'resource://path/data/content/bundle.js', func: 'render', line: 5529, column: 16 }, + { url: 'resource://path/data/content/vendor.bundle.js', func: 'dispatchEvent', line: 18, column: 23028 }, + { url: 'resource://path/data/content/bundle.js', func: 'wrapped', line: 7270, column: 25 }, + ], + }); + }); + + it('should parse Firefox errors with eval URLs', () => { + const FIREFOX_43_EVAL = { + name: 'foo', + columnNumber: 30, + fileName: 'http://localhost:8080/file.js line 25 > eval line 2 > eval', + lineNumber: 1, + message: 'message string', + stack: + 'baz@http://localhost:8080/file.js line 26 > eval line 2 > eval:1:30\n' + + 'foo@http://localhost:8080/file.js line 26 > eval:2:96\n' + + '@http://localhost:8080/file.js line 26 > eval:4:18\n' + + 'speak@http://localhost:8080/file.js:26:17\n' + + '@http://localhost:8080/file.js:33:9', + }; + + const stackFrames = computeStackTrace(FIREFOX_43_EVAL); + + expect(stackFrames).toEqual({ + message: 'message string', + name: 'foo', + stack: [ + { url: 'http://localhost:8080/file.js', func: 'baz', line: 26, column: null }, + { url: 'http://localhost:8080/file.js', func: 'foo', line: 26, column: null }, + { url: 'http://localhost:8080/file.js', func: 'eval', line: 26, column: null }, + { url: 'http://localhost:8080/file.js', func: 'speak', line: 26, column: 17 }, + { url: 'http://localhost:8080/file.js', func: '?', line: 33, column: 9 }, + ], + }); + }); + + it('should parse exceptions with native code frames in Firefox 66', () => { + const FIREFOX66_NATIVE_CODE_EXCEPTION = { + message: 'test', + name: 'Error', + stack: `fooIterator@http://localhost:5000/test:20:17 + foo@http://localhost:5000/test:19:19 + @http://localhost:5000/test:24:7`, + }; + + const stacktrace = computeStackTrace(FIREFOX66_NATIVE_CODE_EXCEPTION); + + expect(stacktrace).toEqual({ + message: 'test', + name: 'Error', + stack: [ + { url: 'http://localhost:5000/test', func: 'fooIterator', line: 20, column: 17 }, + { url: 'http://localhost:5000/test', func: 'foo', line: 19, column: 19 }, + { url: 'http://localhost:5000/test', func: '?', line: 24, column: 7 }, + ], + }); + }); + + it('should parse exceptions with eval frames in Firefox 66', () => { + const FIREFOX66_EVAL_EXCEPTION = { + message: 'aha', + name: 'Error', + stack: `aha@http://localhost:5000/:19:13 + callAnotherThing@http://localhost:5000/:20:15 + callback@http://localhost:5000/:25:7 + test/<@http://localhost:5000/:34:7 + test@http://localhost:5000/:33:23 + @http://localhost:5000/ line 39 > eval:1:1 + aha@http://localhost:5000/:39:5 + testMethod@http://localhost:5000/:44:7 + @http://localhost:5000/:50:19`, + }; + + const stacktrace = computeStackTrace(FIREFOX66_EVAL_EXCEPTION); + + expect(stacktrace).toEqual({ + message: 'aha', + name: 'Error', + stack: [ + { url: 'http://localhost:5000/', func: 'aha', line: 19, column: 13 }, + { url: 'http://localhost:5000/', func: 'callAnotherThing', line: 20, column: 15 }, + { url: 'http://localhost:5000/', func: 'callback', line: 25, column: 7 }, + { url: 'http://localhost:5000/', func: 'test/<', line: 34, column: 7 }, + { url: 'http://localhost:5000/', func: 'test', line: 33, column: 23 }, + { url: 'http://localhost:5000/', func: 'eval', line: 39, column: null }, + { url: 'http://localhost:5000/', func: 'aha', line: 39, column: 5 }, + { url: 'http://localhost:5000/', func: 'testMethod', line: 44, column: 7 }, + { url: 'http://localhost:5000/', func: '?', line: 50, column: 19 }, + ], + }); + }); +}); diff --git a/packages/browser/test/unit/tracekit/ie.test.ts b/packages/browser/test/unit/tracekit/ie.test.ts new file mode 100644 index 000000000000..7d016802175d --- /dev/null +++ b/packages/browser/test/unit/tracekit/ie.test.ts @@ -0,0 +1,83 @@ +import { computeStackTrace } from '../../../src/tracekit'; + +describe('Tracekit - IE Tests', () => { + it('should parse IE 10 error', () => { + const IE_10 = { + name: 'foo', + message: "Unable to get property 'undef' of undefined or null reference", + stack: + "TypeError: Unable to get property 'undef' of undefined or null reference\n" + + ' at Anonymous function (http://path/to/file.js:48:13)\n' + + ' at foo (http://path/to/file.js:46:9)\n' + + ' at bar (http://path/to/file.js:82:1)', + description: "Unable to get property 'undef' of undefined or null reference", + number: -2146823281, + }; + + const stackFrames = computeStackTrace(IE_10); + + // TODO: func should be normalized + expect(stackFrames).toEqual({ + message: "Unable to get property 'undef' of undefined or null reference", + name: 'foo', + stack: [ + { url: 'http://path/to/file.js', func: 'Anonymous function', line: 48, column: 13 }, + { url: 'http://path/to/file.js', func: 'foo', line: 46, column: 9 }, + { url: 'http://path/to/file.js', func: 'bar', line: 82, column: 1 }, + ], + }); + }); + + it('should parse IE 11 error', () => { + const IE_11 = { + message: "Unable to get property 'undef' of undefined or null reference", + name: 'TypeError', + stack: + "TypeError: Unable to get property 'undef' of undefined or null reference\n" + + ' at Anonymous function (http://path/to/file.js:47:21)\n' + + ' at foo (http://path/to/file.js:45:13)\n' + + ' at bar (http://path/to/file.js:108:1)', + description: "Unable to get property 'undef' of undefined or null reference", + number: -2146823281, + }; + + const stackFrames = computeStackTrace(IE_11); + + // TODO: func should be normalized + expect(stackFrames).toEqual({ + message: "Unable to get property 'undef' of undefined or null reference", + name: 'TypeError', + stack: [ + { url: 'http://path/to/file.js', func: 'Anonymous function', line: 47, column: 21 }, + { url: 'http://path/to/file.js', func: 'foo', line: 45, column: 13 }, + { url: 'http://path/to/file.js', func: 'bar', line: 108, column: 1 }, + ], + }); + }); + + it('should parse IE 11 eval error', () => { + const IE_11_EVAL = { + message: "'getExceptionProps' is undefined", + name: 'ReferenceError', + stack: + "ReferenceError: 'getExceptionProps' is undefined\n" + + ' at eval code (eval code:1:1)\n' + + ' at foo (http://path/to/file.js:58:17)\n' + + ' at bar (http://path/to/file.js:109:1)', + description: "'getExceptionProps' is undefined", + number: -2146823279, + }; + + const stackFrames = computeStackTrace(IE_11_EVAL); + + expect(stackFrames).toEqual({ + message: "'getExceptionProps' is undefined", + name: 'ReferenceError', + stack: [ + { url: 'eval code', func: 'eval code', line: 1, column: 1 }, + { url: 'http://path/to/file.js', func: 'foo', line: 58, column: 17 }, + { url: 'http://path/to/file.js', func: 'bar', line: 109, column: 1 }, + ], + }); + }); +}); diff --git a/packages/browser/test/unit/tracekit/misc.test.ts b/packages/browser/test/unit/tracekit/misc.test.ts new file mode 100644 index 000000000000..0ec51e94c26a --- /dev/null +++ b/packages/browser/test/unit/tracekit/misc.test.ts @@ -0,0 +1,26 @@ +import { computeStackTrace } from '../../../src/tracekit'; + +describe('Tracekit - Misc Tests', () => { + it('should parse PhantomJS 1.19 error', () => { + const PHANTOMJS_1_19 = { + name: 'foo', + message: 'bar', + stack: + 'Error: foo\n' + + ' at file:///path/to/file.js:878\n' + + ' at foo (http://path/to/file.js:4283)\n' + + ' at http://path/to/file.js:4287', + }; + const stackFrames = computeStackTrace(PHANTOMJS_1_19); + + expect(stackFrames).toEqual({ + message: 'bar', + name: 'foo', + stack: [ + { url: 'file:///path/to/file.js', func: '?', line: 878, column: null }, + { url: 'http://path/to/file.js', func: 'foo', line: 4283, column: null }, + { url: 'http://path/to/file.js', func: '?', line: 4287, column: null }, + ], + }); + }); +}); diff --git a/packages/browser/test/unit/tracekit/opera.test.ts b/packages/browser/test/unit/tracekit/opera.test.ts new file mode 100644 index 000000000000..5df2ba4a78ae --- /dev/null +++ b/packages/browser/test/unit/tracekit/opera.test.ts @@ -0,0 +1,142 @@ +import { computeStackTrace } from '../../../src/tracekit'; + +describe('Tracekit - Opera Tests', () => { + it('should parse Opera 10 error', () => { + const OPERA_10 = { + name: 'foo', + message: 'Statement on line 42: Type mismatch (usually non-object value supplied where object required)', + 'opera#sourceloc': 42, + stacktrace: + ' Line 42 of linked script http://path/to/file.js\n' + + ' this.undef();\n' + + ' Line 27 of linked script http://path/to/file.js\n' + + ' ex = ex || this.createException();\n' + + ' Line 18 of linked script http://path/to/file.js: In function printStackTrace\n' + + ' var p = new printStackTrace.implementation(), result = p.run(ex);\n' + + ' Line 4 of inline#1 script in http://path/to/file.js: In function bar\n' + + ' printTrace(printStackTrace());\n' + + ' Line 7 of inline#1 script in http://path/to/file.js: In function bar\n' + + ' bar(n - 1);\n' + + ' Line 11 of inline#1 script in http://path/to/file.js: In function foo\n' + + ' bar(2);\n' + + ' Line 15 of inline#1 script in http://path/to/file.js\n' + + ' foo();\n' + + '', + }; + + const stackFrames = computeStackTrace(OPERA_10); + + expect(stackFrames).toEqual({ + message: 'Statement on line 42: Type mismatch (usually non-object value supplied where object required)', + name: 'foo', + stack: [ + { url: 'http://path/to/file.js', func: '?', line: 42, column: null }, + { url: 'http://path/to/file.js', func: '?', line: 27, column: null }, + { url: 'http://path/to/file.js', func: 'printStackTrace', line: 18, column: null }, + { url: 'http://path/to/file.js', func: 'bar', line: 4, column: null }, + { url: 'http://path/to/file.js', func: 'bar', line: 7, column: null }, + { url: 'http://path/to/file.js', func: 'foo', line: 11, column: null }, + { url: 'http://path/to/file.js', func: '?', line: 15, column: null }, + ], + }); + }); + + it('should parse Opera 11 error', () => { + const OPERA_11 = { + name: 'foo', + message: "'this.undef' is not a function", + stack: + '([arguments not available])@http://path/to/file.js:27\n' + + 'bar([arguments not available])@http://domain.com:1234/path/to/file.js:18\n' + + 'foo([arguments not available])@http://domain.com:1234/path/to/file.js:11\n' + + '@http://path/to/file.js:15\n' + + 'Error created at @http://path/to/file.js:15', + stacktrace: + 'Error thrown at line 42, column 12 in () in http://path/to/file.js:\n' + + ' this.undef();\n' + + 'called from line 27, column 8 in (ex) in http://path/to/file.js:\n' + + ' ex = ex || this.createException();\n' + + 'called from line 18, column 4 in printStackTrace(options) in http://path/to/file.js:\n' + + ' var p = new printStackTrace.implementation(), result = p.run(ex);\n' + + 'called from line 4, column 5 in bar(n) in http://path/to/file.js:\n' + + ' printTrace(printStackTrace());\n' + + 'called from line 7, column 4 in bar(n) in http://path/to/file.js:\n' + + ' bar(n - 1);\n' + + 'called from line 11, column 4 in foo() in http://path/to/file.js:\n' + + ' bar(2);\n' + + 'called from line 15, column 3 in http://path/to/file.js:\n' + + ' foo();', + }; + + const stackFrames = computeStackTrace(OPERA_11); + + expect(stackFrames).toEqual({ + message: "'this.undef' is not a function", + name: 'foo', + stack: [ + { url: 'http://path/to/file.js', func: 'createException', line: 42, column: 12 }, + { url: 'http://path/to/file.js', func: 'run', line: 27, column: 8 }, + { url: 'http://path/to/file.js', func: 'printStackTrace', line: 18, column: 4 }, + { url: 'http://path/to/file.js', func: 'bar', line: 4, column: 5 }, + { url: 'http://path/to/file.js', func: 'bar', line: 7, column: 4 }, + { url: 'http://path/to/file.js', func: 'foo', line: 11, column: 4 }, + { url: 'http://path/to/file.js', func: '?', line: 15, column: 3 }, + ], + }); + }); + + it('should parse Opera 12 error', () => { + // TODO: Improve anonymous function name. + const OPERA_12 = { + name: 'foo', + message: "Cannot convert 'x' to object", + stack: + '([arguments not available])@http://localhost:8000/ExceptionLab.html:48\n' + + 'dumpException3([arguments not available])@http://localhost:8000/ExceptionLab.html:46\n' + + '([arguments not available])@http://localhost:8000/ExceptionLab.html:1', + stacktrace: + 'Error thrown at line 48, column 12 in (x) in http://localhost:8000/ExceptionLab.html:\n' + + ' x.undef();\n' + + 'called from line 46, column 8 in dumpException3() in http://localhost:8000/ExceptionLab.html:\n' + + ' dumpException((function(x) {\n' + + 'called from line 1, column 0 in (event) in http://localhost:8000/ExceptionLab.html:\n' + + ' dumpException3();', + }; + + const stackFrames = computeStackTrace(OPERA_12); + + expect(stackFrames).toEqual({ + message: "Cannot convert 'x' to object", + name: 'foo', + stack: [ + { url: 'http://localhost:8000/ExceptionLab.html', func: '', line: 48, column: 12 }, + { url: 'http://localhost:8000/ExceptionLab.html', func: 'dumpException3', line: 46, column: 8 }, + { url: 'http://localhost:8000/ExceptionLab.html', func: '', line: 1, column: 0 }, + ], + }); + }); + + it('should parse Opera 25 error', () => { + const OPERA_25 = { + message: "Cannot read property 'undef' of null", + name: 'TypeError', + stack: + "TypeError: Cannot read property 'undef' of null\n" + + ' at http://path/to/file.js:47:22\n' + + ' at foo (http://path/to/file.js:52:15)\n' + + ' at bar (http://path/to/file.js:108:168)', + }; + + const stackFrames = computeStackTrace(OPERA_25); + + expect(stackFrames).toEqual({ + message: "Cannot read property 'undef' of null", + name: 'TypeError', + stack: [ + { url: 'http://path/to/file.js', func: '?', line: 47, column: 22 }, + { url: 'http://path/to/file.js', func: 'foo', line: 52, column: 15 }, + { url: 'http://path/to/file.js', func: 'bar', line: 108, column: 168 }, + ], + }); + }); +}); diff --git a/packages/browser/test/unit/tracekit/original.test.ts b/packages/browser/test/unit/tracekit/original.test.ts deleted file mode 100644 index 1ec59f3e3ac3..000000000000 --- a/packages/browser/test/unit/tracekit/original.test.ts +++ /dev/null @@ -1,938 +0,0 @@ -import { computeStackTrace } from '../../../src/tracekit'; -import { - ANDROID_REACT_NATIVE, - ANDROID_REACT_NATIVE_HERMES, - ANDROID_REACT_NATIVE_PROD, - CHROME_15, - CHROME_36, - CHROME_48_BLOB, - CHROME_48_EVAL, - CHROME_XX_WEBPACK, - FIREFOX_3, - FIREFOX_7, - FIREFOX_14, - FIREFOX_31, - FIREFOX_43_EVAL, - FIREFOX_44_NS_EXCEPTION, - FIREFOX_50_RESOURCE_URL, - IE_10, - IE_11, - IE_11_EVAL, - OPERA_10, - OPERA_11, - OPERA_12, - OPERA_25, - PHANTOMJS_1_19, - SAFARI_6, - SAFARI_7, - SAFARI_8, - SAFARI_8_EVAL, -} from './originalfixtures'; - -describe('Tracekit - Original Tests', () => { - it('should parse Safari 6 error', () => { - const stackFrames = computeStackTrace(SAFARI_6); - expect(stackFrames).toBeTruthy(); - expect(stackFrames.stack.length).toBe(4); - expect(stackFrames.stack[0]).toEqual({ - url: 'http://path/to/file.js', - func: '?', - args: [], - line: 48, - column: null, - }); - expect(stackFrames.stack[1]).toEqual({ - url: 'http://path/to/file.js', - func: 'dumpException3', - args: [], - line: 52, - column: null, - }); - expect(stackFrames.stack[2]).toEqual({ - url: 'http://path/to/file.js', - func: 'onclick', - args: [], - line: 82, - column: null, - }); - expect(stackFrames.stack[3]).toEqual({ - url: '[native code]', - func: '?', - args: [], - line: null, - column: null, - }); - }); - - it('should parse Safari 7 error', () => { - const stackFrames = computeStackTrace(SAFARI_7); - expect(stackFrames).toBeTruthy(); - expect(stackFrames.stack.length).toBe(3); - expect(stackFrames.stack[0]).toEqual({ - url: 'http://path/to/file.js', - func: '?', - args: [], - line: 48, - column: 22, - }); - expect(stackFrames.stack[1]).toEqual({ - url: 'http://path/to/file.js', - func: 'foo', - args: [], - line: 52, - column: 15, - }); - expect(stackFrames.stack[2]).toEqual({ - url: 'http://path/to/file.js', - func: 'bar', - args: [], - line: 108, - column: 107, - }); - }); - - it('should parse Safari 8 error', () => { - const stackFrames = computeStackTrace(SAFARI_8); - expect(stackFrames).toBeTruthy(); - expect(stackFrames.stack.length).toBe(3); - expect(stackFrames.stack[0]).toEqual({ - url: 'http://path/to/file.js', - func: '?', - args: [], - line: 47, - column: 22, - }); - expect(stackFrames.stack[1]).toEqual({ - url: 'http://path/to/file.js', - func: 'foo', - args: [], - line: 52, - column: 15, - }); - expect(stackFrames.stack[2]).toEqual({ - url: 'http://path/to/file.js', - func: 'bar', - args: [], - line: 108, - column: 23, - }); - }); - - it('should parse Safari 8 eval error', () => { - // TODO: Take into account the line and column properties on the error object and use them for the first stack trace. - const stackFrames = computeStackTrace(SAFARI_8_EVAL); - expect(stackFrames).toBeTruthy(); - expect(stackFrames.stack.length).toBe(3); - expect(stackFrames.stack[0]).toEqual({ - url: '[native code]', - func: 'eval', - args: [], - line: null, - column: null, - }); - expect(stackFrames.stack[1]).toEqual({ - url: 'http://path/to/file.js', - func: 'foo', - args: [], - line: 58, - column: 21, - }); - expect(stackFrames.stack[2]).toEqual({ - url: 'http://path/to/file.js', - func: 'bar', - args: [], - line: 109, - column: 91, - }); - }); - - it('should parse Firefox 3 error', () => { - const stackFrames = computeStackTrace(FIREFOX_3); - expect(stackFrames).toBeTruthy(); - expect(stackFrames.stack.length).toBe(7); - expect(stackFrames.stack[0]).toEqual({ - url: 'http://127.0.0.1:8000/js/stacktrace.js', - func: '?', - args: [], - line: 44, - column: null, - }); - expect(stackFrames.stack[1]).toEqual({ - url: 'http://127.0.0.1:8000/js/stacktrace.js', - func: '?', - args: ['null'], - line: 31, - column: null, - }); - expect(stackFrames.stack[2]).toEqual({ - url: 'http://127.0.0.1:8000/js/stacktrace.js', - func: 'printStackTrace', - args: [], - line: 18, - column: null, - }); - expect(stackFrames.stack[3]).toEqual({ - url: 'http://127.0.0.1:8000/js/file.js', - func: 'bar', - args: ['1'], - line: 13, - column: null, - }); - expect(stackFrames.stack[4]).toEqual({ - url: 'http://127.0.0.1:8000/js/file.js', - func: 'bar', - args: ['2'], - line: 16, - column: null, - }); - expect(stackFrames.stack[5]).toEqual({ - url: 'http://127.0.0.1:8000/js/file.js', - func: 'foo', - args: [], - line: 20, - column: null, - }); - expect(stackFrames.stack[6]).toEqual({ - url: 'http://127.0.0.1:8000/js/file.js', - func: '?', - args: [], - line: 24, - column: null, - }); - }); - - it('should parse Firefox 7 error', () => { - const stackFrames = computeStackTrace(FIREFOX_7); - expect(stackFrames).toBeTruthy(); - expect(stackFrames.stack.length).toBe(7); - expect(stackFrames.stack[0]).toEqual({ - url: 'file:///G:/js/stacktrace.js', - func: '?', - args: [], - line: 44, - column: null, - }); - expect(stackFrames.stack[1]).toEqual({ - url: 'file:///G:/js/stacktrace.js', - func: '?', - args: ['null'], - line: 31, - column: null, - }); - expect(stackFrames.stack[2]).toEqual({ - url: 'file:///G:/js/stacktrace.js', - func: 'printStackTrace', - args: [], - line: 18, - column: null, - }); - expect(stackFrames.stack[3]).toEqual({ - url: 'file:///G:/js/file.js', - func: 'bar', - args: ['1'], - line: 13, - column: null, - }); - expect(stackFrames.stack[4]).toEqual({ - url: 'file:///G:/js/file.js', - func: 'bar', - args: ['2'], - line: 16, - column: null, - }); - expect(stackFrames.stack[5]).toEqual({ - url: 'file:///G:/js/file.js', - func: 'foo', - args: [], - line: 20, - column: null, - }); - expect(stackFrames.stack[6]).toEqual({ - url: 'file:///G:/js/file.js', - func: '?', - args: [], - line: 24, - column: null, - }); - }); - - it('should parse Firefox 14 error', () => { - const stackFrames = computeStackTrace(FIREFOX_14); - expect(stackFrames).toBeTruthy(); - expect(stackFrames.stack.length).toBe(3); - expect(stackFrames.stack[0]).toEqual({ - url: 'http://path/to/file.js', - func: '?', - args: [], - line: 48, - column: null, - }); - expect(stackFrames.stack[1]).toEqual({ - url: 'http://path/to/file.js', - func: 'dumpException3', - args: [], - line: 52, - column: null, - }); - expect(stackFrames.stack[2]).toEqual({ - url: 'http://path/to/file.js', - func: 'onclick', - args: [], - line: 1, - column: null, - }); - }); - - it('should parse Firefox 31 error', () => { - const stackFrames = computeStackTrace(FIREFOX_31); - expect(stackFrames).toBeTruthy(); - expect(stackFrames.stack.length).toBe(3); - expect(stackFrames.stack[0]).toEqual({ - url: 'http://path/to/file.js', - func: 'foo', - args: [], - line: 41, - column: 13, - }); - expect(stackFrames.stack[1]).toEqual({ - url: 'http://path/to/file.js', - func: 'bar', - args: [], - line: 1, - column: 1, - }); - expect(stackFrames.stack[2]).toEqual({ - url: 'http://path/to/file.js', - func: '.plugin/e.fn[c]/<', - args: [], - line: 1, - column: 1, - }); - }); - - it('should parse Firefox 44 ns exceptions', () => { - const stackFrames = computeStackTrace(FIREFOX_44_NS_EXCEPTION); - expect(stackFrames).toBeTruthy(); - expect(stackFrames.stack.length).toBe(4); - expect(stackFrames.stack[0]).toEqual({ - url: 'http://path/to/file.js', - func: '[2] { - const stackFrames = computeStackTrace({ message: 'foo', name: 'bar', stack: 'error\n at Array.forEach (native)' }); - expect(stackFrames.stack.length).toBe(1); - expect(stackFrames.stack[0]).toEqual({ - url: 'native', - func: 'Array.forEach', - args: ['native'], - line: null, - column: null, - }); - }); - - it('should parse Chrome 15 error', () => { - const stackFrames = computeStackTrace(CHROME_15); - expect(stackFrames).toBeTruthy(); - expect(stackFrames.stack.length).toBe(4); - expect(stackFrames.stack[0]).toEqual({ - url: 'http://path/to/file.js', - func: 'bar', - args: [], - line: 13, - column: 17, - }); - expect(stackFrames.stack[1]).toEqual({ - url: 'http://path/to/file.js', - func: 'bar', - args: [], - line: 16, - column: 5, - }); - expect(stackFrames.stack[2]).toEqual({ - url: 'http://path/to/file.js', - func: 'foo', - args: [], - line: 20, - column: 5, - }); - expect(stackFrames.stack[3]).toEqual({ - url: 'http://path/to/file.js', - func: '?', - args: [], - line: 24, - column: 4, - }); - }); - - it('should parse Chrome 36 error with port numbers', () => { - const stackFrames = computeStackTrace(CHROME_36); - expect(stackFrames).toBeTruthy(); - expect(stackFrames.stack.length).toBe(3); - expect(stackFrames.stack[0]).toEqual({ - url: 'http://localhost:8080/file.js', - func: 'dumpExceptionError', - args: [], - line: 41, - column: 27, - }); - expect(stackFrames.stack[1]).toEqual({ - url: 'http://localhost:8080/file.js', - func: 'HTMLButtonElement.onclick', - args: [], - line: 107, - column: 146, - }); - expect(stackFrames.stack[2]).toEqual({ - url: 'http://localhost:8080/file.js', - func: 'I.e.fn.(anonymous function) [as index]', - args: [], - line: 10, - column: 3651, - }); - }); - - it('should parse Chrome error with webpack URLs', () => { - const stackFrames = computeStackTrace(CHROME_XX_WEBPACK); - expect(stackFrames).toBeTruthy(); - expect(stackFrames.stack.length).toBe(4); - expect(stackFrames.stack[0]).toEqual({ - url: 'webpack:///./src/components/test/test.jsx?', - func: 'TESTTESTTEST.eval', - args: [], - line: 295, - column: 108, - }); - expect(stackFrames.stack[1]).toEqual({ - url: 'webpack:///./src/components/test/test.jsx?', - func: 'TESTTESTTEST.render', - args: [], - line: 272, - column: 32, - }); - expect(stackFrames.stack[2]).toEqual({ - url: 'webpack:///./~/react-transform-catch-errors/lib/index.js?', - func: 'TESTTESTTEST.tryRender', - args: [], - line: 34, - column: 31, - }); - expect(stackFrames.stack[3]).toEqual({ - url: 'webpack:///./~/react-proxy/modules/createPrototypeProxy.js?', - func: 'TESTTESTTEST.proxiedMethod', - args: [], - line: 44, - column: 30, - }); - }); - - it('should parse nested eval() from Chrome', () => { - const stackFrames = computeStackTrace(CHROME_48_EVAL); - expect(stackFrames).toBeTruthy(); - expect(stackFrames.stack.length).toBe(5); - expect(stackFrames.stack[0]).toEqual({ - url: 'http://localhost:8080/file.js', - func: 'baz', - args: [], - line: 21, - column: 17, - }); - expect(stackFrames.stack[1]).toEqual({ - url: 'http://localhost:8080/file.js', - func: 'foo', - args: [], - line: 21, - column: 17, - }); - expect(stackFrames.stack[2]).toEqual({ - url: 'http://localhost:8080/file.js', - func: 'eval', - args: [], - line: 21, - column: 17, - }); - expect(stackFrames.stack[3]).toEqual({ - url: 'http://localhost:8080/file.js', - func: 'Object.speak', - args: [], - line: 21, - column: 17, - }); - expect(stackFrames.stack[4]).toEqual({ - url: 'http://localhost:8080/file.js', - func: '?', - args: [], - line: 31, - column: 13, - }); - }); - - it('should parse Chrome error with blob URLs', () => { - const stackFrames = computeStackTrace(CHROME_48_BLOB); - expect(stackFrames).toBeTruthy(); - expect(stackFrames.stack.length).toBe(7); - expect(stackFrames.stack[1]).toEqual({ - url: 'blob:http%3A//localhost%3A8080/abfc40e9-4742-44ed-9dcd-af8f99a29379', - func: 's', - args: [], - line: 31, - column: 29146, - }); - expect(stackFrames.stack[2]).toEqual({ - url: 'blob:http%3A//localhost%3A8080/abfc40e9-4742-44ed-9dcd-af8f99a29379', - func: 'Object.d [as add]', - args: [], - line: 31, - column: 30039, - }); - expect(stackFrames.stack[3]).toEqual({ - url: 'blob:http%3A//localhost%3A8080/d4eefe0f-361a-4682-b217-76587d9f712a', - func: '?', - args: [], - line: 15, - column: 10978, - }); - expect(stackFrames.stack[4]).toEqual({ - url: 'blob:http%3A//localhost%3A8080/abfc40e9-4742-44ed-9dcd-af8f99a29379', - func: '?', - args: [], - line: 1, - column: 6911, - }); - expect(stackFrames.stack[5]).toEqual({ - url: 'blob:http%3A//localhost%3A8080/abfc40e9-4742-44ed-9dcd-af8f99a29379', - func: 'n.fire', - args: [], - line: 7, - column: 3019, - }); - expect(stackFrames.stack[6]).toEqual({ - url: 'blob:http%3A//localhost%3A8080/abfc40e9-4742-44ed-9dcd-af8f99a29379', - func: 'n.handle', - args: [], - line: 7, - column: 2863, - }); - }); - - it('should parse IE 10 error', () => { - const stackFrames = computeStackTrace(IE_10); - expect(stackFrames).toBeTruthy(); - expect(stackFrames.stack.length).toBe(3); - // TODO: func should be normalized - expect(stackFrames.stack[0]).toEqual({ - url: 'http://path/to/file.js', - func: 'Anonymous function', - args: [], - line: 48, - column: 13, - }); - expect(stackFrames.stack[1]).toEqual({ - url: 'http://path/to/file.js', - func: 'foo', - args: [], - line: 46, - column: 9, - }); - expect(stackFrames.stack[2]).toEqual({ - url: 'http://path/to/file.js', - func: 'bar', - args: [], - line: 82, - column: 1, - }); - }); - - it('should parse IE 11 error', () => { - const stackFrames = computeStackTrace(IE_11); - expect(stackFrames).toBeTruthy(); - expect(stackFrames.stack.length).toBe(3); - // TODO: func should be normalized - expect(stackFrames.stack[0]).toEqual({ - url: 'http://path/to/file.js', - func: 'Anonymous function', - args: [], - line: 47, - column: 21, - }); - expect(stackFrames.stack[1]).toEqual({ - url: 'http://path/to/file.js', - func: 'foo', - args: [], - line: 45, - column: 13, - }); - expect(stackFrames.stack[2]).toEqual({ - url: 'http://path/to/file.js', - func: 'bar', - args: [], - line: 108, - column: 1, - }); - }); - - it('should parse IE 11 eval error', () => { - const stackFrames = computeStackTrace(IE_11_EVAL); - expect(stackFrames).toBeTruthy(); - expect(stackFrames.stack.length).toBe(3); - expect(stackFrames.stack[0]).toEqual({ - url: 'eval code', - func: 'eval code', - args: [], - line: 1, - column: 1, - }); - expect(stackFrames.stack[1]).toEqual({ - url: 'http://path/to/file.js', - func: 'foo', - args: [], - line: 58, - column: 17, - }); - expect(stackFrames.stack[2]).toEqual({ - url: 'http://path/to/file.js', - func: 'bar', - args: [], - line: 109, - column: 1, - }); - }); - - it('should parse Opera 10 error', () => { - const stackFrames = computeStackTrace(OPERA_10); - expect(stackFrames).toBeTruthy(); - expect(stackFrames.stack.length).toBe(7); - expect(stackFrames.stack[0]).toEqual({ - url: 'http://path/to/file.js', - func: '?', - args: [], - line: 42, - column: null, - }); - expect(stackFrames.stack[1]).toEqual({ - url: 'http://path/to/file.js', - func: '?', - args: [], - line: 27, - column: null, - }); - expect(stackFrames.stack[2]).toEqual({ - url: 'http://path/to/file.js', - func: 'printStackTrace', - args: [], - line: 18, - column: null, - }); - expect(stackFrames.stack[3]).toEqual({ - url: 'http://path/to/file.js', - func: 'bar', - args: [], - line: 4, - column: null, - }); - expect(stackFrames.stack[4]).toEqual({ - url: 'http://path/to/file.js', - func: 'bar', - args: [], - line: 7, - column: null, - }); - expect(stackFrames.stack[5]).toEqual({ - url: 'http://path/to/file.js', - func: 'foo', - args: [], - line: 11, - column: null, - }); - expect(stackFrames.stack[6]).toEqual({ - url: 'http://path/to/file.js', - func: '?', - args: [], - line: 15, - column: null, - }); - }); - - it('should parse Opera 11 error', () => { - const stackFrames = computeStackTrace(OPERA_11); - expect(stackFrames).toBeTruthy(); - expect(stackFrames.stack.length).toBe(7); - expect(stackFrames.stack[0]).toEqual({ - url: 'http://path/to/file.js', - func: 'createException', - args: [], - line: 42, - column: 12, - }); - expect(stackFrames.stack[1]).toEqual({ - url: 'http://path/to/file.js', - func: 'run', - args: ['ex'], - line: 27, - column: 8, - }); - expect(stackFrames.stack[2]).toEqual({ - url: 'http://path/to/file.js', - func: 'printStackTrace', - args: ['options'], - line: 18, - column: 4, - }); - expect(stackFrames.stack[3]).toEqual({ - url: 'http://path/to/file.js', - func: 'bar', - args: ['n'], - line: 4, - column: 5, - }); - expect(stackFrames.stack[4]).toEqual({ - url: 'http://path/to/file.js', - func: 'bar', - args: ['n'], - line: 7, - column: 4, - }); - expect(stackFrames.stack[5]).toEqual({ - url: 'http://path/to/file.js', - func: 'foo', - args: [], - line: 11, - column: 4, - }); - expect(stackFrames.stack[6]).toEqual({ - url: 'http://path/to/file.js', - func: '?', - args: [], - line: 15, - column: 3, - }); - }); - - it('should parse Opera 12 error', () => { - // TODO: Improve anonymous function name. - const stackFrames = computeStackTrace(OPERA_12); - expect(stackFrames).toBeTruthy(); - expect(stackFrames.stack.length).toBe(3); - expect(stackFrames.stack[0]).toEqual({ - url: 'http://localhost:8000/ExceptionLab.html', - func: '', - args: ['x'], - line: 48, - column: 12, - }); - expect(stackFrames.stack[1]).toEqual({ - url: 'http://localhost:8000/ExceptionLab.html', - func: 'dumpException3', - args: [], - line: 46, - column: 8, - }); - expect(stackFrames.stack[2]).toEqual({ - url: 'http://localhost:8000/ExceptionLab.html', - func: '', - args: ['event'], - line: 1, - column: 0, - }); - }); - - it('should parse Opera 25 error', () => { - const stackFrames = computeStackTrace(OPERA_25); - expect(stackFrames).toBeTruthy(); - expect(stackFrames.stack.length).toBe(3); - expect(stackFrames.stack[0]).toEqual({ - url: 'http://path/to/file.js', - func: '?', - args: [], - line: 47, - column: 22, - }); - expect(stackFrames.stack[1]).toEqual({ - url: 'http://path/to/file.js', - func: 'foo', - args: [], - line: 52, - column: 15, - }); - expect(stackFrames.stack[2]).toEqual({ - url: 'http://path/to/file.js', - func: 'bar', - args: [], - line: 108, - column: 168, - }); - }); - - it('should parse PhantomJS 1.19 error', () => { - const stackFrames = computeStackTrace(PHANTOMJS_1_19); - expect(stackFrames).toBeTruthy(); - expect(stackFrames.stack.length).toBe(3); - expect(stackFrames.stack[0]).toEqual({ - url: 'file:///path/to/file.js', - func: '?', - args: [], - line: 878, - column: null, - }); - expect(stackFrames.stack[1]).toEqual({ - url: 'http://path/to/file.js', - func: 'foo', - args: [], - line: 4283, - column: null, - }); - expect(stackFrames.stack[2]).toEqual({ - url: 'http://path/to/file.js', - func: '?', - args: [], - line: 4287, - column: null, - }); - }); - - it('should parse Firefox errors with resource: URLs', () => { - const stackFrames = computeStackTrace(FIREFOX_50_RESOURCE_URL); - expect(stackFrames).toBeTruthy(); - expect(stackFrames.stack.length).toBe(3); - expect(stackFrames.stack[0]).toEqual({ - url: 'resource://path/data/content/bundle.js', - func: 'render', - args: [], - line: 5529, - column: 16, - }); - }); - - it('should parse Firefox errors with eval URLs', () => { - const stackFrames = computeStackTrace(FIREFOX_43_EVAL); - expect(stackFrames).toBeTruthy(); - expect(stackFrames.stack.length).toBe(5); - expect(stackFrames.stack[0]).toEqual({ - url: 'http://localhost:8080/file.js', - func: 'baz', - args: [], - line: 26, - column: null, - }); - expect(stackFrames.stack[1]).toEqual({ - url: 'http://localhost:8080/file.js', - func: 'foo', - args: [], - line: 26, - column: null, - }); - expect(stackFrames.stack[2]).toEqual({ - url: 'http://localhost:8080/file.js', - func: 'eval', - args: [], - line: 26, - column: null, - }); - expect(stackFrames.stack[3]).toEqual({ - url: 'http://localhost:8080/file.js', - func: 'speak', - args: [], - line: 26, - column: 17, - }); - expect(stackFrames.stack[4]).toEqual({ - url: 'http://localhost:8080/file.js', - func: '?', - args: [], - line: 33, - column: 9, - }); - }); - - it('should parse React Native errors on Android', () => { - const stackFrames = computeStackTrace(ANDROID_REACT_NATIVE); - expect(stackFrames).toBeTruthy(); - expect(stackFrames.stack.length).toBe(8); - expect(stackFrames.stack[0]).toEqual({ - url: '/home/username/sample-workspace/sampleapp.collect.react/src/components/GpsMonitorScene.js', - func: 'render', - args: [], - line: 78, - column: 24, - }); - expect(stackFrames.stack[7]).toEqual({ - url: '/home/username/sample-workspace/sampleapp.collect.react/node_modules/react-native/Libraries/Renderer/src/renderers/native/ReactNativeBaseComponent.js', - func: 'this', - args: [], - line: 74, - column: 41, - }); - }); - - it('should parse React Native errors on Android Production', () => { - const stackFrames = computeStackTrace(ANDROID_REACT_NATIVE_PROD); - expect(stackFrames).toBeTruthy(); - expect(stackFrames.stack.length).toBe(37); - expect(stackFrames.stack[0]).toEqual({ - url: 'index.android.bundle', - func: 'value', - args: [], - line: 12, - column: 1917, - }); - expect(stackFrames.stack[35]).toEqual({ - url: 'index.android.bundle', - func: 'value', - args: [], - line: 29, - column: 927, - }); - expect(stackFrames.stack[36]).toEqual({ - url: '[native code]', - func: '?', - args: [], - line: null, - column: null, - }); - }); - - it('should parse React Native errors on Android Hermes', () => { - const stackFrames = computeStackTrace(ANDROID_REACT_NATIVE_HERMES); - expect(stackFrames).toBeTruthy(); - expect(stackFrames.stack.length).toBe(26); - expect(stackFrames.stack[0]).toEqual({ - url: 'index.android.bundle', - func: 'onPress', - args: [], - line: 1, - column: 452701, - }); - expect(stackFrames.stack[3]).toEqual({ - url: 'native', - func: '_receiveSignal', - args: ['native'], - line: null, - column: null, - }); - }); -}); diff --git a/packages/browser/test/unit/tracekit/originalfixtures.ts b/packages/browser/test/unit/tracekit/originalfixtures.ts deleted file mode 100644 index 2f9977c3084b..000000000000 --- a/packages/browser/test/unit/tracekit/originalfixtures.ts +++ /dev/null @@ -1,513 +0,0 @@ -export const OPERA_854 = { - name: 'foo', - message: - 'Statement on line 44: Type mismatch (usually a non-object value used where an object is required)\n' + - 'Backtrace:\n' + - ' Line 44 of linked script http://path/to/file.js\n' + - ' this.undef();\n' + - ' Line 31 of linked script http://path/to/file.js\n' + - ' ex = ex || this.createException();\n' + - ' Line 18 of linked script http://path/to/file.js\n' + - ' var p = new printStackTrace.implementation(), result = p.run(ex);\n' + - ' Line 4 of inline#1 script in http://path/to/file.js\n' + - ' printTrace(printStackTrace());\n' + - ' Line 7 of inline#1 script in http://path/to/file.js\n' + - ' bar(n - 1);\n' + - ' Line 11 of inline#1 script in http://path/to/file.js\n' + - ' bar(2);\n' + - ' Line 15 of inline#1 script in http://path/to/file.js\n' + - ' foo();\n' + - '', - 'opera#sourceloc': 44, -}; - -export const OPERA_902 = { - name: 'foo', - message: - 'Statement on line 44: Type mismatch (usually a non-object value used where an object is required)\n' + - 'Backtrace:\n' + - ' Line 44 of linked script http://path/to/file.js\n' + - ' this.undef();\n' + - ' Line 31 of linked script http://path/to/file.js\n' + - ' ex = ex || this.createException();\n' + - ' Line 18 of linked script http://path/to/file.js\n' + - ' var p = new printStackTrace.implementation(), result = p.run(ex);\n' + - ' Line 4 of inline#1 script in http://path/to/file.js\n' + - ' printTrace(printStackTrace());\n' + - ' Line 7 of inline#1 script in http://path/to/file.js\n' + - ' bar(n - 1);\n' + - ' Line 11 of inline#1 script in http://path/to/file.js\n' + - ' bar(2);\n' + - ' Line 15 of inline#1 script in http://path/to/file.js\n' + - ' foo();\n' + - '', - 'opera#sourceloc': 44, -}; - -export const OPERA_927 = { - name: 'foo', - message: - 'Statement on line 43: Type mismatch (usually a non-object value used where an object is required)\n' + - 'Backtrace:\n' + - ' Line 43 of linked script http://path/to/file.js\n' + - ' bar(n - 1);\n' + - ' Line 31 of linked script http://path/to/file.js\n' + - ' bar(2);\n' + - ' Line 18 of linked script http://path/to/file.js\n' + - ' foo();\n' + - '', - 'opera#sourceloc': 43, -}; - -export const OPERA_964 = { - name: 'foo', - message: - 'Statement on line 42: Type mismatch (usually non-object value supplied where object required)\n' + - 'Backtrace:\n' + - ' Line 42 of linked script http://path/to/file.js\n' + - ' this.undef();\n' + - ' Line 27 of linked script http://path/to/file.js\n' + - ' ex = ex || this.createException();\n' + - ' Line 18 of linked script http://path/to/file.js: In function printStackTrace\n' + - ' var p = new printStackTrace.implementation(), result = p.run(ex);\n' + - ' Line 4 of inline#1 script in http://path/to/file.js: In function bar\n' + - ' printTrace(printStackTrace());\n' + - ' Line 7 of inline#1 script in http://path/to/file.js: In function bar\n' + - ' bar(n - 1);\n' + - ' Line 11 of inline#1 script in http://path/to/file.js: In function foo\n' + - ' bar(2);\n' + - ' Line 15 of inline#1 script in http://path/to/file.js\n' + - ' foo();\n' + - '', - 'opera#sourceloc': 42, - stacktrace: - ' ... Line 27 of linked script http://path/to/file.js\n' + - ' ex = ex || this.createException();\n' + - ' Line 18 of linked script http://path/to/file.js: In function printStackTrace\n' + - ' var p = new printStackTrace.implementation(), result = p.run(ex);\n' + - ' Line 4 of inline#1 script in http://path/to/file.js: In function bar\n' + - ' printTrace(printStackTrace());\n' + - ' Line 7 of inline#1 script in http://path/to/file.js: In function bar\n' + - ' bar(n - 1);\n' + - ' Line 11 of inline#1 script in http://path/to/file.js: In function foo\n' + - ' bar(2);\n' + - ' Line 15 of inline#1 script in http://path/to/file.js\n' + - ' foo();\n' + - '', -}; - -export const OPERA_10 = { - name: 'foo', - message: 'Statement on line 42: Type mismatch (usually non-object value supplied where object required)', - 'opera#sourceloc': 42, - stacktrace: - ' Line 42 of linked script http://path/to/file.js\n' + - ' this.undef();\n' + - ' Line 27 of linked script http://path/to/file.js\n' + - ' ex = ex || this.createException();\n' + - ' Line 18 of linked script http://path/to/file.js: In function printStackTrace\n' + - ' var p = new printStackTrace.implementation(), result = p.run(ex);\n' + - ' Line 4 of inline#1 script in http://path/to/file.js: In function bar\n' + - ' printTrace(printStackTrace());\n' + - ' Line 7 of inline#1 script in http://path/to/file.js: In function bar\n' + - ' bar(n - 1);\n' + - ' Line 11 of inline#1 script in http://path/to/file.js: In function foo\n' + - ' bar(2);\n' + - ' Line 15 of inline#1 script in http://path/to/file.js\n' + - ' foo();\n' + - '', -}; - -export const OPERA_11 = { - name: 'foo', - message: "'this.undef' is not a function", - stack: - '([arguments not available])@http://path/to/file.js:27\n' + - 'bar([arguments not available])@http://domain.com:1234/path/to/file.js:18\n' + - 'foo([arguments not available])@http://domain.com:1234/path/to/file.js:11\n' + - '@http://path/to/file.js:15\n' + - 'Error created at @http://path/to/file.js:15', - stacktrace: - 'Error thrown at line 42, column 12 in () in http://path/to/file.js:\n' + - ' this.undef();\n' + - 'called from line 27, column 8 in (ex) in http://path/to/file.js:\n' + - ' ex = ex || this.createException();\n' + - 'called from line 18, column 4 in printStackTrace(options) in http://path/to/file.js:\n' + - ' var p = new printStackTrace.implementation(), result = p.run(ex);\n' + - 'called from line 4, column 5 in bar(n) in http://path/to/file.js:\n' + - ' printTrace(printStackTrace());\n' + - 'called from line 7, column 4 in bar(n) in http://path/to/file.js:\n' + - ' bar(n - 1);\n' + - 'called from line 11, column 4 in foo() in http://path/to/file.js:\n' + - ' bar(2);\n' + - 'called from line 15, column 3 in http://path/to/file.js:\n' + - ' foo();', -}; - -export const OPERA_12 = { - name: 'foo', - message: "Cannot convert 'x' to object", - stack: - '([arguments not available])@http://localhost:8000/ExceptionLab.html:48\n' + - 'dumpException3([arguments not available])@http://localhost:8000/ExceptionLab.html:46\n' + - '([arguments not available])@http://localhost:8000/ExceptionLab.html:1', - stacktrace: - 'Error thrown at line 48, column 12 in (x) in http://localhost:8000/ExceptionLab.html:\n' + - ' x.undef();\n' + - 'called from line 46, column 8 in dumpException3() in http://localhost:8000/ExceptionLab.html:\n' + - ' dumpException((function(x) {\n' + - 'called from line 1, column 0 in (event) in http://localhost:8000/ExceptionLab.html:\n' + - ' dumpException3();', -}; - -export const OPERA_25 = { - message: "Cannot read property 'undef' of null", - name: 'TypeError', - stack: - "TypeError: Cannot read property 'undef' of null\n" + - ' at http://path/to/file.js:47:22\n' + - ' at foo (http://path/to/file.js:52:15)\n' + - ' at bar (http://path/to/file.js:108:168)', -}; - -export const CHROME_15 = { - name: 'foo', - arguments: ['undef'], - message: "Object # has no method 'undef'", - stack: - "TypeError: Object # has no method 'undef'\n" + - ' at bar (http://path/to/file.js:13:17)\n' + - ' at bar (http://path/to/file.js:16:5)\n' + - ' at foo (http://path/to/file.js:20:5)\n' + - ' at http://path/to/file.js:24:4', -}; - -export const CHROME_36 = { - message: 'Default error', - name: 'Error', - stack: - 'Error: Default error\n' + - ' at dumpExceptionError (http://localhost:8080/file.js:41:27)\n' + - ' at HTMLButtonElement.onclick (http://localhost:8080/file.js:107:146)\n' + - ' at I.e.fn.(anonymous function) [as index] (http://localhost:8080/file.js:10:3651)', -}; - -// can be generated when Webpack is built with { devtool: eval } -export const CHROME_XX_WEBPACK = { - message: "Cannot read property 'error' of undefined", - name: 'TypeError', - stack: - "TypeError: Cannot read property 'error' of undefined\n" + - ' at TESTTESTTEST.eval(webpack:///./src/components/test/test.jsx?:295:108)\n' + - ' at TESTTESTTEST.render(webpack:///./src/components/test/test.jsx?:272:32)\n' + - ' at TESTTESTTEST.tryRender(webpack:///./~/react-transform-catch-errors/lib/index.js?:34:31)\n' + - ' at TESTTESTTEST.proxiedMethod(webpack:///./~/react-proxy/modules/createPrototypeProxy.js?:44:30)', -}; - -export const FIREFOX_3 = { - fileName: 'http://127.0.0.1:8000/js/stacktrace.js', - lineNumber: 44, - message: 'this.undef is not a function', - name: 'TypeError', - stack: - '()@http://127.0.0.1:8000/js/stacktrace.js:44\n' + - '(null)@http://127.0.0.1:8000/js/stacktrace.js:31\n' + - 'printStackTrace()@http://127.0.0.1:8000/js/stacktrace.js:18\n' + - 'bar(1)@http://127.0.0.1:8000/js/file.js:13\n' + - 'bar(2)@http://127.0.0.1:8000/js/file.js:16\n' + - 'foo()@http://127.0.0.1:8000/js/file.js:20\n' + - '@http://127.0.0.1:8000/js/file.js:24\n' + - '', -}; - -export const FIREFOX_7 = { - name: 'foo', - message: 'bar', - fileName: 'file:///G:/js/stacktrace.js', - lineNumber: 44, - stack: - '()@file:///G:/js/stacktrace.js:44\n' + - '(null)@file:///G:/js/stacktrace.js:31\n' + - 'printStackTrace()@file:///G:/js/stacktrace.js:18\n' + - 'bar(1)@file:///G:/js/file.js:13\n' + - 'bar(2)@file:///G:/js/file.js:16\n' + - 'foo()@file:///G:/js/file.js:20\n' + - '@file:///G:/js/file.js:24\n' + - '', -}; - -export const FIREFOX_14 = { - name: 'foo', - message: 'x is null', - stack: - '@http://path/to/file.js:48\n' + - 'dumpException3@http://path/to/file.js:52\n' + - 'onclick@http://path/to/file.js:1\n' + - '', - fileName: 'http://path/to/file.js', - lineNumber: 48, -}; - -export const FIREFOX_31 = { - message: 'Default error', - name: 'Error', - stack: - 'foo@http://path/to/file.js:41:13\n' + - 'bar@http://path/to/file.js:1:1\n' + - '.plugin/e.fn[c]/<@http://path/to/file.js:1:1\n' + - '', - fileName: 'http://path/to/file.js', - lineNumber: 41, - columnNumber: 12, -}; - -export const FIREFOX_43_EVAL = { - name: 'foo', - columnNumber: 30, - fileName: 'http://localhost:8080/file.js line 25 > eval line 2 > eval', - lineNumber: 1, - message: 'message string', - stack: - 'baz@http://localhost:8080/file.js line 26 > eval line 2 > eval:1:30\n' + - 'foo@http://localhost:8080/file.js line 26 > eval:2:96\n' + - '@http://localhost:8080/file.js line 26 > eval:4:18\n' + - 'speak@http://localhost:8080/file.js:26:17\n' + - '@http://localhost:8080/file.js:33:9', -}; - -// Internal errors sometimes thrown by Firefox -// More here: https://developer.mozilla.org/en-US/docs/Mozilla/Errors -// -// Note that such errors are instanceof "Exception", not "Error" -export const FIREFOX_44_NS_EXCEPTION = { - message: '', - name: 'NS_ERROR_FAILURE', - stack: - '[2] tag - '', - fileName: 'http://path/to/file.js', - columnNumber: 0, - lineNumber: 703, - result: 2147500037, -}; - -export const FIREFOX_50_RESOURCE_URL = { - stack: - 'render@resource://path/data/content/bundle.js:5529:16\n' + - 'dispatchEvent@resource://path/data/content/vendor.bundle.js:18:23028\n' + - 'wrapped@resource://path/data/content/bundle.js:7270:25', - fileName: 'resource://path/data/content/bundle.js', - lineNumber: 5529, - columnNumber: 16, - message: 'this.props.raw[this.state.dataSource].rows is undefined', - name: 'TypeError', -}; - -export const SAFARI_6 = { - name: 'foo', - message: "'null' is not an object (evaluating 'x.undef')", - stack: - '@http://path/to/file.js:48\n' + - 'dumpException3@http://path/to/file.js:52\n' + - 'onclick@http://path/to/file.js:82\n' + - '[native code]', - line: 48, - sourceURL: 'http://path/to/file.js', -}; - -export const SAFARI_7 = { - message: "'null' is not an object (evaluating 'x.undef')", - name: 'TypeError', - stack: 'http://path/to/file.js:48:22\n' + 'foo@http://path/to/file.js:52:15\n' + 'bar@http://path/to/file.js:108:107', - line: 47, - sourceURL: 'http://path/to/file.js', -}; - -export const SAFARI_8 = { - message: "null is not an object (evaluating 'x.undef')", - name: 'TypeError', - stack: 'http://path/to/file.js:47:22\n' + 'foo@http://path/to/file.js:52:15\n' + 'bar@http://path/to/file.js:108:23', - line: 47, - column: 22, - sourceURL: 'http://path/to/file.js', -}; - -export const SAFARI_8_EVAL = { - message: "Can't find variable: getExceptionProps", - name: 'ReferenceError', - stack: - 'eval code\n' + 'eval@[native code]\n' + 'foo@http://path/to/file.js:58:21\n' + 'bar@http://path/to/file.js:109:91', - line: 1, - column: 18, -}; - -export const IE_9 = { - name: 'foo', - message: "Unable to get property 'undef' of undefined or null reference", - description: "Unable to get property 'undef' of undefined or null reference", -}; - -export const IE_10 = { - name: 'foo', - message: "Unable to get property 'undef' of undefined or null reference", - stack: - "TypeError: Unable to get property 'undef' of undefined or null reference\n" + - ' at Anonymous function (http://path/to/file.js:48:13)\n' + - ' at foo (http://path/to/file.js:46:9)\n' + - ' at bar (http://path/to/file.js:82:1)', - description: "Unable to get property 'undef' of undefined or null reference", - number: -2146823281, -}; - -export const IE_11 = { - message: "Unable to get property 'undef' of undefined or null reference", - name: 'TypeError', - stack: - "TypeError: Unable to get property 'undef' of undefined or null reference\n" + - ' at Anonymous function (http://path/to/file.js:47:21)\n' + - ' at foo (http://path/to/file.js:45:13)\n' + - ' at bar (http://path/to/file.js:108:1)', - description: "Unable to get property 'undef' of undefined or null reference", - number: -2146823281, -}; - -export const IE_11_EVAL = { - message: "'getExceptionProps' is undefined", - name: 'ReferenceError', - stack: - "ReferenceError: 'getExceptionProps' is undefined\n" + - ' at eval code (eval code:1:1)\n' + - ' at foo (http://path/to/file.js:58:17)\n' + - ' at bar (http://path/to/file.js:109:1)', - description: "'getExceptionProps' is undefined", - number: -2146823279, -}; - -export const CHROME_48_BLOB = { - message: 'Error: test', - name: 'Error', - stack: - 'Error: test\n' + - ' at Error (native)\n' + - ' at s (blob:http%3A//localhost%3A8080/abfc40e9-4742-44ed-9dcd-af8f99a29379:31:29146)\n' + - ' at Object.d [as add] (blob:http%3A//localhost%3A8080/abfc40e9-4742-44ed-9dcd-af8f99a29379:31:30039)\n' + - ' at blob:http%3A//localhost%3A8080/d4eefe0f-361a-4682-b217-76587d9f712a:15:10978\n' + - ' at blob:http%3A//localhost%3A8080/abfc40e9-4742-44ed-9dcd-af8f99a29379:1:6911\n' + - ' at n.fire (blob:http%3A//localhost%3A8080/abfc40e9-4742-44ed-9dcd-af8f99a29379:7:3019)\n' + - ' at n.handle (blob:http%3A//localhost%3A8080/abfc40e9-4742-44ed-9dcd-af8f99a29379:7:2863)', -}; - -export const CHROME_48_EVAL = { - message: 'message string', - name: 'Error', - stack: - 'Error: message string\n' + - 'at baz (eval at foo (eval at speak (http://localhost:8080/file.js:21:17)), :1:30)\n' + - 'at foo (eval at speak (http://localhost:8080/file.js:21:17), :2:96)\n' + - 'at eval (eval at speak (http://localhost:8080/file.js:21:17), :4:18)\n' + - 'at Object.speak (http://localhost:8080/file.js:21:17)\n' + - 'at http://localhost:8080/file.js:31:13\n', -}; - -export const PHANTOMJS_1_19 = { - name: 'foo', - message: 'bar', - stack: - 'Error: foo\n' + - ' at file:///path/to/file.js:878\n' + - ' at foo (http://path/to/file.js:4283)\n' + - ' at http://path/to/file.js:4287', -}; - -export const ANDROID_REACT_NATIVE = { - message: 'Error: test', - name: 'Error', - stack: - 'Error: test\n' + - 'at render(/home/username/sample-workspace/sampleapp.collect.react/src/components/GpsMonitorScene.js:78:24)\n' + - 'at _renderValidatedComponentWithoutOwnerOrContext(/home/username/sample-workspace/sampleapp.collect.react/node_modules/react-native/Libraries/Renderer/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js:1050:29)\n' + - 'at _renderValidatedComponent(/home/username/sample-workspace/sampleapp.collect.react/node_modules/react-native/Libraries/Renderer/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js:1075:15)\n' + - 'at renderedElement(/home/username/sample-workspace/sampleapp.collect.react/node_modules/react-native/Libraries/Renderer/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js:484:29)\n' + - 'at _currentElement(/home/username/sample-workspace/sampleapp.collect.react/node_modules/react-native/Libraries/Renderer/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js:346:40)\n' + - 'at child(/home/username/sample-workspace/sampleapp.collect.react/node_modules/react-native/Libraries/Renderer/src/renderers/shared/stack/reconciler/ReactReconciler.js:68:25)\n' + - 'at children(/home/username/sample-workspace/sampleapp.collect.react/node_modules/react-native/Libraries/Renderer/src/renderers/shared/stack/reconciler/ReactMultiChild.js:264:10)\n' + - 'at this(/home/username/sample-workspace/sampleapp.collect.react/node_modules/react-native/Libraries/Renderer/src/renderers/native/ReactNativeBaseComponent.js:74:41)\n', -}; - -export const ANDROID_REACT_NATIVE_PROD = { - message: 'Error: test', - name: 'Error', - stack: - 'value@index.android.bundle:12:1917\n' + - 'onPress@index.android.bundle:12:2336\n' + - 'touchableHandlePress@index.android.bundle:258:1497\n' + - '[native code]\n' + - '_performSideEffectsForTransition@index.android.bundle:252:8508\n' + - '[native code]\n' + - '_receiveSignal@index.android.bundle:252:7291\n' + - '[native code]\n' + - 'touchableHandleResponderRelease@index.android.bundle:252:4735\n' + - '[native code]\n' + - 'u@index.android.bundle:79:142\n' + - 'invokeGuardedCallback@index.android.bundle:79:459\n' + - 'invokeGuardedCallbackAndCatchFirstError@index.android.bundle:79:580\n' + - 'c@index.android.bundle:95:365\n' + - 'a@index.android.bundle:95:567\n' + - 'v@index.android.bundle:146:501\n' + - 'g@index.android.bundle:146:604\n' + - 'forEach@[native code]\n' + - 'i@index.android.bundle:149:80\n' + - 'processEventQueue@index.android.bundle:146:1432\n' + - 's@index.android.bundle:157:88\n' + - 'handleTopLevel@index.android.bundle:157:174\n' + - 'index.android.bundle:156:572\n' + - 'a@index.android.bundle:93:276\n' + - 'c@index.android.bundle:93:60\n' + - 'perform@index.android.bundle:177:596\n' + - 'batchedUpdates@index.android.bundle:188:464\n' + - 'i@index.android.bundle:176:358\n' + - 'i@index.android.bundle:93:90\n' + - 'u@index.android.bundle:93:150\n' + - '_receiveRootNodeIDEvent@index.android.bundle:156:544\n' + - 'receiveTouches@index.android.bundle:156:918\n' + - 'value@index.android.bundle:29:3016\n' + - 'index.android.bundle:29:955\n' + - 'value@index.android.bundle:29:2417\n' + - 'value@index.android.bundle:29:927\n' + - '[native code]', -}; - -export const ANDROID_REACT_NATIVE_HERMES = { - message: 'Error: lets throw!', - name: 'Error', - stack: - 'at onPress (address at index.android.bundle:1:452701)\n' + - 'at anonymous (address at index.android.bundle:1:224280)\n' + - 'at _performSideEffectsForTransition (address at index.android.bundle:1:230843)\n' + - 'at _receiveSignal (native)\n' + - 'at touchableHandleResponderRelease (native)\n' + - 'at onResponderRelease (native)\n' + - 'at apply (native)\n' + - 'at b (address at index.android.bundle:1:74037)\n' + - 'at apply (native)\n' + - 'at k (address at index.android.bundle:1:74094)\n' + - 'at apply (native)\n' + - 'at C (address at index.android.bundle:1:74126)\n' + - 'at N (address at index.android.bundle:1:74267)\n' + - 'at A (address at index.android.bundle:1:74709)\n' + - 'at forEach (native)\n' + - 'at z (address at index.android.bundle:1:74642)\n' + - 'at anonymous (address at index.android.bundle:1:77747)\n' + - 'at _e (address at index.android.bundle:1:127755)\n' + - 'at Ne (address at index.android.bundle:1:77238)\n' + - 'at Ue (address at index.android.bundle:1:77571)\n' + - 'at receiveTouches (address at index.android.bundle:1:122512)\n' + - 'at apply (native)\n' + - 'at value (address at index.android.bundle:1:33176)\n' + - 'at anonymous (address at index.android.bundle:1:31603)\n' + - 'at value (address at index.android.bundle:1:32776)\n' + - 'at value (address at index.android.bundle:1:31561)', -}; diff --git a/packages/browser/test/unit/tracekit/react-native.test.ts b/packages/browser/test/unit/tracekit/react-native.test.ts new file mode 100644 index 000000000000..eb8ed1d521ab --- /dev/null +++ b/packages/browser/test/unit/tracekit/react-native.test.ts @@ -0,0 +1,311 @@ +import { computeStackTrace } from '../../../src/tracekit'; + +describe('Tracekit - React Native Tests', () => { + it('should parse exceptions for react-native-v8', () => { + const REACT_NATIVE_V8_EXCEPTION = { + message: 'Manually triggered crash to test Sentry reporting', + name: 'Error', + stack: `Error: Manually triggered crash to test Sentry reporting + at Object.onPress(index.android.bundle:2342:3773) + at s.touchableHandlePress(index.android.bundle:214:2048) + at s._performSideEffectsForTransition(index.android.bundle:198:9608) + at s._receiveSignal(index.android.bundle:198:8309) + at s.touchableHandleResponderRelease(index.android.bundle:198:5615) + at Object.y(index.android.bundle:93:571) + at P(index.android.bundle:93:714)`, + }; + const stacktrace = computeStackTrace(REACT_NATIVE_V8_EXCEPTION); + + expect(stacktrace).toEqual({ + message: 'Manually triggered crash to test Sentry reporting', + name: 'Error', + stack: [ + { url: 'index.android.bundle', func: 'Object.onPress', line: 2342, column: 3773 }, + { url: 'index.android.bundle', func: 's.touchableHandlePress', line: 214, column: 2048 }, + { url: 'index.android.bundle', func: 's._performSideEffectsForTransition', line: 198, column: 9608 }, + { url: 'index.android.bundle', func: 's._receiveSignal', line: 198, column: 8309 }, + { url: 'index.android.bundle', func: 's.touchableHandleResponderRelease', line: 198, column: 5615 }, + { url: 'index.android.bundle', func: 'Object.y', line: 93, column: 571 }, + { url: 'index.android.bundle', func: 'P', line: 93, column: 714 }, + ], + }); + }); + + it('should parse exceptions for react-native Expo bundles', () => { + const REACT_NATIVE_EXPO_EXCEPTION = { + message: 'Test Error Expo', + name: 'Error', + stack: `onPress@/data/user/0/com.sentrytest/files/.expo-internal/bundle-613EDD44F3305B9D75D4679663900F2BCDDDC326F247CA3202A3A4219FD412D3:595:658 + value@/data/user/0/com.sentrytest/files/.expo-internal/bundle-613EDD44F3305B9D75D4679663900F2BCDDDC326F247CA3202A3A4219FD412D3:221:7656 + onResponderRelease@/data/user/0/com.sentrytest/files/.expo-internal/bundle-613EDD44F3305B9D75D4679663900F2BCDDDC326F247CA3202A3A4219FD412D3:221:5666 + p@/data/user/0/com.sentrytest/files/.expo-internal/bundle-613EDD44F3305B9D75D4679663900F2BCDDDC326F247CA3202A3A4219FD412D3:96:385 + forEach@[native code]`, + }; + const stacktrace = computeStackTrace(REACT_NATIVE_EXPO_EXCEPTION); + + expect(stacktrace).toEqual({ + message: 'Test Error Expo', + name: 'Error', + stack: [ + { + url: '/data/user/0/com.sentrytest/files/.expo-internal/bundle-613EDD44F3305B9D75D4679663900F2BCDDDC326F247CA3202A3A4219FD412D3', + func: 'onPress', + line: 595, + column: 658, + }, + { + url: '/data/user/0/com.sentrytest/files/.expo-internal/bundle-613EDD44F3305B9D75D4679663900F2BCDDDC326F247CA3202A3A4219FD412D3', + func: 'value', + line: 221, + column: 7656, + }, + { + url: '/data/user/0/com.sentrytest/files/.expo-internal/bundle-613EDD44F3305B9D75D4679663900F2BCDDDC326F247CA3202A3A4219FD412D3', + func: 'onResponderRelease', + line: 221, + column: 5666, + }, + { + url: '/data/user/0/com.sentrytest/files/.expo-internal/bundle-613EDD44F3305B9D75D4679663900F2BCDDDC326F247CA3202A3A4219FD412D3', + func: 'p', + line: 96, + column: 385, + }, + { url: '[native code]', func: 'forEach', line: null, column: null }, + ], + }); + }); + + it('should parse React Native errors on Android', () => { + const ANDROID_REACT_NATIVE = { + message: 'Error: test', + name: 'Error', + stack: + 'Error: test\n' + + 'at render(/home/username/sample-workspace/sampleapp.collect.react/src/components/GpsMonitorScene.js:78:24)\n' + + 'at _renderValidatedComponentWithoutOwnerOrContext(/home/username/sample-workspace/sampleapp.collect.react/node_modules/react-native/Libraries/Renderer/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js:1050:29)\n' + + 'at _renderValidatedComponent(/home/username/sample-workspace/sampleapp.collect.react/node_modules/react-native/Libraries/Renderer/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js:1075:15)\n' + + 'at renderedElement(/home/username/sample-workspace/sampleapp.collect.react/node_modules/react-native/Libraries/Renderer/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js:484:29)\n' + + 'at _currentElement(/home/username/sample-workspace/sampleapp.collect.react/node_modules/react-native/Libraries/Renderer/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js:346:40)\n' + + 'at child(/home/username/sample-workspace/sampleapp.collect.react/node_modules/react-native/Libraries/Renderer/src/renderers/shared/stack/reconciler/ReactReconciler.js:68:25)\n' + + 'at children(/home/username/sample-workspace/sampleapp.collect.react/node_modules/react-native/Libraries/Renderer/src/renderers/shared/stack/reconciler/ReactMultiChild.js:264:10)\n' + + 'at this(/home/username/sample-workspace/sampleapp.collect.react/node_modules/react-native/Libraries/Renderer/src/renderers/native/ReactNativeBaseComponent.js:74:41)\n', + }; + + const stackFrames = computeStackTrace(ANDROID_REACT_NATIVE); + + expect(stackFrames).toEqual({ + message: 'Error: test', + name: 'Error', + stack: [ + { + url: '/home/username/sample-workspace/sampleapp.collect.react/src/components/GpsMonitorScene.js', + func: 'render', + line: 78, + column: 24, + }, + { + url: '/home/username/sample-workspace/sampleapp.collect.react/node_modules/react-native/Libraries/Renderer/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js', + func: '_renderValidatedComponentWithoutOwnerOrContext', + line: 1050, + column: 29, + }, + { + url: '/home/username/sample-workspace/sampleapp.collect.react/node_modules/react-native/Libraries/Renderer/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js', + func: '_renderValidatedComponent', + line: 1075, + column: 15, + }, + { + url: '/home/username/sample-workspace/sampleapp.collect.react/node_modules/react-native/Libraries/Renderer/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js', + func: 'renderedElement', + line: 484, + column: 29, + }, + { + url: '/home/username/sample-workspace/sampleapp.collect.react/node_modules/react-native/Libraries/Renderer/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js', + func: '_currentElement', + line: 346, + column: 40, + }, + { + url: '/home/username/sample-workspace/sampleapp.collect.react/node_modules/react-native/Libraries/Renderer/src/renderers/shared/stack/reconciler/ReactReconciler.js', + func: 'child', + line: 68, + column: 25, + }, + { + url: '/home/username/sample-workspace/sampleapp.collect.react/node_modules/react-native/Libraries/Renderer/src/renderers/shared/stack/reconciler/ReactMultiChild.js', + func: 'children', + line: 264, + column: 10, + }, + { + url: '/home/username/sample-workspace/sampleapp.collect.react/node_modules/react-native/Libraries/Renderer/src/renderers/native/ReactNativeBaseComponent.js', + func: 'this', + line: 74, + column: 41, + }, + ], + }); + }); + + it('should parse React Native errors on Android Production', () => { + const ANDROID_REACT_NATIVE_PROD = { + message: 'Error: test', + name: 'Error', + stack: + 'value@index.android.bundle:12:1917\n' + + 'onPress@index.android.bundle:12:2336\n' + + 'touchableHandlePress@index.android.bundle:258:1497\n' + + '[native code]\n' + + '_performSideEffectsForTransition@index.android.bundle:252:8508\n' + + '[native code]\n' + + '_receiveSignal@index.android.bundle:252:7291\n' + + '[native code]\n' + + 'touchableHandleResponderRelease@index.android.bundle:252:4735\n' + + '[native code]\n' + + 'u@index.android.bundle:79:142\n' + + 'invokeGuardedCallback@index.android.bundle:79:459\n' + + 'invokeGuardedCallbackAndCatchFirstError@index.android.bundle:79:580\n' + + 'c@index.android.bundle:95:365\n' + + 'a@index.android.bundle:95:567\n' + + 'v@index.android.bundle:146:501\n' + + 'g@index.android.bundle:146:604\n' + + 'forEach@[native code]\n' + + 'i@index.android.bundle:149:80\n' + + 'processEventQueue@index.android.bundle:146:1432\n' + + 's@index.android.bundle:157:88\n' + + 'handleTopLevel@index.android.bundle:157:174\n' + + 'index.android.bundle:156:572\n' + + 'a@index.android.bundle:93:276\n' + + 'c@index.android.bundle:93:60\n' + + 'perform@index.android.bundle:177:596\n' + + 'batchedUpdates@index.android.bundle:188:464\n' + + 'i@index.android.bundle:176:358\n' + + 'i@index.android.bundle:93:90\n' + + 'u@index.android.bundle:93:150\n' + + '_receiveRootNodeIDEvent@index.android.bundle:156:544\n' + + 'receiveTouches@index.android.bundle:156:918\n' + + 'value@index.android.bundle:29:3016\n' + + 'index.android.bundle:29:955\n' + + 'value@index.android.bundle:29:2417\n' + + 'value@index.android.bundle:29:927\n' + + '[native code]', + }; + + const stackFrames = computeStackTrace(ANDROID_REACT_NATIVE_PROD); + + expect(stackFrames).toEqual({ + message: 'Error: test', + name: 'Error', + stack: [ + { url: 'index.android.bundle', func: 'value', line: 12, column: 1917 }, + { url: 'index.android.bundle', func: 'onPress', line: 12, column: 2336 }, + { url: 'index.android.bundle', func: 'touchableHandlePress', line: 258, column: 1497 }, + { url: '[native code]', func: '?', line: null, column: null }, + { url: 'index.android.bundle', func: '_performSideEffectsForTransition', line: 252, column: 8508 }, + { url: '[native code]', func: '?', line: null, column: null }, + { url: 'index.android.bundle', func: '_receiveSignal', line: 252, column: 7291 }, + { url: '[native code]', func: '?', line: null, column: null }, + { url: 'index.android.bundle', func: 'touchableHandleResponderRelease', line: 252, column: 4735 }, + { url: '[native code]', func: '?', line: null, column: null }, + { url: 'index.android.bundle', func: 'u', line: 79, column: 142 }, + { url: 'index.android.bundle', func: 'invokeGuardedCallback', line: 79, column: 459 }, + { url: 'index.android.bundle', func: 'invokeGuardedCallbackAndCatchFirstError', line: 79, column: 580 }, + { url: 'index.android.bundle', func: 'c', line: 95, column: 365 }, + { url: 'index.android.bundle', func: 'a', line: 95, column: 567 }, + { url: 'index.android.bundle', func: 'v', line: 146, column: 501 }, + { url: 'index.android.bundle', func: 'g', line: 146, column: 604 }, + { url: '[native code]', func: 'forEach', line: null, column: null }, + { url: 'index.android.bundle', func: 'i', line: 149, column: 80 }, + { url: 'index.android.bundle', func: 'processEventQueue', line: 146, column: 1432 }, + { url: 'index.android.bundle', func: 's', line: 157, column: 88 }, + { url: 'index.android.bundle', func: 'handleTopLevel', line: 157, column: 174 }, + { url: 'index.android.bundle', func: '?', line: 156, column: 572 }, + { url: 'index.android.bundle', func: 'a', line: 93, column: 276 }, + { url: 'index.android.bundle', func: 'c', line: 93, column: 60 }, + { url: 'index.android.bundle', func: 'perform', line: 177, column: 596 }, + { url: 'index.android.bundle', func: 'batchedUpdates', line: 188, column: 464 }, + { url: 'index.android.bundle', func: 'i', line: 176, column: 358 }, + { url: 'index.android.bundle', func: 'i', line: 93, column: 90 }, + { url: 'index.android.bundle', func: 'u', line: 93, column: 150 }, + { url: 'index.android.bundle', func: '_receiveRootNodeIDEvent', line: 156, column: 544 }, + { url: 'index.android.bundle', func: 'receiveTouches', line: 156, column: 918 }, + { url: 'index.android.bundle', func: 'value', line: 29, column: 3016 }, + { url: 'index.android.bundle', func: '?', line: 29, column: 955 }, + { url: 'index.android.bundle', func: 'value', line: 29, column: 2417 }, + { url: 'index.android.bundle', func: 'value', line: 29, column: 927 }, + { url: '[native code]', func: '?', line: null, column: null }, + ], + }); + }); + + it('should parse React Native errors on Android Hermes', () => { + const ANDROID_REACT_NATIVE_HERMES = { + message: 'Error: lets throw!', + name: 'Error', + stack: + 'at onPress (address at index.android.bundle:1:452701)\n' + + 'at anonymous (address at index.android.bundle:1:224280)\n' + + 'at _performSideEffectsForTransition (address at index.android.bundle:1:230843)\n' + + 'at _receiveSignal (native)\n' + + 'at touchableHandleResponderRelease (native)\n' + + 'at onResponderRelease (native)\n' + + 'at apply (native)\n' + + 'at b (address at index.android.bundle:1:74037)\n' + + 'at apply (native)\n' + + 'at k (address at index.android.bundle:1:74094)\n' + + 'at apply (native)\n' + + 'at C (address at index.android.bundle:1:74126)\n' + + 'at N (address at index.android.bundle:1:74267)\n' + + 'at A (address at index.android.bundle:1:74709)\n' + + 'at forEach (native)\n' + + 'at z (address at index.android.bundle:1:74642)\n' + + 'at anonymous (address at index.android.bundle:1:77747)\n' + + 'at _e (address at index.android.bundle:1:127755)\n' + + 'at Ne (address at index.android.bundle:1:77238)\n' + + 'at Ue (address at index.android.bundle:1:77571)\n' + + 'at receiveTouches (address at index.android.bundle:1:122512)\n' + + 'at apply (native)\n' + + 'at value (address at index.android.bundle:1:33176)\n' + + 'at anonymous (address at index.android.bundle:1:31603)\n' + + 'at value (address at index.android.bundle:1:32776)\n' + + 'at value (address at index.android.bundle:1:31561)', + }; + const stackFrames = computeStackTrace(ANDROID_REACT_NATIVE_HERMES); + + expect(stackFrames).toEqual({ + message: 'Error: lets throw!', + name: 'Error', + stack: [ + { url: 'index.android.bundle', func: 'onPress', line: 1, column: 452701 }, + { url: 'index.android.bundle', func: 'anonymous', line: 1, column: 224280 }, + { url: 'index.android.bundle', func: '_performSideEffectsForTransition', line: 1, column: 230843 }, + { url: 'native', func: '_receiveSignal', line: null, column: null }, + { url: 'native', func: 'touchableHandleResponderRelease', line: null, column: null }, + { url: 'native', func: 'onResponderRelease', line: null, column: null }, + { url: 'native', func: 'apply', line: null, column: null }, + { url: 'index.android.bundle', func: 'b', line: 1, column: 74037 }, + { url: 'native', func: 'apply', line: null, column: null }, + { url: 'index.android.bundle', func: 'k', line: 1, column: 74094 }, + { url: 'native', func: 'apply', line: null, column: null }, + { url: 'index.android.bundle', func: 'C', line: 1, column: 74126 }, + { url: 'index.android.bundle', func: 'N', line: 1, column: 74267 }, + { url: 'index.android.bundle', func: 'A', line: 1, column: 74709 }, + { url: 'native', func: 'forEach', line: null, column: null }, + { url: 'index.android.bundle', func: 'z', line: 1, column: 74642 }, + { url: 'index.android.bundle', func: 'anonymous', line: 1, column: 77747 }, + { url: 'index.android.bundle', func: '_e', line: 1, column: 127755 }, + { url: 'index.android.bundle', func: 'Ne', line: 1, column: 77238 }, + { url: 'index.android.bundle', func: 'Ue', line: 1, column: 77571 }, + { url: 'index.android.bundle', func: 'receiveTouches', line: 1, column: 122512 }, + { url: 'native', func: 'apply', line: null, column: null }, + { url: 'index.android.bundle', func: 'value', line: 1, column: 33176 }, + { url: 'index.android.bundle', func: 'anonymous', line: 1, column: 31603 }, + { url: 'index.android.bundle', func: 'value', line: 1, column: 32776 }, + { url: 'index.android.bundle', func: 'value', line: 1, column: 31561 }, + ], + }); + }); +}); diff --git a/packages/browser/test/unit/tracekit/react.test.ts b/packages/browser/test/unit/tracekit/react.test.ts new file mode 100644 index 000000000000..7f648ff39593 --- /dev/null +++ b/packages/browser/test/unit/tracekit/react.test.ts @@ -0,0 +1,86 @@ +import { computeStackTrace } from '../../../src/tracekit'; + +describe('Tracekit - React Tests', () => { + it('should correctly parse Invariant Violation errors and use framesToPop to drop info message', () => { + const REACT_INVARIANT_VIOLATION_EXCEPTION = { + framesToPop: 1, + message: + 'Minified React error #31; visit https://reactjs.org/docs/error-decoder.html?invariant=31&args[]=object%20with%20keys%20%7B%7D&args[]= for the full message or use the non-minified dev environment for full errors and additional helpful warnings. ', + name: 'Invariant Violation', + stack: `Invariant Violation: Minified React error #31; visit https://reactjs.org/docs/error-decoder.html?invariant=31&args[]=object%20with%20keys%20%7B%7D&args[]= for the full message or use the non-minified dev environment for full errors and additional helpful warnings. + at http://localhost:5000/static/js/foo.chunk.js:1:21738 + at a (http://localhost:5000/static/js/foo.chunk.js:1:21841) + at ho (http://localhost:5000/static/js/foo.chunk.js:1:68735) + at f (http://localhost:5000/:1:980)`, + }; + + const stacktrace = computeStackTrace(REACT_INVARIANT_VIOLATION_EXCEPTION); + + expect(stacktrace).toEqual({ + message: + 'Minified React error #31; visit https://reactjs.org/docs/error-decoder.html?invariant=31&args[]=object%20with%20keys%20%7B%7D&args[]= for the full message or use the non-minified dev environment for full errors and additional helpful warnings. ', + name: 'Invariant Violation', + stack: [ + { url: 'http://localhost:5000/static/js/foo.chunk.js', func: '?', line: 1, column: 21738 }, + { url: 'http://localhost:5000/static/js/foo.chunk.js', func: 'a', line: 1, column: 21841 }, + { url: 'http://localhost:5000/static/js/foo.chunk.js', func: 'ho', line: 1, column: 68735 }, + { url: 'http://localhost:5000/', func: 'f', line: 1, column: 980 }, + ], + }); + }); + + it('should correctly parse production errors and drop initial frame if its not relevant', () => { + const REACT_PRODUCTION_ERROR = { + message: + 'Minified React error #200; visit https://reactjs.org/docs/error-decoder.html?invariant=200 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.', + name: 'Error', + stack: `Error: Minified React error #200; visit https://reactjs.org/docs/error-decoder.html?invariant=200 for the full message or use the non-minified dev environment for full errors and additional helpful warnings. + at http://localhost:5000/static/js/foo.chunk.js:1:21738 + at a (http://localhost:5000/static/js/foo.chunk.js:1:21841) + at ho (http://localhost:5000/static/js/foo.chunk.js:1:68735) + at f (http://localhost:5000/:1:980)`, + }; + + const stacktrace = computeStackTrace(REACT_PRODUCTION_ERROR); + + expect(stacktrace).toEqual({ + message: + 'Minified React error #200; visit https://reactjs.org/docs/error-decoder.html?invariant=200 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.', + name: 'Error', + stack: [ + { url: 'http://localhost:5000/static/js/foo.chunk.js', func: '?', line: 1, column: 21738 }, + { url: 'http://localhost:5000/static/js/foo.chunk.js', func: 'a', line: 1, column: 21841 }, + { url: 'http://localhost:5000/static/js/foo.chunk.js', func: 'ho', line: 1, column: 68735 }, + { url: 'http://localhost:5000/', func: 'f', line: 1, column: 980 }, + ], + }); + }); + + it('should not drop additional frame for production errors if framesToPop is still there', () => { + const REACT_PRODUCTION_ERROR = { + framesToPop: 1, + message: + 'Minified React error #200; visit https://reactjs.org/docs/error-decoder.html?invariant=200 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.', + name: 'Error', + stack: `Error: Minified React error #200; visit https://reactjs.org/docs/error-decoder.html?invariant=200 for the full message or use the non-minified dev environment for full errors and additional helpful warnings. + at http://localhost:5000/static/js/foo.chunk.js:1:21738 + at a (http://localhost:5000/static/js/foo.chunk.js:1:21841) + at ho (http://localhost:5000/static/js/foo.chunk.js:1:68735) + at f (http://localhost:5000/:1:980)`, + }; + + const stacktrace = computeStackTrace(REACT_PRODUCTION_ERROR); + + expect(stacktrace).toEqual({ + message: + 'Minified React error #200; visit https://reactjs.org/docs/error-decoder.html?invariant=200 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.', + name: 'Error', + stack: [ + { url: 'http://localhost:5000/static/js/foo.chunk.js', func: '?', line: 1, column: 21738 }, + { url: 'http://localhost:5000/static/js/foo.chunk.js', func: 'a', line: 1, column: 21841 }, + { url: 'http://localhost:5000/static/js/foo.chunk.js', func: 'ho', line: 1, column: 68735 }, + { url: 'http://localhost:5000/', func: 'f', line: 1, column: 980 }, + ], + }); + }); +}); diff --git a/packages/browser/test/unit/tracekit/safari.test.ts b/packages/browser/test/unit/tracekit/safari.test.ts new file mode 100644 index 000000000000..55e9b4c7fb9c --- /dev/null +++ b/packages/browser/test/unit/tracekit/safari.test.ts @@ -0,0 +1,294 @@ +import { computeStackTrace } from '../../../src/tracekit'; + +describe('Tracekit - Safari Tests', () => { + it('should parse Safari 6 error', () => { + const SAFARI_6 = { + name: 'foo', + message: "'null' is not an object (evaluating 'x.undef')", + stack: + '@http://path/to/file.js:48\n' + + 'dumpException3@http://path/to/file.js:52\n' + + 'onclick@http://path/to/file.js:82\n' + + '[native code]', + line: 48, + sourceURL: 'http://path/to/file.js', + }; + + const stackFrames = computeStackTrace(SAFARI_6); + + expect(stackFrames).toEqual({ + message: "'null' is not an object (evaluating 'x.undef')", + name: 'foo', + stack: [ + { url: 'http://path/to/file.js', func: '?', line: 48, column: null }, + { url: 'http://path/to/file.js', func: 'dumpException3', line: 52, column: null }, + { url: 'http://path/to/file.js', func: 'onclick', line: 82, column: null }, + { url: '[native code]', func: '?', line: null, column: null }, + ], + }); + }); + + it('should parse Safari 7 error', () => { + const SAFARI_7 = { + message: "'null' is not an object (evaluating 'x.undef')", + name: 'TypeError', + stack: + 'http://path/to/file.js:48:22\n' + 'foo@http://path/to/file.js:52:15\n' + 'bar@http://path/to/file.js:108:107', + line: 47, + sourceURL: 'http://path/to/file.js', + }; + + const stackFrames = computeStackTrace(SAFARI_7); + + expect(stackFrames).toEqual({ + message: "'null' is not an object (evaluating 'x.undef')", + name: 'TypeError', + stack: [ + { url: 'http://path/to/file.js', func: '?', line: 48, column: 22 }, + { url: 'http://path/to/file.js', func: 'foo', line: 52, column: 15 }, + { url: 'http://path/to/file.js', func: 'bar', line: 108, column: 107 }, + ], + }); + }); + + it('should parse Safari 8 error', () => { + const SAFARI_8 = { + message: "null is not an object (evaluating 'x.undef')", + name: 'TypeError', + stack: + 'http://path/to/file.js:47:22\n' + 'foo@http://path/to/file.js:52:15\n' + 'bar@http://path/to/file.js:108:23', + line: 47, + column: 22, + sourceURL: 'http://path/to/file.js', + }; + + const stackFrames = computeStackTrace(SAFARI_8); + + expect(stackFrames).toEqual({ + message: "null is not an object (evaluating 'x.undef')", + name: 'TypeError', + stack: [ + { url: 'http://path/to/file.js', func: '?', line: 47, column: 22 }, + { url: 'http://path/to/file.js', func: 'foo', line: 52, column: 15 }, + { url: 'http://path/to/file.js', func: 'bar', line: 108, column: 23 }, + ], + }); + }); + + it('should parse Safari 8 eval error', () => { + // TODO: Take into account the line and column properties on the error object and use them for the first stack trace. + + const SAFARI_8_EVAL = { + message: "Can't find variable: getExceptionProps", + name: 'ReferenceError', + stack: + 'eval code\n' + + 'eval@[native code]\n' + + 'foo@http://path/to/file.js:58:21\n' + + 'bar@http://path/to/file.js:109:91', + line: 1, + column: 18, + }; + + const stackFrames = computeStackTrace(SAFARI_8_EVAL); + + expect(stackFrames).toEqual({ + message: "Can't find variable: getExceptionProps", + name: 'ReferenceError', + stack: [ + { url: '[native code]', func: 'eval', line: null, column: null }, + { url: 'http://path/to/file.js', func: 'foo', line: 58, column: 21 }, + { url: 'http://path/to/file.js', func: 'bar', line: 109, column: 91 }, + ], + }); + }); + + describe('Safari extensions', () => { + it('should parse exceptions for safari-extension', () => { + const SAFARI_EXTENSION_EXCEPTION = { + message: 'wat', + name: 'Error', + stack: `Error: wat + at ClipperError@safari-extension:(//3284871F-A480-4FFC-8BC4-3F362C752446/2665fee0/commons.js:223036:10) + at safari-extension:(//3284871F-A480-4FFC-8BC4-3F362C752446/2665fee0/topee-content.js:3313:26)`, + }; + + const stacktrace = computeStackTrace(SAFARI_EXTENSION_EXCEPTION); + + expect(stacktrace).toEqual({ + message: 'wat', + name: 'Error', + stack: [ + { + url: 'safari-extension://3284871F-A480-4FFC-8BC4-3F362C752446/2665fee0/commons.js', + func: 'ClipperError', + line: 223036, + column: 10, + }, + { + url: 'safari-extension://3284871F-A480-4FFC-8BC4-3F362C752446/2665fee0/topee-content.js', + func: '?', + line: 3313, + column: 26, + }, + ], + }); + }); + + it('should parse exceptions for safari-extension with frames-only stack', () => { + const SAFARI_EXTENSION_EXCEPTION = { + message: `undefined is not an object (evaluating 'e.groups.includes')`, + name: `TypeError`, + stack: `isClaimed@safari-extension://com.grammarly.safari.extension.ext2-W8F64X92K3/ee7759dd/Grammarly.js:2:929865 + safari-extension://com.grammarly.safari.extension.ext2-W8F64X92K3/ee7759dd/Grammarly.js:2:1588410 + promiseReactionJob@[native code]`, + }; + const stacktrace = computeStackTrace(SAFARI_EXTENSION_EXCEPTION); + + expect(stacktrace).toEqual({ + message: "undefined is not an object (evaluating 'e.groups.includes')", + name: 'TypeError', + stack: [ + { + url: 'safari-extension://com.grammarly.safari.extension.ext2-W8F64X92K3/ee7759dd/Grammarly.js', + func: 'isClaimed', + line: 2, + column: 929865, + }, + { + url: 'safari-extension://com.grammarly.safari.extension.ext2-W8F64X92K3/ee7759dd/Grammarly.js', + func: '?', + line: 2, + column: 1588410, + }, + { url: '[native code]', func: 'promiseReactionJob', line: null, column: null }, + ], + }); + }); + + it('should parse exceptions for safari-web-extension', () => { + const SAFARI_WEB_EXTENSION_EXCEPTION = { + message: 'wat', + name: 'Error', + stack: `Error: wat + at ClipperError@safari-web-extension:(//3284871F-A480-4FFC-8BC4-3F362C752446/2665fee0/commons.js:223036:10) + at safari-web-extension:(//3284871F-A480-4FFC-8BC4-3F362C752446/2665fee0/topee-content.js:3313:26)`, + }; + + const stacktrace = computeStackTrace(SAFARI_WEB_EXTENSION_EXCEPTION); + + expect(stacktrace).toEqual({ + message: 'wat', + name: 'Error', + stack: [ + { + url: 'safari-web-extension://3284871F-A480-4FFC-8BC4-3F362C752446/2665fee0/commons.js', + func: 'ClipperError', + line: 223036, + column: 10, + }, + { + url: 'safari-web-extension://3284871F-A480-4FFC-8BC4-3F362C752446/2665fee0/topee-content.js', + func: '?', + line: 3313, + column: 26, + }, + ], + }); + }); + + it('should parse exceptions for safari-web-extension with frames-only stack', () => { + const SAFARI_EXTENSION_EXCEPTION = { + message: `undefined is not an object (evaluating 'e.groups.includes')`, + name: `TypeError`, + stack: `p_@safari-web-extension://46434E60-F5BD-48A4-80C8-A422C5D16897/scripts/content-script.js:29:33314 + safari-web-extension://46434E60-F5BD-48A4-80C8-A422C5D16897/scripts/content-script.js:29:56027 + promiseReactionJob@[native code]`, + }; + const stacktrace = computeStackTrace(SAFARI_EXTENSION_EXCEPTION); + + expect(stacktrace).toEqual({ + message: "undefined is not an object (evaluating 'e.groups.includes')", + name: 'TypeError', + stack: [ + { + url: 'safari-web-extension://46434E60-F5BD-48A4-80C8-A422C5D16897/scripts/content-script.js', + func: 'p_', + line: 29, + column: 33314, + }, + { + url: 'safari-web-extension://46434E60-F5BD-48A4-80C8-A422C5D16897/scripts/content-script.js', + func: '?', + line: 29, + column: 56027, + }, + { url: '[native code]', func: 'promiseReactionJob', line: null, column: null }, + ], + }); + }); + }); + + it('should parse exceptions with native code frames in Safari 12', () => { + const SAFARI12_NATIVE_CODE_EXCEPTION = { + message: 'test', + name: 'Error', + stack: `fooIterator@http://localhost:5000/test:20:26 + map@[native code] + foo@http://localhost:5000/test:19:22 + global code@http://localhost:5000/test:24:10`, + }; + + const stacktrace = computeStackTrace(SAFARI12_NATIVE_CODE_EXCEPTION); + + expect(stacktrace).toEqual({ + message: 'test', + name: 'Error', + stack: [ + { url: 'http://localhost:5000/test', func: 'fooIterator', line: 20, column: 26 }, + { url: '[native code]', func: 'map', line: null, column: null }, + { url: 'http://localhost:5000/test', func: 'foo', line: 19, column: 22 }, + { url: 'http://localhost:5000/test', func: 'global code', line: 24, column: 10 }, + ], + }); + }); + + it('should parse exceptions with eval frames in Safari 12', () => { + const SAFARI12_EVAL_EXCEPTION = { + message: 'aha', + name: 'Error', + stack: `aha@http://localhost:5000/:19:22 + aha@[native code] + callAnotherThing@http://localhost:5000/:20:16 + callback@http://localhost:5000/:25:23 + http://localhost:5000/:34:25 + map@[native code] + test@http://localhost:5000/:33:26 + eval code + eval@[native code] + aha@http://localhost:5000/:39:9 + testMethod@http://localhost:5000/:44:10 + http://localhost:5000/:50:29`, + }; + + const stacktrace = computeStackTrace(SAFARI12_EVAL_EXCEPTION); + + expect(stacktrace).toEqual({ + message: 'aha', + name: 'Error', + stack: [ + { url: 'http://localhost:5000/', func: 'aha', line: 19, column: 22 }, + { url: '[native code]', func: 'aha', line: null, column: null }, + { url: 'http://localhost:5000/', func: 'callAnotherThing', line: 20, column: 16 }, + { url: 'http://localhost:5000/', func: 'callback', line: 25, column: 23 }, + { url: 'http://localhost:5000/', func: '?', line: 34, column: 25 }, + { url: '[native code]', func: 'map', line: null, column: null }, + { url: 'http://localhost:5000/', func: 'test', line: 33, column: 26 }, + { url: '[native code]', func: 'eval', line: null, column: null }, + { url: 'http://localhost:5000/', func: 'aha', line: 39, column: 9 }, + { url: 'http://localhost:5000/', func: 'testMethod', line: 44, column: 10 }, + { url: 'http://localhost:5000/', func: '?', line: 50, column: 29 }, + ], + }); + }); +});