Skip to content
Merged
Changes from all commits
Commits
Show all changes
18 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
667619e
Revert "implementing the initial setup for the framework"
laura-codess Jul 16, 2025
ade9906
remove test.log
laura-codess Jul 16, 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
155 changes: 124 additions & 31 deletions packages/amazonq/test/e2e/amazonq/VET.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,61 +2,126 @@
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
import { Workbench, By, WebviewView, WebElement } from 'vscode-extension-tester'
import { until } from 'selenium-webdriver'

import { Workbench, By, EditorView, WebviewView, WebElement } from 'vscode-extension-tester'

describe('Amazon Q Login Flow', function () {
describe('Amazon Q E2E UI Test', function () {
// need this timeout because Amazon Q takes awhile to load
this.timeout(30000)
let webviewView: WebviewView

// NOTE: I tested all the timeouts and they are necessary for the webview to load properly
// need this timeout
this.timeout(150000)
let webviewView: WebviewView
let workbench: Workbench
before(async function () {
const workbench = new Workbench()
/* TO-DO
possibly before the workbench executes Amazon Q: Open Chat, we can make sure that all the tabs are closed first*/
workbench = new Workbench()
await workbench.executeCommand('Amazon Q: Open Chat')

await new Promise((resolve) => setTimeout(resolve, 15000))
// need this timeout
Copy link
Contributor

Choose a reason for hiding this comment

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

I know its somewhat of a mystery, but it could be worth investigating why we need these timeouts? Maybe we can find another example of someone running into the same issue and link that here.

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

after(async () => {
await webviewView.switchBack()
try {
await new EditorView().closeAllEditors()
} catch {}
})

it('Should click through the Amazon Q login screen', async () => {
// Select company account option
const selectableItems = await webviewView.findWebElements(By.css('.selectable-item'))
console.log(typeof selectableItems)
const selectableItems = await waitForElement(webviewView, By.css('.selectable-item'), true)
if (selectableItems.length === 0) {
throw new Error('No selectable login options found')
}

// Find and click company account
const companyItem = await findItemByText(selectableItems, 'Company account')
await companyItem.click()
await new Promise((resolve) => setTimeout(resolve, 2000))

// Click first continue button
const signInContinue = await webviewView.findWebElement(By.css('#connection-selection-continue-button'))
await signInContinue.click()
await new Promise((resolve) => setTimeout(resolve, 2000))

// Enter start URL
const startUrlInput = await webviewView.findWebElement(By.id('startUrl'))
await startUrlInput.clear()
await startUrlInput.sendKeys('https://amzn.awsapps.com/start')
await new Promise((resolve) => setTimeout(resolve, 1000))

// Click second continue button
const UrlContinue = await webviewView.findWebElement(By.css('button.continue-button.topMargin'))
await UrlContinue.click()
await new Promise((resolve) => setTimeout(resolve, 2000))
console.log('Waiting for manual authentication...')
// need this timeout
await new Promise((resolve) => setTimeout(resolve, 12000))
console.log('Manual authentication should be done')
await webviewView.switchBack()

// AFTER AUTHENTICATION WE MUST RELOAD THE WEBVIEW BECAUSE MULTIPLE WEVIEWS CANNOT BE READ AT THE SAME TIME
const editorView = workbench.getEditorView()
console.log('editorview successfully created')
Copy link
Contributor

Choose a reason for hiding this comment

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

I assume these are just debugging logs? We should be careful of this becoming super noisy, but if its helpful we should keep them. I imagine a subset of these are helpful?

await editorView.closeAllEditors()
console.log('Closed all editors')
webviewView = new WebviewView()
console.log('Reopened webview view')
await webviewView.switchToFrame()
})

after(async () => {
/*
mynah-tabs-container is the css that contains all the mynah ui tabs
inside that there are two spans that have key values
inside those spans there is a div with the css mynah-tab-item-label
and finally INSIDE THAT 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

Logic:
Find all the tahs by looking for the close buttons and then close them one by one. To check if all the tabs are closed, we can check if the mynah-tabs-container is empty.
*/
try {
const closeButtons = await webviewView.findWebElements(By.css('.mynah-tabs-close-button'))
Copy link
Contributor

Choose a reason for hiding this comment

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

do we need to wait here as well?


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

Choose a reason for hiding this comment

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

could we use waitForElement here?

}

// double check that all tabs are closed by checking if the mynah-tabs-container is empty
const tabsContainer = await webviewView.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)
}
await webviewView.switchBack()
})

it('Chat Prompt Test', async () => {
const chatInput = await waitForElement(webviewView, By.css('.mynah-chat-prompt-input'))
await chatInput.sendKeys('Hello, Amazon Q!')
const sendButton = await waitForElement(webviewView, By.css('.mynah-chat-prompt-button'))
await sendButton.click()
const responseReceived = await waitForChatResponse(webviewView)
if (!responseReceived) {
throw new Error('Chat response not received within timeout')
}

console.log('Chat response detected successfully')
})

// Helper to wait for ui elements to load, utilizes typescript function overloading to account for all possible edge cases
async function waitForElement(
webview: WebviewView,
locator: By,
multiple: true,
timeout?: number
): Promise<WebElement[]>
async function waitForElement(
webview: WebviewView,
locator: By,
multiple?: false,
timeout?: number
): Promise<WebElement>
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)
}

// Helper to find item by text content
async function findItemByText(items: WebElement[], text: string) {
for (const item of items) {
Expand All @@ -70,4 +135,32 @@ describe('Amazon Q Login Flow', function () {
}
throw new Error(`Item with text "${text}" not found`)
}

/* My Idea: Basically the conversation container's css is .mynah-chat-items-conversation-container
Instead of looking for a specific message like how we look for other elements in the test,
I can check how many elements there are in our specific conversation container. If there is 2 elements,
we can assume that the chat response has been generated. The challenge is, we must grab the latest
conversation container, as there can be multiple conversations in the webview. */
async function waitForChatResponse(webview: WebviewView, timeout = 15000): Promise<boolean> {
Copy link
Contributor

Choose a reason for hiding this comment

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

nice, I like the use of a default here! It helps simplify the interface for the common case.

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
}
})
Loading