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.
- codemie_test_harness/cli/cli.py +18 -74
- codemie_test_harness/cli/commands/assistant_cmd.py +104 -0
- codemie_test_harness/cli/commands/config_cmd.py +610 -20
- codemie_test_harness/cli/commands/workflow_cmd.py +64 -0
- codemie_test_harness/cli/constants.py +385 -6
- codemie_test_harness/cli/utils.py +9 -0
- codemie_test_harness/tests/test_data/assistant_test_data.py +197 -0
- codemie_test_harness/tests/test_data/project_management_test_data.py +1 -1
- codemie_test_harness/tests/ui/assistants/__init__.py +0 -0
- codemie_test_harness/tests/ui/assistants/test_create_assistant.py +408 -0
- codemie_test_harness/tests/ui/conftest.py +23 -3
- codemie_test_harness/tests/ui/pageobject/assistants/assistants_page.py +3 -4
- codemie_test_harness/tests/ui/pageobject/assistants/create_assistant_page.py +689 -0
- codemie_test_harness/tests/ui/pageobject/assistants/generate_with_ai_modal.py +367 -0
- codemie_test_harness/tests/ui/pageobject/base_page.py +2 -2
- codemie_test_harness/tests/ui/pytest.ini +18 -0
- codemie_test_harness/tests/ui/workflows/test_workflows.py +1 -1
- codemie_test_harness/tests/utils/credentials_manager.py +0 -15
- {codemie_test_harness-0.1.168.dist-info → codemie_test_harness-0.1.170.dist-info}/METADATA +2 -2
- {codemie_test_harness-0.1.168.dist-info → codemie_test_harness-0.1.170.dist-info}/RECORD +22 -14
- {codemie_test_harness-0.1.168.dist-info → codemie_test_harness-0.1.170.dist-info}/WHEEL +0 -0
- {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
|