Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,7 @@ async function settingsScenario(client) {
"agentsGeneral",
"skills",
"mcpServers",
"plugins",
"browser",
"usage",
"archived",
Expand All @@ -421,6 +422,7 @@ async function settingsScenario(client) {
let mcpListScreenshotPath;
let mcpScreenshotPath;
let mcpImportScreenshotPath;
let pluginsScreenshotPath;
let skillsScreenshotPath;
let skillsImportScreenshotPath;
let skillsImportDestinationsScreenshotPath;
Expand Down Expand Up @@ -477,6 +479,9 @@ async function settingsScenario(client) {
await mcpServersSectionDeepDive(client, mcpFixture));
}
}
if (section === "plugins") {
({ pluginsScreenshotPath } = await pluginsSectionDeepDive(client));
}
}
const screenshotPath = join(outDir, "smoke-02-settings.png");
await screenshot(client, screenshotPath);
Expand All @@ -489,6 +494,7 @@ async function settingsScenario(client) {
...(mcpListScreenshotPath ? { mcpListScreenshotPath } : {}),
...(mcpScreenshotPath ? { mcpScreenshotPath } : {}),
...(mcpImportScreenshotPath ? { mcpImportScreenshotPath } : {}),
...(pluginsScreenshotPath ? { pluginsScreenshotPath } : {}),
...(skillsScreenshotPath ? { skillsScreenshotPath } : {}),
...(skillsImportScreenshotPath ? { skillsImportScreenshotPath } : {}),
...(skillsImportDestinationsScreenshotPath ? { skillsImportDestinationsScreenshotPath } : {}),
Expand All @@ -497,6 +503,156 @@ async function settingsScenario(client) {
};
}

async function pluginsSectionDeepDive(client) {
const pluginId = "browser-tools";
const marketplaceState = await waitForValue(
() =>
evaluate(
client,
`(() => {
const search = document.querySelector('[aria-label="Search plugins"]');
const action = document.querySelector("#plugin-browser-tools-action");
return {
visible: Boolean(search && !search.closest("[hidden]")),
pluginCount: document.querySelectorAll("[data-plugin-id]").length,
action: action?.textContent?.trim(),
initialInstalled: window.__poracodeDev.stores.sharedSettings.getState().installedPlugins["browser-tools"] !== undefined,
};
})()`,
),
(state) => state.visible && state.pluginCount > 0 && Boolean(state.action),
"plugins marketplace",
);
assert(
marketplaceState.action === (marketplaceState.initialInstalled ? "Manage" : "Install"),
`Browser Tools marketplace action did not match install state: ${JSON.stringify(marketplaceState)}`,
);

let detailOpened = false;
try {
const opened = await evaluate(
client,
`(() => {
const action = document.querySelector("#plugin-browser-tools-action")?.closest("button");
if (!(action instanceof HTMLButtonElement)) return false;
action.click();
return true;
})()`,
);
assert(opened, "Browser Tools marketplace action was unavailable");
detailOpened = true;

const detailState = await waitForValue(
() =>
evaluate(
client,
`(() => {
const buttonText = [...document.querySelectorAll("button")].map((button) => button.textContent?.trim());
const headings = [...document.querySelectorAll("h2")].map((heading) => heading.textContent?.trim());
const switchNames = [...document.querySelectorAll('[role="switch"]')].map((control) =>
(control.getAttribute("aria-labelledby") ?? "")
.split(/\\s+/u)
.map((id) => document.getElementById(id)?.textContent?.trim() ?? "")
.filter(Boolean)
.join(" "),
);
return {
installed: window.__poracodeDev.stores.sharedSettings.getState().installedPlugins["browser-tools"] !== undefined,
back: buttonText.includes("Back to plugins"),
uninstall: buttonText.includes("Uninstall"),
mcpServers: headings.includes("MCP servers") && document.body.innerText.includes("Browser"),
skills: headings.includes("Skills") && document.body.innerText.includes("Browser Control"),
bundledMcpHasNoSeparateSwitch: !switchNames.includes("Browser MCP"),
skillSwitch: switchNames.includes("Browser Control Skill"),
};
})()`,
),
(state) =>
state.installed &&
state.back &&
state.uninstall &&
state.mcpServers &&
state.skills &&
state.bundledMcpHasNoSeparateSwitch &&
state.skillSwitch,
"Browser Tools plugin detail",
);
assert(
detailState.mcpServers && detailState.skills,
"Browser Tools contributions did not render",
);
assert(
detailState.bundledMcpHasNoSeparateSwitch && detailState.skillSwitch,
"Browser Tools contribution controls did not match the combined plugin contract",
);

const pluginsScreenshotPath = join(outDir, "smoke-02-plugins.png");
await screenshot(client, pluginsScreenshotPath);

if (!marketplaceState.initialInstalled) {
const uninstalled = await evaluate(
client,
`(() => {
const button = [...document.querySelectorAll("button")].find(
(candidate) => candidate.textContent?.trim() === "Uninstall",
);
if (!(button instanceof HTMLButtonElement)) return false;
button.click();
return true;
})()`,
);
assert(uninstalled, "Browser Tools uninstall action was unavailable");
await waitForValue(
() =>
evaluate(
client,
`window.__poracodeDev.stores.sharedSettings.getState().installedPlugins["browser-tools"] === undefined`,
),
Boolean,
"Browser Tools install-state restoration",
);
}

const restored = await evaluate(
client,
`window.__poracodeDev.stores.sharedSettings.getState().installedPlugins[${JSON.stringify(pluginId)}] !== undefined`,
);
assert(
restored === marketplaceState.initialInstalled,
"Browser Tools install state was not restored",
);
return { pluginsScreenshotPath };
} finally {
await evaluate(
client,
`(() => {
const store = window.__poracodeDev.stores.sharedSettings.getState();
const plugin = window.__poracodeDev.stores.plugins
.getState()
.plugins.find((candidate) => candidate.name === ${JSON.stringify(pluginId)});
if (!plugin) return;
const installed = store.installedPlugins[${JSON.stringify(pluginId)}] !== undefined;
if (${JSON.stringify(marketplaceState.initialInstalled)} && !installed) {
store.installPlugin(plugin);
} else if (!${JSON.stringify(marketplaceState.initialInstalled)} && installed) {
store.uninstallPlugin(plugin);
}
})()`,
);
if (detailOpened) {
await evaluate(
client,
`(() => {
const button = [...document.querySelectorAll("button")].find(
(candidate) => candidate.textContent?.trim() === "Back to plugins",
);
if (button instanceof HTMLButtonElement) button.click();
})()`,
);
}
}
}

