Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: support error snippets nested browser commands #972

Merged
merged 1 commit into from
Jul 17, 2024

Conversation

KuznetsovRoman
Copy link
Member

@KuznetsovRoman KuznetsovRoman commented Jul 15, 2024

@KuznetsovRoman KuznetsovRoman force-pushed the TESTPLANE-111.stacktrace_relevance branch from 6477795 to 370090e Compare July 15, 2024 10:27
@@ -15,7 +15,7 @@ export const runWithStacktraceHooks = ({
}): ReturnType<typeof fn> => {
const frames = captureRawStackFrames(stackFilterFunc || runWithStacktraceHooks);

if (stackFrames.isNested(frames)) {
if (stackFrames.areInternal(frames)) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now some nested frames are saved.
Only internal ones are completely ignored (node_modules, node:internal)

@@ -25,7 +25,7 @@ export const runWithStacktraceHooks = ({
before: () => stackFrames.enter(key, frames),
fn,
after: () => stackFrames.leave(key),
error: (err: Error) => applyStackFrames(err, frames),
error: (err: Error) => applyStackTraceIfBetter(err, frames),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From multiple saved stacktraces, we apply the best one

Comment on lines 66 to 100
/**
* @description
* Rank values:
*
* 0: Can't extract code snippet; useless
*
* 1: WebdriverIO internals: Better than nothing
*
* 2: Project internals: Better than WebdriverIO internals, but worse, than user code part
*
* 3: User code: Best choice
*/
export const FRAME_REELVANCE: Record<string, { value: number; matcher: (fileName: string) => boolean }> = {
repl: { value: 0, matcher: fileName => /^REPL\d+$/.test(fileName) },
nodeInternals: { value: 0, matcher: fileName => /^node:[a-zA-Z\-_]/.test(fileName) },
wdioInternals: { value: 1, matcher: fileName => fileName.includes("/node_modules/webdriverio/") },
projectInternals: { value: 2, matcher: fileName => fileName.includes("/node_modules/") },
userCode: { value: 3, matcher: () => true },
} as const;

export const getFrameRelevance = (frame: StackFrame): number => {
if ([frame.fileName, frame.lineNumber, frame.columnNumber].some(_.isUndefined)) {
return 0;
}

const fileName: string = softFileURLToPath(frame.fileName!);

for (const factor in FRAME_REELVANCE) {
if (FRAME_REELVANCE[factor].matcher(fileName)) {
return FRAME_REELVANCE[factor].value;
}
}

return 0;
};
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pulled out from "error-snippets"

Comment on lines +102 to +108
const getStackTraceRelevance = (error: Error): number => {
const framesParsed = ErrorStackParser.parse(error);

return framesParsed.reduce((maxRelevance, frame) => {
return Math.max(maxRelevance, getFrameRelevance(frame));
}, 0);
};
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calculates relevance of each frame and returns stacktrace relevance, based on relevance of the most relevant frame

Comment on lines +110 to +116
const createErrorWithStack = (stack: RawStackFrames, errorMessage = ""): Error => {
const newError = new Error(errorMessage);

newError.stack = getErrorTitle(newError) + "\n" + stack;

return newError;
};
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Helper function. Its only needed to parse stack frames.

}
};

export const applyStackTraceIfBetter = (error: Error, stack: RawStackFrames): Error => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calculates current stacktrace relevance and stacktrace relevance of the next nested level call.
Apples the new one if it suites better

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just leave the old name - applyStackTrace? To my mind user of this method should not know about its implementation details. I would rename applyStackTrace above to something else, for example to modifyStackTrace.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just leave the old name - applyStackTrace?

Lets pretend you don't know implementation details. You call

const error = ...
const myNewStack = ...

applyStackTrace(error, myNewStack);

What do you think error.stack is now equal to? According to applyStackTrace function name, of course it is "myNewStack". But turns out, it is not, and its even worse. It is "myNewStack" sometimes, but also sometimes original "error.stack"

So that's why it is called "applyStackTraceIfBetter". That way developer would not be ensured "error.stack" to be "myNewStack". He would get from this function name that "it only apples stacktrace if it is better"

Comment on lines +225 to +226
const isNodeModulesFrame = (frame: string): boolean => frame.includes("/node_modules/");
const isNodeInternalFrame = (frame: string): boolean => frame.includes(" (node:");
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here it is too expensive to parse frames, because "areInternal" method will be called a lot of times on every browser command. So we are checking node_modules frames and internal frames by these functions

} catch (_) {
return fileName;
}
const trimAsyncPrefix = (fileName: string): string => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also noticed "error-stack-parser" does not trim "async" prefix on files.

Comment on lines +13 to +23
export const softFileURLToPath = (fileName: string): string => {
if (!fileName.startsWith("file://")) {
return fileName;
}

try {
return fileURLToPath(fileName);
} catch (_) {
return fileName;
}
};
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pulled out of "error-snippets" because it is used inside "browser/stacktrace" too

}
};

export const applyStackTraceIfBetter = (error: Error, stack: RawStackFrames): Error => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just leave the old name - applyStackTrace? To my mind user of this method should not know about its implementation details. I would rename applyStackTrace above to something else, for example to modifyStackTrace.

return newError;
};

const applyStackTrace = (error: Error, stack: RawStackFrames): Error => {
if (!error || !error.message) {
return error;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like that we don't need this condition. Because passed error from applyStackTraceIfBetter and filterExtraWdioFrames already perform the same check

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this scenarios, yes. In general, it is critical for the whole "stacktrace" module not to throw errors. Because if it does, the entire run will fail due to unhandled error.

As the check is cheep, i would like to keep it here.

src/browser/stacktrace/utils.ts Outdated Show resolved Hide resolved
@KuznetsovRoman KuznetsovRoman force-pushed the TESTPLANE-111.stacktrace_relevance branch from 370090e to bf8a367 Compare July 17, 2024 11:45
@KuznetsovRoman KuznetsovRoman force-pushed the TESTPLANE-111.stacktrace_relevance branch from bf8a367 to 3c9ce6b Compare July 17, 2024 11:48
@KuznetsovRoman KuznetsovRoman merged commit 64576dc into master Jul 17, 2024
2 checks passed
@KuznetsovRoman KuznetsovRoman deleted the TESTPLANE-111.stacktrace_relevance branch July 17, 2024 11:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants