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
78 changes: 31 additions & 47 deletions packages/amazonq/test/e2e_new/amazonq/helpers/mcpHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,21 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { WebviewView, By, WebElement } from 'vscode-extension-tester'
import { waitForElement } from '../utils/generalUtils'
import { clickButton, waitForElement } from '../utils/generalUtils'
import { dismissOverlayIfPresent } from '../utils/cleanupUtils'

/**
* Clicks the tools to get to the MCP server overlay
* @param webviewView The WebviewView instance
* @returns Promise<boolean> True if tools button was found and clicked, false otherwise
*/
export async function clickToolsButton(webviewView: WebviewView): Promise<void> {
try {
const navWrapper = await waitForElement(webviewView, By.css('.mynah-nav-tabs-wrapper.mynah-ui-clickable-item'))
const buttonsWrapper = await navWrapper.findElement(By.css('.mynah-nav-tabs-bar-buttons-wrapper'))
const buttons = await buttonsWrapper.findElements(
By.css('.mynah-button.mynah-button-secondary.fill-state-always.mynah-ui-clickable-item')
)
for (const button of buttons) {
const icon = await button.findElement(By.css('i.mynah-ui-icon.mynah-ui-icon-tools'))
if (icon) {
await button.click()
await webviewView.getDriver().actions().move({ x: 0, y: 0 }).perform()
}
}
console.log('Tools button not found')
} catch (e) {
console.error('Error clicking tools button:', e)
}
await clickButton(
webviewView,
'[data-testid="tab-bar-buttons-wrapper"]',
'[data-testid="tab-bar-button"] .mynah-ui-icon-tools',
'tools button'
)
}

/**
Expand All @@ -36,15 +26,7 @@ export async function clickToolsButton(webviewView: WebviewView): Promise<void>
* @returns Promise<boolean> True if add button was found and clicked, false otherwise
*/
export async function clickMCPAddButton(webviewView: WebviewView): Promise<void> {
try {
const sheetWrapper = await waitForElement(webviewView, By.id('mynah-sheet-wrapper'))
const header = await sheetWrapper.findElement(By.css('.mynah-sheet-header'))
const actionsContainer = await header.findElement(By.css('.mynah-sheet-header-actions-container'))
const addButton = await actionsContainer.findElement(By.css('button:has(i.mynah-ui-icon-plus)'))
await addButton.click()
} catch (e) {
console.error('Error clicking the MCP add button:', e)
}
await clickButton(webviewView, '.mynah-sheet-header-actions-container', 'i.mynah-ui-icon-plus', 'MCP add button')
}

/**
Expand Down Expand Up @@ -225,17 +207,12 @@ export async function configureMCPServer(webviewView: WebviewView, config: MCPSe
}

export async function saveMCPServerConfiguration(webviewView: WebviewView): Promise<void> {
try {
const sheetWrapper = await waitForElement(webviewView, By.id('mynah-sheet-wrapper'))
const body = await sheetWrapper.findElement(By.css('.mynah-sheet-body'))
const filterActions = await body.findElement(By.css('.mynah-detailed-list-filter-actions-wrapper'))
const saveButton = await filterActions.findElement(
By.css('.mynah-button.fill-state-always.status-primary.mynah-ui-clickable-item')
)
await saveButton.click()
} catch (e) {
console.error('Error saving the MCP server configuration:', e)
}
await clickButton(
webviewView,
'[data-testid="chat-item-action-button"][action-id="save-mcp"]',
'span.mynah-button-label',
'save button'
)
}

export async function cancelMCPServerConfiguration(webviewView: WebviewView): Promise<void> {
Expand All @@ -259,13 +236,21 @@ export async function cancelMCPServerConfiguration(webviewView: WebviewView): Pr
*/
export async function clickMCPRefreshButton(webviewView: WebviewView): Promise<void> {
try {
const sheetWrapper = await waitForElement(webviewView, By.id('mynah-sheet-wrapper'))
const header = await sheetWrapper.findElement(By.css('.mynah-sheet-header'))
const actionsContainer = await header.findElement(By.css('.mynah-sheet-header-actions-container'))
const refreshButton = await actionsContainer.findElement(By.css('button:has(i.mynah-ui-icon-refresh)'))
await refreshButton.click()
// First dismiss any overlay that might be present
await dismissOverlayIfPresent(webviewView)

await clickButton(
webviewView,
'.mynah-sheet-header-actions-container',
'i.mynah-ui-icon-refresh',
'MCP refresh button'
)

// Dismiss any overlay that might appear after clicking
await dismissOverlayIfPresent(webviewView)
} catch (e) {
console.error('Error clicking the MCP refresh button:', e)
throw e
}
}

