codemie-test-harness 0.1.177__py3-none-any.whl → 0.1.178__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.

Files changed (22) hide show
  1. codemie_test_harness/tests/test_data/google_datasource_test_data.py +4 -1
  2. codemie_test_harness/tests/ui/_test_data/datasource_test_data.py +125 -0
  3. codemie_test_harness/tests/ui/datasource/__init__.py +0 -0
  4. codemie_test_harness/tests/ui/datasource/test_create_datasource.py +188 -0
  5. codemie_test_harness/tests/ui/datasource/test_datasource_page.py +61 -0
  6. codemie_test_harness/tests/ui/datasource/test_edit_datasource.py +180 -0
  7. codemie_test_harness/tests/ui/datasource/test_view_datasource.py +220 -0
  8. codemie_test_harness/tests/ui/pageobject/assistants/assistants_page.py +1 -1
  9. codemie_test_harness/tests/ui/pageobject/base_page.py +31 -5
  10. codemie_test_harness/tests/ui/pageobject/components/project_selector.py +116 -0
  11. codemie_test_harness/tests/ui/pageobject/components/workflow_sidebar.py +1 -1
  12. codemie_test_harness/tests/ui/pageobject/datasources/__init__.py +0 -0
  13. codemie_test_harness/tests/ui/pageobject/datasources/create_edit_datasource_page.py +771 -0
  14. codemie_test_harness/tests/ui/pageobject/datasources/datasource_page.py +233 -0
  15. codemie_test_harness/tests/ui/pageobject/datasources/datasource_sidebar.py +303 -0
  16. codemie_test_harness/tests/ui/pageobject/datasources/view_datasource_page.py +335 -0
  17. codemie_test_harness/tests/utils/datasource_utils.py +13 -18
  18. {codemie_test_harness-0.1.177.dist-info → codemie_test_harness-0.1.178.dist-info}/METADATA +2 -2
  19. {codemie_test_harness-0.1.177.dist-info → codemie_test_harness-0.1.178.dist-info}/RECORD +21 -10
  20. codemie_test_harness/tests/ui/pytest.ini +0 -18
  21. {codemie_test_harness-0.1.177.dist-info → codemie_test_harness-0.1.178.dist-info}/WHEEL +0 -0
  22. {codemie_test_harness-0.1.177.dist-info → codemie_test_harness-0.1.178.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,233 @@
1
+ import re
2
+
3
+ from hamcrest import has_length, assert_that, greater_than_or_equal_to
4
+ from reportportal_client import step
5
+ from playwright.sync_api import expect
6
+
7
+ from codemie_test_harness.tests.ui.pageobject.base_page import BasePage
8
+ from tests.ui._test_data.datasource_test_data import DATAS_SOURCE_COLUMN_LIST
9
+ from tests.ui.pageobject.datasources.datasource_sidebar import DataSourceSidebar
10
+
11
+
12
+ class DataSourcePage(BasePage):
13
+ """Data Sources page object with property-based element locators."""
14
+
15
+ page_url = "#/data-sources"
16
+
17
+ def __init__(self, page):
18
+ super().__init__(page)
19
+ self.sidebar = DataSourceSidebar(page)
20
+
21
+ # -----------------
22
+ # Page Elements
23
+ # -----------------
24
+
25
+ @property
26
+ def create_datasource_button(self):
27
+ """'Create Datasource' button."""
28
+ return self.page.locator('button:has-text("Create Datasource")')
29
+
30
+ @property
31
+ def table(self):
32
+ """Datasource table."""
33
+ return self.page.locator("table")
34
+
35
+ @property
36
+ def table_rows(self):
37
+ """Table data rows."""
38
+ return self.table.locator("tbody tr")
39
+
40
+ def table_columns_names(self, column: str):
41
+ """Table data column names."""
42
+ return self.table.locator(".text-left:not(.text-gray-50)").filter(
43
+ has_text=re.compile(f"^{re.escape(column)}$")
44
+ )
45
+
46
+ @property
47
+ def row_menu_view_details_button(self):
48
+ return self.page.locator("span.text-left.grow").filter(has_text="View Details")
49
+
50
+ @property
51
+ def row_menu_edit_button(self):
52
+ return self.page.locator("span.text-left.grow").filter(has_text="Edit")
53
+
54
+ @property
55
+ def row_menu_copy_id_button(self):
56
+ return self.page.locator("span.text-left.grow").filter(has_text="Copy ID")
57
+
58
+ @property
59
+ def row_menu_export_button(self):
60
+ return self.page.locator("span.text-left.grow").filter(has_text="Export")
61
+
62
+ @property
63
+ def row_menu_delete_button(self):
64
+ return self.page.locator("span.text-left.grow").filter(has_text="Delete")
65
+
66
+ @property
67
+ def row_menu_full_reindex_button(self):
68
+ return self.page.locator("span.text-left.grow").filter(has_text="Full Reindex")
69
+
70
+ @property
71
+ def row_menu_incremental_index_button(self):
72
+ return self.page.locator("span.text-left.grow").filter(
73
+ has_text="Incremental Index"
74
+ )
75
+
76
+ # -------------------------------
77
+ # Navigation methods
78
+ # -------------------------------
79
+ @step
80
+ def navigate_to(self):
81
+ """
82
+ Navigate to the DataSource page.
83
+
84
+ Returns:
85
+ self: Returns the page object for method chaining
86
+ """
87
+ self.page.goto(self.page_url)
88
+ self.wait_for_page_load()
89
+
90
+ return self
91
+
92
+ # -----------------
93
+ # Table Methods
94
+ # -----------------
95
+ @step
96
+ def get_table_row_by_name(self, name: str):
97
+ """Get the row as locator by datasource name."""
98
+ return self.page.locator("tbody tr:has(td:nth-child(1) span.font-bold)").filter(
99
+ has_text=name
100
+ )
101
+
102
+ def get_cell_in_row(self, row_locator, col_idx):
103
+ return row_locator.locator(f"td:nth-child({col_idx})")
104
+
105
+ def get_status_in_row(self, row_locator):
106
+ return self.get_cell_in_row(row_locator, 8).locator("div.inline-flex")
107
+
108
+ def get_project_in_row(self, row_locator):
109
+ return self.get_cell_in_row(row_locator, 2).locator("span")
110
+
111
+ def get_type_in_row(self, row_locator):
112
+ return self.get_cell_in_row(row_locator, 3)
113
+
114
+ def get_created_by_in_row(self, row_locator):
115
+ return self.get_cell_in_row(row_locator, 4).locator("span")
116
+
117
+ @step
118
+ def click_datasource_row_by_name(self, name: str):
119
+ self.get_table_row_by_name(name).locator("span.font-bold").click()
120
+ return self
121
+
122
+ def get_row_action_menu_button(self, datasource_name: str):
123
+ """
124
+ Returns the 3-dot menu button locator for a row by datasource name.
125
+ """
126
+ # Find the row with the given datasource name,
127
+ # then get 3-dot menu button in the last column.
128
+ row = self.get_table_row_by_name(datasource_name)
129
+ return row.locator("td:last-child button")
130
+
131
+ @step
132
+ def open_row_action_menu(self, datasource_name: str):
133
+ """
134
+ Opens the 3-dot actions menu for a row by datasource name.
135
+ """
136
+ btn = self.get_row_action_menu_button(datasource_name)
137
+ expect(btn).to_be_visible()
138
+ btn.click()
139
+ return self
140
+
141
+ @step
142
+ def click_row_action(self, action_text: str):
143
+ """
144
+ Clicks a menu item in the currently open 3-dot menu by the visible text.
145
+ """
146
+ self.page.locator("button").filter(has_text=action_text).click()
147
+ return self
148
+
149
+ @step
150
+ def open_and_select_row_action(self, datasource_name: str, action_text: str):
151
+ """
152
+ Opens the 3-dot menu in the specified row and selects a menu action.
153
+ """
154
+ self.open_row_action_menu(datasource_name)
155
+ self.click_row_action(action_text)
156
+ return self
157
+
158
+ # -----------------
159
+ # Verification Methods
160
+ # -----------------
161
+
162
+ @step
163
+ def should_see_create_datasource_button(self):
164
+ expect(self.create_datasource_button).to_be_visible()
165
+ return self
166
+
167
+ @step
168
+ def should_see_table_rows(self, minimum_count: int = 1):
169
+ expect(self.table_rows.first).to_be_visible()
170
+ assert_that(
171
+ self.table_rows.all(), has_length(greater_than_or_equal_to(minimum_count))
172
+ )
173
+ return self
174
+
175
+ @step
176
+ def should_see_table_column_names(self):
177
+ for column in DATAS_SOURCE_COLUMN_LIST:
178
+ column_name = self.table_columns_names(column)
179
+ expect(column_name).to_be_visible()
180
+
181
+ @step
182
+ def should_see_datasource_with_name(self, name: str):
183
+ expect(self.get_table_row_by_name(name)).to_be_visible()
184
+ return self
185
+
186
+ @step
187
+ def should_see_pagination(self, page: int = 1):
188
+ expect(self.pagination_block).to_be_visible()
189
+ expect(self.pagination_page_button(page)).to_be_visible()
190
+ expect(self.show_per_page_dropdown).to_be_visible()
191
+ expect(self.show_per_page_label).to_be_visible()
192
+ return self
193
+
194
+ @step
195
+ def should_see_table_row_with_values(
196
+ self, name, status, project=None, type_=None, created_by=None, timeout=60000
197
+ ):
198
+ """
199
+ Verifies that a table row exists for the given parameters, and has the expected status.
200
+ Optionally checks project, type, created_by columns.
201
+ """
202
+ row = self.get_table_row_by_name(name)
203
+ expect(row).to_be_visible(timeout=5000)
204
+
205
+ if project is not None:
206
+ expect(self.get_project_in_row(row)).to_have_text(project)
207
+ if type_ is not None:
208
+ expect(self.get_type_in_row(row)).to_have_text(type_)
209
+ if created_by is not None:
210
+ expect(self.get_created_by_in_row(row)).to_have_text(created_by)
211
+
212
+ expect(self.get_status_in_row(row)).to_have_text(status, timeout=timeout)
213
+ return self
214
+
215
+ @step
216
+ def should_see_edit_dropdown_values(self):
217
+ expect(self.row_menu_view_details_button).to_be_visible()
218
+ expect(self.row_menu_edit_button).to_be_visible()
219
+ expect(self.row_menu_copy_id_button).to_be_visible()
220
+ expect(self.row_menu_export_button).to_be_visible()
221
+ expect(self.row_menu_delete_button).to_be_visible()
222
+ return self
223
+
224
+ @step
225
+ def should_see_edit_dropdown_index_values(self):
226
+ expect(self.row_menu_incremental_index_button).to_be_visible()
227
+ expect(self.row_menu_full_reindex_button).to_be_visible()
228
+ return self
229
+
230
+ @step
231
+ def should_see_edit_dropdown_full_reindex_value(self):
232
+ expect(self.row_menu_full_reindex_button).to_be_visible()
233
+ return self
@@ -0,0 +1,303 @@
1
+ from reportportal_client import step
2
+ from playwright.sync_api import expect
3
+ from tests.ui._test_data.datasource_test_data import (
4
+ DATA_SOURCE_FILTER_STATUSES_LIST,
5
+ DATA_SOURCE_FILTER_TYPES_LIST,
6
+ PROJECT_LABEL,
7
+ STATUS_LABEL,
8
+ )
9
+ from tests.ui.pageobject.base_page import BasePage
10
+
11
+
12
+ class DataSourceSidebar(BasePage):
13
+ """Sidebar for Data Sources page: filters, search, type, etc."""
14
+
15
+ def __init__(self, page):
16
+ super().__init__(page)
17
+
18
+ @property
19
+ def sidebar_container(self):
20
+ """Main sidebar container."""
21
+ return self.page.locator("aside.bg-sidebar-gradient")
22
+
23
+ @property
24
+ def page_title(self):
25
+ """Page title."""
26
+ return self.sidebar_container.locator("h2.text-2xl")
27
+
28
+ @property
29
+ def page_subtitle(self):
30
+ """Subtitle below title."""
31
+ return self.sidebar_container.locator("p.text-sm")
32
+
33
+ @property
34
+ def search_input(self):
35
+ """Search input in Filters section."""
36
+ return self.sidebar_container.locator('input[placeholder="Search"]')
37
+
38
+ @property
39
+ def filters_section(self):
40
+ """The Filters section."""
41
+ return self.sidebar_container.locator("span").filter(has_text="Filters")
42
+
43
+ @property
44
+ def type_filter_section(self):
45
+ """Type filter accordion."""
46
+ return (
47
+ self.sidebar_container.get_by_role("button", name="TYPE")
48
+ .locator("..")
49
+ .locator("..")
50
+ )
51
+
52
+ def get_type_checkbox(self, type_label: str):
53
+ """
54
+ Returns the locator for a TYPE filter checkbox by its label text.
55
+ """
56
+ return self.sidebar_container.locator("div.p-checkbox + label").filter(
57
+ has_text=type_label
58
+ )
59
+
60
+ def get_type_checkbox_div(self, type_label: str):
61
+ """
62
+ Returns the outer <div class="p-checkbox ..."> for a specific checkbox type.
63
+ """
64
+ # Finds the label first, then goes to the preceding p-checkbox
65
+ return self.get_type_checkbox(type_label).locator(
66
+ "xpath=preceding-sibling::div[contains(@class, 'p-checkbox')]"
67
+ )
68
+
69
+ @property
70
+ def project_filter_multiselect(self):
71
+ """Project filter dropdown."""
72
+ return self.sidebar_container.locator('div[data-pc-name="multiselect"]')
73
+
74
+ @property
75
+ def created_by_me_checkbox(self):
76
+ """'Created by Me' filter checkbox."""
77
+ return self.page.locator("span.text-sm.text-text-gray-100 + div div.p-checkbox")
78
+
79
+ @property
80
+ def created_by_field(self):
81
+ """Created By user autocomplete input."""
82
+ return self.page.locator('#created_by input[type="text"]')
83
+
84
+ @property
85
+ def status_dropdown(self):
86
+ """Status filter dropdown."""
87
+ return self.sidebar_container.locator("#status")
88
+
89
+ def status_dropdown_items(self, status: str):
90
+ """Status dropdown items."""
91
+ return self.page.locator(".target-select-option").filter(has_text=status)
92
+
93
+ @property
94
+ def clear_all_button(self):
95
+ """Clear all button."""
96
+ return self.sidebar_container.locator(".button").filter(has_text="Clear All")
97
+
98
+ @property
99
+ def hide_type_filter_button(self):
100
+ """Hide Type filter button."""
101
+ return self.sidebar_container.locator(
102
+ '.leading-normal.font-semibold:has-text("TYPE")'
103
+ )
104
+
105
+ @property
106
+ def hide_project_filter_button(self):
107
+ """Hide Project filter button."""
108
+ return self.sidebar_container.locator(
109
+ '.leading-normal.font-semibold:has-text("PROJECT")'
110
+ )
111
+
112
+ @property
113
+ def hide_created_by_filter_button(self):
114
+ """Hide Created By filter button."""
115
+ return self.sidebar_container.locator(
116
+ '.leading-normal.font-semibold:has-text("CREATED BY")'
117
+ )
118
+
119
+ @property
120
+ def hide_status_filter_button(self):
121
+ """Hide Status filter button."""
122
+ return self.sidebar_container.locator(
123
+ '.leading-normal.font-semibold:has-text("STATUS")'
124
+ )
125
+
126
+ # -----------------
127
+ # Action Methods
128
+ # -----------------
129
+ @step
130
+ def click_create_datasource(self):
131
+ self.create_datasource_button.click()
132
+ return self
133
+
134
+ @step
135
+ def input_data_search(self, search_term: str):
136
+ self.search_input.fill(search_term)
137
+ return self
138
+
139
+ @step
140
+ def toggle_type_filter(self, type_label: str):
141
+ self.get_type_checkbox(type_label).click()
142
+ return self
143
+
144
+ @step
145
+ def select_project(self, project_name: str):
146
+ self.project_filter_multiselect.click()
147
+ self.page.locator(f'li:has-text("{project_name}")').click()
148
+ return self
149
+
150
+ @step
151
+ def toggle_created_by_me(self):
152
+ self.created_by_me_checkbox.click()
153
+ return self
154
+
155
+ @step
156
+ def filter_by_creator(self, user_name: str):
157
+ self.created_by_field.fill(user_name)
158
+ self.page.keyboard.press("Enter")
159
+ return self
160
+
161
+ @step
162
+ def select_status(self, status_label: str):
163
+ self.status_dropdown.click()
164
+ self.page.locator(f'li:has-text("{status_label}")').click()
165
+ return self
166
+
167
+ @step
168
+ def click_clear_all_button(self):
169
+ self.clear_all_button.click()
170
+ return self
171
+
172
+ @step
173
+ def click_type_filter_hide_button(self):
174
+ self.hide_type_filter_button.click()
175
+ return self
176
+
177
+ @step
178
+ def click_project_filter_hide_button(self):
179
+ self.hide_project_filter_button.click()
180
+ return self
181
+
182
+ @step
183
+ def click_created_by_filter_hide_button(self):
184
+ self.hide_created_by_filter_button.click()
185
+ return self
186
+
187
+ @step
188
+ def click_status_filter_hide_button(self):
189
+ self.hide_status_filter_button.click()
190
+ return self
191
+
192
+ # -----------------
193
+ # Verification Methods
194
+ # -----------------
195
+ @step
196
+ def should_see_title_subtitle(self, title: str, subtitle: str):
197
+ expect(self.page_title).to_be_visible()
198
+ expect(self.page_title).to_have_text(title)
199
+ expect(self.page_subtitle).to_be_visible()
200
+ expect(self.page_subtitle).to_have_text(subtitle)
201
+ return self
202
+
203
+ @step
204
+ def should_see_filters_section(self):
205
+ expect(self.filters_section).to_be_visible()
206
+ expect(self.filters_section).to_be_enabled()
207
+ return self
208
+
209
+ @step
210
+ def should_see_project_filter(self):
211
+ expect(self.project_filter_multiselect).to_be_visible()
212
+ expect(self.project_filter_multiselect).to_be_enabled()
213
+ return self
214
+
215
+ @step
216
+ def should_see_status_dropdown(self):
217
+ expect(self.status_dropdown).to_be_visible()
218
+ self.status_dropdown.click()
219
+ for status in DATA_SOURCE_FILTER_STATUSES_LIST:
220
+ expected_statuses = self.status_dropdown_items(status)
221
+ expect(expected_statuses).to_be_visible()
222
+ self.status_dropdown.click()
223
+ return self
224
+
225
+ @step
226
+ def should_select_status_dropdown(self):
227
+ for status in DATA_SOURCE_FILTER_STATUSES_LIST:
228
+ self.status_dropdown.click()
229
+ expected_status = self.status_dropdown_items(status)
230
+ expected_status.click()
231
+ expect(self.status_dropdown.locator(".p-dropdown-label")).to_have_text(
232
+ status
233
+ )
234
+
235
+ @step
236
+ def should_see_type_checkboxes(self):
237
+ """
238
+ Verifies the type checkbox with the given label is visible in the sidebar.
239
+ """
240
+ for type_label in DATA_SOURCE_FILTER_TYPES_LIST:
241
+ checkbox = self.get_type_checkbox(type_label)
242
+ expect(checkbox).to_be_visible()
243
+ return self
244
+
245
+ @step
246
+ def should_see_selected_checkboxes(self):
247
+ for type_label in DATA_SOURCE_FILTER_TYPES_LIST:
248
+ expect(self.get_type_checkbox_div(type_label)).to_have_attribute(
249
+ "data-p-highlight", "false"
250
+ )
251
+ self.toggle_type_filter(type_label)
252
+ expect(self.get_type_checkbox_div(type_label)).to_have_attribute(
253
+ "data-p-highlight", "true"
254
+ )
255
+ return self
256
+
257
+ @step
258
+ def should_see_search_input(self, text: str):
259
+ expect(self.search_input).to_have_value(text)
260
+ expect(self.clear_all_button).to_be_visible()
261
+ return self
262
+
263
+ @step
264
+ def should_see_created_by_me_value(self, user: str):
265
+ expect(self.created_by_field).to_have_value(user)
266
+ return self
267
+
268
+ @step
269
+ def should_see_cleared_filters(self):
270
+ expect(self.clear_all_button).not_to_be_visible()
271
+ expect(self.search_input).to_have_value("")
272
+ for type_label in DATA_SOURCE_FILTER_TYPES_LIST:
273
+ expect(self.get_type_checkbox_div(type_label)).to_have_attribute(
274
+ "data-p-highlight", "false"
275
+ )
276
+ expect(self.project_filter_multiselect).to_have_text(PROJECT_LABEL)
277
+ expect(self.created_by_field).to_have_value("")
278
+ expect(self.status_dropdown.locator(".p-dropdown-label")).to_have_text(
279
+ STATUS_LABEL
280
+ )
281
+ return self
282
+
283
+ @step
284
+ def should_not_see_type_filters(self):
285
+ for type_label in DATA_SOURCE_FILTER_TYPES_LIST:
286
+ expect(self.get_type_checkbox(type_label)).not_to_be_visible()
287
+ return self
288
+
289
+ @step
290
+ def should_not_see_project_filters(self):
291
+ expect(self.project_filter_multiselect).not_to_be_visible()
292
+ return self
293
+
294
+ @step
295
+ def should_not_see_created_by_filters(self):
296
+ expect(self.created_by_field).not_to_be_visible()
297
+ expect(self.created_by_me_checkbox).not_to_be_visible()
298
+ return self
299
+
300
+ @step
301
+ def should_not_see_status_filters(self):
302
+ expect(self.status_dropdown).not_to_be_visible()
303
+ return self