@iblai/iblai-js 2.2.6 → 2.2.8

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.
@@ -0,0 +1,138 @@
1
+ import { Locator, Page } from '@playwright/test';
2
+ /**
3
+ * Grader tab helpers — Playwright bindings for the standalone
4
+ * `AgentGraderTab` component from `@iblai/web-containers`.
5
+ *
6
+ * The tab is a peer to Tools/LLM/Memory/etc in the edit-agent modal. Its
7
+ * master toggle attaches/detaches the "Grading" tool on the agent; the gated
8
+ * content splits into two sub-tabs — Grading setup (configuration form) and
9
+ * Rubric (criteria table with modal-based add/edit/delete behind a
10
+ * three-dots row menu).
11
+ *
12
+ * Selector policy (flakiness-proof by construction):
13
+ * - Every query is scoped dialog-first: resolve the edit-agent dialog, then
14
+ * the tab body, then the sub-element — never a bare page-wide match. The
15
+ * criterion/delete modals portal outside that dialog, so they are located
16
+ * by their own testids and all fills happen within the modal locator.
17
+ * - Stable hooks only: `data-testid`, role + accessible name, labels.
18
+ * No CSS class or structural selectors.
19
+ * - No `waitForTimeout` / `networkidle`. Progress is gated on UI state that
20
+ * only exists after the awaited transition: sub-tab section testids, a
21
+ * modal closing after a persisted mutation, a rubric row
22
+ * appearing/disappearing after the list refetch.
23
+ */
24
+ export declare const GRADER_LABELS: {
25
+ readonly tabName: "Grader";
26
+ readonly capabilityToggle: "Grading";
27
+ readonly subTabs: {
28
+ readonly setup: "Grading setup";
29
+ readonly rubric: "Rubric";
30
+ };
31
+ readonly addButton: "Add criterion";
32
+ readonly menu: {
33
+ /** aria-label template for a row's three-dots trigger. */
34
+ readonly actionsAria: (name: string) => string;
35
+ readonly edit: "Edit";
36
+ readonly delete: "Delete";
37
+ };
38
+ readonly modal: {
39
+ readonly fields: {
40
+ readonly name: "Name";
41
+ readonly criteria: "Criteria";
42
+ readonly points: "Points";
43
+ };
44
+ readonly cancel: "Cancel";
45
+ };
46
+ readonly toasts: {
47
+ readonly toggleOn: "Grading turned on";
48
+ readonly toggleOff: "Grading turned off — your rubric is kept for next time";
49
+ };
50
+ readonly gradingModeOptions: {
51
+ readonly submission: "A submission";
52
+ readonly conversation: "The conversation";
53
+ };
54
+ readonly feedbackModeOptions: {
55
+ readonly overall: "Overall feedback only";
56
+ readonly per_criteria: "Feedback per criterion";
57
+ readonly both: "Overall + per criterion";
58
+ };
59
+ };
60
+ export type GraderSubTab = keyof typeof GRADER_LABELS.subTabs;
61
+ export type GraderGradingMode = keyof typeof GRADER_LABELS.gradingModeOptions;
62
+ export type GraderFeedbackMode = keyof typeof GRADER_LABELS.feedbackModeOptions;
63
+ export interface GraderCriterionInput {
64
+ name: string;
65
+ criteria: string;
66
+ points: number;
67
+ }
68
+ /** The Grader tab's body, scoped through the edit-agent dialog. */
69
+ export declare function graderTabBody(page: Page): Locator;
70
+ /**
71
+ * Returns false if the Grader tab isn't currently rendered in the
72
+ * edit-agent dialog (host didn't register it, or RBAC hid it).
73
+ */
74
+ export declare function isGraderTabVisible(page: Page): Promise<boolean>;
75
+ /**
76
+ * Switch to the Grader top-level tab. Assumes the edit-agent dialog is
77
+ * open. Completion is gated on the tab body's testid, not on timing.
78
+ */
79
+ export declare function switchToGraderTab(page: Page): Promise<void>;
80
+ /**
81
+ * Switch between the Grader tab's two sub-tabs. Completion is gated on the
82
+ * target section's testid rendering.
83
+ */
84
+ export declare function switchToGraderSubTab(page: Page, subTab: GraderSubTab): Promise<void>;
85
+ /** Read the current on/off state of the Grading capability toggle. */
86
+ export declare function isGradingEnabled(page: Page): Promise<boolean>;
87
+ /**
88
+ * Idempotently set the Grading capability toggle. Waits for the success
89
+ * toast (which only fires after the settings PATCH resolves — the switch
90
+ * itself flips optimistically and would roll back on failure) and then for
91
+ * the gated content to reflect the new state.
92
+ */
93
+ export declare function setGradingEnabled(page: Page, enabled: boolean): Promise<void>;
94
+ /**
95
+ * Fill and persist the Grading setup form (switches to the setup sub-tab
96
+ * first). Only the provided fields are changed. Completion is gated on the
97
+ * Save button returning to disabled: after the config POST/PATCH resolves,
98
+ * the form rehydrates from the server copy and stops being dirty.
99
+ */
100
+ export declare function saveGraderConfig(page: Page, values: {
101
+ instructions?: string;
102
+ gradingMode?: GraderGradingMode;
103
+ feedbackMode?: GraderFeedbackMode;
104
+ }): Promise<void>;
105
+ /**
106
+ * Add a rubric criterion through the Add-criterion modal (switches to the
107
+ * Rubric sub-tab first). Requires a saved grader configuration — the Add
108
+ * button is disabled until one exists. Completion is gated on the modal
109
+ * closing and the new row rendering after the list refetch.
110
+ */
111
+ export declare function addGraderCriterion(page: Page, criterion: GraderCriterionInput): Promise<void>;
112
+ /**
113
+ * Edit an existing rubric criterion via its row's three-dots menu → Edit
114
+ * modal. The row is located by its current name; all three fields are
115
+ * rewritten. Completion is gated on the modal closing and the updated row
116
+ * rendering.
117
+ */
118
+ export declare function editGraderCriterion(page: Page, currentName: string, updates: GraderCriterionInput): Promise<void>;
119
+ /**
120
+ * Delete a rubric criterion via its row's three-dots menu → confirmation
121
+ * modal. Completion is gated on the modal closing and the row disappearing
122
+ * after the server delete + list refetch.
123
+ */
124
+ export declare function deleteGraderCriterion(page: Page, name: string): Promise<void>;
125
+ /**
126
+ * Assert the last remaining criterion's Delete menu action is disabled and
127
+ * the explanatory hint is shown — the backend refuses to delete the final
128
+ * row, so the UI must block it too. Closes the menu again before returning.
129
+ */
130
+ export declare function expectLastCriterionDeleteDisabled(page: Page, name: string): Promise<void>;
131
+ /**
132
+ * Assert whether the amber misconfiguration banner is shown (grading on
133
+ * with no config yet, or with an empty rubric). The banner sits above the
134
+ * sub-tabs, so no sub-tab switch is needed.
135
+ */
136
+ export declare function expectGraderMisconfiguredWarning(page: Page, visible: boolean): Promise<void>;
137
+ /** Assert the rubric's live "total possible points" readout (Rubric sub-tab). */
138
+ export declare function expectGraderTotalPoints(page: Page, total: number): Promise<void>;
@@ -29,6 +29,8 @@ export { VOICE_LABELS, isVoiceTabVisible, switchToVoiceTab, switchToVoiceSubTab,
29
29
  export type { VoiceProvider, CallMode, TtsProvider, SttProvider, LlmProvider, } from './voice-tab-helpers';
30
30
  export { SCREENSHARE_LABELS, isScreenShareTabVisible, switchToScreenShareTab, openScreenSharePromptEditor, setScreenSharePrompt, saveScreenSharePrompts, expectScreenShareDisabledHint, } from './screenshare-tab-helpers';
31
31
  export type { ScreenSharePromptField } from './screenshare-tab-helpers';
32
+ export { graderTabBody, isGraderTabVisible, switchToGraderTab, switchToGraderSubTab, isGradingEnabled, setGradingEnabled, saveGraderConfig, addGraderCriterion, editGraderCriterion, deleteGraderCriterion, expectLastCriterionDeleteDisabled, expectGraderMisconfiguredWarning, expectGraderTotalPoints, GRADER_LABELS, } from './grader-tab-helpers';
33
+ export type { GraderCriterionInput, GraderGradingMode, GraderFeedbackMode, GraderSubTab, } from './grader-tab-helpers';
32
34
  export { billingPlanSection, billingCreditsSection, billingAutoRechargeSection, getBillingPlanLabel, getBillingAutoRechargeStatus, waitForBillingTabReady, expectBillingPlanSection, expectBillingCreditsSection, expectBillingAutoRechargeSection, expectBillingTabForFreePlan, expectBillingTabForTrialPlan, expectBillingTabForPremiumPlan, expectBillingTabForCurrentPlan, clickBillingUpgrade, clickBillingAddCredits, clickBillingManageBilling, clickBillingManageUsage, } from './billing-tab-helpers';
33
35
  export type { BillingAutoRechargeStatus } from './billing-tab-helpers';
34
36
  export { TASKS_LABELS, isTasksTabVisible, switchToTasksTab, getScheduleTaskButton, getSearchInput, getTaskRow, expectTotalTasks, expectCompletedTasks, expectFailedTasks, expectTasksEmpty, expectTaskInList, expectTaskNotInList, selectTaskInList, expectTaskStatus, searchTasks, openScheduleTaskDialog, scheduleTask, expectScheduleStartTimeInPastError, deleteTask, expectLogsForTask, expectNoLogsForSelectedTask, openFirstLogDetails, expectLogDetailsStatus, } from './tasks-tab-helpers';
@@ -4040,6 +4040,318 @@ async function expectScreenShareDisabledHint(scope, visible) {
4040
4040
  }
4041
4041
  }
