Files
steipete--codexbar/Tests/CodexBarTests/OpenAIDashboardScrapeScriptTests.swift
2026-07-13 12:22:33 +08:00

237 lines
7.7 KiB
Swift

#if os(macOS)
import Foundation
import Testing
import WebKit
@testable import CodexBarCore
@MainActor
@Suite(.serialized)
struct OpenAIDashboardScrapeScriptTests {
@Test
func `scraper returns structured account fields without full html`() async throws {
if Self.shouldSkipOnCI() { return }
let webView = WKWebView(frame: .zero, configuration: WKWebViewConfiguration())
_ = webView.loadHTMLString(Self.bootstrapAccountHTML, baseURL: nil)
try await Self.waitForFixture(webView, elementID: "account-fixture")
let any = try await webView.evaluateJavaScript(openAIDashboardScrapeScript)
let dict = try #require(any as? [String: Any])
#expect(dict["bodyHTML"] == nil)
#expect(dict["signedInEmail"] as? String == "user@example.com")
#expect(dict["authStatus"] as? String == "logged_in")
#expect(dict["accountPlan"] as? String == "Pro 5x")
}
@Test
func `usage breakdown scraper ignores neighboring client charts`() async throws {
if Self.shouldSkipOnCI() { return }
let webView = WKWebView(frame: .zero, configuration: WKWebViewConfiguration())
_ = webView.loadHTMLString(Self.multiChartHTML, baseURL: nil)
try await Self.waitForFixture(webView)
let any = try await webView.evaluateJavaScript(openAIDashboardScrapeScript)
let dict = try #require(any as? [String: Any])
let debug = dict["usageBreakdownDebug"] as? String
let raw = try #require(dict["usageBreakdownJSON"] as? String, "debug: \(debug ?? "nil")")
let decoded = try JSONDecoder().decode([OpenAIDashboardDailyBreakdown].self, from: Data(raw.utf8))
#expect(decoded.count == 1)
#expect(decoded.first?.day == "2026-05-01")
#expect(decoded.first?.totalCreditsUsed == 30)
#expect((decoded.first?.services.map(\.service) ?? []) == ["Desktop", "CLI"])
}
@Test
func `usage breakdown scraper reports wrong chart instead of accepting it`() async throws {
if Self.shouldSkipOnCI() { return }
let webView = WKWebView(frame: .zero, configuration: WKWebViewConfiguration())
_ = webView.loadHTMLString(Self.clientOnlyChartHTML, baseURL: nil)
try await Self.waitForFixture(webView, elementID: "client-chart")
let any = try await webView.evaluateJavaScript(openAIDashboardScrapeScript)
let dict = try #require(any as? [String: Any])
#expect((dict["usageBreakdownJSON"] as? String) == nil)
#expect((dict["usageBreakdownError"] as? String)?.contains("Threads and turns by client") == true)
}
@Test
func `usage breakdown scraper rejects non english chart titles`() async throws {
if Self.shouldSkipOnCI() { return }
let webView = WKWebView(frame: .zero, configuration: WKWebViewConfiguration())
_ = webView.loadHTMLString(Self.localizedUsageChartHTML, baseURL: nil)
try await Self.waitForFixture(webView)
let any = try await webView.evaluateJavaScript(openAIDashboardScrapeScript)
let dict = try #require(any as? [String: Any])
#expect((dict["usageBreakdownJSON"] as? String) == nil)
#expect(
(dict["usageBreakdownError"] as? String)?
.contains("No English usage breakdown chart title found") == true)
}
private static func shouldSkipOnCI() -> Bool {
let env = ProcessInfo.processInfo.environment
return env["GITHUB_ACTIONS"] == "true" || env["CI"] == "true"
}
private static func waitForFixture(_ webView: WKWebView, elementID: String = "usage-chart") async throws {
let deadline = Date().addingTimeInterval(2)
while Date() < deadline {
let loaded = try? await webView.evaluateJavaScript(
"document.getElementById('\(elementID)') !== null") as? Bool
if loaded == true { return }
try await Task.sleep(for: .milliseconds(50))
}
}
private static let bootstrapAccountHTML = """
<html>
<body>
<div id="account-fixture">Usage limits</div>
<script type="application/json" id="__NEXT_DATA__">
{
"props": {
"pageProps": {
"user": {
"email": "next@example.com"
}
}
},
"planType": "pro"
}
</script>
<script type="application/json" id="client-bootstrap">
{
"authStatus": "logged_in",
"session": {
"user": {
"email": "user@example.com"
}
},
"subscription": {
"tier": "Codex Pro Lite"
}
}
</script>
</body>
</html>
"""
private static let multiChartHTML = """
<html>
<body>
<section>
<h2>Usage breakdown</h2>
<div>
<h3>Personal usage</h3>
</div>
<span>Daily threads by client</span>
<svg class="recharts-surface">
<g class="recharts-bar-rectangle">
<path id="usage-chart" class="recharts-rectangle" d="M0 0H10V10Z"></path>
</g>
</svg>
</section>
<section>
<h2>Product activity</h2>
<button type="button">1,000 threads</button>
<button type="button">2,000 turns</button>
<span>Daily threads by client</span>
<svg class="recharts-surface">
<g class="recharts-bar-rectangle">
<path id="client-chart" class="recharts-rectangle" d="M0 0H10V10Z"></path>
</g>
</svg>
</section>
<section>
<h2>Tokens by model</h2>
<svg class="recharts-surface">
<g class="recharts-bar-rectangle">
<path id="model-chart" class="recharts-rectangle" d="M0 0H10V10Z"></path>
</g>
</svg>
</section>
<script>
document.getElementById('client-chart')['__reactProps$test'] = {
dataKey: 'values',
payload: {
day: '2026-05-01',
values: { cli: 1000, desktop: 2000 }
}
};
document.getElementById('usage-chart')['__reactProps$test'] = {
dataKey: 'values',
payload: {
day: '2026-05-01',
values: { cli: 10, desktop: 20 }
}
};
document.getElementById('model-chart')['__reactProps$test'] = {
dataKey: 'values',
payload: {
day: '2026-05-01',
values: { cli: 1000, desktop: 2000, vscode: 3000 }
}
};
</script>
</body>
</html>
"""
private static let clientOnlyChartHTML = """
<html>
<body>
<section>
<h2>Threads and turns by client</h2>
<svg class="recharts-surface">
<g class="recharts-bar-rectangle">
<path id="client-chart" class="recharts-rectangle" d="M0 0H10V10Z"></path>
</g>
</svg>
</section>
<script>
document.getElementById('client-chart')['__reactProps$test'] = {
dataKey: 'values',
payload: {
day: '2026-05-01',
values: { cli: 1000, desktop: 2000 }
}
};
</script>
</body>
</html>
"""
private static let localizedUsageChartHTML = """
<html>
<body>
<section>
<h2>Desglose de uso</h2>
<svg class="recharts-surface">
<g class="recharts-bar-rectangle">
<path id="usage-chart" class="recharts-rectangle" d="M0 0H10V10Z"></path>
</g>
</svg>
</section>
<script>
document.getElementById('usage-chart')['__reactProps$test'] = {
dataKey: 'values',
payload: {
day: '2026-05-01',
values: { cli: 10, desktop: 20 }
}
};
</script>
</body>
</html>
"""
}
#endif