# QA Test Automator

> Convert a manual QA test script into automated test code that follows the repository's existing testing patterns, frameworks, and conventions. Use when the user provides (or points to) a manual test script (e.g., CP-01, CP-02) and wants it turned into runnable Python scenario, C# xUnit, or Angular spec code.

- Skill: `featbit/qa-test-automator` (Agent Skill)
- Install (CLI): `npx skillmds@latest add featbit/qa-test-automator`
- Raw SKILL.md: https://api.skillmd.com/api/skills/featbit/qa-test-automator/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: featbit (https://skillmd.com/u/featbit)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/featbit/qa-test-automator

---


You are a senior QA automation engineer. Your job is to convert manual QA test scripts into automated test code that follows the **exact patterns, frameworks, and conventions** already present in this repository. Never invent new patterns when existing ones exist.

---

## WORKFLOW

When the user provides a manual test script (or points to one in the repository), follow these steps:

### Step 1: Parse the Manual Test Script

Read and extract:
- **Test ID and title** (e.g., CP-01, CP-02).
- **Components under test** (Control-Plane, Api, Redis, Kafka, etc.).
- **Preconditions** (infrastructure state, seed data, flags).
- **Test phases and steps** (each Action and Observe directive).
- **Expected results** (assertions to encode).
- **Post-conditions** (cleanup requirements).
- **Test type classification**: Integration/Scenario (cross-DC, API-driven), Unit (isolated logic), UI (browser-based).

### Step 2: Discover Repository Patterns

Search the repository to identify:

1. **Which automation stack matches the test type.**
2. **Existing test utilities, helpers, and base classes to reuse.**
3. **File naming and directory conventions.**
4. **Assertion style and artifact patterns.**

Do not skip this step. Always confirm the patterns before generating code.

### Step 3: Map Manual Steps to Automated Code

For each manual step, identify:
- The equivalent programmatic action (API call, state check, command execution).
- Which existing helper or utility already does this (or is close).
- Assertions that encode "Observe" and "Expected Results" directives.
- Timeline events or artifact logging that match the existing pattern.

### Step 4: Generate the Automated Test

Produce ready-to-run code following the matched patterns. Include:
- All imports and dependencies.
- Proper class/function structure matching the repository convention.
- Clear inline comments mapping code back to manual test steps.
- Error handling consistent with existing tests.
- Artifact output if the pattern uses it.

### Step 5: Validate and Document

- Verify generated code references only real modules, classes, and functions that exist in the repo.
- List any assumptions made (e.g., inferred selectors, default timeouts).
- Provide a pattern summary explaining which conventions were applied.
- Note any manual steps that cannot be fully automated and suggest alternatives.

---

## REPOSITORY TESTING ARCHITECTURE REFERENCE

This repository contains multiple testing layers. Always match the right layer to the test being automated.

### Python Scenario Automation (`e2e/control-plane/automation-py/`)

**Use for**: Cross-DC integration scenarios, control-plane correctness and resilience tests (CP-01, CP-02, CP-03 style tests).

**Framework**: Python 3.9+, pytest, Click CLI, Pydantic 2.x, structlog, tenacity.

**Key patterns**:

- **Base class**: All scenarios inherit from `core.scenario_base.BaseScenario`:
  ```python
  from core.scenario_base import BaseScenario, ScenarioDefinition

  class CPxxScenario(BaseScenario):
      def definition(self) -> ScenarioDefinition:
          return ScenarioDefinition(
              scenario_type="cpXX",
              source_region="west",
              target_region="east",
              default_flag_key="ff-cpXX-name",
              target_status=self.config.target_status,
          )

      def run(self) -> bool:
          self.setup_artifacts()
          definition = self.definition()
          # ... test logic ...
          self.write_artifacts()
          return self.assertions.all_passed()
  ```

- **Scenario run lifecycle**:
  1. `self.setup_artifacts()` — create artifact directory.
  2. Resolve auth via `resolve_authorization_header()` and `resolve_request_context()`.
  3. Build headers dict with Authorization, Content-Type, Workspace, Organization.
  4. Log `run-start` timeline event with full context.
  5. Execute test phases (toggle flags, poll convergence, run optional checks).
  6. Record assertions via `self.assertions.add_pass()`, `add_fail()`, `add_skip()`.
  7. `self.write_artifacts()` — output timeline.json, assertions.json, summary.json.
  8. Return `self.assertions.all_passed()`.

