I've ran into a case where the mutation observer is not triggering, not sure if its a configuration issue or if the observer is busted. My test case:
package mains; import com.gargoylesoftware.htmlunit.BrowserVersion; import com.gargoylesoftware.htmlunit.WebClient; import com.gargoylesoftware.htmlunit.WebClientOptions; import com.gargoylesoftware.htmlunit.javascript.SilentJavaScriptErrorListener; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.openqa.selenium.By; import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.WebDriver; import org.openqa.selenium.firefox.FirefoxDriver; import org.openqa.selenium.firefox.FirefoxDriverLogLevel; import org.openqa.selenium.firefox.FirefoxOptions; import org.openqa.selenium.htmlunit.HtmlUnitDriver; import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.WebDriverWait; public class MutationTest { private WebDriver ffDriver; private WebDriver huDriver; private static final String TRIGGER_OBSERVER = "document.text=\"Text1\";\n" + "const targetNode = document.getElementById('code-tab');\n" + "const config = { attributes: true, childList: true, subtree: true };\n" + "window.count = 0;\n" + "document.count = 0;\n" + "const callback = function(mutationsList, observer) {\n" + " console.log('derp');\n" + " window.count++;\n" + " document.count++;\n" + "};\n" + "const observer = new MutationObserver(callback);\n" + "observer.observe(targetNode, config);"; @BeforeEach void initDrivers() { System.setProperty("webdriver.gecko.driver", "c:\\...\\geckodriver.exe"); FirefoxOptions options = new FirefoxOptions(); options.setLogLevel(FirefoxDriverLogLevel.FATAL); ffDriver = new FirefoxDriver(options); huDriver = new HtmlUnitDriver(BrowserVersion.FIREFOX, true) { @Override protected WebClient modifyWebClient(WebClient client) { final WebClient webClient = super.modifyWebClient(client); WebClientOptions options = webClient.getOptions(); options.setCssEnabled(true); options.setThrowExceptionOnScriptError(false); webClient.setJavaScriptErrorListener(new SilentJavaScriptErrorListener()); return webClient; } }; } @AfterEach void closeDrivers(){ ffDriver.close(); huDriver.close(); } @Test void givenJavascriptPage_whenModifyingElement_triggerMutationObserver(){ Assertions.assertEquals(mutate(ffDriver),mutate(huDriver)); } private Long mutate(WebDriver driver) { driver.get("https://github.com/HtmlUnit/htmlunit"); JavascriptExecutor js = (JavascriptExecutor) driver; js.executeScript(TRIGGER_OBSERVER); WebDriverWait wait = new WebDriverWait(driver,10); By codeTab = By.cssSelector("#code-tab"); wait.until(ExpectedConditions.elementToBeClickable(codeTab)); driver.findElement(codeTab).click(); return (Long) js.executeScript("return document.count") + (Long) js.executeScript("return window.count"); } }
Firefox returns a number, Htmlunit returns null.