Help us shape the future of Go! Take the 2026 Go Developer Survey

webcap

package module
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 18, 2026 License: MIT Imports: 34 Imported by: 0

README

webcap

webcap is a Go package and CLI for browser screenshots, manifest-driven capture batches, image diffs, workflow captures, HTML review reports, and a stdio MCP server.

Install

Install the released binary with Homebrew:

brew tap goliatone/homebrew-tap
brew install webcap

Or install from source with Go:

go install github.com/goliatone/webcap/cmd/webcap@latest

For local development from this checkout:

go run ./cmd/webcap shot --output shots/home.png http://localhost:3000

CLI

webcap help
webcap shot --output shots/home.png http://localhost:3000
webcap shot --visible --output shots/home-visible.png http://localhost:3000
webcap shot --json --output shots/home.png http://localhost:3000
webcap multi --output-dir ./shots ./shots.yaml
webcap diff --output ./diff.png ./baseline.png ./current.png
OPENAI_API_KEY=... webcap semantic-diff --provider openai --model gpt-5.1 --focus "primary CTA,nav labels" --metadata ./semantic.json ./current.png ./baseline.png
webcap semantic-diff --provider codex-cli --model gpt-5.1 --codex-profile work --metadata ./semantic.json ./current.png ./baseline.png
webcap workflow capture-scenario --run-report ./workflow.yaml
webcap report scenario ./workflow.yaml
webcap mcp serve
webcap skill install --agent codex
webcap skill install --agent claude
webcap skill install --agent codex --force

Result commands render concise human summaries by default. Use --json or --format json for the machine-readable result structs used by automation and future API responses. Use --format human to force human output and --no-color for deterministic output in CI, redirected output, and tests. webcap shot <url> captures the full page by default. Add --visible when you only want the current viewport. Oversized full-page or selector captures fail early by default with structured limit metadata. Use --oversize tile to opt into deterministic tile artifacts, for example --tile-max-height 4096; unstitched tiled captures write <output>.tile-0000.png style files plus the metadata sidecar. Add --tile-stitch when the capture must produce a single image for pixel or semantic comparison.

Browser readiness modes such as complete and network_idle do not always mean the app UI is ready. Use selector waits for visible DOM targets, or --wait-for-function when readiness is best expressed as JavaScript:

webcap shot \
  --wait-for-function 'window.__webcapReady === true' \
  --output shots/home.png \
  http://localhost:3000

For important visual review flows, prefer stable app-owned markers such as data-webcap-ready="true". For Storybook iframe captures, wait for the preview to show the story and for the root to contain rendered content:

webcap shot \
  --wait-for-function 'document.body.classList.contains("sb-show-main") && !document.querySelector(".sb-preparing,.sb-errordisplay") && document.querySelector("#storybook-root")?.children.length > 0' \
  --output shots/story.png \
  http://localhost:6006/iframe.html?id=components-button--primary

The standalone CLI does not include application-specific scenario aliases or paths. Provide scenario files explicitly. Use --openai-base-url or --anthropic-base-url with semantic-diff, workflow capture-scenario, report scenario, or mcp serve when routing built-in providers through a local fake server, proxy, or compatible gateway. Use --codex-bin, --codex-profile, --codex-oss, --codex-local-provider, and repeated --codex-extra-arg flags to configure the local Codex CLI process. CLI providers reuse local tool configuration and may still use subscriptions, API accounts, or local model resources.

webcap skill install installs the bundled webcap-agent guidance for AI coding agents. The skill tells agents to prefer webcap MCP tools when available, use the CLI as a fallback or reproducible command surface, and keep capture, pixel diff, semantic diff, and workflow report artifacts organized. Existing modified skill files are not overwritten unless --force is provided.

Go Package

engine, err := webcap.NewEngine(webcap.EngineConfig{
    EngineName: webcap.EngineChromium,
    Headless:   true,
})
if err != nil {
    return err
}

service := webcap.NewService(engine)
result, err := service.Capture(ctx, webcap.CaptureRequest{
    URL:             "http://localhost:3000",
    FullPage:        true,
    WaitForFunction: `document.querySelector("[data-webcap-ready=true]") !== null`,
    OutputPath:      "shots/home.png",
})

Semantic diff is opt-in and uses provider API keys from process configuration, not workflow YAML:

service := webcap.NewServiceWithOptions(nil, webcap.Options{
    SemanticDiff: webcap.SemanticDiffOptions{
        DefaultProvider: "openai",
        DefaultModels: map[string]string{
            "openai": "gpt-5.1",
        },
    },
})

result, err := service.SemanticDiff(ctx, webcap.SemanticDiffRequest{
    CurrentPath:   "shots/current.png",
    ReferencePath: "shots/baseline.png",
    Mode:          webcap.SemanticDiffModeFocused,
    Focus:         []string{"primary CTA", "navigation labels"},
    MetadataPath:  "shots/current.semantic.json",
})

NewService and NewServiceWithOptions include the shipped openai, anthropic, and codex-cli providers by default. Register standalone LLM providers through pkg/llms.Options.Providers; SemanticDiffOptions.Providers remains available for direct webcap.SemanticDiffProvider implementations and overrides any adapted LLM provider with the same normalized name:

service := webcap.NewServiceWithOptions(nil, webcap.Options{
    SemanticDiff: webcap.SemanticDiffOptions{
        LLMs: llms.Options{
            Providers: map[string]llms.Provider{
                "local-agent": myLLMProvider,
            },
        },
        DefaultModels: map[string]string{
            "local-agent": "gpt-5.1",
        },
    },
})

Set OPENAI_API_KEY or ANTHROPIC_API_KEY for the built-in providers. Raw provider responses are not written into metadata; use PersistRawResponse or --persist-raw-response only for local debugging because screenshots and model output can contain sensitive UI content. Hosts can set Options.SemanticDiff.RedactImage to replace or reject screenshot paths before provider payload encoding.

Configure Codex CLI at process level, not in workflow YAML:

service := webcap.NewServiceWithOptions(nil, webcap.Options{
    SemanticDiff: webcap.SemanticDiffOptions{
        LLMs: llms.Options{
            CodexCLI: llms.CodexCLIOptions{
                CommandPath: "/usr/local/bin/codex",
                Profile:     "work",
            },
        },
    },
})

Workflow reports can run semantic diff after the normal pixel diff and reuse the existing diff image as context:

defaults:
  semantic_diff:
    enabled: true
    provider: openai
    model: gpt-5.1
    mode: general
    run: changed_only
    focus:
      - primary CTA visibility
      - navigation labels

Use provider: codex-cli in workflow YAML when the process already configured the Codex binary/profile flags or package options. Claude CLI support is deferred until local image input behavior is proven.

Per-screen overrides can change prompt mode or focus:

screens:
  - id: checkout
    route: /checkout
    reference_image: refs/checkout.png
    semantic_diff:
      mode: focused
      prompt: Compare only the checkout form and payment button state.

Semantic findings are advisory by default. To make them affect workflow report status, configure an explicit policy such as advisory_policy: enforce with failure_severity: major or failure_verdicts: [regression].

Workflow defaults are caller-owned:

opts := webcap.Options{
    Workflow: webcap.WorkflowOptions{
        DefaultSelectedScenario: "launch-success",
        DefaultPresentationMode: "review",
        HandoffQueryParam:       "simulator_handoff",
        BuildHandoff:           webcap.DefaultWorkflowHandoff,
    },
}

scenario, err := webcap.LoadWorkflowScenarioWithOptions("./workflow.yaml", opts.Workflow)
service := webcap.NewServiceWithOptions(engine, opts)

Playwright Runtime

The Playwright engine uses the Node bridge in playwright_runtime.

cd playwright_runtime
npm install
npx playwright install chromium firefox webkit

Then run with:

