Skip to content

Commit 60fc14c

Browse files
committed
finish implementing mcp abstractions + 3 tests
1 parent 83a78dd commit 60fc14c

File tree

2 files changed

+170
-134
lines changed

2 files changed

+170
-134
lines changed

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

Lines changed: 159 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
33
* SPDX-License-Identifier: Apache-2.0
44
*/
5-
import { WebviewView, By } from 'vscode-extension-tester'
5+
import { WebviewView, By, WebElement } from 'vscode-extension-tester'
66
import { waitForElement } from '../utils/generalUtils'
77

88
/**
@@ -57,7 +57,7 @@ export async function clickMCPAddButton(webviewView: WebviewView): Promise<boole
5757
* @param webviewView The WebviewView instance
5858
* @param config Configuration object with optional parameters
5959
* @returns Promise<boolean> True if configuration was successful, false otherwise
60-
* Note: I have the default settings in the config variable
60+
* Note: I have the default settings in the defaultConfig
6161
*/
6262
interface MCPServerConfig {
6363
scope?: 'global' | 'workspace'
@@ -69,135 +69,166 @@ interface MCPServerConfig {
6969
valueEnvironmentVariable?: string
7070
timeout?: number
7171
}
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
72+
73+
const defaultConfig: MCPServerConfig = {
74+
scope: 'global',
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+
}
83+
84+
const formItemsMap = {
85+
SCOPE: 0,
86+
NAME: 1,
87+
TRANSPORT: 2,
88+
COMMAND: 3,
89+
ARGS: 4,
90+
ENV_VARS: 6,
91+
TIMEOUT: 9,
92+
} as const
93+
94+
type McpFormItem = keyof typeof formItemsMap
95+
96+
async function selectScope(container: WebElement, scope: string) {
97+
try {
98+
const a = await container.findElements(By.css('.mynah-form-input-radio-label.mynah-ui-clickable-item'))
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 selecting the scope:', e)
108+
throw e
109+
}
110+
}
111+
112+
async function inputName(container: WebElement, name: string) {
113+
try {
114+
const input = container.findElement(By.css('.mynah-form-input'))
115+
await input.sendKeys(name)
116+
} catch (e) {
117+
console.error('Error inputing the name:', e)
118+
throw e
119+
}
120+
}
121+
122+
async function selectTransport(container: WebElement, transport: number) {
123+
try {
124+
const selectElement = await container.findElement(By.css('select'))
125+
const options = await selectElement.findElements(By.css('option'))
126+
const optionIndex = transport
127+
await options[optionIndex].click()
128+
} catch (e) {
129+
console.error('Error selecting the transport:', e)
130+
throw e
131+
}
132+
}
133+
134+
async function inputCommand(container: WebElement, command: string) {
135+
try {
136+
const input = container.findElement(By.css('.mynah-form-input'))
137+
await input.sendKeys(command)
138+
} catch (e) {
139+
console.error('Error inputing the command:', e)
140+
throw e
141+
}
142+
}
143+
144+
async function inputArgs(container: WebElement, args: string[]) {
83145
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]
146+
const input = container.findElement(By.css('.mynah-form-input'))
147+
const addButton = container.findElement(
148+
By.css(
149+
'.mynah-button.mynah-button-secondary.fill-state-always.mynah-form-item-list-row-remove-button.mynah-ui-clickable-item'
150+
)
151+
)
152+
for (let i = 0; i < args.length; i++) {
153+
await input.sendKeys(args[i])
154+
await addButton.click()
155+
}
156+
} catch (e) {
157+
console.error('Error inputing the arguments:', e)
158+
throw e
159+
}
160+
}
170161

