codemie-test-harness 0.1.168__py3-none-any.whl → 0.1.170__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/cli/cli.py +18 -74
  2. codemie_test_harness/cli/commands/assistant_cmd.py +104 -0
  3. codemie_test_harness/cli/commands/config_cmd.py +610 -20
  4. codemie_test_harness/cli/commands/workflow_cmd.py +64 -0
  5. codemie_test_harness/cli/constants.py +385 -6
  6. codemie_test_harness/cli/utils.py +9 -0
  7. codemie_test_harness/tests/test_data/assistant_test_data.py +197 -0
  8. codemie_test_harness/tests/test_data/project_management_test_data.py +1 -1
  9. codemie_test_harness/tests/ui/assistants/__init__.py +0 -0
  10. codemie_test_harness/tests/ui/assistants/test_create_assistant.py +408 -0
  11. codemie_test_harness/tests/ui/conftest.py +23 -3
  12. codemie_test_harness/tests/ui/pageobject/assistants/assistants_page.py +3 -4
  13. codemie_test_harness/tests/ui/pageobject/assistants/create_assistant_page.py +689 -0
  14. codemie_test_harness/tests/ui/pageobject/assistants/generate_with_ai_modal.py +367 -0
  15. codemie_test_harness/tests/ui/pageobject/base_page.py +2 -2
  16. codemie_test_harness/tests/ui/pytest.ini +18 -0
  17. codemie_test_harness/tests/ui/workflows/test_workflows.py +1 -1
  18. codemie_test_harness/tests/utils/credentials_manager.py +0 -15
  19. {codemie_test_harness-0.1.168.dist-info → codemie_test_harness-0.1.170.dist-info}/METADATA +2 -2
  20. {codemie_test_harness-0.1.168.dist-info → codemie_test_harness-0.1.170.dist-info}/RECORD +22 -14
  21. {codemie_test_harness-0.1.168.dist-info → codemie_test_harness-0.1.170.dist-info}/WHEEL +0 -0
  22. {codemie_test_harness-0.1.168.dist-info → codemie_test_harness-0.1.170.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,689 @@
1
+ from typing import Optional
2
+
3
+ from playwright.sync_api import expect, Locator
4
+ from reportportal_client import step
5
+
6
+ from codemie_test_harness.tests.ui.pageobject.assistants.generate_with_ai_modal import (
7
+ AIAssistantGeneratorPage,
8
+ )
9
+ from codemie_test_harness.tests.ui.pageobject.base_page import BasePage
10
+
11
+
12
+ class CreateAssistantPage(BasePage):
13
+ """
14
+ Create Assistant page object following Page Object Model (POM) best practices.
15
+
16
+ This class encapsulates all interactions with the Create Assistant page,
17
+ providing a clean interface for test automation while hiding implementation details.
18
+ Updated with accurate locators based on real HTML structure.
19
+ """
20
+
21
+ page_url = "/#/assistants/new"
22
+
23
+ def __init__(self, page):
24
+ """Initialize the Create Assistant page object."""
25
+ super().__init__(page)
26
+ self.ai_generator_modal = AIAssistantGeneratorPage(
27
+ page
28
+ ) # AI Assistant Generator modal
29
+
30
+ # =============================================================================
31
+ # LOCATORS - Core Page Elements
32
+ # =============================================================================
33
+
34
+ @property
35
+ def main_container(self) -> Locator:
36
+ """Main page container"""
37
+ return self.page.locator("main.flex.flex-col.h-full.flex-1")
38
+
39
+ @property
40
+ def page_title(self) -> Locator:
41
+ """Page title 'Create Assistant' element"""
42
+ return (
43
+ self.page.locator('.text-h3:has-text("Create Assistant")')
44
+ or self.page.locator(
45
+ 'div.text-h3.text-white.font-semibold:has-text("Create Assistant")'
46
+ )
47
+ or self.page.locator('h1:has-text("Create Assistant")')
48
+ )
49
+
50
+ @property
51
+ def back_button(self) -> Locator:
52
+ """Back button in header with arrow icon"""
53
+ return (
54
+ self.page.locator("button.button.secondary.medium").first
55
+ or self.page.locator(
56
+ 'button:has(svg[xmlns="http://www.w3.org/2000/svg"])'
57
+ ).first
58
+ or self.page.locator(".mr-6 button")
59
+ )
60
+
61
+ @property
62
+ def generate_with_ai_button(self) -> Locator:
63
+ """Generate with AI button in header with magical styling"""
64
+ return (
65
+ self.page.locator(
66
+ 'button.button.magical.medium:has-text("Generate with AI")'
67
+ )
68
+ or self.page.locator('button:has-text("Generate with AI")')
69
+ or self.page.locator("button:has(svg + text)").filter(
70
+ has_text="Generate with AI"
71
+ )
72
+ )
73
+
74
+ @property
75
+ def cancel_button(self) -> Locator:
76
+ """Cancel button in header"""
77
+ return (
78
+ self.page.locator('button.button.secondary.medium:has-text("Cancel")')
79
+ or self.page.locator('button:has-text("Cancel")')
80
+ or self.page.locator('.ml-auto button:has-text("Cancel")')
81
+ )
82
+
83
+ @property
84
+ def create_button(self) -> Locator:
85
+ """Create button with plus icon (primary button)"""
86
+ return (
87
+ self.page.locator("button#submit")
88
+ or self.page.locator('button.button.primary.medium:has-text("Create")')
89
+ or self.page.locator('button:has-text("Create"):has(svg)')
90
+ )
91
+
92
+ # =============================================================================
93
+ # LOCATORS - Assistant Setup Section
94
+ # =============================================================================
95
+
96
+ @property
97
+ def assistant_setup_section(self) -> Locator:
98
+ """Assistant Setup section header"""
99
+ return self.page.locator('h4:has-text("Assistant Setup")')
100
+
101
+ @property
102
+ def project_dropdown(self) -> Locator:
103
+ """Project selection multiselect dropdown"""
104
+ return (
105
+ self.page.locator("div.p-multiselect#project")
106
+ or self.page.locator('[name="project"].p-multiselect')
107
+ or self.page.locator(".p-multiselect-label-container")
108
+ )
109
+
110
+ @property
111
+ def shared_toggle(self) -> Locator:
112
+ """'Shared with project' toggle switch"""
113
+ return self.page.locator("label.switch-wrapper span.switch")
114
+
115
+ @property
116
+ def name_input(self) -> Locator:
117
+ """Assistant name input field with data-testid validation"""
118
+ return (
119
+ self.page.locator('input#name[data-testid="validation"]')
120
+ or self.page.locator('input[placeholder="Name*"]')
121
+ or self.page.locator('input[name="name"]')
122
+ )
123
+
124
+ @property
125
+ def slug_input(self) -> Locator:
126
+ """Assistant slug input field"""
127
+ return (
128
+ self.page.locator('input#slug[data-testid="validation"]')
129
+ or self.page.locator(
130
+ 'input[placeholder="Unique human-readable identifier"]'
131
+ )
132
+ or self.page.locator('input[name="slug"]')
133
+ )
134
+
135
+ @property
136
+ def icon_url_input(self) -> Locator:
137
+ """Assistant icon URL input field"""
138
+ return (
139
+ self.page.locator('input#icon_url[data-testid="validation"]')
140
+ or self.page.locator('input[placeholder="URL to the assistant\'s icon"]')
141
+ or self.page.locator('input[name="icon_url"]')
142
+ )
143
+
144
+ @property
145
+ def description_textarea(self) -> Locator:
146
+ """Assistant description textarea with placeholder"""
147
+ return (
148
+ self.page.locator('textarea#description[name="description"]')
149
+ or self.page.locator('textarea[placeholder="Description*"]')
150
+ or self.page.locator(".textarea-wrapper textarea")
151
+ )
152
+
153
+ @property
154
+ def conversation_starters_input(self) -> Locator:
155
+ """Conversation starters input field with InputGroup"""
156
+ return (
157
+ self.page.locator("input.p-inputtext#conversationStarters-0")
158
+ or self.page.locator('input[name="conversationStarters"]')
159
+ or self.page.locator(".p-inputgroup input")
160
+ )
161
+
162
+ @property
163
+ def add_conversation_starter_button(self) -> Locator:
164
+ """Add conversation starter button with plus icon"""
165
+ return (
166
+ self.page.locator('button.button.secondary.medium:has-text("Add")').nth(0)
167
+ or self.page.locator('button:has-text("Add"):has(svg)').first
168
+ or self.page.locator('.flex.justify-between button:has-text("Add")')
169
+ )
170
+
171
+ @property
172
+ def delete_conversation_starter_button(self) -> Locator:
173
+ """Delete conversation starter button (trash icon in InputGroup)"""
174
+ return (
175
+ self.page.locator(".p-inputgroup-addon button:has(svg)")
176
+ or self.page.locator('button:has(path[d*="M9.5 1.25a3.25"])')
177
+ or self.page.locator(".p-inputgroup button")
178
+ )
179
+
180
+ # =============================================================================
181
+ # LOCATORS - Behavior & Logic Section
182
+ # =============================================================================
183
+
184
+ @property
185
+ def behavior_logic_section(self) -> Locator:
186
+ """Behavior & Logic section header"""
187
+ return self.page.locator('h4:has-text("Behavior & Logic")')
188
+
189
+ @property
190
+ def system_instructions_label(self) -> Locator:
191
+ """System Instructions label"""
192
+ return self.page.locator(
193
+ '.text-sm.font-semibold:has-text("System Instructions")'
194
+ )
195
+
196
+ @property
197
+ def system_prompt_textarea(self) -> Locator:
198
+ """System instructions textarea with full height"""
199
+ return (
200
+ self.page.locator('textarea#system_prompt[name="system_prompt"]')
201
+ or self.page.locator('textarea[placeholder="System Instructions*"]')
202
+ or self.page.locator(".textarea-wrapper.h-full textarea")
203
+ )
204
+
205
+ @property
206
+ def expand_system_prompt_button(self) -> Locator:
207
+ """Expand system prompt button"""
208
+ return (
209
+ self.page.locator('button.button.secondary.medium:has-text("Expand")')
210
+ or self.page.locator('button:has-text("Expand"):has(svg)')
211
+ or self.page.locator('.flex.gap-4 button:has-text("Expand")')
212
+ )
213
+
214
+ @property
215
+ def model_type_dropdown(self) -> Locator:
216
+ """LLM model type multiselect dropdown"""
217
+ return (
218
+ self.page.locator("div.p-multiselect#model_type")
219
+ or self.page.locator('[name="model_type"].p-multiselect')
220
+ or self.page.locator(
221
+ '.p-multiselect:has(.p-multiselect-label:has-text("Default LLM Model"))'
222
+ )
223
+ )
224
+
225
+ @property
226
+ def temperature_input(self) -> Locator:
227
+ """Temperature input field (0-2 range)"""
228
+ return (
229
+ self.page.locator('input#temperature[data-testid="validation"]')
230
+ or self.page.locator('input[placeholder="0-2"]')
231
+ or self.page.locator('input[name="temperature"]')
232
+ )
233
+
234
+ @property
235
+ def top_p_input(self) -> Locator:
236
+ """Top P input field (0-1 range)"""
237
+ return (
238
+ self.page.locator('input#top_p[data-testid="validation"]')
239
+ or self.page.locator('input[placeholder="0-1"]')
240
+ or self.page.locator('input[name="top_p"]')
241
+ )
242
+
243
+ # ==================== NAVIGATION METHODS ====================
244
+
245
+ @step
246
+ def navigate_to(self):
247
+ """
248
+ Navigate to the Create Assistant page.
249
+
250
+ Returns:
251
+ self: Returns the page object for method chaining
252
+ """
253
+ self.page.goto(self.page_url)
254
+ self.wait_for_page_load()
255
+
256
+ # Handle AI Generator modal if it appears
257
+ self.handle_ai_generator_modal_if_visible()
258
+
259
+ return self
260
+
261
+ # ==================== AI GENERATOR MODAL METHODS ====================
262
+
263
+ @step
264
+ def is_ai_generator_modal_visible(self) -> bool:
265
+ """
266
+ Check if the AI Assistant Generator modal is currently visible.
267
+
268
+ Returns:
269
+ bool: True if modal is visible, False otherwise
270
+ """
271
+ return self.ai_generator_modal.is_modal_visible()
272
+
273
+ @step
274
+ def close_ai_generator_modal(self):
275
+ """
276
+ Close the AI Assistant Generator modal if it's visible.
277
+
278
+ Returns:
279
+ self: Returns the page object for method chaining
280
+ """
281
+ if self.is_ai_generator_modal_visible():
282
+ self.ai_generator_modal.close_modal()
283
+ return self
284
+
285
+ @step
286
+ def handle_ai_generator_modal_if_visible(self):
287
+ """
288
+ Handle the AI Generator modal if it appears when navigating to Create Assistant page.
289
+ This method will close the modal to proceed with manual assistant creation.
290
+
291
+ Returns:
292
+ self: Returns the page object for method chaining
293
+ """
294
+ # Wait a short moment for modal to potentially appear
295
+ self.page.wait_for_timeout(1000)
296
+
297
+ if self.is_ai_generator_modal_visible():
298
+ # Modal is visible, close it to proceed with manual creation
299
+ self.close_ai_generator_modal()
300
+
301
+ # Wait for modal to fully disappear before proceeding
302
+ self.page.wait_for_timeout(500)
303
+
304
+ return self
305
+
306
+ @step
307
+ def verify_ai_generator_modal_visible(self):
308
+ """
309
+ Verify that the AI Assistant Generator modal is visible with correct structure.
310
+
311
+ Returns:
312
+ self: Returns the page object for method chaining
313
+ """
314
+ assert self.is_ai_generator_modal_visible(), (
315
+ "AI Assistant Generator modal should be visible"
316
+ )
317
+
318
+ # Verify modal structure using updated methods
319
+ self.ai_generator_modal.verify_modal_title()
320
+ self.ai_generator_modal.verify_description_text()
321
+ self.ai_generator_modal.verify_prompt_label()
322
+ self.ai_generator_modal.verify_note_text()
323
+
324
+ return self
325
+
326
+ @step
327
+ def verify_ai_generator_modal_not_visible(self):
328
+ """
329
+ Verify that the AI Assistant Generator modal is not visible.
330
+
331
+ Returns:
332
+ self: Returns the page object for method chaining
333
+ """
334
+ assert not self.is_ai_generator_modal_visible(), (
335
+ "AI Assistant Generator modal should not be visible"
336
+ )
337
+ return self
338
+
339
+ @step
340
+ def create_manually_from_ai_modal(self):
341
+ """
342
+ Click 'Create Manually' from the AI Generator modal to proceed with manual creation.
343
+
344
+ Returns:
345
+ self: Returns the page object for method chaining
346
+ """
347
+ if self.is_ai_generator_modal_visible():
348
+ self.ai_generator_modal.click_create_manually()
349
+ # Wait for the modal to close and manual form to appear
350
+ self.page.wait_for_timeout(1000)
351
+ return self
352
+
353
+ @step
354
+ def generate_with_ai_from_modal(
355
+ self,
356
+ description: str,
357
+ include_tools: bool = True,
358
+ do_not_show_again: bool = False,
359
+ ):
360
+ """
361
+ Use the AI Generator modal to create an assistant with AI.
362
+
363
+ Args:
364
+ description: Description of the assistant to generate
365
+ include_tools: Whether to include tools in the assistant
366
+ do_not_show_again: Whether to check 'do not show popup' option
367
+
368
+ Returns:
369
+ self: Returns the page object for method chaining
370
+ """
371
+ if self.is_ai_generator_modal_visible():
372
+ self.ai_generator_modal.complete_ai_generation_workflow(
373
+ prompt=description,
374
+ include_tools=include_tools,
375
+ dont_show_again=do_not_show_again,
376
+ )
377
+ return self
378
+
379
+ # ==================== FORM INTERACTION METHODS ====================
380
+
381
+ @step
382
+ def fill_name(self, name: str):
383
+ """
384
+ Fill the assistant name field.
385
+
386
+ Args:
387
+ name: The name for the assistant
388
+
389
+ Returns:
390
+ self: Returns the page object for method chaining
391
+ """
392
+ self.name_input.clear()
393
+ self.name_input.fill(name)
394
+ return self
395
+
396
+ @step
397
+ def fill_description(self, description: str):
398
+ """
399
+ Fill the assistant description field.
400
+
401
+ Args:
402
+ description: The description for the assistant
403
+
404
+ Returns:
405
+ self: Returns the page object for method chaining
406
+ """
407
+ self.description_textarea.clear()
408
+ self.description_textarea.fill(description)
409
+ return self
410
+
411
+ @step
412
+ def fill_system_prompt(self, prompt: str):
413
+ """
414
+ Fill the system prompt field.
415
+
416
+ Args:
417
+ prompt: The system prompt text
418
+
419
+ Returns:
420
+ self: Returns the page object for method chaining
421
+ """
422
+ self.system_prompt_textarea.clear()
423
+ self.system_prompt_textarea.fill(prompt)
424
+ return self
425
+
426
+ @step
427
+ def fill_icon_url(self, icon_url: str):
428
+ """
429
+ Fill the icon URL field.
430
+
431
+ Args:
432
+ icon_url: The URL for the assistant icon
433
+
434
+ Returns:
435
+ self: Returns the page object for method chaining
436
+ """
437
+ self.icon_url_input.clear()
438
+ self.icon_url_input.fill(icon_url)
439
+ return self
440
+
441
+ @step
442
+ def fill_slug(self, slug: str):
443
+ """
444
+ Fill the slug field.
445
+
446
+ Args:
447
+ slug: The unique identifier for the assistant
448
+
449
+ Returns:
450
+ self: Returns the page object for method chaining
451
+ """
452
+ self.slug_input.clear()
453
+ self.slug_input.fill(slug)
454
+ return self
455
+
456
+ @step
457
+ def toggle_shared_assistant(self, shared: bool = True):
458
+ """
459
+ Toggle the shared/public setting for the assistant.
460
+
461
+ Args:
462
+ shared: Whether the assistant should be shared (True) or private (False)
463
+
464
+ Returns:
465
+ self: Returns the page object for method chaining
466
+ """
467
+ # Check current state and toggle if needed
468
+ is_currently_checked = self.shared_toggle.is_checked()
469
+ if (shared and not is_currently_checked) or (
470
+ not shared and is_currently_checked
471
+ ):
472
+ self.shared_toggle.click()
473
+ return self
474
+
475
+ @step
476
+ def fill_temperature(self, temperature: str):
477
+ """
478
+ Fill the temperature field.
479
+
480
+ Args:
481
+ temperature: Temperature value (0-2)
482
+
483
+ Returns:
484
+ self: Returns the page object for method chaining
485
+ """
486
+ self.temperature_input.clear()
487
+ self.temperature_input.fill(temperature)
488
+ return self
489
+
490
+ @step
491
+ def fill_top_p(self, top_p: str):
492
+ """
493
+ Fill the Top P field.
494
+
495
+ Args:
496
+ top_p: Top P value (0-1)
497
+
498
+ Returns:
499
+ self: Returns the page object for method chaining
500
+ """
501
+ self.top_p_input.clear()
502
+ self.top_p_input.fill(top_p)
503
+ return self
504
+
505
+ # ==================== ACTION METHODS ====================
506
+
507
+ @step
508
+ def click_create(self):
509
+ """
510
+ Click the Create button to create the assistant.
511
+
512
+ Returns:
513
+ self: Returns the page object for method chaining
514
+ """
515
+ self.create_button.click()
516
+ return self
517
+
518
+ @step
519
+ def click_cancel(self):
520
+ """
521
+ Click the Cancel button to abort assistant creation.
522
+
523
+ Returns:
524
+ self: Returns the page object for method chaining
525
+ """
526
+ self.cancel_button.click()
527
+ return self
528
+
529
+ @step
530
+ def click_back(self):
531
+ """
532
+ Click the Back button to return to assistants list.
533
+
534
+ Returns:
535
+ self: Returns the page object for method chaining
536
+ """
537
+ self.back_button.click()
538
+ return self
539
+
540
+ @step
541
+ def click_generate_with_ai_header(self):
542
+ """
543
+ Click the Generate with AI button in the header.
544
+
545
+ Returns:
546
+ self: Returns the page object for method chaining
547
+ """
548
+ self.generate_with_ai_button.click()
549
+ return self
550
+
551
+ # ==================== COMPREHENSIVE ASSISTANT CREATION METHOD ====================
552
+
553
+ @step
554
+ def create_assistant(
555
+ self,
556
+ name: str,
557
+ description: str,
558
+ system_prompt: str,
559
+ icon_url: Optional[str] = None,
560
+ shared: bool = False,
561
+ temperature: Optional[str] = None,
562
+ top_p: Optional[str] = None,
563
+ ):
564
+ """
565
+ Complete assistant creation workflow with all required parameters.
566
+
567
+ This method encapsulates the entire assistant creation process,
568
+ following the critical happy path scenario outlined in the requirements.
569
+
570
+ Args:
571
+ name: Assistant name (required)
572
+ description: Assistant description (required)
573
+ system_prompt: System prompt for the assistant (required)
574
+ slug: Optional unique identifier for the assistant
575
+ icon_url: Optional icon URL for the assistant
576
+ shared: Whether to make the assistant shared/public (default: False)
577
+ temperature: Optional temperature value (0-2)
578
+ top_p: Optional Top P value (0-1)
579
+
580
+ Returns:
581
+ self: Returns the page object for method chaining
582
+ """
583
+ # Fill essential required fields
584
+ self.fill_name(name)
585
+ self.fill_description(description)
586
+ self.fill_system_prompt(system_prompt)
587
+
588
+ # Fill optional fields if provided
589
+ # if icon_url:
590
+ # self.fill_icon_url(icon_url)
591
+ # if temperature:
592
+ # self.fill_temperature(temperature)
593
+ # if top_p:
594
+ # self.fill_top_p(top_p)
595
+
596
+ # Set sharing preference
597
+ self.toggle_shared_assistant(shared)
598
+
599
+ # Submit the form
600
+ self.click_create()
601
+
602
+ return self
603
+
604
+ # ==================== VERIFICATION METHODS ====================
605
+
606
+ @step
607
+ def should_be_on_create_assistant_page(self):
608
+ """Verify that we are on the Create Assistant page."""
609
+ expect(self.page_title).to_be_visible()
610
+ expect(self.page).to_have_url(f"{self.page_url}")
611
+ return self
612
+
613
+ @step
614
+ def should_have_all_form_fields_visible(self):
615
+ """Verify that all essential form fields are visible."""
616
+ expect(self.name_input).to_be_visible()
617
+ expect(self.description_textarea).to_be_visible()
618
+ expect(self.system_prompt_textarea).to_be_visible()
619
+ return self
620
+
621
+ @step
622
+ def should_have_action_buttons_visible(self):
623
+ """Verify that action buttons (Create, Cancel) are visible."""
624
+ expect(self.create_button).to_be_visible()
625
+ expect(self.cancel_button).to_be_visible()
626
+ return self
627
+
628
+ @step
629
+ def should_have_name_value(self, expected_name: str):
630
+ """Verify name field has expected value."""
631
+ expect(self.name_input).to_have_value(expected_name)
632
+ return self
633
+
634
+ @step
635
+ def should_have_description_value(self, expected_description: str):
636
+ """Verify description field has expected value."""
637
+ expect(self.description_textarea).to_have_value(expected_description)
638
+ return self
639
+
640
+ @step
641
+ def should_have_system_prompt_value(self, expected_prompt: str):
642
+ """Verify system prompt field has expected value."""
643
+ expect(self.system_prompt_textarea).to_have_value(expected_prompt)
644
+ return self
645
+
646
+ @step
647
+ def should_have_icon_url_value(self, expected_url: str):
648
+ """Verify icon URL field has expected value."""
649
+ expect(self.icon_url_input).to_have_value(expected_url)
650
+ return self
651
+
652
+ @step
653
+ def should_have_shared_checked(self):
654
+ """Verify shared toggle is checked."""
655
+ expect(self.shared_toggle).to_be_checked()
656
+ return self
657
+
658
+ @step
659
+ def should_have_shared_unchecked(self):
660
+ """Verify shared toggle is unchecked."""
661
+ expect(self.shared_toggle).not_to_be_checked()
662
+ return self
663
+
664
+ @step
665
+ def should_have_create_button_enabled(self):
666
+ """Verify create button is enabled."""
667
+ expect(self.create_button).to_be_enabled()
668
+ return self
669
+
670
+ @step
671
+ def should_have_create_button_disabled(self):
672
+ """Verify create button is disabled."""
673
+ expect(self.create_button).to_be_disabled()
674
+ return self
675
+
676
+ @step
677
+ def should_have_cancel_button_enabled(self):
678
+ """Verify cancel button is enabled."""
679
+ expect(self.cancel_button).to_be_enabled()
680
+ return self
681
+
682
+ @step
683
+ def should_have_empty_fields(self):
684
+ """Verify all form fields are empty."""
685
+ expect(self.name_input).to_have_value("")
686
+ expect(self.description_textarea).to_have_value("")
687
+ expect(self.system_prompt_textarea).to_have_value("")
688
+ expect(self.icon_url_input).to_have_value("")
689
+ return self