feat(runner): per-tick HTML capture for WebDriver-capable drivers

Type-asserts driver.WebDriver and writes <run>/html/step-NNNNN.html in
parallel with screenshot/hierarchy/metrics. Step.HTMLAvailable flips so
the inspect UI can hide the html tab on mobile runs.
This commit is contained in:
pj committed 2026-04-27 18:45:23 +07:00
1 parent 01e43a869d
commit d2626eeccc
1 file changed
+43
+43
View File
@@ -73,6 +73,7 @@ func Run(ctx context.Context, options Options) (Summary, error) {
var hierarchyErr error var hierarchyErr error
var metrics *trace.Metrics var metrics *trace.Metrics
var logs []verifier.LogEntry var logs []verifier.LogEntry
var htmlCaptured bool
g, _ := errgroup.WithContext(ctx) g, _ := errgroup.WithContext(ctx)
g.Go(func() error { g.Go(func() error {
@@ -89,12 +90,24 @@ func Run(ctx context.Context, options Options) (Summary, error) {
logs = collectLogs(ctx, options.Driver, logSince) logs = collectLogs(ctx, options.Driver, logSince)
return nil return nil
}) })
if web, ok := options.Driver.(driver.WebDriver); ok {
g.Go(func() error {
htmlCaptured = captureHTML(ctx, web, options.TraceWriter, logger, si, false)
return nil
})
}
if pendingPostScreenshot { if pendingPostScreenshot {
postStep := pendingPostScreenshotStep postStep := pendingPostScreenshotStep
g.Go(func() error { g.Go(func() error {
captureScreenshot(ctx, options, logger, postStep, true) captureScreenshot(ctx, options, logger, postStep, true)
return nil return nil
}) })
if web, ok := options.Driver.(driver.WebDriver); ok {
g.Go(func() error {
captureHTML(ctx, web, options.TraceWriter, logger, postStep, true)
return nil
})
}
pendingPostScreenshot = false pendingPostScreenshot = false
} }
g.Wait() g.Wait()
@@ -156,6 +169,7 @@ func Run(ctx context.Context, options Options) (Summary, error) {
Hierarchy: tree, Hierarchy: tree,
Residuals: residuals, Residuals: residuals,
Metrics: metrics, Metrics: metrics,
HTMLAvailable: htmlCaptured,
} }
if err := options.TraceWriter.WriteStep(step); err != nil { if err := options.TraceWriter.WriteStep(step); err != nil {
return summary, fmt.Errorf("step %d trace: %w", stepIndex, err) return summary, fmt.Errorf("step %d trace: %w", stepIndex, err)
@@ -196,6 +210,9 @@ func Run(ctx context.Context, options Options) (Summary, error) {
if pendingPostScreenshot { if pendingPostScreenshot {
captureScreenshot(ctx, options, logger, pendingPostScreenshotStep, true) captureScreenshot(ctx, options, logger, pendingPostScreenshotStep, true)
if web, ok := options.Driver.(driver.WebDriver); ok {
captureHTML(ctx, web, options.TraceWriter, logger, pendingPostScreenshotStep, true)
}
} }
summary.EndTime = time.Now() summary.EndTime = time.Now()
@@ -397,6 +414,32 @@ func captureMetrics(ctx context.Context, options Options, logger *slog.Logger, s
} }
} }
// captureHTML pulls the current document HTML from a WebDriver-capable
// driver and stamps it into the trace under html/. Returns true on a
// successful non-empty write so the Step.HTMLAvailable flag advertises the
// payload to the inspect UI.
func captureHTML(ctx context.Context, web driver.WebDriver, writer *trace.Writer, logger *slog.Logger, stepIndex int, after bool) bool {
html, err := web.Document(ctx)
if err != nil {
logger.Warn("html capture failed", "step", stepIndex, "after", after, "err", err)
return false
}
if html == "" {
return false
}
var writeErr error
if after {
writeErr = writer.WriteHTMLAfter(stepIndex, []byte(html))
} else {
writeErr = writer.WriteHTML(stepIndex, []byte(html))
}
if writeErr != nil {
logger.Warn("html write failed", "step", stepIndex, "after", after, "err", writeErr)
return false
}
return true
}
func captureScreenshot(ctx context.Context, options Options, logger *slog.Logger, stepIndex int, after bool) { func captureScreenshot(ctx context.Context, options Options, logger *slog.Logger, stepIndex int, after bool) {
image, err := options.Driver.Screenshot(ctx) image, err := options.Driver.Screenshot(ctx)
if err != nil { if err != nil {