From 2a2ead171901808000442e86a7e01f67e481924d Mon Sep 17 00:00:00 2001 From: Josh Pinkney Date: Fri, 13 Dec 2024 09:13:01 -0500 Subject: [PATCH] tests(amazonq): welcome page and explore agents page tests --- packages/amazonq/test/e2e/amazonq/assert.ts | 30 ++++++ .../amazonq/test/e2e/amazonq/explore.test.ts | 45 ++++++++ .../test/e2e/amazonq/framework/framework.ts | 32 +++++- .../test/e2e/amazonq/framework/messenger.ts | 26 ++++- .../amazonq/test/e2e/amazonq/welcome.test.ts | 101 ++++++++++++++++++ packages/core/src/amazonq/index.ts | 3 +- 6 files changed, 227 insertions(+), 10 deletions(-) create mode 100644 packages/amazonq/test/e2e/amazonq/assert.ts create mode 100644 packages/amazonq/test/e2e/amazonq/explore.test.ts create mode 100644 packages/amazonq/test/e2e/amazonq/welcome.test.ts diff --git a/packages/amazonq/test/e2e/amazonq/assert.ts b/packages/amazonq/test/e2e/amazonq/assert.ts new file mode 100644 index 00000000000..7bc7bb2c22e --- /dev/null +++ b/packages/amazonq/test/e2e/amazonq/assert.ts @@ -0,0 +1,30 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import assert from 'assert' +import { Messenger } from './framework/messenger' + +export function assertQuickActions(tab: Messenger, commands: string[]) { + const commandGroup = tab + .getCommands() + .map((groups) => groups.commands) + .flat() + if (!commandGroup) { + assert.fail(`Could not find commands for ${tab.tabID}`) + } + + const commandNames = commandGroup.map((cmd) => cmd.command) + + const missingCommands = [] + for (const command of commands) { + if (!commandNames.includes(command)) { + missingCommands.push(command) + } + } + + if (missingCommands.length > 0) { + assert.fail(`Could not find commands: ${missingCommands.join(', ')} for ${tab.tabID}`) + } +} diff --git a/packages/amazonq/test/e2e/amazonq/explore.test.ts b/packages/amazonq/test/e2e/amazonq/explore.test.ts new file mode 100644 index 00000000000..6833849617d --- /dev/null +++ b/packages/amazonq/test/e2e/amazonq/explore.test.ts @@ -0,0 +1,45 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import assert from 'assert' +import sinon from 'sinon' +import { qTestingFramework } from './framework/framework' +import { Messenger } from './framework/messenger' + +describe('Amazon Q Explore page', function () { + let framework: qTestingFramework + let tab: Messenger + + beforeEach(() => { + framework = new qTestingFramework('agentWalkthrough', true, [], 0) + const welcomeTab = framework.getTabs()[0] + welcomeTab.clickInBodyButton('explore') + + // Find the new explore tab + const exploreTab = framework.findTab('Explore') + if (!exploreTab) { + assert.fail('Explore tab not found') + } + tab = exploreTab + }) + + afterEach(() => { + framework.removeTab(tab.tabID) + framework.dispose() + sinon.restore() + }) + + // TODO refactor page objects so we can associate clicking user guides with actual urls + // TODO test that clicking quick start changes the tab title, etc + it('should have correct button IDs', async () => { + const features = ['featuredev', 'testgen', 'doc', 'review', 'gumby'] + + features.forEach((feature, index) => { + const buttons = (tab.getStore().chatItems ?? [])[index].buttons ?? [] + assert.deepStrictEqual(buttons[0].id, `user-guide-${feature}`) + assert.deepStrictEqual(buttons[1].id, `quick-start-${feature}`) + }) + }) +}) diff --git a/packages/amazonq/test/e2e/amazonq/framework/framework.ts b/packages/amazonq/test/e2e/amazonq/framework/framework.ts index b65e8b184f7..b39dbe4314b 100644 --- a/packages/amazonq/test/e2e/amazonq/framework/framework.ts +++ b/packages/amazonq/test/e2e/amazonq/framework/framework.ts @@ -8,6 +8,7 @@ import { injectJSDOM } from './jsdomInjector' // This needs to be ran before all other imports so that mynah ui gets loaded inside of jsdom injectJSDOM() +import assert from 'assert' import * as vscode from 'vscode' import { MynahUI, MynahUIProps } from '@aws/mynah-ui' import { DefaultAmazonQAppInitContext, TabType, createMynahUI } from 'aws-core-vscode/amazonq' @@ -24,7 +25,12 @@ export class qTestingFramework { lastEventId: string = '' - constructor(featureName: TabType, amazonQEnabled: boolean, featureConfigsSerialized: [string, FeatureContext][]) { + constructor( + featureName: TabType, + amazonQEnabled: boolean, + featureConfigsSerialized: [string, FeatureContext][], + welcomeCount = 0 + ) { /** * Instantiate the UI and override the postMessage to publish using the app message * publishers directly. @@ -44,7 +50,8 @@ export class qTestingFramework { }, }, amazonQEnabled, - featureConfigsSerialized + featureConfigsSerialized, + welcomeCount ) this.mynahUI = ui.mynahUI this.mynahUIProps = (this.mynahUI as any).props @@ -79,18 +86,35 @@ export class qTestingFramework { * functionality against a specific tab */ public createTab(options?: MessengerOptions) { - const newTabID = this.mynahUI.updateStore('', {}) + const oldTabs = Object.keys(this.mynahUI.getAllTabs()) + + // simulate pressing the new tab button + ;(document.querySelectorAll('.mynah-nav-tabs-wrapper > button.mynah-button')[0] as HTMLButtonElement).click() + const newTabs = Object.keys(this.mynahUI.getAllTabs()) + + const newTabID = newTabs.find((tab) => !oldTabs.includes(tab)) if (!newTabID) { - throw new Error('Could not create tab id') + assert.fail('Could not find new tab') } + return new Messenger(newTabID, this.mynahUIProps, this.mynahUI, options) } + public getTabs() { + const tabs = this.mynahUI.getAllTabs() + return Object.entries(tabs).map(([tabId]) => new Messenger(tabId, this.mynahUIProps, this.mynahUI)) + } + + public findTab(title: string) { + return Object.values(this.getTabs()).find((tab) => tab.getStore().tabTitle === title) + } + public removeTab(tabId: string) { this.mynahUI.removeTab(tabId, this.lastEventId) } public dispose() { vscode.Disposable.from(...this.disposables).dispose() + this.mynahUI.destroy() } } diff --git a/packages/amazonq/test/e2e/amazonq/framework/messenger.ts b/packages/amazonq/test/e2e/amazonq/framework/messenger.ts index 80e68f7481e..0aa363d2e3d 100644 --- a/packages/amazonq/test/e2e/amazonq/framework/messenger.ts +++ b/packages/amazonq/test/e2e/amazonq/framework/messenger.ts @@ -51,12 +51,28 @@ export class Messenger { } const lastChatItem = this.getChatItems().pop() - const option = lastChatItem?.followUp?.options?.filter((option) => option.type === type) - if (!option?.length || option.length > 1) { - assert.fail('Could not find follow up option') + const followupOption = lastChatItem?.followUp?.options?.filter((option) => option.type === type) + if (followupOption && followupOption.length > 0) { + this.mynahUIProps.onFollowUpClicked(this.tabID, lastChatItem?.messageId ?? '', followupOption[0]) + return } - this.mynahUIProps.onFollowUpClicked(this.tabID, lastChatItem?.messageId ?? '', option[0]) + assert.fail(`Could not find a button with id ${type} on tabID: ${this.tabID}`) + } + + clickInBodyButton(type: string) { + if (!this.mynahUIProps.onInBodyButtonClicked) { + assert.fail('onInBodyButtonClicked must be defined to use it in the tests') + } + + const lastChatItem = this.getChatItems().pop() + const followupButton = lastChatItem?.buttons?.filter((option) => option.id === type) + if (followupButton && followupButton.length > 0) { + this.mynahUIProps.onInBodyButtonClicked(this.tabID, lastChatItem?.messageId ?? '', followupButton[0]) + return + } + + assert.fail(`Could not find a button with id ${type} on tabID: ${this.tabID}`) } clickCustomFormButton(action: { id: string; text?: string; formItemValues?: Record }) { @@ -193,7 +209,7 @@ export class Messenger { } } - private getStore(): MynahUIDataModel { + getStore(): MynahUIDataModel { const store = this.mynahUI.getAllTabs()[this.tabID].store if (!store) { assert.fail(`${this.tabID} does not have a store`) diff --git a/packages/amazonq/test/e2e/amazonq/welcome.test.ts b/packages/amazonq/test/e2e/amazonq/welcome.test.ts new file mode 100644 index 00000000000..1e15d13381a --- /dev/null +++ b/packages/amazonq/test/e2e/amazonq/welcome.test.ts @@ -0,0 +1,101 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import assert from 'assert' +import { qTestingFramework } from './framework/framework' +import sinon from 'sinon' +import { Messenger } from './framework/messenger' +import { MynahUIDataModel } from '@aws/mynah-ui' +import { assertQuickActions } from './assert' + +describe('Amazon Q Welcome page', function () { + let framework: qTestingFramework + let tab: Messenger + let store: MynahUIDataModel + + const availableCommands = ['/dev', '/test', '/review', '/doc', '/transform'] + + beforeEach(() => { + framework = new qTestingFramework('welcome', true, [], 0) + tab = framework.getTabs()[0] // use the default tab that gets created + store = tab.getStore() + }) + + afterEach(() => { + framework.removeTab(tab.tabID) + framework.dispose() + sinon.restore() + }) + + it(`Shows quick actions: ${availableCommands.join(', ')}`, async () => { + assertQuickActions(tab, availableCommands) + }) + + it('Shows @workspace', async () => { + assert.deepStrictEqual( + store.contextCommands + ?.map((x) => x.commands) + .flat() + .map((x) => x.command), + ['@workspace'] + ) + }) + + describe('shows 3 times', async () => { + it('new tabs', () => { + framework.createTab() + framework.createTab() + framework.createTab() + framework.createTab() + + let welcomeCount = 0 + framework.getTabs().forEach((tab) => { + if (tab.getStore().tabTitle === 'Welcome to Q') { + welcomeCount++ + } + }) + // 3 welcome tabs + assert.deepStrictEqual(welcomeCount, 3) + + // 2 normal tabs + assert.deepStrictEqual(framework.getTabs().length - welcomeCount, 2) + }) + + it('new windows', () => { + // check the initial window + assert.deepStrictEqual(store.tabTitle, 'Welcome to Q') + framework.dispose() + + // check when theres already been two welcome tabs shown + framework = new qTestingFramework('welcome', true, [], 2) + const secondStore = framework.getTabs()[0].getStore() + assert.deepStrictEqual(secondStore.tabTitle, 'Welcome to Q') + framework.dispose() + + // check when theres already been three welcome tabs shown + framework = new qTestingFramework('welcome', true, [], 3) + const thirdStore = framework.getTabs()[0].getStore() + assert.deepStrictEqual(thirdStore.tabTitle, 'Chat') + framework.dispose() + }) + }) + + describe('Welcome actions', () => { + it('explore', () => { + tab.clickInBodyButton('explore') + + // explore opens in a new tab + const exploreTabStore = framework.findTab('Explore')?.getStore() + assert.strictEqual(exploreTabStore?.tabTitle, 'Explore') + }) + + it('quick-start', async () => { + tab.clickInBodyButton('quick-start') + + // clicking quick start opens in the current tab and changes the compact mode + assert.deepStrictEqual(tab.getStore().compactMode, false) + }) + }) +}) diff --git a/packages/core/src/amazonq/index.ts b/packages/core/src/amazonq/index.ts index 5bd20e4dfd0..584042a8462 100644 --- a/packages/core/src/amazonq/index.ts +++ b/packages/core/src/amazonq/index.ts @@ -55,11 +55,12 @@ export function createMynahUI( ideApi: any, amazonQEnabled: boolean, featureConfigsSerialized: [string, FeatureContext][], + welcomeCount: number, disabledCommands?: string[] ) { if (typeof window !== 'undefined') { const mynahUI = require('./webview/ui/main') - return mynahUI.createMynahUI(ideApi, amazonQEnabled, featureConfigsSerialized, true, disabledCommands) + return mynahUI.createMynahUI(ideApi, amazonQEnabled, featureConfigsSerialized, welcomeCount, disabledCommands) } throw new Error('Not implemented for node') }