Registering and handling custom elements Pro only

Many websites are using different kinds of form elements that are not standard HTML input or select elements. For example, the Angular Material Slide toggleopen in new window. That is definitely not a standard HTML checkbox, nor a radio button, but when you see it in a form, you know that you have to click on it. It could look like a form element to the human eye, right? Unfortunately, for Fake Data it doesn't.

For such scenarios, you can register and handle custom form elements in Fake Data, so they will be filled properly.

In the following tutorial we're going to use this sample formopen in new window as reference. On that page, if you try to fill the form with the two dropdown elements you will immediately see that it won't work. This is another example of a custom element that Fake Data does not know what to do with, but we will fix that.

TIP

All of the code snippets from this tutorial will need to be saved in a .js file and loaded into Fake Data.

If you are using a MV2 version of Fake Data, please see how to load a foreground library.

If you are using a MV3 version of Fake Data, please see how to load a custom library.

To start, we will need to define a selector that will match all custom elements that we want Fake Data to handle. In the example link, one of the similarity between the two dropdowns is that they are in a div tag with the class "select", so we are going to tell Fake Data to look for elements that match div.select query. To do this, we are going to use the fakeData.registerElement method.

fakeData.registerElement('div.select', {
	// instructions
});

There are two events that Fake Data will fire when filling an element. onQuery and onFill.

onQuery event

It is triggered everytime an element is going to be filled, and when you right-click on the said element. The method that is executed on this event, will have to return a specific set of properties in order for Fake Data to parse it correctly. Let's first see how to write it for our custom elements, and then we'll see what does each property do:

fakeData.registerElement('div.select', {
	onQuery: function(data) {
		var options         = [];
		let option_elements = data.element.querySelector('select').querySelectorAll('option');
		
		option_elements.forEach(function(el) {
			options.push({
				value:    el.value,
				text:     el.text,
				disabled: el.disabled
			});
		});
		
		return {
			isValidInput:  true,
			kind:          'option',
			inputType:     'select',
			selector:      data.selector,
			selectOptions: options
		}
	},
	// onFill: /* to be continued */
});
fakeData.registerElement('div.select', {
	onQuery: async function(data) {
		var option_elements = await data.element.querySelector('select').querySelectorAll('option');
		
		var options = await Promise.all(option_elements.map(async (el) => {
			return {
				value:    await el.value,
				text:     await el.text,
				disabled: await el.disabled
			};
		}));
		
		return {
			isValidInput:  true,
			kind:          'option',
			inputType:     'select',
			selector:      data.selector,
			selectOptions: options
		}
	},
	// onFill: /* to be continued */
});

IMPORTANT

On MV3 version, the code is executed in a sandboxed frame and DOM elements are proxy objects that rely heavily on Promises. This means that you will need to use async / await on pretty much any action that affects DOM elements. Read more about this on the FakeDOM API page.

Now let's explain what is happening there. The onQuery method will receive as parameter an array with the following values:

  • element Contains the DOM element that matches the selector declared in registerElement method
  • fill_session Contains an array of DOM elements that were filled up to that point by Fake Data in the current trigger (for example when you trigger the "fill entire form" or "fill entire page" action). It is useful if you want to check if your element was already filled by other means so you don't have to fill it again.
  • selector Contains a string with a unique selector that Fake Data extension resolved for the current DOM element
  • url Contains the url of the current page (or frame)

For the return properties, Fake Data will expect to receive the following:

  • isValidInput Will tell Fake Data whether to continue with filling or not. Accepted values are:
    • true Will let Fake Data to fill the field
    • false Will prevent Fake Data from filling and will also show "Cannot manage this element" when right-clicking on it.
  • kind This will open different views when you will try to Manage the element. Accepted values are:
    • option Used for checkboxes, radio buttons and dropdown elements
    • named Used for text inputs (input, textarea), if you can uniquely identify the field by a name
    • unnamed Used for text fields that cannot be uniquely identified by a name
  • inputType Used for showing different options in the "Manage Field" page based on the element type. Accepted values:
    • select for dropdown elements
    • any value that the "type" attribute of input elements can have
  • selector A unique selector that will be used in the "Manage Field" page. You can provide the same one that Fake Data passed in "data.selector"
  • selectOptions Required only for dropdown elements. It must contain all the options of the dropdown. Used when in the "Manage Field" page. Must be an array of objects with the following properties:
    • {value: <string>, text: <string>, disabled: <bool>}.
  • isMultiSelect Whether the <select> dropdown allows multiple items to be selected or not
    • true The dropdown is a multi-select
    • false The dropdown is a basic dropdown (no multi-select)

