-
Notifications
You must be signed in to change notification settings - Fork 62
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
Conversation
6477795
to
370090e
Compare
@@ -15,7 +15,7 @@ export const runWithStacktraceHooks = ({ | |||
}): ReturnType<typeof fn> => { | |||
const frames = captureRawStackFrames(stackFilterFunc || runWithStacktraceHooks); | |||
|
|||
if (stackFrames.isNested(frames)) { | |||
if (stackFrames.areInternal(frames)) { |
There was a problem hiding this comment.
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), |
There was a problem hiding this comment.
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
src/browser/stacktrace/utils.ts
Outdated
/** | ||
* @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; | ||
}; |
There was a problem hiding this comment.
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"
const getStackTraceRelevance = (error: Error): number => { | ||
const framesParsed = ErrorStackParser.parse(error); | ||
|
||
return framesParsed.reduce((maxRelevance, frame) => { | ||
return Math.max(maxRelevance, getFrameRelevance(frame)); | ||
}, 0); | ||
}; |
There was a problem hiding this comment.
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
const createErrorWithStack = (stack: RawStackFrames, errorMessage = ""): Error => { | ||
const newError = new Error(errorMessage); | ||
|
||
newError.stack = getErrorTitle(newError) + "\n" + stack; | ||
|
||
return newError; | ||
}; |
There was a problem hiding this comment.
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 => { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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"
const isNodeModulesFrame = (frame: string): boolean => frame.includes("/node_modules/"); | ||
const isNodeInternalFrame = (frame: string): boolean => frame.includes(" (node:"); |
There was a problem hiding this comment.
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 => { |
There was a problem hiding this comment.
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.
export const softFileURLToPath = (fileName: string): string => { | ||
if (!fileName.startsWith("file://")) { | ||
return fileName; | ||
} | ||
|
||
try { | ||
return fileURLToPath(fileName); | ||
} catch (_) { | ||
return fileName; | ||
} | ||
}; |
There was a problem hiding this comment.
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 => { |
There was a problem hiding this comment.
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; | ||
} |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
370090e
to
bf8a367
Compare
bf8a367
to
3c9ce6b
Compare
Examples: https://nda.ya.ru/t/1XWaQ6YG76y5LA