· Workflow · 9 min de leitura

Como gerar capturas de tela do App Store em apps Flutter: integration_test, golden tests e um caminho mais rápido (2026)

Como gerar capturas de tela do App Store em apps Flutter: integration_test, golden tests e um caminho mais rápido (2026)
TL;DR. O Flutter não oferece um comando nativo de "gerar minhas capturas de tela para a loja". As opções realistas são: capturar frames reais com integration_test + flutter drive, renderizar snapshots de widgets com golden tests (ótimos para regressão, mas um esforço a mais para assets de loja) ou envolver qualquer uma dessas abordagens em um pipeline Fastlane/Codemagic de CI. Nenhuma delas compõe o carrossel de marketing — legendas, molduras, fundos, cópia em 50 idiomas. Essa é uma etapa separada, e é onde o Mokbi se encaixa: ele compõe e localiza as capturas de tela que você capturou. Ele não as captura.

Se você desenvolve com Flutter e já publicou na App Store ou no Play Store, já se deparou com a lacuna: não existe um flutter screenshots. A Apple exige PNGs com pixels exatos em tamanhos como 1320 × 2868 (iPhone de 6,9 polegadas) e 2064 × 2752 (iPad de 13 polegadas); o Flutter entrega um engine de renderização e um harness de testes e deixa o pipeline da loja por sua conta. Este post é o mapa honesto do que realmente funciona em 2026, com código que você pode copiar, e uma linha clara sobre qual ferramenta faz qual trabalho. Se preferir começar pela visão geral específica do framework, veja Mokbi para desenvolvedores Flutter.

Uma distinção importante antes de tudo, porque poupa muita confusão: capturar um frame (levar o app a um estado e salvar um PNG do que está na tela) e compor uma captura de tela para a loja (esse frame dentro de um mockup de dispositivo, com um título, um fundo e cópia traduzida, exportado em todas as dimensões exigidas) são dois problemas diferentes. A maior parte das ferramentas Flutter abaixo resolve o primeiro. Nenhuma delas resolve o segundo.

Opção 1: integration_test + flutter drive (frames capturados reais)

É o caminho mais próximo que o Flutter tem de um fluxo oficial de capturas de tela. O pacote integration_test inicia seu app completo em um dispositivo real ou emulador, conduz-o com a mesma API de finder/tester dos widget tests e expõe binding.takeScreenshot(). O detalhe é que takeScreenshot retorna os bytes para o processo de teste — ele não grava um arquivo —, então você precisa do driver estendido para receber esses bytes na máquina host e salvá-los.

Primeiro, o teste. Note o procedimento específico para Android: convertFlutterSurfaceToImage() precisa ser chamado (e um frame bombeado) antes da captura, porque o Android renderiza o Flutter em uma SurfaceView que o framebuffer não consegue ler diretamente.

// integration_test/screenshot_test.dart
import 'dart:io' show Platform;
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:my_app/main.dart' as app;

void main() {
  final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();

  testWidgets('home + detail screenshots', (tester) async {
    app.main();
    await tester.pumpAndSettle();

    // Android needs the surface converted to an image before capture.
    if (Platform.isAndroid) {
      await binding.convertFlutterSurfaceToImage();
      await tester.pumpAndSettle();
    }

    await binding.takeScreenshot('01_home');

    await tester.tap(find.byKey(const Key('open-detail')));
    await tester.pumpAndSettle();
    await binding.takeScreenshot('02_detail');
  });
}

Depois o driver, que roda na máquina host e grava os bytes PNG que o teste envia de volta. Esse arquivo fica em test_driver/ e usa integration_test_driver_extended.dart (o integration_test_driver.dart simples não tem o hook onScreenshot):

// test_driver/integration_test.dart
import 'dart:io';
import 'package:integration_test/integration_test_driver_extended.dart';

Future<void> main() async {
  await integrationDriver(
    onScreenshot: (String name, List<int> bytes,
        [Map<String, Object?>? args]) async {
      final file = await File('screenshots/$name.png').create(recursive: true);
      file.writeAsBytesSync(bytes);
      return true; // false fails the test (e.g. on a diff mismatch)
    },
  );
}

É fundamental rodar com flutter drive, não com flutter test — somente o caminho do driver invoca o onScreenshot:

flutter drive \
  --driver=test_driver/integration_test.dart \
  --target=integration_test/screenshot_test.dart \
  -d emulator-5554

