I built 118+ device tests that run entirely in your browser — no server, no uploads

I built 118+ device tests that run entirely in your browser — no server, no uploads

Every time I needed to check whether my microphone actually worked before a call, or whether a key on a used keyboard was dead, I ended up on some sketchy "free online tester" that wanted an account, showed six ad banners, and — the worst part — happily streamed my webcam to a server I knew nothing about. So I built the thing I actually wanted: TestOnDevice — 118+ device tests that run 100% in the browser. No installs, no account, and nothing you record ever leaves your machine. Here's what I learned building it. The core constraint: the network is off-limits The single rule that shaped the whole project: no device data touches a server. No webcam frames, no audio buffers, no keystrokes, no sensor readings. That constraint turned out to be freeing rather than limiting, because modern browser APIs are genuinely powerful. Almost every "device test" is just a Web API plus a bit of rendering: What you're testing The API doing the work Microphone / speakers MediaDevices.getUserMedia, Web Audio (AnalyserNode) Webcam getUserMedia, , MediaStreamTrack.getSettings() Keyboard keydown / keyup, KeyboardEvent.code Mouse / touch / stylus Pointer Events, PointerEvent.pressure Gamepads Gamepad API MIDI keyboards Web MIDI API Display (dead pixels, refresh rate) Fullscreen + requestAnimationFrame Motion / orientation DeviceOrientationEvent, DeviceMotionEvent A live mic meter, entirely local The microphone test is a good example of how little you need. Grab a stream, wire it into an AnalyserNode, and compute RMS on each frame for a level meter — no recording, no upload: const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); const ctx = new AudioContext(); const analyser = ctx.createAnalyser(); ctx.createMediaStreamSource(stream).connect(analyser); const data = new Uint8Array(analyser.frequencyBinCount); function tick() { analyser.getByteTimeDomainData(data); let sum = 0; for (const v of data) { const x = (v - 128) / 128; sum += x * x; } drawMeter(Math.sqrt(sum / data.length)); // RMS -> level bar requestAnimationFrame(tick); } tick(); Enter fullscreen mode Exit fullscreen mode The most important line, though, is the cleanup. The moment the user stops or leaves, the hardware indicator light should go off: stream.getTracks().forEach((track) => track.stop()); Enter fullscreen mode Exit fullscreen mode Releasing tracks aggressively is what makes a diagnostics tool feel trustworthy instead of creepy. Gamepads: no events, only polling The Gamepad API surprised me. You get a gamepadconnected event, but after that there are no button events at all — you have to poll the current state every frame: window.addEventListener("gamepadconnected", (e) => { console.log("connected:", e.gamepad.id); }); function poll() { for (const pad of navigator.getGamepads()) { if (!pad) continue; pad.buttons.forEach((b, i) => b.pressed && highlight(i)); // Analog sticks: pad.axes[0], pad.axes[1], ... } requestAnimationFrame(poll); } poll(); Enter fullscreen mode Exit fullscreen mode That polling model is exactly why it's great for detecting stick drift: read pad.axes while the sticks are untouched and watch whether the values sit at zero or quietly wander. If you want to see it raw, the controller input viewer is just a live JSON dump of every button and axis. The permissions gotcha that bites everyone enumerateDevices() will happily list your cameras and microphones before you grant permission — but the label field is an empty string until you do: const devices = await navigator.mediaDevices.enumerateDevices(); // device.label is "" until the user grants permission to that kind of device Enter fullscreen mode Exit fullscreen mode This is a deliberate anti-fingerprinting measure. The UX fix is simple: show generic entries ("Microphone 1", "Camera 1") first, then re-enumerate and reveal real names after the user opts in. I ended up building a whole device enumerator around exactly this behavior. Privacy as an architecture, not a promise "We respect your privacy" in a footer is worthless. What actually earns trust: No backend for device data. There's no endpoint to upload frames to, so there's nothing to leak. Late permission requests. Access is only ever asked for after an explicit click on that specific test. Immediate teardown. Streams are released on stop and on page unload. No account, no fingerprint. Nothing to correlate you across sessions. Because there's no server round-trip, most tests also work as a PWA offline — handy on a fresh machine with no drivers yet installed. Takeaways If you're building anything hardware- or media-adjacent for the web: Reach for platform APIs before libraries — getUserMedia, Gamepad, Web MIDI, Pointer Events and the sensor APIs cover an enormous amount of ground. Treat stream cleanup as a first-class feature, not an afterthought. Let real privacy fall out of the architecture. "No server" is a stronger guarantee than any policy page. You can try the whole thing here: testondevice.com. Start with the Quick Check if you just want a fast pass over everything, or browse all tests — mic, camera, keyboard, mouse, gamepad, MIDI, dead-pixel and refresh-rate — all running locally in your tab. If you want a deep dive into any single one (the refresh-rate detector and the dead-pixel finder both have some fun edge cases), let me know in the comments and I'll write it up.

Original Source

Read the full article at Dev →

KhanList aggregates and links to publicly available news content. We do not host full articles from third-party sources. Always verify important information with original sources.