mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
feat: Maestro driver integration + DeviceDriver architecture (#32)
* proto(driver): drop launcher_activity from LaunchRequest * refactor(driver): rename Driver to DeviceDriver, drop launcherActivity from Launch * refactor(driver): rename package maestro to sidecar * feat(driver): add ChromeDriver backed by chromedp * feat(hierarchy): replace XML parser with TreeNode JSON parser * refactor(driver): update mock and runner to DeviceDriver, drop launcherActivity * feat(runner): add platform routing for web vs sidecar * test(verifier): update hierarchy fixtures from XML to TreeNode JSON * feat(sidecar): extract readLogcat/readProcMetrics; add MaestroDriverBackend - Extract readLogcat() and readProcMetrics() as internal package-level helpers parameterised by serial - Add MaestroDriverBackend wiring maestro-client AndroidDriver - Drop launcherActivity from DriverBackend interface and StubDriverBackend - Add maestro-utils and micrometer-core as explicit compile deps * refactor(sidecar): inject DriverBackend into DriverService; drop launcherActivity - Remove serial and default backend from DriverService constructor - Make backend a required parameter - Drop launcherActivity from launch RPC handler - Create MaestroDriverBackend in Main.kt when platform is android * test(sidecar): update tests for dropped launcherActivity and required backend * chore(sidecar): remove unnecessary micrometer-core direct dependency
This commit is contained in:
26 files changed
+880
-450
No files matched your search
@@ -2,14 +2,30 @@ package hierarchy
|
||||
|
||||
import "testing"
|
||||
|
||||
const sampleDump = `<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
|
||||
<hierarchy rotation="0">
|
||||
<node class="android.widget.LinearLayout" package="app" bounds="[0,0][1080,2340]">
|
||||
<node resource-id="app:id/title" text="Hello" bounds="[10,20][200,60]" clickable="false" enabled="true"/>
|
||||
<node resource-id="app:id/row" text="Alice" content-desc="row" bounds="[0,100][1080,200]" clickable="true" enabled="true"/>
|
||||
<node resource-id="app:id/row" text="Bob" content-desc="row" bounds="[0,200][1080,300]" clickable="true" enabled="true"/>
|
||||
</node>
|
||||
</hierarchy>`
|
||||
// sampleDump is a Maestro TreeNode JSON equivalent of the old XML fixture.
|
||||
const sampleDump = `{
|
||||
"attributes": {"class": "android.widget.LinearLayout", "package": "app", "bounds": "[0,0,1080,2340]"},
|
||||
"children": [
|
||||
{
|
||||
"attributes": {"resource-id": "app:id/title", "text": "Hello", "bounds": "[10,20,200,60]"},
|
||||
"children": [],
|
||||
"clickable": false,
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"attributes": {"resource-id": "app:id/row", "text": "Alice", "content-desc": "row", "bounds": "[0,100,1080,200]"},
|
||||
"children": [],
|
||||
"clickable": true,
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"attributes": {"resource-id": "app:id/row", "text": "Bob", "content-desc": "row", "bounds": "[0,200,1080,300]"},
|
||||
"children": [],
|
||||
"clickable": true,
|
||||
"enabled": true
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
func TestParseCountsNodes(t *testing.T) {
|
||||
tree, err := Parse(sampleDump)
|
||||
@@ -75,14 +91,76 @@ func TestUnknownSelector(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDescPrefix(t *testing.T) {
|
||||
xml := `<hierarchy>
|
||||
<node content-desc="customer_row_abc-123" bounds="[0,0][100,100]"/>
|
||||
<node content-desc="customer_row_def-456" bounds="[0,100][100,200]"/>
|
||||
<node content-desc="supplier_row_xyz" bounds="[0,200][100,300]"/>
|
||||
</hierarchy>`
|
||||
tree, _ := Parse(xml)
|
||||
input := `{
|
||||
"attributes": {},
|
||||
"children": [
|
||||
{"attributes": {"content-desc": "customer_row_abc-123", "bounds": "[0,0,100,100]"}, "children": []},
|
||||
{"attributes": {"content-desc": "customer_row_def-456", "bounds": "[0,100,100,200]"}, "children": []},
|
||||
{"attributes": {"content-desc": "supplier_row_xyz", "bounds": "[0,200,100,300]"}, "children": []}
|
||||
]
|
||||
}`
|
||||
tree, _ := Parse(input)
|
||||
rows := tree.FindAll("descPrefix:customer_row_")
|
||||
if len(rows) != 2 {
|
||||
t.Fatalf("want 2 customer rows, got %d", len(rows))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoolFieldsFromNode(t *testing.T) {
|
||||
input := `{
|
||||
"attributes": {"resource-id": "x", "bounds": "[0,0,100,100]"},
|
||||
"children": [],
|
||||
"clickable": true,
|
||||
"enabled": false,
|
||||
"focused": true,
|
||||
"checked": true,
|
||||
"selected": false
|
||||
}`
|
||||
tree, err := Parse(input)
|
||||
if err != nil {
|
||||
t.Fatalf("Parse: %v", err)
|
||||
}
|
||||
if len(tree.Elements) != 1 {
|
||||
t.Fatalf("want 1 element, got %d", len(tree.Elements))
|
||||
}
|
||||
el := tree.Elements[0]
|
||||
if !el.Clickable {
|
||||
t.Error("expected clickable=true")
|
||||
}
|
||||
if el.Enabled {
|
||||
t.Error("expected enabled=false")
|
||||
}
|
||||
if !el.Focused {
|
||||
t.Error("expected focused=true")
|
||||
}
|
||||
if !el.Checked {
|
||||
t.Error("expected checked=true")
|
||||
}
|
||||
if el.Selected {
|
||||
t.Error("expected selected=false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIdentifierFallback(t *testing.T) {
|
||||
input := `{
|
||||
"attributes": {"identifier": "my-button", "bounds": "[0,0,100,100]"},
|
||||
"children": []
|
||||
}`
|
||||
tree, _ := Parse(input)
|
||||
el := tree.Find("id:my-button")
|
||||
if el == nil {
|
||||
t.Fatal("expected match via identifier fallback")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccessibilityTextFallback(t *testing.T) {
|
||||
input := `{
|
||||
"attributes": {"accessibilityText": "Close dialog", "bounds": "[0,0,50,50]"},
|
||||
"children": []
|
||||
}`
|
||||
tree, _ := Parse(input)
|
||||
el := tree.Find("desc:Close dialog")
|
||||
if el == nil {
|
||||
t.Fatal("expected match via accessibilityText fallback")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user