5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

AWS Device farm を使って、Google Playストアでの見え方を確認してみようかともがいた話。

Last updated at Posted at 2015-08-06

私達の会社では、Playストア掲載用の画像はデザインの担当に作ってもらっています。
で、「特定の端末でだけ、見にくいよ〜」などのフィードバックが必要であればしたいところです。
ただ、そのためだけに確認用端末を買うわけにはいきませんね。

ということで、最近でたAWS Device farmを使って、撮ってみましょう、と思い立ったのです。

個人的に、へんなDSL使うのは好きではないのと、
そもそもテスト対象はアプリではないので、
uiautomatorを使います。

ざっくりいうと

  • Google Playで、スクリーンショットを撮りたいターゲットのページにはちゃんと遷移できているっぽい
  • スクリーンショットを撮って、保存すること自体はうまくいっているっぽい
  • AWS Device Farmのドキュメントに書いてあるパスにスクリーンショットを配置してみたが、AWS Device Farmのシステムがそれを拾ってくれない。
  • ドキュメントに書いてあるやり方でダメなら、自前でDropboxにあげてやろうじゃないの! としたが、 HTTP POSTのさいに勝手にコネクションリセットされてしまい、アップロードできなかった
  • Keep-Aliveをやめたら、アップロードできるようになった!
  • Google アカウントが設定されていない!( ゚д゚) ←イマココ

テストプロジェクトを作成

Android Studioで適当な新規プロジェクトを作ります。

本体(はろーわーるど)は、特に用事はないので、何もいじりません。

Dropboxにファイルはアップロードしようかなと思い、INTERNETパーミッションをつけるところだけはいじりました。

app/src/main/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="jp.co.crowdworks.awssample"
    android:sharedUserId="jp.co.crowdworks.awssample.uid">

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

	<application

uiautomator 2.0を使います。
あと、DropboxにあげるにはHTTPクライアントとして、今回はOkHttp使いました。

app/build.gradle

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.2.0'

    // Set this dependency to use JUnit 4 rules
    androidTestCompile 'com.android.support.test:rules:0.3'
    // Set this dependency to build and run Espresso tests
    androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2'
    // Set this dependency to build and run UI Automator tests
    androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.1'

    // for uploading screenshots into Dropbox.
    androidTestCompile 'com.squareup.okhttp:okhttp:2.4.0'

}

テストを書きます。
注意点としては、uiautomator 2.0は InstrumentationTestだということです。

app/src/androidTest/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="jp.co.crowdworks.awssample.test"
    android:sharedUserId="jp.co.crowdworks.awssample.uid">

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

</manifest>
ApplicationTest.java
public class ApplicationTest extends InstrumentationTestCase {
    private UiDevice mDevice;
    private Context mContext;

    @Override
    protected void setUp() throws Exception {
        super.setUp();
        // Initialize UiDevice instance
        mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
        mContext = getInstrumentation().getTargetContext();
    }

    private File ensureDir(File d){
        if(!d.exists()){
            d.mkdir();
        }
        return d;
    }

    public void test1PlayStoreScreenshot() {
        // Start from the home screen
        mDevice.pressHome();

        Intent intent = new Intent(Intent.ACTION_VIEW,
        	Uri.parse("market://details?id=jp.co.crowdworks.androidapp"));
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        mContext.startActivity(intent);

        try{Thread.sleep(10*1000);}catch(Exception e){}


        File screenshotDir = ensureDir(new File(mContext.getFilesDir().getParentFile(),"app_spoon-screenshots"));
        File screenshotDirCls = ensureDir(new File(screenshotDir,ApplicationTest.class.getName()));
        File screenshotDirMethod = ensureDir(new File(screenshotDirCls,"test1PlayStoreScreenshot"));

        File fileA = new File(screenshotDirMethod,"/a.png");
        File fileB = new File(screenshotDirMethod,"/b.png");

        mDevice.takeScreenshot(fileA);

        try{Thread.sleep(5*1000);}catch(Exception e){}
        
        try {
            mDevice.setOrientationLeft();
        } catch(Exception e){}

        try{Thread.sleep(5*1000);}catch(Exception e){}

        mDevice.takeScreenshot(fileB);

        try{Thread.sleep(5*1000);}catch(Exception e){}

        assertTrue(fileA.exists());
        assertTrue(fileB.exists());
    }

