/mcp-toolkit scenario — 시나리오 한 번에 자동 실행#
| 항목 | 내용 |
|---|---|
| 실행 명령 | /cc-mcp-toolkit:scenario |
| 분류 | Flutter |
| 난이도 | ●●● 높음 |
| MCP 서버 | mcp_flutter, marionette, dart, figma |
한마디로#
여러 단계의 앱 테스트를 각본(YAML 파일) 하나에 적어두면, 순서대로 자동으로 실행해 주는 도구입니다. 연극 대본처럼 "준비 → 본 연기 → 정리"를 미리 적어 두면, 배우(앱) 대신 자동으로 무대를 진행하고 결과를 기록해 줍니다.
누가·언제 쓰나요#
- 앱의 복잡한 흐름을 자동으로 검증하고 싶은 개발자/QA 담당자
- 예: "로그인 → 데이터 준비 → 화면에서 당겨서 새로고침 → 결과가 맞는지 확인 → 화면 캡처"처럼 여러 동작이 섞인 시나리오를 반복 실행해야 할 때
- 특히 앱 내부 상태 확인 + 화면 터치 + 화면 이미지 비교가 한꺼번에 필요한 경우(cocode의 대표적인 사용 사례)에 적합합니다.
👉 이럴 땐 쓰지 마세요: 단순히 화면을 터치하고 스크린샷만 찍는 정도라면, 더 가벼운 cc-marionette:sweep나
cc-pixel-loop:loop를 쓰는 게 낫습니다.
무엇을 해주나요#
각본대로 실행한 뒤, 결과를 폴더 하나에 정리해 줍니다. 폴더 안에는:
scenario.yaml— 실제로 실행한 각본 사본steps/— 각 단계별로 무엇을 했고 어떤 값이 돌아왔는지(01.json등)screens/— 시나리오 중 찍은 화면 캡처 이미지bloc-dumps/— 앱 내부 상태 기록teardown.json— 마무리 정리 단계 기록summary.md— 성공/실패 요약(걸린 시간 포함)
어떻게 쓰나요#
# 각본 파일을 지정해 실행
/mcp-toolkit scenario --file test/scenarios/feed_pull_to_refresh.yaml
# 결과 저장 폴더를 직접 지정 (생략하면 자동 폴더에 저장)
/mcp-toolkit scenario --file ... --output .claude/docs/mcp-toolkit/my-run/
# 실제 실행 없이 각본 문법이 맞는지만 검사 (예행연습)
/mcp-toolkit scenario --file ... --dry-run
--file(필수): 실행할 각본 YAML 파일 경로-
--output(선택): 결과를 저장할 폴더. 안 적으면.claude/docs/mcp-toolkit/<시나리오이름>-<시각>/에 자동 저장 --dry-run(선택): 진짜로 돌리지 않고 각본이 올바른지만 확인
안에서 무슨 일이 벌어지나요#
이 도구는 한 번 쭉 실행하고 끝나는 정해진 순서의 자동 재생입니다(같은 걸 반복하는 루프가 아닙니다).
- 준비(setup) — 로그인, 데이터 초기화, 테스트용 데이터 심기, 화면 이동 등 사전 준비를 한 번 합니다.
-
본 흐름(steps) — 각본에 적힌 동작을 차례로 실행합니다. 앱 내부 상태를 확인하거나(
expect로 "이런 결과가 나와야 한다" 검증), 화면을 터치/스크롤하거나, 화면을 캡처합니다. - 정리(teardown) — 데이터 초기화 등 뒷정리를 합니다. 중간에 실패하더라도 이 정리 단계는 항상 실행됩니다.
- 사후 확인(asserts) — 마지막으로 최종 상태가 기대대로인지 한 번 더 점검합니다.
실패는 이런 경우에 기록됩니다: 도구 호출 자체가 오류일 때, expect로 기대한 값과 실제가 다를 때, 외부 명령이 실패를 돌려줄 때.
⚙️ 상세 옵션·실행 명세 (개발자 / AI 에이전트용)
Parameters#
| Parameter | Required | Description |
|---|---|---|
--file | ✅ | Path to a scenario YAML |
--output | ❌ | Output dir (default .claude/docs/mcp-toolkit/<scenario-name>-<ts>/) |
--dry-run | ❌ | Validate the YAML without executing |
YAML Schema#
name: < scenario-id > # required, snake_case
description: < free text >
# Optional preconditions — run once
setup:
- tool: < name > # dynamic tool from mcp_flutter
args: { ... }
- external: < plugin:command >
args: { ... }
# Main flow
steps:
- tool: < name >
args: { ... }
expect: { ... } # optional assertion on the tool ' s return
- external: < plugin:command >
args: { ... }
# Teardown — runs even on failure
teardown:
- tool: < name >
args: { ... }
# Post-conditions
asserts:
- tool: bloc_dump_state
args: { type: FeedBloc }
expect: { status: loaded, posts: { length: 10 } }tool: vs external:#
tool:— dynamic tool frommcp_flutter(the in-app tools you registered).external:— call into another plugin's command. Currently supported:cc-marionette:tap,enter_text,scroll_to,take_screenshots,get_interactive_elements,hot_reloadcc-pixel-loop:capture,comparecc-jaspr-web:audit,match,regression— when the scenario covers a Web/Jaspr surface
Example Scenario#
# test/scenarios/feed_pull_to_refresh.yaml
name: feed_pull_to_refresh
description: Verify pull-to-refresh on Feed correctly hits the server and updates state.
setup:
- tool: auth_impersonate
args: { persona: " alice " }
- tool: db_reset
- tool: db_seed_fixture
args: { name: " feed_with_3 " }
- tool: nav_go_named
args: { name: " feed " }
steps:
# Confirm initial state has 3 posts
- tool: bloc_dump_state
args: { type: " FeedBloc " }
expect: { status: " loaded " , posts: { length: 3 } }
- external: cc-pixel-loop:capture
args: { route: " /feed " , label: " before-refresh " }
# Seed more posts on the backend
- tool: db_seed_fixture
args: { name: " feed_with_10 " }
# User pulls to refresh
- external: cc-marionette:scroll_to
args: { key: " feed_top " }
- external: cc-marionette:swipe # if pull gesture supported
args: { direction: " down " , from_key: " feed_top " }
- tool: bloc_dump_state
args: { type: " FeedBloc " }
expect: { status: " loaded " , posts: { length: 10 } }
- external: cc-pixel-loop:capture
args: { route: " /feed " , label: " after-refresh " }
teardown:
- tool: db_reset
- tool: auth_impersonate
args: { persona: " default " }
asserts:
# Final state was already asserted in steps; nothing extra here.Convergence#
The scenario runner does not loop. It is a deterministic playback. Failures collected:
- Tool error (non-2xx style response).
expect:mismatch.external:command returned a failure summary.
On any failure, teardown still runs.
Output#
.claude/docs/mcp-toolkit/ < scenario-name > - < ts > /
├── scenario.yaml # copy of the run scenario
├── steps/
│ ├── 01.json # each step ' s args + return
│ └── ...
├── screens/ # any pixel-loop captures forwarded here
├── bloc-dumps/
├── teardown.json
└── summary.md # pass/fail summary with timingsWhen to use vs when to avoid#
- Use: complex flows that mix domain state + UI taps + visual diffs (the cocode bread-and-butter case).
- Avoid for: pure tap-and-screenshot flows — those belong in
cc-marionette:sweeporcc-pixel-loop:loop.