Lifecycle & hooks

Introduction

CSTesting for Python (C:\CSTesting-Python) uses functions, not Java annotations. Inside a describe suite you register hooks and tests with before_all, after_all, beforeEach / before_each, afterEach / after_each, and it. Use describe.only / it.only and describe.skip / it.skip like Jest. The implementation is in cstesting/runner.py.

Hooks and tests (Python)

APIWhen it runs
before_all(fn)Once at the start of this suite (after the parent suite has started its own work).
after_all(fn)Once after this suite’s tests and nested child suites finish.
beforeEach(fn) / before_each(fn)Before each it in this suite. The runner does not pass a browser argument—open Playwright yourself (see below).
afterEach(fn) / after_each(fn)After each it in this suite (still runs if the test failed).
it("name", fn)One test. fn may be sync or async def; it takes no parameters from the runner.

Register hooks by calling before_all(my_fn) inside the suite function. Hooks can be sync or async; the runner awaits coroutines. Multiple beforeEach registrations run in order.

Execution order

Implementation in cstesting/runner.py for each describe suite:

  1. before_all
  2. Every it registered directly on this suite: for each, run beforeEach hooks, the test, then afterEach hooks
  3. Each nested describe child suite (full subtree)
  4. after_all

Define a suite with describe("Name", _suite) where _suite is a function that calls before_all, it, nested describe, etc. This matches README.md and example/math_test.py in C:\CSTesting-Python.

Browser with hooks

The runner does not create a Playwright browser for you. Typical pattern: create_browser in before_all, store on a module-level variable (or page object), browser.close() in after_all—same as README.md and templates/tests/home_test.py. For a fresh browser per test, call create_browser inside each it (with try/finally).

Headed mode: use create_browser(headless=False) (CLI --headed applies to config-driven runs, not to this hook pattern).

it tests

import asyncio
from cstesting import describe, it, expect, before_all, after_all, create_browser

browser = None

def _suite():
    def _before():
        global browser
        loop = asyncio.get_event_loop()
        browser = loop.run_until_complete(create_browser(headless=True))

    def _after():
        global browser
        if browser:
            asyncio.get_event_loop().run_until_complete(browser.close())

    before_all(_before)
    after_all(_after)

    async def _login():
        await browser.goto("https://example.com/login")
        await browser.type("name=user", "alice")
        await browser.click('button[type="submit"]')
        await browser.wait_for_url("**/welcome", {"timeout": 10000})

    it("redirects after login", _login)

    async def _title():
        await browser.goto("https://example.com")
        title = await browser.evaluate("document.title")
        expect(title).to_equal("Example")

    it("shows title", _title)

describe("Login", _suite)

Use it.only / describe.only to focus one test or suite; it.skip / describe.skip to skip.

Runner: CLI and run()

python -m cstesting
python -m cstesting example/math_test.py
python -m cstesting tests/
python -m cstesting --tag smoke "**/*.test.py"

Programmatically (after registering suites at import time): from cstesting import run then result = run(). See README.md in C:\CSTesting-Python.

Full example (hooks only)

from cstesting import describe, it, before_all, after_all, beforeEach, afterEach, expect

def _suite():
    def suite_setup():
        print("before all")

    def suite_teardown():
        print("after all")

    def before_each_test():
        print("before each")

    def after_each_test():
        print("after each")

    before_all(suite_setup)
    after_all(suite_teardown)
    beforeEach(before_each_test)
    afterEach(after_each_test)

    it("one", lambda: expect(1).to_be(1))
    it("two", lambda: expect(2).to_be(2))

describe("Example suite", _suite)

Note: Register hooks with before_all(fn) etc. Do not use @before_all as a decorator—those helpers return None and would replace your function name.

Run: python -m cstesting your_file.py

Nested describe

Inside _suite, call describe("Child", _child_suite) to add a nested suite. Child suites run after all it calls registered on the parent in the same _suite function (see runner.py).

Summary

TopicPython CSTesting
Test caseit("name", fn) — sync or async, no runner-injected args
Suitedescribe("name", suite_fn)
Once per suitebefore_all(fn), after_all(fn)
Per testbeforeEach(fn), afterEach(fn) (aliases before_each, after_each)
BrowserYou call create_browser / close; not injected by the runner
Assertionsexpect(...)
CLIpython -m cstesting [path|glob], tags via --tag