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