{"record":{"id":"7ef5fb3a07969c90","repo":"angular/components","slug":"setcontenteditablevalue-can-only-be-called-on-a-c","errorCode":null,"errorMessage":"setContenteditableValue can only be called on a `contenteditable` element.","messagePattern":"setContenteditableValue can only be called on a `contenteditable` element\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/cdk/testing/protractor/protractor-element.ts","lineNumber":215,"sourceCode":"      return browser.executeScript(_getTextWithExcludedElements, this.element, options.exclude);\n    }\n    // We don't go through Protractor's `getText`, because it excludes text from hidden elements.\n    return browser.executeScript(`return (arguments[0].textContent || '').trim()`, this.element);\n  }\n\n  /**\n   * Sets the value of a `contenteditable` element.\n   * @param value Value to be set on the element.\n   */\n  async setContenteditableValue(value: string): Promise<void> {\n    const contenteditableAttr = await this.getAttribute('contenteditable');\n\n    if (\n      contenteditableAttr !== '' &&\n      contenteditableAttr !== 'true' &&\n      contenteditableAttr !== 'plaintext-only'\n    ) {\n      throw new Error('setContenteditableValue can only be called on a `contenteditable` element.');\n    }\n\n    return browser.executeScript(`arguments[0].textContent = arguments[1];`, this.element, value);\n  }\n\n  /** Gets the value for the given attribute from the element. */\n  async getAttribute(name: string): Promise<string | null> {\n    return browser.executeScript(\n      `return arguments[0].getAttribute(arguments[1])`,\n      this.element,\n      name,\n    );\n  }\n\n  /** Checks whether the element has the given class. */\n  async hasClass(name: string): Promise<boolean> {\n    const classes = (await this.getAttribute('class')) || '';\n    return new Set(classes.split(/\\s+/).filter(c => c)).has(name);","sourceCodeStart":197,"sourceCodeEnd":233,"githubUrl":"https://github.com/angular/components/blob/0411926e7d8ae06b32236ec1048a888cfad5abf2/src/cdk/testing/protractor/protractor-element.ts#L197-L233","documentation":"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.","triggerScenarios":"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\".","commonSituations":"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.","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()."],"exampleFix":"// before\nawait loader.getHarness(MyEditorHarness); await (await harness.host()).setContenteditableValue('hi');\n// after\nconst host = await harness.host();\nconst editable = host.locatorFor('[contenteditable=\"true\"]')();\nawait editable.setContenteditableValue('hi');","handlingStrategy":"validation","validationCode":"const attr = await element.getAttribute('contenteditable');\nif (attr !== '' && attr !== 'true' && attr !== 'plaintext-only') {\n  throw new Error('Element is not contenteditable; use sendKeys for inputs instead');\n}","typeGuard":"function isContenteditableAttr(v: string | null): boolean {\n  return v === '' || v === 'true' || v === 'plaintext-only';\n}","tryCatchPattern":"try {\n  await el.setContenteditableValue('hello');\n} catch (e) {\n  if (String(e.message).includes('setContenteditableValue can only be called')) {\n    await el.sendKeys('hello'); // fallback for regular inputs\n  } else throw e;\n}","preventionTips":["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."],"tags":["angular","cdk-testing","protractor","contenteditable"],"backgroundTag":"invalid-element-state","analyzedSha":"0411926e7d8ae06b32236ec1048a888cfad5abf2","analyzedAt":"2026-08-31T11:58:23.400Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}