Plugin / Docs / Real-World Flows (E2E)

Real-World Flows (E2E) — no heavy tooling

Run realistic journeys from your admin page: navigate → wait → type → click → verify.

Runs locally in the browser · canonical RTO_REQUEST/RTO_RESPONSE · respects the Allow-list

v7.10.1

1) Minimal request helper (canonical envelopes)

One tiny function you can reuse in your pages. It posts RTO_REQUEST with action/args and resolves the paired RTO_RESPONSE (ok / data / error). The askBackoff helper retries transient failures.

<script>
const ADMIN_ORIGIN='admin', EXT_ORIGIN='extension';
let _seq=0, _waiters=new Map();

addEventListener('message', (e) => {
  const d=e.data; if(!d||typeof d!=='object') return;
  if(d.origin!==EXT_ORIGIN) return;
  if(d.requestId && _waiters.has(d.requestId)) { _waiters.get(d.requestId)(d); _waiters.delete(d.requestId); }
});

function ask(action, args={}, timeoutMs=12000){
  const requestId='r'+(++_seq);
  postMessage({ origin:ADMIN_ORIGIN, type:'RTO_REQUEST', requestId, action, args }, '*');
  return new Promise((resolve,reject)=>{
    const t=setTimeout(()=>{ _waiters.delete(requestId); reject(Object.assign(new Error('timeout'),{ code:'TIMEOUT' })); }, timeoutMs);
    _waiters.set(requestId, (resp)=>{ clearTimeout(t); resolve(resp); });
  });
}

async function askBackoff(action, args={}, tries=3, base=300){
  let n=0;
  while(true){
    try{
      const r = await ask(action, args, 12000);
      if(!r.ok) throw Object.assign(new Error(r.error?.message||'error'), r.error||{});
      return r;
    }catch(e){
      if(++n>=tries) throw e;
      await new Promise(r=>setTimeout(r, base*Math.pow(2,n-1)));
    }
  }
}

// Optional: mark this admin tab as "master" so the extension can refocus it via focusMaster
try { postMessage({ origin:ADMIN_ORIGIN, type:'RTO_REQUEST', requestId:'r0', action:'setMasterTab', args:{} }, '*'); } catch(_){}
</script>

2) What does “E2E” mean in RTO?

Plain meaning: run a user path in a real tab that you control through the extension — from your own admin page.

  • Scope: open/navigate/focus → type/click/select/submit → read URL/title → optional small extraction via runJs.
  • Local: everything runs in your browser (no grid, no remote server, no extra backend).
  • Safety: actions run on allow-listed hosts; sensitive fields are guarded.
  • Not a test framework: you add your own assertions; RTO is the automation bridge.

Stable errors: DOMAIN_NOT_ALLOWED, NO_CONTROLLED_TAB, ELEMENT_NOT_FOUND, TIMEOUT, EXECUTION_ERROR, FORBIDDEN, EXT_NOT_DETECTED.

3) Prerequisites — “why does nothing happen?”

4) Message contract (canonical)

Request (Admin → Extension)
{
  "origin": "admin",
  "type": "RTO_REQUEST",
  "requestId": "r123",
  "action": "navigate",
  "args": { "url": "https://example.org", "focus": true }
}
Response (ok)
{
  "origin": "extension",
  "type": "RTO_RESPONSE",
  "requestId": "r123",
  "ok": true,
  "data": { "tabId": 42, "url": "https://example.org" }
}
Response (error)
{
  "origin": "extension",
  "type": "RTO_RESPONSE",
  "requestId": "r123",
  "ok": false,
  "error": { "code": "DOMAIN_NOT_ALLOWED", "message": "Host not in allow-list" }
}
See Diagnostics and API pages for the error cookbook and return shapes.

5) Copy-paste flows (navigate → wait → type → click → verify)

A. Minimal journey
<script type="module">
(async () => {
  // Warm the message path (or use a detect call if available)
  await askBackoff('runJs', { code:'true' }, 1, 1);

  await ask('navigate', { url:'https://example.org', focus:true });
  await ask('waitFor',  { selector:'#q', timeoutMs:20000 });
  await ask('domType',  { selector:'#q', value:'rto demo' });
  await ask('domClick', { selector:'form button[type=submit]' });

  const r = await ask('getCurrentUrl');
  console.log('Now at:', r.ok ? r.data?.url : '(unknown)');
})();
</script>
B. Public “login” form with resilient waits
<script type="module">
(async () => {
  await askBackoff('navigate', { url:'https://site.example/login', focus:true }, 2, 250);
  await ask('waitFor',   { selector:'#email', timeoutMs:20000 });
  await ask('domType',   { selector:'#email',    value:'user@example.org' });
  await ask('domType',   { selector:'#password', value:'s3cret' });
  await ask('domClick',  { selector:'form button[type=submit]' });

  // Wait for a dashboard marker with backoff
  await askBackoff('waitFor', { selector:'.dashboard', timeoutMs:25000 }, 3, 300);
})();
</script>
C. Multi-tab & focus strategy
<script type="module">
(async () => {
  await ask('openTab', { url:'https://site-a.example', focus:true,  newTab:true, tabKey:'a' });
  await ask('openTab', { url:'https://site-b.example', focus:false, newTab:true, tabKey:'b' });

  await ask('focus', { tabKey:'b' }); // bring tab 'b' to front
  // Or go back to this admin page:
  await ask('focusMaster');
})();
</script>
D. Extract text/HTML via runJs
<script type="module">
(async () => {
  await ask('navigate', { url:'https://blog.example.org/post/123' });

  const title = await ask('runJs', { code:'(document.querySelector("h1")||{}).textContent||""' });
  const html  = await ask('runJs', { code:'(document.querySelector(".content")||{}).innerHTML||""' });

  console.log('Title:', title.ok ? title.data : '');
  console.log('HTML :', html.ok ? html.data : '');
})();
</script>

