Files
cthulhu/internal/review/flat_browser_native_test.go

148 lines
4.3 KiB
Go

// Copyright (c) 2026 Storm Dragon
// Authors: Storm Dragon, Angus Kola
// SPDX-License-Identifier: GPL-3.0-or-later
package review
import (
"context"
"os"
"os/exec"
"strings"
"testing"
"time"
"git.stormux.org/storm/cthulhu/internal/atspi"
)
func TestNativeBrowserChromeFlatReview(t *testing.T) {
if os.Getenv("CTHULHU_NATIVE_BROWSER") != "1" {
t.Skip("requires private browser session")
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
adapter := atspi.New()
if err := adapter.Start(ctx); err != nil {
t.Fatal(err)
}
defer adapter.Stop(context.Background())
keys := func(chord string) {
t.Helper()
if out, err := exec.CommandContext(ctx, "xdotool", "key", "--clearmodifiers", chord).CombinedOutput(); err != nil {
t.Fatalf("keys: %v %s", err, out)
}
}
wait := func(match func(atspi.Event) bool) atspi.Event {
t.Helper()
for {
select {
case e := <-adapter.Events():
if match(e) {
return e
}
case <-ctx.Done():
t.Fatal("missing browser event: ", ctx.Err())
return atspi.Event{}
}
}
}
capture := func(focus atspi.ObjectID) ([]flatLine, atspi.ReviewSnapshot) {
t.Helper()
tree, err := adapter.CaptureReviewWindow(ctx, focus)
if err != nil {
t.Fatal(err)
}
snapshot, err := tree.ReviewWindow(ctx, focus)
if err != nil {
t.Fatal(err)
}
lines, status, err := build_flat_layout(ctx, snapshot.Nodes)
if err != nil {
t.Fatal(err)
}
t.Logf("review lines=%d completeness=%+v", len(lines), status)
return lines, snapshot
}
contains := func(lines []flatLine, want string) bool {
for _, line := range lines {
for _, zone := range line.zones {
if strings.Contains(zone.text, want) {
return true
}
}
}
return false
}
keys("ctrl+l")
address := wait(func(e atspi.Event) bool {
return (e.Type == atspi.FocusEvent || e.Type == atspi.FocusedStateEvent && e.Detail1 != 0) && e.Source.Role == "entry"
})
lines, snapshot := capture(address.Source.ID())
for _, name := range []string{"Back", "Forward", "Reload", "New Tab"} {
if !contains(lines, name) {
t.Errorf("browser control missing from review: %q", name)
}
}
tabLabel := false
for _, line := range lines {
for _, zone := range line.zones {
tabLabel = tabLabel || zone.role == "page tab" && strings.Contains(zone.text, "Cthulhu Browser Probe")
}
}
if !tabLabel {
t.Error("tab's label missing beside its Close button")
}
for _, n := range snapshot.Nodes {
if n.Text.Issue != "" {
t.Logf("unresolved text object: role=%s issue=%s children=%d", n.Snapshot.Role, n.Text.Issue, len(n.Children))
}
}
// Chrome currently returns zero character/range geometry for the address
// value. Do not substitute its accessible label for the missing URL.
for _, n := range snapshot.Nodes {
if n.Snapshot.ID() != address.Source.ID() {
continue
}
if !n.Text.Known && n.Text.Issue == "" {
t.Fatal("unavailable address text lost its diagnostic")
}
if n.Text.Issue != "" {
for _, line := range lines {
for _, zone := range line.zones {
if zone.object == address.Source.ID() {
t.Fatalf("address label substituted for unavailable URL: %+v", zone)
}
}
}
t.Logf("address value remains unavailable: %s", n.Text.Issue)
} else {
t.Logf("address value captured in %d spans", len(n.Text.Ranges))
}
}
keys("F6")
wait(func(e atspi.Event) bool {
return (e.Type == atspi.FocusEvent || e.Type == atspi.FocusedStateEvent && e.Detail1 != 0) && e.Source.Role != "entry"
})
unfocusedLines, unfocusedSnapshot := capture(address.Source.ID())
for _, n := range unfocusedSnapshot.Nodes {
if n.Snapshot.ID() == address.Source.ID() && !n.Text.Known && n.Text.Issue == "" {
t.Fatal("unfocused address text lost its diagnostic")
}
}
if !contains(unfocusedLines, "Reload") {
t.Fatal("toolbar lost after leaving address bar")
}
keys("Escape")
keys("F10")
wait(func(e atspi.Event) bool {
return (e.Type == atspi.FocusEvent || e.Type == atspi.FocusedStateEvent && e.Detail1 != 0) && e.Source.Role == "button"
})
keys("Down")
menu := wait(func(e atspi.Event) bool { return e.Type == atspi.WindowActivatedEvent && e.Source.Role == "menu" })
lines, _ = capture(menu.Source.ID())
if !contains(lines, "New tab") && !contains(lines, "New Tab") {
t.Fatal("popup menu entries missing from flat review")
}
keys("Escape")
}