Repository navigation
feat(pagefactory): support decorating By and AppiumBy fields - #2492
hoanle0126 wants to merge 3 commits into
Conversation
mykola-mokhnach
left a comment
There was a problem hiding this comment.
Review notes inline.
|
Thanks @mykola-mokhnach for the thorough review! I have updated the PR in
|
mykola-mokhnach
left a comment
There was a problem hiding this comment.
AppiumFieldDecorator.java:117:elementByBuilderis now one instance shared by everydecoratecall (new field, previously a freshDefaultElementByBuilderper locator).decorateBymutates it withsetAnnotated(field)andsetAnnotated(null). If one decorator is used from two threads, for example parallel page initialisation, thread A'ssetAnnotated(null)infinallycan run while thread B is mid-build. B then gets an NPE or builds a locator from the wrong field. Should this be one builder perdecorateBycall, or synchronised?AppiumFieldDecorator.java:119: The two paths treat missing annotations differently. AContentMappedBy-typed field with no annotations is always replaced with aByIdOrName(fieldName)locator (seeunannotatedContentMappedByin the test), because that branch returns before thehasLocatorAnnotationscheck. A plainByfield in the same state is left alone. A user who assigns theContentMappedByfield in a constructor or setup method gets the value overwritten. Is that intended?AppiumFieldDecorator.java:130:unwrapSingleByreads the privatebysfield ofByChained/ByAllby reflection and falls back silently onReflectiveOperationException. Both classes live in this project, so a public accessor would avoid the reflection. A rename or refactor ofbyswould otherwise make the unwrap stop working with no signal, andcoerceToTypewould returnnull.AppiumFieldDecorator.java:153:hasLocatorAnnotationshard-codes the list of supported annotations separately from the builder. An annotation added to the builder later but missing from this list leavesByfieldsnull, becausedecorateByreturns early. Could the builder expose "has any locator annotation" so there is one source of truth?
36c20e8 to
342f11a
Compare
|
Thanks @mykola-mokhnach for the follow-up review! Updated in
|
mykola-mokhnach
left a comment
There was a problem hiding this comment.
- Annotated
Byfields with an initial value are overwritten.@FindBy(id="x") private By loc = By.id("y");is replaced atinitElements. Only unannotated fields are preserved (preInitializedByin the test). Is overwriting intended now that the earlier "already initialised" guard is gone? The earlier thread on this was resolved based on that guard.
| return this; | ||
| } | ||
|
|
||
| private ContentType resolveContentType(SearchContext context) { |
There was a problem hiding this comment.
The default changes from always NATIVE_MOBILE_SPECIFIC to auto-detection when no content type is set. Code that builds new ContentMappedBy(map) and passes it to driver.findElement used to get the native locator. On a context that is neither HasBrowserCheck nor SupportsContextSwitching (a plain RemoteWebDriver or a mock), it now gets the web one. toString() still reports the native entry when nothing is forced, so logs can show a locator that isn't the one used. The class Javadoc doesn't mention the new behavior.
| public class ContentMappedBy extends By { | ||
| private final Map<ContentType, By> map; | ||
| private ContentType currentContent = NATIVE_MOBILE_SPECIFIC; | ||
| @EqualsAndHashCode.Exclude |
There was a problem hiding this comment.
With @EqualsAndHashCode.Exclude on currentContent, two instances over the same map but forced to different content types now compare equal and share a hash. A Set or Map keyed by By would conflate locators that resolve to different elements. Is that acceptable, or should equality use only the forced type, if any?
| if (result == null) { | ||
| result = coerceToType(type, builder.buildDefaultBy()); | ||
| } | ||
| return result; |
There was a problem hiding this comment.
If the field type is a narrow By subtype and no built locator fits, the field stays null and initElements succeeds. Examples: @AndroidFindBy(xpath=...) AppiumBy, an AppiumBy field with only @FindBy, or an AppiumBy field with several annotations. (unwrapSingleBy only flattens single-element chains.) The first use is an NPE with no hint of the cause, while conflicting annotations do throw IllegalArgumentException. The test pins the null result but nothing documents it. A WARN log or a Javadoc note on the decorator would help.
| if (annotatedElement == null) { | ||
| return false; | ||
| } | ||
| return SeleniumAnnotationsCompat.isPresent(annotatedElement, FindBy.class) |
There was a problem hiding this comment.
this does not look very reliable to me. how can we make sure another annotation will be added to this list and not forgotten?
Proposed changes
Fixes #1940
ByandAppiumByfields inAppiumFieldDecoratorwhen annotated with Appium or Selenium locator annotations (@AndroidFindBy,@AndroidFindBys,@AndroidFindAll,@iOSXCUITFindBy,@iOSXCUITFindBys,@iOSXCUITFindAll,@FindBy,@FindBys,@FindAll).DefaultElementByBuilder.buildMobileBywhen only one locator is present (bys.length == 1) so single@AndroidFindBy/@iOSXCUITFindByannotations resolve directly toAppiumBy(e.g.AppiumBy.id(...),AppiumBy.accessibilityId(...)) rather than wrapping a 1-element array inByChained/ByAll.By/AppiumByfields untouched (nullreturned bydecorate) so pre-initializedByconstants on page objects are preserved.ByFieldDecorationTestcovering Android, iOS, browser/webview fallback,@HowToUseLocators,Widgetsub-fields, pre-initializedByfields, and incompatible subtype validation.Types of changes