Skip to content

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.

  • tapstep token create|list|revoke — a personal tapstep_pat_… token for CI, created through the browser once and printed once, on stdout only. TAPSTEP_TOKEN is verified against the API once a day and cached; a revoked token stops the run, an unreachable API only warns. A TAPSTEP_TOKEN that is not a tapstep_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=1 skips 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. validate loads includes and reports a missing helper, a missing required param or a cycle with its line. tapstep fix-paths respells ../ paths from the root (--write saves); an older CLI or app cannot read the rewritten paths.
  • An installApp: or uploadFile: 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.

  • request: steps keep cookies by default. A flow now has a cookie jar: a Set-Cookie one request receives is sent back by the next, the way a browser does — Path, Secure, Expires and foreign Domain= included, and across redirects. A flow that relied on every request arriving without cookies opts out with http: { cookies: false }.
  • Failure artifacts go under reports/, not the current directory. A failed flow’s screenshot and network log land in the project’s report.output dir (reports/ by default), in a folder named after the flow’s path — or in reports/ next to the flow outside a project. The run prints where, and hints once if .gitignore does not ignore it.
  • An http: block in the flow header or config.yaml: baseUrl for relative request urls, default headers with ${} filled per step, and cookies. A helper’s own block holds for its steps only. curl import writes urls under the base as relative; --to curl joins 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 like key, keyword or passenger stay 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, and copyText copies the same reading.
  • Relative paths resolve against the flow. uploadFile looks 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}/login picks the web driver once -e APP=http://… fills it, instead of falling to the iOS simulator. tapstep live takes -e, --env-file, --env-set and --config and resolves variables as tapstep test does.
  • A hand-written dragAndDrop in 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 the request fixture, elements are found in frames too, a variable made once is one const, launchApp ${APP}/login becomes page.goto, and exporting again refreshes the spec it wrote instead of adding name-2.spec.ts.
  • A YAML error at an unquoted JSONPath or ${…} inside { … } says to quote it; tapstep test -v marks only real warnings with ⚠.
  • 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 uploadFile under 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.

  • Reports name the platform and the device. JUnit gets platform and device properties, the JSON report the same fields, Allure a label each plus environment.properties, and the HTML report says it in its header — platform ios (iPhone 16). tapstep live shows it in its terminal line and in the view’s header.
  • tapstep test --device picks 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() and randomInt(min, max) are globals in every flow expression — no evalScript needed 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=1 opts out.
  • retry gives 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 run maxRetries + 1 times, 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 repeat or 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.
  • 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.

  • within: is an anchor, not the thing that scrolls. Name a heading, or any element inside the scrolling area, and scroll and scrollUntilVisible look 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 in within: 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.
  • scrollUntilVisible inside 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 an overflow: hidden box. The run note says which box it scrolled.
  • Commands carry their platforms. tapstep schema now publishes platforms for every command (web, android, ios, vscode), for the narrower fields (launchApp.arguments, launchApp.permissions), for the pressKey key names (enums.keyNames) and for the selector keys css (web, VS Code), testId (web, VS Code — a phone and a simulator carry an id instead) and traits (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 reads installApp … is not on VS Code — it runs on Android, iOS, and the check looks inside before/after, retry/repeat and inlined runFlow bodies. tapstep mcp started with --driver vscode, a testspace or a device lists only that platform’s commands in its cheat sheet and validates against it.
  • back on 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 offers back there.
  • VS Code is its own platform in when: platform:. A VS Code window now answers vscode, 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 name vscode too.
  • The command reference on this site prints the coverage from the schema — VS Code is a column of its own.
  • 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/installApp fields 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 placeholder matches a field’s hint and value the 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.json is written the way VS Code writes it, and VS Code is started through Code.exe and cli.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.yaml with the same walk as everything else, stopping at the home directory.
  • dragAndDrop.path on 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 vscodeCommand form 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, request or sql row 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.

  • before: clean is 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: 3 used 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 vscodeCommand is a validation error, with the file position, instead of a timeout inside the window.
  • scroll and scrollUntilVisible say what they found and how far they went; giving up reads not visible after N screens.
  • 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 vscodeCommand keeps 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.

  • 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’s mat-input-0 kind — 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 its displayName shows Low Code IDE, not %Low Code IDE%. A key with no translation file shows the key text.
  • 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 + Command menu 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.

  • vscodeCommand: workbench.action.terminal.killAll finishes. 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 no vscode: { 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: with runFlow: 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.
  • 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. vscodeCommand has a by id / by title switch, args only 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: never and terminal.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.

  • index: and of: 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), in tapOn { 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 recorded of: 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.
  • 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.
  • 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; .md files 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.
  • 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_flow picks 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.

  • A step that presses something means the control, not the caption. A bare name on tapOn, doubleTapOn, longPressOn, hover, check/uncheck, selectOption, uploadFile, dragAndDrop or scrollUntilVisible resolves 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; an index: switches the rule off; and steps that read a word (copyText) or prove one is on screen (assertVisible, waitForVisible, assertNotVisible, a when: 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.yaml file 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 own helper are 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.
  • 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 carries within:.
  • 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, and tapstep test --strict-selectors fails 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 is selectOption, a file is uploadFile — named by css where a styled uploader hides the input — a drag is dragAndDrop carrying the way the pointer actually went, and pressKey now 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, with index: 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 but 0 — 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 ref stays with its element across actions and a gone ref says so, detail: and around: decide how much is printed, the first lines name the page and whether it settled, and an act answers with a net: line for the requests it set off. Details: Snapshot notation.
  • An agent tries one step instead of reading the screen twice. try runs 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.
  • Try one step before the flow runs. A request or sql step 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 one assert: or extract: 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 a sql: step’s db, 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 inside repeat, retry, runFlow or 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: none the run stops looking for a browser or an emulator, and inside a mixed suite a device: none flow takes the null driver on its own. assertFile needs a workspace, so device: none now 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 no css at all, and index:/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.
  • 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.js or 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_HOME says 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 try receipt read as screen text and never as instructions — and every provider’s prompt, and the empty chat, say so.
  • 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 $, # and x6 the 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 / End go to the ends, typed letters jump, F2 renames, Enter opens and ⌘Enter runs. 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 offers text, #id, regex, ⌖ point and role with 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.
  • tapstep schema publishes the roles the engine knows under the selector role key, so the reference and the agents read one list instead of a hand list that can fall behind.
  • tapstep skill install writes for every detected agent, OpenCode included — ~/.config/opencode/skills, or wherever XDG_CONFIG_HOME points — and both READMEs name every agent. Details: CLI workspaces.
  • tapstep test --strict-selectors fails a run that leaned on an alternative selector.
  • TAPSTEP_RECORD_LOG makes the recorder name the frames it did not enter, and why.
  • TAPSTEP_NO_DEVICES — any value but 0 — 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; doctor and 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 css selector is counted once over the whole page, frames included.
  • swipe takes a direction or two points, and the parser refuses both.
  • On Linux and Windows a bin/code counts 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.

A flow can now say what runs around its steps:

before:
- launchApp: https://staging.example.com
commands:
- tapOn: "Log in"
- assertVisible: "Welcome"
after:
- takeScreenshot: end

before: 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.

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.

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-test

A 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.

A project can name its targets and keep a set of values per target:

defaultEnvironment: staging
environments:
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.

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.

  • English and Russian, following the system language until you pick one in Settings → General. No restart.
  • Settings → Appearance — theme, interface scale (⌘= / ⌘− / ⌘0 and 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.