@iblai/iblai-js 2.5.8 → 2.5.10

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.
@@ -1613,7 +1613,10 @@ async function openAddMemoryDialog(page) {
1613
1613
  const addMemoryButton = page.getByRole('button', { name: /Add Memory/i });
1614
1614
  await test$1.expect(addMemoryButton).toBeVisible({ timeout: 10000 });
1615
1615
  await addMemoryButton.click();
1616
- const dialog = page.getByRole('dialog').filter({ hasText: 'Add Memory' });
1616
+ // Accessible-name match (Radix wires DialogTitle aria-labelledby), NOT a
1617
+ // `hasText` filter: the profile modal is itself a dialog containing the
1618
+ // "Add Memory" *button* text, so a text filter matches both dialogs.
1619
+ const dialog = page.getByRole('dialog', { name: 'Add Memory', exact: true });
1617
1620
  await test$1.expect(dialog).toBeVisible({ timeout: 5000 });
1618
1621
  return dialog;
1619
1622
  }
@@ -1665,7 +1668,7 @@ async function deleteMemoryRow(page, memoryRow) {
1665
1668
  await test$1.expect(deleteItem).toBeVisible({ timeout: 5000 });
1666
1669
  await deleteItem.click();
1667
1670
  // Deleting asks for confirmation; the dialog stacks on top of everything.
1668
- const confirmDialog = page.getByRole('dialog').filter({ hasText: 'Delete Memory' }).last();
1671
+ const confirmDialog = page.getByRole('dialog', { name: 'Delete Memory', exact: true }).last();
1669
1672
  await test$1.expect(confirmDialog).toBeVisible({ timeout: 5000 });
1670
1673
  await confirmDialog.getByRole('button', { name: 'Delete', exact: true }).click();
1671
1674
  await test$1.expect(confirmDialog).toBeHidden({ timeout: 15000 });
@@ -1905,13 +1908,25 @@ async function closeUserMemoriesPopup(page) {
1905
1908
  await test$1.expect(popup).toBeHidden({ timeout: UI_TIMEOUT$2 });
1906
1909
  }
1907
1910
  /**
1908
- * The innermost open dialog whose text matches `title`. Stacked Radix
1909
- * dialogs portal to the end of `<body>` in mount order, so `.last()` is the
1910
- * one on top — required here because the add/edit/delete dialogs open above
1911
- * the popup, which itself sits above the tenant settings dialog.
1911
+ * The innermost open dialog whose ACCESSIBLE NAME matches `title`. Stacked
1912
+ * Radix dialogs portal to the end of `<body>` in mount order, so `.last()`
1913
+ * is the one on top — required here because the add/edit/delete dialogs open
1914
+ * above the popup, which itself sits above the tenant settings dialog.
1915
+ *
1916
+ * Matches on the dialog's accessible name, NOT a `hasText` filter: the user
1917
+ * and agent memories popups both contain an "Add Memory" *button*, so a
1918
+ * `hasText` filter also matches the popup itself. That made the post-save
1919
+ * `toBeHidden` gate unsatisfiable — while the add dialog was open `.last()`
1920
+ * resolved to it, but the instant it unmounted the same locator re-resolved
1921
+ * to the still-open popup underneath and "waited" on the wrong dialog for
1922
+ * the full timeout. Radix `DialogContent` wires `aria-labelledby` to its
1923
+ * `DialogTitle`, so each stacked dialog's accessible name is exactly its
1924
+ * title ("Add Memory" / "Edit Memory" / "Delete Memory"), while the popups'
1925
+ * names ("Global Memories — …") never equal one — unambiguous, and the
1926
+ * locator resolves to nothing (= hidden) once the dialog closes.
1912
1927
  */
1913
1928
  function topDialogByTitle(page, title) {
1914
- return page.getByRole('dialog').filter({ hasText: title }).last();
1929
+ return page.getByRole('dialog', { name: title, exact: true }).last();
1915
1930
  }
1916
1931
  /** Open a memory row's three-dots menu and click one of its actions. */
1917
1932
  async function clickMemoryRowAction(page, row, action) {
@@ -2002,20 +2017,47 @@ async function toggleUserMemoryAdminSetting(page, popup, setting) {
2002
2017
  /**
2003
2018
  * Filter the agents table to one agent via the autocomplete: types the name,
2004
2019
  * clicks the matching option, and gates on the selected chip rendering.
2020
+ *
2021
+ * Retries with progressively shorter PREFIXES of the name. The options come
2022
+ * from a debounced server-side mentors search whose response RTK Query
2023
+ * caches per search term — so when a just-created mentor hasn't reached the
2024
+ * search backend yet, the first (empty) response keeps being served from
2025
+ * cache for as long as the typed term stays the same, and waiting on the
2026
+ * DOM alone can never recover. Each shorter prefix is a DISTINCT term that
2027
+ * forces a fresh fetch, and still matches the mentor server-side.
2005
2028
  */
2006
2029
  async function filterAgentMemories(page, agentName) {
2007
2030
  const section = memoryAdminAgentSection(page);
2008
2031
  const input = section.getByTestId('agent-memories-filter-input');
2009
2032
  await test$1.expect(input).toBeVisible({ timeout: UI_TIMEOUT$2 });
2010
- await input.fill(agentName);
2011
2033
  const option = section
2012
2034
  .getByTestId('agent-memories-filter-results')
2013
2035
  .getByRole('button', { name: agentName, exact: true });
2014
- await test$1.expect(option).toBeVisible({ timeout: MUTATION_TIMEOUT$2 });
2036
+ const maxAttempts = 6;
2037
+ for (let attempt = 0;; attempt++) {
2038
+ // Attempt 0 types the full name; each retry trims one more trailing
2039
+ // character (never below the autocomplete's 2-char minimum).
2040
+ const term = agentName.slice(0, Math.max(2, agentName.length - attempt));
2041
+ await input.fill(term);
2042
+ try {
2043
+ await test$1.expect(option).toBeVisible({ timeout: MUTATION_TIMEOUT$2 });
2044
+ break;
2045
+ }
2046
+ catch (error) {
2047
+ if (attempt >= maxAttempts - 1)
2048
+ throw error;
2049
+ }
2050
+ }
2015
2051
  await option.click();
2052
+ // Selecting must both render the selected chip AND close the results
2053
+ // dropdown — the open panel overlays the agents table (absolute, z-50),
2054
+ // so a caller's next row interaction would hit the dropdown instead.
2016
2055
  await test$1.expect(section.getByTestId('agent-memories-filter-selected')).toBeVisible({
2017
2056
  timeout: UI_TIMEOUT$2,
2018
2057
  });
2058
+ await test$1.expect(section.getByTestId('agent-memories-filter-results')).toBeHidden({
2059
+ timeout: UI_TIMEOUT$2,
2060
+ });
2019
2061
  logger.info(`Filtered agent memories to "${agentName}"`);
2020
2062
  }
2021
2063
  /** Clear the agents autocomplete filter (back to the full agents list). */
@@ -2063,13 +2105,27 @@ async function addAgentMemoryFromPopup(page, popup, content, categoryName) {
2063
2105
  await addButton.click();
2064
2106
  const dialog = topDialogByTitle(page, MEMORY_ADMIN_LABELS.dialogs.add);
2065
2107
  await test$1.expect(dialog).toBeVisible({ timeout: UI_TIMEOUT$2 });
2108
+ // ALWAYS pick a category — the named one when given, else the first
2109
+ // available option. The dialog's Save enables without one, but the SDK
2110
+ // then falls back to the list's current filter ('All' → the hardcoded
2111
+ // 'general' slug) and the backend 404s with "Category not found or
2112
+ // inactive" for any mentor without a 'general' category (the default
2113
+ // category set has none) — leaving the dialog open with only an error
2114
+ // toast to show for it.
2115
+ await dialog.getByRole('combobox').click();
2116
+ // Radix Select portals its listbox to <body>; one is open at a time.
2117
+ const options = page.getByRole('option');
2118
+ await test$1.expect(options.first()).toBeVisible({ timeout: UI_TIMEOUT$2 });
2066
2119
  if (categoryName) {
2067
- await dialog.getByRole('combobox').click();
2068
- // Radix Select portals its listbox to <body>; one is open at a time.
2069
2120
  const option = page.getByRole('option', { name: categoryName, exact: true });
2070
2121
  await test$1.expect(option).toBeVisible({ timeout: UI_TIMEOUT$2 });
2071
2122
  await option.click();
2072
2123
  }
2124
+ else {
2125
+ await options.first().click();
2126
+ }
2127
+ // Picking closes the listbox; confirm before touching anything below it.
2128
+ await test$1.expect(options).toHaveCount(0, { timeout: 5000 });
2073
2129
  await dialog.getByRole('textbox').fill(content);
2074
2130
  const saveButton = dialog.getByRole('button', { name: 'Save', exact: true });
2075
2131
  await test$1.expect(saveButton).toBeEnabled({ timeout: UI_TIMEOUT$2 });
@@ -3803,7 +3859,7 @@ const PRIVACY_LABELS = {
3803
3859
  * reach DOM that Radix renders outside the dialog subtree (popovers,
3804
3860
  * select options, etc.).
3805
3861
  */
3806
- function asPage$7(scope) {
3862
+ function asPage$8(scope) {
3807
3863
  return 'page' in scope ? scope.page() : scope;
3808
3864
  }
3809
3865
  /**
@@ -3864,7 +3920,7 @@ async function selectPrivacyAction(scope, action) {
3864
3920
  await trigger.click();
3865
3921
  // Radix Select renders options in a portal at the document root, so we
3866
3922
  // always look them up on the Page — never on the dialog Locator.
3867
- const option = asPage$7(scope).getByRole('option', {
3923
+ const option = asPage$8(scope).getByRole('option', {
3868
3924
  name: PRIVACY_LABELS.actionOptions[action],
3869
3925
  });
3870
3926
  await test$1.expect(option).toBeVisible({ timeout: 5000 });
@@ -3976,7 +4032,7 @@ const CHAT_PRIVACY_LABELS = {
3976
4032
  rowLabel: 'Allow users to control chat privacy',
3977
4033
  },
3978
4034
  };
3979
- function asPage$6(scope) {
4035
+ function asPage$7(scope) {
3980
4036
  return 'page' in scope ? scope.page() : scope;
3981
4037
  }
3982
4038
  // ──────────────────────────────────────────────────────────────────────
@@ -4048,7 +4104,7 @@ async function clickChatPrivacyToggle(scope) {
4048
4104
  // ── Mid-session confirm dialog ────────────────────────────────────────
4049
4105
  /** Locator for the AlertDialog content that the mid-session enable opens. */
4050
4106
  function getChatPrivacyConfirmDialog(scope) {
4051
- return asPage$6(scope).getByTestId('chat-privacy-confirm-dialog');
4107
+ return asPage$7(scope).getByTestId('chat-privacy-confirm-dialog');
4052
4108
  }
4053
4109
  async function expectChatPrivacyConfirmDialogOpen(scope, open) {
4054
4110
  if (open) {
@@ -4067,7 +4123,7 @@ async function expectChatPrivacyConfirmDialogOpen(scope, open) {
4067
4123
  */
4068
4124
  async function confirmEnableChatPrivacyMidSession(scope) {
4069
4125
  await clickChatPrivacyToggle(scope);
4070
- const page = asPage$6(scope);
4126
+ const page = asPage$7(scope);
4071
4127
  await expectChatPrivacyConfirmDialogOpen(page, true);
4072
4128
  const action = page.getByTestId('chat-privacy-confirm-action');
4073
4129
  await test$1.expect(action).toBeVisible({ timeout: 10000 });
@@ -4078,7 +4134,7 @@ async function confirmEnableChatPrivacyMidSession(scope) {
4078
4134
  }
4079
4135
  /** Cancel the confirm dialog without enabling. */
4080
4136
  async function cancelEnableChatPrivacyMidSession(scope) {
4081
- const page = asPage$6(scope);
4137
+ const page = asPage$7(scope);
4082
4138
  const cancel = page.getByTestId('chat-privacy-confirm-cancel');
4083
4139
  await test$1.expect(cancel).toBeVisible({ timeout: 10000 });
4084
4140
  await cancel.click();
@@ -4273,7 +4329,7 @@ const VOICE_LABELS = {
4273
4329
  * reach DOM that Radix renders outside the dialog subtree (popovers,
4274
4330
  * select options, etc.).
4275
4331
  */
4276
- function asPage$5(scope) {
4332
+ function asPage$6(scope) {
4277
4333
  return 'page' in scope ? scope.page() : scope;
4278
4334
  }
4279
4335
  // ─── Tab navigation ──────────────────────────────────────────────────────
@@ -4355,7 +4411,7 @@ async function openMentorVoicePicker(scope) {
4355
4411
  const openBtn = scope.getByTestId('mentor-voice-trigger-open');
4356
4412
  await test$1.expect(openBtn).toBeVisible({ timeout: 10000 });
4357
4413
  await openBtn.click();
4358
- await test$1.expect(asPage$5(scope).getByTestId('voice-picker-modal')).toBeVisible({
4414
+ await test$1.expect(asPage$6(scope).getByTestId('voice-picker-modal')).toBeVisible({
4359
4415
  timeout: 10000,
4360
4416
  });
4361
4417
  }
@@ -4367,7 +4423,7 @@ async function openCallConfigVoicePicker(scope) {
4367
4423
  const openBtn = scope.getByTestId('call-config-voice-trigger-open');
4368
4424
  await test$1.expect(openBtn).toBeVisible({ timeout: 10000 });
4369
4425
  await openBtn.click();
4370
- await test$1.expect(asPage$5(scope).getByTestId('voice-picker-modal')).toBeVisible({
4426
+ await test$1.expect(asPage$6(scope).getByTestId('voice-picker-modal')).toBeVisible({
4371
4427
  timeout: 10000,
4372
4428
  });
4373
4429
  }
@@ -4459,7 +4515,7 @@ async function openVoiceInstructionsEditor(scope) {
4459
4515
  const btn = scope.getByRole('button', { name: `Edit ${VOICE_LABELS.voiceInstructions.label}` });
4460
4516
  await test$1.expect(btn).toBeVisible({ timeout: 10000 });
4461
4517
  await btn.click();
4462
- await test$1.expect(asPage$5(scope).getByText(`Edit ${VOICE_LABELS.voiceInstructions.label}`)).toBeVisible({ timeout: 10000 });
4518
+ await test$1.expect(asPage$6(scope).getByText(`Edit ${VOICE_LABELS.voiceInstructions.label}`)).toBeVisible({ timeout: 10000 });
4463
4519
  logger.info('Opened voice-instructions editor');
4464
4520
  }
4465
4521
  /**
@@ -4470,7 +4526,7 @@ async function openVoiceInstructionsEditor(scope) {
4470
4526
  */
4471
4527
  async function setVoiceInstructions(scope, text) {
4472
4528
  await openVoiceInstructionsEditor(scope);
4473
- const page = asPage$5(scope);
4529
+ const page = asPage$6(scope);
4474
4530
  const editor = page.getByRole('dialog').locator('[contenteditable="true"]').first();
4475
4531
  await test$1.expect(editor).toBeVisible({ timeout: 10000 });
4476
4532
  await editor.click();
@@ -4536,7 +4592,7 @@ async function selectCallMode(scope, mode) {
4536
4592
  const trigger = scope.getByRole('combobox', { name: VOICE_LABELS.callConfigFields.mode });
4537
4593
  await test$1.expect(trigger).toBeVisible({ timeout: 10000 });
4538
4594
  await trigger.click();
4539
- const option = asPage$5(scope).getByRole('option', { name: VOICE_LABELS.modeOptions[mode] });
4595
+ const option = asPage$6(scope).getByRole('option', { name: VOICE_LABELS.modeOptions[mode] });
4540
4596
  await test$1.expect(option).toBeVisible({ timeout: 5000 });
4541
4597
  await option.click();
4542
4598
  await test$1.expect(trigger).toHaveText(new RegExp(VOICE_LABELS.modeOptions[mode]));
@@ -4552,7 +4608,7 @@ async function selectFromCombobox(scope, triggerName, optionName) {
4552
4608
  const trigger = scope.getByRole('combobox', { name: triggerName });
4553
4609
  await test$1.expect(trigger).toBeVisible({ timeout: 10000 });
4554
4610
  await trigger.click();
4555
- const option = asPage$5(scope).getByRole('option', { name: optionName });
4611
+ const option = asPage$6(scope).getByRole('option', { name: optionName });
4556
4612
  await test$1.expect(option).toBeVisible({ timeout: 5000 });
4557
4613
  await option.click();
4558
4614
  }
@@ -4649,7 +4705,7 @@ const SCREENSHARE_LABELS = {
4649
4705
  },
4650
4706
  saveButton: 'Save',
4651
4707
  };
4652
- function asPage$4(scope) {
4708
+ function asPage$5(scope) {
4653
4709
  return 'page' in scope ? scope.page() : scope;
4654
4710
  }
4655
4711
  /**
@@ -4689,7 +4745,7 @@ async function openScreenSharePromptEditor(scope, field) {
4689
4745
  const btn = scope.getByRole('button', { name: `Edit ${label}` });
4690
4746
  await test$1.expect(btn).toBeVisible({ timeout: 10000 });
4691
4747
  await btn.click();
4692
- await test$1.expect(asPage$4(scope).getByText(`Edit ${label}`)).toBeVisible({ timeout: 10000 });
4748
+ await test$1.expect(asPage$5(scope).getByText(`Edit ${label}`)).toBeVisible({ timeout: 10000 });
4693
4749
  logger.info(`Opened screen-share ${field} editor`);
4694
4750
  }
4695
4751
  /**
@@ -4699,7 +4755,7 @@ async function openScreenSharePromptEditor(scope, field) {
4699
4755
  */
4700
4756
  async function setScreenSharePrompt(scope, field, text) {
4701
4757
  await openScreenSharePromptEditor(scope, field);
4702
- const page = asPage$4(scope);
4758
+ const page = asPage$5(scope);
4703
4759
  const editor = page.getByRole('dialog').locator('[contenteditable="true"]').first();
4704
4760
  await test$1.expect(editor).toBeVisible({ timeout: 10000 });
4705
4761
  await editor.click();
@@ -6085,7 +6141,7 @@ const TASKS_LABELS = {
6085
6141
  * reach DOM that Radix renders outside the dialog subtree (popovers,
6086
6142
  * select options, the schedule / delete / log-details dialogs, etc.).
6087
6143
  */
6088
- function asPage$3(scope) {
6144
+ function asPage$4(scope) {
6089
6145
  return 'page' in scope ? scope.page() : scope;
6090
6146
  }
6091
6147
  // ── Tab navigation ─────────────────────────────────────────────────────
@@ -6195,7 +6251,7 @@ async function searchTasks(scope, query) {
6195
6251
  * shares an accessible name with the dialog's submit button.
6196
6252
  */
6197
6253
  function getScheduleTaskDialog(scope) {
6198
- return asPage$3(scope).getByRole('dialog', { name: TASKS_LABELS.scheduleDialog.dialogName });
6254
+ return asPage$4(scope).getByRole('dialog', { name: TASKS_LABELS.scheduleDialog.dialogName });
6199
6255
  }
6200
6256
  /** Open the Schedule Task dialog and wait for it to be interactive. */
6201
6257
  async function openScheduleTaskDialog(scope) {
@@ -6214,7 +6270,7 @@ async function openScheduleTaskDialog(scope) {
6214
6270
  */
6215
6271
  async function scheduleTask(scope, opts) {
6216
6272
  await openScheduleTaskDialog(scope);
6217
- const page = asPage$3(scope);
6273
+ const page = asPage$4(scope);
6218
6274
  // Scope every query to the dialog: the toolbar "Schedule Task" button shares
6219
6275
  // an accessible name with the dialog's submit button, so an unscoped
6220
6276
  // getByRole('button', { name: 'Schedule Task' }) matches two elements.
@@ -6249,13 +6305,13 @@ async function scheduleTask(scope, opts) {
6249
6305
  }
6250
6306
  /** Asserts the in-dialog past-time error is currently shown. */
6251
6307
  async function expectScheduleStartTimeInPastError(scope) {
6252
- await test$1.expect(asPage$3(scope).getByText(TASKS_LABELS.scheduleDialog.startTimeInPast)).toBeVisible({
6308
+ await test$1.expect(asPage$4(scope).getByText(TASKS_LABELS.scheduleDialog.startTimeInPast)).toBeVisible({
6253
6309
  timeout: 5000,
6254
6310
  });
6255
6311
  }
6256
6312
  // ── Delete flow ────────────────────────────────────────────────────────
6257
6313
  function getDeleteTaskDialog(scope) {
6258
- return asPage$3(scope).locator(`[role="dialog"][aria-label="${TASKS_LABELS.deleteDialog.dialogName}"]`);
6314
+ return asPage$4(scope).locator(`[role="dialog"][aria-label="${TASKS_LABELS.deleteDialog.dialogName}"]`);
6259
6315
  }
6260
6316
  /**
6261
6317
  * Click the trash icon on a task row and confirm the delete in the
@@ -6276,7 +6332,7 @@ async function deleteTask(scope, taskName) {
6276
6332
  }
6277
6333
  // ── Logs panel + log details ───────────────────────────────────────────
6278
6334
  function getLogDetailsDialog(scope) {
6279
- return asPage$3(scope).locator(`[role="dialog"][aria-label="${TASKS_LABELS.logDetails.dialogName}"]`);
6335
+ return asPage$4(scope).locator(`[role="dialog"][aria-label="${TASKS_LABELS.logDetails.dialogName}"]`);
6280
6336
  }
6281
6337
  /**
6282
6338
  * Wait for the logs panel to finish loading and assert that the selected
@@ -6470,7 +6526,7 @@ const EVALS_LABELS = {
6470
6526
  * toasts) — those queries must run on the page, never on the tab pane or a
6471
6527
  * parent dialog.
6472
6528
  */
6473
- function asPage$2(scope) {
6529
+ function asPage$3(scope) {
6474
6530
  return 'page' in scope ? scope.page() : scope;
6475
6531
  }
6476
6532
  /** Escape a user-supplied string (run / benchmark names) for use in a RegExp. */
@@ -6479,7 +6535,7 @@ function escapeRegExp(value) {
6479
6535
  }
6480
6536
  /** Attribute-based dialog locator — immune to title renames and to Radix's `aria-labelledby` name precedence. */
6481
6537
  function dialogByName(scope, dialogName) {
6482
- return asPage$2(scope).locator(`[role="dialog"][aria-label="${dialogName}"]`);
6538
+ return asPage$3(scope).locator(`[role="dialog"][aria-label="${dialogName}"]`);
6483
6539
  }
6484
6540
  // ── Tab navigation ─────────────────────────────────────────────────────
6485
6541
  /**
@@ -6566,7 +6622,7 @@ async function expectNoBenchmarksNotice(scope) {
6566
6622
  function getRunRow(scope, runName) {
6567
6623
  return scope
6568
6624
  .getByRole('row')
6569
- .filter({ has: asPage$2(scope).getByRole('cell', { name: runName, exact: true }) });
6625
+ .filter({ has: asPage$3(scope).getByRole('cell', { name: runName, exact: true }) });
6570
6626
  }
6571
6627
  async function expectRunInTable(scope, runName) {
6572
6628
  await test$1.expect(getRunRow(scope, runName)).toBeVisible({ timeout: 15000 });
@@ -6593,7 +6649,7 @@ async function openRunActionsMenu(scope, runName) {
6593
6649
  await getRunRow(scope, runName)
6594
6650
  .getByRole('button', { name: `Actions for ${runName}`, exact: true })
6595
6651
  .click();
6596
- const menu = asPage$2(scope).getByRole('menu');
6652
+ const menu = asPage$3(scope).getByRole('menu');
6597
6653
  await test$1.expect(menu).toBeVisible({ timeout: 5000 });
6598
6654
  return menu;
6599
6655
  }
@@ -6630,7 +6686,7 @@ async function openNewReviewForRun(scope, runName) {
6630
6686
  * design (the item prevents default), so we close it with Escape.
6631
6687
  */
6632
6688
  async function checkRunStatus(scope, runName) {
6633
- const page = asPage$2(scope);
6689
+ const page = asPage$3(scope);
6634
6690
  const menu = await openRunActionsMenu(scope, runName);
6635
6691
  await menu.getByRole('menuitem', { name: EVALS_LABELS.actions.checkStatus }).click();
6636
6692
  const toasts = EVALS_LABELS.checkStatusToast;
@@ -6652,7 +6708,7 @@ async function checkRunStatus(scope, runName) {
6652
6708
  * suggested filename (`<benchmark>_<run>_results.csv`).
6653
6709
  */
6654
6710
  async function exportRunCsv(scope, runName) {
6655
- const page = asPage$2(scope);
6711
+ const page = asPage$3(scope);
6656
6712
  const menu = await openRunActionsMenu(scope, runName);
6657
6713
  const item = menu.getByRole('menuitem', { name: EVALS_LABELS.actions.exportCsv });
6658
6714
  await test$1.expect(item).toBeEnabled({ timeout: 5000 });
@@ -6748,7 +6804,7 @@ function getLlmPickerDialog(scope) {
6748
6804
  * title text since it has no aria-label). Both pickers close on selection.
6749
6805
  */
6750
6806
  async function selectJudgeLlm(scope, providerName, modelName) {
6751
- const page = asPage$2(scope);
6807
+ const page = asPage$3(scope);
6752
6808
  const judgeDialog = getLlmJudgeDialog(scope);
6753
6809
  await judgeDialog
6754
6810
  .getByRole('button', { name: EVALS_LABELS.judgeDialog.selectorAriaLabel })
@@ -6873,9 +6929,9 @@ async function removeTraceScore(scope, scoreName) {
6873
6929
  const dialog = getEvaluationDetailDialog(scope);
6874
6930
  const chip = dialog
6875
6931
  .locator('div')
6876
- .filter({ has: asPage$2(scope).getByText(scoreName, { exact: true }) })
6932
+ .filter({ has: asPage$3(scope).getByText(scoreName, { exact: true }) })
6877
6933
  .filter({
6878
- has: asPage$2(scope).getByRole('button', {
6934
+ has: asPage$3(scope).getByRole('button', {
6879
6935
  name: EVALS_LABELS.detailDialog.removeScoreAriaLabel,
6880
6936
  }),
6881
6937
  })
@@ -6915,7 +6971,7 @@ async function expandReview(scope, scoreName) {
6915
6971
  /** Close the detail dialog via Escape and wait for it to be gone. */
6916
6972
  async function closeEvaluationDetailDialog(scope) {
6917
6973
  const dialog = getEvaluationDetailDialog(scope);
6918
- await asPage$2(scope).keyboard.press('Escape');
6974
+ await asPage$3(scope).keyboard.press('Escape');
6919
6975
  await test$1.expect(dialog).toBeHidden({ timeout: 10000 });
6920
6976
  }
6921
6977
  // ── Manage benchmarks dialog (embedded tenant Benchmarks) ──────────────
@@ -7097,13 +7153,13 @@ async function deleteQaItem(scope, questionText) {
7097
7153
  /** Close the benchmark items dialog via Escape and wait for it to be gone. */
7098
7154
  async function closeBenchmarkItemsDialog(scope) {
7099
7155
  const dialog = getBenchmarkItemsDialog(scope);
7100
- await asPage$2(scope).keyboard.press('Escape');
7156
+ await asPage$3(scope).keyboard.press('Escape');
7101
7157
  await test$1.expect(dialog).toBeHidden({ timeout: 10000 });
7102
7158
  }
7103
7159
  /** Close the Manage benchmarks dialog via Escape and wait for it to be gone. */
7104
7160
  async function closeManageBenchmarksDialog(scope) {
7105
7161
  const dialog = getManageBenchmarksDialog(scope);
7106
- await asPage$2(scope).keyboard.press('Escape');
7162
+ await asPage$3(scope).keyboard.press('Escape');
7107
7163
  await test$1.expect(dialog).toBeHidden({ timeout: 10000 });
7108
7164
  }
7109
7165
 
@@ -7241,7 +7297,7 @@ const LTI_TEST_IDS = {
7241
7297
  * DOM that Radix renders in a portal outside the dialog subtree (dropdown
7242
7298
  * menus, select options).
7243
7299
  */
7244
- function asPage$1(scope) {
7300
+ function asPage$2(scope) {
7245
7301
  return 'page' in scope ? scope.page() : scope;
7246
7302
  }
7247
7303
  // ── Tab navigation ─────────────────────────────────────────────────────
@@ -7313,7 +7369,7 @@ function getLinkCopyTargetUriButton(scope) {
7313
7369
  return scope.getByRole('button', { name: 'Copy target link URI' });
7314
7370
  }
7315
7371
  function getLinkModal(scope) {
7316
- return asPage$1(scope).getByTestId(LTI_TEST_IDS.links.modal);
7372
+ return asPage$2(scope).getByTestId(LTI_TEST_IDS.links.modal);
7317
7373
  }
7318
7374
  function getLinkNameInput(scope) {
7319
7375
  return getLinkModal(scope).getByTestId(LTI_TEST_IDS.links.nameInput);
@@ -7407,7 +7463,7 @@ async function waitForLinkReady(scope, name, timeoutMs = 180000) {
7407
7463
  if (Date.now() >= deadline) {
7408
7464
  throw new Error(`Timed out waiting for LTI link to be ready: ${name} (last: ${status})`);
7409
7465
  }
7410
- await asPage$1(scope).waitForTimeout(5000);
7466
+ await asPage$2(scope).waitForTimeout(5000);
7411
7467
  await refreshLinks(scope);
7412
7468
  }
7413
7469
  logger.info(`LTI link ready: ${name}`);
@@ -7442,13 +7498,13 @@ function getKeyActionsTrigger(scope, name) {
7442
7498
  /** Open a key's three-dots menu and return the open menu (portal-rendered). */
7443
7499
  async function openKeyActionsMenu(scope, name) {
7444
7500
  await getKeyActionsTrigger(scope, name).click();
7445
- const menu = asPage$1(scope).getByRole('menu');
7501
+ const menu = asPage$2(scope).getByRole('menu');
7446
7502
  await test$1.expect(menu).toBeVisible({ timeout: 5000 });
7447
7503
  return menu;
7448
7504
  }
7449
7505
  // — Key: create —
7450
7506
  function getKeyCreateModal(scope) {
7451
- return asPage$1(scope).getByTestId(LTI_TEST_IDS.keys.createModal);
7507
+ return asPage$2(scope).getByTestId(LTI_TEST_IDS.keys.createModal);
7452
7508
  }
7453
7509
  function getKeyCreateNameInput(scope) {
7454
7510
  return getKeyCreateModal(scope).getByTestId(LTI_TEST_IDS.keys.createNameInput);
@@ -7468,7 +7524,7 @@ async function createKey(scope, name) {
7468
7524
  }
7469
7525
  // — Key: edit / detail —
7470
7526
  function getKeyDetailModal(scope) {
7471
- return asPage$1(scope).getByTestId(LTI_TEST_IDS.keys.detailModal);
7527
+ return asPage$2(scope).getByTestId(LTI_TEST_IDS.keys.detailModal);
7472
7528
  }
7473
7529
  function getKeyDetailNameInput(scope) {
7474
7530
  return getKeyDetailModal(scope).getByTestId(LTI_TEST_IDS.keys.detailNameInput);
@@ -7512,7 +7568,7 @@ async function closeKeyDetail(scope) {
7512
7568
  }
7513
7569
  // — Key: delete —
7514
7570
  function getKeyDeleteModal(scope) {
7515
- return asPage$1(scope).getByTestId(LTI_TEST_IDS.keys.deleteModal);
7571
+ return asPage$2(scope).getByTestId(LTI_TEST_IDS.keys.deleteModal);
7516
7572
  }
7517
7573
  /** Open the delete confirmation for a key via its actions menu → Delete. */
7518
7574
  async function openKeyDelete(scope, name) {
@@ -7543,7 +7599,7 @@ async function deleteKey(scope, name) {
7543
7599
  * delete-modal convention); the modal stays open so the user can cancel.
7544
7600
  */
7545
7601
  async function expectKeyDeleteError(scope, message) {
7546
- const page = asPage$1(scope);
7602
+ const page = asPage$2(scope);
7547
7603
  await test$1.expect(page.getByText(message, { exact: false }).first()).toBeVisible({ timeout: 15000 });
7548
7604
  await test$1.expect(getKeyDeleteModal(scope)).toBeVisible();
7549
7605
  }
@@ -7575,7 +7631,7 @@ function getToolEditButton(scope, name) {
7575
7631
  return scope.getByRole('button', { name: `Edit ${name}` });
7576
7632
  }
7577
7633
  function getToolModal(scope) {
7578
- return asPage$1(scope).getByTestId(LTI_TEST_IDS.tools.modal);
7634
+ return asPage$2(scope).getByTestId(LTI_TEST_IDS.tools.modal);
7579
7635
  }
7580
7636
  /** Field locators inside the (open) tool modal. */
7581
7637
  const toolFields = {
@@ -7608,7 +7664,7 @@ async function selectToolKeySetMode(scope, mode) {
7608
7664
  /** Pick a signing key by name from the Radix select in the open tool modal. */
7609
7665
  async function selectToolSigningKey(scope, name) {
7610
7666
  await getToolKeySelect(scope).click();
7611
- await asPage$1(scope).getByRole('option', { name }).click();
7667
+ await asPage$2(scope).getByRole('option', { name }).click();
7612
7668
  }
7613
7669
  /** Submit button inside the tool modal ("Create" or "Save"). */
7614
7670
  function getToolSubmitButton(scope) {
@@ -7785,7 +7841,7 @@ const SUPPORT_LABELS = {
7785
7841
  * reach DOM that Radix renders outside the dialog subtree (select option
7786
7842
  * lists, popover contents).
7787
7843
  */
7788
- function asPage(scope) {
7844
+ function asPage$1(scope) {
7789
7845
  return 'page' in scope ? scope.page() : scope;
7790
7846
  }
7791
7847
  // ── Dialog capture ─────────────────────────────────────────────────────
@@ -7974,7 +8030,7 @@ async function filterTicketsByStatus(dialog, status) {
7974
8030
  const label = status === 'all' ? SUPPORT_LABELS.filters.allStatuses : SUPPORT_LABELS.status[status];
7975
8031
  await getStatusFilter(dialog).click();
7976
8032
  // Radix renders the option list in a portal outside the dialog subtree.
7977
- await asPage(dialog).getByRole('option', { name: label, exact: true }).click();
8033
+ await asPage$1(dialog).getByRole('option', { name: label, exact: true }).click();
7978
8034
  logger.info(`Filtered tickets by status: ${label}`);
7979
8035
  }
7980
8036
  /** The "Search for User" combobox trigger (opens the requester popover). */
@@ -8055,7 +8111,7 @@ function getTicketDetail(dialog) {
8055
8111
  function getTicketDescription(dialog) {
8056
8112
  // On mobile the detail renders in a nested preview dialog instead of the
8057
8113
  // side pane; the testid is unique either way, so query from the page.
8058
- return asPage(dialog).getByTestId(SUPPORT_LABELS.testIds.ticketDescription).first();
8114
+ return asPage$1(dialog).getByTestId(SUPPORT_LABELS.testIds.ticketDescription).first();
8059
8115
  }
8060
8116
  /**
8061
8117
  * Open a ticket from the list and wait for its detail to render. Returns
@@ -8082,7 +8138,7 @@ async function setTicketStatus(dialog, status) {
8082
8138
  const detail = getTicketDetail(dialog);
8083
8139
  await detail.getByRole('combobox', { name: SUPPORT_LABELS.detail.statusLabel }).click();
8084
8140
  // Radix renders the option list in a portal outside the dialog subtree.
8085
- await asPage(dialog)
8141
+ await asPage$1(dialog)
8086
8142
  .getByRole('option', { name: SUPPORT_LABELS.status[status], exact: true })
8087
8143
  .click();
8088
8144
  logger.info(`Set ticket status: ${SUPPORT_LABELS.status[status]}`);
@@ -8218,6 +8274,299 @@ async function createSupportTicketViaChatAndVerify(page, opts) {
8218
8274
  return dialog;
8219
8275
  }
8220
8276
 
8277
+ /**
8278
+ * Profile History tab Playwright bindings (user profile modal > History).
8279
+ *
8280
+ * The tab has two sub-tabs:
8281
+ * 1. **Conversations** — filter row (agent autocomplete, date range,
8282
+ * sentiment, topic, Export), a two-column conversation list + transcript
8283
+ * preview, and numbered pagination.
8284
+ * 2. **Exports** — a table of previously generated `my-chat-history`
8285
+ * reports with state badges and re-download actions.
8286
+ *
8287
+ * Anti-flake rules baked into every helper:
8288
+ * - The profile dialog is captured ONCE via `getProfileDialog` — a
8289
+ * `getByRole('dialog')` + `.filter(...)` pair — and every sub-element is
8290
+ * resolved from that scoped locator. Page-level queries are used only for
8291
+ * Radix portals (select dropdowns, toasts).
8292
+ * - Selectors are role / aria-label / data-testid based only — no CSS
8293
+ * classes, no `networkidle`; readiness is always "wait for the element".
8294
+ */
8295
+ const HISTORY_TAB_LABELS = {
8296
+ /** Tab name in the user profile modal sidebar. */
8297
+ tabName: 'History',
8298
+ subTabs: {
8299
+ conversations: 'Conversations',
8300
+ exports: 'Exports',
8301
+ },
8302
+ filters: {
8303
+ /** Placeholder (and accessible name) of the agent autocomplete input. */
8304
+ searchAgents: 'Search Agents',
8305
+ pickDateRange: 'Pick a Date Range',
8306
+ /** `aria-label` of the sentiment select trigger. */
8307
+ sentiment: 'Filter by Sentiment',
8308
+ /** `aria-label` of the topic select trigger. */
8309
+ topic: 'Filter by Topic',
8310
+ export: 'Export',
8311
+ exporting: 'Exporting...',
8312
+ },
8313
+ regions: {
8314
+ /** `aria-label` of the conversation list region. */
8315
+ list: 'Conversation list',
8316
+ /** `aria-label` of the transcript preview region. */
8317
+ preview: 'Conversation preview',
8318
+ },
8319
+ /** Per-conversation download button in the preview header. */
8320
+ download: 'Download',
8321
+ emptyState: 'No conversations found',
8322
+ selectPrompt: 'Select a conversation to view details.',
8323
+ toasts: {
8324
+ exportReady: 'Your chat history has been downloaded.',
8325
+ exportFailed: 'Failed to export chat history. Please try again.',
8326
+ },
8327
+ exports: {
8328
+ columns: {
8329
+ status: 'Status',
8330
+ created: 'Created',
8331
+ filters: 'Filters',
8332
+ expires: 'Expires',
8333
+ },
8334
+ states: {
8335
+ completed: 'Completed',
8336
+ processing: 'Processing',
8337
+ pending: 'Pending',
8338
+ failed: 'Failed',
8339
+ },
8340
+ empty: 'No exports yet.',
8341
+ },
8342
+ };
8343
+ /** data-testid prefix of the agent autocomplete (`SearchSelect`). */
8344
+ const AGENT_FILTER_TEST_ID = 'history-agent-filter';
8345
+ function asPage(scope) {
8346
+ return 'page' in scope ? scope.page() : scope;
8347
+ }
8348
+ // ──────────────────────────────────────────────────────────────────────
8349
+ // Dialog + tab navigation
8350
+ // ──────────────────────────────────────────────────────────────────────
8351
+ /**
8352
+ * The user profile dialog, captured tag-first (`getByRole('dialog')`) and
8353
+ * narrowed by a solid child — the History tab button — so it never matches
8354
+ * a different dialog stacked on the page.
8355
+ */
8356
+ function getProfileDialog(page) {
8357
+ return page
8358
+ .getByRole('dialog')
8359
+ .filter({ has: page.getByRole('tab', { name: HISTORY_TAB_LABELS.tabName, exact: true }) });
8360
+ }
8361
+ /**
8362
+ * Open the History tab inside the (already open) profile dialog and wait
8363
+ * for its Conversations sub-tab to render. Returns the scoped dialog
8364
+ * locator every other helper should be handed.
8365
+ */
8366
+ async function openHistoryTab(page) {
8367
+ const dialog = getProfileDialog(page);
8368
+ await test$1.expect(dialog).toBeVisible({ timeout: 15000 });
8369
+ const historyTab = dialog.getByRole('tab', {
8370
+ name: HISTORY_TAB_LABELS.tabName,
8371
+ exact: true,
8372
+ });
8373
+ await test$1.expect(historyTab).toBeVisible({ timeout: 10000 });
8374
+ await historyTab.click();
8375
+ await test$1.expect(dialog.getByRole('tab', { name: HISTORY_TAB_LABELS.subTabs.conversations, exact: true })).toBeVisible({ timeout: 10000 });
8376
+ logger.info('Opened profile History tab');
8377
+ return dialog;
8378
+ }
8379
+ /**
8380
+ * Switch between the Conversations and Exports sub-tabs, waiting for a
8381
+ * stable landmark of the destination before returning.
8382
+ */
8383
+ async function switchHistorySubTab(dialog, subTab) {
8384
+ const trigger = dialog.getByRole('tab', { name: subTab, exact: true });
8385
+ await test$1.expect(trigger).toBeVisible({ timeout: 10000 });
8386
+ await trigger.click();
8387
+ await test$1.expect(trigger).toHaveAttribute('aria-selected', 'true', { timeout: 10000 });
8388
+ if (subTab === 'Exports') {
8389
+ await test$1.expect(dialog.getByRole('table')).toBeVisible({ timeout: 15000 });
8390
+ }
8391
+ else {
8392
+ await test$1.expect(dialog.getByRole('button', { name: HISTORY_TAB_LABELS.filters.export, exact: true })).toBeVisible({ timeout: 15000 });
8393
+ }
8394
+ logger.info(`Switched History sub-tab to ${subTab}`);
8395
+ }
8396
+ // ──────────────────────────────────────────────────────────────────────
8397
+ // Conversations — list + preview
8398
+ // ──────────────────────────────────────────────────────────────────────
8399
+ function getConversationList(dialog) {
8400
+ return dialog.getByRole('region', { name: HISTORY_TAB_LABELS.regions.list });
8401
+ }
8402
+ function getConversationPreview(dialog) {
8403
+ return dialog.getByRole('region', { name: HISTORY_TAB_LABELS.regions.preview });
8404
+ }
8405
+ /** Every conversation row in the list (each row is a `role="button"`). */
8406
+ function getConversationRows(dialog) {
8407
+ return getConversationList(dialog).getByTestId('history-conversation-row');
8408
+ }
8409
+ /**
8410
+ * Wait for the conversation area to settle into one of its two valid
8411
+ * states: at least one row rendered, or the empty state.
8412
+ */
8413
+ async function waitForConversations(dialog) {
8414
+ const firstRow = getConversationRows(dialog).first();
8415
+ const emptyState = dialog.getByText(HISTORY_TAB_LABELS.emptyState, { exact: true });
8416
+ await test$1.expect(firstRow.or(emptyState).first()).toBeVisible({ timeout: 30000 });
8417
+ }
8418
+ /**
8419
+ * Click a conversation row — by zero-based `index`, or the first row whose
8420
+ * text contains `title` — then wait for the transcript preview to show its
8421
+ * per-conversation Download button (the signal the messages have loaded).
8422
+ */
8423
+ async function selectConversation(dialog, options = {}) {
8424
+ var _a;
8425
+ const rows = getConversationRows(dialog);
8426
+ const row = options.title !== undefined
8427
+ ? rows.filter({ hasText: options.title }).first()
8428
+ : rows.nth((_a = options.index) !== null && _a !== void 0 ? _a : 0);
8429
+ await test$1.expect(row).toBeVisible({ timeout: 30000 });
8430
+ await row.click();
8431
+ const preview = getConversationPreview(dialog);
8432
+ await test$1.expect(preview.getByRole('button', { name: HISTORY_TAB_LABELS.download, exact: true })).toBeVisible({ timeout: 30000 });
8433
+ logger.info('Selected conversation and transcript preview loaded');
8434
+ }
8435
+ /**
8436
+ * Download the currently previewed conversation as CSV (client-side file).
8437
+ * Returns the Playwright `Download` so the test can assert on the file.
8438
+ */
8439
+ async function downloadConversationCsv(dialog) {
8440
+ const page = asPage(dialog);
8441
+ const button = getConversationPreview(dialog).getByRole('button', {
8442
+ name: HISTORY_TAB_LABELS.download,
8443
+ exact: true,
8444
+ });
8445
+ await test$1.expect(button).toBeVisible({ timeout: 10000 });
8446
+ const downloadPromise = page.waitForEvent('download', { timeout: 30000 });
8447
+ await button.click();
8448
+ const download = await downloadPromise;
8449
+ logger.info(`Downloaded conversation file: ${download.suggestedFilename()}`);
8450
+ return download;
8451
+ }
8452
+ // ──────────────────────────────────────────────────────────────────────
8453
+ // Conversations — filters
8454
+ // ──────────────────────────────────────────────────────────────────────
8455
+ /**
8456
+ * Type into the agent autocomplete and pick the result whose label matches
8457
+ * `agentName`, then wait for the picker to collapse into its selected chip.
8458
+ */
8459
+ async function filterHistoryByAgent(dialog, agentName) {
8460
+ const input = dialog.getByTestId(`${AGENT_FILTER_TEST_ID}-input`);
8461
+ await test$1.expect(input).toBeVisible({ timeout: 10000 });
8462
+ await input.fill(agentName);
8463
+ const option = dialog
8464
+ .getByTestId(`${AGENT_FILTER_TEST_ID}-results`)
8465
+ .getByRole('button', { name: agentName })
8466
+ .first();
8467
+ await test$1.expect(option).toBeVisible({ timeout: 30000 });
8468
+ await option.click();
8469
+ await test$1.expect(dialog.getByTestId(`${AGENT_FILTER_TEST_ID}-selected`)).toBeVisible({
8470
+ timeout: 10000,
8471
+ });
8472
+ logger.info(`Filtered History by agent: ${agentName}`);
8473
+ }
8474
+ /** Clear the agent filter chip and wait for the search input to return. */
8475
+ async function clearHistoryAgentFilter(dialog) {
8476
+ const clearButton = dialog.getByTestId(`${AGENT_FILTER_TEST_ID}-clear`);
8477
+ await test$1.expect(clearButton).toBeVisible({ timeout: 10000 });
8478
+ await clearButton.click();
8479
+ await test$1.expect(dialog.getByTestId(`${AGENT_FILTER_TEST_ID}-input`)).toBeVisible({
8480
+ timeout: 10000,
8481
+ });
8482
+ }
8483
+ /**
8484
+ * Pick an option in one of the filter selects (sentiment / topic). The
8485
+ * trigger lives in the dialog; the option list is a Radix portal, so it is
8486
+ * the one place resolved from the page root.
8487
+ */
8488
+ async function pickFilterOption(dialog, triggerLabel, optionName) {
8489
+ const page = asPage(dialog);
8490
+ const trigger = dialog.getByRole('combobox', { name: triggerLabel });
8491
+ await test$1.expect(trigger).toBeVisible({ timeout: 10000 });
8492
+ await trigger.click();
8493
+ const option = page.getByRole('option', { name: optionName, exact: true });
8494
+ await test$1.expect(option).toBeVisible({ timeout: 10000 });
8495
+ await option.click();
8496
+ // The trigger echoes the chosen option — the sign the select applied.
8497
+ await test$1.expect(trigger).toContainText(optionName, { timeout: 10000 });
8498
+ }
8499
+ async function filterHistoryBySentiment(dialog, sentiment) {
8500
+ await pickFilterOption(dialog, HISTORY_TAB_LABELS.filters.sentiment, sentiment);
8501
+ logger.info(`Filtered History by sentiment: ${sentiment}`);
8502
+ }
8503
+ async function filterHistoryByTopic(dialog, topic) {
8504
+ await pickFilterOption(dialog, HISTORY_TAB_LABELS.filters.topic, topic);
8505
+ logger.info(`Filtered History by topic: ${topic}`);
8506
+ }
8507
+ // ──────────────────────────────────────────────────────────────────────
8508
+ // Export (server-side report) + Exports sub-tab
8509
+ // ──────────────────────────────────────────────────────────────────────
8510
+ /**
8511
+ * Click Export on the Conversations sub-tab. The report generates
8512
+ * server-side and downloads automatically when ready — pass the returned
8513
+ * promise handling to `waitForHistoryExportDownload` for the full flow.
8514
+ */
8515
+ async function startHistoryExport(dialog) {
8516
+ const exportButton = dialog.getByRole('button', {
8517
+ name: HISTORY_TAB_LABELS.filters.export,
8518
+ exact: true,
8519
+ });
8520
+ await test$1.expect(exportButton).toBeVisible({ timeout: 10000 });
8521
+ await test$1.expect(exportButton).toBeEnabled({ timeout: 10000 });
8522
+ await exportButton.click();
8523
+ logger.info('Started History export');
8524
+ }
8525
+ /**
8526
+ * Full export flow: click Export, then wait for the report to finish
8527
+ * polling and the browser download to fire. Report generation is a
8528
+ * background task, so the timeout is generous by default.
8529
+ */
8530
+ async function exportHistoryAndWaitForDownload(dialog, { timeout = 120000 } = {}) {
8531
+ const page = asPage(dialog);
8532
+ const downloadPromise = page.waitForEvent('download', { timeout });
8533
+ await startHistoryExport(dialog);
8534
+ const download = await downloadPromise;
8535
+ logger.info(`History export downloaded: ${download.suggestedFilename()}`);
8536
+ return download;
8537
+ }
8538
+ /** The Exports sub-tab's reports table. */
8539
+ function getExportsTable(dialog) {
8540
+ return dialog.getByRole('table');
8541
+ }
8542
+ /**
8543
+ * Rows of the Exports table matching a state badge label (e.g.
8544
+ * `Completed`), each of which carries its own Download action when done.
8545
+ */
8546
+ function getExportRowsByState(dialog, state) {
8547
+ return getExportsTable(dialog).getByRole('row').filter({ hasText: state });
8548
+ }
8549
+ /**
8550
+ * Wait until at least one report row reaches the Completed state. Reports
8551
+ * finish asynchronously, so the timeout is generous by default.
8552
+ */
8553
+ async function waitForCompletedExportRow(dialog, { timeout = 120000 } = {}) {
8554
+ const row = getExportRowsByState(dialog, HISTORY_TAB_LABELS.exports.states.completed).first();
8555
+ await test$1.expect(row.getByRole('button', { name: HISTORY_TAB_LABELS.download, exact: true })).toBeVisible({ timeout });
8556
+ return row;
8557
+ }
8558
+ /** Re-download a completed report from its Exports-table row. */
8559
+ async function downloadExportedReport(dialog, row) {
8560
+ const page = asPage(dialog);
8561
+ const button = row.getByRole('button', { name: HISTORY_TAB_LABELS.download, exact: true });
8562
+ await test$1.expect(button).toBeVisible({ timeout: 10000 });
8563
+ const downloadPromise = page.waitForEvent('download', { timeout: 60000 });
8564
+ await button.click();
8565
+ const download = await downloadPromise;
8566
+ logger.info(`Re-downloaded report: ${download.suggestedFilename()}`);
8567
+ return download;
8568
+ }
8569
+
8221
8570
  /** Extract browser key from device name (e.g., 'Desktop Chrome' -> 'chrome') */
8222
8571
  function getBrowserKey(deviceName) {
8223
8572
  return deviceName.toLowerCase().replace(/^desktop\s+/, '');
@@ -8352,6 +8701,7 @@ exports.CHAT_PRIVACY_LABELS = CHAT_PRIVACY_LABELS;
8352
8701
  exports.CustomReporter = CustomReporter;
8353
8702
  exports.EVALS_LABELS = EVALS_LABELS;
8354
8703
  exports.GRADER_LABELS = GRADER_LABELS;
8704
+ exports.HISTORY_TAB_LABELS = HISTORY_TAB_LABELS;
8355
8705
  exports.LTI_LABELS = LTI_LABELS;
8356
8706
  exports.LTI_TEST_IDS = LTI_TEST_IDS;
8357
8707
  exports.MEMORY_ADMIN_LABELS = MEMORY_ADMIN_LABELS;
@@ -8397,6 +8747,7 @@ exports.clearAgentLimitsFilter = clearAgentLimitsFilter;
8397
8747
  exports.clearAgentMemoriesFilter = clearAgentMemoriesFilter;
8398
8748
  exports.clearDateRangeFilter = clearDateRangeFilter;
8399
8749
  exports.clearGradeResultOverride = clearGradeResultOverride;
8750
+ exports.clearHistoryAgentFilter = clearHistoryAgentFilter;
8400
8751
  exports.clearInstanceSearch = clearInstanceSearch;
8401
8752
  exports.clearUserFilter = clearUserFilter;
8402
8753
  exports.clickBackHome = clickBackHome;
@@ -8453,6 +8804,8 @@ exports.deleteWorkspaceSpendLimit = deleteWorkspaceSpendLimit;
8453
8804
  exports.disableSkill = disableSkill;
8454
8805
  exports.disableSupport = disableSupport;
8455
8806
  exports.disconnectInstance = disconnectInstance;
8807
+ exports.downloadConversationCsv = downloadConversationCsv;
8808
+ exports.downloadExportedReport = downloadExportedReport;
8456
8809
  exports.editAgentPrompt = editAgentPrompt;
8457
8810
  exports.editGraderCriterion = editGraderCriterion;
8458
8811
  exports.editInstance = editInstance;
@@ -8554,6 +8907,7 @@ exports.expectVoiceProviderSelected = expectVoiceProviderSelected;
8554
8907
  exports.expectVoiceVisible = expectVoiceVisible;
8555
8908
  exports.expectWorkspaceActualSpendStats = expectWorkspaceActualSpendStats;
8556
8909
  exports.expectWorkspaceSpendStats = expectWorkspaceSpendStats;
8910
+ exports.exportHistoryAndWaitForDownload = exportHistoryAndWaitForDownload;
8557
8911
  exports.exportRunCsv = exportRunCsv;
8558
8912
  exports.fillLinkName = fillLinkName;
8559
8913
  exports.fillToolForm = fillToolForm;
@@ -8565,6 +8919,9 @@ exports.filterByActor = filterByActor;
8565
8919
  exports.filterByActorAndVerify = filterByActorAndVerify;
8566
8920
  exports.filterByDateRange = filterByDateRange;
8567
8921
  exports.filterGradeResultsByEmail = filterGradeResultsByEmail;
8922
+ exports.filterHistoryByAgent = filterHistoryByAgent;
8923
+ exports.filterHistoryBySentiment = filterHistoryBySentiment;
8924
+ exports.filterHistoryByTopic = filterHistoryByTopic;
8568
8925
  exports.filterTicketsByStatus = filterTicketsByStatus;
8569
8926
  exports.filterTicketsByUser = filterTicketsByUser;
8570
8927
  exports.generateBrowserSetupProjects = generateBrowserSetupProjects;
@@ -8582,6 +8939,9 @@ exports.getCallConfigForm = getCallConfigForm;
8582
8939
  exports.getChatInput = getChatInput;
8583
8940
  exports.getChatPrivacyConfirmDialog = getChatPrivacyConfirmDialog;
8584
8941
  exports.getChatPrivacyToggle = getChatPrivacyToggle;
8942
+ exports.getConversationList = getConversationList;
8943
+ exports.getConversationPreview = getConversationPreview;
8944
+ exports.getConversationRows = getConversationRows;
8585
8945
  exports.getCreateBenchmarkDialog = getCreateBenchmarkDialog;
8586
8946
  exports.getCreateKeyButton = getCreateKeyButton;
8587
8947
  exports.getCreateLinkButton = getCreateLinkButton;
@@ -8598,6 +8958,8 @@ exports.getEndpointCopyButton = getEndpointCopyButton;
8598
8958
  exports.getEndpointUrl = getEndpointUrl;
8599
8959
  exports.getEntityChip = getEntityChip;
8600
8960
  exports.getEvaluationDetailDialog = getEvaluationDetailDialog;
8961
+ exports.getExportRowsByState = getExportRowsByState;
8962
+ exports.getExportsTable = getExportsTable;
8601
8963
  exports.getInstanceHealthLabel = getInstanceHealthLabel;
8602
8964
  exports.getInstanceRowCount = getInstanceRowCount;
8603
8965
  exports.getInstanceStatusLabel = getInstanceStatusLabel;
@@ -8631,6 +8993,7 @@ exports.getNewEvaluationButton = getNewEvaluationButton;
8631
8993
  exports.getOutputFilterSwitch = getOutputFilterSwitch;
8632
8994
  exports.getPaginationInfo = getPaginationInfo;
8633
8995
  exports.getPrivateModeCard = getPrivateModeCard;
8996
+ exports.getProfileDialog = getProfileDialog;
8634
8997
  exports.getReplyComposer = getReplyComposer;
8635
8998
  exports.getReviewRow = getReviewRow;
8636
8999
  exports.getRunRow = getRunRow;
@@ -8717,6 +9080,7 @@ exports.openEditLinkModal = openEditLinkModal;
8717
9080
  exports.openEditSkillDialog = openEditSkillDialog;
8718
9081
  exports.openEditToolModal = openEditToolModal;
8719
9082
  exports.openFirstLogDetails = openFirstLogDetails;
9083
+ exports.openHistoryTab = openHistoryTab;
8720
9084
  exports.openInstanceActionsMenu = openInstanceActionsMenu;
8721
9085
  exports.openKeyActionsMenu = openKeyActionsMenu;
8722
9086
  exports.openKeyDelete = openKeyDelete;
@@ -8775,6 +9139,7 @@ exports.searchTasks = searchTasks;
8775
9139
  exports.searchVoices = searchVoices;
8776
9140
  exports.selectBenchmark = selectBenchmark;
8777
9141
  exports.selectCallMode = selectCallMode;
9142
+ exports.selectConversation = selectConversation;
8778
9143
  exports.selectDateFromCalendar = selectDateFromCalendar;
8779
9144
  exports.selectJudgeLlm = selectJudgeLlm;
8780
9145
  exports.selectLLMModel = selectLLMModel;
@@ -8828,9 +9193,11 @@ exports.shouldVerifyCSVEditorDialogAccessibility = shouldVerifyCSVEditorDialogAc
8828
9193
  exports.signUpWithEmailAndPassword = signUpWithEmailAndPassword;
8829
9194
  exports.spendLimitsTabBody = spendLimitsTabBody;
8830
9195
  exports.startEvaluation = startEvaluation;
9196
+ exports.startHistoryExport = startHistoryExport;
8831
9197
  exports.submitLinkModal = submitLinkModal;
8832
9198
  exports.submitLlmJudge = submitLlmJudge;
8833
9199
  exports.submitToolModal = submitToolModal;
9200
+ exports.switchHistorySubTab = switchHistorySubTab;
8834
9201
  exports.switchToAddItemsSubTab = switchToAddItemsSubTab;
8835
9202
  exports.switchToAgentLimits = switchToAgentLimits;
8836
9203
  exports.switchToAgentSkillsSubTab = switchToAgentSkillsSubTab;
@@ -8895,6 +9262,8 @@ exports.verifySkillsTabVisible = verifySkillsTabVisible;
8895
9262
  exports.waitForAgentChatResponse = waitForAgentChatResponse;
8896
9263
  exports.waitForAuditLogDataLoaded = waitForAuditLogDataLoaded;
8897
9264
  exports.waitForBillingTabReady = waitForBillingTabReady;
9265
+ exports.waitForCompletedExportRow = waitForCompletedExportRow;
9266
+ exports.waitForConversations = waitForConversations;
8898
9267
  exports.waitForCreditBalanceLoaded = waitForCreditBalanceLoaded;
8899
9268
  exports.waitForDialogReady = waitForDialogReady;
8900
9269
  exports.waitForElementStable = waitForElementStable;