@iblai/iblai-js 1.13.0 → 1.13.2

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.
@@ -3152,7 +3152,7 @@ const PRIVACY_LABELS = {
3152
3152
  * reach DOM that Radix renders outside the dialog subtree (popovers,
3153
3153
  * select options, etc.).
3154
3154
  */
3155
- function asPage$1(scope) {
3155
+ function asPage$3(scope) {
3156
3156
  return 'page' in scope ? scope.page() : scope;
3157
3157
  }
3158
3158
  /**
@@ -3236,7 +3236,7 @@ async function selectPrivacyAction(scope, action) {
3236
3236
  await trigger.click();
3237
3237
  // Radix Select renders options in a portal at the document root, so we
3238
3238
  // always look them up on the Page — never on the dialog Locator.
3239
- const option = asPage$1(scope).getByRole('option', {
3239
+ const option = asPage$3(scope).getByRole('option', {
3240
3240
  name: PRIVACY_LABELS.actionOptions[action],
3241
3241
  });
3242
3242
  await test$1.expect(option).toBeVisible({ timeout: 5000 });
@@ -3305,6 +3305,488 @@ async function expectOutputFilterEnabled(scope, enabled) {
3305
3305
  await test$1.expect(getOutputFilterSwitch(scope)).toHaveAttribute('aria-checked', String(enabled));
3306
3306
  }
3307
3307
 
3308
+ /**
3309
+ * Voice tab helpers — Playwright bindings for the `AgentVoiceTab` component
3310
+ * from `@iblai/web-containers`.
3311
+ *
3312
+ * The Voice tab contains two sub-tabs:
3313
+ * 1. "Voice" — provider + voice-picker, persisted via mentor settings
3314
+ * 2. "Call Configuration" — TTS / STT / LLM / video stack, persisted via the
3315
+ * `/call-configurations/` endpoint
3316
+ *
3317
+ * All UI strings mirror `AGENT_VOICE_TAB_LABELS` from
3318
+ * `@iblai/web-containers/next`. If a consumer renames a label via the
3319
+ * `labels` prop, override these constants per spec — don't edit this file.
3320
+ *
3321
+ * Selectors below use stable hooks only: `data-testid`, role + name, and
3322
+ * label-based queries. No CSS class selectors anywhere — the underlying
3323
+ * Tailwind classes are not part of the public contract.
3324
+ *
3325
+ * The helpers assume the Edit Mentor dialog is already open. Call
3326
+ * `switchToVoiceTab(page)` first; from then on every helper accepts either
3327
+ * the `Page` or the dialog `Locator`.
3328
+ */
3329
+ const VOICE_LABELS = {
3330
+ tabName: 'Voice',
3331
+ headerTitle: 'Voice',
3332
+ subTabs: {
3333
+ voice: 'Voice',
3334
+ callConfig: 'Voice call',
3335
+ },
3336
+ providers: {
3337
+ browser: 'Browser',
3338
+ openai: 'OpenAI',
3339
+ google: 'Google',
3340
+ },
3341
+ voicePickerLabel: {
3342
+ openai: 'OpenAI Voice',
3343
+ google: 'Google Voice',
3344
+ },
3345
+ saveVoiceButton: /save voice settings|saving…/i,
3346
+ // The "Save" button reads "Save" when no config exists yet and
3347
+ // "Save changes" once one does. Helpers match both via a regex.
3348
+ callConfigSaveCreate: 'Save',
3349
+ callConfigSaveUpdate: 'Save changes',
3350
+ callConfigResetButton: 'Reset',
3351
+ callConfigFields: {
3352
+ mode: 'Call style',
3353
+ language: 'Spoken language',
3354
+ llmProvider: 'AI provider',
3355
+ ttsProvider: 'Text-to-speech provider',
3356
+ sttProvider: 'Speech-to-text provider',
3357
+ useFunctionCalling: 'Look things up only when needed',
3358
+ enableVideo: 'Allow screen sharing on a call',
3359
+ },
3360
+ modeOptions: {
3361
+ realtime: 'Live conversation',
3362
+ inference: 'Step-by-step',
3363
+ },
3364
+ ttsOptions: {
3365
+ openai: 'OpenAI',
3366
+ google: 'Google',
3367
+ elevenlabs: 'ElevenLabs',
3368
+ },
3369
+ llmOptions: {
3370
+ openai: 'OpenAI',
3371
+ google: 'Google',
3372
+ anthropic: 'Anthropic',
3373
+ },
3374
+ };
3375
+ /**
3376
+ * Resolve a Page from either a Page or a Locator. Used when we need to
3377
+ * reach DOM that Radix renders outside the dialog subtree (popovers,
3378
+ * select options, etc.).
3379
+ */
3380
+ function asPage$2(scope) {
3381
+ return 'page' in scope ? scope.page() : scope;
3382
+ }
3383
+ // ─── Tab navigation ──────────────────────────────────────────────────────
3384
+ /**
3385
+ * Check whether the Voice tab is currently rendered in the Edit Mentor
3386
+ * dialog. Returns false instead of throwing so consumers can guard
3387
+ * conditionally rendered tabs.
3388
+ */
3389
+ async function isVoiceTabVisible(page) {
3390
+ const tab = page.getByRole('tab', { name: VOICE_LABELS.tabName, exact: true });
3391
+ try {
3392
+ await test$1.expect(tab).toBeVisible({ timeout: 5000 });
3393
+ return true;
3394
+ }
3395
+ catch (_a) {
3396
+ return false;
3397
+ }
3398
+ }
3399
+ /**
3400
+ * Switch to the Voice tab. Assumes the Edit Mentor dialog is open.
3401
+ */
3402
+ async function switchToVoiceTab(page) {
3403
+ const tab = page.getByRole('tab', { name: VOICE_LABELS.tabName, exact: true });
3404
+ await test$1.expect(tab).toBeVisible({ timeout: 10000 });
3405
+ await tab.click();
3406
+ // The segmented sub-tab control is unique to the Voice tab; its presence
3407
+ // confirms we landed on the right pane.
3408
+ await test$1.expect(page.getByTestId('voice-sub-tabs')).toBeVisible({ timeout: 10000 });
3409
+ logger.info('Switched to Voice tab');
3410
+ }
3411
+ /**
3412
+ * Switch between the Voice / Voice call sub-tabs.
3413
+ *
3414
+ * Note: "Screen share" used to live here as a third sub-tab but is now
3415
+ * a peer top-level tab in the edit-mentor modal. Use the helpers in
3416
+ * `screenshare-tab-helpers.ts` for it.
3417
+ */
3418
+ async function switchToVoiceSubTab(scope, sub) {
3419
+ const testId = sub === 'voice' ? 'voice-sub-tab-voice' : 'voice-sub-tab-call-config';
3420
+ const pill = scope.getByTestId(testId);
3421
+ await test$1.expect(pill).toBeVisible({ timeout: 10000 });
3422
+ await pill.click();
3423
+ await test$1.expect(pill).toHaveAttribute('aria-selected', 'true', { timeout: 10000 });
3424
+ logger.info(`Switched to "${VOICE_LABELS.subTabs[sub]}" sub-tab`);
3425
+ }
3426
+ // ─── Mentor voice section ────────────────────────────────────────────────
3427
+ /**
3428
+ * Locator for one of the three provider cards on the "Voice" sub-tab.
3429
+ */
3430
+ function getVoiceProviderCard(scope, provider) {
3431
+ return scope.getByTestId(`voice-provider-${provider}`);
3432
+ }
3433
+ /**
3434
+ * Click a provider card. The picker below changes shape based on the
3435
+ * selection (Browser → no picker; OpenAI / Google → searchable voice list).
3436
+ */
3437
+ async function selectVoiceProvider(scope, provider) {
3438
+ const card = getVoiceProviderCard(scope, provider);
3439
+ await test$1.expect(card).toBeVisible({ timeout: 10000 });
3440
+ await card.click();
3441
+ await test$1.expect(card).toHaveAttribute('aria-checked', 'true', { timeout: 10000 });
3442
+ logger.info(`Voice provider set to "${provider}"`);
3443
+ }
3444
+ async function expectVoiceProviderSelected(scope, provider) {
3445
+ await test$1.expect(getVoiceProviderCard(scope, provider)).toHaveAttribute('aria-checked', 'true');
3446
+ }
3447
+ /**
3448
+ * Open the modal voice picker for the active mentor-voice provider
3449
+ * (OpenAI or Google). The trigger renders the currently-picked voice on
3450
+ * the Voice sub-tab; clicking its "open" button opens a Dialog with
3451
+ * the searchable + paginated picker inside.
3452
+ *
3453
+ * The trigger root container exposes `data-testid="mentor-voice-trigger"`,
3454
+ * and the open button (inside it) is `mentor-voice-trigger-open`. The
3455
+ * inline play button is `mentor-voice-trigger-preview` — see
3456
+ * `previewMentorVoiceInline`.
3457
+ */
3458
+ async function openMentorVoicePicker(scope) {
3459
+ const openBtn = scope.getByTestId('mentor-voice-trigger-open');
3460
+ await test$1.expect(openBtn).toBeVisible({ timeout: 10000 });
3461
+ await openBtn.click();
3462
+ await test$1.expect(asPage$2(scope).getByTestId('voice-picker-modal')).toBeVisible({
3463
+ timeout: 10000,
3464
+ });
3465
+ }
3466
+ /**
3467
+ * Open the modal voice picker inside the Call Configuration sub-tab.
3468
+ * Only present when an LLM provider has been chosen.
3469
+ */
3470
+ async function openCallConfigVoicePicker(scope) {
3471
+ const openBtn = scope.getByTestId('call-config-voice-trigger-open');
3472
+ await test$1.expect(openBtn).toBeVisible({ timeout: 10000 });
3473
+ await openBtn.click();
3474
+ await test$1.expect(asPage$2(scope).getByTestId('voice-picker-modal')).toBeVisible({
3475
+ timeout: 10000,
3476
+ });
3477
+ }
3478
+ /**
3479
+ * Click the inline Play button on the mentor-voice trigger to preview
3480
+ * the currently-selected voice without opening the modal. Audio
3481
+ * playback isn't deterministically observable from Playwright, so this
3482
+ * just clicks and returns.
3483
+ */
3484
+ async function previewMentorVoiceInline(scope) {
3485
+ const previewBtn = scope.getByTestId('mentor-voice-trigger-preview');
3486
+ await test$1.expect(previewBtn).toBeVisible({ timeout: 10000 });
3487
+ await previewBtn.click();
3488
+ logger.info('Previewed mentor voice (inline)');
3489
+ }
3490
+ /**
3491
+ * Same as `previewMentorVoiceInline`, but for the Call Configuration
3492
+ * voice trigger.
3493
+ */
3494
+ async function previewCallConfigVoiceInline(scope) {
3495
+ const previewBtn = scope.getByTestId('call-config-voice-trigger-preview');
3496
+ await test$1.expect(previewBtn).toBeVisible({ timeout: 10000 });
3497
+ await previewBtn.click();
3498
+ logger.info('Previewed call-config voice (inline)');
3499
+ }
3500
+ /**
3501
+ * Assert that the trigger shows the given voice name (title-cased).
3502
+ * Useful for verifying that a previously-saved voice loads correctly.
3503
+ */
3504
+ async function expectMentorVoiceTriggerShows(scope, voiceName) {
3505
+ await test$1.expect(scope.getByTestId('mentor-voice-trigger')).toContainText(voiceName);
3506
+ }
3507
+ async function expectCallConfigVoiceTriggerShows(scope, voiceName) {
3508
+ await test$1.expect(scope.getByTestId('call-config-voice-trigger')).toContainText(voiceName);
3509
+ }
3510
+ /**
3511
+ * Search the voice picker. Typing is debounced ~300ms inside the
3512
+ * component, so callers usually want to assert on the eventual results
3513
+ * (see `expectVoiceVisible`). When the picker lives inside the modal,
3514
+ * scope to the dialog: `searchVoices(page.getByTestId('voice-picker-modal'), 'al')`.
3515
+ *
3516
+ * Targets the input via `data-testid="voice-picker-search"` — a stable
3517
+ * hook that doesn't depend on the (overridable) placeholder copy and
3518
+ * avoids ReDoS-shaped placeholder regexes.
3519
+ */
3520
+ async function searchVoices(scope, query) {
3521
+ const input = scope.getByTestId('voice-picker-search');
3522
+ await test$1.expect(input).toBeVisible({ timeout: 10000 });
3523
+ await input.fill(query);
3524
+ }
3525
+ /**
3526
+ * Locator for an individual voice row in the picker. Rows are
3527
+ * `role="radio"` with `aria-label` matching the voice name.
3528
+ */
3529
+ function getVoiceRow(scope, voiceName) {
3530
+ return scope.getByRole('radio', { name: voiceName, exact: true });
3531
+ }
3532
+ async function expectVoiceVisible(scope, voiceName) {
3533
+ await test$1.expect(getVoiceRow(scope, voiceName)).toBeVisible({ timeout: 10000 });
3534
+ }
3535
+ /**
3536
+ * Click a voice row to select it. Returns once the row reports
3537
+ * aria-checked=true.
3538
+ */
3539
+ async function selectVoice(scope, voiceName) {
3540
+ const row = getVoiceRow(scope, voiceName);
3541
+ await test$1.expect(row).toBeVisible({ timeout: 10000 });
3542
+ await row.click();
3543
+ await test$1.expect(row).toHaveAttribute('aria-checked', 'true', { timeout: 10000 });
3544
+ logger.info(`Selected voice "${voiceName}"`);
3545
+ }
3546
+ /**
3547
+ * Click the inline preview button next to a voice. Returns immediately —
3548
+ * audio playback is async and not deterministically observable from
3549
+ * Playwright.
3550
+ */
3551
+ async function previewVoice(scope, voiceName) {
3552
+ const previewBtn = scope.getByRole('button', { name: `Preview ${voiceName} voice` });
3553
+ await test$1.expect(previewBtn).toBeVisible({ timeout: 10000 });
3554
+ await previewBtn.click();
3555
+ }
3556
+ /**
3557
+ * Click the Save button on the Voice sub-tab. Asserts the button is
3558
+ * enabled first (a no-op on a pristine form would be a test bug).
3559
+ */
3560
+ async function saveVoiceSettings(scope) {
3561
+ const btn = scope.getByTestId('voice-save-button');
3562
+ await test$1.expect(btn).toBeVisible({ timeout: 10000 });
3563
+ await test$1.expect(btn).toBeEnabled({ timeout: 10000 });
3564
+ await btn.click();
3565
+ logger.info('Clicked Save voice settings');
3566
+ }
3567
+ // ─── Call configuration section ──────────────────────────────────────────
3568
+ /**
3569
+ * Locator for the call configuration form, useful as a scope root for
3570
+ * subsequent helpers.
3571
+ */
3572
+ function getCallConfigForm(scope) {
3573
+ return scope.getByTestId('call-config-form');
3574
+ }
3575
+ async function expectCallConfigVisible(scope) {
3576
+ await test$1.expect(getCallConfigForm(scope)).toBeVisible({ timeout: 10000 });
3577
+ }
3578
+ /**
3579
+ * Pick a call mode (realtime/inference). The TTS/STT selects below become
3580
+ * disabled in realtime mode — assert via `expectTtsSelectDisabled` if your
3581
+ * test cares.
3582
+ */
3583
+ async function selectCallMode(scope, mode) {
3584
+ const trigger = scope.getByRole('combobox', { name: VOICE_LABELS.callConfigFields.mode });
3585
+ await test$1.expect(trigger).toBeVisible({ timeout: 10000 });
3586
+ await trigger.click();
3587
+ const option = asPage$2(scope).getByRole('option', { name: VOICE_LABELS.modeOptions[mode] });
3588
+ await test$1.expect(option).toBeVisible({ timeout: 5000 });
3589
+ await option.click();
3590
+ await test$1.expect(trigger).toHaveText(new RegExp(VOICE_LABELS.modeOptions[mode]));
3591
+ logger.info(`Call mode set to "${mode}"`);
3592
+ }
3593
+ async function setCallLanguage(scope, code) {
3594
+ const input = scope.getByLabel(VOICE_LABELS.callConfigFields.language);
3595
+ await test$1.expect(input).toBeVisible({ timeout: 10000 });
3596
+ await input.fill(code);
3597
+ await test$1.expect(input).toHaveValue(code);
3598
+ }
3599
+ async function selectFromCombobox(scope, triggerName, optionName) {
3600
+ const trigger = scope.getByRole('combobox', { name: triggerName });
3601
+ await test$1.expect(trigger).toBeVisible({ timeout: 10000 });
3602
+ await trigger.click();
3603
+ const option = asPage$2(scope).getByRole('option', { name: optionName });
3604
+ await test$1.expect(option).toBeVisible({ timeout: 5000 });
3605
+ await option.click();
3606
+ }
3607
+ async function selectLlmProvider(scope, provider) {
3608
+ await selectFromCombobox(scope, VOICE_LABELS.callConfigFields.llmProvider, VOICE_LABELS.llmOptions[provider]);
3609
+ logger.info(`Call LLM provider set to "${provider}"`);
3610
+ }
3611
+ async function selectTtsProvider(scope, provider) {
3612
+ await selectFromCombobox(scope, VOICE_LABELS.callConfigFields.ttsProvider, VOICE_LABELS.ttsOptions[provider]);
3613
+ logger.info(`Call TTS provider set to "${provider}"`);
3614
+ }
3615
+ async function selectSttProvider(scope, provider) {
3616
+ await selectFromCombobox(scope, VOICE_LABELS.callConfigFields.sttProvider, VOICE_LABELS.ttsOptions[provider]);
3617
+ logger.info(`Call STT provider set to "${provider}"`);
3618
+ }
3619
+ async function expectTtsSelectDisabled(scope) {
3620
+ const trigger = scope.getByRole('combobox', {
3621
+ name: VOICE_LABELS.callConfigFields.ttsProvider,
3622
+ });
3623
+ await test$1.expect(trigger).toBeDisabled();
3624
+ }
3625
+ async function expectSttSelectDisabled(scope) {
3626
+ const trigger = scope.getByRole('combobox', {
3627
+ name: VOICE_LABELS.callConfigFields.sttProvider,
3628
+ });
3629
+ await test$1.expect(trigger).toBeDisabled();
3630
+ }
3631
+ /**
3632
+ * Generic toggle helper for the call-config switches (functionCalling /
3633
+ * enableVideo). The switch is `role="switch"`; its name has the
3634
+ * pattern `<label> enabled|disabled`.
3635
+ */
3636
+ async function toggleCallSwitch(scope, label, enabled) {
3637
+ const escapedLabel = label.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
3638
+ const toggle = scope.getByRole('switch', {
3639
+ name: new RegExp(`^${escapedLabel} (enabled|disabled)$`),
3640
+ });
3641
+ await test$1.expect(toggle).toBeVisible({ timeout: 10000 });
3642
+ const isChecked = (await toggle.getAttribute('aria-checked')) === 'true';
3643
+ if (isChecked !== enabled) {
3644
+ await toggle.click();
3645
+ await test$1.expect(toggle).toHaveAttribute('aria-checked', String(enabled), {
3646
+ timeout: 10000,
3647
+ });
3648
+ }
3649
+ }
3650
+ async function setUseFunctionCallingEnabled(scope, enabled) {
3651
+ await toggleCallSwitch(scope, VOICE_LABELS.callConfigFields.useFunctionCalling, enabled);
3652
+ logger.info(`Function calling for RAG set to ${enabled ? 'enabled' : 'disabled'}`);
3653
+ }
3654
+ async function setEnableVideo(scope, enabled) {
3655
+ await toggleCallSwitch(scope, VOICE_LABELS.callConfigFields.enableVideo, enabled);
3656
+ logger.info(`Call video set to ${enabled ? 'enabled' : 'disabled'}`);
3657
+ }
3658
+ // Note: screensharing prompts moved to a standalone top-level tab —
3659
+ // see `screenshare-tab-helpers.ts` for editor / save helpers.
3660
+ /**
3661
+ * Click the call-config Save button. The label switches between
3662
+ * "Create configuration" and "Save changes" depending on whether a config
3663
+ * exists; we match either.
3664
+ */
3665
+ async function saveCallConfig(scope) {
3666
+ const btn = scope.getByRole('button', {
3667
+ name: new RegExp(`${VOICE_LABELS.callConfigSaveCreate}|${VOICE_LABELS.callConfigSaveUpdate}`, 'i'),
3668
+ });
3669
+ await test$1.expect(btn).toBeVisible({ timeout: 10000 });
3670
+ await test$1.expect(btn).toBeEnabled({ timeout: 10000 });
3671
+ await btn.click();
3672
+ logger.info('Clicked Save call configuration');
3673
+ }
3674
+ async function resetCallConfig(scope) {
3675
+ const btn = scope.getByRole('button', { name: VOICE_LABELS.callConfigResetButton });
3676
+ await test$1.expect(btn).toBeVisible({ timeout: 10000 });
3677
+ await btn.click();
3678
+ }
3679
+
3680
+ /**
3681
+ * Screen share tab helpers — Playwright bindings for the standalone
3682
+ * `AgentScreenShareTab` component from `@iblai/web-containers`.
3683
+ *
3684
+ * The tab is a peer to Voice/LLM/Tools/etc in the edit-mentor modal.
3685
+ * It only edits the two screensharing prompts on the mentor's
3686
+ * CallConfiguration; the on/off toggle lives on the Settings tab.
3687
+ *
3688
+ * Selectors below use stable hooks only: `data-testid`, role + name,
3689
+ * and label-based queries. No CSS class selectors.
3690
+ */
3691
+ const SCREENSHARE_LABELS = {
3692
+ tabName: 'Screen Share',
3693
+ headerTitle: 'Screen Share',
3694
+ fields: {
3695
+ systemPrompt: 'Instructions during screen sharing',
3696
+ proactivePrompt: 'What the agent says when screen sharing starts',
3697
+ },
3698
+ saveButton: 'Save',
3699
+ };
3700
+ function asPage$1(scope) {
3701
+ return 'page' in scope ? scope.page() : scope;
3702
+ }
3703
+ /**
3704
+ * Returns false if the Screen share tab isn't currently rendered in
3705
+ * the Edit Mentor dialog (e.g. the host hasn't registered it, or it's
3706
+ * hidden by RBAC).
3707
+ */
3708
+ async function isScreenShareTabVisible(page) {
3709
+ const tab = page.getByRole('tab', { name: SCREENSHARE_LABELS.tabName, exact: true });
3710
+ try {
3711
+ await test$1.expect(tab).toBeVisible({ timeout: 5000 });
3712
+ return true;
3713
+ }
3714
+ catch (_a) {
3715
+ return false;
3716
+ }
3717
+ }
3718
+ /**
3719
+ * Switch to the Screen share top-level tab. Assumes the Edit Mentor
3720
+ * dialog is open.
3721
+ */
3722
+ async function switchToScreenShareTab(page) {
3723
+ const tab = page.getByRole('tab', { name: SCREENSHARE_LABELS.tabName, exact: true });
3724
+ await test$1.expect(tab).toBeVisible({ timeout: 10000 });
3725
+ await tab.click();
3726
+ // The tab body's testid confirms we landed on the right pane.
3727
+ await test$1.expect(page.getByTestId('screenshare-tab-body')).toBeVisible({ timeout: 10000 });
3728
+ logger.info('Switched to Screen share tab');
3729
+ }
3730
+ /**
3731
+ * Open the inline prompt editor for one of the two screensharing
3732
+ * prompts. Mirrors the Prompts tab pattern: each prompt is rendered as
3733
+ * a card with an "Edit <label>" button that pops the rich-text modal.
3734
+ */
3735
+ async function openScreenSharePromptEditor(scope, field) {
3736
+ const label = SCREENSHARE_LABELS.fields[field];
3737
+ const btn = scope.getByRole('button', { name: `Edit ${label}` });
3738
+ await test$1.expect(btn).toBeVisible({ timeout: 10000 });
3739
+ await btn.click();
3740
+ await test$1.expect(asPage$1(scope).getByText(`Edit ${label}`)).toBeVisible({ timeout: 10000 });
3741
+ logger.info(`Opened screen-share ${field} editor`);
3742
+ }
3743
+ /**
3744
+ * Replace the prompt text inside the rich-text modal and confirm. The
3745
+ * new value is written to local form state; call `saveScreenSharePrompts`
3746
+ * afterwards to persist.
3747
+ */
3748
+ async function setScreenSharePrompt(scope, field, text) {
3749
+ await openScreenSharePromptEditor(scope, field);
3750
+ const page = asPage$1(scope);
3751
+ const editor = page.getByRole('dialog').locator('[contenteditable="true"]').first();
3752
+ await test$1.expect(editor).toBeVisible({ timeout: 10000 });
3753
+ await editor.click();
3754
+ await page.keyboard.press('ControlOrMeta+A');
3755
+ await page.keyboard.press('Delete');
3756
+ await editor.pressSequentially(text);
3757
+ // The modal's Save button — scope to dialog so we don't grab the
3758
+ // outer tab's Save.
3759
+ await page.getByRole('dialog').getByRole('button', { name: /^save$/i }).click();
3760
+ await test$1.expect(page.getByRole('dialog')).toBeHidden({ timeout: 10000 });
3761
+ logger.info(`Set screen-share ${field}`);
3762
+ }
3763
+ /**
3764
+ * Click the tab's Save button to persist any pending prompt edits.
3765
+ * Asserts the button is enabled first — a no-op call on a pristine
3766
+ * tab would be a test bug.
3767
+ */
3768
+ async function saveScreenSharePrompts(scope) {
3769
+ const btn = scope.getByTestId('screenshare-save-button');
3770
+ await test$1.expect(btn).toBeVisible({ timeout: 10000 });
3771
+ await test$1.expect(btn).toBeEnabled({ timeout: 10000 });
3772
+ await btn.click();
3773
+ logger.info('Clicked Save on Screen share tab');
3774
+ }
3775
+ /**
3776
+ * Assert whether the amber off-state hint is shown. The hint renders
3777
+ * when `enable_video` is false on the underlying CallConfiguration
3778
+ * (or when no call_configuration exists yet).
3779
+ */
3780
+ async function expectScreenShareDisabledHint(scope, visible) {
3781
+ const hint = scope.getByTestId('screenshare-disabled-hint');
3782
+ if (visible) {
3783
+ await test$1.expect(hint).toBeVisible({ timeout: 10000 });
3784
+ }
3785
+ else {
3786
+ await test$1.expect(hint).toBeHidden();
3787
+ }
3788
+ }
3789
+
3308
3790
  const DEFAULT_TIMEOUT = 10000;
3309
3791
  /** Locator for the Plan section card on the BillingTab. */
3310
3792
  function billingPlanSection(page) {
@@ -3930,7 +4412,9 @@ exports.AuthFlowBuilder = AuthFlowBuilder;
3930
4412
  exports.CustomReporter = CustomReporter;
3931
4413
  exports.MailsacClient = MailsacClient;
3932
4414
  exports.PRIVACY_LABELS = PRIVACY_LABELS;
4415
+ exports.SCREENSHARE_LABELS = SCREENSHARE_LABELS;
3933
4416
  exports.TASKS_LABELS = TASKS_LABELS;
4417
+ exports.VOICE_LABELS = VOICE_LABELS;
3934
4418
  exports.addMemory = addMemory;
3935
4419
  exports.archiveFirstMemory = archiveFirstMemory;
3936
4420
  exports.archiveMemoryByContent = archiveMemoryByContent;
@@ -3982,6 +4466,8 @@ exports.expectBillingTabForCurrentPlan = expectBillingTabForCurrentPlan;
3982
4466
  exports.expectBillingTabForFreePlan = expectBillingTabForFreePlan;
3983
4467
  exports.expectBillingTabForPremiumPlan = expectBillingTabForPremiumPlan;
3984
4468
  exports.expectBillingTabForTrialPlan = expectBillingTabForTrialPlan;
4469
+ exports.expectCallConfigVisible = expectCallConfigVisible;
4470
+ exports.expectCallConfigVoiceTriggerShows = expectCallConfigVoiceTriggerShows;
3985
4471
  exports.expectCompletedTasks = expectCompletedTasks;
3986
4472
  exports.expectCreditBalanceForCurrentPlan = expectCreditBalanceForCurrentPlan;
3987
4473
  exports.expectCreditBalancePanelForFreePlan = expectCreditBalancePanelForFreePlan;
@@ -3992,6 +4478,7 @@ exports.expectEntitySelected = expectEntitySelected;
3992
4478
  exports.expectFailedTasks = expectFailedTasks;
3993
4479
  exports.expectLogDetailsStatus = expectLogDetailsStatus;
3994
4480
  exports.expectLogsForTask = expectLogsForTask;
4481
+ exports.expectMentorVoiceTriggerShows = expectMentorVoiceTriggerShows;
3995
4482
  exports.expectNoAccessibilityViolations = expectNoAccessibilityViolations;
3996
4483
  exports.expectNoAccessibilityViolationsOnDialogs = expectNoAccessibilityViolationsOnDialogs;
3997
4484
  exports.expectNoLogsForSelectedTask = expectNoLogsForSelectedTask;
@@ -4000,11 +4487,16 @@ exports.expectPrivacyFieldsHidden = expectPrivacyFieldsHidden;
4000
4487
  exports.expectPrivacyFieldsVisible = expectPrivacyFieldsVisible;
4001
4488
  exports.expectPrivacyRouterEnabled = expectPrivacyRouterEnabled;
4002
4489
  exports.expectScheduleStartTimeInPastError = expectScheduleStartTimeInPastError;
4490
+ exports.expectScreenShareDisabledHint = expectScreenShareDisabledHint;
4491
+ exports.expectSttSelectDisabled = expectSttSelectDisabled;
4003
4492
  exports.expectTaskInList = expectTaskInList;
4004
4493
  exports.expectTaskNotInList = expectTaskNotInList;
4005
4494
  exports.expectTaskStatus = expectTaskStatus;
4006
4495
  exports.expectTasksEmpty = expectTasksEmpty;
4007
4496
  exports.expectTotalTasks = expectTotalTasks;
4497
+ exports.expectTtsSelectDisabled = expectTtsSelectDisabled;
4498
+ exports.expectVoiceProviderSelected = expectVoiceProviderSelected;
4499
+ exports.expectVoiceVisible = expectVoiceVisible;
4008
4500
  exports.filterByAction = filterByAction;
4009
4501
  exports.filterByActionAndVerify = filterByActionAndVerify;
4010
4502
  exports.filterByActor = filterByActor;
@@ -4017,6 +4509,7 @@ exports.getAvailableActors = getAvailableActors;
4017
4509
  exports.getBillingAutoRechargeStatus = getBillingAutoRechargeStatus;
4018
4510
  exports.getBillingPlanLabel = getBillingPlanLabel;
4019
4511
  exports.getBrowserKey = getBrowserKey;
4512
+ exports.getCallConfigForm = getCallConfigForm;
4020
4513
  exports.getCreditBalancePlanLabel = getCreditBalancePlanLabel;
4021
4514
  exports.getCreditBalanceRemaining = getCreditBalanceRemaining;
4022
4515
  exports.getCurrentModel = getCurrentModel;
@@ -4034,6 +4527,8 @@ exports.getScheduleTaskButton = getScheduleTaskButton;
4034
4527
  exports.getSearchInput = getSearchInput;
4035
4528
  exports.getSkillRowCount = getSkillRowCount;
4036
4529
  exports.getTaskRow = getTaskRow;
4530
+ exports.getVoiceProviderCard = getVoiceProviderCard;
4531
+ exports.getVoiceRow = getVoiceRow;
4037
4532
  exports.goToFirstPage = goToFirstPage;
4038
4533
  exports.goToLastPage = goToLastPage;
4039
4534
  exports.goToNextPage = goToNextPage;
@@ -4047,8 +4542,10 @@ exports.isOnFirstPage = isOnFirstPage;
4047
4542
  exports.isOnLastPage = isOnLastPage;
4048
4543
  exports.isPrivacyTabVisible = isPrivacyTabVisible;
4049
4544
  exports.isSandboxTabVisible = isSandboxTabVisible;
4545
+ exports.isScreenShareTabVisible = isScreenShareTabVisible;
4050
4546
  exports.isSkillEnabled = isSkillEnabled;
4051
4547
  exports.isTasksTabVisible = isTasksTabVisible;
4548
+ exports.isVoiceTabVisible = isVoiceTabVisible;
4052
4549
  exports.logger = logger;
4053
4550
  exports.loginWithEmailAndPassword = loginWithEmailAndPassword;
4054
4551
  exports.loginWithMicrosoftIdp = loginWithMicrosoftIdp;
@@ -4059,35 +4556,56 @@ exports.navigateToDataReports = navigateToDataReports;
4059
4556
  exports.navigateToReportDownload = navigateToReportDownload;
4060
4557
  exports.openAddMemoryDialog = openAddMemoryDialog;
4061
4558
  exports.openAgentPromptEditModal = openAgentPromptEditModal;
4559
+ exports.openCallConfigVoicePicker = openCallConfigVoicePicker;
4062
4560
  exports.openCreditBalanceDropdown = openCreditBalanceDropdown;
4063
4561
  exports.openEditInstanceDialog = openEditInstanceDialog;
4064
4562
  exports.openEditSkillDialog = openEditSkillDialog;
4065
4563
  exports.openFirstLogDetails = openFirstLogDetails;
4066
4564
  exports.openInstanceActionsMenu = openInstanceActionsMenu;
4067
4565
  exports.openLLMProviderPicker = openLLMProviderPicker;
4566
+ exports.openMentorVoicePicker = openMentorVoicePicker;
4068
4567
  exports.openNewInstanceDialog = openNewInstanceDialog;
4069
4568
  exports.openNewSkillDialog = openNewSkillDialog;
4070
4569
  exports.openScheduleTaskDialog = openScheduleTaskDialog;
4570
+ exports.openScreenSharePromptEditor = openScreenSharePromptEditor;
4071
4571
  exports.openSkillActionsMenu = openSkillActionsMenu;
4072
4572
  exports.parseReportUrlParams = parseReportUrlParams;
4573
+ exports.previewCallConfigVoiceInline = previewCallConfigVoiceInline;
4574
+ exports.previewMentorVoiceInline = previewMentorVoiceInline;
4575
+ exports.previewVoice = previewVoice;
4073
4576
  exports.pushConfiguration = pushConfiguration;
4074
4577
  exports.reliableClick = reliableClick;
4075
4578
  exports.reliableFill = reliableFill;
4579
+ exports.resetCallConfig = resetCallConfig;
4076
4580
  exports.retry = retry;
4077
4581
  exports.runConnectedInstanceChecks = runConnectedInstanceChecks;
4078
4582
  exports.runInstanceChecks = runInstanceChecks;
4079
4583
  exports.safeWaitForURL = safeWaitForURL;
4584
+ exports.saveCallConfig = saveCallConfig;
4585
+ exports.saveScreenSharePrompts = saveScreenSharePrompts;
4586
+ exports.saveVoiceSettings = saveVoiceSettings;
4080
4587
  exports.scheduleTask = scheduleTask;
4081
4588
  exports.searchInstances = searchInstances;
4082
4589
  exports.searchTasks = searchTasks;
4590
+ exports.searchVoices = searchVoices;
4591
+ exports.selectCallMode = selectCallMode;
4083
4592
  exports.selectDateFromCalendar = selectDateFromCalendar;
4084
4593
  exports.selectLLMModel = selectLLMModel;
4594
+ exports.selectLlmProvider = selectLlmProvider;
4085
4595
  exports.selectPrivacyAction = selectPrivacyAction;
4596
+ exports.selectSttProvider = selectSttProvider;
4086
4597
  exports.selectTaskInList = selectTaskInList;
4598
+ exports.selectTtsProvider = selectTtsProvider;
4599
+ exports.selectVoice = selectVoice;
4600
+ exports.selectVoiceProvider = selectVoiceProvider;
4087
4601
  exports.setBlockMessage = setBlockMessage;
4602
+ exports.setCallLanguage = setCallLanguage;
4603
+ exports.setEnableVideo = setEnableVideo;
4088
4604
  exports.setEntitySelected = setEntitySelected;
4089
4605
  exports.setOutputFilterEnabled = setOutputFilterEnabled;
4090
4606
  exports.setPrivacyRouterEnabled = setPrivacyRouterEnabled;
4607
+ exports.setScreenSharePrompt = setScreenSharePrompt;
4608
+ exports.setUseFunctionCallingEnabled = setUseFunctionCallingEnabled;
4091
4609
  exports.setupSandboxInstance = setupSandboxInstance;
4092
4610
  exports.shouldAddNewRowWhenClickingAddRowButton = shouldAddNewRowWhenClickingAddRowButton;
4093
4611
  exports.shouldAllowEditingCellValuesInCSVEditor = shouldAllowEditingCellValuesInCSVEditor;
@@ -4109,8 +4627,11 @@ exports.signUpWithEmailAndPassword = signUpWithEmailAndPassword;
4109
4627
  exports.switchToMemoryTab = switchToMemoryTab;
4110
4628
  exports.switchToPrivacyTab = switchToPrivacyTab;
4111
4629
  exports.switchToSandboxTab = switchToSandboxTab;
4630
+ exports.switchToScreenShareTab = switchToScreenShareTab;
4112
4631
  exports.switchToSkillsTab = switchToSkillsTab;
4113
4632
  exports.switchToTasksTab = switchToTasksTab;
4633
+ exports.switchToVoiceSubTab = switchToVoiceSubTab;
4634
+ exports.switchToVoiceTab = switchToVoiceTab;
4114
4635
  exports.teardownSandboxInstance = teardownSandboxInstance;
4115
4636
  exports.test = test;
4116
4637
  exports.toggleAutoPush = toggleAutoPush;