diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000000..ba00b5efdf --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,48 @@ +name: Build Maestro for Dojo + +on: + push: + branches: + - main + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Set up JDK 8 + uses: actions/setup-java@v2 + with: + java-version: '8' + distribution: 'adopt' + + - name: Assemble APKs + run: ./gradlew :maestro-android:assembleAndroidTest :maestro-android:assemble + + - name: Install maestro-cli + run: ./gradlew :maestro-cli:installDist -q + + - name: Create GitHub Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: release-${{ github.run_id }} + release_name: Release ${{ github.run_id }} + draft: false + prerelease: false + + - name: Upload maestro-cli asset to GitHub release + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./maestro-cli/build/install/maestro/bin/maestro + asset_name: maestro + asset_content_type: application/octet-stream diff --git a/maestro-android/build.gradle b/maestro-android/build.gradle index 0e8d161e71..5722d9ce58 100644 --- a/maestro-android/build.gradle +++ b/maestro-android/build.gradle @@ -38,7 +38,7 @@ android { defaultConfig { applicationId "dev.mobile.maestro" - minSdk 21 + minSdk 24 targetSdk 31 versionCode 1 versionName "1.0" diff --git a/maestro-android/src/androidTest/java/dev/mobile/maestro/AccessibilityNodeInfoExt.kt b/maestro-android/src/androidTest/java/dev/mobile/maestro/AccessibilityNodeInfoExt.kt new file mode 100644 index 0000000000..19bba05b81 --- /dev/null +++ b/maestro-android/src/androidTest/java/dev/mobile/maestro/AccessibilityNodeInfoExt.kt @@ -0,0 +1,23 @@ +package dev.mobile.maestro + +import android.os.Build +import android.view.accessibility.AccessibilityNodeInfo + +object AccessibilityNodeInfoExt { + + /** + * Retrieves the hint text associated with this [android.view.accessibility.AccessibilityNodeInfo]. + * + * If the device API level is below 26 (Oreo), this function provides a fallback + * by returning an empty CharSequence instead. + * + * @return [CharSequence] representing the hint text or its fallback. + */ + fun AccessibilityNodeInfo.getHintOrFallback(): CharSequence { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + this.hintText + } else { + "" + } + } +} diff --git a/maestro-android/src/androidTest/java/dev/mobile/maestro/ViewHierarchy.kt b/maestro-android/src/androidTest/java/dev/mobile/maestro/ViewHierarchy.kt index 6632201b03..8979eec597 100644 --- a/maestro-android/src/androidTest/java/dev/mobile/maestro/ViewHierarchy.kt +++ b/maestro-android/src/androidTest/java/dev/mobile/maestro/ViewHierarchy.kt @@ -15,6 +15,7 @@ import android.widget.ListView import android.widget.TableLayout import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice +import dev.mobile.maestro.AccessibilityNodeInfoExt.getHintOrFallback import org.xmlpull.v1.XmlSerializer import java.io.IOException import java.io.OutputStream @@ -120,7 +121,7 @@ object ViewHierarchy { serializer.attribute("", "NAF", java.lang.Boolean.toString(true)) } serializer.attribute("", "index", Integer.toString(index)) - serializer.attribute("", "hintText", safeCharSeqToString(node.hintText)) + serializer.attribute("", "hintText", safeCharSeqToString(node.getHintOrFallback())) serializer.attribute("", "text", safeCharSeqToString(node.text)) serializer.attribute("", "resource-id", safeCharSeqToString(node.viewIdResourceName)) serializer.attribute("", "class", safeCharSeqToString(node.className)) @@ -276,4 +277,4 @@ object ViewHierarchy { } } -} \ No newline at end of file +} diff --git a/maestro-client/src/main/java/maestro/drivers/AndroidDriver.kt b/maestro-client/src/main/java/maestro/drivers/AndroidDriver.kt index b87c8ecc5c..ec5b5a0d29 100644 --- a/maestro-client/src/main/java/maestro/drivers/AndroidDriver.kt +++ b/maestro-client/src/main/java/maestro/drivers/AndroidDriver.kt @@ -88,9 +88,15 @@ class AndroidDriver( private fun startInstrumentationSession() { val startTime = System.currentTimeMillis() - val instrumentationCommand = "am instrument -w -m -e debug false " + - "-e class 'dev.mobile.maestro.MaestroDriverService#grpcServer' " + - "dev.mobile.maestro.test/androidx.test.runner.AndroidJUnitRunner &\n" + val apiLevel = getDeviceApiLevel() + + val instrumentationCommand = buildString { + append("am instrument -w ") + if (apiLevel >= 26) append("-m ") + append("-e debug false ") + append("-e class 'dev.mobile.maestro.MaestroDriverService#grpcServer' ") + append("dev.mobile.maestro.test/androidx.test.runner.AndroidJUnitRunner &\n") + } while (System.currentTimeMillis() - startTime < getStartupTimeout()) { instrumentationSession = dadb.openShell(instrumentationCommand) @@ -105,6 +111,14 @@ class AndroidDriver( throw AndroidInstrumentationSetupFailure("Maestro instrumentation could not be initialized") } + private fun getDeviceApiLevel(): Int { + val response = dadb.openShell("getprop ro.build.version.sdk").readAll() + if (response.exitCode != 0) { + throw IOException("Failed to get device API level: ${response.errorOutput}") + } + return response.output.trim().toIntOrNull() ?: throw IOException("Invalid API level: ${response.output}") + } + private fun allocateForwarder() { PORT_TO_FORWARDER[hostPort]?.close() PORT_TO_ALLOCATION_POINT[hostPort]?.let { diff --git a/maestro-client/src/main/resources/maestro-app.apk b/maestro-client/src/main/resources/maestro-app.apk index 7fd0d8384d..a8b311fe23 100644 Binary files a/maestro-client/src/main/resources/maestro-app.apk and b/maestro-client/src/main/resources/maestro-app.apk differ diff --git a/maestro-client/src/main/resources/maestro-server.apk b/maestro-client/src/main/resources/maestro-server.apk index 227981913f..f41bd08a0a 100644 Binary files a/maestro-client/src/main/resources/maestro-server.apk and b/maestro-client/src/main/resources/maestro-server.apk differ