@iblai/iblai-js 1.20.13 → 1.20.14
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/tasks-tab-helpers.d.ts +6 -0
- package/dist/playwright/index.cjs +72 -49
- package/dist/playwright/index.cjs.map +1 -1
- package/dist/playwright/index.d.ts +4 -0
- package/dist/playwright/index.esm.js +72 -49
- package/dist/playwright/index.esm.js.map +1 -1
- package/dist/playwright/playwright/tasks-tab-helpers.d.ts +6 -0
- package/dist/web-containers/playwright/tasks-tab-helpers.d.ts +6 -0
- package/dist/web-containers/source/next/index.esm.js +2 -2
- package/dist/web-utils/playwright/tasks-tab-helpers.d.ts +6 -0
- package/package.json +2 -2
|
@@ -1470,12 +1470,16 @@ declare const TASKS_LABELS: {
|
|
|
1470
1470
|
readonly startTimeInPast: "Start time must be in the future.";
|
|
1471
1471
|
};
|
|
1472
1472
|
readonly deleteDialog: {
|
|
1473
|
+
/** `aria-label` on the dialog's `DialogContent` — scopes in-dialog locators. */
|
|
1474
|
+
readonly dialogName: "delete-task";
|
|
1473
1475
|
readonly title: "Delete Task";
|
|
1474
1476
|
readonly delete: "Delete Task";
|
|
1475
1477
|
readonly deleting: "Deleting...";
|
|
1476
1478
|
readonly cancel: "Cancel";
|
|
1477
1479
|
};
|
|
1478
1480
|
readonly logDetails: {
|
|
1481
|
+
/** `aria-label` on the dialog's `DialogContent` — scopes in-dialog locators. */
|
|
1482
|
+
readonly dialogName: "log-details";
|
|
1479
1483
|
readonly title: "Log Details";
|
|
1480
1484
|
readonly status: "Status";
|
|
1481
1485
|
readonly createdAt: "Created";
|
|
@@ -1840,20 +1840,23 @@ async function openNewInstanceDialog(page) {
|
|
|
1840
1840
|
* @param gatewayToken - Optional auth token
|
|
1841
1841
|
*/
|
|
1842
1842
|
async function createInstance(page, { name, serverUrl, clawType = 'openclaw', gatewayToken, }) {
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
await page
|
|
1843
|
+
// Scope in-dialog queries to the dialog: "Name" also labels the mentor-name
|
|
1844
|
+
// field on the edit-mentor modal behind it, and "Create" is a generic name.
|
|
1845
|
+
const dialog = await openNewInstanceDialog(page);
|
|
1846
|
+
await dialog.getByLabel('Name').fill(name);
|
|
1847
|
+
await dialog.getByLabel('Server URL').fill(serverUrl);
|
|
1846
1848
|
if (clawType !== 'openclaw') {
|
|
1847
|
-
const typeSelect =
|
|
1849
|
+
const typeSelect = dialog.getByLabel('Type');
|
|
1848
1850
|
await typeSelect.click();
|
|
1851
|
+
// Radix renders the option list in a portal outside the dialog subtree.
|
|
1849
1852
|
const option = page.getByRole('option', { name: new RegExp(clawType, 'i') });
|
|
1850
1853
|
await expect(option).toBeVisible({ timeout: 5000 });
|
|
1851
1854
|
await option.click();
|
|
1852
1855
|
}
|
|
1853
1856
|
if (gatewayToken) {
|
|
1854
|
-
await
|
|
1857
|
+
await dialog.getByLabel('Gateway Token').fill(gatewayToken);
|
|
1855
1858
|
}
|
|
1856
|
-
const createButton =
|
|
1859
|
+
const createButton = dialog.getByRole('button', { name: /^Create$/i });
|
|
1857
1860
|
await expect(createButton).toBeEnabled({ timeout: 5000 });
|
|
1858
1861
|
await createButton.click();
|
|
1859
1862
|
await expect(page.getByText('Instance created', { exact: false })).toBeVisible({
|
|
@@ -1892,23 +1895,25 @@ async function openEditInstanceDialog(page, instanceName) {
|
|
|
1892
1895
|
* @param updates - Fields to update
|
|
1893
1896
|
*/
|
|
1894
1897
|
async function editInstance(page, instanceName, updates) {
|
|
1895
|
-
|
|
1898
|
+
// Scope in-dialog queries to the dialog (see createInstance).
|
|
1899
|
+
const dialog = await openEditInstanceDialog(page, instanceName);
|
|
1896
1900
|
if (updates.name !== undefined) {
|
|
1897
|
-
const nameInput =
|
|
1901
|
+
const nameInput = dialog.getByLabel('Name');
|
|
1898
1902
|
await nameInput.fill(updates.name);
|
|
1899
1903
|
}
|
|
1900
1904
|
if (updates.serverUrl !== undefined) {
|
|
1901
|
-
const urlInput =
|
|
1905
|
+
const urlInput = dialog.getByLabel('Server URL');
|
|
1902
1906
|
await urlInput.fill(updates.serverUrl);
|
|
1903
1907
|
}
|
|
1904
1908
|
if (updates.clawType !== undefined) {
|
|
1905
|
-
const typeSelect =
|
|
1909
|
+
const typeSelect = dialog.getByLabel('Type');
|
|
1906
1910
|
await typeSelect.click();
|
|
1911
|
+
// Radix renders the option list in a portal outside the dialog subtree.
|
|
1907
1912
|
const option = page.getByRole('option', { name: new RegExp(updates.clawType, 'i') });
|
|
1908
1913
|
await expect(option).toBeVisible({ timeout: 5000 });
|
|
1909
1914
|
await option.click();
|
|
1910
1915
|
}
|
|
1911
|
-
const saveButton =
|
|
1916
|
+
const saveButton = dialog.getByRole('button', { name: /^Save$/i });
|
|
1912
1917
|
await expect(saveButton).toBeEnabled({ timeout: 5000 });
|
|
1913
1918
|
await saveButton.click();
|
|
1914
1919
|
await expect(page.getByText('Instance updated', { exact: false })).toBeVisible({
|
|
@@ -2137,9 +2142,10 @@ async function openLLMProviderPicker(page) {
|
|
|
2137
2142
|
* @param modelName - The model name (e.g. 'claude-sonnet-4-5-20250929')
|
|
2138
2143
|
*/
|
|
2139
2144
|
async function selectLLMModel(page, providerName, modelName) {
|
|
2140
|
-
await openLLMProviderPicker(page);
|
|
2141
|
-
// Click the provider card
|
|
2142
|
-
|
|
2145
|
+
const providerDialog = await openLLMProviderPicker(page);
|
|
2146
|
+
// Click the provider card, scoped to the picker dialog (bare
|
|
2147
|
+
// page.getByRole('dialog') also matches the edit-mentor modal behind it).
|
|
2148
|
+
const providerCard = providerDialog.getByText(new RegExp(`^${providerName}$`, 'i'));
|
|
2143
2149
|
await expect(providerCard).toBeVisible({ timeout: 10000 });
|
|
2144
2150
|
await providerCard.click();
|
|
2145
2151
|
// Model selection modal ("LLM Selection")
|
|
@@ -2217,10 +2223,12 @@ async function openAgentPromptEditModal(page, field) {
|
|
|
2217
2223
|
* Edit an agent prompt field (opens the modal, types content, saves).
|
|
2218
2224
|
*/
|
|
2219
2225
|
async function editAgentPrompt(page, field, content) {
|
|
2220
|
-
|
|
2226
|
+
// Scope to the dialog so the contenteditable/textarea `.first()` and the
|
|
2227
|
+
// generic "Save" button don't match elements on the modal behind it.
|
|
2228
|
+
const dialog = await openAgentPromptEditModal(page, field);
|
|
2221
2229
|
// The RichTextEditor renders into a contenteditable element; fall back to textarea if simpler.
|
|
2222
|
-
const editorRegion =
|
|
2223
|
-
const textarea =
|
|
2230
|
+
const editorRegion = dialog.locator('[contenteditable="true"]').first();
|
|
2231
|
+
const textarea = dialog.locator('textarea').first();
|
|
2224
2232
|
const hasEditor = await editorRegion.isVisible().catch(() => false);
|
|
2225
2233
|
if (hasEditor) {
|
|
2226
2234
|
await editorRegion.click();
|
|
@@ -2231,7 +2239,7 @@ async function editAgentPrompt(page, field, content) {
|
|
|
2231
2239
|
else {
|
|
2232
2240
|
await textarea.fill(content);
|
|
2233
2241
|
}
|
|
2234
|
-
const saveButton =
|
|
2242
|
+
const saveButton = dialog.getByRole('button', { name: /^Save$/i });
|
|
2235
2243
|
await expect(saveButton).toBeEnabled({ timeout: 5000 });
|
|
2236
2244
|
await saveButton.click();
|
|
2237
2245
|
await expect(page.getByText(`${field} updated successfully`, { exact: false })).toBeVisible({
|
|
@@ -2362,10 +2370,15 @@ async function openNewSkillDialog(page) {
|
|
|
2362
2370
|
/**
|
|
2363
2371
|
* Type content into the RichTextEditor Instruction field.
|
|
2364
2372
|
* The editor renders as a contenteditable element.
|
|
2373
|
+
*
|
|
2374
|
+
* `dialog` scopes the lookups to the open skill modal so the
|
|
2375
|
+
* contenteditable `.first()` and the "Instruction" label don't match
|
|
2376
|
+
* elements on the modal behind it. The keyboard is page-global.
|
|
2365
2377
|
*/
|
|
2366
|
-
async function fillInstructionEditor(
|
|
2378
|
+
async function fillInstructionEditor(dialog, content) {
|
|
2379
|
+
const page = dialog.page();
|
|
2367
2380
|
// RichTextEditor uses a contenteditable region
|
|
2368
|
-
const editor =
|
|
2381
|
+
const editor = dialog.locator('[contenteditable="true"]').first();
|
|
2369
2382
|
const hasEditor = await editor.isVisible().catch(() => false);
|
|
2370
2383
|
if (hasEditor) {
|
|
2371
2384
|
await editor.click();
|
|
@@ -2377,34 +2390,35 @@ async function fillInstructionEditor(page, content) {
|
|
|
2377
2390
|
return;
|
|
2378
2391
|
}
|
|
2379
2392
|
// Fallback: a plain labelled input/textarea named "Instruction"
|
|
2380
|
-
const textarea =
|
|
2393
|
+
const textarea = dialog.getByLabel('Instruction');
|
|
2381
2394
|
await textarea.fill(content);
|
|
2382
2395
|
}
|
|
2383
2396
|
/**
|
|
2384
2397
|
* Fill the skill form fields (name, slug, description, version, instruction).
|
|
2385
|
-
* The form is used by both the New and Edit dialogs.
|
|
2398
|
+
* The form is used by both the New and Edit dialogs. `dialog` scopes every
|
|
2399
|
+
* field query to the open modal (see fillInstructionEditor).
|
|
2386
2400
|
* The Instruction field is a rich-text (markdown) editor.
|
|
2387
2401
|
*/
|
|
2388
|
-
async function fillSkillForm(
|
|
2389
|
-
await
|
|
2390
|
-
await
|
|
2402
|
+
async function fillSkillForm(dialog, values) {
|
|
2403
|
+
await dialog.getByLabel('Name').fill(values.name);
|
|
2404
|
+
await dialog.getByLabel('Slug').fill(values.slug);
|
|
2391
2405
|
if (values.version !== undefined) {
|
|
2392
|
-
await
|
|
2406
|
+
await dialog.getByLabel('Version').fill(values.version);
|
|
2393
2407
|
}
|
|
2394
2408
|
if (values.description !== undefined) {
|
|
2395
|
-
await
|
|
2409
|
+
await dialog.getByLabel('Description').fill(values.description);
|
|
2396
2410
|
}
|
|
2397
2411
|
if (values.instruction !== undefined) {
|
|
2398
|
-
await fillInstructionEditor(
|
|
2412
|
+
await fillInstructionEditor(dialog, values.instruction);
|
|
2399
2413
|
}
|
|
2400
2414
|
}
|
|
2401
2415
|
/**
|
|
2402
2416
|
* Create a new platform-level skill via the Skills tab.
|
|
2403
2417
|
*/
|
|
2404
2418
|
async function createSkill(page, values) {
|
|
2405
|
-
await openNewSkillDialog(page);
|
|
2406
|
-
await fillSkillForm(
|
|
2407
|
-
const createButton =
|
|
2419
|
+
const dialog = await openNewSkillDialog(page);
|
|
2420
|
+
await fillSkillForm(dialog, values);
|
|
2421
|
+
const createButton = dialog.getByRole('button', { name: /^Create$/i });
|
|
2408
2422
|
await expect(createButton).toBeEnabled({ timeout: 5000 });
|
|
2409
2423
|
await createButton.click();
|
|
2410
2424
|
await expect(page.getByText('Skill created', { exact: false })).toBeVisible({
|
|
@@ -2442,23 +2456,23 @@ async function openEditSkillDialog(page, skillName) {
|
|
|
2442
2456
|
* @param updates - Fields to update on the skill
|
|
2443
2457
|
*/
|
|
2444
2458
|
async function editSkill(page, skillName, updates) {
|
|
2445
|
-
await openEditSkillDialog(page, skillName);
|
|
2459
|
+
const dialog = await openEditSkillDialog(page, skillName);
|
|
2446
2460
|
if (updates.name !== undefined) {
|
|
2447
|
-
await
|
|
2461
|
+
await dialog.getByLabel('Name').fill(updates.name);
|
|
2448
2462
|
}
|
|
2449
2463
|
if (updates.slug !== undefined) {
|
|
2450
|
-
await
|
|
2464
|
+
await dialog.getByLabel('Slug').fill(updates.slug);
|
|
2451
2465
|
}
|
|
2452
2466
|
if (updates.version !== undefined) {
|
|
2453
|
-
await
|
|
2467
|
+
await dialog.getByLabel('Version').fill(updates.version);
|
|
2454
2468
|
}
|
|
2455
2469
|
if (updates.description !== undefined) {
|
|
2456
|
-
await
|
|
2470
|
+
await dialog.getByLabel('Description').fill(updates.description);
|
|
2457
2471
|
}
|
|
2458
2472
|
if (updates.instruction !== undefined) {
|
|
2459
|
-
await fillInstructionEditor(
|
|
2473
|
+
await fillInstructionEditor(dialog, updates.instruction);
|
|
2460
2474
|
}
|
|
2461
|
-
const saveButton =
|
|
2475
|
+
const saveButton = dialog.getByRole('button', { name: /^Save$/i });
|
|
2462
2476
|
await expect(saveButton).toBeEnabled({ timeout: 5000 });
|
|
2463
2477
|
await saveButton.click();
|
|
2464
2478
|
await expect(page.getByText('Skill updated', { exact: false })).toBeVisible({
|
|
@@ -4285,12 +4299,16 @@ const TASKS_LABELS = {
|
|
|
4285
4299
|
startTimeInPast: 'Start time must be in the future.',
|
|
4286
4300
|
},
|
|
4287
4301
|
deleteDialog: {
|
|
4302
|
+
/** `aria-label` on the dialog's `DialogContent` — scopes in-dialog locators. */
|
|
4303
|
+
dialogName: 'delete-task',
|
|
4288
4304
|
title: 'Delete Task',
|
|
4289
4305
|
delete: 'Delete Task',
|
|
4290
4306
|
deleting: 'Deleting...',
|
|
4291
4307
|
cancel: 'Cancel',
|
|
4292
4308
|
},
|
|
4293
4309
|
logDetails: {
|
|
4310
|
+
/** `aria-label` on the dialog's `DialogContent` — scopes in-dialog locators. */
|
|
4311
|
+
dialogName: 'log-details',
|
|
4294
4312
|
title: 'Log Details',
|
|
4295
4313
|
status: 'Status',
|
|
4296
4314
|
createdAt: 'Created',
|
|
@@ -4473,6 +4491,9 @@ async function expectScheduleStartTimeInPastError(scope) {
|
|
|
4473
4491
|
});
|
|
4474
4492
|
}
|
|
4475
4493
|
// ── Delete flow ────────────────────────────────────────────────────────
|
|
4494
|
+
function getDeleteTaskDialog(scope) {
|
|
4495
|
+
return asPage(scope).locator(`[role="dialog"][aria-label="${TASKS_LABELS.deleteDialog.dialogName}"]`);
|
|
4496
|
+
}
|
|
4476
4497
|
/**
|
|
4477
4498
|
* Click the trash icon on a task row and confirm the delete in the
|
|
4478
4499
|
* follow-up dialog.
|
|
@@ -4480,17 +4501,20 @@ async function expectScheduleStartTimeInPastError(scope) {
|
|
|
4480
4501
|
async function deleteTask(scope, taskName) {
|
|
4481
4502
|
const row = getTaskRow(scope, taskName);
|
|
4482
4503
|
await row.getByRole('button', { name: TASKS_LABELS.list.deleteTask }).click();
|
|
4483
|
-
|
|
4484
|
-
|
|
4504
|
+
// Scope to the dialog: the confirm button's "Delete Task" name collides
|
|
4505
|
+
// (case-insensitively) with the row trash buttons' "Delete task" label.
|
|
4506
|
+
const dialog = getDeleteTaskDialog(scope);
|
|
4507
|
+
const confirm = dialog.getByRole('button', { name: TASKS_LABELS.deleteDialog.delete });
|
|
4485
4508
|
await expect(confirm).toBeVisible({ timeout: 5000 });
|
|
4486
4509
|
await confirm.click();
|
|
4487
4510
|
// Wait for the dialog to close.
|
|
4488
|
-
await expect(
|
|
4489
|
-
timeout: 10000,
|
|
4490
|
-
});
|
|
4511
|
+
await expect(dialog).toBeHidden({ timeout: 10000 });
|
|
4491
4512
|
logger.info(`Deleted task: ${taskName}`);
|
|
4492
4513
|
}
|
|
4493
4514
|
// ── Logs panel + log details ───────────────────────────────────────────
|
|
4515
|
+
function getLogDetailsDialog(scope) {
|
|
4516
|
+
return asPage(scope).locator(`[role="dialog"][aria-label="${TASKS_LABELS.logDetails.dialogName}"]`);
|
|
4517
|
+
}
|
|
4494
4518
|
/**
|
|
4495
4519
|
* Wait for the logs panel to finish loading and assert that the selected
|
|
4496
4520
|
* task name is shown in the header.
|
|
@@ -4516,14 +4540,13 @@ async function openFirstLogDetails(scope) {
|
|
|
4516
4540
|
// first interactive child.
|
|
4517
4541
|
const panel = scope.getByText(TASKS_LABELS.logs.title, { exact: true }).locator('xpath=../..');
|
|
4518
4542
|
await panel.getByRole('button').first().click();
|
|
4519
|
-
|
|
4520
|
-
await expect(page.getByRole('heading', { name: TASKS_LABELS.logDetails.title })).toBeVisible({
|
|
4521
|
-
timeout: 10000,
|
|
4522
|
-
});
|
|
4543
|
+
await expect(getLogDetailsDialog(scope)).toBeVisible({ timeout: 10000 });
|
|
4523
4544
|
}
|
|
4524
4545
|
async function expectLogDetailsStatus(scope, status) {
|
|
4525
|
-
|
|
4526
|
-
await expect(
|
|
4546
|
+
// Scope to the dialog so the status text isn't matched on a task row.
|
|
4547
|
+
await expect(getLogDetailsDialog(scope).getByText(status, { exact: true })).toBeVisible({
|
|
4548
|
+
timeout: 5000,
|
|
4549
|
+
});
|
|
4527
4550
|
}
|
|
4528
4551
|
|
|
4529
4552
|
/** Extract browser key from device name (e.g., 'Desktop Chrome' -> 'chrome') */
|