O que você obtém e o que não obtém:

  • Pixels reais do app. São capturas de tela dos seus widgets reais em execução, não uma re-renderização — fontes, tema, dados dinâmicos, tudo nativo.
  • Estados determinísticos. Você leva o app exatamente às telas que deseja e captura sob demanda.
  • Classe de dispositivo = o dispositivo em que você executa. A resolução da saída é a do emulador/dispositivo. Para atingir os exatos 1320 × 2868 da Apple, execute no simulador iOS com classe de 6,9 polegadas; para o conjunto de iPad de 13 polegadas, execute esse simulador. Não há um flag mágico de "redimensionar para o App Store".
  • Locale é responsabilidade sua. Você pode passar um locale para o app na inicialização e executar novamente por idioma, mas isso captura a UI do app naquele locale — não escreve as legendas de marketing.
  • O custo de manutenção é real. Os finders (find.byKey, toques) quebram quando a UI muda, como em qualquer teste de UI.

Opção 2: golden tests — e por que não são assets de loja

Os golden tests do Flutter (matchesGoldenFile, ou o nível mais alto testGoldens de pacotes como golden_toolkit) renderizam uma árvore de widgets em uma imagem e a comparam byte a byte com uma referência commitada. São excelentes, mas foram criados para um propósito diferente das capturas de tela da loja: regressão de pixels. A ideia é detectar o dia em que uma mudança de padding ou uma troca de fonte desloca silenciosamente a sua UI. O golden é o contrato; uma diferença é um teste falhado.

Dois motivos para não usar golden tests brutos como seu pipeline de loja:

  • Goldens renderizam widgets em um ambiente de teste headless, não na superfície real do dispositivo. Platform views, alguns shaders e qualquer coisa que depende do compositor GPU real podem renderizar de forma diferente (ou nem renderizar) do que o usuário vê. Para um contrato de regressão, tudo bem — você compara consigo mesmo. Para um asset de loja, você quer o frame real, que é o que a Opção 1 entrega.
  • A correspondência pixel-perfeita vai contra você. Os goldens padrão exigem uma correspondência exata, então diferenças de antialiasing e hinting de fontes entre máquinas causam falhas instáveis. Essa rigidez é correta para regressão e errada para "produzir uma boa imagem de marketing".

Dito isso, existe um caminho intermediário legítimo. Pacotes como golden_screenshot reutilizam deliberadamente o mecanismo de golden para saída de loja: usam testGoldens, envolvem sua tela em molduras reais de dispositivo (telefone/tablet/desktop), habilitam um comparador fuzzy que tolera uma pequena discrepância (~0,1%, configurável) em vez de exigir perfeição de pixels, e gravam arquivos em um layout de diretório no estilo Fastlane (metadata/<locale>/images/...). Se suas telas são puro Flutter (sem platform views) e você quer molduras ao redor de uma renderização sem precisar iniciar um emulador, é uma rota razoável e rápida. Apenas tenha clareza de que você está renderizando widgets, não capturando o app ao vivo, e que o "marketing" que você obtém é uma moldura de dispositivo — não títulos, fundos ou um carrossel multipainel.

Opção 3: Fastlane / Codemagic para Flutter

O Fastlane é automação iOS/Android, não específica do Flutter, mas se encaixa de duas formas. As ferramentas de captura próprias do Fastlane (snapshot para iOS via XCUITest, screengrab para Android via um harness Espresso/UI Automator) esperam testes de UI nativos, que apps Flutter geralmente não têm — então a maioria das equipes Flutter não usa o snapshot para capturar. Onde o Fastlane tem seu lugar é na entrega: deliver (iOS) e supply (Android) fazem upload dos seus PNGs já capturados e metadados para o App Store Connect / Play Console, e eles leem um diretório no formato Fastlane — que é exatamente o layout que o golden_screenshot grava e o layout que você também pode configurar o driver da Opção 1 para gravar.

Codemagic é o CI nativo do Flutter para o qual a maioria das equipes recorre aqui. O fluxo de CI realista é: executar o drive de capturas de tela do integration_test nos runners macOS/Android nas classes de dispositivo que você precisa, coletar os PNGs como artefatos de build e chamar deliver/supply para publicá-los. Isso é genuinamente útil quando as capturas divergem a cada lançamento e você publica com frequência. Para a comparação mais ampla sobre "automatizar vs no-code" — incluindo quando esse setup é excessivo — veja fastlane snapshot vs capturas de tela no-code para o App Store.

Opção 4: captura manual (ainda completamente válida)

