Why I Started Running Frontend Smoke Tests Under Low‑Memory Cgroups (and the bug it actually caught)

I started running critical frontend flows inside memory‑limited cgroups to simulate low‑end Android devices. It caught a production crash — and introduced a new kind of CI noise.

Written by: Arjun Malhotra

Person typing on a laptop with code visible on the screen
Photo by Brooke Cagle on Unsplash

I was sitting in a client demo room in Bengaluru when the app froze. Not a graceful spinner or a retry — a hard, white screen that left the user having to force‑quit the WebView. The account was on a budget Android phone (told me later: a ₹7,000 hand‑me‑down), multiple tabs open, background apps chewing RAM. The same flow looked fine on my Pixel. That difference — “works on my phone” vs “crashes for real users” — is what made me stop trusting standard headless smoke tests.

We were shipping a heavy, client‑side page with a few large in‑memory caches. On flagship devices it was fine. On phones our Indian users actually use it on, it wasn’t. Reproducing that environment in CI felt impossible at first; buying a matrix of test phones is expensive and brittle. So I tried the cheaper route: force the test runner to behave like a low‑memory device.

Why memory, specifically? Because most low‑end Android crashes I see aren’t network latency or GPU glitches — they’re OOMs, GC stalls, or surprising allocations that trigger the worst user experience: a frozen WebView. If I can make our headless tests feel “memory‑tight”, I get a chance to catch the worst breakages before a client demo.

What I built (short and runnable) I keep a tiny wrapper that runs our Puppeteer/Playwright smoke script inside a Linux memory cgroup. You can do this on your laptop or a tiny VPS (I run it nightly on a ₹300/month VPS we already use for lightweight infra tests).

The idea is: run the exact same smoke flow we use for regular checks, but limit the process memory to mimic phones with ~300–500MB available to the browser.

Minimal example (what I actually used)

systemd-run —user —scope -p MemoryMax=400M node scripts/smoke-flow.js

Why systemd-run? It’s simple, no extra packages, works on CI runners and cheap VPSes. You can also use Docker (—memory) or cgexec if you prefer.

What this buys you

A real failure and an honest tradeoff This isn’t magical. The first month we ran memory‑limited smoke tests, we got a steady stream of failures in CI. Many of them were real, but a worrying number were false positives.

Why? Headless browsers in a cgroup aren’t the same as a WebView on a SoC. Headless Chrome can behave differently under low memory (different allocator behavior, different GPU usage). That produced two problems:

How I adjusted (so it stays useful)

The costs you accept

Small, India‑specific notes

What I walked away with Memory‑limited smoke tests don’t replace device farms. They catch the kind of allocation mistakes and cache‑hogs that cry out in low‑end phones — and they do it cheaply. The posture that saved us was simple: run small, deterministic critical flows under realistic resource caps, collect diagnostics, and keep the signal out of blocking PR checks.

If you’re worried about false positives, don’t gate your PRs on this. Run it nightly, make it easy to run locally, and use the failures to guide surgical fixes — not to punish developers for noisy CI.