EventFiringWebDriver? EventFiringWebElement ? What is this?

I am working on this teardown logic, that is to find and delete all entries created after or during test suite start time.

It is defined like:

@InheritConstructors
public class BaseListPage extends BaseCRMPage {
	public static final String FilterSidebar = 'Shared repository/Zoho pages/CRM/List pages/Filter Sidebar/';
	
	public static final String CreatedByFilterName = "Created By",
	CreatedTimeFilterName = "Created Time";
	
	private static final String TableDataXpath = "//lyte-yield[@yield-name = 'contentYield']";
	
	public static final DateTimeFormatter DateTimeFormat = DateTimeFormatter.ofPattern("MM-dd-yyyy h:mm a");

	// ...business logic and methods...

	private List<TestObject> getAllCreatedAfter(LocalDateTime time) {
		return DriverFactory.getWebDriver()
			.findElements(By.xpath("${this.TableDataXpath}//lyte-exptable-tr"))
			.stream()
			.filter { WebElement rowElement ->
				final LocalDateTime createdTime = getCreatedTimeFromRowElement(rowElement)
				return createdTime.isAfter(time) || createdTime.equals(time);
			}
			.collect { WebElement rowElement -> return WebUI.convertWebElementToTestObject(rowElement) }
	}

	private LocalDateTime getCreatedTimeFromRowElement(WebElement rowElement) {
		return LocalDateTime.parse(rowElement
				.findElement(By.xpath("//*[contains(concat(' ', @class, ' '), ' newDTField ')]"))
				.getText(),
			this.DateTimeFormat);
	}
}

When my business logic hits the method in question, getAllCreatedAfter(), I face the following issue:

