EN / field notes OpenHands feature map

OpenHands / F21

Automations dashboard and actions

The Automate dashboard (/automations) is where a user sees every automation at a glance and acts on one without opening it. It shows four overview tiles, a search box, a Filters popover (status, trigger, sort), a grid/list view toggle, and Active and Inactive groups of cards or rows with run health, a recent-runs sparkline and stats. Each automation has a Play (Run now) button and a kebab menu with Run now, View, Export, Edit, Turn on/off and Delete. The page sits in a Dashboard/Templates sub-page shell. It is reached from the sidebar, the command menu or a pinned home route, and it only exists when the automation interface manifest from @openhands/extensions is admitted.

34 mapped behaviors · 34 recipes and supporting checks · source snapshot 9 October 2026
From upstream main at 8793c111. Read the maintained source.

How to get to it

  • Sidebar Automate (sidebar-automations-link); hovering it reveals the pin (sidebar-pin-home-toggle-automations).
  • Direct URL /automations; the Templates sub-page is /automations/templates (F22).
  • Command menu (Control+k/Meta+k or command-menu-trigger): type Automations or a keyword such as cron, press Enter.
  • With Automate pinned, / (logo, a bare visit) lands on /automations.
  • Sub-page navigation: automations-navbar-desktop (aside) or automations-navbar-mobile (phone strip), links automations-navigation-list and automations-navigation-templates.
  • Header Add Automation (automations-add-automation) → Create Automation (automations-add-automation-create) or Import automation (automations-import-automation, F22); on the empty dashboard, the Recommended automations rail cards (recommended-automation-rail-card-<id>).
  • Getting-started checklist item Schedule a task links to /automations (onboarding checklist family).
  • Per automation: Play (automation-run-now-<id>, name "Run now") and kebab (name "Automation actions") on each card or row; the card itself and the row link open the detail page (F23).

Before you start

Start with the common launch and health checks, then follow this family’s preconditions in order. Recipes share the fixtures and state named below.

Preconditions:

  • Baseline state (launched, doctored, onboard --skip done) with an active LLM profile for Run now (control-openhands llm preset deepseek, or --api-key-file <file> when the key is in a file).
  • No automations exist (control-openhands api GET /api/automation/v1 shows "total": 0); the recipes start from the empty state.
  • Fixtures are imported through Add Automation → Import automation (F22 owns that flow; here it only arranges). Create the files first; each command prints the path to upload ($OH_VERIFY_RUN/evidence/_fixtures/<name>): control-openhands fixture file --name qa-f21-alpha.automation.json --content '{"version":1,"kind":"automation","spec":{"name":"QA_F21 Alpha","trigger":{"type":"cron","schedule":"0 0 1 1 *","timezone":"UTC"},"enabled":false,"prompt":"Reply with the single word: pong. Do not run any tools."}}', control-openhands fixture file --name qa-f21-beta.automation.json --content '{"version":1,"kind":"automation","spec":{"name":"QA_F21 Beta","trigger":{"type":"event","source":"github","on":"pull_request.opened"},"enabled":false,"prompt":"Beta event-driven QA fixture; never runs."}}', control-openhands fixture file --name qa-f21-fail.automation.json --content '{"version":1,"kind":"automation","spec":{"name":"QA_F21 Fail","trigger":{"type":"cron","schedule":"0 0 1 1 *","timezone":"UTC"},"enabled":false,"prompt":"Reply with the single word: pong.","timeout":1}}'. The yearly cron (0 0 1 1 *) keeps a turned-on fixture from firing on its own.
  • Selectors below avoid run-specific ids: [data-testid^="automation-card-"] >> has-text=<name> (grid) and [data-testid^="automation-list-row-"] >> has-text=<name> (list). The kebab menu renders in a portal with no test ids; scope its items as role=list >> has-text=Export >> role=button[name="..."].

Behavior inventory

