Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions internal/webviewbridgesmoke/checks.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,28 @@ func checkAtlasBoardRenders(c mcpCaller) (string, error) {
return "atlas-board rendered after nav click", nil
}

var stableSeq int

// waitForNodeStable polls until selector's bounding rect stops moving
// across two consecutive samples -- the board's fitView animation
// after navigation invalidates any position measured mid-flight, the
// same stale-coordinate class the Playwright suite's own
// clickFrameGutter helper exists for. A click dispatched before this
// settles lands where the card WAS and selects nothing.
func waitForNodeStable(c mcpCaller, selector string) error {
// Fresh slot per invocation: a leftover key from an earlier check
// could otherwise satisfy the very first sample.
stableSeq++
slot := fmt.Sprintf("__millStablePos%d", stableSeq)
return pollJSEval(c, fmt.Sprintf(`const el = document.querySelector(%q);
if (!el) return false;
const r = el.getBoundingClientRect();
const key = Math.round(r.x) + ':' + Math.round(r.y);
if (window[%q] === key) { return true; }
window[%q] = key;
return false;`, selector, slot, slot), 10*time.Second)
}

// checkNoteCardCommit drives the goal 0102 click model end to end:
// plain click selects (React Flow needs the full pointer sequence, so
// mouse_click, never a js_eval .click()), a second click on the
Expand All @@ -225,6 +247,9 @@ func checkAtlasBoardRenders(c mcpCaller) (string, error) {
// so the ring check that follows starts from an unselected board.
func checkNoteCardCommit(c mcpCaller) (string, error) {
selector := `[data-testid="atlas-note-card"]`
if err := waitForNodeStable(c, selector); err != nil {
return "", fmt.Errorf("board never settled before the select click: %w", err)
}
if _, err := c.call("mouse_click", withWindow(map[string]any{"selector": selector})); err != nil {
return "", fmt.Errorf("select click: %w", err)
}
Expand Down Expand Up @@ -274,6 +299,9 @@ func readRing(c mcpCaller, selector string) (ringSnapshot, error) {

func checkNoteCardSelectionRing(c mcpCaller) (string, error) {
selector := `[data-testid="atlas-note-card"]`
if err := waitForNodeStable(c, selector); err != nil {
return "", fmt.Errorf("board never settled before the ring check: %w", err)
}
before, err := readRing(c, selector)
if err != nil {
return "", err
Expand Down
6 changes: 6 additions & 0 deletions internal/webviewbridgesmoke/checks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ func TestCheckAtlasBoardRenders(t *testing.T) {
func TestCheckNoteCardCommit(t *testing.T) {
t.Run("select click, commit click, escape ladder", func(t *testing.T) {
f := newFakeCaller()
f.onJSON("js_eval", true) // poll: node position stable
f.onJSON("js_eval", true) // poll: wrapper selected
f.onJSON("js_eval", true) // poll: page header visible
f.onJSON("js_eval", true) // poll: board unselected again
Expand All @@ -261,6 +262,7 @@ func TestCheckNoteCardCommit(t *testing.T) {

t.Run("select click failing propagates", func(t *testing.T) {
f := newFakeCaller()
f.onJSON("js_eval", true) // poll: node position stable
f.onError("mouse_click", errors.New("no such element"))
if _, err := checkNoteCardCommit(f); err == nil {
t.Fatal("expected the select click's error to propagate")
Expand All @@ -281,6 +283,7 @@ func TestCheckNoteCardCommit(t *testing.T) {
func TestCheckNoteCardSelectionRing(t *testing.T) {
t.Run("box-shadow none -> non-none on shift-click select", func(t *testing.T) {
f := newFakeCaller()
f.onJSON("js_eval", true) // poll: node position stable
f.onJSON("js_eval", ringSnapshot{BoxShadow: "none", Selected: false})
f.on("mouse_click", func(map[string]any) (string, error) { return "ok", nil })
f.onJSON("js_eval", ringSnapshot{BoxShadow: "0 0 0 2px accent", Selected: true})
Expand All @@ -295,6 +298,7 @@ func TestCheckNoteCardSelectionRing(t *testing.T) {

t.Run("already selected before the check ran is an error", func(t *testing.T) {
f := newFakeCaller()
f.onJSON("js_eval", true) // poll: node position stable
f.onJSON("js_eval", ringSnapshot{BoxShadow: "none", Selected: true})
if _, err := checkNoteCardSelectionRing(f); err == nil {
t.Fatal("expected an error for a pre-selected card")
Expand All @@ -303,6 +307,7 @@ func TestCheckNoteCardSelectionRing(t *testing.T) {

t.Run("shift-click that fails to select is an error", func(t *testing.T) {
f := newFakeCaller()
f.onJSON("js_eval", true) // poll: node position stable
f.onJSON("js_eval", ringSnapshot{BoxShadow: "none", Selected: false})
f.on("mouse_click", func(map[string]any) (string, error) { return "ok", nil })
f.onJSON("js_eval", ringSnapshot{BoxShadow: "none", Selected: false})
Expand All @@ -313,6 +318,7 @@ func TestCheckNoteCardSelectionRing(t *testing.T) {

t.Run("ring never renders after selection is an error", func(t *testing.T) {
f := newFakeCaller()
f.onJSON("js_eval", true) // poll: node position stable
f.onJSON("js_eval", ringSnapshot{BoxShadow: "none", Selected: false})
f.on("mouse_click", func(map[string]any) (string, error) { return "ok", nil })
f.onJSON("js_eval", ringSnapshot{BoxShadow: "none", Selected: true})
Expand Down
Loading