replay ui fix (#56)

* refactor: rename inspect to replay across the codebase

Renames inspect-ui/ to replay-ui/, internal/inspect/ to internal/replay/,
the CLI subcommand from `sanderling inspect` to `sanderling replay`, and
updates all references in docs, Makefile, README, and Go comments.

* feat(replay-ui): show spec filename with full path on hover

RunList and RunDetail now render the basename of spec_path (e.g.
login.spec.ts) with the full path available as a title tooltip.
This commit is contained in:
pj authored and GitHub committed 2026-06-03 16:17:26 +05:30
1 parent a6f43e15b2
commit b44077afde
71 files changed
+104 -92

No files matched your search

+4 -4
View File
@@ -34,7 +34,7 @@ Usage:
Commands:
test Run a spec against an app for a fixed duration.
inspect Serve a local web UI for browsing runs/.
replay Serve a local web UI for browsing runs/.
doctor Check that the host environment is ready to run sanderling.
version Print the sanderling version.
@@ -98,12 +98,12 @@ func run(args []string, stdout, stderr io.Writer) error {
return err
}
return runTest(options, stdout)
case "inspect":
options, err := parseInspectArgs(args[2:], stderr)
case "replay":
options, err := parseReplayArgs(args[2:], stderr)
if err != nil {
return err
}
return runInspect(options, stdout)
return runReplay(options, stdout)
case "doctor":
return runDoctor(args[2:], stdout, stderr)
case "version", "-v", "--version":
@@ -14,29 +14,29 @@ import (
"strconv"
"time"
"github.com/priyanshujain/sanderling/internal/inspect"
"github.com/priyanshujain/sanderling/internal/replay"
)
type inspectOptions struct {
type replayOptions struct {
port int
noOpen bool
dev bool
directory string
}
func parseInspectArgs(args []string, stderr io.Writer) (inspectOptions, error) {
flagSet := flag.NewFlagSet("inspect", flag.ContinueOnError)
func parseReplayArgs(args []string, stderr io.Writer) (replayOptions, error) {
flagSet := flag.NewFlagSet("replay", flag.ContinueOnError)
flagSet.SetOutput(stderr)
var options inspectOptions
var options replayOptions
flagSet.IntVar(&options.port, "port", 0, "TCP port to listen on (0 = ephemeral)")
flagSet.BoolVar(&options.noOpen, "no-open", false, "do not open the default browser on startup")
flagSet.BoolVar(&options.dev, "dev", false, "reverse-proxy non-API requests to "+inspect.DevTarget)
flagSet.BoolVar(&options.dev, "dev", false, "reverse-proxy non-API requests to "+replay.DevTarget)
if err := flagSet.Parse(args); err != nil {
return inspectOptions{}, err
return replayOptions{}, err
}
rest := flagSet.Args()
if len(rest) > 1 {
return inspectOptions{}, errors.New("inspect takes at most one positional argument (run or runs directory)")
return replayOptions{}, errors.New("replay takes at most one positional argument (run or runs directory)")
}
if len(rest) == 1 {
options.directory = rest[0]
@@ -44,16 +44,16 @@ func parseInspectArgs(args []string, stderr io.Writer) (inspectOptions, error) {
return options, nil
}
func runInspect(options inspectOptions, stdout io.Writer) error {
runsDirectory, deepLinkID, err := inspect.ResolveRunsDirectory(options.directory)
func runReplay(options replayOptions, stdout io.Writer) error {
runsDirectory, deepLinkID, err := replay.ResolveRunsDirectory(options.directory)
if err != nil {
return err
}
devTarget := ""
if options.dev {
devTarget = inspect.DevTarget
devTarget = replay.DevTarget
}
server, err := inspect.NewServer(inspect.ServerOptions{
server, err := replay.NewServer(replay.ServerOptions{
RunsDirectory: runsDirectory,
DevTarget: devTarget,
})
@@ -67,7 +67,7 @@ func runInspect(options inspectOptions, stdout io.Writer) error {
}
address := listener.Addr().(*net.TCPAddr)
browseURL := buildBrowseURL(address, deepLinkID)
fmt.Fprintf(stdout, "sanderling inspect listening on %s (runs=%s)\n", browseURL, runsDirectory)
fmt.Fprintf(stdout, "sanderling replay listening on %s (runs=%s)\n", browseURL, runsDirectory)
context, cancel := context.WithCancel(context.Background())
defer cancel()
@@ -7,8 +7,8 @@ import (
"testing"
)
func TestParseInspectArgs_Defaults(t *testing.T) {
options, err := parseInspectArgs(nil, io.Discard)
func TestParseReplayArgs_Defaults(t *testing.T) {
options, err := parseReplayArgs(nil, io.Discard)
if err != nil {
t.Fatal(err)
}
@@ -17,8 +17,8 @@ func TestParseInspectArgs_Defaults(t *testing.T) {
}
}
func TestParseInspectArgs_AllFlags(t *testing.T) {
options, err := parseInspectArgs([]string{"--port", "9090", "--no-open", "--dev", "/tmp/runs"}, io.Discard)
func TestParseReplayArgs_AllFlags(t *testing.T) {
options, err := parseReplayArgs([]string{"--port", "9090", "--no-open", "--dev", "/tmp/runs"}, io.Discard)
if err != nil {
t.Fatal(err)
}
@@ -27,19 +27,19 @@ func TestParseInspectArgs_AllFlags(t *testing.T) {
}
}
func TestParseInspectArgs_RejectsTooManyPositional(t *testing.T) {
_, err := parseInspectArgs([]string{"a", "b"}, io.Discard)
func TestParseReplayArgs_RejectsTooManyPositional(t *testing.T) {
_, err := parseReplayArgs([]string{"a", "b"}, io.Discard)
if err == nil || !strings.Contains(err.Error(), "at most one") {
t.Fatalf("expected too-many-args error, got %v", err)
}
}
func TestRun_HelpListsInspectCommand(t *testing.T) {
func TestRun_HelpListsReplayCommand(t *testing.T) {
var stdout bytes.Buffer
if err := run([]string{"sanderling"}, &stdout, io.Discard); err != nil {
t.Fatal(err)
}
if !strings.Contains(stdout.String(), "inspect") {
t.Errorf("usage missing inspect command: %q", stdout.String())
if !strings.Contains(stdout.String(), "replay") {
t.Errorf("usage missing replay command: %q", stdout.String())
}
}