forked from KasperskyLab/Kaspresso
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ISSUE-404: Using documents to avoid storage restrictions; allure reco…
…rds videos
- Loading branch information
1 parent
18876c9
commit d27d220
Showing
17 changed files
with
477 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
126 changes: 126 additions & 0 deletions
126
...lin/com/kaspersky/components/alluresupport/interceptors/testrun/MoveReportsInterceptor.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
package com.kaspersky.components.alluresupport.interceptors.testrun | ||
|
||
import android.app.Instrumentation | ||
import androidx.test.uiautomator.UiDevice | ||
import com.google.common.io.CharStreams | ||
import com.kaspersky.kaspresso.files.dirs.DirsProvider | ||
import com.kaspersky.kaspresso.files.resources.ResourcesDirsProvider | ||
import com.kaspersky.kaspresso.files.resources.ResourcesRootDirsProvider | ||
import com.kaspersky.kaspresso.interceptors.watcher.testcase.TestRunWatcherInterceptor | ||
import com.kaspersky.kaspresso.testcases.models.info.TestInfo | ||
import org.json.JSONObject | ||
import java.io.File | ||
|
||
private const val ATTACHMENTS_JSON_FIELD = "attachments" | ||
private const val NAME_JSON_FIELD = "name" | ||
private const val SOURCE_JSON_FIELD = "source" | ||
private const val MP4_EXTENSION = "mp4" | ||
|
||
/** | ||
* Current allure version stores reports to /data/data/your.package.name/files/allure-results. | ||
* This interceptor moves them to default artifacts folder after test and replaces mock videos with real ones | ||
*/ | ||
class MoveReportsInterceptor( | ||
private val instrumentation: Instrumentation, | ||
private val dirsProvider: DirsProvider, | ||
private val resourcesDirsProvider: ResourcesDirsProvider, | ||
private val rootDirsProvider: ResourcesRootDirsProvider, | ||
private val stateHolder: TestRunStateHolder, | ||
private val device: UiDevice | ||
) : TestRunWatcherInterceptor { | ||
override fun onTestFinished(testInfo: TestInfo, success: Boolean) { | ||
val allureTargetDir = moveAllureReportToSdCard() | ||
removeStubs(allureTargetDir) | ||
moveAttachedVideosToProperDirectories(allureTargetDir) | ||
cleanUp() | ||
} | ||
|
||
/** | ||
* Deletes allure report under /data/data/your.package.name/files and empty directories after moving real videos from them | ||
*/ | ||
private fun cleanUp() { | ||
getOriginalAllureDir().deleteRecursively() | ||
val videosDir = dirsProvider.provideNew(rootDirsProvider.videoRootDir) | ||
device.executeShellCommand("rm -rf ${videosDir.absolutePath}") | ||
|
||
dirsProvider.provideNew(rootDirsProvider.screenshotsRootDir).deleteRecursively() | ||
dirsProvider.provideNew(rootDirsProvider.logcatRootDir).deleteRecursively() | ||
dirsProvider.provideNew(rootDirsProvider.viewHierarchy).deleteRecursively() | ||
} | ||
|
||
/** | ||
* @return allure results dir under /data/data/your.package.name/files | ||
*/ | ||
private fun getOriginalAllureDir(): File = instrumentation.targetContext.filesDir.resolve("allure-results") | ||
|
||
/** | ||
* Moves allure report from /data/data/your.package.name/files/allure-report to external storage e.g. | ||
* /sdcard/Documents/.../allure-results | ||
* @return allure target directory on sdcard | ||
*/ | ||
private fun moveAllureReportToSdCard(): File { | ||
val allureResultsFile = getOriginalAllureDir() | ||
val allureTargetDir = dirsProvider.provideNew(rootDirsProvider.allureRootDir) | ||
if (!allureResultsFile.exists()) { | ||
throw IllegalArgumentException("Unable to move allure results from $allureResultsFile. File not found") | ||
} | ||
allureResultsFile.copyRecursively(allureTargetDir) | ||
|
||
return allureTargetDir | ||
} | ||
|
||
/** | ||
* During allure test stubs are attached to report because real videos can't be recorded directly to /data/data. | ||
* After kaspresso moves report to sdcard it has to replace stubs with real videos. Videos are moved using adb shell | ||
* 👀 https://issuetracker.google.com/issues/258277873 | ||
* @param allureTargetDir allure directory under /sdcard | ||
*/ | ||
private fun removeStubs(allureTargetDir: File) { | ||
allureTargetDir.listFiles()?.forEach { | ||
if (it.extension.contains(MP4_EXTENSION, ignoreCase = true)) { | ||
it.delete() | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Move (and rename) real videos from their original directories to report dir | ||
* @param allureTargetDir allure directory under /sdcard | ||
*/ | ||
private fun moveAttachedVideosToProperDirectories(allureTargetDir: File) { | ||
stateHolder.attachedVideos.forEach { | ||
saveAttachedVideo(it, allureTargetDir) | ||
} | ||
} | ||
|
||
/** | ||
* Used for screen recording workaround: | ||
* screen cast can't be recorded to /data/data/.../allure-results because of permissions issues (👀 https://issuetracker.google.com/issues/258277873). | ||
* We have to save video on /sdcard and in the same time attach a stub to allure report. Before moving report to /sdcard | ||
* we need to parse report and save stub file name to rename and move an actual one to allure report dir after moving report to /sdcard. Then we | ||
* remove stub videos in /sdcard and /data/data and move real video file to allure report directory to replace stubs | ||
* @param attachedVideo video attached to report | ||
* @param allureTargetDir allure directory under /sdcard | ||
*/ | ||
private fun saveAttachedVideo(attachedVideo: AttachedVideo, allureTargetDir: File) { | ||
val allureReportFile = allureTargetDir.resolve("${stateHolder.lastTestCaseUuid ?: ""}-result.json") | ||
if (!allureReportFile.exists()) { | ||
throw IllegalStateException("Can't attach video to report because the latter not found. Tried path ${allureReportFile.absolutePath}") | ||
} | ||
|
||
allureReportFile.inputStream().use { | ||
val json = JSONObject(CharStreams.toString(it.reader())) | ||
val attachments = json.getJSONArray(ATTACHMENTS_JSON_FIELD) | ||
for (i in 0 until attachments.length()) { | ||
val attachment = attachments.getJSONObject(i) | ||
val attachmentName = attachment.getString(NAME_JSON_FIELD) | ||
if (attachmentName.equals(attachedVideo.attachedStubFile.name, ignoreCase = true)) { | ||
val source = attachment.getString(SOURCE_JSON_FIELD) // Attachment real filename | ||
device.executeShellCommand("mv ${attachedVideo.actualFile.absolutePath} ${allureTargetDir.resolve(source)}") | ||
attachedVideo.attachedStubFile.delete() | ||
break | ||
} | ||
} | ||
} | ||
} | ||
} |
34 changes: 34 additions & 0 deletions
34
.../kotlin/com/kaspersky/components/alluresupport/interceptors/testrun/TestRunStateHolder.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package com.kaspersky.components.alluresupport.interceptors.testrun | ||
|
||
import java.io.File | ||
|
||
/** | ||
* Used to store shared state used by multiple interceptors | ||
*/ | ||
class TestRunStateHolder { | ||
private val _attachedVideos = mutableListOf<AttachedVideo>() | ||
val attachedVideos: List<AttachedVideo> | ||
get() = _attachedVideos.toList() | ||
|
||
var lastTestCaseUuid: String? = null | ||
|
||
fun rememberAttachedVideo(stubFile: File, actualFile: File) { | ||
val attachedVideo = AttachedVideo(attachedStubFile = stubFile, actualFile = actualFile) | ||
_attachedVideos.add(attachedVideo) | ||
} | ||
} | ||
|
||
/** | ||
* This model represents allure video attachment. It's used for screen recording workaround | ||
*/ | ||
data class AttachedVideo( | ||
/** | ||
* Stub while which has been used to attach a video to report. Should be replaced with a real video file after moving | ||
* report to /sdcard | ||
*/ | ||
val attachedStubFile: File, | ||
/** | ||
* Actual screen record file which has been saved into /sdcard. Should be used to replace a stub in allure report | ||
*/ | ||
val actualFile: File | ||
) |
13 changes: 13 additions & 0 deletions
13
...lin/com/kaspersky/components/alluresupport/interceptors/testrun/TestRunUuidInterceptor.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package com.kaspersky.components.alluresupport.interceptors.testrun | ||
|
||
import com.kaspersky.kaspresso.interceptors.watcher.testcase.TestRunWatcherInterceptor | ||
import com.kaspersky.kaspresso.testcases.models.info.TestInfo | ||
import io.qameta.allure.kotlin.Allure | ||
|
||
class TestRunUuidInterceptor( | ||
private val stateHolder: TestRunStateHolder | ||
) : TestRunWatcherInterceptor { | ||
override fun onTestStarted(testInfo: TestInfo) { | ||
stateHolder.lastTestCaseUuid = Allure.lifecycle.getCurrentTestCase() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
55 changes: 55 additions & 0 deletions
55
kaspresso/src/main/kotlin/com/kaspersky/kaspresso/files/dirs/AllureDirsProvider.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package com.kaspersky.kaspresso.files.dirs | ||
|
||
import android.annotation.SuppressLint | ||
import android.app.Instrumentation | ||
import android.content.Context | ||
import android.os.Build | ||
import android.os.Environment | ||
import androidx.test.uiautomator.UiDevice | ||
import com.kaspersky.kaspresso.files.resources.ResourcesRootDirsProvider | ||
import com.kaspersky.kaspresso.internal.extensions.other.createDirIfNeeded | ||
import com.kaspersky.kaspresso.internal.extensions.other.createFileIfNeeded | ||
import java.io.File | ||
|
||
class AllureDirsProvider( | ||
private val instrumentation: Instrumentation, | ||
private val resourcesRootDirsProvider: ResourcesRootDirsProvider, | ||
device: UiDevice | ||
) : DefaultDirsProvider(instrumentation, device) { | ||
|
||
@Suppress("DEPRECATION") | ||
@SuppressLint("WorldReadableFiles", "ObsoleteSdkInt") | ||
override fun provideNew(dest: File): File { | ||
if (isVideoDir(dest)) { // screen recorder can't record to /data/data | ||
return super.provideNew(dest) | ||
} | ||
|
||
val dir: File = when { | ||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> instrumentation.targetContext.applicationContext.filesDir.resolve(dest) | ||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP -> Environment.getExternalStorageDirectory().resolve(dest) | ||
else -> instrumentation.targetContext.applicationContext.getDir(dest.canonicalPath, Context.MODE_WORLD_READABLE) | ||
} | ||
|
||
return dir.createDirIfNeeded() | ||
} | ||
|
||
/** | ||
* Used for allure report video attachment workaround. Creates stub video file in package private directory so allure could attach it to report | ||
* @param actualVideoFile vide file saved by screen recorder | ||
* @return stub video file under /data/data | ||
*/ | ||
fun provideReportVideoAttachmentStub(actualVideoFile: File): File { | ||
val defaultVideoDir = super.provideNew(resourcesRootDirsProvider.videoRootDir).absolutePath | ||
val targetVideoDir = instrumentation.targetContext.applicationContext.filesDir.resolve(resourcesRootDirsProvider.videoRootDir).absolutePath | ||
val targetFilePath = actualVideoFile.absolutePath.replace(defaultVideoDir, targetVideoDir) | ||
|
||
return File(targetFilePath).parentFile!! | ||
.createDirIfNeeded() | ||
.resolve(actualVideoFile.name) | ||
.createFileIfNeeded() | ||
} | ||
|
||
private fun isVideoDir(dest: File): Boolean { | ||
return dest == resourcesRootDirsProvider.videoRootDir | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.