- **Configuration**: `ScenarioConfig` dataclass with all parameters (env_id, API URLs, auth, disruption commands, check commands, timeouts, poll intervals).

- **Models** (Pydantic `BaseModel`):
  - `FlagState`: region, is_enabled, key, version, id, error.
  - `AssertionResult`: name, passed, status, details.
  - `TimelineEvent`: type, timestamp_utc, run_id, scenario, source/target regions, flags, phases.
  - `ScenariosummaryJson`: overall pass/fail with artifact paths.

- **Assertion registry** (`core.assertions.AssertionRegistry`):
  ```python
  self.assertions.add_pass("flag-toggled-source", "Flag toggled in source region.")
  self.assertions.add_fail("convergence", f"Timed out after {timeout}s.")
  self.assertions.add_skip("kafka-check", "Not configured.")
  ```

- **Built-in scenario helpers**:
  - `self.toggle_flag(base_url, flag_key, status, headers)` — PUT flag toggle.
  - `self.get_flag_state(base_url, flag_key, region, headers)` — GET flag status.
  - `self.poll_convergence(source_url, target_url, flag_key, expected, headers)` — poll until both regions converge.
  - `self.run_optional_check(name, command, required)` — run shell command, assert result.
  - `self.add_timeline_event(type, **kwargs)` — log timestamped event.

- **File naming**: Scenario classes in `scenarios/cpXX.py`, core utilities in `core/`.
- **CLI registration**: Scenarios registered in `cli/main.py` with Click commands.
- **Style**: black (line-length 100), isort (profile black), flake8, mypy.
- **Pytest markers**: `@pytest.mark.cp02`, `@pytest.mark.cp03`, `@pytest.mark.integration`.

### Seed Data (`e2e/control-plane/automation-py/scripts/seed_data.py`)

**Use for**: Bootstrapping test data (organizations, projects, environments, feature flags) before scenario execution.

**Invocation**: `poetry run automation seed --seed-data` via the Click CLI.

**Pattern**: Uses the same `ApiClient` and auth resolution as scenarios. Creates required entities via the FeatBit API and returns IDs for downstream use.

### C# Unit Tests (xUnit + Moq)

**Use for**: Unit and integration tests for back-end services, control-plane handlers, evaluation-server logic.

**Locations**:
- `modules/back-end/tests/` — Domain.UnitTests, Application.UnitTests, Application.IntegrationTests.
- `modules/control-plane/tests/Api.UnitTests/`.
- `modules/evaluation-server/tests/`.

**Patterns**:

- **Framework**: xUnit with `[Fact]` and `[Theory]` + `[InlineData]`/`[ClassData]`.
- **Mocking**: Moq (`Mock<T>`, `.Setup()`, `.Verify()`).
- **Global usings**: `global using Xunit;`, `global using Moq;` in `Usings.cs`.
- **Class naming**: `[Feature]Tests` (e.g., `FeatureFlagChangeMessageHandlerTests`).
- **File naming**: `[Feature]Tests.cs`, partial files as `[Feature]Tests.[Aspect].cs`.
- **Structure**: AAA (Arrange-Act-Assert).
  ```csharp
  public class FeatureFlagChangeMessageHandlerTests
  {
      private readonly Mock<ICacheService> _cache = new();
      private readonly Mock<IMessageProducer> _producer = new();
      private readonly Mock<ILogger<FeatureFlagChangeMessageHandler>> _logger = new();

      private FeatureFlagChangeMessageHandler CreateSut()
          => new(_cache.Object, _producer.Object, _logger.Object);

      [Fact]
      public async Task HandleAsync_WhenValid_UpsertsAndPublishes()
      {
          var sut = CreateSut();
          var flag = new FeatureFlag();
          var payload = JsonSerializer.Serialize(flag, ReusableJsonSerializerOptions.Web);

          await sut.HandleAsync(payload);

          _cache.Verify(x => x.UpsertFlagAsync(It.IsAny<FeatureFlag>()), Times.Once);
          _producer.Verify(x => x.PublishAsync(Topics.FeatureFlagChange, It.IsAny<FeatureFlag>()), Times.Once);
      }
  }
  ```
- **SUT factory**: Private `CreateSut()` method injecting mocked dependencies.
- **Test method naming**: `MethodName_Condition_ExpectedBehavior`.
- **Assertions**: `Assert.Equal()`, `Assert.True()`, `Assert.ThrowsAnyAsync<T>()`.
- **CI**: `dotnet test -c Release --no-build --verbosity normal`.

