@iblai/iblai-js 1.26.9 → 1.26.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/data-layer/playwright/human-support-tab-helpers.d.ts +262 -0
  2. package/dist/data-layer/playwright/index.d.ts +1 -0
  3. package/dist/data-layer/security/index.d.ts +1 -0
  4. package/dist/data-layer/security/next.d.ts +1 -0
  5. package/dist/playwright/index.cjs +597 -41
  6. package/dist/playwright/index.cjs.map +1 -1
  7. package/dist/playwright/index.d.ts +264 -2
  8. package/dist/playwright/index.esm.js +559 -42
  9. package/dist/playwright/index.esm.js.map +1 -1
  10. package/dist/playwright/playwright/human-support-tab-helpers.d.ts +262 -0
  11. package/dist/playwright/playwright/index.d.ts +1 -0
  12. package/dist/playwright/security/index.d.ts +1 -0
  13. package/dist/playwright/security/next.d.ts +1 -0
  14. package/dist/security/data-layer/index.d.ts +1 -0
  15. package/dist/security/index.d.ts +1 -0
  16. package/dist/security/index.esm.js +2 -0
  17. package/dist/security/index.esm.js.map +1 -0
  18. package/dist/security/index.js +13 -0
  19. package/dist/security/index.js.map +1 -0
  20. package/dist/security/next.d.ts +1 -0
  21. package/dist/security/next.esm.js +2 -0
  22. package/dist/security/next.esm.js.map +1 -0
  23. package/dist/security/next.js +13 -0
  24. package/dist/security/next.js.map +1 -0
  25. package/dist/security/playwright/audit-log-helpers.d.ts +119 -0
  26. package/dist/security/playwright/auth-flow-builder.d.ts +13 -0
  27. package/dist/security/playwright/auth-helpers.d.ts +10 -0
  28. package/dist/security/playwright/auth-setup.d.ts +34 -0
  29. package/dist/security/playwright/billing-tab-helpers.d.ts +88 -0
  30. package/dist/security/playwright/chat-privacy-helpers.d.ts +127 -0
  31. package/dist/security/playwright/claw-sandbox-helpers.d.ts +266 -0
  32. package/dist/security/playwright/credit-balance-helpers.d.ts +73 -0
  33. package/dist/security/playwright/custom-reporter.d.ts +12 -0
  34. package/dist/security/playwright/env-config.d.ts +24 -0
  35. package/dist/security/playwright/evaluation-tab-helpers.d.ts +396 -0
  36. package/dist/security/playwright/helpers.d.ts +11 -0
  37. package/dist/security/playwright/human-support-tab-helpers.d.ts +262 -0
  38. package/dist/security/playwright/index.d.ts +40 -0
  39. package/dist/security/playwright/logger.d.ts +3 -0
  40. package/dist/security/playwright/lti-tab-helpers.d.ts +270 -0
  41. package/dist/security/playwright/mail-client.d.ts +46 -0
  42. package/dist/security/playwright/memory-test-helpers.d.ts +66 -0
  43. package/dist/security/playwright/navigation.d.ts +26 -0
  44. package/dist/security/playwright/page-helpers.d.ts +38 -0
  45. package/dist/security/playwright/playwright-config.d.ts +81 -0
  46. package/dist/security/playwright/privacy-tab-helpers.d.ts +95 -0
  47. package/dist/security/playwright/report-download.d.ts +71 -0
  48. package/dist/security/playwright/screenshare-tab-helpers.d.ts +57 -0
  49. package/dist/security/playwright/shared-test-helpers.d.ts +37 -0
  50. package/dist/security/playwright/tasks-tab-helpers.d.ts +177 -0
  51. package/dist/security/playwright/voice-tab-helpers.d.ts +194 -0
  52. package/dist/security/security/index.d.ts +1 -0
  53. package/dist/security/security/next.d.ts +1 -0
  54. package/dist/security/web-containers/index.d.ts +1 -0
  55. package/dist/security/web-containers/next.d.ts +1 -0
  56. package/dist/security/web-containers/sso.d.ts +1 -0
  57. package/dist/security/web-utils/auth.d.ts +1 -0
  58. package/dist/security/web-utils/index.d.ts +1 -0
  59. package/dist/web-containers/playwright/human-support-tab-helpers.d.ts +262 -0
  60. package/dist/web-containers/playwright/index.d.ts +1 -0
  61. package/dist/web-containers/security/index.d.ts +1 -0
  62. package/dist/web-containers/security/next.d.ts +1 -0
  63. package/dist/web-containers/source/index.esm.js +216 -0
  64. package/dist/web-containers/source/next/index.esm.js +666 -15
  65. package/dist/web-utils/playwright/human-support-tab-helpers.d.ts +262 -0
  66. package/dist/web-utils/playwright/index.d.ts +1 -0
  67. package/dist/web-utils/security/index.d.ts +1 -0
  68. package/dist/web-utils/security/next.d.ts +1 -0
  69. package/package.json +15 -5
