@coveo/quantic 3.11.0 → 3.11.1

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 (60) hide show
  1. package/README.md +1 -1
  2. package/docs/out/quantic-docs.json +27 -0
  3. package/force-app/main/default/lwc/quanticBreadcrumbManager/__tests__/quanticBreadcrumbManager.test.js +341 -0
  4. package/force-app/main/default/lwc/quanticBreadcrumbManager/quanticBreadcrumbManager.html +8 -8
  5. package/force-app/main/default/lwc/quanticBreadcrumbManager/quanticBreadcrumbManager.js +8 -8
  6. package/force-app/main/default/lwc/quanticFeedbackModalQna/templates/quanticFeedbackModalQna.html +1 -0
  7. package/force-app/main/default/lwc/quanticGeneratedAnswer/__tests__/quanticGeneratedAnswer.test.js +538 -21
  8. package/force-app/main/default/lwc/quanticGeneratedAnswer/e2e/data.ts +56 -0
  9. package/force-app/main/default/lwc/quanticGeneratedAnswer/e2e/fixture.ts +87 -0
  10. package/force-app/main/default/lwc/quanticGeneratedAnswer/e2e/pageObject.ts +279 -0
  11. package/force-app/main/default/lwc/quanticGeneratedAnswer/e2e/quanticGeneratedAnswer.e2e.ts +178 -0
  12. package/force-app/main/default/lwc/quanticGeneratedAnswer/templates/generatedAnswer.html +24 -10
  13. package/force-app/main/default/lwc/quanticGeneratedAnswer/templates/retryPrompt.html +2 -2
  14. package/force-app/main/default/lwc/quanticGeneratedAnswerToggle/quanticGeneratedAnswerToggle.html +1 -1
  15. package/force-app/main/default/lwc/quanticPager/e2e/pagerObject.ts +1 -1
  16. package/force-app/main/default/lwc/quanticUserActionsToggle/e2e/pageObject.ts +1 -1
  17. package/force-app/main/default/lwc/quanticUserActionsToggle/quanticUserActionsToggle.js +4 -0
  18. package/force-app/main/default/staticresources/coveobueno/browser/bueno.js +4 -4
  19. package/force-app/main/default/staticresources/coveoheadless/case-assist/headless.js +8 -10
  20. package/force-app/main/default/staticresources/coveoheadless/definitions/api/analytics/instant-result-analytics.d.ts +1 -1
  21. package/force-app/main/default/staticresources/coveoheadless/definitions/api/analytics/search-analytics.d.ts +1 -1
  22. package/force-app/main/default/staticresources/coveoheadless/definitions/api/knowledge/stream-answer-api.d.ts +3 -1
  23. package/force-app/main/default/staticresources/coveoheadless/definitions/app/commerce-engine/commerce-engine.ssr.d.ts +2 -0
  24. package/force-app/main/default/staticresources/coveoheadless/definitions/app/commerce-ssr-engine/factories/build-factory.d.ts +2 -0
  25. package/force-app/main/default/staticresources/coveoheadless/definitions/app/commerce-ssr-engine/factories/utils/recommendation-filter.d.ts +11 -0
  26. package/force-app/main/default/staticresources/coveoheadless/definitions/app/commerce-ssr-engine/types/common.d.ts +12 -2
  27. package/force-app/main/default/staticresources/coveoheadless/definitions/app/commerce-ssr-engine/types/kind.d.ts +6 -0
  28. package/force-app/main/default/staticresources/coveoheadless/definitions/commerce.index.d.ts +1 -1
  29. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/core/breadcrumb-manager/headless-core-breadcrumb-manager.ssr.d.ts +1 -0
  30. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/core/facets/generator/headless-commerce-facet-generator.ssr.d.ts +3 -0
  31. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/core/parameter-manager/headless-core-parameter-manager.d.ts +7 -6
  32. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/core/parameter-manager/headless-core-parameter-manager.ssr.d.ts +3 -1
  33. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/core/sub-controller/headless-sub-controller.d.ts +1 -2
  34. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/core/url-manager/headless-core-url-manager.d.ts +11 -2
  35. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/product-list/headless-product-list.ssr.d.ts +1 -1
  36. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/product-view/headless-product-view.ssr.d.ts +1 -0
  37. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/recommendations/headless-recommendations.d.ts +1 -0
  38. package/force-app/main/default/staticresources/coveoheadless/definitions/features/commerce/parameters/parameters-actions.d.ts +22 -6
  39. package/force-app/main/default/staticresources/coveoheadless/definitions/features/commerce/parameters/parameters-schema.d.ts +4 -0
  40. package/force-app/main/default/staticresources/coveoheadless/definitions/features/commerce/parameters/parameters-selectors.d.ts +0 -11
  41. package/force-app/main/default/staticresources/coveoheadless/definitions/features/commerce/parameters/parameters-serializer.d.ts +6 -1
  42. package/force-app/main/default/staticresources/coveoheadless/definitions/features/commerce/parameters/parameters-serializer.ssr.d.ts +44 -0
  43. package/force-app/main/default/staticresources/coveoheadless/definitions/features/commerce/parameters/parameters-slice.d.ts +4 -0
  44. package/force-app/main/default/staticresources/coveoheadless/definitions/features/commerce/parameters/parameters-state.d.ts +4 -0
  45. package/force-app/main/default/staticresources/coveoheadless/definitions/features/commerce/recommendations/recommendations-actions.d.ts +1 -0
  46. package/force-app/main/default/staticresources/coveoheadless/definitions/features/commerce/recommendations/recommendations-state.d.ts +1 -0
  47. package/force-app/main/default/staticresources/coveoheadless/definitions/features/commerce/search/search-selectors.d.ts +0 -12
  48. package/force-app/main/default/staticresources/coveoheadless/definitions/features/debug/debug-selectors.d.ts +1 -1
  49. package/force-app/main/default/staticresources/coveoheadless/definitions/features/question-answering/question-answering-selectors.d.ts +1 -1
  50. package/force-app/main/default/staticresources/coveoheadless/definitions/features/search/search-selectors.d.ts +1 -1
  51. package/force-app/main/default/staticresources/coveoheadless/definitions/features/search-parameters/search-parameter-serializer.d.ts +5 -2
  52. package/force-app/main/default/staticresources/coveoheadless/definitions/ssr-commerce.index.d.ts +4 -3
  53. package/force-app/main/default/staticresources/coveoheadless/definitions/state/commerce-app-state.d.ts +2 -2
  54. package/force-app/main/default/staticresources/coveoheadless/definitions/state/state-sections.d.ts +4 -0
  55. package/force-app/main/default/staticresources/coveoheadless/definitions/utils/errors.d.ts +3 -0
  56. package/force-app/main/default/staticresources/coveoheadless/headless.js +10 -12
  57. package/force-app/main/default/staticresources/coveoheadless/insight/headless.js +9 -11
  58. package/force-app/main/default/staticresources/coveoheadless/recommendation/headless.js +8 -10
  59. package/force-app/main/default/staticresources/dompurify/purify.min.js +2 -1339
  60. package/package.json +14 -14
@@ -0,0 +1,87 @@
1
+ import {quanticBase} from '../../../../../../playwright/fixtures/baseFixture';
2
+ import {SearchObject} from '../../../../../../playwright/page-object/searchObject';
3
+ import {
4
+ searchRequestRegex,
5
+ insightSearchRequestRegex,
6
+ } from '../../../../../../playwright/utils/requests';
7
+ import {InsightSetupObject} from '../../../../../../playwright/page-object/insightSetupObject';
8
+ import {useCaseEnum} from '../../../../../../playwright/utils/useCase';
9
+ import {GeneratedAnswerObject} from './pageObject';
10
+ import genQaData from './data';
11
+ import type {GenQaData} from './data';
12
+
13
+ const pageUrl = 's/quantic-generated-answer';
14
+
15
+ interface GeneratedAnswerOptions {
16
+ fieldsToIncludeInCitations: string;
17
+ collapsible: boolean;
18
+ withToggle: boolean;
19
+ useCase: string;
20
+ }
21
+
22
+ type QuanticGeneratedAnswerE2ESearchFixtures = {
23
+ genQaData: GenQaData;
24
+ generatedAnswer: GeneratedAnswerObject;
25
+ search: SearchObject;
26
+ options: Partial<GeneratedAnswerOptions>;
27
+ };
28
+
29
+ type QuanticGeneratedAnswerE2EInsightFixtures =
30
+ QuanticGeneratedAnswerE2ESearchFixtures & {
31
+ insightSetup: InsightSetupObject;
32
+ };
33
+
34
+ export const testSearch =
35
+ quanticBase.extend<QuanticGeneratedAnswerE2ESearchFixtures>({
36
+ genQaData,
37
+ options: {},
38
+ search: async ({page}, use) => {
39
+ await use(new SearchObject(page, searchRequestRegex));
40
+ },
41
+ generatedAnswer: async (
42
+ {page, options, configuration, search, genQaData: data},
43
+ use
44
+ ) => {
45
+ const generatedAnswerObject = new GeneratedAnswerObject(
46
+ page,
47
+ data.streamId
48
+ );
49
+ await page.goto(pageUrl);
50
+ await search.mockSearchWithGenerativeQuestionAnsweringId(data.streamId);
51
+ await generatedAnswerObject.mockStreamResponse(data.streams);
52
+ await configuration.configure(options);
53
+ await search.waitForSearchResponse();
54
+ await use(generatedAnswerObject);
55
+ },
56
+ });
57
+
58
+ export const testInsight =
59
+ quanticBase.extend<QuanticGeneratedAnswerE2EInsightFixtures>({
60
+ genQaData,
61
+ options: {},
62
+ search: async ({page}, use) => {
63
+ await use(new SearchObject(page, insightSearchRequestRegex));
64
+ },
65
+ insightSetup: async ({page}, use) => {
66
+ await use(new InsightSetupObject(page));
67
+ },
68
+ generatedAnswer: async (
69
+ {page, options, search, configuration, insightSetup, genQaData: data},
70
+ use
71
+ ) => {
72
+ const generatedAnswerObject = new GeneratedAnswerObject(
73
+ page,
74
+ data.streamId
75
+ );
76
+ await page.goto(pageUrl);
77
+ await search.mockSearchWithGenerativeQuestionAnsweringId(data.streamId);
78
+ await generatedAnswerObject.mockStreamResponse(data.streams);
79
+ configuration.configure({...options, useCase: useCaseEnum.insight});
80
+ await insightSetup.waitForInsightInterfaceInitialization();
81
+ await search.performSearch();
82
+ await search.waitForSearchResponse();
83
+ await use(generatedAnswerObject);
84
+ },
85
+ });
86
+
87
+ export {expect} from '@playwright/test';
@@ -0,0 +1,279 @@
1
+ import type {Locator, Page, Request} from '@playwright/test';
2
+ import {
3
+ isUaClickEvent,
4
+ isUaCustomEvent,
5
+ } from '../../../../../../playwright/utils/requests';
6
+
7
+ const minimumCitationTooltipDisplayDurationMs = 1500;
8
+
9
+ export class GeneratedAnswerObject {
10
+ constructor(
11
+ public page: Page,
12
+ public streamId: string
13
+ ) {
14
+ this.page = page;
15
+ this.streamId = streamId;
16
+ }
17
+
18
+ get likeButton(): Locator {
19
+ return this.page.getByRole('button', {name: 'This answer was helpful'});
20
+ }
21
+
22
+ get dislikeButton(): Locator {
23
+ return this.page.getByRole('button', {name: 'This answer was not helpful'});
24
+ }
25
+
26
+ get copyToClipboardButton(): Locator {
27
+ return this.page.getByRole('button', {name: 'Copy'});
28
+ }
29
+
30
+ get toggleButton(): Locator {
31
+ return this.page.getByTestId('generated-answer__toggle-button');
32
+ }
33
+
34
+ questionContainer(questionId: string): Locator {
35
+ return this.page.getByTestId(questionId);
36
+ }
37
+
38
+ answerOption(questionId: string, answerValue: string): Locator {
39
+ return this.questionContainer(questionId).locator('.slds-radio_button', {
40
+ hasText: new RegExp(`^${answerValue}$`),
41
+ });
42
+ }
43
+
44
+ get feedbackDocumentUrlInput(): Locator {
45
+ return this.page.locator(
46
+ '.feedback-modal-qna__body input[name="documentUrl"]'
47
+ );
48
+ }
49
+
50
+ get feedbackDetailsInput(): Locator {
51
+ return this.page
52
+ .locator('.feedback-modal-qna__body [data-name="details"]')
53
+ .getByRole('textbox');
54
+ }
55
+
56
+ get submitFeedbackButton(): Locator {
57
+ return this.page.getByRole('button', {name: /Send feedback/i});
58
+ }
59
+
60
+ get citationLink(): Locator {
61
+ return this.page
62
+ .getByTestId('generated-answer__citations')
63
+ .locator('.citation__link');
64
+ }
65
+
66
+ async hoverOverCitation(index: number): Promise<void> {
67
+ // waiting 500ms to allow the component to render completely, cause any re-rendering abort the hover action.
68
+ await this.page.waitForTimeout(500);
69
+ await this.citationLink.nth(index).hover();
70
+ await this.page.waitForTimeout(minimumCitationTooltipDisplayDurationMs);
71
+ await this.page.mouse.move(0, 0);
72
+ }
73
+
74
+ async clickOnCitation(index: number): Promise<void> {
75
+ await this.citationLink.nth(index).click();
76
+ }
77
+
78
+ async typeInFeedbackDocumentUrlInput(text: string): Promise<void> {
79
+ await this.feedbackDocumentUrlInput.fill(text);
80
+ }
81
+
82
+ async typeInFeedbackDetailsInput(text: string): Promise<void> {
83
+ await this.feedbackDetailsInput.fill(text);
84
+ }
85
+
86
+ async clickSubmitFeedbackButton(): Promise<void> {
87
+ await this.submitFeedbackButton.click();
88
+ }
89
+
90
+ async fillFeedbackForm(answers: Record<string, string>): Promise<void> {
91
+ for (const [questionId, answerValue] of Object.entries(answers)) {
92
+ const option = this.answerOption(questionId, answerValue);
93
+ // eslint-disable-next-line no-await-in-loop
94
+ await option.click();
95
+ }
96
+ }
97
+
98
+ async clickLikeButton(): Promise<void> {
99
+ await this.likeButton.click();
100
+ }
101
+
102
+ async clickDislikeButton(): Promise<void> {
103
+ await this.dislikeButton.click();
104
+ }
105
+
106
+ async clickCopyToClipboardButton(): Promise<void> {
107
+ await this.copyToClipboardButton.click();
108
+ }
109
+
110
+ async clickToggleButton(): Promise<void> {
111
+ await this.toggleButton.click();
112
+ }
113
+
114
+ async waitForStreamEndUaAnalytics(): Promise<Request> {
115
+ return this.waitForGeneratedAnswerCustomUaAnalytics(
116
+ 'generatedAnswerStreamEnd'
117
+ );
118
+ }
119
+
120
+ async waitForLikeGeneratedAnswerUaAnalytics(): Promise<Request> {
121
+ return this.waitForGeneratedAnswerCustomUaAnalytics('likeGeneratedAnswer');
122
+ }
123
+
124
+ async waitForDislikeGeneratedAnswerUaAnalytics(): Promise<Request> {
125
+ return this.waitForGeneratedAnswerCustomUaAnalytics(
126
+ 'dislikeGeneratedAnswer'
127
+ );
128
+ }
129
+
130
+ async waitForCopyToClipboardUaAnalytics(): Promise<Request> {
131
+ return this.waitForGeneratedAnswerCustomUaAnalytics(
132
+ 'generatedAnswerCopyToClipboard'
133
+ );
134
+ }
135
+
136
+ async waitForShowAnswersUaAnalytics(): Promise<Request> {
137
+ return this.waitForGeneratedAnswerCustomUaAnalytics(
138
+ 'generatedAnswerShowAnswers'
139
+ );
140
+ }
141
+
142
+ async waitForHideAnswersUaAnalytics(): Promise<Request> {
143
+ return this.waitForGeneratedAnswerCustomUaAnalytics(
144
+ 'generatedAnswerHideAnswers'
145
+ );
146
+ }
147
+
148
+ async waitForSourceHoverUaAnalytics(
149
+ expectedFields: Record<string, any>
150
+ ): Promise<Request> {
151
+ return this.waitForGeneratedAnswerCustomUaAnalytics(
152
+ 'generatedAnswerSourceHover',
153
+ (customData: Record<string, any>) => {
154
+ return Object.keys(expectedFields).every(
155
+ (key) => customData?.[key] === expectedFields[key]
156
+ );
157
+ }
158
+ );
159
+ }
160
+
161
+ async waitForFeedbackSubmitUaAnalytics(
162
+ expectedFields: Record<string, any>
163
+ ): Promise<Request> {
164
+ return this.waitForGeneratedAnswerCustomUaAnalytics(
165
+ 'generatedAnswerFeedbackSubmitV2',
166
+ (customData: Record<string, any>) => {
167
+ return Object.keys(expectedFields).every(
168
+ (key) => customData?.[key] === expectedFields[key]
169
+ );
170
+ }
171
+ );
172
+ }
173
+
174
+ async waitForCitationClickUaAnalytics(
175
+ expectedFields: Record<string, any>,
176
+ expectedCustomFields: Record<string, any>
177
+ ): Promise<Request> {
178
+ return this.waitForGeneratedAnswerClickUaAnalytics(
179
+ 'generatedAnswerCitationClick',
180
+ (data: Record<string, any>, customData: Record<string, any>) => {
181
+ return (
182
+ Object.keys(expectedFields).every(
183
+ (key) => data?.[key] === expectedFields[key]
184
+ ) &&
185
+ Object.keys(expectedCustomFields).every(
186
+ (key) => customData?.[key] === expectedCustomFields[key]
187
+ )
188
+ );
189
+ }
190
+ );
191
+ }
192
+
193
+ async waitForGeneratedAnswerClickUaAnalytics(
194
+ actionCause: string,
195
+ customChecker?: Function
196
+ ): Promise<Request> {
197
+ const uaRequest = this.page.waitForRequest((request) => {
198
+ if (isUaClickEvent(request)) {
199
+ const requestBody = request.postDataJSON?.();
200
+ const requestData = JSON.parse(requestBody.clickEvent);
201
+
202
+ const expectedFields: Record<string, any> = {
203
+ actionCause,
204
+ };
205
+
206
+ const matchesExpectedFields = Object.keys(expectedFields).every(
207
+ (key) => requestData?.[key] === expectedFields[key]
208
+ );
209
+
210
+ const customData = requestData?.customData;
211
+
212
+ const matchesGenerativeId =
213
+ customData?.generativeQuestionAnsweringId === this.streamId;
214
+
215
+ return (
216
+ matchesExpectedFields &&
217
+ matchesGenerativeId &&
218
+ (customChecker ? customChecker(requestData, customData) : true)
219
+ );
220
+ }
221
+ return false;
222
+ });
223
+ return uaRequest;
224
+ }
225
+
226
+ async waitForGeneratedAnswerCustomUaAnalytics(
227
+ eventValue: string,
228
+ customChecker?: Function
229
+ ): Promise<Request> {
230
+ const uaRequest = this.page.waitForRequest((request) => {
231
+ if (isUaCustomEvent(request)) {
232
+ const requestBody = request.postDataJSON?.();
233
+ const expectedFields: Record<string, any> = {
234
+ eventType: 'generatedAnswer',
235
+ eventValue: eventValue,
236
+ };
237
+
238
+ const matchesExpectedFields = Object.keys(expectedFields).every(
239
+ (key) => requestBody?.[key] === expectedFields[key]
240
+ );
241
+
242
+ const customData = requestBody?.customData;
243
+
244
+ const matchesGenerativeId =
245
+ customData?.generativeQuestionAnsweringId === this.streamId;
246
+
247
+ return (
248
+ matchesExpectedFields &&
249
+ matchesGenerativeId &&
250
+ (customChecker ? customChecker(customData) : true)
251
+ );
252
+ }
253
+ return false;
254
+ });
255
+ return uaRequest;
256
+ }
257
+
258
+ async mockStreamResponse(
259
+ body: Array<{payloadType: string; payload: string; finishReason?: string}>
260
+ ) {
261
+ await this.page.route(
262
+ `**/machinelearning/streaming/${this.streamId}`,
263
+ (route) => {
264
+ let bodyText = '';
265
+ body.forEach((data) => {
266
+ bodyText += `data: ${JSON.stringify(data)} \n\n`;
267
+ });
268
+
269
+ route.fulfill({
270
+ status: 200,
271
+ body: bodyText,
272
+ headers: {
273
+ 'content-type': 'text/event-stream',
274
+ },
275
+ });
276
+ }
277
+ );
278
+ }
279
+ }
@@ -0,0 +1,178 @@
1
+ import {testSearch, testInsight} from './fixture';
2
+ import {useCaseTestCases} from '../../../../../../playwright/utils/useCase';
3
+ import genQaData from './data';
4
+
5
+ const fixtures = {
6
+ search: testSearch,
7
+ insight: testInsight,
8
+ };
9
+
10
+ useCaseTestCases.forEach((useCase) => {
11
+ let test = fixtures[useCase.value];
12
+
13
+ test.describe(`quantic generated answer ${useCase.label}`, () => {
14
+ test.use({
15
+ genQaData,
16
+ });
17
+
18
+ test.describe('when the answer has been generated', () => {
19
+ test('should send a stream end analytics event', async ({
20
+ generatedAnswer,
21
+ }) => {
22
+ await generatedAnswer.waitForStreamEndUaAnalytics();
23
+ });
24
+ });
25
+
26
+ test.describe('when providing positive feedback', () => {
27
+ test('should send positive feedback analytics containing all details', async ({
28
+ generatedAnswer,
29
+ }) => {
30
+ const likeAnalyticRequestPromise =
31
+ generatedAnswer.waitForLikeGeneratedAnswerUaAnalytics();
32
+ await generatedAnswer.clickLikeButton();
33
+ await likeAnalyticRequestPromise;
34
+
35
+ const exampleDocumentUrl = 'https://www.coveo.com/';
36
+ const exampleDetails = 'example details...';
37
+ await generatedAnswer.fillFeedbackForm({
38
+ correctTopic: 'Yes',
39
+ hallucinationFree: 'Yes',
40
+ documented: 'Not sure',
41
+ readable: 'Yes',
42
+ });
43
+ await generatedAnswer.typeInFeedbackDocumentUrlInput(
44
+ exampleDocumentUrl
45
+ );
46
+ await generatedAnswer.typeInFeedbackDetailsInput(exampleDetails);
47
+
48
+ const feedbackAnalyticRequestPromise =
49
+ generatedAnswer.waitForFeedbackSubmitUaAnalytics({
50
+ correctTopic: 'yes',
51
+ hallucinationFree: 'yes',
52
+ documented: 'unknown',
53
+ readable: 'yes',
54
+ documentUrl: exampleDocumentUrl,
55
+ details: exampleDetails,
56
+ helpful: true,
57
+ });
58
+ await generatedAnswer.clickSubmitFeedbackButton();
59
+ await feedbackAnalyticRequestPromise;
60
+ });
61
+ });
62
+
63
+ test.describe('when providing negative feedback', () => {
64
+ test('should send negative feedback analytics containing all details', async ({
65
+ generatedAnswer,
66
+ }) => {
67
+ const dislikeAnalyticRequestPromise =
68
+ generatedAnswer.waitForDislikeGeneratedAnswerUaAnalytics();
69
+ await generatedAnswer.clickDislikeButton();
70
+ await dislikeAnalyticRequestPromise;
71
+
72
+ const exampleDocumentUrl = 'https://www.coveo.com/';
73
+ const exampleDetails = 'example details...';
74
+ await generatedAnswer.fillFeedbackForm({
75
+ correctTopic: 'No',
76
+ hallucinationFree: 'Not sure',
77
+ documented: 'No',
78
+ readable: 'No',
79
+ });
80
+ await generatedAnswer.typeInFeedbackDocumentUrlInput(
81
+ exampleDocumentUrl
82
+ );
83
+ await generatedAnswer.typeInFeedbackDetailsInput(exampleDetails);
84
+
85
+ const feedbackAnalyticRequestPromise =
86
+ generatedAnswer.waitForFeedbackSubmitUaAnalytics({
87
+ correctTopic: 'no',
88
+ hallucinationFree: 'unknown',
89
+ documented: 'no',
90
+ readable: 'no',
91
+ documentUrl: exampleDocumentUrl,
92
+ details: exampleDetails,
93
+ helpful: false,
94
+ });
95
+ await generatedAnswer.clickSubmitFeedbackButton();
96
+ await feedbackAnalyticRequestPromise;
97
+ });
98
+ });
99
+
100
+ test.describe('when copying the generated answer to clipboard', () => {
101
+ test('should send a copy to clipboard analytics event', async ({
102
+ generatedAnswer,
103
+ }) => {
104
+ const analyticRequestPromise =
105
+ generatedAnswer.waitForCopyToClipboardUaAnalytics();
106
+ await generatedAnswer.clickCopyToClipboardButton();
107
+ await analyticRequestPromise;
108
+ });
109
+ });
110
+
111
+ test.describe('when the property withToggle is set to true', () => {
112
+ test.use({
113
+ options: {
114
+ withToggle: true,
115
+ },
116
+ });
117
+
118
+ test('should allow toggeling the generated OFF and ON and log analytics', async ({
119
+ generatedAnswer,
120
+ }) => {
121
+ const hideAnswerAnalyticRequestPromise =
122
+ generatedAnswer.waitForHideAnswersUaAnalytics();
123
+ await generatedAnswer.clickToggleButton();
124
+ await hideAnswerAnalyticRequestPromise;
125
+
126
+ const showAnswerAnalyticRequestPromise =
127
+ generatedAnswer.waitForShowAnswersUaAnalytics();
128
+ await generatedAnswer.clickToggleButton();
129
+ await showAnswerAnalyticRequestPromise;
130
+ });
131
+ });
132
+
133
+ test.describe('when interacting with citations', () => {
134
+ test.describe('when hovering over a citation', () => {
135
+ test('should log citation hover analytics', async ({
136
+ generatedAnswer,
137
+ }) => {
138
+ const citationIndex = 0;
139
+ const {id, permanentid} = genQaData.citations[citationIndex];
140
+ const citationHoverAnalyticRequestPromise =
141
+ generatedAnswer.waitForSourceHoverUaAnalytics({
142
+ citationId: id,
143
+ permanentId: permanentid,
144
+ });
145
+ await generatedAnswer.hoverOverCitation(citationIndex);
146
+ await citationHoverAnalyticRequestPromise;
147
+ });
148
+ });
149
+
150
+ test.describe('when clicking on a citation', () => {
151
+ test('should log citation click analytics', async ({
152
+ generatedAnswer,
153
+ }) => {
154
+ const citationIndex = 0;
155
+ const {id, title, source, uri, clickUri, permanentid} =
156
+ genQaData.citations[citationIndex];
157
+ const citationClickAnalyticRequestPromise =
158
+ generatedAnswer.waitForCitationClickUaAnalytics(
159
+ {
160
+ documentTitle: title,
161
+ sourceName: source,
162
+ documentPosition: citationIndex + 1,
163
+ documentUri: uri,
164
+ documentUrl: clickUri,
165
+ },
166
+ {
167
+ citationId: id,
168
+ contentIDKey: 'permanentid',
169
+ contentIDValue: permanentid,
170
+ }
171
+ );
172
+ await generatedAnswer.clickOnCitation(citationIndex);
173
+ await citationClickAnalyticRequestPromise;
174
+ });
175
+ });
176
+ });
177
+ });
178
+ });
@@ -1,14 +1,18 @@
1
1
  <template>
