[{"data":1,"prerenderedAt":81},["ShallowReactive",2],{"tutorial-test-end-to-end-in-flutter-su-device-farm-integration-test-screenshot-e-firebase-test-lab":3,"comments-tutorial-test-end-to-end-in-flutter-su-device-farm-integration-test-screenshot-e-firebase-test-lab":80},{"id":4,"title":5,"slug":6,"excerpt":7,"intro":8,"cover_image":9,"cover_remote_url":10,"cover_credit":11,"video_url":15,"video":16,"difficulty":20,"estimated_minutes":21,"flutter_version":22,"status":23,"published_at":24,"meta_title":25,"meta_description":26,"category":27,"author":31,"steps":34},119,"Test end-to-end in Flutter su device farm: integration_test, screenshot e Firebase Test Lab","test-end-to-end-in-flutter-su-device-farm-integration-test-screenshot-e-firebase-test-lab","Porta i test end-to-end Flutter oltre l'emulatore locale: build dell'APK di test, esecuzione su Firebase Test Lab, cattura di screenshot deterministici e report scaricabili dalla CI.","I test `integration_test` eseguiti sull'emulatore locale coprono solo una frazione dei dispositivi reali. Jank, permessi, densità di schermo e versioni Android differenti emergono solo su hardware vero.\n\nIn questo tutorial avanzato vedremo come:\n\n- strutturare una suite e2e **deterministica** con `IntegrationTestWidgetsFlutterBinding`;\n- compilare l'**androidTest APK** senza avviare un device;\n- eseguire la suite su **Firebase Test Lab** su una matrice di dispositivi;\n- catturare **screenshot** reali e scaricare i risultati;\n- orchestrare il tutto in CI con shard e report.\n\nPrerequisiti: conoscenza di `integration_test`, Gradle, gcloud CLI e un progetto Firebase con Blaze o quota gratuita Test Lab.","https:\u002F\u002Fflutter.it\u002Fstorage\u002Ftutorials\u002F5fedfd59-4f67-453b-aba9-2af78cbd9a34.jpg","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1638660354186-36645616cbed?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzA2NTJ8MHwxfHJhbmRvbXx8fHx8fHx8fDE3OTE1MjAzOTJ8&ixlib=rb-4.1.0&q=80&w=1080",{"name":12,"author_url":13,"photo_url":14},"Darío Bonzi","https:\u002F\u002Funsplash.com\u002F@dariobonzi39","https:\u002F\u002Funsplash.com\u002Fphotos\u002Fa-combine-of-grain-being-loaded-onto-a-truck-iALJu5cDR3I","https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=lOtlP646dqk",{"youtube_id":17,"duration_seconds":18,"published_at":19},"lOtlP646dqk",140,"2026-10-09T11:37:27+00:00","advanced",55,"3.x","published","2026-10-09T04:33:13+00:00","Test e2e Flutter su Firebase Test Lab","Esegui i test integration_test di Flutter su device reali con Firebase Test Lab: APK di test, screenshot, matrice dispositivi e integrazione in CI.",{"id":28,"name":29,"slug":30},3,"Best practice","best-practice",{"id":32,"name":33},1,"Flutter Bot",[35,43,50,57,65,73],{"id":36,"position":32,"title":37,"body":38,"code_snippet":39,"code_language":40,"expected_result":41,"demo_url":42,"video_url":42},796,"Preparare una suite e2e deterministica","Crea `integration_test\u002Fapp_test.dart`. Su device reali il tempo è variabile: evita `pumpAndSettle()` infiniti e usa attese condizionate su un *finder*.\n\nPunti chiave:\n\n- `IntegrationTestWidgetsFlutterBinding.ensureInitialized()` abilita il canale verso il driver;\n- `binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive` evita che le animazioni infinite blocchino `pumpAndSettle`;\n- disattiva animazioni non necessarie tramite flag di build (`--dart-define`).","import 'package:flutter_test\u002Fflutter_test.dart';\nimport 'package:integration_test\u002Fintegration_test.dart';\nimport 'package:myapp\u002Fmain.dart' as app;\n\nFuture\u003Cvoid> waitFor(WidgetTester t, Finder f,\n    {Duration timeout = const Duration(seconds: 20)}) async {\n  final end = DateTime.now().add(timeout);\n  while (DateTime.now().isBefore(end)) {\n    await t.pump(const Duration(milliseconds: 100));\n    if (f.evaluate().isNotEmpty) return;\n  }\n  throw TestFailure('Timeout su: $f');\n}\n\nvoid main() {\n  final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();\n  binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive;\n\n  testWidgets('login e apertura dashboard', (tester) async {\n    app.main();\n    await waitFor(tester, find.byKey(const Key('email')));\n\n    await tester.enterText(find.byKey(const Key('email')), 'demo@test.it');\n    await tester.enterText(find.byKey(const Key('password')), 'secret123');\n    await tester.tap(find.byKey(const Key('submit')));\n\n    await waitFor(tester, find.byKey(const Key('dashboard')));\n    expect(find.text('Benvenuto'), findsOneWidget);\n  });\n}","dart","`flutter test integration_test\u002Fapp_test.dart -d \u003Cemulatore>` passa senza timeout casuali.",null,{"id":44,"position":45,"title":46,"body":47,"code_snippet":48,"code_language":40,"expected_result":49,"demo_url":42,"video_url":42},797,2,"Catturare screenshot reali dal device","Su Android servono due passaggi: `convertFlutterSurfaceToImage()` prima dello screenshot e il salvataggio lato host nel `testExecutable` del driver.\n\nCrea `test_driver\u002Fintegration_test.dart` per scrivere i PNG su disco quando esegui con `flutter drive`. Su Test Lab, invece, gli screenshot vanno salvati nella directory pubblica del device e raccolti dal runner.","\u002F\u002F integration_test\u002Fapp_test.dart (estratto)\nFuture\u003Cvoid> shot(IntegrationTestWidgetsFlutterBinding b,\n    WidgetTester t, String name) async {\n  if (!kIsWeb && Platform.isAndroid) {\n    await b.convertFlutterSurfaceToImage();\n    await t.pumpAndSettle();\n  }\n  await b.takeScreenshot(name);\n}\n\n\u002F\u002F test_driver\u002Fintegration_test.dart\nimport 'dart:io';\nimport 'package:integration_test\u002Fintegration_test_driver_extended.dart';\n\nFuture\u003Cvoid> main() => integrationDriver(\n      onScreenshot: (name, bytes, [_]) async {\n        final f = File('screenshots\u002F$name.png')..createSync(recursive: true);\n        f.writeAsBytesSync(bytes);\n        return true;\n      },\n    );","`flutter drive --driver=test_driver\u002Fintegration_test.dart --target=integration_test\u002Fapp_test.dart` produce i PNG in `screenshots\u002F`.",{"id":51,"position":28,"title":52,"body":53,"code_snippet":54,"code_language":55,"expected_result":56,"demo_url":42,"video_url":42},798,"Compilare APK app e APK di test senza device","Firebase Test Lab richiede due artefatti: l'APK dell'app (debug) e l'`androidTest` APK che contiene la suite.\n\nAssicurati che `android\u002Fapp\u002Fbuild.gradle` includa la dipendenza del runner e il `testInstrumentationRunner`. Poi compila con Gradle.","\u002F\u002F android\u002Fapp\u002Fbuild.gradle\nandroid {\n  defaultConfig {\n    testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n  }\n}\ndependencies {\n  androidTestImplementation 'androidx.test:runner:1.5.2'\n  androidTestImplementation 'androidx.test:rules:1.5.0'\n}","groovy","I comandi\n`flutter build apk --debug --dart-define=E2E=true`\n`pushd android && .\u002Fgradlew app:assembleDebugAndroidTest && popd`\ngenerano `app-debug.apk` e `app-debug-androidTest.apk`.",{"id":58,"position":59,"title":60,"body":61,"code_snippet":62,"code_language":63,"expected_result":64,"demo_url":42,"video_url":42},799,4,"Eseguire la matrice su Firebase Test Lab","Con `gcloud` lanci la suite su più dispositivi reali in parallelo. Usa `--directories-to-pull` per recuperare gli screenshot e `--timeout` per evitare costi fuori controllo.\n\nSuggerimenti:\n\n- `--num-flaky-test-attempts 1` distingue i flake dai fallimenti reali;\n- `--results-bucket` centralizza i report in un bucket GCS;\n- includi almeno un device low-end per far emergere il jank.","gcloud firebase test android run \\\n  --type instrumentation \\\n  --app build\u002Fapp\u002Foutputs\u002Fflutter-apk\u002Fapp-debug.apk \\\n  --test build\u002Fapp\u002Foutputs\u002Fapk\u002FandroidTest\u002Fdebug\u002Fapp-debug-androidTest.apk \\\n  --device model=redfin,version=30,locale=it,orientation=portrait \\\n  --device model=Pixel2.arm,version=28,locale=it,orientation=portrait \\\n  --timeout 10m \\\n  --num-flaky-test-attempts 1 \\\n  --directories-to-pull \u002Fsdcard\u002FDownload \\\n  --results-bucket gs:\u002F\u002Fmy-e2e-results \\\n  --results-dir run-$(date +%s)","bash","La console mostra l'esito per ciascun device e un link ai report con video, logcat e performance metrics.",{"id":66,"position":67,"title":68,"body":69,"code_snippet":70,"code_language":71,"expected_result":72,"demo_url":42,"video_url":42},800,5,"Sharding e integrazione in GitHub Actions","Con suite lunghe conviene dividere i test in shard: ogni job compila una sola volta e lancia Test Lab con un filtro diverso (`--test-targets`), riducendo il tempo di wall-clock.\n\nIl workflow autentica l'account di servizio, compila gli APK e lancia il run. Il `exit code` di `gcloud` fa fallire la pipeline in caso di test rossi.","name: e2e\non: [workflow_dispatch, pull_request]\njobs:\n  testlab:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        shard: [login, checkout]\n    steps:\n      - uses: actions\u002Fcheckout@v4\n      - uses: subosito\u002Fflutter-action@v2\n        with: { flutter-version: '3.24.0' }\n      - run: flutter build apk --debug --dart-define=E2E=true\n      - run: cd android && .\u002Fgradlew app:assembleDebugAndroidTest\n      - uses: google-github-actions\u002Fauth@v2\n        with:\n          credentials_json: ${{ secrets.GCP_SA_KEY }}\n      - uses: google-github-actions\u002Fsetup-gcloud@v2\n      - run: |\n          gcloud firebase test android run \\\n            --type instrumentation \\\n            --app build\u002Fapp\u002Foutputs\u002Fflutter-apk\u002Fapp-debug.apk \\\n            --test build\u002Fapp\u002Foutputs\u002Fapk\u002FandroidTest\u002Fdebug\u002Fapp-debug-androidTest.apk \\\n            --device model=redfin,version=30 \\\n            --test-targets \"class com.example.${{ matrix.shard }}E2E\" \\\n            --timeout 10m","yaml","Due job paralleli, uno per shard, con fallimento della PR se un test e2e è rosso.",{"id":74,"position":75,"title":76,"body":77,"code_snippet":78,"code_language":40,"expected_result":79,"demo_url":42,"video_url":42},801,6,"Stabilizzare la suite: flake budget e dati di test","Una suite e2e è utile solo se affidabile. Regole pratiche:\n\n1. **Backend isolato**: punta a un ambiente di staging o a un mock server avviato via `--dart-define=API_BASE_URL`, mai alla produzione.\n2. **Seed deterministico**: ricrea l'utente di test all'inizio di ogni run tramite un endpoint dedicato; evita test che dipendono dall'ordine.\n3. **Niente `sleep` fissi**: sostituiscili con il `waitFor` del passo 1.\n4. **Flake budget**: traccia i retry; se un test supera il 2% di flakiness in una settimana, va riscritto o messo in quarantena, non ritentato all'infinito.\n5. **Piramide**: mantieni poche decine di scenari e2e sui flussi critici (login, pagamento, onboarding); il resto va coperto da widget e golden test, molto più veloci.","const baseUrl = String.fromEnvironment('API_BASE_URL',\n    defaultValue: 'https:\u002F\u002Fapi.example.com');\nconst isE2E = bool.fromEnvironment('E2E');\n\nvoid configureForTests() {\n  if (isE2E) {\n    timeDilation = 1.0; \u002F\u002F niente animazioni rallentate\n    \u002F\u002F disabilita analytics, crash reporting e banner promozionali\n  }\n}","Suite e2e ripetibile: tre run consecutivi sulla stessa matrice danno lo stesso esito.",[],1792316927000]