build static method

Map<String, dynamic> build()

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,
  };
}