Skip to content

Instantly share code, notes, and snippets.

@pathcl
Created July 30, 2026 20:30
Show Gist options
  • Select an option

  • Save pathcl/2473e69d906274b207682e5e977be640 to your computer and use it in GitHub Desktop.

Select an option

Save pathcl/2473e69d906274b207682e5e977be640 to your computer and use it in GitHub Desktop.
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(&currentURL))
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