Bug Report: Cannot submit Ember.js modal forms — framework ignores all programmatic input
Severity: High — blocks real-world automation of any Ember.js app
Component: Browser Harness (helpers.py)
Reproduction Rate: 100%
Platform: Headless Chrome on Xvfb :99, Ubuntu 24.04
Summary
Browser Harness cannot fill and submit forms inside Ember.js 3.x modal dialogs. The Ember framework maintains internal two-way data binding state that is decoupled from the DOM. Setting the DOM value property (via js(), type_text(), cdp("Input.insertText"), or cdp("Input.dispatchKeyEvent")) updates the visible input but does not update Ember's internal tracked state. When the submit button is clicked, Ember validates its own state (not the DOM), finds the field empty, and silently refuses to fire the network request.
This is not a CAPTCHA or anti-bot challenge — it's a framework architecture issue where Ember's data binding is the source of truth, not the DOM.
Environment
- Browser Harness: v0.1.0 (
71f1b3b)
- Chrome: Headless via
chrome-harness systemd service, port 9333
- Display: Xvfb :99, 1920x1080x24
- Target site:
https://www.linkedin.com/developers/apps/230849049/products/mdp-settings
- Ember version: 3.28.12
- Component library: Artdeco (LinkedIn's design system)
Steps to Reproduce
-
Start browser harness:
bh <<'EOF'
goto("https://www.linkedin.com/developers/apps/230849049/products/mdp-settings")
wait_for_load()
wait(3)
# Open the "Add Ad Account" modal
js("[...document.querySelectorAll('button')].find(b=>b.textContent.includes('Add Ad Account'))?.click()")
wait(3)
# Fill the input (Ember TextField component, id="adAccountInput")
js("document.querySelector('#adAccountInput').focus()")
wait(0.5)
cdp("Input.insertText", text="517969837")
wait(1)
# Verify DOM value is set
val = js("document.querySelector('#adAccountInput').value")
print(f"DOM value: {val}") # Prints: 517969837 ✓
# Click Save
js("[...document.querySelectorAll('button')].find(b=>b.textContent.includes('Save Ad Account'))?.click()")
wait(8)
EOF
-
Observe: Dialog remains open. No network request fired. No error shown.
What Was Tried (Exhaustive List)
All methods below successfully set input.value in the DOM, but none updated Ember's internal binding state:
| # |
Method |
DOM Updated |
Ember State Updated |
Network Request |
| 1 |
js("el.value = '517969837'") |
✅ |
❌ |
❌ |
| 2 |
cdp("Input.insertText", text="517969837") |
✅ |
❌ |
❌ |
| 3 |
cdp("Input.dispatchKeyEvent") char-by-char |
✅ |
❌ |
❌ |
| 4 |
type_text("517969837") |
✅ |
❌ |
❌ |
| 5 |
el.dispatchEvent(new Event('input')) |
✅ |
❌ |
❌ |
| 6 |
el.dispatchEvent(new Event('change')) |
✅ |
❌ |
❌ |
| 7 |
Playwright el.fill("517969837") |
✅ |
❌ |
❌ |
| 8 |
Playwright el.type("517969837", delay=50) |
✅ |
❌ |
❌ |
| 9 |
Ember.set(model, 'adAccountData', ['517969837']) |
N/A |
Partial* |
❌ |
| 10 |
CDP DOM.focus + Input.insertText |
✅ |
❌ |
❌ |
| 11 |
cdp("Input.dispatchMouseEvent") coordinate click on Save |
✅ |
❌ |
❌ |
| 12 |
el.dispatchEvent(new MouseEvent('click')) on Save |
✅ |
❌ |
❌ |
| 13 |
Playwright save_btn.click() |
✅ |
❌ |
❌ |
| 14 |
Playwright save_btn.dispatch_event('click') |
✅ |
❌ |
❌ |
| 15 |
Enter key via cdp("Input.dispatchKeyEvent", key="Enter") |
✅ |
❌ |
❌ |
| 16 |
Ember.View.views[id]._actions.save.call(view) |
N/A |
N/A |
N/A** |
* Ember.set updated the route controller's model property, but the save button is handled by a child component (not the route/controller).
** No Ember.View.views entries exist for any element on the page — Glimmer components don't use the legacy view registry.
Root Cause Analysis
The LinkedIn developer portal uses Ember.js 3.28.12 with Glimmer components (not legacy Ember.Component). The form dialog is structured as:
route: apps.app.products.mdp-settings
└─ component: artdeco-modal
└─ component: inputs/input-textbox (id="adAccountInput")
└─ component: artdeco-modal-footer (Save button)
Glimmer components in Ember 3.x have the following properties that prevent harness automation:
-
No DOM↔state sync. The inputs/input-textbox component uses Ember's {{input}} helper which binds value via a tracked property (@value). Setting input.value directly does not trigger the tracked setter. The component's internal @value remains undefined.
-
No legacy view registry. Ember.View.views is empty — Glimmer components don't register there, so there's no way to find component instances via DOM traversal.
-
No __ember_meta__ on DOM nodes. Unlike older Ember, Glimmer doesn't attach metadata to DOM elements, so there's no way to discover component instances from the DOM.
-
Button click is gated on form state. The Save button's click handler checks this.value (the component's tracked property, not input.value). Since tracked state was never set, the handler returns early without making any network request. No error is thrown — it's a silent no-op.
-
No fetch/XHR interception possible. Since the handler returns before making any request, there's no network call to intercept or replay.
Suggested Fixes
Option A: Add a fill_ember_input helper (recommended)
Ember's {{input}} helper listens for the browser's native input event but only if it originates from real keyboard interaction. A helper that:
- Focuses the element via
DOM.focus CDP
- Selects all text (
Ctrl+A → Backspace)
- Types each character with proper
keydown → keypress → input → keyup sequence
- Uses real
KeyboardEvent objects with correct keyCode, which, and charCode
...would likely pass Ember's event validation. The key difference from current type_text is that Ember checks event.isTrusted and/or event.originalEvent in some code paths.
def fill_ember_input(selector, text):
"""Fill an Ember.js bound input field."""
js(f"document.querySelector('{selector}').focus()")
wait(0.3)
# Clear
cdp("Input.dispatchKeyEvent", type="keyDown", key="a", modifiers=2)
cdp("Input.dispatchKeyEvent", type="keyUp", key="a", modifiers=2)
cdp("Input.dispatchKeyEvent", type="keyDown", key="Backspace")
cdp("Input.dispatchKeyEvent", type="keyUp", key="Backspace")
wait(0.2)
# Type each char with full event sequence
for char in text:
cdp("Input.dispatchKeyEvent", type="keyDown", key=char, text=char, unmodifiedText=char)
cdp("Input.dispatchKeyEvent", type="char", text=char, unmodifiedText=char)
cdp("Input.dispatchKeyEvent", type="keyUp", key=char, text=char, unmodifiedText=char)
wait(0.05)
Option B: Expose Ember.set as a first-class helper
For Ember apps where Option A doesn't work (some newer Glimmer components don't listen to DOM events at all), a helper that directly calls into Ember's tracked system:
def ember_set_value(input_selector, value):
"""Set a value on an Ember.js bound input through the framework."""
js(f"""
(() => {{
const inp = document.querySelector('{input_selector}');
if (!inp) return false;
// Find Ember app instance
let app = null;
Ember.Namespace.NAMESPACES.forEach(ns => {{
if (ns.__container__) app = ns;
}});
if (!app) return false;
// Walk up to find the component's tracked property owner
const container = app.__container__;
const router = container.lookup('router:main');
const ctrl = container.lookup('controller:' + router.currentRouteName);
const model = ctrl.get('model');
// Set through Ember's tracked system
Ember.set(model, 'adAccountData', ['{value}']);
// Also set DOM for visual confirmation
inp.value = '{value}';
inp.dispatchEvent(new Event('input', {{bubbles: true}}));
return true;
}})()
""")
Option C: Support Chrome DevTools Protocol DOM.setNodeValue with form submission interception
The most robust approach: intercept the network request the form would make, then replay it with correct cookies/CSRF. This requires:
- A helper that extracts session cookies and CSRF tokens
- A helper that monitors
fetch/XHR during form interaction
- If no request fires, construct and send the expected request manually
Reproduction URL
The LinkedIn Developer Portal is freely accessible (any LinkedIn account):
No API keys or paid services required to reproduce.
Additional Context
I spent ~2 hours on this with 16 distinct approaches. The owner's claim that browser-harness "can accomplish anything" is aspirational, but Ember.js (and similar frameworks like Angular with NgModel) intentionally decouple state from DOM as a design feature. Without framework-aware helpers, the harness is limited to what cdp("Input.insertText") can achieve — and that's not enough when the framework is the source of truth.
The LinkedIn Developer Portal also serves as a good stress test because it combines:
- Ember.js 3.x (state ≠ DOM)
- Artdeco component library (custom modal/form behavior)
- Anti-automation (CSRF tokens, session validation)
- Real-world complexity (multi-step OAuth → developer portal → modal form)
If you can make this work, you'll have proven the harness can handle any modern SPA framework.
Bug Report: Cannot submit Ember.js modal forms — framework ignores all programmatic input
Severity: High — blocks real-world automation of any Ember.js app
Component: Browser Harness (
helpers.py)Reproduction Rate: 100%
Platform: Headless Chrome on Xvfb :99, Ubuntu 24.04
Summary
Browser Harness cannot fill and submit forms inside Ember.js 3.x modal dialogs. The Ember framework maintains internal two-way data binding state that is decoupled from the DOM. Setting the DOM
valueproperty (viajs(),type_text(),cdp("Input.insertText"), orcdp("Input.dispatchKeyEvent")) updates the visible input but does not update Ember's internal tracked state. When the submit button is clicked, Ember validates its own state (not the DOM), finds the field empty, and silently refuses to fire the network request.This is not a CAPTCHA or anti-bot challenge — it's a framework architecture issue where Ember's data binding is the source of truth, not the DOM.
Environment
71f1b3b)chrome-harnesssystemd service, port 9333https://www.linkedin.com/developers/apps/230849049/products/mdp-settingsSteps to Reproduce
Start browser harness:
Observe: Dialog remains open. No network request fired. No error shown.
What Was Tried (Exhaustive List)
All methods below successfully set
input.valuein the DOM, but none updated Ember's internal binding state:js("el.value = '517969837'")cdp("Input.insertText", text="517969837")cdp("Input.dispatchKeyEvent")char-by-chartype_text("517969837")el.dispatchEvent(new Event('input'))el.dispatchEvent(new Event('change'))el.fill("517969837")el.type("517969837", delay=50)Ember.set(model, 'adAccountData', ['517969837'])DOM.focus+Input.insertTextcdp("Input.dispatchMouseEvent")coordinate click on Saveel.dispatchEvent(new MouseEvent('click'))on Savesave_btn.click()save_btn.dispatch_event('click')cdp("Input.dispatchKeyEvent", key="Enter")Ember.View.views[id]._actions.save.call(view)*
Ember.setupdated the route controller's model property, but the save button is handled by a child component (not the route/controller).** No
Ember.View.viewsentries exist for any element on the page — Glimmer components don't use the legacy view registry.Root Cause Analysis
The LinkedIn developer portal uses Ember.js 3.28.12 with Glimmer components (not legacy
Ember.Component). The form dialog is structured as:Glimmer components in Ember 3.x have the following properties that prevent harness automation:
No DOM↔state sync. The
inputs/input-textboxcomponent uses Ember's{{input}}helper which bindsvaluevia a tracked property (@value). Settinginput.valuedirectly does not trigger the tracked setter. The component's internal@valueremainsundefined.No legacy view registry.
Ember.View.viewsis empty — Glimmer components don't register there, so there's no way to find component instances via DOM traversal.No
__ember_meta__on DOM nodes. Unlike older Ember, Glimmer doesn't attach metadata to DOM elements, so there's no way to discover component instances from the DOM.Button click is gated on form state. The Save button's click handler checks
this.value(the component's tracked property, notinput.value). Since tracked state was never set, the handler returns early without making any network request. No error is thrown — it's a silent no-op.No
fetch/XHRinterception possible. Since the handler returns before making any request, there's no network call to intercept or replay.Suggested Fixes
Option A: Add a
fill_ember_inputhelper (recommended)Ember's
{{input}}helper listens for the browser's nativeinputevent but only if it originates from real keyboard interaction. A helper that:DOM.focusCDPCtrl+A→Backspace)keydown→keypress→input→keyupsequenceKeyboardEventobjects with correctkeyCode,which, andcharCode...would likely pass Ember's event validation. The key difference from current
type_textis that Ember checksevent.isTrustedand/orevent.originalEventin some code paths.Option B: Expose
Ember.setas a first-class helperFor Ember apps where Option A doesn't work (some newer Glimmer components don't listen to DOM events at all), a helper that directly calls into Ember's tracked system:
Option C: Support Chrome DevTools Protocol
DOM.setNodeValuewith form submission interceptionThe most robust approach: intercept the network request the form would make, then replay it with correct cookies/CSRF. This requires:
fetch/XHRduring form interactionReproduction URL
The LinkedIn Developer Portal is freely accessible (any LinkedIn account):
{{input}}helperNo API keys or paid services required to reproduce.
Additional Context
I spent ~2 hours on this with 16 distinct approaches. The owner's claim that browser-harness "can accomplish anything" is aspirational, but Ember.js (and similar frameworks like Angular with
NgModel) intentionally decouple state from DOM as a design feature. Without framework-aware helpers, the harness is limited to whatcdp("Input.insertText")can achieve — and that's not enough when the framework is the source of truth.The LinkedIn Developer Portal also serves as a good stress test because it combines:
If you can make this work, you'll have proven the harness can handle any modern SPA framework.