2
2
  <template lwc:if={shouldDisplayGeneratedAnswer}>
3
- <div data-cy="generated-answer__card" class="slds-box">
3
+ <div data-testid="generated-answer__card" class="slds-box">
4
4
  <div class="generated-answer__card-header slds-grid">
5
5
  <span
6
+ data-testid="generated-answer__badge"
6
7
  class="generated-answer__badge slds-var-p-around_x-small slds-text-title_bold"
7
8
  >{labels.generatedAnswerForYou}</span
8
9
  >
9
10
  <div class="generated-answer__header-actions slds-grid">
10
11
  <template lwc:if={shouldDisplayActions}>
11
- <div class="slds-grid flex-one slds-grid_align-end">
12
+ <div
13
+ data-testid="generated-answer__actions"
14
+ class="slds-grid flex-one slds-grid_align-end"
15
+ >
12
16
  <c-quantic-feedback
13
17
  state={feedbackState}
14
18
  onquantic__like={handleLike}
@@ -24,7 +28,7 @@
24
28
  data-cy="generated-answer__feedback"
25
29
  ></c-quantic-feedback>
26
30
  <c-quantic-generated-answer-copy-to-clipboard
27
- data-cy="generated-answer__copy-to-clipboard"
31
+ data-testid="generated-answer__copy-to-clipboard"
28
32
  answer={answer}
