Orcraft 1.1.3 public beta released 08-Nov-2026. The Free Plan and the Team trial are open; card payments and invoices open shortly.

Orcraft 1.1.3 public beta released 08-Nov-2026. The Free Plan and the Team trial are open; card payments and invoices open shortly.

Orcraft

The platform for test development and orchestration
that lets you and your AI Agents run the entire testing operation

Watch the demo

Orcraft Test Group

A group collects related tests. Unlike a test, a group has no body: it is declared, not coded. Its config names the tests it holds, and the framework does the rest.

from __future__ import annotations

from orcraft import (
	OrcraftTestGroup,
	OrcraftTestGroupConfig,
	OrcraftTestGroupResult,
	OrcraftConfigField,
)

from my_group.my_first_test.my_first_test import MyFirstTest
from my_group.my_second_test.my_second_test import MySecondTest


class MyGroupConfig(OrcraftTestGroupConfig):
	name = OrcraftConfigField.Text(default="My Group")
	description = OrcraftConfigField.Text(
		default="Groups the tests that cover one area of the system under test."
	)
	tests = OrcraftConfigField.Tests(
		default_factory=lambda: [
			MyFirstTest(),
			MySecondTest(),
		]
	)


class MyGroupResult(OrcraftTestGroupResult):
	pass


class MyGroup(OrcraftTestGroup):
	def __init__(self) -> None:
		super().__init__(config=MyGroupConfig())
		self.result = MyGroupResult()


def main() -> None:
	MyGroup().run()


if __name__ == "__main__":
	main()

Tests field

tests uses default_factory rather than default because it constructs objects. A factory is called once per config instance, so two group instances never end up sharing the same test objects - a mistake that a plain default list would make easy and hard to spot.

The result class is usually empty. Subclassing OrcraftTestGroupResult without adding anything is still worth doing: it gives the group a result type of its own to extend later, and keeps the three-class shape consistent across the suite.

Purpose

A group is the unit the controller lets you act on as a whole:

  • Configure once, apply to many. Setting a parameter at group level applies it to every test in the group, so a shared executable folder or endpoint is entered once rather than per test.
  • Run a slice. A group can be run without the rest of the suite, which is how a focused rerun stays focused.
  • Aggregate reporting. Group results carry both test counters and case counters, so a group answers how many of its tests came back clean and how many cases that took.

The tutorial works through the first two in Running a group.

Config fields

Overridable in code

Set in your config class, fixed for the run.

Field Default Meaning
name "Test Group" Display name in the GUI, CLI and logs. Not editable from the GUI.
description "" Free text describing what the group does. Not editable from the GUI.
version "" Group version, recorded with the results.
tests [] The tests this group runs.
log_orientation HORIZONTAL Orientation of the logged config table, or OFF to leave it out of the log.
Override carefully

Redeclare a field with the same OrcraftConfigField type and a new default - name = OrcraftConfigField.Text(default="My Group"). A bare assignment such as name = "My Group" is not an override: it shadows the field with a plain attribute, and the GUI, the log table and required validation no longer see it.

Reserved

Written by the framework while a run is in progress.

Field Meaning
start_time When the run started. Stamped by the framework at run time.
log_tab Log indentation. Assigned by the framework at every level.
Refused by the framework

A class of your own that declares one of these names fails as soon as it is defined, with an error naming the field, and an assignment to one at run time raises the same error. The framework alone writes them.

Result fields

A group has no body, so its result is written entirely by the framework as its tests complete. An empty OrcraftTestGroupResult subclass therefore reports everything a group needs; declaring fields of your own is for the rare group that computes something across its tests.

overridable by user (optional)  ·  written by the framework, may still be set directly  ·  reserved

Inherited field Overridden by user (optional) Reserved Meaning
status – PASS, FAIL or ERROR, derived from the children.
failure_description – Why the system under test was judged incorrect. Setting it moves a passing result to FAIL.
error_description – Why the component could not complete.
warning_description – Something worth flagging that does not change the status.
finish_time – When the component finished.
elapsed_time – How long it took.
log_orientation – Whether the result table is logged horizontally, vertically or not at all (OFF). Defaults to vertical.
total_tests, passed_tests, failed_tests, error_tests, warning_tests – Test counters, summed from the tests that ran.
total_cases, done_cases, passed_cases, failed_cases, error_cases, warning_cases – Case counters, summed from every test in the group: the planned total, the done cases and the passed, failed, errored and warned ones, each logged with its share of the total.
Override carefully

The only field that is yours here is log_orientation, and only its default: redeclare it with the same OrcraftResultField type - log_orientation = OrcraftResultField.Format(default=OrcraftResultField.LogTableOrientation.VERTICAL) - never as a bare value, which shadows the field so the log table no longer sees it.

Refused by the framework

A class of your own that declares one of the reserved names fails as soon as it is defined, with an error naming the field, and an assignment to one at run time raises the same error. Only log_orientation is meant to be redeclared, and only to change its default.

Concurrent execution

A group runs its tests as concurrent subprocesses, which is what makes a suite spread across several endpoints finish in a useful amount of time. The order in which tests start belongs to the run as a whole, not to the group: the run keeps one queue over every test of every group, in declaration order, the way the controller keeps one queue for a session. A test starts as soon as every endpoint it needs is free of the run's other tests; until then it waits without holding a process, and the group log notes which endpoints it is waiting for. A waiting test does not hold back the tests after it. A test that declares no remote endpoint starts at once.

Endpoint contention is therefore handled for you: an endpoint is held for the duration of a run, and another run wanting it waits, unless the endpoint field is declared shared.

Standalone execution

Like a test, a group runs on its own:

def main() -> None:
	MyGroup().run()


if __name__ == "__main__":
	main()

This runs every test in the group as concurrent subprocesses, and logs a group summary after the individual test results. Once a group is doing what you want, add it to a suite.

Type to search the docs.

    ↑↓ navigate ↵ open Esc close