本文へ移動
cccskills
無料GitHub で公開

cucumber-automation

Extract Gherkin scenarios from story markdown files into runnable .feature files and generate step definition stubs. Use when syncing stories to test suites.

インストール方法を見る

含まれるファイル(1)

  • SKILL.md11.7 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

SKILL: cucumber-automation

Purpose

Extract Gherkin scenarios from story markdown files into runnable .feature files, and generate step definition stubs for the project's test stack. Keeps tests/features/ in sync with docs/stories/ as the single source of Gherkin truth.

Supported Stacks

StackStep file extensionFramework
TypeScript / Node.js.steps.ts@cucumber/cucumber
JavaScript / Node.js.steps.js@cucumber/cucumber
Python_steps.pybehave
JavaSteps.javaCucumber-JVM

Detect the stack from the repo root:

if [ -f "package.json" ] && grep -q "@cucumber/cucumber" package.json 2>/dev/null; then
  STACK="typescript"
elif [ -f "requirements.txt" ] && grep -q "behave" requirements.txt 2>/dev/null; then
  STACK="python"
elif [ -f "pom.xml" ] || [ -f "build.gradle" ]; then
  STACK="java"
else
  STACK="unknown"
fi

Extract Gherkin from a Story File

Story files contain Gherkin in fenced code blocks tagged with gherkin:

STORY_FILE="docs/stories/user-auth-reset-password-20250416143000-0001.md"
EPIC="user-auth"

# Extract content of ```gherkin ... ``` blocks
python3 - <<'EOF'
import re, sys

story = open(sys.argv[1]).read()
blocks = re.findall(r'```gherkin\n(.*?)```', story, re.DOTALL)
if not blocks:
    print("NO_GHERKIN", file=sys.stderr)
    sys.exit(1)
print('\n\n'.join(blocks))
EOF "$STORY_FILE"

Write the Feature File

STORY_FILE="docs/stories/user-auth-reset-password-20250416143000-0001.md"
FEATURE_DIR="tests/features"
EPIC="user-auth"

mkdir -p "$FEATURE_DIR/$EPIC"
FEATURE_FILE="$FEATURE_DIR/$EPIC/reset-password.feature"

# Extract and write (idempotent — overwrites if Gherkin changed)
python3 - <<'EOF'
import re, sys, os

story_path, feature_path = sys.argv[1], sys.argv[2]
story = open(story_path).read()
blocks = re.findall(r'```gherkin\n(.*?)```', story, re.DOTALL)
if not blocks:
    print(f"[cucumber] NO_GHERKIN in {story_path}", file=sys.stderr)
    sys.exit(1)

os.makedirs(os.path.dirname(feature_path), exist_ok=True)
with open(feature_path, 'w') as f:
    f.write('\n\n'.join(b.rstrip() for b in blocks))
    f.write('\n')

print(f"[cucumber] WRITE  {feature_path}  scenarios={len(re.findall(r'^  Scenario', ''.join(blocks), re.MULTILINE))}")
EOF "$STORY_FILE" "$FEATURE_FILE"

Generate Step Definition Stubs (TypeScript)

FEATURE_FILE="tests/features/user-auth/reset-password.feature"
STEPS_DIR="tests/step-definitions/user-auth"
mkdir -p "$STEPS_DIR"

python3 - <<'EOF'
import re, sys, os

feature_path = sys.argv[1]
steps_dir = sys.argv[2]
feature = open(feature_path).read()

# Collect unique step texts
steps = re.findall(r'^\s+(Given|When|Then|And|But) (.+)$', feature, re.MULTILINE)
seen = set()
unique = []
for keyword, text in steps:
    norm = re.sub(r'"[^"]+"', '"{string}"', text)
    norm = re.sub(r'\b\d+\b', '{int}', norm)
    if norm not in seen:
        seen.add(norm)
        unique.append((keyword if keyword not in ('And','But') else 'Given', norm, text))

# Build TypeScript file
feature_name = os.path.basename(feature_path).replace('.feature','')
out_path = os.path.join(steps_dir, f"{feature_name}.steps.ts")

if os.path.exists(out_path):
    print(f"[cucumber] SKIP  {out_path}  (already exists)")
    sys.exit(0)

lines = [
    "import { Given, When, Then } from '@cucumber/cucumber';",
    "",
]
for keyword, pattern, original in unique:
    fn = keyword.lower()
    # build parameter list from placeholders
    params = []
    if '{string}' in pattern:
        params += [f"arg{i}: string" for i in range(pattern.count('{string}'))]
    if '{int}' in pattern:
        params += [f"n{i}: number" for i in range(pattern.count('{int}'))]
    param_str = ', '.join(params)
    escaped = pattern.replace("'", "\\'")
    lines += [
        f"// {original}",
        f"{fn}('{escaped}', async ({param_str}) => {{",
        f"  // TODO: implement",
        f"  throw new Error('Pending: {escaped}');",
        f"}});",
        "",
    ]

