square / spoon

Distributing instrumentation tests to all your Androids.
https://square.github.io/spoon/
Apache License 2.0
2.7k stars 476 forks source link

IllegalAccessException: Unable to create output dir: /storage/emulated/0/app_spoon-screenshots #484

Open C2H6O opened 7 years ago

C2H6O commented 7 years ago

Spoon fails since dir.mkdirs() returns false...

if(!dir.exists() && !dir.mkdirs()) {
    throw new IllegalAccessException("Unable to create output dir: " + dir.getAbsolutePath());
} else {
    Chmod.chmodPlusRWX(dir);
}

I've given WRITE_EXTERNAL_STORAGE permissions in the AndroidManifest.xml and also tried using the GrantPermissionRule, but it keeps failing at that same step. Any help would be greatly appreciated.

Caused by: java.lang.IllegalAccessException: Unable to create output dir: /storage/emulated/0/app_spoon-screenshots at com.squareup.spoon.Spoon.createDir(Spoon.java:269) at com.squareup.spoon.Spoon.createDir(Spoon.java:266) at com.squareup.spoon.Spoon.createDir(Spoon.java:266) at com.squareup.spoon.Spoon.filesDirectory(Spoon.java:231) at com.squareup.spoon.Spoon.obtainScreenshotDirectory(Spoon.java:147) at com.squareup.spoon.Spoon.screenshot(Spoon.java:80) ... 41 more

arazabishov commented 7 years ago

@C2H6O Have you checked permissions both in the androidTest and main source sets? Are you using maxSdkVersionattribute for the WRITE_EXTERNAL_STORAGE permission in any of those?

faruktoptas commented 6 years ago

@C2H6O It worked for me by adding both READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE permissions.

eric-grab commented 6 years ago

add the following to your test class:

@Rule public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.READ_EXTERNAL_STORAGE);
emartynov commented 6 years ago

I'm also in the same situation. Is it because of the new emulator?

Actually same on the device.

I have storage write permission in the test app. And I don't have "write storage" permission in the app.

I'm trying to use spoon screenshots with the Composer from Juno.

markcerqueira commented 6 years ago

I am seeing the same behavior and have tried a ton of things to get it working all to no avail. From my StackOverflow question:

I am setting up Spoon for Android UI Testing using the gradle-spoon-plugin using the Spoon 2.0.0 snapshot. My project is set up using Android Gradle plug-in 3.0.1.

When taking screenshots via spoonRule.screenshot(activity, "hello"), I get this RuntimeException:

java.lang.RuntimeException: Unable to create output dir: /storage/emulated/0/app_spoon-screenshots
at com.squareup.spoon.SpoonRule.createDir(SpoonRule.java:167)
at com.squareup.spoon.SpoonRule.createDir(SpoonRule.java:164)
at com.squareup.spoon.SpoonRule.createDir(SpoonRule.java:164)
at com.squareup.spoon.SpoonRule.obtainDirectory(SpoonRule.java:108)
at com.squareup.spoon.SpoonRule.screenshot(SpoonRule.java:66)

Things work fine if I run it on a Nexus 4 API 19 emulator but it does not work on a Pixel 2 API 27 emulator. Permissions have changed a bunch from 19 to 27 so this is not totally unexpected.

I've tried most of the advice currently available including adding a manifest in my androidTest directory that grants the read and write external storage (with and without the maxSdkVersion):

<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="tv.twitch.android.test">

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="18"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="18"/>

    <uses-sdk tools:overrideLibrary="android.support.test.uiautomator.v18"/>

</manifest>

I see these permissions being merged into my final manifest of my app's AndroidManifest (unsure how to check the test app's manifest) in both cases.

I've tried granting permissions via UIAutomator to both the app and test package:

val device = UiDevice.getInstance(getInstrumentation())
device.executeShellCommand("pm grant tv.twitch.android.test android.permission.READ_EXTERNAL_STORAGE")
device.executeShellCommand("pm grant tv.twitch.android.debug android.permission.READ_EXTERNAL_STORAGE")
device.executeShellCommand("pm grant tv.twitch.android.test android.permission.WRITE_EXTERNAL_STORAGE")
device.executeShellCommand("pm grant tv.twitch.android.debug android.permission.WRITE_EXTERNAL_STORAGE")

This outputs a Permission denied to Logcat and results in the same exception above.

If I try leveraging GrantPermissionRule like:

@get:Rule var runtimePermissionRule = GrantPermissionRule.grant(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE)

I get a different exception:

junit.framework.AssertionFailedError: Failed to grant permissions, see logcat for details
at junit.framework.Assert.fail(Assert.java:50)
at android.support.test.runner.permission.PermissionRequester.requestPermissions(PermissionRequester.java:110)
at android.support.test.rule.GrantPermissionRule$RequestPermissionStatement.evaluate(GrantPermissionRule.java:108)