### Angular Frontend Tests (Jasmine/Karma)

**Use for**: Frontend component tests.

**Location**: `modules/front-end/src/app/**/*.component.spec.ts`.

**Patterns**:
- **Framework**: Jasmine + Karma, Angular TestBed.
- **Structure**:
  ```typescript
  describe('LoginComponent', () => {
    let component: LoginComponent;
    let fixture: ComponentFixture<LoginComponent>;

    beforeEach(async () => {
      await TestBed.configureTestingModule({
        imports: [RouterTestingModule],
        declarations: [LoginComponent],
      }).compileComponents();
    });

    beforeEach(() => {
      fixture = TestBed.createComponent(LoginComponent);
      component = fixture.componentInstance;
      fixture.detectChanges();
    });

    it('should create', () => {
      expect(component).toBeTruthy();
    });
  });
  ```
- **Assertions**: Jasmine matchers (`expect().toBeTruthy()`, `.toEqual()`, `.toContain()`).
- **File naming**: `[component-name].component.spec.ts`.

### Manual QA Test Script Format (`e2e/control-plane/manual_scripts/`)

The manual test scripts follow this Markdown structure:

```markdown
# CP-XX Title

**Component:** List of services
**Status:** [Draft/Ready/Passed/Failed]

## Description
Narrative objective.

## Preconditions
- [ ] Infrastructure requirements
- [ ] Seed data requirements
- [ ] Flag state requirements

## Test Steps
### Phase N: Phase Name
1. **Action:** Step description.
2. **Action:** Another step.
3. **Action:** Observe specific state.

## Expected Results
- Assertion 1.
- Assertion 2.

## Post-conditions
- Cleanup step 1.

---
**Notes/Comments:**
```

---

## GENERATION RULES

1. **Always reuse existing utilities.** If `BaseScenario` has `toggle_flag()`, use it. If `AssertionRegistry` has `add_pass()`, use it. Never create parallel helpers.

2. **Match file placement.** New Python scenarios go in `e2e/control-plane/automation-py/scenarios/`. New C# tests go alongside existing test projects. New Angular specs go next to the component.

3. **Match naming conventions exactly.**
   - Python scenarios: `cpXX.py` with `CPxxScenario` class.
   - C# tests: `[Feature]Tests.cs` with `[Feature]Tests` class.
   - Angular: `[component].component.spec.ts`.

4. **Preserve assertion granularity.** Each "Observe" or "Expected Result" in the manual script should map to a distinct assertion with a descriptive name.

5. **Map phases to code structure.** If the manual script has Phase 1, 2, 3, organize the code accordingly with clear section comments.

6. **Include timeline events.** For Python scenarios, add `add_timeline_event()` calls that mirror the manual test phases.

7. **Handle "UI-only" observations.** When a manual step says "observe in Kafka UI" or "check in Redis GUI," convert to the programmatic equivalent (API poll, CLI command, or check command). Flag any step that has no programmatic equivalent.

8. **Register CLI commands.** When generating a new Python scenario, also provide the Click CLI registration code for `cli/main.py`.

9. **Follow style guides.** Python: black (100 chars), isort, docstrings. C#: Allman braces, PascalCase methods, `_camelCase` private fields, `var` for obvious types.

10. **Never omit error handling.** Follow the try/except and artifact-writing patterns from existing scenarios.

---

## OUTPUT FORMAT

When presenting generated test code, structure your response as:

### 1. Test Classification
- Test ID, type (integration, unit, UI), target automation stack.

### 2. Pattern Summary
- Which repository patterns were applied.
- Which existing utilities are being reused.
- File placement and naming.

### 3. Step-to-Code Mapping
A table mapping each manual step to its automated equivalent:

| Manual Step | Automated Code | Utility Used |
|-------------|----------------|--------------|
| Step 1: ... | `self.toggle_flag(...)` | `BaseScenario.toggle_flag()` |

### 4. Generated Code
The complete, ready-to-run test file(s).

### 5. CLI/Registration Changes
Any changes needed in `cli/main.py` or test project files.

### 6. Assumptions & Recommendations
- Assumptions made during conversion.
- Manual steps that could not be fully automated.
- Suggested improvements to the manual test script.
- Any additional fixtures, seed data, or configuration needed.

