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
2 changes: 1 addition & 1 deletion plugins/flutter-marionette/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "flutter-marionette",
"displayName": "Flutter Marionette",
"description": "LeanCode Marionette MCP setup and usage — runtime interaction with a live Flutter debug app for AI-driven exploration, smoke verification, and UI debugging.",
"version": "0.1.0",
"version": "0.1.1",
"author": {
"name": "LeanCode"
},
Expand Down
13 changes: 13 additions & 0 deletions plugins/flutter-marionette/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Changelog

## 0.1.1

- Log collection: `LoggingLogCollector` and `LoggerLogCollector` come from the separate `marionette_logging` and `marionette_logger` packages, not from `marionette_flutter`. The README and `references/marionette.md` now say which package to add.
- `references/marionette.md`: the tool list matches Marionette MCP 0.6.0, with selectors for `tap` and `enter_text`, and the gesture, key, restart, device-config and custom-extension tools.
- Build mode: Marionette works in debug and profile builds, not only debug.
- `connect` fails when `marionette_mcp` and `marionette_flutter` versions differ; the README says how to align them.
- `flutter-marionette-usage` description names the runtime actions (tap, type, scroll, screenshots, smoke tests) so the skill loads for them.

## 0.1.0

- Initial `flutter-marionette` plugin in the public marketplace.
24 changes: 18 additions & 6 deletions plugins/flutter-marionette/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ LeanCode Flutter plugin for [Marionette MCP](https://github.com/leancodepl/mario
| Runs against | Live `flutter run` debug session | `patrol develop` / `patrol test` |
| Test files | None; the agent drives the app | Dart test files in `patrol_test/` |
| Best for | Iterating on a feature, smoke after refactor | Regression-proof suites in CI |
| Build mode | Debug only | Debug + release |
| Build mode | Debug and profile | Debug + release |

Both plugins can coexist; they solve different parts of the AI-assisted testing workflow.

Expand Down Expand Up @@ -106,17 +106,29 @@ flutter run

Copy the VM service URI from the run output (format: `ws://127.0.0.1:PORT/ws`). In your AI agent, call the `connect` tool with that URI.

`connect` checks that `marionette_mcp` and the app's `marionette_flutter` are the same version and fails if they differ. After upgrading `marionette_flutter`, activate the matching server too: `dart pub global activate marionette_mcp <version>`.

## Log collection

`get_logs` requires a configured `LogCollector`.
`get_logs` needs a `LogCollector`, passed as `MarionetteConfiguration(logCollector: ...)`. The collectors for the two common logging packages ship as separate packages:

| Your app logs with | Add to the app | Collector |
| --- | --- | --- |
| [`logging`](https://pub.dev/packages/logging) | `flutter pub add marionette_logging` | `LoggingLogCollector()` from `package:marionette_logging/marionette_logging.dart` |
| [`logger`](https://pub.dev/packages/logger) | `flutter pub add marionette_logger` | `LoggerLogCollector()` from `package:marionette_logger/marionette_logger.dart`. Also add it to your `Logger`'s outputs. |
| Anything else | Nothing, it ships in `marionette_flutter` | `PrintLogCollector()`. Call `collector.addLog(message)` wherever your logs flow. |

```dart
MarionetteBinding.ensureInitialized(
MarionetteConfiguration(logCollector: LoggingLogCollector()),
);
```

- Use `LoggingLogCollector()` for apps using the `logging` package.
- Use `LoggerLogCollector()` for apps using the `logger` package.
- Use `PrintLogCollector()` for custom logging setups.
`get_logs` returns the logs since app start or the last hot reload. Without a collector it explains how to enable one. Upstream docs: [Log Collection](https://github.com/leancodepl/marionette_mcp/blob/main/docs/logging.md).

## Build-mode constraint

Marionette relies on Flutter's VM Service and is intended for a live `flutter run` session. It does not work in release builds. Use Patrol for release-mode automation.
Marionette relies on Flutter's VM Service, so it works in debug and profile builds, not in release builds. The `kDebugMode` guard above enables it in debug only; use `!kReleaseMode` to include profile builds. Use Patrol for release-mode automation.

## Example usage

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: flutter-marionette-usage
description: Explain what the `flutter-marionette` plugin does and how to use it. Use when the user invokes `/flutter-marionette-usage`, asks what this plugin covers, or needs help with Marionette MCP setup, runtime exploration, or custom widget configuration.
description: Explain what the `flutter-marionette` plugin does and how to drive a running Flutter app through Marionette MCP. Use when the user invokes `/flutter-marionette-usage`, asks what this plugin covers, wants Claude to tap, type, scroll, take screenshots or smoke-test a running Flutter app, or needs help with Marionette MCP setup, `get_logs`, or custom widget configuration.
---

# Marionette Usage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Both are LeanCode testing tools, but they solve different problems:
| Runs against | A live `flutter run` debug session | `patrol develop` or `patrol test` runner |
| Test files | None — agent drives the app interactively | Dart test files in `patrol_test/` |
| Best for | "Does my new feature work?", smoke after refactor, debugging unresponsive UI | Regression-proof test suites in CI |
| Build mode | Debug only (requires VM Service) | Debug + release (via Patrol runner) |
| Build mode | Debug and profile, not release (requires VM Service) | Debug + release (via Patrol runner) |

Rules of thumb:
- Iterating on a new feature and want the agent to click around? **Marionette.**
Expand All @@ -32,14 +32,21 @@ Prepare the Flutter app before trying to drive it:

When the `marionette` MCP server is configured and connected, use these tools:

- `connect` / `disconnect` — manage the VM service connection. Call `connect` first, passing the `ws://127.0.0.1:PORT/ws` URI printed by `flutter run`.
- `get_interactive_elements` — retrieve the list of currently tappable / actionable UI components. **Always call this before `tap` / `enter_text`** to see what is available and to get stable identifiers.
- `tap` — simulate a button or element press.
- `enter_text` — input data into a text field.
- `scroll_to` — scroll an off-screen element into view.
- `take_screenshots` — capture the current app visuals as base64. Use to verify visual state after an action or to debug why something is not found.
- `get_logs` — retrieve application logs. Use when an action silently fails or state is unclear.
- `hot_reload` — apply code changes without losing app state. Useful when iterating on a feature and wanting to re-check it without restarting.
- `connect` / `disconnect`: manage the VM service connection. Call `connect` first, passing the `ws://127.0.0.1:PORT/ws` URI printed by `flutter run`. It fails when the `marionette_mcp` server and the app's `marionette_flutter` are different versions; align them.
- `get_interactive_elements`: list the visible interactive elements with their type, text, key and Semantics identifier. **Always call this before acting** to see what is available and to get stable selectors.
- `tap`, `double_tap`, `long_press`, `secondary_tap` (desktop only), `pinch_zoom`: gestures on one element, matched by exactly one of `key`, `identifier`, `text`, `type` or `coordinates`. Prefer `key` (a `ValueKey<String>`), then `identifier` (the Semantics identifier). Tapping a text field focuses it.
- `swipe`: element-based (`key`, `identifier` or `text`, plus `direction`) or coordinate-based. Use it for `PageView`, `Dismissible`, `Drawer` and sliders.
- `scroll_to`: scroll until an element matching `key`, `identifier` or `text` is visible.
- `press_back_button`: the system back action (Android back, iOS swipe-back).
- `enter_text`: type into a field. Pass `input` and exactly one of `key`, `identifier`, or `focused_element: true` after tapping the field. There is no text matcher.
- `press_key`: a real key event on the focused element (`enter`, `tab`, `escape`, arrows, characters, optional `modifiers`). On iOS and Android, change a field's value with `enter_text` instead.
- `take_screenshots`: capture all active views as base64 PNGs. Use to verify visual state after an action or to debug why something is not found.
- `get_logs`: app logs since start or the last hot reload. Needs a `LogCollector` (see "Log collection"). Use when an action silently fails or state is unclear.
- `hot_reload`: apply code changes without losing app state. `hot_restart`: restart from `main()` and reset all state.
- `set_device_config`: override text scale, bold text and brightness. The app must mount `MarionetteDeviceConfig` first.
- `list_custom_extensions` / `call_custom_extension`: app-specific extensions registered with `registerMarionetteExtension`.

Parameters for every tool are in the upstream [MCP Tools](https://github.com/leancodepl/marionette_mcp/blob/main/docs/mcp-tools.md) doc.

## Binding initialization

Expand All @@ -66,22 +73,28 @@ For apps with a custom design system, pass a `MarionetteConfiguration` — see `

## Log collection

`get_logs` works only when a `LogCollector` is configured.
`get_logs` works only when a `LogCollector` is passed as `MarionetteConfiguration(logCollector: ...)`. The `logging` and `logger` collectors are separate packages, not part of `marionette_flutter`:

- For apps using the `logging` package, use `LoggingLogCollector()`.
- For apps using the `logger` package, use `LoggerLogCollector()`.
- For other setups, use `PrintLogCollector()` and forward logs into it.
- If no collector is configured, `get_logs` should explain how to enable it.
- `logging` package: `flutter pub add marionette_logging`, then `LoggingLogCollector()` from `package:marionette_logging/marionette_logging.dart`.
- `logger` package: `flutter pub add marionette_logger`, then `LoggerLogCollector()` from `package:marionette_logger/marionette_logger.dart`. It is also a `LogOutput`, so add the same instance to the `Logger`'s outputs.
- Anything else: `PrintLogCollector()` from `marionette_flutter`, fed with `collector.addLog(message)`.
- If no collector is configured, `get_logs` explains how to enable it.

```dart
MarionetteBinding.ensureInitialized(
MarionetteConfiguration(logCollector: LoggingLogCollector()),
);
```

## Workflow: driving the app as an agent

1. Start the app: `flutter run` (take note of the VM service URI).
2. Call `connect` with the URI.
3. Call `get_interactive_elements` to see what is on screen.
4. Act: `tap`, `enter_text`, `scroll_to`.
4. Act: `tap`, `enter_text`, `scroll_to`, `swipe`, `press_back_button` and the other gestures.
5. Call `take_screenshots` to verify visual state (and when the element list looks wrong).
6. Use `get_logs` if an action has no visible effect.
7. Call `hot_reload` after a code change to re-test without losing state.
7. Call `hot_reload` after a code change to re-test without losing state, or `hot_restart` when the change needs a fresh start.
8. Call `disconnect` when finished.

## Best-effort caveat
Expand All @@ -102,4 +115,4 @@ Marionette observes the UI but does not automatically know your product's flows,

## Build-mode constraint

Marionette relies on Flutter's VM Service and is designed to drive a live `flutter run` session. It does not work in release builds. For release-mode automation use Patrol instead.
Marionette relies on Flutter's VM Service, so it works in debug and profile builds, not in release builds. The `kDebugMode` guard above limits it to debug; `!kReleaseMode` also covers profile. For release-mode automation use Patrol instead.
Loading