webcap shot --engine playwright --playwright-browser chromium http://localhost:3000

Documentation

Overview

Package webcap provides reusable browser capture services, manifest loading, image diffing, workflow reports, and engine abstractions for screenshot-oriented QA workflows.

Index

Constants

View Source
const (
	DefaultOversizePolicy      = OversizePolicyFail
	DefaultTileMaxWidth        = 8192
	DefaultTileMaxHeight       = 8192
	DefaultTileMaxPixels       = 67108864
	DefaultTileMaxTargetPixels = 536870912
	DefaultTileMaxStitchPixels = 268435456
	DefaultTileOverlap         = 0
)
View Source
const (
	WorkflowComparisonModeFull        = "full"
	WorkflowComparisonModeContentOnly = "content_only"
	WorkflowComparisonResizeCurrent   = "current"
	WorkflowComparisonResizeReference = "reference"
)
View Source
const (
	WorkflowReportFormatHTML = "html"
	WorkflowHookModeAppend   = "append"
	WorkflowHookModeReplace  = "replace"
)

Variables

This section is empty.

Functions

func DefaultArtifactBaseName

func DefaultArtifactBaseName(req CaptureRequest) string

func DefaultDiffDirectoryName

func DefaultDiffDirectoryName(req DiffRequest) string

func DefaultDiffImageName

func DefaultDiffImageName(req DiffRequest) string

func DefaultPlaywrightRuntimeDir

func DefaultPlaywrightRuntimeDir() string

func DefaultWorkflowHandoff

func DefaultWorkflowHandoff(scenario WorkflowScenario) (string, error)

func ParseDurationOrDefault

func ParseDurationOrDefault(raw string, fallback time.Duration) (time.Duration, error)

func SemanticEnvironmentCredentialResolver

func SemanticEnvironmentCredentialResolver(_ context.Context, provider string) (string, error)

Types

type AnthropicSemanticProviderOptions

type AnthropicSemanticProviderOptions struct {
	CredentialResolver SemanticCredentialResolver
	HTTPClient         *http.Client
	BaseURL            string
}

type BatchResult

type BatchResult struct {
	Results []CaptureResult `json:"results"`
}

type Bounds

type Bounds struct {
	X      float64 `json:"x"`
	Y      float64 `json:"y"`
	Width  float64 `json:"width"`
	Height float64 `json:"height"`
}

type BrowserInfo

type BrowserInfo struct {
	Engine          string `json:"engine"`
	BrowserPath     string `json:"browser_path,omitempty"`
	Product         string `json:"product,omitempty"`
	ProtocolVersion string `json:"protocol_version,omitempty"`
	Revision        string `json:"revision,omitempty"`
	UserAgent       string `json:"user_agent,omitempty"`
	JSVersion       string `json:"js_version,omitempty"`
	Headless        bool   `json:"headless"`
}

type CaptureArtifact

type CaptureArtifact struct {
	Bytes       []byte      `json:"-"`
	ImageFormat string      `json:"image_format"`
	Mode        CaptureMode `json:"mode"`
	Bounds      *Bounds     `json:"bounds,omitempty"`
	MatchCount  int         `json:"match_count,omitempty"`
	URL         string      `json:"url"`
	Viewport    Viewport    `json:"viewport"`
	Selectors   []string    `json:"selectors,omitempty"`
}

type CaptureMode

type CaptureMode string
const (
	CaptureModeViewport     CaptureMode = "viewport"
	CaptureModeFullPage     CaptureMode = "full_page"
	CaptureModeSelector     CaptureMode = "selector"
	CaptureModeSelectors    CaptureMode = "selectors"
	CaptureModeSelectorAll  CaptureMode = "selector_all"
	CaptureModeSelectorsAll CaptureMode = "selectors_all"
)

type CaptureNormalization

type CaptureNormalization struct {
	OutputGenerated   bool          `json:"output_generated"`
	OutputBaseName    string        `json:"output_base_name,omitempty"`
	OutputDirectory   string        `json:"output_directory,omitempty"`
	ViewportPreset    string        `json:"viewport_preset,omitempty"`
	DevicePreset      string        `json:"device_preset,omitempty"`
	Readiness         ReadinessMode `json:"readiness"`
	ReadinessIdle     string        `json:"readiness_idle,omitempty"`
	DisableAnimations bool          `json:"disable_animations"`
	ReducedMotion     bool          `json:"reduced_motion"`
	WaitForFonts      bool          `json:"wait_for_fonts"`
}

type CaptureRequest

type CaptureRequest struct {
	URL               string             `json:"url" yaml:"url"`
	OutputPath        string             `json:"output,omitempty" yaml:"output,omitempty"`
	MetadataPath      string             `json:"metadata,omitempty" yaml:"metadata,omitempty"`
	FullPage          bool               `json:"full_page,omitempty" yaml:"full_page,omitempty"`
	Selector          string             `json:"selector,omitempty" yaml:"selector,omitempty"`
	Selectors         []string           `json:"selectors,omitempty" yaml:"selectors,omitempty"`
	SelectorAll       string             `json:"selector_all,omitempty" yaml:"selector_all,omitempty"`
	SelectorsAll      []string           `json:"selectors_all,omitempty" yaml:"selectors_all,omitempty"`
	Padding           int                `json:"padding,omitempty" yaml:"padding,omitempty"`
	Wait              string             `json:"wait,omitempty" yaml:"wait,omitempty"`
	WaitFor           string             `json:"wait_for,omitempty" yaml:"wait_for,omitempty"`
	WaitForFunction   string             `json:"wait_for_function,omitempty" yaml:"wait_for_function,omitempty"`
	JavaScript        string             `json:"javascript,omitempty" yaml:"javascript,omitempty"`
	Viewport          Viewport           `json:"viewport" yaml:"viewport,omitempty"`
	ViewportPreset    string             `json:"viewport_preset,omitempty" yaml:"viewport_preset,omitempty"`
	DevicePreset      string             `json:"device_preset,omitempty" yaml:"device_preset,omitempty"`
	UserAgent         string             `json:"user_agent,omitempty" yaml:"user_agent,omitempty"`
	Readiness         ReadinessMode      `json:"readiness,omitempty" yaml:"readiness,omitempty"`
	ReadinessIdle     string             `json:"readiness_idle,omitempty" yaml:"readiness_idle,omitempty"`
	DisableAnimations bool               `json:"disable_animations,omitempty" yaml:"disable_animations,omitempty"`
	ReducedMotion     bool               `json:"reduced_motion,omitempty" yaml:"reduced_motion,omitempty"`
	WaitForFonts      bool               `json:"wait_for_fonts,omitempty" yaml:"wait_for_fonts,omitempty"`
	BeforeNavigateJS  string             `json:"before_navigate_js,omitempty" yaml:"before_navigate_js,omitempty"`
	AfterNavigateJS   string             `json:"after_navigate_js,omitempty" yaml:"after_navigate_js,omitempty"`
	BeforeCaptureJS   string             `json:"before_capture_js,omitempty" yaml:"before_capture_js,omitempty"`
	Timeout           string             `json:"timeout,omitempty" yaml:"timeout,omitempty"`
	OversizePolicy    OversizePolicy     `json:"oversize_policy,omitempty" yaml:"oversize_policy,omitempty"`
	Tile              CaptureTileOptions `json:"tile,omitzero" yaml:"tile,omitempty"`
}

func NormalizeCaptureRequest

func NormalizeCaptureRequest(req CaptureRequest) (CaptureRequest, error)

func ResolvePersistedPaths

func ResolvePersistedPaths(req CaptureRequest, outputDir string) (CaptureRequest, string, bool, error)

func (CaptureRequest) Mode

func (req CaptureRequest) Mode() CaptureMode

func (CaptureRequest) Normalization

