Automation e2e test with maestro

This commit is contained in:
2026-08-18 17:40:02 +07:00
parent c8aac2f6db
commit 0ee4e742c9
12 changed files with 145 additions and 35 deletions
+7
View File
@@ -0,0 +1,7 @@
flows:
- flows/*
executionOrder:
continueOnFailure: false
flowsOrder:
- login
+10
View File
@@ -0,0 +1,10 @@
appId: com.digitoolsolutions.app.torquevaultkmp
---
- runFlow:
file: ../subflows/launch-app.yaml
- runFlow:
file: ../subflows/login-steps.yaml
- runFlow:
file: ../subflows/communicate-steps.yaml
+6
View File
@@ -0,0 +1,6 @@
appId: com.digitoolsolutions.app.torquevaultkmp
---
- runFlow:
file: ../subflows/launch-app.yaml
- runFlow:
file: ../subflows/login-steps.yaml
+6
View File
@@ -0,0 +1,6 @@
appId: com.digitoolsolutions.app.torquevaultkmp
---
#- runFlow:
# file: ../subflows/launch-app.yaml
- runFlow:
file: ../subflows/communicate-steps.yaml
+32
View File
@@ -0,0 +1,32 @@
appId: com.digitoolsolutions.app.torquevaultkmp
---
- tapOn: "Scanner"
- extendedWaitUntil:
visible:
text: "3006"
timeout: 30000 # 30s
- tapOn:
text: "3006"
delay: 1500
- waitForAnimationToEnd:
timeout: 1000
# In case of empty work order
# ---------------------------
#- tapOn:
# text: "Back"
# delay: 500
#
#- assertVisible: "Scanner"
#- waitForAnimationToEnd:
# timeout: 1000
#- extendedWaitUntil:
# visible:
# text: "3006"
# timeout: 30000 # 30s
# In case of work orders
# --------------------------
- tapOn: "Manual Command"
- inputText: "ACCEPT_WO;NG"
- tapOn: "Send"
+16
View File
@@ -0,0 +1,16 @@
appId: com.digitoolsolutions.app.torquevaultkmp
---
- runFlow:
file: ../subflows/launch-app.yaml
- runFlow:
when:
visible: "Enable"
commands:
- tapOn: "Enable"
- runFlow:
when:
visible: "Cho phép"
commands:
- tapOn: "Cho phép"
+6
View File
@@ -0,0 +1,6 @@
appId: com.digitoolsolutions.app.torquevaultkmp
---
- launchApp:
clearState: true
- waitForAnimationToEnd:
timeout: 3000
+24
View File
@@ -0,0 +1,24 @@
appId: com.digitoolsolutions.app.torquevaultkmp
---
#- longPressOn: "Username"
#- tapOn:
# text: "Select All"
# optional: true
#- tapOn:
# text: "Chọn tất cả"
# optional: true
#- eraseText: 1
#- inputText: "admin"
#- tapOn: "App Logo"
#- longPressOn: "Password"
#- tapOn:
# text: "Select All"
# optional: true
#- tapOn:
# text: "Chọn tất cả"
# optional: true
#- eraseText: 1
#- inputText: "admin"
#- tapOn: "App Logo"
- tapOn: "Sign In"
- assertVisible: "Home"
+1 -1
View File
@@ -89,7 +89,7 @@ android {
applicationId = "com.digitoolsolutions.app.torquevaultkmp"
minSdk = libs.versions.android.minSdk.get().toInt()
targetSdk = libs.versions.android.targetSdk.get().toInt()
versionCode = 2
versionCode = 3
versionName = "1.0.2"
}
packaging {
@@ -253,6 +253,9 @@ class ScannerViewModel(
val adv = bleManager.getAdvertisement(deviceId)
if (adv != null) {
connect(adv)
// Remove advertisement from list
advertisementMap.remove(deviceId)
updateDeviceList()
} else {
Napier.e("Cannot connect: Advertisement not found for $deviceId")
}
@@ -369,22 +372,22 @@ class ScannerViewModel(
_connectedDevices.value -= deviceIdentifier
}
}
private fun fetchWorkOrders(peripheral: Peripheral, woID: String = "0", action: String = "next") {
private fun fetchWorkOrders(peripheral: Peripheral, command: Helper.Command, woID: String = "0", action: String = "next") {
viewModelScope.launch {
try {
val orders = scannerRepository.getAbleWorkOrders(woID = woID, action = action)
_workOrders.value = orders
sendWorkOrdersToDevice(peripheral,orders)
sendWorkOrdersToDevice(peripheral, command.name, orders)
} catch (e: Exception) {
Napier.e("Failed to load work orders", e)
logRepository.appendLog(title = "HTTP", content = "${e.message}".substringBefore("[url="))
}
}
}
private fun sendWorkOrdersToDevice(peripheral: Peripheral, workOrders: List<WorkOrder>) {
private fun sendWorkOrdersToDevice(peripheral: Peripheral, command: String, workOrders: List<WorkOrder>) {
viewModelScope.launch {
if (workOrders.isEmpty()) {
sendCommand(peripheral, Helper.CMD_EMPTY_WO)
sendCommand(peripheral, Helper.CMD_EMPTY_WO(command))
return@launch
}
val formatted = workOrders.joinToString(separator = "\n") { wo ->
@@ -398,7 +401,7 @@ class ScannerViewModel(
}
private fun acceptWorkOrder(peripheral: Peripheral, type: Int, woId: String, deviceIdentifier: String) {
viewModelScope.launch {
var command = Helper.CMD_RES_OK(Helper.Command.ACCEPT_WO.name)
var command = Helper.CMD_RES_NG(Helper.Command.ACCEPT_WO.name)
try {
val res = scannerRepository.confirmWorkOrder(woId, deviceIdentifier, type)
if (res)
@@ -427,39 +430,40 @@ class ScannerViewModel(
)
Napier.d(">>>>> DTO $dto")
viewModelScope.launch {
var command = Helper.CMD_RES_NG(Helper.Command.FINISH_WO.name)
when (val act = bleData.firstOrNull()) {
Helper.TorqueAction.TORQUE -> {
try {
scannerRepository.sendMeasurementResult(woId, dto)
} catch (e: Exception) {
val res = scannerRepository.sendMeasurementResult(woId, dto)
if (res.isSuccess) {
command = Helper.CMD_RES_OK(Helper.Command.FINISH_WO.name)
} else {
val e = res.exceptionOrNull()
Napier.e("An error occurred while sending the measurement", e, tag = "ScannerViewModel")
logRepository.appendLog(title = "HTTP", content = "${e.message}".substringBefore("[url="))
} finally {
// Currently, always send UPLOADED_WO regardless of success or failure
sendCommand(peripheral, Helper.CMD_RES_OK(Helper.Command.FINISH_WO.name))
logRepository.appendLog(title = "HTTP", content = "${e?.message}".substringBefore("[url="))
}
sendCommand(peripheral, command)
}
Helper.TorqueAction.RE_TORQUE -> {
try {
scannerRepository.sendMeasurementResult(woId, dto, true)
} catch (e: Exception) {
val res = scannerRepository.sendMeasurementResult(woId, dto, true)
if (res.isSuccess) {
command = Helper.CMD_RES_OK(Helper.Command.FINISH_WO.name)
} else {
val e = res.exceptionOrNull()
Napier.e("An error occurred while sending the re-measurement", e, tag = "ScannerViewModel")
logRepository.appendLog(title = "HTTP", content = "${e.message}".substringBefore("[url="))
} finally {
// Currently, always send UPLOADED_WO regardless of success or failure
sendCommand(peripheral, Helper.CMD_RES_OK(Helper.Command.FINISH_WO.name))
logRepository.appendLog(title = "HTTP", content = "${e?.message}".substringBefore("[url="))
}
sendCommand(peripheral, command)
}
Helper.TorqueAction.CANCEL -> {
try {
scannerRepository.sendCancelWorkOrder(woId, dto)
} catch (e: Exception) {
val res = scannerRepository.sendCancelWorkOrder(woId, dto)
if (res.isSuccess) {
command = Helper.CMD_RES_OK(Helper.Command.FINISH_WO.name)
} else {
val e = res.exceptionOrNull()
Napier.e("An error occurred while cancel work order", e, tag = "ScannerViewModel")
logRepository.appendLog(title = "HTTP", content = "${e.message}".substringBefore("[url="))
} finally {
// Currently, always send UPLOADED_WO regardless of success or failure
sendCommand(peripheral, Helper.CMD_RES_OK(Helper.Command.FINISH_WO.name))
logRepository.appendLog(title = "HTTP", content = "${e?.message}".substringBefore("[url="))
}
sendCommand(peripheral, command)
}
else -> Napier.w(">>>>> Unknown action: $act")
}
@@ -476,9 +480,9 @@ class ScannerViewModel(
val woId = decoded.woId
val data = decoded.data
when (cmd) {
Helper.Command.START_WO -> fetchWorkOrders(peripheral)
Helper.Command.NEXT_WO -> fetchWorkOrders(peripheral, woId, "next")
Helper.Command.PREVIOUS_WO -> fetchWorkOrders(peripheral, woId, "back")
Helper.Command.START_WO -> fetchWorkOrders(peripheral, Helper.Command.START_WO)
Helper.Command.NEXT_WO -> fetchWorkOrders(peripheral, Helper.Command.NEXT_WO, woId, "next")
Helper.Command.PREVIOUS_WO -> fetchWorkOrders(peripheral, Helper.Command.PREVIOUS_WO, woId, "back")
Helper.Command.ACCEPT_WO -> acceptWorkOrder(peripheral, type.code, woId, deviceIdentifier)
Helper.Command.FINISH_WO -> {
finishWorkOrder(peripheral, type.code, woId, data, deviceIdentifier)
@@ -8,11 +8,10 @@ object Helper {
const val START_CHARACTER ="$"
const val END_LINE_FEED = "#\n"
const val SECURITY_CRC16 = "00cr16"
/** Command sent to indicate no work orders are available. */
const val CMD_EMPTY_WO = "START_WO;empty"
const val WO_RES = "WO_RES"
/** Generates empty response for a specific command. */
val CMD_EMPTY_WO: (String) -> String = {cmd -> "$cmd;empty"}
/** Generates a success response for a specific command. */
val CMD_RES_OK: (String) -> String = { cmd -> "$cmd;OK" }
@@ -27,7 +27,7 @@ class UartCommunicationTest {
assertEquals("cr16", decoded.crc16)
// --- STEP 2: App prepares response (Empty Case) ---
val emptyCase = Helper.buildUartCommand(Helper.CMD_EMPTY_WO)
val emptyCase = Helper.buildUartCommand(Helper.CMD_EMPTY_WO("START_WO"))
assertEquals($$"$START_WO;empty\n00cr16#\n", emptyCase)
// --- STEP 3: App prepares response (Multiple WO Case) ---