Tutorials / Framework Features
Shared lifecycle rules
As a project grows, the same setup and teardown logic ends up copy-pasted across every suite. A Rules subclass breaks that cycle — define the shared lifecycle once, attach it to any suite with a single parameter.
The problem: repeated teardown
In a browser project, every suite opens a driver and checks for JavaScript errors after each test. Without shared rules, that code lives in every suite:
@Suite() class LoginSuite: @beforeClass() def setup(self): self.driver = new_driver() @afterClass() def teardown(self): Browser.shutdown() @afterTest() def check_js_errors(self): errors = Browser.get_log("browser") if errors: raise AssertionError(str(errors)) @Suite() class CheckoutSuite: @beforeClass() def setup(self): self.driver = new_driver() # identical @afterClass() def teardown(self): Browser.shutdown() # identical @afterTest() def check_js_errors(self): ... # identical again
The solution: a Rules subclass
Move the shared logic into a Rules subclass, then reference it from each suite via rules=.
from test_junkie.rules import Rules class BrowserRules(Rules): def before_class(self): self.driver = new_driver() def after_class(self): Browser.shutdown() def after_test(self, **kwargs): logs = Browser.get_log("browser") errors = [m["message"] for m in logs if m["level"] == "SEVERE"] if errors: raise AssertionError(str(errors))
@Suite(rules=BrowserRules) class LoginSuite: @test() def test_valid_login(self): ... @Suite(rules=BrowserRules) class CheckoutSuite: @test() def test_add_to_cart(self): ... # both suites open/close the browser and check # JS errors after every test — one definition
rules=BrowserRules and its own @beforeClass/@afterClass. Rules hooks run first for before_class/before_test, and last for after_class/after_test. Use Rules for the shared part, and the suite's own hooks for the specific part.Available lifecycle methods
Override any combination — you don't need all four:
from test_junkie.rules import Rules class MyRules(Rules): def before_class(self): # runs before the suite's @beforeClass ... def after_class(self): # runs after the suite's @afterClass ... def before_test(self, **kwargs): # runs before each @beforeTest ... def after_test(self, **kwargs): # runs after each @afterTest ...
Real-world tip: screenshot on failure
A common use of after_test is capturing a screenshot when a test fails. The kwargs argument is passed by Test Junkie but rarely needed in Rules hooks — the test status at teardown time is determined by whether any exception was raised during the test itself.
class BrowserRules(Rules): def after_test(self, **kwargs): # always capture screenshot at end of test for CI artifacts test_name = type(self).__name__ self.driver.save_screenshot(f"screenshots/{test_name}.png") # then check JS errors — raises if any found, marking test failed logs = Browser.get_log("browser") errors = [m["message"] for m in logs if m["level"] == "SEVERE"] if errors: raise AssertionError(str(errors))