func (req CaptureRequest) Normalization(outputGenerated bool, outputBaseName, outputDirectory string) CaptureNormalization

func (CaptureRequest) ReadinessIdleDuration

func (req CaptureRequest) ReadinessIdleDuration() time.Duration

func (CaptureRequest) TargetSelectors

func (req CaptureRequest) TargetSelectors() ([]string, bool)

func (CaptureRequest) TimeoutDuration

func (req CaptureRequest) TimeoutDuration() time.Duration

func (CaptureRequest) WaitDuration

func (req CaptureRequest) WaitDuration() time.Duration

type CaptureResult

type CaptureResult struct {
	OutputPath     string               `json:"output_path"`
	MetadataPath   string               `json:"metadata_path,omitempty"`
	ByteSize       int                  `json:"byte_size"`
	Artifact       CaptureArtifact      `json:"artifact"`
	CapturedAt     time.Time            `json:"captured_at"`
	Engine         string               `json:"engine"`
	Browser        BrowserInfo          `json:"browser"`
	Timing         CaptureTiming        `json:"timing"`
	Warnings       []CaptureWarning     `json:"warnings,omitempty"`
	Tiling         *CaptureTiling       `json:"tiling,omitempty"`
	Normalization  CaptureNormalization `json:"normalization"`
	ResolvedConfig CaptureRequest       `json:"resolved_config"`
}

type CaptureService

type CaptureService interface {
	CaptureArtifact(ctx context.Context, req CaptureRequest) (CaptureArtifact, error)
	Capture(ctx context.Context, req CaptureRequest) (CaptureResult, error)
	CaptureBatch(ctx context.Context, manifest Manifest, outputDir string) (BatchResult, error)
}

type CaptureTile added in v0.5.0

type CaptureTile struct {
	Index             int               `json:"index"`
	Row               int               `json:"row"`
	Column            int               `json:"column"`
	SourceBounds      Bounds            `json:"source_bounds"`
	DestinationBounds *Bounds           `json:"destination_bounds,omitempty"`
	OutputPath        string            `json:"output_path,omitempty"`
	ByteSize          int               `json:"byte_size,omitempty"`
	Status            CaptureTileStatus `json:"status"`
	Error             string            `json:"error,omitempty"`
	Bytes             []byte            `json:"-"`
}

type CaptureTileLimits added in v0.5.0

type CaptureTileLimits struct {
	MaxWidth          int     `json:"max_width"`
	MaxHeight         int     `json:"max_height"`
	MaxPixels         int64   `json:"max_pixels"`
	MaxTargetPixels   int64   `json:"max_target_pixels"`
	MaxStitchedPixels int64   `json:"max_stitched_pixels"`
	Overlap           int     `json:"overlap"`
	ScaleFactor       float64 `json:"scale_factor"`
}

type CaptureTileOptions added in v0.5.0

type CaptureTileOptions struct {
	MaxWidth          int   `json:"max_width,omitempty" yaml:"max_width,omitempty"`
	MaxHeight         int   `json:"max_height,omitempty" yaml:"max_height,omitempty"`
	MaxPixels         int64 `json:"max_pixels,omitempty" yaml:"max_pixels,omitempty"`
	MaxTargetPixels   int64 `json:"max_target_pixels,omitempty" yaml:"max_target_pixels,omitempty"`
	Overlap           int   `json:"overlap,omitempty" yaml:"overlap,omitempty"`
	Stitch            bool  `json:"stitch,omitempty" yaml:"stitch,omitempty"`
	MaxStitchedPixels int64 `json:"max_stitched_pixels,omitempty" yaml:"max_stitched_pixels,omitempty"`
	CleanupTiles      bool  `json:"cleanup_tiles,omitempty" yaml:"cleanup_tiles,omitempty"`
	// contains filtered or unexported fields
}

func (CaptureTileOptions) IsZero added in v0.5.0

func (o CaptureTileOptions) IsZero() bool

func (*CaptureTileOptions) UnmarshalJSON added in v0.5.0

func (o *CaptureTileOptions) UnmarshalJSON(payload []byte) error

func (*CaptureTileOptions) UnmarshalYAML added in v0.5.0

func (o *CaptureTileOptions) UnmarshalYAML(value *yaml.Node) error

type CaptureTileStatus added in v0.5.0

type CaptureTileStatus string
const (
	CaptureTilePending   CaptureTileStatus = "pending"
	CaptureTileCompleted CaptureTileStatus = "completed"
	CaptureTileFailed    CaptureTileStatus = "failed"
)

type CaptureTiling added in v0.5.0

type CaptureTiling struct {
	Status         CaptureTilingStatus `json:"status"`
	TargetBounds   Bounds              `json:"target_bounds"`
	Limits         CaptureTileLimits   `json:"limits"`
	TileCount      int                 `json:"tile_count"`
	CompletedCount int                 `json:"completed_count"`
	FailedCount    int                 `json:"failed_count"`
	MetadataPath   string              `json:"metadata_path,omitempty"`
	StitchedPath   string              `json:"stitched_path,omitempty"`
	Tiles          []CaptureTile       `json:"tiles,omitempty"`
	Warnings       []CaptureWarning    `json:"warnings,omitempty"`
}

type CaptureTilingStatus added in v0.5.0

type CaptureTilingStatus string
const (
	CaptureTilingComplete CaptureTilingStatus = "complete"
	CaptureTilingPartial  CaptureTilingStatus = "partial"
	CaptureTilingFailed   CaptureTilingStatus = "failed"
)

type CaptureTiming

type CaptureTiming struct {
	NavigationStartedAt   time.Time `json:"navigation_started_at"`
	NavigationCompletedAt time.Time `json:"navigation_completed_at"`
	ReadyAt               time.Time `json:"ready_at"`
	CapturedAt            time.Time `json:"captured_at"`
	TotalDuration         string    `json:"total_duration"`
}

type CaptureWarning

type CaptureWarning struct {
	Code    string `json:"code"`
	Message string `json:"message"`
}

type ChromiumEngine

type ChromiumEngine struct {
	// contains filtered or unexported fields
}

func NewChromiumEngine

func NewChromiumEngine(opts ChromiumOptions) *ChromiumEngine

func (*ChromiumEngine) Capture

func (*ChromiumEngine) Name

func (e *ChromiumEngine) Name() string

type ChromiumOptions

type ChromiumOptions struct {
	BrowserPath string
	Headless    bool
	Now         func() time.Time
}

type DeviceProfile

type DeviceProfile struct {
	Name      string   `json:"name"`
	Viewport  Viewport `json:"viewport"`
	UserAgent string   `json:"user_agent,omitempty"`
}

func LookupDevicePreset

func LookupDevicePreset(name string) (DeviceProfile, bool)

type DiffEntry

type DiffEntry struct {
	RelativePath   string           `json:"relative_path,omitempty"`
	BasePath       string           `json:"base_path"`
	ComparePath    string           `json:"compare_path"`
	OutputPath     string           `json:"output_path,omitempty"`
	MetadataPath   string           `json:"metadata_path,omitempty"`
	ByteSize       int              `json:"byte_size,omitempty"`
	Width          int              `json:"width,omitempty"`
	Height         int              `json:"height,omitempty"`
	TotalPixels    int              `json:"total_pixels,omitempty"`
	ChangedPixels  int              `json:"changed_pixels,omitempty"`
	ChangedPercent float64          `json:"changed_percent,omitempty"`
	Threshold      float64          `json:"threshold"`
	Changed        bool             `json:"changed"`
	MissingBase    bool             `json:"missing_base,omitempty"`
	MissingCompare bool             `json:"missing_compare,omitempty"`
	Warnings       []CaptureWarning `json:"warnings,omitempty"`
}

type DiffMode

type DiffMode string
const (
	DiffModeImage     DiffMode = "image"
	DiffModeDirectory DiffMode = "directory"
)

