-
Notifications
You must be signed in to change notification settings - Fork 528
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Task/jenkins 36641 smart web element improvements #1529
Task/jenkins 36641 smart web element improvements #1529
Conversation
…in the WebDriver instance and passing it around; add a couple utility methods for related to waits and element wrapping
…y when finding descendent elements, and (2) returns SmartWebElements, otherwise chaining calls won't work well since we'll lose implict wait behavior B. remove getElement/s since it overlapped with findElements/s and was only visible on SmartWebElement class C. change WebDriverMixin API to differentiate between find/findMany so that client code can work with single element or list of elements D. add WebDrivenMixin.findNow so we have ability to do queries that don't use an implicit wait, so we can check that elements aren't present E. add WebDrivenMinx.untilCondition we can leverage full power of ExpectedConditons.* utilities
…ardPage" to exercise some of the new APIs
* Accepts expressions for css and xpath, if the provided lookup starts with a /, XPath is used | ||
* Provides access to the current WebDriver instance. | ||
* Contains some utility methods for working with waits and SmartWebElement. | ||
* TODO: rename this class as it's *not* a WebDriver but rather more of a DriverHolder. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All of the client code was calling LocalDriver.getDriver()
which returned the thread local'd RemoteWebDriver
. No need for LocalDriver
to impl WebDriver
as far as I can see.
} else { | ||
logger.info(String.format("nothing useful written to browser console; %s entries were hidden", entries.size())); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall these changes make the browser logging stuff a bit more explicit about when 1. nothing is logged, 2. nothing is logged but useless messages, and 3. there are actually useful messages.
} | ||
|
||
@Override | ||
public void click() { | ||
for (int i = 0; i < RETRY_COUNT; i++) { | ||
try { | ||
WebElement e = getElement(); | ||
WebElement e = buildWait().until(ExpectedConditions.elementToBeClickable(element)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This got lost in #1484 and needs to be there to make sure we don't click on elements that are transitioning from disabled to enabled. Frequent cause of flakiness in creation tests when robot picks a repo and clicks "Create Pipeline" while it's still disabled, resulting in a hung test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
interesting
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
right, definitely need this change after talking with Cliff, this was a stupid mistake on my part
return (T)((JavascriptExecutor)getDriver()).executeScript(script, el, elements); | ||
// TODO: validate this change is correct | ||
WebElement el = getElement(); | ||
return (T)((JavascriptExecutor)getDriver()).executeScript(script, el); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kzantow perhaps you can weigh in whether this change is appropriate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why change this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
elements
no longer exists
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you let me know if this change seems ok in light of the fact that there is no List from getElements()
to pass down into executeScript
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kzantow can you let me know if you think this change is okay given that getElements()
no longer exists, therefore passing the elements array as the last arg isn't possible anymore
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also don't think removing getElements() is necessary or the right change. Again, making getElements()
support a parent SmartWebElement
will ensure that everything is always operating on the current state of the page when it's called.
return buildWait().until((WebDriver driver) -> { | ||
WebElement matchedElement = getElement().findElement(locator); | ||
return LocalDriver.wrapElement(getDriver(), matchedElement, locator); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please have a look at this code in particular to make sure it makes sense.
} | ||
|
||
@Override | ||
public boolean isDisplayed() { | ||
WebElement e = getElement(); | ||
WebElement e = buildWait().until(ExpectedConditions.visibilityOf(element)); | ||
return e.isDisplayed(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think in its current state this method can only ever return true or throw an exception. I might be wrong. Not sure if this should have some error handling baked in, or maybe left alone.
Looks interesting to me... cc @imeredith maybe take a look on monday? |
public static final int RETRY_COUNT = 3; | ||
|
||
private WebDriver driver; | ||
protected WebElement element; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this I strongly discourage. Part of the thing is that if you have a SmartWebElement
all operations happen when you invoke them, this avoids any cases where you have a stale reference to a WebElement
for some reason
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me see if I can reintroduce that lazy logic so we don't hold a ref to any WebElement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now I finally understand the motivation, so you can wrap the other returned WebElement
s with this same logic. Ok, let me stew on this for just a little while
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right. Otherwise can't really chain findElement
calls together because we'll be returning a mix of SmartWebElement
and RemoteWebElement
which I think would lead to a lot of flake since some WebElement
's have wait behavior baked in and others do not.
The alternative with original code is that we'd need to do all our finds through WebDriverMixin.find
to ensure we always get a SWE, which could lead to really lengthy selectors:
find(".favorites-card-stack .pipeline-card[data-fullname="cliffs-pipeline"] .Checkbox.Favorite > label").click()
rather than
find(".favorites-card-stack .pipeline-card[data-fullname="cliffs-pipeline"]
.findElement(".Checkbox.Favorite > label")
.click()
I find the latter really useful because we can write individual methods on our page objects to grab specific elements, and we can compose those methods just by chaining the findElement
calls.
I wonder if we could add some exception handling into SWE for StaleElementException
that retries the locator. It wouldn't necessarily work for elements fetched via findMany
since the expression stored to fetch them would match multiple elements. I can't say I've run into any StaleElementException
yet but that's part of why I am working on a branch derived from this one to add really thorough tests to personalization as a testing ground.
*/ | ||
public static void use(WebDriver driver, Procedure proc) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would not remove this, it can be useful to write tests that rely on multiple windows
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... still, I would not remove this
* Iterates over all found elements with the given function | ||
* @param fn to perform on the elements | ||
*/ | ||
public void forEach(java.util.function.Consumer<WebElement> fn) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this provided that way to operate on a collection of elements. You mentioned there are cases where we need to pass the list of the base WebElement
to webdriver functions, are there examples where we're doing that?
* Determines if the element is visible | ||
* @return true if visible, false if not | ||
*/ | ||
public boolean isVisible() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i guess this is just duplicate noise, but i really don't like isDisplayed
this mirrors jQuery's terminology
…ge this over to SmartWebElement stuff once #1529 lands
@kzantow I've made some updates to this PR. Based on our past conversations, can you let me know if you think these changes are suitable or should I continue to iterate? Perhaps we can touch base in a meeting if that's easier to discuss. |
@kzantow indicated he wants to add some similar features as in this PR but with less code, so I am closing this PR. I will carry over the improvements to browser console logging in another PR. |
* improve the display of log entries from browser console * extract resource resolving logic into standalone class so we don't dictate the ATH test superclass * fix a bug where ClassicJobApi.createPipeline would not actually respect the parent folder passed in * add "create" method to FreestyleJob * add dependencies for HttpRequest * add utilities for working with the "fully qualified name" for a pipeline so its favorite card can be located * add a new "FreestyleFavoritesTest" and corresponding "FavoritesDashboardPage" to exercise some of the new APIs * add some tests that verify favorite cards can be added and removed for three primary job types * fix public visibility issue * ensure FavoritesAddRemoveTest cleans up any jobs after it finishes running * remove test case that was mostly for testing purposes * rework FavoritesAddRemoveTest to not rely on API's introduced in abandoned PR #1529 * improve the docs for SSEEvents.activityComplete * add a method to build a Freestyle job * add a helper method to navigate back * testing favorites navigation for freestyle and classic * add more browser navigation utilities * add a utility to serve as an adapter between WebElement and By locators * add extra clickability check for "Branches" tab * comments * add test cases for a multibranch pipeline * refactor page object to appropriate source folder / package * add an enum that contains different "status" values used in UI (flattening of "state" and "result" from BlueRun enums) * add tests against the favorite card actions (freestyle, classic pipeline, multibranch) * scan class hierarchy for @Login annotation so that it can be declared on a superclass * refactor duplicated code into "AbstractFavoritesTest" * remove empty file * fix a flakiness issue observed in FavoritesCardsTest.testMultibranch where the favorite card would sometimes update to white color due to receiving a status of "finished" which would fail the test. soemtimes SSE is received for job_run_ended event where state=FINISHED and result=UNKNOWN which is nonsense. must be some race condition where properties are not mutated together. long term we need a cleaner solution where a run fetched in an inconsistent state is simply refetched
Description
While trying to write more comprehensive tests for favorites, I found some limitations in
SmartWebElement
andWebDriverMixin
API's (see immediately below):SmartWebElement
, chained calls tofindElement/s
method would use raw Selenium API's that don't use the polling/wait behavior making them very subject to timing issues. We definitely want to be able to use these API's so we can write a simple selector for a top level element, and then do searches within that element (keeping individual selector expressions simple).WebDriverMixin.find
API always returned a singleSmartWebElement
which might actually correspond to multiple elements. There are times you want to interact with multiple elements, and the rest ofWebDriver
API's use aList<WebElement>
in many, so it doesn't make sense IMO to conflate "one element" and "many elements" intoSmartWebElement
abstraction.My attempt to improve these are described by these changes:
WebElement
interface.WebDriverMixin.find
API to differentiate between find/findMany so that client code can work with single element or list of elementsWebDrivenMixin.findNow
so we have ability to do queries that don't use an implicit wait, so we can check that elements aren't presentWebDrivenMixin.untilCondition
we can leverage full power ofExpectedConditons.*
utility methods. These are powerful and we should offer full-class support.PLEASE NOTE Still some internal cleanup in this PR that needs to happen but I think it's getting close. Please note the TODOs.
Submitter checklist
Reviewer checklist