Skip to content

Commit 9a2d268

Browse files
committed
tests for Calculation field's "AI Assistant"
1 parent 1a870d6 commit 9a2d268

2 files changed

Lines changed: 74 additions & 8 deletions

File tree

src/org/labkey/test/components/domain/CalculatedColumnAssistantDialog.java

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -96,15 +96,18 @@ public String getLastAssistantResponse()
9696
}
9797

9898
/**
99-
* @return every SQL expression suggested in the most recent assistant response, in display order.
100-
* Usually a single entry, occasionally more.
99+
* @return every <em>applicable</em> SQL expression suggested in the most recent assistant response, in display
100+
* order. Only counts {@code .assistant-expression} blocks that include an "Apply Expression" button — read-only
101+
* SQL the assistant shows for illustration (e.g. an alternative custom-query example) is excluded, since the user
102+
* can't accept it as the field's calculation.
101103
*/
102104
public List<String> getSuggestedExpressions()
103105
{
104106
WebElement lastResponse = lastAssistantResponseElement();
105107
if (lastResponse == null)
106108
return List.of();
107109
return Locator.tagWithClass("div", "assistant-expression")
110+
.withDescendant(Locator.tagWithClass("button", "clickable-text"))
108111
.descendant(Locator.tag("code"))
109112
.findElements(lastResponse).stream()
110113
.map(WebElement::getText)
@@ -125,17 +128,67 @@ public String getFirstSuggestedExpression()
125128
* Returns the underlying field row (the dialog stays open; call {@link #clickEndChat()} to close it).
126129
*/
127130
public DomainFieldRow applyFirstSuggestedExpression()
131+
{
132+
return applySuggestedExpression(0);
133+
}
134+
135+
/**
136+
* Click "Apply Expression" on the suggestion at the given index in the most recent assistant response.
137+
*/
138+
public DomainFieldRow applySuggestedExpression(int index)
128139
{
129140
WebElement lastResponse = lastAssistantResponseElement();
130141
if (lastResponse == null)
131142
throw new IllegalStateException("No assistant response is available to apply.");
132-
Locator.tagWithClass("div", "assistant-expression")
143+
List<WebElement> buttons = Locator.tagWithClass("div", "assistant-expression")
133144
.descendant(Locator.tagWithClass("button", "clickable-text"))
134-
.findElement(lastResponse)
135-
.click();
145+
.findElements(lastResponse);
146+
if (index < 0 || index >= buttons.size())
147+
throw new IndexOutOfBoundsException(
148+
"Requested expression index " + index + " but only " + buttons.size() + " expression(s) available.");
149+
buttons.get(index).click();
136150
return _row;
137151
}
138152

153+
/**
154+
* @return text of the first assistant response in the chat history, or empty string if there are none. Useful
155+
* for asserting the intro message in NEW / CHANGE / VALIDATE entry modes.
156+
*/
157+
public String getFirstAssistantResponse()
158+
{
159+
List<String> responses = getAssistantResponses();
160+
return responses.isEmpty() ? "" : responses.get(0);
161+
}
162+
163+
/**
164+
* @return true while the dialog is waiting for an AI response (the "Thinking..." pending bubble is shown).
165+
*/
166+
public boolean isPending()
167+
{
168+
return Locator.tagWithClass("div", "chat-item").withClass("pending").existsIn(this);
169+
}
170+
171+
/**
172+
* Click the stop button to abort an in-flight AI request. The submit button toggles to a stop button (fa-stop)
173+
* while the dialog is in the pending state; calling this method when no request is pending will fail.
174+
*/
175+
public void clickStop()
176+
{
177+
Locator.tagWithClass("button", "prompt-button")
178+
.withDescendant(Locator.tagWithClass("i", "fa-stop"))
179+
.findElement(this)
180+
.click();
181+
}
182+
183+
/**
184+
* Click submit without waiting for the response. Useful for tests that need to interrupt or otherwise observe
185+
* the pending state before the response arrives. Prefer {@link #submitPrompt()} when the caller wants to wait.
186+
*/
187+
public void clickSubmitWithoutWaiting()
188+
{
189+
elementCache().promptSubmitButton.click();
190+
}
191+
139192
private WebElement lastAssistantResponseElement()
140193
{
141194
List<WebElement> responses = Locator.tagWithClass("div", "chat-item").withClass("assistant-response")
@@ -169,12 +222,12 @@ protected class ElementCache extends ModalDialog.ElementCache
169222
{
170223
final WebElement endChatButton = Locator.tagWithClass("button", "btn")
171224
.withText("End Chat")
172-
.findWhenNeeded(this);
225+
.refindWhenNeeded(this);
173226

174227
final WebElement promptInput = Locator.tagWithClass("textarea", "prompt-input")
175-
.findWhenNeeded(this);
228+
.refindWhenNeeded(this);
176229

177230
final WebElement promptSubmitButton = Locator.tagWithClass("button", "prompt-button")
178-
.findWhenNeeded(this);
231+
.refindWhenNeeded(this);
179232
}
180233
}

src/org/labkey/test/components/domain/DomainFieldRow.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1096,6 +1096,19 @@ public CalculatedColumnAssistantDialog openAIAssistant()
10961096
return new CalculatedColumnAssistantDialog(this);
10971097
}
10981098

1099+
/**
1100+
* @return true if the "AI Assistant" button is present in the expanded Calculation field options.
1101+
* The button is only available when the {@code professional} module is enabled.
1102+
*/
1103+
public boolean hasAIAssistantButton()
1104+
{
1105+
if (!isExpanded())
1106+
expand();
1107+
return Locator.tagWithClass("button", "btn")
1108+
.withText("AI Assistant")
1109+
.findElementOrNull(this) != null;
1110+
}
1111+
10991112
// advanced settings
11001113

11011114
public DomainFieldRow showFieldOnDefaultView(boolean checked)

0 commit comments

Comments
 (0)