Corregge cloud-init montato, PublisherOnline multi-nodo e E2E locale-aware.
Senza volume stream-node i nodi Hetzner nascevano senza MediaMTX; sync live usa ora Client.for_session e rtmpconns (MediaMTX 1.20). Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+70
-24
@@ -17,11 +17,16 @@ import org.junit.runner.RunWith
|
||||
|
||||
/**
|
||||
* E2E sul simulatore: login → nuova partita → wizard 3 step → schermata diretta.
|
||||
* Usa le stringhe dell'app (locale del device), non testo hardcodato IT/EN.
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class E2EWizardFlowTest {
|
||||
private lateinit var device: UiDevice
|
||||
private val pkg = "com.matchlivetv.match_live_tv"
|
||||
private val ctx by lazy { InstrumentationRegistry.getInstrumentation().targetContext }
|
||||
|
||||
private fun s(id: Int): String = ctx.getString(id)
|
||||
private fun su(id: Int): String = s(id).uppercase()
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
@@ -32,30 +37,42 @@ class E2EWizardFlowTest {
|
||||
|
||||
@Test
|
||||
fun login_newMatch_wizard_reachesBroadcastScreen() {
|
||||
waitForAnyText("Email", "ACCEDI", timeoutMs = 45_000)
|
||||
waitForAnyText(s(R.string.login_email), su(R.string.login_submit), timeoutMs = 45_000)
|
||||
fillLogin()
|
||||
waitForText("NUOVA PARTITA", timeoutMs = 45_000)
|
||||
tapClickableText("NUOVA PARTITA")
|
||||
waitForText("Avvia subito", timeoutMs = 15_000)
|
||||
tapClickableText("Avvia subito")
|
||||
waitForText("01 · Partita", timeoutMs = 45_000)
|
||||
waitForText(su(R.string.matches_new), timeoutMs = 45_000)
|
||||
tapClickableText(su(R.string.matches_new))
|
||||
waitForText(s(R.string.sheet_quick_option), timeoutMs = 15_000)
|
||||
tapClickableText(s(R.string.sheet_quick_option))
|
||||
waitForText(s(R.string.wizard_step_title_match), timeoutMs = 45_000)
|
||||
scrollDown()
|
||||
tapClickableText("AVANTI >")
|
||||
waitForText("02 · Trasmissione", timeoutMs = 45_000)
|
||||
waitForText("Piattaforma", timeoutMs = 30_000)
|
||||
tapClickableText(s(R.string.wizard_action_next))
|
||||
waitForText(s(R.string.wizard_step_title_transmission), timeoutMs = 45_000)
|
||||
waitForText(s(R.string.wizard_transmission_platform_title), timeoutMs = 30_000)
|
||||
scrollDown()
|
||||
tapClickableText("AVANTI >")
|
||||
waitForText("03 · Test rete", timeoutMs = 45_000)
|
||||
waitForText("AVVIA TEST RETE", timeoutMs = 30_000)
|
||||
tapClickableText("AVVIA TEST RETE")
|
||||
waitForText("INIZIA >", timeoutMs = 30_000)
|
||||
waitUntilEnabled("INIZIA >", timeoutMs = 25_000)
|
||||
tapClickableText(s(R.string.wizard_action_next))
|
||||
waitForText(s(R.string.wizard_step_title_network), timeoutMs = 45_000)
|
||||
waitForText(s(R.string.wizard_network_test_start_label), timeoutMs = 30_000)
|
||||
tapClickableText(s(R.string.wizard_network_test_start_label))
|
||||
waitForText(s(R.string.wizard_action_start), timeoutMs = 30_000)
|
||||
waitUntilEnabled(s(R.string.wizard_action_start), timeoutMs = 25_000)
|
||||
scrollDown()
|
||||
tapClickableText("INIZIA >")
|
||||
val diretta = waitForText("Diretta", timeoutMs = 60_000)
|
||||
assertNotNull(diretta)
|
||||
assertNotNull(waitForText("TERMINA DIRETTA", timeoutMs = 30_000))
|
||||
assertNotNull(waitForText("CHIUDI SET", timeoutMs = 20_000))
|
||||
tapClickableText(s(R.string.wizard_action_start))
|
||||
waitForAnyText(
|
||||
s(R.string.broadcast_status_live),
|
||||
s(R.string.broadcast_status_connecting),
|
||||
s(R.string.broadcast_status_reconnecting),
|
||||
timeoutMs = 60_000,
|
||||
)
|
||||
// CLOSE SET è un SideIconButton: in hierarchy compare come content-desc, non sempre come text.
|
||||
assertTrue(
|
||||
"Schermata diretta non pronta (né CLOSE SET né End live)",
|
||||
waitForTextOrDesc(
|
||||
timeoutMs = 30_000,
|
||||
s(R.string.broadcast_close_set_button),
|
||||
s(R.string.score_action_close_set),
|
||||
s(R.string.broadcast_terminate_cd),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun grantRuntimePermissions() {
|
||||
@@ -69,11 +86,10 @@ class E2EWizardFlowTest {
|
||||
}
|
||||
|
||||
private fun launchApp() {
|
||||
val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
val intent = context.packageManager.getLaunchIntentForPackage(pkg)?.apply {
|
||||
val intent = ctx.packageManager.getLaunchIntentForPackage(pkg)?.apply {
|
||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
} ?: error("Launch intent mancante per $pkg")
|
||||
context.startActivity(intent)
|
||||
ctx.startActivity(intent)
|
||||
device.wait(Until.hasObject(By.pkg(pkg).depth(0)), 15_000)
|
||||
}
|
||||
|
||||
@@ -81,8 +97,10 @@ class E2EWizardFlowTest {
|
||||
val fields = device.wait(Until.findObjects(By.clazz("android.widget.EditText")), 15_000)
|
||||
if (fields.size < 2) error("Campi login non trovati (${fields.size})")
|
||||
pasteIntoField(fields[0], "coach@matchlivetv.test")
|
||||
pasteIntoField(fields[1], "password123")
|
||||
pasteIntoField(fields[1], "Password123")
|
||||
device.pressKeyCode(KeyEvent.KEYCODE_ENTER)
|
||||
// Preferisci il bottone (ultimo match uppercase), non il titolo.
|
||||
tapLastMatchingText(su(R.string.login_submit))
|
||||
device.waitForIdle()
|
||||
}
|
||||
|
||||
@@ -111,6 +129,34 @@ class E2EWizardFlowTest {
|
||||
error("Nessuno dei testi trovato: ${texts.joinToString()}")
|
||||
}
|
||||
|
||||
private fun waitForTextOrDesc(timeoutMs: Long, vararg labels: String): Boolean {
|
||||
val deadline = SystemClock.elapsedRealtime() + timeoutMs
|
||||
while (SystemClock.elapsedRealtime() < deadline) {
|
||||
for (label in labels) {
|
||||
if (device.hasObject(By.text(label)) || device.hasObject(By.desc(label))) return true
|
||||
}
|
||||
SystemClock.sleep(250)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun tapLastMatchingText(text: String) {
|
||||
val nodes = device.findObjects(By.text(text))
|
||||
val target = nodes.lastOrNull { it.isClickable }
|
||||
?: nodes.lastOrNull()?.let { label ->
|
||||
var node: UiObject2? = label
|
||||
repeat(6) {
|
||||
val current = node ?: return@repeat
|
||||
if (current.isClickable) return@let current
|
||||
node = current.parent
|
||||
}
|
||||
label
|
||||
}
|
||||
?: error("Testo non trovato: $text")
|
||||
target.click()
|
||||
device.waitForIdle()
|
||||
}
|
||||
|
||||
private fun tapClickableText(text: String) {
|
||||
device.findObject(By.text(text).clickable(true))?.let { node ->
|
||||
node.click()
|
||||
|
||||
Reference in New Issue
Block a user