I have a form from Microsoft (forms.office.com) and I get repetitive questions that I am trying to automate/fill the answers with a Chrome snippet.
It is quite dynamic. For instance, the first question is a radio button and the second question only appears if I select “None of the above”.
My code often fails especially when the input texts need to be filled. I focused on keywords rather than the full question because the HTML is more complex (e.g. it might contain spaces or parts). Some questions have the same keywords. The snipped finally worked but I noticed that I had to run it multiple times so that every question got answered.
// Array with keywords and answers
const questionsAndAnswers = [
{ questionKeyword: "first", answerText: "None of the above" },
{ questionKeyword: "second", answerText: "222222" },
{ questionKeyword: "third?", answerText: "333333" },
{ questionKeyword: "fourth", answerText: "444444" },
{ questionKeyword: "fifth", answerText: "Yes" },
{ questionKeyword: "second", answerText: "222222" },
{ questionKeyword: "third?", answerText: "333333" },
{ questionKeyword: "fourth", answerText: "444444" },
{ questionKeyword: "fifth", answerText: "Yes" }
];
// Function to normalize question text by removing HTML entities and unnecessary spaces
function normalizeText(text) {
// Replace non-breaking spaces with regular spaces
text = text.replace(/ /g, ' ');
// Remove any other HTML entities like <, >, &, etc.
text = text.replace(/&[a-zA-Z0-9#]+;/g, '');
// Remove any leading/trailing spaces or extra spaces
text = text.replace(/s+/g, ' ').trim();
// Return the cleaned text
return text;
}
// Function to locate and set the answer based on the question keyword(s)
function setAnswerByKeyword(questionKeywords, answerText) {
// Get all question containers
const questionContainers = document.querySelectorAll('div[data-automation-id="questionItem"]');
for (const container of questionContainers) {
// Get the question text content and normalize it
const questionTextContent = normalizeText(container.textContent.toLowerCase());
// Check if the question text includes any of the keywords
for (let keyword of questionKeywords) {
if (new RegExp(keyword, "i").test(questionTextContent)) {
console.log(`Question matched for keyword "${keyword}" in "${questionTextContent}"`);
// 1. Try to find a matching radio button
const radioButton = Array.from(container.querySelectorAll('input[type="radio"]')).find(
radio => radio.value.toLowerCase() === answerText.toLowerCase()
);
if (radioButton) {
radioButton.checked = true;
radioButton.dispatchEvent(new Event('change', { bubbles: true }));
console.log(`Set radio button for "${keyword}" to "${answerText}".`);
return;
}
// 2. Try to find a text input with `data-automation-id="textInput"` or `type="text"`
const textInput = container.querySelector('input[type="text"][data-automation-id="textInput"], input[data-automation-id="textInput"]');
if (textInput) {
// Adding a delay to ensure the input field is fully ready to accept text
setTimeout(() => {
textInput.focus();
textInput.value = answerText;
// Dispatch the necessary events to simulate user interaction
textInput.dispatchEvent(new Event('input', { bubbles: true }));
textInput.dispatchEvent(new Event('change', { bubbles: true }));
console.log(`Set text input for "${keyword}" to "${answerText}".`);
}, 500); // Add delay to ensure input fields are ready
return;
}
// 3. If no input field was found, log a warning
console.warn(`Input field for "${keyword}" not found in "${questionTextContent}".`);
return;
}
}
}
console.warn(`Question containing "${questionKeywords.join(", ")}" not found!`);
}
// Main function to iterate through each question and set the answer
function fillForm() {
questionsAndAnswers.forEach(qa => setAnswerByKeyword([qa.questionKeyword], qa.answerText));
}
// Run the form-filling function with a slight delay to handle dynamic loading
setTimeout(fillForm, 1000);
Those forms are not as easy as Google forms where my snippets work perfectly. What can I change to fill all the answers without multiple snippet runs?