4042
4042
 
4043
+ /**
4044
+ * Grader tab helpers — Playwright bindings for the standalone
4045
+ * `AgentGraderTab` component from `@iblai/web-containers`.
4046
+ *
4047
+ * The tab is a peer to Tools/LLM/Memory/etc in the edit-agent modal. Its
4048
+ * master toggle attaches/detaches the "Grading" tool on the agent; the gated
4049
+ * content splits into two sub-tabs — Grading setup (configuration form) and
4050
+ * Rubric (criteria table with modal-based add/edit/delete behind a
4051
+ * three-dots row menu).
4052
+ *
4053
+ * Selector policy (flakiness-proof by construction):
4054
+ * - Every query is scoped dialog-first: resolve the edit-agent dialog, then
4055
+ * the tab body, then the sub-element — never a bare page-wide match. The
4056
+ * criterion/delete modals portal outside that dialog, so they are located
4057
+ * by their own testids and all fills happen within the modal locator.
4058
+ * - Stable hooks only: `data-testid`, role + accessible name, labels.
4059
+ * No CSS class or structural selectors.
4060
+ * - No `waitForTimeout` / `networkidle`. Progress is gated on UI state that
4061
+ * only exists after the awaited transition: sub-tab section testids, a
4062
+ * modal closing after a persisted mutation, a rubric row
4063
+ * appearing/disappearing after the list refetch.
4064
+ */
4065
+ const GRADER_LABELS = {
4066
+ tabName: 'Grader',
4067
+ capabilityToggle: 'Grading',
4068
+ subTabs: {
4069
+ setup: 'Grading setup',
4070
+ rubric: 'Rubric',
4071
+ },
4072
+ addButton: 'Add criterion',
4073
+ menu: {
4074
+ /** aria-label template for a row's three-dots trigger. */
4075
+ actionsAria: (name) => `Actions for ${name}`,
4076
+ edit: 'Edit',
4077
+ delete: 'Delete',
4078
+ },
4079
+ modal: {
4080
+ fields: {
4081
+ name: 'Name',
4082
+ criteria: 'Criteria',
4083
+ points: 'Points',
4084
+ },
4085
+ cancel: 'Cancel',
4086
+ },
4087
+ toasts: {
4088
+ toggleOn: 'Grading turned on',
4089
+ toggleOff: 'Grading turned off — your rubric is kept for next time',
4090
+ },
4091
+ gradingModeOptions: {
4092
+ submission: 'A submission',
4093
+ conversation: 'The conversation',
4094
+ },
4095
+ feedbackModeOptions: {
4096
+ overall: 'Overall feedback only',
4097
+ per_criteria: 'Feedback per criterion',
4098
+ both: 'Overall + per criterion',
4099
+ },
4100
+ };
4101
+ const UI_TIMEOUT = 10000;
4102
+ const MUTATION_TIMEOUT = 15000;
4103
+ /**
4104
+ * The edit-agent dialog that hosts the settings tabs. Scoping through the
4105
+ * dialog first keeps every subsequent query away from same-named elements
4106
+ * elsewhere on the page (nested portals, background page content).
4107
+ */
4108
+ function editAgentDialog(page) {
4109
+ return page.getByRole('dialog').filter({ has: page.getByRole('tablist') });
4110
+ }
4111
+ /** The Grader tab's body, scoped through the edit-agent dialog. */
4112
+ function graderTabBody(page) {
4113
+ return editAgentDialog(page).getByTestId('grader-tab-body');
4114
+ }
4115
+ /** The Rubric sub-tab's section within the tab body. */
4116
+ function criteriaSection(page) {
4117
+ return graderTabBody(page).getByTestId('grader-criteria-section');
4118
+ }
4119
+ /** A rubric table row containing the given criterion name (exact match). */
4120
+ function criterionRow(page, name) {
4121
+ return criteriaSection(page)
4122
+ .locator('[data-testid^="grader-criterion-row-"]')
4123
+ .filter({ has: page.getByText(name, { exact: true }) });
4124
+ }
4125
+ /**
4126
+ * Returns false if the Grader tab isn't currently rendered in the
4127
+ * edit-agent dialog (host didn't register it, or RBAC hid it).
4128
+ */
4129
+ async function isGraderTabVisible(page) {
4130
+ const tab = editAgentDialog(page).getByRole('tab', {
4131
+ name: GRADER_LABELS.tabName,
4132
+ exact: true,
4133
+ });
4134
+ try {
4135
+ await test$1.expect(tab).toBeVisible({ timeout: 5000 });
4136
+ return true;
4137
+ }
4138
+ catch (_a) {
4139
+ return false;
4140
+ }
4141
+ }
4142
+ /**
4143
+ * Switch to the Grader top-level tab. Assumes the edit-agent dialog is
4144
+ * open. Completion is gated on the tab body's testid, not on timing.
4145
+ */
4146
+ async function switchToGraderTab(page) {
4147
+ const dialog = editAgentDialog(page);
4148
+ const tab = dialog.getByRole('tab', { name: GRADER_LABELS.tabName, exact: true });
4149
+ await test$1.expect(tab).toBeVisible({ timeout: UI_TIMEOUT });
4150
+ await tab.click();
4151
+ await test$1.expect(graderTabBody(page)).toBeVisible({ timeout: UI_TIMEOUT });
4152
+ logger.info('Switched to Grader tab');
4153
+ }
4154
+ /**
4155
+ * Switch between the Grader tab's two sub-tabs. Completion is gated on the
4156
+ * target section's testid rendering.
4157
+ */
4158
+ async function switchToGraderSubTab(page, subTab) {
4159
+ const body = graderTabBody(page);
4160
+ const trigger = body.getByTestId(`grader-sub-tab-${subTab}`);
4161
+ await test$1.expect(trigger).toBeVisible({ timeout: UI_TIMEOUT });
4162
+ await trigger.click();
4163
+ const section = subTab === 'setup'
4164
+ ? body.getByTestId('grader-setup-section')
4165
+ : body.getByTestId('grader-criteria-section');
4166
+ await test$1.expect(section).toBeVisible({ timeout: UI_TIMEOUT });
4167
+ logger.info(`Switched to Grader ${subTab} sub-tab`);
4168
+ }
4169
+ /** Read the current on/off state of the Grading capability toggle. */
4170
+ async function isGradingEnabled(page) {
4171
+ const toggle = graderTabBody(page).getByTestId('grader-capability-toggle');
4172
+ await test$1.expect(toggle).toBeVisible({ timeout: UI_TIMEOUT });
4173
+ return (await toggle.getAttribute('aria-checked')) === 'true';
4174
+ }
4175
+ /**
4176
+ * Idempotently set the Grading capability toggle. Waits for the success
4177
+ * toast (which only fires after the settings PATCH resolves — the switch
4178
+ * itself flips optimistically and would roll back on failure) and then for
4179
+ * the gated content to reflect the new state.
4180
+ */
4181
+ async function setGradingEnabled(page, enabled) {
4182
+ const body = graderTabBody(page);
4183
+ const toggle = body.getByTestId('grader-capability-toggle');
4184
+ await test$1.expect(toggle).toBeVisible({ timeout: UI_TIMEOUT });
4185
+ if ((await toggle.getAttribute('aria-checked')) === String(enabled)) {
4186
+ logger.info(`Grading already ${enabled ? 'enabled' : 'disabled'} — no toggle needed`);
4187
+ return;
4188
+ }
4189
+ await test$1.expect(toggle).toBeEnabled({ timeout: UI_TIMEOUT });
4190
+ await toggle.click();
4191
+ const toastText = enabled ? GRADER_LABELS.toasts.toggleOn : GRADER_LABELS.toasts.toggleOff;
4192
+ // .first() is deliberate: rapid toggles can stack identical sonner toasts,
4193
+ // and any one of them proves the PATCH resolved.
4194
+ await test$1.expect(page.getByText(toastText).first()).toBeVisible({ timeout: MUTATION_TIMEOUT });
4195
+ await test$1.expect(toggle).toHaveAttribute('aria-checked', String(enabled), {
4196
+ timeout: MUTATION_TIMEOUT,
4197
+ });
4198
+ await test$1.expect(body.getByTestId('capability-gate-content')).toHaveAttribute('data-enabled', String(enabled), { timeout: UI_TIMEOUT });
4199
+ logger.info(`Grading ${enabled ? 'enabled' : 'disabled'}`);
4200
+ }
4201
+ /** Pick an option in one of the setup form's two Radix selects. */
4202
+ async function pickSelectOption(page, triggerTestId, optionLabel) {
4203
+ const trigger = graderTabBody(page).getByTestId(triggerTestId);
4204
+ await test$1.expect(trigger).toBeVisible({ timeout: UI_TIMEOUT });
4205
+ await trigger.click();
4206
+ // Radix renders the listbox in a portal outside the dialog, so the
4207
+ // option is looked up by role at page level — the open listbox is the
4208
+ // only one in the document.
4209
+ const option = page.getByRole('option', { name: optionLabel, exact: true });
4210
+ await test$1.expect(option).toBeVisible({ timeout: UI_TIMEOUT });
4211
+ await option.click();
4212
+ await test$1.expect(option).toBeHidden({ timeout: UI_TIMEOUT });
4213
+ }
4214
+ /**
4215
+ * Fill and persist the Grading setup form (switches to the setup sub-tab
4216
+ * first). Only the provided fields are changed. Completion is gated on the
4217
+ * Save button returning to disabled: after the config POST/PATCH resolves,
4218
+ * the form rehydrates from the server copy and stops being dirty.
4219
+ */
4220
+ async function saveGraderConfig(page, values) {
4221
+ await switchToGraderSubTab(page, 'setup');
4222
+ const body = graderTabBody(page);
4223
+ if (values.gradingMode) {
4224
+ await pickSelectOption(page, 'grader-grading-mode-select', GRADER_LABELS.gradingModeOptions[values.gradingMode]);
4225
+ }
4226
+ if (values.feedbackMode) {
4227
+ await pickSelectOption(page, 'grader-feedback-mode-select', GRADER_LABELS.feedbackModeOptions[values.feedbackMode]);
4228
+ }
4229
+ if (values.instructions !== undefined) {
4230
+ const textarea = body.getByTestId('grader-instructions-textarea');
4231
+ await test$1.expect(textarea).toBeVisible({ timeout: UI_TIMEOUT });
4232
+ await textarea.fill(values.instructions);
4233
+ }
4234
+ const saveButton = body.getByTestId('grader-save-button');
4235
+ await test$1.expect(saveButton).toBeEnabled({ timeout: UI_TIMEOUT });
4236
+ await saveButton.click();
4237
+ await test$1.expect(saveButton).toBeDisabled({ timeout: MUTATION_TIMEOUT });
4238
+ logger.info('Saved grader configuration');
4239
+ }
4240
+ /** Fill the criterion modal's three fields and submit it. */
4241
+ async function submitCriterionModal(page, criterion) {
4242
+ const modal = page.getByTestId('grader-criterion-modal');
4243
+ await test$1.expect(modal).toBeVisible({ timeout: UI_TIMEOUT });
4244
+ await modal.getByLabel(GRADER_LABELS.modal.fields.name, { exact: true }).fill(criterion.name);
4245
+ await modal
4246
+ .getByLabel(GRADER_LABELS.modal.fields.criteria, { exact: true })
4247
+ .fill(criterion.criteria);
4248
+ await modal
4249
+ .getByLabel(GRADER_LABELS.modal.fields.points, { exact: true })
4250
+ .fill(String(criterion.points));
4251
+ const saveButton = modal.getByTestId('grader-criterion-modal-save');
4252
+ await test$1.expect(saveButton).toBeEnabled({ timeout: UI_TIMEOUT });
4253
+ await saveButton.click();
4254
+ // The modal only closes itself after the mutation resolves.
4255
+ await test$1.expect(modal).toBeHidden({ timeout: MUTATION_TIMEOUT });
4256
+ }
4257
+ /** Open a rubric row's three-dots menu and click one of its actions. */
4258
+ async function clickRowMenuAction(page, name, action) {
4259
+ const trigger = criterionRow(page, name).getByRole('button', {
4260
+ name: GRADER_LABELS.menu.actionsAria(name),
4261
+ });
4262
+ await test$1.expect(trigger).toBeVisible({ timeout: UI_TIMEOUT });
4263
+ await trigger.click();
4264
+ // The menu portals to the page root — the open menu is the only one.
4265
+ const item = page.getByRole('menuitem', { name: action, exact: true });
4266
+ await test$1.expect(item).toBeVisible({ timeout: UI_TIMEOUT });
4267
+ await item.click();
4268
+ }
4269
+ /**
4270
+ * Add a rubric criterion through the Add-criterion modal (switches to the
4271
+ * Rubric sub-tab first). Requires a saved grader configuration — the Add
4272
+ * button is disabled until one exists. Completion is gated on the modal
4273
+ * closing and the new row rendering after the list refetch.
4274
+ */
4275
+ async function addGraderCriterion(page, criterion) {
4276
+ await switchToGraderSubTab(page, 'rubric');
4277
+ const addButton = criteriaSection(page).getByTestId('grader-add-criterion-button');
4278
+ await test$1.expect(addButton).toBeEnabled({ timeout: UI_TIMEOUT });
4279
+ await addButton.click();
4280
+ await submitCriterionModal(page, criterion);
4281
+ await test$1.expect(criterionRow(page, criterion.name)).toBeVisible({ timeout: MUTATION_TIMEOUT });
4282
+ logger.info(`Added rubric criterion "${criterion.name}"`);
4283
+ }
4284
+ /**
4285
+ * Edit an existing rubric criterion via its row's three-dots menu → Edit
4286
+ * modal. The row is located by its current name; all three fields are
4287
+ * rewritten. Completion is gated on the modal closing and the updated row
4288
+ * rendering.
4289
+ */
4290
+ async function editGraderCriterion(page, currentName, updates) {
4291
+ await switchToGraderSubTab(page, 'rubric');
4292
+ await clickRowMenuAction(page, currentName, GRADER_LABELS.menu.edit);
4293
+ await submitCriterionModal(page, updates);
4294
+ await test$1.expect(criterionRow(page, updates.name)).toBeVisible({ timeout: MUTATION_TIMEOUT });
4295
+ logger.info(`Edited rubric criterion "${currentName}" → "${updates.name}"`);
4296
+ }
4297
+ /**
4298
+ * Delete a rubric criterion via its row's three-dots menu → confirmation
4299
+ * modal. Completion is gated on the modal closing and the row disappearing
4300
+ * after the server delete + list refetch.
4301
+ */
4302
+ async function deleteGraderCriterion(page, name) {
4303
+ await switchToGraderSubTab(page, 'rubric');
4304
+ await clickRowMenuAction(page, name, GRADER_LABELS.menu.delete);
4305
+ const modal = page.getByTestId('grader-criterion-delete-modal');
4306
+ await test$1.expect(modal).toBeVisible({ timeout: UI_TIMEOUT });
4307
+ const confirmButton = modal.getByTestId('grader-criterion-delete-confirm');
4308
+ await test$1.expect(confirmButton).toBeEnabled({ timeout: UI_TIMEOUT });
4309
+ await confirmButton.click();
4310
+ await test$1.expect(modal).toBeHidden({ timeout: MUTATION_TIMEOUT });
4311
+ await test$1.expect(criterionRow(page, name)).toBeHidden({ timeout: MUTATION_TIMEOUT });
4312
+ logger.info(`Deleted rubric criterion "${name}"`);
4313
+ }
4314
+ /**
4315
+ * Assert the last remaining criterion's Delete menu action is disabled and
4316
+ * the explanatory hint is shown — the backend refuses to delete the final
4317
+ * row, so the UI must block it too. Closes the menu again before returning.
4318
+ */
4319
+ async function expectLastCriterionDeleteDisabled(page, name) {
4320
+ await switchToGraderSubTab(page, 'rubric');
4321
+ await test$1.expect(criteriaSection(page).getByTestId('grader-last-criterion-hint')).toBeVisible({
4322
+ timeout: UI_TIMEOUT,
4323
+ });
4324
+ const trigger = criterionRow(page, name).getByRole('button', {
4325
+ name: GRADER_LABELS.menu.actionsAria(name),
4326
+ });
4327
+ await test$1.expect(trigger).toBeVisible({ timeout: UI_TIMEOUT });
4328
+ await trigger.click();
4329
+ const deleteItem = page.getByRole('menuitem', { name: GRADER_LABELS.menu.delete, exact: true });
4330
+ await test$1.expect(deleteItem).toBeVisible({ timeout: UI_TIMEOUT });
4331
+ await test$1.expect(deleteItem).toHaveAttribute('aria-disabled', 'true');
4332
+ await page.keyboard.press('Escape');
4333
+ await test$1.expect(deleteItem).toBeHidden({ timeout: UI_TIMEOUT });
4334
+ }
4335
+ /**
4336
+ * Assert whether the amber misconfiguration banner is shown (grading on
4337
+ * with no config yet, or with an empty rubric). The banner sits above the
4338
+ * sub-tabs, so no sub-tab switch is needed.
4339
+ */
4340
+ async function expectGraderMisconfiguredWarning(page, visible) {
4341
+ const warning = graderTabBody(page).getByTestId('grader-misconfigured-warning');
4342
+ if (visible) {
4343
+ await test$1.expect(warning).toBeVisible({ timeout: UI_TIMEOUT });
4344
+ }
4345
+ else {
4346
+ await test$1.expect(warning).toBeHidden();
4347
+ }
4348
+ }
4349
+ /** Assert the rubric's live "total possible points" readout (Rubric sub-tab). */
4350
+ async function expectGraderTotalPoints(page, total) {
4351
+ await switchToGraderSubTab(page, 'rubric');
4352
+ await test$1.expect(criteriaSection(page).getByText(`Total possible points: ${total}`, { exact: true })).toBeVisible({ timeout: UI_TIMEOUT });
4353
+ }
4354
+
4043
4355
  const DEFAULT_TIMEOUT = 10000;
