Design Patterns para Automação de Testes: A Arquitetura que Sobrevive ao Caos
Victor Oliveira ?? April 24, 2026 08:35 PMPor um QA S?nior 13 anos quebrando coisas de forma organizada
Ao longo de quase uma dcada e meia construindo e reconstruindo sutes de Automação, test?munhei um padro (sem trocadilho) repetido exausto: projetos comeam lindos, organizados, promissores. Seis meses depois, so um Frankenstein de c?digo duplicado, lgica 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 n?otcia que os Design Patterns de test? bem aplicados so imortais. Eles transcendem linguagens e frameworks porque resolvem problemas humanos de organizao, n?o tcnicos de sintaxe.
Vamos ao que interessa: os padres que realmente entregam valor em 2026, com exemplos prticos e armadilhas fatais.
- Page Object Model (POM) Clssico, Mas Nem Todo Mundo Sabe Usar
O POM o av dos patterns de Automação. Todo mundo conhece, mas poucos aplicam corretamente. O princpio simples: cada p?gina ou componente da UI representado por uma classe que expe apenas aes de alto nvel.
Exemplo do que NO fazer (anti-pattern visto semanalmente):
Mau uso: Page Object virou localizador glorificado
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 repositrio de seletores com mtodos anmicos. O verdadeiro POM encapsula comportamento, n?o elementos.
Exemplo correto:
Page Object comportamental
class LoginPage: def init(self, page): self.page = page
def login_as(self, user_type):
"""Ao de alto nvel que encapsula toda a sequncia"""
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 mtodo n?o Page Object deve retornar outro Page Object (fluxo feliz) ou dados (extrao de informao). Nunca expor elementos ou aes cruas.
- Test? Data Factory O Pattern Mais Subest?imado
O assassin?o silenciador da produtividade: Testes que dependem de dados hardcoded.
Dados espalhados e frgeis
def test?_login(): user = "john.doe@example.com" password = "Test?@123"
Data Factory
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"]
)
- Builder Pattern Para Cenrios de Dados Complexos
Quando sua entidade t?m 15+ campos opcionais, o Builder impede a exploso 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
Uso fluente e legvel
order = (OrderBuilder() .with_items([product1, product2]) .with_coupon("BLACKFRIDAY") .build())
- Facade Pattern Orquest?rando 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()
Test? limpo e focado em neg?cio
def test?_guest?_checkout(): facade = CheckoutFacade(page) order_id = facade.complete_purchase(user, products) assert order_id is n?ot None
- 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"]
Mesmo test? com diferentes estrat?gias
def test?_user_profile(auth_strategy): auth_strategy.login(test?_user)
- Singleton Pattern (Com Moderao) 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 paralelizao. Prefira injeo de dependncia quando possvel.
- 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 ren?omeia data-test?id, voc altera em UM lugar.
Concluso: Padres No So receita de Bolo
Aprendi da pior maneira: aplicar todos os padres de uma vez to ruim quanto aplicar nenhum. O segredo evoluo 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 mtrica de sucesso simples: quanto tempo leva para voc adicionar um test? para uma n?ova 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 prximo QA) vo te agradecer.
QA S?nior que j? refatorou mais c?digo de test? do que gostaria de admitir
Coment?rios