Seems that part of the problem is method overload, as of Xcode 14.1 there are several methods named evaluateJavaScript as part of WKWebView.
Due to optional parameters they seem to have the same signature, and the compiler is having a hard time understanding which one we mean.
Methods
open func evaluateJavaScript(_ javaScriptString: String, completionHandler: ((Any?, Error?) -> Void)? = nil)
open func evaluateJavaScript(_ javaScriptString: String) async throws -> Any
@MainActor public func evaluateJavaScript(_ javaScript: String, in frame: WKFrameInfo? = nil, in contentWorld: WKContentWorld, completionHandler: ((Result<Any, Error>) -> Void)? = nil)
@MainActor public func evaluateJavaScript(_ javaScript: String, in frame: WKFrameInfo? = nil, contentWorld: WKContentWorld) async throws -> Any?
After testing different scenarios it seems that when using async/await version of these methods WKWebView expects JavaScript to return with a value (something other than Void), if there is no value returning from the JavaScript that you evaluate you will have a crash.
Solution
Option 1
Always make sure JavaScript returns a value.
Crashing:
try? await webView.evaluateJavaScript("console.log('hello world')") // fatal error
Not crashing:
try? await webView.evaluateJavaScript("console.log('hello world'); 0")
Option 2
When not possible to return a value explicitly use the signature with a completion handler (even if you pass nil as the handler).
webView.evaluateJavaScript("console.log('hello world')", completionHandler: nil)