From 0c78b1c7f015edad1c17695f192407d451cd6b90 Mon Sep 17 00:00:00 2001 From: Maksim Nartov <81012703+nartovm@users.noreply.github.com> Date: Fri, 14 Feb 2025 17:41:09 +0100 Subject: [PATCH] feat(chat-e2e): new conversation tests (#3114) Co-authored-by: Irina_Kartun --- .../src/assertions/base/baseAssertion.ts | 14 +- .../sharedWithMeConversationAssertion.ts | 4 + .../src/assertions/sideBarAssertion.ts | 22 +- .../src/assertions/sideBarEntityAssertion.ts | 5 +- apps/chat-e2e/src/core/dialFixtures.ts | 45 +- apps/chat-e2e/src/core/dialOverlayFixtures.ts | 12 +- .../src/core/dialSharedWithMeFixtures.ts | 28 +- apps/chat-e2e/src/core/localStorageManager.ts | 13 + .../src/testData/api/itemApiHelper.ts | 2 +- .../chat-e2e/src/testData/expectedMessages.ts | 1 + apps/chat-e2e/src/tests/entityIcon.test.ts | 12 +- .../src/tests/newConversation.test.ts | 540 ++++++++++++++++++ apps/chat-e2e/src/ui/pages/basePage.ts | 36 +- apps/chat-e2e/src/ui/pages/dialHomePage.ts | 40 ++ .../src/ui/selectors/sideBarSelectors.ts | 2 + .../entityTree/sidebar/sideBarEntitiesTree.ts | 10 +- .../marketplace/marketplaceContainer.ts | 12 +- apps/chat-e2e/src/ui/webElements/sideBar.ts | 8 + .../src/ui/webElements/talkToAgentDialog.ts | 17 +- .../src/ui/webElements/temperatureSlider.ts | 7 +- apps/chat/src/components/Common/NoData.tsx | 6 +- 21 files changed, 790 insertions(+), 46 deletions(-) create mode 100644 apps/chat-e2e/src/assertions/sharedWithMeConversationAssertion.ts create mode 100644 apps/chat-e2e/src/tests/newConversation.test.ts diff --git a/apps/chat-e2e/src/assertions/base/baseAssertion.ts b/apps/chat-e2e/src/assertions/base/baseAssertion.ts index cf7b1acbec..e7e5732c2e 100644 --- a/apps/chat-e2e/src/assertions/base/baseAssertion.ts +++ b/apps/chat-e2e/src/assertions/base/baseAssertion.ts @@ -202,22 +202,22 @@ export class BaseAssertion { public async assertElementsCount( element: BaseElement | Locator, expectedCount: number, + expectedMessage?: string, ) { const elementsCount = element instanceof BaseElement ? await element.getElementsCount() : await element.count(); expect - .soft(elementsCount, ExpectedMessages.elementsCountIsValid) - .toBe(expectedCount); - } - public async assertCount(expectedCount: number, actualCount: number) { - expect - .soft(actualCount, ExpectedMessages.elementsCountIsValid) + .soft( + elementsCount, + expectedMessage ?? ExpectedMessages.elementsCountIsValid, + ) .toBe(expectedCount); } + public assertValue( - actualValue: string | number | undefined, + actualValue: string | number | undefined | null, expectedValue: string | number, expectedMessage?: string, ) { diff --git a/apps/chat-e2e/src/assertions/sharedWithMeConversationAssertion.ts b/apps/chat-e2e/src/assertions/sharedWithMeConversationAssertion.ts new file mode 100644 index 0000000000..cda147a6d7 --- /dev/null +++ b/apps/chat-e2e/src/assertions/sharedWithMeConversationAssertion.ts @@ -0,0 +1,4 @@ +import { SideBarEntityAssertion } from '@/src/assertions/sideBarEntityAssertion'; +import { SharedWithMeConversationsTree } from '@/src/ui/webElements/entityTree'; + +export class SharedWithMeConversationAssertion extends SideBarEntityAssertion {} diff --git a/apps/chat-e2e/src/assertions/sideBarAssertion.ts b/apps/chat-e2e/src/assertions/sideBarAssertion.ts index 2f466db17f..d6bd81f8bb 100644 --- a/apps/chat-e2e/src/assertions/sideBarAssertion.ts +++ b/apps/chat-e2e/src/assertions/sideBarAssertion.ts @@ -1,11 +1,13 @@ +import { BaseAssertion } from '@/src/assertions/base/baseAssertion'; import { ElementState, ExpectedMessages } from '@/src/testData'; import { SideBar } from '@/src/ui/webElements'; import { expect } from '@playwright/test'; -export class SideBarAssertion { +export class SideBarAssertion extends BaseAssertion { readonly sideBar: SideBar; constructor(sideBar: SideBar) { + super(); this.sideBar = sideBar; } @@ -19,4 +21,22 @@ export class SideBarAssertion { .soft(buttonLocator, ExpectedMessages.buttonIsNotVisible) .toBeHidden(); } + + public async assertNoDataInConversations() { + await this.assertElementState( + this.sideBar.noDataIcon, + 'visible', + ExpectedMessages.entityIsVisible, + ); + await this.assertElementState( + this.sideBar.noDataPlaceholder, + 'visible', + ExpectedMessages.entityIsVisible, + ); + await this.assertElementText( + this.sideBar.noDataPlaceholder, + 'No data', + ExpectedMessages.noData, + ); + } } diff --git a/apps/chat-e2e/src/assertions/sideBarEntityAssertion.ts b/apps/chat-e2e/src/assertions/sideBarEntityAssertion.ts index ad115ff876..cdd46e6936 100644 --- a/apps/chat-e2e/src/assertions/sideBarEntityAssertion.ts +++ b/apps/chat-e2e/src/assertions/sideBarEntityAssertion.ts @@ -66,7 +66,10 @@ export class SideBarEntityAssertion< actualCount?: number, ) { if (actualCount === undefined) { - await this.assertElementsCount(this.sideBarEntitiesTree, expectedCount); + await this.assertElementsCount( + this.sideBarEntitiesTree.treeEntityNames, + expectedCount, + ); } else { this.assertValue( expectedCount, diff --git a/apps/chat-e2e/src/core/dialFixtures.ts b/apps/chat-e2e/src/core/dialFixtures.ts index ced887a203..1170e3eca9 100644 --- a/apps/chat-e2e/src/core/dialFixtures.ts +++ b/apps/chat-e2e/src/core/dialFixtures.ts @@ -59,6 +59,7 @@ import { MessageTemplateModalAssertion } from '@/src/assertions/messageTemplateM import { RenameConversationModalAssertion } from '@/src/assertions/renameConversationModalAssertion'; import { SelectFolderModalAssertion } from '@/src/assertions/selectFolderModalAssertion'; import { SettingsModalAssertion } from '@/src/assertions/settingsModalAssertion'; +import { SharedWithMeConversationAssertion } from '@/src/assertions/sharedWithMeConversationAssertion'; import { SideBarEntityAssertion } from '@/src/assertions/sideBarEntityAssertion'; import test from '@/src/core/baseFixtures'; import { isApiStorageType } from '@/src/hooks/global-setup'; @@ -98,6 +99,8 @@ import { PromptsToPublishTree, PromptsTree, PublishFolder, + SharedFolderConversations, + SharedWithMeConversationsTree, } from '@/src/ui/webElements/entityTree'; import { OrganizationPromptsTree } from '@/src/ui/webElements/entityTree/sidebar/organizationPromptsTree'; import { ErrorPopup } from '@/src/ui/webElements/errorPopup'; @@ -219,6 +222,10 @@ const dialTest = test.extend<{ adminUserItemApiHelper: ItemApiHelper; adminApplicationApiHelper: ApplicationApiHelper; mainUserShareApiHelper: ShareApiHelper; + sharedWithMeConversations: SharedWithMeConversationsTree; + sharedWithMeConversationDropdownMenu: DropdownMenu; + sharedFolderConversations: SharedFolderConversations; + sharedWithMeFolderDropdownMenu: DropdownMenu; additionalUserShareApiHelper: ShareApiHelper; additionalUserItemApiHelper: ItemApiHelper; additionalSecondUserShareApiHelper: ShareApiHelper; @@ -290,6 +297,7 @@ const dialTest = test.extend<{ organizationFolderConversationAssertions: FolderAssertion; messageTemplateModalAssertion: MessageTemplateModalAssertion; agentVersionsDropdownMenuAssertion: MenuAssertion; + sharedWithMeConversationAssertion: SharedWithMeConversationAssertion; }>({ beforeTestCleanup: [ async ({ dataInjector, fileApiHelper }, use) => { @@ -299,6 +307,39 @@ const dialTest = test.extend<{ }, { scope: 'test', auto: true }, ], + sharedWithMeConversationAssertion: async ( + { sharedWithMeConversations }, + use, + ) => { + const sharedWithMeConversationAssertion = + new SharedWithMeConversationAssertion(sharedWithMeConversations); + await use(sharedWithMeConversationAssertion); + }, + sharedWithMeFolderDropdownMenu: async ( + { sharedFolderConversations }, + use, + ) => { + const sharedWithMeFolderDropdownMenu = + sharedFolderConversations.getDropdownMenu(); + await use(sharedWithMeFolderDropdownMenu); + }, + sharedFolderConversations: async ({ chatBar }, use) => { + const sharedFolderConversations = chatBar.getSharedFolderConversations(); + await use(sharedFolderConversations); + }, + sharedWithMeConversations: async ({ chatBar }, use) => { + const sharedWithMeConversations = + chatBar.getSharedWithMeConversationsTree(); + await use(sharedWithMeConversations); + }, + sharedWithMeConversationDropdownMenu: async ( + { sharedWithMeConversations }, + use, + ) => { + const sharedWithMeConversationDropdownMenu = + sharedWithMeConversations.getDropdownMenu(); + await use(sharedWithMeConversationDropdownMenu); + }, // eslint-disable-next-line no-empty-pattern storageState: async ({}, use) => { await use(stateFilePath(+process.env.TEST_PARALLEL_INDEX!)); @@ -469,8 +510,8 @@ const dialTest = test.extend<{ chatBar.getOrganizationFolderConversations(); await use(organizationFolderConversations); }, - talkToAgentDialog: async ({ page }, use) => { - const talkToAgentDialog = new TalkToAgentDialog(page); + talkToAgentDialog: async ({ page, modelApiHelper }, use) => { + const talkToAgentDialog = new TalkToAgentDialog(page, modelApiHelper); await use(talkToAgentDialog); }, talkToAgents: async ({ talkToAgentDialog }, use) => { diff --git a/apps/chat-e2e/src/core/dialOverlayFixtures.ts b/apps/chat-e2e/src/core/dialOverlayFixtures.ts index 76ee7a4c8f..e9ff2d2ae6 100644 --- a/apps/chat-e2e/src/core/dialOverlayFixtures.ts +++ b/apps/chat-e2e/src/core/dialOverlayFixtures.ts @@ -36,6 +36,7 @@ import { FileApiHelper, IconApiHelper, ItemApiHelper, + ModelApiHelper, PublicationApiHelper, ShareApiHelper, } from '@/src/testData/api'; @@ -86,6 +87,7 @@ const dialOverlayTest = test.extend<{ overlayPublicationApiHelper: PublicationApiHelper; overlayFileApiHelper: FileApiHelper; overlayIconApiHelper: IconApiHelper; + overlayModelApiHelper: ModelApiHelper; overlayApiInjector: ApiInjector; overlayDataInjector: DataInjectorInterface; overlayBaseAssertion: BaseAssertion; @@ -208,6 +210,10 @@ const dialOverlayTest = test.extend<{ const overlayIconApiHelper = new IconApiHelper(request); await use(overlayIconApiHelper); }, + overlayModelApiHelper: async ({ request }, use) => { + const overlayModelApiHelper = new ModelApiHelper(request); + await use(overlayModelApiHelper); + }, overlayApiInjector: async ({ overlayItemApiHelper }, use) => { const overlayApiInjector = new ApiInjector(overlayItemApiHelper); await use(overlayApiInjector); @@ -235,9 +241,13 @@ const dialOverlayTest = test.extend<{ const overlayApiAssertion = new ApiAssertion(); await use(overlayApiAssertion); }, - overlayTalkToAgentDialog: async ({ page, overlayHomePage }, use) => { + overlayTalkToAgentDialog: async ( + { page, overlayHomePage, overlayModelApiHelper }, + use, + ) => { const overlayTalkToAgentDialog = new TalkToAgentDialog( page, + overlayModelApiHelper, overlayHomePage.getOverlayContainer().getElementLocator(), ); await use(overlayTalkToAgentDialog); diff --git a/apps/chat-e2e/src/core/dialSharedWithMeFixtures.ts b/apps/chat-e2e/src/core/dialSharedWithMeFixtures.ts index 3da3cfaa92..252e3acb55 100644 --- a/apps/chat-e2e/src/core/dialSharedWithMeFixtures.ts +++ b/apps/chat-e2e/src/core/dialSharedWithMeFixtures.ts @@ -40,12 +40,13 @@ import { PromptListAssertion } from '@/src/assertions/promptListAssertion'; import { PromptModalAssertion } from '@/src/assertions/promptModalAssertion'; import { SendMessageAssertion } from '@/src/assertions/sendMessageAssertion'; import { SharedPromptPreviewModalAssertion } from '@/src/assertions/sharedPromptPreviewModalAssertion'; +import { SharedWithMeConversationAssertion } from '@/src/assertions/sharedWithMeConversationAssertion'; import { SharedWithMePromptsAssertion } from '@/src/assertions/sharedWithMePromptsAssertion'; import { VariableModalAssertion } from '@/src/assertions/variableModalAssertion'; import dialTest, { stateFilePath } from '@/src/core/dialFixtures'; import { LocalStorageManager } from '@/src/core/localStorageManager'; import { isApiStorageType } from '@/src/hooks/global-setup'; -import { FileApiHelper } from '@/src/testData/api'; +import { FileApiHelper, ModelApiHelper } from '@/src/testData/api'; import { ApiInjector } from '@/src/testData/injector/apiInjector'; import { BrowserStorageInjector } from '@/src/testData/injector/browserStorageInjector'; import { DataInjectorInterface } from '@/src/testData/injector/dataInjectorInterface'; @@ -79,6 +80,7 @@ const dialSharedWithMeTest = dialTest.extend<{ additionalShareUserConversationSettingsModal: ConversationSettingsModal; additionalShareUserAgentSettings: AgentSettings; additionalShareUserChatHeader: ChatHeader; + additionalShareUserModelApiHelper: ModelApiHelper; additionalShareUserTalkToAgentDialog: TalkToAgentDialog; additionalShareUserChatMessages: ChatMessages; additionalShareUserSendMessage: SendMessage; @@ -106,6 +108,7 @@ const dialSharedWithMeTest = dialTest.extend<{ additionalShareUserFileApiHelper: FileApiHelper; additionalShareUserPromptModalDialog: PromptModalDialog; additionalShareUserSharedWithMePromptAssertion: SharedWithMePromptsAssertion; + additionalShareUserSharedWithMeConversationAssertion: SharedWithMeConversationAssertion; additionalShareUserSharedPromptPreviewModalAssertion: SharedPromptPreviewModalAssertion; additionalShareUserSendMessageAssertion: SendMessageAssertion; additionalShareUserVariableModalAssertion: VariableModalAssertion; @@ -378,12 +381,23 @@ const dialSharedWithMeTest = dialTest.extend<{ additionalShareUserChat.getChatHeader(); await use(additionalShareUserChatHeader); }, + additionalShareUserModelApiHelper: async ( + { additionalShareUserRequestContext }, + use, + ) => { + const additionalShareUserModelApiHelper = new ModelApiHelper( + additionalShareUserRequestContext, + BucketUtil.getAdditionalShareUserBucket(), + ); + await use(additionalShareUserModelApiHelper); + }, additionalShareUserTalkToAgentDialog: async ( - { additionalShareUserPage }, + { additionalShareUserPage, additionalShareUserModelApiHelper }, use, ) => { const additionalShareUserTalkToAgentDialog = new TalkToAgentDialog( additionalShareUserPage, + additionalShareUserModelApiHelper, ); await use(additionalShareUserTalkToAgentDialog); }, @@ -535,6 +549,16 @@ const dialSharedWithMeTest = dialTest.extend<{ new SharedWithMePromptsAssertion(additionalShareUserSharedWithMePrompts); await use(additionalShareUserSharedWithMePromptAssertion); }, + additionalShareUserSharedWithMeConversationAssertion: async ( + { additionalShareUserSharedWithMeConversations }, + use, + ) => { + const additionalShareUserSharedWithMeConversationAssertion = + new SharedWithMeConversationAssertion( + additionalShareUserSharedWithMeConversations, + ); + await use(additionalShareUserSharedWithMeConversationAssertion); + }, additionalShareUserSharedPromptPreviewModalAssertion: async ( { additionalShareUserPromptPreviewModal }, use, diff --git a/apps/chat-e2e/src/core/localStorageManager.ts b/apps/chat-e2e/src/core/localStorageManager.ts index ea691a8f49..47881954ae 100644 --- a/apps/chat-e2e/src/core/localStorageManager.ts +++ b/apps/chat-e2e/src/core/localStorageManager.ts @@ -164,6 +164,19 @@ export class LocalStorageManager { ); } + async setLastConversationSettings(storageValue: string) { + await this.page.addInitScript( + (data) => { + const { storageKey, storageValue } = data; + localStorage.setItem(storageKey, storageValue); + }, + { + storageKey: 'lastConversationSettings', + storageValue: storageValue, + }, + ); + } + async setRecentAddonsIds(...addons: DialAIEntityModel[]) { await this.page.addInitScript( this.setRecentAddonsIdsKey(), diff --git a/apps/chat-e2e/src/testData/api/itemApiHelper.ts b/apps/chat-e2e/src/testData/api/itemApiHelper.ts index 672ba684d2..df42c92504 100644 --- a/apps/chat-e2e/src/testData/api/itemApiHelper.ts +++ b/apps/chat-e2e/src/testData/api/itemApiHelper.ts @@ -23,7 +23,7 @@ export class ItemApiHelper extends BaseApiHelper { await this.deleteBackendItem( ...conversations, ...prompts, - ...apps.filter((a) => a.name.startsWith(applicationNamePrefix)), + ...apps.filter((a) => a.name.includes(applicationNamePrefix)), ); } diff --git a/apps/chat-e2e/src/testData/expectedMessages.ts b/apps/chat-e2e/src/testData/expectedMessages.ts index 333ce94921..6bfb39a151 100644 --- a/apps/chat-e2e/src/testData/expectedMessages.ts +++ b/apps/chat-e2e/src/testData/expectedMessages.ts @@ -367,6 +367,7 @@ export enum ExpectedMessages { elementIsVisible = 'Elements is visible', elementIsNotVisible = 'Elements is not visible', noConversationIsSelected = 'No conversation is selected', + noData = 'No data in the conversation section', } export enum PublishingExpectedMessages { diff --git a/apps/chat-e2e/src/tests/entityIcon.test.ts b/apps/chat-e2e/src/tests/entityIcon.test.ts index 1097f9c6b3..7e23ef8382 100644 --- a/apps/chat-e2e/src/tests/entityIcon.test.ts +++ b/apps/chat-e2e/src/tests/entityIcon.test.ts @@ -33,6 +33,7 @@ dialTest( setTestIds('EPMRTC-1036', 'EPMRTC-1038'); const defaultModel = ModelsUtil.getDefaultModel()!; + let allExpectedEntities: DialAIEntityModel[]; await dialTest.step( 'Open initial screen and click "Go to my workspace" to view all available entities', @@ -43,17 +44,18 @@ dialTest( await dialHomePage.waitForPageLoaded(); await chat.changeAgentButton.click(); await talkToAgentDialog.goToMyWorkspace(); - await marketplaceContainer.goToMarketplaceHome(); + allExpectedEntities = ModelsUtil.getLatestOpenAIEntities( + await modelApiHelper.getModels(), + ); + await marketplaceContainer.goToMarketplaceHome( + allExpectedEntities.length, + ); }, ); await dialTest.step('Verify all entities have valid icons', async () => { - const allExpectedEntities = ModelsUtil.getLatestOpenAIEntities( - await modelApiHelper.getModels(), - ); const randomEntity = GeneratorUtil.randomArrayElement(allExpectedEntities); - await marketplaceAgents.waitForAgentByIndex(allExpectedEntities.length); const actualIcons = await marketplaceAgents.getAgentsIcons(); expect diff --git a/apps/chat-e2e/src/tests/newConversation.test.ts b/apps/chat-e2e/src/tests/newConversation.test.ts new file mode 100644 index 0000000000..5817946a11 --- /dev/null +++ b/apps/chat-e2e/src/tests/newConversation.test.ts @@ -0,0 +1,540 @@ +import dialTest from '@/src/core/dialFixtures'; +import dialSharedWithMeTest from '@/src/core/dialSharedWithMeFixtures'; +import { + ExpectedConstants, + ExpectedMessages, + MenuOptions, + MockedChatApiResponseBodies, +} from '@/src/testData'; +import { GeneratorUtil, ModelsUtil } from '@/src/utils'; +import { expect } from '@playwright/test'; + +dialTest( + 'Click on + resets all settings on new conversation. Change agent pop-up opens\n' + + 'Click on + resets all settings on new conversation. When temperature was changed in previous chat.', + async ({ + dialHomePage, + header, + chat, + agentSettings, + temperatureSlider, + addons, + talkToAgentDialog, + marketplacePage, + agentInfoAssertion, + setTestIds, + localStorageManager, + conversationSettingsModal, + iconApiHelper, + setIssueIds, + }) => { + setTestIds('EPMRTC-4717', 'EPMRTC-4920', 'EPMRTC-404', 'EPMRTC-403'); + setIssueIds('3116'); + const models = GeneratorUtil.randomArrayElements( + ModelsUtil.getLatestModels().filter( + (m) => + ModelsUtil.doesModelAllowSystemPrompt(m) && + ModelsUtil.doesModelAllowTemperature(m) && + ModelsUtil.doesModelAllowAddons(m) && + m.iconUrl !== undefined, + ), + 2, + ); + const addon = GeneratorUtil.randomArrayElement(ModelsUtil.getAddons()); + await localStorageManager.setRecentModelsIdsOnce(...models); + await localStorageManager.setRecentAddonsIds(addon); + await localStorageManager.setLastConversationSettings(''); + + await dialTest.step('Open Dial', async () => { + await dialHomePage.openHomePage({ + iconsToBeLoaded: [models[0].iconUrl!], + }); + await dialHomePage.waitForPageLoaded(); + await chat.getSendMessage().waitForState(); + }); + + const PROMPTS = { + DOG: 'Act like a dog', + CAT: 'Act like a cat', + }; + const TEMPERATURE = { + HIGH: '0.7', + LOW: '0.2', + }; + + await dialTest.step('Change settings and apply', async () => { + await chat.configureSettingsButton.click(); + await agentSettings.setSystemPrompt(PROMPTS.DOG); + await temperatureSlider.setTemperature(TEMPERATURE.HIGH); + await addons.selectAddon(addon.name); + await conversationSettingsModal.applyChangesButton.click(); + }); + + await dialTest.step( + 'Send a user message and click on the "New conversation" header button and check that the settings are changed, temperature is not changed', + async () => { + await dialHomePage.mockChatTextResponse( + MockedChatApiResponseBodies.simpleTextBody, + ); + await chat.sendRequestWithButton('test request'); + await header.createNewConversation(); + }, + ); + + await dialTest.step( + 'Check that the settings are reset, temperature is not changed after sending a message and starting a new conversation', + async () => { + await chat.configureSettingsButton.click(); + await agentInfoAssertion.assertElementText( + agentSettings.systemPrompt, + ExpectedConstants.emptyString, + ); + agentInfoAssertion.assertValue( + await temperatureSlider.getTemperature(), + TEMPERATURE.HIGH, + ExpectedMessages.temperatureIsValid, + ); + await agentInfoAssertion.assertElementsCount( + addons.selectedAddons, + 0, + ExpectedMessages.noAddonsSelected, + ); + await conversationSettingsModal.cancelButton.click(); + }, + ); + + await dialTest.step( + 'Change model and verify the correct model is selected', + async () => { + await chat.changeAgentButton.waitForState(); + await chat.configureSettingsButton.waitForState(); + await chat.changeAgentButton.click(); + await talkToAgentDialog.selectAgent(models[1], marketplacePage); + const expectedModelIcon = iconApiHelper.getEntityIcon(models[1]); + await agentInfoAssertion.assertAgentIcon(expectedModelIcon); + }, + ); + + await dialTest.step('Change settings and apply', async () => { + await chat.configureSettingsButton.click(); + await agentSettings.setSystemPrompt(PROMPTS.CAT); + await temperatureSlider.setTemperature(TEMPERATURE.LOW); + await addons.selectAddon(addon.name); + await conversationSettingsModal.applyChangesButton.click(); + }); + + await dialTest.step( + 'Verify settings are completely reset after not sending a message in a chat', + async () => { + await chat.configureSettingsButton.click(); + await agentInfoAssertion.assertElementText( + agentSettings.systemPrompt, + ExpectedConstants.emptyString, + ); + agentInfoAssertion.assertValue( + await temperatureSlider.getTemperature(), + TEMPERATURE.HIGH, + ExpectedMessages.temperatureIsValid, + ); + await agentInfoAssertion.assertElementsCount( + addons.selectedAddons, + 0, + ExpectedMessages.noAddonsSelected, + ); + }, + ); + }, +); + +dialSharedWithMeTest( + 'New conversation disappears, chat history is shown on the central part if to click on the chat with history\n' + + 'New conversation appears if user deletes focused Chat1. Chat2 stays unselected.\n' + + 'New conversation appears if user deletes focused chat. No data label appears instead.\n' + + 'Shared with me. Delete shared chat\n' + + 'New conversation appears if user deletes focused chat from Shared with me\n' + + 'New conversation appears if user deletes folder with focused chat from Shared with me\n' + + 'New conversation appears if user clicks on logo when Chat is opened', + async ({ + dialHomePage, + header, + conversationData, + dataInjector, + conversations, + conversationAssertion, + chatMessagesAssertion, + setTestIds, + conversationDropdownMenu, + confirmationDialog, + chat, + chatBarAssertion, + mainUserShareApiHelper, + additionalUserShareApiHelper, + additionalShareUserDataInjector, + sharedWithMeConversations, + sharedWithMeConversationDropdownMenu, + sharedWithMeFolderDropdownMenu, + sharedFolderConversations, + sharedWithMeConversationAssertion, + }) => { + setTestIds( + 'EPMRTC-4791', + 'EPMRTC-4776', + 'EPMRTC-4804', + 'EPMRTC-1834', + 'EPMRTC-4802', + 'EPMRTC-4805', + 'EPMRTC-4817', + ); + const firstConversation = + conversationData.prepareModelConversationBasedOnRequests([ + 'first request', + 'second request', + ]); + conversationData.resetData(); + const secondConversation = conversationData.prepareDefaultConversation(); + conversationData.resetData(); + const sharedConversation = conversationData.prepareDefaultConversation(); + conversationData.resetData(); + await additionalShareUserDataInjector.createConversations([ + sharedConversation, + ]); + await dataInjector.createConversations([ + firstConversation, + secondConversation, + ]); + + const sharedFolderConversation = + conversationData.prepareDefaultConversationInFolder(); + conversationData.resetData(); + await additionalShareUserDataInjector.createConversations( + sharedFolderConversation.conversations, + sharedFolderConversation.folders, + ); + + await dialTest.step('Prepare shared conversations', async () => { + const shareByLinkResponse = + await additionalUserShareApiHelper.shareEntityByLink([ + sharedConversation, + ]); + await mainUserShareApiHelper.acceptInvite(shareByLinkResponse); + + const shareFolderByLinkResponse = + await additionalUserShareApiHelper.shareEntityByLink( + [sharedFolderConversation.conversations[0]], + true, + ); + await mainUserShareApiHelper.acceptInvite(shareFolderByLinkResponse); + }); + + await dialTest.step('Open app and create new conversation', async () => { + await dialHomePage.openHomePage(); + await dialHomePage.waitForPageLoaded(); + }); + + await dialTest.step( + 'Select conversation with history and verify it is highlighted, its content is displayed, no new conversation is created', + async () => { + await conversations.selectConversation(firstConversation.name); + await conversationAssertion.assertSelectedConversation( + firstConversation.name, + ); + await chatMessagesAssertion.assertMessagesCount( + firstConversation.messages.length, + ); + await conversationAssertion.assertEntitiesCount(2); + }, + ); + + await dialTest.step( + 'Click on DIAL logo and check that new conversation is shown on the central part', + async () => { + await header.logo.click(); + await dialHomePage.waitForPageLoaded(); + await chat.getSendMessage().waitForState({ state: 'attached' }); + await chat.changeAgentButton.waitForState(); + await chat.configureSettingsButton.waitForState(); + await conversationAssertion.assertEntityState( + { name: secondConversation.name }, + 'visible', + ); + await conversationAssertion.assertEntityState( + { name: firstConversation.name }, + 'visible', + ); + await conversationAssertion.assertNoConversationIsSelected(); + await conversationAssertion.assertEntitiesCount(2); + }, + ); + + await dialTest.step('Select first conversation and delete it', async () => { + await conversations.openEntityDropdownMenu(firstConversation.name); + await conversationDropdownMenu.selectMenuOption(MenuOptions.delete); + await confirmationDialog.confirm({ triggeredHttpMethod: 'DELETE' }); + }); + + await dialTest.step( + 'Verify new conversation is shown and second conversation is not selected and verify only one conversation remains', + async () => { + await dialHomePage.waitForPageLoaded(); + await chat.getSendMessage().waitForState({ state: 'attached' }); + await chat.changeAgentButton.waitForState(); + await chat.configureSettingsButton.waitForState(); + await conversationAssertion.assertEntityState( + { name: secondConversation.name }, + 'visible', + ); + await conversationAssertion.assertEntityState( + { name: firstConversation.name }, + 'hidden', + ); + await conversationAssertion.assertNoConversationIsSelected(); + await conversationAssertion.assertEntitiesCount(1); + }, + ); + + await dialTest.step( + 'Select second conversation and delete it', + async () => { + await conversations.openEntityDropdownMenu(secondConversation.name); + await conversationDropdownMenu.selectMenuOption(MenuOptions.delete); + await confirmationDialog.confirm({ triggeredHttpMethod: 'DELETE' }); + await chatBarAssertion.assertNoDataInConversations(); + await chat.getSendMessage().waitForState({ state: 'attached' }); + await chat.changeAgentButton.waitForState(); + await chat.configureSettingsButton.waitForState(); + }, + ); + + await dialTest.step( + 'Open shared conversation by another user, select Delete and confirm', + async () => { + await sharedWithMeConversations.selectConversation( + sharedConversation.name, + ); + await sharedWithMeConversations.openEntityDropdownMenu( + sharedConversation.name, + ); + await sharedWithMeConversationDropdownMenu.selectMenuOption( + MenuOptions.delete, + ); + await confirmationDialog.confirm({ + triggeredHttpMethod: 'POST', + }); + }, + ); + + await dialTest.step( + 'Verify shared conversation is deleted and new conversation is shown', + async () => { + await sharedWithMeConversationAssertion.assertEntityState( + { name: firstConversation.name }, + 'hidden', + ); + await chatBarAssertion.assertNoDataInConversations(); + await chat.getSendMessage().waitForState({ state: 'attached' }); + await chat.changeAgentButton.waitForState(); + await chat.configureSettingsButton.waitForState(); + }, + ); + + await dialTest.step( + 'Select conversation inside shared folder, delete folder and verify new conversation is shown', + async () => { + await sharedFolderConversations.expandFolder( + sharedFolderConversation.folders.name, + ); + await sharedFolderConversations.selectFolderEntity( + sharedFolderConversation.folders.name, + sharedFolderConversation.conversations[0].name, + ); + await sharedFolderConversations.openFolderDropdownMenu( + sharedFolderConversation.folders.name, + ); + await sharedWithMeFolderDropdownMenu.selectMenuOption( + MenuOptions.delete, + ); + await confirmationDialog.confirm({ + triggeredHttpMethod: 'POST', + }); + await chatBarAssertion.assertNoDataInConversations(); + await chat.getSendMessage().waitForState({ state: 'attached' }); + await chat.changeAgentButton.waitForState(); + await chat.configureSettingsButton.waitForState(); + }, + ); + }, +); + +dialTest( + 'New conversation appears if user clicks on logo when DIAL Marketplace panel is opened', + async ({ + dialHomePage, + header, + talkToAgentDialog, + chatBar, + setTestIds, + conversationData, + dataInjector, + conversations, + sendMessageAssertion, + chat, + localStorageManager, + }) => { + setTestIds('EPMRTC-4832'); + const models = GeneratorUtil.randomArrayElements( + ModelsUtil.getLatestModels().filter( + (m) => + ModelsUtil.doesModelAllowSystemPrompt(m) && + ModelsUtil.doesModelAllowTemperature(m) && + ModelsUtil.doesModelAllowAddons(m) && + m.iconUrl !== undefined, + ), + 1, + ); + const conversation = conversationData.prepareDefaultConversation(models[0]); + await dataInjector.createConversations([conversation]); + await localStorageManager.setRecentModelsIdsOnce(...models); + + await dialTest.step('Open Dial, navigate to Marketplace', async () => { + await dialHomePage.openHomePage(); + await dialHomePage.waitForPageLoaded(); + await conversations.selectConversation(conversation.name); + await chatBar.dialMarketplaceLink.click(); + }); + + await dialTest.step( + 'Click on DIAL logo and verify new conversation mode is shown', + async () => { + await header.logo.click(); + await dialHomePage.waitForPageLoaded(); + await chat.getSendMessage().waitForState({ state: 'attached' }); + await chat.changeAgentButton.waitForState(); + await chat.configureSettingsButton.waitForState(); + await sendMessageAssertion.assertInputFieldState('visible', 'enabled'); + }, + ); + + await dialTest.step( + 'Navigate to Marketplace again and click on DIAL logo again', + async () => { + await chat.changeAgentButton.click(); + await talkToAgentDialog.goToMyWorkspace(); + await header.logo.click(); + }, + ); + + await dialTest.step('Verify new conversation is still shown', async () => { + await dialHomePage.waitForPageLoaded(); + await chat.getSendMessage().waitForState({ state: 'attached' }); + await chat.changeAgentButton.waitForState(); + await chat.configureSettingsButton.waitForState(); + await sendMessageAssertion.assertInputFieldState('visible', 'enabled'); + }); + }, +); + +dialTest( + 'New conversation 1 is not created if New conversation is on the screen\n' + + 'Click on logo resets all setting on new conversation', + async ({ + dialHomePage, + header, + chat, + agentSettings, + temperatureSlider, + addons, + talkToAgentDialog, + agentInfoAssertion, + setTestIds, + localStorageManager, + conversationSettingsModal, + conversationAssertion, + }) => { + setTestIds('EPMRTC-4837', 'EPMRTC-5092'); + const model = GeneratorUtil.randomArrayElement( + ModelsUtil.getLatestModels().filter( + (m) => + ModelsUtil.doesModelAllowSystemPrompt(m) && + ModelsUtil.doesModelAllowTemperature(m) && + ModelsUtil.doesModelAllowAddons(m) && + m.iconUrl !== undefined, + ), + ); + const addon = GeneratorUtil.randomArrayElement(ModelsUtil.getAddons()); + await localStorageManager.setRecentModelsIdsOnce(model); + await localStorageManager.setRecentAddonsIds(addon); + await localStorageManager.setLastConversationSettings(''); + let initialConversationIds: string | undefined; + + await dialTest.step( + 'Open Dial and verify the correct model is selected', + async () => { + await dialHomePage.openHomePage({ + iconsToBeLoaded: [model.iconUrl!], + }); + await dialHomePage.waitForPageLoaded(); + await chat.getSendMessage().waitForState({ state: 'attached' }); + initialConversationIds = + await localStorageManager.getSelectedConversationIds(); + }, + ); + + await dialTest.step( + 'Click on "+" button and verify no POST request is made', + async () => { + const requestPromise = dialHomePage.waitForRequest({ + method: 'POST', + shouldNotOccur: true, + timeout: 20000, + }); + await header.createNewConversation(); + await talkToAgentDialog.waitForState(); + await talkToAgentDialog.cancelButton.click(); + await requestPromise; + }, + ); + + await dialTest.step( + 'Verify local storage and conversation list remain unchanged', + async () => { + const updatedConversationIds = + await localStorageManager.getSelectedConversationIds(); + expect + .soft( + updatedConversationIds, + 'selectedConversationIds should remain the same', + ) + .toStrictEqual(initialConversationIds); + await conversationAssertion.assertEntitiesCount(0); + }, + ); + + await dialTest.step( + 'Change settings, apply, click on the logo, verify settings are reset', + async () => { + await chat.configureSettingsButton.click(); + await agentSettings.setSystemPrompt('Act like a cat'); + await temperatureSlider.setTemperature(0.2); + await addons.selectAddon(addon.name); + await conversationSettingsModal.applyChangesButton.click(); + await header.logo.click(); + await chat.configureSettingsButton.click(); + await agentInfoAssertion.assertElementText( + agentSettings.systemPrompt, + ExpectedConstants.emptyString, + ); + agentInfoAssertion.assertValue( + await temperatureSlider.getTemperature(), + ExpectedConstants.defaultTemperature, + ExpectedMessages.temperatureIsValid, + ); + await agentInfoAssertion.assertElementsCount( + addons.selectedAddons, + 0, + ExpectedMessages.noAddonsSelected, + ); + await conversationSettingsModal.cancelButton.click(); + }, + ); + }, +); diff --git a/apps/chat-e2e/src/ui/pages/basePage.ts b/apps/chat-e2e/src/ui/pages/basePage.ts index a859c59462..e90761ba7f 100644 --- a/apps/chat-e2e/src/ui/pages/basePage.ts +++ b/apps/chat-e2e/src/ui/pages/basePage.ts @@ -309,18 +309,30 @@ export class BasePage { public async mockChatTextResponse( responseBody: string, - options?: { isOverlay: boolean }, + options?: { + isOverlay?: boolean; + /** If true, let the request actually hit the server + * and then override the response. + * Defaults to false for backward-compatibility. + */ + passThrough?: boolean; + }, ) { - await this.page.route( - options?.isOverlay - ? `${process.env.NEXT_PUBLIC_OVERLAY_HOST}${API.chatHost}` - : API.chatHost, - async (route) => { - await route.fulfill({ - status: 200, - body: responseBody, - }); - }, - ); + const urlToIntercept = options?.isOverlay + ? `${process.env.NEXT_PUBLIC_OVERLAY_HOST}${API.chatHost}` + : API.chatHost; + + await this.page.route(urlToIntercept, async (route) => { + if (options?.passThrough) { + // 1. Sends the request to the actual server. + await route.fetch(); + } + // 2. Replaces the real response body with our mocked body + // Fulfill with our fake response, never hitting the server + await route.fulfill({ + status: 200, + body: responseBody, + }); + }); } } diff --git a/apps/chat-e2e/src/ui/pages/dialHomePage.ts b/apps/chat-e2e/src/ui/pages/dialHomePage.ts index 9317aa5517..6f15277a7b 100644 --- a/apps/chat-e2e/src/ui/pages/dialHomePage.ts +++ b/apps/chat-e2e/src/ui/pages/dialHomePage.ts @@ -3,6 +3,7 @@ import { BasePage, UploadDownloadData } from './basePage'; import config from '@/config/chat.playwright.config'; import { SharedPromptPreviewModal } from '@/src/ui/webElements'; import { AppContainer } from '@/src/ui/webElements/appContainer'; +import { Request } from 'playwright-chromium'; import { PageFunction } from 'playwright-core/types/structs'; export const loadingTimeout = config.use!.actionTimeout! * 2; @@ -127,4 +128,43 @@ export class DialHomePage extends BasePage { ): Promise { await this.page.addInitScript(script, arg); } + + public async waitForRequest({ + method, + urlPattern, + timeout = 5000, + shouldNotOccur = false, + }: { + method: 'PUT' | 'DELETE' | 'POST' | 'GET'; + urlPattern?: string | RegExp; + timeout?: number; + shouldNotOccur?: boolean; + }) { + const matchRequest = (request: Request) => { + const methodMatches = request.method() === method; + if (!urlPattern) return methodMatches; + return ( + methodMatches && + (urlPattern instanceof RegExp + ? urlPattern.test(request.url()) + : request.url().includes(urlPattern)) + ); + }; + + if (shouldNotOccur) { + try { + await this.page.waitForRequest(matchRequest, { timeout: timeout }); + // If we get here, we found a request when we shouldn't have + throw new Error(`Unexpected ${method} request was sent`); + } catch (error: unknown) { + if (error instanceof Error && error.message.includes('Timeout')) { + // Timeout is expected and good in this case + return; + } + throw error; + } + } else { + return this.page.waitForRequest(matchRequest, { timeout }); + } + } } diff --git a/apps/chat-e2e/src/ui/selectors/sideBarSelectors.ts b/apps/chat-e2e/src/ui/selectors/sideBarSelectors.ts index 0045187f25..411cfa6611 100644 --- a/apps/chat-e2e/src/ui/selectors/sideBarSelectors.ts +++ b/apps/chat-e2e/src/ui/selectors/sideBarSelectors.ts @@ -19,6 +19,8 @@ export const SideBarSelectors = { sharedWithMeContainer: '[data-qa="shared-with-me-container"]', approveRequiredContainer: '[data-qa="approve-required-container"]', organizationContainer: '[data-qa="published-with-me-container"]', + noData: '[data-qa="no-data-placeholder"]', + noDataIcon: '[data-qa="no-data-icon"]', }; export const ChatBarSelectors = { diff --git a/apps/chat-e2e/src/ui/webElements/entityTree/sidebar/sideBarEntitiesTree.ts b/apps/chat-e2e/src/ui/webElements/entityTree/sidebar/sideBarEntitiesTree.ts index bef67c582f..5c187ac3ca 100644 --- a/apps/chat-e2e/src/ui/webElements/entityTree/sidebar/sideBarEntitiesTree.ts +++ b/apps/chat-e2e/src/ui/webElements/entityTree/sidebar/sideBarEntitiesTree.ts @@ -1,4 +1,8 @@ -import { MenuSelectors, SideBarSelectors } from '../../../selectors'; +import { + EntitySelectors, + MenuSelectors, + SideBarSelectors, +} from '../../../selectors'; import { DropdownMenu } from '@/src/ui/webElements/dropdownMenu'; import { EditInput } from '@/src/ui/webElements/editInput'; @@ -8,6 +12,10 @@ import { EntitiesTree } from '@/src/ui/webElements/entityTree'; export class SideBarEntitiesTree extends EntitiesTree { private editEntityInput!: EditInput; + public treeEntityNames = this.getChildElementBySelector( + EntitySelectors.entityName, + ); + getEditEntityInput(): EditInput { if (!this.editEntityInput) { this.editEntityInput = new EditInput( diff --git a/apps/chat-e2e/src/ui/webElements/marketplace/marketplaceContainer.ts b/apps/chat-e2e/src/ui/webElements/marketplace/marketplaceContainer.ts index 393b8624f1..2c4de2dbcc 100644 --- a/apps/chat-e2e/src/ui/webElements/marketplace/marketplaceContainer.ts +++ b/apps/chat-e2e/src/ui/webElements/marketplace/marketplaceContainer.ts @@ -3,7 +3,6 @@ import { BaseElement } from '@/src/ui/webElements'; import { BaseLayoutContainer } from '@/src/ui/webElements/baseLayoutContainer'; import { Marketplace } from '@/src/ui/webElements/marketplace/marketplace'; import { MarketplaceSidebar } from '@/src/ui/webElements/marketplace/marketplaceSidebar'; -import { ModelsUtil } from '@/src/utils'; export class MarketplaceContainer extends BaseLayoutContainer { private marketplace!: Marketplace; @@ -30,11 +29,12 @@ export class MarketplaceContainer extends BaseLayoutContainer { return this.getChildElementBySelector(ChatSelectors.messageSpinner); } - public async goToMarketplaceHome() { + public async goToMarketplaceHome(expectedAgentsCount?: number) { await this.getMarketplaceSidebar().marketplaceHomePageButton.click(); - const allExpectedAgents = ModelsUtil.getLatestOpenAIEntities(); - await this.getMarketplace() - .getAgents() - .waitForAgentByIndex(allExpectedAgents.length); + if (expectedAgentsCount) { + await this.getMarketplace() + .getAgents() + .waitForAgentByIndex(expectedAgentsCount); + } } } diff --git a/apps/chat-e2e/src/ui/webElements/sideBar.ts b/apps/chat-e2e/src/ui/webElements/sideBar.ts index 3a594d5b7b..bad6cd421e 100644 --- a/apps/chat-e2e/src/ui/webElements/sideBar.ts +++ b/apps/chat-e2e/src/ui/webElements/sideBar.ts @@ -206,4 +206,12 @@ export class SideBar extends BaseElement { httpMethod: 'POST', }); } + + public noDataIcon = this.getChildElementBySelector( + SideBarSelectors.noDataIcon, + ); + + public noDataPlaceholder = this.getChildElementBySelector( + SideBarSelectors.noData, + ); } diff --git a/apps/chat-e2e/src/ui/webElements/talkToAgentDialog.ts b/apps/chat-e2e/src/ui/webElements/talkToAgentDialog.ts index 8c822a1146..6324df48c0 100644 --- a/apps/chat-e2e/src/ui/webElements/talkToAgentDialog.ts +++ b/apps/chat-e2e/src/ui/webElements/talkToAgentDialog.ts @@ -1,5 +1,6 @@ import { DialAIEntityModel } from '@/chat/types/models'; import { API, ExpectedConstants } from '@/src/testData'; +import { ModelApiHelper } from '@/src/testData/api'; import { MarketplacePage } from '@/src/ui/pages'; import { OverlayMarketplacePage } from '@/src/ui/pages/overlay/overlayMarketplacePage'; import { @@ -14,10 +15,17 @@ import { ModelsUtil } from '@/src/utils'; import { Locator, Page } from '@playwright/test'; export class TalkToAgentDialog extends BaseElement { - constructor(page: Page, parentLocator?: Locator) { + constructor( + page: Page, + modelApiHelper: ModelApiHelper, + parentLocator?: Locator, + ) { super(page, TalkToAgentDialogSelectors.talkToAgentModal, parentLocator); + this.modelApiHelper = modelApiHelper; } + private modelApiHelper: ModelApiHelper; + private agents!: MarketplaceAgents; private versionDropdownMenu!: DropdownButtonMenu; public goToMyWorkspaceButton = this.getChildElementBySelector( @@ -62,11 +70,12 @@ export class TalkToAgentDialog extends BaseElement { .isAgentUsed(entity); //otherwise go to marketplace "DIAL Marketplace page" if (!isMyApplicationUsed) { - await marketplaceContainer.goToMarketplaceHome(); + const expectedAgents = ModelsUtil.getLatestOpenAIEntities( + await this.modelApiHelper.getModels(), + ); + await marketplaceContainer.goToMarketplaceHome(expectedAgents.length); await marketplacePage.waitForPageLoaded(); // Wait for "Home Page" to load - const expectedAgents = ModelsUtil.getLatestOpenAIEntities(); const allAgents = marketplace.getAgents(); - await allAgents.waitForAgentByIndex(expectedAgents.length); const isAllApplicationUsed = await allAgents.isAgentUsed(entity, { isInstalledDeploymentsUpdated: true, }); diff --git a/apps/chat-e2e/src/ui/webElements/temperatureSlider.ts b/apps/chat-e2e/src/ui/webElements/temperatureSlider.ts index 89f9a8f42f..4d6dbba163 100644 --- a/apps/chat-e2e/src/ui/webElements/temperatureSlider.ts +++ b/apps/chat-e2e/src/ui/webElements/temperatureSlider.ts @@ -15,11 +15,14 @@ export class TemperatureSlider extends BaseElement { return this.slider.getElementContent(); } - async setTemperature(temperature: number) { + async setTemperature(temperature: string | number) { + const numericTemperature = + typeof temperature === 'string' ? parseFloat(temperature) : temperature; + await this.slider.scrollIntoElementView(); const bounding = await this.slider.getElementBoundingBox(); await this.page.mouse.move( - bounding!.x + bounding!.width! * temperature, + bounding!.x + bounding!.width! * numericTemperature, bounding!.y + bounding!.height! / 2, ); await this.page.mouse.down(); diff --git a/apps/chat/src/components/Common/NoData.tsx b/apps/chat/src/components/Common/NoData.tsx index 40c07eee29..263083c0ca 100644 --- a/apps/chat/src/components/Common/NoData.tsx +++ b/apps/chat/src/components/Common/NoData.tsx @@ -7,12 +7,16 @@ import { Translation } from '@/src/types/translation'; export const NoData = () => { const { t } = useTranslation(Translation.Common); return ( -
+
{t('No data')}