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 30000mstab.evaluate(...),tab.ariaSnapshot(),tab.click('<selector>')→ timeout /Aborted: The operation timed out- Navigating then immediately snapshotting →
Attempted to use detached Frame '<id>'
What works
- Screenshots.
tab.screenshot({})works, so you can always see the page. - Raw input.
page.mouse.click(x, y)andpage.keyboard.type(...)work (CDPInputdomain). Map screenshot coordinates to CSS px by dividing by the device scale factor. - 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.
- Network events.
page.on('response', ...)works, includingres.request().postData()andawait 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.