with open(out_path, 'w') as f:
    f.write('\n'.join(lines))

print(f"[cucumber] STUBS  {out_path}  steps={len(unique)}")
EOF "$FEATURE_FILE" "$STEPS_DIR"

Generate Step Definition Stubs (Python / behave)

FEATURE_FILE="tests/features/user_auth/reset_password.feature"
STEPS_DIR="tests/features/user_auth"
mkdir -p "$STEPS_DIR"

python3 - <<'EOF'
import re, sys, os

feature_path = sys.argv[1]
steps_dir = sys.argv[2]
feature = open(feature_path).read()

steps = re.findall(r'^\s+(Given|When|Then|And|But) (.+)$', feature, re.MULTILINE)
seen = set()
unique = []
for keyword, text in steps:
    norm = re.sub(r'"[^"]+"', '"{text}"', text)
    norm = re.sub(r'\b\d+\b', '{n:d}', norm)
    if norm not in seen:
        seen.add(norm)
        unique.append((keyword if keyword not in ('And','But') else 'given', norm, text))

feature_name = os.path.basename(feature_path).replace('.feature','')
out_path = os.path.join(steps_dir, f"{feature_name}_steps.py")

if os.path.exists(out_path):
    print(f"[cucumber] SKIP  {out_path}  (already exists)")
    sys.exit(0)

lines = ["from behave import given, when, then", ""]
for keyword, pattern, original in unique:
    fn = keyword.lower()
    escaped = pattern.replace('"', '\\"')
    lines += [
        f"# {original}",
        f'@{fn}(u"{escaped}")',
        f"def step_impl(context):",
        f'    raise NotImplementedError(u"STEP: {fn} {escaped}")',
        "",
    ]

with open(out_path, 'w') as f:
    f.write('\n'.join(lines))

print(f"[cucumber] STUBS  {out_path}  steps={len(unique)}")
EOF "$FEATURE_FILE" "$STEPS_DIR"

Batch Sync (all stories → all feature files)