async function skillsSectionDeepDive(client) {
const toolbarState = await evaluate(
client,
Expand Down
11 changes: 11 additions & 0 deletions .agents/skills/interactive-testing/scripts/smoke-scenarios.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,17 @@ export const functionalAreas = [
automated: ["baseline", "settings"],
manual: [],
},
{
id: "plugins-marketplace",
title: "Plugin marketplace, installation, and bundled MCP and skill contributions",
patterns: [
/components\/plugins\//i,
/shared\/(?:contracts\/plugin|plugins\/)/i,
/PluginsSettings/i,
],
automated: ["settings"],
manual: [],
},
{
id: "updates-auth-usage",
title: "Updates, authentication, usage, notifications, and diagnostics",
Expand Down
33 changes: 33 additions & 0 deletions resources/plugins/browser-tools/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
"name": "browser-tools",
"version": "1.1.0",
"description": "Browse, inspect, and test websites in Poracode's isolated in-app browser.",
"author": {
"name": "Poracode",
"url": "https://poracode.com"
},
"homepage": "https://poracode.com",
"license": "Apache-2.0",
"keywords": ["browser", "testing", "automation"],
"extensions": {
"com.poracode.client": {
"title": "Browser Tools",
"category": "developer-tools",
"featured": true,
"examplePrompt": "Open my local app in Poracode's browser, test the requested flow, and report visual, console, and network evidence",
"coreSkill": "browser-control",
"nativePluginNames": ["browser"],
"nativeCoreSkill": "control-in-app-browser",
"builtInMcpServerIds": ["browser"],
"skills": {
"browser-control": {
"name": "Browser Control",
"description": "Navigate, inspect, and test pages with the in-app Browser MCP.",
"nativePluginName": "browser",
"nativeSkill": "control-in-app-browser"
}
}
}
}
}
29 changes: 29 additions & 0 deletions resources/plugins/browser-tools/skills/browser-control/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
name: browser-control
description: Open, inspect, interact with, and verify websites or local web apps in Poracode's isolated browser. Use for visible page state, navigation, screenshots, console or network evidence, and end-to-end UI testing; do not use it for semantic service operations when a purpose-built connector is available.
---

# Browser Control

Use Poracode's `browser` MCP when the task depends on a rendered page, visible interaction, or local web app. If the request is really about structured data or a service operation and a purpose-built connector is available, use that connector instead. An explicit request for Poracode's browser wins.

## Workflow

1. Call `browser.api` when you need the current API map, then call `browser.enable` once before the first browser action.
2. Reuse a relevant tab from `browser.list_tabs`; otherwise open the exact URL the user supplied or the known local target. Do not guess a remote site or substitute web search when authentication blocks the requested page.
3. Establish the baseline with the current URL plus `browser.snapshot` or `browser.find`. Prefer accessible roles, names, and returned element refs over brittle selectors or coordinates.
4. Perform the smallest meaningful action. Use `fill` when replacing a field and `type` only when appending is intended.
5. After every navigation or state-changing action, wait for the expected URL, text, or element and inspect the resulting state. For web-app verification, also check relevant console errors and failed network requests.
6. Capture a screenshot when visual layout or appearance is part of the requirement.
7. Call `browser.disable` before asking the user for input, waiting on an external event, or finishing.

## Boundaries

- The in-app browser is isolated from the user's personal Chrome profile. Do not assume it contains existing logins, cookies, or extensions.
- Never inspect cookies or storage unless the task requires it and the user authorized that data access.
- A successful click is not proof of success. Verify the user-visible or application state it was meant to produce.
- Pause before purchases, submissions, messages, deletions, or other irreversible external actions unless the user explicitly authorized that exact action.

## Output

Report the tested URL and flow, the final observed state, and the evidence used. Separate visual, console, and network findings, and state any step that could not be verified.
34 changes: 34 additions & 0 deletions resources/plugins/chrome-tools/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
"name": "chrome-tools",
"version": "1.1.0",
"description": "Work with the pages and signed-in sessions already open in Chrome.",
"author": {
"name": "Poracode",
"url": "https://poracode.com"
},
"homepage": "https://poracode.com",
"license": "Apache-2.0",
"keywords": ["chrome", "browser", "automation"],
"extensions": {
"com.poracode.client": {
"title": "Chrome Tools",
"category": "automation",
"featured": true,
"examplePrompt": "Use my existing Chrome session to complete this browser task and verify the final visible state",
"projectKinds": ["windows", "posix"],
"coreSkill": "chrome-control",
"nativePluginNames": ["chrome"],
"nativeCoreSkill": "control-chrome",
"builtInMcpServerIds": ["chrome"],
"skills": {
"chrome-control": {
"name": "Chrome Control",
"description": "Use Chrome safely when a task needs an existing browser session.",
"nativePluginName": "chrome",
"nativeSkill": "control-chrome"
}
}
}
}
}
28 changes: 28 additions & 0 deletions resources/plugins/chrome-tools/skills/chrome-control/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
name: chrome-control
description: Use the user's real Chrome tabs and signed-in sessions for visible browser workflows. Use when existing authentication, open tabs, cookies, or extensions matter; prefer a purpose-built connector for semantic service operations unless the user explicitly asks for Chrome.
---

# Chrome Control

Use Poracode's `chrome` MCP when a task depends on the user's current Chrome tabs, authenticated sessions, or installed extensions. If a purpose-built connector can complete a semantic service operation, prefer it unless the user explicitly requested Chrome or visual interaction is part of the task.

## Workflow

1. Call `chrome.chrome_status` first. If the extension is disconnected, ask the user to connect it rather than switching surfaces silently.
2. Call `chrome.enable` once before browser actions. Use the background Poracode workspace by default; call `chrome.chrome_attach` only when the user asked to operate an existing tab.
3. Inspect with `chrome.chrome_snapshot` or `chrome.chrome_find` before clicking or typing. Prefer returned element refs and use `chrome_fill` for replacement versus `chrome_type` for appending.
4. Keep every action scoped to the requested site and task. Do not explore other tabs or signed-in content for extra context.
5. After every meaningful action, wait for and verify the resulting URL, text, control state, or screenshot.
6. Call `chrome.disable` before asking the user for input, waiting on an external event, or finishing.

## Boundaries

- Treat tabs, cookies, storage, and signed-in content as sensitive user data. Do not read cookies unless the task requires it and Chrome data access is enabled.
- Never attach to an unrelated existing tab merely because it is already authenticated.
- A successful tool call is not proof that the website accepted the action; verify the visible result.
- Confirm the exact target and payload before purchases, submissions, messages, deletions, account changes, or other irreversible actions unless already authorized.

## Output

Report the target site or tab, what changed, and the final visible evidence. State whether the background workspace or an existing user tab was used, and identify any action left pending for confirmation.
35 changes: 35 additions & 0 deletions resources/plugins/computer-use/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
"name": "computer-use",
"version": "1.1.0",
"description": "Control desktop apps and complete visual workflows.",
"author": {
"name": "Poracode",
"url": "https://poracode.com"
},
"homepage": "https://poracode.com",
"license": "Apache-2.0",
"keywords": ["desktop", "automation", "computer-use"],
"extensions": {
"com.poracode.client": {
"title": "Computer Use",
"category": "automation",
"featured": true,
"examplePrompt": "Operate the requested desktop app in small verified steps and report the final window state",
"platforms": ["win32", "darwin"],
"projectKinds": ["windows", "posix"],
"coreSkill": "computer-use",
"nativePluginNames": ["computer-use"],
"nativeCoreSkill": "computer-use",
"builtInMcpServerIds": ["computer-use"],
"skills": {
"computer-use": {
"name": "Computer Use",
"description": "Operate desktop apps through Poracode's desktop-control tools.",
"nativePluginName": "computer-use",
"nativeSkill": "computer-use"
}
}
}
}
}
Loading