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.

@@ -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.create_assistant_page import (
33
- CreateAssistantPage,
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 = CreateAssistantPage(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 = CreateAssistantPage(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 = CreateAssistantPage(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 = CreateAssistantPage(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 = CreateAssistantPage(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 = CreateAssistantPage(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 = CreateAssistantPage(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 = CreateAssistantPage(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 = CreateAssistantPage(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 = CreateAssistantPage(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())]