Skip to content

Last updated: 2026-10-09

Beta Testing is the only mode that behaves identically to a published app on a real user's phone, and the only mode that puts your app on the glasses menu. You pack the .ehpk exactly as you would for submission, promote it from Private to Beta, invite yourself (and optionally teammates) onto the app's tester list, and install it from the phone app's Beta tester section. The OS, the lifecycle, the lock-screen behavior, the system exit dialog - all of it matches what an end user (and a QA reviewer) sees.

If you're heading into review, this is the gate. Skip it and you'll fail.

Phone says the test version expired? Check both of these

The Even Realities App reports test version expired when a beta install is not backed by both of the following. The portal lets you do either one without the other, so getting one right and missing the other produces exactly that message.

  1. The build is tagged Beta. In the Builds tab, click the build's grey Private tag, pick Beta, and confirm Promote to Beta. The tag itself is the control - the page has no separate distribute button, and the Beta build section stays hidden until something is promoted into it.
  2. The tester is Active. In the Testing group tab, invite the account that will install, and have that person accept the invitation email. The row's Status has to read Active; an Invited tester can install nothing.

With both in place, the app appears under Beta tester in the phone app and on the glasses menu.

See Test for how Beta Testing compares to the simulator, local sideload, and private builds.

The flow ​

bash
# 1. Build and pack
npm run build
evenhub pack app.json dist -o myapp.ehpk

Then in the dev portal:

  1. Open hub.evenrealities.com/login and go to your project.

  2. Testing group tab - Add user, enter your own account email, Send invite. Add teammates the same way if you want them on the same build. For more than a handful of testers use Import CSV instead of typing each address - see Moving a tester list in and out.

    That tab is one flat list of testers for this app. Everyone on it gets the same beta build, and there is no way to create a second list, name it, or split testers into separate cohorts.

  3. Open the invitation email and accept it. The row's Status goes from Invited to Active. Only Active testers can install a beta build.

  4. Builds tab - upload myapp.ehpk. It lands under Private builds carrying a grey Private tag.

  5. Click that Private tag on the build's row. A menu opens with Public / Beta / Private; pick Beta, then confirm with Promote to Beta.

Step 5 is the step people miss. The grey Private tag is also the control that changes it - clicking the tag is how a build goes from Private to Beta. The page carries no separate "distribute" or "publish to beta" button. The Beta build section renders only once something has been promoted into it, so an app that has never been promoted shows one Private builds section and nothing else.

Each app has one beta slot. Promoting a second version replaces the one in it.

Steps 2, 4 and 5 also run from a terminal:

bash
evenhub testers add teammate@example.com     # step 2
evenhub upload myapp.ehpk -m "What changed"  # step 4
evenhub promote 1.0.1 --to beta              # step 5
evenhub ls <package_id>                      # CHANNEL should read beta

Step 3 stays with the tester - accepting the invitation is a link in their email - and installing on the phone stays manual. See Testers.