@@ -3165,7 +3165,7 @@ const PRIVACY_LABELS = {
3165
3165
  * reach DOM that Radix renders outside the dialog subtree (popovers,
3166
3166
  * select options, etc.).
3167
3167
  */
3168
- function asPage$6(scope) {
3168
+ function asPage$7(scope) {
3169
3169
  return 'page' in scope ? scope.page() : scope;
3170
3170
  }
3171
3171
  /**
@@ -3226,7 +3226,7 @@ async function selectPrivacyAction(scope, action) {
3226
3226
  await trigger.click();
3227
3227
  // Radix Select renders options in a portal at the document root, so we
3228
3228
  // always look them up on the Page — never on the dialog Locator.
3229
- const option = asPage$6(scope).getByRole('option', {
3229
+ const option = asPage$7(scope).getByRole('option', {
3230
3230
  name: PRIVACY_LABELS.actionOptions[action],
3231
3231
  });
3232
3232
  await test$1.expect(option).toBeVisible({ timeout: 5000 });
@@ -3338,7 +3338,7 @@ const CHAT_PRIVACY_LABELS = {
3338
3338
  rowLabel: 'Allow users to control chat privacy',
3339
3339
  },
3340
3340
  };
3341
- function asPage$5(scope) {
3341
+ function asPage$6(scope) {
3342
3342
  return 'page' in scope ? scope.page() : scope;
3343
3343
  }
3344
3344
  // ──────────────────────────────────────────────────────────────────────
@@ -3410,7 +3410,7 @@ async function clickChatPrivacyToggle(scope) {
3410
3410
  // ── Mid-session confirm dialog ────────────────────────────────────────
3411
3411
  /** Locator for the AlertDialog content that the mid-session enable opens. */
3412
3412
  function getChatPrivacyConfirmDialog(scope) {
3413
- return asPage$5(scope).getByTestId('chat-privacy-confirm-dialog');
3413
+ return asPage$6(scope).getByTestId('chat-privacy-confirm-dialog');
3414
3414
  }
3415
3415
  async function expectChatPrivacyConfirmDialogOpen(scope, open) {
3416
3416
  if (open) {
@@ -3429,7 +3429,7 @@ async function expectChatPrivacyConfirmDialogOpen(scope, open) {
3429
3429
  */
3430
3430
  async function confirmEnableChatPrivacyMidSession(scope) {
3431
3431
  await clickChatPrivacyToggle(scope);
3432
- const page = asPage$5(scope);
3432
+ const page = asPage$6(scope);
3433
3433
  await expectChatPrivacyConfirmDialogOpen(page, true);
3434
3434
  const action = page.getByTestId('chat-privacy-confirm-action');
3435
3435
  await test$1.expect(action).toBeVisible({ timeout: 10000 });
@@ -3440,7 +3440,7 @@ async function confirmEnableChatPrivacyMidSession(scope) {
3440
3440
  }
3441
3441
  /** Cancel the confirm dialog without enabling. */
3442
3442
  async function cancelEnableChatPrivacyMidSession(scope) {
3443
- const page = asPage$5(scope);
3443
+ const page = asPage$6(scope);
3444
3444
  const cancel = page.getByTestId('chat-privacy-confirm-cancel');
3445
3445
  await test$1.expect(cancel).toBeVisible({ timeout: 10000 });
3446
3446
  await cancel.click();
@@ -3627,7 +3627,7 @@ const VOICE_LABELS = {
3627
3627
  * reach DOM that Radix renders outside the dialog subtree (popovers,
3628
3628
  * select options, etc.).
3629
3629
  */
3630
- function asPage$4(scope) {
3630
+ function asPage$5(scope) {
3631
3631
  return 'page' in scope ? scope.page() : scope;
3632
3632
  }
3633
3633
  // ─── Tab navigation ──────────────────────────────────────────────────────
@@ -3709,7 +3709,7 @@ async function openMentorVoicePicker(scope) {
3709
3709
  const openBtn = scope.getByTestId('mentor-voice-trigger-open');
3710
3710
  await test$1.expect(openBtn).toBeVisible({ timeout: 10000 });
3711
3711
  await openBtn.click();
3712
- await test$1.expect(asPage$4(scope).getByTestId('voice-picker-modal')).toBeVisible({
3712
+ await test$1.expect(asPage$5(scope).getByTestId('voice-picker-modal')).toBeVisible({
3713
3713
  timeout: 10000,
3714
3714
  });
3715
3715
  }
@@ -3721,7 +3721,7 @@ async function openCallConfigVoicePicker(scope) {
3721
3721
  const openBtn = scope.getByTestId('call-config-voice-trigger-open');
3722
3722
  await test$1.expect(openBtn).toBeVisible({ timeout: 10000 });
3723
3723
  await openBtn.click();
3724
- await test$1.expect(asPage$4(scope).getByTestId('voice-picker-modal')).toBeVisible({
3724
+ await test$1.expect(asPage$5(scope).getByTestId('voice-picker-modal')).toBeVisible({
3725
3725
  timeout: 10000,
3726
3726
  });
3727
3727
  }
@@ -3834,7 +3834,7 @@ async function selectCallMode(scope, mode) {
3834
3834
  const trigger = scope.getByRole('combobox', { name: VOICE_LABELS.callConfigFields.mode });
3835
3835
  await test$1.expect(trigger).toBeVisible({ timeout: 10000 });
3836
3836
  await trigger.click();
3837
- const option = asPage$4(scope).getByRole('option', { name: VOICE_LABELS.modeOptions[mode] });
3837
+ const option = asPage$5(scope).getByRole('option', { name: VOICE_LABELS.modeOptions[mode] });
3838
3838
  await test$1.expect(option).toBeVisible({ timeout: 5000 });
3839
3839
  await option.click();
3840
3840
  await test$1.expect(trigger).toHaveText(new RegExp(VOICE_LABELS.modeOptions[mode]));
@@ -3850,7 +3850,7 @@ async function selectFromCombobox(scope, triggerName, optionName) {
3850
3850
  const trigger = scope.getByRole('combobox', { name: triggerName });
3851
3851
  await test$1.expect(trigger).toBeVisible({ timeout: 10000 });
3852
3852
  await trigger.click();
3853
- const option = asPage$4(scope).getByRole('option', { name: optionName });
3853
+ const option = asPage$5(scope).getByRole('option', { name: optionName });
3854
3854
  await test$1.expect(option).toBeVisible({ timeout: 5000 });
3855
3855
  await option.click();
3856
3856
  }
@@ -3947,7 +3947,7 @@ const SCREENSHARE_LABELS = {
3947
3947
  },
3948
3948
  saveButton: 'Save',
3949
3949
  };
3950
- function asPage$3(scope) {
3950
+ function asPage$4(scope) {
3951
3951
  return 'page' in scope ? scope.page() : scope;
3952
3952
  }
3953
3953
  /**
@@ -3987,7 +3987,7 @@ async function openScreenSharePromptEditor(scope, field) {
3987
3987
  const btn = scope.getByRole('button', { name: `Edit ${label}` });
3988
3988
  await test$1.expect(btn).toBeVisible({ timeout: 10000 });
3989
3989
  await btn.click();
3990
- await test$1.expect(asPage$3(scope).getByText(`Edit ${label}`)).toBeVisible({ timeout: 10000 });
3990
+ await test$1.expect(asPage$4(scope).getByText(`Edit ${label}`)).toBeVisible({ timeout: 10000 });
3991
3991
  logger.info(`Opened screen-share ${field} editor`);
3992
3992
  }
3993
3993
  /**
@@ -3997,7 +3997,7 @@ async function openScreenSharePromptEditor(scope, field) {
3997
3997
  */
3998
3998
  async function setScreenSharePrompt(scope, field, text) {
3999
3999
  await openScreenSharePromptEditor(scope, field);
4000
- const page = asPage$3(scope);
4000
+ const page = asPage$4(scope);
4001
4001
  const editor = page.getByRole('dialog').locator('[contenteditable="true"]').first();
4002
4002
  await test$1.expect(editor).toBeVisible({ timeout: 10000 });
4003
4003
  await editor.click();
@@ -4328,7 +4328,7 @@ const TASKS_LABELS = {
4328
4328
  * reach DOM that Radix renders outside the dialog subtree (popovers,
4329
4329
  * select options, the schedule / delete / log-details dialogs, etc.).
4330
4330
  */
4331
- function asPage$2(scope) {
4331
+ function asPage$3(scope) {
4332
4332
  return 'page' in scope ? scope.page() : scope;
4333
4333
  }
4334
4334
  // ── Tab navigation ─────────────────────────────────────────────────────
@@ -4438,7 +4438,7 @@ async function searchTasks(scope, query) {
4438
4438
  * shares an accessible name with the dialog's submit button.
4439
4439
  */
4440
4440
  function getScheduleTaskDialog(scope) {
4441
- return asPage$2(scope).getByRole('dialog', { name: TASKS_LABELS.scheduleDialog.dialogName });
4441
+ return asPage$3(scope).getByRole('dialog', { name: TASKS_LABELS.scheduleDialog.dialogName });
4442
4442
  }
4443
4443
  /** Open the Schedule Task dialog and wait for it to be interactive. */
4444
4444
  async function openScheduleTaskDialog(scope) {
@@ -4457,7 +4457,7 @@ async function openScheduleTaskDialog(scope) {
4457
4457
  */
4458
4458
  async function scheduleTask(scope, opts) {
4459
4459
  await openScheduleTaskDialog(scope);
4460
- const page = asPage$2(scope);
4460
+ const page = asPage$3(scope);
4461
4461
  // Scope every query to the dialog: the toolbar "Schedule Task" button shares
4462
4462
  // an accessible name with the dialog's submit button, so an unscoped
4463
4463
  // getByRole('button', { name: 'Schedule Task' }) matches two elements.
@@ -4492,13 +4492,13 @@ async function scheduleTask(scope, opts) {
4492
4492
  }
4493
4493
  /** Asserts the in-dialog past-time error is currently shown. */
4494
4494
  async function expectScheduleStartTimeInPastError(scope) {
4495
- await test$1.expect(asPage$2(scope).getByText(TASKS_LABELS.scheduleDialog.startTimeInPast)).toBeVisible({
4495
+ await test$1.expect(asPage$3(scope).getByText(TASKS_LABELS.scheduleDialog.startTimeInPast)).toBeVisible({
4496
4496
  timeout: 5000,
4497
4497
  });
4498
4498
  }
4499
4499
  // ── Delete flow ────────────────────────────────────────────────────────
4500
4500
  function getDeleteTaskDialog(scope) {
4501
- return asPage$2(scope).locator(`[role="dialog"][aria-label="${TASKS_LABELS.deleteDialog.dialogName}"]`);
4501
+ return asPage$3(scope).locator(`[role="dialog"][aria-label="${TASKS_LABELS.deleteDialog.dialogName}"]`);
4502
4502
  }
4503
4503
  /**
4504
4504
  * Click the trash icon on a task row and confirm the delete in the
@@ -4519,7 +4519,7 @@ async function deleteTask(scope, taskName) {
4519
4519
  }
4520
4520
  // ── Logs panel + log details ───────────────────────────────────────────
4521
4521
  function getLogDetailsDialog(scope) {
4522
- return asPage$2(scope).locator(`[role="dialog"][aria-label="${TASKS_LABELS.logDetails.dialogName}"]`);
4522
+ return asPage$3(scope).locator(`[role="dialog"][aria-label="${TASKS_LABELS.logDetails.dialogName}"]`);
4523
4523
  }
4524
4524
  /**
4525
4525
  * Wait for the logs panel to finish loading and assert that the selected
@@ -4713,7 +4713,7 @@ const EVALS_LABELS = {
4713
4713
  * toasts) — those queries must run on the page, never on the tab pane or a
4714
4714
  * parent dialog.
4715
4715
  */
4716
- function asPage$1(scope) {
4716
+ function asPage$2(scope) {
4717
4717
  return 'page' in scope ? scope.page() : scope;
4718
4718
  }
4719
4719
  /** Escape a user-supplied string (run / benchmark names) for use in a RegExp. */
@@ -4722,7 +4722,7 @@ function escapeRegExp(value) {
4722
4722
  }
4723
4723
  /** Attribute-based dialog locator — immune to title renames and to Radix's `aria-labelledby` name precedence. */
4724
4724
  function dialogByName(scope, dialogName) {
4725
- return asPage$1(scope).locator(`[role="dialog"][aria-label="${dialogName}"]`);
4725
+ return asPage$2(scope).locator(`[role="dialog"][aria-label="${dialogName}"]`);
4726
4726
  }
4727
4727
  // ── Tab navigation ─────────────────────────────────────────────────────
4728
4728
  /**
@@ -4809,7 +4809,7 @@ async function expectNoBenchmarksNotice(scope) {
4809
4809
  function getRunRow(scope, runName) {
4810
4810
  return scope
4811
4811
  .getByRole('row')
4812
- .filter({ has: asPage$1(scope).getByRole('cell', { name: runName, exact: true }) });
4812
+ .filter({ has: asPage$2(scope).getByRole('cell', { name: runName, exact: true }) });
4813
4813
  }
4814
4814
  async function expectRunInTable(scope, runName) {
4815
4815
  await test$1.expect(getRunRow(scope, runName)).toBeVisible({ timeout: 15000 });
@@ -4836,7 +4836,7 @@ async function openRunActionsMenu(scope, runName) {
4836
4836
  await getRunRow(scope, runName)
4837
4837
  .getByRole('button', { name: `Actions for ${runName}`, exact: true })
4838
4838
  .click();
4839
- const menu = asPage$1(scope).getByRole('menu');
4839
+ const menu = asPage$2(scope).getByRole('menu');
4840
4840
  await test$1.expect(menu).toBeVisible({ timeout: 5000 });
4841
4841
  return menu;
4842
4842
  }
@@ -4873,7 +4873,7 @@ async function openNewReviewForRun(scope, runName) {
4873
4873
  * design (the item prevents default), so we close it with Escape.
4874
4874
  */
4875
4875
  async function checkRunStatus(scope, runName) {
4876
- const page = asPage$1(scope);
4876
+ const page = asPage$2(scope);
4877
4877
  const menu = await openRunActionsMenu(scope, runName);
4878
4878
  await menu.getByRole('menuitem', { name: EVALS_LABELS.actions.checkStatus }).click();
4879
4879
  const toasts = EVALS_LABELS.checkStatusToast;
@@ -4895,7 +4895,7 @@ async function checkRunStatus(scope, runName) {
4895
4895
  * suggested filename (`<benchmark>_<run>_results.csv`).
4896
4896
  */
4897
4897
  async function exportRunCsv(scope, runName) {
4898
- const page = asPage$1(scope);
4898
+ const page = asPage$2(scope);
4899
4899
  const menu = await openRunActionsMenu(scope, runName);
4900
4900
  const item = menu.getByRole('menuitem', { name: EVALS_LABELS.actions.exportCsv });
4901
4901
  await test$1.expect(item).toBeEnabled({ timeout: 5000 });
@@ -4991,7 +4991,7 @@ function getLlmPickerDialog(scope) {
4991
4991
  * title text since it has no aria-label). Both pickers close on selection.
4992
4992
  */
4993
4993
  async function selectJudgeLlm(scope, providerName, modelName) {
4994
- const page = asPage$1(scope);
4994
+ const page = asPage$2(scope);
4995
4995
  const judgeDialog = getLlmJudgeDialog(scope);
4996
4996
  await judgeDialog
4997
4997
  .getByRole('button', { name: EVALS_LABELS.judgeDialog.selectorAriaLabel })
@@ -5116,9 +5116,9 @@ async function removeTraceScore(scope, scoreName) {
5116
5116
  const dialog = getEvaluationDetailDialog(scope);
5117
5117
  const chip = dialog
5118
5118
  .locator('div')
5119
- .filter({ has: asPage$1(scope).getByText(scoreName, { exact: true }) })
5119
+ .filter({ has: asPage$2(scope).getByText(scoreName, { exact: true }) })
5120
5120
  .filter({
5121
- has: asPage$1(scope).getByRole('button', {
5121
+ has: asPage$2(scope).getByRole('button', {
5122
5122
  name: EVALS_LABELS.detailDialog.removeScoreAriaLabel,
5123
5123
  }),
5124
5124
  })
@@ -5158,7 +5158,7 @@ async function expandReview(scope, scoreName) {
5158
5158
  /** Close the detail dialog via Escape and wait for it to be gone. */
5159
5159
  async function closeEvaluationDetailDialog(scope) {
5160
5160
  const dialog = getEvaluationDetailDialog(scope);
5161
- await asPage$1(scope).keyboard.press('Escape');
5161
+ await asPage$2(scope).keyboard.press('Escape');
5162
5162
  await test$1.expect(dialog).toBeHidden({ timeout: 10000 });
5163
5163
  }
5164
5164
  // ── Manage benchmarks dialog (embedded tenant Benchmarks) ──────────────
@@ -5340,13 +5340,13 @@ async function deleteQaItem(scope, questionText) {
5340
5340
  /** Close the benchmark items dialog via Escape and wait for it to be gone. */
5341
5341
  async function closeBenchmarkItemsDialog(scope) {
5342
5342
  const dialog = getBenchmarkItemsDialog(scope);
5343
- await asPage$1(scope).keyboard.press('Escape');
5343
+ await asPage$2(scope).keyboard.press('Escape');
5344
5344
  await test$1.expect(dialog).toBeHidden({ timeout: 10000 });
5345
5345
  }
5346
5346
  /** Close the Manage benchmarks dialog via Escape and wait for it to be gone. */
5347
5347
  async function closeManageBenchmarksDialog(scope) {
5348
5348
  const dialog = getManageBenchmarksDialog(scope);
5349
- await asPage$1(scope).keyboard.press('Escape');
5349
+ await asPage$2(scope).keyboard.press('Escape');
5350
5350
  await test$1.expect(dialog).toBeHidden({ timeout: 10000 });
5351
5351
  }
5352
5352
 
@@ -5473,7 +5473,7 @@ const LTI_TEST_IDS = {
5473
5473
  * DOM that Radix renders in a portal outside the dialog subtree (dropdown
5474
5474
  * menus, select options).
5475
5475
  */
5476
- function asPage(scope) {
5476
+ function asPage$1(scope) {
5477
5477
  return 'page' in scope ? scope.page() : scope;
5478
5478
  }
5479
5479
  // ── Tab navigation ─────────────────────────────────────────────────────
@@ -5545,7 +5545,7 @@ function getLinkCopyTargetUriButton(scope) {
5545
5545
  return scope.getByRole('button', { name: 'Copy target link URI' });
5546
5546
  }
5547
5547
  function getLinkModal(scope) {
5548
- return asPage(scope).getByTestId(LTI_TEST_IDS.links.modal);
5548
+ return asPage$1(scope).getByTestId(LTI_TEST_IDS.links.modal);
5549
5549
  }
5550
5550
  function getLinkNameInput(scope) {
5551
5551
  return getLinkModal(scope).getByTestId(LTI_TEST_IDS.links.nameInput);
@@ -5621,13 +5621,13 @@ function getKeyActionsTrigger(scope, name) {
5621
5621
  /** Open a key's three-dots menu and return the open menu (portal-rendered). */
5622
5622
  async function openKeyActionsMenu(scope, name) {
5623
5623
  await getKeyActionsTrigger(scope, name).click();
5624
- const menu = asPage(scope).getByRole('menu');
5624
+ const menu = asPage$1(scope).getByRole('menu');
5625
5625
  await test$1.expect(menu).toBeVisible({ timeout: 5000 });
5626
5626
  return menu;
5627
5627
  }
5628
5628
  // — Key: create —
5629
5629
  function getKeyCreateModal(scope) {
5630
- return asPage(scope).getByTestId(LTI_TEST_IDS.keys.createModal);
5630
+ return asPage$1(scope).getByTestId(LTI_TEST_IDS.keys.createModal);
5631
5631
  }
5632
5632
  function getKeyCreateNameInput(scope) {
5633
5633
  return getKeyCreateModal(scope).getByTestId(LTI_TEST_IDS.keys.createNameInput);
@@ -5647,7 +5647,7 @@ async function createKey(scope, name) {
5647
5647
  }
5648
5648
  // — Key: edit / detail —
5649
5649
  function getKeyDetailModal(scope) {
5650
- return asPage(scope).getByTestId(LTI_TEST_IDS.keys.detailModal);
5650
+ return asPage$1(scope).getByTestId(LTI_TEST_IDS.keys.detailModal);
5651
5651
  }
5652
5652
  function getKeyDetailNameInput(scope) {
5653
5653
  return getKeyDetailModal(scope).getByTestId(LTI_TEST_IDS.keys.detailNameInput);
@@ -5691,7 +5691,7 @@ async function closeKeyDetail(scope) {
5691
5691
  }
5692
5692
  // — Key: delete —
5693
5693
  function getKeyDeleteModal(scope) {
5694
- return asPage(scope).getByTestId(LTI_TEST_IDS.keys.deleteModal);
5694
+ return asPage$1(scope).getByTestId(LTI_TEST_IDS.keys.deleteModal);
5695
5695
  }
5696
5696
  /** Open the delete confirmation for a key via its actions menu → Delete. */
5697
5697
  async function openKeyDelete(scope, name) {
@@ -5722,7 +5722,7 @@ async function deleteKey(scope, name) {
5722
5722
  * delete-modal convention); the modal stays open so the user can cancel.
5723
5723
  */
5724
5724
  async function expectKeyDeleteError(scope, message) {
5725
- const page = asPage(scope);
5725
+ const page = asPage$1(scope);
5726
5726
  await test$1.expect(page.getByText(message, { exact: false }).first()).toBeVisible({ timeout: 15000 });
5727
5727
  await test$1.expect(getKeyDeleteModal(scope)).toBeVisible();
5728
5728
  }
@@ -5754,7 +5754,7 @@ function getToolEditButton(scope, name) {
5754
5754
  return scope.getByRole('button', { name: `Edit ${name}` });
5755
5755
  }
5756
5756
  function getToolModal(scope) {
5757
- return asPage(scope).getByTestId(LTI_TEST_IDS.tools.modal);
5757
+ return asPage$1(scope).getByTestId(LTI_TEST_IDS.tools.modal);
5758
5758
  }
5759
5759
  /** Field locators inside the (open) tool modal. */
5760
5760
  const toolFields = {
@@ -5787,7 +5787,7 @@ async function selectToolKeySetMode(scope, mode) {
5787
5787
  /** Pick a signing key by name from the Radix select in the open tool modal. */
5788
5788
  async function selectToolSigningKey(scope, name) {
5789
5789
  await getToolKeySelect(scope).click();
5790
- await asPage(scope).getByRole('option', { name }).click();
5790
+ await asPage$1(scope).getByRole('option', { name }).click();
5791
5791
  }
5792
5792
  /** Submit button inside the tool modal ("Create" or "Save"). */
5793
5793
  function getToolSubmitButton(scope) {
@@ -5880,6 +5880,523 @@ async function expectAllEndpointsVisible(scope) {
5880
5880
  }
5881
5881
  }
5882
5882
 
5883
+ /**
5884
+ * Support tab helpers — Playwright bindings for the `AgentHumanSupportTab`
5885
+ * component from `@iblai/web-containers` (Edit Agent modal → Runtime →
5886
+ * Support).
5887
+ *
5888
+ * All UI strings below mirror `AGENT_HUMAN_SUPPORT_TAB_LABELS` from
5889
+ * `@iblai/web-containers/next`. If a consumer renames a label via the
5890
+ * `labels` prop, override these constants per spec — don't edit this file.
5891
+ *
5892
+ * Scoping rule (see playwright-testing skill §12): capture the Edit Agent
5893
+ * dialog FIRST via `getEditAgentDialog(page)`, then resolve every in-panel
5894
+ * locator from that dialog. Radix portals (select option lists, popover
5895
+ * contents) render OUTSIDE the dialog subtree — those queries intentionally
5896
+ * run on the `page` and are commented as such.
5897
+ */
5898
+ const SUPPORT_LABELS = {
5899
+ /** Sidebar segment label (host app) + tab panel value. */
5900
+ tabName: 'Support',
5901
+ /** The host sidebar trigger's `aria-controls` panel id suffix. */
5902
+ panelValue: 'human_support',
5903
+ /** Category strip pill that hosts the Support segment. */
5904
+ navCategory: 'Runtime',
5905
+ header: {
5906
+ title: 'Support',
5907
+ description: 'Review and respond to support requests submitted for this agent.',
5908
+ },
5909
+ toggle: {
5910
+ /** `aria-label` on the availability switch in the info box. */
5911
+ enable: 'Enable support',
5912
+ },
5913
+ filters: {
5914
+ searchUser: 'Search for User',
5915
+ searchUsersPlaceholder: 'Search users...',
5916
+ allUsers: 'All Users',
5917
+ allStatuses: 'All Statuses',
5918
+ },
5919
+ status: {
5920
+ open: 'Open',
5921
+ inProgress: 'In Progress',
5922
+ closed: 'Closed',
5923
+ },
5924
+ list: {
5925
+ noTickets: 'No support tickets found',
5926
+ untitled: 'Support request',
5927
+ /** `aria-label` of the ticket-list region. */
5928
+ regionLabel: 'Support ticket list',
5929
+ },
5930
+ detail: {
5931
+ selectPrompt: 'Select a ticket to view details.',
5932
+ statusLabel: 'Status',
5933
+ requestSection: 'Request',
5934
+ conversationSection: 'Conversation',
5935
+ supportTeam: 'Support Team',
5936
+ noMessages: 'No replies yet.',
5937
+ replyPlaceholder: 'Write a reply to the user…',
5938
+ sendReply: 'Send Reply',
5939
+ closedNotice: 'This ticket is closed. Set it back to Open or In Progress to reply.',
5940
+ /** `aria-label` of the ticket-detail region. */
5941
+ regionLabel: 'Support ticket details',
5942
+ },
5943
+ testIds: {
5944
+ infoBox: 'human-support-info-box',
5945
+ toggle: 'human-support-toggle',
5946
+ ticketList: 'human-support-ticket-list',
5947
+ ticketDetail: 'human-support-ticket-detail',
5948
+ ticketDescription: 'human-support-ticket-description',
5949
+ /** Ticket rows render as `human-support-ticket-item-<id>`. */
5950
+ ticketItemPrefix: 'human-support-ticket-item-',
5951
+ },
5952
+ chat: {
5953
+ /** Accessible name of the chat textbox in the host app. */
5954
+ inputName: 'Ask anything',
5955
+ sendButton: 'Send message',
5956
+ /** Stable class markers from `CSS_CLASS_NAMES.CHAT` in web-containers. */
5957
+ userQueryClass: '.chat-user-message-query',
5958
+ aiResponseClass: '.chat-ai-message-response',
5959
+ stopStreamingClass: '.chat-stop-streaming-button',
5960
+ },
5961
+ };
5962
+ /**
5963
+ * Resolve a Page from either a Page or a Locator. Used when we need to
5964
+ * reach DOM that Radix renders outside the dialog subtree (select option
5965
+ * lists, popover contents).
5966
+ */
5967
+ function asPage(scope) {
5968
+ return 'page' in scope ? scope.page() : scope;
5969
+ }
5970
+ // ── Dialog capture ─────────────────────────────────────────────────────
5971
+ /**
5972
+ * Capture the Edit Agent dialog itself. Every other helper resolves its
5973
+ * locators from this dialog, so in-panel controls never collide with
5974
+ * elements mounted on the page behind the modal.
5975
+ *
5976
+ * The dialog carries no stable test id in the host, so it is identified by
5977
+ * its visible title ("Edit Agent" by default — override for hosts that
5978
+ * rename the modal).
5979
+ */
5980
+ function getEditAgentDialog(page, dialogTitle = 'Edit Agent') {
5981
+ return page.getByRole('dialog').filter({ hasText: dialogTitle });
5982
+ }
5983
+ // ── Tab navigation ─────────────────────────────────────────────────────
5984
+ /**
5985
+ * Check whether the Support tab is currently reachable in the Edit Agent
5986
+ * dialog. Returns false instead of throwing so callers can guard
5987
+ * permission-gated hosts (the segment is admin-only).
5988
+ */
5989
+ async function isSupportTabVisible(page) {
5990
+ const dialog = getEditAgentDialog(page);
5991
+ try {
5992
+ await switchToRuntimeCategory(dialog);
5993
+ await test$1.expect(getSupportTabTrigger(dialog)).toBeVisible({ timeout: 5000 });
5994
+ return true;
5995
+ }
5996
+ catch (_a) {
5997
+ return false;
5998
+ }
5999
+ }
6000
+ /**
6001
+ * The host sidebar trigger for the Support segment. Matched by its
6002
+ * `aria-controls="panel-human_support"` (stable across label renames);
6003
+ * `:visible` excludes the hidden responsive twin the host renders for the
6004
+ * other breakpoint.
6005
+ */
6006
+ function getSupportTabTrigger(dialog) {
6007
+ return dialog
6008
+ .locator(`[role="tab"][aria-controls="panel-${SUPPORT_LABELS.panelValue}"]:visible`)
6009
+ .first();
6010
+ }
6011
+ /**
6012
+ * Best-effort switch to the "Runtime" category strip that hosts the
6013
+ * Support segment. The strip only renders when more than one category has
6014
+ * items, so a missing pill is not an error — the segment is then already
6015
+ * mounted in the single visible list.
6016
+ */
6017
+ async function switchToRuntimeCategory(dialog) {
6018
+ const pill = dialog
6019
+ .getByRole('tab', { name: SUPPORT_LABELS.navCategory, exact: true })
6020
+ .and(dialog.locator(':visible'))
6021
+ .first();
6022
+ const hasStrip = await pill
6023
+ .waitFor({ state: 'visible', timeout: 5000 })
6024
+ .then(() => true)
6025
+ .catch(() => false);
6026
+ if (!hasStrip)
6027
+ return;
6028
+ if ((await pill.getAttribute('data-state').catch(() => null)) !== 'active') {
6029
+ await pill.click();
6030
+ await test$1.expect(pill).toHaveAttribute('data-state', 'active', { timeout: 5000 });
6031
+ }
6032
+ }
6033
+ /**
6034
+ * Switch to the Support tab. Assumes the Edit Agent dialog is open.
6035
+ * Returns the dialog locator so callers can keep every follow-up query
6036
+ * scoped without re-capturing it.
6037
+ */
6038
+ async function switchToSupportTab(page) {
6039
+ const dialog = getEditAgentDialog(page);
6040
+ await test$1.expect(dialog).toBeVisible({ timeout: 15000 });
6041
+ await switchToRuntimeCategory(dialog);
6042
+ const trigger = getSupportTabTrigger(dialog);
6043
+ await test$1.expect(trigger).toBeVisible({ timeout: 10000 });
6044
+ await trigger.click();
6045
+ // The info box is unique to the Support pane and mounts immediately
6046
+ // (unlike the ticket list, which waits on network).
6047
+ await test$1.expect(dialog.getByTestId(SUPPORT_LABELS.testIds.infoBox)).toBeVisible({
6048
+ timeout: 10000,
6049
+ });
6050
+ logger.info('Switched to Support tab');
6051
+ return dialog;
6052
+ }
6053
+ // ── Info box + availability toggle ─────────────────────────────────────
6054
+ /** The info box at the top of the pane (conversational description). */
6055
+ function getSupportInfoBox(dialog) {
6056
+ return dialog.getByTestId(SUPPORT_LABELS.testIds.infoBox);
6057
+ }
6058
+ /**
6059
+ * The availability switch inside the info box — the same "human support"
6060
+ * tool toggle exposed on the Tools tab. Hidden when the tenant's tool
6061
+ * catalog has no human-support tool.
6062
+ */
6063
+ function getSupportToggle(dialog) {
6064
+ return dialog.getByTestId(SUPPORT_LABELS.testIds.toggle);
6065
+ }
6066
+ /** Read the current on/off state of the availability toggle. */
6067
+ async function isSupportEnabled(dialog) {
6068
+ const toggle = getSupportToggle(dialog);
6069
+ await test$1.expect(toggle).toBeVisible({ timeout: 10000 });
6070
+ return (await toggle.getAttribute('aria-checked')) === 'true';
6071
+ }
6072
+ /**
6073
+ * Flip the availability toggle to the requested state (no-op when already
6074
+ * there). Waits for `aria-checked` to settle, which tracks the persisted
6075
+ * mentor-settings round trip.
6076
+ */
6077
+ async function setSupportEnabled(dialog, enabled) {
6078
+ const toggle = getSupportToggle(dialog);
6079
+ await test$1.expect(toggle).toBeVisible({ timeout: 10000 });
6080
+ const current = (await toggle.getAttribute('aria-checked')) === 'true';
6081
+ if (current === enabled) {
6082
+ logger.info(`Support already ${enabled ? 'enabled' : 'disabled'}`);
6083
+ return;
6084
+ }
6085
+ await test$1.expect(toggle).toBeEnabled({ timeout: 10000 });
6086
+ await toggle.click();
6087
+ // Re-locate on every retry — the switch re-renders after the save.
6088
+ await test$1.expect(getSupportToggle(dialog)).toHaveAttribute('aria-checked', String(enabled), {
6089
+ timeout: 15000,
6090
+ });
6091
+ logger.info(`Support ${enabled ? 'enabled' : 'disabled'}`);
6092
+ }
6093
+ /** Turn the support tool on. */
6094
+ async function enableSupport(dialog) {
6095
+ await setSupportEnabled(dialog, true);
6096
+ }
6097
+ /** Turn the support tool off. */
6098
+ async function disableSupport(dialog) {
6099
+ await setSupportEnabled(dialog, false);
6100
+ }
6101
+ // ── Ticket list ────────────────────────────────────────────────────────
6102
+ /** The ticket-list region. Only mounted when at least one ticket exists. */
6103
+ function getTicketList(dialog) {
6104
+ return dialog.getByTestId(SUPPORT_LABELS.testIds.ticketList);
6105
+ }
6106
+ /**
6107
+ * A ticket row matched by (a substring of) its subject. Rows carry
6108
+ * `data-testid="human-support-ticket-item-<id>"`; the id is server-side,
6109
+ * so text matching within the list is the stable route for specs.
6110
+ */
6111
+ function getTicketRow(dialog, subject) {
6112
+ return getTicketList(dialog)
6113
+ .locator(`[data-testid^="${SUPPORT_LABELS.testIds.ticketItemPrefix}"]`)
6114
+ .filter({ hasText: subject })
6115
+ .first();
6116
+ }
6117
+ /** A ticket row by its zero-based position in the list. */
6118
+ function getTicketRowByIndex(dialog, index) {
6119
+ return getTicketList(dialog)
6120
+ .locator(`[data-testid^="${SUPPORT_LABELS.testIds.ticketItemPrefix}"]`)
6121
+ .nth(index);
6122
+ }
6123
+ /** Assert a ticket with the given subject text is listed. */
6124
+ async function expectTicketInList(dialog, subject, timeoutMs = 15000) {
6125
+ await test$1.expect(getTicketRow(dialog, subject)).toBeVisible({ timeout: timeoutMs });
6126
+ }
6127
+ /** Assert the empty state ("No support tickets found") is shown. */
6128
+ async function expectNoTickets(dialog) {
6129
+ await test$1.expect(dialog.getByText(SUPPORT_LABELS.list.noTickets)).toBeVisible({
6130
+ timeout: 15000,
6131
+ });
6132
+ }
6133
+ /** Assert a listed ticket row shows the expected status badge. */
6134
+ async function expectTicketStatusInList(dialog, subject, status) {
6135
+ await test$1.expect(getTicketRow(dialog, subject).getByText(SUPPORT_LABELS.status[status], { exact: true })).toBeVisible({ timeout: 10000 });
6136
+ }
6137
+ // ── Filters ────────────────────────────────────────────────────────────
6138
+ /** The status-filter trigger next to the user search. */
6139
+ function getStatusFilter(dialog) {
6140
+ // The SDK labels the trigger "Filter by status" (i18n key
6141
+ // tabsHumanSupportTab.filterByStatus); fall back cheaply to the displayed
6142
+ // "All Statuses" placeholder if a host overrides the aria-label.
6143
+ return dialog
6144
+ .getByRole('combobox', { name: 'Filter by status' })
6145
+ .or(dialog.getByRole('combobox').filter({ hasText: SUPPORT_LABELS.filters.allStatuses }))
6146
+ .first();
6147
+ }
6148
+ /**
6149
+ * Filter the list by ticket status ('all' clears the filter). Changing
6150
+ * the filter re-queries the backend, so this doubles as a refresh hook.
6151
+ */
6152
+ async function filterTicketsByStatus(dialog, status) {
6153
+ const label = status === 'all' ? SUPPORT_LABELS.filters.allStatuses : SUPPORT_LABELS.status[status];
6154
+ await getStatusFilter(dialog).click();
6155
+ // Radix renders the option list in a portal outside the dialog subtree.
6156
+ await asPage(dialog).getByRole('option', { name: label, exact: true }).click();
6157
+ logger.info(`Filtered tickets by status: ${label}`);
6158
+ }
6159
+ /** The "Search for User" combobox trigger (opens the requester popover). */
6160
+ function getUserFilter(dialog) {
6161
+ return dialog.getByRole('combobox', { name: SUPPORT_LABELS.filters.searchUser });
6162
+ }
6163
+ /**
6164
+ * Filter the list by requester. Types `search` into the popover's search
6165
+ * box, then clicks the entry whose label (email, else username) matches
6166
+ * `optionText`.
6167
+ */
6168
+ async function filterTicketsByUser(dialog, search, optionText = search) {
6169
+ await getUserFilter(dialog).click();
6170
+ // The popover renders in place (`portalled={false}`) so it stays inside
6171
+ // the host dialog's focus trap — queries stay dialog-scoped.
6172
+ const searchBox = dialog.getByPlaceholder(SUPPORT_LABELS.filters.searchUsersPlaceholder);
6173
+ await test$1.expect(searchBox).toBeVisible({ timeout: 10000 });
6174
+ await searchBox.fill(search);
6175
+ await dialog.getByText(optionText).first().click();
6176
+ logger.info(`Filtered tickets by user: ${search}`);
6177
+ }
6178
+ /** Clear the requester filter via the "All Users" popover entry. */
6179
+ async function clearUserFilter(dialog) {
6180
+ await getUserFilter(dialog).click();
6181
+ // In-place popover (see filterTicketsByUser) — dialog-scoped.
6182
+ await dialog.getByText(SUPPORT_LABELS.filters.allUsers, { exact: true }).first().click();
6183
+ }
6184
+ /**
6185
+ * Force the ticket list to re-query by bouncing the status filter
6186
+ * (all → open → all). Each argument change issues a fresh request for
6187
+ * uncached filter combinations, which is how specs pick up tickets
6188
+ * created out-of-band (e.g. by the agent during a chat).
6189
+ */
6190
+ async function refreshTickets(dialog) {
6191
+ await filterTicketsByStatus(dialog, 'open');
6192
+ await filterTicketsByStatus(dialog, 'all');
6193
+ }
6194
+ /**
6195
+ * Wait for a ticket to appear in the list, refreshing between polls.
6196
+ * Agent-created tickets land after the chat tool call completes
6197
+ * server-side, so allow a generous deadline.
6198
+ *
6199
+ * Pass `opts.refresh` to override the default status-filter bounce with a
6200
+ * stronger strategy (e.g. close and re-open the modal, or reload the page
6201
+ * and navigate back) — the callback runs between polls and must leave the
6202
+ * Support tab open.
6203
+ */
6204
+ async function waitForTicketInList(dialog, subject, opts = {}) {
6205
+ var _a;
6206
+ const timeoutMs = (_a = opts.timeoutMs) !== null && _a !== void 0 ? _a : 90000;
6207
+ const deadline = Date.now() + timeoutMs;
6208
+ for (;;) {
6209
+ try {
6210
+ await test$1.expect(getTicketRow(dialog, subject)).toBeVisible({ timeout: 10000 });
6211
+ logger.info(`Ticket visible in list: ${subject}`);
6212
+ return;
6213
+ }
6214
+ catch (error) {
6215
+ if (Date.now() >= deadline) {
6216
+ throw error;
6217
+ }
6218
+ logger.info(`Ticket not yet listed, refreshing: ${subject}`);
6219
+ if (opts.refresh) {
6220
+ await opts.refresh();
6221
+ }
6222
+ else {
6223
+ await refreshTickets(dialog);
6224
+ }
6225
+ }
6226
+ }
6227
+ }
6228
+ // ── Ticket detail ──────────────────────────────────────────────────────
6229
+ /** The detail pane (desktop two-column layout). */
6230
+ function getTicketDetail(dialog) {
6231
+ return dialog.getByTestId(SUPPORT_LABELS.testIds.ticketDetail);
6232
+ }
6233
+ /** The rendered ticket body (HTML/Markdown/plain, already formatted). */
6234
+ function getTicketDescription(dialog) {
6235
+ // On mobile the detail renders in a nested preview dialog instead of the
6236
+ // side pane; the testid is unique either way, so query from the page.
6237
+ return asPage(dialog).getByTestId(SUPPORT_LABELS.testIds.ticketDescription).first();
6238
+ }
6239
+ /**
6240
+ * Open a ticket from the list and wait for its detail to render. Returns
6241
+ * the detail pane locator for chained assertions.
6242
+ */
6243
+ async function openTicket(dialog, subject) {
6244
+ await getTicketRow(dialog, subject).click();
6245
+ const detail = getTicketDetail(dialog);
6246
+ await test$1.expect(detail).toBeVisible({ timeout: 10000 });
6247
+ await test$1.expect(detail).toContainText(SUPPORT_LABELS.detail.requestSection, { timeout: 10000 });
6248
+ logger.info(`Opened ticket: ${subject}`);
6249
+ return detail;
6250
+ }
6251
+ /** Assert the opened ticket's rendered body contains the given text. */
6252
+ async function expectTicketDescriptionContains(dialog, text) {
6253
+ await test$1.expect(getTicketDescription(dialog)).toContainText(text, { timeout: 10000 });
6254
+ }
6255
+ /**
6256
+ * Change the opened ticket's status via the detail pane's Status select.
6257
+ * Selecting "Closed" routes through the dedicated close endpoint and
6258
+ * replaces the composer with the closed notice.
6259
+ */
6260
+ async function setTicketStatus(dialog, status) {
6261
+ const detail = getTicketDetail(dialog);
6262
+ await detail.getByRole('combobox', { name: SUPPORT_LABELS.detail.statusLabel }).click();
6263
+ // Radix renders the option list in a portal outside the dialog subtree.
6264
+ await asPage(dialog)
6265
+ .getByRole('option', { name: SUPPORT_LABELS.status[status], exact: true })
6266
+ .click();
6267
+ logger.info(`Set ticket status: ${SUPPORT_LABELS.status[status]}`);
6268
+ }
6269
+ /** Assert the closed notice is shown (composer hidden). */
6270
+ async function expectTicketClosedNotice(dialog) {
6271
+ await test$1.expect(dialog.getByText(SUPPORT_LABELS.detail.closedNotice)).toBeVisible({
6272
+ timeout: 15000,
6273
+ });
6274
+ }
6275
+ // ── Conversation / replies ─────────────────────────────────────────────
6276
+ /** The reply composer textarea in the opened ticket. */
6277
+ function getReplyComposer(dialog) {
6278
+ return dialog.getByPlaceholder(SUPPORT_LABELS.detail.replyPlaceholder);
6279
+ }
6280
+ /**
6281
+ * Send a reply on the opened ticket and wait for the composer to clear
6282
+ * (the component empties it only after the API call succeeds).
6283
+ */
6284
+ async function replyToTicket(dialog, message) {
6285
+ const composer = getReplyComposer(dialog);
6286
+ await test$1.expect(composer).toBeVisible({ timeout: 10000 });
6287
+ await composer.fill(message);
6288
+ const send = dialog.getByRole('button', { name: SUPPORT_LABELS.detail.sendReply });
6289
+ await test$1.expect(send).toBeEnabled({ timeout: 5000 });
6290
+ await send.click();
6291
+ await test$1.expect(getReplyComposer(dialog)).toHaveValue('', { timeout: 15000 });
6292
+ logger.info(`Replied to ticket: ${message.slice(0, 40)}`);
6293
+ }
6294
+ /** Assert a message (from either side) is visible in the conversation. */
6295
+ async function expectMessageInConversation(dialog, message) {
6296
+ await test$1.expect(getTicketDetail(dialog).getByText(message).first()).toBeVisible({
6297
+ timeout: 15000,
6298
+ });
6299
+ }
6300
+ /** Assert the opened ticket has no replies yet. */
6301
+ async function expectNoRepliesYet(dialog) {
6302
+ await test$1.expect(dialog.getByText(SUPPORT_LABELS.detail.noMessages)).toBeVisible({
6303
+ timeout: 10000,
6304
+ });
6305
+ }
6306
+ // ── Chat side (requesting a ticket from the agent) ─────────────────────
6307
+ /** The main chat textbox. */
6308
+ function getChatInput(page) {
6309
+ return page.getByRole('textbox', { name: SUPPORT_LABELS.chat.inputName, exact: true });
6310
+ }
6311
+ /**
6312
+ * Send a message to the agent from the main chat surface and wait for the
6313
+ * user bubble to render. Does NOT wait for the AI response — pair with
6314
+ * `waitForAgentChatResponse`.
6315
+ */
6316
+ async function sendAgentChatMessage(page, message) {
6317
+ const input = getChatInput(page);
6318
+ await test$1.expect(input).toBeVisible({ timeout: 15000 });
6319
+ await input.fill(message);
6320
+ const send = page.getByRole('button', { name: SUPPORT_LABELS.chat.sendButton });
6321
+ await test$1.expect(send).toBeEnabled({ timeout: 10000 });
6322
+ await send.click();
6323
+ await test$1.expect(page.locator(SUPPORT_LABELS.chat.userQueryClass).filter({ hasText: message.slice(0, 60) })).toBeVisible({ timeout: 15000 });
6324
+ logger.info(`Sent chat message: ${message.slice(0, 60)}`);
6325
+ }
6326
+ /**
6327
+ * Wait for the agent's reply to finish. LLM latency plus tool execution
6328
+ * (the support-ticket tool call) makes this the slow step — default 90s.
6329
+ * Returns the reply text for content assertions.
6330
+ */
6331
+ async function waitForAgentChatResponse(page, timeoutMs = 90000) {
6332
+ var _a;
6333
+ const reply = page.locator(SUPPORT_LABELS.chat.aiResponseClass).last();
6334
+ await test$1.expect(reply).toBeVisible({ timeout: timeoutMs });
6335
+ // Streaming done — the stop button unmounts when the response completes.
6336
+ await test$1.expect(page.locator(SUPPORT_LABELS.chat.stopStreamingClass)).toBeHidden({
6337
+ timeout: timeoutMs,
6338
+ });
6339
+ const text = (_a = (await reply.textContent())) !== null && _a !== void 0 ? _a : '';
6340
+ test$1.expect(text.length).toBeGreaterThan(0);
6341
+ logger.info(`Agent replied (${text.length} chars)`);
6342
+ return text;
6343
+ }
6344
+ /**
6345
+ * Ask the agent (via chat) to open a support ticket with a distinctive
6346
+ * subject, and wait for its reply. The explicit "subject" phrasing gives
6347
+ * the support tool an exact string to file, which the spec then looks
6348
+ * for in the ticket list.
6349
+ */
6350
+ async function requestSupportTicketViaChat(page, opts) {
6351
+ var _a;
6352
+ const description = (_a = opts.description) !== null && _a !== void 0 ? _a : 'I need help from a human. Please escalate this conversation.';
6353
+ const prompt = `Please create a support ticket for me. ` +
6354
+ `Use exactly this subject: "${opts.subject}". ` +
6355
+ `Description: ${description}`;
6356
+ await sendAgentChatMessage(page, prompt);
6357
+ return waitForAgentChatResponse(page);
6358
+ }
6359
+ // ── End-to-end composition ─────────────────────────────────────────────
6360
+ /**
6361
+ * Full flow: with support enabled, ask the agent for a ticket in chat,
6362
+ * then verify it shows up in the Support tab.
6363
+ *
6364
+ * The Edit Agent modal is host-specific to open/close, so the caller
6365
+ * provides `openEditAgentModal` (and optionally `closeEditAgentModal`,
6366
+ * used to get the modal out of the chat's way before sending). Steps:
6367
+ *
6368
+ * 1. `openEditAgentModal()` → Support tab → ensure the toggle is ON.
6369
+ * 2. `closeEditAgentModal()` (when provided) → chat-request the ticket.
6370
+ * 3. `openEditAgentModal()` → Support tab → wait for the ticket row.
6371
+ *
6372
+ * Returns the dialog locator with the Support tab open and the new
6373
+ * ticket listed, so specs can keep asserting (open it, reply, close it).
6374
+ */
6375
+ async function createSupportTicketViaChatAndVerify(page, opts) {
6376
+ // 1. Make sure the support tool is on.
6377
+ await opts.openEditAgentModal();
6378
+ let dialog = await switchToSupportTab(page);
6379
+ await enableSupport(dialog);
6380
+ // 2. Ask the agent for a ticket from the chat surface.
6381
+ if (opts.closeEditAgentModal) {
6382
+ await opts.closeEditAgentModal();
6383
+ await test$1.expect(getEditAgentDialog(page)).toBeHidden({ timeout: 10000 });
6384
+ }
6385
+ await requestSupportTicketViaChat(page, {
6386
+ subject: opts.subject,
6387
+ description: opts.description,
6388
+ });
6389
+ // 3. Back to the Support tab — the ticket must be listed.
6390
+ if (opts.closeEditAgentModal) {
6391
+ await opts.openEditAgentModal();
6392
+ }
6393
+ dialog = await switchToSupportTab(page);
6394
+ await waitForTicketInList(dialog, opts.subject, { timeoutMs: opts.listTimeoutMs });
6395
+ await expectTicketStatusInList(dialog, opts.subject, 'open');
6396
+ logger.info(`Support ticket created via chat and verified: ${opts.subject}`);
6397
+ return dialog;
6398
+ }
6399
+
5883
6400
  /** Extract browser key from device name (e.g., 'Desktop Chrome' -> 'chrome') */
5884
6401
  function getBrowserKey(deviceName) {
5885
6402
  return deviceName.toLowerCase().replace(/^desktop\s+/, '');
@@ -6018,6 +6535,7 @@ exports.LTI_TEST_IDS = LTI_TEST_IDS;
6018
6535
  exports.MailsacClient = MailsacClient;
6019
6536
  exports.PRIVACY_LABELS = PRIVACY_LABELS;
6020
6537
  exports.SCREENSHARE_LABELS = SCREENSHARE_LABELS;
6538
+ exports.SUPPORT_LABELS = SUPPORT_LABELS;
6021
6539
  exports.TASKS_LABELS = TASKS_LABELS;
6022
6540
  exports.VOICE_LABELS = VOICE_LABELS;
6023
6541
  exports.addManualScore = addManualScore;
@@ -6043,6 +6561,7 @@ exports.checkAdminStatus = checkAdminStatus;
6043
6561
  exports.checkRunStatus = checkRunStatus;
6044
6562
  exports.clearDateRangeFilter = clearDateRangeFilter;
6045
6563
  exports.clearInstanceSearch = clearInstanceSearch;
6564
+ exports.clearUserFilter = clearUserFilter;
6046
6565
  exports.clickBackHome = clickBackHome;
6047
6566
  exports.clickBillingAddCredits = clickBillingAddCredits;
6048
6567
  exports.clickBillingManageBilling = clickBillingManageBilling;
@@ -6069,6 +6588,7 @@ exports.createKey = createKey;
6069
6588
  exports.createLink = createLink;
6070
6589
  exports.createPlaywrightConfig = createPlaywrightConfig;
6071
6590
  exports.createSkill = createSkill;
6591
+ exports.createSupportTicketViaChatAndVerify = createSupportTicketViaChatAndVerify;
6072
6592
  exports.createTool = createTool;
6073
6593
  exports.creditBalancePanel = creditBalancePanel;
6074
6594
  exports.creditBalancePlanBadge = creditBalancePlanBadge;
@@ -6082,6 +6602,7 @@ exports.deleteQaItem = deleteQaItem;
6082
6602
  exports.deleteSkill = deleteSkill;
6083
6603
  exports.deleteTask = deleteTask;
6084
6604
  exports.disableSkill = disableSkill;
6605
+ exports.disableSupport = disableSupport;
6085
6606
  exports.disconnectInstance = disconnectInstance;
6086
6607
  exports.editAgentPrompt = editAgentPrompt;
6087
6608
  exports.editInstance = editInstance;
@@ -6089,6 +6610,7 @@ exports.editLink = editLink;
6089
6610
  exports.editSkill = editSkill;
6090
6611
  exports.editTool = editTool;
6091
6612
  exports.enableSkill = enableSkill;
6613
+ exports.enableSupport = enableSupport;
6092
6614
  exports.expandReview = expandReview;
6093
6615
  exports.expandTrace = expandTrace;
6094
6616
  exports.expectAllEndpointsVisible = expectAllEndpointsVisible;
@@ -6129,10 +6651,13 @@ exports.expectLogDetailsStatus = expectLogDetailsStatus;
6129
6651
  exports.expectLogsForTask = expectLogsForTask;
6130
6652
  exports.expectLtiHeader = expectLtiHeader;
6131
6653
  exports.expectMentorVoiceTriggerShows = expectMentorVoiceTriggerShows;
6654
+ exports.expectMessageInConversation = expectMessageInConversation;
6132
6655
  exports.expectNoAccessibilityViolations = expectNoAccessibilityViolations;
6133
6656
  exports.expectNoAccessibilityViolationsOnDialogs = expectNoAccessibilityViolationsOnDialogs;
6134
6657
  exports.expectNoBenchmarksNotice = expectNoBenchmarksNotice;
6135
6658
  exports.expectNoLogsForSelectedTask = expectNoLogsForSelectedTask;
6659
+ exports.expectNoRepliesYet = expectNoRepliesYet;
6660
+ exports.expectNoTickets = expectNoTickets;
6136
6661
  exports.expectOutputFilterEnabled = expectOutputFilterEnabled;
6137
6662
  exports.expectPrivacyFieldsHidden = expectPrivacyFieldsHidden;
6138
6663
  exports.expectPrivacyFieldsVisible = expectPrivacyFieldsVisible;
@@ -6155,6 +6680,10 @@ exports.expectTaskStatus = expectTaskStatus;
6155
6680
  exports.expectTasksEmpty = expectTasksEmpty;
6156
6681
  exports.expectTenantChatPrivacyEnabled = expectTenantChatPrivacyEnabled;
6157
6682
  exports.expectTenantChatPrivacyVisible = expectTenantChatPrivacyVisible;
6683
+ exports.expectTicketClosedNotice = expectTicketClosedNotice;
6684
+ exports.expectTicketDescriptionContains = expectTicketDescriptionContains;
6685
+ exports.expectTicketInList = expectTicketInList;
6686
+ exports.expectTicketStatusInList = expectTicketStatusInList;
6158
6687
  exports.expectToolInList = expectToolInList;
6159
6688
  exports.expectToolNotInList = expectToolNotInList;
6160
6689
  exports.expectToolsEmpty = expectToolsEmpty;
@@ -6171,6 +6700,8 @@ exports.filterByActionAndVerify = filterByActionAndVerify;
6171
6700
  exports.filterByActor = filterByActor;
6172
6701
  exports.filterByActorAndVerify = filterByActorAndVerify;
6173
6702
  exports.filterByDateRange = filterByDateRange;
6703
+ exports.filterTicketsByStatus = filterTicketsByStatus;
6704
+ exports.filterTicketsByUser = filterTicketsByUser;
6174
6705
  exports.generateBrowserSetupProjects = generateBrowserSetupProjects;
6175
6706
  exports.generateProjectConfig = generateProjectConfig;
6176
6707
  exports.getAddItemsDialog = getAddItemsDialog;
@@ -6183,6 +6714,7 @@ exports.getBillingAutoRechargeStatus = getBillingAutoRechargeStatus;
6183
6714
  exports.getBillingPlanLabel = getBillingPlanLabel;
6184
6715
  exports.getBrowserKey = getBrowserKey;
6185
6716
  exports.getCallConfigForm = getCallConfigForm;
6717
+ exports.getChatInput = getChatInput;
6186
6718
  exports.getChatPrivacyConfirmDialog = getChatPrivacyConfirmDialog;
6187
6719
  exports.getChatPrivacyToggle = getChatPrivacyToggle;
6188
6720
  exports.getCreateBenchmarkDialog = getCreateBenchmarkDialog;
@@ -6195,6 +6727,7 @@ exports.getCurrentModel = getCurrentModel;
6195
6727
  exports.getCurrentTenantShowPaywall = getCurrentTenantShowPaywall;
6196
6728
  exports.getDeleteEvaluationDialog = getDeleteEvaluationDialog;
6197
6729
  exports.getDeleteQaItemDialog = getDeleteQaItemDialog;
6730
+ exports.getEditAgentDialog = getEditAgentDialog;
6198
6731
  exports.getEndpoint = getEndpoint;
6199
6732
  exports.getEndpointCopyButton = getEndpointCopyButton;
6200
6733
  exports.getEndpointUrl = getEndpointUrl;
@@ -6232,15 +6765,25 @@ exports.getNewEvaluationButton = getNewEvaluationButton;
6232
6765
  exports.getOutputFilterSwitch = getOutputFilterSwitch;
6233
6766
  exports.getPaginationInfo = getPaginationInfo;
6234
6767
  exports.getPrivateModeCard = getPrivateModeCard;
6768
+ exports.getReplyComposer = getReplyComposer;
6235
6769
  exports.getReviewRow = getReviewRow;
6236
6770
  exports.getRunRow = getRunRow;
6237
6771
  exports.getScheduleTaskButton = getScheduleTaskButton;
6238
6772
  exports.getSearchInput = getSearchInput;
6239
6773
  exports.getSkillRowCount = getSkillRowCount;
6240
6774
  exports.getStartEvaluationDialog = getStartEvaluationDialog;
6775
+ exports.getStatusFilter = getStatusFilter;
6776
+ exports.getSupportInfoBox = getSupportInfoBox;
6777
+ exports.getSupportTabTrigger = getSupportTabTrigger;
6778
+ exports.getSupportToggle = getSupportToggle;
6241
6779
  exports.getTaskRow = getTaskRow;
6242
6780
  exports.getTenantChatPrivacyRow = getTenantChatPrivacyRow;
6243
6781
  exports.getTenantChatPrivacySwitch = getTenantChatPrivacySwitch;
6782
+ exports.getTicketDescription = getTicketDescription;
6783
+ exports.getTicketDetail = getTicketDetail;
6784
+ exports.getTicketList = getTicketList;
6785
+ exports.getTicketRow = getTicketRow;
6786
+ exports.getTicketRowByIndex = getTicketRowByIndex;
6244
6787
  exports.getToolEditButton = getToolEditButton;
6245
6788
  exports.getToolKeySelect = getToolKeySelect;
6246
6789
  exports.getToolModal = getToolModal;
@@ -6248,6 +6791,7 @@ exports.getToolRow = getToolRow;
6248
6791
  exports.getToolsEmptyState = getToolsEmptyState;
6249
6792
  exports.getToolsSection = getToolsSection;
6250
6793
  exports.getTraceRow = getTraceRow;
6794
+ exports.getUserFilter = getUserFilter;
6251
6795
  exports.getVoiceProviderCard = getVoiceProviderCard;
6252
6796
  exports.getVoiceRow = getVoiceRow;
6253
6797
  exports.goToFirstPage = goToFirstPage;
@@ -6268,6 +6812,8 @@ exports.isPrivateModeTabVisible = isPrivateModeTabVisible;
6268
6812
  exports.isSandboxTabVisible = isSandboxTabVisible;
6269
6813
  exports.isScreenShareTabVisible = isScreenShareTabVisible;
6270
6814
  exports.isSkillEnabled = isSkillEnabled;
6815
+ exports.isSupportEnabled = isSupportEnabled;
6816
+ exports.isSupportTabVisible = isSupportTabVisible;
6271
6817
  exports.isTasksTabVisible = isTasksTabVisible;
6272
6818
  exports.isVoiceTabVisible = isVoiceTabVisible;
6273
6819
  exports.logger = logger;
@@ -6309,6 +6855,7 @@ exports.openScheduleTaskDialog = openScheduleTaskDialog;
6309
6855
  exports.openScreenSharePromptEditor = openScreenSharePromptEditor;
6310
6856
  exports.openSkillActionsMenu = openSkillActionsMenu;
6311
6857
  exports.openStartEvaluationDialog = openStartEvaluationDialog;
6858
+ exports.openTicket = openTicket;
6312
6859
  exports.parseReportUrlParams = parseReportUrlParams;
6313
6860
  exports.previewCallConfigVoiceInline = previewCallConfigVoiceInline;
6314
6861
  exports.previewMentorVoiceInline = previewMentorVoiceInline;
@@ -6318,10 +6865,13 @@ exports.readEndpointUrl = readEndpointUrl;
6318
6865
  exports.readKeyPublicJwk = readKeyPublicJwk;
6319
6866
  exports.readKeyPublicKey = readKeyPublicKey;
6320
6867
  exports.refreshEvaluationDetail = refreshEvaluationDetail;
6868
+ exports.refreshTickets = refreshTickets;
6321
6869
  exports.reliableClick = reliableClick;
6322
6870
  exports.reliableFill = reliableFill;
6323
6871
  exports.removeTraceScore = removeTraceScore;
6324
6872
  exports.renameKey = renameKey;
6873
+ exports.replyToTicket = replyToTicket;
6874
+ exports.requestSupportTicketViaChat = requestSupportTicketViaChat;
6325
6875
  exports.resetCallConfig = resetCallConfig;
6326
6876
  exports.retry = retry;
6327
6877
  exports.runConnectedInstanceChecks = runConnectedInstanceChecks;
@@ -6350,13 +6900,16 @@ exports.selectToolSigningKey = selectToolSigningKey;
6350
6900
  exports.selectTtsProvider = selectTtsProvider;
6351
6901
  exports.selectVoice = selectVoice;
6352
6902
  exports.selectVoiceProvider = selectVoiceProvider;
6903
+ exports.sendAgentChatMessage = sendAgentChatMessage;
6353
6904
  exports.setBlockMessage = setBlockMessage;
6354
6905
  exports.setCallLanguage = setCallLanguage;
6355
6906
  exports.setEnableVideo = setEnableVideo;
6356
6907
  exports.setEntitySelected = setEntitySelected;
6357
6908
  exports.setOutputFilterEnabled = setOutputFilterEnabled;
6358
6909
  exports.setScreenSharePrompt = setScreenSharePrompt;
6910
+ exports.setSupportEnabled = setSupportEnabled;
6359
6911
  exports.setTenantChatPrivacyEnabled = setTenantChatPrivacyEnabled;
6912
+ exports.setTicketStatus = setTicketStatus;
6360
6913
  exports.setUseFunctionCallingEnabled = setUseFunctionCallingEnabled;
6361
6914
  exports.setupSandboxInstance = setupSandboxInstance;
6362
6915
  exports.shouldAddNewRowWhenClickingAddRowButton = shouldAddNewRowWhenClickingAddRowButton;
@@ -6390,6 +6943,7 @@ exports.switchToPrivateModeTab = switchToPrivateModeTab;
6390
6943
  exports.switchToSandboxTab = switchToSandboxTab;
6391
6944
  exports.switchToScreenShareTab = switchToScreenShareTab;
6392
6945
  exports.switchToSkillsTab = switchToSkillsTab;
6946
+ exports.switchToSupportTab = switchToSupportTab;
6393
6947
  exports.switchToTasksTab = switchToTasksTab;
6394
6948
  exports.switchToVoiceSubTab = switchToVoiceSubTab;
6395
6949
  exports.switchToVoiceTab = switchToVoiceTab;
@@ -6423,6 +6977,7 @@ exports.verifyPreparingPhase = verifyPreparingPhase;
6423
6977
  exports.verifySkillVisible = verifySkillVisible;
6424
6978
  exports.verifySkillsEmptyState = verifySkillsEmptyState;
6425
6979
  exports.verifySkillsTabVisible = verifySkillsTabVisible;
6980
+ exports.waitForAgentChatResponse = waitForAgentChatResponse;
6426
6981
  exports.waitForAuditLogDataLoaded = waitForAuditLogDataLoaded;
6427
6982
  exports.waitForBillingTabReady = waitForBillingTabReady;
6428
6983
  exports.waitForCreditBalanceLoaded = waitForCreditBalanceLoaded;
@@ -6431,4 +6986,5 @@ exports.waitForElementStable = waitForElementStable;
6431
6986
  exports.waitForPageLoad = waitForPageLoad;
6432
6987
  exports.waitForPageReady = waitForPageReady;
6433
6988
  exports.waitForReportDownload = waitForReportDownload;
6989
+ exports.waitForTicketInList = waitForTicketInList;
6434
6990
  //# sourceMappingURL=index.cjs.map