Skip to content

Commit

Permalink
Feature - Add Status and Time to report.xml (mobile-dev-inc#1520)
Browse files Browse the repository at this point in the history
* add status to testcase

* add status and duration to cloud test report

* address PR issues
  • Loading branch information
xo0ps authored Jan 31, 2024
1 parent 5dab3f3 commit ffae97c
Show file tree
Hide file tree
Showing 8 changed files with 99 additions and 47 deletions.
84 changes: 48 additions & 36 deletions maestro-cli/src/main/java/maestro/cli/cloud/CloudInteractor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,19 @@ package maestro.cli.cloud
import maestro.cli.CliError
import maestro.cli.api.ApiClient
import maestro.cli.api.DeviceInfo
import maestro.cli.api.UploadResponse
import maestro.cli.api.UploadStatus
import maestro.cli.auth.Auth
import maestro.cli.device.Platform
import maestro.cli.model.RunningFlow
import maestro.cli.model.FlowStatus
import maestro.cli.model.RunningFlows
import maestro.cli.model.TestExecutionSummary
import maestro.cli.report.ReportFormat
import maestro.cli.report.ReporterFactory
import maestro.cli.util.EnvUtils
import maestro.cli.util.FileUtils.isZip
import maestro.cli.util.PrintUtils
import maestro.cli.util.TimeUtils
import maestro.cli.util.WorkspaceUtils
import maestro.cli.view.ProgressBar
import maestro.cli.view.TestSuiteStatusView
Expand Down Expand Up @@ -199,7 +201,7 @@ class CloudInteractor(
): Int {
val startTime = System.currentTimeMillis()

val reportedCompletions = mutableSetOf<String>()
val runningFlows = RunningFlows()

var pollingInterval = minPollIntervalMs
var retryCounter = 0
Expand All @@ -225,21 +227,38 @@ class CloudInteractor(
throw CliError("Failed to fetch the status of an upload $uploadId. Status code = ${e.statusCode}")
}

upload.flows
.filter {
it.name !in reportedCompletions && it.status in COMPLETED_STATUSES
for (uploadFlowResult in upload.flows) {
if (runningFlows.flows.none { it.name == uploadFlowResult.name }) {
runningFlows.flows.add(RunningFlow(name = uploadFlowResult.name))
}
.forEach {
TestSuiteStatusView.showFlowCompletion(
it.toViewModel()
)

reportedCompletions.add(it.name)
val runningFlow = runningFlows.flows.find { it.name == uploadFlowResult.name } ?: continue
runningFlow.status = uploadFlowResult.status
when (runningFlow.status) {
UploadStatus.Status.PENDING -> { /* do nothing */ }
UploadStatus.Status.RUNNING -> {
if (runningFlow.startTime == null) {
runningFlow.startTime = System.currentTimeMillis()
}
}
else -> {
if (runningFlow.duration == null) {
runningFlow.duration = TimeUtils.durationInSeconds(startTimeInMillis = runningFlow.startTime, endTimeInMillis = System.currentTimeMillis())
}
if (!runningFlow.reported) {
TestSuiteStatusView.showFlowCompletion(
uploadFlowResult.toViewModel(runningFlow.duration)
)
runningFlow.reported = true
}
}
}
}

if (upload.completed) {
runningFlows.duration = TimeUtils.durationInSeconds(startTimeInMillis = startTime, endTimeInMillis = System.currentTimeMillis())
return handleSyncUploadCompletion(
upload = upload,
runningFlows = runningFlows,
teamId = teamId,
appId = appId,
failOnCancellation = failOnCancellation,
Expand Down Expand Up @@ -276,6 +295,7 @@ class CloudInteractor(

private fun handleSyncUploadCompletion(
upload: UploadStatus,
runningFlows: RunningFlows,
teamId: String,
appId: String,
failOnCancellation: Boolean,
Expand Down Expand Up @@ -309,7 +329,7 @@ class CloudInteractor(
}

if (reportOutputSink != null) {
saveReport(reportFormat, !failed, upload, reportOutputSink, testSuiteName)
saveReport(reportFormat, !failed, createSuiteResult(!failed, upload, runningFlows), reportOutputSink, testSuiteName)
}


Expand All @@ -331,42 +351,34 @@ class CloudInteractor(
private fun saveReport(
reportFormat: ReportFormat,
passed: Boolean,
upload: UploadStatus,
suiteResult: TestExecutionSummary.SuiteResult,
reportOutputSink: BufferedSink,
testSuiteName: String?
) {
ReporterFactory.buildReporter(reportFormat, testSuiteName)
.report(
TestExecutionSummary(
passed = passed,
suites = listOf(
TestExecutionSummary.SuiteResult(
passed = passed,
flows = upload.flows.map { flow ->
val failure = flow.errors.firstOrNull()
TestExecutionSummary.FlowResult(
name = flow.name,
fileName = null,
status = FlowStatus.from(flow.status),
failure = if (failure != null) TestExecutionSummary.Failure(failure) else null
)
}
)
)
suites = listOf(suiteResult)
),
reportOutputSink,
)
}

companion object {

private val COMPLETED_STATUSES = setOf(
UploadStatus.Status.CANCELED,
UploadStatus.Status.WARNING,
UploadStatus.Status.SUCCESS,
UploadStatus.Status.ERROR,
private fun createSuiteResult(passed: Boolean, upload: UploadStatus, runningFlows: RunningFlows): TestExecutionSummary.SuiteResult {
return TestExecutionSummary.SuiteResult(
passed = passed,
flows = upload.flows.map { uploadFlowResult ->
val failure = uploadFlowResult.errors.firstOrNull()
TestExecutionSummary.FlowResult(
name = uploadFlowResult.name,
fileName = null,
status = FlowStatus.from(uploadFlowResult.status),
failure = if (failure != null) TestExecutionSummary.Failure(failure) else null,
duration = runningFlows.flows.find { it.name == uploadFlowResult.name }?.duration
)
},
duration = runningFlows.duration
)

}

}
17 changes: 17 additions & 0 deletions maestro-cli/src/main/java/maestro/cli/model/RunningFlow.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package maestro.cli.model

import maestro.cli.api.UploadStatus
import kotlin.time.Duration

data class RunningFlows(
val flows: MutableSet<RunningFlow> = mutableSetOf(),
var duration: Duration? = null
)

data class RunningFlow(
val name: String,
var status: UploadStatus.Status? = null,
var startTime: Long? = null,
var duration: Duration? = null,
var reported: Boolean = false
)
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ class JUnitTestSuiteReporter(
message = failure.message,
)
},
time = flow.duration?.inWholeSeconds?.toString()
time = flow.duration?.inWholeSeconds?.toString(),
status = flow.status
)
}
)
Expand Down Expand Up @@ -82,6 +83,7 @@ class JUnitTestSuiteReporter(
@JacksonXmlProperty(isAttribute = true) val name: String,
@JacksonXmlProperty(isAttribute = true) val classname: String,
@JacksonXmlProperty(isAttribute = true) val time: String? = null,
@JacksonXmlProperty(isAttribute = true) val status: FlowStatus,
val failure: Failure? = null,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import maestro.cli.model.FlowStatus
import maestro.cli.model.TestExecutionSummary
import maestro.cli.report.*
import maestro.cli.util.PrintUtils
import maestro.cli.util.TimeUtils
import maestro.cli.view.ErrorViewUtils
import maestro.cli.view.TestSuiteStatusView
import maestro.cli.view.TestSuiteStatusView.TestSuiteViewModel
Expand Down Expand Up @@ -215,7 +216,7 @@ class TestSuiteInteractor(
errorMessage = ErrorViewUtils.exceptionToMessage(e)
}
}
val flowDuration = (flowTimeMillis / 1000f).roundToLong().seconds
val flowDuration = TimeUtils.durationInSeconds(flowTimeMillis)

TestDebugReporter.saveFlow(flowName, debug, debugOutputPath)

Expand Down
17 changes: 17 additions & 0 deletions maestro-cli/src/main/java/maestro/cli/util/TimeUtils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package maestro.cli.util

import kotlin.math.roundToLong
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds

object TimeUtils {

fun durationInSeconds(startTimeInMillis: Long?, endTimeInMillis: Long?): Duration {
if (startTimeInMillis == null || endTimeInMillis == null) return Duration.ZERO
return ((endTimeInMillis - startTimeInMillis) / 1000f).roundToLong().seconds
}

fun durationInSeconds(durationInMillis: Long): Duration {
return ((durationInMillis) / 1000f).roundToLong().seconds
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -164,11 +164,14 @@ object TestSuiteStatusView {
}
)

fun UploadStatus.FlowResult.toViewModel() = FlowResult(
fun UploadStatus.FlowResult.toViewModel(
duration: Duration? = null
) = FlowResult(
name = name,
status = FlowStatus.from(status, ),
error = errors.firstOrNull(),
cancellationReason = cancellationReason
cancellationReason = cancellationReason,
duration = duration
)

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ class JUnitTestSuiteReporterTest {
<?xml version='1.0' encoding='UTF-8'?>
<testsuites>
<testsuite name="Test Suite" device="iPhone 14" tests="2" failures="0" time="1915">
<testcase id="Flow A" name="Flow A" classname="Flow A" time="421"/>
<testcase id="Flow B" name="Flow B" classname="Flow B" time="1494"/>
<testcase id="Flow A" name="Flow A" classname="Flow A" time="421" status="SUCCESS"/>
<testcase id="Flow B" name="Flow B" classname="Flow B" time="1494" status="WARNING"/>
</testsuite>
</testsuites>
Expand Down Expand Up @@ -106,8 +106,8 @@ class JUnitTestSuiteReporterTest {
<?xml version='1.0' encoding='UTF-8'?>
<testsuites>
<testsuite name="Test Suite" tests="2" failures="1" time="552">
<testcase id="Flow A" name="Flow A" classname="Flow A" time="421"/>
<testcase id="Flow B" name="Flow B" classname="Flow B" time="131">
<testcase id="Flow A" name="Flow A" classname="Flow A" time="421" status="SUCCESS"/>
<testcase id="Flow B" name="Flow B" classname="Flow B" time="131" status="ERROR">
<failure>Error message</failure>
</testcase>
</testsuite>
Expand Down Expand Up @@ -160,8 +160,8 @@ class JUnitTestSuiteReporterTest {
<?xml version='1.0' encoding='UTF-8'?>
<testsuites>
<testsuite name="Custom test suite name" device="iPhone 14" tests="2" failures="0" time="421">
<testcase id="Flow A" name="Flow A" classname="Flow A" time="421"/>
<testcase id="Flow B" name="Flow B" classname="Flow B"/>
<testcase id="Flow A" name="Flow A" classname="Flow A" time="421" status="SUCCESS"/>
<testcase id="Flow B" name="Flow B" classname="Flow B" status="WARNING"/>
</testsuite>
</testsuites>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ internal class YamlCommandReaderTest {
val resource = this::class.java.getResource("/YamlCommandReaderTest/flow.zip")!!.toURI()
assertThat(resource.scheme).isEqualTo("file")

val commands = FileSystems.newFileSystem(Paths.get(resource), null).use { fs ->
val commands = FileSystems.newFileSystem(Paths.get(resource), null as ClassLoader?).use { fs ->
YamlCommandReader.readCommands(fs.getPath("flow.yaml"))
}

Expand Down

0 comments on commit ffae97c

Please sign in to comment.