sg-visual-discover

star 6

Explore a web project's codebase to discover testable user journeys, then generate YAML test manifests mirroring the UI navigation tree. Use when setting up Visual tests for a new project, or after structural UI changes (new routes, removed pages, navigation updates). Trigger on "sg-visual-discover", "visual discover", "generate visual tests", "discover test routes", "update test manifests", "scan UI for tests".

bacoco By bacoco schedule Updated 4/17/2026

name: sg-visual-discover description: Explore a web project's codebase to discover testable user journeys, then generate YAML test manifests mirroring the UI navigation tree. Use when setting up Visual tests for a new project, or after structural UI changes (new routes, removed pages, navigation updates). Trigger on "sg-visual-discover", "visual discover", "generate visual tests", "discover test routes", "update test manifests", "scan UI for tests". context: conversation argument-hint: "[project-path] [--all] [--diff=ref] [--refresh-existing]"

/sg-visual-discover — Discover & Generate Visual Test Manifests

Explore the codebase of any web application, detect all user-facing routes and interactions, and generate a YAML test manifest tree that mirrors the UI navigation structure.

Recommended model: Sonnet 4.6. This skill crawls routes + generates YAML manifests — mechanical work where Opus 4.7 provides no measurable quality gain. Use /model sonnet before invoking to save Opus weekly quota.

Invocations

Command Behavior
/sg-visual-discover Interactive — detect changes, ask scope
/sg-visual-discover <path> Discover routes in specific project path
/sg-visual-discover --diff=main Generate manifests only for routes impacted by changes since main
/sg-visual-discover --all Discover all routes (skip scope question)
/sg-visual-discover --refresh-existing In diff mode, also regenerate existing manifests for impacted routes

Scope Detection

Before scanning the project, determine scope:

  1. Check for --all flag → skip to Phase 1 with full scope.
  2. Check for --diff=<ref> flag → use that ref.
  3. If BOTH --all and --diff → error: Cannot use --all and --diff together.
  4. If neither flag: a. Detect base reference:
    current_branch=$(git rev-parse --abbrev-ref HEAD)
    if [ "$current_branch" != "main" ] && [ "$current_branch" != "master" ]; then
      if git show-ref --verify --quiet refs/heads/main; then
        base=$(git merge-base HEAD main)
      elif git show-ref --verify --quiet refs/heads/master; then
        base=$(git merge-base HEAD master)
      else
        base="HEAD~1"
      fi
    else
      base="HEAD~1"
    fi
    
    b. Run git diff --name-only {base} → changed files. c. If changes detected, map to routes using the same generic route detection described in sg-code-audit. d. Ask user:

    "I detected {N} files changed since {base}, impacting {R} routes. What scope?"

    1. Only impacted routes — generate manifests for new routes only
    2. Full app — discover all routes
    3. Different base e. If no changes detected, offer same fallback as sg-code-audit: last commit, full app, or different base.
  5. Store scope_mode ("diff" or "full") and impacted_routes[] for Phase 4.

Flag combination: /sg-visual-discover <project-path> --diff=<ref> Both apply: discover within project-path, but only generate manifests for routes impacted by the diff. The diff is computed on the whole repo, but only routes within project-path are considered.

--refresh-existing only applies in diff mode. In full discovery, existing manifests are still skipped by default.

Prerequisites

Before running, verify:

  1. agent-browser --version — must be installed
  2. The project has a web frontend (check for package.json with a framework)
  3. Identify the visual-tests/ directory (create if missing)

Phase 1: Detect Project Structure

Explore the codebase to identify:

1.1 Frontend Framework

Search for framework indicators in this order:

  • next.config.* or app/layout.tsxNext.js (App Router)
  • pages/_app.tsx or pages/index.tsxNext.js (Pages Router)
  • src/App.tsx + react-router in package.json → React Router
  • src/router/index.ts or vue.config.*Vue
  • angular.jsonAngular
  • Fallback: grep for route patterns (<Route, path:, router.get)

1.2 Static HTML Fallback

If NO framework is detected in Phase 1.1:

  1. Scan the project root, src/, and public/ directories for *.html files
  2. Each .html file becomes a test manifest in a pages/ category
  3. Derive the URL from the file path with these rules:
    • Files inside public/ → strip the public/ prefix (e.g., public/about.html{base_url}/about.html)
    • index.html at any level → map to the directory URL (e.g., public/index.html{base_url}/, public/help/index.html{base_url}/help/)
    • Other files → use the relative path as-is (e.g., pages/contact.html{base_url}/pages/contact.html)
  4. Screenshot names must be unique per page — derive from the relative URL path, slugified (e.g., public/help/index.htmlpages-help-index.png, about.htmlpages-about.png)
  5. Generate a minimal manifest per page:
name: "<filename without extension>"
description: "Auto-generated from static HTML file"
priority: medium
requires_auth: false
timeout: 30s
tags: [auto-generated, static-html]

