Blolabel
← Notes
event 6 min read

Appium 4's first beta moves status text to stdout and drops three Node lines, including the one this Mac runs

appium@4.0.0-beta.0 landed on npm at 20:39:59 UTC on 19 September 2026. Two changes break a rig before any test runs, and the iOS driver has not shipped against it yet.

Appium 4.0.0-beta.0 went to npm at 20:39:59 UTC on 19 September 2026 and changed two things that break a rig before a single test runs. Human-readable CLI output moved from STDERR to STDOUT, so a script that captured STDERR to log progress now captures nothing and a script that threw STDOUT away now throws the progress away instead. And the Node engine range tightened from ^20.19.0 || ^22.12.0 || >=24.0.0 in 3.7.0 to ^22.22.2 || ^24.15.0 || >=26.0.0. That range excludes the whole of Node 20, 23 and 25. The Mac this note was written on runs Node v25.8.2, which 3.7.0 accepted and the beta does not.

Abstract technical illustration of two parallel output channels, one thin and one wide, with a bundle of message tokens crossing from the thin channel to the wide one, and a segmented number line beneath with three lit segments and three dark gaps

What shipped, and when

The 4.0.0-beta.0 tag was published on GitHub at 20:35:49 UTC on 19 September 2026 and reached the npm registry four minutes later. A patch, 4.0.0-beta.1, followed 49 minutes after that with a single fix: catch peerDependencies up to the published beta.0. The npm beta dist-tag now points at 4.0.0-beta.1; latest is still 3.7.0, published 24 August 2026.

Fieldappium 3.7.0appium 4.0.0-beta.0Source
npm publish time2026-08-24T20:52:07Z2026-09-19T20:39:59Zregistry.npmjs.org/appium
engines.node^20.19.0 || ^22.12.0 || >=24.0.0^22.22.2 || ^24.15.0 || >=26.0.0registry.npmjs.org/appium
engines.npm>=10>=10registry.npmjs.org/appium
package typenot set (CommonJS)moduleregistry.npmjs.org/appium
Default log leveldebuginfo4.0.0-beta.0 release notes
Status text streamSTDERRSTDOUT4.0.0-beta.0 release notes
MJSONWP GET /session/:idpresentremoved4.0.0-beta.0 release notes
--allow-corspresentremoved, use --allow-insecure=*:cors4.0.0-beta.0 release notes
appium-xcuitest-driver supportshippedPR #2984, opened 2026-09-20, opengithub.com/appium/appium-xcuitest-driver/pull/2984

Where the status text goes now

The release notes state the change three times in three different framings, which is a fair signal of how much downstream code it touches. Before, non-JSON human-readable output (info, success and warning messages from driver list, plugin install, doctor and friends) was written to STDERR, and STDOUT was reserved almost exclusively for --json. Now that output goes to STDOUT and only genuine errors go to STDERR. --json is unaffected: it still writes clean JSON to STDOUT alone.

appium 3.7.0 appium 4.0.0-beta.0 info / success / warn --json payload errors STDOUT STDERR info / success / warn --json payload errors STDOUT STDERR Two failure modes named in the release notes A script that ran `appium ... 1>/dev/null` to hide chatter now hides the status text. A script that captured STDERR to log success messages now sees only errors. Source: appium@4.0.0-beta.0 release notes, 19 September 2026. The default log level also moved from debug to info.

The same release moved @appium/logger's Log class default stream from STDERR to STDOUT for every level except error, and added an errorStream and stderrLevel pair. Code that set .stream on a Log instance expecting it to capture all levels has to set .errorStream to the same target now.

Which Node versions the beta will actually start on

Three disjoint windows, and the gaps between them are not decoration. ^22.22.2 means at least 22.22.2 and below 23. ^24.15.0 means at least 24.15.0 and below 25. >=26.0.0 takes everything from 26 up. Node 20, which 3.7.0 accepted from 20.19.0, is gone entirely, and so are the odd-numbered current lines 23 and 25.

engines.node = ^22.22.2 || ^24.15.0 || >=26.0.0 202122 232425 26 from 22.22.2 from 24.15.0 from 26.0.0 3.7.0 allowed 20.19.0+ this Mac: v25.8.2 excluded Allowed: 22.22.2, 24.15.0, 24.20.0, 26.0.0 Rejected: 20.x, 21.x, 23.0.0, 25.0.0, 25.8.2

Those eight version results are not read off the range by eye. They come from running the semver CLI against the published range, which is the same resolver npm uses, and the command is in the section below.

The ESM change is the one that will surface late