func InferDiffMode

func InferDiffMode(basePath, comparePath string) (DiffMode, error)

type DiffRequest

type DiffRequest struct {
	BasePath     string  `json:"base_path"`
	ComparePath  string  `json:"compare_path"`
	OutputPath   string  `json:"output,omitempty"`
	MetadataPath string  `json:"metadata,omitempty"`
	Threshold    float64 `json:"threshold,omitempty"`
}

func NormalizeDiffRequest

func NormalizeDiffRequest(req DiffRequest) (DiffRequest, error)

func ResolveDiffPaths

func ResolveDiffPaths(req DiffRequest, mode DiffMode) (DiffRequest, error)

type DiffResult

type DiffResult struct {
	Mode         DiffMode    `json:"mode"`
	BasePath     string      `json:"base_path"`
	ComparePath  string      `json:"compare_path"`
	OutputPath   string      `json:"output_path"`
	MetadataPath string      `json:"metadata_path,omitempty"`
	Threshold    float64     `json:"threshold"`
	Entry        *DiffEntry  `json:"entry,omitempty"`
	Entries      []DiffEntry `json:"entries,omitempty"`
	Summary      DiffSummary `json:"summary"`
	CreatedAt    time.Time   `json:"created_at"`
}

type DiffService

type DiffService interface {
	Diff(ctx context.Context, req DiffRequest) (DiffResult, error)
}

type DiffSummary

type DiffSummary struct {
	ComparedFiles       int              `json:"compared_files"`
	ChangedFiles        int              `json:"changed_files"`
	MissingBaseFiles    int              `json:"missing_base_files"`
	MissingCompareFiles int              `json:"missing_compare_files"`
	TotalChangedPixels  int              `json:"total_changed_pixels"`
	Warnings            []CaptureWarning `json:"warnings,omitempty"`
}

type Engine

type Engine interface {
	Name() string
	Capture(ctx context.Context, req CaptureRequest) (EngineResult, error)
}

func NewEngine

func NewEngine(config EngineConfig) (Engine, error)

type EngineConfig

type EngineConfig struct {
	EngineName           EngineName
	BrowserPath          string
	Headless             bool
	PlaywrightBrowser    string
	PlaywrightNodeBinary string
	PlaywrightRuntimeDir string
	PlaywrightScriptPath string
}

type EngineName

type EngineName string
const (
	EngineChromium   EngineName = "chromium"
	EnginePlaywright EngineName = "playwright"
)

func NormalizeEngineName

func NormalizeEngineName(value string) EngineName

type EngineResult

type EngineResult struct {
	Artifact CaptureArtifact  `json:"artifact"`
	Browser  BrowserInfo      `json:"browser"`
	Timing   CaptureTiming    `json:"timing"`
	Warnings []CaptureWarning `json:"warnings,omitempty"`
	Tiling   *CaptureTiling   `json:"tiling,omitempty"`
}

type Error

type Error struct {
	Code      ErrorCode      `json:"code"`
	Operation string         `json:"operation,omitempty"`
	Message   string         `json:"message"`
	Metadata  map[string]any `json:"metadata,omitempty"`
	Err       error          `json:"-"`
}

func (*Error) Error

func (e *Error) Error() string

func (*Error) Unwrap

func (e *Error) Unwrap() error

func (*Error) WithMetadata

func (e *Error) WithMetadata(key string, value any) *Error

type ErrorCode

type ErrorCode string
const (
	CodeValidation       ErrorCode = "validation_error"
	CodeManifest         ErrorCode = "manifest_error"
	CodeBrowserStartup   ErrorCode = "browser_startup_error"
	CodeNavigation       ErrorCode = "navigation_error"
	CodeSelectorNotFound ErrorCode = "selector_not_found"
	CodeNoVisibleMatches ErrorCode = "no_visible_matches"
	CodeTimeout          ErrorCode = "timeout_error"
	CodeWrite            ErrorCode = "write_error"
	CodeCapture          ErrorCode = "capture_error"
	CodeOversize         ErrorCode = "oversize_error"
	CodeUnsupported      ErrorCode = "unsupported_error"
	CodePartialCapture   ErrorCode = "partial_capture_error"
)

type Manifest

type Manifest struct {
	OutputDir         string             `json:"output_dir,omitempty" yaml:"output_dir,omitempty"`
	Viewport          *Viewport          `json:"viewport,omitempty" yaml:"viewport,omitempty"`
	ViewportPreset    string             `json:"viewport_preset,omitempty" yaml:"viewport_preset,omitempty"`
	DevicePreset      string             `json:"device_preset,omitempty" yaml:"device_preset,omitempty"`
	Wait              string             `json:"wait,omitempty" yaml:"wait,omitempty"`
	WaitFor           string             `json:"wait_for,omitempty" yaml:"wait_for,omitempty"`
	WaitForFunction   string             `json:"wait_for_function,omitempty" yaml:"wait_for_function,omitempty"`
	Readiness         ReadinessMode      `json:"readiness,omitempty" yaml:"readiness,omitempty"`
	ReadinessIdle     string             `json:"readiness_idle,omitempty" yaml:"readiness_idle,omitempty"`
	DisableAnimations bool               `json:"disable_animations,omitempty" yaml:"disable_animations,omitempty"`
	ReducedMotion     bool               `json:"reduced_motion,omitempty" yaml:"reduced_motion,omitempty"`
	WaitForFonts      bool               `json:"wait_for_fonts,omitempty" yaml:"wait_for_fonts,omitempty"`
	Timeout           string             `json:"timeout,omitempty" yaml:"timeout,omitempty"`
	OversizePolicy    OversizePolicy     `json:"oversize_policy,omitempty" yaml:"oversize_policy,omitempty"`
	Tile              CaptureTileOptions `json:"tile,omitzero" yaml:"tile,omitempty"`
	Shots             []ManifestShot     `json:"shots" yaml:"shots"`
}

func LoadManifest

func LoadManifest(path string) (Manifest, error)

func (Manifest) Requests

func (m Manifest) Requests(outputDirOverride string) ([]CaptureRequest, error)

type ManifestShot

type ManifestShot struct {
	ID                string             `json:"id,omitempty" yaml:"id,omitempty"`
	URL               string             `json:"url" yaml:"url"`
	Output            string             `json:"output,omitempty" yaml:"output,omitempty"`
	Metadata          string             `json:"metadata,omitempty" yaml:"metadata,omitempty"`
	FullPage          bool               `json:"full_page,omitempty" yaml:"full_page,omitempty"`
	Selector          string             `json:"selector,omitempty" yaml:"selector,omitempty"`
	Selectors         []string           `json:"selectors,omitempty" yaml:"selectors,omitempty"`
	SelectorAll       string             `json:"selector_all,omitempty" yaml:"selector_all,omitempty"`
	SelectorsAll      []string           `json:"selectors_all,omitempty" yaml:"selectors_all,omitempty"`
	Padding           int                `json:"padding,omitempty" yaml:"padding,omitempty"`
	Wait              string             `json:"wait,omitempty" yaml:"wait,omitempty"`
	WaitFor           string             `json:"wait_for,omitempty" yaml:"wait_for,omitempty"`
	WaitForFunction   string             `json:"wait_for_function,omitempty" yaml:"wait_for_function,omitempty"`
	JavaScript        string             `json:"javascript,omitempty" yaml:"javascript,omitempty"`
	Viewport          *Viewport          `json:"viewport,omitempty" yaml:"viewport,omitempty"`
	ViewportPreset    string             `json:"viewport_preset,omitempty" yaml:"viewport_preset,omitempty"`
	DevicePreset      string             `json:"device_preset,omitempty" yaml:"device_preset,omitempty"`
	Readiness         ReadinessMode      `json:"readiness,omitempty" yaml:"readiness,omitempty"`
	ReadinessIdle     string             `json:"readiness_idle,omitempty" yaml:"readiness_idle,omitempty"`
	DisableAnimations bool               `json:"disable_animations,omitempty" yaml:"disable_animations,omitempty"`
	ReducedMotion     bool               `json:"reduced_motion,omitempty" yaml:"reduced_motion,omitempty"`
	WaitForFonts      bool               `json:"wait_for_fonts,omitempty" yaml:"wait_for_fonts,omitempty"`
	Timeout           string             `json:"timeout,omitempty" yaml:"timeout,omitempty"`
	OversizePolicy    OversizePolicy     `json:"oversize_policy,omitempty" yaml:"oversize_policy,omitempty"`
	Tile              CaptureTileOptions `json:"tile,omitzero" yaml:"tile,omitempty"`
}

