Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions content/posts/openadapt-quickstart-break-it/index.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
---
title: "See a bad save get caught: openadapt quickstart --break-it"
title: "Watch an automation report a save that never happened"
date: 2026-08-29
lastmod: 2026-10-09
draft: false
author: "Richard Abrich"
tags: ["openadapt-flow", "quickstart", "safety", "validation", "automation"]
description: "Run openadapt quickstart --break-it to watch a fake clinic app say a note saved after its server rejected it, and see OpenAdapt read the record and stop."
description: "A few-minute demo on your own computer: a fake clinic app shows \"Encounter saved\" after its server rejected the note, and a record check catches it."
thesis: "openadapt quickstart --break-it shows, on your own computer, an app that reports a save its server rejected, and OpenAdapt reading the record and stopping."
audience: "developer"
post_type: "note"
---

Run `openadapt quickstart --break-it` and you'll watch a fake clinic app with synthetic patients report a save that its server rejected. OpenAdapt reads the record through a separate interface and finds no saved note, so it stops at the Save step and asks a person to check the record. The demo takes a few minutes and keeps its files on your computer.
Most automation decides a save worked by looking at the screen. If the app says "Encounter saved," the run is marked done. That holds until the app is wrong about its own save, for example when the server rejects the write after the page has already shown its success banner. Every check that reads the screen passes, and the record is empty.

You can watch that happen on your own computer in a few minutes. OpenAdapt comes with MockMed, a fake clinic app with synthetic patients, and a demo that replays one recorded task twice. The first time, the app behaves. The second time, its server quietly rejects the note. Both runs end on the same "Encounter saved" screen, and only one of them saved anything.

## Run it

Expand All @@ -25,6 +27,13 @@ openadapt quickstart --break-it # the clean run, then the broken one

On Python 3.13 or newer, pip installs an old release that has no `quickstart` command. If you use [uv](https://docs.astral.sh/uv/), run `uv tool install --python 3.12 openadapt` instead, then run the same commands. uv downloads Python 3.12 if you don't have it. If you installed `openadapt-flow` on its own, `openadapt-flow tutorial --break-it` runs the same loop, since `openadapt quickstart` delegates to it.

If you'd rather see the result as one page, the Flow package has a demo that runs a similar pair of runs and then opens a page with the two results side by side. In its broken run, the app shows "Encounter saved" and drops the note. The first run can take a few minutes while Playwright downloads Chromium.

```bash
pip install 'openadapt-flow[browser]'
openadapt-flow demo
```

## What the screen said and what the record held

Both runs replay one recorded task on MockMed, a fake clinic app that ships with OpenAdapt. The task opens a patient, starts a triage encounter, types a note, and clicks Save Encounter. In the second run, the server rejects the save after the app has already shown its success banner.
Expand All @@ -36,7 +45,7 @@ This is what each run produced on 2026-10-09 with openadapt 1.16.0 (synthetic te
| What the screen showed | "Encounter saved", and the note in the encounter list | The same screen |
| What the record held | 1 saved encounter | 0 matching records |
| What OpenAdapt reported | Done and checked | Stopped at Save Encounter for a person to check the record |
| Shareable receipt | Yes | No, only a local report |
| Report you can share | Yes | No, only a local report |

These lines come from the broken run's `run-broken/REPORT.md`:

Expand Down
49 changes: 18 additions & 31 deletions content/posts/openadapt-vs-api/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ lastmod: 2026-10-09
draft: false
author: "Richard Abrich"
# openadapt-vs-autohotkey, openadapt-vs-playwright, and openadapt-vs-selenium
# were merged into this post on 2026-10-09. Their old URLs land here.
# were merged into this post on 2026-10-09 and published again as standalone
# posts on 2026-10-16. This post links to them and carries no alias for them.
tags: ["comparison", "api", "playwright", "selenium", "autohotkey", "gui-automation", "integration"]
description: "Use a supported API whenever it can make the change. Record an OpenAdapt workflow only for the screen-only step, and use the API to check what it saved."
description: "The vendor's API covers most of the task, but the last step only exists on screen. Keep the API for what it reaches. Here's how to handle that step."
faq:
- q: "Should I use an API or screen automation?"
a: "Use the API whenever a supported one can make the change. Automate the screen only for the step no supported API can reach, and keep every other step on the API."
Expand All @@ -22,7 +23,9 @@ audience: "practitioner"
post_type: "essay"
---

If a supported API can make the whole change, use the API, not OpenAdapt. Use OpenAdapt for the step that's left over, the one that only happens on screen, when other people depend on it and you need proof that each save landed in the record. Most real tasks end up using both: the API for lookups and supported writes, and a recorded workflow for the last screen-only step.
The vendor's API handles lookups and most of the updates. The last step, the one that actually changes the record, only exists as a button in its Windows client. If that's your task, you can automate everything except one click, and the question is what to do about it.

Use the API for everything it can reach, and automate the screen only for that leftover step. That's a much smaller job than replacing the integration, and it's the job OpenAdapt is built for. The API can still help there. It can't make the change, but it can often read the result, which gives you a way to check that the save on screen really landed.

## How the options compare

Expand All @@ -40,49 +43,33 @@ Tool behavior in this post comes from each project's documentation, checked on 2

When a supported API exists, it's the better choice almost every time. A direct call skips the fragile parts of screen automation, such as window focus and finding the right field on a page that moved. You can test a request more easily than a click, and the service owner can redesign the screen without breaking your integration. OpenAdapt's [fit checks for a new workflow](https://openadapt.ai/qualify) start with the same rule: if a supported API exposes the operation, use it.

An API call still needs a check when the write matters. [RFC 9110](https://www.rfc-editor.org/rfc/rfc9110.html#name-status-codes) defines a 2xx status code to mean the server received and accepted the request. The server can return 200 after writing to the wrong record, and a later step in the vendor's system can still reject work that the first service accepted.
An API call still needs a check when the write matters. [RFC 9110](https://www.rfc-editor.org/rfc/rfc9110.html#name-status-codes) defines a 2xx status code to mean the server received and accepted the request. The server can return 200 after writing to the wrong record. A later step in the vendor's system can also reject work that the first service accepted.

## Move only the step the API can't reach

An API often covers most of a task and stops short of one step. A product can expose customer lookup through an API and keep the final update in its Windows client. A payer portal may have an internal API that customers aren't allowed to call. An old system may support only the desktop app its vendor ships, and Citrix can put that app behind one more boundary your team doesn't control.

Keep API calls for search, data preparation, routing, and every supported write. Automate the screen only for the operation that has no supported machine interface. Often that's the last step, where someone enters approved information into the system and checks that it saved. For example, a task with 9 API steps and 1 screen step is easier to run than a task with 10 screen steps, and a layout change can't break an API call that never opens the page.
Keep API calls for search, data preparation, routing, and every supported write. Automate the screen only for the operation that has no supported machine interface. Often that's the last step, where someone enters approved information and checks that it saved. A task with 9 API steps and 1 screen step is easier to run than one with 10 screen steps, because a layout change can't break an API call that never opens the page.

## Let a read-only API check what the screen wrote

An API that can't make the change can often still read the result. OpenAdapt keeps the two jobs apart. The browser or desktop session clicks Save, and a different interface reads the stored record. OpenAdapt's [limits page](https://github.com/OpenAdaptAI/openadapt-flow/blob/main/docs/LIMITS.md) describes record checks through a REST or FHIR API or a document store.

OpenAdapt doesn't work out which record to read from the recording itself. A person names the record and the fields to check for each workflow. Without that separate read, OpenAdapt can compare screens but can't report a run as done and checked. A success banner can sit on top of a save that the server rejected, duplicated, or only partly wrote.

A timeout after Save is the hardest case. If the app accepted the click and the reply never arrived, a second click could save the entry twice. Instead of clicking again, OpenAdapt stops and asks a person to check the record, and nothing is retried until they do. It stops even when the save went through, because nothing confirmed it. A read-only API gives that person a quick way to see what the record holds. In the engine's output, a confirmed save is `VERIFIED` and this stop is `RECONCILIATION_REQUIRED`.

## Keep a script while its owner watches it

### Playwright

OpenAdapt's [browser recorder](https://github.com/OpenAdaptAI/openadapt-flow/blob/main/docs/BROWSER_RECORDING.md) runs on a Playwright page. For each action, it keeps the target's DOM identity, such as its ID or test ID, its role, and its accessible name, along with its position on screen. When the automation runs, it compares that identity with the live page before it acts. Playwright's [locator guide](https://playwright.dev/docs/locators) prefers the same kind of identity to CSS and XPath selectors, which break when the page structure changes. The quickstart at the end of this post records and replays its task on a Playwright page.

Two more Playwright features are good reasons to keep it in the browser path. Before a click, its [actionability checks](https://playwright.dev/docs/actionability) wait until the locator matches exactly 1 element that's visible, stable, enabled, and not covered by another element. The [trace viewer](https://playwright.dev/docs/trace-viewer) lets you step through a failed run after it ends. It shows the page before and after each action, the line of code that ran, and the network requests.

If a developer owns a browser-only task and the script's assertions prove the whole result, keep the Playwright script. You still write the business rules yourself. Your code decides which record must be open before a write, what to do after a timeout that follows Submit, and what proves the write happened once. Keep the script until those rules cost more to write and maintain than the script itself.

If the task leaves the browser for a desktop app, record each part as its own OpenAdapt automation. The browser part still runs on Playwright, and the desktop part starts only with a value the browser part saved and checked. [Two apps, one checked fact](/posts/record-each-surface-then-compose/) shows how that handoff works.
OpenAdapt keeps the two jobs apart. The browser or desktop session clicks Save, and a different interface reads the stored record. OpenAdapt's [limits page](https://github.com/OpenAdaptAI/openadapt-flow/blob/main/docs/LIMITS.md) describes record checks through a REST or FHIR API or a document store.

### Selenium
OpenAdapt doesn't guess which record to read. A person names the record and the fields to check for each workflow. Without that separate read, OpenAdapt can compare screens but won't report a run as done and checked, because a success banner can sit on top of a save that the server rejected, duplicated, or only partly wrote.

Selenium WebDriver is a [W3C Recommendation](https://www.selenium.dev/documentation/webdriver/). [Selenium Grid](https://www.selenium.dev/documentation/grid/) routes its commands to browsers on other machines, so you can run the same script across browser versions and operating systems.
A timeout after Save is the hardest case. If the app accepted the click and the reply never arrived, a second click could save the entry twice. So OpenAdapt doesn't click again. It stops and asks a person to check the record, and nothing is retried until they do. It stops even when the save went through, because nothing confirmed it. A read-only API gives that person a quick way to see what the record holds.

Selenium leaves everything around the browser to your code. That's fine in a test suite, where a failed test stops with a stack trace and waits for a developer. A job that writes business records also needs a check that the right record is open before the click, a rule for timeouts, a second read that confirms the save, and a person who handles the runs that stop. If your team already built that code and keeps it running, keep the fleet.
## If you already have a script

A timeout after a click that saves something has 2 possible meanings. The server may never have received the click, or it may have saved the change before the reply was lost. A retry fixes the first case and creates a duplicate in the second. The browser error can't tell you which case you're in, so your code has to read the record before it tries again.
A script is a fine answer while the person who wrote it runs it and watches it. Each of these tools has its own post on where that stops being true.

### AutoHotkey
Playwright is the one OpenAdapt builds on. Its [browser recorder](https://github.com/OpenAdaptAI/openadapt-flow/blob/main/docs/BROWSER_RECORDING.md) runs on a Playwright page and keeps the same kind of target identity, role and accessible name, that Playwright's [locator guide](https://playwright.dev/docs/locators) recommends. Keep a Playwright script while a developer owns it and its assertions prove the whole result ([OpenAdapt vs. Playwright](/posts/openadapt-vs-playwright/)).

AutoHotkey's [Send](https://www.autohotkey.com/docs/v2/lib/Send.htm) function sends simulated keys and mouse clicks to whichever window is active, so focus decides where the input lands. A careful author has better tools. [WinWait](https://www.autohotkey.com/docs/v2/lib/WinWait.htm) pauses the script until the expected window exists. [ControlClick](https://www.autohotkey.com/docs/v2/lib/ControlClick.htm) and [ControlSend](https://www.autohotkey.com/docs/v2/lib/ControlSend.htm) target a specific control, and they throw an error when they can't find the window or control. With logging and error handling added, a script like that can run for years. Keeping it correct is work for whoever maintains it.
Selenium leaves everything around the browser to your code, which is fine in a test suite. A job that writes business records also needs a rule for the click on Save that times out, since the browser error can't tell you whether the save landed ([OpenAdapt vs. Selenium](/posts/openadapt-vs-selenium/)).

A personal macro that its author runs and watches is fine. When the wrong window has focus, the person who can fix it sees the failure.
AutoHotkey's [Send](https://www.autohotkey.com/docs/v2/lib/Send.htm) goes to whichever window is active, so focus decides where the input lands. A careful author can use [WinWait](https://www.autohotkey.com/docs/v2/lib/WinWait.htm) and [ControlSend](https://www.autohotkey.com/docs/v2/lib/ControlSend.htm) to make a macro sturdy. The question is who's watching once coworkers rely on it ([OpenAdapt vs. AutoHotkey](/posts/openadapt-vs-autohotkey/)).

The job changes when coworkers copy the macro, a scheduler runs it after hours, or it starts writing records that are costly to fix. The author may not be there when it fails, and Windows accepts the keystrokes whether or not the business result is right. A recorded workflow has a named owner who reviews each change, and every run uses that reviewed version. Where a step has a right-record check, OpenAdapt confirms that the right record is open before it types, and it stops on a mismatch.
If the task leaves the browser for a desktop app, record each part as its own automation. [Two apps, one checked fact](/posts/record-each-surface-then-compose/) shows how the second part starts only with a value the first part saved and checked.

## When to record a workflow instead

Expand All @@ -106,7 +93,7 @@ openadapt quickstart

On Python 3.13 or newer, pip installs an old release that has no `quickstart` command. If you use [uv](https://docs.astral.sh/uv/), run `uv tool install --python 3.12 openadapt` instead, then run the same command.

The quickstart replays a recorded task in MockMed, a fake clinic app with synthetic patients that ships with OpenAdapt. It saves a record through the app's screens, then confirms the saved value through a read-only API that the app itself never calls. MockMed shows what the check looks like. Your application needs its own second way in, and the quickstart can't tell you whether it has one.
The quickstart replays a recorded task in MockMed, a fake clinic app with synthetic patients that ships with OpenAdapt. It saves a record through the app's screens, then confirms the saved value through a read-only API that the app itself never calls. Your application needs its own second way in, and the quickstart can't tell you whether it has one.

To see the check catch a bad save, run `openadapt quickstart --break-it` ([what it shows](/posts/openadapt-quickstart-break-it/)). To record a task in your own web app, follow the [first-workflow guide](https://docs.openadapt.ai/get-started/first-workflow/). If you're comparing RPA suites, read the posts on [UiPath](/posts/openadapt-vs-uipath/) and [Power Automate](/posts/openadapt-vs-power-automate/).

Expand Down
Loading
Loading