@coveo/quantic 3.42.1 → 3.43.0

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.
Files changed (27) hide show
  1. package/force-app/main/default/labels/CustomLabels.labels-meta.xml +21 -0
  2. package/force-app/main/default/lwc/quanticGeneratedAnswer/e2e/agentData.ts +248 -0
  3. package/force-app/main/default/lwc/quanticGeneratedAnswer/e2e/fixture-agent.ts +77 -0
  4. package/force-app/main/default/lwc/quanticGeneratedAnswer/e2e/fixture.ts +65 -59
  5. package/force-app/main/default/lwc/quanticGeneratedAnswer/e2e/pageObject.ts +158 -13
  6. package/force-app/main/default/lwc/quanticGeneratedAnswer/e2e/quanticGeneratedAnswer.e2e.ts +1 -5
  7. package/force-app/main/default/lwc/quanticGeneratedAnswer/e2e/quanticGeneratedAnswerAgent.e2e.ts +233 -0
  8. package/force-app/main/default/lwc/quanticGeneratedAnswer/templates/conversation.html +1 -1
  9. package/force-app/main/default/lwc/quanticGeneratedAnswerFollowUpInput/__tests__/quanticGeneratedAnswerFollowUpInput.test.js +242 -10
  10. package/force-app/main/default/lwc/quanticGeneratedAnswerFollowUpInput/quanticGeneratedAnswerFollowUpInput.css +2 -2
  11. package/force-app/main/default/lwc/quanticGeneratedAnswerFollowUpInput/quanticGeneratedAnswerFollowUpInput.html +16 -1
  12. package/force-app/main/default/lwc/quanticGeneratedAnswerFollowUpInput/quanticGeneratedAnswerFollowUpInput.js +47 -4
  13. package/force-app/main/default/lwc/quanticGeneratedAnswerStreamOfThought/__tests__/quanticGeneratedAnswerStreamOfThought.test.js +95 -41
  14. package/force-app/main/default/lwc/quanticGeneratedAnswerStreamOfThought/__tests__/stepsUtils.test.js +118 -0
  15. package/force-app/main/default/lwc/quanticGeneratedAnswerStreamOfThought/quanticGeneratedAnswerStreamOfThought.js +21 -35
  16. package/force-app/main/default/lwc/quanticGeneratedAnswerStreamOfThought/stepsUtils.js +66 -0
  17. package/force-app/main/default/staticresources/coveoheadless/case-assist/headless.js +15 -15
  18. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/product-listing/facets/headless-product-listing-facet-options.d.ts +4 -39
  19. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/search/facets/headless-search-facet-options.d.ts +4 -39
  20. package/force-app/main/default/staticresources/coveoheadless/definitions/features/commerce/facets/facet-set/facet-set-selector.d.ts +3 -9
  21. package/force-app/main/default/staticresources/coveoheadless/definitions/features/facets/facet-search-set/category/category-facet-search-state-selector.d.ts +1 -27
  22. package/force-app/main/default/staticresources/coveoheadless/definitions/features/facets/facet-search-set/specific/specific-facet-search-state-selector.d.ts +1 -27
  23. package/force-app/main/default/staticresources/coveoheadless/headless.js +17 -17
  24. package/force-app/main/default/staticresources/coveoheadless/insight/headless.js +16 -16
  25. package/force-app/main/default/staticresources/coveoheadless/recommendation/headless.js +14 -14
  26. package/force-app/main/default/staticresources/dompurify/purify.min.js +2 -2
  27. package/package.json +5 -5
@@ -5,6 +5,7 @@ import {
5
5
  } from '../../../../../../playwright/utils/analyticsMode';
6
6
  import {AnalyticsObject} from '../../../../../../playwright/page-object/analytics';
7
7
  import {isRgaEvaluationRequest} from '../../../../../../playwright/utils/requests';
8
+ import type {AgentMessage} from './agentData';
8
9
 
9
10
  const minimumCitationTooltipDisplayDurationMs = 1500;
10
11
  const removeUnknownFields = (object: Record<string, unknown>) => {
@@ -24,32 +25,45 @@ const facetElementsSelectors = {
24
25
  },
25
26
  };
26
27
 
28
+ export interface GeneratedAnswerObjectConfig {
29
+ answerApiEnabled?: boolean;
30
+ headAnswerRequestRegex?: RegExp;
31
+ agentFollowUpRequestRegex?: RegExp;
32
+ isAgent?: boolean;
33
+ withFacets?: boolean;
34
+ }
35
+
27
36
  export class GeneratedAnswerObject {
28
37
  private analyticsMode: AnalyticsMode;
38
+ private answerApiEnabled: boolean;
39
+ private headAnswerRequestRegex?: RegExp;
40
+ private agentFollowUpRequestRegex?: RegExp;
41
+ private withFacets: boolean;
42
+ private isAgent: boolean;
29
43
 
30
44
  constructor(
31
45
  private page: Page,
32
46
  private streamId: string,
33
47
  private analytics: AnalyticsObject,
34
- private answerApiEnabled: boolean,
35
- private generateRequestRegex: RegExp,
36
- private withFacets: boolean
48
+ config: GeneratedAnswerObjectConfig = {}
37
49
  ) {
38
50
  this.page = page;
39
51
  this.streamId = streamId;
40
52
  this.analytics = analytics;
41
53
  this.analyticsMode = this.analytics.analyticsMode;
42
- this.answerApiEnabled = answerApiEnabled;
43
- this.generateRequestRegex = generateRequestRegex;
44
- this.withFacets = withFacets;
54
+ this.answerApiEnabled = config.answerApiEnabled ?? false;
55
+ this.headAnswerRequestRegex = config.headAnswerRequestRegex;
56
+ this.agentFollowUpRequestRegex = config.agentFollowUpRequestRegex;
57
+ this.withFacets = config.withFacets ?? false;
58
+ this.isAgent = config.isAgent ?? false;
45
59
  }
46
60
 
47
61
  get likeButton(): Locator {
48
- return this.page.getByRole('button', {name: 'This answer was helpful'});
62
+ return this.page.getByRole('button', {name: this.isAgent ? 'Like' : 'This answer was helpful', exact: true});
49
63
  }
50
64
 
51
65
  get dislikeButton(): Locator {
52
- return this.page.getByRole('button', {name: 'This answer was not helpful'});
66
+ return this.page.getByRole('button', {name: this.isAgent ? 'Dislike' : 'This answer was not helpful', exact: true});
53
67
  }
54
68
 
55
69
  get copyToClipboardButton(): Locator {
@@ -118,7 +132,7 @@ export class GeneratedAnswerObject {
118
132
 
119
133
  get citationLink(): Locator {
120
134
  return this.page
121
- .getByTestId('generated-answer__citations')
135
+ .getByTestId(this.isAgent ? 'generated-answer-body__citations' : 'generated-answer__citations')
122
136
  .locator('.citation__link');
123
137
  }
124
138
 
@@ -126,6 +140,22 @@ export class GeneratedAnswerObject {
126
140
  return this.page.getByTestId('generated-answer__answer-toggle');
127
141
  }
128
142
 
143
+ get showPreviousAnswersButton(): Locator {
144
+ return this.page.getByTestId('show-previous-button');
145
+ }
146
+
147
+ get threadItems(): Locator {
148
+ return this.page.locator('c-quantic-thread-item');
149
+ }
150
+
151
+ get threadItemTitleButton(): Locator {
152
+ return this.threadItems.getByTestId('thread-item-title-button');
153
+ }
154
+
155
+ get generatedAnswerBody(): Locator {
156
+ return this.page.locator('c-quantic-generated-answer-body');
157
+ }
158
+
129
159
  async hoverOverCitation(index: number): Promise<void> {
130
160
  // waiting 500ms to allow the component to render completely, cause any re-rendering abort the hover action.
131
161
  await this.page.waitForTimeout(500);
@@ -166,10 +196,18 @@ export class GeneratedAnswerObject {
166
196
  await this.likeButton.click();
167
197
  }
168
198
 
199
+ async clickNthLikeButton(answerIndex: number): Promise<void> {
200
+ await this.likeButton.nth(answerIndex).click();
201
+ }
202
+
169
203
  async clickDislikeButton(): Promise<void> {
170
204
  await this.dislikeButton.click();
171
205
  }
172
206
 
207
+ async clickNthDislikeButton(answerIndex: number): Promise<void> {
208
+ await this.dislikeButton.nth(answerIndex).click();
209
+ }
210
+
173
211
  async clickCopyToClipboardButton(): Promise<void> {
174
212
  await this.copyToClipboardButton.click();
175
213
  }
@@ -178,8 +216,26 @@ export class GeneratedAnswerObject {
178
216
  await this.toggleButton.click();
179
217
  }
180
218
 
181
- async waitForGenerateRequest(): Promise<Request> {
182
- return this.page.waitForRequest(this.generateRequestRegex);
219
+ async clickShowPreviousAnswersButton(): Promise<void> {
220
+ await this.showPreviousAnswersButton.click();
221
+ }
222
+
223
+ async clickNthThreadItem(index: number): Promise<void> {
224
+ await this.threadItemTitleButton.nth(index).click();
225
+ }
226
+
227
+ async waitForHeadAnswerRequest(): Promise<Request> {
228
+ if (!this.headAnswerRequestRegex) {
229
+ throw new Error('headAnswerRequestRegex is not configured');
230
+ }
231
+ return this.page.waitForRequest(this.headAnswerRequestRegex);
232
+ }
233
+
234
+ async waitForAgentFollowUpRequest(): Promise<Request> {
235
+ if (!this.agentFollowUpRequestRegex) {
236
+ throw new Error('agentFollowUpRequestRegex is not configured');
237
+ }
238
+ return this.page.waitForRequest(this.agentFollowUpRequestRegex);
183
239
  }
184
240
 
185
241
  async waitForStreamEndAnalytics(): Promise<Request | boolean> {
@@ -200,6 +256,12 @@ export class GeneratedAnswerObject {
200
256
  }
201
257
 
202
258
  async waitForLikeGeneratedAnswerAnalytics(): Promise<Request> {
259
+ return this.waitForLikeGeneratedAnswerAnalyticsForId(this.streamId);
260
+ }
261
+
262
+ async waitForLikeGeneratedAnswerAnalyticsForId(
263
+ answerId: string
264
+ ): Promise<Request> {
203
265
  if (this.analyticsMode === AnalyticsModeEnum.legacy) {
204
266
  return this.analytics.waitForCustomUaAnalytics(
205
267
  {
@@ -207,7 +269,7 @@ export class GeneratedAnswerObject {
207
269
  eventValue: 'likeGeneratedAnswer',
208
270
  },
209
271
  (event) =>
210
- event?.customData?.generativeQuestionAnsweringId === this.streamId
272
+ event?.customData?.generativeQuestionAnsweringId === answerId
211
273
  );
212
274
  }
213
275
  return this.analytics.waitForEventProtocolAnalytics(
@@ -217,6 +279,12 @@ export class GeneratedAnswerObject {
217
279
  }
218
280
 
219
281
  async waitForDislikeGeneratedAnswerAnalytics(): Promise<Request> {
282
+ return this.waitForDislikeGeneratedAnswerAnalyticsForId(this.streamId);
283
+ }
284
+
285
+ async waitForDislikeGeneratedAnswerAnalyticsForId(
286
+ answerId: string
287
+ ): Promise<Request> {
220
288
  if (this.analyticsMode === AnalyticsModeEnum.legacy) {
221
289
  return this.analytics.waitForCustomUaAnalytics(
222
290
  {
@@ -224,7 +292,7 @@ export class GeneratedAnswerObject {
224
292
  eventValue: 'dislikeGeneratedAnswer',
225
293
  },
226
294
  (event) =>
227
- event?.customData?.generativeQuestionAnsweringId === this.streamId
295
+ event?.customData?.generativeQuestionAnsweringId === answerId
228
296
  );
229
297
  }
230
298
  return this.analytics.waitForEventProtocolAnalytics(
@@ -446,6 +514,83 @@ export class GeneratedAnswerObject {
446
514
  });
447
515
  }
448
516
 
517
+ async mockAgentAnswerResponse(
518
+ body: Array<AgentMessage>
519
+ ) {
520
+ await this.page.route('**/agents/*/answer', (route) => {
521
+ let bodyText = '';
522
+ body.forEach((data) => {
523
+ bodyText += `data:${JSON.stringify(data)} \n\n`;
524
+ });
525
+ route.fulfill({
526
+ status: 200,
527
+ body: bodyText,
528
+ headers: {'content-type': 'text/event-stream'},
529
+ });
530
+ });
531
+ }
532
+
533
+ async mockAgentFollowUpResponse(
534
+ bodies: Array<Array<AgentMessage>>
535
+ ) {
536
+ let callCount = 0;
537
+ await this.page.route('**/agents/*/follow-up', (route) => {
538
+ // After all provided response bodies are used, keep returning the last one
539
+ // so any additional intercepted follow-up requests still receive a mocked stream.
540
+ const streams = bodies[Math.min(callCount, bodies.length - 1)];
541
+ callCount++;
542
+
543
+ let bodyText = '';
544
+ streams.forEach((data) => {
545
+ bodyText += `data:${JSON.stringify(data)} \n\n`;
546
+ });
547
+ route.fulfill({
548
+ status: 200,
549
+ body: bodyText,
550
+ headers: {'content-type': 'text/event-stream'},
551
+ });
552
+ });
553
+ }
554
+
555
+ get followUpInput(): Locator {
556
+ return this.page.locator(
557
+ 'c-quantic-generated-answer-follow-up-input textarea'
558
+ );
559
+ }
560
+
561
+ get followUpSubmitButton(): Locator {
562
+ return this.page
563
+ .locator('c-quantic-generated-answer-follow-up-input')
564
+ .getByRole('button', {name: 'Submit follow-up'});
565
+ }
566
+
567
+ async typeFollowUpQuestion(question: string): Promise<void> {
568
+ await this.followUpInput.fill(question);
569
+ }
570
+
571
+ async submitFollowUp(): Promise<void> {
572
+ await this.followUpSubmitButton.click();
573
+ }
574
+
575
+ async waitForFollowUpStreamEndAnalytics(
576
+ followUpAnswerId: string
577
+ ): Promise<Request | boolean> {
578
+ if (this.analyticsMode === AnalyticsModeEnum.legacy) {
579
+ return this.analytics.waitForCustomUaAnalytics(
580
+ {
581
+ eventType: 'generatedAnswer',
582
+ eventValue: 'generatedAnswerStreamEnd',
583
+ },
584
+ (event) =>
585
+ event?.customData?.generativeQuestionAnsweringId === followUpAnswerId
586
+ );
587
+ }
588
+ return this.analytics.waitForEventProtocolAnalytics(
589
+ 'Rga.AnswerReceived',
590
+ (event) => event.answerGenerated === true
591
+ );
592
+ }
593
+
449
594
  streamEndAnalyticRequestPromise!: Promise<boolean | Request>;
450
595
  generateRequestPromise!: Promise<Request>;
451
596
  }
@@ -14,10 +14,6 @@ useCaseTestCases.forEach((useCase) => {
14
14
  let test = fixtures[useCase.value];
15
15
 
16
16
  test.describe(`quantic generated answer ${useCase.label}`, () => {
17
- test.use({
18
- genQaData,
19
- });
20
-
21
17
  analyticsModeTest.forEach((analytics) => {
22
18
  test.describe(analytics.label, () => {
23
19
  test.use({
@@ -309,7 +305,7 @@ useCaseTestCases.forEach((useCase) => {
309
305
  await generatedAnswer.streamEndAnalyticRequestPromise;
310
306
 
311
307
  const generateRequestPromise =
312
- generatedAnswer.waitForGenerateRequest();
308
+ generatedAnswer.waitForHeadAnswerRequest();
313
309
 
314
310
  const streamEndAnalyticRequestPromise =
315
311
  generatedAnswer.waitForStreamEndAnalytics();
@@ -0,0 +1,233 @@
1
+ import {testAgent, expect} from './fixture-agent';
2
+ import agentData from './agentData';
3
+ import {
4
+ analyticsModeTest,
5
+ AnalyticsModeEnum,
6
+ } from '../../../../../../playwright/utils/analyticsMode';
7
+
8
+ const exampleAgentId = '5fd0b5ea-d368-488e-bdeb-f6221ec0fb98';
9
+
10
+ analyticsModeTest.forEach((analytics) => {
11
+ testAgent.describe(`quantic generated answer - agent - ${analytics.label}`, () => {
12
+ testAgent.use({
13
+ analyticsMode: analytics.mode,
14
+ options: {agentId: exampleAgentId},
15
+ permissions: ['clipboard-read', 'clipboard-write'],
16
+ });
17
+
18
+ if (analytics.mode !== AnalyticsModeEnum.legacy) {
19
+ testAgent.fixme(true, 'Agent next analytics mode not yet implemented');
20
+ return;
21
+ }
22
+
23
+ testAgent(
24
+ 'should handle multiple answer generation, feedback, clipboard, citation, and follow-up interactions',
25
+ async ({generatedAnswer, page}) => {
26
+ await testAgent.step(
27
+ 'should send a stream end analytics event when the first answer is generated',
28
+ async () => {
29
+ await generatedAnswer.streamEndAnalyticRequestPromise;
30
+ }
31
+ );
32
+
33
+ await testAgent.step(
34
+ 'should send like analytics event when liking the first answer',
35
+ async () => {
36
+ const likeAnalyticRequestPromise =
37
+ generatedAnswer.waitForLikeGeneratedAnswerAnalytics();
38
+ await generatedAnswer.clickLikeButton();
39
+ await likeAnalyticRequestPromise;
40
+ }
41
+ );
42
+
43
+ await testAgent.step(
44
+ 'should send dislike analytics event when disliking the first answer',
45
+ async () => {
46
+ const dislikeAnalyticRequestPromise =
47
+ generatedAnswer.waitForDislikeGeneratedAnswerAnalytics();
48
+ await generatedAnswer.clickDislikeButton();
49
+ await dislikeAnalyticRequestPromise;
50
+ }
51
+ );
52
+
53
+ await testAgent.step(
54
+ 'should send a copy to clipboard analytics event and update the clipboard with the first answer content',
55
+ async () => {
56
+ const analyticRequestPromise =
57
+ generatedAnswer.waitForCopyToClipboardAnalytics();
58
+ await generatedAnswer.clickCopyToClipboardButton();
59
+ await analyticRequestPromise;
60
+
61
+ const clipboardContent = await page.evaluate(() =>
62
+ navigator.clipboard.readText()
63
+ );
64
+ expect(clipboardContent).toContain('Testing in Coveo');
65
+ }
66
+ );
67
+
68
+ await testAgent.step(
69
+ 'should log citation hover analytics when hovering over a citation from the first answer',
70
+ async () => {
71
+ const citationIndex = 0;
72
+ const {id, permanentid} = agentData.citations[citationIndex];
73
+ const citationHoverAnalyticRequestPromise =
74
+ generatedAnswer.waitForSourceHoverAnalytics({
75
+ citationId: id,
76
+ permanentId: permanentid,
77
+ });
78
+
79
+ await generatedAnswer.hoverOverCitation(citationIndex);
80
+ await citationHoverAnalyticRequestPromise;
81
+ }
82
+ );
83
+
84
+ await testAgent.step(
85
+ 'should log citation click analytics when clicking on a citation from the first answer',
86
+ async () => {
87
+ const citationIndex = 0;
88
+ const {id, title, source, uri, clickUri, permanentid} =
89
+ agentData.citations[citationIndex];
90
+ const citationClickAnalyticRequestPromise =
91
+ generatedAnswer.waitForCitationClickAnalytics({
92
+ documentTitle: title,
93
+ sourceName: source,
94
+ documentPosition: citationIndex + 1,
95
+ documentUri: uri,
96
+ documentUrl: clickUri,
97
+ citationId: id,
98
+ contentIDKey: 'permanentid',
99
+ contentIDValue: permanentid,
100
+ });
101
+ await generatedAnswer.clickOnCitation(citationIndex);
102
+ await citationClickAnalyticRequestPromise;
103
+ }
104
+ );
105
+
106
+ await testAgent.step(
107
+ 'FIRST FOLLOW-UP: should send the follow-up request with the correct conversationId and conversationToken',
108
+ async () => {
109
+ const followUpRequestPromise =
110
+ generatedAnswer.waitForAgentFollowUpRequest();
111
+
112
+ await generatedAnswer.typeFollowUpQuestion('follow-up question');
113
+ await generatedAnswer.submitFollowUp();
114
+
115
+ const followUpRequest = await followUpRequestPromise;
116
+ const followUpRequestBody = followUpRequest.postDataJSON();
117
+ expect(followUpRequestBody.conversationId).toBe(
118
+ agentData.conversationId
119
+ );
120
+ expect(followUpRequestBody.conversationToken).toBe(
121
+ agentData.conversationToken
122
+ );
123
+ }
124
+ );
125
+
126
+ await testAgent.step(
127
+ 'should send stream end analytics with the right answerId for the follow-up',
128
+ async () => {
129
+ const followUpStreamEndPromise =
130
+ generatedAnswer.waitForFollowUpStreamEndAnalytics(
131
+ agentData.answerId2
132
+ );
133
+ await followUpStreamEndPromise;
134
+ }
135
+ );
136
+
137
+ await testAgent.step(
138
+ 'should send like analytics with the follow-up answerId when liking the first follow-up answer',
139
+ async () => {
140
+ const likeAnalyticRequestPromise =
141
+ generatedAnswer.waitForLikeGeneratedAnswerAnalyticsForId(
142
+ agentData.answerId2
143
+ );
144
+ // Should be the first like button, since the previous answers are collapsed and the follow-up answer is the only one visible.
145
+ await generatedAnswer.clickNthLikeButton(0);
146
+ await likeAnalyticRequestPromise;
147
+ }
148
+ );
149
+
150
+ await testAgent.step(
151
+ 'SECOND FOLLOW-UP: should send the second follow-up request with the correct conversationId and conversationToken',
152
+ async () => {
153
+ const followUpRequestPromise =
154
+ generatedAnswer.waitForAgentFollowUpRequest();
155
+
156
+ await generatedAnswer.typeFollowUpQuestion(
157
+ 'second follow-up question'
158
+ );
159
+ await generatedAnswer.submitFollowUp();
160
+
161
+ const followUpRequest = await followUpRequestPromise;
162
+ const followUpRequestBody = followUpRequest.postDataJSON();
163
+ expect(followUpRequestBody.conversationId).toBe(
164
+ agentData.conversationId
165
+ );
166
+ expect(followUpRequestBody.conversationToken).toBe(
167
+ agentData.conversationToken
168
+ );
169
+ }
170
+ );
171
+
172
+ await testAgent.step(
173
+ 'should send stream end analytics with the right answerId for the second follow-up',
174
+ async () => {
175
+ const followUpStreamEndPromise =
176
+ generatedAnswer.waitForFollowUpStreamEndAnalytics(
177
+ agentData.answerId3
178
+ );
179
+ await followUpStreamEndPromise;
180
+ }
181
+ );
182
+
183
+ await testAgent.step(
184
+ 'should send dislike analytics with the right answerId for the second follow-up when disliking the second follow-up answer',
185
+ async () => {
186
+ const dislikeAnalyticRequestPromise =
187
+ generatedAnswer.waitForDislikeGeneratedAnswerAnalyticsForId(
188
+ agentData.answerId3
189
+ );
190
+ // Should be the first dislike button, since the previous answers are collapsed and the second follow-up answer is the only one visible.
191
+ await generatedAnswer.clickNthDislikeButton(0);
192
+ await dislikeAnalyticRequestPromise;
193
+ }
194
+ );
195
+
196
+ await testAgent.step(
197
+ 'should expand previous answers when clicking on the "Show previous answers" button',
198
+ async () => {
199
+ await generatedAnswer.clickShowPreviousAnswersButton();
200
+ await expect(generatedAnswer.threadItems).toHaveCount(3);
201
+ }
202
+ );
203
+
204
+ await testAgent.step(
205
+ 'should expand the first answer when clicking on the toggle button of the first answer',
206
+ async () => {
207
+ await generatedAnswer.clickNthThreadItem(0);
208
+ await expect(generatedAnswer.generatedAnswerBody.nth(0)).toBeVisible();
209
+ await expect(generatedAnswer.generatedAnswerBody.nth(1)).not.toBeVisible();
210
+ await expect(generatedAnswer.generatedAnswerBody.nth(2)).toBeVisible();
211
+ }
212
+ );
213
+
214
+ await testAgent.step(
215
+ 'should allow to like the first answer, sending the correct analytics and not affecting the other answers',
216
+ async () => {
217
+ const likeAnalyticRequestPromise =
218
+ generatedAnswer.waitForLikeGeneratedAnswerAnalyticsForId(
219
+ agentData.answerId1
220
+ );
221
+ await generatedAnswer.clickNthLikeButton(0);
222
+ await likeAnalyticRequestPromise;
223
+ await expect(generatedAnswer.likeButton.nth(0)).toHaveAttribute('aria-pressed', 'true');
224
+ await expect(generatedAnswer.dislikeButton.nth(0)).toHaveAttribute('aria-pressed', 'false');
225
+ // Only two answers are visible, so only 2 like/dislike buttons visible.
226
+ await expect(generatedAnswer.dislikeButton.nth(1)).toHaveAttribute('aria-pressed', 'true');
227
+ await expect(generatedAnswer.likeButton.nth(1)).toHaveAttribute('aria-pressed', 'false');
228
+ }
229
+ );
230
+ }
231
+ );
232
+ });
233
+ });
@@ -43,7 +43,7 @@
43
43
  >
44
44
  </c-quantic-generated-answer-thread>
45
45
  </div>
46
- <div class="slds-p-horizontal_large slds-p-vertical_medium">
46
+ <div class="slds-border_top slds-p-horizontal_large slds-p-vertical_medium">
47
47
  <c-quantic-generated-answer-follow-up-input
48
48
  onquantic__submitfollowup={handleFollowUpSubmit}
49
49
  submit-button-disabled={isAnswerGenerationOngoing}