Framework e melhores práticas de design de aplicativos Automation First

Um conceito que promove como os desenvolvedores podem projetar seus aplicativos para serem compatíveis com a automação desde o início – a perspectiva de um testador.

Introdução

Como testador de automação, passei incontáveis ​​horas lutando com aplicações que pareciam deliberadamente projetadas para resistir à automação. Seletores frAgile que quebram com pequenos ajustes na interface do usuário, componentes sem propriedades identificáveis ​​e fluxos de trabalho complexos ocultos por trás de estados pouco claros — essas são as frustrações diárias que enfrentamos.

Mas eis a questão: a maioria desses desafios pode ser evitada. Quando os desenvolvedores projetam aplicativos com a automação em mente desde o início, os testes se tornam mais rápidos, mais confiáveis ​​e significativamente mais fáceis de manter.

Não se trata apenas de facilitar nosso trabalho, mas sim de entregar software de maior qualidade mais rapidamente.

Este artigo compartilha recomendações práticas que todo desenvolvedor de aplicativos deve seguir para tornar seus aplicativos "prontos para automação".

Tornar os elementos identificáveis ​​e estáveis.

O problema:

Muitos aplicativos dependem exclusivamente de nomes de classes CSS ou IDs gerados que mudam a cada compilação. Frameworks de automação como Selenium, Playwright e Appium dependem de localizadores (identificadores únicos) para interagir com elementos.

O que os desenvolvedores devem fazer:

  • Adicione data-testid Atributos para elementos interativos (botões, campos de entrada, formulários, links)
  • Mantenha esses IDs estáveis ​​entre as versões — trate-os como APIs públicas.
  • Evite depender exclusivamente de nomes de classe dinâmicos ou posições XPath.

Exemplo real:

// ❌ BAD - Changes frequently 

<button className="btn-primary-v2 btn-submit-form"> 

Submit 

</button> 

// ✅ GOOD - Stable identifier for testers 

<button data-testid="form-submit-button" className="btn-primary-v2"> 

Submit 

</button>

Aceitar

Por que isso é importante:

Quando você adiciona data-testid, os testadores podem localizar elementos de forma confiável usando page.locator('[data-testid="form-submit-button"]') em dramaturgo ou find_element(By.CSS_SELECTOR, '[data-testid="form-submit-button"]') em Selenium.

Utilize HTML semântico e associações de rótulos adequadas.

O problema:

Generic <div> e <span> E elementos sem rótulos tornam praticamente impossível para os testadores entenderem a finalidade dos controles e para as ferramentas de acessibilidade funcionarem corretamente.

O que os desenvolvedores devem fazer:

  • Utilize elementos HTML semânticos: <button>, <input>, <label>, <form>
  • Associe rótulos a entradas usando <label for="inputId">
  • Uso aria-label or aria-labelledby para componentes complexos

Exemplo real:

// ❌ BAD - No semantic meaning 

<div onClick={() => setSelected(!selected)}> 

<span>Enable notifications</span> 

</div> 

 

// ✅ GOOD - Semantic and testable 

<label htmlFor="notifications-toggle"> 

<input  

id="notifications-toggle"  

type="checkbox"  

data-testid="notifications-toggle" 

/> 

Enable notifications 

</label>

Perspectiva do testador:

Com HTML semântico, posso usar seletores baseados em funções: page.locator('input[role=”checkbox”]') ou até mesmo getByRole(“checkbox”, { name: /enable notifications/i }) no Playwright—tornando os testes mais legíveis e resilientes a mudanças de estilo.

Implementar estados de espera explícitos e indicadores de carregamento.

O problema:

Aplicações que utilizam muito JavaScript carregam conteúdo dinamicamente. Os testadores frequentemente encontram testes instáveis ​​porque os elementos aparecem/desaparecem de forma assíncrona e eles não sabem quando a página está realmente "pronta".

O que os desenvolvedores devem fazer:

  • Uso data-testid para estados de carregamento:
    <div data-testid="loading-spinner"> 
  • Marque as seções de conteúdo com atributos de status: aria-busy="true" durante o carregamento
  • Certifique-se de que o aplicativo atinja um estado estável antes de responder às interações do usuário.
  • Forneça sinais explícitos de conclusão de carregamento.