Para muitos apps Flutter indie, a resposta honesta é: basta tirar as capturas de tela à mão uma vez por lançamento. Execute o app no iOS Simulator na classe de dispositivo correta (iPhone de 6,9 polegadas, iPad de 13 polegadas), navegue até cada tela e use o comando de captura do simulador — ele captura na resolução exata do dispositivo, que é o que o App Store Connect quer. No Android, o botão de câmera do emulador faz o mesmo. Tempo total para um app de cinco telas: talvez dez minutos.

A captura manual não tem custo de manutenção, não tem finders instáveis e não tem CI para supervisionar. Seu único ponto fraco é que nada se regenera automaticamente — se você publicar uma mudança de UI, você recaptura. Para uma equipe que lança algumas vezes por ano, essa troca é amplamente vantajosa. A automação das Opções 1 a 3 se paga exatamente quando "recapturar à mão" deixa de ser um trabalho de dez minutos porque você publica semanalmente e em muitos idiomas.

A parte que nenhuma das opções acima resolve: composição + localização em 50 idiomas

Aqui está o parágrafo honesto e delimitado. Todas as opções acima produzem frames brutos — a UI do seu app na resolução de um dispositivo. Os carrosséis do App Store e do Play Store que convertem não são frames brutos: são o frame dentro de um mockup de dispositivo, sobre um fundo, com um título curto, frequentemente em uma sequência multipainel, e repetidos em cada locale que você suporta. Essa etapa de composição é para o que o Mokbi serve. Você traz os frames que capturou (do integration_test, do golden_screenshot, do simulador, de onde for), os importa para um editor no navegador, adiciona molduras de dispositivo, legendas e fundos, traduz as legendas para até 50 idiomas do App Store com praticamente um clique e exporta em lote em todos os tamanhos exigidos. O design é gratuito com visualização com marca d'água; exportação e publicação ilimitadas vêm com uma assinatura — Solo a €29.99/mo (1 app) ou Studio a €49.99/mo (até 5 apps), sem compra única. Para ser explícito sobre o limite: o Mokbi não captura as capturas de tela de origem — ele não executa seu app nem conduz seus widgets. A captura fica com as ferramentas Flutter acima; o Mokbi compõe e localiza o que essas ferramentas produzem.

Um fluxo de trabalho Flutter realista e combinado

  1. Capture os frames de origem. Escreva um drive de capturas de tela com integration_test (Opção 1) para as telas que você quer no carrossel — ou, se você publica raramente, apenas capture manualmente no simulador. Execute em um dispositivo da classe iPhone de 6,9 polegadas e em um dispositivo da classe iPad de 13 polegadas para ter as resoluções certas.
  2. (Opcional) Integre ao CI. Se a divergência é um problema real, execute o drive no Codemagic e colete os PNGs como artefatos (Opção 3). Se não for, pule esta etapa.
  3. Componha o carrossel de marketing. Importe os frames para o Mokbi, adicione molduras, legendas e fundos, construa a sequência multipainel. Esta é a etapa que as ferramentas Flutter não fazem.
  4. Localize e exporte. Traduza as legendas para os locales do App Store que você quer atingir com um clique, exporte em lote em todas as dimensões exigidas e, se quiser, coloque a saída em uma pasta do Fastlane para que o deliver/supply faça o upload.
  5. Na próxima mudança de UI, recapture e reabra. Execute o drive novamente (ou recapture manualmente), reabra o projeto salvo no Mokbi, troque os frames e reexporte. A composição e as traduções são preservadas.

Quando pular a automação completamente

Se você publica de um a três lançamentos por ano, montar um harness de capturas de tela com integration_test e um pipeline de CI vai custar mais horas do que jamais economizará. Capture manualmente no simulador, componha e localize uma vez e siga em frente. A automação das Opções 1 a 3 é para equipes em que a divergência das capturas é um custo recorrente — lançamentos frequentes, muitos locales ou vários apps em um portfólio. Adeque a maquinaria ao ritmo de lançamento, não ao que parece rigoroso.

Antes de exportar qualquer coisa, confira os alvos: obtenha as dimensões exatas de pixels no guia de tamanhos de captura de tela do App Store e as regras de formato (PNG/JPEG, RGB, sem canal alfa, 1 a 10 por classe de dispositivo) no guia de requisitos de captura de tela do App Store. A Apple rejeita dimensões fora do padrão sem tolerância alguma, então vale trinta segundos.

O que ler a seguir

Abrir o editor →