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

system-testing

v1.0.78

Published

System testing with Selenium and browsers.

Readme

System testing

Rails inspired system testing for Expo apps.

Install

npm install --save-dev system-testing

Usage

import retry from "awaitery/build/retry.js"
import SystemTest from "system-testing/src/system-test.js"
import wait from "awaitery/build/wait.js"
import waitFor from "awaitery/build/wait-for.js"

import createUser from "@/src/testing/create-user.js"
import initialize from "@/src/initialize"
import Option from "@/src/models/option"

describe("Sign in page", () => {
  test("it navigates to the sign in page and signs in", async () => {
    await initialize()

    await SystemTest.run(async (systemTest) => {
      await createUser(userAttributes)

      await systemTest.visit("/")
      await systemTest.findByTestID("frontpageScreen", {useBaseSelector: false})
      await wait(250)

      await retry(async () => {
        await systemTest.click("[data-testid='signInButton']")
        await systemTest.findByTestID("app/sign-in")
      })

      await systemTest.interact("[data-testid='signInEmailInput']", "sendKeys", "[email protected]")
      await systemTest.interact("[data-testid='signInPasswordInput']", "sendKeys", "password")

      const emailInputValue = await systemTest.interact("[data-testid='signInEmailInput']", "getAttribute", "value")
      const passwordInputValue = await systemTest.interact("[data-testid='signInPasswordInput']", "getAttribute", "value")

      expect(emailInputValue).toEqual("[email protected]")
      expect(passwordInputValue).toEqual("password")

      await systemTest.click("[data-testid='signInSubmitButton']")
      await systemTest.expectNotificationMessage("You were signed in.")

      await waitFor(async () => {
        const optionUserID = await Option.findBy({key: "userID"})

        if (!optionUserID) {
          throw new Error("Option for user ID didn't exist")
        }

        expect(optionUserID.value()).toEqual("805")
      })
    })
  })
})

Driver selection

SystemTest uses Selenium by default. To use Appium instead, pass a driver config when creating the instance:

await SystemTest.run({
  driver: {
    type: "appium",
    options: {
      serverArgs: {
        useDrivers: ["uiautomator2"],
        port: 4723
      },
      capabilities: {
        platformName: "Android",
        "appium:automationName": "UiAutomator2",
        "appium:deviceName": "Android Emulator",
        "appium:app": "/path/to/app.apk"
      }
    }
  }
}, async (systemTest) => {
  await systemTest.findByTestID("loginScreen")
})

If you already run an Appium server, provide serverUrl instead of serverArgs. By default, findByTestID uses the Appium accessibility id strategy. To use CSS instead (for web contexts), set options.testIdStrategy to "css" and optionally options.testIdAttribute (defaults to "data-testid").

Using useSystemTest in your Expo app

useSystemTest wires your Expo app to the system-testing runner: it listens for WebSocket commands, initializes the browser helper, and lets tests navigate or reset state. Add it near the root layout of your Expo Router app (for example in _layout.tsx or a top-level provider component).

To enable system tests in native builds, set EXPO_PUBLIC_SYSTEM_TEST=true at build time (and optionally EXPO_PUBLIC_SYSTEM_TEST_HOST to reach the test runner from a device/emulator). For native Appium runs, set SYSTEM_TEST_HOST=native in the test environment and point Appium at your APK.

Minimal example:

import {Stack} from "expo-router"
import useSystemTest from "system-testing/build/use-system-test.js"

export default function RootLayout() {
  const {enabled, systemTestBrowserHelper} = useSystemTest({
    onFirstInitialize: () => {
      // One-time setup the first time the helper initializes
    },
    onInitialize: () => {
      // Reset any app state before each test run
    }
  })

  // Optionally register classes for remote eval once scoundrel is ready
  // useEffect(() => {
  //   if (systemTestBrowserHelper) {
  //     systemTestBrowserHelper.getScoundrel().registerClass("MyModel", MyModel)
  //   }
  // }, [systemTestBrowserHelper])

  return (
    <Stack screenOptions={{headerShown: false}}>
      <Stack.Screen name="(tabs)" />
    </Stack>
  )
}

Notes:

  • The hook auto-connects when the page is opened with ?systemTest=true (as the runner does).
  • onFirstInitialize runs only on the first initialize command; use it for one-time setup.
  • onInitialize is registered once when the helper is ready, but it runs on every initialize command (each SystemTest.run); use it to reset globals/session.
  • If you need scoundrel remote evaluation, wait for systemTestBrowserHelper and register your classes there, as shown in the commented snippet above.
  • Add a root wrapper with testID="systemTestingComponent" (and optionally data-focussed="true") around your app so the runner has a stable element to detect and scope selectors against.
  • From your tests, use await systemTest.getScoundrelClient() to obtain the browser Scoundrel client for remote evaluation.
  • useSystemTest calls useRouter() from expo-router. If you are not using Expo Router, install expo-router or provide your own guard to avoid navigation errors.

Root path and blankText

SystemTest.run() visits SystemTest.rootPath (defaults to /blank?systemTest=true) and waits for an element with testID="blankText" inside the focused systemTestingComponent. If your app does not have a /blank route, set a custom root path and ensure the element exists on that screen.

Example setup:

import SystemTest from "system-testing/build/system-test.js"

SystemTest.rootPath = "/?platform=web&systemTest=true"
<View testID="systemTestingComponent" dataSet={{focussed: "true"}}>
  <Text testID="blankText">Blank</Text>
  {children}
</View>

Base selector and focused container

System tests scope selectors to the active screen by default. The app marks the active layout container with data-focussed="true" on the element with data-testid="systemTestingComponent". In the dummy app, the root layout wraps the navigator and sets data-focussed="true" once so the base selector stays stable across screens.

SystemTest.find and SystemTest.findByTestID use a base selector that targets the focused container:

[data-testid='systemTestingComponent'][data-focussed='true']

This prevents tests from matching elements on inactive or background screens.

When to bypass base selector: Some UI (modals, overlays, portals) can render outside the focused container. For those cases, use useBaseSelector: false so the selector is not scoped:

await systemTest.findByTestID("scannerModeExitPinInput", {useBaseSelector: false})

Use useBaseSelector: false only for modal or overlay content. Keep the default scoping for regular screens to avoid false matches.

Finder options

Most selector helpers accept the same options:

  • timeout (number): override how long the lookup should wait.
  • visible (boolean|null): require elements to be visible (true) or hidden (false), or disable visibility filtering with null.
  • useBaseSelector (boolean): scope the selector to the focused container.

These options are supported by find, findByTestID, and all. click also accepts the same options when a selector string is used:

await systemTest.click("[data-testid='signInButton']", {useBaseSelector: false, visible: true})

interact supports a selector object so you can pass finder options inline:

await systemTest.interact({selector: "[data-testid='scanFooterMenuButton']", useBaseSelector: false}, "click")

Reinitialize a system test

Some test failures can leave the app in a broken state (for example a crashed React tree or a stuck WebSocket session). In those cases, fully restart the SystemTest instance to restore a clean browser/app state before continuing.

await systemTest.reinitialize()

This tears down the browser, servers, and sockets, then starts them again so subsequent steps run against a fresh app instance.

Dummy Expo app

A ready-to-run Expo Router dummy app that uses system-testing lives in spec/dummy. Build the web bundle with npm run export:web and execute the sample system test with npm run test:system from that folder.