4044
4356
  /** Locator for the Plan section card on the BillingTab. */
4045
4357
  function billingPlanSection(page) {
@@ -6530,6 +6842,7 @@ exports.AuthFlowBuilder = AuthFlowBuilder;
6530
6842
  exports.CHAT_PRIVACY_LABELS = CHAT_PRIVACY_LABELS;
6531
6843
  exports.CustomReporter = CustomReporter;
6532
6844
  exports.EVALS_LABELS = EVALS_LABELS;
6845
+ exports.GRADER_LABELS = GRADER_LABELS;
6533
6846
  exports.LTI_LABELS = LTI_LABELS;
6534
6847
  exports.LTI_TEST_IDS = LTI_TEST_IDS;
6535
6848
  exports.MailsacClient = MailsacClient;
@@ -6538,6 +6851,7 @@ exports.SCREENSHARE_LABELS = SCREENSHARE_LABELS;
6538
6851
  exports.SUPPORT_LABELS = SUPPORT_LABELS;
6539
6852
  exports.TASKS_LABELS = TASKS_LABELS;
6540
6853
  exports.VOICE_LABELS = VOICE_LABELS;
6854
+ exports.addGraderCriterion = addGraderCriterion;
6541
6855
  exports.addManualScore = addManualScore;
6542
6856
  exports.addMemory = addMemory;
6543
6857
  exports.addQaPairsManually = addQaPairsManually;
@@ -6595,6 +6909,7 @@ exports.creditBalancePlanBadge = creditBalancePlanBadge;
6595
6909
  exports.creditBalanceTrigger = creditBalanceTrigger;
6596
6910
  exports.deleteEvaluation = deleteEvaluation;
6597
6911
  exports.deleteFirstMemory = deleteFirstMemory;
6912
+ exports.deleteGraderCriterion = deleteGraderCriterion;
6598
6913
  exports.deleteInstance = deleteInstance;
6599
6914
  exports.deleteKey = deleteKey;
6600
6915
  exports.deleteMemoryByContent = deleteMemoryByContent;
@@ -6605,6 +6920,7 @@ exports.disableSkill = disableSkill;
6605
6920
  exports.disableSupport = disableSupport;
6606
6921
  exports.disconnectInstance = disconnectInstance;
6607
6922
  exports.editAgentPrompt = editAgentPrompt;
6923
+ exports.editGraderCriterion = editGraderCriterion;
6608
6924
  exports.editInstance = editInstance;
6609
6925
  exports.editLink = editLink;
6610
6926
  exports.editSkill = editSkill;
@@ -6639,10 +6955,13 @@ exports.expectDetailWaitingForTraces = expectDetailWaitingForTraces;
6639
6955
  exports.expectEndpointUrl = expectEndpointUrl;
6640
6956
  exports.expectEntitySelected = expectEntitySelected;
6641
6957
  exports.expectFailedTasks = expectFailedTasks;
6958
+ exports.expectGraderMisconfiguredWarning = expectGraderMisconfiguredWarning;
6959
+ exports.expectGraderTotalPoints = expectGraderTotalPoints;
6642
6960
  exports.expectKeyDeleteError = expectKeyDeleteError;
6643
6961
  exports.expectKeyInList = expectKeyInList;
6644
6962
  exports.expectKeyNotInList = expectKeyNotInList;
6645
6963
  exports.expectKeysEmpty = expectKeysEmpty;
6964
+ exports.expectLastCriterionDeleteDisabled = expectLastCriterionDeleteDisabled;
6646
6965
  exports.expectLinkInList = expectLinkInList;
6647
6966
  exports.expectLinkNotInList = expectLinkNotInList;
6648
6967
  exports.expectLinkTargetUri = expectLinkTargetUri;
@@ -6799,9 +7118,12 @@ exports.goToLastPage = goToLastPage;
6799
7118
  exports.goToNextPage = goToNextPage;
6800
7119
  exports.goToPage = goToPage;
6801
7120
  exports.goToPreviousPage = goToPreviousPage;
7121
+ exports.graderTabBody = graderTabBody;
6802
7122
  exports.inviteUserTest = inviteUserTest;
6803
7123
  exports.isEvaluationTabVisible = isEvaluationTabVisible;
6804
7124
  exports.isFirefox = isFirefox;
7125
+ exports.isGraderTabVisible = isGraderTabVisible;
7126
+ exports.isGradingEnabled = isGradingEnabled;
6805
7127
  exports.isJSON = isJSON;
6806
7128
  exports.isLtiTabVisible = isLtiTabVisible;
6807
7129
  exports.isMemoryTabVisible = isMemoryTabVisible;
@@ -6878,6 +7200,7 @@ exports.runConnectedInstanceChecks = runConnectedInstanceChecks;
6878
7200
  exports.runInstanceChecks = runInstanceChecks;
6879
7201
  exports.safeWaitForURL = safeWaitForURL;
6880
7202
  exports.saveCallConfig = saveCallConfig;
7203
+ exports.saveGraderConfig = saveGraderConfig;
6881
7204
  exports.saveScreenSharePrompts = saveScreenSharePrompts;
6882
7205
  exports.saveVoiceSettings = saveVoiceSettings;
6883
7206
  exports.scheduleTask = scheduleTask;
@@ -6905,6 +7228,7 @@ exports.setBlockMessage = setBlockMessage;
6905
7228
  exports.setCallLanguage = setCallLanguage;
6906
7229
  exports.setEnableVideo = setEnableVideo;
6907
7230
  exports.setEntitySelected = setEntitySelected;
7231
+ exports.setGradingEnabled = setGradingEnabled;
6908
7232
  exports.setOutputFilterEnabled = setOutputFilterEnabled;
6909
7233
  exports.setScreenSharePrompt = setScreenSharePrompt;
6910
7234
  exports.setSupportEnabled = setSupportEnabled;
@@ -6935,6 +7259,8 @@ exports.submitLlmJudge = submitLlmJudge;
6935
7259
  exports.submitToolModal = submitToolModal;
6936
7260
  exports.switchToAddItemsSubTab = switchToAddItemsSubTab;
6937
7261
  exports.switchToEvaluationTab = switchToEvaluationTab;
7262
+ exports.switchToGraderSubTab = switchToGraderSubTab;
7263
+ exports.switchToGraderTab = switchToGraderTab;
6938
7264
  exports.switchToLtiSubTab = switchToLtiSubTab;
6939
7265
  exports.switchToLtiTab = switchToLtiTab;
6940
7266
  exports.switchToMemoryTab = switchToMemoryTab;