ci(release): publish through npm trusted publishing (OIDC) - #707
Conversation
Drops NPM_TOKEN from release.yml; each package grants release.yml publish rights via an npm trusted publisher. Release job moves to ubuntu-latest because npm rejects OIDC from self-hosted runners (Blacksmith included). publishConfig.provenance pins removed so provenance attaches automatically once the repo is public. Setup recipe documented in CONTRIBUTING.md.
🟢 e2e web: 90 passedAll 90 tests in 41 files
e2e 0.15.0 · 1m 56s · web · run artifacts |
🟢 e2e web agent: 32 passed33 agent steps · 25 replayed from cache · 20 model calls · 76.4k tokens (92% cached) · $0.0036 All 32 tests in 5 files
e2e 0.15.0 · 3m 3s · web · run artifacts |
🟢 e2e ios: 1 flaky, 51 passed, 1 skipped
|
| Test | Time | |
|---|---|---|
| tests/controls.e2e.ts · 1 flaky, 22 passed, 1 skipped | 3m 34s | |
| 🟢 | control inventory › a label query resolves a labeled text field | 11.9s |
| 🟢 | control inventory › a labeled field answers name, display value, value reads, fill, and clear | 18.6s |
| 🟢 | control inventory › a placeholder query and attribute reads find the field | 5.4s |
| 🟢 | control inventory › a secure field takes a fill and denies value reads | 11.1s |
| 🟢 | control inventory › press sends a character and Enter through the soft keyboard | 12.5s |
| 🟢 | control inventory › a field autofocuses on mount and focus() moves the focus | 12.0s |
| control inventory › toBeFocused follows the focus (2 failed attempts first) | 14.2s | |
| 🟢 | control inventory › a switch checks, unchecks, and reports its state | 11.9s |
| 🟢 | control inventory › checkbox and radio roles answer getByRole, and a checkbox toggles by tap | 9.3s |
| 🟢 | control inventory › checkbox and radio roles check, uncheck, and report their state | 14.9s |
| 🟢 | control inventory › a radio group reports the checked option | 10.8s |
| 🟢 | control inventory › buttons report disabled, enabled, and a state that arrives later | 9.2s |
| 🟢 | control inventory › a display: none element is absent rather than hidden, its visible twin attached | 8.3s |
| 🟢 | control inventory › a header answers its accessible name | 4.9s |
| 🟢 | control inventory › a tab row answers count, all, texts, filters, positions, and switching | 8.5s |
| 🟢 | control inventory › a list answers counts, list-form text, reads, and boxes in order | 8.2s |
| ⏭️ | control inventory › a row contains its texts (skipped: React Native flattens a plain View out of the accessibility tree on both platforms: XCTest and the Android helper report a View with a testID as a leaf beside its children, so filter({ has }) and a query scoped to it match nothing; only Sc…) | |
| 🟢 | control inventory › a long press lands as one | 9.1s |
| 🟢 | control inventory › pointer verbs land at the point asked for | 12.1s |
| 🟢 | control inventory › an ambiguous locator fails at once | 6.6s |
| 🟢 | control inventory › screenshot, restart, and clearState reopen the app on its home list | 12.3s |
| 🟢 | refusals › verbs a device never declares are refused before the engine | 3ms |
| 🟢 | refusals › keys the soft keyboard cannot send are refused by the engine | 499ms |
| 🟢 | refusals › a missing locator fails as not found once its timeout passes | 1.3s |
| 🟢 | tests/device-fixture.e2e.ts · 9 passed | 1m 25s |
| 🟢 | device fixture › setAppearance flips the color scheme the app reads | 12.3s |
| 🟢 | device fixture › setOrientation rotates the window the app measures | 9.8s |
| 🟢 | device fixture › the clipboard reads back what was written | 5.9s |
| 🟢 | device fixture › home leaves the app, openApp brings it back where it was | 5.9s |
| 🟢 | device fixture › closeApp then openApp relaunches on the home list | 11.8s |
| 🟢 | device fixture › foregroundApp after closeApp reports the closed session as APP_NOT_OPEN | 5.4s |
| 🟢 | device fixture › device.back pops the scenario | 6.6s |
| 🟢 | device fixture › setLocation and clearLocation change what the app reads | 12.8s |
| 🟢 | device fixture › enrollBiometrics and setBiometrics answer a biometric prompt | 14.3s |
| 🟢 | tests/device.e2e.ts · 2 passed | 59.3s |
| 🟢 | sequential onboarding echoes the exact values | 44.9s |
| 🟢 | onboarding rejects a malformed email before advancing | 14.5s |
| 🟢 | tests/flows-lists.e2e.ts · 7 passed | 3m 51s |
| 🟢 | infinite scroll reaches the target item | 30.1s |
| 🟢 | product catalog adds exactly two of the right variant | 11.3s |
| 🟢 | gestures: long-press, then swipe left | 9.8s |
| 🟢 | sticky chrome: accept below the fold, then continue | 15.2s |
| 🟢 | async states: load, pull to refresh, claim through the toast | 13.4s |
| 🟢 | huge virtualized list reaches row 512 | 2m 17s |
| 🟢 | web view coupon form applies the code shown on the page | 14.8s |
| 🟢 | tests/flows.e2e.ts · 7 passed | 1m 19s |
| 🟢 | text inputs unlock the submit button | 17.1s |
| 🟢 | filled text inputs report their values | 10.7s |
| 🟢 | modal flow confirms through the native alert | 11.3s |
| 🟢 | bottom tabs act inside the last tab | 8.5s |
| 🟢 | error recovery retries, then confirms the delete | 10.9s |
| 🟢 | choice controls place the exact order | 9.4s |
| 🟢 | debounced search selects the target once results arrive | 10.9s |
| 🟢 | tests/login-form.e2e.ts · 3 passed | 49.8s |
| 🟢 | login form › rejects a malformed email | 12.5s |
| 🟢 | login form › rejects an unknown account | 13.7s |
| 🟢 | login form › signs in and logs out | 23.7s |
| 🟢 | tests/smoke.e2e.ts · 1 passed | 13.5s |
| 🟢 | home lists the scenarios and opens one | 13.5s |
e2e 0.15.0 · 6m 34s · ios-simulator · run artifacts
🟢 e2e android: 54 passed, 1 skippedAll 55 tests in 7 files
e2e 0.15.0 · 8m 27s · android-emulator · run artifacts |
🟢 e2e android agent: 1 flaky, 19 passed24 agent steps · 11 replayed from cache · 41 model calls · 262.1k tokens (86% cached) · $0.0156
|
| Test | Agent | Time | |
|---|---|---|---|
| tests-agent/scenarios.e2e.ts · 1 flaky, 19 passed | 24 steps · 41 calls | 9m 58s | |
| 🟢 | act completes Login Form | 1 step · 4 calls | 53.1s |
| 🟢 | act completes Infinite Scroll List | 1 step · 3 calls | 2m 15s |
| 🟢 | act completes Modal Flow | 1 step | 9.8s |
| 🟢 | act completes Bottom Tabs | 1 step · 3 calls | 22.8s |
| 🟢 | act completes Text Input Variations | 1 step | 6.0s |
| 🟢 | act completes Broken Accessibility | 2 steps · 5 calls | 29.4s |
| 🟢 | act completes Vision Only | 2 steps · 1 call | 11.4s |
| act completes Gestures (1 failed attempt first) | 1 step · 10 calls | 37.0s | |
| 🟢 | act completes Async States | 1 step | 10.8s |
| 🟢 | act completes Debounced Search | 1 step | 7.2s |
| 🟢 | act completes Huge Virtualized List | 1 step · 2 calls | 1m 29s |
| 🟢 | act completes Flattened Registration Form | 2 steps · 1 call | 22.1s |
| 🟢 | act completes Flattened Login | 2 steps · 6 calls | 31.4s |
| 🟢 | act completes Sticky Chrome Target | 1 step | 52.0s |
| 🟢 | act completes WebView Accessibility | 1 step · 4 calls | 17.0s |
| 🟢 | act completes Permission Prompt | 1 step · 2 calls | 23.0s |
| 🟢 | act completes Product Catalog | 1 step | 10.0s |
| 🟢 | act completes Error Recovery | 1 step | 9.4s |
| 🟢 | act completes Choice Controls | 1 step | 10.8s |
| 🟢 | act completes Sequential Onboarding | 1 step | 11.0s |
e2e 0.15.0 · 10m 34s · android-emulator · run artifacts
🟢 e2e ios agent: 1 flaky, 20 passed, 1 skipped25 agent steps · 11 replayed from cache · 57 model calls · 380.4k tokens (81% cached) · $0.0271
|
| Test | Agent | Time | |
|---|---|---|---|
| tests-agent/scenarios.e2e.ts · 1 flaky, 20 passed, 1 skipped | 25 steps · 57 calls | 10m 33s | |
| act completes Login Form (1 failed attempt first) | 1 step · 9 calls | 1m 4s | |
| 🟢 | act completes Infinite Scroll List | 1 step | 43.6s |
| 🟢 | act completes Modal Flow | 1 step | 14.1s |
| 🟢 | act completes Bottom Tabs | 1 step | 10.7s |
| 🟢 | act completes Text Input Variations | 1 step | 19.1s |
| 🟢 | act completes Broken Accessibility | 2 steps · 6 calls | 37.3s |
| 🟢 | act completes Vision Only | 2 steps · 1 call | 16.1s |
| 🟢 | act completes Gestures | 1 step · 9 calls | 59.3s |
| 🟢 | act completes Async States | 1 step · 6 calls | 28.4s |
| 🟢 | act completes Debounced Search | 1 step · 3 calls | 21.2s |
| 🟢 | act completes Flattened Registration Form | 2 steps · 1 call | 38.2s |
| 🟢 | act completes Flattened Login | 2 steps · 10 calls | 1m 18s |
| 🟢 | act completes Sticky Chrome Target | 1 step | 19.6s |
| 🟢 | act completes WebView Accessibility | 1 step | 17.5s |
| 🟢 | act completes Permission Prompt | 1 step · 2 calls | 30.5s |
| 🟢 | act completes Photo Picker | 1 step · 2 calls | 27.8s |
| 🟢 | act completes Product Catalog | 1 step | 13.6s |
| 🟢 | act completes Error Recovery | 1 step | 14.2s |
| 🟢 | act completes Choice Controls | 1 step | 18.2s |
| 🟢 | act completes Stripe PaymentSheet | 1 step · 7 calls | 42.7s |
| 🟢 | act completes Apple Pay | 1 step · 1 call | 19.7s |
| ⏭️ | act completes Apple Pay Billing Address (skipped: the CI simulator's Apple Pay sheet skips the billing-address step, so the scenario cannot be completed there) |
e2e 0.15.0 · 11m 33s · ios-simulator · run artifacts
Publishing now authenticates with npm Trusted Publishing (OIDC) instead of the
NPM_TOKENsecret, same setup as callstack/react-native-bottom-tabs. Once the repo is public, npm attaches provenance automatically: the "built and signed on GitHub Actions" badge.release.ymlgainsid-token: writeand dropsNODE_AUTH_TOKEN; the OIDC exchange innpm publish(npm 11.5.1+, bundled with node 26) replaces it.releasejob moves toubuntu-latest: npm rejects OIDC tokens from self-hosted runners, and Blacksmith registers as one (npm docs: "Self-hosted runners are not currently supported"). AGENTS.md records the exception to the Blacksmith rule. Timeout bumped 20 to 30 min for the slower runner.registry-urlremoved from setup-node: its.npmrctemplate readsNODE_AUTH_TOKENand errors once the variable is gone.publishConfig.provenance: falseremoved from all 6 public packages. Default is auto: npm skips provenance while the repo is private, attaches it once public. No changeset: metadata only, no behavior change.e2e,@e2e-dev/web,@e2e-dev/mobile,@e2e-dev/github,@e2e-dev/kernel,@e2e-dev/eas) already have the trusted publisher connection on npmjs: GitHub Actions,tester-army/e2e,release.yml,Allow npm publishchecked.Verified
Ran it locally: no - the publish path only runs on a release push to
main, and OIDC cannot be exercised from a laptop (the token exchange only works inside the configured workflow on a GitHub-hosted runner).Checked statically instead:
actionlinton the workflow: clean.action.ymlat the pinnedchangesets/actionSHA:create-github-releasesandpush-git-tagsdefault to true, the token input is optional; the action's.npmrchandling is OIDC-safe since fix: conditionally append NPM_TOKEN to .npmrc for trusted publishing … changesets/action#545.pnpm checkgreen after thepublishConfigedits.NPM_TOKEN(secret still set).Fresh-context review found 3 issues, all fixed here: the Blacksmith/OIDC incompatibility, provenance claims contradicted by the
publishConfigpins and repo visibility, and the stale AGENTS.md release notes.