3.7.0's package manifest has no type field, so it is CommonJS. 4.0.0-beta.0 sets "type": "module". Along with it the release removes default exports from @appium/base-driver, @appium/base-plugin and @appium/logger, so require('appium') and const BasePlugin = require('@appium/base-plugin') both stop working. A team that consumes Appium only through the CLI will not see this. A team that wrote a plugin will, and will see it at import time rather than at test time.

Two more removals matter for anything that talks to the server directly rather than through a client library. BaseDriver.getSession and the JSONWP GET /session/:sessionId route are gone; the replacements are getAppiumSessionCapabilities for capabilities and EventCommands.getLogEvents for event history. The appium:eventTimings capability went with them, because it only ever controlled whether getSession's response carried event history that getLogEvents already returns unconditionally. Separately, SPDY and HTTP/2 negotiation are gone, leaving plain HTTP/1.1.

What this costs a team driving real iPhones

Nothing yet, and that is the point. appium-xcuitest-driver has not shipped against Appium 4: PR #2984, "feat!: target Appium 4 beta", was opened on 20 September 2026 and is still open as of 22 September. Its own description says it bumps the appium peer dependency from ^3.0.0-rc.2 to ^4.0.0-beta.0 and updates driver code for the API removals. So an iOS rig cannot move to Appium 4 today even if it wants to, and that gap is the window to fix the plumbing.

The ordering judgement, ours: fix the stream capture first, because it fails silently. A Node version mismatch stops the process with a message. A status line that has moved to the other stream produces a green build with an empty log, which is the failure mode that costs a day of debugging three weeks later. If you have a CI step that greps STDERR for "Driver installed" or similar, it will simply never match.

Note also what did not change. --json still writes clean JSON to STDOUT only, and errors still go to STDERR. A script that already used --json and parsed STDOUT is unaffected by the whole stream rearrangement. That is an argument for using the machine-readable flag in a rig even when a human is watching.

Adjacent, and worth knowing if you pin WebDriverAgent separately: WDA v16.12.9 shipped on 19 September 2026 with one fix, "resolve real device udid to xcodebuild's own case before building -destination id=" (issue #1260, opened 18 September). That is the same case-sensitivity class of bug we measured across five tools last week, now fixed upstream at the point where the UDID reaches xcodebuild.

Check it yourself

Every number above comes from one of these three commands. None of them need a device.

# engine ranges, publish times and the ESM flag, straight from the registry
curl -s https://registry.npmjs.org/appium | python3 -c "
import json,sys
d=json.load(sys.stdin)
print('dist-tags:', d['dist-tags'])
for v in ['3.7.0','4.0.0-beta.0','4.0.0-beta.1']:
    m=d['versions'][v]
    print(v, m.get('engines'), 'type=', m.get('type'), d['time'][v])
"

# which Node versions satisfy the beta's range
npx --yes semver@7 -r "^22.22.2 || ^24.15.0 || >=26.0.0" \
  22.22.2 23.0.0 24.15.0 24.20.0 25.0.0 25.8.2 26.0.0
# prints only: 22.22.2 24.15.0 24.20.0 26.0.0

# the breaking-change list, verbatim
curl -s "https://api.github.com/repos/appium/appium/releases/tags/appium%404.0.0-beta.0" \
  | python3 -c "import json,sys; d=json.load(sys.stdin); print(d['published_at']); print(d['body'])"

To see the stream change on your own machine without upgrading anything, run any Appium CLI subcommand twice under 3.7.0, once with 1>/dev/null and once with 2>/dev/null, and note which one still prints. Under 4.0.0-beta.0 the answer flips.

What would prove this wrong

Two claims here are dated predictions rather than measurements, and both are checkable before the end of 2026.

First: that appium-xcuitest-driver's first release supporting Appium 4 carries engines.node identical to ^22.22.2 || ^24.15.0 || >=26.0.0. PR #2984 says it bumps engines.node to match Appium 4's minimum, so a published driver with a wider range would mean the constraint is negotiable and the Node 25 exclusion is not load-bearing.

Second: that the STDOUT move survives to 4.0.0 stable. It is listed as a bug fix, not a feature, which is a mild signal that the maintainers consider the old layout the defect. If a later beta reverts it or adds a compatibility flag, the migration advice above is wasted work. Check appium@4.0.0's release notes for a --legacy-streams style escape hatch when it lands.

Sources

  1. Appium, release notes for appium@4.0.0-beta.0, published 19 September 2026
  2. npm registry metadata for the appium package: engines, type and publish times, read 22 September 2026
  3. Appium, release notes for appium@4.0.0-beta.1, published 19 September 2026
  4. appium-xcuitest-driver PR #2984, 'feat!: target Appium 4 beta', opened 20 September 2026, open as of 22 September 2026
  5. WebDriverAgent v16.12.9, published 19 September 2026, resolving real device UDID case for -destination id=
  6. WebDriverAgent issue #1260, opened 18 September 2026