Repository: epam/ai-dial-chat
Pull request #3381: feat(chat-e2e): marketplace sources filter tests
Author: login: irinakartun
Created: 2025-03-12T14:02:26Z | Merged: 2025-03-19T12:38:06Z

**Description:**

marketplace sources filter tests

**Checklist:**

- [x] the pull request name complies with [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)
- [x] the pull request name starts with `fix(<scope>):`, `feat(<scope>):`, `feature(<scope>):`, `chore(<scope>):`, `hotfix(<scope>):` or `e2e(<scope>):`. If contains breaking changes then the pull request name must start with `fix(<scope>)!:`, `feat(<scope>)!:`, `feature(<scope>)!:`, `chore(<scope>)!:`, `hotfix(<scope>)!:` or `e2e(<scope>)!:` where `<scope>` is name of affected project: `chat`, `chat-e2e`, `overlay`, `shared`, `sandbox-overlay`, etc.
- [ ] the pull request name ends with `(Issue #<TICKET_ID>)` (comma-separated list of issues)
- [x] I confirm that do not share any confidential information like API keys or any other secrets and private URLs



Diff:
diff --git a/apps/chat-e2e/src/assertions/base/baseAssertion.ts b/apps/chat-e2e/src/assertions/base/baseAssertion.ts
index d11d94890e..d0a3ed38bb 100644
--- a/apps/chat-e2e/src/assertions/base/baseAssertion.ts
+++ b/apps/chat-e2e/src/assertions/base/baseAssertion.ts
@@ -12,7 +12,7 @@ import { SortingUtil } from '@/src/utils/sortingUtil';
 import { Locator, expect } from '@playwright/test';
 
 export class BaseAssertion {
-  public async assertStringsSorting(arrayToSort: string[], sorting: Sorting) {
+  public assertStringsSorting(arrayToSort: string[], sorting: Sorting) {
     const expectedOrder = SortingUtil.sortStringsArray(
       arrayToSort,
       (f) => f.toLowerCase(),
diff --git a/apps/chat-e2e/src/core/dialFixtures.ts b/apps/chat-e2e/src/core/dialFixtures.ts
index a145a93653..043bedeb24 100644
--- a/apps/chat-e2e/src/core/dialFixtures.ts
+++ b/apps/chat-e2e/src/core/dialFixtures.ts
@@ -244,6 +244,9 @@ const dialTest = test.extend<{
   sharedWithMeFolderDropdownMenu: DropdownMenu;
   additionalUserShareApiHelper: ShareApiHelper;
   additionalUserItemApiHelper: ItemApiHelper;
+  additionalUserFileApiHelper: FileApiHelper;
+  additionalUserApplicationApiHelper: ApplicationApiHelper;
+  additionalUserModelApiHelper: ModelApiHelper;
   additionalSecondUserShareApiHelper: ShareApiHelper;
   additionalSecondUserItemApiHelper: ItemApiHelper;
   chatNotFound: ChatNotFound;
@@ -804,6 +807,36 @@ const dialTest = test.extend<{
     ); // Use User2's bucket
     await use(additionalUserItemApiHelper);
   },
+  additionalUserFileApiHelper: async (
+    { additionalShareUserRequestContext },
+    use,
+  ) => {
+    const additionalUserFileApiHelper = new FileApiHelper(
+      additionalShareUserRequestContext,
+      BucketUtil.getAdditionalShareUserBucket(),
+    ); // Use User2's bucket
+    await use(additionalUserFileApiHelper);
+  },
+  additionalUserApplicationApiHelper: async (
+    { additionalShareUserRequestContext },
+    use,
+  ) => {
+    const additionalUserApplicationApiHelper = new ApplicationApiHelper(
+      additionalShareUserRequestContext,
+      BucketUtil.getAdditionalShareUserBucket(),
+    ); // Use User2's bucket
+    await use(additionalUserApplicationApiHelper);
+  },
+  additionalUserModelApiHelper: async (
+    { additionalShareUserRequestContext },
+    use,
+  ) => {
+    const additionalUserModelApiHelper = new ModelApiHelper(
+      additionalShareUserRequestContext,
+      BucketUtil.getAdditionalShareUserBucket(),
+    ); // Use User2's bucket
+    await use(additionalUserModelApiHelper);
+  },
   chatNotFound: async ({ page }, use) => {
     const chatNotFound = new ChatNotFound(page);
     await use(chatNotFound);
diff --git a/apps/chat-e2e/src/core/dialSharedWithMeFixtures.ts b/apps/chat-e2e/src/core/dialSharedWithMeFixtures.ts
index 944c5d36f1..1b49522f77 100644
--- a/apps/chat-e2e/src/core/dialSharedWithMeFixtures.ts
+++ b/apps/chat-e2e/src/core/dialSharedWithMeFixtures.ts
@@ -1,4 +1,4 @@
-import { DialHomePage } from '../ui/pages';
+import { DialHomePage, MarketplacePage } from '../ui/pages';
 import {
   AgentSettings,
   AttachFilesModal,
@@ -11,6 +11,11 @@ import {
   ConversationSettingsModal,
   ConversationToCompare,
   DropdownMenu,
+  Marketplace,
+  MarketplaceAgents,
+  MarketplaceContainer,
+  MarketplaceFilter,
+  MarketplaceSidebar,
   ModelInfoTooltip,
   PromptBar,
   PromptModalDialog,
@@ -22,6 +27,7 @@ import {
   VariableModalDialog,
 } from '../ui/webElements';
 
+import { BackendResourceType } from '@/chat/types/common';
 import config from '@/config/chat.playwright.config';
 import {
   ChatAssertion,
@@ -60,6 +66,7 @@ import {
 import { SharedFolderConversations } from '@/src/ui/webElements/entityTree/sidebar/sharedFolderConversations';
 import { SharedWithMeConversationsTree } from '@/src/ui/webElements/entityTree/sidebar/sharedWithMeConversationsTree';
 import { SharedWithMePromptsTree } from '@/src/ui/webElements/entityTree/sidebar/sharedWithMePromptsTree';
+import { MarketplaceAgentsSection } from '@/src/ui/webElements/marketplace/marketplaceAgentsSection';
 import { PlaybackControl } from '@/src/ui/webElements/playbackControl';
 import { BucketUtil } from '@/src/utils';
 import { Page } from '@playwright/test';
@@ -130,6 +137,13 @@ const dialSharedWithMeTest = dialTest.extend<{
   additionalShareUserChatAssertion: ChatAssertion;
   additionalShareUserConversationAssertion: ConversationAssertion;
   additionalShareUserTalkToAgentDialogAssertion: TalkToAgentDialogAssertion;
+  additionalShareUserMarketplacePage: MarketplacePage;
+  additionalShareUserMarketplaceContainer: MarketplaceContainer;
+  additionalShareUserMarketplaceSidebar: MarketplaceSidebar;
+  additionalShareUserMarketplaceFilter: MarketplaceFilter;
+  additionalShareUserMarketplace: Marketplace;
+  additionalShareUserMarketplaceAgentsSection: MarketplaceAgentsSection;
+  additionalShareUserMarketplaceAgents: MarketplaceAgents;
 }>({
   beforeAdditionalShareUserTestCleanup: [
     async (
@@ -147,27 +161,19 @@ const dialSharedWithMeTest = dialTest.extend<{
       await additionalSecondUserItemApiHelper.deleteAllData(
         BucketUtil.getAdditionalSecondShareUserBucket(),
       );
-      const additionalUserSharedConversations =
-        await additionalUserShareApiHelper.listSharedWithMeConversations();
-      const additionalUserSharedPrompts =
-        await additionalUserShareApiHelper.listSharedWithMePrompts();
-      const additionalUserSharedFiles =
-        await additionalUserShareApiHelper.listSharedWithMeFiles();
+      const additionalUserSharedEntities =
+        await additionalUserShareApiHelper.listSharedWithMeEntities(
+          ...Object.values(BackendResourceType),
+        );
       await additionalUserShareApiHelper.deleteSharedWithMeEntities([
-        ...additionalUserSharedConversations.resources,
-        ...additionalUserSharedPrompts.resources,
-        ...additionalUserSharedFiles.resources,
+        ...additionalUserSharedEntities.resources,
       ]);
-      const additionalSecondUserSharedConversations =
-        await additionalSecondUserShareApiHelper.listSharedWithMeConversations();
-      const additionalSecondUserSharedPrompts =
-        await additionalSecondUserShareApiHelper.listSharedWithMePrompts();
-      const additionalSecondUserSharedFiles =
-        await additionalSecondUserShareApiHelper.listSharedWithMeFiles();
+      const additionalSecondUserSharedEntities =
+        await additionalSecondUserShareApiHelper.listSharedWithMeEntities(
+          ...Object.values(BackendResourceType),
+        );
       await additionalSecondUserShareApiHelper.deleteSharedWithMeEntities([
-        ...additionalSecondUserSharedConversations.resources,
-        ...additionalSecondUserSharedPrompts.resources,
-        ...additionalSecondUserSharedFiles.resources,
+        ...additionalSecondUserSharedEntities.resources,
       ]);
       await use('beforeAdditionalShareUserTestCleanup');
     },
@@ -691,6 +697,63 @@ const dialSharedWithMeTest = dialTest.extend<{
       new TalkToAgentDialogAssertion(additionalShareUserTalkToAgentDialog);
     await use(additionalShareUserTalkToAgentDialogAssertion);
   },
+  additionalShareUserMarketplacePage: async (
+    { additionalShareUserPage },
+    use,
+  ) => {
+    const additionalShareUserMarketplacePage = new MarketplacePage(
+      additionalShareUserPage,
+    );
+    await use(additionalShareUserMarketplacePage);
+  },
+  additionalShareUserMarketplaceContainer: async (
+    { additionalShareUserMarketplacePage },
+    use,
+  ) => {
+    const additionalShareUserMarketplaceContainer =
+      additionalShareUserMarketplacePage.getMarketplaceContainer();
+    await use(additionalShareUserMarketplaceContainer);
+  },
+  additionalShareUserMarketplaceSidebar: async (
+    { additionalShareUserMarketplaceContainer },
+    use,
+  ) => {
+    const additionalShareUserMarketplaceSidebar =
+      additionalShareUserMarketplaceContainer.getMarketplaceSidebar();
+    await use(additionalShareUserMarketplaceSidebar);
+  },
+  additionalShareUserMarketplaceFilter: async (
+    { additionalShareUserMarketplaceSidebar },
+    use,
+  ) => {
+    const additionalShareUserMarketplaceFilter =
+      additionalShareUserMarketplaceSidebar.getMarketplaceFilter();
+    await use(additionalShareUserMarketplaceFilter);
+  },
+  additionalShareUserMarketplace: async (
+    { additionalShareUserMarketplaceContainer },
+    use,
+  ) => {
+    const additionalShareUserMarketplace =
+      additionalShareUserMarketplaceContainer.getMarketplace();
+    await use(additionalShareUserMarketplace);
+  },
+  additionalShareUserMarketplaceAgentsSection: async (
+    { additionalShareUserMarketplace },
+    use,
+  ) => {
+    const additionalShareUserMarketplaceAgentsSection =
+      additionalShareUserMarketplace.getMarketplaceAgentsSection();
+    await use(additionalShareUserMarketplaceAgentsSection);
+  },
+  additionalShareUserMarketplaceAgents: async (
+    { additionalShareUserMarketplaceAgentsSection },
+    use,
+  ) => {
+    const additionalShareUserMarketplaceAgents =
+      additionalShareUserMarketplaceAgentsSection.getAgents();
+    await use(additionalShareUserMarketplaceAgents);
+  },
 });
 
 export let shareUserBucket: string;
diff --git a/apps/chat-e2e/src/testData/api/modelApiHelper.ts b/apps/chat-e2e/src/testData/api/modelApiHelper.ts
index 641543eaeb..006e760651 100644
--- a/apps/chat-e2e/src/testData/api/modelApiHelper.ts
+++ b/apps/chat-e2e/src/testData/api/modelApiHelper.ts
@@ -1,5 +1,5 @@
 import { DialAIEntityModel } from '@/chat/types/models';
-import { API } from '@/src/testData';
+import { API, ExpectedConstants } from '@/src/testData';
 import { BaseApiHelper } from '@/src/testData/api/baseApiHelper';
 import { expect } from '@playwright/test';
 
@@ -13,4 +13,17 @@ export class ModelApiHelper extends BaseApiHelper {
     ).toBe(200);
     return (await response.json()) as DialAIEntityModel[];
   }
+
+  public async getAgentByNameAndVersion(
+    agentProps: { name: string; version?: string },
+    configAgents?: DialAIEntityModel[],
+  ) {
+    const allAgents = configAgents ?? (await this.getModels());
+    return allAgents.find(
+      (a) =>
+        a.name === agentProps.name &&
+        (a.version === agentProps.version ??
+          ExpectedConstants.defaultAppVersion),
+    );
+  }
 }
diff --git a/apps/chat-e2e/src/testData/api/shareApiHelper.ts b/apps/chat-e2e/src/testData/api/shareApiHelper.ts
index 9e018a533f..bcdf3c117f 100644
--- a/apps/chat-e2e/src/testData/api/shareApiHelper.ts
+++ b/apps/chat-e2e/src/testData/api/shareApiHelper.ts
@@ -1,5 +1,9 @@
 import { Conversation } from '@/chat/types/chat';
-import { BackendChatEntity, BackendResourceType } from '@/chat/types/common';
+import {
+  BackendChatEntity,
+  BackendEntity,
+  BackendResourceType,
+} from '@/chat/types/common';
 import { Prompt } from '@/chat/types/prompt';
 import {
   ShareAcceptRequestModel,
@@ -66,7 +70,32 @@ export class ShareApiHelper extends BaseApiHelper {
       resources: resources,
     };
     const response = await this.request.post(
-      this.getHost(API.shareConversationHost),
+      this.getHost(API.shareEntityHost),
+      {
+        data: requestData,
+      },
+    );
+    expect(
+      response.status(),
+      `Successfully created share invitation link`,
+    ).toBe(200);
+    const responseText = await response.text();
+    return JSON.parse(responseText) as ShareByLinkResponseModel;
+  }
+
+  public async shareAppByLink(app: BackendEntity) {
+    const resources: { url: string }[] = [];
+    let url: string;
+    if (!resources.find((r) => r.url === url)) {
+      resources.push({ url: app.url });
+    }
+
+    const requestData: ShareRequestModel = {
+      invitationType: ShareRequestType.link,
+      resources: resources,
+    };
+    const response = await this.request.post(
+      this.getHost(API.shareEntityHost),
       {
         data: requestData,
       },
@@ -112,9 +141,11 @@ export class ShareApiHelper extends BaseApiHelper {
     return this.listSharedWithMeEntities(BackendResourceType.PROMPT);
   }
 
-  public async listSharedWithMeEntities(resourceType: BackendResourceType) {
+  public async listSharedWithMeEntities(
+    ...resourceType: BackendResourceType[]
+  ) {
     const requestData: ShareListingRequestModel = {
-      resourceTypes: [resourceType],
+      resourceTypes: resourceType,
       with: ShareRelations.me,
       order: 'popular_asc',
     };
diff --git a/apps/chat-e2e/src/testData/expectedConstants.ts b/apps/chat-e2e/src/testData/expectedConstants.ts
index 313a795cdb..089f49e317 100644
--- a/apps/chat-e2e/src/testData/expectedConstants.ts
+++ b/apps/chat-e2e/src/testData/expectedConstants.ts
@@ -316,7 +316,7 @@ export const API = {
   importFilePath: (bucket: string, modelId: string) =>
     `${API.importFileRootPath(bucket)}/${API.modelFilePath(modelId)}`,
   shareInviteAcceptanceHost: '/api/share/accept',
-  shareConversationHost: '/api/share/create',
+  shareEntityHost: '/api/share/create',
   shareListing: '/api/share/listing',
   discardShareWithMeItem: '/api/share/discard',
   installedDeploymentsFolder: 'clientdata',
diff --git a/apps/chat-e2e/src/testData/types.ts b/apps/chat-e2e/src/testData/types.ts
index ea8e22221f..9bce6dc5c0 100644
--- a/apps/chat-e2e/src/testData/types.ts
+++ b/apps/chat-e2e/src/testData/types.ts
@@ -78,3 +78,8 @@ export enum SourcesFilterOptions {
   myQuickApps = 'My Quick apps',
   myCodeApps = 'My Code apps',
 }
+
+export enum ApplicationTypes {
+  CUSTOM_APP = 'custom app',
+  CODE_APP = 'code app',
+}
diff --git a/apps/chat-e2e/src/tests/chatBarConversation.test.ts b/apps/chat-e2e/src/tests/chatBarConversation.test.ts
index 558adbe185..1b2830f877 100644
--- a/apps/chat-e2e/src/tests/chatBarConversation.test.ts
+++ b/apps/chat-e2e/src/tests/chatBarConversation.test.ts
@@ -40,11 +40,12 @@ dialTest(
   async ({
     dialHomePage,
     conversations,
-    chat,
+    sendMessage,
     chatMessages,
     setTestIds,
     header,
     baseAssertion,
+    conversationAssertion,
     chatBar,
   }) => {
     setTestIds('EPMRTC-583', 'EPMRTC-776', 'EPMRTC-2894', 'EPMRTC-2957');
@@ -63,26 +64,22 @@ dialTest(
         await dialHomePage.mockChatTextResponse(
           MockedChatApiResponseBodies.simpleTextBody,
         );
-        await chat.sendRequestWithButton(messageToSend);
-
-        await expect
-          .soft(
-            conversations.getEntityByName(expectedConversationName),
-            ExpectedMessages.conversationNameUpdated,
-          )
-          .toBeVisible();
+        await sendMessage.send(messageToSend);
+        await conversationAssertion.assertEntityState(
+          { name: expectedConversationName },
+          'visible',
+        );
       },
     );
 
     await dialTest.step(
       'Verify request is fully displayed in chat history',
       async () => {
-        await expect
-          .soft(
-            chatMessages.getChatMessage(1),
-            ExpectedMessages.messageContentIsValid,
-          )
-          .toHaveText(messageToSend);
+        await baseAssertion.assertElementText(
+          chatMessages.getChatMessage(1),
+          messageToSend,
+          ExpectedMessages.messageContentIsValid,
+        );
       },
     );
 
@@ -90,12 +87,11 @@ dialTest(
       'Verify conversation is placed in Today section',
       async () => {
         const todayConversations = await conversations.getTodayConversations();
-        expect
-          .soft(
-            todayConversations.includes(expectedConversationName),
-            ExpectedMessages.conversationOfToday,
-          )
-          .toBeTruthy();
+        baseAssertion.assertArrayIncludesAll(
+          todayConversations,
+          [expectedConversationName],
+          ExpectedMessages.conversationOfToday,
+        );
       },
     );
   },
diff --git a/apps/chat-e2e/src/tests/marketplaceSourceFilter.test.ts b/apps/chat-e2e/src/tests/marketplaceSourceFilter.test.ts
new file mode 100644
index 0000000000..e24e725482
--- /dev/null
+++ b/apps/chat-e2e/src/tests/marketplaceSourceFilter.test.ts
@@ -0,0 +1,473 @@
+import { EntityType } from '@/chat/types/common';
+import { Publication } from '@/chat/types/publication';
+import { ShareByLinkResponseModel } from '@/chat/types/share';
+import dialTest from '@/src/core/dialFixtures';
+import dialSharedWithMeTest from '@/src/core/dialSharedWithMeFixtures';
+import {
+  AddAppMenuOptions,
+  ApplicationTypes,
+  CheckboxState,
+  ExpectedMessages,
+  MarketplaceExpectedMessages,
+  MarketplaceFilterTypes,
+  MenuOptions,
+  SourcesFilterOptions,
+} from '@/src/testData';
+import { GeneratorUtil, ModelsUtil } from '@/src/utils';
+import { PublishActions } from '@epam/ai-dial-shared';
+import { Locator, expect } from '@playwright/test';
+
+const publicationsToUnpublish: Publication[] = [];
+
+dialTest(
+  'Sources: check My Custom apps.\n' +
+    'Sources: combination inside sources filter works as OR; combination sources + type/topic works as AND',
+  async ({
+    customApplicationBuilder,
+    applicationApiHelper,
+    marketplacePage,
+    marketplace,
+    marketplaceFilter,
+    marketplaceHeader,
+    marketplaceAgentsSection,
+    modelApiHelper,
+    setTestIds,
+    baseAssertion,
+  }) => {
+    setTestIds('EPMRTC-5234', 'EPMRTC-5239');
+    const appName = GeneratorUtil.randomApplicationName();
+    const appTopic = GeneratorUtil.randomString(7);
+
+    await dialTest.step(
+      'Prepare a custom application with some topic in the "My Workspace"',
+      async () => {
+        const applicationModel = customApplicationBuilder
+          .withDisplayName(appName)
+          .withDescriptionKeywords(appTopic)
+          .build();
+        await applicationApiHelper.createApplication(applicationModel);
+      },
+    );
+
+    await dialTest.step(
+      'Open "My Workspace" and verify "My Custom apps" and "Public" options are available in the Sources filter',
+      async () => {
+        await marketplacePage.openMarketplacePage();
+        await marketplacePage.waitForPageLoaded();
+        await baseAssertion.assertElementState(
+          marketplaceFilter.filterByProperty(MarketplaceFilterTypes.sources),
+          'visible',
+        );
+        const actualSourcesFilterOptions =
+          await marketplaceFilter.filterByPropertyOptionLabels(
+            MarketplaceFilterTypes.sources,
+          );
+        baseAssertion.assertArrayIncludesAll(
+          actualSourcesFilterOptions,
+          [SourcesFilterOptions.myCustomApps, SourcesFilterOptions.public],
+          MarketplaceExpectedMessages.filterOptionsAreValid,
+        );
+      },
+    );
+
+    await dialTest.step(
+      'Check "My Custom apps" option and verify only custom apps are displayed',
+      async () => {
+        await marketplaceFilter
+          .filterByPropertyOptionInput(
+            MarketplaceFilterTypes.sources,
+            SourcesFilterOptions.myCustomApps,
+          )
+          .click();
+        const actualAgents = await marketplaceAgentsSection.getAllAgents();
+        baseAssertion.assertArrayIncludesAll(
+          actualAgents.map((agent) => agent.name),
+          [appName],
+          MarketplaceExpectedMessages.filteredAgentsAreValid,
+        );
+
+        const configAgents = await modelApiHelper.getModels();
+        for (const actualAgent of actualAgents) {
+          const agent = await modelApiHelper.getAgentByNameAndVersion(
+            { name: actualAgent.name, version: actualAgent.version },
+            configAgents,
+          );
+          if (agent) {
+            baseAssertion.assertValue(
+              ModelsUtil.getApplicationType(agent),
+              ApplicationTypes.CUSTOM_APP,
+            );
+          }
+        }
+      },
+    );
+
+    await dialTest.step(
+      'Check app topic filter option and verify only created app is displayed',
+      async () => {
+        await marketplaceFilter
+          .filterByPropertyOptionInput(MarketplaceFilterTypes.topics, appTopic)
+          .click();
+        const actualAgents = await marketplaceAgentsSection.getAllAgents();
+        baseAssertion.assertValue(
+          actualAgents.length,
+          1,
+          ExpectedMessages.elementsCountIsValid,
+        );
+        baseAssertion.assertArrayIncludesAll(
+          actualAgents.map((agent) => agent.name),
+          [appName],
+          MarketplaceExpectedMessages.filteredAgentsAreValid,
+        );
+      },
+    );
+
+    await dialTest.step(
+      'Set random string in the search field and verify no results are displayed',
+      async () => {
+        await marketplaceHeader.searchInput.fillInInput(
+          GeneratorUtil.randomString(10),
+        );
+        await baseAssertion.assertElementState(
+          marketplace.noResultsFound,
+          'visible',
+        );
+      },
+    );
+  },
+);
+
+dialTest(
+  'Sources: the search results are updated if to remove/add custom application.\n' +
+    'Sources: the filter disappears and search results are updated if to remove custom application when only one existed',
+  async ({
+    customApplicationBuilder,
+    applicationApiHelper,
+    marketplacePage,
+    addAppDropdownMenu,
+    marketplaceSidebar,
+    marketplaceFilter,
+    marketplaceHeader,
+    marketplaceAgentsSection,
+    marketplaceAgents,
+    appEditorPage,
+    appEditorGeneralForm,
+    appEditorViewForm,
+    appEditorHeader,
+    confirmationDialog,
+    setTestIds,
+    baseAssertion,
+  }) => {
+    setTestIds('EPMRTC-5351', 'EPMRTC-5238');
+    const firstAppName = GeneratorUtil.randomApplicationName();
+    const secondAppName = GeneratorUtil.randomApplicationName();
+    let myCustomAppsSourceFilterElement: Locator;
+
+    await dialTest.step(
+      'Prepare a custom application in the "My Workspace"',
+      async () => {
+        const applicationModel = customApplicationBuilder
+          .withDisplayName(firstAppName)
+          .build();
+        await applicationApiHelper.createApplication(applicationModel);
+      },
+    );
+
+    await dialTest.step(
+      'Open "My Workspace", check "My Custom apps" option and verify created app is displayed',
+      async () => {
+        await marketplacePage.openMarketplacePage();
+        await marketplacePage.waitForPageLoaded();
+        await baseAssertion.assertElementState(
+          marketplaceFilter.filterByProperty(MarketplaceFilterTypes.sources),
+          'visible',
+        );
+        myCustomAppsSourceFilterElement =
+          marketplaceFilter.filterByPropertyOptionInput(
+            MarketplaceFilterTypes.sources,
+            SourcesFilterOptions.myCustomApps,
+          );
+        await myCustomAppsSourceFilterElement.click();
+        const actualAgents = await marketplaceAgentsSection.getAllAgents();
+        baseAssertion.assertArrayIncludesAll(
+          actualAgents.map((agent) => agent.name),
+          [firstAppName],
+          MarketplaceExpectedMessages.filteredAgentsAreValid,
+        );
+      },
+    );
+
+    await dialTest.step(
+      'Create one more custom application in the "My Workspace"',
+      async () => {
+        await marketplaceSidebar.myWorkspaceButton.click();
+        await marketplaceHeader.addAppButton.click();
+        await addAppDropdownMenu.selectMenuOption(AddAppMenuOptions.customApp);
+        await appEditorPage.waitForPageLoaded();
+        await appEditorGeneralForm.fillInAppFields({
+          name: secondAppName,
+        });
+        await appEditorGeneralForm.goNext();
+        await appEditorViewForm.fillInAppFields();
+        await appEditorHeader.saveAppAndExit();
+        await marketplacePage.waitForPageLoaded();
+      },
+    );
+
+    await dialTest.step(
+      'Verify newly added app is displayed immediately',
+      async () => {
+        //TODO: remove filter check when fixed https://github.com/epam/ai-dial-chat/issues/3221
+        await myCustomAppsSourceFilterElement.click();
+        const actualAgents = await marketplaceAgentsSection.getAllAgents();
+        baseAssertion.assertArrayIncludesAll(
+          actualAgents.map((agent) => agent.name),
+          [firstAppName, secondAppName],
+          MarketplaceExpectedMessages.filteredAgentsAreValid,
+        );
+      },
+    );
+
+    await dialTest.step(
+      'Delete the first app and verify it disappears immediately',
+      async () => {
+        const agentElement =
+          await marketplaceAgentsSection.findAgentElement(firstAppName);
+        await agentElement.hoverOver();
+        await marketplaceAgents.getAgentElementDotsMenu(agentElement).click();
+        await marketplaceAgents
+          .getAgentDropdownMenu()
+          .selectMenuOption(MenuOptions.delete);
+        await confirmationDialog.confirm({ triggeredHttpMethod: 'PUT' });
+
+        await baseAssertion.assertCheckboxState(
+          myCustomAppsSourceFilterElement,
+          CheckboxState.checked,
+        );
+        const actualAgents = await marketplaceAgentsSection.getAllAgents();
+        const actualAgentNames = actualAgents.map((agent) => agent.name);
+        baseAssertion.assertArrayExcludesAll(
+          actualAgentNames,
+          [firstAppName],
+          MarketplaceExpectedMessages.filteredAgentsAreValid,
+        );
+        baseAssertion.assertArrayIncludesAll(
+          actualAgentNames,
+          [secondAppName],
+          MarketplaceExpectedMessages.filteredAgentsAreValid,
+        );
+      },
+    );
+  },
+);
+
+dialSharedWithMeTest(
+  'Sources: check Shared with me.\n' +
+    'Sources: check Public. And Sorting order - alphabetically',
+  async ({
+    customApplicationBuilder,
+    applicationApiHelper,
+    adminApplicationApiHelper,
+    mainUserShareApiHelper,
+    adminPublicationApiHelper,
+    publishRequestBuilder,
+    additionalUserApplicationApiHelper,
+    additionalUserShareApiHelper,
+    additionalUserFileApiHelper,
+    additionalUserModelApiHelper,
+    additionalShareUserLocalStorageManager,
+    additionalShareUserMarketplacePage,
+    additionalShareUserMarketplace,
+    additionalShareUserMarketplaceFilter,
+    additionalShareUserMarketplaceAgentsSection,
+    additionalShareUserMarketplaceSidebar,
+    setTestIds,
+    baseAssertion,
+  }) => {
+    setTestIds('EPMRTC-5237', 'EPMRTC-5233');
+    const sharedAppName = GeneratorUtil.randomApplicationName();
+    const publishedAppName = GeneratorUtil.randomApplicationName();
+    const additionalUserAppName = GeneratorUtil.randomApplicationName();
+    let shareByLinkResponse: ShareByLinkResponseModel;
+    let sharedWithMeSourceFilterElement: Locator;
+
+    await dialSharedWithMeTest.step(
+      'By main user create a custom application and share it',
+      async () => {
+        const applicationModel = customApplicationBuilder
+          .withDisplayName(sharedAppName)
+          .build();
+        const backendApp =
+          await applicationApiHelper.createApplication(applicationModel);
+        shareByLinkResponse =
+          await mainUserShareApiHelper.shareAppByLink(backendApp);
+        await additionalUserShareApiHelper.acceptInvite(shareByLinkResponse);
+      },
+    );
+
+    await dialSharedWithMeTest.step(
+      'By admin user create a custom application and publish it',
+      async () => {
+        const applicationModel = customApplicationBuilder
+          .withDisplayName(publishedAppName)
+          .build();
+        const adminApp =
+          await adminApplicationApiHelper.createApplication(applicationModel);
+        const publishRequest = publishRequestBuilder
+          .withName(GeneratorUtil.randomPublicationRequestName())
+          .withApplicationResource(adminApp, PublishActions.ADD)
+          .build();
+        const appPublication =
+          await adminPublicationApiHelper.createPublishRequest(publishRequest);
+        publicationsToUnpublish.push(appPublication);
+        await adminPublicationApiHelper.approveRequest(appPublication);
+      },
+    );
+
+    await dialSharedWithMeTest.step(
+      'By additional user create a custom application',
+      async () => {
+        const applicationModel = customApplicationBuilder
+          .withDisplayName(additionalUserAppName)
+          .build();
+        await additionalUserApplicationApiHelper.createApplication(
+          applicationModel,
+        );
+      },
+    );
+
+    await dialSharedWithMeTest.step(
+      'Open "Marketplace" and verify Sources filter contains "Shared with me", "My Custom apps", "Public" options sorted alphabetically',
+      async () => {
+        await additionalUserFileApiHelper.updateInstalledDeployments([]);
+        await additionalShareUserLocalStorageManager.setRecentModelsIds();
+        await additionalShareUserMarketplacePage.openMarketplacePage();
+        await additionalShareUserMarketplacePage.waitForPageLoaded();
+        const sourceFilterOptions =
+          await additionalShareUserMarketplaceFilter.filterByPropertyOptionLabels(
+            MarketplaceFilterTypes.sources,
+          );
+        baseAssertion.assertArrayIncludesAll(
+          sourceFilterOptions,
+          [
+            SourcesFilterOptions.myCustomApps,
+            SourcesFilterOptions.public,
+            SourcesFilterOptions.sharedWithMe,
+          ],
+          MarketplaceExpectedMessages.filterOptionsAreValid,
+        );
+        baseAssertion.assertStringsSorting(sourceFilterOptions, 'asc');
+      },
+    );
+
+    await dialTest.step(
+      'Check "Shared with me" filter option and verify shared app is displayed',
+      async () => {
+        sharedWithMeSourceFilterElement =
+          additionalShareUserMarketplaceFilter.filterByPropertyOptionInput(
+            MarketplaceFilterTypes.sources,
+            SourcesFilterOptions.sharedWithMe,
+          );
+        await sharedWithMeSourceFilterElement.click();
+        await baseAssertion.assertCheckboxState(
+          sharedWithMeSourceFilterElement,
+          CheckboxState.checked,
+        );
+        const actualAgents =
+          await additionalShareUserMarketplaceAgentsSection.getAllAgents();
+        const actualAgentNames = actualAgents.map((agent) => agent.name);
+        baseAssertion.assertArrayIncludesAll(
+          actualAgentNames,
+          [sharedAppName],
+          MarketplaceExpectedMessages.filteredAgentsAreValid,
+        );
+        baseAssertion.assertArrayExcludesAll(
+          actualAgentNames,
+          [publishedAppName, additionalUserAppName],
+          MarketplaceExpectedMessages.filteredAgentsAreValid,
+        );
+      },
+    );
+
+    for (const tab of ['Marketplace', 'My workspace']) {
+      await dialTest.step(
+        `Uncheck "Shared with me" filter option, check "Public" and verify only published and config apps are displayed on ${tab}`,
+        async () => {
+          let actualAgents;
+          if (tab === 'Marketplace') {
+            await sharedWithMeSourceFilterElement.click();
+            await baseAssertion.assertCheckboxState(
+              sharedWithMeSourceFilterElement,
+              CheckboxState.unchecked,
+            );
+            const publicSourceFilterElement =
+              additionalShareUserMarketplaceFilter.filterByPropertyOptionInput(
+                MarketplaceFilterTypes.sources,
+                SourcesFilterOptions.public,
+              );
+            await publicSourceFilterElement.click();
+            await baseAssertion.assertCheckboxState(
+              publicSourceFilterElement,
+              CheckboxState.checked,
+            );
+            actualAgents =
+              await additionalShareUserMarketplaceAgentsSection.getAllAgents();
+          } else {
+            await additionalShareUserMarketplaceSidebar.myWorkspaceButton.click();
+            await additionalShareUserMarketplacePage.waitForPageLoaded();
+            //remove next line when fixed https://github.com/epam/ai-dial-chat/issues/3303
+            await additionalShareUserMarketplaceAgentsSection.goTop();
+            actualAgents =
+              await additionalShareUserMarketplaceAgentsSection.getAllAgents();
+            baseAssertion.assertValue(
+              actualAgents.filter((agent) => agent.isWorkspaceAgent).length,
+              0,
+            );
+            await baseAssertion.assertElementState(
+              additionalShareUserMarketplace.noWorkspaceResultsFound,
+              'visible',
+            );
+          }
+
+          const actualAgentNames = actualAgents.map((agent) => agent.name);
+          baseAssertion.assertArrayIncludesAll(
+            actualAgentNames,
+            [publishedAppName],
+            MarketplaceExpectedMessages.filteredAgentsAreValid,
+          );
+          baseAssertion.assertArrayExcludesAll(
+            actualAgentNames,
+            [sharedAppName, additionalUserAppName],
+            MarketplaceExpectedMessages.filteredAgentsAreValid,
+          );
+
+          const allConfigAgents =
+            await additionalUserModelApiHelper.getModels();
+          //exclude Application type agents from verification since the list of application is changeable
+          const groupedConfigAgents = ModelsUtil.groupEntitiesByName(
+            allConfigAgents.filter((a) => a.type !== EntityType.Application),
+          );
+          const expectedAgentNames = Array.from(
+            groupedConfigAgents.keys(),
+          ).filter((k) => k !== sharedAppName && k !== additionalUserAppName);
+          for (const expectedAgentName of expectedAgentNames) {
+            expect
+              .soft(
+                actualAgents.find((agent) => agent.name === expectedAgentName),
+                MarketplaceExpectedMessages.filteredAgentsAreValid,
+              )
+              .toBeDefined();
+          }
+        },
+      );
+    }
+  },
+);
+
+dialTest.afterAll(async ({ adminPublicationApiHelper }) => {
+  for (const publication of publicationsToUnpublish) {
+    const unpublishResponse =
+      await adminPublicationApiHelper.createUnpublishRequest(publication);
+    await adminPublicationApiHelper.approveRequest(unpublishResponse);
+  }
+});
diff --git a/apps/chat-e2e/src/tests/marketplaceTopicFilter.test.ts b/apps/chat-e2e/src/tests/marketplaceTopicFilter.test.ts
index 049fc7a9d2..78dcd22cf6 100644
--- a/apps/chat-e2e/src/tests/marketplaceTopicFilter.test.ts
+++ b/apps/chat-e2e/src/tests/marketplaceTopicFilter.test.ts
@@ -125,10 +125,7 @@ dialTest(
           [firstTopic, secondTopic],
           MarketplaceExpectedMessages.filterOptionsAreValid,
         );
-        await baseAssertion.assertStringsSorting(
-          actualTopicsFilterOptions,
-          'asc',
-        );
+        baseAssertion.assertStringsSorting(actualTopicsFilterOptions, 'asc');
       },
     );
 
diff --git a/apps/chat-e2e/src/tests/publishConversationToOrganisation.test.ts b/apps/chat-e2e/src/tests/publishConversationToOrganisation.test.ts
index c14d7a73cd..a0442e9b2b 100644
--- a/apps/chat-e2e/src/tests/publishConversationToOrganisation.test.ts
+++ b/apps/chat-e2e/src/tests/publishConversationToOrganisation.test.ts
@@ -108,7 +108,7 @@ dialAdminTest(
         await publishingRequestModal
           .getChangePublishToPath()
           .changeButton.click();
-        await selectFoldersAssertion.assertStringsSorting(
+        selectFoldersAssertion.assertStringsSorting(
           await selectFolders.getFolderNames(),
           'asc',
         );
@@ -206,7 +206,7 @@ dialAdminTest(
     await dialTest.step(
       'Verify folders sorting in "Organization" section',
       async () => {
-        await selectFoldersAssertion.assertStringsSorting(
+        selectFoldersAssertion.assertStringsSorting(
           await adminOrganizationFolderConversations.getFolderNames(),
           'asc',
         );
diff --git a/apps/chat-e2e/src/tests/sharedChatIcons.test.ts b/apps/chat-e2e/src/tests/sharedChatIcons.test.ts
index 3441dda59d..ef23697af0 100644
--- a/apps/chat-e2e/src/tests/sharedChatIcons.test.ts
+++ b/apps/chat-e2e/src/tests/sharedChatIcons.test.ts
@@ -431,6 +431,10 @@ dialTest(
           { name: thirdConversationToShare.name },
           expectedRandomModelIcon,
         );
+        await conversationAssertion.assertEntityArrowIconState(
+          { name: thirdConversationToShare.name },
+          'visible',
+        );
       },
     );
 
diff --git a/apps/chat-e2e/src/ui/webElements/marketplace/marketplaceAgents.ts b/apps/chat-e2e/src/ui/webElements/marketplace/marketplaceAgents.ts
index 255129c122..418412eecd 100644
--- a/apps/chat-e2e/src/ui/webElements/marketplace/marketplaceAgents.ts
+++ b/apps/chat-e2e/src/ui/webElements/marketplace/marketplaceAgents.ts
@@ -15,6 +15,7 @@ export enum FoundMarketplaceAgents {
 
 export interface MarketplaceAgentProperties {
   name: string;
+  version?: string;
   isSuggested: boolean;
   isWorkspaceAgent: boolean;
 }
diff --git a/apps/chat-e2e/src/ui/webElements/marketplace/marketplaceAgentsSection.ts b/apps/chat-e2e/src/ui/webElements/marketplace/marketplaceAgentsSection.ts
index fd90710dec..342bb766e8 100644
--- a/apps/chat-e2e/src/ui/webElements/marketplace/marketplaceAgentsSection.ts
+++ b/apps/chat-e2e/src/ui/webElements/marketplace/marketplaceAgentsSection.ts
@@ -159,11 +159,18 @@ export class MarketplaceAgentsSection extends BaseElement {
         const agentName = visibleAgentNames[i];
         //iterate through visible agents to define filtered/suggested type
         if (!allAgents.map((a) => a.name).includes(agentName)) {
-          const agentType = await visibleAgents
-            .getAgent(agentName)
-            .getAttribute(Attributes.ariaDetails);
+          const visibleAgent = visibleAgents.getAgent(agentName);
+          const agentType = await visibleAgent.getAttribute(
+            Attributes.ariaDetails,
+          );
+          const versionElement = visibleAgents.getAgentVersion(visibleAgent);
+          let agentVersion;
+          if (await versionElement.isVisible()) {
+            agentVersion = await versionElement.getElementInnerContent();
+          }
           allAgents.push({
             name: agentName,
+            version: agentVersion ?? undefined,
             isSuggested:
               agentType ===
               FoundMarketplaceAgents[FoundMarketplaceAgents.suggested],
@@ -185,6 +192,8 @@ export class MarketplaceAgentsSection extends BaseElement {
     const bounding = await this.getElementBoundingBox();
     await this.page.mouse.click(bounding!.x, bounding!.y);
     await this.page.keyboard.press(keys.home);
+    // eslint-disable-next-line playwright/no-wait-for-timeout
+    await this.page.waitForTimeout(1500);
   }
 
   private async getPositionAndScrollInto() {
diff --git a/apps/chat-e2e/src/utils/modelsUtil.ts b/apps/chat-e2e/src/utils/modelsUtil.ts
index 02d199f9dc..6a968fddb8 100644
--- a/apps/chat-e2e/src/utils/modelsUtil.ts
+++ b/apps/chat-e2e/src/utils/modelsUtil.ts
@@ -4,6 +4,7 @@ import {
   doesModelAllowSystemPrompt,
   doesModelAllowTemperature,
 } from '@/chat/utils/app/models';
+import { ApplicationTypes } from '@/src/testData';
 
 export class ModelsUtil {
   public static getOpenAIEntities() {
@@ -224,4 +225,16 @@ export class ModelsUtil {
       .filter((r) => r.version !== undefined)
       .map(({ version }) => version ?? '');
   }
+
+  public static getApplicationType(entity: DialAIEntityModel) {
+    if (entity.applicationTypeSchemaId) {
+      return entity.applicationTypeSchemaId;
+    }
+    if (ModelsUtil.isExecutableApp(entity)) return ApplicationTypes.CODE_APP;
+    return ApplicationTypes.CUSTOM_APP;
+  }
+
+  public static isExecutableApp(entity: DialAIEntityModel) {
+    return !!entity.functionStatus;
+  }
 }


Reviews:
user: login: nartovm
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T20:45:38Z
user: login: irinakartun
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T08:15:32Z
user: login: nartovm
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T10:21:01Z

Review comments:
diff_hunk: @@ -13,4 +13,17 @@ export class ModelApiHelper extends BaseApiHelper {
     ).toBe(200);
     return (await response.json()) as DialAIEntityModel[];
   }
+
+  public async getAgentByNameAndVersion(
+    agentProps: { name: string; version?: string },
+    configAgents?: DialAIEntityModel[],
path: apps/chat-e2e/src/testData/api/modelApiHelper.ts
user: login: nartovm
body: it is better to add a field
```
  private agentsMap: Map<string, DialAIEntityModel> = new Map();
```
and populate it at the first fetch of getModels or getAgentByNameAndVersion and then get it
created_at: 2025-03-18T20:45:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -13,4 +13,17 @@ export class ModelApiHelper extends BaseApiHelper {
     ).toBe(200);
     return (await response.json()) as DialAIEntityModel[];
   }
+
+  public async getAgentByNameAndVersion(
+    agentProps: { name: string; version?: string },
+    configAgents?: DialAIEntityModel[],
path: apps/chat-e2e/src/testData/api/modelApiHelper.ts
user: login: irinakartun
body: didn't really understand your proposal. Since the list of models is changeable, there is a need to retrieve the list at the moment the method is invoked
created_at: 2025-03-19T08:15:32Z
author_association: CONTRIBUTOR

PR comments:
user: login: irinakartun
created_at: 2025-03-12T14:29:42Z
body: /deploy-review
>Environment: [review-environment](https://chat-ai-dial-chat-pr-3381.nightly-test.deltixhub.io) | [pipeline](https://gitlab.deltixhub.com/Clients/deltix/nightly-test/dial/review/-/pipelines/579931)
>Overlay E2E tests status: [success](https://gitlab.deltixhub.com/Clients/deltix/nightly-test/dial/review/-/jobs/1702084)
>Chat E2E tests status: [failed](https://gitlab.deltixhub.com/Clients/deltix/nightly-test/dial/review/-/jobs/1702083)
author_association: CONTRIBUTOR
user: login: irinakartun
created_at: 2025-03-13T16:48:44Z
body: /deploy-review
>Environment: [review-environment](https://chat-ai-dial-chat-pr-3381.nightly-test.deltixhub.io) | [pipeline](https://gitlab.deltixhub.com/Clients/deltix/nightly-test/dial/review/-/pipelines/580796)
>Overlay E2E tests status: [success](https://gitlab.deltixhub.com/Clients/deltix/nightly-test/dial/review/-/jobs/1705808)
>Chat E2E tests status: [success](https://gitlab.deltixhub.com/Clients/deltix/nightly-test/dial/review/-/jobs/1705807)
author_association: CONTRIBUTOR
user: login: irinakartun
created_at: 2025-03-13T17:23:47Z
body: /deploy-review
>Environment: [review-environment](https://chat-ai-dial-chat-pr-3381.nightly-test.deltixhub.io) | [pipeline](https://gitlab.deltixhub.com/Clients/deltix/nightly-test/dial/review/-/pipelines/580844)
>Overlay E2E tests status: [success](https://gitlab.deltixhub.com/Clients/deltix/nightly-test/dial/review/-/jobs/1706044)
>Chat E2E tests status: [failed](https://gitlab.deltixhub.com/Clients/deltix/nightly-test/dial/review/-/jobs/1706043)
author_association: CONTRIBUTOR
user: login: irinakartun
created_at: 2025-03-13T19:17:07Z
body: /deploy-review
>Environment: [review-environment](https://chat-ai-dial-chat-pr-3381.nightly-test.deltixhub.io) | [pipeline](https://gitlab.deltixhub.com/Clients/deltix/nightly-test/dial/review/-/pipelines/580882)
>Overlay E2E tests status: [success](https://gitlab.deltixhub.com/Clients/deltix/nightly-test/dial/review/-/jobs/1706216)
>Chat E2E tests status: [success](https://gitlab.deltixhub.com/Clients/deltix/nightly-test/dial/review/-/jobs/1706215)
author_association: CONTRIBUTOR
user: login: irinakartun
created_at: 2025-03-18T15:27:24Z
body: /deploy-review
>Environment: [review-environment](https://chat-ai-dial-chat-pr-3381.nightly-test.deltixhub.io) | [pipeline](https://gitlab.deltixhub.com/Clients/deltix/nightly-test/dial/review/-/pipelines/582778)
>Overlay E2E tests status: [success](https://gitlab.deltixhub.com/Clients/deltix/nightly-test/dial/review/-/jobs/1714647)
>Chat E2E tests status: [success](https://gitlab.deltixhub.com/Clients/deltix/nightly-test/dial/review/-/jobs/1714646)
author_association: CONTRIBUTOR
user: login: irinakartun
created_at: 2025-03-19T11:36:12Z
body: /deploy-review
>Environment: [review-environment](https://chat-ai-dial-chat-pr-3381.nightly-test.deltixhub.io) | [pipeline](https://gitlab.deltixhub.com/Clients/deltix/nightly-test/dial/review/-/pipelines/583299)
>Overlay E2E tests status: [success](https://gitlab.deltixhub.com/Clients/deltix/nightly-test/dial/review/-/jobs/1717127)
>Chat E2E tests status: [failed](https://gitlab.deltixhub.com/Clients/deltix/nightly-test/dial/review/-/jobs/1717126)
author_association: CONTRIBUTOR