Cthulhu is no longer a fork. Now it is a screen reader written in Golang.
This commit is contained in:
1 parent
a998fe67de
commit
069fc674dc
708 files changed
+112482
-532
No files matched your search
@@ -0,0 +1,99 @@
|
||||
// Copyright (c) 2026 Storm Dragon
|
||||
// Authors: Storm Dragon, Angus Kola
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package atspi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNativeBrowserCatalogCapture(t *testing.T) {
|
||||
if os.Getenv("CTHULHU_NATIVE_BROWSER") != "1" {
|
||||
t.Skip("requires private browser session")
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 25*time.Second)
|
||||
defer cancel()
|
||||
adapter := New()
|
||||
adapter.SetCaptureSoundGeometry(true)
|
||||
if err := adapter.Start(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer adapter.Stop(context.Background())
|
||||
// Move away from the autofocus control and back to obtain a fresh event.
|
||||
if out, err := exec.CommandContext(ctx, "xdotool", "key", "--clearmodifiers", "Tab", "shift+Tab").CombinedOutput(); err != nil {
|
||||
t.Fatalf("focus catalog: %v %s", err, out)
|
||||
}
|
||||
var focus ObjectID
|
||||
for !focus.Valid() {
|
||||
select {
|
||||
case event := <-adapter.Events():
|
||||
if event.Type == FocusedStateEvent && event.Detail1 == 1 && event.Source.Name == "Choose language" {
|
||||
focus = event.Source.ID()
|
||||
}
|
||||
case <-ctx.Done():
|
||||
t.Fatal(ctx.Err())
|
||||
}
|
||||
}
|
||||
started := time.Now()
|
||||
captureContext, captureCancel := context.WithTimeout(ctx, 2*time.Second)
|
||||
tree, err := adapter.CaptureDocument(captureContext, focus)
|
||||
captureCancel()
|
||||
elapsed := time.Since(started)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
root, _ := tree.Root()
|
||||
document, err := tree.Document(ctx, root)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("cold catalog capture: %v, %d nodes", elapsed, len(document.Nodes))
|
||||
if len(document.Nodes) < 4800 {
|
||||
t.Fatalf("catalog capture lost nodes: %d", len(document.Nodes))
|
||||
}
|
||||
lastHeading := findNamedReference(t, tree, "Movie 599")
|
||||
line, err := adapter.ReadDocumentCaret(ctx, lastHeading, 0, DocumentCaretBrailleLine)
|
||||
if err != nil || strings.TrimSpace(line.Text) != "Movie 599" {
|
||||
t.Fatalf("last catalog heading text = %+v, %v", line, err)
|
||||
}
|
||||
focused, err := tree.Resolve(findNamedReference(t, tree, "Choose language"))
|
||||
if err != nil || !focused.SoundGeometry.ObjectKnown || !focused.SoundGeometry.ReferenceKnown {
|
||||
t.Fatalf("catalog control sound geometry = %+v, %v", focused.SoundGeometry, err)
|
||||
}
|
||||
if elapsed >= 2*time.Second {
|
||||
t.Fatalf("cold catalog capture exceeded runtime budget: %v", elapsed)
|
||||
}
|
||||
// Dirty semantics/children must refresh even though cold nodes reuse the
|
||||
// native event cache. Removed objects must not survive the refresh.
|
||||
if out, err := exec.CommandContext(ctx, "xdotool", "key", "F9").CombinedOutput(); err != nil {
|
||||
t.Fatalf("update catalog: %v %s", err, out)
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case event := <-adapter.Events():
|
||||
if event.Type != AccessibleNameEvent || event.Source.Name != "Catalog updated" {
|
||||
continue
|
||||
}
|
||||
captureContext, captureCancel = context.WithTimeout(ctx, 2*time.Second)
|
||||
tree, err = adapter.CaptureDocument(captureContext, focus)
|
||||
captureCancel()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
findNamedReference(t, tree, "Updated movie")
|
||||
findNamedReference(t, tree, "New movie")
|
||||
if _, err := tree.Resolve(lastHeading); err == nil {
|
||||
t.Fatal("removed catalog heading remained live")
|
||||
}
|
||||
return
|
||||
case <-ctx.Done():
|
||||
t.Fatal(ctx.Err())
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user