Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 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
5 changes: 5 additions & 0 deletions docs/lsp.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ sequenceDiagram
```

## Language Server Debugging

Copy link
Contributor

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?

If you want to connect a local version of language-servers to aws-toolkit-vscode, follow these steps:

1. Clone https://github.com/aws/language-servers.git and set it up in the same workspace as this project by cmd+shift+p and "add folder to workspace" and selecting the language-servers folder that you just cloned. Your VS code folder structure should look like below.
Expand Down Expand Up @@ -57,13 +58,16 @@ If you want to connect a local version of language-servers to aws-toolkit-vscode
6. (Optional): Enable `"amazonq.trace.server": "on"` or `"amazonq.trace.server": "verbose"` in your VSCode settings to view detailed log messages sent to/from the language server. These log messages will show up in the "Amazon Q Language Server" output channel

### Breakpoints Work-Around

If the breakpoints in your language-servers project remain greyed out and do not trigger when you run `Launch LSP with Debugging`, your debugger may be attaching to the language server before it has launched. You can follow the work-around below to avoid this problem. If anyone fixes this issue, please remove this section.

1. Set your breakpoints and click `Launch LSP with Debugging`
2. Once the debugging session has started, click `Launch LSP with Debugging` again, then `Cancel` on any pop-ups that appear
3. On the debug panel, click `Attach to Language Server (amazonq)` next to the red stop button
4. Click `Launch LSP with Debugging` again, then `Cancel` on any pop-ups that appear

## Language Server Runtimes Debugging

If you want to connect a local version of language-server-runtimes to aws-toolkit-vscode, follow these steps:

1. Clone https://github.com/aws/language-server-runtimes.git and set it up in the same workspace as this project by cmd+shift+p and "add folder to workspace" and selecting the language-server-runtimes folder that you just cloned. Your VS code folder structure should look like below.
Expand All @@ -75,6 +79,7 @@ If you want to connect a local version of language-server-runtimes to aws-toolki
/amazonq
/language-server-runtimes
```

2. Inside of the language-server-runtimes project run:
```
npm install
Expand Down
20 changes: 17 additions & 3 deletions packages/amazonq/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1322,26 +1322,40 @@
"fontCharacter": "\\f1de"
}
},
"aws-schemas-registry": {
"aws-sagemaker-code-editor": {
Copy link
Contributor

Choose a reason for hiding this comment

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

this seems unrelated to your changes?

Copy link
Author

Choose a reason for hiding this comment

The 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?

Copy link
Contributor

Choose a reason for hiding this comment

The 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": [
Expand Down
Empty file added packages/amazonq/test.log
Empty file.
142 changes: 113 additions & 29 deletions packages/amazonq/test/e2e/amazonq/VET.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,59 +2,114 @@
* 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)
this.timeout(150000)
let webviewView: WebviewView

let workbench: Workbench
// NOTE: I tested all the timeouts and they are necessary for the webview to load properly
before(async function () {
const workbench = new Workbench()
this.timeout(120000)
workbench = new Workbench()
await workbench.executeCommand('Amazon Q: Open Chat')

await new Promise((resolve) => setTimeout(resolve, 15000))
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 driver = webviewView.getDriver()
await driver.wait(until.elementsLocated(By.css('.selectable-item')), 30000)
const selectableItems = await webviewView.findWebElements(By.css('.selectable-item'))
console.log(typeof selectableItems)
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...')
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')
await new Promise((resolve) => setTimeout(resolve, 1500))
webviewView = new WebviewView()
console.log('Reopened webview view')
await webviewView.switchToFrame()
await new Promise((resolve) => setTimeout(resolve, 1200))
})

after(async () => {
// TO-DO: Close all the chat windows after the test is done so that when the test runs again it does not have memory
// from the previous test

/*
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'))
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()
// small delay to ensure the tab closes properly
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 driver = webviewView.getDriver()
await driver.wait(until.elementsLocated(By.css('.mynah-chat-prompt-input')), 300000)
// In order to test the chat prompt, we need to find the input field and send keys
const chatInput = await webviewView.findWebElement(By.css('.mynah-chat-prompt-input'))
await chatInput.sendKeys('Hello, Amazon Q!')
await driver.wait(until.elementsLocated(By.css('.mynah-chat-prompt-button')), 300000)
// In order to submit the chat prompt, we need to find the send button and click it
const sendButton = await webviewView.findWebElement(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 find item by text content
Expand All @@ -70,4 +125,33 @@ 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 = 30000): 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
}
})
38 changes: 20 additions & 18 deletions scripts/package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import * as child_process from 'child_process' // eslint-disable-line no-restricted-imports
import * as nodefs from 'fs' // eslint-disable-line no-restricted-imports
import * as path from 'path'
import { platform } from 'os';
import { platform } from 'os'
import { downloadLanguageServer } from './lspArtifact'

function parseArgs() {
Expand Down Expand Up @@ -112,10 +112,10 @@ function getVersionSuffix(feature: string, debug: boolean): string {
*/
function isCurlAvailable(): boolean {
try {
child_process.execFileSync('curl', ['--version']);
return true;
child_process.execFileSync('curl', ['--version'])
return true
} catch {
return false;
return false
}
}

Expand All @@ -124,23 +124,23 @@ function isCurlAvailable(): boolean {
*/
function downloadFiles(urls: string[], outputDir: string, outputFile: string): void {
if (platform() !== 'linux') {
return;
return
}

if (!isCurlAvailable()) {
return;
return
}

// Create output directory if it doesn't exist
if (!nodefs.existsSync(outputDir)) {
nodefs.mkdirSync(outputDir, { recursive: true });
nodefs.mkdirSync(outputDir, { recursive: true })
}

urls.forEach(url => {
const filePath = path.join(outputDir, outputFile || '');
urls.forEach((url) => {
const filePath = path.join(outputDir, outputFile || '')

try {
child_process.execFileSync('curl', ['-o', filePath, url]);
child_process.execFileSync('curl', ['-o', filePath, url])
} catch {}
})
}
Expand All @@ -151,21 +151,23 @@ function downloadFiles(urls: string[], outputDir: string, outputFile: string): v
* TODO: retrieve from authoritative system
*/
function preparePackager(): void {
const dir = process.cwd();
const REPO_NAME = "aws/aws-toolkit-vscode"
const TAG_NAME = "stability"
const dir = process.cwd()
const REPO_NAME = 'aws/aws-toolkit-vscode'
const TAG_NAME = 'stability'

if (!dir.includes('amazonq')) {
return;
return
}

if (process.env.STAGE !== 'prod') {
return;
return
}

downloadFiles([
`https://raw.githubusercontent.com/${REPO_NAME}/${TAG_NAME}/scripts/extensionNode.bk`
], "src/", "extensionNode.ts")
downloadFiles(
[`https://raw.githubusercontent.com/${REPO_NAME}/${TAG_NAME}/scripts/extensionNode.bk`],
'src/',
'extensionNode.ts'
)
}

async function main() {
Expand Down
Loading
Loading