@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.
- package/dist/data-layer/playwright/grader-tab-helpers.d.ts +138 -0
- package/dist/data-layer/playwright/index.d.ts +2 -0
- package/dist/playwright/index.cjs +326 -0
- package/dist/playwright/index.cjs.map +1 -1
- package/dist/playwright/index.d.ts +140 -2
- package/dist/playwright/index.esm.js +313 -1
- package/dist/playwright/index.esm.js.map +1 -1
- package/dist/playwright/playwright/grader-tab-helpers.d.ts +138 -0
- package/dist/playwright/playwright/index.d.ts +2 -0
- package/dist/security/playwright/grader-tab-helpers.d.ts +138 -0
- package/dist/security/playwright/index.d.ts +2 -0
- package/dist/web-containers/playwright/grader-tab-helpers.d.ts +138 -0
- package/dist/web-containers/playwright/index.d.ts +2 -0
- package/dist/web-containers/source/index.esm.js +452 -0
- package/dist/web-containers/source/next/index.esm.js +995 -2
- package/dist/web-utils/playwright/grader-tab-helpers.d.ts +138 -0
- package/dist/web-utils/playwright/index.d.ts +2 -0
- package/package.json +5 -5
|
@@ -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;
|