-
Notifications
You must be signed in to change notification settings - Fork 7
Error messaging + idling resource registration #48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| root = true | ||
|
|
||
| [*] | ||
| indent_style = space | ||
| indent_size = 2 | ||
| charset = utf-8 | ||
|
|
||
| [*.{kt,kts}] | ||
| ij_continuation_indent_size = 2 | ||
|
|
||
| [*.{java,xml}] | ||
| ij_continuation_indent_size = 4 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,13 +21,14 @@ import com.wealthfront.screencaptor.globalmutator.CursorHider | |
| import com.wealthfront.screencaptor.globalmutator.ScrollbarHider | ||
| import com.wealthfront.screencaptor.globalmutator.ViewTreeMutator | ||
| import com.wealthfront.screencaptor.idlingresource.ScreenshotIdlingResource | ||
| import eu.bolt.screenshotty.Screenshot | ||
| import eu.bolt.screenshotty.ScreenshotActionOrder | ||
| import eu.bolt.screenshotty.ScreenshotManagerBuilder | ||
| import eu.bolt.screenshotty.util.ScreenshotFileSaver | ||
| import java.io.File | ||
| import java.io.FileOutputStream | ||
| import java.util.Locale.ENGLISH | ||
| import kotlin.io.path.Path | ||
| import kotlin.io.path.createDirectories | ||
|
|
||
| /** | ||
| * Has the ability to take a screenshot of the current view displayed on the screen using the method [takeScreenshot]. | ||
|
|
@@ -45,38 +46,50 @@ object ScreenCaptor { | |
| screenshotNameSuffix: String = "", | ||
| screenshotFormat: ScreenshotFormat = PNG, | ||
| ): File { | ||
| if (!File(screenshotDirectory).exists()) { | ||
| if (!File(screenshotDirectory).isDirectory) { | ||
| Log.d(SCREENSHOT, "Creating directory $screenshotDirectory since it does not exist") | ||
| val screenshotDirsCreated = File(screenshotDirectory).mkdirs() | ||
| assert(screenshotDirsCreated) | ||
| } | ||
| ensureScreenshotDirectoryExists(screenshotDirectory) | ||
|
|
||
| val deviceName = MANUFACTURER.replaceWithUnderscore() + "_" + MODEL.replaceWithUnderscore() | ||
| val screenshotId = | ||
| "${screenshotName.toLowerCase(ENGLISH)}_${deviceName}_${SDK_INT}_$screenshotNameSuffix" | ||
| "${screenshotName.lowercase(ENGLISH)}_${deviceName}_${SDK_INT}_$screenshotNameSuffix" | ||
| return File("$screenshotDirectory/$screenshotId.${screenshotFormat.extension}") | ||
| } | ||
|
|
||
| private fun captureScreenshot( | ||
| activity: Activity, | ||
| screenshotFile: File, | ||
| screenshotQuality: ScreenshotQuality = BEST, | ||
| onSuccess: (Screenshot) -> Unit | ||
| onComplete: () -> Unit | ||
| ) { | ||
| val screenshotManager = ScreenshotManagerBuilder(activity) | ||
| .withCustomActionOrder(ScreenshotActionOrder.fallbacksFirst()) | ||
| .build() | ||
|
|
||
| screenshotManager.makeScreenshot() | ||
| .observe({ screenshot -> | ||
| val fileSaver = ScreenshotFileSaver.create( | ||
| compressFormat = Bitmap.CompressFormat.PNG, | ||
| compressQuality = screenshotQuality.value | ||
| ) | ||
| fileSaver.saveToFile(screenshotFile, screenshot) | ||
| try { | ||
| val fileSaver = ScreenshotFileSaver.create( | ||
| compressFormat = Bitmap.CompressFormat.PNG, | ||
| compressQuality = screenshotQuality.value | ||
| ) | ||
| fileSaver.saveToFile(screenshotFile, screenshot) | ||
| } finally { | ||
| onComplete() | ||
| } | ||
| }, { throwable -> | ||
| try { | ||
| onComplete() | ||
| } finally { | ||
| throw throwable | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| onSuccess.invoke(screenshot) | ||
| }, { throwable -> throw throwable }) | ||
| private fun releaseScreenshotIdlingResource(idlingResource: ScreenshotIdlingResource) { | ||
| idlingResource.setScreenshotCaptured() | ||
| IdlingRegistry.getInstance().unregister(idlingResource) | ||
| } | ||
|
|
||
| private fun captureScreenshot( | ||
|
|
@@ -156,33 +169,37 @@ object ScreenCaptor { | |
|
|
||
| val idlingResource = ScreenshotIdlingResource() | ||
| IdlingRegistry.getInstance().register(idlingResource) | ||
| val screenshotFile = getScreenshotFile( | ||
| screenshotDirectory = screenshotDirectory, | ||
| screenshotName = screenshotName, | ||
| screenshotNameSuffix = screenshotNameSuffix, | ||
| screenshotFormat = screenshotFormat | ||
| ) | ||
| activityScenario.onActivity { activity -> | ||
| ViewTreeMutator.Builder() | ||
| .addMutations(defaultGlobalMutations) | ||
| .addMutations(globalViewMutations) | ||
| .build() | ||
| .mutate(activity) | ||
| try { | ||
| val screenshotFile = getScreenshotFile( | ||
| screenshotDirectory = screenshotDirectory, | ||
| screenshotName = screenshotName, | ||
| screenshotNameSuffix = screenshotNameSuffix, | ||
| screenshotFormat = screenshotFormat | ||
| ) | ||
| activityScenario.onActivity { activity -> | ||
| ViewTreeMutator.Builder() | ||
| .addMutations(defaultGlobalMutations) | ||
| .addMutations(globalViewMutations) | ||
| .build() | ||
| .mutate(activity) | ||
|
|
||
| captureScreenshot( | ||
| activity, | ||
| screenshotFile, | ||
| screenshotQuality | ||
| ) { screenshot -> | ||
| idlingResource.setScreenshotCaptured() | ||
| IdlingRegistry.getInstance().unregister(idlingResource) | ||
| captureScreenshot( | ||
| activity, | ||
| screenshotFile, | ||
| screenshotQuality | ||
| ) { | ||
| releaseScreenshotIdlingResource(idlingResource) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| viewMutations.forEach { viewMutation -> | ||
| with(viewMutation) { | ||
| getViewInteraction().perform(getRestoreAction()) | ||
| viewMutations.forEach { viewMutation -> | ||
| with(viewMutation) { | ||
| getViewInteraction().perform(getRestoreAction()) | ||
| } | ||
| } | ||
| } catch (error: Throwable) { | ||
| releaseScreenshotIdlingResource(idlingResource) | ||
| throw error | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -265,6 +282,31 @@ object ScreenCaptor { | |
| } | ||
| } | ||
|
|
||
| internal fun ensureScreenshotDirectoryExists( | ||
| screenshotDirectory: String, | ||
| createDirectories: (java.nio.file.Path) -> Unit = { it.createDirectories() } | ||
| ) { | ||
| val directory = File(screenshotDirectory) | ||
| if (directory.isDirectory) { | ||
| return | ||
| } | ||
|
|
||
| try { | ||
| createDirectories(Path(screenshotDirectory)) | ||
| } catch (e: Exception) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. which exceptions might the try block throw? Should we just let it bubble up
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Usually a |
||
| throw IllegalStateException( | ||
| "Failed to create screenshot directory for path: $screenshotDirectory", | ||
| e | ||
| ) | ||
| } | ||
|
|
||
| if (!directory.isDirectory) { | ||
| throw IllegalStateException( | ||
| "Failed to create screenshot directory for path: $screenshotDirectory" | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| private fun String.replaceWithUnderscore(): String { | ||
| return lowercase(ENGLISH).replace(" ", "_") | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android"> | ||
| <application android:theme="@style/Theme.AppCompat"> | ||
| <activity android:name="androidx.appcompat.app.AppCompatActivity" /> | ||
| </application> | ||
| </manifest> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| package com.wealthfront.screencaptor | ||
|
|
||
| import androidx.appcompat.app.AppCompatActivity | ||
| import androidx.test.core.app.ActivityScenario | ||
| import androidx.test.espresso.IdlingRegistry | ||
| import com.google.common.truth.Truth.assertThat | ||
| import org.junit.After | ||
| import org.junit.Assert.assertThrows | ||
| import org.junit.Rule | ||
| import org.junit.Test | ||
| import org.junit.rules.TemporaryFolder | ||
| import org.junit.runner.RunWith | ||
| import org.robolectric.RobolectricTestRunner | ||
| import java.io.File | ||
|
|
||
| @RunWith(RobolectricTestRunner::class) | ||
| class ScreenCaptorTest { | ||
|
|
||
| @get:Rule | ||
| val temporaryFolder = TemporaryFolder() | ||
|
|
||
| @After | ||
| fun unregisterIdlingResources() { | ||
| IdlingRegistry.getInstance().resources.toList().forEach { resource -> | ||
| IdlingRegistry.getInstance().unregister(resource) | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| fun takeScreenshot_releasesIdlingResourceAndRethrows_whenDirectoryCreationFails() { | ||
| val blockingFile = temporaryFolder.newFile("not-a-directory") | ||
| val screenshotDirectory = File(blockingFile, "screenshots").absolutePath | ||
| val scenario = ActivityScenario.launch(AppCompatActivity::class.java) | ||
| scenario.use { scenario -> | ||
| val exception = assertThrows(IllegalStateException::class.java) { | ||
| ScreenCaptor.takeScreenshot( | ||
| activityScenario = scenario, | ||
| screenshotName = "test", | ||
| screenshotDirectory = screenshotDirectory, | ||
| ) | ||
| } | ||
|
|
||
| assertThat(exception).hasMessageThat().isEqualTo( | ||
| "Failed to create screenshot directory for path: $screenshotDirectory" | ||
| ) | ||
| assertThat(IdlingRegistry.getInstance().resources).isEmpty() | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| package com.wealthfront.screencaptor | ||
|
|
||
| import com.google.common.truth.Truth.assertThat | ||
| import org.junit.Assert.assertThrows | ||
| import org.junit.Rule | ||
| import org.junit.Test | ||
| import org.junit.rules.TemporaryFolder | ||
| import java.io.File | ||
| import java.io.IOException | ||
|
|
||
| class ScreenshotDirectoryTest { | ||
|
|
||
| @get:Rule | ||
| val temporaryFolder = TemporaryFolder() | ||
|
|
||
| @Test | ||
| fun ensureScreenshotDirectoryExists_createsMissingDirectory() { | ||
| val screenshotDirectory = File(temporaryFolder.root, "screenshots").absolutePath | ||
|
|
||
| ensureScreenshotDirectoryExists(screenshotDirectory) | ||
|
|
||
| assertThat(File(screenshotDirectory).isDirectory).isTrue() | ||
| } | ||
|
|
||
| @Test | ||
| fun ensureScreenshotDirectoryExists_doesNothingWhenDirectoryAlreadyExists() { | ||
| val screenshotDirectory = temporaryFolder.newFolder("screenshots").absolutePath | ||
|
|
||
| ensureScreenshotDirectoryExists(screenshotDirectory) | ||
|
|
||
| assertThat(File(screenshotDirectory).isDirectory).isTrue() | ||
| } | ||
|
|
||
| @Test | ||
| fun ensureScreenshotDirectoryExists_throwsIllegalStateException_whenDirectoryCannotBeCreated() { | ||
| val blockingFile = temporaryFolder.newFile("not-a-directory") | ||
| val screenshotDirectory = File(blockingFile, "screenshots").absolutePath | ||
|
|
||
| val exception = assertThrows(IllegalStateException::class.java) { | ||
| ensureScreenshotDirectoryExists(screenshotDirectory) | ||
| } | ||
|
|
||
| assertThat(exception).hasMessageThat().isEqualTo( | ||
| "Failed to create screenshot directory for path: $screenshotDirectory" | ||
| ) | ||
| } | ||
|
|
||
| @Test | ||
| fun ensureScreenshotDirectoryExists_wrapsCause_whenCreationThrows() { | ||
| val screenshotDirectory = File(temporaryFolder.root, "screenshots").absolutePath | ||
| val cause = IOException("disk full") | ||
|
|
||
| val exception = assertThrows(IllegalStateException::class.java) { | ||
| ensureScreenshotDirectoryExists(screenshotDirectory) { throw cause } | ||
| } | ||
|
|
||
| assertThat(exception).hasMessageThat().isEqualTo( | ||
| "Failed to create screenshot directory for path: $screenshotDirectory" | ||
| ) | ||
| assertThat(exception.cause).isSameInstanceAs(cause) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This more closely aligns with how we configure formatting rules in wf-android
https://www.jetbrains.com/help/idea/editorconfig.html
Without this my IDE was formatting with 4 spaces, which didn't align with the rest of the codebase