Skip to content

Commit 83a78dd

Browse files
committed
mcpconfigureserver
1 parent 3956219 commit 83a78dd

File tree

2 files changed

+264
-48
lines changed

2 files changed

+264
-48
lines changed

packages/amazonq/test/e2e_new/amazonq/helpers/mcpHelper.ts

Lines changed: 246 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,25 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55
import { WebviewView, By } from 'vscode-extension-tester'
6-
import { sleep, waitForElement } from '../utils/generalUtils'
6+
import { waitForElement } from '../utils/generalUtils'
77

8+
/**
9+
* Clicks the tools to get to the MCP server overlay
10+
* @param webviewView The WebviewView instance
11+
* @returns Promise<boolean> True if tools button was found and clicked, false otherwise
12+
*/
813
export async function clickToolsButton(webviewView: WebviewView): Promise<boolean> {
914
try {
10-
const menuList = await waitForElement(webviewView, By.css('.mynah-nav-tabs-wrapper.mynah-ui-clickable-item'))
11-
const menuListItem = await menuList.findElement(By.css('.mynah-nav-tabs-bar-buttons-wrapper'))
12-
const menuListItems = await menuListItem.findElements(
15+
const navWrapper = await waitForElement(webviewView, By.css('.mynah-nav-tabs-wrapper.mynah-ui-clickable-item'))
16+
const buttonsWrapper = await navWrapper.findElement(By.css('.mynah-nav-tabs-bar-buttons-wrapper'))
17+
const buttons = await buttonsWrapper.findElements(
1318
By.css('.mynah-button.mynah-button-secondary.fill-state-always.mynah-ui-clickable-item')
1419
)
15-
for (const item of menuListItems) {
16-
const icon = await item.findElement(By.css('i.mynah-ui-icon.mynah-ui-icon-tools'))
20+
for (const button of buttons) {
21+
const icon = await button.findElement(By.css('i.mynah-ui-icon.mynah-ui-icon-tools'))
1722
if (icon) {
18-
await item.click()
23+
await button.click()
24+
await webviewView.getDriver().actions().move({ x: 0, y: 0 }).perform()
1925
return true
2026
}
2127
}
@@ -27,39 +33,245 @@ export async function clickToolsButton(webviewView: WebviewView): Promise<boolea
2733
}
2834
}
2935

30-
export async function clickMCPCloseButton(webviewView: WebviewView): Promise<boolean> {
36+
/**
37+
* Clicks the add button in the MCP server configuration panel
38+
* @param webviewView The WebviewView instance
39+
* @returns Promise<boolean> True if add button was found and clicked, false otherwise
40+
*/
41+
export async function clickMCPAddButton(webviewView: WebviewView): Promise<boolean> {
3142
try {
32-
const menuList = await waitForElement(webviewView, By.id('mynah-sheet-wrapper'))
33-
console.log('THIS WORKS 1')
34-
sleep(5000)
35-
const menuu = await menuList.findElement(By.css('.mynah-sheet-header'))
36-
console.log('THIS WORKS 2')
37-
sleep(5000)
38-
const menuListItems = await menuu.findElement(
39-
By.css('.mynah-button.mynah-button-secondary.fill-state-always.mynah-ui-clickable-item')
43+
const sheetWrapper = await waitForElement(webviewView, By.id('mynah-sheet-wrapper'))
44+
const header = await sheetWrapper.findElement(By.css('.mynah-sheet-header'))
45+
const actionsContainer = await header.findElement(By.css('.mynah-sheet-header-actions-container'))
46+
const addButton = await actionsContainer.findElement(By.css('button:has(i.mynah-ui-icon-plus)'))
47+
await addButton.click()
48+
return true
49+
} catch (e) {
50+
console.error('Error clicking the MCP add button:', e)
51+
return false
52+
}
53+
}
54+
55+
/**
56+
* Configures an MCP server with the provided settings
57+
* @param webviewView The WebviewView instance
58+
* @param config Configuration object with optional parameters
59+
* @returns Promise<boolean> True if configuration was successful, false otherwise
60+
* Note: I have the default settings in the config variable
61+
*/
62+
interface MCPServerConfig {
63+
scope?: 'global' | 'workspace'
64+
name?: string
65+
transport?: number
66+
command?: string
67+
args?: string[]
68+
nameEnvironmentVariable?: string
69+
valueEnvironmentVariable?: string
70+
timeout?: number
71+
}
72+
export async function configureMCPServer(webviewView: WebviewView, config: MCPServerConfig = {}): Promise<boolean> {
73+
const {
74+
scope = 'workspace',
75+
name = 'aws-documentation',
76+
transport = 0,
77+
command = 'uvx',
78+
args = ['awslabs.aws-documentation-mcp-server@latest'],
79+
nameEnvironmentVariable = 'hi',
80+
valueEnvironmentVariable = 'hi',
81+
timeout = 0,
82+
} = config
83+
try {
84+
const a = await waitForElement(webviewView, By.id('mynah-sheet-wrapper'))
85+
const b = await a.findElement(By.css('.mynah-sheet-body'))
86+
const c = await b.findElement(By.css('.mynah-detailed-list-filters-wrapper'))
87+
const d = await c.findElement(By.css('.mynah-chat-item-form-items-container'))
88+
const items = await d.findElements(By.css('.mynah-form-input-wrapper'))
89+
console.log('THERE ARE X ITEMS:', items.length) // returns 10 items
90+
for (let i = 0; i < items.length; i++) {
91+
switch (i) {
92+
// select the scope
93+
case 0:
94+
try {
95+
const scopeContainer = items[i]
96+
const a = await scopeContainer.findElements(
97+
By.css('.mynah-form-input-radio-label.mynah-ui-clickable-item')
98+
)
99+
if (scope === 'global') {
100+
const b = a[0]
101+
await b.click()
102+
} else {
103+
const b = a[1]
104+
await b.click()
105+
}
106+
} catch (e) {
107+
console.error('Error in case 0:', e)
108+
throw e
109+
}
110+
break
111+
// input the name
112+
case 1:
113+
try {
114+
const scopeContainer = items[i]
115+
const input = scopeContainer.findElement(By.css('.mynah-form-input'))
116+
await input.sendKeys(name)
117+
} catch (e) {
118+
console.error('Error in case 1:', e)
119+
throw e
120+
}
121+
break
122+
// select the transport (must know the index of your selection)
123+
case 2:
124+
try {
125+
const scopeContainer = items[i]
126+
const selectElement = await scopeContainer.findElement(By.css('select'))
127+
const options = await selectElement.findElements(By.css('option'))
128+
const optionIndex = transport
129+
await options[optionIndex].click()
130+
} catch (e) {
131+
console.error('Error in case 2:', e)
132+
throw e
133+
}
134+
break
135+
// type the command
136+
case 3:
137+
try {
138+
const scopeContainer = items[i]
139+
const input = scopeContainer.findElement(By.css('.mynah-form-input'))
140+
await input.sendKeys(command)
141+
} catch (e) {
142+
console.error('Error in case 3:', e)
143+
throw e
144+
}
145+
break
146+
// add arguments (NOTE: I AM PURPOSELY SKIPPING CASE 5)
147+
case 4:
148+
try {
149+
const scopeContainer = items[i]
150+
const input = scopeContainer.findElement(By.css('.mynah-form-input'))
151+
const addButton = scopeContainer.findElement(
152+
By.css(
153+
'.mynah-button.mynah-button-secondary.fill-state-always.mynah-form-item-list-row-remove-button.mynah-ui-clickable-item'
154+
)
155+
)
156+
for (let i = 0; i < args.length; i++) {
157+
await input.sendKeys(args[i])
158+
await addButton.click()
159+
}
160+
} catch (e) {
161+
console.error('Error in case 5:', e)
162+
throw e
163+
}
164+
break
165+
// THE ISSUE IS THAT CASE 5 ENCOMPASSES ALL THE HTML ELEMENTS NEEDED
166+
case 5:
167+
try {
168+
if (nameEnvironmentVariable && valueEnvironmentVariable) {
169+
const scopeContainer = items[i]
170+
171+
const nameContainer = items[6]
172+
const inputName = nameContainer.findElement(By.css('.mynah-form-input'))
173+
await inputName.sendKeys(nameEnvironmentVariable)
174+
175+
const valueContainer = items[7]
176+
const inputValue = valueContainer.findElement(By.css('.mynah-form-input'))
177+
await inputValue.sendKeys(valueEnvironmentVariable)
178+
const addButton = scopeContainer.findElement(
179+
By.css(
180+
'.mynah-button.mynah-button-secondary.fill-state-always.mynah-form-item-list-row-remove-button.mynah-ui-clickable-item'
181+
)
182+
)
183+
await addButton.click()
184+
}
185+
} catch (e) {
186+
console.error('Error in case 5:', e)
187+
throw e
188+
}
189+
break
190+
// this timeout container goes to the environment variable
191+
case 9:
192+
try {
193+
const scopeContainer = items[i]
194+
const input = scopeContainer.findElement(By.css('.mynah-form-input'))
195+
await input.sendKeys(timeout)
196+
} catch (e) {
197+
console.error('Error in case 8:', e)
198+
throw e
199+
}
200+
break
201+
}
202+
}
203+
return true
204+
} catch (e) {
205+
console.log('Error configuring the MCP Server')
206+
return false
207+
}
208+
}
209+
210+
export async function saveMCPServerConfiguration(webviewView: WebviewView): Promise<boolean> {
211+
try {
212+
const sheetWrapper = await waitForElement(webviewView, By.id('mynah-sheet-wrapper'))
213+
const body = await sheetWrapper.findElement(By.css('.mynah-sheet-body'))
214+
const filterActions = await body.findElement(By.css('.mynah-detailed-list-filter-actions-wrapper'))
215+
const saveButton = await filterActions.findElement(
216+
By.css('.mynah-button.fill-state-always.status-primary.mynah-ui-clickable-item')
40217
)
41-
console.log('THIS WORKS 3')
42-
sleep(5000)
43-
await menuListItems.click()
44-
console.log('THIS WORKS 4')
45-
sleep(5000)
46-
// for (const item of menuListItems) {
47-
// const icon = await item.findElement(By.css('i.mynah-ui-icon.mynah-ui-icon-cancel'))
48-
// console.log('THIS WORKS 4')
49-
// if (icon) {
50-
// await webviewView.getDriver().executeScript('arguments[0].click()', item)
51-
// sleep(5000)
52-
// return true
53-
// }
54-
// }
55-
// console.log('I DID NOT ACTUALLY CLICK THE CLOSE BUTTON')
218+
await saveButton.click()
56219
return true
57220
} catch (e) {
58-
console.error('Error closing the MCP overlay:', e)
221+
console.error('Error saving the MCP server configuration:', e)
59222
return false
60223
}
61224
}
62225

63-
export async function dismissOverlay(webviewView: WebviewView): Promise<void> {
64-
await webviewView.getDriver().executeScript('document.elementFromPoint(200, 200).click()')
226+
export async function cancelMCPServerConfiguration(webviewView: WebviewView): Promise<boolean> {
227+
try {
228+
const sheetWrapper = await waitForElement(webviewView, By.id('mynah-sheet-wrapper'))
229+
const body = await sheetWrapper.findElement(By.css('.mynah-sheet-body'))
230+
const filterActions = await body.findElement(By.css('.mynah-detailed-list-filter-actions-wrapper'))
231+
const saveButton = await filterActions.findElement(
232+
By.css('.mynah-button.mynah-button-secondary.mynah-button-border.fill-state-always.mynah-ui-clickable-item')
233+
)
234+
await saveButton.click()
235+
return true
236+
} catch (e) {
237+
console.error('Error saving the MCP server configuration:', e)
238+
return false
239+
}
240+
}
241+
242+
/**
243+
* Clicks the refresh button in the MCP server configuration panel
244+
* @param webviewView The WebviewView instance
245+
* @returns Promise<boolean> True if refresh button was found and clicked, false otherwise
246+
*/
247+
export async function clickMCPRefreshButton(webviewView: WebviewView): Promise<boolean> {
248+
try {
249+
const sheetWrapper = await waitForElement(webviewView, By.id('mynah-sheet-wrapper'))
250+
const header = await sheetWrapper.findElement(By.css('.mynah-sheet-header'))
251+
const actionsContainer = await header.findElement(By.css('.mynah-sheet-header-actions-container'))
252+
const refreshButton = await actionsContainer.findElement(By.css('button:has(i.mynah-ui-icon-refresh)'))
253+
await refreshButton.click()
254+
return true
255+
} catch (e) {
256+
console.error('Error clicking the MCP refresh button:', e)
257+
return false
258+
}
259+
}
260+
261+
/**
262+
* Clicks the close/cancel button in the MCP server configuration panel
263+
* @param webviewView The WebviewView instance
264+
* @returns Promise<boolean> True if close button was found and clicked, false otherwise
265+
*/
266+
export async function clickMCPCloseButton(webviewView: WebviewView): Promise<boolean> {
267+
try {
268+
const sheetWrapper = await waitForElement(webviewView, By.id('mynah-sheet-wrapper'))
269+
const header = await sheetWrapper.findElement(By.css('.mynah-sheet-header'))
270+
const cancelButton = await header.findElement(By.css('button:has(i.mynah-ui-icon-cancel)'))
271+
await webviewView.getDriver().executeScript('arguments[0].click()', cancelButton)
272+
return true
273+
} catch (e) {
274+
console.error('Error closing the MCP overlay:', e)
275+
return false
276+
}
65277
}

packages/amazonq/test/e2e_new/amazonq/tests/mcp.test.ts

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,14 @@
55
import '../utils/setup'
66
import { WebviewView } from 'vscode-extension-tester'
77
import { testContext } from '../utils/testContext'
8+
import {
9+
clickMCPAddButton,
10+
clickMCPCloseButton,
11+
clickToolsButton,
12+
configureMCPServer,
13+
saveMCPServerConfiguration,
14+
} from '../helpers/mcpHelper'
815
import { sleep } from '../utils/generalUtils'
9-
import { clickMCPCloseButton, clickToolsButton, dismissOverlay } from '../helpers/mcpHelper'
1016

1117
describe('Amazon Q MCP Functionality', function () {
1218
// this timeout is the general timeout for the entire test suite
@@ -21,20 +27,18 @@ describe('Amazon Q MCP Functionality', function () {
2127

2228
afterEach(async () => {})
2329

24-
it('MCP Server', async () => {
25-
/**
26-
* TO-DO
27-
* Write a command to click the button DONE
28-
*
29-
* Close MCP Server
30-
*/
30+
it('Test Amazon Q MCP Servers and Built-in Tools Access', async () => {
3131
await clickToolsButton(webviewView)
32-
console.log('TOOLS BUTTON CLICKED')
33-
await dismissOverlay(webviewView)
34-
console.log('DISMISS OVERLAY')
35-
sleep(5000)
3632
await clickMCPCloseButton(webviewView)
37-
console.log('CLOSE BUTTON CLICKED')
38-
await dismissOverlay(webviewView)
33+
})
34+
35+
it('Add new MCP Server', async () => {
36+
await clickToolsButton(webviewView)
37+
await clickMCPAddButton(webviewView)
38+
await configureMCPServer(webviewView)
39+
await sleep(5000)
40+
await saveMCPServerConfiguration(webviewView)
41+
await sleep(5000)
42+
await clickMCPCloseButton(webviewView)
3943
})
4044
})

0 commit comments

Comments
 (0)