Upgrading
Upgrading Xenon is updating the plugin and restarting Appium. This page covers what to read first, the update itself, database changes, hubs with nodes, what changes when moving from 2.16 to 2.17, 2.15 to 2.16, 2.14 to 2.15 and 2.13 to 2.14, and what test clients must change when moving from 1.x to 2.x.
Before you upgrade
- Read the release notes for every version between yours and the one you are moving to. Each release says whether it brings a database migration, whether a hub and its nodes must be upgraded in a particular order, and what behaves differently. A release that needs something from you says so near the top, often under "Changed — operator action may be needed".
- Find your version.
appium plugin list --installedlists it, and so does the account menu at the top right of the dashboard, under Version. - Back up the database and the signing key:
~/.cache/xenon/xenon.dband~/.cache/xenon/xenon-jwt-private.pem. Copy them while Xenon is stopped. - Mind running sessions. Restarting a server ends the sessions running on its own devices. Sessions on a node's devices keep running when the hub restarts, and the hub routes them again once it is back. Wait for tests to finish, or tell people to expect a restart.
Update the plugin
appium plugin update xenon
Then restart Appium. appium plugin list --installed --updates shows whether a newer version is available. By default Appium updates only within the version's major number: to move to a new major version, such as 1.x to 2.x, add --unsafe:
appium plugin update xenon --unsafe
- Xenon Control: Set up this Mac, on Setup, runs
appium plugin update xenonon a plugin that is already installed, so it stays within the major version. It then installs the iPhone support (go-ios) version the new Xenon expects, for a profile whose platform includes iOS. For a move to a new major version, runappium plugin update xenon --unsafein a terminal first, withAPPIUM_HOMEset to the folder the app uses (with technical details on, Setup's Xenon row names it, and Open Appium folder in Settings → All settings → Technical opens it). Then press Set up this Mac, so that the iPhone support matches the new version (the iPhone support row says when it doesn't), and restart the server from the app. - A source checkout: run
git pull,npm installandnpm run build:all, thennpm run dev, which also applies database changes.
Database changes
Xenon applies pending database changes when it starts, so a normal upgrade needs nothing from you. The database itself decides how:
- A database with no migration history, which is every database Xenon makes with its default settings, is updated with
prisma db push. - A database that keeps a migration history (Prisma's
_prisma_migrationstable) matching its tables is updated withprisma migrate deploy. One whose tables differ from its history, or whose history records a failed migration, is updated withprisma db pushtoo.
The log says Syncing database schema (<command>) at <database>: <why> and then Database schema in sync. databaseProvider plays no part. Each server has its own database, so a hub and each node apply their own changes when they start.
On a database with a migration history, db push doesn't delete a table or column that holds data. If an update would, the server stops instead and prints the commands to run, with its own paths: a backup, the command that lets the listed changes go, and, when no failed migration is recorded, how to try the migrations on a copy first. See The server doesn't start.
If your pipeline applies database changes instead, set XENON_AUTO_MIGRATE=false. Xenon then skips them at startup, and you must apply them before you start the new version, with the command Xenon would run. Back up the database first. For the default SQLite database, which has no migration history:
cd "${APPIUM_HOME:-$HOME/.appium}/node_modules/@xenon-device-management/xenon"
DATABASE_URL="file:$HOME/.cache/xenon/xenon.db" node_modules/.bin/prisma db push --skip-generate --accept-data-loss
For a database whose migration history matches its tables, run node_modules/.bin/prisma migrate deploy there instead. Use your own DATABASE_URL if you changed it. From a source checkout, npm run db:migrate chooses the command for you.
A hub and its nodes
Upgrade each server the same way. Whether the order matters is up to the release: its notes say. The 2.12 and 2.13 releases say a hub and its nodes can be upgraded in any order, and some earlier ones asked for the nodes or the hub first. If you skip several releases, read the notes for each one.
Hub and nodes explains how the two kinds of server work together.
From 2.16 to 2.17
Version 2.17.0 brings no database migration. Upgrade the hub and its nodes together: a node's phones' Device logs need both, and the network capture certificate is made on the server a phone is plugged into.
What a lab may have to do:
- Startup no longer rewrites a database's migration history. 2.16.0 could record migrations as applied, mark a failed one rolled back, and try migrations on a copy beside the database. 2.17.0 does none of that: it follows the rule above. A history 2.16.0 changed is read like any other, and needs nothing from you.
- Leftover copies from 2.16.0. If a 2.16.0 start was stopped while it tried migrations on a copy, delete any
<database file>.xenon-trial-<number>file left beside the database, with its-journal,-walor-shmfiles, once no 2.16.0 server is running. That happened only to a SQLite database with a migration history whose tables had been changed by hand. - A node's phones' Device logs need the hub and the node on 2.17.0. With an older node, the hub logs it once and those sessions keep an empty Device logs tab. See What goes through the hub.
- A new network capture certificate. The certificate authority an earlier version made had an invalid serial number, which OpenSSL 3, Go and BoringSSL-based clients refuse, and Android didn't find it under the file name Xenon gave it. It is replaced the first time a session uses the interceptor. An emulator gets the new one by itself; on a real phone, install the new file by hand, as you did the old one. See The certificate.
Other changes:
- Device logs for a node's phones. On a hub with the dashboard on, a session on a node's phone keeps the same Device logs as one on the hub's own phone, and its failure analysis reads them. See Sessions and builds.
- A hub no longer checks a node's iPhone before a session. The node checks it when it starts the session. A session on a node's iPhone used to be refused as unhealthy when the node ran on another machine. See How a session gets a device.
From 2.15 to 2.16
Version 2.16.0 brings no database migration. Upgrade the hub and its nodes: iOS sessions, their Device logs and the proxy option run on the server a phone is plugged into.
What a lab may have to do:
- The database decides how it is updated at startup, not
databaseProvider, which has no effect. See Database changes.databaseProvider: postgresqlno longer stops the server. - Failure categories come from what Appium and the drivers report. Two are new, Session Lost and Stale Element. Wda Failure and Xenon Command Failure are no longer given to new sessions, and a session ended by the server shutting down is Hub Restart. A webhook template or a report that filters on the two retired categories needs the new ones. See The category.
- OpenTelemetry follows Xenon's own settings with tracing on. Metrics go only to
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT, never to the trace URL +/v1/metrics, so a collector that received them there needs the metrics URL set. In JSON log mode, Xenon's lines go out at their own level, so--log-levelapplies and errors go to stderr. See Turn on OpenTelemetry. - The
proxyoption applies to every call to another server, not only a session's create. Without it, those calls followHTTP_PROXY,HTTPS_PROXYandNO_PROXY. See If the servers reach each other through a proxy. - A live connection to the event stream is closed when its sign-in stops being accepted. A client of your own on a bearer token must fetch a fresh token for each connection. See Real-time events.
xe:save_device_logs: falsekeeps a session's device log off. Through 2.15 it was read nowhere. See Session settings.- The first session on an iOS simulator takes a few minutes, while the XCUITest driver builds WebDriverAgent. See How a session gets a device.
Security changes to check:
- Network capture stays with admins. Captured requests go live to Admins and Super admins only. A session's command log no longer keeps a test's
exportHar,getRequestsandgetMocksanswers, oraddMock's mock, which everyone who could see the session could read, and whose first 500 characters went to the AI provider when the session's failure was analysed. Rows earlier versions saved stay until their session is cleaned up. - Selector Health's live events follow the page's rule: a Member's dashboard gets a selector's events only if they may see the selector. See Real-time events.
- A live dashboard connection follows its user. A change of role or team applies to an open dashboard at once, and a connection whose user is made Inactive or deleted, whose sign-in is signed out, or whose key or token is revoked or runs out, is closed. See Real-time events.
Other changes:
- Sessions on iOS simulators start again, end as they ran, and keep their video.
- iPhone and simulator sessions' Device logs hold the whole run. See Sessions and builds.
From 2.14 to 2.15
Version 2.15.0 adds one database column, refuses old session tokens and changes a few answers. Check these before you upgrade, and upgrade the hub and its nodes: healing, selector learning and the H.264 preview run on the server a phone is plugged into.
What a lab may have to do:
- A new column,
LocatorEtalon.path, which the Resilio healing tier uses. Xenon adds it when it starts. If you setXENON_AUTO_MIGRATE=false, apply it first with the command above. Selectors learnt before get their path the next time they are found. See How healing works. - Mint new session tokens. A session token made by 2.14 or earlier carries no scopes, and a session create refuses a session token without
sessions, whether or notXENON_REQUIRE_SESSION_TOKENis on.POST /xenon/api/auth/tokennow gives one only to a credential withsessionsoradmin, for MCP scopes that includeappium:use. See Credentials in a test session. - Set
XENON_PUBLIC_URLto the server's address, such ashttps://xenon.example.comorhttp://lab-mac:4723. The dashboard's own address, ending in/xenon/, works too; a value with any other path, a query or a user name in it is ignored, with a warning at startup. Password reset links point there, never at the address a request came to, and without it Xenon emails none: the sign-in page sends people to an administrator. Each device'sdashboard_link, its link to the appium-dashboard-plugin when that plugin runs on the server, uses it too; without it the link is a bare/dashboardpath. See A forgotten password. - Node.js 20.19+, 22.12+ or 24+, with npm 10+. The plugin's
enginesnow states Appium 3's range. See Installation and requirements. POST /xenon/api/apikeysrefuses an unknown scope with400, where it used to store any name. A script that creates keys with a scope other thanread,sessions,devicesoradminnow fails. See On the API keys page.healingTiersholds for every session.[]now turns healing off for that session, and a value that isn't a list of tier numbers from 1 to 5 runs tiers 1, 2 and 3 only, with a warning in the server log.[], and a value that wasn't a list or had no numbers in it, used to run every tier; a list that held some numbers ran those and skipped the rest ([1, 6]ran tier 1,[6]none). See Choose tiers for one session.- The live
session_commandevent is a summary. It no longer carriesbody,response,screenshot,url,titleorsubtitle. A client of your own that read them should readGET /xenon/api/session/<id>/session_log. See Real-time events. - The AI engine page's choices win and are kept. A provider, model or base URL saved on the page or with
POST /xenon/api/confignow replaces the option or variable the server starts with, and survives a restart.POST /configrefuses a value that can't work with400 invalid_setting. See AI providers. - A failed session's end doesn't wait for the AI. The category is saved before
driver.quit()returns; the analysis follows, and Xenon gives up on the call after 2 minutes, soai_analysismay still be empty whendriver.quit()returns. Test Connection fails on a rate limit. See AI failure analysis. - A session Appium ends for being idle is filed as Timeout, with Appium's own reason, and the
session_failedwebhook'sfailureReasonchanges with it. See Notifications and webhooks. - Elements found in a screenshot answer like real ones. A
-custom:ai-textor-custom:ai-iconfind that matches nothing fails withno such element, and isn't healed.getTexton an element AI vision found fails withunsupported operation. An element belongs to the session that found it, and goes when the session ends. See Omni-Vision. - An Android session keeps far more device log lines, up to 12,000 rows in the database where it kept about 100. Build cleanup removes them with their session. See Sessions and builds.
- Every server stores selector fingerprints, nodes and servers with
enableDashboardoff included, in their own database. On an iPhone, learning a new selector can delay the test's next command, once per selector. See Fingerprints.
Security changes to check:
-
healingTierskeeps healing away from the AI provider. Through 2.14 it was ignored for a session with video off on a server withenableDashboardoff, a node's for its hub included. If you ran such sessions with an AI provider set up, their screenshots, and for the LLM tier their page source, may have gone to it. See Choose tiers for one session. -
An Inactive or deleted user's credentials no longer create sessions as them, a bearer token's
adminscope lapses when its user becomes a Member, nothing a credential mints outlives it, and rotating an access key needs a dashboard sign-in or theadminscope. See Authentication. -
A long
XENON_USER_SESSION_TTL_MSnow holds. A value longer than a day keeps people signed in that long after their last request, where the browser used to drop the sign-in after a day without a request. If you set one, check it is the time you want. See Sign-in and passwords. -
The event log no longer keeps a session's own data. Older versions wrote every command's request and answer, and every captured request, to the
EventLogtable, forXENON_EVENT_LOG_RETENTION_DAYS(30 by default). To remove them now, stop the server and run this against the file yourDATABASE_URLnames; backups taken before still hold them. See The event log.sqlite3 ~/.cache/xenon/xenon.db "DELETE FROM \"EventLog\" WHERE type IN ('session_command', 'interceptor_request'); VACUUM;"
Other changes:
- OCR's language data comes with the plugin. The
eng.traineddataan older version left in the directory the server was started from can be deleted. See Omni-Vision. - The H.264 preview on a node's phone. With
streaming.androidH264on, a node on 2.13 or older keeps its H.264 capture running beside a Live devices tile's MJPEG until its idle stop. See Live preview.
From 2.13 to 2.14
Version 2.14.0 adds one database table and changes some answers a test or a receiver may depend on. Check these before you upgrade.
- A new table,
DeviceSetting. Xenon adds it when it starts, with the rest of the database changes. If you setXENON_AUTO_MIGRATE=false, apply it before you start 2.14.0. The first start saves the team, tags, maintenance flag and reservation each phone has now, so upgrading loses none of them. From then on a phone keeps them when it disconnects: see Devices and allocation. - The
session_failedwebhook has new field names. A generic JSON webhook used to get the session's database row, with fields such asidandfailure_reason. It now getssessionId,sessionName,failureReason,udid,deviceName,platform,osVersion,startTimeandendTime. A receiver or a custom template that reads the old names,{{id}}or{{failure_reason}}for example, must change to the new ones. The event is also sent when a session times out for inactivity, its driver crashes or its heartbeat stops. See Notifications and webhooks. - An unknown
xenon:orxe:command fails. A script such asxenon: setNetworkProfile, which Xenon has never had, used to answernull. It now fails withunknown command, and the message lists the commands Xenon has. Check your tests for names Xenon doesn't have: see Execute commands. assertVisualStatecan fail. It used to pass whenever the AI gave no answer. It now asks the AI provider for a true or false verdict about the screenshot, and fails when it can't get one: no AI provider, a failed or rate-limited call, or an answer that isn't clear. A test that passed only because nothing was checked will fail until the provider is set up. A plain string works as the condition.visualTap, andsmartTapwith an icon or a description, fail too when they can't look.- The session-details commands never fail.
setSessionName,setSessionStatus,debug,addTagandcaptureEvidenceanswer{ recorded: true }or{ recorded: false, message }. With the dashboard off, a test no longer gets the error it used to get from some of them. - The
interceptorserver option now works. It used to be ignored. It is now the default for every session on the server's own Android phones, so a config that listsinterceptor: { enabled: true }starts capturing every Android session. Remove it, or setenabled: falsein the sessions that should not be captured. See Network interceptor. maxSessionscounts sessions only. It counts the Appium sessions that are running or being started, not live previews, recordings or SDK leases with no session on them, and a new session waits whenever that many are running. A lab that ran past its limit now stops at it, and a value below1means no limit.
Some options and settings that did nothing now work as documented, so check the values you have set:
- Maintenance page values. A retention window, build cap, asset purge or schedule saved on the page now replaces the option the server started with, and the cleanup job acts on it.
POST /xenon/api/configrefuses a value that can't work, such as a retention window of 0, a bad cron expression or a switch that isn't true or false, with400 invalid_setting. See Data retention. emulators. With the defaultplatformofboth, the Android virtual devices you list are now started when the server starts. See Devices and allocation.appium:iPhoneOnlyandappium:iPadOnlykeep only iPhones, or only iPads. See Capabilities.maxConcurrentRecordingsandrecordingsAssetsPathare read, ahead of their environment variables.XENON_JSON_LOGGINGnow takes effect when theenableJsonLoggingoption isn't set. See Recordings and Production deployment.- The AI self-healing switch on the Settings page is saved, and applies from the next command. See Self-healing.
- The Shell tab in device control runs only the commands it lists, exactly as listed, in plain words. A command that chained others or took other arguments is refused, and on iOS
list,syslog,deviceinfo,diagnostics,topandnetstatare gone. See Live device control.
Other changes:
- The phone's network is put back. Xenon restores Wi-Fi, mobile data and the phone's own proxy however a session ends, and a hub no longer changes the network of a node's phone. See Network conditioning.
- The Network panel's wording for a Member. The Network panel and the capture routes were always for admins. A Member now sees "Only admins can see network requests" where the panel used to say there was no capture.
- A team with a phone that is away can't be deleted, and
POST /xenon/api/device/tagsanswers404for a phone Xenon doesn't have. See Teams. - A
postgresql://database URL stops the server. Xenon stores its data in SQLite only. LeavedatabaseProviderunset: set topostgresql, it logs a warning that it has no effect, but then a database made with the default setting stops the server at startup. See Installation and requirements. - Upgrade the hub and its nodes. The network, Shell and iPhone tap fixes run on the server a phone is plugged into, and the team, webhook and script-forwarding fixes run on the hub.
From 1.x to 2.x
Version 2.0.0 changed what test clients send, so update them as well as the server.
- Capabilities move to
xe:options. It is Xenon's one capability namespace: credentials (accessKeyandtoken, orsessionToken), a lease (leaseIdandleaseToken) and Xenon's other options such ashealingTiers.xenon:optionsis still read as an alias. When a session sends both,xe:optionswins field by field. df:optionsis no longer read. Neither arexenon:df:optionsorappium:df:options. A session that sends its credentials only there counts as having none: Xenon admits it with a warning, but it has no owner, so the device ownership guard denies its phone to every non-admin, including whoever started it. WithXENON_REQUIRE_SESSION_TOKENon, Xenon refuses the session.
Before, in WebdriverIO:
'df:options': { accessKey: process.env.XENON_ACCESS_KEY, token: process.env.XENON_TOKEN },
'xenon:options': { healingTiers: [1, 2] },
After:
'xe:options': {
accessKey: process.env.XENON_ACCESS_KEY,
token: process.env.XENON_TOKEN,
healingTiers: [1, 2],
},
In Java:
options.setCapability("xe:options", Map.of("accessKey", accessKey, "token", token));
In Python:
options.set_capability("xe:options", {"accessKey": access_key, "token": token})
A client that uses a session token sends "xe:options": {"sessionToken": ...} instead.
Also for this move:
- Upgrade the nodes before the hub. A node still on 1.x reads only
df:optionsandxenon:options, so a client onxe:optionswould lose its owner and its lease there. - Rotate API tokens that clients sent in
df:options, if you keep session rows, queue history or debug logs from before the upgrade. Until 2.0.0 those tokens reached the driver, were stored and were logged, and old rows are not rewritten. - The database gains a column that Xenon adds at startup, as above. If you set
XENON_AUTO_MIGRATE=false, apply it before starting 2.0.0.
The 2.0.0 release notes also cover team rules for members' Appium sessions, uploaded apps that belong to a team, and the new XENON_REQUIRE_COMMAND_AUTH setting.
Related
- Release notes
- Installation and requirements
- Capabilities: everything a test can send in
xe:options. - Authentication