Skip to content

Commit 9db3a91

Browse files
authored
fix(amazonq): Reducing error propagation flakiness in UI E2E Tests in utils and Helpers (#7882)
## Problem Our tests are currently experiencing flakiness due to our usage of try-catch blocks which allow errors to pass through our tests. One of our more egregious examples of this is our `quickActions.test.ts` file which is unable to find the proper CSS element of our quickAction command overlay, but still allows our test to pass as this error is non-critical due to our try-catch block. This leaves an inherent problem in our tests making them flaky while still containing UI errors. Additionally, many of our helper functions return Booleans, and this logic of booleans is used in some instances like our `chat.test.ts`. ## Solution We will remove the majority of try-catch blocks that are used in our helpers to have more stricter errors in our tests. Our tests should fail if an error occurs while finding a UI element. Additionally, we update our `chat.test.ts` so that it can properly stop using the Boolean returned from the chat helper function. We have updated: - generalUtils - cleanupUtils - chat.test.ts - quickActionsHelper - switchModelHelper --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent a9a8b27 commit 9db3a91

File tree

5 files changed

+77
-139
lines changed

5 files changed

+77
-139
lines changed

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

Lines changed: 23 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -13,62 +13,39 @@ import { sleep, waitForElements } from '../utils/generalUtils'
1313
* @returns Promise<{items: WebElement[], texts: string[]}> Array of menu items and their text labels
1414
*/
1515
export async function getQuickActionsCommands(webview: WebviewView): Promise<{ items: WebElement[]; texts: string[] }> {
16-
try {
17-
await writeToChat('/', webview, false)
18-
await sleep(2000)
16+
await writeToChat('/', webview, false)
17+
await sleep(2000)
1918

20-
const menuItems = await waitForElements(
21-
webview,
22-
By.css('.mynah-detailed-list-item.mynah-ui-clickable-item.target-command'),
23-
10000
24-
)
19+
const menuItems = await waitForElements(
20+
webview,
21+
By.css('.mynah-detailed-list-item.mynah-ui-clickable-item.target-command'),
22+
10000
23+
)
2524

26-
const menuTexts = []
27-
for (let i = 0; i < menuItems.length; i++) {
28-
try {
29-
const text = await menuItems[i].getText()
30-
menuTexts.push(text)
31-
console.log(`Command ${i + 1}: ${text}`)
32-
} catch (e) {
33-
menuTexts.push('')
34-
console.log(`Could not get text for command ${i + 1}`)
35-
}
36-
}
37-
38-
console.log(`Found ${menuItems.length} quick action command items`)
39-
return { items: menuItems, texts: menuTexts }
40-
} catch (e) {
41-
console.error('Error getting quick action commands:', e)
42-
return { items: [], texts: [] }
25+
const menuTexts = []
26+
for (let i = 0; i < menuItems.length; i++) {
27+
const text = await menuItems[i].getText()
28+
menuTexts.push(text)
4329
}
30+
31+
return { items: menuItems, texts: menuTexts }
4432
}
4533

4634
/**
4735
* Clicks a specific quick action command by name
4836
* @param webview The WebviewView instance
4937
* @param commandName The name of the command to click
50-
* @returns Promise<boolean> True if command was found and clicked, false otherwise
5138
*/
52-
export async function clickQuickActionsCommand(webview: WebviewView, commandName: string): Promise<boolean> {
53-
try {
54-
const { items, texts } = await getQuickActionsCommands(webview)
55-
if (items.length === 0) {
56-
console.log('No quick action commands found to click')
57-
return false
58-
}
59-
const indexToClick = texts.findIndex((text) => text === commandName)
39+
export async function clickQuickActionsCommand(webview: WebviewView, commandName: string): Promise<void> {
40+
const { items, texts } = await getQuickActionsCommands(webview)
41+
if (items.length === 0) {
42+
throw new Error('No quick action commands found')
43+
}
44+
const indexToClick = texts.findIndex((text) => text === commandName)
6045

61-
if (indexToClick === -1) {
62-
console.log(`Command "${commandName}" not found`)
63-
return false
64-
}
65-
console.log(`Clicking on command: ${commandName}`)
66-
await items[indexToClick].click()
67-
await sleep(3000)
68-
console.log('Command clicked successfully')
69-
return true
70-
} catch (e) {
71-
console.error('Error clicking quick action command:', e)
72-
return false
46+
if (indexToClick === -1) {
47+
throw new Error(`Command "${commandName}" not found`)
7348
}
49+
await items[indexToClick].click()
50+
await sleep(3000)
7451
}

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

Lines changed: 17 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,10 @@ import { waitForElement } from '../utils/generalUtils'
1010
* @param webviewView The WebviewView instance
1111
*/
1212
export async function listModels(webviewView: WebviewView): Promise<void> {
13-
try {
14-
const selectElement = await waitForElement(webviewView, By.css('.mynah-form-input.auto-width'))
15-
const options = await selectElement.findElements(By.css('option'))
16-
const optionTexts = await Promise.all(options.map(async (option) => await option.getText()))
17-
18-
console.log('Available model options:', optionTexts)
19-
} catch (e) {
20-
console.error('Error listing model options:', e)
21-
throw e
22-
}
13+
const selectElement = await waitForElement(webviewView, By.css('.mynah-form-input.auto-width'))
14+
const options = await selectElement.findElements(By.css('option'))
15+
const optionTexts = await Promise.all(options.map(async (option) => await option.getText()))
16+
console.log('Available model options:', optionTexts)
2317
}
2418

2519
/**
@@ -28,25 +22,19 @@ export async function listModels(webviewView: WebviewView): Promise<void> {
2822
* @param modelName The exact name of the model to select
2923
*/
3024
export async function selectModel(webviewView: WebviewView, modelName: string): Promise<void> {
31-
try {
32-
const selectElement = await waitForElement(webviewView, By.css('.mynah-form-input.auto-width'))
33-
await selectElement.click()
34-
const options = await selectElement.findElements(By.css('option'))
35-
let targetOption: WebElement | undefined
36-
for (const option of options) {
37-
const optionText = await option.getText()
38-
if (optionText === modelName) {
39-
targetOption = option
40-
break
41-
}
25+
const selectElement = await waitForElement(webviewView, By.css('.mynah-form-input.auto-width'))
26+
await selectElement.click()
27+
const options = await selectElement.findElements(By.css('option'))
28+
let targetOption: WebElement | undefined
29+
for (const option of options) {
30+
const optionText = await option.getText()
31+
if (optionText === modelName) {
32+
targetOption = option
33+
break
4234
}
43-
if (!targetOption) {
44-
throw new Error(`Model option "${modelName}" not found`)
45-
}
46-
await targetOption.click()
47-
console.log(`Selected model option: ${modelName}`)
48-
} catch (e) {
49-
console.error('Error selecting model option:', e)
50-
throw e
5135
}
36+
if (!targetOption) {
37+
throw new Error(`Model option "${modelName}" not found`)
38+
}
39+
await targetOption.click()
5240
}

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

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,7 @@ describe('Amazon Q Chat Basic Functionality', function () {
2323

2424
it('Allows User to Chat with AmazonQ', async () => {
2525
await writeToChat('Hello, Amazon Q!', webviewView)
26-
const responseReceived = await waitForChatResponse(webviewView)
27-
if (!responseReceived) {
28-
throw new Error('Chat response not received within timeout')
29-
}
30-
console.log('Chat response detected successfully')
26+
await waitForChatResponse(webviewView)
3127
})
3228
it('Allows User to Add Multiple Chat Tabs', async () => {
3329
console.log('Starting Multiple Chat Test')

packages/amazonq/test/e2e_new/amazonq/utils/cleanupUtils.ts

Lines changed: 22 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8,55 +8,41 @@ import { sleep } from './generalUtils'
88
/**
99
* Closes all open chat tabs
1010
* @param webview The WebviewView instance
11-
* @returns Promise<boolean> True if all tabs were successfully closed
1211
* @throws Error if tabs could not be closed
1312
*/
14-
export async function closeAllTabs(webview: WebviewView): Promise<boolean> {
15-
try {
16-
const closeButtons = await webview.findWebElements(By.css('.mynah-tabs-close-button'))
13+
export async function closeAllTabs(webview: WebviewView): Promise<void> {
14+
const closeButtons = await webview.findWebElements(By.css('.mynah-tabs-close-button'))
1715

18-
for (const button of closeButtons) {
19-
await button.click()
20-
await sleep(500)
21-
}
16+
for (const button of closeButtons) {
17+
await button.click()
18+
await sleep(500)
19+
}
2220

23-
const tabsContainer = await webview.findWebElements(By.css('.mynah-tabs-container'))
24-
const allClosed =
25-
tabsContainer.length === 1 ||
26-
(await tabsContainer[0].findElements(By.css('.mynah-tab-item-label'))).length === 0
21+
const tabsContainer = await webview.findWebElements(By.css('.mynah-tabs-container'))
22+
const allClosed =
23+
tabsContainer.length === 1 ||
24+
(await tabsContainer[0].findElements(By.css('.mynah-tab-item-label'))).length === 0
2725

28-
if (allClosed) {
29-
console.log('All chat tabs successfully closed')
30-
return true
31-
} else {
32-
throw new Error('Failed to close all tabs')
33-
}
34-
} catch (error) {
35-
console.error('Error closing tabs:', error)
36-
throw error
26+
if (!allClosed) {
27+
throw new Error('Failed to close all tabs')
3728
}
3829
}
3930

4031
/**
4132
* Attempts to dismiss any open overlays
4233
* @param webview The WebviewView instance
43-
* @returns Promise<boolean> True if overlay was dismissed or none was present, false if dismissal failed
34+
* @throws Error if overlay dismissal failed
4435
*/
45-
export async function dismissOverlayIfPresent(webview: WebviewView): Promise<boolean> {
46-
try {
47-
const overlays = await webview.findWebElements(By.css('.mynah-overlay.mynah-overlay-open'))
48-
if (overlays.length > 0) {
49-
console.log('Overlay detected, attempting to dismiss...')
50-
const driver = webview.getDriver()
51-
await driver.executeScript('document.body.click()')
36+
export async function dismissOverlayIfPresent(webview: WebviewView): Promise<void> {
37+
const overlays = await webview.findWebElements(By.css('.mynah-overlay.mynah-overlay-open'))
38+
if (overlays.length > 0) {
39+
const driver = webview.getDriver()
40+
await driver.executeScript('document.body.click()')
5241

53-
await sleep(1000)
54-
const overlaysAfter = await webview.findWebElements(By.css('.mynah-overlay.mynah-overlay-open'))
55-
return overlaysAfter.length === 0
42+
await sleep(1000)
43+
const overlaysAfter = await webview.findWebElements(By.css('.mynah-overlay.mynah-overlay-open'))
44+
if (overlaysAfter.length > 0) {
45+
throw new Error('Failed to dismiss overlay')
5646
}
57-
return true
58-
} catch (e) {
59-
console.log('Error while trying to dismiss overlay:', e)
60-
return false
6147
}
6248
}

packages/amazonq/test/e2e_new/amazonq/utils/generalUtils.ts

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -132,25 +132,23 @@ export async function pressShortcut(driver: WebDriver, ...keys: (string | keyof
132132
* @param prompt The text to write in the chat input
133133
* @param webview The WebviewView instance
134134
* @param send Whether to click the send button (defaults to true)
135-
* @returns Promise<boolean> True if successful
136135
*/
137-
export async function writeToChat(prompt: string, webview: WebviewView, send = true): Promise<boolean> {
136+
export async function writeToChat(prompt: string, webview: WebviewView, send = true): Promise<void> {
138137
const chatInput = await waitForElement(webview, By.css('.mynah-chat-prompt-input'))
139138
await chatInput.sendKeys(prompt)
140-
if (send === true) {
139+
if (send) {
141140
const sendButton = await waitForElement(webview, By.css('.mynah-chat-prompt-button'))
142141
await sendButton.click()
143142
}
144-
return true
145143
}
146144

147145
/**
148146
* Waits for a chat response to be generated
149147
* @param webview The WebviewView instance
150148
* @param timeout The timeout in milliseconds
151-
* @returns Promise<boolean> True if a response was detected, false if timeout occurred
149+
* @throws Error if timeout occurs before response is detected
152150
*/
153-
export async function waitForChatResponse(webview: WebviewView, timeout = 8000): Promise<boolean> {
151+
export async function waitForChatResponse(webview: WebviewView, timeout = 15000): Promise<void> {
154152
const startTime = Date.now()
155153

156154
while (Date.now() - startTime < timeout) {
@@ -162,34 +160,27 @@ export async function waitForChatResponse(webview: WebviewView, timeout = 8000):
162160
const chatItems = await latestContainer.findElements(By.css('*'))
163161

164162
if (chatItems.length >= 2) {
165-
return true
163+
return
166164
}
167165
}
168166
await sleep(500)
169167
}
170168

171-
return false
169+
throw new Error('Timeout waiting for chat response')
172170
}
173171

174172
/**
175173
* Clears the text in the chat input field
176174
* @param webview The WebviewView instance
177-
* @returns Promise<boolean> True if successful, false if an error occurred
178175
*/
179-
export async function clearChatInput(webview: WebviewView): Promise<boolean> {
180-
try {
181-
const chatInput = await waitForElement(webview, By.css('.mynah-chat-prompt-input'))
182-
await chatInput.sendKeys(
183-
process.platform === 'darwin'
184-
? '\uE03D\u0061' // Command+A on macOS
185-
: '\uE009\u0061' // Ctrl+A on Windows/Linux
186-
)
187-
await chatInput.sendKeys('\uE003') // Backspace
188-
return true
189-
} catch (e) {
190-
console.error('Error clearing chat input:', e)
191-
return false
192-
}
176+
export async function clearChatInput(webview: WebviewView): Promise<void> {
177+
const chatInput = await waitForElement(webview, By.css('.mynah-chat-prompt-input'))
178+
await chatInput.sendKeys(
179+
process.platform === 'darwin'
180+
? '\uE03D\u0061' // Command+A on macOS
181+
: '\uE009\u0061' // Ctrl+A on Windows/Linux
182+
)
183+
await chatInput.sendKeys('\uE003') // Backspace
193184
}
194185

195186
/**

0 commit comments

Comments
 (0)