@iblai/iblai-js 1.20.13 → 1.20.15

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