Implementation
static Map<String, dynamic> build() {
final defs = <String, dynamic>{
'initialState': _initialStateDef(),
'scenario': {
'type': 'object',
'additionalProperties': false,
'properties': {
'id': {'type': 'string', 'minLength': 1},
'description': {'type': 'string'},
'vars': {'type': 'object', 'additionalProperties': true},
},
'required': ['id'],
},
'mockResponse': _mockResponseDef(),
'inlineMocks': _inlineMocksDef(),
'testCase': {
'type': 'object',
'additionalProperties': false,
'properties': {
'id': {
'type': 'string',
'minLength': 1,
'description': 'Unique test identifier',
},
'type': {'type': 'string'},
'feature': {
'type': 'string',
'minLength': 1,
'description': 'Feature or flow name, e.g. login or wifi',
},
'tags': {
'type': 'array',
'items': {'type': 'string'},
'description': 'Free-form labels for filtering and organization',
},
'description': {'type': 'string'},
'owner': {'type': 'string'},
'priority': {
'type': 'string',
'enum': [
'p0',
'p1',
'p2',
'p3',
'critical',
'high',
'medium',
'low'
],
},
'parallel': {
'type': 'boolean',
'description':
'Set false for tests that mutate shared external state and must not run in a parallel worker shard.',
},
'retry': {
'type': 'integer',
'minimum': 0,
'description':
'Number of additional attempts after the first failure.',
},
'startScreen': {
'type': 'string',
'minLength': 1,
'description': 'Ensemble screen name or id to load first',
},
'startScreenInputs': {
'type': 'object',
'additionalProperties': true,
'description': 'Inputs passed to startScreen',
},
'session': {
'type': 'string',
'minLength': 1,
'description':
'ID of a successful test whose captured app state is restored before startScreen',
},
'profiles': {
'oneOf': [
{'type': 'string', 'minLength': 1},
{
'type': 'array',
'minItems': 1,
'items': {'type': 'string', 'minLength': 1},
},
],
'description':
'Profile selector(s) from tests/config.yaml profiles. Each '
'selector can be a concrete profile or a group.',
},
'initialState': {'\$ref': '#/\$defs/initialState'},
'setup': {
'type': 'array',
'minItems': 1,
'items': {'\$ref': '#/\$defs/setupStep'},
'description':
'Headless httpRequest actions executed before startScreen mounts',
},
'mocks': _mocksPropertySchema(),
'scenarios': {
'type': 'array',
'items': {'\$ref': '#/\$defs/scenario'},
'minItems': 1,
},
'steps': {
'type': 'array',
'minItems': 1,
'items': {'\$ref': '#/\$defs/step'},
},
},
'required': ['id', 'startScreen', 'steps'],
},
};
// Register per-step arg defs and step wrappers.
final stepOneOf = <Map<String, dynamic>>[];
final registeredArgDefs = <String>{};
final mocksSchema = Map<String, dynamic>.from(
(defs['testCase'] as Map)['properties']['mocks']);
for (final yamlKey in TestStepVocabulary.yamlStepKeys) {
final entry = TestStepRegistry.entries[yamlKey]!;
final description = entry.description;
final argsSchema = yamlKey == 'mocks'
? mocksSchema
: TestStepVocabulary.argJsonSchemaForYamlKey(yamlKey);
final argsDefName = 'args_$yamlKey';
final exampleArgs = Map<String, dynamic>.from(entry.example);
final exampleStep = {yamlKey: exampleArgs};
if (registeredArgDefs.add(argsDefName)) {
defs[argsDefName] = {
...argsSchema,
'title': yamlKey,
'description': description,
'examples': [exampleArgs],
};
}
stepOneOf.add({
'type': 'object',
'title': yamlKey,
'description': description,
'examples': [exampleStep],
'additionalProperties': false,
'minProperties': 1,
'maxProperties': 1,
'properties': {
yamlKey: {
'\$ref': '#/\$defs/$argsDefName',
'description': description,
'examples': [exampleArgs],
},
},
'required': [yamlKey],
});
}
defs['step'] = {'oneOf': stepOneOf};
defs['setupStep'] = {
'oneOf': stepOneOf
.where((step) =>
step['title'] == 'httpRequest' ||
step['title'] == 'group' ||
step['title'] == 'optional')
.toList(),
};
final testCase = defs.remove('testCase') as Map<String, dynamic>;
return {
'\$schema': schemaVersion,
'\$id': schemaId,
'title': 'Ensemble declarative test file',
'description':
'Schema for app-local tests/*.test.yaml — one test per file; '
'see tools/ensemble_test_runner/STEP_VOCABULARY.md',
...testCase,
'\$defs': defs,
};
}