Skip to content

feat(amazonq): Implement Rules Abstraction + Rules Option Test #7841

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

Open
wants to merge 22 commits into
base: feature/ui-e2e-tests
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e81d004
refactor(amazonq): reorganize transformation history code (#7843)
tgodara-aws Aug 8, 2025
809a3a3
fix(amazonq): discard/reject edit suggestion if it isn't valid (#7848)
andrewyuq Aug 8, 2025
105f714
fix(amazonq): update lsp client name to support Sagemaker AI origin f…
parameja1 Aug 9, 2025
bb6cd0e
fix(amazonq): Don't show inline completions when a edit is displayed …
floralph Aug 9, 2025
5bcc4e1
feat(amazonq): add setup for ui-tests (#7597)
laura-codess Aug 11, 2025
a98c815
feat(amazonq): adding a simple chat prompt test (#7643)
laura-codess Jul 16, 2025
61a5cb2
feat(amazonq): package.json npm scripts for build issues with vscode-…
laura-codess Aug 11, 2025
411e79c
feat(amazonq): UI Tests Baseline Framework Complete Set Up (#7727)
laura-codess Aug 11, 2025
0609767
updated npm scripts in order to work correctly with our packaging and…
surajrdy-aws Jul 23, 2025
5050e67
Merge master into feature/ui-e2e-tests (#7680)
laura-codess Aug 11, 2025
a5fac9e
feat(amazonq): Implement inline completion test (#7752)
laura-codess Aug 11, 2025
0810e78
Merge master into feature/ui-e2e-tests (#7767)
aws-toolkit-automation Jul 31, 2025
7c86b73
fix(amazonq): Inline test after function addition, writeToTextEditor …
surajrdy-aws Aug 4, 2025
0cb1eb3
initializing rules
laura-codess Aug 5, 2025
ac7cdc6
some initial implementation, and a gitignore fix, as well as a testFi…
laura-codess Jul 24, 2025
168827a
changes to rules
laura-codess Aug 6, 2025
172fc3e
implementation done
laura-codess Aug 7, 2025
b872127
lint actions fix
laura-codess Aug 10, 2025
5d27373
some initial implementation, and a gitignore fix, as well as a testFi…
laura-codess Aug 11, 2025
07f5a06
implementation done
laura-codess Aug 11, 2025
d6d437e
fix merge issues"
laura-codess Aug 11, 2025
af5bdbd
undoing changes
laura-codess Aug 11, 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
53 changes: 14 additions & 39 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions packages/amazonq/src/app/inline/editSuggestionState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*!
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

/**
* Manages the state of edit suggestions to avoid circular dependencies
*/
export class EditSuggestionState {
private static isEditSuggestionCurrentlyActive = false

static setEditSuggestionActive(active: boolean): void {
this.isEditSuggestionCurrentlyActive = active
}

static isEditSuggestionActive(): boolean {
return this.isEditSuggestionCurrentlyActive
}
}
113 changes: 113 additions & 0 deletions packages/amazonq/test/e2e_new/amazonq/helpers/rulesHelper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*!
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
import { WebviewView, By } from 'vscode-extension-tester'
import { printElementHTML, sleep, waitForElement } from '../utils/generalUtils'

/**
* Clicks the Rules button in the top bar
* @param webview The WebviewView instance
*/
export async function clickRulesButton(webview: WebviewView): Promise<void> {
const body = await webview.findWebElement(By.css('body'))
await printElementHTML(body)

const chatPromptWrapper = await waitForElement(webview, By.css('.mynah-chat-prompt-wrapper'))
const topBar = await chatPromptWrapper.findElement(By.css('[data-testid="prompt-input-top-bar"]'))
const buttons = await topBar.findElements(By.css('.mynah-button.mynah-button-secondary'))

for (const button of buttons) {
try {
const labelElement = await button.findElement(By.css('.mynah-button-label'))
const text = await labelElement.getText()
if (text.trim() === 'Rules') {
await button.click()
return
}
} catch (e) {
continue
}
}
throw new Error('Rules button not found')
}

/**
* Clicks on "Create a new rule" option from the rules menu
* @param webview The WebviewView instance
*/
export async function clickCreateNewRuleOption(webview: WebviewView): Promise<void> {
// needs a bit of time because the overlay has to load
await sleep(1000)
const overlayContainer = await waitForElement(webview, By.css('.mynah-overlay-container'))
const quickPickItems = await overlayContainer.findElements(By.css('[data-testid="prompt-input-quick-pick-item"]'))

if (quickPickItems.length === 0) {
throw new Error('No quick pick items found')
}
const lastItem = quickPickItems[quickPickItems.length - 1]
const bdiElement = await lastItem.findElement(By.css('.mynah-detailed-list-item-description.ltr bdi'))
const text = await bdiElement.getText()

if (text.trim() !== 'Create a new rule') {
throw new Error(`Expected "Create a new rule" but found "${text}"`)
}
await lastItem.click()
}

/**
* Enters a rule name in the rule creation form
* @param webview The WebviewView instance
* @param ruleName The name of the rule
*/
export async function enterRuleName(webview: WebviewView, ruleName: string): Promise<void> {
// needs a bit of time because the overlay has to load
await sleep(1000)
const sheetWrapper = await waitForElement(webview, By.css('[data-testid="sheet-wrapper"]'))
const ruleNameInput = await sheetWrapper.findElement(By.css('[data-testid="chat-item-form-item-text-input"]'))

await ruleNameInput.clear()
await ruleNameInput.sendKeys(ruleName)
}

/**
* Clicks the Create button in the rule creation form
* @param webview The WebviewView instance
*/
export async function clickCreateButton(webview: WebviewView): Promise<void> {
const sheetWrapper = await waitForElement(webview, By.css('[data-testid="sheet-wrapper"]'))
const createButton = await sheetWrapper.findElement(By.xpath('.//button[@action-id="submit-create-rule"]'))

await webview.getDriver().wait(
async () => {
const isDisabled = await createButton.getAttribute('disabled')
return isDisabled === null
},
5000,
'Create button did not become enabled'
)

await createButton.click()
}

/**
* Clicks the Cancel button in the rule creation form
* @param webview The WebviewView instance
*/
export async function clickCancelButton(webview: WebviewView): Promise<void> {
const sheetWrapper = await waitForElement(webview, By.css('[data-testid="sheet-wrapper"]'))
const cancelButton = await sheetWrapper.findElement(By.xpath('.//button[@action-id="cancel-create-rule"]'))
await cancelButton.click()
}

/**
* Creates a new rule with the specified name (complete workflow)
* @param webview The WebviewView instance
* @param ruleName The name of the rule to create
*/
export async function createNewRule(webview: WebviewView, ruleName: string): Promise<void> {
await clickRulesButton(webview)
await clickCreateNewRuleOption(webview)
await enterRuleName(webview, ruleName)
await clickCreateButton(webview)
}
51 changes: 51 additions & 0 deletions packages/amazonq/test/e2e_new/amazonq/tests/rules.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*!
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
import '../utils/setup'
import { WebviewView, ActivityBar, DefaultTreeSection, SideBarView, VSBrowser } from 'vscode-extension-tester'
import { testContext } from '../utils/testContext'
import { createNewRule } from '../helpers/rulesHelper'
import path from 'path'
import { sleep } from '../utils/generalUtils'

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

before(async function () {
// we assume that we've left off on a webview from a previous test
webviewView = testContext.webviewView
await webviewView.switchBack()

// the "rules" menu won't show unless we have a folder open
await VSBrowser.instance.openResources(path.join('..', 'utils', 'resources', 'testFolder'))
const explorerControl = await new ActivityBar().getViewControl('Explorer')
await explorerControl?.openView()
const view = new SideBarView()
const content = view.getContent()
const tree = (await content.getSection('testFolder')) as DefaultTreeSection
await tree.openItem('test-folder')
const workbench = testContext.workbench
await workbench.executeCommand('Amazon Q: Open Chat')

// sleep is needed because the workbench needs some time to load
await sleep(5000)
const activityBar = new ActivityBar()
const amazonQControl = await activityBar.getViewControl('Amazon Q')
await amazonQControl?.openView()

// sleep is needed because it takes time to switch to the AmazonQ webview
await sleep(5000)
webviewView = new WebviewView()
await webviewView.switchToFrame()
testContext.webviewView = webviewView
})

after(async function () {})

it('Rules Option Test', async () => {
await createNewRule(webviewView, 'testRule')
})
})
30 changes: 27 additions & 3 deletions packages/amazonq/test/e2e_new/amazonq/utils/generalUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export async function sleep(timeout: number) {
* @param timeout The timeout in milliseconds (Optional)
* @returns Promise<WebElement> Returns the element found
*/
export async function waitForElement(webview: WebviewView, locator: By, timeout?: number): Promise<WebElement> {
export async function waitForElement(webview: WebviewView, locator: By, timeout: number = 8000): Promise<WebElement> {
const driver = webview.getDriver()
await driver.wait(until.elementsLocated(locator), timeout)
return await webview.findWebElement(locator)
Expand All @@ -33,7 +33,11 @@ export async function waitForElement(webview: WebviewView, locator: By, timeout?
* @param timeout The timeout in milliseconds (Optional)
* @returns Promise<WebElement[]> Returns an array of elements found
*/
export async function waitForElements(webview: WebviewView, locator: By, timeout?: number): Promise<WebElement[]> {
export async function waitForElements(
webview: WebviewView,
locator: By,
timeout: number = 8000
): Promise<WebElement[]> {
const driver = webview.getDriver()
await driver.wait(until.elementsLocated(locator), timeout)
return await webview.findWebElements(locator)
Expand Down Expand Up @@ -91,7 +95,7 @@ export async function writeToChat(prompt: string, webview: WebviewView, send = t
* @param timeout The timeout in milliseconds
* @returns Promise<boolean> True if a response was detected, false if timeout occurred
*/
export async function waitForChatResponse(webview: WebviewView, timeout = 15000): Promise<boolean> {
export async function waitForChatResponse(webview: WebviewView, timeout = 8000): Promise<boolean> {
const startTime = Date.now()

while (Date.now() - startTime < timeout) {
Expand Down Expand Up @@ -183,3 +187,23 @@ export async function findItemByText(items: WebElement[], text: string) {
}
throw new Error(`Item with text "${text}" not found`)
}

/**
* Prints the HTML content of a web element for debugging purposes
* @param element The WebElement to print HTML for
*/
export async function printElementHTML(element: WebElement): Promise<void> {
const htmlContent = await element.getAttribute('outerHTML')

const formattedHTML = htmlContent
.replace(/></g, '>\n<')
.replace(/\s+/g, ' ')
.split('\n')
.map((line) => line.trim())
.filter((line) => line.length > 0)
.join('\n')

console.log(`=== HTML CONTENT ===`)
console.log(formattedHTML)
console.log('=== END HTML ===')
}
Loading
Loading