find docs/stories -name "*.md" ! -name "_template.md" | while read -r story; do
  EPIC=$(python3 -c "
import re
m = re.search(r'^epic:\s*[\"\'](.*?)[\"\']', open('$story').read(), re.MULTILINE)
print(m.group(1) if m else 'unknown')
")
  SLUG=$(basename "$story" | sed 's/-[0-9]\{14\}-[0-9]\{4\}\.md$//')
  FEATURE_FILE="tests/features/$EPIC/$SLUG.feature"
  # extract-and-write step (see above) ...
  echo "[cucumber] SYNC  $story → $FEATURE_FILE"
done

Playwright + behave Integration

When the stack includes Playwright (requirements.txt contains playwright or behave), step definitions must follow these patterns. Read the QA agent's BDD section for the full antipatterns list.

environment.py template

import json
import threading
from playwright.sync_api import sync_playwright

BASE_URL = "http://localhost:3000"
DEFAULT_GEO = {"latitude": 40.7128, "longitude": -74.0060}

MOCK_FORECAST = {
    "daily": {
        "time": ["2025-01-01","2025-01-02","2025-01-03",
                 "2025-01-04","2025-01-05","2025-01-06","2025-01-07"],
        "temperature_2m_max": [22.1, 18.3, 15.0, 20.5, 25.2, 19.8, 17.4],
        "temperature_2m_min": [12.0, 10.5,  9.3, 13.2, 16.1, 11.4, 10.9],
        "weathercode":        [0,    3,     61,   0,    1,    80,   71 ],
        "precipitation_probability_max": [5, 30, 85, 10, 20, 70, 60],
        "windspeed_10m_max":  [12.0, 20.5, 35.2, 8.4, 15.6, 28.9, 22.3],
        "relative_humidity_2m_max": [55, 72, 90, 48, 62, 85, 78],
    }
}

def before_all(context):
    context.playwright = sync_playwright().start()
    context.browser = context.playwright.chromium.launch()

def after_all(context):
    context.browser.close()
    context.playwright.stop()

def before_scenario(context, scenario):
    context.browser_context = context.browser.new_context(
        permissions=["geolocation"],
        geolocation=DEFAULT_GEO,
    )
    context.page = context.browser_context.new_page()
    context._forecast_event = None

def after_scenario(context, scenario):
    context.page.close()
    context.browser_context.close()

# ── Helpers ────────────────────────────────────────────────────────────────────

def setup_forecast_route(page, error=False, delay_event=None):
    """Network-level Open-Meteo intercept. NEVER overrides window.fetch."""
    def handle(route):
        if delay_event is not None:
            delay_event.wait(timeout=30)
        if error:
            route.fulfill(status=500, content_type="application/json",
                          body='{"error":"service unavailable"}')
        else:
            route.fulfill(status=200, content_type="application/json",
                          body=json.dumps(MOCK_FORECAST))
    page.route("**/api.open-meteo.com/**", handle)

def navigate_and_wait(page):
    page.goto(BASE_URL, wait_until="domcontentloaded")
    page.wait_for_selector(".forecast-card", timeout=10_000)

Browser capability scenarios

# Geolocation granted — default, handled by before_scenario

# Geolocation denied — create context without the permission
context.browser_context = context.browser.new_context()  # no geolocation permission

# Geolocation unsupported — NO Playwright native API; add_init_script is acceptable HERE ONLY
context.page.add_init_script(
    "Object.defineProperty(navigator,'geolocation',{value:undefined,configurable:true});"
)

# Geolocation timeout — NO Playwright native API; add_init_script acceptable HERE ONLY
context.page.add_init_script(
    "navigator.geolocation.getCurrentPosition = function(){};"  # never calls back
)

Loading state (route with threading.Event)

# Setup step — route stays blocked until event is set
event = threading.Event()
setup_forecast_route(context.page, delay_event=event)
context._forecast_event = event

# Assert spinner visible while blocked
spinner = context.page.locator('[role="status"]')
spinner.wait_for(state="visible", timeout=3000)

# Unblock route → cards should appear
context._forecast_event.set()
context.page.wait_for_selector(".forecast-card", timeout=5000)

API error state

setup_forecast_route(context.page, error=True)
# route returns HTTP 500 — app must handle gracefully

Antipatterns checklist (rubber duck will flag these)

AntipatternWhy it's wrong
add_init_script("window.fetch = ...")Bypasses all app network code; test passes regardless of implementation
page.evaluate("window._appResolve()")Reaches into app internals; not observable behaviour
add_init_script(GEO_SUCCESS_SCRIPT) for geolocationPlaywright's context API exists — use it
page.evaluate("window.__mock = ...")Same as window.fetch override — leaks test state into app
Assertions checking internal state (data-* set by test, not app)Test the rendered output, not the test's own injected data

Failure Modes

ConditionAction
Story file has no gherkin fenced blockLog NO_GHERKIN. Skip. Do not create empty feature file.
Feature file already exists with manual editsCompare checksums. If changed: log MANUAL_EDIT — skip overwrite. Never overwrite manual edits.
Step file already existsSkip generation. Log SKIP. Agents implement stubs; generator does not regenerate.
Stack is unknownLog warning. Write .feature file only; skip step stubs.
Gherkin parse error (malformed block)Log the offending line. Skip the file. Do not attempt partial extraction.

Logging

[cucumber] WRITE   tests/features/user-auth/reset-password.feature  scenarios=3
[cucumber] STUBS   tests/step-definitions/user-auth/reset-password.steps.ts  steps=7
[cucumber] SKIP    tests/step-definitions/user-auth/login.steps.ts  (already exists)
[cucumber] NO_GHERKIN  docs/stories/auth-spike-20250416143000-0002.md  (spike — expected)

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Run WCAG 2.2 Level AA accessibility audits against generated UI. Use when a story has ui:true or when asked to audit accessibility.

日本語の概要は準備中です。原文の説明を表示しています。

kinncj/Heimdall702026年9月2日 更新

Generate a complete component file tree wired to design tokens with tests and Gherkin spec. Use when scaffolding a new UI component.

日本語の概要は準備中です。原文の説明を表示しています。

kinncj/Heimdall702026年9月2日 更新

Read and write design tokens in W3C DTCG format (tokens.json) and emit CSS, Tailwind, and Mantine outputs. Use when modifying or generating design tokens.

日本語の概要は準備中です。原文の説明を表示しています。

kinncj/Heimdall702026年9月2日 更新

Apply Docker and Docker Compose best practices for containerising services. Use when writing or reviewing Dockerfiles and compose configs.

日本語の概要は準備中です。原文の説明を表示しています。

kinncj/Heimdall702026年9月2日 更新

Identify and annotate cloud cost implications of architectural decisions. Use when reviewing infrastructure changes or designing new services.

日本語の概要は準備中です。原文の説明を表示しています。

kinncj/Heimdall702026年9月2日 更新

gh-issues

無料

Create, read, update, and link GitHub Issues as story artifacts throughout the story lifecycle. Use when managing issues for stories.

日本語の概要は準備中です。原文の説明を表示しています。

kinncj/Heimdall702026年9月2日 更新

kinncj のスキルをすべて見る

このスキルの問題を報告する