Files
webstudio/apps/builder/e2e/flows/template-insertion.ts
Oleg Isonen d291e4b671 feat: Builder api runtime contracts guardrails for MCP (#5857)
This PR expands Webstudio MCP and CLI into a more complete agent
authoring interface. Agents can discover project capabilities, edit real
Webstudio content, use data/resources, verify visual output, and report
useful issues when workflows fail.

## MCP User Capabilities

- Create, update, duplicate, reorder, and delete pages.
- Create pages from templates and manage reusable page templates.
- Inspect project pages, instances, props, styles, assets, variables,
resources, and metadata.
- Insert Webstudio components and authored JSX fragments into pages.
- Edit text, props, styles, breakpoints, design tokens, and CSS
variables.
- Update page metadata, SEO fields, redirects, marketplace metadata, and
project settings.
- Create and use static data variables.
- Create and use dynamic resources fetched over HTTP.
- Use REST, GraphQL, and built-in system resources in authored pages.
- Bind data variables and resources to visible page content.
- Create resource-backed pages such as sitemap XML, structured SEO data,
and API-driven content sections.
- Preview current project state without requiring a publish step.
- Capture screenshots for visual verification.
- Verify responsive designs across desktop, tablet, and mobile
breakpoints.
- Use screenshot diff and OCR-style checks to inspect rendered output.
- Reset and refresh local MCP project state when needed.
- Discover available CLI/MCP commands from generated manuals.
- Use clearer examples for resources, data variables, JSX authoring,
previewing, and visual checks.
- Read marketplace product metadata without requiring a full project
snapshot.
- Get safer guidance when using low-level patch tools.
- Get a ready-to-copy Discord report template when MCP/CLI behavior is
confusing or broken.
2026-07-10 02:13:22 +02:00

50 lines
1.3 KiB
TypeScript

import type { Page } from "playwright";
import { waitForCanvasFrame } from "./builder";
import { waitForSyncStatus } from "./sync-status";
const getVisibleInsertBlockButton = async ({
page,
anchorText,
}: {
page: Page;
anchorText: string;
}) => {
await page.keyboard.press("Escape");
const canvas = await waitForCanvasFrame({ page });
const target = canvas.getByText(anchorText, { exact: true }).first();
const button = page.getByRole("button", { name: "Insert block" }).last();
const startedAt = Date.now();
let lastError: unknown;
while (Date.now() - startedAt < 10_000) {
try {
await target.scrollIntoViewIfNeeded();
await target.hover();
await button.waitFor({ state: "visible", timeout: 1_000 });
return button;
} catch (error) {
lastError = error;
}
}
throw new Error(`Expected insert block button for "${anchorText}"`, {
cause: lastError,
});
};
export const insertTemplateAfterCanvasText = async ({
page,
anchorText,
templateName,
}: {
page: Page;
anchorText: string;
templateName: string;
}) => {
const insertButton = await getVisibleInsertBlockButton({ page, anchorText });
await insertButton.click();
await page.getByRole("menuitemradio", { name: templateName }).click();
await waitForSyncStatus({ page, status: "idle" });
};