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.
@@ -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