Skip to content
Closed
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
98688bf
initial VET commit with baseline setup + dependencies
laura-codess Jul 2, 2025
5959a38
deleting unnecessary dependencies and refactoring some of VET.test.ts
laura-codess Jul 2, 2025
4540f16
added back the buffer dependency because it failed the tests without it
laura-codess Jul 3, 2025
f1afe95
it was not any of the dependencies that was causing it to break, it w…
laura-codess Jul 3, 2025
4fa90ec
added back some more node: protocol imports to see if it passes all t…
laura-codess Jul 3, 2025
e1c33d8
forgot to save a webpack change. hoping it will fix the one failing t…
laura-codess Jul 3, 2025
ea46464
added back basically all the dependencies because 1 test keeps failing
laura-codess Jul 3, 2025
66f198d
Adding a simple test to send a chat prompt and check if theres a resp…
laura-codess Jul 11, 2025
dd62e27
Adding the wait to improve the robustness
laura-codess Jul 11, 2025
866478a
added logic to the after function to close all the chat tabs after th…
laura-codess Jul 11, 2025
35c656f
fixed the merge conflicts
laura-codess Jul 14, 2025
ed3c220
tested the timeouts, implemented a general wait function
laura-codess Jul 14, 2025
824635e
Merge branch 'feature/ui-e2e-tests' of https://github.com/aws/aws-too…
laura-codess Jul 15, 2025
b30f7f4
checked out master version
laura-codess Jul 15, 2025
5da6336
fix: ignore scripts change
laura-codess Jul 15, 2025
c8ccbae
implementing the initial setup for the framework
laura-codess Jul 16, 2025
74011d2
added setup.ts and testContext so that auth can be shared across tests
laura-codess Jul 17, 2025
a4d6b32
Removed VET.test.ts
laura-codess Jul 17, 2025
d2bc9dd
small . fix
laura-codess Jul 17, 2025
693404d
fixed the PR based on the comments
laura-codess Jul 17, 2025
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
73 changes: 0 additions & 73 deletions packages/amazonq/test/e2e/amazonq/VET.test.ts

This file was deleted.

32 changes: 32 additions & 0 deletions packages/amazonq/test/e2e_new/amazonq/chat.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*!
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
import './utils/setup'
import { WebviewView } from 'vscode-extension-tester'
import { closeAllTabs } from './framework/cleanupHelper'
import { testContext } from './utils/testContext'
import { waitForChatResponse, writeToChat } from './framework/chatHelper'

describe('Amazon Q Chat Basic Functionality', function () {
// this timeout is the general timeout for the entire test suite
this.timeout(150000)
let webviewView: WebviewView

before(async function () {
webviewView = testContext.webviewView!
Copy link
Contributor

Choose a reason for hiding this comment

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

why do we need to assert defined here?

Copy link
Author

Choose a reason for hiding this comment

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

sorry, Im a bit confused on what you mean by assert defined?

the testContext is used to keep track of the state that we're in. So we need to grab the webview from testContext because that testContext.webview can be updated depending on what webview we're looking at.

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

yes, thanks for the link!

})

afterEach(async () => {
await closeAllTabs(webviewView)
})

it('Chat Prompt Test', async () => {
await writeToChat('Hello, Amazon Q!', webviewView)
const responseReceived = await waitForChatResponse(webviewView)
if (!responseReceived) {
throw new Error('Chat response not received within timeout')
}
console.log('Chat response detected successfully')
})
})
50 changes: 50 additions & 0 deletions packages/amazonq/test/e2e_new/amazonq/framework/chatHelper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*!
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
import { By, WebviewView } from 'vscode-extension-tester'
import { waitForElement } from './generalHelper'

/* Writes a prompt to the chat input and waits for a response
Logic:
Finds the chat input element using the .mynah-chat-prompt-input CSS selector,
sends the provided prompt test, clicks the send button, and waits for a chat
response. Returns true if successful, throws an error if the response times out */

