diff --git a/gradle.properties b/gradle.properties index e2e796024e1..a206b37099f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,7 +8,7 @@ toolkitVersion=3.62-SNAPSHOT publishToken= publishChannel= -ideProfileName=2025.1 +ideProfileName=2024.3 remoteRobotPort=8080 diff --git a/ui-tests-starter/tst-243+/software/aws/toolkits/jetbrains/uitests/testTests/QTestGenerationChatTest.kt b/ui-tests-starter/tst-243+/software/aws/toolkits/jetbrains/uitests/testTests/QTestGenerationChatTest.kt index d836b0f5304..b2260dba24b 100644 --- a/ui-tests-starter/tst-243+/software/aws/toolkits/jetbrains/uitests/testTests/QTestGenerationChatTest.kt +++ b/ui-tests-starter/tst-243+/software/aws/toolkits/jetbrains/uitests/testTests/QTestGenerationChatTest.kt @@ -50,6 +50,153 @@ class QTestGenerationChatTest { setupTestEnvironment() } + @Test + fun `test method not found error handling`() { + val testCase = TestCase( + IdeProductProvider.IC, + LocalProjectInfo( + Paths.get("tstData", "qTestGenerationTestProject/") + ) + ).useRelease(System.getProperty("org.gradle.project.ideProfileName")) + + // inject connection + useExistingConnectionForTest() + + Starter.newContext(CurrentTestMethod.hyphenateWithClass(), testCase).apply { + System.getProperty("ui.test.plugins").split(File.pathSeparator).forEach { path -> + pluginConfigurator.installPluginFromPath( + Path.of(path) + ) + } + + copyExistingConfig(Paths.get("tstData", "configAmazonQTests")) + updateGeneralSettings() + }.runIdeWithDriver() + .useDriverAndCloseIde { + waitForProjectOpen() + openFile(Paths.get("testModule1", "HappyPath.java").toString()) + Thread.sleep(30000) + val result = executePuppeteerScript(testMethodNotFoundErrorScript) + assertThat(result) + .contains( + "new tab opened", + "Error message displayed correctly", + "Input field re-enabled after error", + ) + } + } + + @Test + fun `test cancel button during test generation`() { + val testCase = TestCase( + IdeProductProvider.IC, + LocalProjectInfo( + Paths.get("tstData", "qTestGenerationTestProject/") + ) + ).useRelease(System.getProperty("org.gradle.project.ideProfileName")) + + // inject connection + useExistingConnectionForTest() + + Starter.newContext(CurrentTestMethod.hyphenateWithClass(), testCase).apply { + System.getProperty("ui.test.plugins").split(File.pathSeparator).forEach { path -> + pluginConfigurator.installPluginFromPath( + Path.of(path) + ) + } + + copyExistingConfig(Paths.get("tstData", "configAmazonQTests")) + updateGeneralSettings() + }.runIdeWithDriver() + .useDriverAndCloseIde { + waitForProjectOpen() + openFile(Paths.get("testModule1", "HappyPath.java").toString()) + Thread.sleep(30000) + val result = executePuppeteerScript(testCancelButtonScript) + assertThat(result) + .contains( + "new tab opened", + "Progress bar text displayed", + "Cancel button found", + "Cancel button clicked", + "Test generation cancelled successfully", + "Input field re-enabled after cancellation", + ) + } + } + + @Test + fun `test documentation generation error handling`() { + val testCase = TestCase( + IdeProductProvider.IC, + LocalProjectInfo( + Paths.get("tstData", "qTestGenerationTestProject/") + ) + ).useRelease(System.getProperty("org.gradle.project.ideProfileName")) + + // inject connection + useExistingConnectionForTest() + + Starter.newContext(CurrentTestMethod.hyphenateWithClass(), testCase).apply { + System.getProperty("ui.test.plugins").split(File.pathSeparator).forEach { path -> + pluginConfigurator.installPluginFromPath( + Path.of(path) + ) + } + + copyExistingConfig(Paths.get("tstData", "configAmazonQTests")) + updateGeneralSettings() + }.runIdeWithDriver() + .useDriverAndCloseIde { + waitForProjectOpen() + openFile(Paths.get("testModule1", "HappyPath.java").toString()) + Thread.sleep(30000) + val result = executePuppeteerScript(testDocumentationErrorScript) + assertThat(result) + .contains( + "new tab opened", + "Error message displayed correctly", + "Input field re-enabled after error", + ) + } + } + + @Test + fun `test remove function error handling`() { + val testCase = TestCase( + IdeProductProvider.IC, + LocalProjectInfo( + Paths.get("tstData", "qTestGenerationTestProject/") + ) + ).useRelease(System.getProperty("org.gradle.project.ideProfileName")) + + // inject connection + useExistingConnectionForTest() + + Starter.newContext(CurrentTestMethod.hyphenateWithClass(), testCase).apply { + System.getProperty("ui.test.plugins").split(File.pathSeparator).forEach { path -> + pluginConfigurator.installPluginFromPath( + Path.of(path) + ) + } + + copyExistingConfig(Paths.get("tstData", "configAmazonQTests")) + updateGeneralSettings() + }.runIdeWithDriver() + .useDriverAndCloseIde { + waitForProjectOpen() + openFile(Paths.get("testModule1", "HappyPath.java").toString()) + Thread.sleep(30000) + val result = executePuppeteerScript(testRemoveFunctionErrorScript) + assertThat(result) + .contains( + "new tab opened", + "Error message displayed correctly", + "Input field re-enabled after error", + ) + } + } + @Test fun `can run a test from the chat`() { val testCase = TestCase( @@ -193,6 +340,116 @@ class QTestGenerationChatTest { } } + @Test + fun `test reject path from the chat`() { + val testCase = TestCase( + IdeProductProvider.IC, + LocalProjectInfo( + Paths.get("tstData", "qTestGenerationTestProject/") + ) + ).useRelease(System.getProperty("org.gradle.project.ideProfileName")) + + // inject connection + useExistingConnectionForTest() + + Starter.newContext(CurrentTestMethod.hyphenateWithClass(), testCase).apply { + System.getProperty("ui.test.plugins").split(File.pathSeparator).forEach { path -> + pluginConfigurator.installPluginFromPath( + Path.of(path) + ) + } + + copyExistingConfig(Paths.get("tstData", "configAmazonQTests")) + updateGeneralSettings() + }.runIdeWithDriver() + .useDriverAndCloseIde { + waitForProjectOpen() + openFile(Paths.get("testModule1", "HappyPath.java").toString()) + Thread.sleep(30000) + val result = executePuppeteerScript(testRejectPathScript) + assertThat(result) + .contains( + "new tab opened", + "View Diff opened", + "Result Reject", + "Unit test generation completed.", + "Input field re-enabled after rejection", + ) + } + } + + @Test + fun `test NL error from the chat`() { + val testCase = TestCase( + IdeProductProvider.IC, + LocalProjectInfo( + Paths.get("tstData", "qTestGenerationTestProject/") + ) + ).useRelease(System.getProperty("org.gradle.project.ideProfileName")) + + // inject connection + useExistingConnectionForTest() + + Starter.newContext(CurrentTestMethod.hyphenateWithClass(), testCase).apply { + System.getProperty("ui.test.plugins").split(File.pathSeparator).forEach { path -> + pluginConfigurator.installPluginFromPath( + Path.of(path) + ) + } + + copyExistingConfig(Paths.get("tstData", "configAmazonQTests")) + updateGeneralSettings() + }.runIdeWithDriver() + .useDriverAndCloseIde { + waitForProjectOpen() + openFile(Paths.get("testModule1", "HappyPath.java").toString()) + Thread.sleep(30000) + val result = executePuppeteerScript(testNLErrorPathScript) + assertThat(result) + .contains( + "new tab opened", + "Command entered: /test /something/", + "Error message displayed correctly" + ) + } + } + + @Test + fun `test progress bar during test generation`() { + val testCase = TestCase( + IdeProductProvider.IC, + LocalProjectInfo( + Paths.get("tstData", "qTestGenerationTestProject/") + ) + ).useRelease(System.getProperty("org.gradle.project.ideProfileName")) + + // inject connection + useExistingConnectionForTest() + + Starter.newContext(CurrentTestMethod.hyphenateWithClass(), testCase).apply { + System.getProperty("ui.test.plugins").split(File.pathSeparator).forEach { path -> + pluginConfigurator.installPluginFromPath( + Path.of(path) + ) + } + + copyExistingConfig(Paths.get("tstData", "configAmazonQTests")) + updateGeneralSettings() + }.runIdeWithDriver() + .useDriverAndCloseIde { + waitForProjectOpen() + openFile(Paths.get("testModule1", "HappyPath.java").toString()) + Thread.sleep(30000) + val result = executePuppeteerScript(testProgressBarScript) + assertThat(result) + .contains( + "new tab opened", + "Progress bar text displayed", + "Test generation completed successfully" + ) + } + } + companion object { @JvmStatic @AfterAll diff --git a/ui-tests-starter/tst-243+/software/aws/toolkits/jetbrains/uitests/testTests/QTestGenerationChatTestScripts.kt b/ui-tests-starter/tst-243+/software/aws/toolkits/jetbrains/uitests/testTests/QTestGenerationChatTestScripts.kt index 50c7522849a..5e1835927de 100644 --- a/ui-tests-starter/tst-243+/software/aws/toolkits/jetbrains/uitests/testTests/QTestGenerationChatTestScripts.kt +++ b/ui-tests-starter/tst-243+/software/aws/toolkits/jetbrains/uitests/testTests/QTestGenerationChatTestScripts.kt @@ -2,14 +2,16 @@ // SPDX-License-Identifier: Apache-2.0 package software.aws.toolkits.jetbrains.uitests.testTests +import org.intellij.lang.annotations.Language +@Language("JavaScript") val testHappyPathScript = """ const puppeteer = require('puppeteer'); async function testNavigation() { const browser = await puppeteer.connect({ browserURL: "http://localhost:9222" }) - try { + try { const pages = await browser.pages() for(const page of pages) { const contents = await page.evaluate(el => el.innerHTML, await page.${'$'}(':root')); @@ -22,7 +24,13 @@ val testHappyPathScript = """ await page.type('.mynah-chat-prompt-input', '/test') await page.keyboard.press('Enter'); await page.keyboard.press('Enter'); - try { + try { + await page.evaluate(() => { + const acknowledgeButton = document.querySelector('button[action-id=amazonq-disclaimer-acknowledge-button-id]'); + if (acknowledgeButton) { + acknowledgeButton.click(); + } + }); await waitForElementWithText(page, "Q - Test") console.log("new tab opened") await page.waitForFunction( @@ -30,7 +38,7 @@ val testHappyPathScript = """ const button = document.querySelector('button[action-id="utg_view_diff"]'); return button && button.isEnabled !== false && button.disabled !== true; }, - { timeout: 300000 } + { timeout: 4000000 } ); await page.evaluate(() => { const button = document.querySelector('button[action-id="utg_view_diff"]'); @@ -46,7 +54,7 @@ val testHappyPathScript = """ const button = document.querySelector('button[action-id="utg_accept"]'); return button && button.isEnabled !== false && button.disabled !== true; }, - { timeout: 300000 } + { timeout: 4000000 } ); await page.evaluate(() => { const button = document.querySelector('button[action-id="utg_accept"]'); @@ -59,6 +67,16 @@ val testHappyPathScript = """ console.log("Result Accepted") await waitForElementWithText(page, "Unit test generation completed.") console.log("Unit test generation completed.") + await page.waitForFunction( + () => { + const inputElement = document.querySelector('.mynah-chat-prompt-input'); + return inputElement && !inputElement.disabled; + }, + { timeout: 4000000 } + ); + + console.log("Input field re-enabled after acceptance") + } catch (e) { console.log("Element with text not found") console.log(e) @@ -72,35 +90,13 @@ val testHappyPathScript = """ } } - async function waitForElementWithText(page, text) { - await page.waitForFunction( - (expectedText) => { - const elements = document.querySelectorAll('*'); - return Array.from(elements).find(element => - element.textContent?.trim() === expectedText - ); - }, - {}, - text - ); - } - - async function waitAndGetElementByText(page, text) { - const element = await page.waitForFunction( - (expectedText) => { - const elements = document.querySelectorAll('*'); - return Array.from(elements).find(element => - element.textContent?.trim() === expectedText - ); - }, - {}, - text - ); - return element; - } + $waitForElementWithTextFunction + + $waitAndGetElementByTextFunction testNavigation().catch(console.error); """.trimIndent() +@Language("JavaScript") val testNoFilePathScript = """ const puppeteer = require('puppeteer'); async function testNavigation() { @@ -124,7 +120,7 @@ async function testNavigation() { await waitForElementWithText(page, "Q - Test") console.log("new tab opened") const errorMessage = await page.waitForSelector('text/Sorry, there isn\'t a source file open right now that I can generate a test for. Make sure you open a source file so I can generate tests.', { - timeout: 5000 + timeout: 4000000 }) console.log('Error message:', await errorMessage.evaluate(el => el.textContent)) } catch (e) { @@ -139,35 +135,13 @@ async function testNavigation() { } } -async function waitForElementWithText(page, text) { - await page.waitForFunction( - (expectedText) => { - const elements = document.querySelectorAll('*'); - return Array.from(elements).find(element => - element.textContent?.trim() === expectedText - ); - }, - {}, - text - ); -} - -async function waitAndGetElementByText(page, text) { - const element = await page.waitForFunction( - (expectedText) => { - const elements = document.querySelectorAll('*'); - return Array.from(elements).find(element => - element.textContent?.trim() === expectedText - ); - }, - {}, - text - ); - return element; -} +$waitForElementWithTextFunction + +$waitAndGetElementByTextFunction testNavigation().catch(console.error); """.trimIndent() +@Language("JavaScript") val expectedErrorPath = """ const puppeteer = require('puppeteer'); async function testNavigation() { @@ -200,7 +174,7 @@ val expectedErrorPath = """ return pageContent.includes(expectedText); }, { - timeout: 300000 // 5 minutes timeout + timeout: 4000000 // 5 minutes timeout }, "I apologize, but the specified methods are private or protected. I can only generate tests for public methods. Try /test again and specify public methods to generate tests." ); @@ -219,35 +193,13 @@ val expectedErrorPath = """ } } - async function waitForElementWithText(page, text) { - await page.waitForFunction( - (expectedText) => { - const elements = document.querySelectorAll('*'); - return Array.from(elements).find(element => - element.textContent?.trim() === expectedText - ); - }, - {}, - text - ); - } - - async function waitAndGetElementByText(page, text) { - const element = await page.waitForFunction( - (expectedText) => { - const elements = document.querySelectorAll('*'); - return Array.from(elements).find(element => - element.textContent?.trim() === expectedText - ); - }, - {}, - text - ); - return element; - } + $waitForElementWithTextFunction + + $waitAndGetElementByTextFunction testNavigation().catch(console.error); """.trimIndent() +@Language("JavaScript") val unsupportedLanguagePath = """ const puppeteer = require('puppeteer'); async function testNavigation() { @@ -280,7 +232,7 @@ val unsupportedLanguagePath = """ return pageContent.includes(expectedText); }, { - timeout: 300000 // 5 minutes timeout + timeout: 4000000 // 5 minutes timeout }, "is not a language I support specialized unit test generation for at the moment." ); @@ -299,31 +251,549 @@ val unsupportedLanguagePath = """ } } - async function waitForElementWithText(page, text) { - await page.waitForFunction( - (expectedText) => { - const elements = document.querySelectorAll('*'); - return Array.from(elements).find(element => - element.textContent?.trim() === expectedText - ); - }, - {}, - text - ); + $waitForElementWithTextFunction + + $waitAndGetElementByTextFunction + testNavigation().catch(console.error); +""".trimIndent() + +@Language("JavaScript") +val testRejectPathScript = """ + const puppeteer = require('puppeteer'); + async function testNavigation() { + const browser = await puppeteer.connect({ + browserURL: "http://localhost:9222" + }) + try { + const pages = await browser.pages() + for(const page of pages) { + const contents = await page.evaluate(el => el.innerHTML, await page.${'$'}(':root')); + const element = await page.${'$'}('.mynah-chat-prompt-input') + if(element) { + const elements = await page.${'$'}${'$'}('.mynah-chat-command-selector-command'); + const attr = await Promise.all( + elements.map(elem => elem.evaluate(el => el.getAttribute('command'))) + ); + await page.type('.mynah-chat-prompt-input', '/test') + await page.keyboard.press('Enter'); + await page.keyboard.press('Enter'); + try { + await waitForElementWithText(page, "Q - Test") + console.log("new tab opened") + await page.evaluate(() => { + const acknowledgeButton = document.querySelector('button[action-id=amazonq-disclaimer-acknowledge-button-id]'); + if (acknowledgeButton) { + acknowledgeButton.click(); + } + }); + await page.waitForFunction( + () => { + const button = document.querySelector('button[action-id="utg_view_diff"]'); + return button && button.isEnabled !== false && button.disabled !== true; + }, + { timeout: 4000000 } + ); + await page.evaluate(() => { + const button = document.querySelector('button[action-id="utg_view_diff"]'); + if (button) { + button.click(); + } else { + throw new Error('Button not found after waiting'); + } + }); + console.log("View Diff opened") + await page.waitForFunction( + () => { + const button = document.querySelector('button[action-id="utg_reject"]'); + return button && button.isEnabled !== false && button.disabled !== true; + }, + { timeout: 4000000 } + ); + await page.evaluate(() => { + const button = document.querySelector('button[action-id="utg_reject"]'); + if (button) { + button.click(); + } else { + throw new Error('Accept button not found after waiting'); + } + }); + console.log("Result Reject") + await waitForElementWithText(page, "Unit test generation completed.") + console.log("Unit test generation completed.") + await page.waitForFunction( + () => { + const inputElement = document.querySelector('.mynah-chat-prompt-input'); + return inputElement && !inputElement.disabled; + }, + { timeout: 4000000 } + ); + + console.log("Input field re-enabled after rejection") + + } catch (e) { + console.log("Element with text not found") + console.log(e) + throw e + } + + } + } + } finally { + await browser.close(); + } + } + + $waitForElementWithTextFunction + + $waitAndGetElementByTextFunction + + testNavigation().catch(console.error); +""".trimIndent() + +@Language("JavaScript") +val testNLErrorPathScript = """ + const puppeteer = require('puppeteer'); + async function testNavigation() { + const browser = await puppeteer.connect({ + browserURL: "http://localhost:9222" + }) + try { + const pages = await browser.pages() + //console.log(pages) + for(const page of pages) { + const contents = await page.evaluate(el => el.innerHTML, await page.${'$'}(':root')); + //console.log(contents) + const element = await page.${'$'}('.mynah-chat-prompt-input') + if(element) { + const elements = await page.${'$'}${'$'}('.mynah-chat-command-selector-command'); + const attr = await Promise.all( + elements.map(elem => elem.evaluate(el => el.getAttribute('command'))) + ); + await page.type('.mynah-chat-prompt-input', '/test /something/') + await page.keyboard.press('Enter'); + + try { + console.log("Command entered: /test /something/") + await waitForElementWithText(page, "Q - Test") + console.log("new tab opened") + await page.waitForFunction( + (expectedText) => { + const pageContent = document.body.textContent || ''; + return pageContent.includes(expectedText); + }, + { + timeout: 4000000 // 5 minutes timeout + }, + "I apologize, but I couldn't process your /test instruction." + ); + + await page.waitForFunction( + (expectedText) => { + const pageContent = document.body.textContent || ''; + return pageContent.includes(expectedText); + }, + { + timeout: 4000000 // 5 minutes timeout + }, + "Try: /test and optionally specify a class, function or method." + ); + console.log("Error message displayed correctly") + + } catch (e) { + console.log("Element with text not found") + console.log(e) + throw e + } + + } + } + } finally { + await browser.close(); + } } + + $waitForElementWithTextFunction + + $waitAndGetElementByTextFunction + testNavigation().catch(console.error); +""".trimIndent() - async function waitAndGetElementByText(page, text) { - const element = await page.waitForFunction( - (expectedText) => { - const elements = document.querySelectorAll('*'); - return Array.from(elements).find(element => - element.textContent?.trim() === expectedText - ); - }, - {}, - text - ); - return element; +@Language("JavaScript") +val testProgressBarScript = """ + const puppeteer = require('puppeteer'); + async function testNavigation() { + const browser = await puppeteer.connect({ + browserURL: "http://localhost:9222" + }) + try { + const pages = await browser.pages() + for(const page of pages) { + const contents = await page.evaluate(el => el.innerHTML, await page.${'$'}(':root')); + const element = await page.${'$'}('.mynah-chat-prompt-input') + if(element) { + const elements = await page.${'$'}${'$'}('.mynah-chat-command-selector-command'); + const attr = await Promise.all( + elements.map(elem => elem.evaluate(el => el.getAttribute('command'))) + ); + await page.type('.mynah-chat-prompt-input', '/test') + await page.keyboard.press('Enter'); + await page.keyboard.press('Enter'); + + try { + await waitForElementWithText(page, "Q - Test") + console.log("new tab opened") + await page.waitForFunction( + (expectedText) => { + const pageContent = document.body.textContent || ''; + return pageContent.includes(expectedText); + }, + { + timeout: 4000000 + }, + "Generating unit tests" + ); + + console.log("Progress bar text displayed") + + await page.waitForFunction( + () => { + const button = document.querySelector('button[action-id="utg_view_diff"]'); + return button && button.isEnabled !== false && button.disabled !== true; + }, + { timeout: 4000000 } + ); + + console.log("Test generation completed successfully") + + } catch (e) { + console.log("Test failed") + console.log(e) + throw e + } + } + } + } finally { + await browser.close(); + } + } + + $waitForElementWithTextFunction + + $waitAndGetElementByTextFunction + + testNavigation().catch(console.error); +""".trimIndent() + +@Language("JavaScript") +val testCancelButtonScript = """ + const puppeteer = require('puppeteer'); + async function testNavigation() { + const browser = await puppeteer.connect({ + browserURL: "http://localhost:9222" + }) + try { + const pages = await browser.pages() + for(const page of pages) { + const contents = await page.evaluate(el => el.innerHTML, await page.${'$'}(':root')); + const element = await page.${'$'}('.mynah-chat-prompt-input') + if(element) { + const elements = await page.${'$'}${'$'}('.mynah-chat-command-selector-command'); + const attr = await Promise.all( + elements.map(elem => elem.evaluate(el => el.getAttribute('command'))) + ); + await page.type('.mynah-chat-prompt-input', '/test') + await page.keyboard.press('Enter'); + await page.keyboard.press('Enter'); + + try { + await waitForElementWithText(page, "Q - Test") + console.log("new tab opened") + await page.evaluate(() => { + const acknowledgeButton = document.querySelector('button[action-id=amazonq-disclaimer-acknowledge-button-id]'); + if (acknowledgeButton) { + acknowledgeButton.click(); + } + }); + + + + await page.waitForFunction( + (expectedText) => { + const pageContent = document.body.textContent || ''; + return pageContent.includes(expectedText); + }, + { + timeout: 4000000 + }, + "Generating unit tests" + ); + + console.log("Progress bar text displayed") + + + const cancelButton = await waitAndGetElementByText(page, "Cancel"); + console.log("Cancel button found") + + + await cancelButton.evaluate(button => button.click()) + console.log("Cancel button clicked") + + + await page.waitForFunction( + (expectedText) => { + const pageContent = document.body.textContent || ''; + return pageContent.includes(expectedText); + }, + { + timeout: 4000000 + }, + "Unit test generation cancelled." + ); + + console.log("Test generation cancelled successfully") + + await page.waitForFunction( + () => { + const inputElement = document.querySelector('.mynah-chat-prompt-input'); + return inputElement && !inputElement.disabled; + }, + { timeout: 4000000 } + ); + + console.log("Input field re-enabled after cancellation") + + } catch (e) { + console.log("Test failed") + console.log(e) + throw e + } + } + } + } finally { + await browser.close(); + } + } + + $waitForElementWithTextFunction + + $waitAndGetElementByTextFunction + testNavigation().catch(console.error); +""".trimIndent() + +@Language("JavaScript") +val testDocumentationErrorScript = """ + const puppeteer = require('puppeteer'); + async function testNavigation() { + const browser = await puppeteer.connect({ + browserURL: "http://localhost:9222" + }) + try { + const pages = await browser.pages() + for(const page of pages) { + const contents = await page.evaluate(el => el.innerHTML, await page.${'$'}(':root')); + const element = await page.${'$'}('.mynah-chat-prompt-input') + if(element) { + const elements = await page.${'$'}${'$'}('.mynah-chat-command-selector-command'); + const attr = await Promise.all( + elements.map(elem => elem.evaluate(el => el.getAttribute('command'))) + ); + + await page.type('.mynah-chat-prompt-input', '/test generate documentation for this file') + await page.keyboard.press('Enter'); + + try { + await waitForElementWithText(page, "Q - Test") + console.log("new tab opened") + + await page.evaluate(() => { + const acknowledgeButton = document.querySelector('button[action-id=amazonq-disclaimer-acknowledge-button-id]'); + if (acknowledgeButton) { + acknowledgeButton.click(); + } + }); + await page.waitForFunction( + (expectedText) => { + const pageContent = document.body.textContent || ''; + return pageContent.includes(expectedText); + }, + { + timeout: 4000000 + }, + "I apologize, but I couldn't process your /test instruction" + ); + + console.log("Error message displayed correctly") + + await page.waitForFunction( + () => { + const inputElement = document.querySelector('.mynah-chat-prompt-input'); + return inputElement && !inputElement.disabled; + }, + { timeout: 4000000 } + ); + + console.log("Input field re-enabled after error") + + } catch (e) { + console.log("Test failed") + console.log(e) + throw e + } + } + } + } finally { + await browser.close(); + } + } + + $waitForElementWithTextFunction + + $waitAndGetElementByTextFunction + + testNavigation().catch(console.error); +""".trimIndent() + +@Language("JavaScript") +val testRemoveFunctionErrorScript = """ + const puppeteer = require('puppeteer'); + async function testNavigation() { + const browser = await puppeteer.connect({ + browserURL: "http://localhost:9222" + }) + try { + const pages = await browser.pages() + for(const page of pages) { + const contents = await page.evaluate(el => el.innerHTML, await page.${'$'}(':root')); + const element = await page.${'$'}('.mynah-chat-prompt-input') + if(element) { + const elements = await page.${'$'}${'$'}('.mynah-chat-command-selector-command'); + const attr = await Promise.all( + elements.map(elem => elem.evaluate(el => el.getAttribute('command'))) + ); + + await page.type('.mynah-chat-prompt-input', '/test remove multiply function') + await page.keyboard.press('Enter'); + + try { + await waitForElementWithText(page, "Q - Test") + console.log("new tab opened") + await page.evaluate(() => { + const acknowledgeButton = document.querySelector('button[action-id=amazonq-disclaimer-acknowledge-button-id]'); + if (acknowledgeButton) { + acknowledgeButton.click(); + } + }); + + await page.waitForFunction( + (expectedText) => { + const pageContent = document.body.textContent || ''; + return pageContent.includes(expectedText); + }, + { + timeout: 4000000 + }, + "I apologize, but I couldn't process your /test instruction." + ); + + console.log("Error message displayed correctly") + await page.waitForFunction( + () => { + const inputElement = document.querySelector('.mynah-chat-prompt-input'); + return inputElement && !inputElement.disabled; + }, + { timeout: 4000000 } + ); + + console.log("Input field re-enabled after error") + + } catch (e) { + console.log("Test failed") + console.log(e) + throw e + } + } + } + } finally { + await browser.close(); + } } + + $waitForElementWithTextFunction + + $waitAndGetElementByTextFunction + + testNavigation().catch(console.error); +""".trimIndent() + +@Language("JavaScript") +val testMethodNotFoundErrorScript = """ + const puppeteer = require('puppeteer'); + async function testNavigation() { + const browser = await puppeteer.connect({ + browserURL: "http://localhost:9222" + }) + try { + const pages = await browser.pages() + for(const page of pages) { + const contents = await page.evaluate(el => el.innerHTML, await page.${'$'}(':root')); + const element = await page.${'$'}('.mynah-chat-prompt-input') + if(element) { + const elements = await page.${'$'}${'$'}('.mynah-chat-command-selector-command'); + const attr = await Promise.all( + elements.map(elem => elem.evaluate(el => el.getAttribute('command'))) + ); + + await page.type('.mynah-chat-prompt-input', '/test generate tests for zipping function') + await page.keyboard.press('Enter'); + + try { + await waitForElementWithText(page, "Q - Test") + console.log("new tab opened") + await page.evaluate(() => { + const acknowledgeButton = document.querySelector('button[action-id=amazonq-disclaimer-acknowledge-button-id]'); + if (acknowledgeButton) { + acknowledgeButton.click(); + } + }); + + await page.waitForFunction( + (expectedText) => { + const pageContent = document.body.textContent || ''; + return pageContent.includes(expectedText); + }, + { + timeout: 4000000 + }, + "I apologize, but I could not find the specified class" + ); + + console.log("Error message displayed correctly") + await page.waitForFunction( + () => { + const inputElement = document.querySelector('.mynah-chat-prompt-input'); + return inputElement && !inputElement.disabled; + }, + { timeout: 4000000 } + ); + + console.log("Input field re-enabled after error") + + + } catch (e) { + console.log("Test failed") + console.log(e) + throw e + } + } + } + } finally { + await browser.close(); + } + } + + $waitForElementWithTextFunction + + $waitAndGetElementByTextFunction + testNavigation().catch(console.error); """.trimIndent() diff --git a/ui-tests-starter/tst-243+/software/aws/toolkits/jetbrains/uitests/testTests/QTestGenerationChatTestUtils.kt b/ui-tests-starter/tst-243+/software/aws/toolkits/jetbrains/uitests/testTests/QTestGenerationChatTestUtils.kt new file mode 100644 index 00000000000..ab44eeb1b8c --- /dev/null +++ b/ui-tests-starter/tst-243+/software/aws/toolkits/jetbrains/uitests/testTests/QTestGenerationChatTestUtils.kt @@ -0,0 +1,41 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.uitests.testTests + +/** + * JavaScript function to wait for an element with specific text to appear on the page + */ +val waitForElementWithTextFunction = """ + async function waitForElementWithText(page, text) { + await page.waitForFunction( + (expectedText) => { + const elements = document.querySelectorAll('*'); + return Array.from(elements).find(element => + element.textContent?.trim() === expectedText + ); + }, + {}, + text + ); + } +""".trimIndent() + +/** + * JavaScript function to wait for an element with specific text to appear and return it + */ +val waitAndGetElementByTextFunction = """ + async function waitAndGetElementByText(page, text) { + const element = await page.waitForFunction( + (expectedText) => { + const elements = document.querySelectorAll('*'); + return Array.from(elements).find(element => + element.textContent?.trim() === expectedText + ); + }, + {}, + text + ); + return element; + } +""".trimIndent()