feat: confirm playback from the media session, and stop demoting the top row
Phase 3 DoD reached end to end: `artemis run spotify_play query="Rolling Stones Honky Tonk"` played the right song in 13 steps / 35.3s, with playback paused first so the pass proves the row tap started it. Two things stopped it before that, both found on the real phone: - The playback check asserted on an on-screen "Pause" label that Spotify never shows: its transport button keeps the label "Play" while music is playing. The recipe now ends with a `playing` step that reads the app's media session (state PLAYING) and confirms the track title contains the query words. A failure reports the state and the track it found. - tap_row pressed the wrong row. Once a track is loaded its row carries a `Paused` state indicator, and counting that content description as row text dropped the top hit below an identical duplicate further down, so the tap went to y=1443 instead of y=878. Rows are now scored on their title and subtitle only (state lives in content descriptions), and scores within rowScoreTolerance are treated as equal so the higher row wins, because Spotify already ranks by relevance. internal/ui/rows_test.go reproduces that results screen, including the state indicator and the duplicate row. docs/phase-3-spotify-recipe.md records the successful run, the independent media-session and audio-output reads, and the failure that preceded it.
This commit is contained in:
@@ -18,9 +18,12 @@ const spotifyRowDump = `<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
|
||||
</node>
|
||||
<node index="1" class="android.view.ViewGroup" resource-id="com.spotify.music:id/row_root" clickable="true" enabled="true" bounds="[0,794][1080,962]">
|
||||
<node index="0" class="android.widget.ImageView" resource-id="com.spotify.music:id/artwork" clickable="false" enabled="true" bounds="[42,815][168,941]"/>
|
||||
<node index="1" class="android.widget.TextView" resource-id="com.spotify.music:id/title" text="Honky Tonk Women" clickable="false" enabled="true" bounds="[200,815][828,881]"/>
|
||||
<node index="2" class="android.widget.TextView" resource-id="com.spotify.music:id/subtitle" text="Song • The Rolling Stones" clickable="false" enabled="true" bounds="[200,886][828,941]"/>
|
||||
<node index="3" class="android.view.ViewGroup" resource-id="com.spotify.music:id/action_bar" clickable="false" enabled="true" bounds="[828,815][1080,941]">
|
||||
<node index="1" class="android.view.ViewGroup" resource-id="com.spotify.music:id/play_indicator" clickable="false" enabled="true" bounds="[200,832][232,864]">
|
||||
<node index="0" class="android.view.View" content-desc="Paused" clickable="false" enabled="true" bounds="[200,832][232,864]"/>
|
||||
</node>
|
||||
<node index="2" class="android.widget.TextView" resource-id="com.spotify.music:id/title" text="Honky Tonk Women" clickable="false" enabled="true" bounds="[243,815][828,881]"/>
|
||||
<node index="3" class="android.widget.TextView" resource-id="com.spotify.music:id/subtitle" text="Song • The Rolling Stones" clickable="false" enabled="true" bounds="[200,886][828,941]"/>
|
||||
<node index="4" class="android.view.ViewGroup" resource-id="com.spotify.music:id/action_bar" clickable="false" enabled="true" bounds="[828,815][1080,941]">
|
||||
<node index="0" class="android.widget.ImageButton" resource-id="com.spotify.music:id/context_menu_button" content-desc="More options for song Honky Tonk Women" clickable="true" enabled="true" bounds="[828,815][954,941]"/>
|
||||
<node index="1" class="android.widget.Button" resource-id="com.spotify.music:id/add_button" content-desc="Add Honky Tonk Women to My Library" clickable="true" enabled="true" bounds="[954,815][1080,941]"/>
|
||||
</node>
|
||||
@@ -124,6 +127,46 @@ func TestRowScorePrefersPrecision(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestBestRowPrefersTheTopHitWhenItsRowCarriesStateText is the regression that
|
||||
// cost the most: the top hit's row carries a "Paused" state indicator once it is
|
||||
// the loaded track, and counting that description as row text pushed the score
|
||||
// below an identical duplicate further down the list. tap_row then pressed the
|
||||
// duplicate at y=1443 and playback never started.
|
||||
func TestBestRowPrefersTheTopHitWhenItsRowCarriesStateText(t *testing.T) {
|
||||
h := spotifyRows(t)
|
||||
row, err := h.BestRow("Rolling Stones Honky Tonk", spotifyRowID, 1080, 2400)
|
||||
if err != nil {
|
||||
t.Fatalf("BestRow: %v", err)
|
||||
}
|
||||
if row.Title != "Honky Tonk Women" {
|
||||
t.Fatalf("chose %q", row.Title)
|
||||
}
|
||||
if row.Top != 794 {
|
||||
t.Fatalf("chose the row at y=%d, want the top hit at y=794", row.Top)
|
||||
}
|
||||
if row.Centre.Y != (794+962)/2 {
|
||||
t.Fatalf("tap point y=%d is not the top row's centre", row.Centre.Y)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRowTextsIgnoreStateDescriptions(t *testing.T) {
|
||||
h := spotifyRows(t)
|
||||
var top *Node
|
||||
h.Walk(func(n *Node) {
|
||||
if n.ResourceID == spotifyRowID && n.Bounds == "[0,794][1080,962]" {
|
||||
top = n
|
||||
}
|
||||
})
|
||||
if top == nil {
|
||||
t.Fatal("top row not found in the sample dump")
|
||||
}
|
||||
for _, line := range rowTexts(top) {
|
||||
if line == "Paused" {
|
||||
t.Fatal("the row's state description must not count as row text")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRowTextsSkipActionButtons(t *testing.T) {
|
||||
h := spotifyRows(t)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user