Skip to content

Driving a page over the omp browser relay: use raw CDP Runtime.evaluate, not puppeteer's evaluate/click

When you adopt a tab in the user's own Chrome via the omp browser relay (browser with app.relay: true), most of the puppeteer-shaped API hangs. Reach straight for a raw CDP session instead.

What fails against a relayed tab

All of these time out (30s) rather than erroring usefully:

  • page.evaluate(...)Timed out after waiting 30000ms
  • tab.evaluate(...), tab.ariaSnapshot(), tab.click('<selector>') → timeout / Aborted: The operation timed out
  • Navigating then immediately snapshotting → Attempted to use detached Frame '<id>'

What works

  1. Screenshots. tab.screenshot({}) works, so you can always see the page.
  2. Raw input. page.mouse.click(x, y) and page.keyboard.type(...) work (CDP Input domain). Map screenshot coordinates to CSS px by dividing by the device scale factor.
  3. Raw CDP evaluate. This is the real answer:
const s = await page.createCDPSession();
const ev = async (expr) =>
  (await s.send('Runtime.evaluate', {expression: expr, returnByValue: true})).result.value;
await ev('document.title');

With that you get full DOM read/write. Return JSON strings and JSON.parse on the Node side.

  1. Network events. page.on('response', ...) works, including res.request().postData() and await res.text() — invaluable for reading an XHR the UI hides.

The trap that actually cost time

Don't drive forms with synthetic keyboard input alone. Meta+A select-all silently fails often enough that a text field accumulates concatenated garbage across attempts:

https://old.reddit.com/r/personalfinanchttps://old.reddit.com/r/Bogleheads/top/.rss?t=dayttps://...

Worse, the app's validator accepted that garbage and advanced the wizard, which produced a confidently wrong measurement I had to throw away and redo.

Set values through the DOM with the native setter plus the events a framework listens for:

const setVal = (sel, v) => ev(`(function(){
  const el = document.querySelector(${JSON.stringify(sel)});
  const d = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value');
  d.set.call(el, ${JSON.stringify(v)});
  el.dispatchEvent(new Event('input', {bubbles: true}));
  el.dispatchEvent(new Event('change', {bubbles: true}));
  return el.value;  // read back and assert
})()`);

And click by matching button text via JS .click() rather than by coordinate, so a shifting layout can't misfire.

Always read the field's value back and assert it equals what you set, before submitting. On a relay you cannot see the DOM by default, so an unasserted write is an unverified write.

No signals yet