From 26b27c09ed5f61530d667a0ee29a2f4c1f224705 Mon Sep 17 00:00:00 2001 From: PJ Date: Mon, 27 Apr 2026 18:51:39 +0700 Subject: [PATCH] feat(testrun): bundle + install web runtime when platform=web BundleWeb composes the user spec with web-runtime.ts; the chrome driver installs the resulting IIFE via Page.AddScriptToEvaluateOnNewDocument post-Launch so the per-tick V8 extractor + action evaluation can begin on step 1. --- internal/testrun/testrun.go | 67 +++++++++++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 6 deletions(-) diff --git a/internal/testrun/testrun.go b/internal/testrun/testrun.go index fb25818..7fb2ac4 100644 --- a/internal/testrun/testrun.go +++ b/internal/testrun/testrun.go @@ -11,6 +11,7 @@ import ( "github.com/priyanshujain/sanderling/internal/android" "github.com/priyanshujain/sanderling/internal/bundler" + "github.com/priyanshujain/sanderling/internal/driver" "github.com/priyanshujain/sanderling/internal/ios" "github.com/priyanshujain/sanderling/internal/runner" "github.com/priyanshujain/sanderling/internal/trace" @@ -45,24 +46,44 @@ func Execute(ctx context.Context, options Options, stdout io.Writer) error { } } aliases := map[string]string{} - if specAPIPath := resolveSpecAPIPath(options.Spec); specAPIPath != "" { + specAPIPath := resolveSpecAPIPath(options.Spec) + if specAPIPath != "" { aliases["@sanderling/spec"] = specAPIPath base := filepath.Dir(specAPIPath) aliases["@sanderling/spec/defaults/properties"] = filepath.Join(base, "defaults/properties.ts") } + defines := map[string]string{ + "SANDERLING_TEST_PHONE": os.Getenv("SANDERLING_TEST_PHONE"), + "SANDERLING_TEST_OTP": os.Getenv("SANDERLING_TEST_OTP"), + } bundle, err := bundler.Bundle(bundler.Options{ EntryFile: options.Spec, - Defines: map[string]string{ - "SANDERLING_TEST_PHONE": os.Getenv("SANDERLING_TEST_PHONE"), - "SANDERLING_TEST_OTP": os.Getenv("SANDERLING_TEST_OTP"), - }, - Aliases: aliases, + Defines: defines, + Aliases: aliases, }) if err != nil { return fmt.Errorf("bundle spec: %w", err) } fmt.Fprintf(stdout, "bundled spec: %d bytes (sha256=%s)\n", len(bundle.JavaScript), bundle.SHA256[:12]) + var webBundle bundler.Result + if options.Platform == "web" { + runtimePath := resolveWebRuntimePath(specAPIPath, options.Spec) + if runtimePath == "" { + return fmt.Errorf("web-runtime.ts not found near %s; checkout pkg/spec or set @sanderling/spec alias", options.Spec) + } + webBundle, err = bundler.BundleWeb(bundler.WebOptions{ + EntryFile: options.Spec, + WebRuntimeFile: runtimePath, + Defines: defines, + Aliases: aliases, + }) + if err != nil { + return fmt.Errorf("bundle web spec: %w", err) + } + fmt.Fprintf(stdout, "bundled web spec: %d bytes (sha256=%s)\n", len(webBundle.JavaScript), webBundle.SHA256[:12]) + } + activeDriver, cleanup, err := buildDriver(ctx, options, stdout) if err != nil { return err @@ -73,6 +94,12 @@ func Execute(ctx context.Context, options Options, stdout io.Writer) error { return fmt.Errorf("launch app: %w", err) } + if web, ok := activeDriver.(driver.WebDriver); ok && len(webBundle.JavaScript) > 0 { + if err := web.InstallBundle(ctx, webBundle.JavaScript); err != nil { + return fmt.Errorf("install web bundle: %w", err) + } + } + seed := options.Seed if seed == 0 { seed = time.Now().UnixNano() @@ -142,6 +169,34 @@ func Execute(ctx context.Context, options Options, stdout io.Writer) error { return nil } +// resolveWebRuntimePath returns the path to pkg/spec/src/web-runtime.ts. +// Tries the spec-API checkout first (so monorepo development works without +// publishing the package), then falls back to a sibling of the resolved +// @sanderling/spec entry, and finally to a node_modules path. +func resolveWebRuntimePath(specAPIPath, userSpecPath string) string { + if specAPIPath != "" { + candidate := filepath.Join(filepath.Dir(specAPIPath), "web-runtime.ts") + if _, err := os.Stat(candidate); err == nil { + return candidate + } + } + if absoluteSpec, err := filepath.Abs(userSpecPath); err == nil { + directory := filepath.Dir(absoluteSpec) + for { + candidate := filepath.Join(directory, "node_modules", "@sanderling", "spec", "src", "web-runtime.ts") + if _, err := os.Stat(candidate); err == nil { + return candidate + } + parent := filepath.Dir(directory) + if parent == directory { + break + } + directory = parent + } + } + return "" +} + // resolveSpecAPIPath returns the path to pkg/spec/src/index.ts inside // a sanderling source checkout, searched upward from the spec file and the cwd. // Returns "" when not found, in which case esbuild resolves @sanderling/spec via