    public void test2UploadScreenshotFile() {
        File screenshotDir = ensureDir(new File(mContext.getFilesDir().getParentFile(),"app_spoon-screenshots"));
        File screenshotDirCls = ensureDir(new File(screenshotDir,ApplicationTest.class.getName()));
        File screenshotDirMethod = ensureDir(new File(screenshotDirCls,"test1PlayStoreScreenshot"));

        File fileA = new File(screenshotDirMethod,"/a.png");
        File fileB = new File(screenshotDirMethod,"/b.png");

        assertTrue(fileA.exists());
        assertTrue(fileB.exists());

        upload(fileA);
        upload(fileB);
    }

    private void upload(File file){
        final OkHttpClient client = new OkHttpClient();
        Request req = new Request.Builder()
                .url("https://api-content.dropbox.com/1/files_put/auto/"+ Build.MODEL+"_"+file.getName())
                .addHeader("Authorization", "Bearer XXXXXXXXXXXXXX(DropboxのAPIトークン)XXXXXXXXXXXXXX")
                .post(RequestBody.create(MediaType.parse("image/png"),file))
                .build();

        try {
            Response response = client.newCall(req).execute();
            assertTrue(response.isSuccessful());
        }
        catch(IOException e){
            Log.e("hoge","error",e);
            assertTrue(false);
        }
    }

    @Override
    protected void tearDown() throws Exception {
        mDevice.unfreezeRotation();
        super.tearDown();
    }
}

test1PlayStoreScreenshot()のなかで、やたらと深い階層にスクリーンショットを保存していますが、なぜこんなところに置いているかというと、
スクリーンショット_2015-08-06_16_27_20.png
こんなかんじでAWSのマニュアルに書いてあるからです。

AWSとしては、Spoonというライブラリで保存することを期待しているんだと思います。