GrantPermissionCallable: Permission: android.permission.WRITE_EXTERNAL_STORAGE cannot be granted!

Removing Manifest.permission.WRITE_EXTERNAL_STORAGE and leaving just the read one gets us back to the original exception: java.lang.RuntimeException: Unable to create output dir: /storage/emulated/0/app_spoon-screenshots

Running from within Android Studio or on the command-line using gradle-spoon-plugin doesn't affect any of the above.

Looking at the permissions my app and test app are granted in Settings, I see my app has Storage permission but my test app (tv.twitch.android.test) does not have any permissions requested.

I also tried using the Baristra library:

PermissionGranter.allowPermissionsIfNeeded(Manifest.permission.WRITE_EXTERNAL_STORAGE)

With no luck their either. I tried making my test app target SDK version 22 in hopes it would get write permissions.

<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="tv.twitch.android.test">

    <uses-sdk
        android:minSdkVersion="16"
        android:targetSdkVersion="22"
        tools:overrideLibrary="android.support.test.uiautomator.v18"/>

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

</manifest>

Via Gradle (./gradlew spoon) I tried configuring the gradle-spoon-plugin to request all permissions:

spoon {
    // Grant all runtime permissions during installation on Marshmallow and above devices.
    grantAll = true
}

With no luck. And I even tried using Spoon library version 1.3.1 to no avail as well.

tnainani commented 6 years ago

@markcerqueira any luck? I am facing same issues :(

markcerqueira commented 6 years ago

@tnainani No luck yet. Planning to reach out to my network to see if I can get some eyes and help on this. 🙏

tnainani commented 6 years ago

sigh! thanks for the reply. I'll try to see if I can fix this issue. This is really breaking our automation. BTW if I change target SDK to 22 everything works fine.

tnainani commented 6 years ago

@markcerqueira So I tried instructions here https://afterecho.uk/blog/granting-marshmallow-permissions-for-testing-flavoured-builds.html and it worked for me.

markcerqueira commented 6 years ago

Figured out my issue! My issue was caused by an external library merging in the maxSdkVersion with the WRITE_EXTERNAL_STORAGE permission. To get around this I removed the attribute and re-added the WRITE_EXTERNAL_STORAGE permission. This is only needed for my application manifest and not the test application manifest.

<!-- Strip away maxSdkVersion -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
                 tools:remove="android:maxSdkVersion"/>

<!-- Add the permission with no maxSdkVersion defined -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

Then you can build, grant permissions, and run the tests:

# Uninstall existing APKs and install our app APK and test APK
./gradlew uninstallAll installDebug installDebugAndroidTest

# List all APKs installed with adb shell 'pm list packages -f'
# Grant the app APK write and read to external storage permissions
adb shell pm grant gg.mark.debug android.permission.WRITE_EXTERNAL_STORAGE
adb shell pm grant gg.mark.debug android.permission.READ_EXTERNAL_STORAGE

export APK=build/outputs/apk/debug/debug.apk
export TEST_APK=build/outputs/apk/androidTest/debug/debug-androidTest.apk

# TEST_APK and APK are positional arguments so keep them in this order
# Disable GIF generation because it's slow
java -jar spoon-runner-2.0.0.jar --debug --disable-gif "$TEST_APK" "$APK"

The pm grant commands will fail if the permissions for your app are not set up properly. Make sure those commands succeed!

Thanks for the help @tnainani! 😄

trevjonez commented 5 years ago

similar issue shows up when targeting api 29. I suspect the scoped storage changes are the cause.

JonatanPlesko commented 4 years ago

Until this is fixed, you can use: android:requestLegacyExternalStorage="true"

Put it in the tag in AndroidManifest.xml. This will only work when targeting API 29.

I hope this issue is fixed until API 30 targeting becomes a requirement.

Mercandj commented 4 years ago

Indeed @JonatanPlesko, targeting API 30 is causing this issue on devices with Android 11.

My code: on the app build.gradle, spoon.grantAll = true and on the debug manifest:

<uses-permission
    android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    tools:ignore="ScopedStorage"
    tools:remove="android:maxSdkVersion" /> <!-- https://stackoverflow.com/q/49040071/12057504 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application android:requestLegacyExternalStorage="true" />

Thank you for this great tool!

iadcialim commented 3 years ago

Indeed @JonatanPlesko, targeting API 30 is causing this issue on devices with Android 11.

My code: on the app build.gradle, spoon.grantAll = true and on the debug manifest:

<uses-permission
    android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    tools:ignore="ScopedStorage"
    tools:remove="android:maxSdkVersion" /> <!-- https://stackoverflow.com/q/49040071/12057504 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application android:requestLegacyExternalStorage="true" />

The issue is still there when targeting API 30 even after following this

tl-madhulika-mitra commented 2 years ago

Yes this issue persists post API 29