Test WYSIWYG editors using Selenium WebDriver

Published: by Creative Commons Licence

What is a WYSIWYG HTML editor?
A WYSIWYG HTML editor provides an editing interface which resembles how the page will be displayed in a web browser. Because using a WYSIWYG editor may not require any HTML knowledge, they are often easier for an average computer user to get started with. (Wikipedia)

WYSIWYG HTML editors (an acronym for "What You See Is What You Get") are widely used in web applications as embedded text editor nowadays. The most common way of using those editors is framed editing, which uses <iframe> instead of <textarea> and displays the content inside the <body> element, however, it makes the UI automation process a little bit different.

Taking CKEditor and TinyMCE as examples, this article will illustrate:

  1. How to automate these editors using Selenium WebDriver's native Ruby API
  2. How to inject JavaScript through Selenium WebDriver to automate using editors' native JavaScript API.

For other editors created with similar technology, for instance, CLEditor (including Primefaces editor) and CuteEditor, the same logic also applies and should work in theory.

WYSIWYG HTML editors' comparison

CKEditor (Standard) TinyMCE CLEditor
Homepage ckeditor.com tinymce.com premiumsoftware.net
Current version 4.3.2 4.0.15 1.4.4
License GPL, LGPL and MPL LGPL MIT or GPL v2
Size (Distribution package) 1022 kB 291.3 kB 20.4 kB
Size (Core .js file) 461.2 kB 282.7 kB 12.5 kB

Demos

CKEditor 4.3.2 Standard

TinyMCE 4.0.15

Automate using native Selenium WebDriver API

Both CKEditor and TinyMCE are JavaScript based, which means they can be automated using Selenium WebDriver's native API just like any other HTML web applications.

Click toolbar buttons

WYSIWYG editors normally provide native methods to set raw HTML content directly through API, automating the toolbar doesn't seem to be really necessary. If it's needed to be done for some reason, it shouldn't be much of a problem, because toolbar elements are just ordinary web elements, the automating process is fairly straight-forward without any frame switching required.

  1. Find the elements by appropriate locators according to the HTML markup.
  2. Manipulate those elements see if they work or not.

Markup for CKEditor's toolbar "Numbered List" button: Show

Markup for TinyMCE's toolbar "Numbered List" button: Show

Selenium WebDriver Ruby code to click the buttons:

# click CKEditor's 'Numbered List' button
ckeditor_btn_numbered_list = driver.find_element(:class => "cke_button__numberedlist")
ckeditor_btn_numbered_list.click

# click TinyMCE editor's 'Numbered List' button
tinymce_btn_numbered_list = driver.find_element(:css => ".mce-btn[aria-label='Numbered list'] button")
tinymce_btn_numbered_list.click

Switch into input iframe

Although CKEditor and TinyMCE are initialized with <textarea> tag, the editor body is actually constructed within an <iframe>, which is still technically a web element, but all elements inside can only be accessed by WebDriver after switching into the iframe. As seen in the HTML mark up below, CKEditor's iframe can be identified using class, while TinyMCE's can be located by id directly. Equivalent CSS Selectors and XPaths also exist if needed.

Markup for CKEditor's body: Show

Markup for TinyMCE's body: Show

Locate the iframes

ckeditor_frame = driver.find_element(:class => 'cke_wysiwyg_frame')
tinymce_frame = driver.find_element(:id => 'tinymce-editor_ifr')

Switch into

driver.switch_to.frame(ckeditor_frame) # ckeditor_frame or tinymce_frame, one at a time

Switch out (if necessary)

If the WebDriver instance is already inside any kind of frames, switch out the current frame to default content is required. For example, after switching into CKEditor's iframe and sending some keys, the only way to get into TinyMCE's input area is to switch back to default content first, then locate TinyMCE's iframe and switch into frame again.

# if driver is already inside ckeditor_frame, switch out first
driver.switch_to.default_content

# then switch to another iframe, e.g. tinymce_frame
driver.switch_to.frame(tinymce_frame)

Automate content

Send keys

After switching into the editor's <iframe>, the text can be sent to the <body> directly, which is possible using Selenium's native send_keys method. Unlike injecting JavaScript using editors' built-in methods or changing innerHTML, the keys sent into the editor will always be inside <p> tag. As a result, sending <h1>Heading</h1> won't show up as real WYSIWYG heading, but in plain text. Furthermore, this approach has been reported behaving incorrectly on Firefox, which might be better to avoid if possible.

editor_body = driver.find_element(:tag_name => 'body')
editor_body.send_keys("<h1>Heading</h1>Yi Zeng")

Set innerHTML

In order to set editor content with raw HTML like WYSIWYG mode, one approach is to change the innerHTML of editor body by injecting JavaScript. In this case, sending <h1>Heading</h1> will actually show up as heading one.

editor_body = driver.find_element(:css => 'body')
driver.execute_script("arguments[0].innerHTML = '<h1>Heading</h1>Yi Zeng'", editor_body)

Clear all input

A quote from Selenium Ruby's API documentation on clear() method:

If this element is a text entry element, this will clear the value. Has no effect on other elements.

Although clear() method is stated as not available for non-text entry elements, it seems it can actually clear the input iframe without any exceptions.

Apart from clear(), an alternative is to use Selenium's ActionBuilder to construct an action chain to mimic keyboard shortcut pressing. Ctrl + A will select all, then push Backspace to clear.

# Method 1. Using clear() method
editor_body.clear

# Method 2. Using ActionBuilder
driver.action.click(editor_body)
             .key_down(:control)
             .send_keys("a")
             .key_up(:control)
             .perform
driver.action.send_keys(:backspace).perform

Automate using editors' built-in JavaScript API

Without worrying about frame switching like using Selenium WebDriver's native API, it would also be a stable solution to inject JavaScript directly using driver.execute_script() to call editors' built-in JS functions.

Set content

Both editors have built-in methods to set the content of entire input area. CKEditor's API provides a method called setData(), which replaces editor data with raw input HTML data. Similar method setContent() also exists in TinyMCE editor's API.

driver.execute_script("CKEDITOR.instances.ckeditor.setData('<h1>Yi Zeng</h1> CKEditor')")
driver.execute_script("tinyMCE.activeEditor.setContent('<h1>Yi Zeng</h1> TinyMCE')")

Clear content

With the same logic, clearing content can be done by injecting JavaScript to set the entire content to empty string.

driver.execute_script("CKEDITOR.instances.ckeditor.setData( '' )")
driver.execute_script("tinyMCE.activeEditor.setContent('')")

Insert content

Instead of setting the entire content, it is also possible to insert some content to the editors. CKEditor's has a method called insertHTML(), which inserts content at currently selected position, in TinyMCE, it's called insertContent().

driver.execute_script("CKEDITOR.instances.ckeditor.insertHtml('<p>Christchurch</p>')")
driver.execute_script("tinyMCE.activeEditor.insertContent('<p>Christchurch</p>')")

Examples

Set content using Selenium WebDriver API

Show

Select all content

Show

Click "Numbered list" from toolbar

Show

Set content using editors' API

Show

References