Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ A Model Context Protocol (MCP) server for Robot Framework test automation with c
- 📊 Performance monitoring and metrics collection
- 🔄 Data-driven testing templates
- 🌐 API integration testing capabilities
- 🖥️ Live browser control — launch, click, type, screenshot without writing `.robot` files
- 🚀 CI/CD pipeline generation for GitHub Actions

## Quick Demo Video

Expand Down Expand Up @@ -178,6 +180,58 @@ The MCP server provides the following comprehensive tools for Robot Framework te
### 🔍 Validation & Syntax
- **`validate_robot_framework_syntax(robot_code)`** - Validate Robot Framework syntax and provide improvement suggestions

### 🖥️ Live Browser Control

Drive a real browser directly — no `.robot` file needed. All tools share a persistent session and support Robot Framework-style selector prefixes (`id=`, `css=`, `xpath=`, `name=`, `class=`, `tag=`, `link=`, `partial_link=`).

| Tool | Description |
|---|---|
| **`browser_launch(url, browser="Chrome", headless=False)`** | Open Chrome or Firefox and navigate to a URL |
| **`browser_navigate(url)`** | Go to a new URL in the active session |
| **`browser_click(selector, timeout=10)`** | Click an element, waits until it is clickable |
| **`browser_send_keys(selector, text, clear_first=True, timeout=10)`** | Type into an input field |
| **`browser_get_text(selector, timeout=10)`** | Read visible text from an element |
| **`browser_wait_for_element(selector, state="visible", timeout=10)`** | Wait for `visible`, `present`, `clickable`, or `hidden` |
| **`browser_screenshot(filename="")`** | Save a PNG screenshot, returns the file path |
| **`browser_close()`** | Quit the browser and clean up the session |

**Example — AI-driven login flow:**
```python
browser_launch("https://example.com")
browser_send_keys("id=username", "admin")
browser_send_keys("id=password", "secret")
browser_click("css=button[type='submit']")
browser_screenshot("results/after_login.png")
browser_close()
```

