test(e2e): guard the sheet, track, locale, and TV settings behaviour
Four regression flows, each run on a Pixel 7 and, where relevant, a real Android TV box. Discriminating — the baseline fails, HEAD passes: `07_sheet_back_dismiss` pins both halves of the hosted-sheet fix on touch: the barrier removes the rows behind it from the semantics tree, and one Back closes only the sheet while Settings stays the current route, including the nested per-library options page. It fails onc48cbf70, the commit before8e1904dd. `03_tv_library_focus` gains the same occlusion assertion for the TV sort sheet and fails there too. `08_track_choice_survives_pending_pass` picks a non-default audio and subtitle track, lets playback outlive the automatic pass's 5s attempt and 25s deadline, and asserts the choice is still selected. Ona56b9a3dthe audio reverts to the container's default. Both new flows onboard from a cleared install: the TV regressions in the same group leave "Force TV mode" enabled, and a remembered track selection would pre-select the rows under test. Coverage without a comparable baseline: `09_language_picker_locales` asserts the four new endonyms, switches to Turkish, reads root navigation labels from the generated locale, and restores English. The locales do not exist before 7677d159/100d7729, so there is nothing to fail against — this is forward coverage, not a reproduction. `10_tv_settings_navigation` runs on real Android TV hardware, which no existing flow covers: the rail layout a device reports on its own, the TV dialog path for Manage Libraries, the Apple-only Atmos gate staying closed on Android, and the D-pad-only route to the number spinner's accessibility labels, which a single tap would hide. It passes on either side of the range and is verified against both an empty server and one with a resume position, since rail order shifts with that. It does not assert the15b54e2erow density: that change is invisible to a semantics-tree driver, and a pixel `height` assertion would only hold for one DPR. It registers under a new `android-tv-device` group no workflow dispatches. No TV playback flow is included. Entering content on the TV Recommended view has no stable anchor: the accessibility `focused` flag sits on the hero backdrop rather than the rail card, and a resume position anywhere in the library pushes "Recently Added" below the fold and out of the semantics tree entirely. Search is not a way around it either — inputText does not reach the TV search field. Covering TV playback needs a testID on the rail card, not a cleverer selector. Also repairs three assertions that could never fail: `03_tv_library_focus` gated the sort sheet closing on `notVisible: "Sort by"`, but the header renders "Sort By" and Maestro selectors are case-sensitive regexes, so the wait returned immediately and the next D-pad press landed in the sheet's close animation. `open_codec_sample` matched a card's watch state as `watched|unwatched` only. A codec sample keeps a resume position once any earlier flow has played it, so the row announces "N percent watched" and the subflow stopped finding it on a fixture container that outlives one suite. `06_playback_recovery` tapped "Zulu Zone" out of the Recently Added rail, but every seeded alphabet title shares one dateadded, so which of them the rail returns is a tie-break. The flow only needs some playable movie.
This commit is contained in:
@@ -22,14 +22,23 @@ tags:
|
||||
- pressKey: "Remote Dpad Up"
|
||||
- pressKey: "Remote Dpad Right"
|
||||
- pressKey: "Remote Dpad Right"
|
||||
# The grid row must be matchable before the sort sheet opens, otherwise the
|
||||
# occlusion assertion below would pass for the wrong reason.
|
||||
- extendedWaitUntil:
|
||||
visible: "(?s).*Alpha Archive.*"
|
||||
timeout: 15000
|
||||
- pressKey: "Remote Dpad Center"
|
||||
- extendedWaitUntil:
|
||||
visible: "Title"
|
||||
timeout: 10000
|
||||
# The host renders the grid, the barrier, and the sheet in one Stack and never
|
||||
# unmounts the grid, so before 8e1904dd wrapped the barrier in BlockSemantics
|
||||
# an occluded row still read as visible to accessibility and to Maestro.
|
||||
- assertNotVisible: "(?s).*Alpha Archive.*"
|
||||
- pressKey: "Remote Dpad Center"
|
||||
- pressKey: "Remote Dpad Left"
|
||||
- extendedWaitUntil:
|
||||
notVisible: "Sort by"
|
||||
notVisible: "Sort By"
|
||||
timeout: 10000
|
||||
- pressKey: "Remote Dpad Down"
|
||||
- extendedWaitUntil:
|
||||
|
||||
@@ -8,10 +8,14 @@ tags:
|
||||
---
|
||||
- runFlow: ../subflows/onboard_jellyfin.yaml
|
||||
- tapOn: "(?s)^Libraries.*"
|
||||
# Every seeded alphabet title carries the same dateadded, so which of them the
|
||||
# "Recently Added" rail returns is not stable across scans. This flow only
|
||||
# needs some playable movie, so accept any of them rather than pinning one and
|
||||
# failing whenever the tie-break lands elsewhere.
|
||||
- extendedWaitUntil:
|
||||
visible: "(?s).*Zulu Zone.*"
|
||||
visible: "(?s).*(?:Alpha Archive|Bravo Beacon|Charlie Circuit|Delta Drive|Echo Engine|Foxtrot Frame|Gamma Garden|Hotel Horizon|India Index|Juliet Junction|Kilo Key|Lima Loop|Mike Matrix|November Node|Oscar Orbit|Papa Pipeline|Quebec Queue|Romeo Relay|Sierra Signal|Tango Track|Uniform Update|Victor View|Whiskey Widget|Xray XML|Yankee Yield|Zulu Zone).*, movie, .*"
|
||||
timeout: 15000
|
||||
- tapOn: "(?s).*Zulu Zone.*"
|
||||
- tapOn: "(?s).*(?:Alpha Archive|Bravo Beacon|Charlie Circuit|Delta Drive|Echo Engine|Foxtrot Frame|Gamma Garden|Hotel Horizon|India Index|Juliet Junction|Kilo Key|Lima Loop|Mike Matrix|November Node|Oscar Orbit|Papa Pipeline|Quebec Queue|Romeo Relay|Sierra Signal|Tango Track|Uniform Update|Victor View|Whiskey Widget|Xray XML|Yankee Yield|Zulu Zone).*, movie, .*"
|
||||
- extendedWaitUntil:
|
||||
visible: "Play"
|
||||
timeout: 15000
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
appId: com.edde746.plezy
|
||||
name: System back dismisses a hosted sheet without leaving Settings
|
||||
|
||||
tags:
|
||||
- e2e
|
||||
- regression
|
||||
- settings
|
||||
- sheets
|
||||
---
|
||||
# Guards 8e1904dd and c48cbf70 together.
|
||||
#
|
||||
# On a phone, Settings is a pushed route. Before c48cbf70 it carried no
|
||||
# OverlaySheetHost, so Manage Libraries fell back to showModalBottomSheet and a
|
||||
# single Back tore down the sheet *and* Settings. Before 8e1904dd the host
|
||||
# answered the platform pop with a TV-only dedup marker that could already be
|
||||
# spent, stranding the sheet open with no way out. Either defect fails the
|
||||
# "Manage Libraries is still there after one Back" assertion below.
|
||||
#
|
||||
# The same commit blocks semantics behind the barrier, so the occlusion
|
||||
# assertions pin that an open sheet actually hides the rows underneath.
|
||||
# Pristine state on purpose. The TV regressions in the same CI group leave
|
||||
# "Force TV mode" enabled, and this flow asserts the handheld Settings route,
|
||||
# so it must not inherit whichever layout the previous flow left behind.
|
||||
- runFlow: ../subflows/onboard_jellyfin.yaml
|
||||
- runFlow: ../subflows/open_settings.yaml
|
||||
- assertVisible: "(?s)^Services.*"
|
||||
- assertVisible: "(?s)^Video Playback.*"
|
||||
- tapOn: "(?s)^Manage Libraries.*"
|
||||
- extendedWaitUntil:
|
||||
visible: "Maestro Movies"
|
||||
timeout: 15000
|
||||
# The barrier swallows every pointer event, so the rows behind it must leave
|
||||
# the semantics tree too. Without BlockSemantics an occluded row still reads as
|
||||
# visible and a tap on it lands on the barrier instead. "Manage Libraries" is
|
||||
# excluded on purpose: it is also the sheet's own title.
|
||||
- assertNotVisible: "(?s)^Services.*"
|
||||
- assertNotVisible: "(?s)^Video Playback.*"
|
||||
- assertNotVisible: "(?s)^Appearance.*"
|
||||
- assertNotVisible: "(?s)^Connections.*"
|
||||
- back
|
||||
- waitForAnimationToEnd:
|
||||
timeout: 3000
|
||||
- assertNotVisible: "Maestro Movies"
|
||||
# One Back closed only the sheet: Settings is still the current route.
|
||||
- extendedWaitUntil:
|
||||
visible: "(?s)^Manage Libraries.*"
|
||||
timeout: 10000
|
||||
- assertVisible: "(?s)^Services.*"
|
||||
- assertVisible: "(?s)^Video Playback.*"
|
||||
- assertNotVisible: "Discover"
|
||||
# Reopening proves the host did not strand the one-shot dedup marker: a second
|
||||
# dismissal has to work exactly like the first.
|
||||
- tapOn: "(?s)^Manage Libraries.*"
|
||||
- extendedWaitUntil:
|
||||
visible: "Maestro Movies"
|
||||
timeout: 15000
|
||||
# The per-library menu is a nested page of the same sheet now that the host
|
||||
# exists, so Back returns to the library list instead of tearing the sheet down.
|
||||
- tapOn: "Library options"
|
||||
- extendedWaitUntil:
|
||||
visible: "(?s).*Refresh Metadata.*"
|
||||
timeout: 15000
|
||||
- back
|
||||
- waitForAnimationToEnd:
|
||||
timeout: 3000
|
||||
- extendedWaitUntil:
|
||||
visible: "Maestro Movies"
|
||||
timeout: 10000
|
||||
- assertNotVisible: "(?s).*Refresh Metadata.*"
|
||||
# Back at the library list, one more Back leaves the sheet entirely.
|
||||
- back
|
||||
- waitForAnimationToEnd:
|
||||
timeout: 3000
|
||||
- assertNotVisible: "Maestro Movies"
|
||||
- extendedWaitUntil:
|
||||
visible: "(?s)^Manage Libraries.*"
|
||||
timeout: 10000
|
||||
- tapOn: "(?s)^Manage Libraries.*"
|
||||
- extendedWaitUntil:
|
||||
visible: "Maestro Movies"
|
||||
timeout: 15000
|
||||
- back
|
||||
- waitForAnimationToEnd:
|
||||
timeout: 3000
|
||||
- assertNotVisible: "Maestro Movies"
|
||||
- extendedWaitUntil:
|
||||
visible: "(?s)^Manage Libraries.*"
|
||||
timeout: 10000
|
||||
# With no sheet open the host must let the route pop normally.
|
||||
- back
|
||||
- extendedWaitUntil:
|
||||
visible: "Discover"
|
||||
timeout: 15000
|
||||
- assertNotVisible: "(?s)^Manage Libraries.*"
|
||||
@@ -0,0 +1,112 @@
|
||||
appId: com.edde746.plezy
|
||||
name: An explicit track choice survives the pending automatic pass
|
||||
|
||||
tags:
|
||||
- e2e
|
||||
- regression
|
||||
- playback
|
||||
- subtitles
|
||||
---
|
||||
# Guards 468d6804. When a source advertises tracks the native list has not
|
||||
# produced yet, TrackManager arms an automatic selection pass with a 5s first
|
||||
# attempt and a 25s deadline. A user pick made inside that window used to only
|
||||
# persist the preference, leaving the pass armed, so the choice snapped back to
|
||||
# the server/profile preferred track up to ~30s later. The pick now retires the
|
||||
# pending pass, and the wait below is long enough to outlive both timers.
|
||||
#
|
||||
# Fixture: Codec H264 EAC3 Multisub advertises three E-AC-3 audio streams
|
||||
# (Hindi and Japanese flagged default, English not) and 36 SRT subtitle
|
||||
# streams, so both picks below are genuine changes away from the automatic one.
|
||||
# Pristine state on purpose, for two reasons: the TV regressions in the same
|
||||
# CI group leave "Force TV mode" enabled, and a remembered track selection
|
||||
# from an earlier run would pre-select the very rows this flow picks, making
|
||||
# every assertion below vacuous.
|
||||
- runFlow: ../subflows/onboard_jellyfin.yaml
|
||||
- runFlow:
|
||||
file: ../subflows/open_codec_sample.yaml
|
||||
env:
|
||||
SAMPLE_TITLE: "Codec H264 EAC3 Multisub"
|
||||
SAMPLE_OVERVIEW: "H.264 video with three E-AC-3 audio tracks and a broad multilingual SRT subtitle set."
|
||||
- assertVisible: "(?is)^English\\s+[^,]*(?:Dolby Digital Plus|E-AC3|Surround|5\\.1)[^,]*$"
|
||||
- tapOn:
|
||||
text: "(?is)^English\\s+[^,]*(?:Dolby Digital Plus|E-AC3|Surround|5\\.1)[^,]*$"
|
||||
- extendedWaitUntil:
|
||||
visible: "(?s)^.*Audio & Subtitles$"
|
||||
timeout: 30000
|
||||
- tapOn: "(?s)^.*Audio & Subtitles$"
|
||||
# German is the only German subtitle in the set and is never the automatic
|
||||
# pick, so a revert to the preferred language or to Off is unambiguous.
|
||||
- repeat:
|
||||
times: 12
|
||||
while:
|
||||
notVisible: "(?is)^German\\s+[^,]*(?:SRT|APPLICATION/X-SUBRIP)[^,]*$"
|
||||
commands:
|
||||
- swipe:
|
||||
start: 70%, 82%
|
||||
end: 70%, 68%
|
||||
duration: 300
|
||||
- tapOn:
|
||||
text: "(?is)^German\\s+[^,]*(?:SRT|APPLICATION/X-SUBRIP)[^,]*$"
|
||||
- extendedWaitUntil:
|
||||
visible: "(?s)^.*Audio & Subtitles$"
|
||||
timeout: 30000
|
||||
# The pending pass is wall-clock driven and a paused frame settles instantly,
|
||||
# so playback has to run for the wait below to consume real time.
|
||||
- runFlow:
|
||||
when:
|
||||
visible: "Play"
|
||||
commands:
|
||||
- tapOn: "Play"
|
||||
- extendedWaitUntil:
|
||||
visible: "Pause"
|
||||
timeout: 20000
|
||||
- repeat:
|
||||
times: 14
|
||||
commands:
|
||||
- waitForAnimationToEnd:
|
||||
timeout: 3000
|
||||
# Chrome auto-hides after 30s even in Maestro builds, so bring it back.
|
||||
- tapOn:
|
||||
point: 50%, 50%
|
||||
- extendedWaitUntil:
|
||||
visible: "(?s)^.*Audio & Subtitles$"
|
||||
timeout: 30000
|
||||
# The tracks button announces the live selection, so this catches a revert
|
||||
# without reopening the sheet.
|
||||
- assertVisible: "(?is)^English[^,]*(?:Dolby Digital Plus|E-AC3|Surround|5\\.1)[^,]*,.*Audio & Subtitles$"
|
||||
- tapOn: "(?s)^.*Audio & Subtitles$"
|
||||
- assertVisible:
|
||||
text: "(?is)^English\\s+[^,]*(?:Dolby Digital Plus|E-AC3|Surround|5\\.1)[^,]*$"
|
||||
selected: true
|
||||
- assertNotVisible:
|
||||
text: "(?is)^Japanese\\s+[^,]*(?:Dolby Digital Plus|E-AC3|Surround|5\\.1)[^,]*$"
|
||||
selected: true
|
||||
- repeat:
|
||||
times: 12
|
||||
while:
|
||||
notVisible: "(?is)^German\\s+[^,]*(?:SRT|APPLICATION/X-SUBRIP)[^,]*$"
|
||||
commands:
|
||||
- swipe:
|
||||
start: 70%, 82%
|
||||
end: 70%, 68%
|
||||
duration: 300
|
||||
- assertVisible:
|
||||
text: "(?is)^German\\s+[^,]*(?:SRT|APPLICATION/X-SUBRIP)[^,]*$"
|
||||
selected: true
|
||||
- assertNotVisible:
|
||||
text: "Off"
|
||||
selected: true
|
||||
- back
|
||||
- waitForAnimationToEnd:
|
||||
timeout: 3000
|
||||
- back
|
||||
- waitForAnimationToEnd:
|
||||
timeout: 3000
|
||||
- runFlow:
|
||||
when:
|
||||
notVisible: "Overview"
|
||||
commands:
|
||||
- back
|
||||
- extendedWaitUntil:
|
||||
visible: "Overview"
|
||||
timeout: 15000
|
||||
@@ -0,0 +1,90 @@
|
||||
appId: com.edde746.plezy
|
||||
name: Language picker offers the new locales and switching one relocalizes the app
|
||||
|
||||
tags:
|
||||
- e2e
|
||||
- regression
|
||||
- settings
|
||||
- i18n
|
||||
---
|
||||
# Guards 7677d159 and 100d7729. AppLocale is switched over exhaustively in
|
||||
# appearance_settings_screen.dart, so a missing arm is a compile error, but a
|
||||
# locale whose JSON never reached the generator ships a picker entry that falls
|
||||
# back to English. Selecting Turkish and reading root-level navigation labels
|
||||
# proves the generated locale is wired end to end.
|
||||
#
|
||||
# This flow mutates a persisted pref, and the app language decides every
|
||||
# selector after the tap, so it starts from a cleared install rather than
|
||||
# ensure_onboarded: a run that dies mid-flow must not leave the next flow
|
||||
# hunting for English strings in a Turkish tree.
|
||||
- runFlow: ../subflows/onboard_jellyfin.yaml
|
||||
- runFlow: ../subflows/open_settings.yaml
|
||||
- tapOn: "(?s)^Appearance.*"
|
||||
- extendedWaitUntil:
|
||||
visible: "(?s).*Theme.*"
|
||||
timeout: 15000
|
||||
- tapOn: "(?s)^Language.*"
|
||||
- extendedWaitUntil:
|
||||
visible: "Azərbaycanca"
|
||||
timeout: 10000
|
||||
- assertVisible: "English"
|
||||
- assertVisible: "Қазақша"
|
||||
# The dialog holds 22 endonyms and only the first 16 fit on a phone, so the two
|
||||
# locales that sort last need scrolling into view.
|
||||
- repeat:
|
||||
times: 8
|
||||
while:
|
||||
notVisible: "Türkçe"
|
||||
commands:
|
||||
- swipe:
|
||||
start: 50%, 80%
|
||||
end: 50%, 45%
|
||||
duration: 300
|
||||
- assertVisible: "Türkçe"
|
||||
- assertVisible: "Oʻzbekcha"
|
||||
- tapOn: "Türkçe"
|
||||
# Selecting a language rebuilds the app from the root route, so everything
|
||||
# below runs against the Turkish tree.
|
||||
- extendedWaitUntil:
|
||||
visible: "(?s)^Kitaplıklar.*"
|
||||
timeout: 30000
|
||||
- assertVisible: "(?s)^Ana Sayfa.*"
|
||||
- assertVisible: "(?s)^Ara.*"
|
||||
- assertVisible: "(?s)^İndirmeler.*"
|
||||
- assertVisible: "Keşfet"
|
||||
- assertNotVisible: "(?s)^Libraries.*"
|
||||
- assertNotVisible: "Discover"
|
||||
# Walk back through the Turkish tree and restore English. The pref outlives a
|
||||
# plain launchApp, so leaving the device in Turkish would break every later
|
||||
# flow in the suite.
|
||||
- repeat:
|
||||
times: 3
|
||||
while:
|
||||
notVisible: "Ayarlar"
|
||||
commands:
|
||||
- tapOn: "(?s)^M(?:.*Profiller.*)?$"
|
||||
- waitForAnimationToEnd:
|
||||
timeout: 3000
|
||||
- tapOn:
|
||||
text: "Ayarlar"
|
||||
below:
|
||||
text: "Profiller"
|
||||
- extendedWaitUntil:
|
||||
visible: "(?s)^Görünüm.*"
|
||||
timeout: 15000
|
||||
- assertVisible: "(?s)^Servisler.*"
|
||||
- assertVisible: "(?s)^Video Oynatma.*"
|
||||
- tapOn: "(?s)^Görünüm.*"
|
||||
- extendedWaitUntil:
|
||||
visible: "(?s).*Tema.*"
|
||||
timeout: 15000
|
||||
- tapOn: "(?s)^Dil.*"
|
||||
- extendedWaitUntil:
|
||||
visible: "English"
|
||||
timeout: 10000
|
||||
- tapOn: "English"
|
||||
- extendedWaitUntil:
|
||||
visible: "(?s)^Libraries.*"
|
||||
timeout: 30000
|
||||
- assertVisible: "Discover"
|
||||
- assertNotVisible: "(?s)^Kitaplıklar.*"
|
||||
@@ -0,0 +1,142 @@
|
||||
appId: com.edde746.plezy
|
||||
name: TV settings stay navigable and dismiss their overlays
|
||||
|
||||
tags:
|
||||
- e2e
|
||||
- regression
|
||||
- tv
|
||||
- settings
|
||||
---
|
||||
# Guards ef310459 and the Apple-only gates touched by 6c14049e, and pins TV
|
||||
# settings navigation generally.
|
||||
#
|
||||
# It deliberately does NOT guard the 15b54e2e density change. That commit took
|
||||
# every settings row from ~80dp to ~61dp, but the rows below the fold stay in
|
||||
# the semantics tree either way, so this flow passes identically on both sides
|
||||
# of it — measured, not assumed. A real guard would need a pixel `height`
|
||||
# assertion, and Maestro has no DPR-relative form, so pinning one here would
|
||||
# only hold for the box it was written on. Row geometry belongs to
|
||||
# test/widgets/setting_tile_test.dart.
|
||||
#
|
||||
# Every step uses D-pad keys, never taps. InputModeTracker flips to pointer
|
||||
# mode on the first touch event and the TV number spinner only builds in
|
||||
# keyboard mode, so a single tapOn anywhere above would silently swap the
|
||||
# spinner for a plain text field.
|
||||
- runFlow: ../subflows/onboard_jellyfin_tv_device.yaml
|
||||
- pressKey: "Remote Dpad Left"
|
||||
- extendedWaitUntil:
|
||||
visible: "Settings"
|
||||
timeout: 15000
|
||||
- repeat:
|
||||
times: 12
|
||||
while:
|
||||
notVisible:
|
||||
text: "Settings"
|
||||
focused: true
|
||||
commands:
|
||||
- pressKey: "Remote Dpad Down"
|
||||
- pressKey: "Remote Dpad Center"
|
||||
- extendedWaitUntil:
|
||||
visible: "(?s)^Appearance.*"
|
||||
timeout: 15000
|
||||
- assertVisible: "(?s)^Video Playback.*"
|
||||
- assertVisible: "(?s)^Manage Libraries.*"
|
||||
- assertVisible: "(?s)^Services.*"
|
||||
# The Connections card sits below the first card, so reaching it also proves
|
||||
# the list scrolled or fits — not that any particular row height applies.
|
||||
- assertVisible: "(?s)^Add connection.*"
|
||||
- assertVisible: "(?s)^Profiles.*"
|
||||
# Manage Libraries takes the TV dialog path, not the overlay-sheet path, and
|
||||
# one Back has to return to Settings rather than leave it.
|
||||
- repeat:
|
||||
times: 6
|
||||
while:
|
||||
notVisible:
|
||||
text: "(?s)^Manage Libraries.*"
|
||||
focused: true
|
||||
commands:
|
||||
- pressKey: "Remote Dpad Down"
|
||||
- pressKey: "Remote Dpad Center"
|
||||
- extendedWaitUntil:
|
||||
visible: "Maestro Movies"
|
||||
timeout: 15000
|
||||
- assertVisible: "Maestro Shows"
|
||||
- assertNotVisible: "(?s)^Services.*"
|
||||
- assertNotVisible: "(?s)^Video Playback.*"
|
||||
- pressKey: "back"
|
||||
- waitForAnimationToEnd:
|
||||
timeout: 3000
|
||||
- assertNotVisible: "Maestro Movies"
|
||||
- extendedWaitUntil:
|
||||
visible: "(?s)^Services.*"
|
||||
timeout: 15000
|
||||
- assertVisible: "(?s)^Manage Libraries.*"
|
||||
# Services keeps every hub row after losing its own density overrides.
|
||||
- repeat:
|
||||
times: 6
|
||||
while:
|
||||
notVisible:
|
||||
text: "(?s)^Services.*"
|
||||
focused: true
|
||||
commands:
|
||||
- pressKey: "Remote Dpad Down"
|
||||
- pressKey: "Remote Dpad Center"
|
||||
- extendedWaitUntil:
|
||||
visible: "(?s)^Trakt.*"
|
||||
timeout: 15000
|
||||
- assertVisible: "(?s)^MyAnimeList.*"
|
||||
- assertVisible: "(?s)^AniList.*"
|
||||
- assertVisible: "(?s)^Simkl.*"
|
||||
- assertVisible: "(?s)^Seerr.*"
|
||||
- pressKey: "back"
|
||||
- extendedWaitUntil:
|
||||
visible: "(?s)^Manage Libraries.*"
|
||||
timeout: 15000
|
||||
# Video Playback: the Atmos test screen is gated to Apple TV, and the D-pad
|
||||
# numeric dialog must expose the spinner's restored accessibility labels.
|
||||
- repeat:
|
||||
times: 8
|
||||
while:
|
||||
notVisible:
|
||||
text: "(?s)^Video Playback.*"
|
||||
focused: true
|
||||
commands:
|
||||
- pressKey: "Remote Dpad Up"
|
||||
- pressKey: "Remote Dpad Center"
|
||||
- extendedWaitUntil:
|
||||
visible: "(?s)^Player.*"
|
||||
timeout: 15000
|
||||
- assertNotVisible: "(?s)^Atmos Output Test.*"
|
||||
- repeat:
|
||||
times: 30
|
||||
while:
|
||||
notVisible:
|
||||
text: "(?s)^Small Skip Duration.*"
|
||||
focused: true
|
||||
commands:
|
||||
- pressKey: "Remote Dpad Down"
|
||||
- pressKey: "Remote Dpad Center"
|
||||
- extendedWaitUntil:
|
||||
visible: "Decrease"
|
||||
timeout: 15000
|
||||
- assertVisible: "Increase"
|
||||
- assertVisible: "(?s)^Enter duration.*"
|
||||
- pressKey: "back"
|
||||
- extendedWaitUntil:
|
||||
visible: "(?s)^Small Skip Duration.*"
|
||||
timeout: 15000
|
||||
- pressKey: "back"
|
||||
- extendedWaitUntil:
|
||||
visible: "(?s)^Manage Libraries.*"
|
||||
timeout: 15000
|
||||
# Settings is a rail tab on TV rather than a pushed route, so Back hands focus
|
||||
# back to the rail and expands it instead of leaving the screen.
|
||||
- pressKey: "back"
|
||||
- extendedWaitUntil:
|
||||
visible:
|
||||
text: "Settings"
|
||||
focused: true
|
||||
timeout: 20000
|
||||
- assertVisible: "Home"
|
||||
- assertVisible: "Libraries"
|
||||
- assertVisible: "Downloads"
|
||||
@@ -0,0 +1,57 @@
|
||||
appId: com.edde746.plezy
|
||||
---
|
||||
# Onboarding for a device that already reports itself as a TV.
|
||||
#
|
||||
# `onboard_jellyfin_tv.yaml` exists for the phone emulator CI runs on: it
|
||||
# onboards through the handheld layout and then walks Settings to flip
|
||||
# "Force TV mode". A real Android TV box takes the TV layout from the first
|
||||
# frame, so that walk has no handheld chrome to tap and the toggle would only
|
||||
# re-assert what PlatformDetector already reports.
|
||||
- retry:
|
||||
maxRetries: 1
|
||||
commands:
|
||||
- launchApp:
|
||||
clearState: true
|
||||
permissions:
|
||||
all: allow
|
||||
- extendedWaitUntil:
|
||||
visible: "(?s)^(?:Connect to Jellyfin|Wait).*"
|
||||
timeout: 30000
|
||||
- runFlow:
|
||||
when:
|
||||
visible: "Wait"
|
||||
commands:
|
||||
- tapOn: "Wait"
|
||||
- extendedWaitUntil:
|
||||
visible: "Connect to Jellyfin"
|
||||
timeout: 30000
|
||||
- tapOn: "Connect to Jellyfin"
|
||||
- extendedWaitUntil:
|
||||
visible: "Add Jellyfin server"
|
||||
timeout: 15000
|
||||
- tapOn: "(?s)Server URLs.*"
|
||||
- inputText: ${JELLYFIN_URL}
|
||||
- tapOn: "Find server"
|
||||
# A TV-sized layout offers Quick Connect first because typing on a remote is
|
||||
# slow. The code path needs a second device to approve, so fall back to the
|
||||
# username form the phone flow already uses.
|
||||
- extendedWaitUntil:
|
||||
visible: "(?s)^(?:Username|Cancel)$"
|
||||
timeout: 30000
|
||||
- runFlow:
|
||||
when:
|
||||
notVisible: "Username"
|
||||
commands:
|
||||
- tapOn: "Cancel"
|
||||
- extendedWaitUntil:
|
||||
visible: "Username"
|
||||
timeout: 20000
|
||||
- tapOn: "Username"
|
||||
- inputText: "maestro"
|
||||
- tapOn: "Password"
|
||||
- inputText: "maestro"
|
||||
- tapOn: "Sign in"
|
||||
- extendedWaitUntil:
|
||||
visible:
|
||||
id: "tv_browse_rail_selection"
|
||||
timeout: 45000
|
||||
@@ -17,10 +17,14 @@ appId: com.edde746.plezy
|
||||
platform: Android
|
||||
commands:
|
||||
- hideKeyboard
|
||||
# A card announces its watch state three ways, and the codec samples keep a
|
||||
# resume position on the server once any earlier flow has played them, so the
|
||||
# partial form has to be accepted for this subflow to be re-runnable against a
|
||||
# fixture container that is not thrown away between suites.
|
||||
- extendedWaitUntil:
|
||||
visible: "(?s)^${SAMPLE_TITLE}, movie, (?:watched|unwatched)$"
|
||||
visible: "(?s)^${SAMPLE_TITLE}, movie, (?:watched|unwatched|[0-9]+ percent watched)$"
|
||||
timeout: 15000
|
||||
- tapOn: "(?s)^${SAMPLE_TITLE}, movie, (?:watched|unwatched)$"
|
||||
- tapOn: "(?s)^${SAMPLE_TITLE}, movie, (?:watched|unwatched|[0-9]+ percent watched)$"
|
||||
- extendedWaitUntil:
|
||||
visible: "Overview"
|
||||
timeout: 15000
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
appId: com.edde746.plezy
|
||||
---
|
||||
# Reach Settings from a signed-in Home on a phone layout.
|
||||
#
|
||||
# The profile menu is itself an overlay sheet on the MainScreen host, so a tap
|
||||
# issued while the previous route is still settling opens nothing. The guarded
|
||||
# repeat re-opens the menu instead of failing the flow, matching the pattern
|
||||
# already used by flows/07_profiles_settings.yaml.
|
||||
- extendedWaitUntil:
|
||||
visible: "Discover"
|
||||
timeout: 30000
|
||||
- repeat:
|
||||
times: 3
|
||||
while:
|
||||
notVisible: "Settings"
|
||||
commands:
|
||||
- tapOn: "(?s)^M(?:.*Profiles.*)?$"
|
||||
- waitForAnimationToEnd:
|
||||
timeout: 3000
|
||||
- tapOn:
|
||||
text: "Settings"
|
||||
below:
|
||||
text: "Profiles"
|
||||
- extendedWaitUntil:
|
||||
visible: "(?s)^Appearance.*"
|
||||
timeout: 15000
|
||||
- assertVisible: "(?s)^Manage Libraries.*"
|
||||
@@ -49,6 +49,33 @@ GROUPS: dict[str, tuple[tuple[str, ...], ...]] = {
|
||||
"--diagnostics-dir",
|
||||
"build/maestro-tv/next-episode-diagnostics",
|
||||
),
|
||||
(
|
||||
"basic",
|
||||
"--flow",
|
||||
".maestro/regression_flows/07_sheet_back_dismiss.yaml",
|
||||
"--jellyfin-log",
|
||||
"build/maestro-sheets/back-dismiss.log",
|
||||
"--diagnostics-dir",
|
||||
"build/maestro-sheets/back-dismiss-diagnostics",
|
||||
),
|
||||
(
|
||||
"basic",
|
||||
"--flow",
|
||||
".maestro/regression_flows/08_track_choice_survives_pending_pass.yaml",
|
||||
"--jellyfin-log",
|
||||
"build/maestro-tracks/pending-pass.log",
|
||||
"--diagnostics-dir",
|
||||
"build/maestro-tracks/pending-pass-diagnostics",
|
||||
),
|
||||
(
|
||||
"basic",
|
||||
"--flow",
|
||||
".maestro/regression_flows/09_language_picker_locales.yaml",
|
||||
"--jellyfin-log",
|
||||
"build/maestro-i18n/language-picker.log",
|
||||
"--diagnostics-dir",
|
||||
"build/maestro-i18n/language-picker-diagnostics",
|
||||
),
|
||||
(
|
||||
"basic",
|
||||
"--fault",
|
||||
@@ -106,6 +133,25 @@ GROUPS: dict[str, tuple[tuple[str, ...], ...]] = {
|
||||
"build/maestro-legacy/diagnostics",
|
||||
),
|
||||
),
|
||||
# Real Android TV hardware only, so no workflow dispatches it. The three
|
||||
# `tv` regressions above run on a phone emulator that `onboard_jellyfin_tv`
|
||||
# forces into TV mode; this drives the rail layout a device reports on its
|
||||
# own. The D-pad-only path is also the only way to reach the TV number
|
||||
# spinner, which InputModeTracker hides as soon as a tap arrives. Run as
|
||||
# `python3 scripts/run_maestro_ci.py android-tv-device` with
|
||||
# MAESTRO_DEVICE_ID set to the box.
|
||||
"android-tv-device": (
|
||||
(
|
||||
"basic",
|
||||
"--adb-reverse",
|
||||
"--flow",
|
||||
".maestro/regression_flows/10_tv_settings_navigation.yaml",
|
||||
"--jellyfin-log",
|
||||
"build/maestro-tv-device/settings-navigation.log",
|
||||
"--diagnostics-dir",
|
||||
"build/maestro-tv-device/settings-navigation-diagnostics",
|
||||
),
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user