How do you apply SOLID principles in a test automation framework?
60-second answer
Single responsibility: each class has one reason to change, so page objects model pages, API clients call APIs, and tests verify behaviour. Open-closed: I extend behaviour without editing core code, like adding a new browser through a new factory branch or a new reporter through a listener. Liskov substitution: any WebDriver or any implementation of my ApiClient interface can be swapped without breaking tests. Interface segregation: small interfaces like Clickable or Searchable instead of one huge BasePage. Dependency inversion: tests depend on abstractions injected through fixtures or DI, not on concrete drivers or environments.
Explanation
Smells SOLID fixes in test code:
- A 3,000-line
CommonUtils(violates SRP). if (browser == ...)scattered everywhere (OCP).- Tests instantiating
new ChromeDriver()directly (DIP). BasePagewith 80 methods nobody fully uses (ISP).
Senior-level point: apply principles pragmatically. Test code should stay simple and readable; over-abstraction is also a smell.
Example
Payment tests depend on a PaymentGateway interface. Locally it's a WireMock-backed fake, in staging the real sandbox. Swapping is a config change, not a code change.
Sample code
public interface PaymentGateway {
PaymentResult charge(String orderId, BigDecimal amount);
}
public class SandboxGateway implements PaymentGateway { /* real sandbox API */ }
public class FakeGateway implements PaymentGateway { /* in-memory, deterministic */
public PaymentResult charge(String id, BigDecimal amt) { return PaymentResult.success(id); }
}
public class CheckoutTest extends BaseTest {
private final PaymentGateway gateway = Gateways.fromConfig(); // injected by config
}