Selenium: Watching for a dom event while executing

2019-03-05 11:41发布

问题:

Using Selenium I need to validate that an element, as it is dragged across the window, will trigger a 'drop zone' and then release that element in the zone. I can drag the element all over the page, but need to determine when the 'drop zone' element is added to the dom so I know I can release the element.

I have looked at 'Mutation Observers' and that would be perfect except the observer does not return until the callback is executed. I can't drag the element around at the same time I am waiting for something to change in the Dom, can I?

Initially, I thought I could set up a very specific observer for the node and the exit the async javascript call, and then execute the drag and drop before checking the results of the callback. BUT, the script won't exit and return until the callback. Catch-22! I can't drag and drop while watching at the same time. Help?

I have several test cases where I need to watch for a specific event while executing other actions at the same time and the selenium drivers are single threads. Does anyone have any ideas?

回答1:

It took awhile, but I finally figure out how to watch with MutationObservers. However, I have yet to apply it to a 'drag and drop'.

Once I know which node I need to watch for a change, in most cases if a node is added or removed, I can inject a MutationObserver. I ended up creating a class for all my MO work.

namespace Framework.Utilities
{
public class Watcher
{
    private static IWebDriver _driver;

    public Watcher(Driver driver) { this.driver = driver; }

    public void WatchBody() {
        var mutationObserverScript = @”(function (){     
            var myObserver = window.document.MutationObserver || {};     
                window.myObserver = {
                    observer: new MutationObserver(function (mutationsList) {
                        for (let mutation of mutationsList) {
                            if (mutation.addedNodes.length >= 1) {
                                myObserver.occured = true;
                                myObserver.observer.disconnect();
                            }
                        }
                    }),
                occurred: false
            };
            var config = {childList: true};
            var element = document.querySelector(‘boy’);
            window. myObserver.observer.observe(element, config);});"; 

        JavascriptExecutor js = (JavascriptExecutor) driver;  
        js.executeScript(mutationObserverScript);
    }
    public bool WaitForChange()
    {
        var results = defaultWait.Until( driver1 =>  ((IJavaScriptExecutor)_driver).ExecuteScript("returnwindow.myObserver.occurred;").Equals(true));
        return results;
    }       

    public bool AffectWith(Action watcher, Action action)
    {
        watcher();
        action();
        return WaitForChange();
    }
}
}

Some of the above will look familiar. I pulled in various pieces from a variety of postings on the internet.

My use case is watching for an overlay modal to appear:

Instantiate the Watcher class

var watcher = new Watcher(driver);

Setup the action

var myBtn = driver.FindElement(By.CssSelector(buttonSelector));
Action clickButton = () => myBtn.Click();

Observe for the change

if(watcher.AffectWith(watcher.WatchBody, clickButton)) 
{
    <detected modal is gone, keep testing>
}
else 
{ 
    <problems with modal> 
}

From what I've read, they work well since MutationObservers are executed the same as a ‘Promise’, they are a microtask and are executed before the next task in the DOM. By watching for the ‘flip to true’, I also know that any ‘async’ Microtask Angular call has completed as well.

Of note, disconnecting an observer will not remove it, a good practice it to set it to null. And, by including the ‘{}’ when defining the MutationObserver, you can index it. This prevents multiple MutationObservers from resetting parameters while another one is still active.