mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-04 03:57:09 +00:00
merge origin/master into llm-recording-and-analysis
both sides independently fixed the same three bugs, so each one had to pick a winner rather than keep both implementations. extractor encoding: master's recordableValue in worker.go wins over ours in marshal.go, since master's is pinned by extractor_encoding_test.go and ours had no tests. our error semantics stay: encodeExtractorValue still returns an error instead of nil, so an extractor cannot vanish from the trace silently. apply errors: only the residual generic branch takes master's unconfirmed copy, where the device may have committed the action before the call failed. the finer branches that know nothing was dispatched keep lastAction = nil, and our actionSkipReason taxonomy stays alongside master's held/skippedVerification. selector matching: our matchAttr with matchSelectorKind wins over master's match, since ours also handles idPrefix. matchSelector now calls it, which git did not flag as a conflict and left calling a function our side had deleted. the ltl doc comment takes master's correction: an unbounded eventually that never fires IS violated at run end.
This commit is contained in:
commit
6e85cac8b3
130 files changed
+12772
-1617
No files matched your search
@@ -26,6 +26,7 @@ import (
|
||||
"github.com/priyanshujain/sanderling/internal/bundler"
|
||||
"github.com/priyanshujain/sanderling/internal/driver"
|
||||
"github.com/priyanshujain/sanderling/internal/driver/chrome"
|
||||
"github.com/priyanshujain/sanderling/internal/hierarchy"
|
||||
chromerunner "github.com/priyanshujain/sanderling/internal/runner"
|
||||
"github.com/priyanshujain/sanderling/internal/trace"
|
||||
"github.com/priyanshujain/sanderling/internal/verifier"
|
||||
@@ -275,3 +276,86 @@ func TestBrowserUncaughtExceptionReachesTheTrace(t *testing.T) {
|
||||
t.Errorf("exception recorded without class or message: %+v", recorded[0])
|
||||
}
|
||||
}
|
||||
|
||||
// One page, one selector, two hosts, two answers.
|
||||
//
|
||||
// The V8 host resolves state.ax.find against the live DOM; the goja host
|
||||
// resolves the same selector against the hierarchy dump. The fixture puts a
|
||||
// shadow-hosted #x above a light-DOM #x, the one shape where the two walks can
|
||||
// disagree, and on web it is V8's answer that reaches the properties. Each host
|
||||
// is driven through its production path (EvaluateExtractors in the page,
|
||||
// PushSnapshot over the dump) and neither is asked what the other said, so the
|
||||
// comparison is evidence rather than an assertion about one of them.
|
||||
func TestBrowserAxFindAgreesAcrossHosts(t *testing.T) {
|
||||
server := httptest.NewServer(http.FileServer(http.Dir(testdataDir(t))))
|
||||
t.Cleanup(server.Close)
|
||||
|
||||
gojaBundle, webBundle := bundleSpec(t, filepath.Join(testdataDir(t), "find-order", "spec.ts"))
|
||||
|
||||
driverInstance := chrome.New()
|
||||
t.Cleanup(func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
_ = driverInstance.Terminate(ctx)
|
||||
})
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := driverInstance.Launch(ctx, server.URL+"/find-order/", false, nil); err != nil {
|
||||
t.Fatalf("launch: %v", err)
|
||||
}
|
||||
if err := driverInstance.InstallBundle(ctx, webBundle); err != nil {
|
||||
t.Fatalf("install web bundle: %v", err)
|
||||
}
|
||||
readings, err := driverInstance.EvaluateExtractors(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("evaluate extractors in the page: %v", err)
|
||||
}
|
||||
fromV8 := string(readings[0])
|
||||
|
||||
dump, err := driverInstance.Hierarchy(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("hierarchy: %v", err)
|
||||
}
|
||||
tree, err := hierarchy.Parse(dump)
|
||||
if err != nil {
|
||||
t.Fatalf("parse hierarchy: %v", err)
|
||||
}
|
||||
verifierInstance, err := verifier.New(
|
||||
verifier.WithSeed(fixtureSeed),
|
||||
verifier.WithPlatform("web"),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("verifier: %v", err)
|
||||
}
|
||||
if err := verifierInstance.Load(string(gojaBundle)); err != nil {
|
||||
t.Fatalf("load spec: %v", err)
|
||||
}
|
||||
if err := verifierInstance.PushSnapshot(verifier.SnapshotInput{Tree: tree}); err != nil {
|
||||
t.Fatalf("push snapshot: %v", err)
|
||||
}
|
||||
if count := verifierInstance.ExtractorCount(); count != 1 {
|
||||
t.Fatalf("the goja host registered %d extractors, want the fixture's 1", count)
|
||||
}
|
||||
// ChangedExtractors omits an extractor whose reading is null and unchanged,
|
||||
// which is exactly what a selector resolving nothing produces. With the
|
||||
// count checked above, an absent entry is a null reading and not a missing
|
||||
// extractor, so reporting it as null names the real failure.
|
||||
fromGoja := "null"
|
||||
if change, ok := verifierInstance.ChangedExtractors()["found"]; ok {
|
||||
fromGoja = string(change.Curr)
|
||||
}
|
||||
|
||||
// Pinned, not just compared: two hosts that both resolved nothing would
|
||||
// agree on undefined and prove nothing about the walk.
|
||||
if fromGoja != `"shadow"` {
|
||||
t.Errorf("the goja host read %s off the dump, want the shadow-hosted %q", fromGoja, "shadow")
|
||||
}
|
||||
if fromV8 != fromGoja {
|
||||
t.Fatalf(
|
||||
"one page, one selector, two answers: the V8 host read %s and the goja host read %s",
|
||||
fromV8,
|
||||
fromGoja,
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user