-
Notifications
You must be signed in to change notification settings - Fork 746
feat(amazonq): adding a simple chat prompt test #7643
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 12 commits
98688bf
5959a38
4540f16
f1afe95
4fa90ec
e1c33d8
ea46464
66f198d
dd62e27
866478a
35c656f
ed3c220
824635e
b30f7f4
5da6336
c8ccbae
667619e
ade9906
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1322,26 +1322,40 @@ | |
| "fontCharacter": "\\f1de" | ||
| } | ||
| }, | ||
| "aws-schemas-registry": { | ||
| "aws-sagemaker-code-editor": { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this seems unrelated to your changes?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes this is unrelated to my changes, i think they appeared when i ran "npm install" when we were resolving the merge conflicts, should i delete them?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should we able to resolve this by rebasing. If not, we can manually take the changes out of the PR since I'm unsure of the side effects. |
||
| "description": "AWS Contributed Icon", | ||
| "default": { | ||
| "fontPath": "./resources/fonts/aws-toolkit-icons.woff", | ||
| "fontCharacter": "\\f1df" | ||
| } | ||
| }, | ||
| "aws-schemas-schema": { | ||
| "aws-sagemaker-jupyter-lab": { | ||
| "description": "AWS Contributed Icon", | ||
| "default": { | ||
| "fontPath": "./resources/fonts/aws-toolkit-icons.woff", | ||
| "fontCharacter": "\\f1e0" | ||
| } | ||
| }, | ||
| "aws-stepfunctions-preview": { | ||
| "aws-schemas-registry": { | ||
| "description": "AWS Contributed Icon", | ||
| "default": { | ||
| "fontPath": "./resources/fonts/aws-toolkit-icons.woff", | ||
| "fontCharacter": "\\f1e1" | ||
| } | ||
| }, | ||
| "aws-schemas-schema": { | ||
| "description": "AWS Contributed Icon", | ||
| "default": { | ||
| "fontPath": "./resources/fonts/aws-toolkit-icons.woff", | ||
| "fontCharacter": "\\f1e2" | ||
| } | ||
| }, | ||
| "aws-stepfunctions-preview": { | ||
| "description": "AWS Contributed Icon", | ||
| "default": { | ||
| "fontPath": "./resources/fonts/aws-toolkit-icons.woff", | ||
| "fontCharacter": "\\f1e3" | ||
| } | ||
| } | ||
| }, | ||
| "walkthroughs": [ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,61 +2,129 @@ | |
| * 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() | ||
| workbench = new Workbench() | ||
| await workbench.executeCommand('Amazon Q: Open Chat') | ||
|
|
||
| await new Promise((resolve) => setTimeout(resolve, 15000)) | ||
laura-codess marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // need this timeout | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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') | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||
| // find all the close buttons and click them | ||
| const closeButtons = await webviewView.findWebElements(By.css('.mynah-tabs-close-button')) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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() | ||
| // small delay to ensure the tab closes properly | ||
| await new Promise((resolve) => setTimeout(resolve, 500)) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could we use |
||
| } | ||
|
|
||
| // 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() | ||
|
|
||
| // await new Promise((resolve) => setTimeout(resolve, 12000)) | ||
| // wait for response using conversation container check | ||
| 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) { | ||
|
|
@@ -70,4 +138,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> { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||
Hweinstock marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return true | ||
| } | ||
| } | ||
| await new Promise((resolve) => setTimeout(resolve, 500)) | ||
| } | ||
|
|
||
| return false | ||
| } | ||
| }) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
are these automatically applied by the linter?