codemie-test-harness 0.1.172__py3-none-any.whl → 0.1.174__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.
- codemie_test_harness/tests/ui/_test_data/__init__.py +0 -0
- codemie_test_harness/tests/ui/_test_data/integration_test_data.py +121 -0
- codemie_test_harness/tests/ui/assistants/test_create_assistant.py +1 -1
- codemie_test_harness/tests/ui/conftest.py +25 -0
- codemie_test_harness/tests/ui/integrations/__init__.py +0 -0
- codemie_test_harness/tests/ui/integrations/test_create_integration.py +320 -0
- codemie_test_harness/tests/ui/pageobject/assistants/create_assistant_page.py +0 -20
- codemie_test_harness/tests/ui/pageobject/base_page.py +19 -6
- codemie_test_harness/tests/ui/pageobject/components/integration_row.py +299 -0
- codemie_test_harness/tests/ui/pageobject/integrations/create_integration_page.py +772 -0
- codemie_test_harness/tests/ui/pageobject/integrations/integrations_page.py +434 -0
- codemie_test_harness-0.1.174.dist-info/METADATA +567 -0
- {codemie_test_harness-0.1.172.dist-info → codemie_test_harness-0.1.174.dist-info}/RECORD +16 -9
- codemie_test_harness-0.1.172.dist-info/METADATA +0 -306
- /codemie_test_harness/tests/{test_data → ui/_test_data}/assistant_test_data.py +0 -0
- {codemie_test_harness-0.1.172.dist-info → codemie_test_harness-0.1.174.dist-info}/WHEEL +0 -0
- {codemie_test_harness-0.1.172.dist-info → codemie_test_harness-0.1.174.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,772 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Create Integration page object for the integration creation workflow.
|
|
3
|
+
Contains methods for filling out integration forms, selecting types, and completing the creation process.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from codemie_sdk.models.integration import CredentialTypes
|
|
7
|
+
from playwright.sync_api import expect, Locator
|
|
8
|
+
from reportportal_client import step
|
|
9
|
+
|
|
10
|
+
from codemie_test_harness.tests.ui._test_data.integration_test_data import (
|
|
11
|
+
IntegrationTestData,
|
|
12
|
+
)
|
|
13
|
+
from codemie_test_harness.tests.ui.pageobject.base_page import BasePage
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# noinspection PyArgumentList
|
|
17
|
+
class CreateIntegrationPage(BasePage):
|
|
18
|
+
"""Create Integration page with comprehensive form handling and validation."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, page):
|
|
21
|
+
super().__init__(page)
|
|
22
|
+
|
|
23
|
+
# Header elements
|
|
24
|
+
@property
|
|
25
|
+
def page_title(self) -> Locator:
|
|
26
|
+
"""Page title element showing 'New User Integration'."""
|
|
27
|
+
return self.page.locator(".text-h3.font-semibold")
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def create_button(self) -> Locator:
|
|
31
|
+
"""Create button to save the integration."""
|
|
32
|
+
return self.page.locator('button.button.primary.medium[role="submit"]').filter(
|
|
33
|
+
has_text="Create"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def test_button(self) -> Locator:
|
|
38
|
+
"""Test button to test the integration."""
|
|
39
|
+
return self.page.locator("button.button.secondary.medium").filter(
|
|
40
|
+
has_text="Test"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# Form container
|
|
44
|
+
@property
|
|
45
|
+
def form_container(self) -> Locator:
|
|
46
|
+
"""Main form container."""
|
|
47
|
+
return self.page.locator("form.flex.flex-col.gap-y-6")
|
|
48
|
+
|
|
49
|
+
# Project selection
|
|
50
|
+
@property
|
|
51
|
+
def project_selector(self) -> Locator:
|
|
52
|
+
"""Project multiselect dropdown."""
|
|
53
|
+
return self.page.locator('#projectSelector[data-pc-name="multiselect"]')
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def project_selector_label(self) -> Locator:
|
|
57
|
+
"""Project selector label container."""
|
|
58
|
+
return self.project_selector.locator(".p-multiselect-label")
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def project_selector_trigger(self) -> Locator:
|
|
62
|
+
"""Project selector dropdown trigger button."""
|
|
63
|
+
return self.project_selector.locator(".p-multiselect-trigger")
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def project_search_input(self) -> Locator:
|
|
67
|
+
"""Project selector lookup."""
|
|
68
|
+
return self.page.locator(
|
|
69
|
+
'input[role="searchbox"][placeholder="Search for projects"]'
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Global Integration toggle
|
|
73
|
+
@property
|
|
74
|
+
def global_integration_checkbox(self) -> Locator:
|
|
75
|
+
"""Global Integration checkbox input."""
|
|
76
|
+
return self.page.locator('input#isGlobal[name="is_global"]')
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def global_integration_switch(self) -> Locator:
|
|
80
|
+
"""Global Integration switch wrapper."""
|
|
81
|
+
return self.page.locator("label.switch-wrapper#isGlobal")
|
|
82
|
+
|
|
83
|
+
# Cloud toggle
|
|
84
|
+
@property
|
|
85
|
+
def is_cloud_checkbox(self) -> Locator:
|
|
86
|
+
"""Cloud checkbox input."""
|
|
87
|
+
return self.page.locator("input#isCloud")
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def is_cloud_switch(self) -> Locator:
|
|
91
|
+
"""Cloud switch wrapper."""
|
|
92
|
+
return self.page.locator("label.switch-wrapper#isCloud")
|
|
93
|
+
|
|
94
|
+
# Credential Type selection
|
|
95
|
+
@property
|
|
96
|
+
def credential_type_input(self) -> Locator:
|
|
97
|
+
"""Credential Type autocomplete input field."""
|
|
98
|
+
return self.page.locator("#credentialType input.p-autocomplete-input")
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def credential_type_dropdown_button(self) -> Locator:
|
|
102
|
+
"""Credential Type dropdown button."""
|
|
103
|
+
return self.page.locator("#credentialType button.p-autocomplete-dropdown")
|
|
104
|
+
|
|
105
|
+
# Alias field (required)
|
|
106
|
+
@property
|
|
107
|
+
def alias_input(self) -> Locator:
|
|
108
|
+
"""Integration alias input field (required)."""
|
|
109
|
+
return self.page.locator('input#settingAlias[data-testid="validation"]')
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def alias_label(self) -> Locator:
|
|
113
|
+
"""Alias field label."""
|
|
114
|
+
return self.page.locator('label[for="settingAlias"] .input-label')
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def alias_required_indicator(self) -> Locator:
|
|
118
|
+
"""Required indicator for alias field."""
|
|
119
|
+
return self.alias_label.locator(".input-label-required")
|
|
120
|
+
|
|
121
|
+
# Authentication section
|
|
122
|
+
@property
|
|
123
|
+
def authentication_heading(self) -> Locator:
|
|
124
|
+
"""Authentication section heading."""
|
|
125
|
+
return self.page.locator('h4:has-text("Authentication")')
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def url_input(self) -> Locator:
|
|
129
|
+
"""URL input field in authentication section."""
|
|
130
|
+
return self.page.locator('input#url[data-testid="validation"]')
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
def token_name_input(self) -> Locator:
|
|
134
|
+
"""Token name input field."""
|
|
135
|
+
return self.page.locator('input#name[data-testid="validation"]')
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def token_input(self) -> Locator:
|
|
139
|
+
"""Token input field."""
|
|
140
|
+
return self.page.locator('input#token[data-testid="validation"]')
|
|
141
|
+
|
|
142
|
+
@property
|
|
143
|
+
def user_name_input(self) -> Locator:
|
|
144
|
+
"""User Name input."""
|
|
145
|
+
return self.page.locator("input#username")
|
|
146
|
+
|
|
147
|
+
# Information tooltips and help text
|
|
148
|
+
@property
|
|
149
|
+
def global_integration_help_text(self) -> Locator:
|
|
150
|
+
"""Information text explaining global integration feature."""
|
|
151
|
+
return self.page.locator(".text-text-secondary.text-xs").filter(
|
|
152
|
+
has_text="By enabling, it will become versatile"
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
@property
|
|
156
|
+
def authentication_help_text(self) -> Locator:
|
|
157
|
+
"""Security information text about masked sensitive data."""
|
|
158
|
+
return self.page.locator(".text-text-secondary.text-xs").filter(
|
|
159
|
+
has_text="Important note: Your sensitive information is encrypted for security"
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
# Validation elements
|
|
163
|
+
@property
|
|
164
|
+
def validation_errors(self) -> Locator:
|
|
165
|
+
"""All validation error messages on the page."""
|
|
166
|
+
return self.page.locator(".error-message, .validation-error, .field-error")
|
|
167
|
+
|
|
168
|
+
# Toast notification elements
|
|
169
|
+
@property
|
|
170
|
+
def error_toast_header(self) -> Locator:
|
|
171
|
+
"""Error toast notification header."""
|
|
172
|
+
return self.page.locator(".codemie-toast-err .codemie-toast-header")
|
|
173
|
+
|
|
174
|
+
@property
|
|
175
|
+
def error_toast_content(self) -> Locator:
|
|
176
|
+
"""Error toast notification content message."""
|
|
177
|
+
return self.page.locator(".codemie-toast-err .codemie-toast-content")
|
|
178
|
+
|
|
179
|
+
# Navigation methods
|
|
180
|
+
@step
|
|
181
|
+
def navigate_to_user_integration_creation(self):
|
|
182
|
+
"""Navigate to the create user integration page."""
|
|
183
|
+
self.page.goto("/#/integrations/user/new")
|
|
184
|
+
self.wait_for_page_load()
|
|
185
|
+
return self
|
|
186
|
+
|
|
187
|
+
@step
|
|
188
|
+
def navigate_to_project_integration_creation(self):
|
|
189
|
+
"""Navigate to the create project integration page."""
|
|
190
|
+
self.page.goto("/#/integrations/project/new")
|
|
191
|
+
self.wait_for_page_load()
|
|
192
|
+
return self
|
|
193
|
+
|
|
194
|
+
@step
|
|
195
|
+
def go_back(self):
|
|
196
|
+
"""Click the back button to return to previous page."""
|
|
197
|
+
self.back_button.click()
|
|
198
|
+
return self
|
|
199
|
+
|
|
200
|
+
# Project selection methods
|
|
201
|
+
@step
|
|
202
|
+
def open_project_selector(self):
|
|
203
|
+
"""Open the project selector dropdown."""
|
|
204
|
+
self.project_selector_trigger.click()
|
|
205
|
+
return self
|
|
206
|
+
|
|
207
|
+
@step
|
|
208
|
+
def select_project(self, project_name: str):
|
|
209
|
+
"""
|
|
210
|
+
Select a project from the dropdown.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
project_name (str): Name of the project to select
|
|
214
|
+
"""
|
|
215
|
+
self.open_project_selector()
|
|
216
|
+
self.project_search_input.fill(project_name)
|
|
217
|
+
project_option = self.page.locator(
|
|
218
|
+
f'[data-pc-section="item"]:has-text("{project_name}")'
|
|
219
|
+
)
|
|
220
|
+
if project_option.is_visible():
|
|
221
|
+
project_option.click()
|
|
222
|
+
else:
|
|
223
|
+
# If exact match not found, press Enter to accept typed value
|
|
224
|
+
self.project_search_input.press("Enter")
|
|
225
|
+
return self
|
|
226
|
+
|
|
227
|
+
@step
|
|
228
|
+
def get_selected_project(self) -> str:
|
|
229
|
+
"""
|
|
230
|
+
Get the currently selected project name.
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
str: Currently selected project name
|
|
234
|
+
"""
|
|
235
|
+
return self.project_selector_label.text_content().strip()
|
|
236
|
+
|
|
237
|
+
# Global Integration methods
|
|
238
|
+
@step
|
|
239
|
+
def enable_global_integration(self):
|
|
240
|
+
"""Enable the Global Integration toggle."""
|
|
241
|
+
if not self.global_integration_checkbox.is_checked():
|
|
242
|
+
self.global_integration_switch.click()
|
|
243
|
+
return self
|
|
244
|
+
|
|
245
|
+
@step
|
|
246
|
+
def disable_global_integration(self):
|
|
247
|
+
"""Disable the Global Integration toggle."""
|
|
248
|
+
if self.global_integration_checkbox.is_checked():
|
|
249
|
+
self.global_integration_switch.click()
|
|
250
|
+
return self
|
|
251
|
+
|
|
252
|
+
@step
|
|
253
|
+
def is_global_integration_enabled(self) -> bool:
|
|
254
|
+
"""
|
|
255
|
+
Check if Global Integration is enabled.
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
bool: True if enabled, False otherwise
|
|
259
|
+
"""
|
|
260
|
+
return self.global_integration_checkbox.is_checked()
|
|
261
|
+
|
|
262
|
+
# Credential Type methods
|
|
263
|
+
@step
|
|
264
|
+
def select_credential_type(self, credential_type: CredentialTypes):
|
|
265
|
+
"""
|
|
266
|
+
Select a credential type from the autocomplete dropdown.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
credential_type: Type of credential to select (e.g., 'Git', 'AWS', etc.)
|
|
270
|
+
"""
|
|
271
|
+
# Clear current selection and type new value
|
|
272
|
+
self.credential_type_input.clear()
|
|
273
|
+
self.credential_type_input.fill(credential_type.value)
|
|
274
|
+
|
|
275
|
+
# Wait for dropdown options to appear and select the matching option
|
|
276
|
+
option = self.page.locator(
|
|
277
|
+
f'li[role="option"][aria-label="{credential_type.value}"]'
|
|
278
|
+
)
|
|
279
|
+
if option.is_visible():
|
|
280
|
+
option.click()
|
|
281
|
+
else:
|
|
282
|
+
# If exact match not found, press Enter to accept typed value
|
|
283
|
+
self.credential_type_input.press("Enter")
|
|
284
|
+
return self
|
|
285
|
+
|
|
286
|
+
@step
|
|
287
|
+
def get_credential_type(self) -> str:
|
|
288
|
+
"""
|
|
289
|
+
Get the currently selected credential type.
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
str: Currently selected credential type
|
|
293
|
+
"""
|
|
294
|
+
return self.credential_type_input.input_value()
|
|
295
|
+
|
|
296
|
+
# Form filling methods
|
|
297
|
+
@step
|
|
298
|
+
def fill_alias(self, alias: str):
|
|
299
|
+
"""
|
|
300
|
+
Fill the integration alias field.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
alias (str): Integration alias name
|
|
304
|
+
"""
|
|
305
|
+
self.alias_input.clear()
|
|
306
|
+
self.alias_input.fill(alias)
|
|
307
|
+
return self
|
|
308
|
+
|
|
309
|
+
@step
|
|
310
|
+
def fill_url(self, url: str):
|
|
311
|
+
"""
|
|
312
|
+
Fill the URL field in authentication section.
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
url (str): URL value
|
|
316
|
+
"""
|
|
317
|
+
self.url_input.clear()
|
|
318
|
+
self.url_input.fill(url)
|
|
319
|
+
return self
|
|
320
|
+
|
|
321
|
+
@step
|
|
322
|
+
def fill_token_name(self, token_name: str):
|
|
323
|
+
"""
|
|
324
|
+
Fill the token name field.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
token_name (str): Token name value
|
|
328
|
+
"""
|
|
329
|
+
self.token_name_input.clear()
|
|
330
|
+
self.token_name_input.fill(token_name)
|
|
331
|
+
return self
|
|
332
|
+
|
|
333
|
+
@step
|
|
334
|
+
def fill_token(self, token: str):
|
|
335
|
+
"""
|
|
336
|
+
Fill the token field.
|
|
337
|
+
|
|
338
|
+
Args:
|
|
339
|
+
token (str): Token value
|
|
340
|
+
"""
|
|
341
|
+
self.token_input.clear()
|
|
342
|
+
self.token_input.fill(token)
|
|
343
|
+
return self
|
|
344
|
+
|
|
345
|
+
@step
|
|
346
|
+
def fill_username(self, username: str):
|
|
347
|
+
"""
|
|
348
|
+
Fill the username field.
|
|
349
|
+
|
|
350
|
+
Args:
|
|
351
|
+
username (str): Username value
|
|
352
|
+
"""
|
|
353
|
+
self.user_name_input.clear()
|
|
354
|
+
self.user_name_input.fill(username)
|
|
355
|
+
return self
|
|
356
|
+
|
|
357
|
+
# Cloud mode methods
|
|
358
|
+
@step
|
|
359
|
+
def enable_cloud_mode(self):
|
|
360
|
+
"""Enable the Cloud mode toggle."""
|
|
361
|
+
if not self.is_cloud_checkbox.is_checked():
|
|
362
|
+
self.is_cloud_switch.click()
|
|
363
|
+
return self
|
|
364
|
+
|
|
365
|
+
@step
|
|
366
|
+
def disable_cloud_mode(self):
|
|
367
|
+
"""Disable the Cloud mode toggle."""
|
|
368
|
+
if self.is_cloud_checkbox.is_checked():
|
|
369
|
+
self.is_cloud_switch.click()
|
|
370
|
+
return self
|
|
371
|
+
|
|
372
|
+
@step
|
|
373
|
+
def is_cloud_mode_enabled(self) -> bool:
|
|
374
|
+
"""
|
|
375
|
+
Check if Cloud mode is enabled.
|
|
376
|
+
|
|
377
|
+
Returns:
|
|
378
|
+
bool: True if enabled, False otherwise
|
|
379
|
+
"""
|
|
380
|
+
return self.is_cloud_checkbox.is_checked()
|
|
381
|
+
|
|
382
|
+
@step
|
|
383
|
+
def fill_git_integration_form(self, test_data: IntegrationTestData):
|
|
384
|
+
"""
|
|
385
|
+
Fill the complete Git integration form with provided data.
|
|
386
|
+
|
|
387
|
+
Args:
|
|
388
|
+
test_data: IntegrationTestData
|
|
389
|
+
"""
|
|
390
|
+
# Fill project
|
|
391
|
+
self.select_project(test_data.project)
|
|
392
|
+
|
|
393
|
+
# Fill required alias field
|
|
394
|
+
self.fill_alias(test_data.alias)
|
|
395
|
+
|
|
396
|
+
# Select credential type
|
|
397
|
+
self.select_credential_type(test_data.credential_type)
|
|
398
|
+
|
|
399
|
+
# Set global integration if requested
|
|
400
|
+
if test_data.is_global:
|
|
401
|
+
self.enable_global_integration()
|
|
402
|
+
else:
|
|
403
|
+
self.disable_global_integration()
|
|
404
|
+
|
|
405
|
+
# Fill authentication fields
|
|
406
|
+
self.fill_url(test_data.fields["url"])
|
|
407
|
+
self.fill_token(test_data.fields["token"])
|
|
408
|
+
|
|
409
|
+
return self
|
|
410
|
+
|
|
411
|
+
@step
|
|
412
|
+
def fill_jira_integration_form(self, test_data: IntegrationTestData):
|
|
413
|
+
"""
|
|
414
|
+
Fill the complete Jira integration form with provided data.
|
|
415
|
+
|
|
416
|
+
Args:
|
|
417
|
+
test_data: IntegrationTestData containing Jira-specific fields
|
|
418
|
+
"""
|
|
419
|
+
# Fill project
|
|
420
|
+
self.select_project(test_data.project)
|
|
421
|
+
|
|
422
|
+
# Fill required alias field
|
|
423
|
+
self.fill_alias(test_data.alias)
|
|
424
|
+
|
|
425
|
+
# Select credential type
|
|
426
|
+
self.select_credential_type(test_data.credential_type)
|
|
427
|
+
|
|
428
|
+
# Set global integration if requested
|
|
429
|
+
if test_data.is_global:
|
|
430
|
+
self.enable_global_integration()
|
|
431
|
+
else:
|
|
432
|
+
self.disable_global_integration()
|
|
433
|
+
|
|
434
|
+
# Fill authentication fields specific to Jira
|
|
435
|
+
self.fill_url(test_data.fields["url"])
|
|
436
|
+
|
|
437
|
+
# Set cloud mode if specified
|
|
438
|
+
if test_data.fields.get("is_cloud", False):
|
|
439
|
+
self.enable_cloud_mode()
|
|
440
|
+
self.fill_username(test_data.fields["username"])
|
|
441
|
+
else:
|
|
442
|
+
self.disable_cloud_mode()
|
|
443
|
+
|
|
444
|
+
self.fill_token(test_data.fields["token"])
|
|
445
|
+
|
|
446
|
+
return self
|
|
447
|
+
|
|
448
|
+
@step
|
|
449
|
+
def fill_confluence_integration_form(self, test_data: IntegrationTestData):
|
|
450
|
+
"""
|
|
451
|
+
Fill the complete Confluence integration form with provided data.
|
|
452
|
+
|
|
453
|
+
Args:
|
|
454
|
+
test_data: IntegrationTestData containing Confluence-specific fields
|
|
455
|
+
"""
|
|
456
|
+
# Fill project
|
|
457
|
+
self.select_project(test_data.project)
|
|
458
|
+
|
|
459
|
+
# Fill required alias field
|
|
460
|
+
self.fill_alias(test_data.alias)
|
|
461
|
+
|
|
462
|
+
# Select credential type
|
|
463
|
+
self.select_credential_type(test_data.credential_type)
|
|
464
|
+
|
|
465
|
+
# Set global integration if requested
|
|
466
|
+
if test_data.is_global:
|
|
467
|
+
self.enable_global_integration()
|
|
468
|
+
else:
|
|
469
|
+
self.disable_global_integration()
|
|
470
|
+
|
|
471
|
+
# Fill authentication fields specific to Confluence
|
|
472
|
+
self.fill_url(test_data.fields["url"])
|
|
473
|
+
|
|
474
|
+
# Set cloud mode if specified
|
|
475
|
+
if test_data.fields.get("is_cloud", False):
|
|
476
|
+
self.enable_cloud_mode()
|
|
477
|
+
self.fill_username(test_data.fields["username"])
|
|
478
|
+
else:
|
|
479
|
+
self.disable_cloud_mode()
|
|
480
|
+
|
|
481
|
+
self.fill_token(test_data.fields["token"])
|
|
482
|
+
|
|
483
|
+
return self
|
|
484
|
+
|
|
485
|
+
# Action methods
|
|
486
|
+
@step
|
|
487
|
+
def create_integration(self):
|
|
488
|
+
"""Click the Create button to save the integration."""
|
|
489
|
+
self.create_button.click()
|
|
490
|
+
return self
|
|
491
|
+
|
|
492
|
+
@step
|
|
493
|
+
def cancel_creation(self):
|
|
494
|
+
"""Click the Cancel button to exit without saving."""
|
|
495
|
+
self.cancel_button.click()
|
|
496
|
+
return self
|
|
497
|
+
|
|
498
|
+
# Helper methods
|
|
499
|
+
@step
|
|
500
|
+
def wait_for_form_to_load(self):
|
|
501
|
+
"""Wait for the form to fully load with all elements visible."""
|
|
502
|
+
expect(self.form_container).to_be_visible()
|
|
503
|
+
expect(self.alias_input).to_be_visible()
|
|
504
|
+
expect(self.create_button).to_be_visible()
|
|
505
|
+
return self
|
|
506
|
+
|
|
507
|
+
@step
|
|
508
|
+
def get_form_data(self) -> dict:
|
|
509
|
+
"""
|
|
510
|
+
Get current form data as a dictionary.
|
|
511
|
+
|
|
512
|
+
Returns:
|
|
513
|
+
dict: Current form field values
|
|
514
|
+
"""
|
|
515
|
+
return {
|
|
516
|
+
"project": self.get_selected_project(),
|
|
517
|
+
"credential_type": self.get_credential_type(),
|
|
518
|
+
"alias": self.alias_input.input_value(),
|
|
519
|
+
"url": self.url_input.input_value(),
|
|
520
|
+
"token_name": self.token_name_input.input_value(),
|
|
521
|
+
"token": self.token_input.input_value(),
|
|
522
|
+
"is_global": self.is_global_integration_enabled(),
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
@step
|
|
526
|
+
def clear_all_fields(self):
|
|
527
|
+
"""Clear all form fields."""
|
|
528
|
+
self.alias_input.clear()
|
|
529
|
+
self.url_input.clear()
|
|
530
|
+
self.token_name_input.clear()
|
|
531
|
+
self.token_input.clear()
|
|
532
|
+
self.credential_type_input.clear()
|
|
533
|
+
self.disable_global_integration()
|
|
534
|
+
return self
|
|
535
|
+
|
|
536
|
+
# ==================== VERIFICATION METHODS ====================
|
|
537
|
+
|
|
538
|
+
@step
|
|
539
|
+
def should_have_action_buttons_visible(
|
|
540
|
+
self, credential_type: CredentialTypes
|
|
541
|
+
) -> "CreateIntegrationPage":
|
|
542
|
+
"""
|
|
543
|
+
Verify that all action buttons (back, cancel, test, create) are visible.
|
|
544
|
+
|
|
545
|
+
Returns:
|
|
546
|
+
self: For method chaining
|
|
547
|
+
"""
|
|
548
|
+
#
|
|
549
|
+
expect(self.back_button).to_be_visible()
|
|
550
|
+
expect(self.cancel_button).to_be_visible()
|
|
551
|
+
expect(self.create_button).to_be_visible()
|
|
552
|
+
if credential_type in [CredentialTypes.JIRA, CredentialTypes.CONFLUENCE]:
|
|
553
|
+
expect(self.test_button).to_be_visible()
|
|
554
|
+
else:
|
|
555
|
+
expect(self.test_button).not_to_be_visible()
|
|
556
|
+
return self
|
|
557
|
+
|
|
558
|
+
@step
|
|
559
|
+
def should_have_project_selector_visible(self) -> "CreateIntegrationPage":
|
|
560
|
+
"""
|
|
561
|
+
Verify that project selector and its label are visible.
|
|
562
|
+
|
|
563
|
+
Returns:
|
|
564
|
+
self: For method chaining
|
|
565
|
+
"""
|
|
566
|
+
expect(self.project_selector).to_be_visible()
|
|
567
|
+
expect(self.project_selector_label).to_be_visible()
|
|
568
|
+
return self
|
|
569
|
+
|
|
570
|
+
@step
|
|
571
|
+
def should_have_global_integration_toggle_visible(self) -> "CreateIntegrationPage":
|
|
572
|
+
"""
|
|
573
|
+
Verify that global integration toggle elements are visible.
|
|
574
|
+
|
|
575
|
+
Returns:
|
|
576
|
+
self: For method chaining
|
|
577
|
+
"""
|
|
578
|
+
expect(self.global_integration_switch).to_be_visible()
|
|
579
|
+
return self
|
|
580
|
+
|
|
581
|
+
@step
|
|
582
|
+
def should_have_credential_type_field_visible(self) -> "CreateIntegrationPage":
|
|
583
|
+
"""
|
|
584
|
+
Verify that credential type field elements are visible.
|
|
585
|
+
|
|
586
|
+
Returns:
|
|
587
|
+
self: For method chaining
|
|
588
|
+
"""
|
|
589
|
+
expect(self.credential_type_input).to_be_visible()
|
|
590
|
+
expect(self.credential_type_dropdown_button).to_be_visible()
|
|
591
|
+
return self
|
|
592
|
+
|
|
593
|
+
@step
|
|
594
|
+
def should_have_alias_field_visible(self) -> "CreateIntegrationPage":
|
|
595
|
+
"""
|
|
596
|
+
Verify that alias field and required indicator are visible.
|
|
597
|
+
|
|
598
|
+
Returns:
|
|
599
|
+
self: For method chaining
|
|
600
|
+
"""
|
|
601
|
+
expect(self.alias_input).to_be_visible()
|
|
602
|
+
expect(self.alias_required_indicator).to_be_visible()
|
|
603
|
+
return self
|
|
604
|
+
|
|
605
|
+
# Mapping of credential types to their specific fields
|
|
606
|
+
def credential_based_fields(self):
|
|
607
|
+
return {
|
|
608
|
+
CredentialTypes.GIT: [
|
|
609
|
+
self.url_input,
|
|
610
|
+
self.token_name_input,
|
|
611
|
+
self.token_input,
|
|
612
|
+
],
|
|
613
|
+
CredentialTypes.JIRA: [
|
|
614
|
+
self.url_input,
|
|
615
|
+
self.user_name_input,
|
|
616
|
+
self.is_cloud_switch,
|
|
617
|
+
self.token_input,
|
|
618
|
+
],
|
|
619
|
+
CredentialTypes.CONFLUENCE: [
|
|
620
|
+
self.url_input,
|
|
621
|
+
self.user_name_input,
|
|
622
|
+
self.is_cloud_switch,
|
|
623
|
+
self.token_input,
|
|
624
|
+
],
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
@step
|
|
628
|
+
def should_have_authentication_section_visible(
|
|
629
|
+
self, credentials_type: CredentialTypes
|
|
630
|
+
) -> "CreateIntegrationPage":
|
|
631
|
+
"""
|
|
632
|
+
Verify that authentication section heading and fields are visible.
|
|
633
|
+
|
|
634
|
+
Returns:
|
|
635
|
+
self: For method chaining
|
|
636
|
+
"""
|
|
637
|
+
expect(self.authentication_heading).to_be_visible()
|
|
638
|
+
for element in self.credential_based_fields()[credentials_type]:
|
|
639
|
+
expect(element).to_be_visible()
|
|
640
|
+
return self
|
|
641
|
+
|
|
642
|
+
@step
|
|
643
|
+
def should_have_help_texts_visible(self) -> "CreateIntegrationPage":
|
|
644
|
+
"""
|
|
645
|
+
Verify that help texts and information tooltips are visible.
|
|
646
|
+
|
|
647
|
+
Returns:
|
|
648
|
+
self: For method chaining
|
|
649
|
+
"""
|
|
650
|
+
expect(self.global_integration_help_text).to_be_visible()
|
|
651
|
+
(
|
|
652
|
+
expect(self.global_integration_help_text).to_have_text(
|
|
653
|
+
"By enabling, it will become versatile and can be applied across multiple projects without being tied to any specific one."
|
|
654
|
+
)
|
|
655
|
+
)
|
|
656
|
+
expect(self.authentication_help_text).to_be_visible()
|
|
657
|
+
(
|
|
658
|
+
expect(self.authentication_help_text).to_have_text(
|
|
659
|
+
"Important note: Your sensitive information is encrypted for security and displayed here in a masked format. If you're updating non-sensitive information, there's no need to modify the masked values — they will remain unchanged and secure."
|
|
660
|
+
)
|
|
661
|
+
)
|
|
662
|
+
return self
|
|
663
|
+
|
|
664
|
+
def should_have_input_fields_editable(self) -> "CreateIntegrationPage":
|
|
665
|
+
expect(self.alias_input).to_be_editable()
|
|
666
|
+
if self.url_input.is_visible():
|
|
667
|
+
expect(self.url_input).to_be_editable()
|
|
668
|
+
if self.token_name_input.is_visible():
|
|
669
|
+
expect(self.token_name_input).to_be_editable()
|
|
670
|
+
if self.token_input.is_visible():
|
|
671
|
+
expect(self.token_input).to_be_editable()
|
|
672
|
+
return self
|
|
673
|
+
|
|
674
|
+
@step
|
|
675
|
+
def should_be_on_create_user_integration_page(self):
|
|
676
|
+
"""Verify that the user is on the create user integration page."""
|
|
677
|
+
expect(self.page).to_have_url("/#/integrations/user/new")
|
|
678
|
+
expect(self.page_title).to_have_text("New User Integration")
|
|
679
|
+
return self
|
|
680
|
+
|
|
681
|
+
@step
|
|
682
|
+
def should_be_on_create_project_integration_page(self):
|
|
683
|
+
"""Verify that the user is on the create project integration page."""
|
|
684
|
+
expect(self.page).to_have_url("/#/integrations/project/new")
|
|
685
|
+
expect(self.page_title).to_have_text("New Project Integration")
|
|
686
|
+
return self
|
|
687
|
+
|
|
688
|
+
@step
|
|
689
|
+
def should_have_create_button_enabled(self):
|
|
690
|
+
"""Verify that the create button is enabled."""
|
|
691
|
+
expect(self.create_button).to_be_enabled()
|
|
692
|
+
return self
|
|
693
|
+
|
|
694
|
+
@step
|
|
695
|
+
def should_have_create_button_disabled(self):
|
|
696
|
+
"""Verify that the create button is disabled."""
|
|
697
|
+
expect(self.create_button).to_be_disabled()
|
|
698
|
+
return self
|
|
699
|
+
|
|
700
|
+
@step
|
|
701
|
+
def should_see_validation_error(self, field_name: str, error_message: str):
|
|
702
|
+
"""
|
|
703
|
+
Verify that a specific validation error is displayed for a field.
|
|
704
|
+
|
|
705
|
+
Args:
|
|
706
|
+
field_name (str): Name of the field with error
|
|
707
|
+
error_message (str): Expected error message
|
|
708
|
+
"""
|
|
709
|
+
error_locator = self.page.locator(
|
|
710
|
+
f'[data-testid="validation-error-{field_name}"]'
|
|
711
|
+
)
|
|
712
|
+
if not error_locator.is_visible():
|
|
713
|
+
# Fallback to generic error message locator
|
|
714
|
+
error_locator = self.validation_errors.filter(has_text=error_message)
|
|
715
|
+
expect(error_locator).to_be_visible()
|
|
716
|
+
expect(error_locator).to_contain_text(error_message)
|
|
717
|
+
return self
|
|
718
|
+
|
|
719
|
+
@step
|
|
720
|
+
def should_not_see_validation_errors(self):
|
|
721
|
+
"""Verify that no validation errors are displayed."""
|
|
722
|
+
expect(self.validation_errors).to_have_count(0)
|
|
723
|
+
return self
|
|
724
|
+
|
|
725
|
+
@step
|
|
726
|
+
def should_have_project_selected(self, project_name: str):
|
|
727
|
+
"""
|
|
728
|
+
Verify that a specific project is selected.
|
|
729
|
+
|
|
730
|
+
Args:
|
|
731
|
+
project_name (str): Expected selected project name
|
|
732
|
+
"""
|
|
733
|
+
expect(self.project_selector_label).to_have_text(project_name)
|
|
734
|
+
return self
|
|
735
|
+
|
|
736
|
+
@step
|
|
737
|
+
def should_have_credential_type_selected(self, credential_type: CredentialTypes):
|
|
738
|
+
"""
|
|
739
|
+
Verify that a specific credential type is selected.
|
|
740
|
+
|
|
741
|
+
Args:
|
|
742
|
+
credential_type: Expected credential type
|
|
743
|
+
"""
|
|
744
|
+
expect(self.credential_type_input).to_have_value(credential_type.value)
|
|
745
|
+
return self
|
|
746
|
+
|
|
747
|
+
@step
|
|
748
|
+
def should_have_global_integration_enabled(self):
|
|
749
|
+
"""Verify that global integration toggle is enabled."""
|
|
750
|
+
expect(self.global_integration_checkbox).to_be_checked()
|
|
751
|
+
return self
|
|
752
|
+
|
|
753
|
+
@step
|
|
754
|
+
def should_have_global_integration_disabled(self):
|
|
755
|
+
"""Verify that global integration toggle is disabled."""
|
|
756
|
+
expect(self.global_integration_checkbox).not_to_be_checked()
|
|
757
|
+
return self
|
|
758
|
+
|
|
759
|
+
@step
|
|
760
|
+
def should_see_error_toast_message(self, header: str, content: str):
|
|
761
|
+
"""
|
|
762
|
+
Verify that error toast notification is displayed with expected header and content.
|
|
763
|
+
|
|
764
|
+
Args:
|
|
765
|
+
header (str): Expected toast header text
|
|
766
|
+
content (str): Expected toast content message
|
|
767
|
+
"""
|
|
768
|
+
expect(self.error_toast_header).to_be_visible()
|
|
769
|
+
expect(self.error_toast_header).to_have_text(header)
|
|
770
|
+
expect(self.error_toast_content).to_be_visible()
|
|
771
|
+
expect(self.error_toast_content).to_contain_text(content)
|
|
772
|
+
return self
|