import AppKit import Darwin import Foundation import WebKit @MainActor final class WkWebViewHarness: NSObject, WKNavigationDelegate { private let app: NSApplication private let window: NSWindow private let webView: WKWebView private var finished = false init(app: NSApplication, pagePath: String, readRoot: String) { self.app = app let configuration = WKWebViewConfiguration() configuration.websiteDataStore = .nonPersistent() webView = WKWebView( frame: NSRect(x: 0, y: 0, width: 1224, height: 768), configuration: configuration ) window = NSWindow( contentRect: webView.frame, styleMask: [ .titled, .closable, .resizable ], backing: .buffered, defer: false ) super.init() webView.navigationDelegate = self window.title = "Juzhen WKWebView E2E" window.contentView = webView window.center() window.makeKeyAndOrderFront(nil) let pageUrl = URL(fileURLWithPath: pagePath) let readUrl = URL(fileURLWithPath: readRoot, isDirectory: true) webView.loadFileURL(pageUrl, allowingReadAccessTo: readUrl) DispatchQueue.main.asyncAfter(deadline: .now() + 20) { [weak self] in self?.finish(code: 2, reason: "timeout") } } func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { evaluate("navigator.userAgent") { value in print("WKWEBVIEW_USER_AGENT=\(value)") } click(buttonId: "run-core") pollTitle(expectedPrefix: "CORE PASS", timeoutAt: Date().addingTimeInterval(8)) { [weak self] coreTitle in guard let self else { return } guard coreTitle.hasPrefix("CORE PASS") else { self.collectAndFinish(code: 3, reason: "core title: \(coreTitle)") return } self.click(buttonId: "run-observer") self.pollTitle(expectedPrefix: "PASS", timeoutAt: Date().addingTimeInterval(8)) { [weak self] finalTitle in guard let self else { return } let success = finalTitle.hasPrefix("PASS") self.collectAndFinish( code: success ? 0 : 4, reason: "final title: \(finalTitle)" ) } } } func webView( _ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error ) { finish(code: 5, reason: "navigation failed: \(error)") } func webView( _ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error ) { finish(code: 6, reason: "provisional navigation failed: \(error)") } private func click(buttonId: String) { let script = "document.getElementById('\(buttonId)')?.click(); document.title;" evaluate(script) { value in print("WKWEBVIEW_CLICK_\(buttonId)=\(value)") } } private func pollTitle( expectedPrefix: String, timeoutAt: Date, completion: @escaping (String) -> Void ) { evaluate("document.title") { [weak self] value in guard let self else { return } if value.hasPrefix(expectedPrefix) || value.hasPrefix("FAIL") || Date() >= timeoutAt { completion(value) return } DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in self?.pollTitle( expectedPrefix: expectedPrefix, timeoutAt: timeoutAt, completion: completion ) } } } private func collectAndFinish(code: Int32, reason: String) { let script = """ JSON.stringify({ title: document.title, summary: document.getElementById('summary-text')?.textContent || '', failures: Array.from(document.querySelectorAll('#results li[data-state="fail"]')) .map((item) => item.textContent), results: Array.from(document.querySelectorAll('#results li')) .map((item) => item.textContent) }) """ evaluate(script) { [weak self] value in print("WKWEBVIEW_RESULT=\(value)") self?.finish(code: code, reason: reason) } } private func evaluate(_ script: String, completion: @escaping (String) -> Void) { webView.evaluateJavaScript(script) { value, error in if let error { completion("EVALUATION_ERROR: \(error)") return } completion(String(describing: value ?? "nil")) } } private func finish(code: Int32, reason: String) { guard !finished else { return } finished = true print("WKWEBVIEW_EXIT_REASON=\(reason)") fflush(stdout) window.close() app.stop(nil) exit(code) } } @main struct Main { @MainActor private static var harness: WkWebViewHarness? @MainActor static func main() { guard CommandLine.arguments.count == 3 else { fputs("usage: harness \n", stderr) exit(64) } let app = NSApplication.shared app.setActivationPolicy(.accessory) harness = WkWebViewHarness( app: app, pagePath: CommandLine.arguments[1], readRoot: CommandLine.arguments[2] ) app.run() } }