On your phone (and any teammate's phone):

  1. Open the Even Realities App.
  2. Me → Beta tester lists the build.
  3. Tap Install.

From here on, the build behaves exactly as if it were published: it appears on the glasses menu, with full OS lifecycle and real backgrounding.

Moving a tester list in and out ​

The Testing group tab has Import CSV and Export CSV next to Add user. Since each app carries one flat tester list, CSV is how a roster moves between apps - export from one app, import into the next, instead of retyping every address.

evenhub testers add --from <file> reads the same file, header row and all.

File format ​

One column, UTF-8, row 1 is the literal header email, then one address per row:

csv
email
qa.lead@example.com
tester.one@example.com
tester.two@example.com
  • A file whose first row is anything other than email is refused before anything is sent.
  • At most 500 addresses per file. Split larger lists.
  • Export writes exactly this shape, so an exported file imports straight into another app.

Import ​

Pick a file and the portal shows a breakdown before anything is committed:

LineMeaning
To inviteNew addresses, plus testers whose invitation was rejected or expired
SkippedDuplicated inside the file, or already Invited / Active on this app's list
Rejected rowsRows that are not valid email addresses, listed (up to 50) so you can fix the file

Confirm and every remaining address is invited in one request. Already invited and already active testers are never re-sent, so re-importing an exported file is safe: it invites nobody and disturbs nobody.

Two things can stop an import:

  • Rejected rows - you may still commit the valid rows, or cancel and fix the file first.
  • A tester who has hit the ten-invitation limit - the server refuses the whole batch and names the address. Nothing is imported. Remove that address from the file and import again.

Export ​

Export CSV downloads every tester on the list - not just the rows currently on screen - sorted alphabetically, with no status column. The file is named {date}_{app}_beta.csv, e.g. 2026-09-18_com.example.myapp_beta.csv.

What only Beta Testing validates ​

The things you can't test anywhere else:

The 5-minute locked-phone test ​

Open your app, put it in a steady state (something visible on glasses), lock the phone, wait 5 minutes, unlock. The app should still be where you left it. This is the exact test the QA reviewer runs. Private builds survive briefly but not 5 minutes; Local Testing dies the second the phone locks.

shutDownPageContainer(1) system exit dialog ​

The system exit-confirmation dialog only renders for a real install. Root-page double-tap must call bridge.shutDownPageContainer(1). Apps using 0 (immediate exit) or a custom in-app exit UI on the root page are auto-rejected at review.

Real permission denial paths ​

What does your app do when the user denies a permission? Beta Testing is where you actually exercise that branch. Local Testing skips some prompts; Private Testing fires them but the reviewer's denial path is a Beta-equivalent install.

What still requires care ​

  • Console logs are visible in the phone app's Developer Mode console. Handy for debugging, but don't log secrets - that install path is reviewer-visible too.
  • Crashes don't auto-report in beta yet. If your app vanishes, the console buffer is your only signal - check it immediately.
  • Beta install is a real install. localStorage, IndexedDB, everything persists exactly as it would for an end user. Reset between test rounds when you need a clean state.

The pre-submission checklist ​

Walk through each of these on the beta build before promoting to Submitted:

#CheckPass criteria
15-minute locked-phone testOpen the app, lock the phone for 5 minutes, unlock - state preserved, no spinner, no black screen.
2Root double-tap fires the system exit dialogVisible confirmation; WebView closes on confirm.
3Permission denial paths handledDeny each declared permission once - app degrades gracefully or surfaces a clear next-step message.
4Re-launch a first-party app (Conversate, Navigate) after exitLaunches cleanly without restarting glasses.
5No console errors at bootConsole buffer is clean immediately after launch.

The full reviewer rubric: App Submission & QA Guidelines.

Common failure modes ​

SymptomLikely causeFix
Portal shows only Private builds, no way to distributeThe build was never promoted; the Beta build section is hidden until it isClick the build's grey Private tag → Beta → Promote to Beta
Phone reports test version expiredOne of the two gates is missing - nothing promoted to Beta, or the tester Status is still InvitedPromote the build to Beta, and accept the invitation so the status reads Active. Both, then reinstall
Tester accepted the invite but the build is gone againBeta availability requires both an assigned beta build and Active membershipCheck the beta build assignment and the tester’s membership. Adding an already accepted tester preserves membership. evenhub testers add skips exact Invited and Active email matches by default; --reinvite replaces pending invitation links and leaves Active members alone. Email matching preserves case.
App installs but never appears on the glasses menuThe install came from a Private buildThe glasses menu lists Beta and published apps. Promote the build to Beta and reinstall
Install succeeds, app vanishes on lockBackgrounding kills the WebView, no resume handlerBackground & Lifecycle - persist eagerly to localStorage and rebuild on relaunch
Double-tap exits silentlyshutDownPageContainer(0) or no exit handlerUse shutDownPageContainer(1) on the root page
Permission prompt copy is wrongEdit app.json permission desc, repack, re-uploadOld beta still has old copy until reinstalled
Build updates don't propagateHigher min_sdk_version than tester's firmwareTester needs firmware update first, or you need to lower min_sdk_version

When you're done ​

The .ehpk that passed every box above is the one you submit. Click that build's tag, pick Public, and confirm in Submit this build for review? - the tag then tracks the review itself: In Review, then Approved or Rejected, and Published once it is live. The reviewer installs from your beta build's lineage - if your beta passes the 5-minute test, the reviewer almost certainly will too.

→ Next: App Submission & QA Guidelines