Ao longo de quase uma década e meia construindo e reconstruindo suites de automação, testemunhei um padrão (sem trocadilho) repetido à exaustão: projetos começam lindos, organizados, promissores. Seis meses depois, só um Frankenstein de código duplicado, lógica de negócio vazando para os testes e manutenção que consome 70% do tempo do time.

O problema raramente é a ferramenta (Cypress, Playwright, Selenium, o que vier amanhã). O problema é arquitetural. E a boa notícia é que os Design Patterns de teste bem aplicados são imortais. Eles transcendem linguagens e frameworks porque resolvem problemas humanos de organização, não técnicos de sintaxe.

Vamos ao que interessa: os padrões que realmente entregam valor em 2026, com exemplos práticos e armadilhas fatais.

  1. Page Object Model (POM) Clássico, Mas Nem Todo Mundo Sabe Usar

O POM é o avô dos patterns de automação. Todo mundo conhece, mas poucos aplicam corretamente. O princípio é simples: cada página ou componente da UI é representado por uma classe que expõe apenas ações de alto nível.

Exemplo do que NÃO fazer (anti-pattern visto semanalmente):

class LoginPage: username_input = "#username" password_input = "#password" login_button = "#login"

def type_username(self, text): driver.find_element(self.username_input).send_keys(text)

Isso não é um Page Object, é um repositório de seletores com métodos anêmicos. O verdadeiro POM encapsula comportamento, não elementos.

Exemplo correto:

class LoginPage: def __init__(self, page): self.page = page

def login_as(self, user_type): """Ação de alto nível que encapsula toda a sequência""" if user_type == "standard": self._fill_credentials("standard_user", "secret_sauce") elif user_type == "locked": self._fill_credentials("locked_user", "secret_sauce") self.click_login() return InventoryPage(self.page) def get_error_message(self): return self.page.locator("[data-test='error']").text_content() def _fill_credentials(self, username, password): self.page.fill("#user-name", username) self.page.fill("#password", password) def click_login(self): self.page.click("#login-button")

A regra de ouro: Um método de Page Object deve retornar outro Page Object (fluxo feliz) ou dados (extração de informação). Nunca expor elementos ou ações cruas.

  1. Test Data Factory: O Pattern Mais Subestimado

O assassino silencioso da produtividade: testes que dependem de dados hardcoded.

def test_login(): user = "john.doe@example.com" password = "Test@123"

class UserFactory: @staticmethod def standard_user(): return User( email = f"test+{uuid4()}@example.com", password = "P@ssw0rd!", role = "customer" )

@staticmethod def admin_user(): return User( email = f"admin+{uuid4()}@example.com", password = "Admin@456", role = "admin", permissions = ["read", "write", "delete"] )
  1. Builder Pattern Para Cenários de Dados Complexos

Quando sua entidade tem 15+ campos opcionais, o Builder impede a explosão de construtores.

class OrderBuilder: def __init__(self): self.order = {"customer_id": 1, "items": []}

def with_items(self, items): self.order["items"] = items return self def with_coupon(self, code): self.order["coupon_code"] = code return self def build(self): return self.order

order = (OrderBuilder() .with_items([product1, product2]) .with_coupon("BLACKFRIDAY") .build())

  1. Facade Pattern Orquestrando Múltiplos Page Objects

class CheckoutFacade: def __init__(self, page): self.cart = CartPage(page) self.checkout = CheckoutPage(page)

def complete_purchase(self, user, cart_items): self.cart.add_items(cart_items) self.checkout.fill_shipping(user.address) return self.confirmation.get_order_number()

def test_guest_checkout(): facade = CheckoutFacade(page) order_id = facade.complete_purchase(user, products) assert order_id is not None

  1. Strategy Pattern Lidando com Múltiplos Contextos

class AuthenticationStrategy(ABC): @abstractmethod def login(self, credentials): pass

class WebAuthStrategy(AuthenticationStrategy): def login(self, credentials): self.page.goto("/login") self.page.fill("#email", credentials.email)

class APIAuthStrategy(AuthenticationStrategy): def login(self, credentials): response = self.api_client.post("/auth/login", json=credentials) self.token = response.json()["token"]

def test_user_profile(auth_strategy): auth_strategy.login(test_user)

  1. Singleton Pattern (Com Moderação) Para Recursos Caros

class DriverManager: _instance = None def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls) cls._instance._initialize() return cls._instance

Cuidado: Singleton pode esconder problemas de paralelização. Prefira injeção de dependência quando possível.

  1. Repository Pattern Centralizando Seletores

class LoginRepository: USERNAME_INPUT = "input[data-test='username']" PASSWORD_INPUT = "input[data-test='password']" LOGIN_BUTTON = "input[data-test='login-button']"

@staticmethod def get_user_menu_item(user_name): return f"div.user-menu:has-text('{user_name}')"

Vantagem: Quando um dev renomeia data-test-id, você altera em UM lugar.

Conclusão: Padrões Não São Receita de Bolo

Aprendi da pior maneira: aplicar todos os padrões de uma vez é tão ruim quanto aplicar nenhum. O segredo é evolução incremental.

Comece com POM. Quando sentir dor de dados repetidos, adicione Factory. Quando os testes ficarem muito longos, introduza Facade.

A boa arquitetura não é imposta desde o dia 1: ela emerge para resolver dores reais do time. E a melhor métrica de sucesso é simples: quanto tempo leva para você adicionar um teste para uma nova funcionalidade? Se a resposta for "minutos", você acertou.

Agora vá e organize esse legado. Ou comece certo do zero. Seu eu do futuro (e o onboarding do próximo QA) vão te agradecer.

QA Sênior que já refatorou mais código de teste do que gostaria de admitir