@mcp-pane/playwright 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,123 @@
1
+ # @mcp-pane/playwright
2
+
3
+ **Playwright fixtures for end-to-end testing of MCP Apps in a real browser.**
4
+
5
+ ```bash
6
+ npm install --save-dev @mcp-pane/playwright @playwright/test
7
+ ```
8
+
9
+ `@mcp-pane/playwright` lets you test the **actual UI** of your MCP App — the iframe, the postMessage bridge, the bidirectional flow — in a real browser, the same way Claude Desktop or VS Code would render it.
10
+
11
+ This is the **only** testing tool for MCP Apps that exercises the iframe and the host↔UI postMessage protocol end-to-end. Protocol-level tests with `@mcp-pane/test` are great for tools and resources, but they can't catch UI regressions.
12
+
13
+ ## What it gives you
14
+
15
+ - A `mcpApp` fixture that spins up your server, renders the UI in an iframe inside a real Chromium page, and bridges postMessage exactly like a real MCP host does
16
+ - `mcpApp.iframe` — Playwright `FrameLocator` for the UI inside the sandbox
17
+ - `mcpApp.toolCalls` — observable list of tool calls the UI made via postMessage
18
+ - `mcpApp.mockTool()` — override any tool's response, propagated to the UI
19
+ - `mcpApp.getRecording()` — full session capture (tool calls + postMessage) auto-attached to failed test reports
20
+
21
+ ## Quick start
22
+
23
+ ```ts
24
+ import { test, expect } from '@mcp-pane/playwright';
25
+
26
+ test.use({
27
+ server: {
28
+ command: 'node',
29
+ args: ['./dist/server.js'],
30
+ },
31
+ });
32
+
33
+ test('dashboard opens and renders KPIs', async ({ mcpApp }) => {
34
+ await mcpApp.open('show_dashboard', { period: 'week' });
35
+
36
+ await expect(mcpApp.iframe.locator('text=Revenue')).toBeVisible();
37
+ await expect(mcpApp.iframe.locator('text=/\\$[\\d,]+/')).toBeVisible();
38
+ });
39
+
40
+ test('bidirectional: clicking in iframe triggers tool/call back to server', async ({ mcpApp }) => {
41
+ await mcpApp.open('show_dashboard', { period: 'week' });
42
+
43
+ // User clicks a button inside the iframe
44
+ await mcpApp.iframe.locator('button:has-text("month")').click();
45
+
46
+ // The UI posts a tool/call back through our host. We observe it.
47
+ await expect.poll(() => mcpApp.toolCalls).toContainEqual(
48
+ expect.objectContaining({
49
+ name: 'show_dashboard',
50
+ args: expect.objectContaining({ period: 'month' }),
51
+ })
52
+ );
53
+
54
+ // And the UI re-renders with the new data.
55
+ await expect(mcpApp.iframe.locator('text=monthly')).toBeVisible();
56
+ });
57
+
58
+ test('UI handles extreme values gracefully', async ({ mcpApp }) => {
59
+ // Mock before open so the UI gets our crafted data
60
+ mcpApp.mockTool('show_dashboard', () => ({
61
+ totals: { revenue: 0, deals: 0 }, // edge case
62
+ }));
63
+
64
+ await mcpApp.open('show_dashboard', { period: 'week' });
65
+
66
+ await expect(mcpApp.iframe.locator('text=$0')).toBeVisible();
67
+ });
68
+ ```
69
+
70
+ ## How it works under the hood
71
+
72
+ ```
73
+ ┌─────────────────────────────────────────────────────┐
74
+ │ Playwright (Chromium) │
75
+ │ ┌───────────────────────────────────────────────┐ │
76
+ │ │ Headless host page │ │
77
+ │ │ ┌─────────────────────────────────────────┐ │ │
78
+ │ │ │ <iframe sandbox="allow-scripts"> │ │ │
79
+ │ │ │ YOUR MCP APP UI │ │ │
80
+ │ │ │ (real, sandboxed, no same-origin) │ │ │
81
+ │ │ └─────────────────────────────────────────┘ │ │
82
+ │ │ ↕ postMessage │ │
83
+ │ │ window.__mcpPane (host bridge) │ │
84
+ │ └────────────────┬──────────────────────────────┘ │
85
+ └───────────────────┼─────────────────────────────────┘
86
+ ↕ Playwright evaluate
87
+ ↕ poll for new tool/calls
88
+ ┌───────────────────┴─────────────────────────────────┐
89
+ │ McpAppFixture (Node.js test process) │
90
+ │ ↕ McpHarness │
91
+ │ ↕ stdio JSON-RPC │
92
+ │ YOUR MCP SERVER (child process) │
93
+ └─────────────────────────────────────────────────────┘
94
+ ```
95
+
96
+ The fixture spawns your MCP server through the same `McpHarness` from `@mcp-pane/test`, opens a Playwright page with a minimal "host shell" HTML, and polls `window.__mcpPane.getToolCalls()` to detect new tool calls from the UI. Each call is automatically proxied through the harness (or a mock you set), and the result is pushed back as a `tool/result` postMessage.
97
+
98
+ This is **exactly** the lifecycle a real MCP host implements. If your tests pass here, your UI works in Claude Desktop and VS Code.
99
+
100
+ ## Auto-attached recordings on failure
101
+
102
+ When a test fails, the fixture automatically attaches a JSON recording of the session to the Playwright report:
103
+
104
+ - Every `tools/call` with args, result, duration
105
+ - Every `postMessage` between host and iframe, with direction
106
+ - Timing relative to test start
107
+
108
+ Open the HTML report, click the failed test, find `mcp-recording.json` in the attachments. This usually tells you immediately what went wrong.
109
+
110
+ ## Browser support
111
+
112
+ Anything Playwright supports — Chromium, Firefox, WebKit. Defaults to Chromium since that's what Claude Desktop and VS Code use under the hood (Electron / Chromium-based).
113
+
114
+ ## Companion packages
115
+
116
+ - **[@mcp-pane/test](https://npmjs.com/@mcp-pane/test)** — Vitest/Jest utilities for protocol-level tests (same `McpHarness` under the hood)
117
+ - **[mcp-pane](https://github.com/YOUR/mcp-pane)** — scaffolder for new MCP Apps
118
+
119
+ Works with any MCP server — official SDK, `mcp-use`, anything that speaks the standard MCP protocol with the Apps extension.
120
+
121
+ ## License
122
+
123
+ MIT
@@ -0,0 +1,79 @@
1
+ /**
2
+ * McpAppFixture — то, что тест получает через Playwright fixture.
3
+ *
4
+ * Объединяет McpHarness (наш test-пакет) и Playwright Page.
5
+ * Жизненный цикл:
6
+ * 1. Playwright создаёт fixture: поднимает MCP-сервер через Harness,
7
+ * открывает страницу с host-page HTML.
8
+ * 2. Тест вызывает `mcpApp.open('show_dashboard', {...})`:
9
+ * - harness.callTool('show_dashboard', args) — данные
10
+ * - harness.readUiForTool('show_dashboard') — HTML
11
+ * - page.evaluate(__mcpPane.openApp(...)) — рендерим в iframe
12
+ * 3. Тест взаимодействует с iframe через `mcpApp.iframe` (Playwright FrameLocator).
13
+ * 4. После теста — fixture closes harness и context.
14
+ *
15
+ * Bidirectional flow: UI внутри iframe шлёт postMessage `tool/call`,
16
+ * host-page их собирает. Fixture регулярно опрашивает через
17
+ * `getToolCalls()` и автоматически проксирует на реальный сервер через
18
+ * harness (или применяет mock'и).
19
+ */
20
+ import type { Page, FrameLocator } from '@playwright/test';
21
+ import { type HarnessOptions } from '@mcp-pane/test';
22
+ import type { ToolMock } from '@mcp-pane/test';
23
+ export type McpAppFixtureOptions = HarnessOptions & {
24
+ /** Интервал опроса postMessage от UI в миллисекундах (по умолчанию 50ms). */
25
+ pollInterval?: number;
26
+ };
27
+ type ToolCallFromUi = {
28
+ name: string;
29
+ args: Record<string, unknown>;
30
+ callId?: string;
31
+ };
32
+ export declare class McpAppFixture {
33
+ readonly page: Page;
34
+ private harness;
35
+ private polling;
36
+ private pollTimer?;
37
+ private processedSeq;
38
+ private toolCallHistory;
39
+ private pollInterval;
40
+ private constructor();
41
+ /**
42
+ * Фабрика. Подключает MCP-сервер, открывает host-page, начинает polling.
43
+ * Должна вызываться внутри Playwright fixture'а.
44
+ */
45
+ static create(page: Page, opts: McpAppFixtureOptions): Promise<McpAppFixture>;
46
+ /**
47
+ * Вызвать tool сервера и отрендерить его UI в iframe.
48
+ *
49
+ * После вызова `mcpApp.iframe` указывает на свежемонтированный iframe.
50
+ */
51
+ open(toolName: string, args?: Record<string, unknown>): Promise<void>;
52
+ /** Playwright FrameLocator для iframe с загруженным UI. */
53
+ get iframe(): FrameLocator;
54
+ /**
55
+ * Все tool/call сообщения, которые UI прислал из iframe с момента
56
+ * открытия (или последнего clearHistory). Каждый вызов в этом
57
+ * списке означает, что UI в iframe попросил host'а вызвать tool.
58
+ */
59
+ get toolCalls(): readonly ToolCallFromUi[];
60
+ /** Очистить историю tool/call'ов от UI. */
61
+ clearToolCalls(): void;
62
+ /** Делегируем в harness. */
63
+ mockTool(name: string, mock: ToolMock): void;
64
+ unmockTool(name: string): void;
65
+ clearMocks(): void;
66
+ /**
67
+ * Возвращает полную запись сессии, включая postMessage'ы между
68
+ * host'ом и iframe (для time-travel debugging).
69
+ */
70
+ getRecording(outcome?: 'pass' | 'fail' | 'unknown'): Promise<import("@mcp-pane/test").SessionRecording>;
71
+ close(): Promise<void>;
72
+ private startPolling;
73
+ private stopPolling;
74
+ private scheduleNextPoll;
75
+ private poll;
76
+ private proxyToolCall;
77
+ }
78
+ export {};
79
+ //# sourceMappingURL=fixture.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixture.d.ts","sourceRoot":"","sources":["../src/fixture.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACjE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAI/C,MAAM,MAAM,oBAAoB,GAAG,cAAc,GAAG;IAClD,6EAA6E;IAC7E,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,KAAK,cAAc,GAAG;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,qBAAa,aAAa;aASN,IAAI,EAAE,IAAI;IAR5B,OAAO,CAAC,OAAO,CAAa;IAC5B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAC,CAAgC;IAClD,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,eAAe,CAAwB;IAC/C,OAAO,CAAC,YAAY,CAAS;IAE7B,OAAO;IASP;;;OAGG;WACU,MAAM,CACjB,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,oBAAoB,GACzB,OAAO,CAAC,aAAa,CAAC;IAmBzB;;;;OAIG;IACG,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA2B/E,2DAA2D;IAC3D,IAAI,MAAM,IAAI,YAAY,CAEzB;IAMD;;;;OAIG;IACH,IAAI,SAAS,IAAI,SAAS,cAAc,EAAE,CAEzC;IAED,2CAA2C;IAC3C,cAAc,IAAI,IAAI;IAQtB,4BAA4B;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,IAAI;IAI5C,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAI9B,UAAU,IAAI,IAAI;IAQlB;;;OAGG;IACG,YAAY,CAAC,OAAO,GAAE,MAAM,GAAG,MAAM,GAAG,SAAqB;IAsB7D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAS5B,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,gBAAgB;YAKV,IAAI;YA0BJ,aAAa;CAyB5B"}
@@ -0,0 +1,200 @@
1
+ /**
2
+ * McpAppFixture — то, что тест получает через Playwright fixture.
3
+ *
4
+ * Объединяет McpHarness (наш test-пакет) и Playwright Page.
5
+ * Жизненный цикл:
6
+ * 1. Playwright создаёт fixture: поднимает MCP-сервер через Harness,
7
+ * открывает страницу с host-page HTML.
8
+ * 2. Тест вызывает `mcpApp.open('show_dashboard', {...})`:
9
+ * - harness.callTool('show_dashboard', args) — данные
10
+ * - harness.readUiForTool('show_dashboard') — HTML
11
+ * - page.evaluate(__mcpPane.openApp(...)) — рендерим в iframe
12
+ * 3. Тест взаимодействует с iframe через `mcpApp.iframe` (Playwright FrameLocator).
13
+ * 4. После теста — fixture closes harness и context.
14
+ *
15
+ * Bidirectional flow: UI внутри iframe шлёт postMessage `tool/call`,
16
+ * host-page их собирает. Fixture регулярно опрашивает через
17
+ * `getToolCalls()` и автоматически проксирует на реальный сервер через
18
+ * harness (или применяет mock'и).
19
+ */
20
+ import { McpHarness } from '@mcp-pane/test';
21
+ import { buildHostPageHtml } from './host-page.js';
22
+ export class McpAppFixture {
23
+ page;
24
+ harness;
25
+ polling = false;
26
+ pollTimer;
27
+ processedSeq = 0;
28
+ toolCallHistory = [];
29
+ pollInterval;
30
+ constructor(page, harness, opts) {
31
+ this.page = page;
32
+ this.harness = harness;
33
+ this.pollInterval = opts.pollInterval ?? 50;
34
+ }
35
+ /**
36
+ * Фабрика. Подключает MCP-сервер, открывает host-page, начинает polling.
37
+ * Должна вызываться внутри Playwright fixture'а.
38
+ */
39
+ static async create(page, opts) {
40
+ const harness = await McpHarness.create(opts);
41
+ // Грузим host-page как data URL — не нужен веб-сервер.
42
+ const html = buildHostPageHtml();
43
+ await page.setContent(html, { waitUntil: 'load' });
44
+ // Ждём, пока __mcpPane появится на window (наша guarantee).
45
+ await page.waitForFunction(() => window.__mcpPane?.ready === true);
46
+ const fixture = new McpAppFixture(page, harness, opts);
47
+ fixture.startPolling();
48
+ return fixture;
49
+ }
50
+ // ============================================================
51
+ // Открытие приложения
52
+ // ============================================================
53
+ /**
54
+ * Вызвать tool сервера и отрендерить его UI в iframe.
55
+ *
56
+ * После вызова `mcpApp.iframe` указывает на свежемонтированный iframe.
57
+ */
58
+ async open(toolName, args = {}) {
59
+ // 1. Узнаём URI UI-ресурса
60
+ const meta = await this.harness.getToolUiMeta(toolName);
61
+ if (!meta?.resourceUri) {
62
+ throw new Error(`Tool "${toolName}" has no UI metadata — not an MCP App tool`);
63
+ }
64
+ // 2. Вызываем tool и читаем HTML параллельно
65
+ const [data, html] = await Promise.all([
66
+ this.harness.callTool(toolName, args),
67
+ this.harness.readUiResource(meta.resourceUri),
68
+ ]);
69
+ // 3. Просим host-page смонтировать iframe
70
+ await this.page.evaluate(({ uri, html, data }) => window.__mcpPane.openApp(uri, html, data), { uri: meta.resourceUri, html, data });
71
+ // 4. Сбрасываем processedSeq — это новый iframe, история начинается с 0
72
+ this.processedSeq = 0;
73
+ }
74
+ // ============================================================
75
+ // Доступ к UI
76
+ // ============================================================
77
+ /** Playwright FrameLocator для iframe с загруженным UI. */
78
+ get iframe() {
79
+ return this.page.frameLocator('iframe[data-mcp-uri]');
80
+ }
81
+ // ============================================================
82
+ // Tool calls от UI
83
+ // ============================================================
84
+ /**
85
+ * Все tool/call сообщения, которые UI прислал из iframe с момента
86
+ * открытия (или последнего clearHistory). Каждый вызов в этом
87
+ * списке означает, что UI в iframe попросил host'а вызвать tool.
88
+ */
89
+ get toolCalls() {
90
+ return this.toolCallHistory;
91
+ }
92
+ /** Очистить историю tool/call'ов от UI. */
93
+ clearToolCalls() {
94
+ this.toolCallHistory = [];
95
+ }
96
+ // ============================================================
97
+ // Mocking
98
+ // ============================================================
99
+ /** Делегируем в harness. */
100
+ mockTool(name, mock) {
101
+ this.harness.mockTool(name, mock);
102
+ }
103
+ unmockTool(name) {
104
+ this.harness.unmockTool(name);
105
+ }
106
+ clearMocks() {
107
+ this.harness.clearMocks();
108
+ }
109
+ // ============================================================
110
+ // Recording
111
+ // ============================================================
112
+ /**
113
+ * Возвращает полную запись сессии, включая postMessage'ы между
114
+ * host'ом и iframe (для time-travel debugging).
115
+ */
116
+ async getRecording(outcome = 'unknown') {
117
+ // Подтягиваем последние postMessages из браузерного контекста.
118
+ const [received, sent] = await Promise.all([
119
+ this.page.evaluate(() => window.__mcpPane.getReceivedMessages()),
120
+ this.page.evaluate(() => window.__mcpPane.getSentMessages()),
121
+ ]);
122
+ // Аккумулируем postMessages в harness recording.
123
+ for (const m of received) {
124
+ this.harness.recordPostMessage({ direction: 'ui-to-host', data: m.data });
125
+ }
126
+ for (const m of sent) {
127
+ this.harness.recordPostMessage({ direction: 'host-to-ui', data: m.data });
128
+ }
129
+ return this.harness.getRecording(outcome);
130
+ }
131
+ // ============================================================
132
+ // Lifecycle
133
+ // ============================================================
134
+ async close() {
135
+ this.stopPolling();
136
+ await this.harness.close();
137
+ }
138
+ // ============================================================
139
+ // Internal: polling postMessage от UI
140
+ // ============================================================
141
+ startPolling() {
142
+ this.polling = true;
143
+ this.scheduleNextPoll();
144
+ }
145
+ stopPolling() {
146
+ this.polling = false;
147
+ if (this.pollTimer) {
148
+ clearTimeout(this.pollTimer);
149
+ this.pollTimer = undefined;
150
+ }
151
+ }
152
+ scheduleNextPoll() {
153
+ if (!this.polling)
154
+ return;
155
+ this.pollTimer = setTimeout(() => this.poll(), this.pollInterval);
156
+ }
157
+ async poll() {
158
+ if (!this.polling)
159
+ return;
160
+ try {
161
+ const calls = await this.page.evaluate(() => window.__mcpPane.getToolCalls());
162
+ // Берём только новые (после processedSeq).
163
+ const newCalls = calls.slice(this.processedSeq);
164
+ this.processedSeq = calls.length;
165
+ // Проксируем каждый tool call через harness (он сам решит — реальный или mock).
166
+ for (const call of newCalls) {
167
+ this.toolCallHistory.push(call);
168
+ this.proxyToolCall(call).catch((err) => {
169
+ // Логируем, но не падаем — тест сам решит, как реагировать.
170
+ console.error('[mcp-pane/playwright] tool/call failed:', err);
171
+ });
172
+ }
173
+ }
174
+ catch (err) {
175
+ // Page might be navigating or closed — skip this tick.
176
+ }
177
+ finally {
178
+ this.scheduleNextPoll();
179
+ }
180
+ }
181
+ async proxyToolCall(call) {
182
+ try {
183
+ const result = await this.harness.callTool(call.name, call.args);
184
+ await this.page.evaluate(({ result, callId }) => window.__mcpPane.pushToUi({
185
+ type: 'tool/result',
186
+ callId,
187
+ payload: result,
188
+ }), { result, callId: call.callId });
189
+ }
190
+ catch (err) {
191
+ const message = err instanceof Error ? err.message : String(err);
192
+ await this.page.evaluate(({ message, callId }) => window.__mcpPane.pushToUi({
193
+ type: 'tool/result',
194
+ callId,
195
+ error: message,
196
+ }), { message, callId: call.callId });
197
+ }
198
+ }
199
+ }
200
+ //# sourceMappingURL=fixture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixture.js","sourceRoot":"","sources":["../src/fixture.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Headless test host — HTML, который рендерится в Playwright-странице
3
+ * и предоставляет окружение для MCP App.
4
+ *
5
+ * Эта функция возвращает HTML-строку (с инлайн JS), которая:
6
+ * 1. Создаёт iframe с переданным UI HTML
7
+ * 2. Слушает все postMessage от iframe
8
+ * 3. Экспортирует window.__mcpPane API для управления из теста:
9
+ * __mcpPane.openApp(uri, html, initialData)
10
+ * __mcpPane.pushToUi(payload)
11
+ * __mcpPane.getReceivedMessages()
12
+ *
13
+ * Playwright-fixture использует evaluate() / exposeFunction() поверх этого
14
+ * API, чтобы дать тесту удобный TypeScript-интерфейс.
15
+ */
16
+ export declare function buildHostPageHtml(): string;
17
+ //# sourceMappingURL=host-page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-page.d.ts","sourceRoot":"","sources":["../src/host-page.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,wBAAgB,iBAAiB,IAAI,MAAM,CA+I1C"}
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Headless test host — HTML, который рендерится в Playwright-странице
3
+ * и предоставляет окружение для MCP App.
4
+ *
5
+ * Эта функция возвращает HTML-строку (с инлайн JS), которая:
6
+ * 1. Создаёт iframe с переданным UI HTML
7
+ * 2. Слушает все postMessage от iframe
8
+ * 3. Экспортирует window.__mcpPane API для управления из теста:
9
+ * __mcpPane.openApp(uri, html, initialData)
10
+ * __mcpPane.pushToUi(payload)
11
+ * __mcpPane.getReceivedMessages()
12
+ *
13
+ * Playwright-fixture использует evaluate() / exposeFunction() поверх этого
14
+ * API, чтобы дать тесту удобный TypeScript-интерфейс.
15
+ */
16
+ export function buildHostPageHtml() {
17
+ return `<!DOCTYPE html>
18
+ <html lang="en">
19
+ <head>
20
+ <meta charset="UTF-8">
21
+ <title>mcp-pane test host</title>
22
+ <style>
23
+ body { margin: 0; font-family: -apple-system, sans-serif; }
24
+ #host-shell {
25
+ display: flex;
26
+ flex-direction: column;
27
+ height: 100vh;
28
+ }
29
+ #host-status {
30
+ padding: 4px 12px;
31
+ background: #f3f4f6;
32
+ border-bottom: 1px solid #e5e7eb;
33
+ font-size: 11px;
34
+ font-family: ui-monospace, monospace;
35
+ color: #666;
36
+ }
37
+ #host-frame-container {
38
+ flex: 1;
39
+ min-height: 0;
40
+ display: flex;
41
+ }
42
+ iframe {
43
+ flex: 1;
44
+ border: none;
45
+ width: 100%;
46
+ background: #fff;
47
+ }
48
+ </style>
49
+ </head>
50
+ <body>
51
+ <div id="host-shell">
52
+ <div id="host-status">mcp-pane test host • no app loaded</div>
53
+ <div id="host-frame-container"></div>
54
+ </div>
55
+ <script>
56
+ (function () {
57
+ // ============================================================
58
+ // Internal state
59
+ // ============================================================
60
+ var iframe = null;
61
+ var currentUri = null;
62
+ var currentData = null;
63
+ var receivedFromUi = [];
64
+ var sentToUi = [];
65
+
66
+ // ============================================================
67
+ // Message handler — слушаем все сообщения от iframe
68
+ // ============================================================
69
+ window.addEventListener('message', function (event) {
70
+ // Принимаем только от нашего iframe.
71
+ if (!iframe || event.source !== iframe.contentWindow) return;
72
+
73
+ receivedFromUi.push({
74
+ at: Date.now(),
75
+ data: event.data,
76
+ });
77
+
78
+ // ui/ready — пушим initial data, если есть.
79
+ if (event.data && event.data.type === 'ui/ready' && currentData !== null) {
80
+ pushToUi({ type: 'tool/result', payload: currentData });
81
+ }
82
+
83
+ // tool/call — Playwright фикстура подхватит это через
84
+ // __mcpPane.getReceivedMessages() и решит, как обработать
85
+ // (через реальный сервер, или через mock).
86
+ });
87
+
88
+ function pushToUi(payload) {
89
+ if (!iframe || !iframe.contentWindow) return false;
90
+ sentToUi.push({ at: Date.now(), data: payload });
91
+ iframe.contentWindow.postMessage(payload, '*');
92
+ return true;
93
+ }
94
+
95
+ // ============================================================
96
+ // Public API на window
97
+ // ============================================================
98
+ window.__mcpPane = {
99
+ // Открыть UI ресурс. html — srcdoc для iframe, initialData —
100
+ // данные tool/result для broadcast после ui/ready.
101
+ openApp: function (uri, html, initialData) {
102
+ currentUri = uri;
103
+ currentData = initialData;
104
+
105
+ var container = document.getElementById('host-frame-container');
106
+ container.innerHTML = '';
107
+
108
+ iframe = document.createElement('iframe');
109
+ iframe.srcdoc = html;
110
+ iframe.sandbox = 'allow-scripts';
111
+ iframe.setAttribute('data-mcp-uri', uri);
112
+ container.appendChild(iframe);
113
+
114
+ document.getElementById('host-status').textContent =
115
+ 'mcp-pane test host • loaded: ' + uri;
116
+
117
+ return new Promise(function (resolve) {
118
+ iframe.addEventListener('load', function () { resolve(); }, { once: true });
119
+ });
120
+ },
121
+
122
+ // Push новой data в UI (например, после tool call с новыми параметрами).
123
+ pushToUi: function (payload) {
124
+ return pushToUi(payload);
125
+ },
126
+
127
+ // Полный список того, что UI прислал нам.
128
+ getReceivedMessages: function () {
129
+ return receivedFromUi.slice();
130
+ },
131
+
132
+ // Что мы отправили UI.
133
+ getSentMessages: function () {
134
+ return sentToUi.slice();
135
+ },
136
+
137
+ // Только tool/call'ы от UI — самая частая нужда в тестах.
138
+ getToolCalls: function () {
139
+ return receivedFromUi
140
+ .filter(function (m) { return m.data && m.data.type === 'tool/call'; })
141
+ .map(function (m) {
142
+ return { name: m.data.name, args: m.data.args || {}, callId: m.data.callId };
143
+ });
144
+ },
145
+
146
+ // Очистить историю — полезно при beforeEach.
147
+ clearHistory: function () {
148
+ receivedFromUi = [];
149
+ sentToUi = [];
150
+ },
151
+
152
+ // Признак того, что хост готов. Используется ожиданием.
153
+ ready: true,
154
+ };
155
+ })();
156
+ </script>
157
+ </body>
158
+ </html>`;
159
+ }
160
+ //# sourceMappingURL=host-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-page.js","sourceRoot":"","sources":["../src/host-page.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,MAAM,UAAU,iBAAiB;IAC/B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA6ID,CAAC;AACT,CAAC"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @mcp-pane/playwright — Playwright fixtures for testing MCP Apps.
3
+ *
4
+ * Quick start:
5
+ *
6
+ * import { test, expect } from '@mcp-pane/playwright';
7
+ *
8
+ * test.use({
9
+ * server: { command: 'node', args: ['./dist/server.js'] }
10
+ * });
11
+ *
12
+ * test('dashboard works', async ({ mcpApp }) => {
13
+ * await mcpApp.open('show_dashboard', { period: 'week' });
14
+ * await expect(mcpApp.iframe.locator('text=Revenue')).toBeVisible();
15
+ *
16
+ * await mcpApp.iframe.locator('button:has-text("month")').click();
17
+ * await expect.poll(() => mcpApp.toolCalls).toContainEqual({
18
+ * name: 'show_dashboard',
19
+ * args: { period: 'month' },
20
+ * });
21
+ * });
22
+ */
23
+ export { test, expect, McpAppFixture, type McpFixtures } from './test.js';
24
+ export type { McpAppFixtureOptions } from './fixture.js';
25
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAC1E,YAAY,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,24 @@
1
+ /**
2
+ * @mcp-pane/playwright — Playwright fixtures for testing MCP Apps.
3
+ *
4
+ * Quick start:
5
+ *
6
+ * import { test, expect } from '@mcp-pane/playwright';
7
+ *
8
+ * test.use({
9
+ * server: { command: 'node', args: ['./dist/server.js'] }
10
+ * });
11
+ *
12
+ * test('dashboard works', async ({ mcpApp }) => {
13
+ * await mcpApp.open('show_dashboard', { period: 'week' });
14
+ * await expect(mcpApp.iframe.locator('text=Revenue')).toBeVisible();
15
+ *
16
+ * await mcpApp.iframe.locator('button:has-text("month")').click();
17
+ * await expect.poll(() => mcpApp.toolCalls).toContainEqual({
18
+ * name: 'show_dashboard',
19
+ * args: { period: 'month' },
20
+ * });
21
+ * });
22
+ */
23
+ export { test, expect, McpAppFixture } from './test.js';
24
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,EAAoB,MAAM,WAAW,CAAC"}
package/dist/test.d.ts ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Playwright fixtures for MCP Apps.
3
+ *
4
+ * Использование:
5
+ *
6
+ * import { test, expect } from '@mcp-pane/playwright';
7
+ *
8
+ * test.use({ server: { command: 'node', args: ['./dist/server.js'] } });
9
+ *
10
+ * test('dashboard renders', async ({ mcpApp }) => {
11
+ * await mcpApp.open('show_dashboard', { period: 'week' });
12
+ * await expect(mcpApp.iframe.locator('text=Revenue')).toBeVisible();
13
+ * });
14
+ *
15
+ * Fixture `server` пользователь задаёт через test.use(). Fixture `mcpApp`
16
+ * автоматически поднимает harness, рендерит host-page, начинает polling.
17
+ */
18
+ import { McpAppFixture } from './fixture.js';
19
+ import type { ServerSpec } from '@mcp-pane/test';
20
+ export type McpFixtures = {
21
+ /** Описание сервера, который надо запустить. Задаётся через test.use(). */
22
+ server: ServerSpec;
23
+ /** McpAppFixture — главный объект для теста. */
24
+ mcpApp: McpAppFixture;
25
+ };
26
+ export declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & McpFixtures, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
27
+ /**
28
+ * Расширенный expect с MCP-specific матчерами для FrameLocator.
29
+ * Можно использовать как обычный expect.
30
+ */
31
+ export declare const expect: import("@playwright/test").Expect<{}>;
32
+ export { McpAppFixture };
33
+ //# sourceMappingURL=test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test.d.ts","sourceRoot":"","sources":["../src/test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD,MAAM,MAAM,WAAW,GAAG;IACxB,2EAA2E;IAC3E,MAAM,EAAE,UAAU,CAAC;IACnB,gDAAgD;IAChD,MAAM,EAAE,aAAa,CAAC;CACvB,CAAC;AAEF,eAAO,MAAM,IAAI,2PA4Cf,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,MAAM,uCAAa,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,CAAC"}
package/dist/test.js ADDED
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Playwright fixtures for MCP Apps.
3
+ *
4
+ * Использование:
5
+ *
6
+ * import { test, expect } from '@mcp-pane/playwright';
7
+ *
8
+ * test.use({ server: { command: 'node', args: ['./dist/server.js'] } });
9
+ *
10
+ * test('dashboard renders', async ({ mcpApp }) => {
11
+ * await mcpApp.open('show_dashboard', { period: 'week' });
12
+ * await expect(mcpApp.iframe.locator('text=Revenue')).toBeVisible();
13
+ * });
14
+ *
15
+ * Fixture `server` пользователь задаёт через test.use(). Fixture `mcpApp`
16
+ * автоматически поднимает harness, рендерит host-page, начинает polling.
17
+ */
18
+ import { test as base, expect as baseExpect } from '@playwright/test';
19
+ import { McpAppFixture } from './fixture.js';
20
+ export const test = base.extend({
21
+ // pollInterval можно настраивать через test.use({ server: ... }),
22
+ // но базовое значение — undefined и попадёт в default.
23
+ server: [
24
+ // По умолчанию падаем с понятным сообщением, если пользователь
25
+ // забыл задать `test.use({ server: ... })`.
26
+ {
27
+ command: '',
28
+ args: [],
29
+ },
30
+ { option: true },
31
+ ],
32
+ mcpApp: async ({ page, server }, use, testInfo) => {
33
+ if (!server.command) {
34
+ throw new Error('@mcp-pane/playwright: please configure `test.use({ server: { command, args } })` ' +
35
+ 'before using mcpApp fixture');
36
+ }
37
+ const fixture = await McpAppFixture.create(page, {
38
+ ...server,
39
+ testName: testInfo.title,
40
+ });
41
+ try {
42
+ await use(fixture);
43
+ }
44
+ finally {
45
+ // Если тест упал — автоматически прикрепляем recording к отчёту.
46
+ if (testInfo.status !== testInfo.expectedStatus) {
47
+ try {
48
+ const recording = await fixture.getRecording('fail');
49
+ await testInfo.attach('mcp-recording.json', {
50
+ body: JSON.stringify(recording, null, 2),
51
+ contentType: 'application/json',
52
+ });
53
+ }
54
+ catch {
55
+ // Recording attach — best-effort, не должен ронять teardown.
56
+ }
57
+ }
58
+ await fixture.close();
59
+ }
60
+ },
61
+ });
62
+ /**
63
+ * Расширенный expect с MCP-specific матчерами для FrameLocator.
64
+ * Можно использовать как обычный expect.
65
+ */
66
+ export const expect = baseExpect;
67
+ export { McpAppFixture };
68
+ //# sourceMappingURL=test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test.js","sourceRoot":"","sources":["../src/test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,IAAI,IAAI,IAAI,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAU7C,MAAM,CAAC,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAc;IAC3C,kEAAkE;IAClE,uDAAuD;IACvD,MAAM,EAAE;QACN,+DAA+D;QAC/D,4CAA4C;QAC5C;YACE,OAAO,EAAE,EAAE;YACX,IAAI,EAAE,EAAE;SACT;QACD,EAAE,MAAM,EAAE,IAAI,EAAE;KACjB;IAED,MAAM,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,EAAE;QAChD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CACb,mFAAmF;gBACnF,6BAA6B,CAC9B,CAAC;QACJ,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,MAAM,CAAC,IAAI,EAAE;YAC/C,GAAG,MAAM;YACT,QAAQ,EAAE,QAAQ,CAAC,KAAK;SACzB,CAAC,CAAC;QAEH,IAAI,CAAC;YACH,MAAM,GAAG,CAAC,OAAO,CAAC,CAAC;QACrB,CAAC;gBAAS,CAAC;YACT,iEAAiE;YACjE,IAAI,QAAQ,CAAC,MAAM,KAAK,QAAQ,CAAC,cAAc,EAAE,CAAC;gBAChD,IAAI,CAAC;oBACH,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;oBACrD,MAAM,QAAQ,CAAC,MAAM,CAAC,oBAAoB,EAAE;wBAC1C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;wBACxC,WAAW,EAAE,kBAAkB;qBAChC,CAAC,CAAC;gBACL,CAAC;gBAAC,MAAM,CAAC;oBACP,6DAA6D;gBAC/D,CAAC;YACH,CAAC;YACD,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;QACxB,CAAC;IACH,CAAC;CACF,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,UAAU,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,CAAC"}
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@mcp-pane/playwright",
3
+ "version": "0.1.0",
4
+ "description": "Playwright fixtures for testing MCP Apps — interactive UIs rendered in iframe by MCP hosts",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsc",
20
+ "test": "vitest run",
21
+ "clean": "rm -rf dist"
22
+ },
23
+ "keywords": [
24
+ "mcp",
25
+ "mcp-apps",
26
+ "playwright",
27
+ "testing",
28
+ "ui-testing"
29
+ ],
30
+ "license": "MIT",
31
+ "engines": {
32
+ "node": ">=18"
33
+ },
34
+ "peerDependencies": {
35
+ "@playwright/test": ">=1.40.0"
36
+ },
37
+ "dependencies": {
38
+ "@modelcontextprotocol/sdk": "^1.29.0",
39
+ "@mcp-pane/test": "workspace:*"
40
+ },
41
+ "devDependencies": {
42
+ "@playwright/test": "^1.48.0",
43
+ "vitest": "^2.0.0"
44
+ }
45
+ }