diff --git a/.maestro/regression_flows/03_tv_library_focus.yaml b/.maestro/regression_flows/03_tv_library_focus.yaml index f18793e1..4951eb75 100644 --- a/.maestro/regression_flows/03_tv_library_focus.yaml +++ b/.maestro/regression_flows/03_tv_library_focus.yaml @@ -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: diff --git a/.maestro/regression_flows/06_playback_recovery.yaml b/.maestro/regression_flows/06_playback_recovery.yaml index f6c7b3c7..8cbc5fcb 100644 --- a/.maestro/regression_flows/06_playback_recovery.yaml +++ b/.maestro/regression_flows/06_playback_recovery.yaml @@ -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 diff --git a/.maestro/regression_flows/07_sheet_back_dismiss.yaml b/.maestro/regression_flows/07_sheet_back_dismiss.yaml new file mode 100644 index 00000000..d9c09782 --- /dev/null +++ b/.maestro/regression_flows/07_sheet_back_dismiss.yaml @@ -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.*" diff --git a/.maestro/regression_flows/08_track_choice_survives_pending_pass.yaml b/.maestro/regression_flows/08_track_choice_survives_pending_pass.yaml new file mode 100644 index 00000000..79ce2958 --- /dev/null +++ b/.maestro/regression_flows/08_track_choice_survives_pending_pass.yaml @@ -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 diff --git a/.maestro/regression_flows/09_language_picker_locales.yaml b/.maestro/regression_flows/09_language_picker_locales.yaml new file mode 100644 index 00000000..ff2fd96b --- /dev/null +++ b/.maestro/regression_flows/09_language_picker_locales.yaml @@ -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.*" diff --git a/.maestro/regression_flows/10_tv_settings_navigation.yaml b/.maestro/regression_flows/10_tv_settings_navigation.yaml new file mode 100644 index 00000000..1b1f532d --- /dev/null +++ b/.maestro/regression_flows/10_tv_settings_navigation.yaml @@ -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" diff --git a/.maestro/subflows/onboard_jellyfin_tv_device.yaml b/.maestro/subflows/onboard_jellyfin_tv_device.yaml new file mode 100644 index 00000000..b61da160 --- /dev/null +++ b/.maestro/subflows/onboard_jellyfin_tv_device.yaml @@ -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 diff --git a/.maestro/subflows/open_codec_sample.yaml b/.maestro/subflows/open_codec_sample.yaml index 9b00255f..7bae2124 100644 --- a/.maestro/subflows/open_codec_sample.yaml +++ b/.maestro/subflows/open_codec_sample.yaml @@ -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 diff --git a/.maestro/subflows/open_settings.yaml b/.maestro/subflows/open_settings.yaml new file mode 100644 index 00000000..00bc0449 --- /dev/null +++ b/.maestro/subflows/open_settings.yaml @@ -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.*" diff --git a/scripts/run_maestro_ci.py b/scripts/run_maestro_ci.py index 1df7a475..546b611d 100755 --- a/scripts/run_maestro_ci.py +++ b/scripts/run_maestro_ci.py @@ -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", + ), + ), }