angular/components · error · Error

setContenteditableValue can only be called on a `contentedit

Error message

setContenteditableValue can only be called on a `contenteditable` element.

What it means

ProtractorElement.setContenteditableValue in src/cdk/testing/protractor/protractor-element.ts implements TestElement's setContenteditableValue API. Before setting textContent via browser.executeScript it checks the element's contenteditable attribute; if the attribute is missing or not one of ''/true/plaintext-only, the element is not editable and the method throws this Error instead of silently writing to a non-editable node.

Source

Thrown at src/cdk/testing/protractor/protractor-element.ts:215

      return browser.executeScript(_getTextWithExcludedElements, this.element, options.exclude);
    }
    // We don't go through Protractor's `getText`, because it excludes text from hidden elements.
    return browser.executeScript(`return (arguments[0].textContent || '').trim()`, this.element);
  }

  /**
   * Sets the value of a `contenteditable` element.
   * @param value Value to be set on the element.
   */
  async setContenteditableValue(value: string): Promise<void> {
    const contenteditableAttr = await this.getAttribute('contenteditable');

    if (
      contenteditableAttr !== '' &&
      contenteditableAttr !== 'true' &&
      contenteditableAttr !== 'plaintext-only'
    ) {
      throw new Error('setContenteditableValue can only be called on a `contenteditable` element.');
    }

    return browser.executeScript(`arguments[0].textContent = arguments[1];`, this.element, value);
  }

  /** Gets the value for the given attribute from the element. */
  async getAttribute(name: string): Promise<string | null> {
    return browser.executeScript(
      `return arguments[0].getAttribute(arguments[1])`,
      this.element,
      name,
    );
  }

  /** Checks whether the element has the given class. */
  async hasClass(name: string): Promise<boolean> {
    const classes = (await this.getAttribute('class')) || '';
    return new Set(classes.split(/\s+/).filter(c => c)).has(name);

View on GitHub (pinned to 0411926e7d)

Solutions

  1. Ensure the target element has contenteditable="true" (or "plaintext-only") in the component under test.
  2. Use sendKeys or setInputValue/input harness methods for regular form controls instead of setContenteditableValue.
  3. Assert the correct element was located (log the outerHTML) — point the locator at the actual editable node, not its wrapper.
  4. Pre-check the attribute in the test before calling: expect(await el.getAttribute('contenteditable')).toBeTruthy().

Example fix

// before
await loader.getHarness(MyEditorHarness); await (await harness.host()).setContenteditableValue('hi');
// after
const host = await harness.host();
const editable = host.locatorFor('[contenteditable="true"]')();
await editable.setContenteditableValue('hi');
Defensive patterns

Strategy: validation

Validate before calling

const attr = await element.getAttribute('contenteditable');
if (attr !== '' && attr !== 'true' && attr !== 'plaintext-only') {
  throw new Error('Element is not contenteditable; use sendKeys for inputs instead');
}

Type guard

function isContenteditableAttr(v: string | null): boolean {
  return v === '' || v === 'true' || v === 'plaintext-only';
}

Try / catch

try {
  await el.setContenteditableValue('hello');
} catch (e) {
  if (String(e.message).includes('setContenteditableValue can only be called')) {
    await el.sendKeys('hello'); // fallback for regular inputs
  } else throw e;
}

Prevention

When it happens

Trigger: Calling element.setContenteditableValue('...') in a Protractor-based Component TestBed (TestbedHarnessEnvironment with Protractor harnesses) on an element lacking a contenteditable attribute, e.g. a plain <input>/<div> or an element whose contenteditable is set to "false".

Common situations: Test code written for contenteditable rich-text components accidentally pointed at a normal input; harness locator returning the wrapper element rather than the editable node; dynamic rendering removing the contenteditable attribute before the call.

Related errors


AI-assisted analysis of angular/components@0411926e7d (2026-08-31). Data as JSON: /api/errors/7ef5fb3a07969c90. Report an issue: GitHub.