34 stable behavior IDs and their expected behavior
  • F21.sidebar-entry the sidebar Automate item opens /automations with the heading Dashboard and the Automate sub-page aside. Read recipe ↓
  • F21.command-menu the command menu offers Automations ("Review scheduled and webhook automations."), also found by the keywords automate, cron, schedule, webhook and jobs; choosing it opens /automations. Read recipe ↓
  • F21.pin-home the pin on the sidebar Automate item makes / redirect to /automations; unpinning restores the normal home. Read recipe ↓
  • F21.subpage-nav Dashboard and Templates sub-pages appear in a left aside from 1024 px up and in a horizontal strip below that (phones and tablets); the current page has aria-current="page". Read recipe ↓
  • F21.manifest-404 without an admitted automation interface manifest, /automations is a 404 from the layout error boundary and the Automate entries are hidden. Read recipe ↓
  • F21.health-loading while the health check runs, the page shows the title and three card skeletons. Read recipe ↓
  • F21.list-loading once the backend is healthy, the header, tiles and controls render at once while three card skeletons stand in for the list until the first page arrives. Read recipe ↓
  • F21.backend-unavailable when the health check fails, the page shows Automations Unavailable with a Retry button. Read recipe ↓
  • F21.list-error when the list request fails with nothing cached, the page shows Failed to load automations with Retry. Read recipe ↓
  • F21.add-menu the header Add Automation menu offers Create Automation and Import automation; Create Automation opens the How to create an automation dialog (instructions, docs link, Create Automation), which X and Escape close. Read recipe ↓
  • F21.empty-state with zero automations the page shows No automations configured, the "Create an automation" instructions, a docs link, Create Automation and the Recommended automations rail; the view toggle is disabled. Read recipe ↓
  • F21.empty-rail a Recommended automations card on the empty dashboard opens its template setup at /automations/new/<template-id> (F22 owns the setup flow). Read recipe ↓
  • F21.overview-tiles four tiles summarize the loaded org list: Automations (N active), Needs attention (Latest run failed or No latest-run failures; an enabled automation counts when its latest run is Failed or its task outcome is failed or blocked, while Partial and Needs review do not count), Total runs and Average duration. Read recipe ↓
  • F21.search the search box narrows the visible automations client-side by name, prompt, repository or model; the tiles do not change. Read recipe ↓
  • F21.filters the Filters popover filters by status (All / Active / Needs attention / Disabled) and trigger (All / Scheduled / Event-driven), counts non-default choices in a badge and offers Reset all; it stays open while the user changes several choices. Read recipe ↓
  • F21.sort Sort (Latest run / Most runs / Name) orders automations inside each group and counts as a non-default choice. Read recipe ↓
  • F21.filtered-empty when nothing matches, No automations match these filters and Clear filters appear; Clear filters resets search, status and trigger but keeps the sort. Read recipe ↓
  • F21.view-toggle the view-mode menu switches Grid and List; the choice survives a reload. Read recipe ↓
  • F21.card-grid a grid card shows name, prompt preview, trigger pills, sparkline, a status strip (latest run status, summary, relative time, or No activity yet) and Runs / Recent success / Avg. duration stats. Read recipe ↓
  • F21.list-row a list row shows a health dot, name, schedule or event, relative last run, run badge, sparkline, Play and kebab; its link is labelled <name> <status>. Read recipe ↓
  • F21.row-tooltip hovering a list row shows a preview card with the name, Trigger, Status and, once it has run, Last run and Task (the run summary); the Play button's tooltip reads Run now. Read recipe ↓
  • F21.summary-hovercard hovering a grid card's truncated run summary shows the full summary in a hovercard. Read recipe ↓
  • F21.open-detail kebab View, the row link, a card click and Enter on a focused card all open /automations/<id>. Read recipe ↓
  • F21.load-more past 50 automations a Load more button fetches the next page and disappears when everything is loaded. Read recipe ↓
  • F21.git-sync-button the header Git Sync button (manage permission; always on local) opens /automations/git-sync (see F24). Read recipe ↓
  • F21.kebab-menu the Automation actions menu lists Run now, View, Export, Edit, Turn on/Turn off and Delete, closes on Escape or an outside click, and flips above the trigger near the viewport bottom. Read recipe ↓
  • F21.run-now Play or kebab Run now on an enabled automation dispatches a run with the toast Automation dispatched; the card moves through Pending/Running to the result. Both are disabled on a disabled automation. Read recipe ↓
  • F21.run-now-error a failed dispatch shows exactly one error toast with the API message; when the automation is gone (Automation not found), its card then drops out of the dashboard without a reload. Read recipe ↓
  • F21.toggle kebab Turn on / Turn off flips the enabled flag immediately (no confirmation on the dashboard) and moves the automation between the Active and Inactive groups. Read recipe ↓
  • F21.edit kebab Edit opens the edit modal prefilled from the automation; Save persists (field-level editing is F23). Read recipe ↓
  • F21.export kebab Export downloads <name-slug>.automation.json with version, kind: "automation" and the spec. Read recipe ↓
  • F21.delete kebab Delete asks Delete "<name>"? This cannot be undone.; Cancel and the X keep it, Delete removes it for good, and Escape dismisses the dialog. Read recipe ↓
  • F21.phone at 390 px the dashboard has no horizontal overflow, tiles wrap 2×2 and the kebab menu stays inside the viewport. Read recipe ↓

Readable recipes

Read each script from top to bottom. Code is copied from the map; prose gives the action, expected observation, and conditions. <id>, <run> and similar placeholders stand for values from your own run. Short forms such as browser count continue the same control-openhands invocation; they are kept as documented.

Expected observations describe the recipe’s contract. Captures below selected recipes show representative real states from this snapshot; they do not mark every mapped behavior as passed. Follow cleanup before moving to another family.

Sidebar entry #

  1. Note
    From / run
  2. Do
    control-openhands browser click 'testid=sidebar-automations-link'
  3. Check
    control-openhands browser url
  4. Check
    control-openhands browser testids --filter automations-navigation
  5. Expect
    The URL is /automations, the main heading is Dashboard, and automations-navigation-list and automations-navigation-templates are listed.

Command menu #

  1. Do
    control-openhands browser goto /

    (Sidebar entry ends on /automations, where the URL check would pass at once),

  2. Do
    control-openhands browser press Control+k
  3. Do
    control-openhands browser type 'testid=command-menu >> role=combobox' cron
  4. Check
    control-openhands browser snapshot 'testid=command-menu'
  5. Do
    control-openhands browser press Enter
  6. Check
    control-openhands browser url
  7. Expect
    The snapshot lists exactly one option, Automations Review scheduled and webhook automations. Go ([selected], under Navigation), and the URL ends in /automations.

Pin as home #

  1. Note
    On any page run
  2. Do
    control-openhands browser hover 'testid=sidebar-automations-link'
  3. Do
    control-openhands browser click 'testid=sidebar-pin-home-toggle-automations'
  4. Check
    control-openhands browser attr 'testid=sidebar-pin-home-toggle-automations' aria-pressed

    (true; aria-label becomes Unpin as home page), then

  5. Do
    control-openhands browser goto /
  6. Check
    control-openhands browser url
  7. Note
    : the URL is /automations.
  8. Note
    Unpin with the same hover and click (aria-pressed false);
  9. Do
    control-openhands browser goto /
  10. Note
    then stays on /.

Sub-page navigation #

  1. Note
    On /automations (control-openhands browser goto /automations; Pin as home ends on /) run
  2. Do
    control-openhands browser click 'testid=automations-navbar-desktop >> testid=automations-navigation-templates'

    (URL /automations/templates, h1 Templates),

  3. Do
    control-openhands browser click 'testid=automations-navbar-desktop >> testid=automations-navigation-list'
  4. Check
    control-openhands browser attr 'testid=automations-navbar-desktop >> testid=automations-navigation-list' aria-current

    (page).

  5. Note
    Below 1024 px the same links live under testid=automations-navbar-mobile (see Phone), tablets included: run
  6. Do
    control-openhands browser viewport tablet

    (820 px),

  7. Check
    control-openhands browser visible 'testid=automations-navbar-mobile'

    (true) and

  8. Check
    control-openhands browser visible 'testid=automations-navbar-desktop'

    (false), then

  9. Do
    control-openhands browser viewport desktop

Backend unavailable #

  1. Do
    control-openhands service stop automation
  2. Do
    control-openhands browser goto /automations
  3. Wait
    control-openhands browser wait-text 'Automations Unavailable'
  4. Check
    control-openhands browser text 'role=main'
  5. Do
    control-openhands browser screenshot --feature F21.backend-unavailable --name unavailable
  6. Expect
    The main pane reads Dashboard, the subtitle, Automations Unavailable, The automations backend is not available right now. ... and Retry; the sidebar Automate item stays.
  7. Do
    control-openhands restart

    (the page stays put),

  8. Do
    control-openhands browser click 'role=button[name="Retry"]'
  9. Wait
    control-openhands browser wait 'testid=automations-empty'
  10. Note
    : the dashboard returns without a reload.

List error #

  1. Expect
    The health result is cached for 30 s, and every page's sidebar checklist fetches it but not the 50-row list.
  2. Do
    control-openhands browser goto /settings/secrets
  3. Note
    then within 30 s
  4. Do
    control-openhands service stop automation
  5. Check
    control-openhands browser click 'testid=sidebar-automations-link' --expect-url '/automations'
  6. Wait
    control-openhands browser wait-text 'Failed to load automations'
  7. Expect
    The header, tiles (zeros) and Filters render above Failed to load automations and Retry; screenshot with --feature F21.list-error --name list-error.
  8. Do
    control-openhands restart
  9. Note
    click role=button[name="Retry"] and wait for testid=automations-empty.
  10. Note
    Starting from / does not work: home loads the same list, so the cached copy is shown instead of the error.

List loading #

  1. Do
    control-openhands browser goto /settings/secrets
  2. Do
    control-openhands browser click 'testid=sidebar-automations-link' --observe '[data-testid="automation-card-skeleton"], [data-testid="automations-add-automation"]' --observe-ms 2000
  3. Note
    One observed state is Add Automation plus three empty skeleton entries (about 50 ms), followed by Add Automation alone.

Health loading and missing manifest #

  1. Note
    Blocked.
  2. Expect
    The health-only skeleton (title and three automation-card-skeleton, no header buttons) shows only on the first health fetch, which the sidebar checklist always makes before a user can reach the dashboard, and stopping the service makes the fetch fail at once rather than hang.
  3. Expect
    The 404 needs a build without the automation interface manifest.

Empty state #

  1. Note
    With no automations run
  2. Do
    control-openhands browser goto /automations
  3. Check
    control-openhands browser text 'testid=automations-empty'
  4. Check
    control-openhands browser enabled 'testid=automations-view-toggle'
  5. Do
    control-openhands browser screenshot --feature F21.empty-state --name empty
  6. Expect
    The text starts No automations configured, contains Create an automation, Learn more in the documentation and Create Automation, and lists the Recommended automations; enabled is false; the tiles read Automations 0 / 0 active and Needs attention 0 / No latest-run failures. Create Automation (automations-create-automation) starts a model-backed chat: see F22.
Automations dashboard with zero automations and recommended templates.
The empty dashboard provides creation controls and recommended workflows. CLI capture · 1440 × 1000 · 9 October 2026 · Canvas 8793c111

Empty dashboard shows no configured automations and recommended workflows.

No automation is executed or scheduled.

How this screenshot was taken

agent server: 1.53.0 · automation: 1.19.0 (launcher default) · canvas: 1.26.0

control-openhands browser goto /automations
control-openhands browser wait testid=automations-empty --timeout 10000
control-openhands browser screenshot --feature F21.empty-state --name empty-dashboard

Add Automation menu #

  1. Do
    control-openhands browser click 'testid=automations-add-automation'
  2. Check
    control-openhands browser snapshot 'testid=automations-add-automation-menu'
  3. Note
    : buttons Create Automation and Import automation.
  4. Do
    control-openhands browser press Escape

    (or a click on role=heading[name="Dashboard"]) makes browser count 'testid=automations-add-automation-menu' 0.

  5. Note
    Reopen it,
  6. Do
    control-openhands browser click 'testid=automations-add-automation-create'
  7. Check
    control-openhands browser snapshot 'role=dialog'
  8. Note
    : dialog "How to create an automation" with Close, the instructions (Create an automation as code), Learn more in the documentation (https://docs.openhands.dev/openhands/usage/automations/overview) and Create Automation (automations-create-automation, model-backed, F22).
  9. Do
    control-openhands browser click 'testid=add-automation-modal-close'
  10. Note
    and browser press Escape each make browser count 'testid=add-automation-modal' 0.

Recommended rail on the empty dashboard #

  1. Note
    With no automations,
  2. Do
    control-openhands browser click 'testid=automations-empty >> testid=recommended-automation-rail-card-custom-automation' --expect-url 'automations/new'
  3. Note
    lands on /automations/new/custom-automation with the Before you start prerequisites dialog (F22).
  4. Do
    control-openhands browser back

    returns to /automations; there browser attr 'testid=recommended-automations-rail-fade-left' data-visible is false and ...-fade-right is true (scrolling is F03.recommended-automations-rail).

Arrange two automations (import, F22) #

  1. Do
    control-openhands browser click 'testid=automations-add-automation'
  2. Do
    control-openhands browser click 'testid=automations-import-automation'
  3. Do
    control-openhands browser upload 'testid=automations-import-file' "$OH_VERIFY_RUN/evidence/_fixtures/qa-f21-alpha.automation.json"
  4. Do
    control-openhands browser click 'testid=import-automation-confirm'
  5. Wait
    control-openhands browser wait 'testid=import-automation-modal' --state detached
  6. Note
    Repeat with qa-f21-beta.automation.json.
  7. Expect
    Both arrive disabled, under Inactive 2.

Grid cards #

  1. Do
    control-openhands browser reload
  2. Check
    control-openhands browser snapshot 'role=main'
  3. Do
    control-openhands browser screenshot --feature F21.card-grid --name grid
  4. Note
    Each card shows its name, prompt, pills (pull_request.opened + GitHub logo for Beta, cron for Alpha), No activity yet, and Runs 0 / Recent success — / Avg. duration —; both Play buttons are [disabled].
Automations dashboard with two inactive local QA automations.
Cards expose each automation trigger, inactive state, and available actions. CLI capture · 1440 × 1000 · 9 October 2026 · Canvas 8793c111

Dashboard grid shows two local inactive automation cards.

Fixtures arranged with API; only UI cards are proof. Both automations disabled; no run or webhook dispatched.

How this screenshot was taken

agent server: 1.53.0 · automation: 1.19.0 (launcher default) · canvas: 1.26.0

control-openhands api POST /api/automation/v1/preset/prompt --write --data '{"name":"QA_F23 Pong","prompt":"Reply with the single word: pong. Do not run any tools.","trigger":{"type":"cron","schedule":"0 9 * * *","timezone":"UTC"},"enabled":false}'
control-openhands api POST /api/automation/v1/preset/prompt --write --data '{"name":"QA_F21 Beta","prompt":"Beta event-driven QA fixture; never runs.","trigger":{"type":"event","source":"github","on":"pull_request.opened"},"enabled":false}'
control-openhands browser goto /automations
control-openhands browser reload
control-openhands browser screenshot --feature F21.card-grid --name grid-cards

Kebab menu #

  1. Do
    control-openhands browser click '[data-testid^="automation-card-"] >> has-text=QA_F21 Alpha >> role=button[name="Automation actions"]'
  2. Check
    control-openhands browser snapshot 'role=list >> has-text=Export'
  3. Note
    : buttons Run now [disabled], View, Export, Edit, Turn on, Delete.
  4. Do
    control-openhands browser press Escape
  5. Check
    control-openhands browser count 'role=list >> has-text=Export'

    is 0.

  6. Note
    Reopen it and click role=heading[name="Dashboard"]; the count is 0 again.

Turn on #

  1. Note
    Open Alpha's kebab as above, run
  2. Do
    control-openhands browser click 'role=list >> has-text=Export >> role=button[name="Turn on"]'
  3. Do
    control-openhands browser reload
  4. Check
    control-openhands browser snapshot 'role=main'
  5. Expect
    No dialog appears; an Active 1 group holds QA_F21 Alpha, the Automations tile reads 2 / 1 active, and Alpha's Play is enabled (control-openhands browser enabled '[data-testid^="automation-card-"] >> has-text=QA_F21 Alpha >> role=button[name="Run now"]' is true).

Run now from Play #

  1. Do
    control-openhands browser click '[data-testid^="automation-card-"] >> has-text=QA_F21 Alpha >> role=button[name="Run now"]'
  2. Wait
    control-openhands browser wait-text 'Automation dispatched'
  3. Do
    control-openhands browser screenshot --feature F21.run-now --name toast
  4. Expect
    The URL stays /automations, the status strip reads Pending, then Running, and Total runs becomes 1.
  5. Note
    About 40 s later, after a reload, the strip reads Successful with the agent's summary and the sidebar lists a conversation titled QA_F21 Alpha — <UTC timestamp> (control-openhands conversation list).

List view #

  1. Do
    control-openhands browser click 'testid=automations-view-toggle'
  2. Do
    control-openhands browser click 'testid=automations-view-toggle-list'
  3. Do
    control-openhands browser reload
  4. Check
    control-openhands browser count '[data-testid^="automation-list-row-"]'

    (2) and

  5. Do
    control-openhands browser eval "localStorage.getItem('openhands-automations-view')"

    ("list").

  6. Check
    control-openhands browser text '[data-testid^="automation-list-row-"] >> has-text=QA_F21 Alpha'

    reads QA_F21 Alpha / 0 0 1 1 * / <relative time> / <status>, and

  7. Check
    control-openhands browser attr '[data-testid^="automation-list-row-"] >> has-text=QA_F21 Alpha >> role=link >> nth=0' aria-label

    is QA_F21 Alpha <status> (for example QA_F21 Alpha Running).

  8. Note
    Screenshot with
  9. Do
    control-openhands browser screenshot --feature F21.list-row --name list
  10. Note
    Escape closes the open view menu (control-openhands browser count 'testid=automations-view-toggle-list' is 0).

Hover previews #

  1. Expect
    After Alpha's run has succeeded, in list view run
  2. Do
    control-openhands browser tooltip '[data-testid^="automation-list-row-"] >> has-text=QA_F21 Alpha >> role=link >> nth=0'
  3. Note
    : QA_F21 Alpha / Trigger / 0 0 1 1 * / Status / Successful / Last run / <relative time> / Task / <the run summary> (a row without runs shows only Trigger and Status / No activity yet).
  4. Do
    control-openhands browser tooltip '[data-testid^="automation-list-row-"] >> has-text=QA_F21 Alpha >> role=button[name="Run now"]'

    is Run now.

  5. Note
    In grid view (control-openhands browser click 'testid=automations-view-toggle', control-openhands browser click 'testid=automations-view-toggle-grid'),
  6. Do
    control-openhands browser tooltip '[data-testid^="automation-card-"] >> has-text=QA_F21 Alpha >> text=Replied'

    (the start of the truncated summary) returns the full summary, for example Replied with the single word "pong" as requested, without running any tools. Switch back with testid=automations-view-toggle and testid=automations-view-toggle-list: the next bullets count list rows.

Sparkline deep link #

  1. Expect
    After Alpha has a run, run
  2. Check
    control-openhands browser attr '[data-testid^="automation-activity-"] >> role=link >> nth=0' href
  3. Do
    control-openhands browser click '[data-testid^="automation-activity-"] >> role=link >> nth=0'
  4. Check
    control-openhands browser url
  5. Check
    control-openhands browser testids --filter run
  6. Expect
    The href and URL are /automations/<id>?run=<runId>, and automation-run-highlight-<runId> is visible.
  7. Note
    Return with
  8. Do
    control-openhands browser goto /automations

Search #

  1. Do
    control-openhands browser fill 'role=textbox[name="Search automations..."]' pong
  2. Check
    control-openhands browser count '[data-testid^="automation-list-row-"]'
  3. Expect
    The count is 1 (Alpha matches by prompt only).
  4. Note
    Then fill zzqa-nomatch;
  5. Check
    control-openhands browser text 'testid=overview-tile-automations'
  6. Note
    still reads Automations 2 / 1 active.

Filtered empty and Clear filters #

  1. Note
    With zzqa-nomatch in the search box run
  2. Check
    control-openhands browser text 'testid=automations-filtered-empty'

    (No automations match these filters / Clear filters),

  3. Do
    control-openhands browser click 'testid=automations-clear-filters'
  4. Check
    control-openhands browser value 'role=textbox[name="Search automations..."]'

    (empty) and the row count (2).

Filters #

  1. Do
    control-openhands browser click 'testid=automations-filters >> testid=dropdown-trigger >> nth=0'
  2. Check
    control-openhands browser snapshot 'testid=automations-filters-menu'

    (Filter by status All statuses, Filter by trigger All triggers, Sort automations Latest run),

  3. Check
    control-openhands browser click 'testid=automations-filter-status >> testid=dropdown-trigger'
  4. Check
    control-openhands browser click 'testid=automations-filter-status-disabled'
  5. Check
    control-openhands browser text 'testid=automations-filters >> testid=dropdown-trigger >> nth=0'

    (Filters 1) and the row text (only QA_F21 Beta).

  6. Expect
    The popover stays open:
  7. Check
    control-openhands browser count 'testid=automations-filters-menu'

    is 1 and

  8. Check
    control-openhands browser attr 'testid=automations-filters >> testid=dropdown-trigger >> nth=0' aria-expanded

    is true.

  9. Note
    In the same popover run
  10. Do
    control-openhands browser click 'testid=automations-filter-trigger >> testid=dropdown-trigger'
  11. Do
    control-openhands browser click 'testid=automations-filter-trigger-schedule'
  12. Note
    : the badge is Filters 2, testid=automations-filtered-empty appears and the menu count is still 1 (control-openhands browser screenshot --feature F21.filters --name two-filters).
  13. Do
    control-openhands browser click 'testid=automations-filters-reset'
  14. Note
    : both rows return, the button reads Filters and the popover stays open.
  15. Note
    Trigger Event-driven (automations-filter-trigger-event, chosen in the same open popover) alone leaves only QA_F21 Beta.
  16. Expect
    An outside click closes the popover:
  17. Do
    control-openhands browser click 'role=heading[name="Dashboard"]'
  18. Note
    then the menu count is 0 and aria-expanded is false.
  19. Do
    control-openhands browser reload
  20. Note
    to clear the filter (filters are page state): the next bullets need Alpha visible.

Turn off #

  1. Note
    In list view run
  2. Do
    control-openhands browser click '[data-testid^="automation-list-row-"] >> has-text=QA_F21 Alpha >> role=button[name="Automation actions"]'
  3. Do
    control-openhands browser click 'role=list >> has-text=Export >> role=button[name="Turn off"]'
  4. Check
    control-openhands browser count 'testid=confirmation-modal'

    (0, no confirmation),

  5. Do
    control-openhands browser reload
  6. Expect
    The page shows Inactive 2 with the tile at 0 active, and Play reports enabled false.

Edit from the dashboard #

  1. Note
    Turn off (above) left both automations in the Inactive group, as Sort needs.
  2. Note
    Open Alpha's kebab,
  3. Do
    control-openhands browser click 'role=list >> has-text=Export >> role=button[name="Edit"]'
  4. Check
    control-openhands browser value 'testid=edit-automation-name'

    (QA_F21 Alpha; edit-automation-cron is 0 0 1 1 *, edit-automation-model is Active profile),

  5. Do
    control-openhands browser fill 'testid=edit-automation-name' 'QA_F21 Zulu'
  6. Do
    control-openhands browser click 'testid=edit-automation-save'
  7. Wait
    control-openhands browser wait 'testid=edit-automation-save' --state detached
  8. Do
    control-openhands browser reload
  9. Check
    control-openhands api GET /api/automation/v1

    shows QA_F21 Zulu with the schedule unchanged.

Sort #

  1. Note
    With QA_F21 Zulu (1 run) and QA_F21 Beta (no runs) both inactive, open Filters,
  2. Do
    control-openhands browser click 'testid=automations-sort >> testid=dropdown-trigger'
  3. Do
    control-openhands browser click 'testid=automations-sort-name'
  4. Check
    control-openhands browser text '[data-testid^="automation-list-row-"] >> nth=0'
  5. Note
    Beta comes first, the badge reads Filters 1 and the popover stays open.
  6. Note
    Fill the search with zzqa-nomatch and click testid=automations-clear-filters (a click outside the popover, which closes it): the badge stays Filters 1 and Beta stays first (the sort is kept).
  7. Note
    Open Filters and the sort field again and choose automations-sort-runs; Zulu comes first.
  8. Note
    Close the popover with
  9. Do
    control-openhands browser click 'role=heading[name="Dashboard"]'

    (browser count 'testid=automations-filters-menu' is 0).

Export #

  1. Note
    Open Zulu's kebab,
  2. Do
    control-openhands browser click 'role=list >> has-text=Export >> role=button[name="Export"]'
  3. Check
    control-openhands browser downloads --last 1 --inspect
  4. Expect
    The download path ends in qa-f21-zulu.automation.json (under <run>/private/downloads/); the inspected head holds "version": 1, "kind": "automation" and a spec with name, trigger, enabled, prompt, model (null for Active profile), timeout and timezone.

Open the detail page #

  1. Note
    Kebab role=list >> has-text=Export >> role=button[name="View"],
  2. Do
    control-openhands browser click '[data-testid^="automation-list-row-"] >> has-text=QA_F21 Zulu >> role=link >> nth=0'
  3. Do
    control-openhands browser click '[data-testid^="automation-card-"] >> has-text=QA_F21 Zulu >> role=heading'

    (grid: switch first with control-openhands browser click 'testid=automations-view-toggle' and control-openhands browser click 'testid=automations-view-toggle-grid'; the later bullets use cards), and

  4. Do
    control-openhands browser focus '[data-testid^="automation-card-"] >> has-text=QA_F21 Beta'
  5. Note
    +
  6. Do
    control-openhands browser press Enter
  7. Note
    each make
  8. Check
    control-openhands browser url
  9. Note
    read /automations/<that automation's id>.
  10. Note
    Go back with
  11. Do
    control-openhands browser back

Git Sync button #

  1. Do
    control-openhands browser click 'testid=automations-git-sync'
  2. Check
    control-openhands browser url
  3. Note
    : /automations/git-sync (F24).

Needs attention, kebab Run now #

  1. Do
    control-openhands browser goto /automations

    (the Git Sync button left /automations/git-sync), import qa-f21-fail.automation.json as above, turn it on from its kebab, then

  2. Do
    control-openhands browser click '[data-testid^="automation-card-"] >> has-text=QA_F21 Fail >> role=button[name="Automation actions"]'
  3. Check
    control-openhands browser enabled 'role=list >> has-text=Export >> role=button[name="Run now"]'

    (true), click it,

  4. Wait
    control-openhands browser wait-text 'Automation dispatched'
  5. Expect
    The 1 s timeout run ends FAILED after about 3-4 minutes (check with control-openhands api GET /api/automation/v1/<id>/runs; the id is in api GET /api/automation/v1).
  6. Do
    control-openhands browser reload
  7. Check
    control-openhands browser text 'testid=overview-tile-needs-attention'

    (Needs attention 1 / Latest run failed), and

  8. Do
    control-openhands browser screenshot --feature F21.overview-tiles --name needs-attention
  9. Expect
    The Fail card shows Failed with Timed out: command timed out or was killed, a red sparkline bar and Recent success 0%; Total runs is 2.
  10. Note
    Open Filters and choose status automations-filter-status-failing (testid=automations-filter-status >> testid=dropdown-trigger, then testid=automations-filter-status-failing): only QA_F21 Fail is left.
  11. Note
    In the still-open popover click testid=automations-filters-reset (all three automations return), then close it with
  12. Do
    control-openhands browser click 'role=heading[name="Dashboard"]'
  13. Note
    : the next bullets need QA_F21 Fail and QA_F21 Beta visible.

Run now error #

  1. Note
    Arrange a stale card: with the dashboard open, run
  2. Arrange
    control-openhands api DELETE /api/automation/v1/<QA_F21 Fail id> --write
  3. Check
    control-openhands browser toasts --history --clear

    (empties the toast history), then click '[data-testid^="automation-card-"] >> has-text=QA_F21 Fail >> role=button[name="Run now"]', then

  4. Check
    control-openhands browser toasts --history
  5. Do
    control-openhands browser screenshot --feature F21.run-now-error --name error-toast
  6. Expect
    The history holds exactly one toast, Automation not found, and no Request failed with status code 404. browser errors --app-only then lists the induced 404 on /dispatch.
  7. Note
    Expected: the card then drops out without a reload:
  8. Wait
    control-openhands browser wait '[data-testid^="automation-card-"] >> has-text=QA_F21 Fail' --state detached --timeout 10000
  9. Note
    succeeds.
  10. Note
    Known failure: the wait times out and the card stays until
  11. Do
    control-openhands browser reload

    (#18062, see Gotchas).

Delete with confirmation #

  1. Note
    Open Beta's kebab and click role=list >> has-text=Export >> role=button[name="Delete"].
  2. Check
    control-openhands browser snapshot 'role=dialog[name="Delete automation"]'

    shows dialog Delete automation with Close, the heading, Delete "QA_F21 Beta"? This cannot be undone., Cancel and Delete; focus starts on Cancel (control-openhands browser eval "document.activeElement.textContent" is Cancel).

  3. Note
    Take
  4. Do
    control-openhands browser screenshot --feature F21.delete --name dialog
  5. Do
    control-openhands browser click 'role=dialog[name="Delete automation"] >> role=button[name="Cancel"]'
  6. Note
    closes it (control-openhands browser count 'role=dialog[name="Delete automation"]' is 0) with the card kept.
  7. Note
    Reopen and close it with the X (control-openhands browser click 'role=dialog[name="Delete automation"] >> role=button[name="Close"]'), then reopen and
  8. Do
    control-openhands browser press Escape
  9. Note
    : the dialog count is 0 and the card count 1 each time.
  10. Note
    Reopen, then
  11. Do
    control-openhands browser click 'role=dialog[name="Delete automation"] >> role=button[name="Delete"]'
  12. Do
    control-openhands browser reload
  13. Check
    control-openhands browser count '[data-testid^="automation-card-"] >> has-text=QA_F21 Beta'
  14. Expect
    The count is 0.
  15. Note
    Delete the remaining fixtures the same way (rows work too) until testid=automations-empty returns.

Load more #

  1. Note
    Arrange 51 inert automations (event trigger that never fires): for i in $(seq -w 1 51); do control-openhands api POST /api/automation/v1/preset/prompt --write --data "{\"name\":\"QA_F21 Page $i\",\"prompt\":\"QA paging fixture; never runs.\",\"trigger\":{\"type\":\"event\",\"source\":\"qa-f21\",\"on\":\"qa.f21.never\"}}"; done.
  2. Do
    control-openhands browser goto /automations
  3. Wait
    control-openhands browser wait 'role=button[name="Load more"]'
  4. Note
    row/card count 50,
  5. Do
    control-openhands browser click 'role=button[name="Load more"]'
  6. Wait
    control-openhands browser wait 'role=button[name="Load more"]' --state detached
  7. Note
    count 51.
  8. Expect
    The preset fixtures arrive enabled (51 active) but never fire.
  9. Note
    Keep them for Phone (it needs at least one automation) and clean up after it.

Phone #

  1. Note
    With at least one automation, run
  2. Do
    control-openhands browser viewport phone
  3. Check
    control-openhands browser bbox 'role=main'

    (insideViewport true, pageHorizontalOverflow false),

  4. Do
    control-openhands browser screenshot --feature F21.phone --name dashboard
  5. Do
    control-openhands browser click 'testid=automations-navbar-mobile >> testid=automations-navigation-templates'

    (URL /automations/templates) and back through automations-navigation-list.

  6. Note
    Open the first card's kebab and run
  7. Check
    control-openhands browser bbox 'role=list >> has-text=Export'
  8. Note
    : the menu is insideViewport true and sits above the kebab (flipped).
  9. Note
    Take
  10. Do
    control-openhands browser screenshot --feature F21.phone --name kebab
  11. Do
    control-openhands browser press Escape
  12. Do
    control-openhands browser viewport desktop
  13. Note
    Now clean up the paging fixtures with
  14. Arrange
    control-openhands api DELETE /api/automation/v1/<id> --write
  15. Note
    for each QA_F21 Page id from
  16. Check
    control-openhands api GET '/api/automation/v1?limit=100'

    (UI deletion is covered above) until total is 0.

Errors #

  1. Expect
    After each group,
  2. Check
    control-openhands browser errors --app-only

    shows no page errors; the only app errors are the induced 404s for the deleted QA_F21 Fail: /dispatch and the stale card's /runs?limit=20&offset=0.

  3. Expect
    The backend-down recipes add 502s on /api/automation/health, /sdk-version and the list while the service is stopped; clear them with browser errors --clear after restart.

Gotchas and known limits

  • Both sub-page navs are always in the DOM (one hidden by CSS). Unscoped testid=automations-navigation-templates is a strict-mode violation; scope it with testid=automations-navbar-desktop >> or testid=automations-navbar-mobile >> .
  • The pin toggle is invisible until the Automate item is hovered on a fine pointer: browser hover 'testid=sidebar-automations-link' before clicking it. The pin is stored per backend and org in localStorage (oh:pinned-home-route:<backend>:<org>); unpin before leaving the run.
  • testid=dropdown-trigger is reused by the backend selector, the Filters button and every inner dropdown. Always scope it (testid=automations-filters >> testid=dropdown-trigger >> nth=0 for the Filters button, testid=automations-filter-status >> testid=dropdown-trigger for a field).
  • The Filters popover stays open while choices change and closes only on a click outside it or on the Filters button; Escape does not close it. Open, it covers the right part of the list (in list view the first rows' Play buttons): close it with a click on role=heading[name="Dashboard"] before clicking row or card controls.
  • A card whose automation was deleted elsewhere stays on the dashboard after its Run now fails with Automation not found. Expected: the card drops out once the dispatch returns 404. Known failure: nothing refetches the list after the failed dispatch, so the card stays until a reload or another list refetch (still there 2 minutes later) (#18062).
  • The delete confirmation has no test ids; scope it as the named dialog role=dialog[name="Delete automation"]. It opens with focus on Cancel.
  • Delete closes the dialog at once and shows no success toast; confirm with a reload. Turn off on the dashboard never asks for confirmation (home cards do).
  • The kebab menu has no test ids; role=list >> has-text=Export is the reliable scope. A card's own Run now has the same accessible name as the kebab item, so scope Play to the card.
  • Imported automations are always disabled (Import automation says so), and Run now is disabled until you Turn on. Use the yearly cron 0 0 1 1 * or an event trigger so an enabled fixture never fires by itself, and turn it back off.
  • Run now uses the active LLM profile when the automation's model is Active profile. A successful run creates a conversation titled <automation name> — <UTC timestamp> that stays after the automation is deleted. Delete it separately (control-openhands conversation list, conversations family) to leave the run clean.
  • A timeout of 1 s does not fail fast: the run sat in Installing dependencies and failed with Timed out: command timed out or was killed about 3-4 minutes after dispatch. Budget for that when you need a failing run for Needs attention.
  • The Automations tile counts only loaded pages: with 51 automations it reads 50 until Load more is clicked, although the API total is 51.
  • The row health dot and label reflect the latest run only; a disabled automation with no runs reads No activity yet, not Disabled.
  • In grid view a cron automation without schedule_human shows the pill cron, while list view shows the raw expression (0 0 1 1 *).
  • The view mode persists in localStorage (openhands-automations-view), and the toggle is disabled when the org has no automations. Switch back to Grid before deleting the last automation, or the next run starts in List.
  • Filters, search and sort are page state only: a reload resets them.
  • service stop automation stops only this run's automation service; restart brings the whole stack back with the same state and the browser on the same page. Run control-openhands doctor afterwards.
  • The Automation dispatched toast lasts only a few seconds: run browser wait-text 'Automation dispatched' right after the click, not after a long --observe window.
  • browser screenshot --full-page at phone width does not capture below the fold, because the page scrolls inside the main pane; screenshot sections or use bbox.

Source paths: src/routes/automations-list.tsx, src/components/features/automations/ (automation-card.tsx, automation-list-row.tsx, kebab-menu.tsx, build-automation-menu-items.tsx, delete-confirmation-modal.tsx, automation-view-toggle.tsx, empty-state.tsx, dashboard/), src/components/features/manifest/manifest-subpage-layout.tsx, src/components/features/manifest/manifest-overview-tiles.tsx, src/manifests/automation-insights.ts, src/manifests/automation-interface.ts, src/hooks/query/use-automations.ts, src/hooks/use-pinned-home-route.ts, node_modules/@openhands/extensions/automations/interface.json.