import { ErrorTracker } from '../../packages/core/src/crawlers/error_tracker'; const random = () => Math.random().toString(36).slice(2); const g = (error: { name?: string; message?: string; code?: string | number; stack?: string; cause?: any }) => { return { name: random(), message: random(), code: 'code' in error ? error.code : random(), stack: random(), ...error, }; }; const s = (stack: string) => { const evalIndex = stack.indexOf('eval at '); const atIndex = stack.indexOf('at '); const index = evalIndex === -1 ? atIndex : evalIndex; return stack .slice(index) .split('\n') .map((line) => line.trim()) .join('\n'); }; // A case for // https://github.com/microsoft/playwright/blob/99d1ad5a88c3e89360829eee92dbaa98d75beaa4/packages/playwright-core/src/server/dispatchers/dispatcher.ts#L329 const multilineError = { name: 'TypeError', message: 'Invalid URL', code: 'ERR_INVALID_URL', stack: [ 'TypeError: Invalid URL', 'foobar', ' eval at Foo.a (eval at Bar.z (node:url))', ' eval at Foo.a (eval at Bar.z (myscript.js:10:3))', ' eval at Foo.a (eval at Bar.z (node:http))', ].join('\n'), cause: undefined as any, } as const; const error = { name: 'TypeError', message: 'Invalid URL', code: 'ERR_INVALID_URL', stack: 'eval at Foo.a (eval at Bar.z (myscript.js:10:3))', cause: undefined as any, } as const; const errorNoCode = { name: 'TypeError', message: 'Invalid URL', stack: 'eval at Foo.a (eval at Bar.z (myscript.js:10:3))', code: undefined as undefined, cause: undefined as any, } as const; const errorRandomCode = { name: 'TypeError', message: 'Invalid URL', stack: 'eval at Foo.a (eval at Bar.z (myscript.js:10:3))', cause: undefined as any, } as const; const errorRandomName = { message: 'Invalid URL', code: 'ERR_INVALID_URL', stack: 'eval at Foo.a (eval at Bar.z (myscript.js:10:3))', cause: undefined as any, } as const; const errorRandomMessage = { name: 'TypeError', code: 'ERR_INVALID_URL', stack: 'eval at Foo.a (eval at Bar.z (myscript.js:10:3))', cause: undefined as any, } as const; const errorRandomStack = { name: 'TypeError', message: 'Invalid URL', code: 'ERR_INVALID_URL', cause: undefined as any, } as const; test('works', () => { const tracker = new ErrorTracker({ showErrorCode: true, showErrorMessage: true, showErrorName: true, showStackTrace: true, showFullStack: false, showFullMessage: true, }); const e = error; expect(tracker.result).toMatchObject({}); tracker.add(g(e)); expect(tracker.result).toMatchObject({ 'myscript.js:10:3': { // source [e.code]: { // code [e.name]: { // name [e.message]: { // message count: 1, }, }, }, }, }); expect(tracker.getUniqueErrorCount()).toBe(1); tracker.add(g(e)); expect(tracker.result).toMatchObject({ 'myscript.js:10:3': { // source [e.code]: { // code [e.name]: { // name [e.message]: { // message count: 2, }, }, }, }, }); expect(tracker.getUniqueErrorCount()).toBe(1); }); test('no code is null code', () => { const tracker = new ErrorTracker({ showErrorCode: true, showErrorMessage: true, showErrorName: true, showStackTrace: true, showFullStack: false, showFullMessage: true, }); const e = errorNoCode; tracker.add(g(e)); tracker.add(g(e)); expect(tracker.result).toMatchObject({ 'myscript.js:10:3': { // source 'missing error code': { // code [e.name]: { // name [e.message]: { // message count: 2, }, }, }, }, }); expect(tracker.getUniqueErrorCount()).toBe(1); }); test('can hide error code', () => { const tracker = new ErrorTracker({ showErrorCode: false, showErrorMessage: true, showErrorName: true, showStackTrace: true, showFullStack: false, showFullMessage: true, }); const e = errorRandomCode; tracker.add(g(errorRandomCode)); tracker.add(g(errorRandomCode)); expect(tracker.result).toMatchObject({ 'myscript.js:10:3': { // source [e.name]: { // name [e.message]: { // message count: 2, }, }, }, }); expect(tracker.getUniqueErrorCount()).toBe(1); }); test('can hide error name', () => { const tracker = new ErrorTracker({ showErrorCode: true, showErrorMessage: true, showErrorName: false, showStackTrace: true, showFullStack: false, showFullMessage: true, }); const e = errorRandomName; tracker.add(g(e)); tracker.add(g(e)); expect(tracker.result).toMatchObject({ 'myscript.js:10:3': { // source [e.code]: { // code [e.message]: { // message count: 2, }, }, }, }); expect(tracker.getUniqueErrorCount()).toBe(1); }); test('can hide error message', () => { const tracker = new ErrorTracker({ showErrorCode: true, showErrorMessage: false, showErrorName: true, showStackTrace: true, showFullStack: false, showFullMessage: true, }); const e = errorRandomMessage; tracker.add(g(errorRandomMessage)); tracker.add(g(errorRandomMessage)); expect(tracker.result).toMatchObject({ 'myscript.js:10:3': { // source [e.code]: { // code [e.name]: { // name count: 2, }, }, }, }); expect(tracker.getUniqueErrorCount()).toBe(1); }); test('can hide error stack', () => { const tracker = new ErrorTracker({ showErrorCode: true, showErrorMessage: true, showErrorName: true, showStackTrace: false, showFullStack: false, showFullMessage: true, }); tracker.add(g(errorRandomStack)); tracker.add(g(errorRandomStack)); expect(tracker.result).toMatchObject({ 'ERR_INVALID_URL': { // code 'TypeError': { // name 'Invalid URL': { // message count: 2, }, }, }, }); expect(tracker.getUniqueErrorCount()).toBe(1); }); test('can display full stack', () => { const tracker = new ErrorTracker({ showErrorCode: true, showErrorMessage: true, showErrorName: true, showStackTrace: true, showFullStack: true, showFullMessage: true, }); const e = multilineError; tracker.add(g(e)); tracker.add(g(e)); expect(tracker.result).toMatchObject({ [s(e.stack)]: { // source [e.code]: { // code [e.name]: { // name [e.message]: { // message count: 2, }, }, }, }, }); expect(tracker.getUniqueErrorCount()).toBe(1); }); test('stack looks for user files first', () => { const tracker = new ErrorTracker({ showErrorCode: true, showErrorMessage: true, showErrorName: true, showStackTrace: true, showFullStack: false, showFullMessage: true, }); const e = multilineError; tracker.add(g(e)); tracker.add(g(e)); expect(tracker.result).toMatchObject({ 'myscript.js:10:3': { // source [e.code]: { // code [e.name]: { // name [e.message]: { // message count: 2, }, }, }, }, }); expect(tracker.getUniqueErrorCount()).toBe(1); }); test('can shorten the message to the first line', () => { const tracker = new ErrorTracker({ showErrorCode: true, showErrorMessage: true, showErrorName: true, showStackTrace: true, showFullStack: false, showFullMessage: false, }); const e = g(multilineError); tracker.add(e); expect(tracker.result).toMatchObject({ 'myscript.js:10:3': { // source [e.code!]: { // code [e.name]: { // name [e.message.split('\n')[0]]: { // message count: 1, }, }, }, }, }); expect(tracker.getUniqueErrorCount()).toBe(1); }); test('supports error.cause', () => { const tracker = new ErrorTracker({ showErrorCode: true, showErrorMessage: true, showErrorName: true, showStackTrace: true, showFullStack: false, showFullMessage: false, }); const e = g(multilineError); e.cause = g(errorRandomMessage); tracker.add(e); expect(tracker.result).toMatchObject({ 'myscript.js:10:3': { // source [e.code!]: { // code [e.name]: { // name [e.message.split('\n')[0]]: { // message count: 1, }, [e.cause.message]: { count: 1, }, }, }, }, }); expect(tracker.getUniqueErrorCount()).toBe(2); }); test('placeholder #1', () => { const tracker = new ErrorTracker({ showErrorCode: true, showErrorMessage: true, showErrorName: true, showStackTrace: true, showFullStack: false, showFullMessage: true, }); tracker.add({ name: 'Error', message: 'Expected boolean, got number', }); tracker.add({ name: 'Error', message: 'Expected boolean, got string', }); tracker.add({ name: 'Error', message: 'Expected boolean, got undefined', }); expect(tracker.result).toMatchObject({ 'missing stack trace': { // source 'missing error code': { // code Error: { // name 'Expected boolean, got _': { // message count: 3, }, }, }, }, }); expect(tracker.getUniqueErrorCount()).toBe(1); }); test('placeholder #2', () => { const tracker = new ErrorTracker({ showErrorCode: true, showErrorMessage: true, showErrorName: true, showStackTrace: true, showFullStack: false, showFullMessage: true, }); tracker.add({ name: 'Error', message: 'Expected `boolean`, got `number`', }); tracker.add({ name: 'Error', message: 'Expected `boolean`, got `string`', }); tracker.add({ name: 'Error', message: 'Expected `boolean`, got `undefined`', }); expect(tracker.result).toMatchObject({ 'missing stack trace': { // source 'missing error code': { // code Error: { // name 'Expected `boolean`, got _': { // message count: 3, }, }, }, }, }); expect(tracker.getUniqueErrorCount()).toBe(1); }); test('placeholder #3', () => { const tracker = new ErrorTracker({ showErrorCode: true, showErrorMessage: true, showErrorName: true, showStackTrace: true, showFullStack: false, showFullMessage: true, }); tracker.add({ name: 'Error', message: '1 2 3', }); tracker.add({ name: 'Error', message: '1 4 3', }); tracker.add({ name: 'Error', message: '1 5 3', }); expect(tracker.result).toMatchObject({ 'missing stack trace': { // source 'missing error code': { // code Error: { // name '1 _ 3': { // message count: 3, }, }, }, }, }); expect(tracker.getUniqueErrorCount()).toBe(1); }); test('placeholder #4', () => { const tracker = new ErrorTracker({ showErrorCode: true, showErrorMessage: true, showErrorName: true, showStackTrace: true, showFullStack: false, showFullMessage: true, }); tracker.add({ name: 'Error', message: '1 2 3', }); tracker.add({ name: 'Error', message: '1 2 4', }); tracker.add({ name: 'Error', message: '1 2 5', }); expect(tracker.result).toMatchObject({ 'missing stack trace': { // source 'missing error code': { // code Error: { // name '1 2 _': { // message count: 3, }, }, }, }, }); expect(tracker.getUniqueErrorCount()).toBe(1); }); test('placeholder #5', () => { const tracker = new ErrorTracker({ showErrorCode: true, showErrorMessage: true, showErrorName: true, showStackTrace: true, showFullStack: false, showFullMessage: true, }); tracker.add({ name: 'Error', message: '1 2 3', }); tracker.add({ name: 'Error', message: '4 2 3', }); tracker.add({ name: 'Error', message: '5 2 3', }); expect(tracker.result).toMatchObject({ 'missing stack trace': { // source 'missing error code': { // code Error: { // name '_ 2 3': { // message count: 3, }, }, }, }, }); expect(tracker.getUniqueErrorCount()).toBe(1); }); test('placeholder #6', () => { const tracker = new ErrorTracker({ showErrorCode: true, showErrorMessage: true, showErrorName: true, showStackTrace: true, showFullStack: false, showFullMessage: true, }); tracker.add({ name: 'Error', message: 'The weather is sunny today, but the grass is wet.', }); tracker.add({ name: 'Error', message: 'The weather is rainy today, but the grass is still dry.', }); tracker.add({ name: 'Error', message: 'The weather is wild today, however the grass is yellow.', }); expect(tracker.result).toMatchObject({ 'missing stack trace': { // source 'missing error code': { // code Error: { // name 'The weather is _ today, _ the grass is _': { // message count: 3, }, }, }, }, }); expect(tracker.getUniqueErrorCount()).toBe(1); }); test('placeholder #7', () => { const tracker = new ErrorTracker({ showErrorCode: true, showErrorMessage: true, showErrorName: true, showStackTrace: true, showFullStack: false, showFullMessage: true, }); tracker.add({ name: 'Error', message: 'Expected `boolean`, got `number`', }); tracker.add({ name: 'Error', message: 'Expected `boolean`, got `number`', }); expect(tracker.result).toMatchObject({ 'missing stack trace': { // source 'missing error code': { // code Error: { // name 'Expected `boolean`, got `number`': { // message count: 2, }, }, }, }, }); expect(tracker.getUniqueErrorCount()).toBe(1); tracker.add({ name: 'Error', message: 'Expected `boolean`, got `falsy value`', }); expect(tracker.result).toMatchObject({ 'missing stack trace': { // source 'missing error code': { // code Error: { // name 'Expected `boolean`, got _': { // message count: 3, }, }, }, }, }); expect(tracker.getUniqueErrorCount()).toBe(1); }); test('placeholder #8', () => { const tracker = new ErrorTracker({ showErrorCode: true, showErrorMessage: true, showErrorName: true, showStackTrace: true, showFullStack: false, showFullMessage: true, }); tracker.add({ name: 'Error', message: 'Expected `boolean`, got `number`', }); tracker.add({ name: 'Error', message: 'Expected `string`, got `null`', }); expect(tracker.result).toMatchObject({ 'missing stack trace': { // source 'missing error code': { // code Error: { // name 'Expected `boolean`, got `number`': { // message count: 1, }, 'Expected `string`, got `null`': { count: 1, }, }, }, }, }); expect(tracker.getUniqueErrorCount()).toBe(2); }); test('placeholder #9', () => { const tracker = new ErrorTracker({ showErrorCode: true, showErrorMessage: true, showErrorName: true, showStackTrace: true, showFullStack: false, showFullMessage: true, }); tracker.add({ name: 'Error', message: 'Unexpected `show` property in `options` object', }); tracker.add({ name: 'Error', message: 'Missing `display` in style', }); const expected = { 'missing stack trace': { // source 'missing error code': { // code Error: { // name 'Unexpected `show` property in `options` object': { // message count: 1, }, 'Missing `display` in style': { count: 1, }, }, }, }, }; expect(tracker.result).toMatchObject(expected); expect(tracker.getUniqueErrorCount()).toBe(2); });