Job Application Auto-Filler - WorkDay

Auto-fill job application forms on WorkDay by first adding all sections, then filling them.

// ==UserScript==
// @name         Job Application Auto-Filler - WorkDay
// @namespace    https://greasyfork.org/en/users/670188-hacker09?sort=daily_installs
// @version      2
// @description  Auto-fill job application forms on WorkDay by first adding all sections, then filling them.
// @author       hacker09
// @icon         https://i.imgur.com/3R9QyLR.png
// @match        https://*.myworkday.com/*
// @grant        none
// ==/UserScript==

(function() {
  'use strict';

  //Configuration - Your actual data.
  const formData = {
    jobs: [
        { //Newest job first
            title: "Senior Software Developer",
            company: "Tech Solutions Inc.",
            location: "San Francisco",
            startMonth: "01",
            startYear: "2022",
            currentlyWork: true, //Set to 'true' for a current job
            endMonth: "", //Leave empty if currently working here
            endYear: "", //Leave empty if currently working here
            summary: "• Led a team of 5 developers in creating a new e-commerce platform.\n\n• Wrote high-quality, scalable code using React and Node.js.\n\n• Optimized application performance, resulting in a 30% reduction in load times."
        },
        { //Older job
            title: "IT Support Intern",
            company: "Global Innovations LLC.",
            location: "New York",
            startMonth: "06",
            startYear: "2021",
            currentlyWork: false,
            endMonth: "12",
            endYear: "2021",
            summary: "• Provided technical assistance to over 200 employees.\n\n• Managed user accounts and system permissions.\n\n• Documented and resolved IT support tickets efficiently."
        }
    ],
    education: [
        { //Most recent education first
            schoolName: "State University",
            degree: "BS", //Use abbreviations like BS, MS, PhD, or GED
            fieldOfStudy: "Computer Science",
            startYear: "2018",
            endYear: "2022"
        },
        { //Older education
            schoolName: "Community College",
            degree: "GED",
            fieldOfStudy: "Information Technology",
            startYear: "2016",
            endYear: "2018"
        }
    ],
    skills: "JavaScript, React, Node.js, HTML, CSS, SQL, Python, Project Management, Team Leadership, Agile Methodologies, AWS, Docker",
    languages: [
        {
            name: "English",
            isNative: true, //Set to 'true' for a native language
            listeningproficiency: "5 - Native or Bilingual Proficiency",
            readingproficiency: "5 - Native or Bilingual Proficiency",
            speakingproficiency: "5 - Native or Bilingual Proficiency",
            writingproficiency: "5 - Native or Bilingual Proficiency"
        },
        {
            name: "Spanish",
            isNative: false,
            listeningproficiency: "3 - Professional Working Proficiency",
            readingproficiency: "3 - Professional Working Proficiency",
            speakingproficiency: "2 - Limited Working Proficiency",
            writingproficiency: "2 - Limited Working Proficiency"
        }
    ]
};

  const buttonId = 'workday-autofill-btn'; //Unique ID for the button

  function delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  async function fillInputField(element, value) {
    if (!element) return;
    element.focus();
    await delay(100);
    element.value = value;
    element.dispatchEvent(new Event('input', {
      bubbles: true
    }));
    element.dispatchEvent(new Event('change', {
      bubbles: true
    }));
    element.blur();
    await delay(100);
  }

  async function fillDegreeField(element, value) {
    if (!element) return;
    element.focus();
    element.select();
    element.click(); //Click to open dropdown
    await delay(500);

    //Type value to filter options
    element.value = '';
    for (let char of value) {
      element.value += char;
      element.dispatchEvent(new KeyboardEvent('keydown', {
        key: char,
        bubbles: true
      }));
      element.dispatchEvent(new KeyboardEvent('keypress', {
        key: char,
        bubbles: true
      }));
      element.dispatchEvent(new Event('input', {
        bubbles: true
      }));
      element.dispatchEvent(new KeyboardEvent('keyup', {
        key: char,
        bubbles: true
      }));
      await delay(100);
    }

    //Press Enter to filter/search
    element.dispatchEvent(new KeyboardEvent('keydown', {
      key: 'Enter',
      code: 'Enter',
      bubbles: true
    }));
    element.dispatchEvent(new KeyboardEvent('keyup', {
      key: 'Enter',
      code: 'Enter',
      bubbles: true
    }));
    await delay(1000);

    //Find and click matching option - FIXED: exclude already selected items
    const dropdownOptions = document.querySelectorAll('[data-automation-id="promptOption"]');

    for (const option of dropdownOptions) {
      const optionText = (option.textContent || option.dataset.automationLabel || '').trim();

      //Skip if this is a selected item (pill)
      const parentContainer = option.closest('[data-automation-id="selectedItem"]');
      if (parentContainer) {
        continue;
      }

      if (optionText === value || optionText.toLowerCase() === value.toLowerCase()) {
        const clickTarget = option.closest('[role="option"]') || option.parentElement.closest('[role="option"]') || option;

        //Try clicking the radio button inside
        const radioBtn = clickTarget.querySelector('input[type="radio"]') || clickTarget.querySelector('[data-automation-id="radioBtn"]');
        if (radioBtn) {
          radioBtn.click();
          radioBtn.dispatchEvent(new Event('change', {
            bubbles: true
          }));
        } else {
          clickTarget.click();
        }
        await delay(500);
        return;
      }
    }
  }

  async function fillDateFields(monthElement, yearElement, month, year) {
    if (monthElement && month) {
      monthElement.click();
      monthElement.focus();
      await delay(100);
      monthElement.select();
      monthElement.value = '';
      for (let char of month) {
        monthElement.value += char;
        monthElement.dispatchEvent(new KeyboardEvent('keydown', {
          key: char,
          code: `Digit${char}`,
          bubbles: true
        }));
        monthElement.dispatchEvent(new KeyboardEvent('keypress', {
          key: char,
          code: `Digit${char}`,
          bubbles: true
        }));
        monthElement.dispatchEvent(new Event('input', {
          bubbles: true
        }));
        monthElement.dispatchEvent(new KeyboardEvent('keyup', {
          key: char,
          code: `Digit${char}`,
          bubbles: true
        }));
        await delay(100);
      }
      monthElement.dispatchEvent(new Event('change', {
        bubbles: true
      }));
      await delay(100);
    }

    if (yearElement && year) {
      yearElement.click();
      yearElement.focus();
      await delay(100);
      yearElement.select();
      yearElement.value = '';
      for (let char of year) {
        yearElement.value += char;
        yearElement.dispatchEvent(new KeyboardEvent('keydown', {
          key: char,
          code: `Digit${char}`,
          bubbles: true
        }));
        yearElement.dispatchEvent(new KeyboardEvent('keypress', {
          key: char,
          code: `Digit${char}`,
          bubbles: true
        }));
        yearElement.dispatchEvent(new Event('input', {
          bubbles: true
        }));
        yearElement.dispatchEvent(new KeyboardEvent('keyup', {
          key: char,
          code: `Digit${char}`,
          bubbles: true
        }));
        await delay(100);
      }
      yearElement.dispatchEvent(new Event('change', {
        bubbles: true
      }));
      yearElement.dispatchEvent(new KeyboardEvent('keydown', {
        key: 'Tab',
        code: 'Tab',
        bubbles: true
      }));
      await delay(100);
    }
  }

  async function selectDropdownOption(triggerElement, valueToSelect) {
    if (!triggerElement || !valueToSelect) return;
    triggerElement.click();
    await delay(1000); //Increased delay
    const options = document.querySelectorAll('[data-automation-id="promptOption"]');
    for (const option of options) {
      const optionText = (option.textContent || option.dataset.automationLabel || '').trim();
      if (optionText.toLowerCase() === valueToSelect.toLowerCase()) {
        option.closest('[role="option"]')?.click();
        await delay(500);
        return;
      }
    }
    document.body.click(); //Click away to close dropdown if no match found
    await delay(200);
  }

  //NEW FUNCTION: Clicks all "Add" buttons first
  async function addSections() {
    const addBtns = document.querySelectorAll('[data-automation-id="panelSetAddButton"]');
    if (addBtns.length === 0) return;

    //Add job sections (assumes 1 is already visible)
    for (let i = 1; i < formData.jobs.length; i++) {
      addBtns[0]?.click(); //Assumes first add button is for jobs
      await delay(1500);
    }

    //Add education sections (assumes 1 is already visible)
    for (let i = 1; i < formData.education.length; i++) {
      addBtns[1]?.click(); //Assumes second add button is for education
      await delay(1500);
    }

    //Add language sections (assumes it starts with 0)
    const lastAddBtn = addBtns[addBtns.length - 1];
    for (let i = 0; i < formData.languages.length; i++) {
      lastAddBtn?.click(); //Assumes last add button is for languages
      await delay(1500);
    }
  }

  //MODIFIED: Removed "Add" button logic
  async function fillJobSection() {
    for (let i = 0; i < formData.jobs.length; i++) {
      const job = formData.jobs[i];
      const titleInputs = document.querySelectorAll('input[id*="jobHistoryTitle"]');
      await fillInputField(titleInputs[i], job.title);
      const companyInputs = document.querySelectorAll('input[id*="jobHistoryCompany"]');
      await fillInputField(companyInputs[i], job.company);
      const locationInputs = document.querySelectorAll('input[id*="jobHistoryLocation"]');
      await fillInputField(locationInputs[i], job.location);
      const allMonthInputs = document.querySelectorAll('input[id*="dateSectionMonth"]');
      const allYearInputs = document.querySelectorAll('input[id*="dateSectionYear"]');
      await fillDateFields(allMonthInputs[i * 2], allYearInputs[i * 2], job.startMonth, job.startYear);
      const currentlyWorkInputs = document.querySelectorAll('input[id*="currentlyWorkHere"]');
      if (currentlyWorkInputs[i] && job.currentlyWork) {
        if (!currentlyWorkInputs[i].checked) currentlyWorkInputs[i].click();
      }
      if (!job.currentlyWork) {
        await fillDateFields(allMonthInputs[i * 2 + 1], allYearInputs[i * 2 + 1], job.endMonth, job.endYear);
      }
      const summaryTextareas = document.querySelectorAll('textarea[id*="jobSummary"]');
      await fillInputField(summaryTextareas[i], job.summary);
    }
  }

  //MODIFIED: Removed "Add" button logic
  async function fillEducationSection() {
    for (let i = 0; i < formData.education.length; i++) {
      const edu = formData.education[i];
      const schoolInputs = document.querySelectorAll('input[id*="schoolName"]');
      await fillInputField(schoolInputs[i], edu.schoolName);
      const degreeInputs = document.querySelectorAll('input[id*="schoolDegrees"]');
      await fillDegreeField(degreeInputs[i], edu.degree);
      await delay(1500);
      const fieldInputs = document.querySelectorAll('input[id*="fieldOfStudy"]');
      await fillInputField(fieldInputs[i], edu.fieldOfStudy);
      const allYearInputs = document.querySelectorAll('input[id*="dateSectionYear"]');
      const jobYearFieldsCount = document.querySelectorAll('input[id*="jobHistoryTitle"]').length * 2;
      const eduStartYearIndex = jobYearFieldsCount + (i * 2);
      const eduEndYearIndex = jobYearFieldsCount + (i * 2) + 1;
      await fillDateFields(null, allYearInputs[eduStartYearIndex], '', edu.startYear);
      await fillDateFields(null, allYearInputs[eduEndYearIndex], '', edu.endYear);
    }
  }

  async function fillSkillsSection() {
    const skillsTextarea = document.querySelector('textarea[id*="skills"]');
    await fillInputField(skillsTextarea, formData.skills);
  }

  //MODIFIED: Removed "Add" button logic and added proficiency filling
  async function fillLanguagesSection() {
    for (let i = 0; i < formData.languages.length; i++) {
      const lang = formData.languages[i];

      //Select language name
      const langSelectWidgets = document.querySelectorAll('[data-automation-id="selectWidget"][id*="languages"]');
      await selectDropdownOption(langSelectWidgets[i], lang.name);

      //Check "native language" if applicable
      const nativeCheckboxes = document.querySelectorAll('input[id*="myNativeLanguage"]');
      if (nativeCheckboxes[i] && lang.isNative && !nativeCheckboxes[i].checked) {
        nativeCheckboxes[i].click();
      }

      //Select proficiency levels
      const allProficiencyTriggers = document.querySelectorAll('[data-automation-id="selectWidget"][id*="langProficiencies"]');
      const proficiencyTriggersForCurrentLang = Array.from(allProficiencyTriggers).slice(i * 4, i * 4 + 4);

      if (proficiencyTriggersForCurrentLang.length >= 4) {
        await selectDropdownOption(proficiencyTriggersForCurrentLang[0], lang.listeningproficiency);
        await selectDropdownOption(proficiencyTriggersForCurrentLang[1], lang.readingproficiency);
        await selectDropdownOption(proficiencyTriggersForCurrentLang[2], lang.speakingproficiency);
        await selectDropdownOption(proficiencyTriggersForCurrentLang[3], lang.writingproficiency);
      }
    }
  }

  //NEW main function to orchestrate adding and then filling
  async function runAutoFiller() {
    const triggerBtn = document.getElementById(buttonId);
    if (!triggerBtn) return;

    console.log("Starting form auto-fill...");
    triggerBtn.disabled = true;
    triggerBtn.textContent = 'Filling...';

    console.log("Phase 1: Adding all necessary sections.");
    await addSections();
    await delay(1000); //Wait for DOM to update

    console.log("Phase 2: Populating data into sections.");
    await fillJobSection();
    await fillEducationSection();
    await fillSkillsSection();
    await fillLanguagesSection();

    triggerBtn.disabled = false;
    triggerBtn.textContent = 'Auto-Fill Form';
    console.log("Form auto-fill completed!");
  }

  function handlePageChange() {
    const onApplyPage = location.pathname.includes('/apply/');
    const buttonExists = document.getElementById(buttonId);

    if (onApplyPage && !buttonExists) {
      //Create and append the button if on the apply page and it doesn't exist
      const triggerBtn = document.createElement('button');
      triggerBtn.id = buttonId;
      triggerBtn.textContent = 'Auto-Fill Form';
      triggerBtn.style.position = 'fixed';
      triggerBtn.style.top = '10px';
      triggerBtn.style.right = '10px';
      triggerBtn.style.zIndex = '9999';
      triggerBtn.style.backgroundColor = '#007bff';
      triggerBtn.style.color = 'white';
      triggerBtn.style.border = 'none';
      triggerBtn.style.padding = '10px';
      triggerBtn.style.borderRadius = '5px';
      triggerBtn.style.cursor = 'pointer';
      triggerBtn.onclick = runAutoFiller;
      document.body.appendChild(triggerBtn);
    } else if (!onApplyPage && buttonExists) {
      //Remove the button if not on the apply page and it exists
      buttonExists.remove();
    }
  }

  //Initial check when the script loads
  handlePageChange();

  //Use a MutationObserver to detect page changes in the SPA
  new MutationObserver(handlePageChange).observe(document.body, {
    childList: true,
    subtree: true
  });
})();