@iblai/iblai-js 2.4.4 → 2.5.0
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/agent-skills-helpers.d.ts +23 -10
- package/dist/data-layer/playwright/index.d.ts +3 -1
- package/dist/data-layer/playwright/spend-limits-helpers.d.ts +275 -0
- package/dist/playwright/index.cjs +770 -66
- package/dist/playwright/index.cjs.map +1 -1
- package/dist/playwright/index.d.ts +300 -12
- package/dist/playwright/index.esm.js +737 -67
- package/dist/playwright/index.esm.js.map +1 -1
- package/dist/playwright/playwright/agent-skills-helpers.d.ts +23 -10
- package/dist/playwright/playwright/index.d.ts +3 -1
- package/dist/playwright/playwright/spend-limits-helpers.d.ts +275 -0
- package/dist/security/playwright/agent-skills-helpers.d.ts +23 -10
- package/dist/security/playwright/index.d.ts +3 -1
- package/dist/security/playwright/spend-limits-helpers.d.ts +275 -0
- package/dist/web-containers/playwright/agent-skills-helpers.d.ts +23 -10
- package/dist/web-containers/playwright/index.d.ts +3 -1
- package/dist/web-containers/playwright/spend-limits-helpers.d.ts +275 -0
- package/dist/web-containers/source/index.esm.js +24662 -22460
- package/dist/web-containers/source/next/index.esm.js +1969 -268
- package/dist/web-utils/playwright/agent-skills-helpers.d.ts +23 -10
- package/dist/web-utils/playwright/index.d.ts +3 -1
- package/dist/web-utils/playwright/spend-limits-helpers.d.ts +275 -0
- package/package.json +5 -5
|
@@ -2329,8 +2329,9 @@ async function verifySkillsTabVisible(page) {
|
|
|
2329
2329
|
logger.info('Skills section is visible with Agent/Available sub-tabs');
|
|
2330
2330
|
}
|
|
2331
2331
|
/**
|
|
2332
|
-
* Switch to the "Agent Skills" sub-tab (the agent's
|
|
2333
|
-
* toggles; the default sub-tab) and
|
|
2332
|
+
* Switch to the "Agent Skills" sub-tab (the agent's assigned skills with
|
|
2333
|
+
* enable toggles, paged server-side 10 per page; the default sub-tab) and
|
|
2334
|
+
* wait for its panel to render.
|
|
2334
2335
|
*/
|
|
2335
2336
|
async function switchToAgentSkillsSubTab(page) {
|
|
2336
2337
|
await page.getByTestId('agent-skills-tab-agent').click();
|
|
@@ -2416,8 +2417,8 @@ async function addSkillToAgent(page, skillName) {
|
|
|
2416
2417
|
logger.info(`Added skill "${skillName}" to the agent`);
|
|
2417
2418
|
}
|
|
2418
2419
|
/**
|
|
2419
|
-
* Verify a catalog row shows the "Added" chip (
|
|
2420
|
-
*
|
|
2420
|
+
* Verify a catalog row shows the "Added" chip (the skill is assigned to the
|
|
2421
|
+
* agent) and offers no Add button.
|
|
2421
2422
|
*/
|
|
2422
2423
|
async function verifySkillAdded(page, skillName) {
|
|
2423
2424
|
const row = availableSkillRow(page, skillName);
|
|
@@ -2426,8 +2427,10 @@ async function verifySkillAdded(page, skillName) {
|
|
|
2426
2427
|
logger.info(`Skill "${skillName}" shows as Added`);
|
|
2427
2428
|
}
|
|
2428
2429
|
/**
|
|
2429
|
-
* Open the three-dots actions menu for a skill row
|
|
2430
|
-
*
|
|
2430
|
+
* Open the three-dots actions menu for a skill row on the active sub-tab.
|
|
2431
|
+
* Agent-tab rows offer "Remove from Agent" only; catalog rows offer
|
|
2432
|
+
* Edit/Delete (for editable skills). The menu content portals to
|
|
2433
|
+
* document.body, so the returned locator is page-level by design.
|
|
2431
2434
|
*/
|
|
2432
2435
|
async function openSkillActionsMenu(page, skillName) {
|
|
2433
2436
|
const actionsButton = page.getByRole('button', { name: `${skillName} actions` });
|
|
@@ -2503,6 +2506,23 @@ async function toggleSkill(page, skillName) {
|
|
|
2503
2506
|
await enableSkill(page, skillName);
|
|
2504
2507
|
return true;
|
|
2505
2508
|
}
|
|
2509
|
+
/**
|
|
2510
|
+
* Set a skill's platform-wide enabled flag via its "Available Skills"
|
|
2511
|
+
* catalog row Switch (skill-level PATCH — the agent-tab switches PATCH the
|
|
2512
|
+
* assignment instead). No-op when already in the requested state; waits for
|
|
2513
|
+
* the success toast otherwise. Read-only featured rows have no switch.
|
|
2514
|
+
*/
|
|
2515
|
+
async function setCatalogSkillEnabled(page, skillName, enabled) {
|
|
2516
|
+
const switchEl = availableSkillRow(page, skillName).getByRole('switch');
|
|
2517
|
+
await test$1.expect(switchEl).toBeVisible({ timeout: 10000 });
|
|
2518
|
+
if ((await switchEl.isChecked()) === enabled) {
|
|
2519
|
+
logger.info(`Catalog skill "${skillName}" already ${enabled ? 'enabled' : 'disabled'}`);
|
|
2520
|
+
return;
|
|
2521
|
+
}
|
|
2522
|
+
await switchEl.click();
|
|
2523
|
+
await test$1.expect(page.getByText(`${skillName} ${enabled ? 'enabled' : 'disabled'}`, { exact: false })).toBeVisible({ timeout: 10000 });
|
|
2524
|
+
logger.info(`${enabled ? 'Enabled' : 'Disabled'} catalog skill "${skillName}"`);
|
|
2525
|
+
}
|
|
2506
2526
|
/**
|
|
2507
2527
|
* Open the "New Skill" dialog from the Skills section. Returns the dialog
|
|
2508
2528
|
* locator — resolve every field from it (the dialog stacks over the
|
|
@@ -2573,7 +2593,9 @@ async function createSkill(page, values) {
|
|
|
2573
2593
|
logger.info(`Created skill "${values.name}"`);
|
|
2574
2594
|
}
|
|
2575
2595
|
/**
|
|
2576
|
-
* Open the Edit Skill dialog for a row (lands on the General
|
|
2596
|
+
* Open the Edit Skill dialog for a catalog row (lands on the General
|
|
2597
|
+
* sub-tab). Skill edit/delete actions live on the "Available Skills"
|
|
2598
|
+
* sub-tab — switch there first (`switchToAvailableSkillsSubTab`).
|
|
2577
2599
|
* Returns the dialog locator — resolve fields and sub-tabs from it.
|
|
2578
2600
|
*/
|
|
2579
2601
|
async function openEditSkillDialog(page, skillName) {
|
|
@@ -2583,7 +2605,8 @@ async function openEditSkillDialog(page, skillName) {
|
|
|
2583
2605
|
return waitForDialogReady(page, dialog);
|
|
2584
2606
|
}
|
|
2585
2607
|
/**
|
|
2586
|
-
* Edit an existing platform-level skill (General sub-tab fields)
|
|
2608
|
+
* Edit an existing platform-level skill (General sub-tab fields) from its
|
|
2609
|
+
* catalog row — switch to the "Available Skills" sub-tab first.
|
|
2587
2610
|
*/
|
|
2588
2611
|
async function editSkill(page, skillName, updates) {
|
|
2589
2612
|
const dialog = await openEditSkillDialog(page, skillName);
|
|
@@ -2621,8 +2644,8 @@ async function editSkill(page, skillName, updates) {
|
|
|
2621
2644
|
logger.info(`Edited skill "${skillName}"`);
|
|
2622
2645
|
}
|
|
2623
2646
|
/**
|
|
2624
|
-
* Delete a platform-level skill via its row menu
|
|
2625
|
-
* "Delete Skill" dialog.
|
|
2647
|
+
* Delete a platform-level skill via its catalog row menu (switch to the
|
|
2648
|
+
* "Available Skills" sub-tab first), confirming the "Delete Skill" dialog.
|
|
2626
2649
|
*/
|
|
2627
2650
|
async function deleteSkill(page, skillName) {
|
|
2628
2651
|
const menu = await openSkillActionsMenu(page, skillName);
|
|
@@ -4359,19 +4382,19 @@ const GRADER_LABELS = {
|
|
|
4359
4382
|
both: 'Overall + per Criterion',
|
|
4360
4383
|
},
|
|
4361
4384
|
};
|
|
4362
|
-
const UI_TIMEOUT = 10000;
|
|
4363
|
-
const MUTATION_TIMEOUT = 15000;
|
|
4385
|
+
const UI_TIMEOUT$1 = 10000;
|
|
4386
|
+
const MUTATION_TIMEOUT$1 = 15000;
|
|
4364
4387
|
/**
|
|
4365
4388
|
* The edit-agent dialog that hosts the settings tabs. Scoping through the
|
|
4366
4389
|
* dialog first keeps every subsequent query away from same-named elements
|
|
4367
4390
|
* elsewhere on the page (nested portals, background page content).
|
|
4368
4391
|
*/
|
|
4369
|
-
function editAgentDialog(page) {
|
|
4392
|
+
function editAgentDialog$1(page) {
|
|
4370
4393
|
return page.getByRole('dialog').filter({ has: page.getByRole('tablist') });
|
|
4371
4394
|
}
|
|
4372
4395
|
/** The Grader tab's body, scoped through the edit-agent dialog. */
|
|
4373
4396
|
function graderTabBody(page) {
|
|
4374
|
-
return editAgentDialog(page).getByTestId('grader-tab-body');
|
|
4397
|
+
return editAgentDialog$1(page).getByTestId('grader-tab-body');
|
|
4375
4398
|
}
|
|
4376
4399
|
/** The Rubric sub-tab's section within the tab body. */
|
|
4377
4400
|
function criteriaSection(page) {
|
|
@@ -4388,7 +4411,7 @@ function criterionRow(page, name) {
|
|
|
4388
4411
|
* edit-agent dialog (host didn't register it, or RBAC hid it).
|
|
4389
4412
|
*/
|
|
4390
4413
|
async function isGraderTabVisible(page) {
|
|
4391
|
-
const tab = editAgentDialog(page).getByRole('tab', {
|
|
4414
|
+
const tab = editAgentDialog$1(page).getByRole('tab', {
|
|
4392
4415
|
name: GRADER_LABELS.tabName,
|
|
4393
4416
|
exact: true,
|
|
4394
4417
|
});
|
|
@@ -4405,11 +4428,11 @@ async function isGraderTabVisible(page) {
|
|
|
4405
4428
|
* open. Completion is gated on the tab body's testid, not on timing.
|
|
4406
4429
|
*/
|
|
4407
4430
|
async function switchToGraderTab(page) {
|
|
4408
|
-
const dialog = editAgentDialog(page);
|
|
4431
|
+
const dialog = editAgentDialog$1(page);
|
|
4409
4432
|
const tab = dialog.getByRole('tab', { name: GRADER_LABELS.tabName, exact: true });
|
|
4410
|
-
await test$1.expect(tab).toBeVisible({ timeout: UI_TIMEOUT });
|
|
4433
|
+
await test$1.expect(tab).toBeVisible({ timeout: UI_TIMEOUT$1 });
|
|
4411
4434
|
await tab.click();
|
|
4412
|
-
await test$1.expect(graderTabBody(page)).toBeVisible({ timeout: UI_TIMEOUT });
|
|
4435
|
+
await test$1.expect(graderTabBody(page)).toBeVisible({ timeout: UI_TIMEOUT$1 });
|
|
4413
4436
|
logger.info('Switched to Grader tab');
|
|
4414
4437
|
}
|
|
4415
4438
|
/**
|
|
@@ -4419,7 +4442,7 @@ async function switchToGraderTab(page) {
|
|
|
4419
4442
|
async function switchToGraderSubTab(page, subTab) {
|
|
4420
4443
|
const body = graderTabBody(page);
|
|
4421
4444
|
const trigger = body.getByTestId(`grader-sub-tab-${subTab}`);
|
|
4422
|
-
await test$1.expect(trigger).toBeVisible({ timeout: UI_TIMEOUT });
|
|
4445
|
+
await test$1.expect(trigger).toBeVisible({ timeout: UI_TIMEOUT$1 });
|
|
4423
4446
|
await trigger.click();
|
|
4424
4447
|
const sectionTestId = {
|
|
4425
4448
|
setup: 'grader-setup-section',
|
|
@@ -4427,13 +4450,13 @@ async function switchToGraderSubTab(page, subTab) {
|
|
|
4427
4450
|
results: 'grader-results-section',
|
|
4428
4451
|
}[subTab];
|
|
4429
4452
|
const section = body.getByTestId(sectionTestId);
|
|
4430
|
-
await test$1.expect(section).toBeVisible({ timeout: UI_TIMEOUT });
|
|
4453
|
+
await test$1.expect(section).toBeVisible({ timeout: UI_TIMEOUT$1 });
|
|
4431
4454
|
logger.info(`Switched to Grader ${subTab} sub-tab`);
|
|
4432
4455
|
}
|
|
4433
4456
|
/** Read the current on/off state of the Grading capability toggle. */
|
|
4434
4457
|
async function isGradingEnabled(page) {
|
|
4435
4458
|
const toggle = graderTabBody(page).getByTestId('grader-capability-toggle');
|
|
4436
|
-
await test$1.expect(toggle).toBeVisible({ timeout: UI_TIMEOUT });
|
|
4459
|
+
await test$1.expect(toggle).toBeVisible({ timeout: UI_TIMEOUT$1 });
|
|
4437
4460
|
return (await toggle.getAttribute('aria-checked')) === 'true';
|
|
4438
4461
|
}
|
|
4439
4462
|
/**
|
|
@@ -4445,35 +4468,35 @@ async function isGradingEnabled(page) {
|
|
|
4445
4468
|
async function setGradingEnabled(page, enabled) {
|
|
4446
4469
|
const body = graderTabBody(page);
|
|
4447
4470
|
const toggle = body.getByTestId('grader-capability-toggle');
|
|
4448
|
-
await test$1.expect(toggle).toBeVisible({ timeout: UI_TIMEOUT });
|
|
4471
|
+
await test$1.expect(toggle).toBeVisible({ timeout: UI_TIMEOUT$1 });
|
|
4449
4472
|
if ((await toggle.getAttribute('aria-checked')) === String(enabled)) {
|
|
4450
4473
|
logger.info(`Grading already ${enabled ? 'enabled' : 'disabled'} — no toggle needed`);
|
|
4451
4474
|
return;
|
|
4452
4475
|
}
|
|
4453
|
-
await test$1.expect(toggle).toBeEnabled({ timeout: UI_TIMEOUT });
|
|
4476
|
+
await test$1.expect(toggle).toBeEnabled({ timeout: UI_TIMEOUT$1 });
|
|
4454
4477
|
await toggle.click();
|
|
4455
4478
|
const toastText = enabled ? GRADER_LABELS.toasts.toggleOn : GRADER_LABELS.toasts.toggleOff;
|
|
4456
4479
|
// .first() is deliberate: rapid toggles can stack identical sonner toasts,
|
|
4457
4480
|
// and any one of them proves the PATCH resolved.
|
|
4458
|
-
await test$1.expect(page.getByText(toastText).first()).toBeVisible({ timeout: MUTATION_TIMEOUT });
|
|
4481
|
+
await test$1.expect(page.getByText(toastText).first()).toBeVisible({ timeout: MUTATION_TIMEOUT$1 });
|
|
4459
4482
|
await test$1.expect(toggle).toHaveAttribute('aria-checked', String(enabled), {
|
|
4460
|
-
timeout: MUTATION_TIMEOUT,
|
|
4483
|
+
timeout: MUTATION_TIMEOUT$1,
|
|
4461
4484
|
});
|
|
4462
|
-
await test$1.expect(body.getByTestId('capability-gate-content')).toHaveAttribute('data-enabled', String(enabled), { timeout: UI_TIMEOUT });
|
|
4485
|
+
await test$1.expect(body.getByTestId('capability-gate-content')).toHaveAttribute('data-enabled', String(enabled), { timeout: UI_TIMEOUT$1 });
|
|
4463
4486
|
logger.info(`Grading ${enabled ? 'enabled' : 'disabled'}`);
|
|
4464
4487
|
}
|
|
4465
4488
|
/** Pick an option in one of the setup form's two Radix selects. */
|
|
4466
|
-
async function pickSelectOption(page, triggerTestId, optionLabel) {
|
|
4489
|
+
async function pickSelectOption$1(page, triggerTestId, optionLabel) {
|
|
4467
4490
|
const trigger = graderTabBody(page).getByTestId(triggerTestId);
|
|
4468
|
-
await test$1.expect(trigger).toBeVisible({ timeout: UI_TIMEOUT });
|
|
4491
|
+
await test$1.expect(trigger).toBeVisible({ timeout: UI_TIMEOUT$1 });
|
|
4469
4492
|
await trigger.click();
|
|
4470
4493
|
// Radix renders the listbox in a portal outside the dialog, so the
|
|
4471
4494
|
// option is looked up by role at page level — the open listbox is the
|
|
4472
4495
|
// only one in the document.
|
|
4473
4496
|
const option = page.getByRole('option', { name: optionLabel, exact: true });
|
|
4474
|
-
await test$1.expect(option).toBeVisible({ timeout: UI_TIMEOUT });
|
|
4497
|
+
await test$1.expect(option).toBeVisible({ timeout: UI_TIMEOUT$1 });
|
|
4475
4498
|
await option.click();
|
|
4476
|
-
await test$1.expect(option).toBeHidden({ timeout: UI_TIMEOUT });
|
|
4499
|
+
await test$1.expect(option).toBeHidden({ timeout: UI_TIMEOUT$1 });
|
|
4477
4500
|
}
|
|
4478
4501
|
/**
|
|
4479
4502
|
* Fill and persist the Grading setup form (switches to the setup sub-tab
|
|
@@ -4485,26 +4508,26 @@ async function saveGraderConfig(page, values) {
|
|
|
4485
4508
|
await switchToGraderSubTab(page, 'setup');
|
|
4486
4509
|
const body = graderTabBody(page);
|
|
4487
4510
|
if (values.gradingMode) {
|
|
4488
|
-
await pickSelectOption(page, 'grader-grading-mode-select', GRADER_LABELS.gradingModeOptions[values.gradingMode]);
|
|
4511
|
+
await pickSelectOption$1(page, 'grader-grading-mode-select', GRADER_LABELS.gradingModeOptions[values.gradingMode]);
|
|
4489
4512
|
}
|
|
4490
4513
|
if (values.feedbackMode) {
|
|
4491
|
-
await pickSelectOption(page, 'grader-feedback-mode-select', GRADER_LABELS.feedbackModeOptions[values.feedbackMode]);
|
|
4514
|
+
await pickSelectOption$1(page, 'grader-feedback-mode-select', GRADER_LABELS.feedbackModeOptions[values.feedbackMode]);
|
|
4492
4515
|
}
|
|
4493
4516
|
if (values.instructions !== undefined) {
|
|
4494
4517
|
const textarea = body.getByTestId('grader-instructions-textarea');
|
|
4495
|
-
await test$1.expect(textarea).toBeVisible({ timeout: UI_TIMEOUT });
|
|
4518
|
+
await test$1.expect(textarea).toBeVisible({ timeout: UI_TIMEOUT$1 });
|
|
4496
4519
|
await textarea.fill(values.instructions);
|
|
4497
4520
|
}
|
|
4498
4521
|
const saveButton = body.getByTestId('grader-save-button');
|
|
4499
|
-
await test$1.expect(saveButton).toBeEnabled({ timeout: UI_TIMEOUT });
|
|
4522
|
+
await test$1.expect(saveButton).toBeEnabled({ timeout: UI_TIMEOUT$1 });
|
|
4500
4523
|
await saveButton.click();
|
|
4501
|
-
await test$1.expect(saveButton).toBeDisabled({ timeout: MUTATION_TIMEOUT });
|
|
4524
|
+
await test$1.expect(saveButton).toBeDisabled({ timeout: MUTATION_TIMEOUT$1 });
|
|
4502
4525
|
logger.info('Saved grader configuration');
|
|
4503
4526
|
}
|
|
4504
4527
|
/** Fill the criterion modal's three fields and submit it. */
|
|
4505
4528
|
async function submitCriterionModal(page, criterion) {
|
|
4506
4529
|
const modal = page.getByTestId('grader-criterion-modal');
|
|
4507
|
-
await test$1.expect(modal).toBeVisible({ timeout: UI_TIMEOUT });
|
|
4530
|
+
await test$1.expect(modal).toBeVisible({ timeout: UI_TIMEOUT$1 });
|
|
4508
4531
|
await modal.getByLabel(GRADER_LABELS.modal.fields.name, { exact: true }).fill(criterion.name);
|
|
4509
4532
|
await modal
|
|
4510
4533
|
.getByLabel(GRADER_LABELS.modal.fields.criteria, { exact: true })
|
|
@@ -4513,21 +4536,21 @@ async function submitCriterionModal(page, criterion) {
|
|
|
4513
4536
|
.getByLabel(GRADER_LABELS.modal.fields.points, { exact: true })
|
|
4514
4537
|
.fill(String(criterion.points));
|
|
4515
4538
|
const saveButton = modal.getByTestId('grader-criterion-modal-save');
|
|
4516
|
-
await test$1.expect(saveButton).toBeEnabled({ timeout: UI_TIMEOUT });
|
|
4539
|
+
await test$1.expect(saveButton).toBeEnabled({ timeout: UI_TIMEOUT$1 });
|
|
4517
4540
|
await saveButton.click();
|
|
4518
4541
|
// The modal only closes itself after the mutation resolves.
|
|
4519
|
-
await test$1.expect(modal).toBeHidden({ timeout: MUTATION_TIMEOUT });
|
|
4542
|
+
await test$1.expect(modal).toBeHidden({ timeout: MUTATION_TIMEOUT$1 });
|
|
4520
4543
|
}
|
|
4521
4544
|
/** Open a rubric row's three-dots menu and click one of its actions. */
|
|
4522
4545
|
async function clickRowMenuAction(page, name, action) {
|
|
4523
4546
|
const trigger = criterionRow(page, name).getByRole('button', {
|
|
4524
4547
|
name: GRADER_LABELS.menu.actionsAria(name),
|
|
4525
4548
|
});
|
|
4526
|
-
await test$1.expect(trigger).toBeVisible({ timeout: UI_TIMEOUT });
|
|
4549
|
+
await test$1.expect(trigger).toBeVisible({ timeout: UI_TIMEOUT$1 });
|
|
4527
4550
|
await trigger.click();
|
|
4528
4551
|
// The menu portals to the page root — the open menu is the only one.
|
|
4529
4552
|
const item = page.getByRole('menuitem', { name: action, exact: true });
|
|
4530
|
-
await test$1.expect(item).toBeVisible({ timeout: UI_TIMEOUT });
|
|
4553
|
+
await test$1.expect(item).toBeVisible({ timeout: UI_TIMEOUT$1 });
|
|
4531
4554
|
await item.click();
|
|
4532
4555
|
}
|
|
4533
4556
|
/**
|
|
@@ -4539,10 +4562,10 @@ async function clickRowMenuAction(page, name, action) {
|
|
|
4539
4562
|
async function addGraderCriterion(page, criterion) {
|
|
4540
4563
|
await switchToGraderSubTab(page, 'rubric');
|
|
4541
4564
|
const addButton = criteriaSection(page).getByTestId('grader-add-criterion-button');
|
|
4542
|
-
await test$1.expect(addButton).toBeEnabled({ timeout: UI_TIMEOUT });
|
|
4565
|
+
await test$1.expect(addButton).toBeEnabled({ timeout: UI_TIMEOUT$1 });
|
|
4543
4566
|
await addButton.click();
|
|
4544
4567
|
await submitCriterionModal(page, criterion);
|
|
4545
|
-
await test$1.expect(criterionRow(page, criterion.name)).toBeVisible({ timeout: MUTATION_TIMEOUT });
|
|
4568
|
+
await test$1.expect(criterionRow(page, criterion.name)).toBeVisible({ timeout: MUTATION_TIMEOUT$1 });
|
|
4546
4569
|
logger.info(`Added rubric criterion "${criterion.name}"`);
|
|
4547
4570
|
}
|
|
4548
4571
|
/**
|
|
@@ -4555,7 +4578,7 @@ async function editGraderCriterion(page, currentName, updates) {
|
|
|
4555
4578
|
await switchToGraderSubTab(page, 'rubric');
|
|
4556
4579
|
await clickRowMenuAction(page, currentName, GRADER_LABELS.menu.edit);
|
|
4557
4580
|
await submitCriterionModal(page, updates);
|
|
4558
|
-
await test$1.expect(criterionRow(page, updates.name)).toBeVisible({ timeout: MUTATION_TIMEOUT });
|
|
4581
|
+
await test$1.expect(criterionRow(page, updates.name)).toBeVisible({ timeout: MUTATION_TIMEOUT$1 });
|
|
4559
4582
|
logger.info(`Edited rubric criterion "${currentName}" → "${updates.name}"`);
|
|
4560
4583
|
}
|
|
4561
4584
|
/**
|
|
@@ -4567,12 +4590,12 @@ async function deleteGraderCriterion(page, name) {
|
|
|
4567
4590
|
await switchToGraderSubTab(page, 'rubric');
|
|
4568
4591
|
await clickRowMenuAction(page, name, GRADER_LABELS.menu.delete);
|
|
4569
4592
|
const modal = page.getByTestId('grader-criterion-delete-modal');
|
|
4570
|
-
await test$1.expect(modal).toBeVisible({ timeout: UI_TIMEOUT });
|
|
4593
|
+
await test$1.expect(modal).toBeVisible({ timeout: UI_TIMEOUT$1 });
|
|
4571
4594
|
const confirmButton = modal.getByTestId('grader-criterion-delete-confirm');
|
|
4572
|
-
await test$1.expect(confirmButton).toBeEnabled({ timeout: UI_TIMEOUT });
|
|
4595
|
+
await test$1.expect(confirmButton).toBeEnabled({ timeout: UI_TIMEOUT$1 });
|
|
4573
4596
|
await confirmButton.click();
|
|
4574
|
-
await test$1.expect(modal).toBeHidden({ timeout: MUTATION_TIMEOUT });
|
|
4575
|
-
await test$1.expect(criterionRow(page, name)).toBeHidden({ timeout: MUTATION_TIMEOUT });
|
|
4597
|
+
await test$1.expect(modal).toBeHidden({ timeout: MUTATION_TIMEOUT$1 });
|
|
4598
|
+
await test$1.expect(criterionRow(page, name)).toBeHidden({ timeout: MUTATION_TIMEOUT$1 });
|
|
4576
4599
|
logger.info(`Deleted rubric criterion "${name}"`);
|
|
4577
4600
|
}
|
|
4578
4601
|
/**
|
|
@@ -4583,18 +4606,18 @@ async function deleteGraderCriterion(page, name) {
|
|
|
4583
4606
|
async function expectLastCriterionDeleteDisabled(page, name) {
|
|
4584
4607
|
await switchToGraderSubTab(page, 'rubric');
|
|
4585
4608
|
await test$1.expect(criteriaSection(page).getByTestId('grader-last-criterion-hint')).toBeVisible({
|
|
4586
|
-
timeout: UI_TIMEOUT,
|
|
4609
|
+
timeout: UI_TIMEOUT$1,
|
|
4587
4610
|
});
|
|
4588
4611
|
const trigger = criterionRow(page, name).getByRole('button', {
|
|
4589
4612
|
name: GRADER_LABELS.menu.actionsAria(name),
|
|
4590
4613
|
});
|
|
4591
|
-
await test$1.expect(trigger).toBeVisible({ timeout: UI_TIMEOUT });
|
|
4614
|
+
await test$1.expect(trigger).toBeVisible({ timeout: UI_TIMEOUT$1 });
|
|
4592
4615
|
await trigger.click();
|
|
4593
4616
|
const deleteItem = page.getByRole('menuitem', { name: GRADER_LABELS.menu.delete, exact: true });
|
|
4594
|
-
await test$1.expect(deleteItem).toBeVisible({ timeout: UI_TIMEOUT });
|
|
4617
|
+
await test$1.expect(deleteItem).toBeVisible({ timeout: UI_TIMEOUT$1 });
|
|
4595
4618
|
await test$1.expect(deleteItem).toHaveAttribute('aria-disabled', 'true');
|
|
4596
4619
|
await page.keyboard.press('Escape');
|
|
4597
|
-
await test$1.expect(deleteItem).toBeHidden({ timeout: UI_TIMEOUT });
|
|
4620
|
+
await test$1.expect(deleteItem).toBeHidden({ timeout: UI_TIMEOUT$1 });
|
|
4598
4621
|
}
|
|
4599
4622
|
/**
|
|
4600
4623
|
* Assert whether the amber misconfiguration banner is shown (grading on
|
|
@@ -4604,7 +4627,7 @@ async function expectLastCriterionDeleteDisabled(page, name) {
|
|
|
4604
4627
|
async function expectGraderMisconfiguredWarning(page, visible) {
|
|
4605
4628
|
const warning = graderTabBody(page).getByTestId('grader-misconfigured-warning');
|
|
4606
4629
|
if (visible) {
|
|
4607
|
-
await test$1.expect(warning).toBeVisible({ timeout: UI_TIMEOUT });
|
|
4630
|
+
await test$1.expect(warning).toBeVisible({ timeout: UI_TIMEOUT$1 });
|
|
4608
4631
|
}
|
|
4609
4632
|
else {
|
|
4610
4633
|
await test$1.expect(warning).toBeHidden();
|
|
@@ -4613,7 +4636,7 @@ async function expectGraderMisconfiguredWarning(page, visible) {
|
|
|
4613
4636
|
/** Assert the rubric's live "total possible points" readout (Rubric sub-tab). */
|
|
4614
4637
|
async function expectGraderTotalPoints(page, total) {
|
|
4615
4638
|
await switchToGraderSubTab(page, 'rubric');
|
|
4616
|
-
await test$1.expect(criteriaSection(page).getByText(`Total Possible Points: ${total}`, { exact: true })).toBeVisible({ timeout: UI_TIMEOUT });
|
|
4639
|
+
await test$1.expect(criteriaSection(page).getByText(`Total Possible Points: ${total}`, { exact: true })).toBeVisible({ timeout: UI_TIMEOUT$1 });
|
|
4617
4640
|
}
|
|
4618
4641
|
/** The Results sub-tab's section within the tab body. */
|
|
4619
4642
|
function resultsSection(page) {
|
|
@@ -4633,21 +4656,21 @@ function gradeResultRow(page, email) {
|
|
|
4633
4656
|
async function filterGradeResultsByEmail(page, email) {
|
|
4634
4657
|
await switchToGraderSubTab(page, 'results');
|
|
4635
4658
|
const trigger = resultsSection(page).getByTestId('grader-results-user-filter');
|
|
4636
|
-
await test$1.expect(trigger).toBeVisible({ timeout: UI_TIMEOUT });
|
|
4659
|
+
await test$1.expect(trigger).toBeVisible({ timeout: UI_TIMEOUT$1 });
|
|
4637
4660
|
await trigger.click();
|
|
4638
4661
|
const searchBox = resultsSection(page).getByPlaceholder(GRADER_LABELS.results.searchUsersPlaceholder);
|
|
4639
|
-
await test$1.expect(searchBox).toBeVisible({ timeout: UI_TIMEOUT });
|
|
4662
|
+
await test$1.expect(searchBox).toBeVisible({ timeout: UI_TIMEOUT$1 });
|
|
4640
4663
|
await searchBox.fill(email);
|
|
4641
4664
|
const option = resultsSection(page).getByRole('option', { name: email });
|
|
4642
|
-
await test$1.expect(option).toBeVisible({ timeout: UI_TIMEOUT });
|
|
4665
|
+
await test$1.expect(option).toBeVisible({ timeout: UI_TIMEOUT$1 });
|
|
4643
4666
|
await option.click();
|
|
4644
|
-
await test$1.expect(gradeResultRow(page, email)).toBeVisible({ timeout: MUTATION_TIMEOUT });
|
|
4667
|
+
await test$1.expect(gradeResultRow(page, email)).toBeVisible({ timeout: MUTATION_TIMEOUT$1 });
|
|
4645
4668
|
logger.info(`Filtered grade results by learner "${email}"`);
|
|
4646
4669
|
}
|
|
4647
4670
|
/** Assert a grade-result row for the given learner email is visible. */
|
|
4648
4671
|
async function expectGradeResultRow(page, email) {
|
|
4649
4672
|
await switchToGraderSubTab(page, 'results');
|
|
4650
|
-
await test$1.expect(gradeResultRow(page, email)).toBeVisible({ timeout: UI_TIMEOUT });
|
|
4673
|
+
await test$1.expect(gradeResultRow(page, email)).toBeVisible({ timeout: UI_TIMEOUT$1 });
|
|
4651
4674
|
}
|
|
4652
4675
|
/**
|
|
4653
4676
|
* Override a learner's grade via the row's three-dots menu → Override grade
|
|
@@ -4659,10 +4682,10 @@ async function overrideGradeResult(page, email, values) {
|
|
|
4659
4682
|
const overrideButton = gradeResultRow(page, email).getByRole('button', {
|
|
4660
4683
|
name: GRADER_LABELS.results.overrideButtonAria(email),
|
|
4661
4684
|
});
|
|
4662
|
-
await test$1.expect(overrideButton).toBeVisible({ timeout: UI_TIMEOUT });
|
|
4685
|
+
await test$1.expect(overrideButton).toBeVisible({ timeout: UI_TIMEOUT$1 });
|
|
4663
4686
|
await overrideButton.click();
|
|
4664
4687
|
const modal = page.getByTestId('grader-override-modal');
|
|
4665
|
-
await test$1.expect(modal).toBeVisible({ timeout: UI_TIMEOUT });
|
|
4688
|
+
await test$1.expect(modal).toBeVisible({ timeout: UI_TIMEOUT$1 });
|
|
4666
4689
|
await modal
|
|
4667
4690
|
.getByLabel(GRADER_LABELS.results.modal.pointsLabel, { exact: true })
|
|
4668
4691
|
.fill(String(values.points));
|
|
@@ -4672,9 +4695,9 @@ async function overrideGradeResult(page, email, values) {
|
|
|
4672
4695
|
.fill(values.feedback);
|
|
4673
4696
|
}
|
|
4674
4697
|
const saveButton = modal.getByTestId('grader-override-save');
|
|
4675
|
-
await test$1.expect(saveButton).toBeEnabled({ timeout: UI_TIMEOUT });
|
|
4698
|
+
await test$1.expect(saveButton).toBeEnabled({ timeout: UI_TIMEOUT$1 });
|
|
4676
4699
|
await saveButton.click();
|
|
4677
|
-
await test$1.expect(modal).toBeHidden({ timeout: MUTATION_TIMEOUT });
|
|
4700
|
+
await test$1.expect(modal).toBeHidden({ timeout: MUTATION_TIMEOUT$1 });
|
|
4678
4701
|
logger.info(`Overrode grade for "${email}" with ${values.points} points`);
|
|
4679
4702
|
}
|
|
4680
4703
|
/**
|
|
@@ -4686,14 +4709,14 @@ async function clearGradeResultOverride(page, email) {
|
|
|
4686
4709
|
const overrideButton = gradeResultRow(page, email).getByRole('button', {
|
|
4687
4710
|
name: GRADER_LABELS.results.overrideButtonAria(email),
|
|
4688
4711
|
});
|
|
4689
|
-
await test$1.expect(overrideButton).toBeVisible({ timeout: UI_TIMEOUT });
|
|
4712
|
+
await test$1.expect(overrideButton).toBeVisible({ timeout: UI_TIMEOUT$1 });
|
|
4690
4713
|
await overrideButton.click();
|
|
4691
4714
|
const modal = page.getByTestId('grader-override-modal');
|
|
4692
|
-
await test$1.expect(modal).toBeVisible({ timeout: UI_TIMEOUT });
|
|
4715
|
+
await test$1.expect(modal).toBeVisible({ timeout: UI_TIMEOUT$1 });
|
|
4693
4716
|
const clearButton = modal.getByTestId('grader-override-clear');
|
|
4694
|
-
await test$1.expect(clearButton).toBeEnabled({ timeout: UI_TIMEOUT });
|
|
4717
|
+
await test$1.expect(clearButton).toBeEnabled({ timeout: UI_TIMEOUT$1 });
|
|
4695
4718
|
await clearButton.click();
|
|
4696
|
-
await test$1.expect(modal).toBeHidden({ timeout: MUTATION_TIMEOUT });
|
|
4719
|
+
await test$1.expect(modal).toBeHidden({ timeout: MUTATION_TIMEOUT$1 });
|
|
4697
4720
|
logger.info(`Cleared grade override for "${email}"`);
|
|
4698
4721
|
}
|
|
4699
4722
|
|
|
@@ -4892,6 +4915,653 @@ async function clickBillingManageUsage(page) {
|
|
|
4892
4915
|
await btn.click();
|
|
4893
4916
|
}
|
|
4894
4917
|
|
|
4918
|
+
/**
|
|
4919
|
+
* Spend limits ("Billing") helpers — Playwright bindings for the LLM
|
|
4920
|
+
* spend-cap admin UI from `@iblai/web-containers`:
|
|
4921
|
+
*
|
|
4922
|
+
* - the **agent settings Billing tab** (`AgentSpendCapsTab`) with its
|
|
4923
|
+
* "This Agent" / "Per User" sub-tabs, the per-user table with a
|
|
4924
|
+
* three-dots row menu, and the add/edit user-limit modal (email-based
|
|
4925
|
+
* user picker), and
|
|
4926
|
+
* - the **tenant settings Billing tab** with its "Plan & Credits" /
|
|
4927
|
+
* "Spend Limits" tabs and the workspace-wide limit section
|
|
4928
|
+
* (`SpendLimitsSection` inside `BillingTab`).
|
|
4929
|
+
*
|
|
4930
|
+
* Selector policy (flakiness-proof by construction):
|
|
4931
|
+
* - Dialog-first scoping: every modal is resolved into a Locator variable
|
|
4932
|
+
* first (`user-cap-modal`, the delete dialogs, the edit-agent dialog, the
|
|
4933
|
+
* Agent Limits manage popup) and all sub-elements are queried from that
|
|
4934
|
+
* variable — never a bare page-wide match that could hit same-named
|
|
4935
|
+
* elements in nested portals.
|
|
4936
|
+
* - Nested dialogs: the tenant Agent Limits tab opens the agent Billing
|
|
4937
|
+
* editor in a popup *on top of* the tenant settings dialog, so several
|
|
4938
|
+
* dialogs with tablists can be open at once. Every agent-scope helper
|
|
4939
|
+
* accepts an optional `scope` Locator (the host dialog) for that case;
|
|
4940
|
+
* `openAgentLimitsManage` returns the popup Locator to pass as `scope`.
|
|
4941
|
+
* - Stable hooks only: `data-testid`, role + accessible name (aria-labels).
|
|
4942
|
+
* No CSS class or structural selectors.
|
|
4943
|
+
* - No `waitForTimeout` / `networkidle`. Progress is gated on UI state that
|
|
4944
|
+
* only exists after the awaited transition: a section testid rendering, a
|
|
4945
|
+
* modal closing after its mutation resolves, a table row
|
|
4946
|
+
* appearing/disappearing after the list refetch, a success toast.
|
|
4947
|
+
*/
|
|
4948
|
+
const SPEND_LIMITS_LABELS = {
|
|
4949
|
+
/** Agent-settings modal tab name (host registries label it "Billing"). */
|
|
4950
|
+
tabName: 'Billing',
|
|
4951
|
+
subTabs: {
|
|
4952
|
+
agent: 'This Agent',
|
|
4953
|
+
users: 'Per User',
|
|
4954
|
+
},
|
|
4955
|
+
tenantTabs: {
|
|
4956
|
+
planAndCredits: 'Plan & Credits',
|
|
4957
|
+
spendLimits: 'Spend Limits',
|
|
4958
|
+
agentLimits: 'Agent Limits',
|
|
4959
|
+
},
|
|
4960
|
+
/** aria-label of the tenant Billing tab's tablist. */
|
|
4961
|
+
tenantTablistAria: 'Billing sections',
|
|
4962
|
+
addUserLimitButton: 'Add User Limit',
|
|
4963
|
+
menu: {
|
|
4964
|
+
/**
|
|
4965
|
+
* aria-label template for a table row's three-dots trigger. Takes the
|
|
4966
|
+
* DISPLAYED name — the user's email when the API provides one, username
|
|
4967
|
+
* only as fallback. Helpers locate the trigger by its username-keyed
|
|
4968
|
+
* testid instead (`user-cap-actions-<username>`), which is immune to
|
|
4969
|
+
* that distinction; prefer the testid in new code.
|
|
4970
|
+
*/
|
|
4971
|
+
actionsAria: (displayName) => `Actions for ${displayName}`,
|
|
4972
|
+
edit: 'Edit',
|
|
4973
|
+
delete: 'Delete',
|
|
4974
|
+
},
|
|
4975
|
+
toasts: {
|
|
4976
|
+
saved: 'Spend limit saved',
|
|
4977
|
+
deleted: 'Spend limit deleted',
|
|
4978
|
+
},
|
|
4979
|
+
intervalOptions: {
|
|
4980
|
+
day: 'Day',
|
|
4981
|
+
week: 'Week',
|
|
4982
|
+
month: 'Month',
|
|
4983
|
+
year: 'Year',
|
|
4984
|
+
},
|
|
4985
|
+
enforcementOptions: {
|
|
4986
|
+
block: 'Block Requests',
|
|
4987
|
+
alert_only: 'Alert Only',
|
|
4988
|
+
},
|
|
4989
|
+
};
|
|
4990
|
+
const UI_TIMEOUT = 10000;
|
|
4991
|
+
const MUTATION_TIMEOUT = 15000;
|
|
4992
|
+
/**
|
|
4993
|
+
* The dialog that hosts the agent settings tabs. Several dialogs can be
|
|
4994
|
+
* open at once (the Agent Limits manage popup stacks on top of the tenant
|
|
4995
|
+
* settings dialog, and both host a tablist), so this resolves to the
|
|
4996
|
+
* TOPMOST match — stacked Radix dialogs portal to the end of `<body>` in
|
|
4997
|
+
* mount order, making the last match the one on top. Helpers that must
|
|
4998
|
+
* target a specific dialog take an explicit `scope` Locator instead, which
|
|
4999
|
+
* always wins over this default.
|
|
5000
|
+
*/
|
|
5001
|
+
function editAgentDialog(page) {
|
|
5002
|
+
return page
|
|
5003
|
+
.getByRole('dialog')
|
|
5004
|
+
.filter({ has: page.getByRole('tablist') })
|
|
5005
|
+
.last();
|
|
5006
|
+
}
|
|
5007
|
+
/**
|
|
5008
|
+
* The agent Billing tab's body. `scope` is the dialog hosting the editor
|
|
5009
|
+
* (e.g. the Locator returned by `openAgentLimitsManage`); defaults to the
|
|
5010
|
+
* topmost settings dialog.
|
|
5011
|
+
*/
|
|
5012
|
+
function spendLimitsTabBody(page, scope) {
|
|
5013
|
+
return (scope !== null && scope !== void 0 ? scope : editAgentDialog(page)).getByTestId('spend-caps-tab-body');
|
|
5014
|
+
}
|
|
5015
|
+
/** The per-user limits section within the Billing tab body. */
|
|
5016
|
+
function userCapsSection(page, scope) {
|
|
5017
|
+
return spendLimitsTabBody(page, scope).getByTestId('user-caps-section');
|
|
5018
|
+
}
|
|
5019
|
+
/** A per-user table row for the given username. */
|
|
5020
|
+
function userSpendLimitRow(page, username, scope) {
|
|
5021
|
+
return userCapsSection(page, scope).getByTestId(`user-cap-row-${username}`);
|
|
5022
|
+
}
|
|
5023
|
+
/**
|
|
5024
|
+
* Returns false if the Billing tab isn't currently rendered in the
|
|
5025
|
+
* edit-agent dialog (host didn't register it, or RBAC hid it).
|
|
5026
|
+
*/
|
|
5027
|
+
async function isSpendLimitsTabVisible(page) {
|
|
5028
|
+
const tab = editAgentDialog(page).getByRole('tab', {
|
|
5029
|
+
name: SPEND_LIMITS_LABELS.tabName,
|
|
5030
|
+
exact: true,
|
|
5031
|
+
});
|
|
5032
|
+
try {
|
|
5033
|
+
await test$1.expect(tab).toBeVisible({ timeout: 5000 });
|
|
5034
|
+
return true;
|
|
5035
|
+
}
|
|
5036
|
+
catch (_a) {
|
|
5037
|
+
return false;
|
|
5038
|
+
}
|
|
5039
|
+
}
|
|
5040
|
+
/**
|
|
5041
|
+
* Switch to the Billing top-level tab of the edit-agent dialog. Completion
|
|
5042
|
+
* is gated on the tab body's testid, not on timing.
|
|
5043
|
+
*/
|
|
5044
|
+
async function switchToSpendLimitsTab(page) {
|
|
5045
|
+
const dialog = editAgentDialog(page);
|
|
5046
|
+
const tab = dialog.getByRole('tab', { name: SPEND_LIMITS_LABELS.tabName, exact: true });
|
|
5047
|
+
await test$1.expect(tab).toBeVisible({ timeout: UI_TIMEOUT });
|
|
5048
|
+
await tab.click();
|
|
5049
|
+
await test$1.expect(spendLimitsTabBody(page)).toBeVisible({ timeout: UI_TIMEOUT });
|
|
5050
|
+
logger.info('Switched to agent Billing (spend limits) tab');
|
|
5051
|
+
}
|
|
5052
|
+
/**
|
|
5053
|
+
* Switch between the Billing tab's two sub-tabs. Completion is gated on the
|
|
5054
|
+
* sub-tab's content rendering: the agent limit form (or its loading/denied
|
|
5055
|
+
* states) for "agent", the per-user section (or its spinner) for "users".
|
|
5056
|
+
* `scope` is the dialog hosting the editor (see `spendLimitsTabBody`).
|
|
5057
|
+
*/
|
|
5058
|
+
async function switchToSpendLimitsSubTab(page, subTab, scope) {
|
|
5059
|
+
const body = spendLimitsTabBody(page, scope);
|
|
5060
|
+
const trigger = body.getByTestId(`spend-caps-sub-tab-${subTab}`);
|
|
5061
|
+
await test$1.expect(trigger).toBeVisible({ timeout: UI_TIMEOUT });
|
|
5062
|
+
await trigger.click();
|
|
5063
|
+
const content = subTab === 'agent'
|
|
5064
|
+
? body
|
|
5065
|
+
.getByTestId('agent-cap-form')
|
|
5066
|
+
.or(body.getByTestId('agent-cap-loading'))
|
|
5067
|
+
.or(body.getByTestId('spend-caps-denied'))
|
|
5068
|
+
: body
|
|
5069
|
+
.getByTestId('user-caps-section')
|
|
5070
|
+
.or(body.getByTestId('user-caps-loading'))
|
|
5071
|
+
.or(body.getByTestId('spend-caps-denied'));
|
|
5072
|
+
await test$1.expect(content.first()).toBeVisible({ timeout: UI_TIMEOUT });
|
|
5073
|
+
logger.info(`Switched to spend limits "${subTab}" sub-tab`);
|
|
5074
|
+
}
|
|
5075
|
+
/** Pick an option in one of the limit form's Radix selects. */
|
|
5076
|
+
async function pickSelectOption(scope, page, triggerTestId, label) {
|
|
5077
|
+
const trigger = scope.getByTestId(triggerTestId);
|
|
5078
|
+
await test$1.expect(trigger).toBeVisible({ timeout: UI_TIMEOUT });
|
|
5079
|
+
await trigger.click();
|
|
5080
|
+
// Radix renders the listbox in a portal outside the scope — the open
|
|
5081
|
+
// listbox is the only one in the document, so a role-level lookup is safe.
|
|
5082
|
+
const option = page.getByRole('option', { name: label, exact: true });
|
|
5083
|
+
await test$1.expect(option).toBeVisible({ timeout: UI_TIMEOUT });
|
|
5084
|
+
await option.click();
|
|
5085
|
+
await test$1.expect(option).toBeHidden({ timeout: UI_TIMEOUT });
|
|
5086
|
+
}
|
|
5087
|
+
/**
|
|
5088
|
+
* Fill a spend-limit form (any scope — agent, workspace, or the user-limit
|
|
5089
|
+
* modal). Only the provided fields are changed. `testIdPrefix` matches the
|
|
5090
|
+
* form's testId prop: 'agent-cap', 'tenant-cap' or 'user-cap-modal'.
|
|
5091
|
+
*/
|
|
5092
|
+
async function fillSpendLimitForm(page, scope, testIdPrefix, values) {
|
|
5093
|
+
if (values.enabled !== undefined) {
|
|
5094
|
+
const toggle = scope.getByTestId(`${testIdPrefix}-enabled-switch`);
|
|
5095
|
+
await test$1.expect(toggle).toBeVisible({ timeout: UI_TIMEOUT });
|
|
5096
|
+
if ((await toggle.getAttribute('aria-checked')) !== String(values.enabled)) {
|
|
5097
|
+
await toggle.click();
|
|
5098
|
+
await test$1.expect(toggle).toHaveAttribute('aria-checked', String(values.enabled), {
|
|
5099
|
+
timeout: UI_TIMEOUT,
|
|
5100
|
+
});
|
|
5101
|
+
}
|
|
5102
|
+
}
|
|
5103
|
+
if (values.limitUsd !== undefined) {
|
|
5104
|
+
const input = scope.getByTestId(`${testIdPrefix}-limit-input`);
|
|
5105
|
+
await test$1.expect(input).toBeVisible({ timeout: UI_TIMEOUT });
|
|
5106
|
+
await input.fill(String(values.limitUsd));
|
|
5107
|
+
}
|
|
5108
|
+
if (values.interval) {
|
|
5109
|
+
await pickSelectOption(scope, page, `${testIdPrefix}-interval-select`, SPEND_LIMITS_LABELS.intervalOptions[values.interval]);
|
|
5110
|
+
}
|
|
5111
|
+
if (values.enforcement) {
|
|
5112
|
+
await pickSelectOption(scope, page, `${testIdPrefix}-enforcement-select`, SPEND_LIMITS_LABELS.enforcementOptions[values.enforcement]);
|
|
5113
|
+
}
|
|
5114
|
+
if (values.alertThresholds !== undefined) {
|
|
5115
|
+
const input = scope.getByTestId(`${testIdPrefix}-thresholds-input`);
|
|
5116
|
+
await test$1.expect(input).toBeVisible({ timeout: UI_TIMEOUT });
|
|
5117
|
+
await input.fill(values.alertThresholds);
|
|
5118
|
+
}
|
|
5119
|
+
}
|
|
5120
|
+
/** Click a form's Save and gate on the saved toast (fires after the PUT resolves). */
|
|
5121
|
+
async function saveSpendLimitForm(page, scope, testIdPrefix) {
|
|
5122
|
+
const saveButton = scope.getByTestId(`${testIdPrefix}-save-button`);
|
|
5123
|
+
await test$1.expect(saveButton).toBeEnabled({ timeout: UI_TIMEOUT });
|
|
5124
|
+
await saveButton.click();
|
|
5125
|
+
// .first() is deliberate: repeated saves can stack identical sonner
|
|
5126
|
+
// toasts, and any one of them proves the mutation resolved.
|
|
5127
|
+
await test$1.expect(page.getByText(SPEND_LIMITS_LABELS.toasts.saved).first()).toBeVisible({
|
|
5128
|
+
timeout: MUTATION_TIMEOUT,
|
|
5129
|
+
});
|
|
5130
|
+
}
|
|
5131
|
+
/** Confirm a captured delete dialog and gate on it closing + the deleted toast. */
|
|
5132
|
+
async function confirmDeleteDialog(page, dialogTestId) {
|
|
5133
|
+
const dialog = page.getByTestId(dialogTestId);
|
|
5134
|
+
await test$1.expect(dialog).toBeVisible({ timeout: UI_TIMEOUT });
|
|
5135
|
+
// All three delete dialogs share the '<x>-delete-modal' / '<x>-delete-confirm'
|
|
5136
|
+
// testid convention.
|
|
5137
|
+
const confirmButton = dialog.getByTestId(`${dialogTestId.replace('-modal', '')}-confirm`);
|
|
5138
|
+
await test$1.expect(confirmButton).toBeEnabled({ timeout: UI_TIMEOUT });
|
|
5139
|
+
await confirmButton.click();
|
|
5140
|
+
await test$1.expect(dialog).toBeHidden({ timeout: MUTATION_TIMEOUT });
|
|
5141
|
+
await test$1.expect(page.getByText(SPEND_LIMITS_LABELS.toasts.deleted).first()).toBeVisible({
|
|
5142
|
+
timeout: MUTATION_TIMEOUT,
|
|
5143
|
+
});
|
|
5144
|
+
}
|
|
5145
|
+
/**
|
|
5146
|
+
* Create or update the agent-scoped spend limit (switches to the "This
|
|
5147
|
+
* agent" sub-tab first). Completion is gated on the saved toast and the
|
|
5148
|
+
* usage strip rendering (it only exists once a cap is persisted).
|
|
5149
|
+
*/
|
|
5150
|
+
async function setAgentSpendLimit(page, values, scope) {
|
|
5151
|
+
await switchToSpendLimitsSubTab(page, 'agent', scope);
|
|
5152
|
+
const body = spendLimitsTabBody(page, scope);
|
|
5153
|
+
await fillSpendLimitForm(page, body, 'agent-cap', values);
|
|
5154
|
+
await saveSpendLimitForm(page, body, 'agent-cap');
|
|
5155
|
+
await test$1.expect(body.getByTestId('agent-cap-usage')).toBeVisible({ timeout: MUTATION_TIMEOUT });
|
|
5156
|
+
logger.info('Saved agent spend limit');
|
|
5157
|
+
}
|
|
5158
|
+
/**
|
|
5159
|
+
* Delete the agent-scoped spend limit via the form's Delete button →
|
|
5160
|
+
* confirmation dialog. Completion is gated on the dialog closing, the
|
|
5161
|
+
* deleted toast, and the form returning to create mode (no-cap hint).
|
|
5162
|
+
*/
|
|
5163
|
+
async function deleteAgentSpendLimit(page, scope) {
|
|
5164
|
+
await switchToSpendLimitsSubTab(page, 'agent', scope);
|
|
5165
|
+
const body = spendLimitsTabBody(page, scope);
|
|
5166
|
+
const deleteButton = body.getByTestId('agent-cap-delete-button');
|
|
5167
|
+
await test$1.expect(deleteButton).toBeEnabled({ timeout: UI_TIMEOUT });
|
|
5168
|
+
await deleteButton.click();
|
|
5169
|
+
await confirmDeleteDialog(page, 'spend-cap-delete-modal');
|
|
5170
|
+
await test$1.expect(body.getByTestId('agent-cap-no-cap-hint')).toBeVisible({
|
|
5171
|
+
timeout: MUTATION_TIMEOUT,
|
|
5172
|
+
});
|
|
5173
|
+
logger.info('Deleted agent spend limit');
|
|
5174
|
+
}
|
|
5175
|
+
/**
|
|
5176
|
+
* Fill the user-limit modal's form and save it. The modal Locator is
|
|
5177
|
+
* captured once and every sub-element is resolved from it.
|
|
5178
|
+
*/
|
|
5179
|
+
async function submitUserCapModal(page, values) {
|
|
5180
|
+
const modal = page.getByTestId('user-cap-modal');
|
|
5181
|
+
await test$1.expect(modal).toBeVisible({ timeout: UI_TIMEOUT });
|
|
5182
|
+
await fillSpendLimitForm(page, modal, 'user-cap-modal', values);
|
|
5183
|
+
await saveSpendLimitForm(page, modal, 'user-cap-modal');
|
|
5184
|
+
// The modal only closes itself after the upsert resolves.
|
|
5185
|
+
await test$1.expect(modal).toBeHidden({ timeout: MUTATION_TIMEOUT });
|
|
5186
|
+
}
|
|
5187
|
+
/**
|
|
5188
|
+
* Add a per-user spend limit through the modal (switches to the "Per User"
|
|
5189
|
+
* sub-tab first): opens the modal, searches the user by email, clicks the
|
|
5190
|
+
* email in the results (the picker is email-only by design), fills the
|
|
5191
|
+
* form, saves. Completion is gated on the modal closing and the user's
|
|
5192
|
+
* table row rendering after the list refetch.
|
|
5193
|
+
*/
|
|
5194
|
+
async function addUserSpendLimit(page, values, scope) {
|
|
5195
|
+
await switchToSpendLimitsSubTab(page, 'users', scope);
|
|
5196
|
+
const addButton = userCapsSection(page, scope).getByTestId('user-caps-add-button');
|
|
5197
|
+
await test$1.expect(addButton).toBeEnabled({ timeout: UI_TIMEOUT });
|
|
5198
|
+
await addButton.click();
|
|
5199
|
+
const modal = page.getByTestId('user-cap-modal');
|
|
5200
|
+
await test$1.expect(modal).toBeVisible({ timeout: UI_TIMEOUT });
|
|
5201
|
+
const pickerInput = modal.getByTestId('user-cap-modal-user-picker-input');
|
|
5202
|
+
await test$1.expect(pickerInput).toBeVisible({ timeout: UI_TIMEOUT });
|
|
5203
|
+
await pickerInput.fill(values.email);
|
|
5204
|
+
// Options render as buttons labelled with the email only.
|
|
5205
|
+
const option = modal
|
|
5206
|
+
.getByTestId('user-cap-modal-user-picker-results')
|
|
5207
|
+
.getByRole('button', { name: values.email, exact: true });
|
|
5208
|
+
await test$1.expect(option).toBeVisible({ timeout: MUTATION_TIMEOUT });
|
|
5209
|
+
await option.click();
|
|
5210
|
+
await test$1.expect(modal.getByTestId('user-cap-modal-user-picker-selected')).toBeVisible({
|
|
5211
|
+
timeout: UI_TIMEOUT,
|
|
5212
|
+
});
|
|
5213
|
+
await submitUserCapModal(page, values);
|
|
5214
|
+
await test$1.expect(userSpendLimitRow(page, values.username, scope)).toBeVisible({
|
|
5215
|
+
timeout: MUTATION_TIMEOUT,
|
|
5216
|
+
});
|
|
5217
|
+
logger.info(`Added user spend limit for "${values.email}"`);
|
|
5218
|
+
}
|
|
5219
|
+
/**
|
|
5220
|
+
* Open a per-user table row's three-dots menu and click one of its actions.
|
|
5221
|
+
* The trigger is located by testid, not aria-label — the accessible name is
|
|
5222
|
+
* email-first while rows stay keyed by username.
|
|
5223
|
+
*/
|
|
5224
|
+
async function clickUserRowMenuAction(page, username, action, scope) {
|
|
5225
|
+
const trigger = userSpendLimitRow(page, username, scope).getByTestId(`user-cap-actions-${username}`);
|
|
5226
|
+
await test$1.expect(trigger).toBeVisible({ timeout: UI_TIMEOUT });
|
|
5227
|
+
await trigger.click();
|
|
5228
|
+
// The menu portals to the page root — the open menu is the only one.
|
|
5229
|
+
const item = page.getByRole('menuitem', { name: action, exact: true });
|
|
5230
|
+
await test$1.expect(item).toBeVisible({ timeout: UI_TIMEOUT });
|
|
5231
|
+
await item.click();
|
|
5232
|
+
}
|
|
5233
|
+
/**
|
|
5234
|
+
* Edit an existing per-user limit via its row's three-dots menu → Edit
|
|
5235
|
+
* modal. Completion is gated on the modal closing after the upsert.
|
|
5236
|
+
*/
|
|
5237
|
+
async function editUserSpendLimit(page, username, values, scope) {
|
|
5238
|
+
await switchToSpendLimitsSubTab(page, 'users', scope);
|
|
5239
|
+
await clickUserRowMenuAction(page, username, SPEND_LIMITS_LABELS.menu.edit, scope);
|
|
5240
|
+
await submitUserCapModal(page, values);
|
|
5241
|
+
logger.info(`Edited user spend limit for "${username}"`);
|
|
5242
|
+
}
|
|
5243
|
+
/**
|
|
5244
|
+
* Idempotently flip a per-user limit's Status toggle (the switch in the
|
|
5245
|
+
* table's Status column — datasets-table pattern, saves immediately).
|
|
5246
|
+
* Completion is gated on the saved toast and the switch reflecting the new
|
|
5247
|
+
* state after the row refetch.
|
|
5248
|
+
*/
|
|
5249
|
+
async function setUserSpendLimitEnabled(page, username, enabled, scope) {
|
|
5250
|
+
await switchToSpendLimitsSubTab(page, 'users', scope);
|
|
5251
|
+
const toggle = userSpendLimitRow(page, username, scope).getByTestId(`user-cap-status-switch-${username}`);
|
|
5252
|
+
await test$1.expect(toggle).toBeVisible({ timeout: UI_TIMEOUT });
|
|
5253
|
+
if ((await toggle.getAttribute('aria-checked')) === String(enabled)) {
|
|
5254
|
+
logger.info(`User spend limit for "${username}" already ${enabled ? 'enabled' : 'disabled'}`);
|
|
5255
|
+
return;
|
|
5256
|
+
}
|
|
5257
|
+
await test$1.expect(toggle).toBeEnabled({ timeout: UI_TIMEOUT });
|
|
5258
|
+
await toggle.click();
|
|
5259
|
+
await test$1.expect(page.getByText(SPEND_LIMITS_LABELS.toasts.saved).first()).toBeVisible({
|
|
5260
|
+
timeout: MUTATION_TIMEOUT,
|
|
5261
|
+
});
|
|
5262
|
+
await test$1.expect(toggle).toHaveAttribute('aria-checked', String(enabled), {
|
|
5263
|
+
timeout: MUTATION_TIMEOUT,
|
|
5264
|
+
});
|
|
5265
|
+
logger.info(`User spend limit for "${username}" ${enabled ? 'enabled' : 'disabled'}`);
|
|
5266
|
+
}
|
|
5267
|
+
/**
|
|
5268
|
+
* Delete a per-user limit via its row's three-dots menu → confirmation
|
|
5269
|
+
* dialog. Completion is gated on the dialog closing, the deleted toast and
|
|
5270
|
+
* the row disappearing after the list refetch.
|
|
5271
|
+
*/
|
|
5272
|
+
async function deleteUserSpendLimit(page, username, scope) {
|
|
5273
|
+
await switchToSpendLimitsSubTab(page, 'users', scope);
|
|
5274
|
+
await clickUserRowMenuAction(page, username, SPEND_LIMITS_LABELS.menu.delete, scope);
|
|
5275
|
+
await confirmDeleteDialog(page, 'user-cap-delete-modal');
|
|
5276
|
+
await test$1.expect(userSpendLimitRow(page, username, scope)).toBeHidden({ timeout: MUTATION_TIMEOUT });
|
|
5277
|
+
logger.info(`Deleted user spend limit for "${username}"`);
|
|
5278
|
+
}
|
|
5279
|
+
// ---------------------------------------------------------------------------
|
|
5280
|
+
// Tenant settings — Billing tab ("Plan & Credits" / "Spend Limits")
|
|
5281
|
+
// ---------------------------------------------------------------------------
|
|
5282
|
+
/** The tenant Billing tab's tablist (scoped by its aria-label). */
|
|
5283
|
+
function tenantBillingTablist(page) {
|
|
5284
|
+
return page.getByRole('tablist', { name: SPEND_LIMITS_LABELS.tenantTablistAria });
|
|
5285
|
+
}
|
|
5286
|
+
/** The workspace spend limit section card in the tenant Billing tab. */
|
|
5287
|
+
function workspaceSpendLimitSection(page) {
|
|
5288
|
+
return page.getByTestId('spend-limits-workspace-section');
|
|
5289
|
+
}
|
|
5290
|
+
/**
|
|
5291
|
+
* Switch the tenant Billing tab to "Spend Limits". Assumes the tenant
|
|
5292
|
+
* settings Billing tab is already open (see `waitForBillingTabReady`).
|
|
5293
|
+
* Completion is gated on the workspace section rendering.
|
|
5294
|
+
*/
|
|
5295
|
+
async function switchToWorkspaceSpendLimits(page) {
|
|
5296
|
+
const tab = tenantBillingTablist(page).getByRole('tab', {
|
|
5297
|
+
name: SPEND_LIMITS_LABELS.tenantTabs.spendLimits,
|
|
5298
|
+
exact: true,
|
|
5299
|
+
});
|
|
5300
|
+
await test$1.expect(tab).toBeVisible({ timeout: UI_TIMEOUT });
|
|
5301
|
+
await tab.click();
|
|
5302
|
+
await test$1.expect(workspaceSpendLimitSection(page)).toBeVisible({ timeout: UI_TIMEOUT });
|
|
5303
|
+
logger.info('Switched to tenant Spend Limits tab');
|
|
5304
|
+
}
|
|
5305
|
+
/**
|
|
5306
|
+
* Switch the tenant Billing tab back to "Plan & Credits". Completion is
|
|
5307
|
+
* gated on the Plan section rendering.
|
|
5308
|
+
*/
|
|
5309
|
+
async function switchToPlanAndCredits(page) {
|
|
5310
|
+
const tab = tenantBillingTablist(page).getByRole('tab', {
|
|
5311
|
+
name: SPEND_LIMITS_LABELS.tenantTabs.planAndCredits,
|
|
5312
|
+
exact: true,
|
|
5313
|
+
});
|
|
5314
|
+
await test$1.expect(tab).toBeVisible({ timeout: UI_TIMEOUT });
|
|
5315
|
+
await tab.click();
|
|
5316
|
+
await test$1.expect(page.getByTestId('billing-plan-section')).toBeVisible({ timeout: UI_TIMEOUT });
|
|
5317
|
+
logger.info('Switched to tenant Plan & Credits tab');
|
|
5318
|
+
}
|
|
5319
|
+
/**
|
|
5320
|
+
* Create or update the workspace-wide spend limit (switches to the Spend
|
|
5321
|
+
* Limits tab first). Completion is gated on the saved toast and the
|
|
5322
|
+
* usage strip rendering (it only exists once a cap is persisted).
|
|
5323
|
+
*/
|
|
5324
|
+
async function setWorkspaceSpendLimit(page, values) {
|
|
5325
|
+
await switchToWorkspaceSpendLimits(page);
|
|
5326
|
+
const section = workspaceSpendLimitSection(page);
|
|
5327
|
+
await fillSpendLimitForm(page, section, 'tenant-cap', values);
|
|
5328
|
+
await saveSpendLimitForm(page, section, 'tenant-cap');
|
|
5329
|
+
await test$1.expect(section.getByTestId('tenant-cap-usage')).toBeVisible({
|
|
5330
|
+
timeout: MUTATION_TIMEOUT,
|
|
5331
|
+
});
|
|
5332
|
+
logger.info('Saved workspace spend limit');
|
|
5333
|
+
}
|
|
5334
|
+
/**
|
|
5335
|
+
* Delete the workspace-wide spend limit. Completion is gated on the dialog
|
|
5336
|
+
* closing, the deleted toast, and the usage strip disappearing.
|
|
5337
|
+
*/
|
|
5338
|
+
async function deleteWorkspaceSpendLimit(page) {
|
|
5339
|
+
await switchToWorkspaceSpendLimits(page);
|
|
5340
|
+
const section = workspaceSpendLimitSection(page);
|
|
5341
|
+
const deleteButton = section.getByTestId('tenant-cap-delete-button');
|
|
5342
|
+
await test$1.expect(deleteButton).toBeEnabled({ timeout: UI_TIMEOUT });
|
|
5343
|
+
await deleteButton.click();
|
|
5344
|
+
await confirmDeleteDialog(page, 'tenant-cap-delete-modal');
|
|
5345
|
+
await test$1.expect(section.getByTestId('tenant-cap-usage')).toBeHidden({
|
|
5346
|
+
timeout: MUTATION_TIMEOUT,
|
|
5347
|
+
});
|
|
5348
|
+
logger.info('Deleted workspace spend limit');
|
|
5349
|
+
}
|
|
5350
|
+
/**
|
|
5351
|
+
* Assert the workspace usage strip (the same one the agent editor shows) is
|
|
5352
|
+
* visible, optionally matching formatted dollar values like '$30.50' inside
|
|
5353
|
+
* its "Spent: …" / "Remaining: …" texts.
|
|
5354
|
+
*/
|
|
5355
|
+
async function expectWorkspaceSpendStats(page, values) {
|
|
5356
|
+
const section = workspaceSpendLimitSection(page);
|
|
5357
|
+
const usage = section.getByTestId('tenant-cap-usage');
|
|
5358
|
+
await test$1.expect(usage).toBeVisible({ timeout: UI_TIMEOUT });
|
|
5359
|
+
if (values === null || values === void 0 ? void 0 : values.spent) {
|
|
5360
|
+
await test$1.expect(section.getByTestId('tenant-cap-spent')).toContainText(values.spent);
|
|
5361
|
+
}
|
|
5362
|
+
if (values === null || values === void 0 ? void 0 : values.remaining) {
|
|
5363
|
+
await test$1.expect(section.getByTestId('tenant-cap-remaining')).toContainText(values.remaining);
|
|
5364
|
+
}
|
|
5365
|
+
}
|
|
5366
|
+
/**
|
|
5367
|
+
* Assert the "actual spend" stats (financial analytics) shown while no
|
|
5368
|
+
* workspace cap is configured yet.
|
|
5369
|
+
*/
|
|
5370
|
+
async function expectWorkspaceActualSpendStats(page) {
|
|
5371
|
+
await test$1.expect(workspaceSpendLimitSection(page).getByTestId('workspace-actual-spend-stats')).toBeVisible({ timeout: UI_TIMEOUT });
|
|
5372
|
+
}
|
|
5373
|
+
// ---------------------------------------------------------------------------
|
|
5374
|
+
// Tenant settings — Billing tab, "Agent Limits" tab (configured caps table)
|
|
5375
|
+
// ---------------------------------------------------------------------------
|
|
5376
|
+
/** The Agent Limits section card in the tenant Billing tab. */
|
|
5377
|
+
function agentLimitsSection(page) {
|
|
5378
|
+
return page.getByTestId('spend-limits-agents-section');
|
|
5379
|
+
}
|
|
5380
|
+
/** An Agent Limits table row for the given agent unique_id. */
|
|
5381
|
+
function agentLimitsRow(page, mentorUniqueId) {
|
|
5382
|
+
return agentLimitsSection(page).getByTestId(`agent-limits-row-${mentorUniqueId}`);
|
|
5383
|
+
}
|
|
5384
|
+
/**
|
|
5385
|
+
* Switch the tenant Billing tab to "Agent Limits". Assumes the tenant
|
|
5386
|
+
* Billing tab is already open. Completion is gated on the section rendering.
|
|
5387
|
+
*/
|
|
5388
|
+
async function switchToAgentLimits(page) {
|
|
5389
|
+
const tab = tenantBillingTablist(page).getByRole('tab', {
|
|
5390
|
+
name: SPEND_LIMITS_LABELS.tenantTabs.agentLimits,
|
|
5391
|
+
exact: true,
|
|
5392
|
+
});
|
|
5393
|
+
await test$1.expect(tab).toBeVisible({ timeout: UI_TIMEOUT });
|
|
5394
|
+
await tab.click();
|
|
5395
|
+
await test$1.expect(agentLimitsSection(page)).toBeVisible({ timeout: UI_TIMEOUT });
|
|
5396
|
+
logger.info('Switched to tenant Agent Limits tab');
|
|
5397
|
+
}
|
|
5398
|
+
/**
|
|
5399
|
+
* Filter the Agent Limits table to one agent via the autocomplete: types the
|
|
5400
|
+
* name, clicks the matching option, and gates on the selected chip.
|
|
5401
|
+
*/
|
|
5402
|
+
async function filterAgentLimits(page, agentName) {
|
|
5403
|
+
const section = agentLimitsSection(page);
|
|
5404
|
+
const input = section.getByTestId('agent-limits-filter-input');
|
|
5405
|
+
await test$1.expect(input).toBeVisible({ timeout: UI_TIMEOUT });
|
|
5406
|
+
await input.fill(agentName);
|
|
5407
|
+
const option = section
|
|
5408
|
+
.getByTestId('agent-limits-filter-results')
|
|
5409
|
+
.getByRole('button', { name: agentName, exact: true });
|
|
5410
|
+
await test$1.expect(option).toBeVisible({ timeout: MUTATION_TIMEOUT });
|
|
5411
|
+
await option.click();
|
|
5412
|
+
await test$1.expect(section.getByTestId('agent-limits-filter-selected')).toBeVisible({
|
|
5413
|
+
timeout: UI_TIMEOUT,
|
|
5414
|
+
});
|
|
5415
|
+
logger.info(`Filtered agent limits to "${agentName}"`);
|
|
5416
|
+
}
|
|
5417
|
+
/** Clear the Agent Limits autocomplete filter (back to the full caps list). */
|
|
5418
|
+
async function clearAgentLimitsFilter(page) {
|
|
5419
|
+
const section = agentLimitsSection(page);
|
|
5420
|
+
const clearButton = section.getByTestId('agent-limits-filter-clear');
|
|
5421
|
+
await test$1.expect(clearButton).toBeVisible({ timeout: UI_TIMEOUT });
|
|
5422
|
+
await clearButton.click();
|
|
5423
|
+
await test$1.expect(section.getByTestId('agent-limits-filter-input')).toBeVisible({
|
|
5424
|
+
timeout: UI_TIMEOUT,
|
|
5425
|
+
});
|
|
5426
|
+
}
|
|
5427
|
+
/** Gate on the Agent Limits manage popup being open with the Billing editor loaded. */
|
|
5428
|
+
async function expectAgentLimitsPopupOpen(page) {
|
|
5429
|
+
const popup = page.getByTestId('agent-limits-modal');
|
|
5430
|
+
await test$1.expect(popup).toBeVisible({ timeout: UI_TIMEOUT });
|
|
5431
|
+
await test$1.expect(popup.getByTestId('spend-caps-tab-body')).toBeVisible({ timeout: UI_TIMEOUT });
|
|
5432
|
+
return popup;
|
|
5433
|
+
}
|
|
5434
|
+
/**
|
|
5435
|
+
* Open the manage popup for a capped agent's row and return the popup's
|
|
5436
|
+
* Locator. The popup hosts the same `AgentSpendCapsTab` editor as the
|
|
5437
|
+
* agent's own settings and stacks ON TOP of the tenant settings dialog, so
|
|
5438
|
+
* pass the returned Locator as the `scope` argument of the agent-scope
|
|
5439
|
+
* helpers (`setAgentSpendLimit`, `addUserSpendLimit`, …) — that pins every
|
|
5440
|
+
* query to the popup instead of the dialog underneath.
|
|
5441
|
+
*/
|
|
5442
|
+
async function openAgentLimitsManage(page, mentorUniqueId) {
|
|
5443
|
+
const manageButton = agentLimitsRow(page, mentorUniqueId).getByTestId(`agent-limits-manage-${mentorUniqueId}`);
|
|
5444
|
+
await test$1.expect(manageButton).toBeVisible({ timeout: UI_TIMEOUT });
|
|
5445
|
+
await manageButton.click();
|
|
5446
|
+
const popup = await expectAgentLimitsPopupOpen(page);
|
|
5447
|
+
logger.info(`Opened agent limits popup for "${mentorUniqueId}"`);
|
|
5448
|
+
return popup;
|
|
5449
|
+
}
|
|
5450
|
+
/**
|
|
5451
|
+
* With the filter set to an agent that has no cap yet, click the empty
|
|
5452
|
+
* state's "Set Spend Limit" button and return the opened popup's Locator
|
|
5453
|
+
* (pass it as `scope` to the agent-scope helpers, see
|
|
5454
|
+
* `openAgentLimitsManage`).
|
|
5455
|
+
*/
|
|
5456
|
+
async function clickSetSpendLimitForFilteredAgent(page) {
|
|
5457
|
+
const button = agentLimitsSection(page).getByTestId('agent-limits-set-limit-button');
|
|
5458
|
+
await test$1.expect(button).toBeVisible({ timeout: UI_TIMEOUT });
|
|
5459
|
+
await button.click();
|
|
5460
|
+
const popup = await expectAgentLimitsPopupOpen(page);
|
|
5461
|
+
logger.info('Opened agent limits popup from the filtered empty state');
|
|
5462
|
+
return popup;
|
|
5463
|
+
}
|
|
5464
|
+
/** Close the Agent Limits manage popup (Escape) and gate on it disappearing. */
|
|
5465
|
+
async function closeAgentLimitsPopup(page) {
|
|
5466
|
+
const popup = page.getByTestId('agent-limits-modal');
|
|
5467
|
+
await test$1.expect(popup).toBeVisible({ timeout: UI_TIMEOUT });
|
|
5468
|
+
await page.keyboard.press('Escape');
|
|
5469
|
+
await test$1.expect(popup).toBeHidden({ timeout: UI_TIMEOUT });
|
|
5470
|
+
}
|
|
5471
|
+
/**
|
|
5472
|
+
* Idempotently flip an agent cap's Status toggle in the Agent Limits table
|
|
5473
|
+
* (saves immediately). Gates on the saved toast and the switch reflecting
|
|
5474
|
+
* the new state after the list refetch.
|
|
5475
|
+
*/
|
|
5476
|
+
async function setAgentLimitsRowEnabled(page, mentorUniqueId, enabled) {
|
|
5477
|
+
const toggle = agentLimitsRow(page, mentorUniqueId).getByTestId(`agent-limits-status-switch-${mentorUniqueId}`);
|
|
5478
|
+
await test$1.expect(toggle).toBeVisible({ timeout: UI_TIMEOUT });
|
|
5479
|
+
if ((await toggle.getAttribute('aria-checked')) === String(enabled)) {
|
|
5480
|
+
logger.info(`Agent cap "${mentorUniqueId}" already ${enabled ? 'enabled' : 'disabled'}`);
|
|
5481
|
+
return;
|
|
5482
|
+
}
|
|
5483
|
+
await test$1.expect(toggle).toBeEnabled({ timeout: UI_TIMEOUT });
|
|
5484
|
+
await toggle.click();
|
|
5485
|
+
await test$1.expect(page.getByText(SPEND_LIMITS_LABELS.toasts.saved).first()).toBeVisible({
|
|
5486
|
+
timeout: MUTATION_TIMEOUT,
|
|
5487
|
+
});
|
|
5488
|
+
await test$1.expect(toggle).toHaveAttribute('aria-checked', String(enabled), {
|
|
5489
|
+
timeout: MUTATION_TIMEOUT,
|
|
5490
|
+
});
|
|
5491
|
+
logger.info(`Agent cap "${mentorUniqueId}" ${enabled ? 'enabled' : 'disabled'}`);
|
|
5492
|
+
}
|
|
5493
|
+
/**
|
|
5494
|
+
* Assert an Agent Limits row is visible, optionally checking its formatted
|
|
5495
|
+
* cell contents, e.g. `{ name: 'Math Tutor', limit: '$250.00', spent: '$30.50' }`.
|
|
5496
|
+
* Text matching is substring-based so a limit can be checked with or without
|
|
5497
|
+
* its "/ Month" interval suffix.
|
|
5498
|
+
*/
|
|
5499
|
+
async function expectAgentLimitsRowContent(page, mentorUniqueId, texts) {
|
|
5500
|
+
const row = agentLimitsRow(page, mentorUniqueId);
|
|
5501
|
+
await test$1.expect(row).toBeVisible({ timeout: UI_TIMEOUT });
|
|
5502
|
+
for (const text of Object.values(texts !== null && texts !== void 0 ? texts : {})) {
|
|
5503
|
+
if (text)
|
|
5504
|
+
await test$1.expect(row).toContainText(text);
|
|
5505
|
+
}
|
|
5506
|
+
}
|
|
5507
|
+
// ---------------------------------------------------------------------------
|
|
5508
|
+
// Tenant settings — end-to-end composites through the manage popup
|
|
5509
|
+
// ---------------------------------------------------------------------------
|
|
5510
|
+
/**
|
|
5511
|
+
* End to end from the tenant Agent Limits tab, for an agent with NO cap yet:
|
|
5512
|
+
* filter to the agent by name, open the popup from the empty state's "Set
|
|
5513
|
+
* Spend Limit" button, save the agent-scoped limit inside the popup, close
|
|
5514
|
+
* it, and gate on the agent's row rendering after the list refetch.
|
|
5515
|
+
*/
|
|
5516
|
+
async function createAgentSpendLimitViaFilter(page, agent, values) {
|
|
5517
|
+
await filterAgentLimits(page, agent.name);
|
|
5518
|
+
const popup = await clickSetSpendLimitForFilteredAgent(page);
|
|
5519
|
+
await setAgentSpendLimit(page, values, popup);
|
|
5520
|
+
await closeAgentLimitsPopup(page);
|
|
5521
|
+
await test$1.expect(agentLimitsRow(page, agent.mentorUniqueId)).toBeVisible({
|
|
5522
|
+
timeout: MUTATION_TIMEOUT,
|
|
5523
|
+
});
|
|
5524
|
+
logger.info(`Created agent spend limit for "${agent.name}" via the Agent Limits filter`);
|
|
5525
|
+
}
|
|
5526
|
+
/**
|
|
5527
|
+
* End to end from the tenant Agent Limits tab: open a capped agent's manage
|
|
5528
|
+
* popup, save the agent-scoped limit inside it, close the popup, and gate on
|
|
5529
|
+
* the row still rendering after the list refetch.
|
|
5530
|
+
*/
|
|
5531
|
+
async function setAgentSpendLimitFromTenantBilling(page, mentorUniqueId, values) {
|
|
5532
|
+
const popup = await openAgentLimitsManage(page, mentorUniqueId);
|
|
5533
|
+
await setAgentSpendLimit(page, values, popup);
|
|
5534
|
+
await closeAgentLimitsPopup(page);
|
|
5535
|
+
await test$1.expect(agentLimitsRow(page, mentorUniqueId)).toBeVisible({ timeout: MUTATION_TIMEOUT });
|
|
5536
|
+
logger.info(`Saved agent spend limit for "${mentorUniqueId}" from tenant Billing`);
|
|
5537
|
+
}
|
|
5538
|
+
/**
|
|
5539
|
+
* End to end from the tenant Agent Limits tab: open the agent's manage popup
|
|
5540
|
+
* and delete its agent-scoped limit through the confirmation dialog. Closing
|
|
5541
|
+
* the popup afterwards gates on the agent's row disappearing from the table
|
|
5542
|
+
* (the tenant-wide list only holds configured caps).
|
|
5543
|
+
*/
|
|
5544
|
+
async function deleteAgentSpendLimitFromTenantBilling(page, mentorUniqueId) {
|
|
5545
|
+
const popup = await openAgentLimitsManage(page, mentorUniqueId);
|
|
5546
|
+
await deleteAgentSpendLimit(page, popup);
|
|
5547
|
+
await closeAgentLimitsPopup(page);
|
|
5548
|
+
await test$1.expect(agentLimitsRow(page, mentorUniqueId)).toBeHidden({ timeout: MUTATION_TIMEOUT });
|
|
5549
|
+
logger.info(`Deleted agent spend limit for "${mentorUniqueId}" from tenant Billing`);
|
|
5550
|
+
}
|
|
5551
|
+
/**
|
|
5552
|
+
* End to end per-user limit from the tenant Agent Limits tab: open the
|
|
5553
|
+
* agent's manage popup, add the user limit through its "Per User" sub-tab
|
|
5554
|
+
* (nested user-cap modal inside the popup), then close the popup. Completion
|
|
5555
|
+
* is gated inside `addUserSpendLimit` on the modal closing and the user's
|
|
5556
|
+
* row rendering in the popup's table.
|
|
5557
|
+
*/
|
|
5558
|
+
async function addUserSpendLimitFromTenantBilling(page, mentorUniqueId, values) {
|
|
5559
|
+
const popup = await openAgentLimitsManage(page, mentorUniqueId);
|
|
5560
|
+
await addUserSpendLimit(page, values, popup);
|
|
5561
|
+
await closeAgentLimitsPopup(page);
|
|
5562
|
+
logger.info(`Added user spend limit for "${values.email}" on "${mentorUniqueId}" from tenant Billing`);
|
|
5563
|
+
}
|
|
5564
|
+
|
|
4895
5565
|
/**
|
|
4896
5566
|
* Tasks tab helpers — Playwright bindings for the `AgentTasksTab`
|
|
4897
5567
|
* component from `@iblai/web-containers`.
|
|
@@ -7193,6 +7863,7 @@ exports.LTI_TEST_IDS = LTI_TEST_IDS;
|
|
|
7193
7863
|
exports.MailsacClient = MailsacClient;
|
|
7194
7864
|
exports.PRIVACY_LABELS = PRIVACY_LABELS;
|
|
7195
7865
|
exports.SCREENSHARE_LABELS = SCREENSHARE_LABELS;
|
|
7866
|
+
exports.SPEND_LIMITS_LABELS = SPEND_LIMITS_LABELS;
|
|
7196
7867
|
exports.SUPPORT_LABELS = SUPPORT_LABELS;
|
|
7197
7868
|
exports.TASKS_LABELS = TASKS_LABELS;
|
|
7198
7869
|
exports.VOICE_LABELS = VOICE_LABELS;
|
|
@@ -7202,6 +7873,10 @@ exports.addMemory = addMemory;
|
|
|
7202
7873
|
exports.addQaPairsManually = addQaPairsManually;
|
|
7203
7874
|
exports.addSkillToAgent = addSkillToAgent;
|
|
7204
7875
|
exports.addTextResource = addTextResource;
|
|
7876
|
+
exports.addUserSpendLimit = addUserSpendLimit;
|
|
7877
|
+
exports.addUserSpendLimitFromTenantBilling = addUserSpendLimitFromTenantBilling;
|
|
7878
|
+
exports.agentLimitsRow = agentLimitsRow;
|
|
7879
|
+
exports.agentLimitsSection = agentLimitsSection;
|
|
7205
7880
|
exports.archiveFirstMemory = archiveFirstMemory;
|
|
7206
7881
|
exports.archiveMemoryByContent = archiveMemoryByContent;
|
|
7207
7882
|
exports.billingAutoRechargeSection = billingAutoRechargeSection;
|
|
@@ -7220,6 +7895,7 @@ exports.cancelNewInstanceDialog = cancelNewInstanceDialog;
|
|
|
7220
7895
|
exports.cancelStartEvaluation = cancelStartEvaluation;
|
|
7221
7896
|
exports.checkAdminStatus = checkAdminStatus;
|
|
7222
7897
|
exports.checkRunStatus = checkRunStatus;
|
|
7898
|
+
exports.clearAgentLimitsFilter = clearAgentLimitsFilter;
|
|
7223
7899
|
exports.clearDateRangeFilter = clearDateRangeFilter;
|
|
7224
7900
|
exports.clearGradeResultOverride = clearGradeResultOverride;
|
|
7225
7901
|
exports.clearInstanceSearch = clearInstanceSearch;
|
|
@@ -7232,6 +7908,8 @@ exports.clickBillingUpgrade = clickBillingUpgrade;
|
|
|
7232
7908
|
exports.clickChatPrivacyToggle = clickChatPrivacyToggle;
|
|
7233
7909
|
exports.clickDownloadAgain = clickDownloadAgain;
|
|
7234
7910
|
exports.clickManualDownloadLink = clickManualDownloadLink;
|
|
7911
|
+
exports.clickSetSpendLimitForFilteredAgent = clickSetSpendLimitForFilteredAgent;
|
|
7912
|
+
exports.closeAgentLimitsPopup = closeAgentLimitsPopup;
|
|
7235
7913
|
exports.closeBenchmarkItemsDialog = closeBenchmarkItemsDialog;
|
|
7236
7914
|
exports.closeCreditBalanceDropdown = closeCreditBalanceDropdown;
|
|
7237
7915
|
exports.closeEvaluationDetailDialog = closeEvaluationDetailDialog;
|
|
@@ -7242,6 +7920,7 @@ exports.confirmEnableChatPrivacyMidSession = confirmEnableChatPrivacyMidSession;
|
|
|
7242
7920
|
exports.confirmKeyDelete = confirmKeyDelete;
|
|
7243
7921
|
exports.connectToInstance = connectToInstance;
|
|
7244
7922
|
exports.copyEndpoint = copyEndpoint;
|
|
7923
|
+
exports.createAgentSpendLimitViaFilter = createAgentSpendLimitViaFilter;
|
|
7245
7924
|
exports.createAuthSetup = createAuthSetup;
|
|
7246
7925
|
exports.createBenchmark = createBenchmark;
|
|
7247
7926
|
exports.createEnvConfig = createEnvConfig;
|
|
@@ -7255,6 +7934,8 @@ exports.createTool = createTool;
|
|
|
7255
7934
|
exports.creditBalancePanel = creditBalancePanel;
|
|
7256
7935
|
exports.creditBalancePlanBadge = creditBalancePlanBadge;
|
|
7257
7936
|
exports.creditBalanceTrigger = creditBalanceTrigger;
|
|
7937
|
+
exports.deleteAgentSpendLimit = deleteAgentSpendLimit;
|
|
7938
|
+
exports.deleteAgentSpendLimitFromTenantBilling = deleteAgentSpendLimitFromTenantBilling;
|
|
7258
7939
|
exports.deleteEvaluation = deleteEvaluation;
|
|
7259
7940
|
exports.deleteFirstMemory = deleteFirstMemory;
|
|
7260
7941
|
exports.deleteGraderCriterion = deleteGraderCriterion;
|
|
@@ -7265,6 +7946,8 @@ exports.deleteQaItem = deleteQaItem;
|
|
|
7265
7946
|
exports.deleteResource = deleteResource;
|
|
7266
7947
|
exports.deleteSkill = deleteSkill;
|
|
7267
7948
|
exports.deleteTask = deleteTask;
|
|
7949
|
+
exports.deleteUserSpendLimit = deleteUserSpendLimit;
|
|
7950
|
+
exports.deleteWorkspaceSpendLimit = deleteWorkspaceSpendLimit;
|
|
7268
7951
|
exports.disableSkill = disableSkill;
|
|
7269
7952
|
exports.disableSupport = disableSupport;
|
|
7270
7953
|
exports.disconnectInstance = disconnectInstance;
|
|
@@ -7275,10 +7958,12 @@ exports.editLink = editLink;
|
|
|
7275
7958
|
exports.editSkill = editSkill;
|
|
7276
7959
|
exports.editTextResource = editTextResource;
|
|
7277
7960
|
exports.editTool = editTool;
|
|
7961
|
+
exports.editUserSpendLimit = editUserSpendLimit;
|
|
7278
7962
|
exports.enableSkill = enableSkill;
|
|
7279
7963
|
exports.enableSupport = enableSupport;
|
|
7280
7964
|
exports.expandReview = expandReview;
|
|
7281
7965
|
exports.expandTrace = expandTrace;
|
|
7966
|
+
exports.expectAgentLimitsRowContent = expectAgentLimitsRowContent;
|
|
7282
7967
|
exports.expectAllEndpointsVisible = expectAllEndpointsVisible;
|
|
7283
7968
|
exports.expectBenchmarkListed = expectBenchmarkListed;
|
|
7284
7969
|
exports.expectBillingAutoRechargeSection = expectBillingAutoRechargeSection;
|
|
@@ -7362,9 +8047,12 @@ exports.expectTraceScore = expectTraceScore;
|
|
|
7362
8047
|
exports.expectTtsSelectDisabled = expectTtsSelectDisabled;
|
|
7363
8048
|
exports.expectVoiceProviderSelected = expectVoiceProviderSelected;
|
|
7364
8049
|
exports.expectVoiceVisible = expectVoiceVisible;
|
|
8050
|
+
exports.expectWorkspaceActualSpendStats = expectWorkspaceActualSpendStats;
|
|
8051
|
+
exports.expectWorkspaceSpendStats = expectWorkspaceSpendStats;
|
|
7365
8052
|
exports.exportRunCsv = exportRunCsv;
|
|
7366
8053
|
exports.fillLinkName = fillLinkName;
|
|
7367
8054
|
exports.fillToolForm = fillToolForm;
|
|
8055
|
+
exports.filterAgentLimits = filterAgentLimits;
|
|
7368
8056
|
exports.filterByAction = filterByAction;
|
|
7369
8057
|
exports.filterByActionAndVerify = filterByActionAndVerify;
|
|
7370
8058
|
exports.filterByActor = filterByActor;
|
|
@@ -7487,6 +8175,7 @@ exports.isPrivateModeTabVisible = isPrivateModeTabVisible;
|
|
|
7487
8175
|
exports.isSandboxTabVisible = isSandboxTabVisible;
|
|
7488
8176
|
exports.isScreenShareTabVisible = isScreenShareTabVisible;
|
|
7489
8177
|
exports.isSkillEnabled = isSkillEnabled;
|
|
8178
|
+
exports.isSpendLimitsTabVisible = isSpendLimitsTabVisible;
|
|
7490
8179
|
exports.isSupportEnabled = isSupportEnabled;
|
|
7491
8180
|
exports.isSupportTabVisible = isSupportTabVisible;
|
|
7492
8181
|
exports.isTasksTabVisible = isTasksTabVisible;
|
|
@@ -7501,6 +8190,7 @@ exports.navigateToDataReports = navigateToDataReports;
|
|
|
7501
8190
|
exports.navigateToReportDownload = navigateToReportDownload;
|
|
7502
8191
|
exports.openAddItemsDialog = openAddItemsDialog;
|
|
7503
8192
|
exports.openAddMemoryDialog = openAddMemoryDialog;
|
|
8193
|
+
exports.openAgentLimitsManage = openAgentLimitsManage;
|
|
7504
8194
|
exports.openAgentPromptEditModal = openAgentPromptEditModal;
|
|
7505
8195
|
exports.openBenchmarkItems = openBenchmarkItems;
|
|
7506
8196
|
exports.openCallConfigVoicePicker = openCallConfigVoicePicker;
|
|
@@ -7581,8 +8271,12 @@ exports.selectTtsProvider = selectTtsProvider;
|
|
|
7581
8271
|
exports.selectVoice = selectVoice;
|
|
7582
8272
|
exports.selectVoiceProvider = selectVoiceProvider;
|
|
7583
8273
|
exports.sendAgentChatMessage = sendAgentChatMessage;
|
|
8274
|
+
exports.setAgentLimitsRowEnabled = setAgentLimitsRowEnabled;
|
|
8275
|
+
exports.setAgentSpendLimit = setAgentSpendLimit;
|
|
8276
|
+
exports.setAgentSpendLimitFromTenantBilling = setAgentSpendLimitFromTenantBilling;
|
|
7584
8277
|
exports.setBlockMessage = setBlockMessage;
|
|
7585
8278
|
exports.setCallLanguage = setCallLanguage;
|
|
8279
|
+
exports.setCatalogSkillEnabled = setCatalogSkillEnabled;
|
|
7586
8280
|
exports.setEnableVideo = setEnableVideo;
|
|
7587
8281
|
exports.setEntitySelected = setEntitySelected;
|
|
7588
8282
|
exports.setGradingEnabled = setGradingEnabled;
|
|
@@ -7592,6 +8286,8 @@ exports.setSupportEnabled = setSupportEnabled;
|
|
|
7592
8286
|
exports.setTenantChatPrivacyEnabled = setTenantChatPrivacyEnabled;
|
|
7593
8287
|
exports.setTicketStatus = setTicketStatus;
|
|
7594
8288
|
exports.setUseFunctionCallingEnabled = setUseFunctionCallingEnabled;
|
|
8289
|
+
exports.setUserSpendLimitEnabled = setUserSpendLimitEnabled;
|
|
8290
|
+
exports.setWorkspaceSpendLimit = setWorkspaceSpendLimit;
|
|
7595
8291
|
exports.setupSandboxInstance = setupSandboxInstance;
|
|
7596
8292
|
exports.shouldAddNewRowWhenClickingAddRowButton = shouldAddNewRowWhenClickingAddRowButton;
|
|
7597
8293
|
exports.shouldAllowEditingCellValuesInCSVEditor = shouldAllowEditingCellValuesInCSVEditor;
|
|
@@ -7610,11 +8306,13 @@ exports.shouldSaveEditedCSVAndTriggerDownload = shouldSaveEditedCSVAndTriggerDow
|
|
|
7610
8306
|
exports.shouldShowCombiningReportsDialog = shouldShowCombiningReportsDialog;
|
|
7611
8307
|
exports.shouldVerifyCSVEditorDialogAccessibility = shouldVerifyCSVEditorDialogAccessibility;
|
|
7612
8308
|
exports.signUpWithEmailAndPassword = signUpWithEmailAndPassword;
|
|
8309
|
+
exports.spendLimitsTabBody = spendLimitsTabBody;
|
|
7613
8310
|
exports.startEvaluation = startEvaluation;
|
|
7614
8311
|
exports.submitLinkModal = submitLinkModal;
|
|
7615
8312
|
exports.submitLlmJudge = submitLlmJudge;
|
|
7616
8313
|
exports.submitToolModal = submitToolModal;
|
|
7617
8314
|
exports.switchToAddItemsSubTab = switchToAddItemsSubTab;
|
|
8315
|
+
exports.switchToAgentLimits = switchToAgentLimits;
|
|
7618
8316
|
exports.switchToAgentSkillsSubTab = switchToAgentSkillsSubTab;
|
|
7619
8317
|
exports.switchToAvailableSkillsSubTab = switchToAvailableSkillsSubTab;
|
|
7620
8318
|
exports.switchToEvaluationTab = switchToEvaluationTab;
|
|
@@ -7623,16 +8321,20 @@ exports.switchToGraderTab = switchToGraderTab;
|
|
|
7623
8321
|
exports.switchToLtiSubTab = switchToLtiSubTab;
|
|
7624
8322
|
exports.switchToLtiTab = switchToLtiTab;
|
|
7625
8323
|
exports.switchToMemoryTab = switchToMemoryTab;
|
|
8324
|
+
exports.switchToPlanAndCredits = switchToPlanAndCredits;
|
|
7626
8325
|
exports.switchToPrivacyTab = switchToPrivacyTab;
|
|
7627
8326
|
exports.switchToPrivateModeTab = switchToPrivateModeTab;
|
|
7628
8327
|
exports.switchToSandboxTab = switchToSandboxTab;
|
|
7629
8328
|
exports.switchToScreenShareTab = switchToScreenShareTab;
|
|
7630
8329
|
exports.switchToSkillResourcesSubTab = switchToSkillResourcesSubTab;
|
|
7631
8330
|
exports.switchToSkillsTab = switchToSkillsTab;
|
|
8331
|
+
exports.switchToSpendLimitsSubTab = switchToSpendLimitsSubTab;
|
|
8332
|
+
exports.switchToSpendLimitsTab = switchToSpendLimitsTab;
|
|
7632
8333
|
exports.switchToSupportTab = switchToSupportTab;
|
|
7633
8334
|
exports.switchToTasksTab = switchToTasksTab;
|
|
7634
8335
|
exports.switchToVoiceSubTab = switchToVoiceSubTab;
|
|
7635
8336
|
exports.switchToVoiceTab = switchToVoiceTab;
|
|
8337
|
+
exports.switchToWorkspaceSpendLimits = switchToWorkspaceSpendLimits;
|
|
7636
8338
|
exports.teardownSandboxInstance = teardownSandboxInstance;
|
|
7637
8339
|
exports.test = test;
|
|
7638
8340
|
exports.toggleAutoPush = toggleAutoPush;
|
|
@@ -7641,6 +8343,7 @@ exports.toggleSkill = toggleSkill;
|
|
|
7641
8343
|
exports.toolFields = toolFields;
|
|
7642
8344
|
exports.uploadAssetResource = uploadAssetResource;
|
|
7643
8345
|
exports.uploadQaCsv = uploadQaCsv;
|
|
8346
|
+
exports.userSpendLimitRow = userSpendLimitRow;
|
|
7644
8347
|
exports.verifyAgentConfigPromptsVisible = verifyAgentConfigPromptsVisible;
|
|
7645
8348
|
exports.verifyAgentSkillsEmptyState = verifyAgentSkillsEmptyState;
|
|
7646
8349
|
exports.verifyAuditLogEmptyState = verifyAuditLogEmptyState;
|
|
@@ -7676,4 +8379,5 @@ exports.waitForPageLoad = waitForPageLoad;
|
|
|
7676
8379
|
exports.waitForPageReady = waitForPageReady;
|
|
7677
8380
|
exports.waitForReportDownload = waitForReportDownload;
|
|
7678
8381
|
exports.waitForTicketInList = waitForTicketInList;
|
|
8382
|
+
exports.workspaceSpendLimitSection = workspaceSpendLimitSection;
|
|
7679
8383
|
//# sourceMappingURL=index.cjs.map
|