type OpenAISemanticProviderOptions

type OpenAISemanticProviderOptions struct {
	CredentialResolver SemanticCredentialResolver
	HTTPClient         *http.Client
	BaseURL            string
}

type Options

type Options struct {
	Workflow     WorkflowOptions
	SemanticDiff SemanticDiffOptions
	Now          func() time.Time
}

type OversizePolicy added in v0.5.0

type OversizePolicy string
const (
	OversizePolicyFail OversizePolicy = "fail"
	OversizePolicyTile OversizePolicy = "tile"
)

type PartialCaptureError added in v0.5.0

type PartialCaptureError struct {
	Operation       string         `json:"operation"`
	FailedTileIndex int            `json:"failed_tile_index,omitempty"`
	CompletedCount  int            `json:"completed_count"`
	TotalCount      int            `json:"total_count"`
	Result          *CaptureResult `json:"result,omitempty"`
	Err             error          `json:"-"`
}

func (*PartialCaptureError) Error added in v0.5.0

func (e *PartialCaptureError) Error() string

func (*PartialCaptureError) Unwrap added in v0.5.0

func (e *PartialCaptureError) Unwrap() error

func (*PartialCaptureError) WithResult added in v0.5.0

func (e *PartialCaptureError) WithResult(result CaptureResult) *PartialCaptureError

type PlaywrightEngine

type PlaywrightEngine struct {
	// contains filtered or unexported fields
}

func NewPlaywrightEngine

func NewPlaywrightEngine(opts PlaywrightOptions) (*PlaywrightEngine, error)

func (*PlaywrightEngine) Capture

func (*PlaywrightEngine) Name

func (e *PlaywrightEngine) Name() string

type PlaywrightOptions

type PlaywrightOptions struct {
	NodeBinary  string
	BrowserName string
	BrowserPath string
	Headless    bool
	RuntimeDir  string
	ScriptPath  string
}

type ReadinessMode

type ReadinessMode string
const (
	ReadinessNone        ReadinessMode = "none"
	ReadinessInteractive ReadinessMode = "interactive"
	ReadinessComplete    ReadinessMode = "complete"
	ReadinessNetworkIdle ReadinessMode = "network_idle"
)

type SemanticCredentialResolver

type SemanticCredentialResolver func(ctx context.Context, provider string) (string, error)

type SemanticDiffAdvisoryPolicy

type SemanticDiffAdvisoryPolicy string
const (
	SemanticDiffAdvisoryOnly    SemanticDiffAdvisoryPolicy = "advisory"
	SemanticDiffAdvisoryEnforce SemanticDiffAdvisoryPolicy = "enforce"
)

type SemanticDiffMode

type SemanticDiffMode string
const (
	SemanticDiffModeGeneral       SemanticDiffMode = "general"
	SemanticDiffModeFocused       SemanticDiffMode = "focused"
	SemanticDiffModeCopy          SemanticDiffMode = "copy"
	SemanticDiffModeLayout        SemanticDiffMode = "layout"
	SemanticDiffModeAccessibility SemanticDiffMode = "accessibility"
	SemanticDiffModeCustom        SemanticDiffMode = "custom"
)

type SemanticDiffOptions

type SemanticDiffOptions struct {
	Providers           map[string]SemanticDiffProvider
	CredentialResolver  SemanticCredentialResolver
	RedactImage         SemanticImageRedactor
	HTTPClient          *http.Client
	DefaultProvider     string
	DefaultModels       map[string]string
	DefaultTimeout      time.Duration
	MaxImageBytes       int64
	MaxOutputTokens     int
	PersistRawResponses bool
	LLMs                llms.Options
	OpenAIBaseURL       string
	AnthropicBaseURL    string
}

type SemanticDiffProvider

type SemanticDiffProvider interface {
	Name() string
	CompareImages(ctx context.Context, req SemanticProviderRequest) (SemanticProviderResponse, error)
}

func NewLLMSSemanticDiffProvider

func NewLLMSSemanticDiffProvider(provider llms.Provider) SemanticDiffProvider

func NewOpenAISemanticDiffProvider

func NewOpenAISemanticDiffProvider(options OpenAISemanticProviderOptions) SemanticDiffProvider

type SemanticDiffRequest

type SemanticDiffRequest struct {
	CurrentPath        string               `json:"current_path" yaml:"current_path"`
	ReferencePath      string               `json:"reference_path" yaml:"reference_path"`
	Provider           string               `json:"provider,omitempty" yaml:"provider,omitempty"`
	Model              string               `json:"model,omitempty" yaml:"model,omitempty"`
	Mode               SemanticDiffMode     `json:"mode,omitempty" yaml:"mode,omitempty"`
	Prompt             string               `json:"prompt,omitempty" yaml:"prompt,omitempty"`
	PromptPath         string               `json:"prompt_path,omitempty" yaml:"prompt_path,omitempty"`
	Focus              []string             `json:"focus,omitempty" yaml:"focus,omitempty"`
	PixelContext       SemanticPixelContext `json:"pixel_context" yaml:"pixel_context,omitempty"`
	MetadataPath       string               `json:"metadata_path,omitempty" yaml:"metadata_path,omitempty"`
	RawResponsePath    string               `json:"raw_response_path,omitempty" yaml:"raw_response_path,omitempty"`
	Timeout            string               `json:"timeout,omitempty" yaml:"timeout,omitempty"`
	MaxOutputTokens    int                  `json:"max_output_tokens,omitempty" yaml:"max_output_tokens,omitempty"`
	PersistRawResponse bool                 `json:"persist_raw_response,omitempty" yaml:"persist_raw_response,omitempty"`
}

func NormalizeSemanticDiffRequest

func NormalizeSemanticDiffRequest(req SemanticDiffRequest) (SemanticDiffRequest, error)

type SemanticDiffResult

type SemanticDiffResult struct {
	CurrentPath      string                 `json:"current_path"`
	ReferencePath    string                 `json:"reference_path"`
	Provider         string                 `json:"provider"`
	Model            string                 `json:"model,omitempty"`
	Summary          string                 `json:"summary"`
	Differences      []SemanticDifference   `json:"differences,omitempty"`
	Verdict          SemanticDiffVerdict    `json:"verdict"`
	Severity         SemanticDiffSeverity   `json:"severity"`
	Prompt           SemanticPromptMetadata `json:"prompt"`
	PixelContext     SemanticPixelContext   `json:"pixel_context"`
	MetadataPath     string                 `json:"metadata_path,omitempty"`
	RawResponsePath  string                 `json:"raw_response_path,omitempty"`
	Warnings         []CaptureWarning       `json:"warnings,omitempty"`
	ProviderMetadata map[string]string      `json:"provider_metadata,omitempty"`
	Usage            SemanticProviderUsage  `json:"usage"`
	StartedAt        time.Time              `json:"started_at"`
	CompletedAt      time.Time              `json:"completed_at"`
	Duration         string                 `json:"duration,omitempty"`
	RawResponse      string                 `json:"-"`
}