Expand All @@ -276,11 +261,10 @@ export async function clickMCPRefreshButton(webviewView: WebviewView): Promise<v
*/
export async function clickMCPCloseButton(webviewView: WebviewView): Promise<void> {
try {
const sheetWrapper = await waitForElement(webviewView, By.id('mynah-sheet-wrapper'))
const header = await sheetWrapper.findElement(By.css('.mynah-sheet-header'))
const cancelButton = await header.findElement(By.css('button:has(i.mynah-ui-icon-cancel)'))
await webviewView.getDriver().executeScript('arguments[0].click()', cancelButton)
await dismissOverlayIfPresent(webviewView)
await clickButton(webviewView, '.mynah-sheet-header', 'i.mynah-ui-icon-cancel', 'MCP close button')
} catch (e) {
console.error('Error closing the MCP overlay:', e)
throw e
}
}
2 changes: 1 addition & 1 deletion packages/amazonq/test/e2e_new/amazonq/tests/mcp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import {
clickMCPAddButton,
clickMCPCloseButton,
clickMCPRefreshButton,
clickToolsButton,
configureMCPServer,
saveMCPServerConfiguration,
clickToolsButton,
} from '../helpers/mcpHelper'
import { closeAllTabs } from '../utils/cleanupUtils'

Expand Down
48 changes: 48 additions & 0 deletions packages/amazonq/test/e2e_new/amazonq/utils/generalUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,54 @@ export async function waitForElements(webview: WebviewView, locator: By, timeout
return await webview.findWebElements(locator)
}

/**
* Robust button clicking function that locates a button by its wrapper and content, then clicks it
* @param webviewView The WebviewView instance
* @param buttonWrapperSelector CSS selector for the button's wrapper element
* @param buttonContentSelector CSS selector for the content inside the button (icon, text, etc.)
* @param buttonName Descriptive name for the button (used in error messages)
* @param timeout Timeout in milliseconds (defaults to 5000)
* @returns Promise<void>
*/
export async function clickButton(
webviewView: WebviewView,
buttonWrapperSelector: string,
buttonContentSelector: string,
buttonName: string = 'button',
timeout: number = 5000

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wondering but is this timeout just arbitrary or needed for waiting for the css?

): Promise<void> {
try {
const buttonWrapper = await webviewView
.getDriver()
.wait(until.elementLocated(By.css(buttonWrapperSelector)), timeout, `${buttonName} wrapper not found`)

await webviewView
.getDriver()
.wait(until.elementIsVisible(buttonWrapper), timeout, `${buttonName} wrapper not visible`)

const buttonContent = await webviewView
.getDriver()
.wait(until.elementLocated(By.css(buttonContentSelector)), timeout, `${buttonName} content not found`)

const button = await buttonContent.findElement(By.xpath('./..'))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this xpath do? A little curious since it seems new

await webviewView.getDriver().wait(until.elementIsEnabled(button), timeout, `${buttonName} not clickable`)
await button.click()
await webviewView.getDriver().sleep(300)
} catch (e) {
console.error(`Failed to click ${buttonName}:`, {
error: e,
timestamp: new Date().toISOString(),
})
try {
const screenshot = await webviewView.getDriver().takeScreenshot()
console.log(`Screenshot taken at time of ${buttonName} failure`, screenshot)
} catch (screenshotError) {
console.error('Failed to take error screenshot:', screenshotError)
}
throw new Error(`Failed to click ${buttonName}: ${e}`)
}
}

/**
* Presses a single key globally
* @param driver The WebDriver instance
Expand Down
Loading