steps:
  - action: open
    url: "{base_url}/<derived-url-path>"
  - action: llm-check
    description: "Page loads and renders content"
    criteria: "Page content is visible, no blank screen, no broken images or missing resources"
    severity: critical
    screenshot: "pages-<slugified-path>.png"
  1. Element-aware manifest generation: Before generating the minimal manifest, parse each HTML file for interactive elements and enrich the steps:

    • If the file contains <video> or <iframe> → add: - action: llm-check with criteria "Media element loads and is playable/visible, no broken embed"
    • If the file contains <form> → add: - action: fill + - action: click (submit button) + - action: llm-check with criteria "Form submission feedback is visible"
    • If the file contains <img> (3+ images) → add: - action: llm-check with criteria "All images loaded, no broken image icons"
    • If the file contains role="tab" or class containing tab → add tab click + assert steps (see SPA Tab Detection below)
  2. Batch mode: When invoked with --all on a static site, generate all manifests in a single pass without step-by-step user interaction. Print a summary at the end instead of asking confirmation per file.

  3. Log detection: "No framework detected — falling back to static HTML scan"

  4. If no .html files found either, ask the user to specify the route source

1.2b SPA Tab Detection

If no JS framework is detected AND only 1-3 HTML files are found, the site may be a single-page app with tabs:

  1. Scan each HTML file for tab indicators:
    • role="tab" or role="tablist" attributes
    • Elements with class containing tab (e.g., nav-tabs, fr-tabs, tab-button)
    • <a href="#section"> hash links that act as tab navigation
  2. Scan JS files for:
    • Files named tab-*.js, *-tab.js, or *tabs*.js
    • Hash route handling: window.location.hash, hashchange event
  3. For backend projects (FastAPI/Flask/Django), scan server.py, app.py, main.py for:
    • @app.get("...") or @app.route("...") decorators → each is a route
    • Hash routes in JS files (#import, #clean, etc.) → each is a tab/view
  4. For each detected tab/section:
    • Generate a manifest with: openclick on the tab → llm-check "Tab content is visible and correct"
    • Category: tabs/ subdirectory
    • Name derived from tab text or hash

1.2c Auto-Detect Dev Server Command

Before asking the user for base_url, auto-detect the dev server command:

  1. Check playwright.config.js or playwright.config.ts → look for webServer.command field
  2. Check package.json → look for scripts.dev or scripts.start
  3. Check for Python dev scripts: scripts/*.py, run.py, server.py with uvicorn or flask patterns
  4. Check for docker-compose.yml or docker-compose.yaml → propose docker compose up -d

If found, propose the result as build_command in _config.yaml:

build_command: "<detected command>"  # auto-detected from {source}

If nothing detected, leave as build_command: null.

1.3 Route Definitions

Based on the detected framework:

Framework Where to look
Next.js App Router app/**/page.tsx — each directory = route
Next.js Pages pages/**/*.tsx — file path = route
React Router Router config files, <Route path=...> patterns
Vue Router router/index.ts, routes: [...]
Angular *-routing.module.ts
Generic Grep for path:, route:, URL patterns in config

Collect: route path, page component file, any associated layout.

1.4 Navigation Structure

Find navigation components that define the UI hierarchy:

  • Search for files named navigation.ts, nav-*.ts, sidebar-*.tsx, menu-*.tsx
  • Search for dashboard-data.ts, route-registry.ts or similar
  • Look for arrays of navigation items with labels, paths, icons
  • This defines the test directory structure

1.5 Feature Flags

Search for feature flag systems:

  • NEXT_PUBLIC_FEATURE_*, FEATURE_* env vars
  • isFeatureEnabled(), featureFlag patterns
  • Record which features are flagged — tests for disabled features get priority: low

1.6 Interactive Components

For each route, scan the page component for:

  • Forms (<form, onSubmit, input fields)
  • Modals (Dialog, Modal, Sheet)
  • File uploads (<input type="file", Upload, Dropzone)
  • Chat interfaces (onSend, message, chat input patterns)
  • Data tables, lists with actions
  • These become the steps in the manifest

1.7 Test Data

Search the project for usable test files:

  • test/fixtures/, data-sample/, __fixtures__/
  • *.pdf, *.docx, *.csv in data directories
  • Seed scripts, sample data generators
  • Record paths for use in manifest data: sections

1.8 Credentials

Look for dev credentials in:

  • CLAUDE.md, README.md — search for "credentials", "login", "username", "password"
  • .env.example — search for auth-related vars
  • Test files — search for login helpers

Phase 2: Generate Config

If visual-tests/_config.yaml does not exist, create it:

# visual-tests/_config.yaml — Generated by /sg-visual-discover
base_url: "<detected_url>"       # from dev server config, docker-compose, or README
credentials:
  username: "<detected>"
  password: "<detected>"
screenshots_dir: "visual-tests/_results/screenshots"
report_path: "visual-tests/_results/report.md"
agent_browser_path: "agent-browser"
build_command: null              # set to rebuild command (e.g. "docker compose up -d --build frontend") or leave null if no rebuild needed

If it already exists, do NOT overwrite.

Phase 3: Generate Shared Bricks

_shared/login.yaml

If authentication is detected, create (if not exists):

name: "Login"
description: "Authenticate with dev credentials"
steps:
  - action: open
    url: "{base_url}"
  - action: click
    target: "<detected_login_button>"
  - action: fill
    target: "<detected_username_field>"
    value: "{credentials.username}"
  - action: fill
    target: "<detected_password_field>"
    value: "{credentials.password}"
  - action: click
    target: "<detected_submit_button>"
  - action: wait
    duration: 3s
  - action: assert_url
    expected: "<detected_post_login_url>"

Fill in the targets by reading the actual login component or by running agent-browser open {base_url} and doing a snapshot to identify the real labels.

Phase 4: Generate Test Manifests

For each discovered route, organized by the navigation hierarchy:

4.1 Directory Structure

Mirror the navigation tree:

visual-tests/
  _config.yaml
  _regressions.yaml        # create empty if not exists
  _shared/
    login.yaml
  <nav-group>/
    <page>.yaml
    <sub-group>/
      <page>.yaml

4.2 Manifest Generation Rules

For each route:

  1. If scope_mode == "diff" AND route is NOT in impacted_routes → SKIP (not impacted by changes)
  2. If a manifest already exists AND --refresh-existing is NOT set → SKIP (never overwrite without explicit flag)
  3. If a manifest already exists AND --refresh-existing IS set → REGENERATE (re-scan route components, overwrite manifest). Warn: Refreshing {N} existing manifests.
  4. If no manifest exists → CREATE new manifest

Skeleton manifest (minimum viable test):

name: "<Page Name>"
description: "Auto-generated — customize with real test steps"
priority: medium
requires_auth: true
timeout: 30s
tags: [auto-generated]

steps:
  - action: open
    url: "{base_url}<route_path>"

  - action: llm-check
    description: "Page loads correctly"
    criteria: "Page content is visible, no error messages, no blank screen"
    severity: critical
    screenshot: "<page-name>-load.png"

Enhanced manifest (when interactive components are detected):

If the page has forms, uploads, chat, etc., generate steps that exercise them:

  • Forms → fill + click submit + llm-check result
  • Uploads → upload + llm-wait for processing + llm-check result
  • Chat → fill message + press Enter + wait + llm-check response
  • Tables → llm-check "data is displayed, rows visible"

Pre-fill data: section with discovered test files when relevant.

Report:

  • scope_mode == "diff": Created {N} new manifests. Skipped {S} routes (manifests exist). {U} uncovered routes (no component match).
  • scope_mode == "full": Created {N} new manifests. Skipped {S} routes (manifests exist). {D} routes deprecated.

4.3 Deprecated Handling

For each existing manifest whose route no longer exists in the codebase:

  • Add deprecated: true to the manifest frontmatter
  • Do NOT delete the file

Phase 5: Output Summary

After generation, output:

## /sg-visual-discover Summary

**Framework:** Next.js App Router (or "Static HTML fallback — no framework detected")
**Routes found:** 24
**Navigation groups:** 4

### Generated
- _config.yaml (new)
- _shared/login.yaml (new)
- auth/login.yaml (new)
- dashboard/home.yaml (new)
- ...

### Skipped (already exist)
- dashboard/home.yaml
- ...

### Deprecated
- dashboard/old-feature.yaml (route removed)

### Test Data Found
- data-sample/clement acte.pdf
- data/notarial-corpus/acte_vente/ (15 PDFs)

Run `/sg-visual-run` to execute tests.
Run `/sg-visual-run --regressions` to run only known failures.

agent-browser Reference

Command Usage Example
open <url> Navigate to URL agent-browser open http://localhost:3000
snapshot Accessibility tree with refs (for AI) agent-browser snapshot
click <ref> Click element by ref agent-browser click @e12
fill <ref> <text> Clear and fill input agent-browser fill @e10 "alex"
upload <sel> <files> Upload file to input agent-browser upload "#file-input" ./test.md
eval <js> Run JavaScript in page agent-browser eval 'document.querySelector("input").id'
screenshot <path> Take screenshot agent-browser screenshot /tmp/capture.png
get url Get current URL agent-browser get url
close Close browser agent-browser close

Key Rules

  1. NEVER overwrite existing manifests UNLESS --refresh-existing is passed. When refreshing, preserve user-added comments and custom steps. By default, only create new ones.
  2. NEVER delete manifests — mark deprecated
  3. Always verify agent-browser is installed before running
  4. Use real element labels — do a snapshot of each page to find actual button/input text
  5. Pre-fill test data when fixtures are found
  6. Ask the user if framework or route detection fails

Final Checklist

Before considering the discovery complete, verify:

  • agent-browser installed and functional
  • Framework detected (or generic fallback documented)
  • At least one route collected
  • _config.yaml created or existing one left untouched
  • _regressions.yaml created (empty) if absent
  • No existing manifest overwritten
  • Summary displayed (generated / skipped / deprecated)
Install via CLI
npx skills add https://github.com/bacoco/ShipGuard --skill sg-visual-discover
Repository Details
star Stars 6
call_split Forks 2
navigation Branch main
article Path SKILL.md
More from Creator