You can think of this method as the one that returns the metadata of the element. Based on these fields, Fake Data will either proceed with filling or not, and will also decide what settings to show you when you right-click on the element and choose to manage it.

onFill event

This method is where you will have to do the actual filling of your custom element. It will be executed once and after the onQuery will finish. Let's see the final code for our custom elements:

fakeData.registerElement('div.select', {
	onQuery: function(data) {
		var options = [];
		let option_elements = data.element.querySelector('select').querySelectorAll('option');
		
		option_elements.forEach(function(el) {
			options.push({
				value:    el.value,
				text:     el.text,
				disabled: el.disabled
			});
		});
		
		return {
			isValidInput:  true,
			kind:          'option',
			inputType:     'select',
			selector:      data.selector,
			selectOptions: options
		}
	},
	onFill:  function(data) {
		if (data.value === false) {
			return;
		}
		
		var select = data.element.querySelector('select');
		
		if (data.value === true) {
			select.selectedIndex = Math.floor(Math.random() * select.options.length);
		} else {
			select.selectedIndex = Array.from(select.options).findIndex(el => el.value == data.value)
		}
		
		fakeData.triggerInputChangeEvent(select, 'change');
	}
});
fakeData.registerElement('div.select', {
	onQuery: async function(data) {
		var option_elements = await data.element.querySelector('select').querySelectorAll('option');
		
		var options = await Promise.all(option_elements.map(async (el) => {
			return {
				value:    await el.value,
				text:     await el.text,
				disabled: await el.disabled
			};
		}));
		
		return {
			isValidInput:  true,
			kind:          'option',
			inputType:     'select',
			selector:      data.selector,
			selectOptions: options
		}
	},
	onFill:  async function(data) {
		if (data.value === false) {
			return;
		}
		
		var select = await data.element.querySelector('select');
		
		if (data.value === true) {
			select.selectedIndex = Math.floor(Math.random() * ((await select.options).length));
		} else {
			(await select.options).forEach(async (el, i) => {
				if (await el.value == data.value) {
					select.selectedIndex = i;
				}
			})
		}
		
		setTimeout(function() {
			fakeData.triggerInputChangeEvent(select, 'change');
		})
	}
});

The onFill method will also receive an array with the following values:

  • element Contains the DOM element that matches the selector declared in registerElement method
  • event The Event object that triggered the fill for this element
  • fill_session An array of DOM elements that were filled up to that point by Fake Data in the current trigger (for example when you trigger the "fill entire form" or "fill entire page" action)
  • fill_session_id A unique identifier that will identify the fill session
  • fill_session_type The type of event that triggered the current fill session. could be "single" (when filling a single field), "form" (when filling an entire form), "page" (when filling an entire page) or "multiple-fields" (when filling multiple fields at once)
  • selector A unique selector for the DOM element that is filled
  • value The value that should be filled in the element. For dropdown elements, it could also be true which means that you can select any random element, or false which means that the current field should be ignored
  • isMultiSelect Whether the <select> dropdown allows multiple items to be selected or not
    • true The dropdown is a multi-select
    • false The dropdown is a basic dropdown (no multi-select)
  • options A list with parsed select options returned by onQuery event
  • excluded_options A list with excluded options selected in the "Manage Field" modal

The method doesn't have to return anything. It should only process the value that Fake Data decided to fill and make the changes to the element accordingly.

This should sum up the tutorial on registering custom elements for Fake Data. If you load the above code as a foreground library in Fake Data and then go to our sample formopen in new window, you should be able to fill the two custom fields as well as managing them through the right-click menu.