Created
July 30, 2026 20:30
-
-
Save pathcl/2473e69d906274b207682e5e977be640 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package main | |
| import ( | |
| "context" | |
| "encoding/json" | |
| "fmt" | |
| "log" | |
| "os" | |
| "path/filepath" | |
| "strings" | |
| "time" | |
| "github.com/chromedp/cdproto/network" | |
| "github.com/chromedp/chromedp" | |
| ) | |
| // ---- Config ---------------------------------------------------------------- | |
| const ( | |
| GrafanaURL = "https://your-grafana.internal" | |
| DatasourceName = "traces-test-" | |
| OutputDir = "evidence" | |
| // Paste your cookie(s) from Chrome DevTools: | |
| // Application → Cookies → your-grafana.internal | |
| CookieName = "grafana_session" // or "_oauth2_proxy" | |
| CookieValue = "your-cookie-value-here" | |
| CookieDomain = "your-grafana.internal" | |
| ) | |
| // ---- Evidence types -------------------------------------------------------- | |
| type NetworkEvent struct { | |
| RequestID string | |
| URL string | |
| Status int | |
| Timestamp time.Time | |
| } | |
| type TraceEvidence struct { | |
| TraceID string `json:"trace_id"` | |
| Status int `json:"http_status"` | |
| RequestURL string `json:"request_url"` | |
| Screenshot string `json:"screenshot"` | |
| Timestamp time.Time `json:"timestamp"` | |
| Error string `json:"error,omitempty"` | |
| } | |
| // ---- Main ------------------------------------------------------------------ | |
| func main() { | |
| // Create timestamped output directory | |
| dir := fmt.Sprintf("%s-%s", OutputDir, time.Now().Format("2006-01-02T15-04-05")) | |
| if err := os.MkdirAll(dir, 0755); err != nil { | |
| log.Fatalf("could not create output dir: %v", err) | |
| } | |
| fmt.Printf("📁 Output directory: %s\n\n", dir) | |
| // ---- Browser setup ----------------------------------------------------- | |
| opts := append(chromedp.DefaultExecAllocatorOptions[:], | |
| chromedp.Flag("headless", true), | |
| chromedp.Flag("no-sandbox", true), | |
| chromedp.Flag("disable-gpu", true), | |
| chromedp.Flag("ignore-certificate-errors", true), // for internal certs | |
| chromedp.WindowSize(1920, 1080), | |
| ) | |
| allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...) | |
| defer cancel() | |
| ctx, cancel := chromedp.NewContext(allocCtx, chromedp.WithLogf(log.Printf)) | |
| defer cancel() | |
| ctx, cancel = context.WithTimeout(ctx, 10*time.Minute) | |
| defer cancel() | |
| // ---- Network interception ---------------------------------------------- | |
| var networkEvents []NetworkEvent | |
| chromedp.ListenTarget(ctx, func(ev interface{}) { | |
| if e, ok := ev.(*network.EventResponseReceived); ok { | |
| networkEvents = append(networkEvents, NetworkEvent{ | |
| RequestID: string(e.RequestID), | |
| URL: e.Response.URL, | |
| Status: int(e.Response.Status), | |
| Timestamp: time.Now(), | |
| }) | |
| } | |
| }) | |
| // ---- Step 1: Enable network and inject cookie ------------------------- | |
| fmt.Println("→ Injecting session cookie...") | |
| if err := chromedp.Run(ctx, | |
| network.Enable(), | |
| chromedp.ActionFunc(func(ctx context.Context) error { | |
| expr := network.TimeSinceEpoch(time.Now().Add(24 * time.Hour)) | |
| return network.SetCookie(CookieName, CookieValue). | |
| WithDomain(CookieDomain). | |
| WithPath("/"). | |
| WithHTTPOnly(true). | |
| WithSecure(true). | |
| WithExpires(&expr). | |
| Do(ctx) | |
| }), | |
| ); err != nil { | |
| log.Fatalf("could not inject cookie: %v", err) | |
| } | |
| fmt.Println("✅ Cookie injected") | |
| // ---- Step 2: Navigate to Grafana Explore with Tempo datasource -------- | |
| exploreURL := fmt.Sprintf( | |
| "%s/explore?orgId=1&left={\"datasource\":\"%s\",\"queries\":[{\"queryType\":\"nativeSearch\",\"refId\":\"A\"}],\"range\":{\"from\":\"now-1h\",\"to\":\"now\"}}", | |
| GrafanaURL, DatasourceName, | |
| ) | |
| fmt.Println("→ Navigating to Grafana Explore...") | |
| if err := chromedp.Run(ctx, | |
| chromedp.Navigate(exploreURL), | |
| chromedp.WaitVisible(`body`, chromedp.ByQuery), | |
| chromedp.Sleep(3*time.Second), | |
| ); err != nil { | |
| log.Fatalf("navigation to Explore failed: %v", err) | |
| } | |
| // Confirm we landed (not on a login redirect) | |
| var currentURL string | |
| chromedp.Run(ctx, chromedp.Location(¤tURL)) | |
| if strings.Contains(currentURL, "login") { | |
| log.Fatal("❌ Redirected to login — cookie may be expired or wrong") | |
| } | |
| fmt.Printf("✅ Landed at: %s\n", currentURL) | |
| // Screenshot: initial Explore view | |
| takeScreenshot(ctx, filepath.Join(dir, "00-explore-initial.png")) | |
| // ---- Step 3: Select "Search" query type and run ----------------------- | |
| fmt.Println("→ Setting query type to Search and running...") | |
| if err := chromedp.Run(ctx, | |
| // Click the Search tab in query type selector | |
| // NOTE: selector may vary by Grafana version — inspect your DOM if this fails | |
| chromedp.WaitVisible(`[data-testid="data-testid query-type-tab-Search"]`, chromedp.ByQuery), | |
| chromedp.Click(`[data-testid="data-testid query-type-tab-Search"]`, chromedp.ByQuery), | |
| chromedp.Sleep(time.Second), | |
| // Click Run Query button | |
| chromedp.Click(`[data-testid="data-testid run-query-button"]`, chromedp.ByQuery), | |
| chromedp.Sleep(4*time.Second), // wait for traces to populate | |
| ); err != nil { | |
| // Fallback: try alternative selectors for older Grafana versions | |
| log.Printf("⚠️ Primary selectors failed (%v), trying fallbacks...", err) | |
| chromedp.Run(ctx, | |
| chromedp.Click(`button[aria-label="Search"]`, chromedp.ByQuery), | |
| chromedp.Sleep(time.Second), | |
| chromedp.Click(`button[aria-label="Run query"]`, chromedp.ByQuery), | |
| chromedp.Sleep(4*time.Second), | |
| ) | |
| } | |
| // Screenshot: traces visible in search results | |
| takeScreenshot(ctx, filepath.Join(dir, "01-traces-search-results.png")) | |
| fmt.Println("✅ Search results captured") | |
| // ---- Step 4: Extract trace IDs from the results ----------------------- | |
| fmt.Println("→ Extracting trace IDs from results...") | |
| var traceIDs []string | |
| // Try multiple selector strategies — Grafana renders traces differently by version | |
| strategies := []string{ | |
| // Strategy 1: data-testid on trace rows | |
| `Array.from(document.querySelectorAll('[data-testid="trace-id"]')).map(el => el.textContent.trim())`, | |
| // Strategy 2: links containing /trace/ in href | |
| `Array.from(document.querySelectorAll('a[href*="/trace/"]')).map(el => { const m = el.href.match(/\/trace\/([a-f0-9]+)/i); return m ? m[1] : ''; }).filter(Boolean)`, | |
| // Strategy 3: span text that looks like a hex trace ID | |
| `Array.from(document.querySelectorAll('span,td')).map(el => el.textContent.trim()).filter(t => /^[a-f0-9]{16,32}$/i.test(t))`, | |
| } | |
| for i, js := range strategies { | |
| if err := chromedp.Run(ctx, chromedp.Evaluate(js, &traceIDs)); err == nil && len(traceIDs) > 0 { | |
| fmt.Printf("✅ Found %d trace IDs (strategy %d)\n", len(traceIDs), i+1) | |
| break | |
| } | |
| } | |
| if len(traceIDs) == 0 { | |
| takeScreenshot(ctx, filepath.Join(dir, "ERROR-no-traces-found.png")) | |
| log.Fatal("❌ No trace IDs found — check selector strategy or screenshot above") | |
| } | |
| // ---- Step 5: Click each trace and capture evidence -------------------- | |
| var evidence []TraceEvidence | |
| for i, traceID := range traceIDs { | |
| if traceID == "" { | |
| continue | |
| } | |
| fmt.Printf("\n[%d/%d] Probing trace %s...\n", i+1, len(traceIDs), traceID) | |
| // Reset network events for this trace | |
| networkEvents = nil | |
| // Click the trace link | |
| clickErr := chromedp.Run(ctx, | |
| chromedp.Click( | |
| fmt.Sprintf(`a[href*="%s"]`, traceID), | |
| chromedp.ByQuery, | |
| ), | |
| chromedp.Sleep(3*time.Second), // wait for response + render | |
| ) | |
| // Capture screenshot regardless of success/failure — this is our evidence | |
| screenshotName := fmt.Sprintf("%02d-trace-%s.png", i+2, truncate(traceID, 8)) | |
| screenshotPath := filepath.Join(dir, screenshotName) | |
| takeScreenshot(ctx, screenshotPath) | |
| // Find the HTTP status for this trace's API call from intercepted events | |
| status := 0 | |
| requestURL := "" | |
| for _, ev := range networkEvents { | |
| if strings.Contains(ev.URL, traceID) || strings.Contains(ev.URL, strings.ToLower(traceID)) { | |
| status = ev.Status | |
| requestURL = ev.URL | |
| break | |
| } | |
| } | |
| // Build evidence entry | |
| ev := TraceEvidence{ | |
| TraceID: traceID, | |
| Status: status, | |
| RequestURL: requestURL, | |
| Screenshot: screenshotPath, | |
| Timestamp: time.Now(), | |
| } | |
| if clickErr != nil { | |
| ev.Error = clickErr.Error() | |
| } | |
| evidence = append(evidence, ev) | |
| // Log result | |
| switch { | |
| case status == 200: | |
| fmt.Printf(" ✅ HTTP 200 — trace loaded OK\n") | |
| case status == 400: | |
| fmt.Printf(" ❌ HTTP 400 — BAD REQUEST (proxy rewrite issue?)\n") | |
| fmt.Printf(" URL: %s\n", requestURL) | |
| case status == 404: | |
| fmt.Printf(" ❌ HTTP 404 — NOT FOUND\n") | |
| fmt.Printf(" URL: %s\n", requestURL) | |
| case status == 0: | |
| fmt.Printf(" ❓ No matching network event captured for this traceID\n") | |
| default: | |
| fmt.Printf(" ⚠️ HTTP %d\n", status) | |
| } | |
| // Go back to search results for next trace | |
| if err := chromedp.Run(ctx, | |
| chromedp.Navigate("javascript:history.back()"), | |
| chromedp.Sleep(2*time.Second), | |
| ); err != nil { | |
| // If back navigation fails, re-navigate to Explore | |
| chromedp.Run(ctx, | |
| chromedp.Navigate(exploreURL), | |
| chromedp.Sleep(3*time.Second), | |
| ) | |
| } | |
| } | |
| // ---- Step 6: Write JSON evidence report ------------------------------- | |
| reportPath := filepath.Join(dir, "report.json") | |
| writeReport(reportPath, evidence) | |
| // ---- Summary ---------------------------------------------------------- | |
| total := len(evidence) | |
| failures := 0 | |
| for _, e := range evidence { | |
| if e.Status >= 400 { | |
| failures++ | |
| } | |
| } | |
| fmt.Printf("\n========================================\n") | |
| fmt.Printf(" Evidence Report\n") | |
| fmt.Printf("========================================\n") | |
| fmt.Printf(" Directory : %s\n", dir) | |
| fmt.Printf(" Traces : %d\n", total) | |
| fmt.Printf(" OK (2xx) : %d\n", total-failures) | |
| fmt.Printf(" Failed 4xx : %d\n", failures) | |
| fmt.Printf(" Report : %s\n", reportPath) | |
| fmt.Printf("========================================\n") | |
| } | |
| // ---- Helpers --------------------------------------------------------------- | |
| func takeScreenshot(ctx context.Context, path string) { | |
| var buf []byte | |
| if err := chromedp.Run(ctx, chromedp.FullScreenshot(&buf, 90)); err != nil { | |
| log.Printf("⚠️ Screenshot failed (%s): %v", path, err) | |
| return | |
| } | |
| if err := os.WriteFile(path, buf, 0644); err != nil { | |
| log.Printf("⚠️ Could not write screenshot (%s): %v", path, err) | |
| return | |
| } | |
| fmt.Printf(" 📸 %s\n", path) | |
| } | |
| func writeReport(path string, evidence []TraceEvidence) { | |
| data, err := json.MarshalIndent(evidence, "", " ") | |
| if err != nil { | |
| log.Printf("⚠️ Could not marshal report: %v", err) | |
| return | |
| } | |
| if err := os.WriteFile(path, data, 0644); err != nil { | |
| log.Printf("⚠️ Could not write report: %v", err) | |
| return | |
| } | |
| fmt.Printf("\n📄 JSON report written: %s\n", path) | |
| } | |
| func truncate(s string, n int) string { | |
| if len(s) <= n { | |
| return s | |
| } | |
| return s[:n] | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment