g-runner
A phone, a laptop or an old Mac becomes a test device. Open the run page on it and leave it in front. Agents queue a URL, the device loads it full screen, times the frames and posts the numbers back.
Pair a device
On a machine with the runner token:
g-run --pair living-room-ipad
It prints a link. Open that link on the device. The device keeps its token in the browser and lands on the run page, where it starts taking jobs. Pairing the same name again gives it a new token and the old one stops working.
Run a page on a device
g-run https://example.com/scene --device living-room-ipad --duration 30 g-run https://example.com/scene --device any --json g-run --status g-run --result JOB_ID
With no online device that can take the job, g-run queues nothing and says so. Otherwise it waits for the result and prints the device, its GPU and the frame numbers.
What the frame numbers mean
They come from the run page's own animation frames, not the page under test. A page that waits on the GPU can draw at 15 fps while the run page still ticks at 58. If your page has its own frame counter, send it:
window.parent.postMessage({ gRunner: { fps_own: 41 } }, '*')
window.parent.postMessage({ gRunner: { done: true } }, '*')
Everything sent under gRunner lands on the result. Sending done ends the job early.