-
Notifications
You must be signed in to change notification settings - Fork 3
React Animal History - Search By Id #1085
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
base: develop
Are you sure you want to change the base?
Conversation
| 8. Permissions: Folder Read Permission and dataset read permissions | ||
| 9. Metrics: Report and filter usage. |
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.
Are these requirements or non-requiremnents? I'm unclear on the break between them and the first seven.
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.
requirements
| - Shows read-only summary: "Viewing {count} animal(s): {subject1}, {subject2}, ..." | ||
| - Shows "Modify Search" button that switches to ID Search mode with current subjects pre-populated | ||
| - Reports filter by URL subjects without requiring ID resolution | ||
| - No ID limit applies (URL-provided subjects are assumed already validated/resolved) |
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.
If it's worth capping the limit in "normal" usage (for performance, I guess?), it seems like we shouldn't exempt URL-provided values. But I'm also not really clear on why this is a useful mode.
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 is the same UI as participant history which completely derives the filters from the URL. This is pretty much only used when clicking on links generated in the EHR, so we assume we don't need to validate the parameters. In the normal search we're just trying to scope the amount of Ids that can be filtered, but may need to revisit this as we get more testing done.
| }, [activeReport]); | ||
|
|
||
| const handleFilterChange = useCallback(( | ||
| newFilterType: 'idSearch' | 'all' | 'aliveAtCenter' | 'urlParams', |
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.
Seems like a lot of separate hard-codings of these values.
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.
These were moved to variables.
| 2. Likelihood: Medium | ||
| 3. Mitigation: | ||
| 1. Id resolution feedback for in UI. | ||
| 2. Implemented as experimental feature to be able to view old and new animal history results side-by-side. |
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.
What does this feature flag control? Being able to open the new and old versions without toggling an experimental flag seems like it would be more convenient. Or does the flag control which is the target URL by default for existing UI?
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 updated the spec. This is going to just be hidden with link in admin page for now.
| - Repeat with tab-separated and semicolon-separated lists | ||
|
|
||
| 4. **Multi-Animal Search (Mixed Separators)** | ||
| - Enter IDs using multiple separators in single input: "ID1, ID2\nID3;ID4\tID5" |
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.
Because IDs can contain special characters the test should try IDs with separator values (e.g. ID123,A).
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.
added
| - Verify validation error appears: "Maximum of 100 animal IDs allowed. You entered 101 IDs." | ||
| - Verify "Update Report" button is disabled | ||
| - Remove one ID to get back to 100 | ||
| - Verify error clears and "Update Report" button re-enables |
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.
Are IDs stored as string values? Would another boundary case be an ID that is a very long string, or MAXINT + 1?
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.
added
| 4. **Mixed Valid/Invalid IDs:** Resolve valid IDs; show invalid in "Not Found" section | ||
| 5. **All IDs Not Found:** Display "Not Found" section only; reports panel shows no data message | ||
| 6. **Alias Resolves to Same ID:** If multiple input values resolve to the same animal ID, show all in "Resolved" section but pass de-duplicated list to reports | ||
| 7. **Special Characters in IDs:** Support IDs with hyphens, underscores, and other special characters |
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.
It might be worthwhile to call out some explicit special characters to test. For example, any characters that could be interpreted as a comment(// ' #), string(" ') or something could have special meaning in a SQL query or a url (? @ :).
Also worthwhile to consider where in the ID the special character is. An ID that starts with @123, or '123 or an ID that looks like a link / email address (123@abc.def)
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.
added
| - Close browser, reopen bookmark | ||
| - Verify exactly 50 animals display correctly | ||
| - Verify no ID limit validation (URL Params mode bypasses 100 limit) | ||
| - Verify URL hash length doesn't cause browser issues |
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.
Another boundary case could be 10 IDs that are 1K characters long (or some other ridiculous size) and bookmark that.
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.
added
| private void clickFilterButton(String buttonText) | ||
| { | ||
| clickButton(buttonText); // "All Records", "Alive at Center", or "ID Search" | ||
| sleep(500); // Allow mode transition |
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'm not opposed to using sleeps, if they work that's great. Sometimes waiting for an element to go stale, the button or some element to show up can make the test a little more reliable and have a quicker run time. Something like:
shortWait().until(ExpectedConditions.stalenessOf(button));
Not sure if that will be applicable, but just a thought.
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 need to work through a few of these.
labkey-tchad
left a comment
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.
asserts are out of scope for page (and component) classes. Page classes should provide methods to inspect and control the page but test classes should assert functionality.
If these assertions would be useful elsewhere, you could put them into a separate helper class, maybe.
| waitForElement(Locators.REPORT_TARGET); | ||
| // Wait for data region to appear and stabilize | ||
| longWait().until(d -> { | ||
| try | ||
| { | ||
| WebElement reportTarget = Locators.REPORT_TARGET.findElement(getDriver()); | ||
| return DataRegion(getDriver()).timeout(5000).find(reportTarget) != null; | ||
| } | ||
| catch (Exception e) | ||
| { | ||
| return false; | ||
| } | ||
| }); |
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.
A normal wait for this element would give more informative error messages. longWait().until only includes a useful message if you use one of the ExpectedConditions classes.
| waitForElement(Locators.REPORT_TARGET); | |
| // Wait for data region to appear and stabilize | |
| longWait().until(d -> { | |
| try | |
| { | |
| WebElement reportTarget = Locators.REPORT_TARGET.findElement(getDriver()); | |
| return DataRegion(getDriver()).timeout(5000).find(reportTarget) != null; | |
| } | |
| catch (Exception e) | |
| { | |
| return false; | |
| } | |
| }); | |
| WebElement reportTarget = Locators.REPORT_TARGET.refindWhenNeeded(getDriver()); | |
| // Wait for data region to appear and stabilize | |
| DataRegion(getDriver()).timeout(5000).waitFor(reportTarget); |
| longWait().until(d -> { | ||
| boolean buttonReady = !getText(Locators.SEARCH_BY_IDS_BUTTON).contains("Searching"); | ||
| boolean hasResults = isElementPresent(Locators.REPORT_TARGET) || | ||
| isElementPresent(Locators.ID_RESOLUTION_FEEDBACK) || | ||
| isElementPresent(Locators.VALIDATION_ERROR); | ||
| return buttonReady && hasResults; | ||
| }); |
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.
Add a message to the wait to have more informative failures.
| longWait().until(d -> { | |
| boolean buttonReady = !getText(Locators.SEARCH_BY_IDS_BUTTON).contains("Searching"); | |
| boolean hasResults = isElementPresent(Locators.REPORT_TARGET) || | |
| isElementPresent(Locators.ID_RESOLUTION_FEEDBACK) || | |
| isElementPresent(Locators.VALIDATION_ERROR); | |
| return buttonReady && hasResults; | |
| }); | |
| longWait().withMessage("Search did not complete").until(d -> { | |
| boolean buttonReady = !getText(Locators.SEARCH_BY_IDS_BUTTON).contains("Searching"); | |
| boolean hasResults = isElementPresent(Locators.REPORT_TARGET) || | |
| isElementPresent(Locators.ID_RESOLUTION_FEEDBACK) || | |
| isElementPresent(Locators.VALIDATION_ERROR); | |
| return buttonReady && hasResults; | |
| }); |
| public ReactAnimalHistoryPage waitForReportPanelToLoad() | ||
| { | ||
| // Wait for the report target area to appear | ||
| longWait().until(d -> isElementPresent(Locators.REPORT_TARGET)); |
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.
DRY
| longWait().until(d -> isElementPresent(Locators.REPORT_TARGET)); | |
| waitForReportToLoad(); |
| // Wait for the report target area to appear | ||
| longWait().until(d -> isElementPresent(Locators.REPORT_TARGET)); | ||
| // Wait for category tabs to be present (indicates panel is fully rendered) | ||
| longWait().until(d -> isElementPresent(Locators.CATEGORY_TAB)); |
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.
Use standard element wait for better error message
| longWait().until(d -> isElementPresent(Locators.CATEGORY_TAB)); | |
| Locators.CATEGORY_TAB.waitForElement(longWait()); |
| public ReactAnimalHistoryPage assertResolvedAliasContains(String inputId, String resolvedId, String aliasType) | ||
| { | ||
| assertElementPresent(Locators.RESOLVED_SECTION_TITLE); | ||
| Locator resolvedItem = Locators.RESOLVED_ITEMS.containing(inputId); |
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.
Partial text match doesn't seem correct here.
| Locator resolvedItem = Locators.RESOLVED_ITEMS.containing(inputId); | |
| Locator resolvedItem = Locators.RESOLVED_ITEMS.withText(inputId); |
| WebElement searchByIdPanel = Locators.SEARCH_BY_ID_PANEL.findWhenNeeded(this); | ||
| WebElement animalIdTextarea = Locators.ANIMAL_ID_TEXTAREA.findWhenNeeded(this); | ||
| WebElement searchByIdsButton = Locators.SEARCH_BY_IDS_BUTTON.findWhenNeeded(this); | ||
| WebElement allAnimalsButton = Locators.ALL_ANIMALS_BUTTON.findWhenNeeded(this); | ||
| WebElement aliveAtCenterButton = Locators.ALIVE_AT_CENTER_BUTTON.findWhenNeeded(this); | ||
| WebElement reportTarget = Locators.REPORT_TARGET.findWhenNeeded(this); | ||
| WebElement idResolutionFeedback = Locators.ID_RESOLUTION_FEEDBACK.findWhenNeeded(this); | ||
| WebElement validationError = Locators.VALIDATION_ERROR.findWhenNeeded(this); | ||
| WebElement emptyStatePlaceholder = Locators.EMPTY_STATE_PLACEHOLDER.findWhenNeeded(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.
ElementCache members should be declared final
| WebElement searchByIdPanel = Locators.SEARCH_BY_ID_PANEL.findWhenNeeded(this); | ||
| WebElement animalIdTextarea = Locators.ANIMAL_ID_TEXTAREA.findWhenNeeded(this); | ||
| WebElement searchByIdsButton = Locators.SEARCH_BY_IDS_BUTTON.findWhenNeeded(this); | ||
| WebElement allAnimalsButton = Locators.ALL_ANIMALS_BUTTON.findWhenNeeded(this); | ||
| WebElement aliveAtCenterButton = Locators.ALIVE_AT_CENTER_BUTTON.findWhenNeeded(this); | ||
| WebElement reportTarget = Locators.REPORT_TARGET.findWhenNeeded(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.
The element cache should only contain elements that won't disappear as long as you remain on the page. Probable just the search panel and its form elements.
Also, the page should actually use the cached elements instead of referencing Locators directly.
| animalHistoryPage | ||
| .clearIdInput() | ||
| .enterAnimalIds(testAnimalId1, testAnimalId2, testAnimalId3) | ||
| .clickSearchByIds() | ||
| .waitForReportToLoad() |
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.
Tests shouldn't have to clear inputs and wait for things to load, except in special cases. The page should handle most of that. Searching for some animals should look more like this:
| animalHistoryPage | |
| .clearIdInput() | |
| .enterAnimalIds(testAnimalId1, testAnimalId2, testAnimalId3) | |
| .clickSearchByIds() | |
| .waitForReportToLoad() | |
| animalHistoryPage | |
| .searchByIds(testAnimalId1, testAnimalId2, testAnimalId3) |
This comment is really for ReactAnimalHistoryPage but, hopefully, it is more clear here
| log("Alive at Center button is disabled - skipping mode activation test"); | ||
| // Button may be disabled if current report doesn't support non-ID filters | ||
| // This is expected behavior based on spec |
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.
Isn't this always testing the Demographics report? Why is this check conditionalized?
| /** | ||
| * Wait for the DataRegion in the report to fully load. | ||
| */ | ||
| public ReactAnimalHistoryPage waitForDataRegionToLoad() |
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.
waitForDataRegionToLoad seems redundant. All of the search methods wait for Locators.REPORT_TARGET, getActiveReportDataRegion waits for the data region, and data regions know how to wait for their data to load.
Rationale
Implements the first phase of React conversion of Animal History. This is currently a hidden view until we get to MVP. Link in the ehrAdmin page. Participant history is available if you have the experimental feature on, you'll get a link at the top of the old participant view which comes up when you click on an Id.
Spec is in this PR
Changes