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
- Ensure the target element has contenteditable="true" (or "plaintext-only") in the component under test.
- Use sendKeys or setInputValue/input harness methods for regular form controls instead of setContenteditableValue.
- Assert the correct element was located (log the outerHTML) — point the locator at the actual editable node, not its wrapper.
- 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
- Reserve setContenteditableValue for elements rendered with contenteditable="true".
- Prefer harness methods (input harness setValue) for standard form controls.
- In tests, assert the contenteditable attribute before invoking the API.
- Locate the actual editable node, not its container wrapper.
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
- setContenteditableValue can only be called on a `contentedit
- setContenteditableValue can only be called on a `contentedit
- Cannot retrieve popup content because the combobox is closed
- Could not find tab matching filters: ${JSON.stringify(filter
- ListKeyManager constructed with a signal must receive an inj
AI-assisted analysis of angular/components@0411926e7d (2026-08-31).
Data as JSON: /api/errors/7ef5fb3a07969c90.
Report an issue: GitHub.