Skip to content

Commit

Permalink
feat: Add compose element screenshot (#847)
Browse files Browse the repository at this point in the history
  • Loading branch information
mykola-mokhnach authored Jan 13, 2023
1 parent e372401 commit 95bded7
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 74 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ In order to change between subdrivers use the [driver](#settings-api) setting. S
- getPageSource: The returned page source is retrieved from Compose and all elements there contain [Compose-specific](#compose-element-attributes) attributes.
- click, isDisplayed, isEnabled, clear, getText, sendKeys, getElementRect, getValue, isSelected: These commands should properly support compose elements.
- getAttribute: Accepts and returns Compose-specific element attributes. See [Compose Element Attributes](#compose-element-attributes) for the full list of supported Compose element attributes.
- getElementScreenshot: Fetches a screenshot of the given Compose element. Available since driver version *2.14.0*

Calling other driver element-specific APIs not listed above would most likely throw an exception as Compose and Espresso elements are being stored in completely separated internal caches and must not be mixed.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,17 @@

package io.appium.espressoserver.lib.handlers

import io.appium.espressoserver.lib.handlers.exceptions.AppiumException
import io.appium.espressoserver.lib.helpers.ScreenshotsHelper
import io.appium.espressoserver.lib.helpers.getNodeInteractionById
import io.appium.espressoserver.lib.helpers.takeComposeNodeScreenshot
import io.appium.espressoserver.lib.helpers.takeEspressoViewScreenshot
import io.appium.espressoserver.lib.model.AppiumParams
import io.appium.espressoserver.lib.model.EspressoElement

class ElementScreenshot : RequestHandler<AppiumParams, String> {

@Throws(AppiumException::class)
override fun handleInternal(params: AppiumParams): String {
val view = EspressoElement.getCachedViewStateById(params.elementId).view
return ScreenshotsHelper(view).screenshot
}
override fun handleEspresso(params: AppiumParams): String =
takeEspressoViewScreenshot(EspressoElement.getCachedViewStateById(params.elementId).view)

override fun handleCompose(params: AppiumParams): String =
takeComposeNodeScreenshot(getNodeInteractionById(params.elementId!!))
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,9 @@

package io.appium.espressoserver.lib.handlers

import io.appium.espressoserver.lib.handlers.exceptions.AppiumException
import io.appium.espressoserver.lib.helpers.ScreenshotsHelper
import io.appium.espressoserver.lib.helpers.takeScreenshot
import io.appium.espressoserver.lib.model.AppiumParams

class ScreenshotHandler : RequestHandler<AppiumParams, String> {

@Throws(AppiumException::class)
override fun handleInternal(params: AppiumParams): String {
return ScreenshotsHelper().screenshot
}
override fun handleInternal(params: AppiumParams): String = takeScreenshot()
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ package io.appium.espressoserver.lib.handlers.exceptions
import fi.iki.elonen.NanoHTTPD


class ScreenCaptureException(reason: String) : AppiumException(reason) {
class ScreenCaptureException : AppiumException {
constructor(reason: String) : super(reason)

constructor(reason: String, e: Throwable) : super(reason, e)

override fun error(): String {
return "unable to capture screen"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.appium.espressoserver.lib.helpers

import android.graphics.Bitmap
import android.util.Base64
import android.view.View
import androidx.compose.ui.graphics.asAndroidBitmap
import androidx.compose.ui.test.SemanticsNodeInteraction
import androidx.compose.ui.test.captureToImage

import java.io.ByteArrayOutputStream
import androidx.test.runner.screenshot.Screenshot
import io.appium.espressoserver.lib.handlers.exceptions.ScreenCaptureException

private fun encodeBitmap(bitmap: Bitmap?, targetNameSupplier: () -> String): String {
if (bitmap == null || bitmap.height == 0 || bitmap.width == 0) {
throw ScreenCaptureException("Cannot capture a shot of the ${targetNameSupplier()}. " +
"Make sure none of the currently visible " +
"views have FLAG_SECURE set and that it is possible to take a screenshot manually")
}
val outputStream = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
return Base64.encodeToString(outputStream.toByteArray(), Base64.NO_WRAP)
}

fun takeScreenshot(): String {
return encodeBitmap(Screenshot.capture().bitmap) { "current screen" }
}

fun takeEspressoViewScreenshot(view: View): String =
try {
encodeBitmap(Screenshot.capture(view).bitmap) { view.javaClass.name }
} catch (e: RuntimeException) {
throw ScreenCaptureException("Cannot take a screenshot of a view", e)
}

fun takeComposeNodeScreenshot(nodeInteraction: SemanticsNodeInteraction): String =
try {
encodeBitmap(
nodeInteraction.captureToImage().asAndroidBitmap()
) { nodeInteraction.fetchSemanticsNode().toString() }
} catch (e: RuntimeException) {
throw ScreenCaptureException("Cannot take a screenshot of a node", e)
}

This file was deleted.

0 comments on commit 95bded7

Please sign in to comment.