export async function writeToChat(prompt: string, webview: WebviewView): Promise<boolean> {
const chatInput = await waitForElement(webview, By.css('.mynah-chat-prompt-input'))
await chatInput.sendKeys(prompt)
const sendButton = await waitForElement(webview, By.css('.mynah-chat-prompt-button'))
await sendButton.click()
return true
}

/* Waits for a chat response and outputs whether the response is "correct"
Copy link
Contributor

Choose a reason for hiding this comment

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

what is 'correct'? Based on the implementation it looks like its checking for any response.

Copy link
Author

Choose a reason for hiding this comment

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

yes, we are just checking for any response. Since I cant actually read the content of the response, i just check if a response exists. is there a different way you recommend doing this?

Logic:
The overall conversation container's css is .mynah-chat-items-conversation-container.
Within that container we can check how many elements exist. If there is 2 elements,
we can assume that the chat response has been generated. However, we must grab the
latest conversation container, as there can be multiple conversations in the webview. */

export async function waitForChatResponse(webview: WebviewView, timeout = 15000): Promise<boolean> {
const startTime = Date.now()

while (Date.now() - startTime < timeout) {
const conversationContainers = await webview.findWebElements(By.css('.mynah-chat-items-conversation-container'))

if (conversationContainers.length > 0) {
const latestContainer = conversationContainers[conversationContainers.length - 1]

const chatItems = await latestContainer.findElements(By.css('*'))

if (chatItems.length >= 2) {
return true
}
}
await new Promise((resolve) => setTimeout(resolve, 500))
}

return false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*!
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
import { By, WebviewView } from 'vscode-extension-tester'

/* Finds all the tabs by looking for the close buttons and then closes them one by one.
Logic:
There is a button with the css mynah-tabs-close-button, we need to click that button and
close all the tabs after the test is done to avoid memory from a previous test. To double
check if all the tabs are closed, we can check if the mynah-tabs-container is empty. */

export async function closeAllTabs(webview: WebviewView) {
try {
const closeButtons = await webview.findWebElements(By.css('.mynah-tabs-close-button'))

for (const button of closeButtons) {
await button.click()
await new Promise((resolve) => setTimeout(resolve, 500))
}

// double check that all tabs are closed by checking if the mynah-tabs-container is empty
const tabsContainer = await webview.findWebElements(By.css('.mynah-tabs-container'))
if (
tabsContainer.length === 0 ||
(await tabsContainer[0].findElements(By.css('.mynah-tab-item-label'))).length === 0
) {
console.log('All chat tabs successfully closed')
}
} catch (error) {
console.log('Error closing tabs:', error)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*!
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
import { By, WebviewView, WebElement } from 'vscode-extension-tester'
import { until } from 'selenium-webdriver'

/* Waits for an element (or multiple elements) to appear based on the parameters
Logic:
The function utilizes the Selenium wait driver. We can call that driver from our
WebviewView but it can also be called on parts of the VSCode Editor that are not
part of the WebviewView.
(TO-DO: create a more general function that can be called on any part of the VSCode
Editor. Will do when a use case appears for it.*/

export async function waitForElement(
webview: WebviewView,
locator: By,
multiple: true,
timeout?: number
): Promise<WebElement[]>
export async function waitForElement(
webview: WebviewView,
locator: By,
multiple?: false,
timeout?: number
): Promise<WebElement>
export async function waitForElement(
webview: WebviewView,
locator: By,
multiple = false,
timeout = 15000
): Promise<WebElement | WebElement[]> {
const driver = webview.getDriver()
await driver.wait(until.elementsLocated(locator), timeout)
return multiple ? await webview.findWebElements(locator) : await webview.findWebElement(locator)
Copy link
Contributor

Choose a reason for hiding this comment

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

if multiple is false, which element does it pick? What gives one element higher priority over another?

Copy link
Author

Choose a reason for hiding this comment

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

if multiple is false, it will just pick the first element it finds. my goal for the waitForElement function when multiple is false when the writer knows that there is a single element (ie. a button) but the function is flexible enough that it can also be used to find multiple elements (ie. a menu list)

Copy link
Contributor

Choose a reason for hiding this comment

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

not blocking, but I am also curious what might make something the 'first' element it finds. For example, does it mean it appears first the in the raw html? If so, do we know what determines that?

}

/* General function for finding WebElement by their text content
Logic:
It searches through an array of WebElements and looks for an element with
the ".tittle" CSS class within each item. Compares the text content and returns
the first matching parent element, or throws an error if not found. */

export async function findItemByText(items: WebElement[], text: string) {
for (const item of items) {
const titleDivs = await item.findElements(By.css('.title'))
for (const titleDiv of titleDivs) {
const titleText = await titleDiv.getText()
if (titleText?.trim().startsWith(text)) {
return item
}
}
}
throw new Error(`Item with text "${text}" not found`)
}
53 changes: 53 additions & 0 deletions packages/amazonq/test/e2e_new/amazonq/framework/loginHelper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*!
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
import { Workbench, By, WebviewView } from 'vscode-extension-tester'
import { waitForElement, findItemByText } from './generalHelper'
import { testContext } from '../utils/testContext'
/* Completes the entire Amazon Q login flow
Currently, the function will
1. Open AmazonQ
2. Clicks Company Account
3. Inputs the Start URL
4. IMPORTANT: you must click manually open yourself when the popup window asks to open the browser and complete the authentication in the browser**
TO-DO: Currently this loginToAmazonQ is not fully autonomous as we ran into a blocker when the browser window pops up
Documentation: https://quip-amazon.com/PoJOAyt4ja8H/Authentication-for-UI-Tests-Documentation */

export async function loginToAmazonQ(): Promise<void> {
const workbench = new Workbench()
await workbench.executeCommand('Amazon Q: Open Chat')

await new Promise((resolve) => setTimeout(resolve, 5000))
let webviewView = new WebviewView()
await webviewView.switchToFrame()

const selectableItems = await waitForElement(webviewView, By.css('.selectable-item'), true)
if (selectableItems.length === 0) {
throw new Error('No selectable login options found')
}

const companyItem = await findItemByText(selectableItems, 'Company account')
await companyItem.click()
const signInContinue = await webviewView.findWebElement(By.css('#connection-selection-continue-button'))
await signInContinue.click()
const startUrlInput = await webviewView.findWebElement(By.id('startUrl'))
await startUrlInput.clear()
await startUrlInput.sendKeys('https://amzn.awsapps.com/start')
const UrlContinue = await webviewView.findWebElement(By.css('button.continue-button.topMargin'))
await UrlContinue.click()
console.log('Waiting for manual authentication...')
await new Promise((resolve) => setTimeout(resolve, 12000))
console.log('Manual authentication should be done')
await webviewView.switchBack()

const editorView = workbench.getEditorView()
await editorView.closeAllEditors()
webviewView = new WebviewView()
await webviewView.switchToFrame()

testContext.workbench = workbench
testContext.webviewView = webviewView
}
13 changes: 13 additions & 0 deletions packages/amazonq/test/e2e_new/amazonq/utils/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*!
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
import { loginToAmazonQ } from '../framework/loginHelper'

before(async function () {
this.timeout(60000) // Increase timeout to 60 seconds
console.log('\n\n*** MANUAL INTERVENTION REQUIRED ***')
console.log('When prompted, you must manually click to open the browser and complete authentication')
console.log('You have 60 seconds to complete this step\n\n')
await loginToAmazonQ()
})
13 changes: 13 additions & 0 deletions packages/amazonq/test/e2e_new/amazonq/utils/testContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*!
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
import { Workbench, WebviewView } from 'vscode-extension-tester'

export interface TestContext {
workbench?: Workbench
webviewView?: WebviewView
}

// arr to store shared context
export const testContext: TestContext = {}