npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@svatah/yam-adapter-appium

v0.2.0

Published

Appium adapter: Android Chrome and native

Readme

@svatah/yam-adapter-appium

The Appium adapter: AgentSurface on Android and iOS (REQ-ADP-5, LLD §7.4).

One session, two worlds. Inside a webview the device is a browser and every web candidate kind means what it means anywhere else; inside the native app there is no DOM, and a screen is an XML page source converted into the same snapshot shape a web page produces (REQ-SURF-4). Nothing above the surface knows which of the two it is in — that is the point of the surface being a boundary.

Configuring it

# yam.config.yaml
adapter: appium
mobile:
  appium: "http://127.0.0.1:4723"
  capabilities:
    platformName: Android
    "appium:automationName": UiAutomator2
    "appium:deviceName": emulator-5554
    # A browser session (Android Chrome), or an app under test:
    browserName: Chrome
    # "appium:app": /path/to/sample.apk
app:
  # `10.0.2.2` is the emulator's route to the host machine.
  baseUrl: "http://10.0.2.2:4173"

| Variable | Meaning | |---|---| | YAM_APPIUM_URL | Where the Appium server is; overrides mobile.appium | | YAM_APPIUM_CAPS | A JSON object of capabilities, merged over mobile.capabilities |

The emulator gate

T4.2's Validate list has a half that no fake can establish, and this is it. The adapter's conversion, candidate mapping, action table and predicates are unit tested against recorded page sources and a fake device (pnpm --filter @svatah/yam-adapter-appium test); what those cannot show is that a real driver accepts these selectors and that a real screen looks like the fixtures. Run this on a machine with an emulator:

# 1. A server and a device.
npm install -g appium
appium driver install uiautomator2
appium --port 4723 &
emulator -avd Pixel_7_API_34 -no-window &
adb wait-for-device

# 2. The sample application, reachable from the emulator at 10.0.2.2.
pnpm --filter sample-web start &

# 3. Android Chrome replays the migrated fixtures (T4.2's first Validate item).
cp -R evals/fixtures /tmp/appium-fixtures
sed -i '' 's/^adapter: playwright$/adapter: appium/' /tmp/appium-fixtures/yam.config.yaml
YAM_APPIUM_CAPS='{"platformName":"Android","appium:automationName":"UiAutomator2","browserName":"Chrome"}' \
YAM_BASE_URL=http://10.0.2.2:4173 \
  node packages/cli/dist/bin.js run /tmp/appium-fixtures --host none --flow flows/svatah.flow

# 4. The surface conformance subset that does not need windows, dialogs,
#    frames or a file picker — the capabilities a phone does not have.
YAM_APPIUM_CAPS='{"platformName":"Android","appium:automationName":"UiAutomator2","browserName":"Chrome"}' \
  node packages/cli/dist/bin.js surface conform --adapter appium \
    --base-url http://10.0.2.2:4173 \
    --only home.snapshot,home.click-navigates,login.snapshot-states,login.type-changes-value,\
login.checkbox-state,login.describe,login.locate-cardinality,dashboard.read-kinds,\
dashboard.state,widgets.select,errors.typed,capabilities.descriptor

# 5. Native: grounding and three steps against an app under test.
YAM_APPIUM_CAPS='{"platformName":"Android","appium:automationName":"UiAutomator2","appium:app":"/path/to/sample.apk"}' \
  node packages/cli/dist/bin.js record /tmp/native-project --gateway fake --rebind

Step 4 names a subset because the whole suite cannot pass on a phone and should not: widgets.dialog, widgets.frame, widgets.windows and widgets.canvas-coords need capabilities APPIUM_CAPABILITIES declares false, and the suite skips a case whose capability is missing rather than failing it (LLD §14). Running the whole suite is therefore also correct; the subset is what a person watching wants to see.

What a phone does not have

Capability flags declare it rather than emulating it (LLD §2.4). The executor refuses a plan whose actions need a missing capability at start, not mid-run.

| Flag | | Why | |---|---|---| | screenshot | yes | takeScreenshot, masked by rewriting the PNG (see below) | | drag | yes | W3C pointer actions with pointerType: touch | | restore | yes | The context, and the URL when there is one | | dialogs | no | A permission prompt is another application's window; an in-app modal is just more of the page source | | frames | no | A phone has contexts, not frames. switchFrame switches context, which is the nearest thing the platform has | | windows | no | There is one screen | | upload | no | There is no file picker to drive | | trace | no | Appium has no tracing |

Some actions are refused the same way, as UnsupportedError, rather than sent as something that half works. keyDown and keyUp have no held-key primitive: a W3C key action sequence releases its keys when it ends. press sends one character as itself or a named key — Enter, Tab, Escape, Backspace, Delete, the four arrows, Home, End, PageUp, PageDown, Space — as its W3C codepoint, and refuses any other name. In a native context waitFor with a url is refused at once, because a native screen has no URL. A reference waitFor waits for the step's timeoutMs when it gives one, and for the state args.state names: attached and detached (the driver finds it, or answers that it is stale or gone), visible and hidden (isDisplayed), enabled and disabled (isEnabled); any other state is a DataError. scrollToTop and scrollToBottom in a native context swipe four tenths of the screen's height from its centre.

Masking a screenshot

The web adapters mask before the picture is taken — Playwright takes a mask argument and the BiDi adapter paints an overlay into the page. Appium screenshots the device, so there is nothing to paint into and the masking happens to the PNG that comes back (src/mask.ts, about a page, with node:zlib doing the compression).

It handles what the drivers emit — 8-bit RGB and RGBA, non-interlaced — and refuses anything else rather than writing a picture it could not mask. A refusal is recoverable; a screenshot of a password field is not (REQ-NFR-6).

Why AppiumClient is an interface

WebdriverIO is the client, as LLD §7.4 chooses. It sits behind an interface so that everything above it — the page-source conversion, the candidate mapping, the context switching, the action table, the predicates — is testable without a device. An adapter whose logic could only be exercised on an emulator would be an adapter nobody could check, and the emulator gate above would be the only evidence there ever was.