Spoon
    public static File screenshot(Activity activity, String tag) {
        StackTraceElement testClass = findTestClassTraceElement(Thread.currentThread().getStackTrace());
        String className = testClass.getClassName().replaceAll("[^A-Za-z0-9._-]", "_");
        String methodName = testClass.getMethodName();

Spoon-Clientをみてみると、↑のような実装になっているので、
/data/data/アプリのパッケージ名/app_spoon-screenshots/テストパッケージ名.テストクラス名/テストメソッド名/ 配下にスクリーンショットを保存すれば良い、というわけです。

テストプロジェクトをビルド

ビルド
./gradlew clean assembleDebug assembleDebugAndroidTest

すると、成果物が、

  • app/build/outputs/apk/app-debug.apk
  • app/build/outputs/apk/app-debug-androidTest-unaligned.apk

みたいなかんじでできるかと思います。こいつをAWS Device Farmにアップロードして、テストを走らせます。

ローカルで試験が通ることを確認する

実機をUSB接続し、

ローカルで動作確認
adb install -r app/build/outputs/apk/app-debug.apk
adb install -r app/build/outputs/apk/app-debug-androidTest-unaligned.apk
 
adb shell am instrument -w jp.co.crowdworks.awssample.test/android.support.test.runner.AndroidJUnitRunner

を実行すると、スクリーンショットが縦・横あわせてDropboxにアップロードされることを確認。

AWS Device FarmにAPKを上げて実行する

ローカル確認がうまくいったところで、AWSに上げます。

スクリーンショット 2015-08-06 12.59.17.png
app-debug.apkをアップします。
   ↓
スクリーンショット 2015-08-06 12.59.52.png
uiautomator 2.0はInstrumentationTestなので、
Instrumentationを選択してapp-debug-androidTest-unaligned.apkをアップします。
   ↓
スクリーンショット 2015-08-06 12.59.58.png
デバイスを選択する画面ですが、面倒なのでデフォルトのままとします。
   ↓
あとは、「Next step」を連打。
 
 
 
さて、テストが始まります。

スクリーンショット 2015-08-06 13.01.19.png
 
 
→ test1PlayStoreScreenshot はテストが通る = スクリーンショットはちゃんととれている
→ test2UploadScreenshotFile はテストにFailする。logcatを見てみると、

logcat
08-06 00:29:47.445 23838 23852 E hoge    : error
08-06 00:29:47.445 23838 23852 E hoge    : java.io.IOException: unexpected end of stream on Connection{api-content.dropbox.com:443, proxy=DIRECT@ hostAddress=184.72.246.165 cipherSuite=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA protocol=http/1.1} (recycle count=0)
08-06 00:29:47.445 23838 23852 E hoge    : 	at com.squareup.okhttp.internal.http.HttpConnection.readResponse(HttpConnection.java:211)
08-06 00:29:47.445 23838 23852 E hoge    : 	at com.squareup.okhttp.internal.http.HttpTransport.readResponseHeaders(HttpTransport.java:80)
08-06 00:29:47.445 23838 23852 E hoge    : 	at com.squareup.okhttp.internal.http.HttpEngine.readNetworkResponse(HttpEngine.java:917)
08-06 00:29:47.445 23838 23852 E hoge    : 	at com.squareup.okhttp.internal.http.HttpEngine.access$300(HttpEngine.java:95)
08-06 00:29:47.445 23838 23852 E hoge    : 	at com.squareup.okhttp.internal.http.HttpEngine$NetworkInterceptorChain.proceed(HttpEngine.java:902)
08-06 00:29:47.445 23838 23852 E hoge    : 	at com.squareup.okhttp.internal.http.HttpEngine.readResponse(HttpEngine.java:760)
08-06 00:29:47.445 23838 23852 E hoge    : 	at com.squareup.okhttp.Call.getResponse(Call.java:274)
08-06 00:29:47.445 23838 23852 E hoge    : 	at com.squareup.okhttp.Call$ApplicationInterceptorChain.proceed(Call.java:230)
08-06 00:29:47.445 23838 23852 E hoge    : 	at com.squareup.okhttp.Call.getResponseWithInterceptorChain(Call.java:201)
08-06 00:29:47.445 23838 23852 E hoge    : 	at com.squareup.okhttp.Call.execute(Call.java:81)
08-06 00:29:47.445 23838 23852 E hoge    : 	at jp.co.crowdworks.flavorsample.ApplicationTest.upload(ApplicationTest.java:113)
08-06 00:29:47.445 23838 23852 E hoge    : 	at jp.co.crowdworks.flavorsample.ApplicationTest.test2UploadScreenshotFile(ApplicationTest.java:100)
08-06 00:29:47.445 23838 23852 E hoge    : 	at java.lang.reflect.Method.invokeNative(Native Method)
08-06 00:29:47.445 23838 23852 E hoge    : 	at java.lang.reflect.Method.invoke(Method.java:515)
08-06 00:29:47.445 23838 23852 E hoge    : 	at android.test.InstrumentationTestCase.runMethod(InstrumentationTestCase.java:214)
08-06 00:29:47.445 23838 23852 E hoge    : 	at android.test.InstrumentationTestCase.runTest(InstrumentationTestCase.java:199)
08-06 00:29:47.445 23838 23852 E hoge    : 	at junit.framework.TestCase.runBare(TestCase.java:134)
08-06 00:29:47.445 23838 23852 E hoge    : 	at junit.framework.TestResult$1.protect(TestResult.java:115)
08-06 00:29:47.445 23838 23852 E hoge    : 	at junit.framework.TestResult.runProtected(TestResult.java:133)
08-06 00:29:47.445 23838 23852 E hoge    : 	at android.support.test.internal.runner.junit3.DelegatingTestResult.runProtected(DelegatingTestResult.java:90)
08-06 00:29:47.445 23838 23852 E hoge    : 	at junit.framework.TestResult.run(TestResult.java:118)
08-06 00:29:47.445 23838 23852 E hoge    : 	at android.support.test.internal.runner.junit3.AndroidTestResult.run(AndroidTestResult.java:49)
08-06 00:29:47.445 23838 23852 E hoge    : 	at junit.framework.TestCase.run(TestCase.java:124)
08-06 00:29:47.445 23838 23852 E hoge    : 	at android.support.test.internal.runner.junit3.NonLeakyTestSuite$NonLeakyTest.run(NonLeakyTestSuite.java:63)
08-06 00:29:47.445 23838 23852 E hoge    : 	at junit.framework.TestSuite.runTest(TestSuite.java:243)
08-06 00:29:47.445 23838 23852 E hoge    : 	at junit.framework.TestSuite.run(TestSuite.java:238)
08-06 00:29:47.445 23838 23852 E hoge    : 	at android.support.test.internal.runner.junit3.DelegatingTestSuite.run(DelegatingTestSuite.java:103)
08-06 00:29:47.445 23838 23852 E hoge    : 	at android.support.test.internal.runner.junit3.AndroidTestSuite.run(AndroidTestSuite.java:63)
08-06 00:29:47.445 23838 23852 E hoge    : 	at android.support.test.internal.runner.junit3.JUnit38ClassRunner.run(JUnit38ClassRunner.java:90)
08-06 00:29:47.445 23838 23852 E hoge    : 	at org.junit.runners.Suite.runChild(Suite.java:128)
08-06 00:29:47.445 23838 23852 E hoge    : 	at org.junit.runners.Suite.runChild(Suite.java:27)
08-06 00:29:47.445 23838 23852 E hoge    : 	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
08-06 00:29:47.445 23838 23852 E hoge    : 	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
08-06 00:29:47.445 23838 23852 E hoge    : 	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
08-06 00:29:47.445 23838 23852 E hoge    : 	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
08-06 00:29:47.445 23838 23852 E hoge    : 	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
08-06 00:29:47.445 23838 23852 E hoge    : 	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
08-06 00:29:47.445 23838 23852 E hoge    : 	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
08-06 00:29:47.445 23838 23852 E hoge    : 	at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
08-06 00:29:47.445 23838 23852 E hoge    : 	at android.support.test.internal.runner.TestExecutor.execute(TestExecutor.java:54)
08-06 00:29:47.445 23838 23852 E hoge    : 	at android.support.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:228)
08-06 00:29:47.445 23838 23852 E hoge    : 	at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1729)
08-06 00:29:47.445 23838 23852 E hoge    : Caused by: java.io.EOFException: \n not found: size=0 content=...
08-06 00:29:47.445 23838 23852 E hoge    : 	at okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.java:200)
08-06 00:29:47.445 23838 23852 E hoge    : 	at com.squareup.okhttp.internal.http.HttpConnection.readResponse(HttpConnection.java:191)
08-06 00:29:47.445 23838 23852 E hoge    : 	... 41 more