29
33
  class="slds-var-m-horizontal_xx-small"
30
34
  ></c-quantic-generated-answer-copy-to-clipboard>
@@ -49,34 +53,44 @@
49
53
  answer-content-format={answerContentFormat}
50
54
  answer={answer}
51
55
  is-streaming={isStreaming}
52
- data-cy="generated-answer__content">
56
+ data-cy="generated-answer__content"
57
+ >
53
58
  </c-quantic-generated-answer-content>
54
59
  </div>
55
60
  <div
56
61
  data-cy="generated-answer__footer"
57
62
  class={generatedAnswerFooterCssClass}
58
63
  >
59
- <div class={generatedAnswerFooterRowClass} data-cy="generated-answer__footer-row">
64
+ <div
65
+ class={generatedAnswerFooterRowClass}
66
+ data-cy="generated-answer__footer-row"
67
+ >
60
68
  <template lwc:if={shouldDisplayCitations}>
61
69
  <div class="slds-var-m-top_small">
62
70
  <c-quantic-source-citations
63
71
  engine-id={engineId}
64
- data-cy="generated-answer__citations"
72
+ data-testid="generated-answer__citations"
65
73
  citations={citations}
66
74
  citation-hover-handler={handleCitationHover}
67
75
  ></c-quantic-source-citations>