171-
const nameContainer = items[6]
172-
const inputName = nameContainer.findElement(By.css('.mynah-form-input'))
173-
await inputName.sendKeys(nameEnvironmentVariable)
162+
async function inputEnvironmentVariables(
163+
container: WebElement,
164+
nameEnvironmentVariable?: string,
165+
valueEnvironmentVariable?: string
166+
) {
167+
try {
168+
if (nameEnvironmentVariable && valueEnvironmentVariable) {
169+
const a = await container.findElements(By.css('.mynah-form-input'))
170+
await a[0].sendKeys(nameEnvironmentVariable)
171+
await a[1].sendKeys(valueEnvironmentVariable)
172+
const addButton = await container.findElement(By.css('.mynah-form-item-list-add-button'))
173+
await addButton.click()
174+
} else {
175+
console.log('No environmental variables for this configuration')
176+
}
177+
} catch (e) {
178+
console.error('Error inputing the environment variables:', e)
179+
throw e
180+
}
181+
}
182+
183+
async function inputTimeout(container: WebElement, timeout: number) {
184+
try {
185+
const input = container.findElement(By.css('.mynah-form-input'))
186+
await input.clear()
187+
await input.sendKeys(timeout)
188+
} catch (e) {
189+
console.error('Error inputing the timeout:', e)
190+
throw e
191+
}
192+
}
193+
194+
async function processFormItems(mcpFormItem: McpFormItem, container: WebElement, config: MCPServerConfig) {
195+
switch (mcpFormItem) {
196+
case 'SCOPE':
197+
await selectScope(container, config.scope!)
198+
break
199+
case 'NAME':
200+
await inputName(container, config.name!)
201+
break
202+
case 'TRANSPORT':
203+
await selectTransport(container, config.transport!)
204+
break
205+
case 'COMMAND':
206+
await inputCommand(container, config.command!)
207+
break
208+
case 'ARGS':
209+
await inputArgs(container, config.args!)
210+
break
211+
case 'ENV_VARS':
212+
await inputEnvironmentVariables(container, config.nameEnvironmentVariable, config.valueEnvironmentVariable)
213+
break
214+
case 'TIMEOUT':
215+
await inputTimeout(container, config.timeout!)
216+
break
217+
}
218+
}
219+
220+
export async function configureMCPServer(webviewView: WebviewView, config: MCPServerConfig = {}): Promise<boolean> {
221+
const mergedConfig = { ...defaultConfig, ...config }
222+
try {
223+
const sheetWrapper = await waitForElement(webviewView, By.id('mynah-sheet-wrapper'))
224+
const sheetBody = await sheetWrapper.findElement(By.css('.mynah-sheet-body'))
225+
const filtersWrapper = await sheetBody.findElement(By.css('.mynah-detailed-list-filters-wrapper'))
226+
const formContainer = await filtersWrapper.findElement(By.css('.mynah-chat-item-form-items-container'))
227+
const items = await formContainer.findElements(By.css('.mynah-form-input-wrapper'))
174228

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
229+
for (const [formItem, index] of Object.entries(formItemsMap)) {
230+
if (index < items.length) {
231+
await processFormItems(formItem as McpFormItem, items[index], mergedConfig)
201232
}
202233
}
203234
return true

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

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ import { testContext } from '../utils/testContext'
88
import {
99
clickMCPAddButton,
1010
clickMCPCloseButton,
11+
clickMCPRefreshButton,
1112
clickToolsButton,
1213
configureMCPServer,
1314
saveMCPServerConfiguration,
1415
} from '../helpers/mcpHelper'
15-
import { sleep } from '../utils/generalUtils'
16+
import { closeAllTabs } from '../utils/cleanupUtils'
1617

1718
describe('Amazon Q MCP Functionality', function () {
1819
// this timeout is the general timeout for the entire test suite
@@ -23,9 +24,9 @@ describe('Amazon Q MCP Functionality', function () {
2324
webviewView = testContext.webviewView
2425
})
2526

26-
after(async function () {})
27-
28-
afterEach(async () => {})
27+
after(async function () {
28+
await closeAllTabs(webviewView)
29+
})
2930

3031
it('Test Amazon Q MCP Servers and Built-in Tools Access', async () => {
3132
await clickToolsButton(webviewView)
@@ -36,9 +37,13 @@ describe('Amazon Q MCP Functionality', function () {
3637
await clickToolsButton(webviewView)
3738
await clickMCPAddButton(webviewView)
3839
await configureMCPServer(webviewView)
39-
await sleep(5000)
4040
await saveMCPServerConfiguration(webviewView)
41-
await sleep(5000)
41+
await clickMCPCloseButton(webviewView)
42+
})
43+
44+
it('Refresh MCP Server', async () => {
45+
await clickToolsButton(webviewView)
46+
await clickMCPRefreshButton(webviewView)
4247
await clickMCPCloseButton(webviewView)
4348
})
4449
})

0 commit comments

Comments
 (0)