UnexpectedなEOSと。

ぐぐってみると、こんな記事が引っかかりました。
http://d.hatena.ne.jp/knight_9999/20141119/1416365367

connection: close

ヘッダを付けてやれば治る。(もしくは、http.keepAlive=falseにすれば確実に治る?)と。

ということで

修正
    private void upload(File file){
        final OkHttpClient client = new OkHttpClient();
        Request req = new Request.Builder()
                .url("https://api-content.dropbox.com/1/files_put/auto/"+ Build.MODEL+"_"+file.getName())
                .addHeader("Authorization", "Bearer XXXXXXXXXXXXXX(DropboxのAPIトークン)XXXXXXXXXXXXXX"))
                .addHeader("connection", "close") //★追加
                .post(RequestBody.create(MediaType.parse("image/png"), file))
                .build();

とりあえずconnection:closeを追加してみました。
すると・・・

スクリーンショット 2015-08-26 14.16.15.png

キタ━━━━(゚∀゚)━━━━!!

となりました!

ただ・・・
SM-G900T_SM-G900T_a.png

∑(゚Д゚)ガーン

これはいろいろと大変そうだ。

P.S.

いちおう、AWSのフォーラムにも類似のissue報告はあげてみましたが、8/26現在、報告はないですね・・・。
https://forums.aws.amazon.com/thread.jspa?messageID=667834&#667834

5
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?