68
76
  </div>
69
77
  </template>
70
78
  </div>
71
- <div class={generatedAnswerFooterRowClass} data-cy="generated-answer__footer-row">
79
+ <div
80
+ class={generatedAnswerFooterRowClass}
81
+ data-cy="generated-answer__footer-row"
82
+ >
72
83
  <template lwc:if={shouldShowCollapseGeneratingMessage}>
73
- <div class="generated-answer__collapse-generating-message">
84
+ <div
85
+ data-testid="generated-answer__collapse-generating-message"
86
+ class="generated-answer__collapse-generating-message"
87
+ >
74
88
  {labels.generatingAnswer}...
75
89
  </div>
76
90
  </template>
77
91
  <template lwc:if={shouldShowToggleCollapseAnswer}>
78
92
  <lightning-button
79
- data-cy="generated-answer__answer-toggle"
93
+ data-testid="generated-answer__answer-toggle"
80
94
  icon-name={toggleCollapseAnswerIcon}
81
95
  variant="base"
82
96
  label={toggleCollapseAnswerLabel}
@@ -89,7 +103,7 @@
89
103
  <template lwc:if={shouldShowDisclaimer}>
90
104
  <div
91
105
  class="generated-answer__disclaimer slds-grid_vertical-align-center slds-col slds-text-color_weak slds-text-body_small"
