Open sbabcoc opened 6 years ago
To clarify, the issue here is that collections can represent elements that no longer exist, or that collections will fail to represent elements that have appeared since the collection was initially built.
It may be sufficient to repopulate collections whenever element references they contain go stale. The current behavior merely refreshes existing references as they are used, ignoring the larger implications of stale references regarding the integrity of the entire collection.
To facility collection integrity checking, each item's root element will need a reference to the collection that contains it. If an item root element goes stale, the collection that contains it should be entirely repopulated.
The structure required to implement collection integrity checking is probably already in place, given that every enhanced web element retains the context it belongs to - its parent container. The element refresh logic will need to keep track of the highest-level component collection it encounters, repopulating this collection from top to bottom.
The refreshContext()
method of the WrapsContext interface is no help here, as this is a bottom-up operation rather than the top-down repopulation we need in this scenario. Here, we'll need to invoke the core implementation of newComponentList()
or newComponentMap()
to repopulate.
If the size of a component list changes, or if the key set of a component map changes, this indicates that the collection has gone out of sync with the content it models. It may be desirable to implement some sort of notification mechanism, to enable models to synchronize with the system under test.
The methods used to retrieve elements from collections would need to perform the check for referential integrity, rebuilding the collection if a stale reference is detected. Each item in the collection (list or map) includes an element reference. When a client retrieves an item from the collection, the element reference must be tested:
element.aquiredAt()
element.getTagName()
Open question: How should we handle "optional" elements? I think that collections should explicitly prohibit the use of "optional" elements as item containers, because there's no way to build the collection without know what's in it.
I don't know for certain, but I think the code needed to rebuild collections would just need to be extracted from the constructors for ContainerList
and ContainerMap
. These would be declared as new protected
methods, so that subclasses could perform additional operations as required.
I don't know if it's possible for collection items to have "native" container elements. The implementation may need to ensure that container elements are "enhanced" (i.e. - RobustWebElement) objects, as native elements lack the ability to refresh themselves.
Another thing to consider is how to handle map entries that no longer exist. These should probably throw StaleElementReferenceException for entries that once existed but no longer do after the collection is rebuilt.
I'm not sure that this issue is solvable for component lists, save to determine that the size of the collection has changed. The reason is that the component container element selectors are just indexed versions of the selector from which the collection was originally populated. By definition, the index of the component in the list will always match the index in the selector.
For component maps, I should be able to compare the output of the getKey
method to the associated container element key on the switchTo
call. However, there's currently no explicit indication that a component is an element in a map. To resolve this part of the issue, I'll need to define a new interface that identifies the component as a map element. The API currently verifies collectability, but I need to add the ability for components to retain specific collection association:
Currently, I retain the parent container and locator used to acquire the container elements for the members of the collection. However, collections aren't currently linked into the page model hierarchy. This is a bit sticky, because collections aren't search contexts, and the current structure consists entirely of nested search contexts. You don't switch to a collection; you can only switch to search contexts.
I could add the collection membership interface/implementation during the "enhancement" process. This is a bit sneaky, but the client code shouldn't need to be aware of this implementation detail. Once the association is established between component and collection, I need to add code to the switchTo
method to verify that the container element is still valid. For maps, this should be a matter of confirming that the value returned by the getKey
method matches the corresponding map key.
Collection integrity checks will be performed when we switch to the search context of a member of a collection. If the size of the list or the composition of the map changes, the response of the API will be to publish notifications to registered listeners. To be useful, these notifications will need to include an indication of which collection has changed. These notifications could be used for synchronization.
Due to the auto-recovery behavior of RobustWebElement objects, it's possible to have the items in an existing component collection silently fall out of sync with the actual page content they represent.
All of the solutions I've thought about so far will either impose overhead on every collection access or fire too late to be bulletproof.
This issue is complicated by the prospect of nested collections. When an element goes stale, all of the collections that contain the element must be rebuilt. This will require logic in the reference refresh implementation to check for collection associations. Ultimately, it may prove to be impossible to implement bulletproof auto-recovery for nested collections.