-
Notifications
You must be signed in to change notification settings - Fork 9.7k
[instrumentation_adapter] enable Firebase Test Lab Android testing #1866
Changes from 56 commits
6e19bf1
40e6fb6
1339154
17b865f
799a698
f60918c
1566115
fed897d
1743e6d
724cca2
366c0dd
89c64a7
3cd9fa1
c2171fc
b81b2ee
a501aa9
d0edaad
e3b539e
6f39b81
900c764
f67af6a
c104d7e
7332e33
48886db
71c69bb
cc945bc
4392190
51abd9b
33f226a
24dd881
c1ac1e6
4323cae
e1e2694
1d1dc70
df085c2
830c137
5ddc3d1
8d087cf
a739395
4ee27a1
a7fa78c
8d8cbb6
d129b5e
5ca7974
ea1f47d
542359c
5622b44
1adc8e2
fa8690f
2524211
9901bf8
bc8160b
e99089f
5c93814
4d12729
6183ca3
1516264
44cb9da
5b462ac
4af2bbe
69e10e5
4c6ec4e
cdf97f4
6bfc0c1
e851a83
7a9fbe2
23abd93
91ba4aa
a269aff
a95a286
21c96e3
e14c5b5
58a93a7
6f61e3c
c797834
79bfe5c
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 |
|---|---|---|
| @@ -1,11 +1,24 @@ | ||
|
|
||
| FROM cirrusci/flutter:latest | ||
|
|
||
| RUN sudo apt-get update | ||
| RUN sudo apt-get install -y git wget curl unzip python lsb-release sudo apt-transport-https | ||
|
|
||
| # Add repo for gcloud sdk and install it | ||
| RUN echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | \ | ||
| sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list | ||
|
|
||
| RUN curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | \ | ||
| sudo apt-key --keyring /usr/share/keyrings/cloud.google.gpg add - | ||
|
|
||
| RUN sudo apt-get update && sudo apt-get install -y google-cloud-sdk && \ | ||
| gcloud config set core/disable_usage_reporting true && \ | ||
| gcloud config set component_manager/disable_update_check true | ||
|
|
||
| RUN yes | sdkmanager \ | ||
| "platforms;android-27" \ | ||
| "build-tools;27.0.3" \ | ||
| "extras;google;m2repository" \ | ||
| "extras;android;m2repository" \ | ||
| "system-images;android-21;default;armeabi-v7a" | ||
| "extras;android;m2repository" | ||
|
|
||
| RUN yes | sdkmanager --licenses |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| .DS_Store | ||
| .dart_tool/ | ||
|
|
||
| .packages | ||
| .pub/ | ||
|
|
||
| build/ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| # This file tracks properties of this Flutter project. | ||
| # Used by Flutter tool to assess capabilities and perform upgrades etc. | ||
| # | ||
| # This file should be version controlled and should not be manually edited. | ||
|
|
||
| version: | ||
| revision: 3374ee380b499d99c50ed6dfdd45510aa8318741 | ||
| channel: master | ||
|
|
||
| project_type: plugin |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| ## 0.0.1 | ||
|
|
||
| * Initial release |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| // Copyright 2019 The Chromium Authors. All rights reserved. | ||
| // | ||
| // Redistribution and use in source and binary forms, with or without | ||
| // modification, are permitted provided that the following conditions are | ||
| // met: | ||
| // | ||
| // * Redistributions of source code must retain the above copyright | ||
| // notice, this list of conditions and the following disclaimer. | ||
| // * Redistributions in binary form must reproduce the above | ||
| // copyright notice, this list of conditions and the following disclaimer | ||
| // in the documentation and/or other materials provided with the | ||
| // distribution. | ||
| // * Neither the name of Google Inc. nor the names of its | ||
| // contributors may be used to endorse or promote products derived from | ||
| // this software without specific prior written permission. | ||
| // | ||
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||
| // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||
| // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||
| // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||
| // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||
| // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||
| // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||
| // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
| // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| # instrumentation_test | ||
collinjackson marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| Adapts flutter_test results as Android instrumentation tests, making them usable for Firebase Test Lab and other Android CI providers. | ||
|
|
||
| iOS support is not available yet, but is planned in the future. | ||
|
|
||
| ## Usage | ||
|
|
||
| Use `InstrumentationTestFlutterBinding()` at the start of a test file. | ||
|
|
||
| ```dart | ||
| import 'package:instrumentation_test/instrumentation_test.dart'; | ||
| import '../test/package_info.dart' as test; | ||
| void main() { | ||
| InstrumentationTestFlutterBinding(); | ||
| testWidgets("failing test example", (WidgetTester tester) async { | ||
| expect(2 + 2, equals(5)); | ||
| }); | ||
| } | ||
| ``` | ||
|
|
||
| Use gradle commands to build an instrumentation test for Android. | ||
|
|
||
| ``` | ||
| pushd android | ||
| ./gradlew assembleAndroidTest | ||
| ./gradlew assembleDebug -Ptarget=<path_to_test>.dart | ||
| popd | ||
| ``` | ||
|
|
||
| Upload to Firebase Test Lab, making sure to replace <PATH_TO_KEY_FILE>, <PROJECT_NAME>, <RESULTS_BUCKET>, and <RESULTS_DIRECTORY> with your values. | ||
|
|
||
| ``` | ||
| gcloud auth activate-service-account --key-file=<PATH_TO_KEY_FILE> | ||
| gcloud --quiet config set project <PROJECT_NAME> | ||
| gcloud firebase test android run --type instrumentation \ | ||
| --app build/app/outputs/apk/debug/app-debug.apk \ | ||
| --test build/app/outputs/apk/androidTest/debug/app-debug-androidTest.apk\ | ||
| --timeout 2m \ | ||
| --results-bucket=<RESULTS_BUCKET> \ | ||
| --results-dir=<RESULTS_DIRECTORY> | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| *.iml | ||
| .gradle | ||
| /local.properties | ||
| /.idea/workspace.xml | ||
| /.idea/libraries | ||
| .DS_Store | ||
| /build | ||
| /captures |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| group 'com.example.instrumentation_test' | ||
| version '1.0-SNAPSHOT' | ||
|
|
||
| buildscript { | ||
| repositories { | ||
| google() | ||
| jcenter() | ||
| } | ||
|
|
||
| dependencies { | ||
| classpath 'com.android.tools.build:gradle:3.2.1' | ||
| } | ||
| } | ||
|
|
||
| rootProject.allprojects { | ||
| repositories { | ||
| google() | ||
| jcenter() | ||
| } | ||
| } | ||
|
|
||
| apply plugin: 'com.android.library' | ||
|
|
||
| android { | ||
| compileSdkVersion 28 | ||
|
|
||
| defaultConfig { | ||
| minSdkVersion 16 | ||
| testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" | ||
| } | ||
| lintOptions { | ||
| disable 'InvalidPackage' | ||
| } | ||
| dependencies { | ||
| api 'junit:junit:4.12' | ||
| api 'androidx.test:core:1.0.0' | ||
| api 'androidx.test:runner:1.1.1' | ||
| api 'androidx.test:rules:1.1.1' | ||
| api 'androidx.test.espresso:espresso-core:3.1.1' | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| org.gradle.jvmargs=-Xmx1536M | ||
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| rootProject.name = 'instrumentation_test' |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||
| package="com.example.instrumentation_test"> | ||
collinjackson marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| </manifest> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| package io.flutter.plugins.instrumentationtest; | ||
|
||
|
|
||
| import androidx.test.rule.ActivityTestRule; | ||
| import io.flutter.app.FlutterActivity; | ||
| import io.flutter.plugin.common.MethodCall; | ||
| import io.flutter.plugin.common.MethodChannel; | ||
| import io.flutter.plugin.common.MethodChannel.MethodCallHandler; | ||
| import io.flutter.plugin.common.MethodChannel.Result; | ||
| import io.flutter.view.FlutterView; | ||
| import java.util.Map; | ||
| import java.util.concurrent.CompletableFuture; | ||
| import java.util.concurrent.ExecutionException; | ||
| import org.junit.runner.Description; | ||
| import org.junit.runner.Runner; | ||
| import org.junit.runner.notification.Failure; | ||
| import org.junit.runner.notification.RunNotifier; | ||
|
|
||
| public class FlutterRunner extends Runner { | ||
|
|
||
| private static final String CHANNEL = "dev.flutter/InstrumentationTestFlutterBinding"; | ||
| CompletableFuture<Map<String, String>> testResults; | ||
|
|
||
| final Class activityClass; | ||
|
|
||
| public FlutterRunner(Class<?> klass) { | ||
| activityClass = klass; | ||
| ActivityTestRule<FlutterActivity> rule = new ActivityTestRule<>(activityClass); | ||
| FlutterActivity activity = rule.launchActivity(null); | ||
| FlutterView view = activity.getFlutterView(); | ||
|
||
| MethodChannel methodChannel = new MethodChannel(view, CHANNEL); | ||
| testResults = new CompletableFuture<>(); | ||
| methodChannel.setMethodCallHandler( | ||
| new MethodCallHandler() { | ||
| @Override | ||
| public void onMethodCall(MethodCall call, Result result) { | ||
| if (call.method.equals("testFinished")) { | ||
| Map<String, String> results = call.argument("results"); | ||
| testResults.complete(results); | ||
| result.success(null); | ||
| } else { | ||
| result.notImplemented(); | ||
| } | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| @Override | ||
| public Description getDescription() { | ||
| return Description.createTestDescription(activityClass, "Flutter Tests"); | ||
| } | ||
|
|
||
| @Override | ||
| public void run(RunNotifier notifier) { | ||
| Map<String, String> results = null; | ||
| try { | ||
| results = testResults.get(); | ||
| } catch (ExecutionException e) { | ||
| e.printStackTrace(); | ||
| } catch (InterruptedException e) { | ||
| e.printStackTrace(); | ||
| } | ||
|
|
||
| for (String name : results.keySet()) { | ||
| Description d = Description.createTestDescription(activityClass, name); | ||
| notifier.fireTestStarted(d); | ||
| String outcome = results.get(name); | ||
collinjackson marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if (outcome.equals("failed")) { | ||
| Exception dummyException = new Exception(outcome); | ||
| notifier.fireTestFailure(new Failure(d, dummyException)); | ||
| } | ||
| notifier.fireTestFinished(d); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| package com.example.instrumentationtest; | ||
|
|
||
| import io.flutter.plugin.common.MethodCall; | ||
| import io.flutter.plugin.common.MethodChannel; | ||
| import io.flutter.plugin.common.MethodChannel.MethodCallHandler; | ||
| import io.flutter.plugin.common.MethodChannel.Result; | ||
| import io.flutter.plugin.common.PluginRegistry.Registrar; | ||
|
|
||
| /** InstrumentationTestPlugin */ | ||
| public class InstrumentationTestPlugin implements MethodCallHandler { | ||
|
||
|
|
||
| private static final String CHANNEL = "dev.flutter/InstrumentationTestFlutterBinding"; | ||
|
|
||
| /** Plugin registration. */ | ||
| public static void registerWith(Registrar registrar) { | ||
| final MethodChannel channel = new MethodChannel(registrar.messenger(), CHANNEL); | ||
| channel.setMethodCallHandler(new InstrumentationTestPlugin()); | ||
| } | ||
|
|
||
| @Override | ||
| public void onMethodCall(MethodCall call, Result result) { | ||
| if (call.method.equals("testFinished")) { | ||
| Map<String, String> results = call.argument("results"); | ||
| FlutterRunner.completeTestResults(results); | ||
| result.success(null); | ||
| } else { | ||
| result.notImplemented(); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| import 'package:flutter_test/flutter_test.dart'; | ||
| import 'package:flutter/foundation.dart'; | ||
| import 'package:flutter/services.dart'; | ||
|
|
||
| /// A subclass of [LiveTestWidgetsFlutterBinding] that reports tests results | ||
| /// on a channel to adapt them to native instrumentation test format. | ||
| class InstrumentationTestFlutterBinding extends LiveTestWidgetsFlutterBinding { | ||
| InstrumentationTestFlutterBinding() { | ||
| tearDownAll(() { | ||
| _channel.invokeMethod<void>('testFinished', <String, dynamic>{'results': _results}); | ||
| }); | ||
| } | ||
| static const MethodChannel _channel = | ||
| MethodChannel('dev.flutter/InstrumentationTestFlutterBinding'); | ||
|
|
||
| static Map<String, String> _results = <String, String>{}; | ||
|
|
||
| @override | ||
| Future<void> runTest(Future<void> testBody(), VoidCallback invariantTester, | ||
| {String description = '', Duration timeout}) async { | ||
| // TODO(jackson): Report the results individually instead of all at once | ||
collinjackson marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| // See https://github.com/flutter/flutter/issues/38985 | ||
| reportTestException = | ||
| (FlutterErrorDetails details, String testDescription) { | ||
| _results[description] = 'failed'; | ||
| }; | ||
| await super.runTest(testBody, invariantTester, | ||
| description: description, timeout: timeout); | ||
| _results[description] ??= 'success'; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| name: instrumentation_test | ||
| description: Runs tests that use the flutter_test API as platform native instrumentation tests. | ||
| version: 0.0.1 | ||
| author: | ||
| homepage: | ||
|
|
||
| environment: | ||
| sdk: ">=2.1.0 <3.0.0" | ||
|
|
||
| dependencies: | ||
| flutter: | ||
| sdk: flutter | ||
| flutter_test: | ||
| sdk: flutter | ||
|
|
||
| flutter: | ||
| plugin: | ||
| androidPackage: io.flutter.plugins.instrumentationtest | ||
| pluginClass: InstrumentationTestPlugin |
Uh oh!
There was an error while loading. Please reload this page.