Tutorials / Framework Features

Multi-layer parametrization

Parametrize at the suite level and the test level independently. Write one test method, run it across every combination of environments, browsers, or user roles — and get per-variant tracking, retries, and reports for free.

Suite-level parameters

parameters= on @Suite runs the entire suite once per entry. The current entry is injected into any lifecycle method or test that declares suite_parameter as an argument:

checkout_suite.pypython
@Suite(parameters=[
    {"env": "staging"},
    {"env": "prod"},
])
class CheckoutSuite:

    @beforeClass()
    def setup(self, suite_parameter):
        self.base_url = suite_parameter["env"]

    @test()
    def test_add_to_cart(self, suite_parameter):
        # suite_parameter is {"env": "staging"} on first run
        # suite_parameter is {"env": "prod"} on second run
        checkout(self.base_url, suite_parameter["env"])

Test-level parameters

parameters= on @test runs that test once per entry within each suite run. The current entry is injected as parameter:

checkout_suite.pypython
@Suite()
class CheckoutSuite:

    @test(parameters=[
        {"browser": "chrome"},
        {"browser": "firefox"},
        {"browser": "safari"},
    ])
    def test_checkout(self, parameter):
        browser = parameter["browser"]
        driver = new_driver(browser)
        ...

Both layers together: cross-product coverage

When both suite and test have parameters=, Test Junkie runs every combination. Two environments × three browsers gives six independent variants from a single method:

checkout_suite.pypython
@Suite(parameters=[
    {"env": "staging"},
    {"env": "prod"},
])
class CheckoutSuite:

    @beforeClass()
    def setup(self, suite_parameter):
        self.base_url = suite_parameter["env"]

    @test(parameters=[
        {"browser": "chrome"},
        {"browser": "firefox"},
        {"browser": "safari"},
    ])
    def test_checkout(self, parameter, suite_parameter):
        browser = parameter["browser"]
        env = suite_parameter["env"]
        driver = new_driver(browser)
        checkout(driver, env)

# 6 variants: staging/chrome, staging/firefox,
# staging/safari, prod/chrome, prod/firefox, prod/safari
parameter and suite_parameter are injected by name
Test Junkie detects whether your method declares parameter and/or suite_parameter in its signature and injects the current values. These names are fixed — use parameter["browser"] to get the browser value, not browser directly.

Opting in: signature-based injection

Parameter injection is opt-in via the function signature — for every function in the suite, not just @test. This applies equally to @beforeClass, @afterClass, @beforeTest, and @afterTest:

  • Lifecycle functions (@beforeClass, @afterClass, @beforeTest, @afterTest) — declare suite_parameter to receive the current suite parameter. They do not have access to parameter (test-level).
  • Test functions (@test) — declare parameter, suite_parameter, or both. A test without suite_parameter runs exactly once regardless of how many suite parameters are defined.
checkout_suite.pypython
@Suite(parameters=[
    {"env": "staging"},
    {"env": "prod"},
])
class CheckoutSuite:

    @beforeClass()
    def setup(self, suite_parameter):
        # suite_parameter declared: runs twice (staging, prod)
        self.base_url = suite_parameter["env"]

    @afterClass()
    def teardown(self):
        # no suite_parameter: still runs twice (once per suite run)
        Browser.shutdown()

    @beforeTest()
    def before_each(self, suite_parameter):
        # suite_parameter declared: receives current env before each test
        log(f"starting test on {suite_parameter['env']}")

    @test()
    def test_homepage(self):
        # no suite_parameter: runs once total, not twice
        assert self.base_url is not None

    @test()
    def test_checkout(self, suite_parameter):
        # suite_parameter declared: runs twice (staging + prod)
        checkout(self.base_url, suite_parameter["env"])
@beforeClass and @afterClass always run per iteration
@beforeClass and @afterClass run once per suite-parameter iteration regardless of whether they declare suite_parameter — they live outside the test loop. @beforeTest and @afterTest follow the test they wrap: if the test is skipped for a suite-parameter iteration (because it doesn't declare suite_parameter), those hooks are skipped too. For @test, the declaration controls both whether the value is injected and whether the test participates in the cross-product at all.

Per-variant tracking and retries

Each variant has its own metrics entry. If test_checkout[prod][safari] fails, it fails independently of test_checkout[staging][chrome]. Retries apply per variant, not per method:

checkout_suite.pypython
@test(
    retry=2,
    retry_on=[ConnectionError],
    parameters=[
        {"browser": "chrome"},
        {"browser": "safari"},
    ]
)
def test_checkout(self, parameter, suite_parameter):
    ...
# if safari variant gets a ConnectionError, only it retries
# chrome variant result is unaffected