移除 AbstractEventListener、EventFiringWebDriver 與 WebDriverEventListener

這篇部落格將介紹一些範例,說明如何轉換使用上述類別的程式碼。

升級至 WebDriverListener 與 EventFiringDecorator

裝飾 webdriver

new EventFiringWebDriver(driver); // Old approach
new EventFiringDecorator().decorate(driver); // New approach

實作方法包裝器

有時您可能會需要使用自訂實作來處理底層裝飾方法的呼叫。例如,您可能想要使用自己的 findElement 實作來儲存來自網頁元素的元數據。裝飾器 (擴展 WebDriverDecorator 等) 可能會變得非常複雜,為了簡化,我們將擴展 EventFiringDecorator,因為我們希望使用單一裝飾器來處理所有 listener 事件。

public class WebDriverWrapper implements WebDriver {
    private final WebDriver driver;
    WebDriverWrapper(WebDriver driver) {
        this.driver = driver;
    }
    // custom implementation goes here
    @Override
    public WebElement findElement(final By by) {
        // custom implementation goes here
        return driver.findElement(by);
    }
}

public class testDecorator extends EventFiringDecorator<WebDriver> {

    @Override
    public Object call(Decorated<?> target, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        if ("findElement".equals(methodName)) {
            WebDriverWrapper newDriver = new WebDriverWrapper((WebDriver) target.getOriginal());
            return newDriver.findElement((By) args[0]);
        }
        return super.call(target, method, args);
    }
}

關於上述範例的一些注意事項,我們僅覆寫了 'general' call 方法,並針對每次呼叫檢查方法名稱。若不深入探討裝飾器,也可以覆寫類別實例的呼叫,以提供更精確的方法。為了展示更多功能,讓我們修改一下範例。我們可以修改 WebElement context,因為我們可能關心子元素和 WebDriver 找到的元素 (WebDriver 和 WebElement 都擴展了 SearchContext)。

public class WebElementWrapper implements WebElement {
    private final WebElement element;
    WebElementWrapper(WebElement element) {
        this.element = element;
    }
    @Override
    public WebElement findElement(final By by) {
        // custom implementation goes here
        return element.findElement(by);
    }
}

public class WebElementDecorator extends EventFiringDecorator<WebDriver> {
    @Override
    public Decorated<WebElement> createDecorated(WebElement original) {
        return new DefaultDecorated<>(original, this) {
            @Override
            public Object call(Method method, Object[] args) throws Throwable {
                String methodName = method.getName();
                if ("findElement".equals(methodName)) {
                    // custom implementation goes here
                    WebElementWrapper element = new WebElementWrapper(getOriginal());
                    return element.findElement((By) args[0]);
                }
                return super.call(method, args);
            }
        };
    }
}

在上面的範例中,我們仍然採用非常類似的方法來覆寫 call 方法,但現在我們也針對 WebElement 實例。

註冊 Listener

new EventFiringWebDriver(driver).register(listener1).register(listener2); // Old approach
new EventFiringDecorator(listener1, listener2); // New approach

監聽事件

WebDriverListener 類別中一項提升生活品質的變更,是使用了 'default'。在 Java 中,default 關鍵字用於介面方法時,表示該方法具有預設實作。如果實作介面的類別選擇不覆寫該方法,它將繼承預設實作。這項變更允許拆分 listener,而無需實作您不需要或不關心的不必要方法。

使用 before/after 方法呼叫監聽特定事件

// Old approach
public class AlertListener implements WebDriverEventListener {
    @Override
    public void beforeAlertAccept(final WebDriver driver) {
        // custom implementation goes here
    }
// implement every method in interface
}

// New approach
public class AlertListener implements WebDriverListener {
    @Override
    public void beforeAccept(Alert alert) {
        // custom implementation goes here
    }
// does not need to implement every method in interface
}

監聽通用事件

一項引入的變更是能夠監聽通用事件。一個使用案例是在平行化測試套件中記錄資訊。現在有一種更簡單的替代方案,即覆寫一個方法呼叫,而不是建立一個 listener 並覆寫每個方法來新增簡單的日誌語句。以下我覆寫了 beforeAnyCall,但也存在 afterAnyCall,它也具有對裝飾方法的呼叫結果。

public class Listener implements WebDriverEventListener {
    private static final Logger LOGGER = Logger.getLogger(Listener.class.getName());

    @Override
    public void beforeAnyCall(Object target, Method method, Object[] args) {
        logger.debug("Thread: " + Thread.currentThread().getName() +
                " | Method Name: " + method.getName() +
                " | Method Args: " + Arrays.toString(args));
    }
}

此外,還新增了監聽更具體的通用事件的功能。回到日誌記錄範例,beforeAnyCall 是除錯資訊或追蹤執行緒動作的好方法,但可能會產生過多雜訊。在相同的使用案例中,我們可能只關心 WebDriver 或 WebElement 呼叫。可以針對 WebDriver 和衍生物件 (WebElement、Alert 等) 的實例覆寫 before/after 事件。

public class Listener implements WebDriverEventListener {
    private static final Logger LOGGER = Logger.getLogger(Listener.class.getName());

    @Override
    public void beforeAnyWebDriverCall(WebDriver driver, Method method, Object[] args) {
        logger.debug("Thread: " + Thread.currentThread().getName() +
                " | Method Name: " + method.getName() +
                " | Method Args: " + Arrays.toString(args));
    }

    @Override
    public void beforeAnyWebElementCall(WebElement element, Method method, Object[] args) {
        logger.debug("Thread: " + Thread.currentThread().getName() +
                " | Method Name: " + method.getName() +
                " | Method Args: " + Arrays.toString(args));
    }
}

以上是一些關於如何轉換程式碼的通用範例!祝您測試愉快!