sbabcoc / Selenium-Foundation

Selenium Foundation is an automation framework designed to extend and enhance the capabilities provided by Selenium (WebDriver).
Apache License 2.0
60 stars 16 forks source link

Add referencial integrity checks to component collections #13

Open sbabcoc opened 6 years ago

sbabcoc commented 6 years ago

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.

sbabcoc commented 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.

sbabcoc commented 4 years ago

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.

sbabcoc commented 3 years ago

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:

  1. Grab the time the reference was acquired: element.aquiredAt()
  2. Perform a low-cost operation that uses the reference: element.getTagName()
  3. Grab the element acquisition time again and compare it to the previous value.
  4. If the times don't match, this indicates that the element had gone stale and had to be refreshed. The collection must be rebuilt if the reference goes stale.
  5. Item retrieval is performed following the referential integrity check and potential repopulation of the collection.

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.

sbabcoc commented 3 years ago

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.

sbabcoc commented 3 years ago

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.

sbabcoc commented 2 years ago

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.

sbabcoc commented 2 years ago

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.

sbabcoc commented 2 years ago