Files
apify--crawlee/test/core/error_tracker.test.ts
2026-07-13 13:23:39 +08:00

826 lines
20 KiB
TypeScript

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