How do you implement object-oriented programming principles when building a scalable test automation framework?
Implement a small, framework-agnostic test runner that executes declarative test steps through interchangeable action objects. Use abstraction, encapsulation, composition, and polymorphism so new action types can be added without modifying the runner.
The function receives a list of test definitions and returns one result per test with its name, pass status, and failed step index, or null when all steps pass.
def run_tests(test_definitions):