type SemanticDiffRunPolicy

type SemanticDiffRunPolicy string
const (
	SemanticDiffRunNever       SemanticDiffRunPolicy = "never"
	SemanticDiffRunAlways      SemanticDiffRunPolicy = "always"
	SemanticDiffRunChangedOnly SemanticDiffRunPolicy = "changed_only"
)

type SemanticDiffService

type SemanticDiffService interface {
	SemanticDiff(ctx context.Context, req SemanticDiffRequest) (SemanticDiffResult, error)
}

type SemanticDiffSeverity

type SemanticDiffSeverity string
const (
	SemanticDiffSeverityInfo     SemanticDiffSeverity = "info"
	SemanticDiffSeverityMinor    SemanticDiffSeverity = "minor"
	SemanticDiffSeverityMajor    SemanticDiffSeverity = "major"
	SemanticDiffSeverityBlocking SemanticDiffSeverity = "blocking"
)

type SemanticDiffVerdict

type SemanticDiffVerdict string
const (
	SemanticDiffVerdictNoMeaningfulChange SemanticDiffVerdict = "no_meaningful_change"
	SemanticDiffVerdictNeedsReview        SemanticDiffVerdict = "needs_review"
	SemanticDiffVerdictRegression         SemanticDiffVerdict = "regression"
	SemanticDiffVerdictImprovement        SemanticDiffVerdict = "improvement"
	SemanticDiffVerdictInconclusive       SemanticDiffVerdict = "inconclusive"
)

type SemanticDifference

type SemanticDifference struct {
	Area           string               `json:"area,omitempty"`
	Description    string               `json:"description"`
	Severity       SemanticDiffSeverity `json:"severity"`
	Evidence       string               `json:"evidence,omitempty"`
	Recommendation string               `json:"recommendation,omitempty"`
}

type SemanticImagePayload

type SemanticImagePayload struct {
	Role       string `json:"role"`
	Path       string `json:"path"`
	MIMEType   string `json:"mime_type"`
	Base64Data string `json:"-"`
	ByteSize   int64  `json:"byte_size"`
}

type SemanticImageRedactionInput

type SemanticImageRedactionInput struct {
	Role string
	Path string
}

type SemanticImageRedactor

type SemanticImageRedactor func(ctx context.Context, input SemanticImageRedactionInput) (string, error)

type SemanticPixelContext

type SemanticPixelContext struct {
	PixelDiffImagePath string           `json:"pixel_diff_image_path,omitempty" yaml:"pixel_diff_image_path,omitempty"`
	ChangedPixels      int              `json:"changed_pixels,omitempty" yaml:"changed_pixels,omitempty"`
	TotalPixels        int              `json:"total_pixels,omitempty" yaml:"total_pixels,omitempty"`
	ChangedPercent     float64          `json:"changed_percent,omitempty" yaml:"changed_percent,omitempty"`
	Threshold          float64          `json:"threshold,omitempty" yaml:"threshold,omitempty"`
	Warnings           []CaptureWarning `json:"warnings,omitempty" yaml:"warnings,omitempty"`
}

type SemanticPromptMetadata

type SemanticPromptMetadata struct {
	Mode                 SemanticDiffMode `json:"mode"`
	Focus                []string         `json:"focus,omitempty"`
	PromptSource         string           `json:"prompt_source"`
	StructuredOutput     bool             `json:"structured_output"`
	PixelContextIncluded bool             `json:"pixel_context_included"`
}

type SemanticProviderRequest

type SemanticProviderRequest struct {
	Provider        string
	Model           string
	Prompt          string
	Images          []SemanticImagePayload
	Timeout         time.Duration
	MaxOutputTokens int
	StructuredJSON  bool
}

type SemanticProviderResponse

type SemanticProviderResponse struct {
	Provider string
	Model    string
	RawText  string
	Warnings []CaptureWarning
	Metadata map[string]string
	Usage    SemanticProviderUsage
}

type SemanticProviderUsage

type SemanticProviderUsage struct {
	InputTokens  int `json:"input_tokens,omitempty"`
	OutputTokens int `json:"output_tokens,omitempty"`
	TotalTokens  int `json:"total_tokens,omitempty"`
}

type Service

type Service struct {
	// contains filtered or unexported fields
}

func NewService

func NewService(engine Engine) *Service

func NewServiceWithOptions

func NewServiceWithOptions(engine Engine, opts Options) *Service

func (*Service) Capture

func (s *Service) Capture(ctx context.Context, req CaptureRequest) (CaptureResult, error)

func (*Service) CaptureArtifact

func (s *Service) CaptureArtifact(ctx context.Context, req CaptureRequest) (CaptureArtifact, error)

func (*Service) CaptureBatch

func (s *Service) CaptureBatch(ctx context.Context, manifest Manifest, outputDir string) (BatchResult, error)

func (*Service) CaptureScenario

func (s *Service) CaptureScenario(ctx context.Context, scenario WorkflowScenario) (WorkflowCaptureResult, error)

func (*Service) Diff

func (s *Service) Diff(ctx context.Context, req DiffRequest) (DiffResult, error)

func (*Service) GenerateWorkflowReport

func (s *Service) GenerateWorkflowReport(ctx context.Context, req WorkflowReportRequest) (WorkflowReportResult, error)

func (*Service) SemanticDiff

func (s *Service) SemanticDiff(ctx context.Context, req SemanticDiffRequest) (SemanticDiffResult, error)

type Viewport

type Viewport struct {
	Width       int     `json:"width" yaml:"width"`
	Height      int     `json:"height" yaml:"height"`
	ScaleFactor float64 `json:"scale_factor,omitempty" yaml:"scale_factor,omitempty"`
	Mobile      bool    `json:"mobile,omitempty" yaml:"mobile,omitempty"`
}

func LookupViewportPreset

func LookupViewportPreset(name string) (Viewport, bool)

type WorkflowArtifactLayout

type WorkflowArtifactLayout struct {
	Root         string `json:"root,omitempty" yaml:"root,omitempty"`
	CurrentDir   string `json:"current_dir,omitempty" yaml:"current_dir,omitempty"`
	ReferenceDir string `json:"reference_dir,omitempty" yaml:"reference_dir,omitempty"`
	DiffDir      string `json:"diff_dir,omitempty" yaml:"diff_dir,omitempty"`
	ReportDir    string `json:"report_dir,omitempty" yaml:"report_dir,omitempty"`
}

type WorkflowCaptureResult

type WorkflowCaptureResult struct {
	ScenarioID   string                        `json:"scenario_id"`
	ScenarioPath string                        `json:"scenario_path"`
	CurrentDir   string                        `json:"current_dir"`
	CapturedAt   time.Time                     `json:"captured_at"`
	Results      []WorkflowScreenCaptureResult `json:"results"`
}

type WorkflowCompareRect

type WorkflowCompareRect struct {
	X      int `json:"x" yaml:"x"`
	Y      int `json:"y" yaml:"y"`
	Width  int `json:"width" yaml:"width"`
	Height int `json:"height" yaml:"height"`
}

type WorkflowComparison

type WorkflowComparison struct {
	Mode          string               `json:"mode,omitempty" yaml:"mode,omitempty"`
	CurrentCrop   *WorkflowCompareRect `json:"current_crop,omitempty" yaml:"current_crop,omitempty"`
	ReferenceCrop *WorkflowCompareRect `json:"reference_crop,omitempty" yaml:"reference_crop,omitempty"`
	ResizeTo      string               `json:"resize_to,omitempty" yaml:"resize_to,omitempty"`
}

type WorkflowDefaults

