codemie-test-harness 0.1.206__py3-none-any.whl → 0.1.207__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of codemie-test-harness might be problematic. Click here for more details.
- codemie_test_harness/tests/ui/assistants/test_create_assistant.py +13 -13
- codemie_test_harness/tests/ui/assistants/test_edit_assistant.py +200 -0
- codemie_test_harness/tests/ui/pageobject/assistants/assistant_mcp_server.py +171 -0
- codemie_test_harness/tests/ui/pageobject/assistants/assistant_sidebar.py +140 -0
- codemie_test_harness/tests/ui/pageobject/assistants/assistant_view_page.py +256 -0
- codemie_test_harness/tests/ui/pageobject/assistants/assistants_page.py +63 -0
- codemie_test_harness/tests/ui/pageobject/assistants/{create_assistant_page.py → create_edit_assistant_page.py} +379 -95
- codemie_test_harness/tests/ui/test_data/assistant_test_data.py +347 -18
- {codemie_test_harness-0.1.206.dist-info → codemie_test_harness-0.1.207.dist-info}/METADATA +2 -2
- {codemie_test_harness-0.1.206.dist-info → codemie_test_harness-0.1.207.dist-info}/RECORD +12 -8
- {codemie_test_harness-0.1.206.dist-info → codemie_test_harness-0.1.207.dist-info}/WHEEL +0 -0
- {codemie_test_harness-0.1.206.dist-info → codemie_test_harness-0.1.207.dist-info}/entry_points.txt +0 -0
|
@@ -29,8 +29,8 @@ import pytest
|
|
|
29
29
|
from codemie_test_harness.tests.ui.pageobject.assistants.assistants_page import (
|
|
30
30
|
AssistantsPage,
|
|
31
31
|
)
|
|
32
|
-
from codemie_test_harness.tests.ui.pageobject.assistants.
|
|
33
|
-
|
|
32
|
+
from codemie_test_harness.tests.ui.pageobject.assistants.create_edit_assistant_page import (
|
|
33
|
+
CreateEditAssistantPage,
|
|
34
34
|
)
|
|
35
35
|
from codemie_test_harness.tests.ui.test_data.assistant_test_data import (
|
|
36
36
|
get_minimal_assistant_data,
|
|
@@ -56,7 +56,7 @@ class TestCreateAssistantPageElements:
|
|
|
56
56
|
- Action buttons (create, cancel)
|
|
57
57
|
- Common page components (header, navigation)
|
|
58
58
|
"""
|
|
59
|
-
create_page =
|
|
59
|
+
create_page = CreateEditAssistantPage(page)
|
|
60
60
|
create_page.navigate_to()
|
|
61
61
|
|
|
62
62
|
# Verify we are on the correct page
|
|
@@ -95,7 +95,7 @@ class TestCreateAssistantPageElements:
|
|
|
95
95
|
assistants_page.click_create_assistant()
|
|
96
96
|
|
|
97
97
|
# Verify we're on the create assistant page
|
|
98
|
-
create_page =
|
|
98
|
+
create_page = CreateEditAssistantPage(page)
|
|
99
99
|
create_page.should_be_on_create_assistant_page()
|
|
100
100
|
|
|
101
101
|
@pytest.mark.assistant
|
|
@@ -113,7 +113,7 @@ class TestCreateAssistantPageElements:
|
|
|
113
113
|
3. Verify modal can be closed or handled appropriately
|
|
114
114
|
4. Ensure manual form is accessible after modal handling
|
|
115
115
|
"""
|
|
116
|
-
create_page =
|
|
116
|
+
create_page = CreateEditAssistantPage(page)
|
|
117
117
|
|
|
118
118
|
# Navigate to create assistant page - this will handle the modal automatically
|
|
119
119
|
create_page.navigate_to()
|
|
@@ -140,7 +140,7 @@ class TestCreateAssistantPageElements:
|
|
|
140
140
|
3. Verify modal closes and manual form is accessible
|
|
141
141
|
4. Proceed with manual assistant creation
|
|
142
142
|
"""
|
|
143
|
-
create_page =
|
|
143
|
+
create_page = CreateEditAssistantPage(page)
|
|
144
144
|
|
|
145
145
|
# Navigate without automatic modal handling
|
|
146
146
|
page.goto(create_page.page_url)
|
|
@@ -168,7 +168,7 @@ class TestCreateAssistantFormInteractions:
|
|
|
168
168
|
|
|
169
169
|
@pytest.mark.assistant
|
|
170
170
|
@pytest.mark.ui
|
|
171
|
-
def test_create_assistant_form_field_interactions(self, page):
|
|
171
|
+
def test_create_assistant_form_field_interactions(self, page, client):
|
|
172
172
|
"""
|
|
173
173
|
Test form field interactions and input handling.
|
|
174
174
|
|
|
@@ -182,7 +182,7 @@ class TestCreateAssistantFormInteractions:
|
|
|
182
182
|
- Icon URL input
|
|
183
183
|
- Shared toggle switch
|
|
184
184
|
"""
|
|
185
|
-
create_page =
|
|
185
|
+
create_page = CreateEditAssistantPage(page)
|
|
186
186
|
create_page.navigate_to()
|
|
187
187
|
|
|
188
188
|
# Test shared toggle interaction
|
|
@@ -232,7 +232,7 @@ class TestCreateAssistantFormInteractions:
|
|
|
232
232
|
- Unchecked shared toggle
|
|
233
233
|
- Default assistant type selection
|
|
234
234
|
"""
|
|
235
|
-
create_page =
|
|
235
|
+
create_page = CreateEditAssistantPage(page)
|
|
236
236
|
create_page.navigate_to()
|
|
237
237
|
|
|
238
238
|
# Verify default empty field values
|
|
@@ -273,7 +273,7 @@ class TestCreateAssistantCriticalHappyPath:
|
|
|
273
273
|
test_data = get_minimal_assistant_data()
|
|
274
274
|
|
|
275
275
|
# Navigate to create assistant page
|
|
276
|
-
create_page =
|
|
276
|
+
create_page = CreateEditAssistantPage(page)
|
|
277
277
|
create_page.navigate_to()
|
|
278
278
|
|
|
279
279
|
# Use the comprehensive create_assistant method with test data
|
|
@@ -297,7 +297,7 @@ class TestCreateAssistantCriticalHappyPath:
|
|
|
297
297
|
This test ensures that the form validation works correctly and provides
|
|
298
298
|
appropriate feedback to users about required fields.
|
|
299
299
|
"""
|
|
300
|
-
create_page =
|
|
300
|
+
create_page = CreateEditAssistantPage(page)
|
|
301
301
|
create_page.navigate_to()
|
|
302
302
|
|
|
303
303
|
# Get test data using the factory
|
|
@@ -333,7 +333,7 @@ class TestCreateAssistantNavigation:
|
|
|
333
333
|
3. Click Cancel button
|
|
334
334
|
4. Verify return to Assistants page
|
|
335
335
|
"""
|
|
336
|
-
create_page =
|
|
336
|
+
create_page = CreateEditAssistantPage(page)
|
|
337
337
|
create_page.navigate_to()
|
|
338
338
|
|
|
339
339
|
# Get test data using the factory
|
|
@@ -371,7 +371,7 @@ class TestCreateAssistantNavigation:
|
|
|
371
371
|
assistants_page.click_create_assistant()
|
|
372
372
|
|
|
373
373
|
# Now on create assistant page
|
|
374
|
-
create_page =
|
|
374
|
+
create_page = CreateEditAssistantPage(page)
|
|
375
375
|
create_page.should_be_on_create_assistant_page()
|
|
376
376
|
create_page.handle_ai_generator_modal_if_visible()
|
|
377
377
|
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from tests import TEST_USER, PROJECT
|
|
3
|
+
from tests.ui.pageobject.assistants.assistant_view_page import AssistantViewPage
|
|
4
|
+
|
|
5
|
+
from tests.ui.pageobject.assistants.assistants_page import (
|
|
6
|
+
AssistantsPage,
|
|
7
|
+
)
|
|
8
|
+
from codemie_test_harness.tests.ui.test_data.assistant_test_data import (
|
|
9
|
+
ExternalToolKit,
|
|
10
|
+
MCPServersTool,
|
|
11
|
+
get_minimal_assistant_mcp_config_data,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
from tests.ui.test_data.assistant_test_data import (
|
|
16
|
+
TOOLKIT_TOOLS,
|
|
17
|
+
AssistantPopUpMessages,
|
|
18
|
+
AssistantValidationErrors,
|
|
19
|
+
)
|
|
20
|
+
from tests.ui.pageobject.assistants.create_edit_assistant_page import (
|
|
21
|
+
CreateEditAssistantPage,
|
|
22
|
+
)
|
|
23
|
+
from tests.utils.base_utils import get_random_name
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@pytest.mark.assistant_ui
|
|
27
|
+
@pytest.mark.ui
|
|
28
|
+
def test_edit_assistant_page_elements_visibility(page, assistant):
|
|
29
|
+
"""
|
|
30
|
+
Test all main elements are visible on Edit Assistant page—fields, toolkits, checkboxes, etc.
|
|
31
|
+
"""
|
|
32
|
+
edit_page = CreateEditAssistantPage(page)
|
|
33
|
+
assistant_page = AssistantsPage(page)
|
|
34
|
+
assistant = assistant()
|
|
35
|
+
|
|
36
|
+
assistant_page.navigate_to()
|
|
37
|
+
assistant_page.search_assistants(assistant.name)
|
|
38
|
+
assistant_page.click_assistant_edit(assistant.name)
|
|
39
|
+
|
|
40
|
+
edit_page.should_have_all_form_fields_visible()
|
|
41
|
+
edit_page.should_have_top_p_and_temperature()
|
|
42
|
+
edit_page.should_have_datasource_context()
|
|
43
|
+
edit_page.should_have_sub_assistants_context()
|
|
44
|
+
edit_page.should_have_categories_visible()
|
|
45
|
+
|
|
46
|
+
for section, toolkits in TOOLKIT_TOOLS.items():
|
|
47
|
+
edit_page.select_section(section.value)
|
|
48
|
+
for toolkit, tools in toolkits.items():
|
|
49
|
+
edit_page.select_toolkit(section.value, toolkit.value)
|
|
50
|
+
for tool in tools:
|
|
51
|
+
edit_page.should_be_visible_tool(tool.value)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@pytest.mark.assistant_ui
|
|
55
|
+
@pytest.mark.ui
|
|
56
|
+
def test_edit_assistant_form_field_interactions(page, assistant):
|
|
57
|
+
"""
|
|
58
|
+
Test interacting with all editable form fields.
|
|
59
|
+
"""
|
|
60
|
+
EDITED = " EDITED"
|
|
61
|
+
edit_page = CreateEditAssistantPage(page)
|
|
62
|
+
view_page = AssistantViewPage(page)
|
|
63
|
+
assistant_page = AssistantsPage(page)
|
|
64
|
+
assistant = assistant()
|
|
65
|
+
|
|
66
|
+
assistant_page.navigate_to()
|
|
67
|
+
assistant_page.search_assistants(assistant.name)
|
|
68
|
+
assistant_page.click_assistant_edit(assistant.name)
|
|
69
|
+
|
|
70
|
+
edit_page.fill_name(assistant.name + EDITED)
|
|
71
|
+
edit_page.should_have_name_value(assistant.name + EDITED)
|
|
72
|
+
edit_page.fill_description(assistant.description + EDITED)
|
|
73
|
+
edit_page.should_have_description_value(assistant.description + EDITED)
|
|
74
|
+
|
|
75
|
+
edit_page.toggle_shared_assistant(True)
|
|
76
|
+
edit_page.should_have_shared_checked()
|
|
77
|
+
edit_page.toggle_shared_assistant(False)
|
|
78
|
+
edit_page.should_have_shared_unchecked()
|
|
79
|
+
|
|
80
|
+
edit_page.fill_temperature("0.5")
|
|
81
|
+
edit_page.fill_top_p("0.5")
|
|
82
|
+
edit_page.should_have_top_p_and_temperature_value("0.5", "0.5")
|
|
83
|
+
edit_page.click_save()
|
|
84
|
+
assistant_page.should_see_updating_popup(
|
|
85
|
+
AssistantPopUpMessages.ASSISTANT_UPDATED_SUCCESS.value
|
|
86
|
+
)
|
|
87
|
+
assistant_page.should_see_assistant_with_name(assistant.name + EDITED)
|
|
88
|
+
|
|
89
|
+
assistant_page.click_assistant_view(assistant.name + EDITED)
|
|
90
|
+
view_page.should_have_all_form_fields_visible(
|
|
91
|
+
name=assistant.name + EDITED,
|
|
92
|
+
author=TEST_USER,
|
|
93
|
+
description=assistant.description + EDITED,
|
|
94
|
+
)
|
|
95
|
+
view_page.should_have_overview_form_fields_visible(
|
|
96
|
+
project=PROJECT, status="Not shared", assistant_id=assistant.id
|
|
97
|
+
)
|
|
98
|
+
view_page.should_have_access_links_form_fields_visible(
|
|
99
|
+
assistant_id=assistant.id, assistant_name=assistant.name
|
|
100
|
+
)
|
|
101
|
+
view_page.should_have_configuration_form_fields_visible(
|
|
102
|
+
temperature="0.5", top_p="0.5"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@pytest.mark.assistant_ui
|
|
107
|
+
@pytest.mark.ui
|
|
108
|
+
def test_edit_assistant_tools_interactions(page, assistant):
|
|
109
|
+
"""
|
|
110
|
+
Test interacting with all tools in assistant.
|
|
111
|
+
"""
|
|
112
|
+
edit_page = CreateEditAssistantPage(page)
|
|
113
|
+
view_page = AssistantViewPage(page)
|
|
114
|
+
assistant_page = AssistantsPage(page)
|
|
115
|
+
assistant = assistant()
|
|
116
|
+
|
|
117
|
+
assistant_page.navigate_to()
|
|
118
|
+
assistant_page.search_assistants(assistant.name)
|
|
119
|
+
assistant_page.click_assistant_edit(assistant.name)
|
|
120
|
+
|
|
121
|
+
for section, toolkits in TOOLKIT_TOOLS.items():
|
|
122
|
+
edit_page.select_section(section.value)
|
|
123
|
+
for toolkit, tools in toolkits.items():
|
|
124
|
+
edit_page.select_toolkit(section.value, toolkit.value)
|
|
125
|
+
for tool in tools:
|
|
126
|
+
edit_page.select_tool(tool.value)
|
|
127
|
+
edit_page.click_save()
|
|
128
|
+
assistant_page.should_see_updating_popup(
|
|
129
|
+
AssistantPopUpMessages.ASSISTANT_UPDATED_SUCCESS.value
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
assistant_page.click_assistant_view(assistant.name)
|
|
133
|
+
for section, toolkits in TOOLKIT_TOOLS.items():
|
|
134
|
+
for toolkit, tools in toolkits.items():
|
|
135
|
+
toolkit_label = toolkit.value
|
|
136
|
+
if ExternalToolKit.MCP_SERVERS.value in toolkit.value:
|
|
137
|
+
toolkit_label = "MCP"
|
|
138
|
+
view_page.should_see_toolkit_visible(toolkit_label)
|
|
139
|
+
for tool in tools:
|
|
140
|
+
test_data = get_minimal_assistant_mcp_config_data()
|
|
141
|
+
tool_label = tool.value
|
|
142
|
+
if MCPServersTool.ADD_MCP_SERVER.value in tool.value:
|
|
143
|
+
tool_label = test_data.name
|
|
144
|
+
view_page.should_see_toolkit_contains(toolkit_label, tool_label)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@pytest.mark.assistant_ui
|
|
148
|
+
@pytest.mark.ui
|
|
149
|
+
def test_edit_assistant_incorrect_form(page, assistant):
|
|
150
|
+
"""
|
|
151
|
+
Test interacting with incorrect data.
|
|
152
|
+
"""
|
|
153
|
+
edit_page = CreateEditAssistantPage(page)
|
|
154
|
+
assistant_page = AssistantsPage(page)
|
|
155
|
+
assistant = assistant()
|
|
156
|
+
|
|
157
|
+
assistant_page.navigate_to()
|
|
158
|
+
assistant_page.search_assistants(assistant.name)
|
|
159
|
+
assistant_page.click_assistant_edit(assistant.name)
|
|
160
|
+
|
|
161
|
+
edit_page.fill_name("")
|
|
162
|
+
edit_page.should_have_name_error_textarea(
|
|
163
|
+
AssistantValidationErrors.NAME_REQUIRED.value
|
|
164
|
+
)
|
|
165
|
+
edit_page.fill_name(get_random_name())
|
|
166
|
+
|
|
167
|
+
edit_page.fill_icon_url(get_random_name())
|
|
168
|
+
edit_page.should_have_icon_error_textarea(
|
|
169
|
+
AssistantValidationErrors.ICON_URL_NOT_VALID.value
|
|
170
|
+
)
|
|
171
|
+
edit_page.fill_icon_url("")
|
|
172
|
+
|
|
173
|
+
edit_page.fill_description("")
|
|
174
|
+
edit_page.should_have_description_error_textarea(
|
|
175
|
+
AssistantValidationErrors.DESCRIPTION_REQUIRED.value
|
|
176
|
+
)
|
|
177
|
+
edit_page.fill_description(get_random_name())
|
|
178
|
+
|
|
179
|
+
edit_page.fill_system_prompt("")
|
|
180
|
+
edit_page.should_have_system_prompt_error_textarea(
|
|
181
|
+
AssistantValidationErrors.SYSTEM_PROMPT_REQUIRED.value
|
|
182
|
+
)
|
|
183
|
+
edit_page.fill_system_prompt(get_random_name())
|
|
184
|
+
|
|
185
|
+
edit_page.fill_temperature("3")
|
|
186
|
+
edit_page.should_have_temperature_error_textarea(
|
|
187
|
+
AssistantValidationErrors.TEMPERATURE_NOT_VALID.value
|
|
188
|
+
)
|
|
189
|
+
edit_page.fill_temperature("1")
|
|
190
|
+
|
|
191
|
+
edit_page.fill_top_p("2")
|
|
192
|
+
edit_page.should_have_top_p_error_textarea(
|
|
193
|
+
AssistantValidationErrors.TOP_P_NOT_VALID.value
|
|
194
|
+
)
|
|
195
|
+
edit_page.fill_top_p("1")
|
|
196
|
+
|
|
197
|
+
edit_page.click_save()
|
|
198
|
+
assistant_page.should_see_updating_popup(
|
|
199
|
+
AssistantPopUpMessages.ASSISTANT_UPDATED_SUCCESS.value
|
|
200
|
+
)
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
from playwright.sync_api import Page, Locator, expect
|
|
2
|
+
from reportportal_client import step
|
|
3
|
+
from tests.ui.test_data.assistant_test_data import get_minimal_assistant_mcp_config_data
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AssistantMCPIntegrationModal:
|
|
7
|
+
def __init__(self, page: Page):
|
|
8
|
+
self.page = page
|
|
9
|
+
|
|
10
|
+
@property
|
|
11
|
+
def mcp_pop_up(self):
|
|
12
|
+
"""Main dialog container using p-dialog class"""
|
|
13
|
+
return self.page.locator("div.p-dialog[role='dialog']").filter(
|
|
14
|
+
has_text="Add new MCP server"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
def mcp_pop_up_header(self):
|
|
18
|
+
"""Dialog header with 'Add new MCP server' text"""
|
|
19
|
+
return self.mcp_pop_up.locator(
|
|
20
|
+
"h4.text-base.font-semibold:has-text('Add new MCP server')"
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# --- Name input ---
|
|
24
|
+
@property
|
|
25
|
+
def name_input(self) -> Locator:
|
|
26
|
+
return self.mcp_pop_up.locator("input[name='name'][placeholder='Name*']")
|
|
27
|
+
|
|
28
|
+
# --- Description textarea ---
|
|
29
|
+
@property
|
|
30
|
+
def description_textarea(self) -> Locator:
|
|
31
|
+
return self.mcp_pop_up.locator(
|
|
32
|
+
"textarea[name='description'][placeholder='Description*']"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# --- Tools Tokens Size Limit input ---
|
|
36
|
+
@property
|
|
37
|
+
def tokens_size_limit_input(self) -> Locator:
|
|
38
|
+
return self.mcp_pop_up.locator("input[name='tokensSizeLimit'][type='number']")
|
|
39
|
+
|
|
40
|
+
# --- Configuration mode select (JSON/Form) ---
|
|
41
|
+
@property
|
|
42
|
+
def config_mode_switch(self) -> Locator:
|
|
43
|
+
return self.mcp_pop_up.locator("div.p-selectbutton.p-button-group")
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def config_mode_json_radio(self) -> Locator:
|
|
47
|
+
return self.config_mode_switch.locator(
|
|
48
|
+
"div.p-button[role='button']:has(span:has-text('JSON'))"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def config_mode_form_radio(self) -> Locator:
|
|
53
|
+
return self.config_mode_switch.locator(
|
|
54
|
+
"div.p-button[role='button']:has(span:has-text('Form'))"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# --- Configuration (JSON) textarea ---
|
|
58
|
+
@property
|
|
59
|
+
def configuration_json_textarea(self) -> Locator:
|
|
60
|
+
return self.mcp_pop_up.locator("textarea[name='configJson']#json-config")
|
|
61
|
+
|
|
62
|
+
# --- Environment Variables dropdown ---
|
|
63
|
+
@property
|
|
64
|
+
def env_vars_dropdown(self) -> Locator:
|
|
65
|
+
return self.mcp_pop_up.locator(
|
|
66
|
+
"div.p-dropdown:has(span.p-dropdown-label:text('Environment Variables'))"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# --- MCP-Connect URL input ---
|
|
70
|
+
@property
|
|
71
|
+
def mcp_connect_url_input(self) -> Locator:
|
|
72
|
+
return self.mcp_pop_up.locator(
|
|
73
|
+
"input[name='connectUrl'][placeholder='https://']"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# --- Buttons ---
|
|
77
|
+
@property
|
|
78
|
+
def cancel_button(self) -> Locator:
|
|
79
|
+
return self.mcp_pop_up.locator(
|
|
80
|
+
"button.bg-button-secondary-bg:has-text('Cancel')"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def test_integration_button(self) -> Locator:
|
|
85
|
+
return self.mcp_pop_up.locator(
|
|
86
|
+
"button.bg-button-secondary-bg:has-text('Test Integration')"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def add_button(self) -> Locator:
|
|
91
|
+
return self.mcp_pop_up.locator("button.bg-button-primary-bg:has-text('Add')")
|
|
92
|
+
|
|
93
|
+
# ---- Action Methods ----
|
|
94
|
+
|
|
95
|
+
@step
|
|
96
|
+
def fill_name(self, value: str):
|
|
97
|
+
self.name_input.fill(value)
|
|
98
|
+
|
|
99
|
+
@step
|
|
100
|
+
def fill_description(self, value: str):
|
|
101
|
+
self.description_textarea.fill(value)
|
|
102
|
+
|
|
103
|
+
@step
|
|
104
|
+
def fill_tokens_size_limit(self, value: int):
|
|
105
|
+
self.tokens_size_limit_input.fill(str(value))
|
|
106
|
+
|
|
107
|
+
@step
|
|
108
|
+
def select_config_mode(self, mode: str):
|
|
109
|
+
if mode.lower() == "json":
|
|
110
|
+
self.config_mode_json_radio.click()
|
|
111
|
+
elif mode.lower() == "form":
|
|
112
|
+
self.config_mode_form_radio.click()
|
|
113
|
+
else:
|
|
114
|
+
raise ValueError("Mode must be 'json' or 'form'")
|
|
115
|
+
|
|
116
|
+
@step
|
|
117
|
+
def fill_configuration_json(self, value: str):
|
|
118
|
+
self.configuration_json_textarea.fill(value)
|
|
119
|
+
|
|
120
|
+
@step
|
|
121
|
+
def open_env_vars_dropdown(self):
|
|
122
|
+
self.env_vars_dropdown.click()
|
|
123
|
+
|
|
124
|
+
@step
|
|
125
|
+
def fill_mcp_connect_url(self, value: str):
|
|
126
|
+
self.mcp_connect_url_input.fill(value)
|
|
127
|
+
|
|
128
|
+
@step
|
|
129
|
+
def click_cancel(self):
|
|
130
|
+
self.cancel_button.click()
|
|
131
|
+
|
|
132
|
+
@step
|
|
133
|
+
def click_test_integration(self):
|
|
134
|
+
self.test_integration_button.click()
|
|
135
|
+
|
|
136
|
+
@step
|
|
137
|
+
def click_add(self):
|
|
138
|
+
self.add_button.click()
|
|
139
|
+
|
|
140
|
+
@step
|
|
141
|
+
def fill_mcp_server_base_form(self):
|
|
142
|
+
test_data = get_minimal_assistant_mcp_config_data()
|
|
143
|
+
self.fill_name(test_data.name)
|
|
144
|
+
self.fill_description(test_data.description)
|
|
145
|
+
self.fill_configuration_json(test_data.configuration)
|
|
146
|
+
self.click_add()
|
|
147
|
+
|
|
148
|
+
# --- Assertions (optional helpers) ---
|
|
149
|
+
|
|
150
|
+
def is_pop_visible(self):
|
|
151
|
+
return self.mcp_pop_up_header().count() > 0
|
|
152
|
+
|
|
153
|
+
@step
|
|
154
|
+
def should_see_name(self, name: str):
|
|
155
|
+
expect(self.name_input).to_have_value(name)
|
|
156
|
+
|
|
157
|
+
@step
|
|
158
|
+
def should_see_description(self, description: str):
|
|
159
|
+
expect(self.description_textarea).to_have_value(description)
|
|
160
|
+
|
|
161
|
+
@step
|
|
162
|
+
def should_see_tokens_size_limit(self, token_size: str):
|
|
163
|
+
expect(self.tokens_size_limit_input).to_have_value(token_size)
|
|
164
|
+
|
|
165
|
+
@step
|
|
166
|
+
def should_see_configuration_json(self, config: str):
|
|
167
|
+
expect(self.configuration_json_textarea).to_have_value(config)
|
|
168
|
+
|
|
169
|
+
@step
|
|
170
|
+
def should_see_mcp_connect_url(self, url: str):
|
|
171
|
+
expect(self.mcp_connect_url_input).to_have_value(url)
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
from playwright.sync_api import Page, Locator
|
|
2
|
+
from reportportal_client import step
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class AssistantSidebar:
|
|
6
|
+
"""Page Object Model for the Assistants Page Sidebar."""
|
|
7
|
+
|
|
8
|
+
def __init__(self, page: Page):
|
|
9
|
+
self.page = page
|
|
10
|
+
|
|
11
|
+
# --- Locators ---
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def main_sidebar(self) -> Locator:
|
|
15
|
+
"""The sidebar <aside>."""
|
|
16
|
+
return self.page.locator("aside.flex.flex-col.border-r.bg-sidebar-gradient")
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def title(self) -> Locator:
|
|
20
|
+
"""Sidebar title: 'Assistants'."""
|
|
21
|
+
return self.main_sidebar.locator("h2:text-is('Assistants')")
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def subtitle(self) -> Locator:
|
|
25
|
+
"""Sidebar subtitle paragraph."""
|
|
26
|
+
return self.main_sidebar.locator("p.text-sm.text-text-secondary")
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def categories_label(self) -> Locator:
|
|
30
|
+
"""The 'Categories' label/heading."""
|
|
31
|
+
return self.main_sidebar.locator("span:text-is('Categories')")
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def categories_list(self) -> Locator:
|
|
35
|
+
"""<ul> under 'Categories'."""
|
|
36
|
+
return self.categories_label.locator("xpath=../../ul")
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def category_items(self) -> Locator:
|
|
40
|
+
"""All category <li> elements."""
|
|
41
|
+
return self.categories_list.locator("li")
|
|
42
|
+
|
|
43
|
+
def category_button(self, name: str) -> Locator:
|
|
44
|
+
"""Button for a category by visible text."""
|
|
45
|
+
return self.categories_list.locator(f"button:has-text('{name}')")
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def filters_label(self) -> Locator:
|
|
49
|
+
"""'Filters' span above filters section."""
|
|
50
|
+
return self.main_sidebar.locator("span:text-is('Filters')")
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def clear_all_filters_button(self) -> Locator:
|
|
54
|
+
"""'Clear all' filter reset button."""
|
|
55
|
+
return self.main_sidebar.locator("button:has-text('Clear all')")
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def search_input(self) -> Locator:
|
|
59
|
+
"""Filter/search input."""
|
|
60
|
+
return self.main_sidebar.locator("input[placeholder='Search']")
|
|
61
|
+
|
|
62
|
+
# Accordion and filter options are unique/complex:
|
|
63
|
+
def accordion_tab(self, tab_title: str) -> Locator:
|
|
64
|
+
"""Accordion tab by title, e.g. 'PROJECT', 'CREATED BY', ..."""
|
|
65
|
+
return self.main_sidebar.locator(
|
|
66
|
+
f".p-accordion-header-text span:has-text('{tab_title.upper()}')"
|
|
67
|
+
).locator("xpath=../../..")
|
|
68
|
+
|
|
69
|
+
def multiselect_filter(self, label: str) -> Locator:
|
|
70
|
+
"""Filter dropdown by its label in filter accordion, e.g. 'Project'."""
|
|
71
|
+
return self.main_sidebar.locator(f".p-multiselect-label:has-text('{label}')")
|
|
72
|
+
|
|
73
|
+
def created_by_input(self) -> Locator:
|
|
74
|
+
"""Input for 'Created By' filter."""
|
|
75
|
+
return self.main_sidebar.locator("input[placeholder='Created By']")
|
|
76
|
+
|
|
77
|
+
def radio_filter_option(self, label: str) -> Locator:
|
|
78
|
+
"""Radio 'Shared' filter options like All/With Project/Not Shared."""
|
|
79
|
+
return (
|
|
80
|
+
self.main_sidebar.locator(f"label.flex.items-center span:text('{label}')")
|
|
81
|
+
.locator("xpath=..")
|
|
82
|
+
.locator("input[type='radio']")
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# --- Methods ---
|
|
86
|
+
|
|
87
|
+
@step
|
|
88
|
+
def click_category(self, name: str):
|
|
89
|
+
"""Clicks on a category item by label."""
|
|
90
|
+
self.category_button(name).click()
|
|
91
|
+
|
|
92
|
+
@step
|
|
93
|
+
def select_filter_tab(self, tab_title: str):
|
|
94
|
+
"""Expands a filter accordion tab by label."""
|
|
95
|
+
tab = self.accordion_tab(tab_title)
|
|
96
|
+
tab.locator("a.p-accordion-header-link").click()
|
|
97
|
+
|
|
98
|
+
@step
|
|
99
|
+
def clear_filters(self):
|
|
100
|
+
"""Clicks the 'Clear all' button in filters."""
|
|
101
|
+
self.clear_all_filters_button.click()
|
|
102
|
+
|
|
103
|
+
@step
|
|
104
|
+
def search(self, text: str):
|
|
105
|
+
"""Sets sidebar search/filter."""
|
|
106
|
+
self.search_input.fill(text)
|
|
107
|
+
|
|
108
|
+
@step
|
|
109
|
+
def select_multiselect_option(self, label: str):
|
|
110
|
+
"""Clicks on a filter dropdown and selects the label (if supported)."""
|
|
111
|
+
ms = self.multiselect_filter(label)
|
|
112
|
+
ms.click()
|
|
113
|
+
ms.locator(f"..//li[.='{label}']").click()
|
|
114
|
+
|
|
115
|
+
@step
|
|
116
|
+
def set_created_by(self, author: str):
|
|
117
|
+
"""Sets the 'Created By' filter text."""
|
|
118
|
+
input_field = self.created_by_input()
|
|
119
|
+
input_field.fill(author)
|
|
120
|
+
|
|
121
|
+
@step
|
|
122
|
+
def select_radio_option(self, option_label: str):
|
|
123
|
+
"""Selects a radio option for shared status."""
|
|
124
|
+
self.radio_filter_option(option_label).check(force=True)
|
|
125
|
+
|
|
126
|
+
@step
|
|
127
|
+
def visible_categories(self):
|
|
128
|
+
"""Returns the names of all visible categories."""
|
|
129
|
+
return [
|
|
130
|
+
self.category_items.nth(i).inner_text().strip()
|
|
131
|
+
for i in range(self.category_items.count())
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
@step
|
|
135
|
+
def visible_filter_radios(self):
|
|
136
|
+
"""Returns the text values of radio options in Shared filter."""
|
|
137
|
+
# Find all labels within the 'Shared' tab
|
|
138
|
+
tab = self.accordion_tab("SHARED")
|
|
139
|
+
radios = tab.locator("label.flex.items-center")
|
|
140
|
+
return [radios.nth(i).inner_text().strip() for i in range(radios.count())]
|