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
137 changes: 98 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ A [Model Context Protocol server](https://modelcontextprotocol.io/quickstart/use
<details>
<summary>Set up in Cursor</summary>

Open Cursor `Settings` (button at the top right corner of the screen), find `MCP` section, click on the `Add new global MCP server` button, edit the config to include Testplane MCP as seen below.
Open Cursor `Settings` (button at the top right corner of the screen), find `Tools & Integrations` section, click on the `New MCP Server` button, edit the config to include Testplane MCP as seen below.

```json
{
Expand Down Expand Up @@ -173,9 +173,12 @@ Close a specific browser tab by its number (1-based), or close the current tab i
### `clickOnElement`
Click an element on the page using semantic queries (`testing-library`-style) or CSS selectors.

- Semantic Queries:
- **Parameters:**
- `queryType` (string, optional): Semantic query type. One of:
- **Parameters:**
- `locator` (object, required): Element location strategy
- `strategy` (string, required): Either `"testing-library"` or `"webdriverio"`

For **testing-library strategy**:
- `queryType` (string, required): Semantic query type. One of:
- `"role"` - Find by ARIA role (e.g., "button", "link", "heading")
- `"text"` - Find by visible text content
- `"labelText"` - Find form inputs by their label text
Expand All @@ -184,37 +187,69 @@ Click an element on the page using semantic queries (`testing-library`-style) or
- `"testId"` - Find by data-testid attribute
- `"title"` - Find by title attribute
- `"displayValue"` - Find inputs by their current value
- `queryValue` (string, required when using queryType): The value to search for
- `queryValue` (string, required): The value to search for
- `queryOptions` (object, optional): Additional options:
- `name` (string): Accessible name for role queries
- `exact` (boolean): Whether to match exact text (default: true)
- `hidden` (boolean): Include hidden elements (default: false)
- `level` (number): Heading level for role="heading" (1-6)

- CSS Selectors:
- **Parameters:**
- `selector` (string, optional): CSS selector or XPath when semantic queries cannot locate the element

For **webdriverio strategy**:
- `selector` (string, required): CSS selector, XPath or WebdriverIO locator

**Examples:**
```javascript
// Semantic queries (preferred)
{ queryType: "role", queryValue: "button", queryOptions: { name: "Submit" } }
{ queryType: "text", queryValue: "Click here" }
{ queryType: "labelText", queryValue: "Email Address" }

// CSS selector fallback
{ selector: ".submit-btn" }
{ selector: "#unique-element" }
// Testing Library strategy
{
locator: {
strategy: "testing-library",
queryType: "role",
queryValue: "button",
queryOptions: { name: "Submit" }
}
}

{
locator: {
strategy: "testing-library",
queryType: "text",
queryValue: "Click here"
}
}

{
locator: {
strategy: "testing-library",
queryType: "labelText",
queryValue: "Email Address"
}
}

// WebdriverIO strategy
{
locator: {
strategy: "webdriverio",
selector: ".submit-btn"
}
}

{
locator: {
strategy: "webdriverio",
selector: "button*=Submit"
}
}
```

**Note:** Provide either semantic query parameters OR selector, not both.

### `typeIntoElement`
Type text into an input element on the page using semantic queries (`testing-library`-style) or CSS selectors.

- Semantic Queries:
- **Parameters:**
- `queryType` (string, optional): Semantic query type. One of:
- **Parameters:**
- `locator` (object, required): Element location strategy
- `strategy` (string, required): Either `"testing-library"` or `"webdriverio"`

For **testing-library strategy**:
- `queryType` (string, required): Semantic query type. One of:
- `"role"` - Find by ARIA role (e.g., "textbox", "searchbox")
- `"text"` - Find by visible text content
- `"labelText"` - Find form inputs by their label text
Expand All @@ -223,31 +258,55 @@ Type text into an input element on the page using semantic queries (`testing-lib
- `"testId"` - Find by data-testid attribute
- `"title"` - Find by title attribute
- `"displayValue"` - Find inputs by their current value
- `queryValue` (string, required when using queryType): The value to search for
- `text` (string, required): The text to type into the element
- `queryValue` (string, required): The value to search for
- `queryOptions` (object, optional): Additional options:
- `name` (string): Accessible name for role queries
- `exact` (boolean): Whether to match exact text (default: true)
- `hidden` (boolean): Include hidden elements (default: false)

For **webdriverio strategy**:
- `selector` (string, required): CSS selector or XPath

- `text` (string, required): The text to type into the element

**Examples:**

- CSS Selectors:
- **Parameters:**
- `selector` (string, optional): CSS selector or XPath when semantic queries cannot locate the element
- `text` (string, required): The text to type into the element
See above in the `clickOnElement` tool.

### `waitForElement`
Wait for an element to appear or disappear on the page. Useful for waiting until page loads fully or loading spinners disappear.

- **Parameters:**
- `locator` (object, required): Element location strategy
- `strategy` (string, required): Either `"testing-library"` or `"webdriverio"`

For **testing-library strategy**:
- `queryType` (string, required): Semantic query type. One of:
- `"role"` - Find by ARIA role (e.g., "button", "link", "heading")
- `"text"` - Find by visible text content
- `"labelText"` - Find form inputs by their label text
- `"placeholderText"` - Find inputs by placeholder text
- `"altText"` - Find images by alt text
- `"testId"` - Find by data-testid attribute
- `"title"` - Find by title attribute
- `"displayValue"` - Find inputs by their current value
- `queryValue` (string, required): The value to search for
- `queryOptions` (object, optional): Additional options:
- `name` (string): Accessible name for role queries
- `exact` (boolean): Whether to match exact text (default: true)
- `hidden` (boolean): Include hidden elements (default: false)
- `level` (number): Heading level for role="heading" (1-6)

For **webdriverio strategy**:
- `selector` (string, required): CSS selector or XPath

- `disappear` (boolean, optional): Whether to wait for element to disappear. Default: false (wait for element to appear)
- `timeout` (number, optional): Maximum time to wait in milliseconds. Default: 3000
- `includeSnapshotInResponse` (boolean, optional): Whether to include page snapshot in response. Default: true

**Examples:**
```javascript
// Semantic queries (preferred)
{ queryType: "labelText", queryValue: "Email Address", text: "[email protected]" }
{ queryType: "placeholderText", queryValue: "Enter your name", text: "John Smith" }
{ queryType: "role", queryValue: "textbox", queryOptions: { name: "Username" }, text: "john_doe" }

// CSS selector fallback
{ selector: "#username", text: "john_doe" }
{ selector: "input[name='email']", text: "[email protected]" }
```

**Note:** Provide either semantic query parameters OR selector, not both.
See above in the `clickOnElement` tool.

</details>

Expand Down
20 changes: 5 additions & 15 deletions src/tools/click-on-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,17 @@ const clickOnElementCb: ToolCallback<typeof elementClickSchema> = async args =>
const context = contextProvider.getContext();
const browser = await context.browser.get();

const { element, queryDescription, testplaneCode } = await findElement(browser, args, `await element.click();`);
const { element, queryDescription, testplaneCode } = await findElement(browser, args.locator);

await element.click();

console.error(`Successfully clicked element with ${queryDescription}`);

return await createElementStateResponse(element, {
action: `Successfully clicked element found by ${queryDescription}`,
testplaneCode,
additionalInfo: `Element selection strategy: ${args.queryType ? `Semantic query (${args.queryType})` : "CSS selector (fallback)"}`,
testplaneCode: testplaneCode.startsWith("await")
? `await (${testplaneCode}).click();`
: `await ${testplaneCode}.click();`,
});
} catch (error) {
console.error("Error clicking element:", error);
Expand All @@ -37,18 +38,7 @@ const clickOnElementCb: ToolCallback<typeof elementClickSchema> = async args =>

export const clickOnElement: ToolDefinition<typeof elementClickSchema> = {
name: "clickOnElement",
description: `Click an element on the page.

PREFERRED APPROACH (for AI agents): Use semantic queries (queryType + queryValue) which are more robust and accessibility-focused:
- queryType="role" + queryValue="button" + queryOptions.name="Submit" → finds submit button
- queryType="text" + queryValue="Click here" → finds element containing that text
- queryType="labelText" + queryValue="Email" → finds input with Email label

FALLBACK APPROACH: Use selector only when semantic queries cannot locate the element:
- selector="button.submit-btn" → CSS selector
- selector="//button[text()='Submit']" → XPath

AI agents should prioritize semantic queries for better accessibility and test maintainability.`,
description: "Click an element on the page.",
schema: elementClickSchema,
cb: clickOnElementCb,
};
2 changes: 2 additions & 0 deletions src/tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { listTabs } from "./list-tabs.js";
import { switchToTab } from "./switch-to-tab.js";
import { openNewTab } from "./open-new-tab.js";
import { closeTab } from "./close-tab.js";
import { waitForElement } from "./wait-for-element.js";

export const tools = [
navigate,
Expand All @@ -19,4 +20,5 @@ export const tools = [
switchToTab,
openNewTab,
closeTab,
waitForElement,
] as const satisfies ToolDefinition<any>[]; // eslint-disable-line @typescript-eslint/no-explicit-any
4 changes: 2 additions & 2 deletions src/tools/navigate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ const navigateCb: ToolCallback<typeof navigateSchema> = async args => {
const browser = await context.browser.get();

console.error(`Navigating to: ${url}`);
await browser.url(url);
await browser.openAndWait(url);

return await createBrowserStateResponse(browser, {
action: `Successfully navigated to ${url}`,
testplaneCode: `await browser.url("${url}");`,
testplaneCode: `await browser.openAndWait("${url}");`,
});
} catch (error) {
console.error("Error navigating to URL:", error);
Expand Down
2 changes: 1 addition & 1 deletion src/tools/take-page-snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const takePageSnapshotCb: ToolCallback<typeof takePageSnapshotSchema> = async ar
export const takePageSnapshot: ToolDefinition<typeof takePageSnapshotSchema> = {
name: "takePageSnapshot",
description:
"Capture a DOM snapshot of the current page. Note: by default, only useful tags and attributes are included. Prefer to use defaults. Response contains info as to what was omitted. If you need more info, request a snapshot with more tags and attributes.",
"Capture a DOM snapshot of the current page. Note: by default, not useful tags and attributes are excluded (e.g. script, style, etc.). Prefer to use defaults. Response contains info as to what was omitted. If you need more info, request a snapshot with more tags and attributes.",
schema: takePageSnapshotSchema,
cb: takePageSnapshotCb,
};
26 changes: 6 additions & 20 deletions src/tools/type-into-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,22 @@ export const typeIntoElementSchema = {

const typeIntoElementCb: ToolCallback<typeof typeIntoElementSchema> = async args => {
try {
const { text, ...selectorArgs } = args;
const { text } = args;

const context = contextProvider.getContext();
const browser = await context.browser.get();

const { element, queryDescription, testplaneCode } = await findElement(
browser,
selectorArgs,
`await element.setValue("${text}");`,
);
const { element, queryDescription, testplaneCode } = await findElement(browser, args.locator);

await element.setValue(text);

console.error(`Successfully typed "${text}" into element with ${queryDescription}`);

return await createElementStateResponse(element, {
action: `Successfully typed "${text}" into element found by ${queryDescription}`,
testplaneCode,
additionalInfo: `Element selection strategy: ${selectorArgs.queryType ? `Semantic query (${selectorArgs.queryType})` : "CSS selector (fallback)"}`,
testplaneCode: testplaneCode.startsWith("await")
? `await (${testplaneCode}).setValue("${text}");`
: `await ${testplaneCode}.setValue("${text}");`,
});
} catch (error) {
console.error("Error typing into element:", error);
Expand All @@ -47,18 +44,7 @@ const typeIntoElementCb: ToolCallback<typeof typeIntoElementSchema> = async args

export const typeIntoElement: ToolDefinition<typeof typeIntoElementSchema> = {
name: "typeIntoElement",
description: `Type text into an element on the page. The API is very similar to clickOnElement for consistency.

PREFERRED APPROACH (for AI agents): Use semantic queries (queryType + queryValue) which are more robust and accessibility-focused:
- queryType="role" + queryValue="textbox" + queryOptions.name="Email" → finds email input
- queryType="labelText" + queryValue="Password" → finds input with Password label
- queryType="placeholderText" + queryValue="Enter your name" → finds input with specific placeholder

FALLBACK APPROACH: Use selector only when semantic queries cannot locate the element:
- selector="input[name='email']" → CSS selector
- selector="//input[@placeholder='Search...']" → XPath

AI agents should prioritize semantic queries for better accessibility and test maintainability.`,
description: "Type text into an element on the page.",
schema: typeIntoElementSchema,
cb: typeIntoElementCb,
};
Loading