type WorkflowDefaults struct {
	Viewport          Viewport             `json:"viewport" yaml:"viewport,omitempty"`
	ViewportPreset    string               `json:"viewport_preset,omitempty" yaml:"viewport_preset,omitempty"`
	DevicePreset      string               `json:"device_preset,omitempty" yaml:"device_preset,omitempty"`
	Comparison        WorkflowComparison   `json:"comparison" yaml:"comparison,omitempty"`
	SemanticDiff      WorkflowSemanticDiff `json:"semantic_diff" yaml:"semantic_diff,omitempty"`
	Readiness         ReadinessMode        `json:"readiness,omitempty" yaml:"readiness,omitempty"`
	ReadinessIdle     string               `json:"readiness_idle,omitempty" yaml:"readiness_idle,omitempty"`
	DisableAnimations bool                 `json:"disable_animations,omitempty" yaml:"disable_animations,omitempty"`
	ReducedMotion     bool                 `json:"reduced_motion,omitempty" yaml:"reduced_motion,omitempty"`
	WaitForFonts      bool                 `json:"wait_for_fonts,omitempty" yaml:"wait_for_fonts,omitempty"`
	Timeout           string               `json:"timeout,omitempty" yaml:"timeout,omitempty"`
	Wait              string               `json:"wait,omitempty" yaml:"wait,omitempty"`
	WaitFor           string               `json:"wait_for,omitempty" yaml:"wait_for,omitempty"`
	WaitForFunction   string               `json:"wait_for_function,omitempty" yaml:"wait_for_function,omitempty"`
	FullPage          *bool                `json:"full_page,omitempty" yaml:"full_page,omitempty"`
	OversizePolicy    OversizePolicy       `json:"oversize_policy,omitempty" yaml:"oversize_policy,omitempty"`
	Tile              CaptureTileOptions   `json:"tile,omitzero" yaml:"tile,omitempty"`
}

type WorkflowEnvironment

type WorkflowEnvironment struct {
	BaseURL              string            `json:"base_url" yaml:"base_url"`
	Engine               string            `json:"engine,omitempty" yaml:"engine,omitempty"`
	BrowserPath          string            `json:"browser_path,omitempty" yaml:"browser_path,omitempty"`
	Headless             *bool             `json:"headless,omitempty" yaml:"headless,omitempty"`
	PlaywrightBrowser    string            `json:"playwright_browser,omitempty" yaml:"playwright_browser,omitempty"`
	PlaywrightNodeBinary string            `json:"playwright_node_binary,omitempty" yaml:"playwright_node_binary,omitempty"`
	PlaywrightRuntimeDir string            `json:"playwright_runtime_dir,omitempty" yaml:"playwright_runtime_dir,omitempty"`
	FixtureMode          string            `json:"fixture_mode,omitempty" yaml:"fixture_mode,omitempty"`
	ReportFormat         string            `json:"report_format,omitempty" yaml:"report_format,omitempty"`
	SelectedScenario     string            `json:"selected_scenario,omitempty" yaml:"selected_scenario,omitempty"`
	PresentationMode     string            `json:"presentation_mode,omitempty" yaml:"presentation_mode,omitempty"`
	RuntimeOverrides     map[string]string `json:"runtime_overrides,omitempty" yaml:"runtime_overrides,omitempty"`
	Query                map[string]string `json:"query,omitempty" yaml:"query,omitempty"`
}

type WorkflowEvidenceItem

type WorkflowEvidenceItem struct {
	ID                string   `json:"id" yaml:"id"`
	Text              string   `json:"text" yaml:"text"`
	Stories           []string `json:"stories,omitempty" yaml:"stories,omitempty"`
	ExpectedSelectors []string `json:"expected_selectors,omitempty" yaml:"expected_selectors,omitempty"`
	Notes             []string `json:"notes,omitempty" yaml:"notes,omitempty"`
}

type WorkflowHandoff

type WorkflowHandoff struct {
	SelectedScenario string            `json:"selected_scenario,omitempty"`
	PresentationMode string            `json:"presentation_mode,omitempty"`
	RuntimeOverrides map[string]string `json:"runtime_overrides,omitempty"`
}

type WorkflowHookSet

type WorkflowHookSet struct {
	BeforeNavigate string         `json:"before_navigate,omitempty" yaml:"before_navigate,omitempty"`
	AfterNavigate  string         `json:"after_navigate,omitempty" yaml:"after_navigate,omitempty"`
	BeforeCapture  string         `json:"before_capture,omitempty" yaml:"before_capture,omitempty"`
	Mode           string         `json:"mode,omitempty" yaml:"mode,omitempty"`
	Context        map[string]any `json:"context,omitempty" yaml:"context,omitempty"`
}

type WorkflowHooks

type WorkflowHooks struct {
	AuthSetup  WorkflowHookSet `json:"auth_setup" yaml:"auth_setup,omitempty"`
	StateSetup WorkflowHookSet `json:"state_setup" yaml:"state_setup,omitempty"`
	Navigation WorkflowHookSet `json:"navigation" yaml:"navigation,omitempty"`
}

type WorkflowOptions

type WorkflowOptions struct {
	DefaultSelectedScenario string
	DefaultPresentationMode string
	HandoffQueryParam       string
	BuildHandoff            func(WorkflowScenario) (string, error)
}

func DefaultWorkflowOptions

func DefaultWorkflowOptions() WorkflowOptions

type WorkflowReportEntry

type WorkflowReportEntry struct {
	ScreenID                   string                 `json:"screen_id"`
	Label                      string                 `json:"label"`
	Route                      string                 `json:"route"`
	TargetURL                  string                 `json:"target_url"`
	CurrentImagePath           string                 `json:"current_image_path,omitempty"`
	CurrentMetadata            string                 `json:"current_metadata,omitempty"`
	CurrentCapture             *CaptureResult         `json:"current_capture,omitempty"`
	ReferenceImage             string                 `json:"reference_image"`
	ComparisonMode             string                 `json:"comparison_mode,omitempty"`
	ComparedCurrentImagePath   string                 `json:"compared_current_image_path,omitempty"`
	ComparedReferenceImagePath string                 `json:"compared_reference_image_path,omitempty"`
	DiffImagePath              string                 `json:"diff_image_path,omitempty"`
	DiffMetadataPath           string                 `json:"diff_metadata_path,omitempty"`
	DiffEntry                  *DiffEntry             `json:"diff_entry,omitempty"`
	DiffSummary                *DiffSummary           `json:"diff_summary,omitempty"`
	SemanticDiff               *SemanticDiffResult    `json:"semantic_diff,omitempty"`
	SemanticMetadataPath       string                 `json:"semantic_metadata_path,omitempty"`
	SemanticFailure            bool                   `json:"semantic_failure,omitempty"`
	MissingCurrent             bool                   `json:"missing_current,omitempty"`
	MissingReference           bool                   `json:"missing_reference,omitempty"`
	PrimaryStories             []WorkflowStory        `json:"primary_stories,omitempty"`
	SupportingStories          []WorkflowStory        `json:"supporting_stories,omitempty"`
	Evidence                   []WorkflowEvidenceItem `json:"evidence,omitempty"`
	ExpectedElements           []string               `json:"expected_elements,omitempty"`
	Notes                      []string               `json:"notes,omitempty"`
	Annotations                []string               `json:"annotations,omitempty"`
	Warnings                   []CaptureWarning       `json:"warnings,omitempty"`
	Status                     WorkflowReviewStatus   `json:"status"`
}

type WorkflowReportRequest

type WorkflowReportRequest struct {
	Scenario WorkflowScenario `json:"scenario"`
}

type WorkflowReportResult

