Skip to content

Commit bde02dd

Browse files
authored
feat: implement waitFor tool (#20)
* feat: implement waitFor tool * docs: update README to reflect new location API and waitForElement tool * docs: udpate comments in examples * fix: use openAndWait instead of url in navigate tool * fix: update error messages to be more readable
1 parent 331c183 commit bde02dd

14 files changed

+1159
-385
lines changed

README.md

Lines changed: 98 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ A [Model Context Protocol server](https://modelcontextprotocol.io/quickstart/use
3737
<details>
3838
<summary>Set up in Cursor</summary>
3939

40-
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.
40+
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.
4141

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

176-
- Semantic Queries:
177-
- **Parameters:**
178-
- `queryType` (string, optional): Semantic query type. One of:
176+
- **Parameters:**
177+
- `locator` (object, required): Element location strategy
178+
- `strategy` (string, required): Either `"testing-library"` or `"webdriverio"`
179+
180+
For **testing-library strategy**:
181+
- `queryType` (string, required): Semantic query type. One of:
179182
- `"role"` - Find by ARIA role (e.g., "button", "link", "heading")
180183
- `"text"` - Find by visible text content
181184
- `"labelText"` - Find form inputs by their label text
@@ -184,37 +187,69 @@ Click an element on the page using semantic queries (`testing-library`-style) or
184187
- `"testId"` - Find by data-testid attribute
185188
- `"title"` - Find by title attribute
186189
- `"displayValue"` - Find inputs by their current value
187-
- `queryValue` (string, required when using queryType): The value to search for
190+
- `queryValue` (string, required): The value to search for
188191
- `queryOptions` (object, optional): Additional options:
189192
- `name` (string): Accessible name for role queries
190193
- `exact` (boolean): Whether to match exact text (default: true)
191194
- `hidden` (boolean): Include hidden elements (default: false)
192195
- `level` (number): Heading level for role="heading" (1-6)
193-
194-
- CSS Selectors:
195-
- **Parameters:**
196-
- `selector` (string, optional): CSS selector or XPath when semantic queries cannot locate the element
196+
197+
For **webdriverio strategy**:
198+
- `selector` (string, required): CSS selector, XPath or WebdriverIO locator
197199

198200
**Examples:**
199201
```javascript
200-
// Semantic queries (preferred)
201-
{ queryType: "role", queryValue: "button", queryOptions: { name: "Submit" } }
202-
{ queryType: "text", queryValue: "Click here" }
203-
{ queryType: "labelText", queryValue: "Email Address" }
204-
205-
// CSS selector fallback
206-
{ selector: ".submit-btn" }
207-
{ selector: "#unique-element" }
202+
// Testing Library strategy
203+
{
204+
locator: {
205+
strategy: "testing-library",
206+
queryType: "role",
207+
queryValue: "button",
208+
queryOptions: { name: "Submit" }
209+
}
210+
}
211+
212+
{
213+
locator: {
214+
strategy: "testing-library",
215+
queryType: "text",
216+
queryValue: "Click here"
217+
}
218+
}
219+
220+
{
221+
locator: {
222+
strategy: "testing-library",
223+
queryType: "labelText",
224+
queryValue: "Email Address"
225+
}
226+
}
227+
228+
// WebdriverIO strategy
229+
{
230+
locator: {
231+
strategy: "webdriverio",
232+
selector: ".submit-btn"
233+
}
234+
}
235+
236+
{
237+
locator: {
238+
strategy: "webdriverio",
239+
selector: "button*=Submit"
240+
}
241+
}
208242
```
209243

210-
**Note:** Provide either semantic query parameters OR selector, not both.
211-
212244
### `typeIntoElement`
213245
Type text into an input element on the page using semantic queries (`testing-library`-style) or CSS selectors.
214246

215-
- Semantic Queries:
216-
- **Parameters:**
217-
- `queryType` (string, optional): Semantic query type. One of:
247+
- **Parameters:**
248+
- `locator` (object, required): Element location strategy
249+
- `strategy` (string, required): Either `"testing-library"` or `"webdriverio"`
250+
251+
For **testing-library strategy**:
252+
- `queryType` (string, required): Semantic query type. One of:
218253
- `"role"` - Find by ARIA role (e.g., "textbox", "searchbox")
219254
- `"text"` - Find by visible text content
220255
- `"labelText"` - Find form inputs by their label text
@@ -223,31 +258,55 @@ Type text into an input element on the page using semantic queries (`testing-lib
223258
- `"testId"` - Find by data-testid attribute
224259
- `"title"` - Find by title attribute
225260
- `"displayValue"` - Find inputs by their current value
226-
- `queryValue` (string, required when using queryType): The value to search for
227-
- `text` (string, required): The text to type into the element
261+
- `queryValue` (string, required): The value to search for
228262
- `queryOptions` (object, optional): Additional options:
229263
- `name` (string): Accessible name for role queries
230264
- `exact` (boolean): Whether to match exact text (default: true)
231265
- `hidden` (boolean): Include hidden elements (default: false)
266+
267+
For **webdriverio strategy**:
268+
- `selector` (string, required): CSS selector or XPath
269+
270+
- `text` (string, required): The text to type into the element
271+
272+
**Examples:**
232273

233-
- CSS Selectors:
234-
- **Parameters:**
235-
- `selector` (string, optional): CSS selector or XPath when semantic queries cannot locate the element
236-
- `text` (string, required): The text to type into the element
274+
See above in the `clickOnElement` tool.
275+
276+
### `waitForElement`
277+
Wait for an element to appear or disappear on the page. Useful for waiting until page loads fully or loading spinners disappear.
278+
279+
- **Parameters:**
280+
- `locator` (object, required): Element location strategy
281+
- `strategy` (string, required): Either `"testing-library"` or `"webdriverio"`
282+
283+
For **testing-library strategy**:
284+
- `queryType` (string, required): Semantic query type. One of:
285+
- `"role"` - Find by ARIA role (e.g., "button", "link", "heading")
286+
- `"text"` - Find by visible text content
287+
- `"labelText"` - Find form inputs by their label text
288+
- `"placeholderText"` - Find inputs by placeholder text
289+
- `"altText"` - Find images by alt text
290+
- `"testId"` - Find by data-testid attribute
291+
- `"title"` - Find by title attribute
292+
- `"displayValue"` - Find inputs by their current value
293+
- `queryValue` (string, required): The value to search for
294+
- `queryOptions` (object, optional): Additional options:
295+
- `name` (string): Accessible name for role queries
296+
- `exact` (boolean): Whether to match exact text (default: true)
297+
- `hidden` (boolean): Include hidden elements (default: false)
298+
- `level` (number): Heading level for role="heading" (1-6)
299+
300+
For **webdriverio strategy**:
301+
- `selector` (string, required): CSS selector or XPath
302+
303+
- `disappear` (boolean, optional): Whether to wait for element to disappear. Default: false (wait for element to appear)
304+
- `timeout` (number, optional): Maximum time to wait in milliseconds. Default: 3000
305+
- `includeSnapshotInResponse` (boolean, optional): Whether to include page snapshot in response. Default: true
237306

238307
**Examples:**
239-
```javascript
240-
// Semantic queries (preferred)
241-
{ queryType: "labelText", queryValue: "Email Address", text: "[email protected]" }
242-
{ queryType: "placeholderText", queryValue: "Enter your name", text: "John Smith" }
243-
{ queryType: "role", queryValue: "textbox", queryOptions: { name: "Username" }, text: "john_doe" }
244-
245-
// CSS selector fallback
246-
{ selector: "#username", text: "john_doe" }
247-
{ selector: "input[name='email']", text: "[email protected]" }
248-
```
249308

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

252311
</details>
253312

src/tools/click-on-element.ts

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,17 @@ const clickOnElementCb: ToolCallback<typeof elementClickSchema> = async args =>
1111
const context = contextProvider.getContext();
1212
const browser = await context.browser.get();
1313

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

1616
await element.click();
1717

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

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

3839
export const clickOnElement: ToolDefinition<typeof elementClickSchema> = {
3940
name: "clickOnElement",
40-
description: `Click an element on the page.
41-
42-
PREFERRED APPROACH (for AI agents): Use semantic queries (queryType + queryValue) which are more robust and accessibility-focused:
43-
- queryType="role" + queryValue="button" + queryOptions.name="Submit" → finds submit button
44-
- queryType="text" + queryValue="Click here" → finds element containing that text
45-
- queryType="labelText" + queryValue="Email" → finds input with Email label
46-
47-
FALLBACK APPROACH: Use selector only when semantic queries cannot locate the element:
48-
- selector="button.submit-btn" → CSS selector
49-
- selector="//button[text()='Submit']" → XPath
50-
51-
AI agents should prioritize semantic queries for better accessibility and test maintainability.`,
41+
description: "Click an element on the page.",
5242
schema: elementClickSchema,
5343
cb: clickOnElementCb,
5444
};

src/tools/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { listTabs } from "./list-tabs.js";
88
import { switchToTab } from "./switch-to-tab.js";
99
import { openNewTab } from "./open-new-tab.js";
1010
import { closeTab } from "./close-tab.js";
11+
import { waitForElement } from "./wait-for-element.js";
1112

1213
export const tools = [
1314
navigate,
@@ -19,4 +20,5 @@ export const tools = [
1920
switchToTab,
2021
openNewTab,
2122
closeTab,
23+
waitForElement,
2224
] as const satisfies ToolDefinition<any>[]; // eslint-disable-line @typescript-eslint/no-explicit-any

src/tools/navigate.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ const navigateCb: ToolCallback<typeof navigateSchema> = async args => {
1616
const browser = await context.browser.get();
1717

1818
console.error(`Navigating to: ${url}`);
19-
await browser.url(url);
19+
await browser.openAndWait(url);
2020

2121
return await createBrowserStateResponse(browser, {
2222
action: `Successfully navigated to ${url}`,
23-
testplaneCode: `await browser.url("${url}");`,
23+
testplaneCode: `await browser.openAndWait("${url}");`,
2424
});
2525
} catch (error) {
2626
console.error("Error navigating to URL:", error);

src/tools/take-page-snapshot.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ const takePageSnapshotCb: ToolCallback<typeof takePageSnapshotSchema> = async ar
4646
export const takePageSnapshot: ToolDefinition<typeof takePageSnapshotSchema> = {
4747
name: "takePageSnapshot",
4848
description:
49-
"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.",
49+
"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.",
5050
schema: takePageSnapshotSchema,
5151
cb: takePageSnapshotCb,
5252
};

src/tools/type-into-element.ts

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,22 @@ export const typeIntoElementSchema = {
1212

1313
const typeIntoElementCb: ToolCallback<typeof typeIntoElementSchema> = async args => {
1414
try {
15-
const { text, ...selectorArgs } = args;
15+
const { text } = args;
1616

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

20-
const { element, queryDescription, testplaneCode } = await findElement(
21-
browser,
22-
selectorArgs,
23-
`await element.setValue("${text}");`,
24-
);
20+
const { element, queryDescription, testplaneCode } = await findElement(browser, args.locator);
2521

2622
await element.setValue(text);
2723

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

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

4845
export const typeIntoElement: ToolDefinition<typeof typeIntoElementSchema> = {
4946
name: "typeIntoElement",
50-
description: `Type text into an element on the page. The API is very similar to clickOnElement for consistency.
51-
52-
PREFERRED APPROACH (for AI agents): Use semantic queries (queryType + queryValue) which are more robust and accessibility-focused:
53-
- queryType="role" + queryValue="textbox" + queryOptions.name="Email" → finds email input
54-
- queryType="labelText" + queryValue="Password" → finds input with Password label
55-
- queryType="placeholderText" + queryValue="Enter your name" → finds input with specific placeholder
56-
57-
FALLBACK APPROACH: Use selector only when semantic queries cannot locate the element:
58-
- selector="input[name='email']" → CSS selector
59-
- selector="//input[@placeholder='Search...']" → XPath
60-
61-
AI agents should prioritize semantic queries for better accessibility and test maintainability.`,
47+
description: "Type text into an element on the page.",
6248
schema: typeIntoElementSchema,
6349
cb: typeIntoElementCb,
6450
};

0 commit comments

Comments
 (0)