Automation e2e test with maestro
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
flows:
|
||||||
|
- flows/*
|
||||||
|
|
||||||
|
executionOrder:
|
||||||
|
continueOnFailure: false
|
||||||
|
flowsOrder:
|
||||||
|
- login
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
appId: com.digitoolsolutions.app.torquevaultkmp
|
||||||
|
---
|
||||||
|
- runFlow:
|
||||||
|
file: ../subflows/launch-app.yaml
|
||||||
|
- runFlow:
|
||||||
|
file: ../subflows/login-steps.yaml
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
appId: com.digitoolsolutions.app.torquevaultkmp
|
||||||
|
---
|
||||||
|
#- runFlow:
|
||||||
|
# file: ../subflows/launch-app.yaml
|
||||||
|
- runFlow:
|
||||||
|
file: ../subflows/communicate-steps.yaml
|
||||||
@@ -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"
|
||||||
@@ -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"
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
appId: com.digitoolsolutions.app.torquevaultkmp
|
||||||
|
---
|
||||||
|
- launchApp:
|
||||||
|
clearState: true
|
||||||
|
- waitForAnimationToEnd:
|
||||||
|
timeout: 3000
|
||||||
@@ -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"
|
||||||
@@ -89,7 +89,7 @@ android {
|
|||||||
applicationId = "com.digitoolsolutions.app.torquevaultkmp"
|
applicationId = "com.digitoolsolutions.app.torquevaultkmp"
|
||||||
minSdk = libs.versions.android.minSdk.get().toInt()
|
minSdk = libs.versions.android.minSdk.get().toInt()
|
||||||
targetSdk = libs.versions.android.targetSdk.get().toInt()
|
targetSdk = libs.versions.android.targetSdk.get().toInt()
|
||||||
versionCode = 2
|
versionCode = 3
|
||||||
versionName = "1.0.2"
|
versionName = "1.0.2"
|
||||||
}
|
}
|
||||||
packaging {
|
packaging {
|
||||||
|
|||||||
+33
-29
@@ -253,6 +253,9 @@ class ScannerViewModel(
|
|||||||
val adv = bleManager.getAdvertisement(deviceId)
|
val adv = bleManager.getAdvertisement(deviceId)
|
||||||
if (adv != null) {
|
if (adv != null) {
|
||||||
connect(adv)
|
connect(adv)
|
||||||
|
// Remove advertisement from list
|
||||||
|
advertisementMap.remove(deviceId)
|
||||||
|
updateDeviceList()
|
||||||
} else {
|
} else {
|
||||||
Napier.e("Cannot connect: Advertisement not found for $deviceId")
|
Napier.e("Cannot connect: Advertisement not found for $deviceId")
|
||||||
}
|
}
|
||||||
@@ -369,22 +372,22 @@ class ScannerViewModel(
|
|||||||
_connectedDevices.value -= deviceIdentifier
|
_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 {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
val orders = scannerRepository.getAbleWorkOrders(woID = woID, action = action)
|
val orders = scannerRepository.getAbleWorkOrders(woID = woID, action = action)
|
||||||
_workOrders.value = orders
|
_workOrders.value = orders
|
||||||
sendWorkOrdersToDevice(peripheral,orders)
|
sendWorkOrdersToDevice(peripheral, command.name, orders)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Napier.e("Failed to load work orders", e)
|
Napier.e("Failed to load work orders", e)
|
||||||
logRepository.appendLog(title = "HTTP", content = "${e.message}".substringBefore("[url="))
|
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 {
|
viewModelScope.launch {
|
||||||
if (workOrders.isEmpty()) {
|
if (workOrders.isEmpty()) {
|
||||||
sendCommand(peripheral, Helper.CMD_EMPTY_WO)
|
sendCommand(peripheral, Helper.CMD_EMPTY_WO(command))
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
val formatted = workOrders.joinToString(separator = "\n") { wo ->
|
val formatted = workOrders.joinToString(separator = "\n") { wo ->
|
||||||
@@ -398,7 +401,7 @@ class ScannerViewModel(
|
|||||||
}
|
}
|
||||||
private fun acceptWorkOrder(peripheral: Peripheral, type: Int, woId: String, deviceIdentifier: String) {
|
private fun acceptWorkOrder(peripheral: Peripheral, type: Int, woId: String, deviceIdentifier: String) {
|
||||||
viewModelScope.launch {
|
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 {
|
try {
|
||||||
val res = scannerRepository.confirmWorkOrder(woId, deviceIdentifier, type)
|
val res = scannerRepository.confirmWorkOrder(woId, deviceIdentifier, type)
|
||||||
if (res)
|
if (res)
|
||||||
@@ -427,39 +430,40 @@ class ScannerViewModel(
|
|||||||
)
|
)
|
||||||
Napier.d(">>>>> DTO $dto")
|
Napier.d(">>>>> DTO $dto")
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
|
var command = Helper.CMD_RES_NG(Helper.Command.FINISH_WO.name)
|
||||||
when (val act = bleData.firstOrNull()) {
|
when (val act = bleData.firstOrNull()) {
|
||||||
Helper.TorqueAction.TORQUE -> {
|
Helper.TorqueAction.TORQUE -> {
|
||||||
try {
|
val res = scannerRepository.sendMeasurementResult(woId, dto)
|
||||||
scannerRepository.sendMeasurementResult(woId, dto)
|
if (res.isSuccess) {
|
||||||
} catch (e: Exception) {
|
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")
|
Napier.e("An error occurred while sending the measurement", e, tag = "ScannerViewModel")
|
||||||
logRepository.appendLog(title = "HTTP", content = "${e.message}".substringBefore("[url="))
|
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))
|
|
||||||
}
|
}
|
||||||
|
sendCommand(peripheral, command)
|
||||||
}
|
}
|
||||||
Helper.TorqueAction.RE_TORQUE -> {
|
Helper.TorqueAction.RE_TORQUE -> {
|
||||||
try {
|
val res = scannerRepository.sendMeasurementResult(woId, dto, true)
|
||||||
scannerRepository.sendMeasurementResult(woId, dto, true)
|
if (res.isSuccess) {
|
||||||
} catch (e: Exception) {
|
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")
|
Napier.e("An error occurred while sending the re-measurement", e, tag = "ScannerViewModel")
|
||||||
logRepository.appendLog(title = "HTTP", content = "${e.message}".substringBefore("[url="))
|
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))
|
|
||||||
}
|
}
|
||||||
|
sendCommand(peripheral, command)
|
||||||
}
|
}
|
||||||
Helper.TorqueAction.CANCEL -> {
|
Helper.TorqueAction.CANCEL -> {
|
||||||
try {
|
val res = scannerRepository.sendCancelWorkOrder(woId, dto)
|
||||||
scannerRepository.sendCancelWorkOrder(woId, dto)
|
if (res.isSuccess) {
|
||||||
} catch (e: Exception) {
|
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")
|
Napier.e("An error occurred while cancel work order", e, tag = "ScannerViewModel")
|
||||||
logRepository.appendLog(title = "HTTP", content = "${e.message}".substringBefore("[url="))
|
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))
|
|
||||||
}
|
}
|
||||||
|
sendCommand(peripheral, command)
|
||||||
}
|
}
|
||||||
else -> Napier.w(">>>>> Unknown action: $act")
|
else -> Napier.w(">>>>> Unknown action: $act")
|
||||||
}
|
}
|
||||||
@@ -476,9 +480,9 @@ class ScannerViewModel(
|
|||||||
val woId = decoded.woId
|
val woId = decoded.woId
|
||||||
val data = decoded.data
|
val data = decoded.data
|
||||||
when (cmd) {
|
when (cmd) {
|
||||||
Helper.Command.START_WO -> fetchWorkOrders(peripheral)
|
Helper.Command.START_WO -> fetchWorkOrders(peripheral, Helper.Command.START_WO)
|
||||||
Helper.Command.NEXT_WO -> fetchWorkOrders(peripheral, woId, "next")
|
Helper.Command.NEXT_WO -> fetchWorkOrders(peripheral, Helper.Command.NEXT_WO, woId, "next")
|
||||||
Helper.Command.PREVIOUS_WO -> fetchWorkOrders(peripheral, woId, "back")
|
Helper.Command.PREVIOUS_WO -> fetchWorkOrders(peripheral, Helper.Command.PREVIOUS_WO, woId, "back")
|
||||||
Helper.Command.ACCEPT_WO -> acceptWorkOrder(peripheral, type.code, woId, deviceIdentifier)
|
Helper.Command.ACCEPT_WO -> acceptWorkOrder(peripheral, type.code, woId, deviceIdentifier)
|
||||||
Helper.Command.FINISH_WO -> {
|
Helper.Command.FINISH_WO -> {
|
||||||
finishWorkOrder(peripheral, type.code, woId, data, deviceIdentifier)
|
finishWorkOrder(peripheral, type.code, woId, data, deviceIdentifier)
|
||||||
|
|||||||
+2
-3
@@ -9,10 +9,9 @@ object Helper {
|
|||||||
const val END_LINE_FEED = "#\n"
|
const val END_LINE_FEED = "#\n"
|
||||||
const val SECURITY_CRC16 = "00cr16"
|
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"
|
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. */
|
/** Generates a success response for a specific command. */
|
||||||
val CMD_RES_OK: (String) -> String = { cmd -> "$cmd;OK" }
|
val CMD_RES_OK: (String) -> String = { cmd -> "$cmd;OK" }
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -27,7 +27,7 @@ class UartCommunicationTest {
|
|||||||
assertEquals("cr16", decoded.crc16)
|
assertEquals("cr16", decoded.crc16)
|
||||||
|
|
||||||
// --- STEP 2: App prepares response (Empty Case) ---
|
// --- 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)
|
assertEquals($$"$START_WO;empty\n00cr16#\n", emptyCase)
|
||||||
|
|
||||||
// --- STEP 3: App prepares response (Multiple WO Case) ---
|
// --- STEP 3: App prepares response (Multiple WO Case) ---
|
||||||
|
|||||||
Reference in New Issue
Block a user