type WorkflowReportResult struct {
	ScenarioID   string                `json:"scenario_id"`
	ScenarioPath string                `json:"scenario_path"`
	ReportFormat string                `json:"report_format"`
	ReportPath   string                `json:"report_path"`
	MetadataPath string                `json:"metadata_path"`
	CurrentDir   string                `json:"current_dir"`
	DiffDir      string                `json:"diff_dir"`
	Entries      []WorkflowReportEntry `json:"entries"`
	Stories      []WorkflowStoryReport `json:"stories"`
	CreatedAt    time.Time             `json:"created_at"`
	Status       WorkflowReviewStatus  `json:"status"`
}

type WorkflowReviewStatus

type WorkflowReviewStatus struct {
	Level   string `json:"level"`
	Label   string `json:"label"`
	Summary string `json:"summary,omitempty"`
}

type WorkflowScenario

type WorkflowScenario struct {
	ID          string                   `json:"id" yaml:"id"`
	Label       string                   `json:"label,omitempty" yaml:"label,omitempty"`
	Description string                   `json:"description,omitempty" yaml:"description,omitempty"`
	StorySource string                   `json:"story_source,omitempty" yaml:"story_source,omitempty"`
	Environment WorkflowEnvironment      `json:"environment" yaml:"environment,omitempty"`
	Artifacts   WorkflowArtifactLayout   `json:"artifacts" yaml:"artifacts,omitempty"`
	Defaults    WorkflowDefaults         `json:"defaults" yaml:"defaults,omitempty"`
	Hooks       WorkflowHooks            `json:"hooks" yaml:"hooks,omitempty"`
	Stories     map[string]WorkflowStory `json:"stories,omitempty" yaml:"stories,omitempty"`
	Screens     []WorkflowScreen         `json:"screens" yaml:"screens"`
	Context     map[string]any           `json:"context,omitempty" yaml:"context,omitempty"`
	SourcePath  string                   `json:"-" yaml:"-"`
	SourceDir   string                   `json:"-" yaml:"-"`
}

func LoadWorkflowScenario

func LoadWorkflowScenario(path string) (WorkflowScenario, error)

func LoadWorkflowScenarioWithOptions

func LoadWorkflowScenarioWithOptions(path string, opts WorkflowOptions) (WorkflowScenario, error)

type WorkflowScreen

type WorkflowScreen struct {
	ID                string                 `json:"id" yaml:"id"`
	Label             string                 `json:"label,omitempty" yaml:"label,omitempty"`
	Route             string                 `json:"route" yaml:"route"`
	Query             map[string]string      `json:"query,omitempty" yaml:"query,omitempty"`
	OutputName        string                 `json:"output_name,omitempty" yaml:"output_name,omitempty"`
	Comparison        WorkflowComparison     `json:"comparison" yaml:"comparison,omitempty"`
	SemanticDiff      WorkflowSemanticDiff   `json:"semantic_diff" yaml:"semantic_diff,omitempty"`
	ReferenceImage    string                 `json:"reference_image" yaml:"reference_image"`
	PrimaryStories    []string               `json:"primary_stories,omitempty" yaml:"primary_stories,omitempty"`
	SupportingStories []string               `json:"supporting_stories,omitempty" yaml:"supporting_stories,omitempty"`
	ExpectedElements  []string               `json:"expected_elements,omitempty" yaml:"expected_elements,omitempty"`
	Notes             []string               `json:"notes,omitempty" yaml:"notes,omitempty"`
	Annotations       []string               `json:"annotations,omitempty" yaml:"annotations,omitempty"`
	Hooks             WorkflowHooks          `json:"hooks" yaml:"hooks,omitempty"`
	Evidence          []WorkflowEvidenceItem `json:"evidence,omitempty" yaml:"evidence,omitempty"`
	Capture           CaptureRequest         `json:"capture" yaml:"capture,omitempty"`
}

type WorkflowScreenCaptureResult

type WorkflowScreenCaptureResult struct {
	ScreenID         string        `json:"screen_id"`
	Label            string        `json:"label"`
	Route            string        `json:"route"`
	TargetURL        string        `json:"target_url"`
	OutputPath       string        `json:"output_path"`
	MetadataPath     string        `json:"metadata_path,omitempty"`
	ReferenceImage   string        `json:"reference_image"`
	Capture          CaptureResult `json:"capture"`
	ExpectedElements []string      `json:"expected_elements,omitempty"`
}

type WorkflowSemanticDiff

type WorkflowSemanticDiff struct {
	Enabled            *bool                      `json:"enabled,omitempty" yaml:"enabled,omitempty"`
	Provider           string                     `json:"provider,omitempty" yaml:"provider,omitempty"`
	Model              string                     `json:"model,omitempty" yaml:"model,omitempty"`
	Mode               SemanticDiffMode           `json:"mode,omitempty" yaml:"mode,omitempty"`
	Run                SemanticDiffRunPolicy      `json:"run,omitempty" yaml:"run,omitempty"`
	Focus              []string                   `json:"focus,omitempty" yaml:"focus,omitempty"`
	Prompt             string                     `json:"prompt,omitempty" yaml:"prompt,omitempty"`
	PromptPath         string                     `json:"prompt_path,omitempty" yaml:"prompt_path,omitempty"`
	Timeout            string                     `json:"timeout,omitempty" yaml:"timeout,omitempty"`
	MaxOutputTokens    int                        `json:"max_output_tokens,omitempty" yaml:"max_output_tokens,omitempty"`
	PersistRawResponse bool                       `json:"persist_raw_response,omitempty" yaml:"persist_raw_response,omitempty"`
	RawResponsePath    string                     `json:"raw_response_path,omitempty" yaml:"raw_response_path,omitempty"`
	AdvisoryPolicy     SemanticDiffAdvisoryPolicy `json:"advisory_policy,omitempty" yaml:"advisory_policy,omitempty"`
	FailureSeverity    SemanticDiffSeverity       `json:"failure_severity,omitempty" yaml:"failure_severity,omitempty"`
	FailureVerdicts    []SemanticDiffVerdict      `json:"failure_verdicts,omitempty" yaml:"failure_verdicts,omitempty"`
	APIKey             string                     `json:"-" yaml:"api_key,omitempty"`
	OpenAIAPIKey       string                     `json:"-" yaml:"openai_api_key,omitempty"`
	AnthropicAPIKey    string                     `json:"-" yaml:"anthropic_api_key,omitempty"`
}

type WorkflowService

type WorkflowService interface {
	CaptureScenario(ctx context.Context, scenario WorkflowScenario) (WorkflowCaptureResult, error)
	GenerateWorkflowReport(ctx context.Context, req WorkflowReportRequest) (WorkflowReportResult, error)
}

type WorkflowStory

type WorkflowStory struct {
	ID                 string   `json:"id" yaml:"id"`
	Priority           string   `json:"priority,omitempty" yaml:"priority,omitempty"`
	Title              string   `json:"title,omitempty" yaml:"title,omitempty"`
	AcceptanceCriteria []string `json:"acceptance_criteria,omitempty" yaml:"acceptance_criteria,omitempty"`
	Notes              []string `json:"notes,omitempty" yaml:"notes,omitempty"`
}

type WorkflowStoryReport

type WorkflowStoryReport struct {
	StoryID      string                 `json:"story_id"`
	Story        WorkflowStory          `json:"story"`
	ScreenIDs    []string               `json:"screen_ids,omitempty"`
	Evidence     []WorkflowEvidenceItem `json:"evidence,omitempty"`
	MissingPaths []string               `json:"missing_paths,omitempty"`
	Status       WorkflowReviewStatus   `json:"status"`
}

Directories

Path Synopsis
cmd
webcap command
commands
webcap
Package webcap provides go-command handlers for reusable webcap services.
Package webcap provides go-command handlers for reusable webcap services.
internal
Package mcp exposes a stdio MCP server over the webcap capture and diff services so AI clients can trigger artifact-oriented screenshot workflows.
Package mcp exposes a stdio MCP server over the webcap capture and diff services so AI clients can trigger artifact-oriented screenshot workflows.
pkg

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL