What's new
0.13.13 — CLI only.
- Headless on Linux fits the viewport — on Ubuntu runners the browser
window sometimes kept its old size after a relaunch, and the run warned
could not grow to the 1280x800 viewport, so the video and live view showed only the top 1280×657. tapstep now waits for the new size to take effect. Screenshots were always full size.
0.13.12 — CLI and desktop app, released together.
CI signs in with a real token now: one you create, see once and can revoke.
Changed
Section titled “Changed”tapstep token create|list|revoke— a personaltapstep_pat_…token for CI, created through the browser once and printed once, on stdout only.TAPSTEP_TOKENis verified against the API once a day and cached; a revoked token stops the run, an unreachable API only warns. ATAPSTEP_TOKENthat is not atapstep_pat_…token still works with a warning until 0.15.- CI tokens in the desktop app — Settings → General → CI tokens lists your tokens, creates one (shown once, with Copy) and revokes it.
- A CI / GitHub Actions page — the token, a minimal workflow, reaching the app under test, and Android, VS Code and Windows runners (iOS in CI is not supported yet).
- Chromium starts on Ubuntu runners and in containers — on Linux, a
Chromium that reports no usable sandbox (Ubuntu 23.10+) or refuses to run as
root is relaunched once with
--no-sandbox, with a notice;TAPSTEP_NO_SANDBOX=1skips the first try. Headless windows are sized to the viewport instead of 800×600. runFlow:paths from the project root — every file a step names (helpers,runScript, request schemas,uploadFile,installApp) is looked up beside the including file, then from the project root.validateloads includes and reports a missing helper, a missing required param or a cycle with its line.tapstep fix-pathsrespells../paths from the root (--writesaves); an older CLI or app cannot read the rewritten paths.
Behaviour change
Section titled “Behaviour change”- An
installApp:oruploadFile:inside a helper resolves beside the helper first. A helper included from another folder no longer finds the file beside the top flow; move the file next to the helper or write its path from the project root.
0.13.11 — CLI and desktop app, released together.
API steps get a shared header: one http: block sets the base URL, default
headers and a cookie jar for every request: in a flow. Credentials never
leave a run in the clear, and a failed run no longer drops its screenshot in
whatever directory you ran it from.
Behaviour changes
Section titled “Behaviour changes”request:steps keep cookies by default. A flow now has a cookie jar: aSet-Cookieone request receives is sent back by the next, the way a browser does —Path,Secure,Expiresand foreignDomain=included, and across redirects. A flow that relied on every request arriving without cookies opts out withhttp: { cookies: false }.- Failure artifacts go under
reports/, not the current directory. A failed flow’s screenshot and network log land in the project’sreport.outputdir (reports/by default), in a folder named after the flow’s path — or inreports/next to the flow outside a project. The run prints where, and hints once if.gitignoredoes not ignore it.
Changed
Section titled “Changed”- An
http:block in the flow header orconfig.yaml:baseUrlfor relativerequesturls, defaultheaderswith${}filled per step, andcookies. A helper’s own block holds for its steps only. curl import writes urls under the base as relative;--to curljoins them back. - Credentials are masked everywhere — extracted tokens, bearer, cookie and
API-key values, DSN passwords, JWTs and credential-named JSON fields are cut
to a hint or
***in step notes, errors, live events, every report and a try’s response,sql:steps included. Keys likekey,keywordorpassengerstay plain, and a short password no longer masks/admin/users. contains:reads a sentence across inline tags — Sign in to continue split by a<b>still matches, andcopyTextcopies the same reading.- Relative paths resolve against the flow.
uploadFilelooks next to the flow file, then the project root, then the working directory, and names all of them when the file is missing.openLink: /login(or./next,?q=,#x) goes to it on the page already open. appId: ${APP}/loginpicks the web driver once-e APP=http://…fills it, instead of falling to the iOS simulator.tapstep livetakes-e,--env-file,--env-setand--configand resolves variables astapstep testdoes.- A hand-written
dragAndDropin the browser works with the page’s own HTML5 drag instead of starting an OS drag that never ends. - The recorder ignores anything marked
data-tapstep-ignore, frames inside it included. - Playwright export covers more:
request:maps onto therequestfixture, elements are found in frames too, a variable made once is oneconst,launchApp ${APP}/loginbecomespage.goto, and exporting again refreshes the spec it wrote instead of addingname-2.spec.ts. - A YAML error at an unquoted JSONPath or
${…}inside{ … }says to quote it;tapstep test -vmarks only real warnings with ⚠.
Desktop
Section titled “Desktop”- The try panel, curl in/out and the chat follow the
http:defaults — a tried request uses the open flow’s block over the project’s, a pasted curl under the project base comes in relative, and Copy as curl joins it back with the default headers.http:completes in the YAML view. - A failed run keeps its frame as a file under
~/.tapstep/fail-frames/for as long as the run stays in history, instead of base64 in the run store; reports attach that file. - A step the recorder runs on its own reads a relative
uploadFileunder the recording’s project.
- On Windows, the built-in terminal closes when its shell exits.
0.13.10 — 24 September 2026, CLI and desktop app, released together.
Every report now says which platform and which device a run drove, a machine with several phones attached lets you pick one, and a switch the page hides behind its label is finally pressed where a person would press it.
Changed
Section titled “Changed”- Reports name the platform and the device. JUnit gets
platformanddeviceproperties, the JSON report the same fields, Allure a label each plusenvironment.properties, and the HTML report says it in its header — platform ios (iPhone 16).tapstep liveshows it in its terminal line and in the view’s header. tapstep test --devicepicks one of several. With more than one Android device or iOS simulator up, name the one you want by serial, udid or name.uuid(),randomEmail()andrandomInt(min, max)are globals in every flow expression — noevalScriptneeded for a fresh sign-up.- Interactive runs update themselves. When the daily check finds a newer
release, an interactive run installs it in place for the next run instead of
only hinting. CI and non-terminal runs are left alone;
TAPSTEP_NO_AUTO_UPDATE=1opts out. retrygives up at once on a step that cannot run as written. A static refusal reads the same the second time, so the block is no longer runmaxRetries + 1times, and the reason is the first refusal, not the last.- A key combo is checked before it reaches the driver, and a name the drivers cannot send is refused in the same words everywhere.
- MCP: a host can subscribe to a run’s events, frames included — every flow the server runs, not only the ones on its own driver, each named and bounded, with its own step numbers.
- A failed step inside
repeator a helper carries the rows under it in a live run, as it always did in the report — so the repair can walk down to the command that actually failed.
Desktop
Section titled “Desktop”- Run and suite reports name the platform and device, the same way the CLI’s do.
- The mirror beside the chat shows the flow the agent is running, and lets go of it when the run ends.
- The chat says which provider ran a turn, and an OpenCode turn counts the tokens it paid to cache as well as those it read back.
- Usage statistics consent is asked again. The privacy policy (1.3) now describes the opt-in statistics, and the consent screen names every event the app sends — tests, screenshots and chat text never go.
- The recorder card speaks the window’s language for a hover, a drag, an upload, a check and a field the page named nothing.
- A recorded ⌘+arrow is a shortcut, not a key the mirror drops.
- A checkbox or switch the page draws nothing of (sr-only, off-canvas) is pressed through its label instead of at a one-pixel box or off the screen, and a click on such a label is recorded as one step on the label, not two.
- On Windows, VS Code no longer shows an EPIPE error dialog for every line of its startup.
- A Kazakh toolbar no longer pushes Run’s chevron past a 1024 px window; a long action in the triage header wraps; the chip beside a step’s argument no longer cuts the text next to it.
- Editor tooltips follow the theme, and the chat’s working clock no longer falls behind in a hidden window.
0.13.9 — 16 September 2026, CLI and desktop app, released together.
A wheel inside a VS Code webview now lands on the box that actually scrolls,
and every command says which platforms it runs on — so a VS Code test is never
offered an installApp, and one that already carries it hears about it before
it runs.
Changed
Section titled “Changed”within:is an anchor, not the thing that scrolls. Name a heading, or any element inside the scrolling area, andscrollandscrollUntilVisiblelook up the scroller holding it — inside the named box first, then above it — put the wheel there and measure how far its content moved. A heading named inwithin:used to scroll by its own 48 px while the next wheel fell on the workbench behind it, and a webview pane a few hundred pixels tall could not be scrolled through at all.scrollUntilVisibleinside a scrollable pane asks the pane first. A VS Code side view, a list, a nested frame: the pane is asked to show the element before the screen is swiped. On a plain page it still swipes screen by screen, and it never scrolls anoverflow: hiddenbox. The run note says which box it scrolled.- Commands carry their platforms.
tapstep schemanow publishesplatformsfor every command (web, android, ios, vscode), for the narrower fields (launchApp.arguments,launchApp.permissions), for thepressKeykey names (enums.keyNames) and for the selector keyscss(web, VS Code),testId(web, VS Code — a phone and a simulator carry anidinstead) andtraits(iOS). A test in the engine keeps the table never wider than what the four drivers accept. - A flow is told which of its steps cannot run where it runs. With a
vscode:header — or validated for a known platform through the CLI or the MCP server — it readsinstallApp… is not on VS Code — it runs on Android, iOS, and the check looks insidebefore/after,retry/repeatand inlinedrunFlowbodies.tapstep mcpstarted with--driver vscode, a testspace or a device lists only that platform’s commands in its cheat sheet and validates against it. backon iOS is refused instead of passing in silence. The simulator has no back gesture of its own — the step used to send nothing and go green — so the table no longer offersbackthere.- VS Code is its own platform in
when: platform:. A VS Code window now answersvscode, and{ platform: web }no longer fires there — a block that has to run in the browser and in the workbench writes{ platform: [web, vscode] }. Reports and the run log namevscodetoo. - The command reference on this site prints the coverage from the schema — VS Code is a column of its own.
Desktop
Section titled “Desktop”- The step picker offers only what the test can run. The picker, the More
menu, autocomplete and hover show the commands of the test’s platform, and
the On: web, android, ios row appears only in a multi-platform project.
Key names, the
launchApp/installAppfields and the selector conditions follow the same rule. A field already written in the file stays visible whatever the platform. - A VS Code test is edited as one. No App (appId) section in Details; a
recording made in VS Code opens as a VS Code test; project-level
Before/After in Settings are written against the project’s platform; and a
step limited with
only:can be edited even in a single-platform project. - Run: a flow with a
vscode:header goes through the VS Code dialog wherever it lives — with a link to Extensions when the project has no testspace. A VS Code suite no longer probes for Android and iOS devices, and a second Run never rewrites a testspace request into a browser. - The create menu says “Record in VS Code” in a VS Code project, in all sixteen languages.
- A double-click in the recorder is written as one
doubleTapOn, not two taps. - On Android
placeholdermatches a field’s hint andvaluethe text an editable field holds. - Two buttons of the same name in two frames of a VS Code window are counted once each, not four times.
- An iframe inside a shadow root is found, and a frame that has navigated away is no longer answered out of the cache.
- On Windows
extensions.jsonis written the way VS Code writes it, and VS Code is started throughCode.exeandcli.js— a%or a"in the path no longer breaks the launch. %TEMP%\ts-vs-*session folders are cleaned up: the delete is retried, and folders left by a dead session are swept at the next launch.- An exported ExTester or Playwright spec names a precondition command by its id, and retries an optional command before calling it absent.
- A Playwright spec exported from a VS Code flow is refused on import, with a sentence saying why, instead of arriving as half a flow.
- A spec looks for its
config.yamlwith the same walk as everything else, stopping at the home directory. dragAndDrop.pathon a phone says in the report that its path was straightened.- Launch from Extensions no longer takes a window a suite is holding, and a suite no longer closes a window you opened yourself; Launch says when it kept the window already open.
- Extensions and the Run dialog say who holds the window, and a VS Code suite shows that window by default.
- A member of a mixed project whose header is web runs in the browser.
- A build’s tick comes off only once the removal is confirmed.
- The
vscodeCommandform keeps by title or by id per step. - The Before and After zones show the testspace the next run will use, and an error in a preset is shown instead of swallowed.
- A fresh
selectOption,requestorsqlrow stays a draft until its first field is filled, so no invalid YAML is written. - A converted curl command is written where the paste sits now, or a toast says it moved.
- The API run dialog shows the hooks that run around it, and a preset error clears when the testspace changes.
- A file that is no flow and carries a backslash is no longer listed as left out.
- A new chat clears everything with it — queued messages and Send now included — and an empty reply is not taken for an answer.
- The tool budget arms again after Send now and after the verify nudge, and its field commits when it loses focus.
- A slow simulator no longer blanks the Agent view: frame timeouts follow the platform, a frame behind status is visible, and there is no silent 30 s pause.
- Send now survives a window reload, and the chat mirror shows the chat’s own run only.
- The Polish wording of the recorder hint fits the row it sits in.
0.13.8 — 15 September 2026, CLI and desktop app, released together.
The engine learned why a VS Code test passed one run and failed the next, and the chat got the controls a tester asked for after watching an agent click for two minutes without a word.
Changed
Section titled “Changed”before: cleanis six commands, not five. Clicking an activity-bar icon in VS Code is a toggle: if its view is already open, the click hides the side bar — which is why a recorded flow passed every other run. The preset now closes the primary side bar too (workbench.action.closeSidebar), and a tap on the icon of a view that is already showing is skipped with a note instead of sent.- A step recorded against three matches waits for the third to draw. A
tree inside a webview renders in bursts;
index: 1 of: 3used to fire on the second row. The run now waits for the count it was recorded with (or for it to stop changing) before acting. - A click on an element the window does not draw is refused, not sent blind. Inside a webview, scroll-into-view now runs in the frame’s own world; if the element still cannot be brought on screen the step fails with offscreen, could not scroll into view instead of clicking whatever was painted there.
- Settling watches webviews. Network and DOM quiet used to be measured on the workbench only; a webview still drawing now keeps the screen unsettled, and a webview that just opened counts as loading.
- Two palette titles follow VS Code 1.137: View: Hide Panel and View: Hide Secondary Side Bar. Exported Playwright and ExTester specs use them, and a preset command the palette does not offer (its precondition is false) is skipped with a warning instead of failing the spec.
- Empty
vscodeCommandis a validation error, with the file position, instead of a timeout inside the window. scrollandscrollUntilVisiblesay what they found and how far they went; giving up reads not visible after N screens.
Desktop
Section titled “Desktop”- Chat: a message typed while Claude Code works says it will be read when the turn ends — the old label promised mid-turn delivery the CLI does not do. Send now on that bubble stops the turn and continues the same session with your message; for the other CLI providers it stops and starts a new turn. The app’s own agent loop still delivers mid-turn.
- Chat: an agent answers before it acts. A turn opened by something you typed into the last one starts with a paragraph — what was done, what was found, what is next — before any tool. A tool budget per turn (Settings → AI, 15 by default) stops a runaway loop with a report and a Continue; the status line counts it.
- Run details for the agent lead with the verdict — the failing step, its error and screenshot first, the network log filtered to what went wrong.
- Extensions is its testspaces. Builds are linked, ticked and dropped inside the testspace dialog; the Builds section and the sidebar list are gone, and the rail button opens the screen in one click.
- Before/After zones: the Other command… row now opens where it should
and never writes an empty step; a file that already holds an empty
vscodeCommandkeeps it, with the diagnostic on it. - A test that differs from its last version is marked with a blue dot distinct from the run-status dot.
0.13.7 — 15 September 2026, CLI and desktop app, released together.
Eight fixes from a day of testing a real VS Code extension on Windows, on top of 0.13.6.
Changed
Section titled “Changed”- The recorder no longer anchors a click on a VS Code tree row by its id.
VS Code mints ids like
vscode-tree-durbuqt9uek-…per window, so a flow recorded that way failed on its second run. Such ids — and Angular Material’smat-input-0kind — are now treated as unstable, and the step is recorded by role and name (role: treeitem, name: "Platform"). Re-record a step that carries one of those ids, or promote the alternative the step already lists. - An extension’s display name is read through
package.nls.json, so a build that localises itsdisplayNameshows Low Code IDE, not%Low Code IDE%. A key with no translation file shows the key text.
Desktop
Section titled “Desktop”- A step named by a long Windows path stays inside its row. One recorded path without spaces used to stretch the whole Steps view sideways and leave the rows looking empty; the same in the lists a run draws.
- Dialogs no longer let a long line push their buttons off-screen — the VS Code run dialog’s Run and Cancel were half hidden by the setup line.
- Before and After zones: a
+ Commandmenu replaces the row of chips that read as “these will run”. Each entry says what the command does, with its id; Start clean adds the preset; Other command… opens a row for any id or palette title. - Inherited setup is explained where it sits: “5 steps inherited from testspace X” with Don’t inherit for this test beside it, and when it is off, one line saying so with Inherit again. The Inherit checkbox is gone.
- A test that differs from its last version is marked by a dot, the one the tab strip uses, instead of italics; the status bar says “changes not in a version” rather than “not saved” — the editor saves on its own.
- Extensions in the left rail opens its screen in one click, the way Settings does; the Manage… button is gone.
- A testspace is made in one dialog from an empty project: the build is linked inside it, no “link a build first” step.
0.13.6 — 15 September 2026, CLI and desktop app, released together.
A fix-first release for teams that test VS Code extensions: before: clean
no longer hangs on Kill All Terminals, the run dialog says what runs around a
test, suites work for VS Code projects, and the Tests panel behaves on Windows.
Changed
Section titled “Changed”vscodeCommand: workbench.action.terminal.killAllfinishes. VS Code’s own command waits for each terminal’s exit event, and a terminal whose process had already finished — a task’s Terminal will be reused, a shell that failed to start — never fires it again: the step sat for 60 s and setup failed at B5. The helper extension now closes terminals through the extension API and answers when the list is empty.- The testspace picked in the run dialog is the one whose
before:/after:run. A flow with novscode: { testspace: … }in its header inherited none of the testspace’s hooks, silently; a flow naming testspace X run against Y ran X’s hooks in Y’s window. The dialog’s choice now replaces the header before the hooks fold. - Project-level
before:/after:withrunFlow:includes load on Windows. The config lookup returned a\\?\-prefixed path, and an include with a forward slash under it did not open. Path lookups no longer resolve symlinks either: the project root is found from the path you typed.
Desktop
Section titled “Desktop”- The run dialog shows what runs before and after the test in the testspace it points at — the sources (project, testspace, the flow itself), the counts, a preset by its name, every row on hover, an Edit link — and names a config it cannot read instead of showing nothing.
- The Before and After zones of a VS Code test offer the five clean-preset
commands as buttons and Start clean as one click.
vscodeCommandhas a by id / by title switch,argsonly with an id, and a hint when an id looks like a palette title. - The bottom panel keeps setup rows apart from steps: B1 is no longer overwritten by step 1, and network traffic is scoped per block.
- Suites for VS Code projects. Tag-assembled suites show for
kind: vscode; a suite run picks a testspace — each test’s own header, or one for all — and shares one window when a testspace is named. - The Tests panel on Windows shows real folders. Paths are normalised at the
scan, so a new folder keeps its name and survives a restart, and Move to…
in the row menu moves tests into it — dragging used to bury the file in
flows/<folder>/flows/without a word. Run history recorded before this version is respelled once at start. - The runner’s VS Code profile sets
terminal.integrated.confirmOnKill: neverandterminal.integrated.enablePersistentSessions: false.
0.13.5 — 14 September 2026, CLI and desktop app, released together.
The recording window was rebuilt around one idea: it is a log of what you did,
not an editor. The interface speaks sixteen languages. The mark and the colours
are new. Read the Changed section before updating a team: one behaviour of
index: changed on purpose.
Changed
Section titled “Changed”index:andof:count elements, not tree nodes. A control that carries its own name and wraps a word reading the same thing —<button aria-label="Save"><span>Save</span></button>, what every design system writes — is one element everywhere: on a snapshot line (x2, indexes 0 and 1), intapOn { text: "Save", index: 1 }, in a recorded click. A flow recorded before 0.13.5 against such a page may carry the old number and now reach the wrong element or none: re-record the step, or lower the index by one per folded word ahead of it. The recordedof:still catches it — the run says N matched when this was recorded, M match now.css:steps count the same way.- The legal documents are at version 1.2. The party named in the EULA, the Terms and the Privacy Policy changed; the app asks you to accept them again on the next start. Nothing else in them moved.
Recording
Section titled “Recording”- One header row.
● REC 0:41 · 7 steps, then Check, Save and ⋯. Re-read, the card switch, Check the page address and Discard recording… live in the ⋯ menu. The hint under the header is one line and leaves with the first step; after fifteen seconds without one it asks whether the page blocks recording. - Steps read as sentences. Click button “Pay”, Type “demo@acme.test” into “Email”, Type into “Password” with a 🔒, Check “Paid” is shown. The selector is still in the file; the window shows the name a person would use. One ⚠ marks a step worth a second look — several elements answer to the name, the element was not found again once the page settled, or it is addressed by position — and says why on hover. 🗑 and ⋯ appear on hover; Details, Other ways to find it, Make it a variable and Add a check after this step are in the ⋯.
- Undo where you deleted. A deleted step leaves a ghost in its place — Step 7 deleted · Undo — for ten seconds or until the next step lands; ⌘Z does the same. Skipping a step from the card goes the same way.
- Check is one action. Press Check, click what should be on the screen: the check is recorded and the page never sees the click, in a browser and in VS Code alike. ⌥-click (⌃⌥ in VS Code, Ctrl+Shift on Windows and Linux) still does it without the button.
- Save asks for the name after you press it. The header turns into an empty Name this test field; Enter writes the file, Esc returns to the running recording. Nothing is prefilled from the address any more, and the name keeps the case you typed. Save & run is gone: the main window opens the saved test and offers Run in a toast.
- The card beside the element is small. Step 7 · Click button “Pay”, one
line of actions, a bar that drains while it stands. It asks only when the
engine is unsure — 6 buttons say “Pay” — the step takes the 2nd — and the
answers are things you can see: the one in row “#1042”, the 2nd “Pay”.
Two ways of naming that read the same are one answer. Typed text gets two
answers, Keep it as text and Make it a variable; a password field gets
none — it went to
.env, the card says under which name. The element the card is about is outlined by a window of its own. Hover or ⋯ opens the full card of 0.13.4. - The repair screen (Show it again from here) wears the same surface as the recording window.
Desktop
Section titled “Desktop”- Sixteen interface languages. Spanish, Ukrainian, Polish, Kazakh, German, Turkish, French, Italian, Japanese, Simplified Chinese, Korean, Vietnamese and Indonesian join English, Russian and Brazilian Portuguese. System reads the language off the machine, not off the web view.
- A new mark and new colours. Ink and lime, an app icon drawn from the same master, the same palette on tapstep.dev and docs.tapstep.dev.
- Details of a test as a side panel beside Recordings and Versions, with
sections for tags, app and environment;
.mdfiles open with a preview; Suites fold to one line; the highlighted test follows the open tab; a file in the Project group opens the file. - Chat fixes. A refused CLI turn is reported once; a provider error opens the provider chooser; a narrow column keeps its header buttons; Open test on a run shows the steps; a text tab no longer loses input typed during a save; a turn cut short by an API error ends honestly.
- The version button is in the header of any project; clicking a rail item closes a full-screen view standing over it; a report’s screenshot no longer waits on a Screen Recording permission.
CLI, MCP and skill
Section titled “CLI, MCP and skill”- MCP reaches VS Code.
tapstep mcp --driver vscode --testspace <name>drives the project’s VS Code the way it drives a device: a webview panel is a named region of the overview, the server says which frames it skipped, a name inside a frame resolves, and a button that exists in two webviews is counted once per frame — never twice per frame. run_flowpicks a flow by its title as well as its path.
0.13.4 — 11 September 2026, CLI and desktop app, released together.
Update every machine in the team, and this time the older build says so. A flow
recorded here can carry or:, of: and scroll … within: — keys an engine
before 0.13.4 has never seen — and an unknown key is named and refused rather
than read past, so such a flow stops on an old build instead of quietly running
a weaker version of itself.
Changed
Section titled “Changed”- A step that presses something means the control, not the caption. A bare
name on
tapOn,doubleTapOn,longPressOn,hover,check/uncheck,selectOption,uploadFile,dragAndDroporscrollUntilVisibleresolves to the elements that can be pressed, and no longer to a caption reading the same words — so a flow that leaned on tapping a caption by name now taps the control beside it. Where no control answers to the name the word is acted on as before and the row notes matched only text, nothing to press; anindex:switches the rule off; and steps that read a word (copyText) or prove one is on screen (assertVisible,waitForVisible,assertNotVisible, awhen:gate) match every element as they always did. A control wearing its name on an element it wraps is still the control. Details: Selectors. - A helper is called shared steps. Nothing a person reads in the desktop app
calls a
*.helper.yamlfile a helper any more — the menus, the rows, the toasts and the README a new project is given all say Shared steps. The file extension and the engine’s ownhelperare unchanged. - The tests panel’s «View as files» switch is gone. One panel, one model: tests and shared steps in the folders they live in. A machine that had the switch on keeps showing file names — once, as Settings → Appearance → Tests panel → Names, a preference over that one list rather than a second view.
Recording
Section titled “Recording”- A run waits for the page before it acts. A screen has settled when four
signals say so — loaded, nothing in flight, the DOM still, the address still
— and an absence is only believed on a settled screen. A slow screen reads
slow rather than failing as unsettled, and a step’s own
timeout:raises the budget. Details: How a run knows the page settled. - The recorder looks at the screen again after each step. A recorded
selector carries
of:— how many elements answered it when the step was written — and the run says so when the page stops answering with that number. The card adds what the second look found and repeats nothing the step already said. - Text is read twice. When nothing on screen answers the text as written, the screen is read once more with case folded and runs of whitespace collapsed, and the step carries the note matched ignoring case/spacing. Steps that look for an absence read the text as written only.
- A scroll becomes a step only when it was one. Scroll to reach something
and click it, and nothing is written — the run scrolls there itself. Scroll
and stop, and the window offers
scroll; a scroll inside a box that scrolls on its own names that box, and the step carrieswithin:. - A selector may name alternatives.
or:gives it up to three fallbacks, read only on a settled page and only when the primary matches nothing. A hit passes the step and marks the row drifted in every report, andtapstep test --strict-selectorsfails a run that leaned on one. The steps list shows the fallback names and triage offers to put one first. Details: Selectors. - The recorder hears more than clicks. A rest that opened a menu is
hover, a native list isselectOption, a file isuploadFile— named by css where a styled uploader hides the input — a drag isdragAndDropcarrying the way the pointer actually went, andpressKeynow takes the four arrows. - A link that opens a new tab is said, not written. The recording window notes it once the click that opened it lands; the second tab is the browser’s own, outside the page this flow drives.
- A button with no word on it is recorded as a button. The recorder asks
the page what answered the click — an
onclick, a React or Vue handler, a registered listener, the pointer cursor — then names the step by its label, the caption in its row or the picture it draws, withindex:where two rows carry the same icon. - VS Code webview panels record deeper. A frame two frames down, a
cross-origin frame and a sidebar webview-view are watched, re-entered after a
navigation and dropped when their target goes;
TAPSTEP_RECORD_LOG— any value but0— makes the recorder say why it did not enter one. - A frame that shows up while the recorder is still opening one is not lost. A frame’s watch is armed once, after the recorder is already listening, and a watch the browser refused to arm is said in the window instead of the recording going quiet on that frame.
- Snapshot v2 for agents. One grammar, one renderer, one parser: a
refstays with its element across actions and a gone ref says so,detail:andaround:decide how much is printed, the first lines name the page and whether it settled, and anactanswers with anet:line for the requests it set off. Details: Snapshot notation. - An agent tries one step instead of reading the screen twice.
tryruns a single flow step on the live screen and answers with what it did, what changed, and — when the name reaches more than one element — the candidates to pick from;snapshot { detail: "regions" }is the one overview it starts from. Details: How an agent drives a screen now.
Steps and flows
Section titled “Steps and flows”- Try one step before the flow runs. A
requestorsqlstep runs on its own from the step — status, timing, headers and body, or the rows in a table — and a click on the response writes oneassert:orextract:key into that one step. Details: Try a step. tapstep sql --ping "${DATABASE_URL}"checks a connection string before a flow leans on it, and names the stage that failed: DNS, connect, auth. It takes the environment from the config it was given; in the desktop it is the Check beside asql:step’sdb, which is itself picked from the variables so the connection string never reaches the YAML.- A
sql:step’s rows keep the SELECT column order, so the first row reads the way the query was written. - A failed
request:says the status and the body it came back with on the run row, instead of the error alone. - A failed
sql:step says the statement, its parameters and the database’s own words — on the saved report’s row, and now on the live run’s row too. Secrets in the parameters and in the cells are masked, a wide result keeps 32 columns and the evidence is cut to 64 KB with a note saying how much there was, and a step that failed insiderepeat,retry,runFlowor a hook carries it where it happened. - A suite of API-only flows runs where there is no device. When every flow
in the folder says
device: nonethe run stops looking for a browser or an emulator, and inside a mixed suite adevice: noneflow takes the null driver on its own.assertFileneeds a workspace, sodevice: nonenow flags it instead of offering it. css:names a set, the keys beside it narrow it. Every other key the selector carries —text,name,role,label,placeholder, a trait, a relation, a state — now filters what the CSS matched exactly as it would with nocssat all, andindex:/of:count the survivors. So{ css: ".monaco-editor", text: "hello" }is the editor showing hello, and a set nothing survives is not found, with the failure naming both keys. Details: Selectors.
Exports
Section titled “Exports”- A Playwright export carries its own config. A folder with a VS Code spec
in it gets a one-worker
playwright.config.ts, and an Electron spec takes the two temp directories it makes with it. - The folder’s README follows the folder — refreshed when its text has moved on, including one an older release wrote, and never left standing beside no specs.
- A missing testspace is reported as structure, not as a wire error. The
desktop names the testspace the flow’s
vscode:header asks for and offers Open Extensions, rather than printing the failure into the terminal. - A spec header names its flow from the project root the caller gave, and the desktop resolves it through the same function the CLI runs — so a header written by one is read by the other, and a header naming a flow that is gone is not reported as another test’s.
- A re-export never takes unsaved keystrokes out of the spec tab, and the confirm names the flow whose spec a same-named test would replace.
- A Claude Code that was never signed in says so before the turn starts. The header names it, and a CLI that answers run /login ends the turn with that sentence instead of six lines of stderr.
- A turn that hears nothing for 90 seconds fails rather than counting up, every stage before the AI CLI speaks says its name in the chat and the log, and a turn that panics still answers.
- A message typed into a running turn says whether the agent read it or is waiting for the turn to end.
- One usage line per turn, opened to the calls behind the folded lines.
- An import takes a spec —
.spec.ts,.spec.js,.test.ts,.test.jsor a folder of them — the card promises only what parses, and the verifying line names the flow the import wrote, not the spec it read. - OpenCode is found where
XDG_CONFIG_HOMEsays it lives, and its bridge closes with the app that opened it. - The chat drives a screen the way an agent does — one overview, then one
step at a time, with a
tryreceipt read as screen text and never as instructions — and every provider’s prompt, and the empty chat, say so.
Desktop
Section titled “Desktop”- The run panel’s verdict is the sentence the engine wrote, with the wire taken off.
- Repair names the testspace it will replay in, takes the one you pick over the flow header’s, writes that testspace back into the header, and the tab names Option on a Mac.
- Report a bug captures the main window and says why when it cannot; a report and a bug issue no longer write over each other’s archive.
- A pasted curl offer outlives the typing in it — the first keystroke after
a paste is an edit of it, not an answer — and a
request:step reads as a call rather than a shape the editor never learnt. - The recorder card stands with the step number already on it, a live
selector hint is one element rather than one label, and the picker, the
hotspot and the hints spell
$,#andx6the way the snapshot does. - The role picker offers the engine’s own vocabulary, from the first moment rather than once the schema has arrived, and the recorder names a control’s role from that same published list.
- A test, a folder or a shared-steps file may be named in any alphabet. A Russian name becomes a Russian file name instead of silently doing nothing, and a name no file system can hold — one already taken, or one of the names Windows keeps for its devices — says so.
- Delete moves to the Trash and stays undoable for ten seconds. A test, a folder or a shared-steps file goes to the system Trash rather than away, and the toast offers Undo while it is still there.
- The tests panel gives every project file an address. A row is a name, one
dot and words for what it is: shared steps say how many tests use them, a
file that is gone says missing, and Copy path is on the row. A group
can be made in any folder the panel shows rather than only under
flows/, and nothing a person reads calls shared steps a helper any more. - The tests panel has settings of its own, under Settings → Appearance → Tests panel: whether a row reads as a name or as a file name, whether shared steps stand in their folders or in one group at the end, what order tests come in — name, last run, or most recently written — and whether the run dot, the git mark and the Project group are drawn at all. A test running right now still pulses with Run status switched off.
- The panel is a tree the keyboard walks. Arrows step through it and open
folders,
Home/Endgo to the ends, typed letters jump,F2renames,Enteropens and⌘Enterruns.Shift- and⌘-click pick a range or one more, and what is picked is deleted, exported or dragged onto a folder in one go — a drag of several rows carries a ghost saying how many. - A step’s selector says
role, and can say what else has to be true. The control at the top of the form offerstext,#id,regex,⌖ pointandrolewith the element’s name beside it, and + Condition adds any other selector key the engine publishes, ranked the way the engine ranks locators. - The screen picker says it needs a recording and offers Record… beside itself, instead of standing there as a button that does nothing.
- Open test on a run whose file is gone says so — which path went, and Show the folder to look where it was — and a test that was renamed or moved takes its run history with it instead of leaving it on the old path.
- The line where Show it again would be says why for a setup failure too, and says what it is still waiting for rather than offering a dead Start.
- A branch name follows git’s own rule for what a ref may be called, rather than the rule for file names.
CLI and skill
Section titled “CLI and skill”tapstep schemapublishes the roles the engine knows under the selectorrolekey, so the reference and the agents read one list instead of a hand list that can fall behind.tapstep skill installwrites for every detected agent, OpenCode included —~/.config/opencode/skills, or whereverXDG_CONFIG_HOMEpoints — and both READMEs name every agent. Details: CLI workspaces.tapstep test --strict-selectorsfails a run that leaned on an alternative selector.TAPSTEP_RECORD_LOGmakes the recorder name the frames it did not enter, and why.TAPSTEP_NO_DEVICES— any value but0— keeps driver detection from consulting adb or simctl at all, so a run that needs no device is unaffected by whatever is plugged into the box;doctorand a driver you name outright still read the box as it is.
- A request whose end overtakes its start is no longer left in flight.
- A scrollbar grab whose release went missing stops owning every scroll that follows.
- An overflow box only hides what is laid out inside it, so a modal or a portal is not called out of sight.
- A
cssselector is counted once over the whole page, frames included. swipetakes a direction or two points, and the parser refuses both.- On Linux and Windows a
bin/codecounts as a VS Code install only with the app beside it. - A spec the filesystem spells in capitals is still a spec, and a spec path that starts with a dash still opens in VS Code.
- A step the YAML writer cannot spell no longer takes the recorder down, and a failed step finds its command through the blocks it ran inside.
- A chat reopened from disk stops promising a turn that already ran, and a chat width saved on a wider screen is held to the one it opens on.
0.13.3 — CLI and desktop app, released together.
Update every machine in the team — older builds silently skip before/after.
A flow that carries hooks is valid YAML to an older engine, which ignores the
two keys and runs the numbered steps without the setup or the teardown: green,
and wrong. The same goes for vscodeCommand, environments: and the
exports: setting — a machine that has not updated reads past them.
Setup and teardown
Section titled “Setup and teardown”A flow can now say what runs around its steps:
before: - launchApp: https://staging.example.comcommands: - tapOn: "Log in" - assertVisible: "Welcome"after: - takeScreenshot: endbefore: runs ahead of step 1, and a failure there fails the flow with its own
verdict (setup failed at B2: …) without running the steps. after: runs
after the last step always — after a failure, after a stop — and every row
of it runs even when one of them fails; a teardown failure is reported beside
the verdict and does not change it. Reports and the desktop’s run view show the
two as Setup and Teardown blocks.
config.yaml may carry project-wide before: / after:, and a VS Code
testspace its own; they wrap the flow’s, outermost first in and last out.
inheritHooks: false in a flow drops what it inherited. onFlowStart: /
onFlowComplete: are the older names of the two keys and still work.
Details: Flow file & selectors.
VS Code: run a command, start clean
Section titled “VS Code: run a command, start clean”vscodeCommand runs a VS Code
command outright — by id through the helper extension the driver bundles
(which answers when the command actually resolves), or by the title the
palette shows. args go with id; the palette takes none, and asking for both
is an error rather than a silent drop.
before: clean — a string instead of a list — puts a VS Code window back to a
known state before the steps: close all editors, close the panel, close the
auxiliary bar, clear the notifications, kill the terminals. In the desktop app
it is a testspace’s Start clean switch.
Clicks inside an extension’s own panel (vscode-webview://) are recorded now,
including panels opened during the recording. A VS Code run can also be shown a
step again from triage, in the testspace it ran in.
Exports: Playwright and ExTester
Section titled “Exports: Playwright and ExTester”tapstep export playwright now carries the resolved hooks into the spec’s
test.beforeEach / test.afterEach, and a vscode: flow leaves as a
standalone Electron spec that launches the installed VS Code.
tapstep export extester is new: a vscode: flow as a
vscode-extension-tester
test — Mocha and chai over Selenium — with a README carrying the extest
commands, the --code_version this machine reports and the workspace the
runner must open. The MCP tool is export_extester, and the desktop offers the
kind only where the test has a vscode: header.
Where an export lands is a project setting now, read by the CLI, the MCP tools and the desktop alike:
exports: playwright: playwright extester: ../qa-suite/src/ui-testA relative path travels with the project; an absolute one is yours alone, which
is how a QA suite in its own repository works. --out still wins.
Environments
Section titled “Environments”A project can name its targets and keep a set of values per target:
defaultEnvironment: stagingenvironments: staging: { BASE_URL: https://staging.example.com } prod: { BASE_URL: https://example.com }tapstep test flows/ --env-set prod picks one for the whole run, and without
the flag defaultEnvironment does. .env.<name> is loaded with it, so an
environment’s local secrets travel with it and never reach git; the chosen name
is ${TAPSTEP_ENV}.
The layer order changed. A local file now beats a committed one at every locality:
params: defaults < flow env: < config env: < environments.<current> <
.env < .env.<current> < --env-file < -e
Before 0.13.3 .env sat lower; if you were relying on a committed value
overriding a local one, that is the case to re-check. tapstep validate warns
about a ${VAR} an environment leaves open and names which, every report says
which environment the run worked against, and the desktop grows an environment
switcher in the title bar plus an Environments matrix that writes plain
config.yaml and dotenv files.
Details: config.yaml and Desktop → Environments.
The recorder reads controls better
Section titled “The recorder reads controls better”A click on a switch or a checkbox is written as check / uncheck rather than
tapOn, from the state the press found — replayed blind, a tap flips
whatever it lands on. A control nothing names is addressed by the caption
beside it, as a relational selector ({ role: switch, leftOf: "Published" }),
with index: where one caption reaches several controls.
The engine reads those relations more strictly to match:
above/below/leftOf/rightOf now ask for the anchor’s own column or row,
and order matches nearest the anchor first — so a caption names the control
beside it, not everything further along the page. A screen with nothing in the
anchor’s line falls back to the older, looser rule, so existing flows keep
resolving.
The desktop app
Section titled “The desktop app”- English and Russian, following the system language until you pick one in Settings → General. No restart.
- Settings → Appearance — theme, interface scale (
⌘=/⌘−/⌘0and the View menu), editor font, what the editors draw, density and reduce motion, with one Reset. - The AI chat is one stream — steps inline where they happened, one Details toggle, and a status line naming the phase: Thinking, Working, Verifying. Every turn ends with a done. OpenCode joins the providers.
- Report a bug in the title bar: a signed issue with severity, a screenshot and masked logs, and your own issues listed with their status.
- The steps editor is three zones — Before, Steps, After — with inherited rows marked by where they came from.
Details: Desktop.