92
- data-cy="generated-answer__disclaimer"
106
+ data-testid="generated-answer__disclaimer"
93
107
  >
94
108
  <slot name="disclaimer"> {labels.rgaDisclaimer} </slot>
95
109
  </div>
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <template lwc:if={shouldDisplayGeneratedAnswer}>
3
3
  <div
4
- data-cy="generated-answer__card"
4
+ data-testid="generated-answer__card"
5
5
  class="slds-box"
6
6
  >
7
7
  <div
@@ -15,7 +15,7 @@
15
15
  <div>{labels.couldNotGenerateAnAnswer}</div>
16
16
  <div class="slds-var-m-top_small">
17
17
  <lightning-button
18
- data-cy="generated-answer__retry-button"
18
+ data-testid="generated-answer__retry-button"
19
19
  variant="brand-outline"
20
20
  label={labels.tryAgain}
21
21
  title={labels.tryAgain}
@@ -2,7 +2,7 @@
2
2
  <div class="slds-is-relative" onmouseenter={handleMouseEnter}
3
3
  onmouseleave={handleMouseLeave}>
4
4
  <lightning-input
5
- data-cy="generated-answer__toggle-button"
5
+ data-testid="generated-answer__toggle-button"
6
6
  type="toggle"
7
7
  onchange={toggleGeneratedAnswer}
8
8
  checked={isGeneratedAnswerVisible}
@@ -1,4 +1,4 @@
1
- import {Locator, Page, Request} from '@playwright/test';
1
+ import type {Locator, Page, Request} from '@playwright/test';
2
2
  import {isUaCustomEvent} from '../../../../../../playwright/utils/requests';
3
3
 
4
4
  export class PagerObject {
@@ -1,4 +1,4 @@
1
- import {Locator, Page, Request, Response} from '@playwright/test';
1
+ import type {Locator, Page, Request, Response} from '@playwright/test';
2
2
  import {isUaCustomEvent} from '../../../../../../playwright/utils/requests';
3
3
 
4
4
  const userActionsRequestRegex =