Exemplo real:

// ❌ BAD - No semantic meaning 

<div onClick={() => setSelected(!selected)}> 

<span>Enable notifications</span> 

</div> 

 

// ✅ GOOD - Semantic and testable 

<label htmlFor="notifications-toggle"> 

<input  

id="notifications-toggle"  

type="checkbox"  

data-testid="notifications-toggle" 

/> 

Enable notifications 

</label>

Código de Automação do Testador (Dramaturgo):

// Wait for loading to complete 

await page.locator('[data-testid="loading-spinner"]').waitFor({ state: 'hidden' }); 

 

// Now interact with the list 

const userList = page.locator('[data-testid="user-list"]'); 

await expect(userList).toBeVisible();

Crie formulários com tratamento de entrada de dados intuitivo. 

O problema: 

Formulários complexos com estados de erro pouco claros, validações ocultas ou comportamentos de entrada não padronizados confundem os scripts de automação. 

O que os desenvolvedores devem fazer: 

  • Utilize elementos de formulário HTML padrão com atributos de nome claros. 
  • Forneça mensagens de erro explícitas com data-testid marcadores 
  • Garantir que a validação do formulário seja observável (e não falhas silenciosas). 
  • Incluir confirmações de sucesso 

Exemplo real: 

// ✅ GOOD - Clear form with observable states 

<form data-testid="login-form"> 

<div> 

<label htmlFor="email-input">Email:</label> 

<input 

id="email-input" 

name="email" 

type="email" 

data-testid="email-input" 

required 

/> 

{errors.email && ( 

<span data-testid="email-error" role="alert"> 

{errors.email} 

</span> 

)} 

</div> 

 

<div> 

<label htmlFor="password-input">Password:</label> 

<input 

id="password-input" 

name="password" 

type="password" 

data-testid="password-input" 

required 

/> 

</div> 

 

<button  

type="submit"  

data-testid="login-submit-button" 

disabled={isSubmitting} 

> 

{isSubmitting ? 'Logging in...' : 'Login'} 

</button> 

 

{submitSuccess && ( 

<div data-testid="login-success-message" role="status"> 

Login successful! Redirecting... 

</div> 

)} 

</form>

Código de automação do testador (Selenium):

from selenium import webdriver 

from selenium.webdriver.common.by import By 

from selenium.webdriver.support.ui import WebDriverWait 

from selenium.webdriver.support import expected_conditions as EC 

 

driver = webdriver.Chrome() 

driver.get("https://app.com/login") 

 

# Fill form 

driver.find_element(By.CSS_SELECTOR, '[data-testid="email-input"]').send_keys("test@example.com") 

driver.find_element(By.CSS_SELECTOR, '[data-testid="password-input"]').send_keys("password123") 

 

# Submit 

driver.find_element(By.CSS_SELECTOR, '[data-testid="login-submit-button"]').click() 

 

# Wait for success message 

WebDriverWait(driver, 10).until( 

EC.presence_of_element_located((By.CSS_SELECTOR, '[data-testid="login-success-message"]')) 

)

Torne as respostas da API previsíveis e consistentes.

O problema:

Aplicativos móveis e web que dependem de respostas de API precisam de estruturas de dados estáveis. Respostas de API inconsistentes ou imprevisíveis fazem com que os testes falhem aleatoriamente.

O que os desenvolvedores devem fazer:

  • Utilize uma estrutura JSON consistente em todos os endpoints.
  • Inclua identificadores únicos (IDs) em cada objeto.
  • Documentar contratos de API (OpenAPI/Swagger)
  • Retorna códigos de status HTTP e mensagens de erro relevantes.
  • Testar respostas da API durante o desenvolvimento

Exemplo real:

// ✅ GOOD - Predictable API response 

{ 

"success": true, 

"data": { 

"id": "user-123", 

"email": "test@example.com", 

"name": "John Doe", 

"created_at": "2026-03-01T10:00:00Z" 

}, 

"timestamp": "2026-03-22T14:30:00Z" 

} 

 

// ❌ BAD - Inconsistent response 

{ 

"status": "ok", 

"user": { 

"uid": "123", 

"email_address": "test@example.com" 

}, 

"meta": { "time": 1679574600 } 

}

