From f7f30bbf5d2b25d896302525d6f41e7c217d6b0a Mon Sep 17 00:00:00 2001 From: Huaxing YUAN Date: Tue, 8 Sep 2026 21:31:54 +0200 Subject: [PATCH 1/2] MCP stdio plugins for coding agents (Copilot/Codex) + package readmes --- .agents/plugins/marketplace.json | 32 +++++ .vscode/mcp.json | 31 +++++ README.md | 18 ++- plugins/README.md | 18 +++ .../.codex-plugin/plugin.json | 15 +++ plugins/webengine-mobile/.mcp.json | 7 ++ plugins/webengine-mobile/README.md | 38 ++++++ plugins/webengine-mobile/config.toml.snippet | 14 +++ plugins/webengine-mobile/mcp.json | 16 +++ plugins/webengine-mobile/plugin.json | 13 ++ .../skills/webengine-mobile/SKILL.md | 48 +++++++ .../webengine-mobile/references/blockers.md | 23 ++++ .../webengine-mobile/references/codegen.md | 23 ++++ .../webengine-mobile/references/inspection.md | 10 ++ .../webengine-mobile/references/locators.md | 13 ++ .../skills/webengine-scaffold/SKILL.md | 43 +++++++ .../references/structure.md | 42 +++++++ .../webengine-web/.codex-plugin/plugin.json | 15 +++ plugins/webengine-web/.mcp.json | 7 ++ plugins/webengine-web/README.md | 44 +++++++ plugins/webengine-web/config.toml.snippet | 14 +++ plugins/webengine-web/mcp.json | 16 +++ plugins/webengine-web/plugin.json | 13 ++ .../skills/webengine-scaffold/SKILL.md | 43 +++++++ .../references/structure.md | 42 +++++++ .../skills/webengine-web/SKILL.md | 54 ++++++++ .../webengine-web/references/blockers.md | 41 ++++++ .../webengine-web/references/codegen.md | 25 ++++ .../webengine-web/references/inspection.md | 11 ++ .../webengine-web/references/locators.md | 14 +++ .../webengine-web/references/webengine.md | 23 ++++ .../AxaFrance.AxeExtended.HtmlReport.csproj | 5 + .../README.md | 13 ++ .../AxaFrance.AxeExtended.Selenium.csproj | 5 + src/AxaFrance.AxeExtended.Selenium/README.md | 13 ++ .../articles/github-copilot.md | 3 + .../articles/mcp-plugins.md | 87 +++++++++++++ src/AxaFrance.WebEngine.Doc/articles/toc.yml | 2 + .../AxaFrance.WebEngine.Mcp.csproj | 18 +++ .../Extensions/McpCliOptions.cs | 96 ++++++++++++++ .../Extensions/McpServiceExtensions.cs | 118 +++++++++++++++--- src/AxaFrance.WebEngine.Mcp/Program.cs | 45 ++++++- .../Properties/launchSettings.json | 16 +++ src/AxaFrance.WebEngine.Mcp/README.md | 25 ++++ .../AxaFrance.WebEngine.MobileApp.csproj | 5 + src/AxaFrance.WebEngine.MobileApp/README.md | 19 +++ .../AxaFrance.WebEngine.ReportViewer.nuspec | 3 + .../README.md | 11 ++ src/AxaFrance.WebEngine.Runner/README.md | 15 +++ .../axafrance.webengine.webrunner.nuspec | 2 + .../AxaFrance.WebEngine.Web.csproj | 5 + src/AxaFrance.WebEngine.Web/README.md | 18 +++ .../AxaFrance.WebEngine.csproj | 2 + src/AxaFrance.WebEngine/README.md | 15 +++ src/WebEngineMCP.md | 30 ++++- 55 files changed, 1306 insertions(+), 31 deletions(-) create mode 100644 .agents/plugins/marketplace.json create mode 100644 .vscode/mcp.json create mode 100644 plugins/README.md create mode 100644 plugins/webengine-mobile/.codex-plugin/plugin.json create mode 100644 plugins/webengine-mobile/.mcp.json create mode 100644 plugins/webengine-mobile/README.md create mode 100644 plugins/webengine-mobile/config.toml.snippet create mode 100644 plugins/webengine-mobile/mcp.json create mode 100644 plugins/webengine-mobile/plugin.json create mode 100644 plugins/webengine-mobile/skills/webengine-mobile/SKILL.md create mode 100644 plugins/webengine-mobile/skills/webengine-mobile/references/blockers.md create mode 100644 plugins/webengine-mobile/skills/webengine-mobile/references/codegen.md create mode 100644 plugins/webengine-mobile/skills/webengine-mobile/references/inspection.md create mode 100644 plugins/webengine-mobile/skills/webengine-mobile/references/locators.md create mode 100644 plugins/webengine-mobile/skills/webengine-scaffold/SKILL.md create mode 100644 plugins/webengine-mobile/skills/webengine-scaffold/references/structure.md create mode 100644 plugins/webengine-web/.codex-plugin/plugin.json create mode 100644 plugins/webengine-web/.mcp.json create mode 100644 plugins/webengine-web/README.md create mode 100644 plugins/webengine-web/config.toml.snippet create mode 100644 plugins/webengine-web/mcp.json create mode 100644 plugins/webengine-web/plugin.json create mode 100644 plugins/webengine-web/skills/webengine-scaffold/SKILL.md create mode 100644 plugins/webengine-web/skills/webengine-scaffold/references/structure.md create mode 100644 plugins/webengine-web/skills/webengine-web/SKILL.md create mode 100644 plugins/webengine-web/skills/webengine-web/references/blockers.md create mode 100644 plugins/webengine-web/skills/webengine-web/references/codegen.md create mode 100644 plugins/webengine-web/skills/webengine-web/references/inspection.md create mode 100644 plugins/webengine-web/skills/webengine-web/references/locators.md create mode 100644 plugins/webengine-web/skills/webengine-web/references/webengine.md create mode 100644 src/AxaFrance.AxeExtended.HtmlReport/README.md create mode 100644 src/AxaFrance.AxeExtended.Selenium/README.md create mode 100644 src/AxaFrance.WebEngine.Doc/articles/mcp-plugins.md create mode 100644 src/AxaFrance.WebEngine.Mcp/Extensions/McpCliOptions.cs create mode 100644 src/AxaFrance.WebEngine.Mcp/README.md create mode 100644 src/AxaFrance.WebEngine.MobileApp/README.md create mode 100644 src/AxaFrance.WebEngine.ReportViewer/README.md create mode 100644 src/AxaFrance.WebEngine.Runner/README.md create mode 100644 src/AxaFrance.WebEngine.Web/README.md create mode 100644 src/AxaFrance.WebEngine/README.md diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json new file mode 100644 index 0000000..034fa43 --- /dev/null +++ b/.agents/plugins/marketplace.json @@ -0,0 +1,32 @@ +{ + "name": "webengine-plugins", + "interface": { + "displayName": "WebEngine Plugins" + }, + "plugins": [ + { + "name": "webengine-web", + "source": { + "source": "local", + "path": "./plugins/webengine-web" + }, + "policy": { + "installation": "AVAILABLE", + "authentication": "ON_INSTALL" + }, + "category": "Testing" + }, + { + "name": "webengine-mobile", + "source": { + "source": "local", + "path": "./plugins/webengine-mobile" + }, + "policy": { + "installation": "AVAILABLE", + "authentication": "ON_INSTALL" + }, + "category": "Testing" + } + ] +} diff --git a/.vscode/mcp.json b/.vscode/mcp.json new file mode 100644 index 0000000..2fdc7b4 --- /dev/null +++ b/.vscode/mcp.json @@ -0,0 +1,31 @@ +{ + "$comment": "Workspace MCP servers for contributors (local build, no NuGet needed). Build first: dotnet build src/AxaFrance.WebEngine.Mcp -c Release", + "servers": { + "webengine-web": { + "type": "stdio", + "command": "dotnet", + "args": [ + "exec", + "${workspaceFolder}/src/AxaFrance.WebEngine.Mcp/bin/Release/net10.0/AxaFrance.WebEngine.Mcp.dll", + "--profile", + "web", + "--transport", + "stdio" + ], + "cwd": "${workspaceFolder}" + }, + "webengine-mobile": { + "type": "stdio", + "command": "dotnet", + "args": [ + "exec", + "${workspaceFolder}/src/AxaFrance.WebEngine.Mcp/bin/Release/net10.0/AxaFrance.WebEngine.Mcp.dll", + "--profile", + "mobile", + "--transport", + "stdio" + ], + "cwd": "${workspaceFolder}" + } + } +} diff --git a/README.md b/README.md index 622bd99..d70afe0 100644 --- a/README.md +++ b/README.md @@ -38,9 +38,23 @@ WebEngine includes an **MCP server** that enables AI-powered coding agents (like **Key Features:** - **Observe**: Capture page state, inspect elements, and analyze accessibility - **Execute**: Interact with UI elements and perform bulk actions on live applications -- **Generate**: Automatically create PageModels and test scripts in WebEngine format +- **Generate**: Automatically create code in your stack (Playwright+TS, Selenium, WebEngine C#) from observed behavior -**For detailed information on running the MCP server locally, available tools, and how to use it with coding agents, see [WebEngineMCP.md](WebEngineMCP.md).** +**Plugins (stdio, recommended):** one `webengine-mcp` binary, two plugins — `plugins/webengine-web` (Selenium, 29 tools) and `plugins/webengine-mobile` (Appium, 17 tools) — each bundling Agent Skills + MCP server for Copilot (Agent Plugins 1.0) and Codex. Details in `plugins/README.md`. + +**Install the plugins in your coding agent:** + +| Agent | Install | +|---|---| +| GitHub Copilot (VS Code) | `Chat: Install Plugin From Source` → this repo URL, or marketplace `AxaFrance/webengine-dotnet` | +| Codex (CLI/IDE/desktop) | `codex plugin marketplace add AxaFrance/webengine-dotnet --sparse .agents/plugins` | +| Claude Code | copy `plugins//skills/*` to `.claude/skills/` + stdio entry in `mcpServers` | +| OpenCode | copy skills to `.agents/skills/` + `type: local` entry in `opencode.json` | +| Cursor | copy skills to `.cursor/skills/` + entry in `.cursor/mcp.json` | + +Server runs zero-install via `dnx AxaFrance.WebEngine.Mcp --profile --transport stdio` (.NET 10). Full per-agent guide: documentation article *MCP Plugins for Coding Agents*. + +**For detailed information on running the MCP server locally, available tools, and how to use it with coding agents, see [WebEngineMCP.md](src/WebEngineMCP.md).** ## WebEngine 2.0 Roadmap We are working on the next version of WebEngine Framework, in the next versions we will bring. diff --git a/plugins/README.md b/plugins/README.md new file mode 100644 index 0000000..bd5a85f --- /dev/null +++ b/plugins/README.md @@ -0,0 +1,18 @@ +# Plugins + +Two plugins, one binary (`src/AxaFrance.WebEngine.Mcp`, `webengine-mcp` → NuGet `AxaFrance.WebEngine.Mcp`, run via `dnx`). + +| Plugin | Profile | Tools | Bundled skills | +|---|---|---|---| +| `webengine-web/` | `--profile web` (Selenium) | 29 | `webengine-web`, `webengine-scaffold` | +| `webengine-mobile/` | `--profile mobile` (Appium) | 17 | `webengine-mobile`, `webengine-scaffold` | + +Each folder carries **both** packaging formats (they coexist): +- `plugin.json` + `mcp.json` → Agent Plugins 1.0 (VS Code/Copilot) +- `.codex-plugin/plugin.json` + `.mcp.json` → Codex (+ `config.toml.snippet` for manual `config.toml`) + +Repo marketplace (Codex/ChatGPT desktop): `.agents/plugins/marketplace.json`. +Workspace dogfood (local build): `.vscode/mcp.json`. + +Transports: `--transport stdio` for all local plugins (`http` kept for backward compat at `/mcp`). +Install details per plugin: see each `README.md`. diff --git a/plugins/webengine-mobile/.codex-plugin/plugin.json b/plugins/webengine-mobile/.codex-plugin/plugin.json new file mode 100644 index 0000000..e48c2af --- /dev/null +++ b/plugins/webengine-mobile/.codex-plugin/plugin.json @@ -0,0 +1,15 @@ +{ + "name": "webengine-mobile", + "version": "1.0.0", + "description": "Observe and interact with live Android/iOS apps via WebEngine MCP (Appium): snapshot, tap/type/swipe, then generate mobile UI code.", + "author": { + "name": "AXA France", + "url": "https://github.com/AxaFrance/webengine-dotnet" + }, + "homepage": "https://github.com/AxaFrance/webengine-dotnet", + "repository": "https://github.com/AxaFrance/webengine-dotnet", + "license": "MIT", + "keywords": ["webengine", "appium", "mobile", "mcp", "testing"], + "skills": "./skills/", + "mcpServers": "./.mcp.json" +} diff --git a/plugins/webengine-mobile/.mcp.json b/plugins/webengine-mobile/.mcp.json new file mode 100644 index 0000000..41da3fd --- /dev/null +++ b/plugins/webengine-mobile/.mcp.json @@ -0,0 +1,7 @@ +{ + "$comment": "Codex bundled MCP server (direct server map). Requires .NET 10 (dnx), AxaFrance.WebEngine.Mcp on NuGet, and an Appium server (default http://localhost:4723).", + "webengine-mobile": { + "command": "dnx", + "args": ["AxaFrance.WebEngine.Mcp", "--profile", "mobile", "--transport", "stdio"] + } +} diff --git a/plugins/webengine-mobile/README.md b/plugins/webengine-mobile/README.md new file mode 100644 index 0000000..80ccf2f --- /dev/null +++ b/plugins/webengine-mobile/README.md @@ -0,0 +1,38 @@ +# Plugin webengine-mobile (Appium) + +Interactive gate between coding agents and a live Android/iOS app. Bundles skills + MCP server for **VS Code/Copilot** (Agent Plugins 1.0) and **Codex** in one folder. + +``` +plugins/webengine-mobile/ + plugin.json / mcp.json # Agent Plugins 1.0 (Copilot/VS Code) + .codex-plugin/plugin.json / .mcp.json # Codex + skills/webengine-mobile/ # observe/act/generate skill + skills/webengine-scaffold/ # solution scaffolding skill (mirror of web plugin's copy) +``` + +Prerequisites: .NET 10 + `AxaFrance.WebEngine.Mcp` on NuGet (see web plugin README for publish) + Appium server (default `http://localhost:4723`). + +## Option A — GitHub Copilot (VS Code) + +1. `Chat: Install Plugin From Source` → `https://github.com/AxaFrance/webengine-dotnet` (or marketplace / `chat.pluginLocations` for a local clone). +2. Enable the plugin; its MCP server starts automatically. +3. Verify: `webengine-mobile` in `MCP: List Servers`, skills in `Chat: Configure Skills`. + +Manual: copy `skills/webengine-mobile` (+ `skills/webengine-scaffold`) to `.github/skills/`, and `mcp.json` into `.vscode/mcp.json`. + +## Option B — Codex + +```bash +codex plugin marketplace add AxaFrance/webengine-dotnet --sparse .agents/plugins +# then install webengine-mobile from the Plugins Directory +``` + +Manual: `codex mcp add webengine-mobile -- dnx AxaFrance.WebEngine.Mcp --profile mobile --transport stdio` + copy `skills/` to `~/.codex/skills/`. + +## Local build fallback + +See web plugin README (same steps with `--profile mobile`). + +## Use + +`start_session(platform, appPath, deviceName)` → `get_accessibility_snapshot` → `execute_bulk_actions` → `close_session`. Verify: 17 tools. diff --git a/plugins/webengine-mobile/config.toml.snippet b/plugins/webengine-mobile/config.toml.snippet new file mode 100644 index 0000000..33670cf --- /dev/null +++ b/plugins/webengine-mobile/config.toml.snippet @@ -0,0 +1,14 @@ +# Codex CLI — copy into ~/.codex/config.toml (or .codex/config.toml for trusted projects) + +[mcp_servers.webengine-mobile] +command = "dotnet" +args = ["exec", "D:/Projets/Github/webengine-dotnet/src/AxaFrance.WebEngine.Mcp/bin/Release/net10.0/AxaFrance.WebEngine.Mcp.dll", "--profile", "mobile", "--transport", "stdio"] + +# NuGet/dnx variant (no local build, needs .NET 10): +# [mcp_servers.webengine-mobile] +# command = "dnx" +# args = ["AxaFrance.WebEngine.Mcp", "--profile", "mobile", "--transport", "stdio"] + +# Skills: copy skills/webengine-mobile -> ~/.codex/skills/webengine-mobile +# copy skills/webengine-scaffold -> ~/.codex/skills/webengine-scaffold +# Requires Appium server (default http://localhost:4723). diff --git a/plugins/webengine-mobile/mcp.json b/plugins/webengine-mobile/mcp.json new file mode 100644 index 0000000..0c7d8da --- /dev/null +++ b/plugins/webengine-mobile/mcp.json @@ -0,0 +1,16 @@ +{ + "$comment": "Agent Plugins 1.0 portable MCP config (VS Code discovers this file). Requires .NET 10 (dnx), AxaFrance.WebEngine.Mcp on NuGet, and an Appium server (default http://localhost:4723). For a local build see README.md.", + "servers": { + "webengine-mobile": { + "type": "stdio", + "command": "dnx", + "args": ["AxaFrance.WebEngine.Mcp", "--profile", "mobile", "--transport", "stdio"] + } + }, + "mcpServers": { + "webengine-mobile": { + "command": "dnx", + "args": ["AxaFrance.WebEngine.Mcp", "--profile", "mobile", "--transport", "stdio"] + } + } +} diff --git a/plugins/webengine-mobile/plugin.json b/plugins/webengine-mobile/plugin.json new file mode 100644 index 0000000..e1cbe42 --- /dev/null +++ b/plugins/webengine-mobile/plugin.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", + "name": "webengine-mobile", + "version": "1.0.0", + "description": "Observe and interact with live Android/iOS apps via WebEngine MCP (Appium): snapshot, tap/type/swipe, then generate mobile UI code.", + "author": { + "name": "AXA France" + }, + "homepage": "https://github.com/AxaFrance/webengine-dotnet", + "repository": "https://github.com/AxaFrance/webengine-dotnet", + "license": "MIT", + "keywords": ["webengine", "appium", "mobile", "mcp", "testing"] +} diff --git a/plugins/webengine-mobile/skills/webengine-mobile/SKILL.md b/plugins/webengine-mobile/skills/webengine-mobile/SKILL.md new file mode 100644 index 0000000..cbf02e3 --- /dev/null +++ b/plugins/webengine-mobile/skills/webengine-mobile/SKILL.md @@ -0,0 +1,48 @@ +--- +name: webengine-mobile +description: Inspect and interact with live mobile apps via WebEngine MCP (Appium). Use when the user asks to observe an Android/iOS screen, tap/type/swipe, debug an app, or generate mobile UI code (Appium, WebEngine MobileApp C#). +license: MIT +metadata: + author: axafrance + version: "1.0" +--- + +# WebEngine Mobile — observe, act, generate + +You drive a real device/emulator through WebEngine MCP Appium tools. Inspect first, act in bulk, generate in the user's stack. + +## 1. Workflow (always this order) + +1. `start_session(platform: 'Android'|'iOS', appPath, deviceName)` → `sessionId`. Ask for missing platform/app/device before proceeding. +2. `get_accessibility_snapshot(sessionId)` — default inspection (`[Role] accId="…" id="…" text="…"`). +3. `execute_bulk_actions(sessionId, [...])` — batch all same-screen actions. +4. `close_session(sessionId)` → log path. Share it. + +Escalate to `get_page_source_chunk` / `get_page_source` only for missing attributes — see [inspection policy](references/inspection.md). + +## 2. Locators (critical) + +Priority: `AccessibilityId` (`content-desc`) > `Id` (short resource-id, strip package) > `UIAutomatorSelector` (Android) / `IosClassChain` / `IosPredicate` (iOS) > `Text` > `ClassName`+attribute > `XPath` (last resort). +Full table + native type map: [locators](references/locators.md). + +## 3. Acting + +- Single: `tap_element`, `long_press_element(durationMs)`, `type_text(clearFirst)`, `clear_text`, `wait_for_element`, `swipe_screen(Up|Down|Left|Right)`, `press_back` (Android), `hide_keyboard`, `take_screenshot`, `get_element_text`. +- Bulk `ActionType`: `Tap|TypeText|SetText|Clear|LongPress|SwipeUp|SwipeDown|SwipeLeft|SwipeRight`. Continues on failure. +- Every success returns `Element tag: <...xml.../>` — ground truth for codegen. + +## 4. Gestures + +`SwipeScreen Up` reveals content below; `Down` reveals above. After swipe, re-snapshot. `HideKeyboard` before tapping covered buttons. iOS back = tap visible back element, not `press_back`. + +## 5. Generate code in the USER's stack + +Ask once if unknown: language + framework (WebEngine MobileApp C#? raw Appium Java/Python?). +Mapping + PageModel rules: [codegen](references/codegen.md). Full solution scaffolding: skill `webengine-scaffold`. + +## 6. Blocking points (system dialogs, onboarding, keyboard) + +Protocol: failure → re-snapshot FIRST → classify (dismissible vs escalate) → dismiss once → re-snapshot → retry once. +Dismiss: permissions per scenario need, onboarding/rate-us carousels, OS popups if tappable, keyboard via `hide_keyboard` before taps. +STOP + ask human: biometric/PIN, wrong app start-state (report, don't improvise a login). +Full catalog + script setup rules: [blockers](references/blockers.md). diff --git a/plugins/webengine-mobile/skills/webengine-mobile/references/blockers.md b/plugins/webengine-mobile/skills/webengine-mobile/references/blockers.md new file mode 100644 index 0000000..ea42859 --- /dev/null +++ b/plugins/webengine-mobile/skills/webengine-mobile/references/blockers.md @@ -0,0 +1,23 @@ +# Blocking points (mobile) — system and app overlays that are NOT the test subject + +Same protocol as web: Detect → Classify → Dismiss → Re-snapshot → Retry once. +Mobile adds one rule: **never improvise around identity or system state** — wrong start state (logged-out vs logged-in, fresh install vs upgraded) invalidates the whole run. If the app is not in the expected state, report instead of improvising a login. + +## Catalog + +| Blocker | Detection signal | Treatment | +|---|---|---| +| System permission dialog (camera, location, notifications) | System package in source, buttons « Autoriser »/« Allow » | Accept/deny per scenario need via text locator; log as setup | +| Biometric / PIN / lock screen | Non-app screen, no app hierarchy | STOP, ask the human to authenticate, resume | +| Onboarding carousel, push-opt-in, rate-us prompt | First-launch screens before the expected start screen | Dismiss once (swipe/close), log as setup | +| Software keyboard covering the CTA | Tap fails on a button below the input | `hide_keyboard` before tapping; re-snapshot | +| OS popups (update, battery saver) | System UI outside app | Dismiss if tappable, else escalate | +| Spinner / skeleton lists | Same snapshot twice with loading indicators | `wait_for_element` on the next stable element, not a fixed sleep | +| WebView screens | Web content inside native tree | Native locators only (see skill); no context switching | +| Deep link / cold start variance | App opens on unexpected screen | Re-align: navigate within app to the expected start screen, or report wrong-state | + +## For generated scripts + +- Setup ensures start state (reinstall flag or logout/login keyword) before the first step. +- Permission/onboarding dismissal as exception-safe setup keywords with `Exists()` guards. +- Every tap after text input calls hide-keyboard first (page-object helper). diff --git a/plugins/webengine-mobile/skills/webengine-mobile/references/codegen.md b/plugins/webengine-mobile/skills/webengine-mobile/references/codegen.md new file mode 100644 index 0000000..0d443bd --- /dev/null +++ b/plugins/webengine-mobile/skills/webengine-mobile/references/codegen.md @@ -0,0 +1,23 @@ +# Codegen from ElementTag (mobile) + +## WebEngine MobileApp C# + +```csharp +using AxaFrance.WebEngine.MobileApp; +namespace MyProject.PageModels { + public class LoginPage : PageModel { + // Source: + public AppElementDescription EmailField { get; set; } = new() { AccessibilityId = "email_field" }; + // Source: + public AppElementDescription ErrorMessage { get; set; } = new() { Id = "error_text" }; + public LoginPage(WebDriver driver) : base(driver) { } + } +} +// driver: AppFactory.GetDriver(Platform.Android) +``` + +Rules: descriptions in PageModel only; ctor takes `WebDriver`. Keyword: `SharedActionApp.DoAction(AppiumDriver)/DoCheckpoint(AppiumDriver)`. + +## Raw Appium (Java/Python) + +`AccessibilityId`→`AppiumBy.accessibilityId`, `Id`→`AppiumBy.id("email")`, Android→`AppiumBy.androidUIAutomator(...)`, iOS→`AppiumBy.iOSClassChain(...)`, `Text`→XPath last resort. diff --git a/plugins/webengine-mobile/skills/webengine-mobile/references/inspection.md b/plugins/webengine-mobile/skills/webengine-mobile/references/inspection.md new file mode 100644 index 0000000..128830e --- /dev/null +++ b/plugins/webengine-mobile/skills/webengine-mobile/references/inspection.md @@ -0,0 +1,10 @@ +# Inspection policy (mobile) + +| Tool | When | +|---|---| +| `get_accessibility_snapshot` | First choice — one line per element, ~5-30 KB | +| `get_page_source_chunk(0, 15KB)` | Snapshot missing bounds/platform attributes | +| `get_page_source` | Rarely — full XML | +| `take_screenshot` | Visual verify / debug | + +Re-inspect only: element missing, screen/activity change, alert to verify, locator failure. Batch same-screen actions via `execute_bulk_actions`. diff --git a/plugins/webengine-mobile/skills/webengine-mobile/references/locators.md b/plugins/webengine-mobile/skills/webengine-mobile/references/locators.md new file mode 100644 index 0000000..b36dc60 --- /dev/null +++ b/plugins/webengine-mobile/skills/webengine-mobile/references/locators.md @@ -0,0 +1,13 @@ +# Locators (mobile) + +Priority: `AccessibilityId` (`content-desc`) > `Id` (short resource-id) > `UIAutomatorSelector` (Android) / `IosClassChain` / `IosPredicate` (iOS) > `Text` > `ClassName`+attribute > `XPath` (last resort). + +| Captured tag | AppElementDescription | +|---|---| +| `content-desc="login_button"` | `AccessibilityId = "login_button"` | +| `resource-id="com.app:id/email"` | `Id = "email"` (strip package) | +| `text="Sign in"` + Button class | `Text="Sign in", ClassName="android.widget.Button"` | +| No stable id (Android) | `UIAutomatorSelector = "new UiSelector().text(\"Email\")"` | +| iOS label Email | ``IosClassChain = "**/XCUIElementTypeTextField[`label == 'Email'`]"`` | + +Native types: EditText/TextField, Button, CheckBox, Switch, Spinner/PickerWheel, TextView/Cell. Combine attributes for uniqueness; `Index` only when unavoidable. diff --git a/plugins/webengine-mobile/skills/webengine-scaffold/SKILL.md b/plugins/webengine-mobile/skills/webengine-scaffold/SKILL.md new file mode 100644 index 0000000..fc69d8d --- /dev/null +++ b/plugins/webengine-mobile/skills/webengine-scaffold/SKILL.md @@ -0,0 +1,43 @@ +--- +name: webengine-scaffold +description: Scaffold a UI automation solution with WebEngine (C#) or a generic stack. Use when the user asks to create a test project, choose gherkin/unit/keyword/data-driven, install packages, or structure PageModels/Actions/TestCases. +license: MIT +metadata: + author: axafrance + version: "1.0" +--- + +# WebEngine Scaffold — from observation to solution + + + +Use after `webengine-web` / `webengine-mobile` observation, or standalone when bootstrapping a project. + +## 1. Ask two questions first (if unknown) + +1. **Stack**: WebEngine C# (Selenium/Appium)? Or generic (Playwright TS, Appium Java/Python)? Default: follow current repo. +2. **Approach** (WebEngine C#): + - Linear Scripting — simple/unit, PageModels directly, no SharedAction/TestCase. + - BDD/Gherkin (Reqnroll) — `.feature` + step defs (ask for step class if missing). + - Keyword-Driven — `PageModels/` + `Actions/SharedAction*` + `TestCases/TestCase*` + `TestData/` XML. + - Data-Driven — parameterize with XML/Excel datasets. + +Detect existing approach from project structure and state it before generating. + +## 2. Packages and drivers + +- Web: `AxaFrance.WebEngine.Web`; Mobile: `AxaFrance.WebEngine.MobileApp`; Keyword only: `AxaFrance.WebEngine.Runner`. Check refs; install or ask user. +- Driver: `BrowserFactory.GetDriver(Platform.Windows, BrowserType.Chrome)` / `AppFactory.GetDriver(Platform.Android)`. +- Generic stacks: Playwright `npm i -D @playwright/test`, Appium Java/Python per their docs — then map observed `ElementTag`s (see web/mobile skill codegen refs). + +## 3. Generation rules (all approaches) + +- Locators from captured `ElementTag` only; PageModel owns descriptions (never in SharedAction/test). +- PageModel: properties `get; set;`, no driver in description ctor, ctor takes `WebDriver`. +- SharedAction: `DoAction` + `DoCheckpoint` (Arrange-Assert), `RequiredParameters => null` unless specified, externalize data via `GetParameter` + `ParameterList`. +- Overlays are not the test but break the run: always generate a `DismissOverlays()` setup (cookie-accept + promo-close, `Exists()`-guarded, exception-safe, called in `TestInitialize`/`BeforeScenario`) plus `Exists(timeout)` guards before clicks on overlay-prone pages. See web/mobile skill `references/blockers.md`. +- Structure: [structure](references/structure.md). Test-data XML + `ParameterList`: same file. + +## 4. Output order + +1. PageModels from log tags. 2. Actions/steps per approach. 3. TestCases + data. 4. Run instructions. diff --git a/plugins/webengine-mobile/skills/webengine-scaffold/references/structure.md b/plugins/webengine-mobile/skills/webengine-scaffold/references/structure.md new file mode 100644 index 0000000..34e8087 --- /dev/null +++ b/plugins/webengine-mobile/skills/webengine-scaffold/references/structure.md @@ -0,0 +1,42 @@ +# Structure + test data (scaffold) + +## Keyword-Driven layout (inside project folder) + +``` +PageModels/ <- *ElementDescription + PageModel +Actions/ <- SharedActionWeb / SharedActionApp (DoAction + DoCheckpoint) +TestCases/ <- TestCaseWeb / TestCaseApp with TestSteps[] +TestData/ <- XML datasets +ParameterList.cs <- string constants, use GetParameter(ParameterList.X) +``` + +TestCase: +```csharp +[Description("Car insurance quote")] +public class TC_InsuranceQuote : TestCaseWeb { + public TC_InsuranceQuote() { + TestSteps = new TestStep[] { + new() { Action = nameof(Login) }, + new() { Action = nameof(ValidateQuote) } }; + } +} +``` + +Test data XML (`http://www.axa.fr/WebEngine/2022`): +```xml + + Devis_Auto_Standard + + TESTCASEDevis_Auto_Standard + URLhttps://www.example.com/devis + + +``` + +ParameterList: +```csharp +public static class ParameterList { + /// Target environment URL + public static string URL { get; } = "URL"; +} +``` diff --git a/plugins/webengine-web/.codex-plugin/plugin.json b/plugins/webengine-web/.codex-plugin/plugin.json new file mode 100644 index 0000000..6a01f0a --- /dev/null +++ b/plugins/webengine-web/.codex-plugin/plugin.json @@ -0,0 +1,15 @@ +{ + "name": "webengine-web", + "version": "1.0.0", + "description": "Observe and interact with live web pages via WebEngine MCP (Selenium): snapshot, click/type/select, then generate UI code in any stack.", + "author": { + "name": "AXA France", + "url": "https://github.com/AxaFrance/webengine-dotnet" + }, + "homepage": "https://github.com/AxaFrance/webengine-dotnet", + "repository": "https://github.com/AxaFrance/webengine-dotnet", + "license": "MIT", + "keywords": ["webengine", "selenium", "browser", "mcp", "testing"], + "skills": "./skills/", + "mcpServers": "./.mcp.json" +} diff --git a/plugins/webengine-web/.mcp.json b/plugins/webengine-web/.mcp.json new file mode 100644 index 0000000..854946a --- /dev/null +++ b/plugins/webengine-web/.mcp.json @@ -0,0 +1,7 @@ +{ + "$comment": "Codex bundled MCP server (direct server map). Requires .NET 10 (dnx) and AxaFrance.WebEngine.Mcp published to NuGet. No local build needed.", + "webengine-web": { + "command": "dnx", + "args": ["AxaFrance.WebEngine.Mcp", "--profile", "web", "--transport", "stdio"] + } +} diff --git a/plugins/webengine-web/README.md b/plugins/webengine-web/README.md new file mode 100644 index 0000000..aaee8dd --- /dev/null +++ b/plugins/webengine-web/README.md @@ -0,0 +1,44 @@ +# Plugin webengine-web (Selenium) + +Interactive gate between coding agents and a live browser. Bundles skills + MCP server for **VS Code/Copilot** (Agent Plugins 1.0) and **Codex** in one folder. + +``` +plugins/webengine-web/ + plugin.json # Agent Plugins 1.0 manifest (Copilot/VS Code) + mcp.json # portable MCP config (VS Code discovers it) + .codex-plugin/plugin.json # Codex manifest + .mcp.json # Codex bundled MCP server + skills/webengine-web/ # observe/act/generate skill + skills/webengine-scaffold/# solution scaffolding skill +``` + +Prerequisite (both agents): .NET 10 + package on NuGet (`dnx AxaFrance.WebEngine.Mcp ...`). +Publish it once: `dotnet pack src/AxaFrance.WebEngine.Mcp -c Release` then `dotnet nuget push *.nupkg --source https://api.nuget.org/v3/index.json --api-key `. + +## Option A — GitHub Copilot (VS Code) + +1. `Chat: Install Plugin From Source` → paste `https://github.com/AxaFrance/webengine-dotnet` (or add marketplace `AxaFrance/webengine-dotnet` via `chat.plugins.marketplaces`, or register a local clone via `chat.pluginLocations`). +2. Enable the plugin; its MCP server starts automatically (no separate trust prompt). +3. Verify: `webengine-web` in `MCP: List Servers`, skills in `Chat: Configure Skills`. + +Without plugin install (manual): copy `skills/webengine-web` (+ `skills/webengine-scaffold`) to `.github/skills/`, and `mcp.json` content into `.vscode/mcp.json`. + +## Option B — Codex (CLI / IDE / desktop) + +```bash +codex plugin marketplace add AxaFrance/webengine-dotnet --sparse .agents/plugins +# then install webengine-web from the Plugins Directory (marketplace: WebEngine Plugins) +``` + +Manual alternative: `codex mcp add webengine-web -- dnx AxaFrance.WebEngine.Mcp --profile web --transport stdio` ++ copy `skills/` to `~/.codex/skills/`. This repo also ships `.agents/plugins/marketplace.json` for repo-scoped installs. + +## Local build fallback (before NuGet publish) + +`dotnet build src/AxaFrance.WebEngine.Mcp -c Release`, then replace the `dnx` command with +`dotnet exec /src/AxaFrance.WebEngine.Mcp/bin/Release/net10.0/AxaFrance.WebEngine.Mcp.dll --profile web --transport stdio`. +NEVER use plain `dotnet run` (its launch banner pollutes stdout). This repo's own `.vscode/mcp.json` already uses this form. + +## Use + +Ask the agent to observe/act in the browser; it generates code in YOUR stack (Playwright TS, Selenium, WebEngine C#). Verify: 29 tools, `initialize` returns server instructions. diff --git a/plugins/webengine-web/config.toml.snippet b/plugins/webengine-web/config.toml.snippet new file mode 100644 index 0000000..a1dbc25 --- /dev/null +++ b/plugins/webengine-web/config.toml.snippet @@ -0,0 +1,14 @@ +# Codex CLI — copy into ~/.codex/config.toml (or .codex/config.toml for trusted projects) + +[mcp_servers.webengine-web] +command = "dotnet" +args = ["exec", "D:/Projets/Github/webengine-dotnet/src/AxaFrance.WebEngine.Mcp/bin/Release/net10.0/AxaFrance.WebEngine.Mcp.dll", "--profile", "web", "--transport", "stdio"] + +# NuGet/dnx variant (no local build, needs .NET 10): +# [mcp_servers.webengine-web] +# command = "dnx" +# args = ["AxaFrance.WebEngine.Mcp", "--profile", "web", "--transport", "stdio"] + +# Skills: copy skills/webengine-web -> ~/.codex/skills/webengine-web +# copy skills/webengine-scaffold -> ~/.codex/skills/webengine-scaffold +# Then: codex mcp list diff --git a/plugins/webengine-web/mcp.json b/plugins/webengine-web/mcp.json new file mode 100644 index 0000000..8d0abdb --- /dev/null +++ b/plugins/webengine-web/mcp.json @@ -0,0 +1,16 @@ +{ + "$comment": "Agent Plugins 1.0 portable MCP config (VS Code discovers this file). Both keys carried: 'servers' (VS Code style) and 'mcpServers' (Claude style). Requires .NET 10 (dnx) and AxaFrance.WebEngine.Mcp on NuGet; for a local build see README.md.", + "servers": { + "webengine-web": { + "type": "stdio", + "command": "dnx", + "args": ["AxaFrance.WebEngine.Mcp", "--profile", "web", "--transport", "stdio"] + } + }, + "mcpServers": { + "webengine-web": { + "command": "dnx", + "args": ["AxaFrance.WebEngine.Mcp", "--profile", "web", "--transport", "stdio"] + } + } +} diff --git a/plugins/webengine-web/plugin.json b/plugins/webengine-web/plugin.json new file mode 100644 index 0000000..0f4bce4 --- /dev/null +++ b/plugins/webengine-web/plugin.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", + "name": "webengine-web", + "version": "1.0.0", + "description": "Observe and interact with live web pages via WebEngine MCP (Selenium): snapshot, click/type/select, then generate UI code in any stack.", + "author": { + "name": "AXA France" + }, + "homepage": "https://github.com/AxaFrance/webengine-dotnet", + "repository": "https://github.com/AxaFrance/webengine-dotnet", + "license": "MIT", + "keywords": ["webengine", "selenium", "browser", "mcp", "testing", "playwright-codegen"] +} diff --git a/plugins/webengine-web/skills/webengine-scaffold/SKILL.md b/plugins/webengine-web/skills/webengine-scaffold/SKILL.md new file mode 100644 index 0000000..e69a83e --- /dev/null +++ b/plugins/webengine-web/skills/webengine-scaffold/SKILL.md @@ -0,0 +1,43 @@ +--- +name: webengine-scaffold +description: Scaffold a UI automation solution with WebEngine (C#) or a generic stack. Use when the user asks to create a test project, choose gherkin/unit/keyword/data-driven, install packages, or structure PageModels/Actions/TestCases. +license: MIT +metadata: + author: axafrance + version: "1.0" +--- + +# WebEngine Scaffold — from observation to solution + + + +Use after `webengine-web` / `webengine-mobile` observation, or standalone when bootstrapping a project. + +## 1. Ask two questions first (if unknown) + +1. **Stack**: WebEngine C# (Selenium/Appium)? Or generic (Playwright TS, Appium Java/Python)? Default: follow current repo. +2. **Approach** (WebEngine C#): + - Linear Scripting — simple/unit, PageModels directly, no SharedAction/TestCase. + - BDD/Gherkin (Reqnroll) — `.feature` + step defs (ask for step class if missing). + - Keyword-Driven — `PageModels/` + `Actions/SharedAction*` + `TestCases/TestCase*` + `TestData/` XML. + - Data-Driven — parameterize with XML/Excel datasets. + +Detect existing approach from project structure and state it before generating. + +## 2. Packages and drivers + +- Web: `AxaFrance.WebEngine.Web`; Mobile: `AxaFrance.WebEngine.MobileApp`; Keyword only: `AxaFrance.WebEngine.Runner`. Check refs; install or ask user. +- Driver: `BrowserFactory.GetDriver(Platform.Windows, BrowserType.Chrome)` / `AppFactory.GetDriver(Platform.Android)`. +- Generic stacks: Playwright `npm i -D @playwright/test`, Appium Java/Python per their docs — then map observed `ElementTag`s (see web/mobile skill codegen refs). + +## 3. Generation rules (all approaches) + +- Locators from captured `ElementTag` only; PageModel owns descriptions (never in SharedAction/test). +- PageModel: properties `get; set;`, no driver in description ctor, ctor takes `WebDriver`. +- SharedAction: `DoAction` + `DoCheckpoint` (Arrange-Assert), `RequiredParameters => null` unless specified, externalize data via `GetParameter` + `ParameterList`. +- Overlays are not the test but break the run: always generate a `DismissOverlays()` setup (cookie-accept + promo-close, `Exists()`-guarded, exception-safe, called in `TestInitialize`/`BeforeScenario`) plus `Exists(timeout)` guards before clicks on overlay-prone pages. See web/mobile skill `references/blockers.md`. +- Structure: [structure](references/structure.md). Test-data XML + `ParameterList`: same file. + +## 4. Output order + +1. PageModels from log tags. 2. Actions/steps per approach. 3. TestCases + data. 4. Run instructions. diff --git a/plugins/webengine-web/skills/webengine-scaffold/references/structure.md b/plugins/webengine-web/skills/webengine-scaffold/references/structure.md new file mode 100644 index 0000000..34e8087 --- /dev/null +++ b/plugins/webengine-web/skills/webengine-scaffold/references/structure.md @@ -0,0 +1,42 @@ +# Structure + test data (scaffold) + +## Keyword-Driven layout (inside project folder) + +``` +PageModels/ <- *ElementDescription + PageModel +Actions/ <- SharedActionWeb / SharedActionApp (DoAction + DoCheckpoint) +TestCases/ <- TestCaseWeb / TestCaseApp with TestSteps[] +TestData/ <- XML datasets +ParameterList.cs <- string constants, use GetParameter(ParameterList.X) +``` + +TestCase: +```csharp +[Description("Car insurance quote")] +public class TC_InsuranceQuote : TestCaseWeb { + public TC_InsuranceQuote() { + TestSteps = new TestStep[] { + new() { Action = nameof(Login) }, + new() { Action = nameof(ValidateQuote) } }; + } +} +``` + +Test data XML (`http://www.axa.fr/WebEngine/2022`): +```xml + + Devis_Auto_Standard + + TESTCASEDevis_Auto_Standard + URLhttps://www.example.com/devis + + +``` + +ParameterList: +```csharp +public static class ParameterList { + /// Target environment URL + public static string URL { get; } = "URL"; +} +``` diff --git a/plugins/webengine-web/skills/webengine-web/SKILL.md b/plugins/webengine-web/skills/webengine-web/SKILL.md new file mode 100644 index 0000000..4e9c959 --- /dev/null +++ b/plugins/webengine-web/skills/webengine-web/SKILL.md @@ -0,0 +1,54 @@ +--- +name: webengine-web +description: Inspect and interact with live web pages via WebEngine MCP (Selenium). Use when the user asks to observe a browser, click/type/select, debug a page, or generate web UI code (Playwright TS, Selenium C#/Java/Python, WebEngine C#). +license: MIT +metadata: + author: axafrance + version: "1.0" +--- + +# WebEngine Web — observe, act, generate + +You drive a real browser through WebEngine MCP tools. You do NOT guess DOM. You inspect first, act in bulk, then generate code in the user's stack. + +## 1. Workflow (always this order) + +1. `start_session(browserType: 'Chrome', headless: false)` → `sessionId`. +2. `navigate_to(sessionId, url)`. +3. `get_accessibility_snapshot(sessionId)` — default inspection. Compact, one ref per element. +4. `execute_bulk_actions(sessionId, [...])` — submit ALL actions from one snapshot in one call. +5. `close_session(sessionId)` → action-log path. Share it with the user. + +Escalate inspection only when needed — see [inspection policy](references/inspection.md). + +## 2. Refs and locators (critical) + +- Every element in the snapshot has `ref=N`. Pass it as `Element.Ref` — zero-guess mapping (Playwright-style). +- Refs die on re-render (React/Vue/Angular). After any mutating action, re-snapshot before new refs. +- Fallback priority: `Id` > `Name` > test attribute (`data-testid`) > `aria-label` > `TagName+InnerText` > `LinkText` (links only) > `ClassName` (stable only) > `CssSelector`/`XPath` (last resort). +- Full table + examples: [locators](references/locators.md). + +## 3. Acting + +- Single tools: `click_element`, `type_text`, `set_text`, `select_from_dropdown_by_text|value`, `check_element`, `uncheck_element`, `wait_for_element`, `scroll_by`, `scroll_to_element`, `click_at` (canvas last resort), `take_screenshot`, `execute_script`. +- Bulk `ActionType`: `Click|TypeText|SetText|Clear|SelectByText|SelectByValue|Check|Uncheck`. Continues on failure — check `Results[]`, retry failures individually. +- Every success returns `Element tag: <...>` — the ground truth for codegen. Never invent a locator. + +## 4. Generate code in the USER's stack + +Ask once if unknown: language + framework (Playwright TS? Selenium C#? WebEngine C#? Java? Python?). + +- Mapping `ElementTag` → target stack: [codegen](references/codegen.md). +- For WebEngine C# (PageModel + approaches): [webengine](references/webengine.md). +- For scaffolding a full solution (gherkin/unit/keyword/data-driven): activate skill `webengine-scaffold`. + +## 5. Rules + +- Re-inspect only on: missing element, URL change, validation message to verify, locator failure. +- Batch same-page actions; split on navigation. +- Communicate the `close_session` log path; offer to generate PageModels/tests from it. + +## 6. Blocking points (cookies, modals, traps) + +Overlays are not the test but they break it. Protocol: failed/intercepted click → re-snapshot FIRST (never blind-retry) → classify (dismissible vs escalate) → dismiss once → re-snapshot (old refs are dead) → retry original action once → log dismissal as setup. +Dismiss: cookies/promo/chat/survey/sticky bars. STOP + report: CAPTCHA/bot wall, unexpected login/SSO, payment iframe. Validation errors are CHECKPOINTS (capture, report, stop — don't invent data). Full catalog + script setup rules (`DismissOverlays()` with `Exists()` guards): [blockers](references/blockers.md). diff --git a/plugins/webengine-web/skills/webengine-web/references/blockers.md b/plugins/webengine-web/skills/webengine-web/references/blockers.md new file mode 100644 index 0000000..5919119 --- /dev/null +++ b/plugins/webengine-web/skills/webengine-web/references/blockers.md @@ -0,0 +1,41 @@ +# Blocking points (web) — overlays and traps that are NOT the test subject + +Learned from real runs (e.g. axa.fr: cookie dialog intercepted the first click on « Devis Habitation »). +Rule of thumb: the agent optimizes the happy path and will retry a failed click blindly. +Never do that — an interception almost always means the DOM changed under you. + +## Protocol: Detect → Classify → Dismiss → Re-snapshot → Retry once + +1. **Detect**: click fails (`intercepted`, `not clickable`, `stale`) or refs suddenly match nothing → re-snapshot FIRST, do not retry the same ref. +2. **Classify** the overlay from the fresh snapshot: + - Dismissible → step 3 (cookies, promo, chat, survey, sticky bars). + - Escalate → STOP and report (CAPTCHA, bot wall, unexpected login/SSO, payment 3DS). +3. **Dismiss once** via its own close/accept control (prefer `Id`/test-id). One attempt only. +4. **Re-snapshot** (dismissal re-renders; old refs are dead), then retry the original action **once**. +5. **Log** the dismissal as a setup step. Never assert business behavior on an overlay. + +## Catalog + +| Blocker | Detection signal in snapshot | Treatment | +|---|---|---| +| Consent / cookies (OneTrust `#onetrust-accept-btn-handler`, Axeptio, custom e.g. `id="footer_tc_privacy_button"`) | `[dialog]` + accept/personalize buttons, sometimes in iframe | Accept once per session; guard with `Exists()` in scripts | +| Promo / newsletter / exit-intent modal | `[dialog]` appearing after delay/scroll, close X | Close, never fill marketing fields unless the scenario asks | +| Chat / survey widgets (Intercom, Qualtrics) | Fixed corner iframe covering CTAs | Close widget, or `scroll_to_element` (center) before clicking | +| Sticky header/footer, floating cookie-settings button | Click lands on wrong element at viewport edge | Scroll target to center, screenshot-verify | +| Unexpected login / SSO / MFA redirect | URL jumps to login domain | STOP, ask user to authenticate, then resume. Never invent credentials | +| CAPTCHA / bot wall (Datadome, Cloudflare challenge) | Checkbox « je ne suis pas un robot », « verify you are human » | STOP + report as blocked. Never attempt bypass | +| New tab opened by click | URL + snapshot unchanged after click | Known MCP gap (no switch-window tool): read target `href` from `ElementTag` and `navigate_to` it directly; note it in the report | +| Iframe content (payment, embeds) | `