← Nesim Apps

Capturas de tela da App Store sem celular: Robolectric, um carregador de imagens falso e um navegador sem interface

Por Nesim Razon, desenvolvedor Android independente · 2026-11-16

#Notas de desenvolvimento#Ferramentas e calculadoras

Meus apps de iPhone compartilham as telas com as versões Android, escritas uma única vez em Compose. A App Store Connect pede capturas de 1206x2622 pixels, e eu trabalho em um notebook com Windows, sem um iPhone sobrando para isso e com uma quantidade limitada de minutos de build na nuvem que prefiro gastar em builds de verdade. Então deixo os testes unitários desenharem as telas. Veja como funciona, o que quebrou e o que essas imagens realmente são.

A ideia: deixar um teste unitário desenhar a tela

O Robolectric executa código Android na JVM do desktop e, com o modo gráfico nativo, consegue desenhar o Compose em um bitmap real. O Roborazzi salva esse bitmap como PNG. Assim, uma captura de tela vira um teste: preparo a tela, clico até o estado que quero, capturo o nó raiz e pronto. Sem emulador, sem celular, sem serviço de build.

Um único qualificador define o tamanho: w402dp-h874dp-xxhdpi. É uma tela de 402 por 874 dp com três pixels por dp, o que dá exatamente 1206x2622 pixels, o tamanho que a App Store Connect pede. Conferi os arquivos de saída em vez de confiar na aritmética.

Áreas seguras feitas à mão

A janela de um teste Android não tem entalhe, então o topo de cada tela ficaria embaixo de onde fica a Dynamic Island. No teste envolvo o app em uma caixa com 47 dp de espaçamento no topo, que representa a área segura do iPhone. Em um app acrescentei também 34 dp embaixo para o indicador de início, porque esse app tem uma barra de navegação inferior.

É um substituto aproximado, não uma medição. Só precisa fazer o layout parecer como seria no celular, sem nada escondido sob a ilha.

Imagens de rede que nunca carregam

Nesses testes o Robolectric não tem uma rede útil, então as capas de artigos e as imagens de satélite sairiam como caixas vazias. O Coil tem um FakeImageLoaderEngine para isso: registro imagens reais, guardadas como recursos de teste, para as URLs ou padrões de URL que o app pediria. Assim a captura mostra imagens reais, e não marcadores.

Os dados recebem o mesmo tratamento. No leitor de notícias, o feed do teste é uma cópia do feed real tirada do site no ar. No app do espaço, os dados diários são uma cópia do arquivo diário real. Não invento conteúdo para a página da loja; congelo conteúdo real.

Conteúdo ao vivo e laços infinitos

Algumas telas mostram coisas que mudam, como a posição atual da estação espacial. Para elas o teste passa um objeto de exemplo fixo, e a tela o desenha como se tivesse acabado de chegar.

Isso exigiu uma mudança no código do app. A tela ao vivo consultava em um laço, e um laço while(true) com uma espera dentro pode impedir que o waitForIdle do framework de testes chegue a retornar, porque o app nunca fica ocioso. Não esperei para ver se travava; adicionei de antemão um parâmetro de pré-visualização: se um exemplo é dado, a tela o mostra e não inicia o laço. É uma pequena costura de teste que também facilita pré-visualizar a tela.

Telas com WebView

O Robolectric não desenha um WebView. A tela de artigo e as páginas estáticas dos meus apps de notícias são WebViews, então a captura saía com um buraco onde a página deveria estar.

A solução tem duas metades. O teste escreve o HTML da página, montado pela mesma função compartilhada que o app usa, mais uma captura da tela sem a página: só a barra superior, com 111 dp ou 333 pixels de altura. Depois um script em Python desenha esse HTML no Edge sem interface, dentro de um iframe de 402 pixels de largura com escala 3x, e cola o resultado sob a barra. A página é desenhada por um motor de navegador de verdade, a barra pelo Compose, e as duas se encontram em uma linha fixa.

As armadilhas

Cada uma custou uma noite minha, ou parte de uma.

O que essas imagens são e o que não são

São renderizações do Android no tamanho de um iPhone. As fontes, a sensação da rolagem, as barras do sistema e alguns controles pequenos não são o que o iOS desenharia. Aceito isso para a página da loja porque a interface compartilhada é o mesmo código, mas não as chamo de capturas do iOS.

O comportamento real desses apps no iPhone eu verifiquei à parte, em um iPhone 7 com o app instalado pelo TestFlight. As capturas mostram as telas; o dispositivo real me disse se as telas funcionam.

Perguntas frequentes

Isso produz capturas reais do iOS?

Não. São renderizações Android da interface Compose compartilhada, no tamanho 1206x2622. O comportamento real no iPhone foi verificado à parte em um dispositivo pelo TestFlight.

Por que o tamanho é exatamente 1206x2622?

O teste usa uma janela de 402 por 874 dp em xxhdpi, que são três pixels por dp, então o resultado é de 1206 por 2622 pixels.

Por que não tirar as capturas em um iPhone real ou em um simulador?

Não tenho um dispositivo sobrando para isso nem um Mac, e quero guardar meus minutos de build na nuvem para builds de verdade. Os testes unitários rodam de graça no meu notebook e podem ser repetidos quando eu quiser.

Por que as telas com WebView são tratadas à parte?

O Robolectric não desenha WebViews, então o HTML da página é desenhado no Edge sem interface e colado sob uma barra superior desenhada pelo Compose.

Mais do blog