Automação Móvel do Testador (Appium):

# With predictable API, testers can mock and validate 

from appium import webdriver 

 

driver = webdriver.Remote("http://localhost:4723/wd/hub", capabilities) 

 

# Mock the API response 

mock_response = { 

"success": True, 

"data": { 

"id": "user-123", 

"name": "Test User" 

} 

} 

 

# Now reliably access user data in the app 

user_name_element = driver.find_element("xpath", "//text[@value='Test User']")

Forneça configurações e modos fáceis de testar

O problema:

Funcionalidades de produção como análises, anúncios, rastreadores de terceiros e atrasos de rede podem interferir na execução dos testes.

O que os desenvolvedores devem fazer:

  • Forneça um modo de teste ou uma variável de ambiente.
  • Permitir a desativação de análises, anúncios e rastreamento em ambientes de teste.
  • Suporte para pular animações ou atrasos desnecessários.
  • Forneça endpoints de API para redefinir o estado do aplicativo para testes.

Exemplo real (React):

// ✅ GOOD - Test-friendly configuration 

const isTestMode = process.env.REACT_APP_TEST_MODE === 'true'; 

 

function App() { 

return ( 

<div> 

{!isTestMode && <Analytics />} 

{!isTestMode && <Ads />} 

<MainContent /> 

</div> 

); 

}

Variável de ambiente:

REACT_APP_TEST_MODE=true npm start

Expectativas de Automação de Documentos

O problema:

Os testadores dedicam tempo a fazer engenharia reversa de como interagir com um aplicativo, especialmente com componentes personalizados.

O que os desenvolvedores devem fazer:

  • ISO data-testid convenções de nomenclatura no seu README
  • Forneça um guia de testes/automação para funcionalidades complexas.
  • Compartilhar documentação de componentes (Storybook, etc.)
  • Liste todos os eventos e callbacks personalizados que os testadores devem conhecer.

Documentação de exemplo:

## Automation Testing Guide 

 

### Naming Conventions 

- Buttons: `data-testid="[feature]-[action]-button"` (e.g., `user-logout-button`) 

- Form inputs: `data-testid="[form]-[field]-input"` (e.g., `login-email-input`) 

- Lists: `data-testid="[list]-container"` and items as `[list]-item-[id]` 

 

### Key Components 

- **Modal**: `.modal-overlay` (hidden when closed), contains `data-testid="modal-close-button"` 

- **Dropdown**: Use semantic `<select>` or `aria-haspopup="listbox"` 

- **Date Picker**: Supports keyboard navigation and `aria-label` 

 

### Test Environment Setup 

Set `API_URL=http://localhost:3001/api` to use mock server

Principais conclusões para desenvolvedores

O que os testadores precisam O que os desenvolvedores devem fazer
Identificadores de elementos estáveis Use os atributos data-testid
Hierarquia de componentes clara Use HTML semântico
Estados de carregamento/pronto observáveis Forneça indicadores de carregamento com identificadores.
Comportamento de forma previsível Validação de documentos e utilização de elementos padrão
Respostas consistentes da API Siga os contratos de API e a estrutura do documento.
Ambiente favorável aos testes Modo de teste ativado, anúncios/análises desativados
Orientações claras sobre automação Documente suas expectativas de automação.

Conclusão

Criar aplicações prontas para automação não se trata de contornar limitações — trata-se de projetar aplicações corretamente desde o início.

Quando os desenvolvedores priorizam clareza, consistência e observabilidade, todos saem ganhando: os testadores escrevem testes mais rápidos e confiáveis; os usuários obtêm software de maior qualidade e toda a equipe trabalha com mais agilidade.

A boa notícia? A maioria dessas práticas já são boas práticas de engenharia de software. HTML semântico, APIs previsíveis e tratamento claro de erros beneficiam a acessibilidade, o desempenho e a experiência do usuário — e não apenas os testes automatizados.

Começar com data-testid Use atributos, HTML semântico, torne os estados de carregamento visíveis e documente seus componentes. Seus testadores de automação agradecerão e seu aplicativo se beneficiará disso.

Recursos e Ferramentas

Também recomendamos