DEV Community

Cover image for 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

KAAN TOKALI on July 21, 2026

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 ...
Collapse
 
nazar-boyko profile image
Nazar Boyko

The stick-drift detection is a neat use of the polling model. One thing worth watching with those requestAnimationFrame loops: rAF keeps firing while a gamepad or mic test is open, so if a user leaves the tab and comes back you can end up with a couple of loops running at once. Gating each on a "is this test active" flag and cancelling on teardown keeps it clean.

Collapse
 
kaan_tokali_5a4828a3f897c profile image
KAAN TOKALI

that's a solid fix. There's a cancelAnimationFrame on teardown, but the tab-switch remount case is exactly where a stale loop can slip through, so an explicit "is active" guard inside the loop is the safer approach. Thanks for the tip, I'll add it.

Collapse
 
voltagegpu profile image
VoltageGPU

Very cool project — it's great to see more tools prioritizing privacy by design. From an infrastructure angle, I'm curious how you handle GPU detection without relying on WebGL, which can be inconsistent across browsers. On VoltageGPU, we often run into similar challenges when trying to get accurate, cross-platform device info without introducing security risks.

Collapse
 
kaan_tokali_5a4828a3f897c profile image
KAAN TOKALI

Thanks! Mostly WebGPU's adapter.info and adapter limits, with WebGL as fallback. we only need rough capability tiers, not exact hardware. Still plenty of room to improve here, so open to suggestions.

Collapse
 
raju_dandigam profile image
Raju Dandigam

"Privacy as architecture, not a promise" is the right framing here. Once you make "nothing leaves the machine" a hard constraint, a lot of design decisions get cleaner because every new feature has to justify itself against local execution.

The permissions section also rings true. Browser-based device testing usually fails less on the API and more on the mismatch between what the user thinks they granted and what the page can actually access at that moment.

Have you found a good way to surface those permission-state transitions so failed tests read like evidence instead of just "mic unavailable"?

Collapse
 
kaan_tokali_5a4828a3f897c profile image
KAAN TOKALI

Thanks and yes, that mismatch is exactly the hard part. What helped most was treating permission state as a first-class part of the test result rather than an error case. Every test records the PermissionStatus.state at the moment it runs, subscribes to the change event, and stores the transition history alongside the outcome. So instead of "mic unavailable," the result reads "permission was prompt at start, user dismissed the dialog, NotAllowedError at t+4s."

Collapse
 
volod_isachenko profile image
Volodymyr Isachenko

118+? How much?

Collapse
 
kaan_tokali_5a4828a3f897c profile image
KAAN TOKALI

There are 118 tests, but I thought I might add more later. Since I wanted the text to be forward-looking, I didn't want it to end up incomplete.

Collapse
 
frank_signorini profile image
Frank

"I've been experimenting with similar browser-based diagnostics, but I'm curious how you handled audio and video input without relying on a server-side solution. Could you elaborate on the tech stack you used for those tests? I'm following you for more insights on browser-based testing"

Collapse
 
kaan_tokali_5a4828a3f897c profile image
KAAN TOKALI • Edited

Thanks Frank! There's really no server-side piece for the media — it's all client-side Web APIs. For audio I grab the stream with getUserMedia({ audio: true }) and run it through a Web Audio AnalyserNode for the live meter and the tone/frequency stuff. Video is the same getUserMedia piped into a

Collapse
 
merbayerp profile image
Mustafa ERBAY

Really like the “privacy by architecture” approach. One nuance I’d add is that “no backend” doesn’t completely eliminate fingerprinting concerns, since some browser APIs still expose characteristics that can contribute to passive fingerprinting. Not transmitting that data is a huge improvement, but it’s worth making that distinction. Also, a short section on browser compatibility (especially Safari/iOS) would make this an even stronger practical guide.

Collapse
 
kaan_tokali_5a4828a3f897c profile image
KAAN TOKALI

Good nuance, and you're right — "no backend" only kills the transmission risk, it doesn't make the browser un-fingerprintable. These APIs still leak device characteristics passively; the win is just that none of it gets collected or sent anywhere, not that the surface goes away. I should make that distinction clearer in the post. Same with compatibility — Safari/iOS has its own quirks around Web MIDI, autoplay and the gesture requirements, so I'll add a short browser-support section. Thanks, this is the kind of feedback that actually makes the writeup better.

Collapse
 
merbayerp profile image
Mustafa ERBAY

Good point about “privacy by architecture.” That’s much stronger than just saying “we respect your privacy.” Nice work.

Collapse
 
jianqiang_lou_46699ce6ff0 profile image
jianqiang lou

I think useful pages still win when the intent is specific enough. The tricky part is making the page actually solve the problem, not just target the keyword. People can feel that pretty fast.

Collapse
 
kaan_tokali_5a4828a3f897c profile image
KAAN TOKALI

Yeah, agreed. The keyword just gets someone to the page — after that it's on you to actually solve their problem fast, or they bounce. That's why I kept each test to one job and no signup wall. You can feel the difference right away, like you said. Thanks for reading.