@iblai/iblai-js 1.4.2 → 1.4.4

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.
@@ -824,6 +824,7 @@ async function expectNoAccessibilityViolationsOnDialogs(page, rules, exclude = [
824
824
  expect(results.violations).toEqual([]);
825
825
  }
826
826
 
827
+ const deferredNotificationText = 'You will be notified once the report is available.';
827
828
  async function inviteUserTest(page, inviteModal) {
828
829
  const emailInput = inviteModal.locator('#email-invite');
829
830
  await expect(emailInput).toBeVisible({ timeout: 10000 });
@@ -847,6 +848,28 @@ async function navigateToAccountComponent(page, profileBtn) {
847
848
  await expect(tenantDialog).toBeVisible();
848
849
  return tenantDialog;
849
850
  }
851
+ /**
852
+ * Click a report card's primary action and confirm the date-range picker, so callers
853
+ * always end up in the post-generation state.
854
+ *
855
+ * Prefers the "Regenerate report" button when present (completed reports) because the plain
856
+ * "Download report" button on a completed card re-downloads the existing URL and skips the
857
+ * picker. When no Regenerate button exists, clicks "Download report" — which on a
858
+ * non-completed card also opens the picker.
859
+ *
860
+ * Either way we wait for the picker and click Generate/Regenerate to start the report.
861
+ */
862
+ async function triggerReportFromCard(page, card) {
863
+ const regenerateButton = card.getByRole('button', { name: 'Regenerate report' });
864
+ const downloadButton = card.getByRole('button', { name: 'Download report' });
865
+ const hasRegenerate = await regenerateButton.isVisible().catch(() => false);
866
+ const trigger = hasRegenerate ? regenerateButton : downloadButton;
867
+ await trigger.click();
868
+ const dateRangePopover = page.getByTestId('report-date-range-popover');
869
+ await expect(dateRangePopover).toBeVisible({ timeout: 10000 });
870
+ await dateRangePopover.getByRole('button', { name: /^(Generate|Regenerate)$/ }).click();
871
+ await expect(dateRangePopover).not.toBeVisible({ timeout: 10000 });
872
+ }
850
873
  async function navigateToDataReports(page) {
851
874
  const dataReportsTab = page.getByRole('tab', { name: 'Data Reports' });
852
875
  await expect(dataReportsTab).toBeVisible({ timeout: 120000 });
@@ -898,14 +921,40 @@ async function shouldOpenCSVEditorDialog(page) {
898
921
  test$2.skip();
899
922
  return;
900
923
  }
901
- const downloadButton = userReportCard.getByRole('button', {
902
- name: 'Download report',
903
- });
904
- await downloadButton.click();
905
- const csvEditorDialog = page.getByRole('dialog', {
906
- name: 'Edit CSV Data',
907
- });
908
- await expect(csvEditorDialog).toBeVisible({ timeout: 60000 });
924
+ await triggerReportFromCard(page, userReportCard);
925
+ //expect Report generation processing...
926
+ await expect(page.getByText('Report generation processing...')).toBeVisible({ timeout: 5000 });
927
+ // After the processing toast appears, the backend either finishes in time (CSV editor opens)
928
+ // or polling times out (deferred-notification toast appears). Treat both as valid outcomes.
929
+ await waitForReportOutcomeOrCSVDialogOpen(page);
930
+ }
931
+ async function waitForReportOutcomeOrCSVDialogOpen(page) {
932
+ const csvEditorDialog = page.getByRole('dialog', { name: 'Edit CSV Data' });
933
+ const deferredNotification = page.getByText(deferredNotificationText);
934
+ const downloadPromise = page.waitForEvent('download', { timeout: 60000 });
935
+ const outcome = await Promise.race([
936
+ csvEditorDialog
937
+ .waitFor({ state: 'visible', timeout: 60000 })
938
+ .then(() => 'csv-editor'),
939
+ deferredNotification
940
+ .waitFor({ state: 'visible', timeout: 60000 })
941
+ .then(() => 'deferred'),
942
+ downloadPromise.then(() => 'download'),
943
+ ]).catch(() => null);
944
+ if (outcome === 'deferred') {
945
+ logger.info('Report generation deferred — user will be notified when available');
946
+ return false;
947
+ }
948
+ if (outcome === 'download') {
949
+ const download = await downloadPromise;
950
+ const filename = download.suggestedFilename();
951
+ expect(filename).toBe('report.csv');
952
+ logger.info(`CSV file "${filename}" downloaded successfully`);
953
+ return false;
954
+ }
955
+ if (outcome !== 'csv-editor') {
956
+ throw new Error('Report generation did not complete: neither CSV editor nor deferred-notification toast appeared within 60s');
957
+ }
909
958
  const dialogTitle = csvEditorDialog.getByRole('heading', {
910
959
  name: 'Edit CSV Data',
911
960
  level: 2,
@@ -919,6 +968,7 @@ async function shouldOpenCSVEditorDialog(page) {
919
968
  await expect(csvEditorDialog.getByRole('button', { name: 'Save' })).toBeVisible();
920
969
  await expect(csvEditorDialog.getByRole('button', { name: 'Close' }).first()).toBeVisible();
921
970
  logger.info('CSV Editor dialog opened successfully');
971
+ return csvEditorDialog;
922
972
  }
923
973
  async function shouldDisplayCSVInEditableTableFormat(page) {
924
974
  const dataReportsTab = page.getByRole('tab', { name: 'Data Reports' });
@@ -931,14 +981,12 @@ async function shouldDisplayCSVInEditableTableFormat(page) {
931
981
  test$2.skip();
932
982
  return;
933
983
  }
934
- const downloadButton = userReportCard.getByRole('button', {
935
- name: 'Download report',
936
- });
937
- await downloadButton.click();
938
- const csvEditorDialog = page.getByRole('dialog', {
939
- name: 'Edit CSV Data',
940
- });
941
- await expect(csvEditorDialog).toBeVisible({ timeout: 60000 });
984
+ await triggerReportFromCard(page, userReportCard);
985
+ const csvEditorDialog = await waitForReportOutcomeOrCSVDialogOpen(page);
986
+ if (!csvEditorDialog) {
987
+ test$2.skip();
988
+ return;
989
+ }
942
990
  const table = csvEditorDialog.getByRole('table', {
943
991
  name: 'CSV data table',
944
992
  });
@@ -962,14 +1010,12 @@ async function shouldAllowEditingCellValuesInCSVEditor(page) {
962
1010
  test$2.skip();
963
1011
  return;
964
1012
  }
965
- const downloadButton = userReportCard.getByRole('button', {
966
- name: 'Download report',
967
- });
968
- await downloadButton.click();
969
- const csvEditorDialog = page.getByRole('dialog', {
970
- name: 'Edit CSV Data',
971
- });
972
- await expect(csvEditorDialog).toBeVisible({ timeout: 60000 });
1013
+ await triggerReportFromCard(page, userReportCard);
1014
+ const csvEditorDialog = await waitForReportOutcomeOrCSVDialogOpen(page);
1015
+ if (!csvEditorDialog) {
1016
+ test$2.skip();
1017
+ return;
1018
+ }
973
1019
  const table = csvEditorDialog.getByRole('table', {
974
1020
  name: 'CSV data table',
975
1021
  });
@@ -1002,14 +1048,12 @@ async function shouldAddNewRowWhenClickingAddRowButton(page) {
1002
1048
  test$2.skip();
1003
1049
  return;
1004
1050
  }
1005
- const downloadButton = userReportCard.getByRole('button', {
1006
- name: 'Download report',
1007
- });
1008
- await downloadButton.click();
1009
- const csvEditorDialog = page.getByRole('dialog', {
1010
- name: 'Edit CSV Data',
1011
- });
1012
- await expect(csvEditorDialog).toBeVisible({ timeout: 60000 });
1051
+ await triggerReportFromCard(page, userReportCard);
1052
+ const csvEditorDialog = await waitForReportOutcomeOrCSVDialogOpen(page);
1053
+ if (!csvEditorDialog) {
1054
+ test$2.skip();
1055
+ return;
1056
+ }
1013
1057
  const table = csvEditorDialog.locator('table');
1014
1058
  const initialRowCount = await table.locator('tbody tr').count();
1015
1059
  const addRowButton = csvEditorDialog.getByRole('button', {
@@ -1032,16 +1076,14 @@ async function shouldSaveEditedCSVAndTriggerDownload(page) {
1032
1076
  test$2.skip();
1033
1077
  return;
1034
1078
  }
1035
- const downloadButton = userReportCard.getByRole('button', {
1036
- name: 'Download report',
1037
- });
1038
- await downloadButton.click();
1079
+ await triggerReportFromCard(page, userReportCard);
1039
1080
  await page.waitForLoadState('networkidle');
1040
- const csvEditorDialog = page.getByRole('dialog', {
1041
- name: 'Edit CSV Data',
1042
- });
1043
- await expect(csvEditorDialog).toBeVisible({ timeout: 60000 });
1044
- const downloadPromise = page.waitForEvent('download', { timeout: 30000 });
1081
+ const csvEditorDialog = await waitForReportOutcomeOrCSVDialogOpen(page);
1082
+ if (!csvEditorDialog) {
1083
+ test$2.skip();
1084
+ return;
1085
+ }
1086
+ const downloadPromise = page.waitForEvent('download', { timeout: 40000 });
1045
1087
  const saveButton = csvEditorDialog.getByRole('button', { name: 'Save' });
1046
1088
  await saveButton.click();
1047
1089
  const download = await downloadPromise;
@@ -1061,15 +1103,13 @@ async function shouldCloseCSVEditorWithoutSavingWhenClickingCancel(page) {
1061
1103
  test$2.skip();
1062
1104
  return;
1063
1105
  }
1064
- const downloadButton = userReportCard.getByRole('button', {
1065
- name: 'Download report',
1066
- });
1067
- await downloadButton.click();
1106
+ await triggerReportFromCard(page, userReportCard);
1068
1107
  await page.waitForLoadState('networkidle');
1069
- const csvEditorDialog = page.getByRole('dialog', {
1070
- name: 'Edit CSV Data',
1071
- });
1072
- await expect(csvEditorDialog).toBeVisible({ timeout: 60000 });
1108
+ const csvEditorDialog = await waitForReportOutcomeOrCSVDialogOpen(page);
1109
+ if (!csvEditorDialog) {
1110
+ test$2.skip();
1111
+ return;
1112
+ }
1073
1113
  const cancelButton = csvEditorDialog.getByRole('button', {
1074
1114
  name: 'Cancel',
1075
1115
  });
@@ -1089,15 +1129,13 @@ async function shouldCloseCSVEditorWhenClickingCloseButton(page) {
1089
1129
  test$2.skip();
1090
1130
  return;
1091
1131
  }
1092
- const downloadButton = userReportCard.getByRole('button', {
1093
- name: 'Download report',
1094
- });
1095
- await downloadButton.click();
1132
+ await triggerReportFromCard(page, userReportCard);
1096
1133
  await page.waitForLoadState('networkidle');
1097
- const csvEditorDialog = page.getByRole('dialog', {
1098
- name: 'Edit CSV Data',
1099
- });
1100
- await expect(csvEditorDialog).toBeVisible({ timeout: 60000 });
1134
+ const csvEditorDialog = await waitForReportOutcomeOrCSVDialogOpen(page);
1135
+ if (!csvEditorDialog) {
1136
+ test$2.skip();
1137
+ return;
1138
+ }
1101
1139
  const closeButton = csvEditorDialog
1102
1140
  .getByRole('button', {
1103
1141
  name: 'Close',
@@ -1118,15 +1156,13 @@ async function shouldVerifyCSVEditorDialogAccessibility(page) {
1118
1156
  test$2.skip();
1119
1157
  return;
1120
1158
  }
1121
- const downloadButton = userReportCard.getByRole('button', {
1122
- name: 'Download report',
1123
- });
1124
- await downloadButton.click();
1159
+ await triggerReportFromCard(page, userReportCard);
1125
1160
  await page.waitForLoadState('networkidle');
1126
- const csvEditorDialog = page.getByRole('dialog', {
1127
- name: 'Edit CSV Data',
1128
- });
1129
- await expect(csvEditorDialog).toBeVisible({ timeout: 60000 });
1161
+ const csvEditorDialog = await waitForReportOutcomeOrCSVDialogOpen(page);
1162
+ if (!csvEditorDialog) {
1163
+ test$2.skip();
1164
+ return;
1165
+ }
1130
1166
  await expect(csvEditorDialog).toHaveAttribute('role', 'dialog');
1131
1167
  const csvTable = csvEditorDialog.getByRole('table', {
1132
1168
  name: 'CSV data table',
@@ -1161,15 +1197,12 @@ async function shouldOpenCSVEditorForUserMetadataReport(page) {
1161
1197
  test$2.skip();
1162
1198
  return;
1163
1199
  }
1164
- const downloadButton = userMetadataReportCard.getByRole('button', {
1165
- name: 'Download report',
1166
- });
1167
- await downloadButton.click();
1168
- await page.waitForLoadState('networkidle');
1169
- const csvEditorDialog = page.getByRole('dialog', {
1170
- name: 'Edit CSV Data',
1171
- });
1172
- await expect(csvEditorDialog).toBeVisible({ timeout: 60000 });
1200
+ await triggerReportFromCard(page, userMetadataReportCard);
1201
+ const csvEditorDialog = await waitForReportOutcomeOrCSVDialogOpen(page);
1202
+ if (!csvEditorDialog) {
1203
+ test$2.skip();
1204
+ return;
1205
+ }
1173
1206
  const table = csvEditorDialog.locator('table');
1174
1207
  const companyHeader = table.locator('thead th input[value="company"]');
1175
1208
  const hasCompanyColumn = await companyHeader.isVisible().catch(() => false);
@@ -1189,21 +1222,32 @@ async function shouldDirectlyDownloadChatHistoryReportWithoutCSVEditor(page) {
1189
1222
  test$2.skip();
1190
1223
  return;
1191
1224
  }
1192
- const downloadButton = chatHistoryCard.getByRole('button', {
1193
- name: 'Download report',
1194
- });
1195
- const downloadPromise = page.waitForEvent('download', { timeout: 120000 });
1196
- await expect(downloadButton).toBeVisible({ timeout: 30000 });
1197
- await downloadButton.click();
1198
- try {
1199
- const download = await downloadPromise;
1200
- const filename = download.suggestedFilename();
1225
+ // Prefer the Regenerate button (guaranteed to open the picker); fall back to Download.
1226
+ // Start listening for the download event before clicking since the auto-download fires
1227
+ // as soon as the picker is confirmed.
1228
+ const downloadPromise = page.waitForEvent('download', { timeout: 40000 });
1229
+ await triggerReportFromCard(page, chatHistoryCard);
1230
+ // Either the browser fires a download within 40s, or the backend defers and shows the
1231
+ // "You will be notified once the report is available." toast. Both are valid outcomes.
1232
+ const deferredNotification = page.getByText(deferredNotificationText);
1233
+ const outcome = await Promise.race([
1234
+ downloadPromise.then((download) => ({ kind: 'download', download })),
1235
+ deferredNotification
1236
+ .waitFor({ state: 'visible', timeout: 40000 })
1237
+ .then(() => ({ kind: 'deferred' })),
1238
+ ]).catch(() => null);
1239
+ if (!outcome) {
1240
+ throw new Error('Chat History generation did not complete: neither a download nor the deferred-notification toast appeared within 40s');
1241
+ }
1242
+ if (outcome.kind === 'download') {
1243
+ const filename = outcome.download.suggestedFilename();
1201
1244
  expect(filename.endsWith('.csv')).toBeTruthy();
1202
1245
  logger.info(`Chat History report downloaded: ${filename}`);
1203
1246
  }
1204
- catch (_a) {
1205
- logger.info('Chat History download may have completed without triggering download event');
1247
+ else {
1248
+ logger.info('Chat History generation deferred user will be notified when available');
1206
1249
  }
1250
+ // Regardless of outcome, the CSV editor dialog should never have opened for chat history.
1207
1251
  const csvEditorDialog = page.getByRole('dialog', {
1208
1252
  name: 'Edit CSV Data',
1209
1253
  });
@@ -1224,23 +1268,23 @@ async function shouldDisableOtherDownloadButtonsWhileGeneratingReport(page) {
1224
1268
  test$2.skip();
1225
1269
  return;
1226
1270
  }
1227
- await downloadButtons.first().click();
1271
+ // Trigger generation on the first report card (preferring Regenerate when present so the
1272
+ // date-range picker always shows, then confirming it).
1273
+ const firstCard = page.getByLabel(/ report card$/).first();
1274
+ await triggerReportFromCard(page, firstCard);
1275
+ //slight timeout to wait for other download buttons to be disabled
1228
1276
  await page.waitForTimeout(500);
1229
1277
  const secondButton = downloadButtons.nth(1);
1230
1278
  const isSecondDisabled = await secondButton.isDisabled();
1231
1279
  if (isSecondDisabled) {
1232
1280
  logger.info('Other download buttons correctly disabled during report generation');
1233
1281
  }
1234
- const csvEditorDialog = page.getByRole('dialog', {
1235
- name: 'Edit CSV Data',
1236
- });
1237
- const dialogVisible = await csvEditorDialog
1238
- .waitFor({ state: 'visible', timeout: 30000 })
1239
- .then(() => true)
1240
- .catch(() => false);
1241
- if (dialogVisible) {
1242
- await csvEditorDialog.getByRole('button', { name: 'Close' }).first().click();
1282
+ const csvEditorDialog = await waitForReportOutcomeOrCSVDialogOpen(page);
1283
+ if (!csvEditorDialog) {
1284
+ test$2.skip();
1285
+ return;
1243
1286
  }
1287
+ await csvEditorDialog.getByRole('button', { name: 'Close' }).first().click();
1244
1288
  }
1245
1289
  // ============================================
1246
1290
  // Combined Recommendation Reports Test Helpers
@@ -1260,12 +1304,9 @@ async function shouldShowCombiningReportsDialog(page) {
1260
1304
  test$2.skip();
1261
1305
  return;
1262
1306
  }
1263
- const downloadButton = combinedReportCard
1264
- .first()
1265
- .getByRole('button', { name: 'Download report' });
1266
- await downloadButton.click();
1307
+ await triggerReportFromCard(page, combinedReportCard.first());
1267
1308
  const combiningDialog = page.locator('[data-testid="combining-reports-dialog"]');
1268
- await expect(combiningDialog).toBeVisible({ timeout: 30000 });
1309
+ await expect(combiningDialog).toBeVisible({ timeout: 40000 });
1269
1310
  await expect(combiningDialog.getByText('Combining Reports')).toBeVisible();
1270
1311
  await expect(combiningDialog.getByText('Loading & combining recommendations data')).toBeVisible();
1271
1312
  logger.info('Combining reports dialog displayed successfully');
@@ -1285,12 +1326,9 @@ async function shouldCancelCombiningReports(page) {
1285
1326
  test$2.skip();
1286
1327
  return;
1287
1328
  }
1288
- const downloadButton = combinedReportCard
1289
- .first()
1290
- .getByRole('button', { name: 'Download report' });
1291
- await downloadButton.click();
1329
+ await triggerReportFromCard(page, combinedReportCard.first());
1292
1330
  const combiningDialog = page.locator('[data-testid="combining-reports-dialog"]');
1293
- await expect(combiningDialog).toBeVisible({ timeout: 30000 });
1331
+ await expect(combiningDialog).toBeVisible({ timeout: 40000 });
1294
1332
  const cancelButton = page.locator('[data-testid="cancel-combining-button"]');
1295
1333
  await expect(cancelButton).toBeVisible();
1296
1334
  await cancelButton.click();
@@ -1331,10 +1369,7 @@ async function shouldCombineRecommendationReports(page) {
1331
1369
  test$2.skip();
1332
1370
  return;
1333
1371
  }
1334
- const downloadButton = combinedReportCard
1335
- .first()
1336
- .getByRole('button', { name: 'Download report' });
1337
- await downloadButton.click();
1372
+ await triggerReportFromCard(page, combinedReportCard.first());
1338
1373
  const combiningDialog = page.locator('[data-testid="combining-reports-dialog"]');
1339
1374
  const dialogOrEditor = await Promise.race([
1340
1375
  combiningDialog.waitFor({ state: 'visible', timeout: 10000 }).then(() => 'dialog'),
@@ -1674,6 +1709,371 @@ async function verifyMemoryNotExists(page, content) {
1674
1709
  logger.info(`Verified memory removed: "${content}"`);
1675
1710
  }
1676
1711
 
1712
+ // ============================
1713
+ // Navigation Helpers
1714
+ // ============================
1715
+ /**
1716
+ * Navigate to the Audit tab within the Analytics section.
1717
+ * Assumes the user is already on the analytics page (any tab).
1718
+ */
1719
+ async function navigateToAuditLog(page) {
1720
+ const auditLogTab = page.getByRole('tab', { name: 'Audit' });
1721
+ await expect(auditLogTab).toBeVisible({ timeout: 60000 });
1722
+ await auditLogTab.click();
1723
+ await waitForPageReady(page);
1724
+ await safeWaitForURL(page, /\/analytics\/audit-log$/);
1725
+ logger.info('Successfully navigated to Audit page');
1726
+ }
1727
+ // ============================
1728
+ // Table Verification Helpers
1729
+ // ============================
1730
+ /**
1731
+ * Verify the audit log table is visible with correct headers.
1732
+ */
1733
+ async function verifyAuditLogTableVisible(page) {
1734
+ const table = page.locator('table');
1735
+ await expect(table).toBeVisible({ timeout: 30000 });
1736
+ await expect(table.locator('th').filter({ hasText: 'Person' })).toBeVisible();
1737
+ await expect(table.locator('th').filter({ hasText: 'Action' })).toBeVisible();
1738
+ await expect(table.locator('th').filter({ hasText: 'Timestamp' })).toBeVisible();
1739
+ logger.info('Audit log table is visible with correct headers');
1740
+ }
1741
+ /**
1742
+ * Get the number of rows currently displayed in the audit log table.
1743
+ * Returns 0 if no table body rows are found.
1744
+ */
1745
+ async function getAuditLogRowCount(page) {
1746
+ const rows = page.locator('table tbody tr');
1747
+ const count = await rows.count();
1748
+ logger.info(`Audit log table has ${count} rows`);
1749
+ return count;
1750
+ }
1751
+ /**
1752
+ * Verify that audit log entries contain expected data structure
1753
+ * (person name in first cell, action description in second, timestamp in third).
1754
+ */
1755
+ async function verifyAuditLogEntryStructure(page) {
1756
+ const firstRow = page.locator('table tbody tr').first();
1757
+ await expect(firstRow).toBeVisible({ timeout: 30000 });
1758
+ const cells = firstRow.locator('td');
1759
+ const cellCount = await cells.count();
1760
+ expect(cellCount).toBe(3);
1761
+ // Person cell should have text content
1762
+ const personText = await cells.nth(0).textContent();
1763
+ expect(personText === null || personText === void 0 ? void 0 : personText.trim().length).toBeGreaterThan(0);
1764
+ // Action cell should have text content
1765
+ const actionText = await cells.nth(1).textContent();
1766
+ expect(actionText === null || actionText === void 0 ? void 0 : actionText.trim().length).toBeGreaterThan(0);
1767
+ // Timestamp cell should have text content
1768
+ const timestampText = await cells.nth(2).textContent();
1769
+ expect(timestampText === null || timestampText === void 0 ? void 0 : timestampText.trim().length).toBeGreaterThan(0);
1770
+ logger.info(`Audit log entry: Person="${personText === null || personText === void 0 ? void 0 : personText.trim()}", Action="${actionText === null || actionText === void 0 ? void 0 : actionText.trim()}", Timestamp="${timestampText === null || timestampText === void 0 ? void 0 : timestampText.trim()}"`);
1771
+ }
1772
+ // ============================
1773
+ // Empty & Error State Helpers
1774
+ // ============================
1775
+ /**
1776
+ * Verify the empty state is displayed when there are no audit log entries.
1777
+ */
1778
+ async function verifyAuditLogEmptyState(page) {
1779
+ const emptyMessage = page.getByText('No audit log entries found for this tenant.');
1780
+ await expect(emptyMessage).toBeVisible({ timeout: 30000 });
1781
+ logger.info('Audit log empty state is displayed');
1782
+ }
1783
+ /**
1784
+ * Verify the 403 permission error state is displayed.
1785
+ */
1786
+ async function verifyAuditLogPermissionError(page) {
1787
+ const errorMessage = page.getByText('You do not have permission to view audit logs.');
1788
+ await expect(errorMessage).toBeVisible({ timeout: 30000 });
1789
+ logger.info('Audit log permission error is displayed');
1790
+ }
1791
+ /**
1792
+ * Verify the generic error state is displayed.
1793
+ */
1794
+ async function verifyAuditLogGenericError(page) {
1795
+ const errorMessage = page.getByText('An error occurred while loading audit logs.');
1796
+ await expect(errorMessage).toBeVisible({ timeout: 30000 });
1797
+ logger.info('Audit log generic error is displayed');
1798
+ }
1799
+ /**
1800
+ * Verify the loading spinner is displayed while audit logs are being fetched.
1801
+ */
1802
+ async function verifyAuditLogLoading(page) {
1803
+ const spinner = page.locator('.animate-spin');
1804
+ await expect(spinner).toBeVisible({ timeout: 10000 });
1805
+ logger.info('Audit log loading spinner is displayed');
1806
+ }
1807
+ /**
1808
+ * Wait for the audit log data to finish loading.
1809
+ * Waits for either table data, empty state, or error state to appear.
1810
+ */
1811
+ async function waitForAuditLogDataLoaded(page, timeout = 60000) {
1812
+ await page
1813
+ .locator('table tbody tr, [class*="empty"], [class*="error"]')
1814
+ .first()
1815
+ .waitFor({ state: 'visible', timeout })
1816
+ .catch(() => {
1817
+ // Also check for known text states
1818
+ });
1819
+ // Wait for spinner to disappear
1820
+ const spinner = page.locator('.animate-spin');
1821
+ const isSpinnerVisible = await spinner.isVisible().catch(() => false);
1822
+ if (isSpinnerVisible) {
1823
+ await expect(spinner).not.toBeVisible({ timeout });
1824
+ }
1825
+ logger.info('Audit log data has finished loading');
1826
+ }
1827
+ // ============================
1828
+ // Filter Interaction Helpers
1829
+ // ============================
1830
+ /**
1831
+ * Filter audit log entries by action type.
1832
+ * @param action - One of 'All Actions', 'Create', 'Update', or 'Delete'
1833
+ */
1834
+ async function filterByAction(page, action) {
1835
+ const selectTrigger = page.locator('[role="combobox"][dir="ltr"]');
1836
+ await expect(selectTrigger).toBeVisible({ timeout: 10000 });
1837
+ await selectTrigger.click();
1838
+ const option = page.getByRole('option', { name: action });
1839
+ await expect(option).toBeVisible({ timeout: 5000 });
1840
+ await option.click();
1841
+ await waitForAuditLogDataLoaded(page);
1842
+ logger.info(`Filtered audit log by action: ${action}`);
1843
+ }
1844
+ /**
1845
+ * Filter audit log entries by actor/person using the search combobox.
1846
+ * @param actorName - The username to filter by, or empty string to clear the filter
1847
+ */
1848
+ async function filterByActor(page, actorName) {
1849
+ const actorButton = page.getByLabel('Search for User');
1850
+ await expect(actorButton).toBeVisible({ timeout: 10000 });
1851
+ await actorButton.click();
1852
+ // Wait for the command popover to open
1853
+ const searchInput = page.getByPlaceholder('Search user...');
1854
+ await expect(searchInput).toBeVisible({ timeout: 5000 });
1855
+ if (actorName) {
1856
+ await searchInput.fill(actorName);
1857
+ // Select the matching actor from the dropdown
1858
+ const actorItem = page.locator('[cmdk-item]').filter({ hasText: actorName });
1859
+ await expect(actorItem).toBeVisible({ timeout: 5000 });
1860
+ await actorItem.click();
1861
+ }
1862
+ else {
1863
+ // Select "All Users" to clear the filter
1864
+ const allPeopleItem = page.locator('[cmdk-item]').filter({ hasText: 'All Users' });
1865
+ await expect(allPeopleItem).toBeVisible({ timeout: 5000 });
1866
+ await allPeopleItem.click();
1867
+ }
1868
+ await waitForAuditLogDataLoaded(page);
1869
+ logger.info(`Filtered audit log by actor: ${actorName || 'All Users'}`);
1870
+ }
1871
+ /**
1872
+ * Get the list of actors available in the actor filter dropdown.
1873
+ * Opens the dropdown, reads the options, then closes it.
1874
+ */
1875
+ async function getAvailableActors(page) {
1876
+ const actorButton = page.getByLabel('Search for User');
1877
+ await expect(actorButton).toBeVisible({ timeout: 10000 });
1878
+ await actorButton.click();
1879
+ // Wait for the command popover
1880
+ await expect(page.getByPlaceholder('Search user...')).toBeVisible({ timeout: 5000 });
1881
+ const actorItems = page.locator('[cmdk-item] .font-medium.text-gray-700');
1882
+ const count = await actorItems.count();
1883
+ const actors = [];
1884
+ for (let i = 0; i < count; i++) {
1885
+ const text = await actorItems.nth(i).textContent();
1886
+ if (text)
1887
+ actors.push(text.trim());
1888
+ }
1889
+ // Close the dropdown by pressing Escape
1890
+ await page.keyboard.press('Escape');
1891
+ logger.info(`Available actors: ${actors.join(', ')}`);
1892
+ return actors;
1893
+ }
1894
+ /**
1895
+ * Open the date range picker and select a date range.
1896
+ * @param fromDate - Start date to select
1897
+ * @param toDate - End date to select
1898
+ */
1899
+ async function filterByDateRange(page, fromDate, toDate) {
1900
+ const dateButton = page.getByRole('button', {
1901
+ name: /Pick a Date Range|Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec/,
1902
+ });
1903
+ await expect(dateButton).toBeVisible({ timeout: 10000 });
1904
+ await dateButton.click();
1905
+ // Wait for calendar to appear
1906
+ const calendar = page.locator('[role="grid"]').first();
1907
+ await expect(calendar).toBeVisible({ timeout: 5000 });
1908
+ // Select the from date
1909
+ const fromDay = String(fromDate.getDate());
1910
+ const fromButton = calendar.getByRole('gridcell', { name: fromDay, exact: true }).first();
1911
+ await fromButton.click();
1912
+ // Select the to date
1913
+ const toDay = String(toDate.getDate());
1914
+ const toButton = calendar.getByRole('gridcell', { name: toDay, exact: true }).first();
1915
+ await toButton.click();
1916
+ // Close by clicking outside
1917
+ await page.locator('body').click({ position: { x: 0, y: 0 } });
1918
+ await waitForAuditLogDataLoaded(page);
1919
+ logger.info(`Filtered audit log by date range: ${fromDate.toISOString().split('T')[0]} to ${toDate.toISOString().split('T')[0]}`);
1920
+ }
1921
+ /**
1922
+ * Clear the date range filter by clicking the date picker and resetting.
1923
+ */
1924
+ async function clearDateRangeFilter(page) {
1925
+ const dateButton = page.getByRole('button', { name: /Pick a Date Range/ });
1926
+ // If button shows "Pick a Date Range", filter is already cleared
1927
+ const isCleared = await dateButton.isVisible().catch(() => false);
1928
+ if (isCleared) {
1929
+ logger.info('Date range filter is already cleared');
1930
+ return;
1931
+ }
1932
+ logger.info('Date range filter cleared');
1933
+ }
1934
+ // ============================
1935
+ // Pagination Helpers
1936
+ // ============================
1937
+ /**
1938
+ * Get the current pagination info text (e.g., "Showing 1 to 20 of 45 results").
1939
+ * Returns null if pagination is not visible.
1940
+ */
1941
+ async function getPaginationInfo(page) {
1942
+ var _a;
1943
+ const paginationText = page.locator('.text-sm.text-gray-700');
1944
+ const isVisible = await paginationText.isVisible().catch(() => false);
1945
+ if (!isVisible) {
1946
+ logger.info('Pagination info is not visible (likely single page)');
1947
+ return null;
1948
+ }
1949
+ const text = await paginationText.textContent();
1950
+ logger.info(`Pagination info: ${text === null || text === void 0 ? void 0 : text.trim()}`);
1951
+ return (_a = text === null || text === void 0 ? void 0 : text.trim()) !== null && _a !== void 0 ? _a : null;
1952
+ }
1953
+ /**
1954
+ * Navigate to the next page of audit log results.
1955
+ */
1956
+ async function goToNextPage(page) {
1957
+ const nextLink = page.getByRole('link', { name: 'Go to next page' });
1958
+ await expect(nextLink).toBeVisible({ timeout: 5000 });
1959
+ await nextLink.click();
1960
+ await waitForAuditLogDataLoaded(page);
1961
+ logger.info('Navigated to next page');
1962
+ }
1963
+ /**
1964
+ * Navigate to the previous page of audit log results.
1965
+ */
1966
+ async function goToPreviousPage(page) {
1967
+ const prevLink = page.getByRole('link', { name: 'Go to previous page' });
1968
+ await expect(prevLink).toBeVisible({ timeout: 5000 });
1969
+ await prevLink.click();
1970
+ await waitForAuditLogDataLoaded(page);
1971
+ logger.info('Navigated to previous page');
1972
+ }
1973
+ /**
1974
+ * Navigate to the first page of audit log results.
1975
+ */
1976
+ async function goToFirstPage(page) {
1977
+ await goToPage(page, 1);
1978
+ logger.info('Navigated to first page');
1979
+ }
1980
+ /**
1981
+ * Navigate to the last page of audit log results.
1982
+ * Finds the highest numbered page link in the pagination.
1983
+ */
1984
+ async function goToLastPage(page) {
1985
+ const paginationNav = page.getByRole('navigation', { name: 'pagination' });
1986
+ await expect(paginationNav).toBeVisible({ timeout: 5000 });
1987
+ const pageLinks = paginationNav.getByRole('link');
1988
+ const count = await pageLinks.count();
1989
+ // The last numeric link before "Next" is the last page
1990
+ let lastPageNum = 1;
1991
+ for (let i = 0; i < count; i++) {
1992
+ const text = await pageLinks.nth(i).textContent();
1993
+ const num = Number(text === null || text === void 0 ? void 0 : text.trim());
1994
+ if (!isNaN(num) && num > lastPageNum) {
1995
+ lastPageNum = num;
1996
+ }
1997
+ }
1998
+ await goToPage(page, lastPageNum);
1999
+ logger.info(`Navigated to last page (${lastPageNum})`);
2000
+ }
2001
+ /**
2002
+ * Navigate to a specific page number by clicking its page button.
2003
+ */
2004
+ async function goToPage(page, pageNumber) {
2005
+ const pageLink = page
2006
+ .getByRole('navigation', { name: 'pagination' })
2007
+ .getByRole('link', { name: String(pageNumber), exact: true });
2008
+ await expect(pageLink).toBeVisible({ timeout: 5000 });
2009
+ await pageLink.click();
2010
+ await waitForAuditLogDataLoaded(page);
2011
+ logger.info(`Navigated to page ${pageNumber}`);
2012
+ }
2013
+ /**
2014
+ * Verify the current page is highlighted/active in pagination.
2015
+ */
2016
+ async function verifyCurrentPage(page, expectedPage) {
2017
+ const pageLink = page
2018
+ .getByRole('navigation', { name: 'pagination' })
2019
+ .getByRole('link', { name: String(expectedPage), exact: true });
2020
+ await expect(pageLink).toBeVisible({ timeout: 5000 });
2021
+ await expect(pageLink).toHaveClass(/bg-blue-500/);
2022
+ logger.info(`Verified current page is ${expectedPage}`);
2023
+ }
2024
+ /**
2025
+ * Check if the next page button is disabled (i.e., we're on the last page).
2026
+ */
2027
+ async function isOnLastPage(page) {
2028
+ const nextLink = page.getByRole('link', { name: 'Go to next page' });
2029
+ const hasDisabledClass = await nextLink
2030
+ .evaluate((el) => el.classList.contains('pointer-events-none'))
2031
+ .catch(() => true);
2032
+ return hasDisabledClass;
2033
+ }
2034
+ /**
2035
+ * Check if the previous page button is disabled (i.e., we're on the first page).
2036
+ */
2037
+ async function isOnFirstPage(page) {
2038
+ const prevLink = page.getByRole('link', { name: 'Go to previous page' });
2039
+ const hasDisabledClass = await prevLink
2040
+ .evaluate((el) => el.classList.contains('pointer-events-none'))
2041
+ .catch(() => true);
2042
+ return hasDisabledClass;
2043
+ }
2044
+ // ============================
2045
+ // Combined Workflow Helpers
2046
+ // ============================
2047
+ /**
2048
+ * Full workflow: Navigate to audit log tab, wait for data to load,
2049
+ * and verify the table is visible. Use this as a test setup helper.
2050
+ */
2051
+ async function navigateToAuditLogAndWaitForData(page) {
2052
+ await navigateToAuditLog(page);
2053
+ await waitForAuditLogDataLoaded(page);
2054
+ logger.info('Audit log page ready with data loaded');
2055
+ }
2056
+ /**
2057
+ * Verify audit log entries are displayed after applying action filter.
2058
+ * Returns the row count after filtering.
2059
+ */
2060
+ async function filterByActionAndVerify(page, action) {
2061
+ await filterByAction(page, action);
2062
+ const count = await getAuditLogRowCount(page);
2063
+ logger.info(`After filtering by "${action}": ${count} entries found`);
2064
+ return count;
2065
+ }
2066
+ /**
2067
+ * Verify audit log entries are displayed after applying actor filter.
2068
+ * Returns the row count after filtering.
2069
+ */
2070
+ async function filterByActorAndVerify(page, actorName) {
2071
+ await filterByActor(page, actorName);
2072
+ const count = await getAuditLogRowCount(page);
2073
+ logger.info(`After filtering by actor "${actorName}": ${count} entries found`);
2074
+ return count;
2075
+ }
2076
+
1677
2077
  /** Extract browser key from device name (e.g., 'Desktop Chrome' -> 'chrome') */
1678
2078
  function getBrowserKey(deviceName) {
1679
2079
  return deviceName.toLowerCase().replace(/^desktop\s+/, '');
@@ -1799,5 +2199,5 @@ function createPlaywrightConfig(options) {
1799
2199
  });
1800
2200
  }
1801
2201
 
1802
- export { AuthFlowBuilder, CustomReporter, MailsacClient, addMemory, archiveFirstMemory, archiveMemoryByContent, buildReportUrl, canChatWithEmbedMentor, checkAdminStatus, clickBackHome, clickDownloadAgain, clickManualDownloadLink, closeWithEsc, createAuthSetup, createEnvConfig, createPlaywrightConfig, deleteFirstMemory, deleteMemoryByContent, expectNoAccessibilityViolations, expectNoAccessibilityViolationsOnDialogs, generateBrowserSetupProjects, generateProjectConfig, getBrowserKey, getMemoryCount, getMentorIdFromUrl, inviteUserTest, isFirefox, isJSON, isMemoryTabVisible, logger, loginWithEmailAndPassword, loginWithMicrosoftIdp, navigateToAccountComponent, navigateToDataReports, navigateToReportDownload, openAddMemoryDialog, parseReportUrlParams, reliableClick, reliableFill, retry, safeWaitForURL, selectDateFromCalendar, shouldAddNewRowWhenClickingAddRowButton, shouldAllowEditingCellValuesInCSVEditor, shouldCancelCombiningReports, shouldCloseCSVEditorWhenClickingCloseButton, shouldCloseCSVEditorWithoutSavingWhenClickingCancel, shouldCombineRecommendationReports, shouldDirectlyDownloadChatHistoryReportWithoutCSVEditor, shouldDisableOtherDownloadButtonsWhileGeneratingReport, shouldDisplayCSVInEditableTableFormat, shouldDisplayReportCards, shouldHaveCombinedReportDataTestIds, shouldOpenCSVEditorDialog, shouldOpenCSVEditorForUserMetadataReport, shouldSaveEditedCSVAndTriggerDownload, shouldShowCombiningReportsDialog, shouldVerifyCSVEditorDialogAccessibility, signUpWithEmailAndPassword, switchToMemoryTab, test, toggleMemorySwitch, verifyDonePhase, verifyDownloadingPhase, verifyErrorPhase, verifyMemoryExists, verifyMemoryNotExists, verifyMemoryTabMemoriesList, verifyMemoryTabSettings, verifyPreparingPhase, waitForDialogReady, waitForElementStable, waitForPageLoad, waitForPageReady, waitForReportDownload };
2202
+ export { AuthFlowBuilder, CustomReporter, MailsacClient, addMemory, archiveFirstMemory, archiveMemoryByContent, buildReportUrl, canChatWithEmbedMentor, checkAdminStatus, clearDateRangeFilter, clickBackHome, clickDownloadAgain, clickManualDownloadLink, closeWithEsc, createAuthSetup, createEnvConfig, createPlaywrightConfig, deleteFirstMemory, deleteMemoryByContent, expectNoAccessibilityViolations, expectNoAccessibilityViolationsOnDialogs, filterByAction, filterByActionAndVerify, filterByActor, filterByActorAndVerify, filterByDateRange, generateBrowserSetupProjects, generateProjectConfig, getAuditLogRowCount, getAvailableActors, getBrowserKey, getMemoryCount, getMentorIdFromUrl, getPaginationInfo, goToFirstPage, goToLastPage, goToNextPage, goToPage, goToPreviousPage, inviteUserTest, isFirefox, isJSON, isMemoryTabVisible, isOnFirstPage, isOnLastPage, logger, loginWithEmailAndPassword, loginWithMicrosoftIdp, navigateToAccountComponent, navigateToAuditLog, navigateToAuditLogAndWaitForData, navigateToDataReports, navigateToReportDownload, openAddMemoryDialog, parseReportUrlParams, reliableClick, reliableFill, retry, safeWaitForURL, selectDateFromCalendar, shouldAddNewRowWhenClickingAddRowButton, shouldAllowEditingCellValuesInCSVEditor, shouldCancelCombiningReports, shouldCloseCSVEditorWhenClickingCloseButton, shouldCloseCSVEditorWithoutSavingWhenClickingCancel, shouldCombineRecommendationReports, shouldDirectlyDownloadChatHistoryReportWithoutCSVEditor, shouldDisableOtherDownloadButtonsWhileGeneratingReport, shouldDisplayCSVInEditableTableFormat, shouldDisplayReportCards, shouldHaveCombinedReportDataTestIds, shouldOpenCSVEditorDialog, shouldOpenCSVEditorForUserMetadataReport, shouldSaveEditedCSVAndTriggerDownload, shouldShowCombiningReportsDialog, shouldVerifyCSVEditorDialogAccessibility, signUpWithEmailAndPassword, switchToMemoryTab, test, toggleMemorySwitch, verifyAuditLogEmptyState, verifyAuditLogEntryStructure, verifyAuditLogGenericError, verifyAuditLogLoading, verifyAuditLogPermissionError, verifyAuditLogTableVisible, verifyCurrentPage, verifyDonePhase, verifyDownloadingPhase, verifyErrorPhase, verifyMemoryExists, verifyMemoryNotExists, verifyMemoryTabMemoriesList, verifyMemoryTabSettings, verifyPreparingPhase, waitForAuditLogDataLoaded, waitForDialogReady, waitForElementStable, waitForPageLoad, waitForPageReady, waitForReportDownload };
1803
2203
  //# sourceMappingURL=index.esm.js.map