6) Simple assertions (optional)

Keep assertions in your admin page. Mix getCurrentUrl/getTitle with targeted waitFor and bounded timeouts.
<script type="module">
async function expectExists(selector, timeoutMs = 12000) {
  const t0 = Date.now();
  while (Date.now() - t0 < timeoutMs) {
    const r = await ask('runJs', { code:`!!document.querySelector(${JSON.stringify(selector)})` });
    if (r.ok && r.data) return true;
    await new Promise(r => setTimeout(r, 300));
  }
  const err = new Error('ELEMENT_NOT_FOUND');
  err.code='ELEMENT_NOT_FOUND';
  err.selector=selector;
  throw err;
}
</script>

7) Best practices (stability & safety)

  • Deny-by-default: add only the hosts you need in the Allow-list.
  • Assert before act: check getCurrentUrl and waitFor a concrete marker; avoid arbitrary sleeps.
  • Timeouts: 12–30s for heavy SPAs; wrap critical steps with askBackoff.
  • Selectors: prefer stable (#id, [data-testid], aria-*).
  • Focus hygiene: use suppressNextFocus for one-shot silent navigations; focusMaster to come back here.
  • Sensitive data: password-like inputs are guarded; don’t try to exfiltrate secrets.

8) Troubleshooting & error handling

Error cookbook (quick map)
switch (err.code) {
  case 'DOMAIN_NOT_ALLOWED': /* add host to Allow-list (popup) */ break;
  case 'NO_CONTROLLED_TAB' : /* call openTab/navigate first */ break;
  case 'ELEMENT_NOT_FOUND' : /* fix selector or waitFor */ break;
  case 'TIMEOUT'           : /* increase timeout or add backoff */ break;
  case 'EXECUTION_ERROR'   : /* check page console; guard nulls */ break;
  case 'FORBIDDEN'         : /* sensitive field blocked; redesign the step */ break;
  case 'EXT_NOT_DETECTED'  : /* install/enable the extension */ break;
}
Use the Diagnostics page’s inline console to see live events & payloads.
Allow-list banner pattern

If an action yields DOMAIN_NOT_ALLOWED, show a friendly hint and provide a “Try again”.

function handleActionError(e, url){
  const code = e?.code || e?.message || '';
  if (String(code).includes('DOMAIN_NOT_ALLOWED')){
    const host = (()=>{ try { return new URL(url).host; } catch(_) { return ''; } })();
    const box = document.getElementById('allow-box');
    if (box){ box.classList.remove('d-none'); box.querySelector('.domain').textContent = host; }
  } else {
    console.warn('Action failed:', e);
  }
}

Next pages

Page Description
Start Overview, how it works, setup, quick start, and links to all sections.
Detect the Extension Detection contract (ping/pong), install prompts, and graceful fallbacks when the extension isn’t present.
Allow-list & Permissions Deny-by-default model, domain allow-list via popup, and suggested UX to guide users to enable hosts.
Remote Tab Control Open/navigate/focus the controlled tab, read URL/title, select an existing tab, lifecycle & restore patterns.
DOM & Automation Action catalog (domType, domSetValue, domClick, selectSetValue, submitForm, waitFor) and safe interaction tips.
Recipes & Flows Copy-paste flows: resilient login, dashboard checks, filters, playlists, and allow-list banners.
Events Event stream reference, subscription helpers, live logger widget, and basic metrics/durations collection.
Diagnostics Inline console, error cookbook, QA self-check, and performance tips for troubleshooting flows quickly.
Full API Reference Canonical message envelopes, request/response schemas, error codes, and return shapes for every action.
Compatibility Supported browsers/versions, permissions nuances, CSP/iframe notes, and known limitations or workarounds.
Advanced Patterns Idempotent flows, retries/backoff, state restore after reload, playlist strategies, and robust selectors.
Favorites Star and group URLs, quick actions (open/focus/navigate), export/import sets, and playlist runs from favorites.
Real-World Flows (E2E) Run end-to-end flows from your admin page with safe, stable message contracts and resilient waits.