jackwener/OpenCLI · error · CommandExecutionError

Browser Bridge native click support is required

Error message

Browser Bridge native click support is required

What it means

CommandExecutionError thrown by the Midjourney CLI automation helpers when a browser automation page object does not implement nativeClick. The helper found a visible Midjourney control and computed its screen coordinates, but clicking it requires the Browser Bridge's OS-level (native) click API rather than synthetic DOM events, because some Midjourney UI controls ignore synthetic clicks. The library throws defensively instead of silently failing the interaction.

Solutions

  1. Upgrade/enable the Browser Bridge so the page object exposes nativeClick(x, y)
  2. Check feature support before calling: typeof page.nativeClick === 'function' and fall back to page.click on a marked selector otherwise
  3. Update the opencli client package to match the CLI's expected bridge API
  4. Fix test mocks/stubs to include a nativeClick no-op

Example fix

// before
await clickVisibleControl(page, 'Switch to Image');
// after
if (typeof page.nativeClick !== 'function') {
  throw new Error('This command requires Browser Bridge native click support; update your bridge client');
}
await clickVisibleControl(page, 'Switch to Image');
Defensive patterns

Strategy: type-guard

Validate before calling

if (typeof page.nativeClick !== 'function') {
  throw new Error('Browser Bridge nativeClick required; update your bridge client');
}

Type guard

const supportsNativeClick = (page) => typeof page?.nativeClick === 'function';

Try / catch

try {
  await clickVisibleControl(page, label);
} catch (e) {
  if (String(e.message).includes('native click support')) {
    throw new Error('Upgrade/enable Browser Bridge to use this command');
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling clickVisibleControl (e.g. clickComposerSubmit flows or 'Switch to Image') against a page object lacking a nativeClick function — typically when the page is driven by a plain automation client instead of the Browser Bridge-enabled bridge page.

Common situations: Using an older browser-bridge client version that predates nativeClick; constructing a fake/mock page in tests without nativeClick; pointing the CLI at a page wrapper from a different automation backend.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/6fa52fe6eacbad6c. Report an issue: GitHub.

Appendix: source

Thrown at clis/midjourney/utils.js:1241

}

export async function clickVisibleControl(page, label) {
  const target = unwrapEvaluateResult(await page.evaluate((wanted) => {
    const visible = (element) => {
      const rect = element.getBoundingClientRect();
      return rect.width > 0 && rect.height > 0 && getComputedStyle(element).display !== 'none';
    };
    const button = [...document.querySelectorAll('button')].find((node) => visible(node) && (
      node.textContent?.trim().replace(/\s+/g, ' ') === wanted
      || node.title === wanted
      || node.getAttribute('aria-label') === wanted
    ));
    if (!button || button.disabled) return null;
    const rect = button.getBoundingClientRect();
    return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
  }, label));
  if (!target) throw new CommandExecutionError(`Visible Midjourney control "${label}" was not found`);
  if (typeof page.nativeClick !== 'function') throw new CommandExecutionError('Browser Bridge native click support is required');
  await page.nativeClick(target.x, target.y);
}

export async function clickComposerSubmit(page) {
  const marked = unwrapEvaluateResult(await page.evaluate(() => {
    document.querySelectorAll('[data-opencli-composer-submit]').forEach((node) => {
      node.removeAttribute('data-opencli-composer-submit');
    });
    const visible = (element) => {
      const rect = element.getBoundingClientRect();
      return rect.width > 0 && rect.height > 0 && getComputedStyle(element).display !== 'none';
    };
    const input = document.querySelector('#desktop_input_bar');
    const row = input?.parentElement;
    if (!input || !row) return false;
    const textSubmit = [...row.querySelectorAll('button')]
      .find((button) => visible(button) && button.textContent?.trim() === 'Submit');
    const following = [...row.querySelectorAll('button')].filter((button) => (

View on GitHub (pinned to 49907e53dc)