Test Cases/New Zoho/Practice Creation/Teardown FAILED.
Reason:
groovy.lang.MissingMethodException: No signature of method: com.signaturemd.pages.list.PracticeListPage.getCreatedTimeFromRowElement() is applicable for argument types: (org.openqa.selenium.support.events.EventFiringWebDriver$EventFiringWebElement) values: [[[ChromeDriver: chrome on WINDOWS (daddc317e0da7c81804544d6a48cf732)] -> xpath: //lyte-yield[@yield-name = 'contentYield']//lyte-exptable-tr]]

The topmost entry in the stack trace for this Exception…is the first line in the callback, where I am trying to hit getCreatedTimeFromRowElement().

Keep in mind, that I have a NewTestListener that uses my SMDWebDriverUtils, which is defined to be:

package com.signaturemd.utils

import org.openqa.selenium.WebDriver
import org.openqa.selenium.chrome.ChromeDriver
import org.openqa.selenium.chrome.ChromeOptions

import com.kms.katalon.core.webui.driver.DriverFactory

import internal.GlobalVariable

public final class SMDWebDriverUtils {
	private static WebDriver driver;


	public static void SetUpDriver() {
		final String userHomeDirectory = System.getProperty('user.home');

		System.setProperty('webdriver.chrome.driver',
				"${userHomeDirectory}\\${GlobalVariable.pathToKatalon}\\configuration\\resources\\drivers\\chromedriver_win32\\chromedriver.exe");

		ChromeOptions chromeProfile = new ChromeOptions();
		chromeProfile.addArguments("user-data-dir=${userHomeDirectory}\\${GlobalVariable.pathToUserDataDir}");
		chromeProfile.addArguments('profile-directory=Default');

		this.driver = new ChromeDriver(chromeProfile);
		this.driver.manage().window().maximize();

		DriverFactory.changeWebDriver(this.driver);
	}

	public static void CloseDriver() {
		if (this.driver != null)
			this.driver.quit()
	}

	public static void DoWebAction(Closure onAction) {
		this.SetUpDriver();

		onAction();

		this.CloseDriver();
	}
}

When I attempt to debug the code, putting breakpoint on the offending line, I see that:

but, when I look into this EventFiringWebDriver, I see it is deprecated.

The SMDWebDriverUtils explicitly sets DriverFactory.getWebDriver() to ChromeDriver object.

What is causing this absurd state issue to happen, and what can I do about it?

NOTE: Using SMDWebDriverUtils.driver to get WebDriver doesn’t work either, as it gives similar issue:

image

You are a bit confused.

EventFiringWebDriver was available in Selenium3 and was deprecated in Selenium 4

Katalon Studio bundles Selenium 4, not Selenium 4.

Therefore you should look at the javadoc of Selenium 3, not 4.

ok, but what is causing this to happen? No matter what I do: whether I:

  • keep my code the way it is,
  • use SMDWebDriverUtils.driver instead of DriverFactory.getWebDriver(), or
  • create some temp test object, and then use WebUiCommonHelper.findWebElements() on it

I face some variation of this issue.

And when I debug those expression, I do indeed see that there are multiple WebElements returned by them…

Test Cases/New Zoho/Practice Creation/Teardown FAILED.
Reason:
groovy.lang.MissingMethodException: No signature of method: com.signaturemd.pages.list.PracticeListPage.getCreatedTimeFromRowElement() is applicable for argument types: (org.openqa.selenium.support.events.EventFiringWebDriver$EventFiringWebElement) values: [[[ChromeDriver: chrome on WINDOWS (daddc317e0da7c81804544d6a48cf732)] → xpath: //lyte-yield[@yield-name = ‘contentYield’]//lyte-exptable-tr]]

EventFiringWebElement ? What is this? The name suggests an HTML element that is capable of firing some events? Just strange. I can not imagine what it is.

The following is the javadoc of selenium-support 3.141.59
https://javadoc.io/doc/org.seleniumhq.selenium/selenium-support/3.141.59/index.html

In this javadoc, I can not find a class named EventFiringWebElement.

Any typo?

I guess, you just want org.openqa.selenium.WebElement instead.

Not sure from where that came, in the first post, EventFiringWebDriver is mentioned:
https://javadoc.io/doc/org.seleniumhq.selenium/selenium-support/3.141.59/index.html

Altough I found some reference of that in:
https://www.selenium.dev/selenium/docs/api/py/webdriver_support/selenium.webdriver.support.event_firing_webdriver.html
which is about a python wrapper for Selenium 4

I wish there was…

image

I’m blocked by this issue, on code that was working the other day, but is not working today…

You didn’t see the message groovy.lang.MissingMethodException: No signature of method: ... the other day. Fine. But it does not necessarily proves that the problematic line of the code was executed and passed OK. It is likely the case that the line was just skipped and unexecuted.

Perhaps, you should forget how your code worked the other day. Just concentrate how it works now.


@mwarren04011990

Are you sure, there is a class EventFiringWebElement provided by Selenium, and you should be able to use it ?

When I switch back to the branch from that other day, and debug it around the same point (it was on some derived class instead of the base list page), I see that bizarre EventFiringWebElement class again…

I’m losing sanity over this…

When I de-compile the EventFiringWebDriver class on my computer, I see EventFiringWebElement pop up in the source code, in several places:



I have no more input.

If I understand your code right, this is where you have to dig more.
You expect here to retrieve a WebElement object, therefore the signature of your method, but you get that bull**it instead.

So, the issue may came from the DiverFactory I guess, but I have no idea how.
Also, i have no idea how stream behave in your case.
It may hapen that, at the moment you attempt to filter the list of candidate elements, the locating process is still ongoing, so you get an events list instead of a list of elements.

What happens if you try to use Katalon keywords instead?

Or split your code to let the locators work and filter after that?

I change the method implementation to

	private List<TestObject> getAllCreatedAfter(LocalDateTime time) {
		List<WebElement> rowElements = DriverFactory.getWebDriver()
			.findElements(By.xpath("${this.TableDataXpath}//lyte-exptable-tr"))
			
		return rowElements.stream()
			.filter { WebElement rowElement ->
				final LocalDateTime createdTime = this.getCreatedTimeFromRowElement(rowElement)
				return createdTime.isAfter(time) || createdTime.equals(time);
			}
			.collect { WebElement rowElement -> return WebUI.convertWebElementToTestObject(rowElement) }
	}

and it’s still not working.

Groovy-fying it also doesn’t work:

	private List<TestObject> getAllCreatedAfter(LocalDateTime time) {
		List<WebElement> rowElements = DriverFactory.getWebDriver()
			.findElements(By.xpath("${this.TableDataXpath}//lyte-exptable-tr"))
		
		return rowElements.findResults { WebElement rowElement ->
				final LocalDateTime createdTime = this.getCreatedTimeFromRowElement(rowElement)
				if (createdTime.isAfter(time) || createdTime.equals(time))
					return rowElement;
				
				return null;
			}
			.collect { WebElement rowElement -> return WebUI.convertWebElementToTestObject(rowElement) }
	}

omg… I was able to fix it, but idk if spelling out what I ended up doing, is going to add value to any future readers who may face this issue…

Some more context, before we proceed…

I have two classes:

  • PracticeListPage, which extends…
  • BaseListPage

Several commits ago, when this code was working, all these concerns were implemented on the PracticeListPage. My code need to move to the BaseListPage, which is what I did across those several commits.

PracticeListPage was defined to be:

package com.signaturemd.pages.list

import static com.kms.katalon.core.testobject.ObjectRepository.findTestObject

import java.time.LocalDateTime
import java.time.format.DateTimeFormatter

import org.openqa.selenium.By
import org.openqa.selenium.Keys
import org.openqa.selenium.WebElement

import com.kms.katalon.core.testobject.ConditionType
import com.kms.katalon.core.testobject.TestObject
import com.kms.katalon.core.webui.driver.DriverFactory
import com.kms.katalon.core.webui.keyword.WebUiBuiltInKeywords as WebUI
import com.signaturemd.utils.GeneralWebUIUtils

public class PracticeListPage extends BaseListPage {
	public static final String CreatedByFilterName = "Created By",
		CreatedTimeFilterName = "Created Time";
		
	private static final String ResultsTable = "Page_Practice List/Results Table";
	private static final String TableDataXpath = "//lyte-yield[@yield-name = 'contentYield']";
	
	public static final DateTimeFormatter DateTimeFormat = DateTimeFormatter.ofPattern("MM-dd-yyyy h:mm a");
	
	public PracticeListPage() {
		super("https://crm.zoho.com/crm/org724168703/tab/Accounts/custom-view/4623170000000087515/list?page=1")
	}
	
	public void applyCreationFilters() {
		this.searchAndSetFilter(this.CreatedByFilterName,
			findTestObject("Shared repository/Zoho pages/CRM/List pages/Filter Sidebar/Filter By Fields Subsection/Created By Filter/Created By checkbox"),
			findTestObject("Shared repository/Zoho pages/CRM/List pages/Filter Sidebar/Filter By Fields Subsection/Created By Filter/Created By Dropdown/Created By dropdown button"),
			{ TestObject dropdownBtn ->
				WebUI.click(dropdownBtn);
				
				final TestObject loggedInUserOption = findTestObject("Page_Practice List/Filter Sidebar/Filter By Fields Subsection/Created By Filter/Created By Dropdown/Logged in User dropdown option")
				
				WebUI.waitForElementVisible(loggedInUserOption, 2)
				
				WebUI.click(loggedInUserOption)
				
				final TestObject searchField = findTestObject("Page_Practice List/Filter Sidebar/Filter By Fields Subsection/Created By Filter/Created By Dropdown/Search field")
				
				WebUI.sendKeys(searchField,
					Keys.ESCAPE.toString())
				
				WebUI.waitForElementNotVisible(searchField, 2)
			},
		);
		
		this.searchAndSetFilter(this.CreatedTimeFilterName,
			findTestObject("Page_Practice List/Filter Sidebar/Filter By Fields Subsection/Created Time Filter/Created Time checkbox"), 
			findTestObject("Page_Practice List/Filter Sidebar/Filter By Fields Subsection/Created Time Filter/Created Time input field"), 
			"0",
		);
	}
	
	public void deleteAllPracticesCreatedAfter(LocalDateTime time) { 
		this.getPracticesCreatedAfter(time)
			.eachWithIndex { TestObject tableRow, int idx ->
				WebUI.scrollToElement(tableRow, 2);
				
				WebUI.click(getTableRowCheckbox(idx + 1));
			}
		
		WebUI.click(findTestObject("Page_Practice List/Top Row/Actions dropdown button"))
		
		final TestObject deleteDropdownOption = findTestObject("Page_Practice List/Top Row/Actions dropdown/Delete dropdown option")
		
		WebUI.waitForElementVisible(deleteDropdownOption, 2)
		
		WebUI.click(deleteDropdownOption)
		
		WebUI.waitForElementNotVisible(deleteDropdownOption, 2)
		
		GeneralWebUIUtils.HandleConfirmableModal(findTestObject("Page_Practice List/Modal/Modal"), 
			findTestObject("Page_Practice List/Modal/Delete button"),
			GeneralWebUIUtils.OnWaitForDisappear(WebUI.&waitForElementNotPresent, 4))
	}
	
	private List<TestObject> getPracticesCreatedAfter(LocalDateTime time) {
		return DriverFactory.getWebDriver()
			.findElements(By.xpath("${this.TableDataXpath}//lyte-exptable-tr"))
			.stream()
			.filter { WebElement rowElement -> 
				final LocalDateTime createdTime = this.getCreatedTimeFromRowElement(rowElement) 
				return createdTime.isAfter(time) || createdTime.equals(time);
			}
			.collect { WebElement rowElement -> return WebUI.convertWebElementToTestObject(rowElement) }
	}
	
	private TestObject getTableRowCheckbox(int rowNum) { 
		return new TestObject("${this.ResultsTable}/#${rowNum} table row checkbox")
			.addProperty("xpath", 
				ConditionType.EQUALS, 
				"(${this.TableDataXpath}//*[@lt-prop-class = 'customCheckBox'])[${rowNum}]",
			)
	}
	
	private LocalDateTime getCreatedTimeFromRowElement(WebElement rowElement) { 
		return LocalDateTime.parse(rowElement
				.findElement(By.xpath("//*[contains(concat(' ', @class, ' '), ' newDTField ')]"))
				.getText(), 
			this.DateTimeFormat);
	}
}

What the BaseListPage ends up being…well, you see that in this question details up above.

What I did…

I went back to the last known git commit, for which this code was working. I then started redoing those changes (namely moving the code to the BaseListPage). When I do so, right away, I face the issue! This has got to be where the problem is!

I back-step:

  • put a stub for getAllCreatedAfter on the base class, implement it on the practice list page, which requires
  • anything that was private to become protected

I dry-run it (run it in debug mode, put breakpoint right after we click the action button to delete the records in the AUT), it works!

I then remove the stub, move the implementation back to base class, go back to present-code-state, apply what I did. (This include changing the private stuff to public, even though PracticeListPage at this point contains just constructor.)

It WORKS!!!

Well … it helps, at least for my eyes.
What we learned from here, imho.
The dark side of Page Object Modeling aproach.
With the intention to make the testcases code more clean by using custom wrapping classes, you may land into the situation, sometime, that you break the model :slight_smile:
So everything is screwed.

As somebody told me once:

Sometime the error is between the chair and the keyboard

(no offence)
:smiley:

Yea, and I was finna write an article on here promoting the Page Object Model, while illustrating some of its potential pitfalls…