@iblai/iblai-js 1.11.1 → 1.11.4

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.
@@ -25,5 +25,7 @@ export { PRIVACY_LABELS, isPrivacyTabVisible, switchToPrivacyTab, getPrivacyRout
25
25
  export type { PrivacyAction, PrivacyEntity } from './privacy-tab-helpers';
26
26
  export { billingPlanSection, billingCreditsSection, billingAutoRechargeSection, getBillingPlanLabel, getBillingAutoRechargeStatus, waitForBillingTabReady, expectBillingPlanSection, expectBillingCreditsSection, expectBillingAutoRechargeSection, expectBillingTabForFreePlan, expectBillingTabForTrialPlan, expectBillingTabForPremiumPlan, expectBillingTabForCurrentPlan, clickBillingUpgrade, clickBillingAddCredits, clickBillingManageBilling, clickBillingManageUsage, } from './billing-tab-helpers';
27
27
  export type { BillingAutoRechargeStatus } from './billing-tab-helpers';
28
+ 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';
29
+ export type { TaskRepeat, TaskStatus } from './tasks-tab-helpers';
28
30
  export { createPlaywrightConfig, generateProjectConfig, generateBrowserSetupProjects, getBrowserKey, } from './playwright-config';
29
31
  export type { PlatformConfig, CreatePlaywrightConfigOptions } from './playwright-config';
@@ -0,0 +1,163 @@
1
+ import { Locator, Page } from '@playwright/test';
2
+ /**
3
+ * Tasks tab helpers — Playwright bindings for the `AgentTasksTab`
4
+ * component from `@iblai/web-containers`.
5
+ *
6
+ * All UI strings below mirror `AGENT_TASKS_TAB_LABELS` from
7
+ * `@iblai/web-containers/next`. If a consumer renames a label via the
8
+ * `labels` prop, override these constants per spec — don't edit this file.
9
+ *
10
+ * The helpers assume the Edit Mentor dialog is already open. Call
11
+ * `switchToTasksTab(page)` first; from then on, every helper accepts
12
+ * either the `Page` or the dialog `Locator`.
13
+ */
14
+ export declare const TASKS_LABELS: {
15
+ readonly tabName: "Tasks";
16
+ readonly headerTitle: "Tasks";
17
+ readonly headerDescription: "Configure automated tasks for your mentor.";
18
+ readonly toolbar: {
19
+ readonly searchPlaceholder: "Search all tasks...";
20
+ readonly selectDate: "Select date";
21
+ readonly scheduleTask: "Schedule Task";
22
+ };
23
+ readonly metrics: {
24
+ readonly total: "Total Tasks";
25
+ readonly completed: "Completed";
26
+ readonly failed: "Failed";
27
+ };
28
+ /** Status badges displayed on each task row. */
29
+ readonly status: {
30
+ readonly completed: "Completed";
31
+ readonly failed: "Failed";
32
+ readonly running: "Running";
33
+ readonly scheduled: "Scheduled";
34
+ readonly disabled: "Disabled";
35
+ };
36
+ readonly list: {
37
+ readonly selectHint: "Select a task to view its run logs.";
38
+ readonly repeatOnce: "Once";
39
+ readonly deleteTask: "Delete task";
40
+ };
41
+ readonly logs: {
42
+ readonly title: "Task Logs";
43
+ readonly selectPrompt: "Select a task on the left to view its run logs.";
44
+ readonly noLogs: "No run logs for this task yet.";
45
+ readonly loading: "Loading logs...";
46
+ readonly forTask: "Logs for";
47
+ };
48
+ readonly states: {
49
+ readonly loading: "Loading tasks...";
50
+ readonly empty: "No tasks scheduled.";
51
+ };
52
+ readonly scheduleDialog: {
53
+ readonly taskName: "Task Name";
54
+ readonly taskNamePlaceholder: "Enter task name";
55
+ readonly taskPrompt: "Task Prompt";
56
+ readonly taskPromptPlaceholder: "Enter task prompt";
57
+ readonly time: "Time";
58
+ readonly repeat: "Repeat";
59
+ readonly notifyByEmail: "Notify me by email";
60
+ readonly schedule: "Schedule Task";
61
+ readonly scheduling: "Scheduling...";
62
+ readonly cancel: "Cancel";
63
+ readonly dontRepeat: "Don't repeat";
64
+ readonly daily: "Daily";
65
+ readonly weekly: "Weekly";
66
+ readonly monthly: "Monthly";
67
+ readonly startTimeInPast: "Start time must be in the future.";
68
+ };
69
+ readonly deleteDialog: {
70
+ readonly title: "Delete Task";
71
+ readonly delete: "Delete Task";
72
+ readonly deleting: "Deleting...";
73
+ readonly cancel: "Cancel";
74
+ };
75
+ readonly logDetails: {
76
+ readonly title: "Log Details";
77
+ readonly status: "Status";
78
+ readonly createdAt: "Created";
79
+ readonly startTime: "Started";
80
+ readonly endTime: "Ended";
81
+ readonly output: "Output";
82
+ };
83
+ };
84
+ /** The four repeat cadences accepted by `scheduleTask`. */
85
+ export type TaskRepeat = "don't-repeat" | 'daily' | 'weekly' | 'monthly';
86
+ /** Possible status badges on a task row. */
87
+ export type TaskStatus = keyof typeof TASKS_LABELS.status;
88
+ /**
89
+ * Check whether the Tasks tab is currently rendered in the Edit Mentor
90
+ * dialog. Returns false instead of throwing so callers can guard
91
+ * conditionally rendered tabs (e.g. permission-gated).
92
+ */
93
+ export declare function isTasksTabVisible(page: Page): Promise<boolean>;
94
+ /**
95
+ * Switch to the Tasks tab. Assumes the Edit Mentor dialog is open. After
96
+ * the click we wait for the unique "Schedule Task" button before
97
+ * returning, so callers can rely on the panel being interactive.
98
+ */
99
+ export declare function switchToTasksTab(page: Page): Promise<void>;
100
+ /** Locator for the "Schedule Task" toolbar button (opens the dialog). */
101
+ export declare function getScheduleTaskButton(scope: Page | Locator): Locator;
102
+ /** Locator for the task list's search input. */
103
+ export declare function getSearchInput(scope: Page | Locator): Locator;
104
+ /**
105
+ * Locator for a task row by its title. We scope to the click-target
106
+ * `role="button"` rendered by `TaskList` so callers can `.click()` to
107
+ * select, or chain `.getByRole('button', { name: 'Delete task' })` to
108
+ * delete.
109
+ */
110
+ export declare function getTaskRow(scope: Page | Locator, taskName: string): Locator;
111
+ export declare function expectTotalTasks(scope: Page | Locator, n: number): Promise<void>;
112
+ export declare function expectCompletedTasks(scope: Page | Locator, n: number): Promise<void>;
113
+ export declare function expectFailedTasks(scope: Page | Locator, n: number): Promise<void>;
114
+ export declare function expectTasksEmpty(scope: Page | Locator): Promise<void>;
115
+ export declare function expectTaskInList(scope: Page | Locator, taskName: string): Promise<void>;
116
+ export declare function expectTaskNotInList(scope: Page | Locator, taskName: string): Promise<void>;
117
+ /**
118
+ * Click a task row to select it. Marks the row's `aria-pressed=true` and
119
+ * the logs panel rebinds to that task.
120
+ */
121
+ export declare function selectTaskInList(scope: Page | Locator, taskName: string): Promise<void>;
122
+ /**
123
+ * Assert that a given task's status badge matches the expected status.
124
+ */
125
+ export declare function expectTaskStatus(scope: Page | Locator, taskName: string, status: TaskStatus): Promise<void>;
126
+ /** Type into the search input. Returns once the input value matches. */
127
+ export declare function searchTasks(scope: Page | Locator, query: string): Promise<void>;
128
+ /** Open the Schedule Task dialog and wait for it to be interactive. */
129
+ export declare function openScheduleTaskDialog(scope: Page | Locator): Promise<void>;
130
+ /**
131
+ * End-to-end: open the dialog, fill the form, click Schedule, and wait
132
+ * for it to close.
133
+ *
134
+ * `time` is an HTML `<input type="time">` string ("HH:mm", 24-hour). If
135
+ * the chosen date is today and the chosen time is in the past, the
136
+ * dialog will block submission — choose accordingly.
137
+ */
138
+ export declare function scheduleTask(scope: Page | Locator, opts: {
139
+ name: string;
140
+ prompt?: string;
141
+ time: string;
142
+ repeat?: TaskRepeat;
143
+ notifyByEmail?: boolean;
144
+ }): Promise<void>;
145
+ /** Asserts the in-dialog past-time error is currently shown. */
146
+ export declare function expectScheduleStartTimeInPastError(scope: Page | Locator): Promise<void>;
147
+ /**
148
+ * Click the trash icon on a task row and confirm the delete in the
149
+ * follow-up dialog.
150
+ */
151
+ export declare function deleteTask(scope: Page | Locator, taskName: string): Promise<void>;
152
+ /**
153
+ * Wait for the logs panel to finish loading and assert that the selected
154
+ * task name is shown in the header.
155
+ */
156
+ export declare function expectLogsForTask(scope: Page | Locator, taskName: string): Promise<void>;
157
+ export declare function expectNoLogsForSelectedTask(scope: Page | Locator): Promise<void>;
158
+ /**
159
+ * Click the first log entry under the selected task to open the log
160
+ * details modal. Returns once the dialog title is visible.
161
+ */
162
+ export declare function openFirstLogDetails(scope: Page | Locator): Promise<void>;
163
+ export declare function expectLogDetailsStatus(scope: Page | Locator, status: 'success' | 'error' | 'running' | string): Promise<void>;
@@ -3152,7 +3152,7 @@ const PRIVACY_LABELS = {
3152
3152
  * reach DOM that Radix renders outside the dialog subtree (popovers,
3153
3153
  * select options, etc.).
3154
3154
  */
3155
- function asPage(scope) {
3155
+ function asPage$1(scope) {
3156
3156
  return 'page' in scope ? scope.page() : scope;
3157
3157
  }
3158
3158
  /**
@@ -3236,7 +3236,7 @@ async function selectPrivacyAction(scope, action) {
3236
3236
  await trigger.click();
3237
3237
  // Radix Select renders options in a portal at the document root, so we
3238
3238
  // always look them up on the Page — never on the dialog Locator.
3239
- const option = asPage(scope).getByRole('option', {
3239
+ const option = asPage$1(scope).getByRole('option', {
3240
3240
  name: PRIVACY_LABELS.actionOptions[action],
3241
3241
  });
3242
3242
  await test$1.expect(option).toBeVisible({ timeout: 5000 });
@@ -3500,6 +3500,303 @@ async function clickBillingManageUsage(page) {
3500
3500
  await btn.click();
3501
3501
  }
3502
3502
 
3503
+ /**
3504
+ * Tasks tab helpers — Playwright bindings for the `AgentTasksTab`
3505
+ * component from `@iblai/web-containers`.
3506
+ *
3507
+ * All UI strings below mirror `AGENT_TASKS_TAB_LABELS` from
3508
+ * `@iblai/web-containers/next`. If a consumer renames a label via the
3509
+ * `labels` prop, override these constants per spec — don't edit this file.
3510
+ *
3511
+ * The helpers assume the Edit Mentor dialog is already open. Call
3512
+ * `switchToTasksTab(page)` first; from then on, every helper accepts
3513
+ * either the `Page` or the dialog `Locator`.
3514
+ */
3515
+ const TASKS_LABELS = {
3516
+ tabName: 'Tasks',
3517
+ headerTitle: 'Tasks',
3518
+ headerDescription: 'Configure automated tasks for your mentor.',
3519
+ toolbar: {
3520
+ searchPlaceholder: 'Search all tasks...',
3521
+ selectDate: 'Select date',
3522
+ scheduleTask: 'Schedule Task',
3523
+ },
3524
+ metrics: {
3525
+ total: 'Total Tasks',
3526
+ completed: 'Completed',
3527
+ failed: 'Failed',
3528
+ },
3529
+ /** Status badges displayed on each task row. */
3530
+ status: {
3531
+ completed: 'Completed',
3532
+ failed: 'Failed',
3533
+ running: 'Running',
3534
+ scheduled: 'Scheduled',
3535
+ disabled: 'Disabled',
3536
+ },
3537
+ list: {
3538
+ selectHint: 'Select a task to view its run logs.',
3539
+ repeatOnce: 'Once',
3540
+ deleteTask: 'Delete task',
3541
+ },
3542
+ logs: {
3543
+ title: 'Task Logs',
3544
+ selectPrompt: 'Select a task on the left to view its run logs.',
3545
+ noLogs: 'No run logs for this task yet.',
3546
+ loading: 'Loading logs...',
3547
+ forTask: 'Logs for',
3548
+ },
3549
+ states: {
3550
+ loading: 'Loading tasks...',
3551
+ empty: 'No tasks scheduled.',
3552
+ },
3553
+ scheduleDialog: {
3554
+ taskName: 'Task Name',
3555
+ taskNamePlaceholder: 'Enter task name',
3556
+ taskPrompt: 'Task Prompt',
3557
+ taskPromptPlaceholder: 'Enter task prompt',
3558
+ time: 'Time',
3559
+ repeat: 'Repeat',
3560
+ notifyByEmail: 'Notify me by email',
3561
+ schedule: 'Schedule Task',
3562
+ scheduling: 'Scheduling...',
3563
+ cancel: 'Cancel',
3564
+ dontRepeat: "Don't repeat",
3565
+ daily: 'Daily',
3566
+ weekly: 'Weekly',
3567
+ monthly: 'Monthly',
3568
+ startTimeInPast: 'Start time must be in the future.',
3569
+ },
3570
+ deleteDialog: {
3571
+ title: 'Delete Task',
3572
+ delete: 'Delete Task',
3573
+ deleting: 'Deleting...',
3574
+ cancel: 'Cancel',
3575
+ },
3576
+ logDetails: {
3577
+ title: 'Log Details',
3578
+ status: 'Status',
3579
+ createdAt: 'Created',
3580
+ startTime: 'Started',
3581
+ endTime: 'Ended',
3582
+ output: 'Output',
3583
+ },
3584
+ };
3585
+ /**
3586
+ * Resolve a Page from either a Page or a Locator. Used when we need to
3587
+ * reach DOM that Radix renders outside the dialog subtree (popovers,
3588
+ * select options, the schedule / delete / log-details dialogs, etc.).
3589
+ */
3590
+ function asPage(scope) {
3591
+ return 'page' in scope ? scope.page() : scope;
3592
+ }
3593
+ // ── Tab navigation ─────────────────────────────────────────────────────
3594
+ /**
3595
+ * Check whether the Tasks tab is currently rendered in the Edit Mentor
3596
+ * dialog. Returns false instead of throwing so callers can guard
3597
+ * conditionally rendered tabs (e.g. permission-gated).
3598
+ */
3599
+ async function isTasksTabVisible(page) {
3600
+ const tab = page.getByRole('tab', { name: TASKS_LABELS.tabName, exact: true });
3601
+ try {
3602
+ await test$1.expect(tab).toBeVisible({ timeout: 5000 });
3603
+ return true;
3604
+ }
3605
+ catch (_a) {
3606
+ return false;
3607
+ }
3608
+ }
3609
+ /**
3610
+ * Switch to the Tasks tab. Assumes the Edit Mentor dialog is open. After
3611
+ * the click we wait for the unique "Schedule Task" button before
3612
+ * returning, so callers can rely on the panel being interactive.
3613
+ */
3614
+ async function switchToTasksTab(page) {
3615
+ const tab = page.getByRole('tab', { name: TASKS_LABELS.tabName, exact: true });
3616
+ await test$1.expect(tab).toBeVisible({ timeout: 10000 });
3617
+ await tab.click();
3618
+ // The toolbar "Schedule Task" button is unique to the Tasks pane.
3619
+ await test$1.expect(page.getByRole('button', { name: TASKS_LABELS.toolbar.scheduleTask })).toBeVisible({
3620
+ timeout: 10000,
3621
+ });
3622
+ logger.info('Switched to Tasks tab');
3623
+ }
3624
+ // ── Top-level locators ─────────────────────────────────────────────────
3625
+ /** Locator for the "Schedule Task" toolbar button (opens the dialog). */
3626
+ function getScheduleTaskButton(scope) {
3627
+ return scope.getByRole('button', { name: TASKS_LABELS.toolbar.scheduleTask });
3628
+ }
3629
+ /** Locator for the task list's search input. */
3630
+ function getSearchInput(scope) {
3631
+ return scope.getByPlaceholder(TASKS_LABELS.toolbar.searchPlaceholder);
3632
+ }
3633
+ /**
3634
+ * Locator for a task row by its title. We scope to the click-target
3635
+ * `role="button"` rendered by `TaskList` so callers can `.click()` to
3636
+ * select, or chain `.getByRole('button', { name: 'Delete task' })` to
3637
+ * delete.
3638
+ */
3639
+ function getTaskRow(scope, taskName) {
3640
+ return scope.getByRole('button', { name: new RegExp(taskName, 'i') }).first();
3641
+ }
3642
+ // ── Metric card assertions ─────────────────────────────────────────────
3643
+ async function metricValue(scope, label) {
3644
+ // The metric card structure is <h3>label</h3><p>N</p> inside a flex col;
3645
+ // the heading's parent is the wrapper around both.
3646
+ const heading = scope.getByText(label, { exact: true }).first();
3647
+ const wrapper = heading.locator('xpath=..');
3648
+ return (await wrapper.innerText()).replace(label, '').trim();
3649
+ }
3650
+ async function expectTotalTasks(scope, n) {
3651
+ await test$1.expect.poll(() => metricValue(scope, TASKS_LABELS.metrics.total)).toBe(String(n));
3652
+ }
3653
+ async function expectCompletedTasks(scope, n) {
3654
+ await test$1.expect.poll(() => metricValue(scope, TASKS_LABELS.metrics.completed)).toBe(String(n));
3655
+ }
3656
+ async function expectFailedTasks(scope, n) {
3657
+ await test$1.expect.poll(() => metricValue(scope, TASKS_LABELS.metrics.failed)).toBe(String(n));
3658
+ }
3659
+ // ── Listing / row state ────────────────────────────────────────────────
3660
+ async function expectTasksEmpty(scope) {
3661
+ await test$1.expect(scope.getByText(TASKS_LABELS.states.empty)).toBeVisible({ timeout: 10000 });
3662
+ }
3663
+ async function expectTaskInList(scope, taskName) {
3664
+ await test$1.expect(getTaskRow(scope, taskName)).toBeVisible({ timeout: 10000 });
3665
+ }
3666
+ async function expectTaskNotInList(scope, taskName) {
3667
+ await test$1.expect(getTaskRow(scope, taskName)).toBeHidden({ timeout: 10000 });
3668
+ }
3669
+ /**
3670
+ * Click a task row to select it. Marks the row's `aria-pressed=true` and
3671
+ * the logs panel rebinds to that task.
3672
+ */
3673
+ async function selectTaskInList(scope, taskName) {
3674
+ const row = getTaskRow(scope, taskName);
3675
+ await row.click();
3676
+ await test$1.expect(row).toHaveAttribute('aria-pressed', 'true', { timeout: 5000 });
3677
+ }
3678
+ /**
3679
+ * Assert that a given task's status badge matches the expected status.
3680
+ */
3681
+ async function expectTaskStatus(scope, taskName, status) {
3682
+ const row = getTaskRow(scope, taskName);
3683
+ await test$1.expect(row.getByText(TASKS_LABELS.status[status], { exact: true })).toBeVisible({
3684
+ timeout: 10000,
3685
+ });
3686
+ }
3687
+ /** Type into the search input. Returns once the input value matches. */
3688
+ async function searchTasks(scope, query) {
3689
+ const input = getSearchInput(scope);
3690
+ await input.fill(query);
3691
+ await test$1.expect(input).toHaveValue(query);
3692
+ }
3693
+ // ── Schedule flow ──────────────────────────────────────────────────────
3694
+ /** Open the Schedule Task dialog and wait for it to be interactive. */
3695
+ async function openScheduleTaskDialog(scope) {
3696
+ await getScheduleTaskButton(scope).click();
3697
+ const page = asPage(scope);
3698
+ await test$1.expect(page.getByLabel(TASKS_LABELS.scheduleDialog.taskName)).toBeVisible({
3699
+ timeout: 10000,
3700
+ });
3701
+ }
3702
+ /**
3703
+ * End-to-end: open the dialog, fill the form, click Schedule, and wait
3704
+ * for it to close.
3705
+ *
3706
+ * `time` is an HTML `<input type="time">` string ("HH:mm", 24-hour). If
3707
+ * the chosen date is today and the chosen time is in the past, the
3708
+ * dialog will block submission — choose accordingly.
3709
+ */
3710
+ async function scheduleTask(scope, opts) {
3711
+ await openScheduleTaskDialog(scope);
3712
+ const page = asPage(scope);
3713
+ await page.getByLabel(TASKS_LABELS.scheduleDialog.taskName).fill(opts.name);
3714
+ if (opts.prompt !== undefined) {
3715
+ await page.getByLabel(TASKS_LABELS.scheduleDialog.taskPrompt).fill(opts.prompt);
3716
+ }
3717
+ await page.getByLabel(TASKS_LABELS.scheduleDialog.time).fill(opts.time);
3718
+ if (opts.repeat) {
3719
+ // The repeat <Select> renders as a combobox via Radix.
3720
+ const repeatLabel = {
3721
+ "don't-repeat": TASKS_LABELS.scheduleDialog.dontRepeat,
3722
+ daily: TASKS_LABELS.scheduleDialog.daily,
3723
+ weekly: TASKS_LABELS.scheduleDialog.weekly,
3724
+ monthly: TASKS_LABELS.scheduleDialog.monthly,
3725
+ }[opts.repeat];
3726
+ await page.getByLabel(TASKS_LABELS.scheduleDialog.repeat).click();
3727
+ await page.getByRole('option', { name: repeatLabel }).click();
3728
+ }
3729
+ if (opts.notifyByEmail) {
3730
+ await page.getByLabel(TASKS_LABELS.scheduleDialog.notifyByEmail).click();
3731
+ }
3732
+ const submit = page.getByRole('button', { name: TASKS_LABELS.scheduleDialog.schedule });
3733
+ await test$1.expect(submit).toBeEnabled({ timeout: 5000 });
3734
+ await submit.click();
3735
+ await test$1.expect(page.getByLabel(TASKS_LABELS.scheduleDialog.taskName)).toBeHidden({
3736
+ timeout: 15000,
3737
+ });
3738
+ logger.info(`Scheduled task: ${opts.name}`);
3739
+ }
3740
+ /** Asserts the in-dialog past-time error is currently shown. */
3741
+ async function expectScheduleStartTimeInPastError(scope) {
3742
+ await test$1.expect(asPage(scope).getByText(TASKS_LABELS.scheduleDialog.startTimeInPast)).toBeVisible({
3743
+ timeout: 5000,
3744
+ });
3745
+ }
3746
+ // ── Delete flow ────────────────────────────────────────────────────────
3747
+ /**
3748
+ * Click the trash icon on a task row and confirm the delete in the
3749
+ * follow-up dialog.
3750
+ */
3751
+ async function deleteTask(scope, taskName) {
3752
+ const row = getTaskRow(scope, taskName);
3753
+ await row.getByRole('button', { name: TASKS_LABELS.list.deleteTask }).click();
3754
+ const page = asPage(scope);
3755
+ const confirm = page.getByRole('button', { name: TASKS_LABELS.deleteDialog.delete });
3756
+ await test$1.expect(confirm).toBeVisible({ timeout: 5000 });
3757
+ await confirm.click();
3758
+ // Wait for the dialog to close.
3759
+ await test$1.expect(page.getByRole('heading', { name: TASKS_LABELS.deleteDialog.title })).toBeHidden({
3760
+ timeout: 10000,
3761
+ });
3762
+ logger.info(`Deleted task: ${taskName}`);
3763
+ }
3764
+ // ── Logs panel + log details ───────────────────────────────────────────
3765
+ /**
3766
+ * Wait for the logs panel to finish loading and assert that the selected
3767
+ * task name is shown in the header.
3768
+ */
3769
+ async function expectLogsForTask(scope, taskName) {
3770
+ await test$1.expect(scope.getByText(TASKS_LABELS.logs.forTask, { exact: false })).toBeVisible({
3771
+ timeout: 10000,
3772
+ });
3773
+ await test$1.expect(scope.getByText(taskName, { exact: true }).first()).toBeVisible({
3774
+ timeout: 10000,
3775
+ });
3776
+ }
3777
+ async function expectNoLogsForSelectedTask(scope) {
3778
+ await test$1.expect(scope.getByText(TASKS_LABELS.logs.noLogs)).toBeVisible({ timeout: 10000 });
3779
+ }
3780
+ /**
3781
+ * Click the first log entry under the selected task to open the log
3782
+ * details modal. Returns once the dialog title is visible.
3783
+ */
3784
+ async function openFirstLogDetails(scope) {
3785
+ // Log rows are buttons. Filter by the chevron pattern won't be reliable
3786
+ // across renders, so target the panel by its header text and pick the
3787
+ // first interactive child.
3788
+ const panel = scope.getByText(TASKS_LABELS.logs.title, { exact: true }).locator('xpath=../..');
3789
+ await panel.getByRole('button').first().click();
3790
+ const page = asPage(scope);
3791
+ await test$1.expect(page.getByRole('heading', { name: TASKS_LABELS.logDetails.title })).toBeVisible({
3792
+ timeout: 10000,
3793
+ });
3794
+ }
3795
+ async function expectLogDetailsStatus(scope, status) {
3796
+ const page = asPage(scope);
3797
+ await test$1.expect(page.getByText(status, { exact: true })).toBeVisible({ timeout: 5000 });
3798
+ }
3799
+
3503
3800
  /** Extract browser key from device name (e.g., 'Desktop Chrome' -> 'chrome') */
3504
3801
  function getBrowserKey(deviceName) {
3505
3802
  return deviceName.toLowerCase().replace(/^desktop\s+/, '');
@@ -3633,6 +3930,7 @@ exports.AuthFlowBuilder = AuthFlowBuilder;
3633
3930
  exports.CustomReporter = CustomReporter;
3634
3931
  exports.MailsacClient = MailsacClient;
3635
3932
  exports.PRIVACY_LABELS = PRIVACY_LABELS;
3933
+ exports.TASKS_LABELS = TASKS_LABELS;
3636
3934
  exports.addMemory = addMemory;
3637
3935
  exports.archiveFirstMemory = archiveFirstMemory;
3638
3936
  exports.archiveMemoryByContent = archiveMemoryByContent;
@@ -3670,6 +3968,7 @@ exports.deleteFirstMemory = deleteFirstMemory;
3670
3968
  exports.deleteInstance = deleteInstance;
3671
3969
  exports.deleteMemoryByContent = deleteMemoryByContent;
3672
3970
  exports.deleteSkill = deleteSkill;
3971
+ exports.deleteTask = deleteTask;
3673
3972
  exports.disableSkill = disableSkill;
3674
3973
  exports.disconnectInstance = disconnectInstance;
3675
3974
  exports.editAgentPrompt = editAgentPrompt;
@@ -3683,18 +3982,29 @@ exports.expectBillingTabForCurrentPlan = expectBillingTabForCurrentPlan;
3683
3982
  exports.expectBillingTabForFreePlan = expectBillingTabForFreePlan;
3684
3983
  exports.expectBillingTabForPremiumPlan = expectBillingTabForPremiumPlan;
3685
3984
  exports.expectBillingTabForTrialPlan = expectBillingTabForTrialPlan;
3985
+ exports.expectCompletedTasks = expectCompletedTasks;
3686
3986
  exports.expectCreditBalanceForCurrentPlan = expectCreditBalanceForCurrentPlan;
3687
3987
  exports.expectCreditBalancePanelForFreePlan = expectCreditBalancePanelForFreePlan;
3688
3988
  exports.expectCreditBalancePanelForPremiumPlan = expectCreditBalancePanelForPremiumPlan;
3689
3989
  exports.expectCreditBalancePanelForTrialPlan = expectCreditBalancePanelForTrialPlan;
3690
3990
  exports.expectCreditBalanceVisibilityForTenant = expectCreditBalanceVisibilityForTenant;
3691
3991
  exports.expectEntitySelected = expectEntitySelected;
3992
+ exports.expectFailedTasks = expectFailedTasks;
3993
+ exports.expectLogDetailsStatus = expectLogDetailsStatus;
3994
+ exports.expectLogsForTask = expectLogsForTask;
3692
3995
  exports.expectNoAccessibilityViolations = expectNoAccessibilityViolations;
3693
3996
  exports.expectNoAccessibilityViolationsOnDialogs = expectNoAccessibilityViolationsOnDialogs;
3997
+ exports.expectNoLogsForSelectedTask = expectNoLogsForSelectedTask;
3694
3998
  exports.expectOutputFilterEnabled = expectOutputFilterEnabled;
3695
3999
  exports.expectPrivacyFieldsHidden = expectPrivacyFieldsHidden;
3696
4000
  exports.expectPrivacyFieldsVisible = expectPrivacyFieldsVisible;
3697
4001
  exports.expectPrivacyRouterEnabled = expectPrivacyRouterEnabled;
4002
+ exports.expectScheduleStartTimeInPastError = expectScheduleStartTimeInPastError;
4003
+ exports.expectTaskInList = expectTaskInList;
4004
+ exports.expectTaskNotInList = expectTaskNotInList;
4005
+ exports.expectTaskStatus = expectTaskStatus;
4006
+ exports.expectTasksEmpty = expectTasksEmpty;
4007
+ exports.expectTotalTasks = expectTotalTasks;
3698
4008
  exports.filterByAction = filterByAction;
3699
4009
  exports.filterByActionAndVerify = filterByActionAndVerify;
3700
4010
  exports.filterByActor = filterByActor;
@@ -3720,7 +4030,10 @@ exports.getMentorIdFromUrl = getMentorIdFromUrl;
3720
4030
  exports.getOutputFilterSwitch = getOutputFilterSwitch;
3721
4031
  exports.getPaginationInfo = getPaginationInfo;
3722
4032
  exports.getPrivacyRouterSwitch = getPrivacyRouterSwitch;
4033
+ exports.getScheduleTaskButton = getScheduleTaskButton;
4034
+ exports.getSearchInput = getSearchInput;
3723
4035
  exports.getSkillRowCount = getSkillRowCount;
4036
+ exports.getTaskRow = getTaskRow;
3724
4037
  exports.goToFirstPage = goToFirstPage;
3725
4038
  exports.goToLastPage = goToLastPage;
3726
4039
  exports.goToNextPage = goToNextPage;
@@ -3735,6 +4048,7 @@ exports.isOnLastPage = isOnLastPage;
3735
4048
  exports.isPrivacyTabVisible = isPrivacyTabVisible;
3736
4049
  exports.isSandboxTabVisible = isSandboxTabVisible;
3737
4050
  exports.isSkillEnabled = isSkillEnabled;
4051
+ exports.isTasksTabVisible = isTasksTabVisible;
3738
4052
  exports.logger = logger;
3739
4053
  exports.loginWithEmailAndPassword = loginWithEmailAndPassword;
3740
4054
  exports.loginWithMicrosoftIdp = loginWithMicrosoftIdp;
@@ -3748,10 +4062,12 @@ exports.openAgentPromptEditModal = openAgentPromptEditModal;
3748
4062
  exports.openCreditBalanceDropdown = openCreditBalanceDropdown;
3749
4063
  exports.openEditInstanceDialog = openEditInstanceDialog;
3750
4064
  exports.openEditSkillDialog = openEditSkillDialog;
4065
+ exports.openFirstLogDetails = openFirstLogDetails;
3751
4066
  exports.openInstanceActionsMenu = openInstanceActionsMenu;
3752
4067
  exports.openLLMProviderPicker = openLLMProviderPicker;
3753
4068
  exports.openNewInstanceDialog = openNewInstanceDialog;
3754
4069
  exports.openNewSkillDialog = openNewSkillDialog;
4070
+ exports.openScheduleTaskDialog = openScheduleTaskDialog;
3755
4071
  exports.openSkillActionsMenu = openSkillActionsMenu;
3756
4072
  exports.parseReportUrlParams = parseReportUrlParams;
3757
4073
  exports.pushConfiguration = pushConfiguration;
@@ -3761,10 +4077,13 @@ exports.retry = retry;
3761
4077
  exports.runConnectedInstanceChecks = runConnectedInstanceChecks;
3762
4078
  exports.runInstanceChecks = runInstanceChecks;
3763
4079
  exports.safeWaitForURL = safeWaitForURL;
4080
+ exports.scheduleTask = scheduleTask;
3764
4081
  exports.searchInstances = searchInstances;
4082
+ exports.searchTasks = searchTasks;
3765
4083
  exports.selectDateFromCalendar = selectDateFromCalendar;
3766
4084
  exports.selectLLMModel = selectLLMModel;
3767
4085
  exports.selectPrivacyAction = selectPrivacyAction;
4086
+ exports.selectTaskInList = selectTaskInList;
3768
4087
  exports.setBlockMessage = setBlockMessage;
3769
4088
  exports.setEntitySelected = setEntitySelected;
3770
4089
  exports.setOutputFilterEnabled = setOutputFilterEnabled;
@@ -3791,6 +4110,7 @@ exports.switchToMemoryTab = switchToMemoryTab;
3791
4110
  exports.switchToPrivacyTab = switchToPrivacyTab;
3792
4111
  exports.switchToSandboxTab = switchToSandboxTab;
3793
4112
  exports.switchToSkillsTab = switchToSkillsTab;
4113
+ exports.switchToTasksTab = switchToTasksTab;
3794
4114
  exports.teardownSandboxInstance = teardownSandboxInstance;
3795
4115
  exports.test = test;
3796
4116
  exports.toggleAutoPush = toggleAutoPush;