> **Note:** ChromeDriver must match your installed Chrome version. Update with `brew upgrade chromedriver` (macOS) or download from [googlechromelabs.github.io/chrome-for-testing](https://googlechromelabs.github.io/chrome-for-testing/).

### 🚀 CI/CD Pipeline Generation

- **`create_cicd_pipeline(project_name, python_version, test_directory, trigger_branches, schedule, output_dir, requirements_file)`** - Generate a GitHub Actions workflow file ready to drop into `.github/workflows/`

The generated workflow:
- Triggers on push and pull requests to specified branches
- Supports optional cron schedule (e.g. `"0 9 * * 1-5"` for weekdays at 09:00 UTC)
- Auto-detects the Python version from the current environment (can be overridden)
- Sets up Chrome in headless mode on the runner
- Installs from `requirements.txt` if present, falls back to core packages
- Uploads the full `results/` folder as a build artifact (30-day retention)
- Parses `output.xml` and reports pass/fail counts; fails the job if any test fails

**Example:**
```python
create_cicd_pipeline(
project_name="my-rf-tests",
trigger_branches="main, develop",
schedule="0 9 * * 1-5", # weekdays 09:00 UTC
test_directory="tests/",
)
```

Save the output to `.github/workflows/robot-tests.yml` in your repository.

### 📋 Template Options

The server supports multiple selector templates for different applications:
Expand Down
141 changes: 141 additions & 0 deletions mcp_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -1379,6 +1379,147 @@ def validate_robot_framework_syntax(robot_code: str) -> str:
return f"# VALIDATION ERROR: {str(e)}"


@mcp.tool()
def create_cicd_pipeline(
project_name: str = "robot-tests",
python_version: str = "",
test_directory: str = "tests/",
trigger_branches: str = "main",
schedule: str = "",
output_dir: str = "results/",
requirements_file: str = "requirements.txt",
) -> str:
"""Generate a GitHub Actions workflow file for running Robot Framework tests.

Returns the complete YAML content to save as .github/workflows/robot-tests.yml.

project_name: workflow and artifact name (default 'robot-tests').
python_version: Python version for the runner. Defaults to the version currently
running this MCP server (e.g. '3.11'). Pass explicitly to override.
test_directory: path to the Robot Framework test folder (default 'tests/').
trigger_branches: comma-separated branch names that trigger the workflow (default 'main').
schedule: optional cron expression for scheduled runs, e.g. '0 9 * * 1-5' for
weekdays at 09:00 UTC. Leave empty to disable scheduled runs.
output_dir: folder where Robot Framework writes results (default 'results/').
requirements_file: pip requirements file path (default 'requirements.txt').
"""
import sys
try:
if not python_version or not python_version.strip():
python_version = f"{sys.version_info.major}.{sys.version_info.minor}"
branches = [b.strip() for b in trigger_branches.split(",") if b.strip()]
if not branches:
return "VALIDATION ERROR: trigger_branches cannot be empty."

branch_lines = "\n".join(f" - {b}" for b in branches)
Comment on lines +1408 to +1414

schedule_block = ""
if schedule.strip():
schedule_block = f""" schedule:
- cron: "{schedule.strip()}"
"""

# Build the fallback requirements block used when no requirements.txt exists
fallback_install = (
"pip install robotframework robotframework-seleniumlibrary selenium"
)
Comment on lines +1422 to +1425

workflow = f"""\
# GitHub Actions workflow — generated by robotframework-mcp
# Save this file to: .github/workflows/robot-tests.yml

name: {project_name}

on:
push:
branches:
{branch_lines}
pull_request:
branches:
{branch_lines}
{schedule_block}
jobs:
robot-tests:
name: Run Robot Framework Tests
runs-on: ubuntu-latest

steps:
# ── Checkout ────────────────────────────────────────────────────────────
- name: Checkout repository
uses: actions/checkout@v4

# ── Python ──────────────────────────────────────────────────────────────
- name: Set up Python {python_version}
uses: actions/setup-python@v5
with:
python-version: "{python_version}"
cache: pip

# ── Dependencies ────────────────────────────────────────────────────────
- name: Install dependencies
run: |
python -m pip install --upgrade pip
if [ -f {requirements_file} ]; then
pip install -r {requirements_file}
else
{fallback_install}
fi
Comment on lines +1462 to +1466

# ── Chrome (headless) ───────────────────────────────────────────────────
- name: Set up Chrome
uses: browser-actions/setup-chrome@v1
with:
chrome-version: stable

- name: Verify Chrome installation
run: google-chrome --version

# ── Run tests ───────────────────────────────────────────────────────────
- name: Run Robot Framework tests
run: |
robot \\
--outputdir {output_dir} \\
--variable BROWSER:headlesschrome \\
--loglevel INFO \\
{test_directory}
Comment on lines +1480 to +1484
continue-on-error: true # upload results even on test failure

# ── Upload results ──────────────────────────────────────────────────────
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: {project_name}-results-${{{{ github.run_number }}}}
path: {output_dir}
retention-days: 30

# ── Summary ─────────────────────────────────────────────────────────────
- name: Report test outcome
if: always()
run: |
if [ -f {output_dir}output.xml ]; then
python - <<'PY'
import xml.etree.ElementTree as ET, sys
tree = ET.parse("{output_dir}output.xml")
stats = tree.find(".//total/stat")
if stats is not None:
passed = stats.get("pass", "?")
failed = stats.get("fail", "?")
print(f"Results — passed: {{passed}}, failed: {{failed}}")
sys.exit(1 if int(failed) > 0 else 0)
Comment on lines +1500 to +1509
PY
else
echo "output.xml not found — tests may not have run"
exit 1
fi
"""

return workflow

except Exception as e:
return f"UNEXPECTED ERROR: {str(e)}"


def main():
"""Entry point for the Robot Framework MCP server"""
import sys
Expand Down