NetrisTV/ws-scrcpy: a browser client whose test script always fails
Web client prototype for scrcpy.
At a glance
- What is it?
- ws-scrcpy puts a modified scrcpy stream, an adb shell and a file drop target in a browser. Its manifest and its README disagree about Appium, a video path is documented as unwired but its dependency remains, and npm test exits 1 by design.
- Who is it for?
- ws-scrcpy is worth trying if you want a phone on a browser screen over a network you control, and it is not a finished product: the repository calls itself a prototype, npm test is a placeholder that exits with status 1, and there is no test suite to look at.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 6 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 4, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The test script is the npm placeholder that always exits 1
The scripts block in the package manifest ends with the entry every freshly generated package has and nobody deleted.
"lint": "eslint src/ --ext .ts",
"format": "eslint src/ --fix --ext .ts",
"test": "echo \"Error: no test specified\" && exit 1"So npm test prints an error message and returns a failing status, every time, by construction. There is no test directory at the top level either, and no test framework in the dependencies. What the project does have is linting over the TypeScript sources, wired as two scripts, and that is the only static check the manifest offers. For a repository whose description calls it a web client prototype, that is coherent, and it is also the first thing a new contributor discovers: the test entry point is a lie that fails loudly. The other scripts explain the build shape better than the documentation does. The distribution is two webpack configurations, one development and one production, and the start script builds the production bundle and then runs npm start again inside the output directory, which works because a webpack plugin generates a package.json for the built tree.
The README calls Appium a dependency and the manifest calls it a peer dependency
The iOS section says ws-scrcpy bundles Appium, calls it a dependency, and states that no global Appium installation is required. The manifest says something else, in a comment block that exists precisely to explain the arrangement.
"config": {
"_comment_xcuitest": "Appium drivers are NOT npm deps; this version is installed into APPIUM_HOME by scripts/setup-appium.js (postinstall). Keep it compatible with the pinned `appium` (peer dep: appium ^3.x).",
"xcuitestDriverVersion": "10.14.6"
}So Appium itself is a peer dependency on the 3.x line rather than a bundled package, and what the project actually pins and installs is the XCUITest driver, at an exact version, through a postinstall script that writes it into a project-local Appium home. The driver version sitting in a config block rather than in the dependency list is its own decision, since nothing in the manifest's dependency sections constrains it. The practical difference between the README's description and the manifest's is who supplies the server: one says it is included, the other says you have to provide it and the project only works with a compatible major version.
A video path is documented as unwired, and its dependency is still in the manifest
One of the iOS screen capture routes is switched off in the documentation while remaining in the build. The MJPEG server section is marked as temporarily suspended, and the explanation is a migration: after the move to a standalone Appium, the WDA-MJPEG video path is not wired up and is planned to be restored in a follow-up, so iOS screen casting currently runs through the ws-qvh binary instead. The feature is still described well enough to use, including the build configuration flag that enables it, and it is pitched honestly on its own terms: it needs no extra software where ws-qvh does, at the cost of encoding every frame as a JPEG image and the resources that go with it. What is left behind is the dependency. node-mjpeg-proxy sits in the runtime dependencies at a 0.3 line, so an npm install still pulls a proxy server for a code path the documentation says is disconnected.
Eight control features on Android, three on iOS
The two platforms get very different feature lists, and the difference is worth reading as a statement about the two control stacks rather than about the devices. Android offers touch events including multi-touch, an explicit multi-touch emulation scheme where CTRL anchors at the centre of the screen and SHIFT with CTRL anchors at the current point, mouse wheel and touchpad scrolling, captured keyboard events, injected text, clipboard copy in both directions, and device rotation. iOS offers simple touch, scroll or swipe, and a home button click. Two Android limits are stated rather than implied: injected text is ASCII only, and clipboard transfer and keyboard capture are listed as features without any qualification about which input methods they cover. The iOS path has a different kind of limit. WebDriverAgent has to be built, signed and trusted on the device once, with your team and a unique bundle identifier, and the file warns that a free Apple developer account's provisioning expires every seven days, so it has to be re-signed weekly.
xcodebuild exit code 65 is documented as a signing failure, not a build failure
The iOS section includes a troubleshooting note that is more useful than most of the documentation around it. An xcodebuild failure with code 65, it says, almost always means WebDriverAgent could not be signed or launched on the device, with four named causes: an untrusted certificate, Developer Mode off, a wrong team ID, or expired provisioning. It adds the sentence that matters for anyone triaging this from the outside, that this is not a build error in ws-scrcpy. The configuration table around it explains how those four causes get introduced. A team ID is the Apple team identifier used to sign WebDriverAgent and matches the certificate's organisational unit, the signing identity defaults to Apple Development, and the bundle identifier is expected to be unique per machine, in the form com.something.WebDriverAgentRunner. There is also a switch to reuse an already built and installed WebDriverAgent instead of rebuilding it, which is the setting to reach for once the one-time setup has been done.
A markdown heading sits inside the shell block in the build instructions
The build section is one fenced shell block, and inside it, between the clone and the install, there is a line that begins with two hash characters and reads like a section heading. Rendered as written, it is a shell comment that tells the reader that for a stable version you should list the tags and check one out, with both of those commands commented out. It is a small thing that suggests the instructions were assembled rather than composed, and it is the kind of detail that makes a newcomer wonder whether the rest of the file has been reviewed as carefully. The same section has a larger inconsistency. The requirements say Node.js v10 or later, while the development dependencies carry type definitions for Node 12 and the Express dependency is on the 4.22 line. A documented floor that sits below what the pinned dependencies assume is the kind of gap that turns into a confusing first install.
The browser gets an adb shell, an APK drop target and file upload
The Android feature list is what makes this worth thinking about before you deploy it. Beyond the picture, the browser can drive the device's clipboard and rotation, push an APK by drag and drop into a directory on the device, install it by hand from a terminal emulator built into the page, and list, upload and download files. There is a remote shell section whose whole description is that you can control the device from adb shell in your browser. So the page is not a screen mirror with a few buttons on it; it is a file transfer channel and a shell, rendered in a browser tab, talking to a device you have adb access to. The repository ships a security policy file at the root, which is the right place for that conversation to start. The iOS side has its own version of the same constraint, stated as a tip rather than a warning: only one process may hold a device, so a standalone capture running against the same phone will break the stream.
Editorial conclusion
ws-scrcpy is worth trying if you want a phone on a browser screen over a network you control, and it is not a finished product: the repository calls itself a prototype, npm test is a placeholder that exits with status 1, and there is no test suite to look at. Three things to know before you rely on it. iOS is experimental, is not built by default, and its control path needs a WebDriverAgent build signed with your own Apple team, where free provisioning expires every seven days. The app shell it exposes is a real adb shell with file push and upload, so treat the server as privileged and put it where that matters. And decide your Node version from the dependencies rather than the requirements list, which still says v10 while the pinned type definitions and the Express line assume something later.
Frequently asked questions
What does ws-scrcpy do?
It is a web client for the Android screen mirroring tool scrcpy. A modified build of scrcpy streams H264 video from the device, and the browser decodes it with one of four players included in the repository.
What do I need to run ws-scrcpy?
On the server, Node.js, node-gyp and an adb executable available in PATH. On the device, Android 5.0 or later with adb debugging enabled, plus one extra setting on some devices for keyboard and mouse control. The browser needs WebSockets, h264 decoding, WebWorkers and WebAssembly.
Which video decoders does ws-scrcpy include?
Four. A Media Source player based on h264-converter that needs the Media Source API and one specific codec string, two WebAssembly players based on Broadway and tinyh264, and a WebCodecs player that uses the browser's own decoder and is listed as available only in Chromium and its derivatives.
Can ws-scrcpy control an iOS device?
As an experimental feature that is not built by default. Screen casting needs a ws-qvh binary available in PATH, and control goes through a bundled Appium server speaking to WebDriverAgent. The supported actions are simple touch, scroll or swipe, and the home button.
Does ws-scrcpy have automated tests?
No. The package's test script is the generated placeholder that prints an error message and exits with status 1, and there is no test directory in the repository. What is configured instead is linting, through an eslint script run over the TypeScript sources.
Which version of Node.js does ws-scrcpy support?
The requirements section says Node.js v10 or later. The development dependencies carry type definitions for Node 12, and the Express dependency is on the 4.22 line, so the documented floor sits below what the pinned dependencies assume.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/netristv-ws-scrcpy)