From 063e82cc0ff82538ee4887c05ac553e9164535a7 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Thu, 14 May 2026 11:39:37 -0700 Subject: [PATCH 1/2] Add synchronization to QueryChartDialog --- .../components/react/QueryChartDialog.java | 123 +++++++++++------- 1 file changed, 77 insertions(+), 46 deletions(-) diff --git a/src/org/labkey/test/components/react/QueryChartDialog.java b/src/org/labkey/test/components/react/QueryChartDialog.java index 2e726ee72f..360415532d 100644 --- a/src/org/labkey/test/components/react/QueryChartDialog.java +++ b/src/org/labkey/test/components/react/QueryChartDialog.java @@ -46,36 +46,48 @@ public String getName() return elementCache().nameInput.get(); } - public QueryChartDialog setTitle(String value) { - elementCache().titleInput.set(value); - elementCache().title.click(); // blur the element + public QueryChartDialog setTitle(String value) + { + doAndWaitForPreview(() -> { + elementCache().titleInput.set(value); + elementCache().title.click(); // blur the element + }); return this; } public QueryChartDialog setSubtitle(String value) { - elementCache().subtitleInput.set(value); - elementCache().title.click(); // blur the element + doAndWaitForPreview(() -> { + elementCache().subtitleInput.set(value); + elementCache().title.click(); // blur the element + }); return this; } public QueryChartDialog setHeight(String value) { - elementCache().heightInput.set(value); - elementCache().title.click(); // blur the element + doAndWaitForPreview(() -> { + elementCache().heightInput.set(value); + elementCache().title.click(); // blur the element + }); return this; } public QueryChartDialog setWidth(String value) { - elementCache().widthInput.set(value); - elementCache().title.click(); // blur the element + doAndWaitForPreview(() -> { + elementCache().widthInput.set(value); + elementCache().title.click(); // blur the element + }); return this; } public QueryChartDialog setUseFullWidth(boolean checked) { - elementCache().fullWidthCheckbox.set(checked); + if (checked) + doAndWaitForPreview(() -> elementCache().fullWidthCheckbox.check()); // always refreshes the svg + else + elementCache().fullWidthCheckbox.uncheck(); return this; } @@ -110,7 +122,7 @@ public boolean getInheritable() public QueryChartDialog clearFieldValue(String fieldLabel) { - elementCache().reactSelectByLabel(fieldLabel).clearSelection(); + doAndWaitForPreview(() -> elementCache().reactSelectByLabel(fieldLabel).clearSelection()); return this; } @@ -119,7 +131,7 @@ public QueryChartDialog clearFieldValue(String fieldLabel) */ public QueryChartDialog selectXAxis(String field) { - elementCache().reactSelectByLabel("X Axis").select(field); + doAndWaitForPreview(() -> elementCache().reactSelectByLabel("X Axis").select(field)); return this; } @@ -145,14 +157,16 @@ private void clickFieldOptions(String label) public QueryChartDialog setLegendPos(String legendPos) { - + RadioButton radio; if ("bottom".equals(legendPos)) - elementCache().legendBottomRadio.check(); + radio = elementCache().legendBottomRadio; else if ("right".equals(legendPos)) - elementCache().legendRightRadio.check(); + radio = elementCache().legendRightRadio; else throw new IllegalArgumentException("Invalid legend value: " + legendPos); + if (!radio.isChecked()) + doAndWaitForPreview(radio::check); return this; } @@ -173,10 +187,7 @@ public boolean isLegendPosVisible() public QueryChartDialog setPointsHidden(boolean hidden) { - if (hidden) - elementCache().pointsHideRadio.check(); - else - elementCache().pointsShowRadio.check(); + doAndWaitForPreview(() -> elementCache().pointsHideRadio.set(hidden)); return this; } @@ -194,12 +205,15 @@ public boolean hasPointsHiddenOption() public QueryChartDialog setAxisScaleType(String label, String value) { clickFieldOptions(label); // open the popover + RadioButton radio; if ("linear".equalsIgnoreCase(value)) - elementCache().scaleLinearRadio.check(); + radio = elementCache().scaleLinearRadio; else if ("log".equalsIgnoreCase(value)) - elementCache().scaleLogRadio.check(); + radio = elementCache().scaleLogRadio; else throw new IllegalArgumentException("Invalid scale value: " + value); + if (!radio.isChecked()) + doAndWaitForPreview(radio::check); clickFieldOptions(label); // close the popover return this; } @@ -228,12 +242,15 @@ public boolean isAxisScaleTypeAvailable(String label) public QueryChartDialog setAxisRangeType(String label, String value) { clickFieldOptions(label); // open the popover + RadioButton radio; if ("automatic".equalsIgnoreCase(value)) - elementCache().scaleAutomaticRadio.check(); + radio = elementCache().scaleAutomaticRadio; else if ("manual".equalsIgnoreCase(value)) - elementCache().scaleManualRadio.check(); + radio = elementCache().scaleManualRadio; else throw new IllegalArgumentException("Invalid range value: " + value); + if (!radio.isChecked()) + doAndWaitForPreview(radio::check); clickFieldOptions(label); // close the popover return this; } @@ -252,8 +269,8 @@ public QueryChartDialog setAxisRange(String label, String min, String max) clickFieldOptions(label); // open the popover if (elementCache().scaleManualRadio.isChecked()) { - elementCache().scaleRangeMinInput.set(min); - elementCache().scaleRangeMaxInput.set(max); + doAndWaitForPreview(() -> elementCache().scaleRangeMinInput.set(min)); + doAndWaitForPreview(() -> elementCache().scaleRangeMaxInput.set(max)); } clickFieldOptions(label); // close the popover return this; @@ -280,7 +297,7 @@ public String getAxisRangeMax(String label) */ public QueryChartDialog selectYAxis(String field) { - elementCache().reactSelectByLabel("Y Axis").select(field); + doAndWaitForPreview(() -> elementCache().reactSelectByLabel("Y Axis").select(field)); return this; } @@ -294,10 +311,20 @@ public List getYAxisSelectionOptions() return elementCache().reactSelectByLabel("Y Axis").getOptions(); } + private void doAndWaitForPreview(Runnable action) + { + WebElement svg = isPreviewPresent() ? elementCache().svg() : null; + action.run(); + + // preview might not appear after action, but it will definitely go stale. + if (svg != null) + getWrapper().shortWait().until(ExpectedConditions.stalenessOf(svg)); + } + public QueryChartDialog selectYAxisAggregateMethod(String option) { clickFieldOptions("Y Axis"); // open the popover - getAggregateMethodSelect().select(option); + doAndWaitForPreview(() -> getAggregateMethodSelect().select(option)); Locator.tagWithText("label", "Name *").findElement(this).click(); // close the popover return this; } @@ -334,7 +361,9 @@ private RadioButton getErrorBarsRadio(String value) public QueryChartDialog selectYAxisErrorBar(String value) { clickFieldOptions("Y Axis"); // open the popover - getErrorBarsRadio(value).check(); + RadioButton errorBarsRadio = getErrorBarsRadio(value); + if (!errorBarsRadio.isChecked()) + doAndWaitForPreview(errorBarsRadio::check); Locator.tagWithText("label", "Name *").findElement(this).click(); // close the popover return this; } @@ -350,7 +379,7 @@ public boolean isYAxisErrorBarOptionEnabled(String value) public QueryChartDialog setXAxisLabel(String value) { clickFieldOptions("X Axis"); - elementCache().xLabelInput.set(value); + doAndWaitForPreview(() -> elementCache().xLabelInput.set(value)); Locator.tagWithText("label", "Name *").findElement(this).click(); // close the popover return this; } @@ -358,7 +387,7 @@ public QueryChartDialog setXAxisLabel(String value) public QueryChartDialog setYAxisLabel(String value) { clickFieldOptions("Y Axis"); - elementCache().yLabelInput.set(value); + doAndWaitForPreview(() -> elementCache().yLabelInput.set(value)); Locator.tagWithText("label", "Name *").findElement(this).click(); // close the popover return this; } @@ -368,7 +397,7 @@ public QueryChartDialog setYAxisLabel(String value) */ public QueryChartDialog selectGroupBy(String field) { - elementCache().reactSelectByLabel("Group By").select(field); + doAndWaitForPreview(() -> elementCache().reactSelectByLabel("Group By").select(field)); return this; } @@ -387,7 +416,7 @@ public List getGroupBySelectionOptions() */ public QueryChartDialog selectColor(String field) { - elementCache().reactSelectByLabel("Color").select(field); + doAndWaitForPreview(() -> elementCache().reactSelectByLabel("Color").select(field)); return this; } @@ -401,7 +430,7 @@ public String getSelectedColor() */ public QueryChartDialog selectShape(String field) { - elementCache().reactSelectByLabel("Shape").select(field); + doAndWaitForPreview(() -> elementCache().reactSelectByLabel("Shape").select(field)); return this; } @@ -415,7 +444,7 @@ public String getSelectedShape() */ public QueryChartDialog selectSeries(String field) { - elementCache().reactSelectByLabel("Series").select(field); + doAndWaitForPreview(() -> elementCache().reactSelectByLabel("Series").select(field)); return this; } @@ -440,17 +469,19 @@ public List getTrendlineOptions() */ public QueryChartDialog selectTrendline(String field) { - elementCache().reactSelectByLabel("Trendline").select(field); + doAndWaitForPreview(() -> elementCache().reactSelectByLabel("Trendline").select(field)); return this; } public QueryChartDialog setTrendlineProvidedParameters(String field) { clickFieldOptions("Trendline"); // open the popover - if (field != null) - getTrendlineProvidedParametersSelect().select(field); - else - getTrendlineProvidedParametersSelect().clearSelection(); + doAndWaitForPreview(() -> { + if (field != null) + getTrendlineProvidedParametersSelect().select(field); + else + getTrendlineProvidedParametersSelect().clearSelection(); + }); elementCache().title.click(); // close the popover return this; } @@ -480,7 +511,7 @@ public String getSelectedSeries() */ public QueryChartDialog selectCategories(String field) { - elementCache().reactSelectByLabel("Categories").select(field); + doAndWaitForPreview(() -> elementCache().reactSelectByLabel("Categories").select(field)); return this; } @@ -508,7 +539,7 @@ public QueryChartDialog setChartType(CHART_TYPE chartType) var chartTypeDropdown = elementCache().reactSelectByLabel("Chart Type"); // ChartTypeDropdown component uses a custom option renderer chartTypeDropdown.setOptionLocator((String type) -> Locator.byClass("chart-builder-type-option").withAttribute("data-chart-type", type)); - chartTypeDropdown.select(chartType.getChartType()); + doAndWaitForPreview(() -> chartTypeDropdown.select(chartType.getChartType())); WebDriverWrapper.waitFor(()-> getSelectedChartType().equals(chartType), "The requested chart type did not become selected", 2000); @@ -538,7 +569,7 @@ public List getPreviewErrors() public WebElement getSvgChart() { - WebDriverWrapper.waitFor(()-> isPreviewPresent(), + WebDriverWrapper.waitFor(this::isPreviewPresent, "the preview was not present in time", 2000); return elementCache().svg(); } @@ -654,7 +685,7 @@ private QueryChartDialog setColor(WebElement colorPickerContainer, String hexCol { Locator.tagWithClassContaining("button", "color-picker__button").findElement(colorPickerContainer).click(); ColorPickerInput colorInput = new ColorPickerInput.ColorPickerInputFinder(getDriver()).findWhenNeeded(); - colorInput.setHexValue(hexColor); + doAndWaitForPreview(() -> colorInput.setHexValue(hexColor)); colorPickerContainer.click(); // need to click outside the color picker to close it return this; } @@ -666,7 +697,7 @@ public boolean hasColorPaletteOption() public QueryChartDialog selectColorPalette(String option) { - elementCache().reactSelectByLabel("Color Palette").select(option); + doAndWaitForPreview(() -> elementCache().reactSelectByLabel("Color Palette").select(option)); return this; } @@ -680,7 +711,7 @@ public QueryChartDialog selectLineColorAndStyleOption(String option, String hexC var seriesDropdown = elementCache().reactSelectByLabel("Line Color and Style"); // series select component uses a custom option renderer seriesDropdown.setOptionLocator((String type) -> Locator.byClass("chart-builder-type-option").withAttribute("data-series-option", type)); - seriesDropdown.select(option); + doAndWaitForPreview(() -> seriesDropdown.select(option)); if (hexColor != null) setColor(elementCache().seriesColorPicker, hexColor); @@ -690,7 +721,7 @@ public QueryChartDialog selectLineColorAndStyleOption(String option, String hexC { var lineTypeDropdown = elementCache().reactSelectByLabel("Line Type"); lineTypeDropdown.setOptionLocator((String type) -> Locator.byClass("chart-builder-type-option").withAttribute("data-series-linetype", type)); - lineTypeDropdown.select(lineType); + doAndWaitForPreview(() -> lineTypeDropdown.select(lineType)); } return this; From 9a2bdc60cb3a824da3e9ff948c17135bbcb8c4f7 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Fri, 15 May 2026 11:26:51 -0700 Subject: [PATCH 2/2] Fix SVG refresh --- .../test/components/react/QueryChartDialog.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/org/labkey/test/components/react/QueryChartDialog.java b/src/org/labkey/test/components/react/QueryChartDialog.java index 360415532d..854ec42a36 100644 --- a/src/org/labkey/test/components/react/QueryChartDialog.java +++ b/src/org/labkey/test/components/react/QueryChartDialog.java @@ -269,8 +269,14 @@ public QueryChartDialog setAxisRange(String label, String min, String max) clickFieldOptions(label); // open the popover if (elementCache().scaleManualRadio.isChecked()) { - doAndWaitForPreview(() -> elementCache().scaleRangeMinInput.set(min)); - doAndWaitForPreview(() -> elementCache().scaleRangeMaxInput.set(max)); + doAndWaitForPreview(() -> { + elementCache().scaleRangeMinInput.set(min); + elementCache().scaleRangeMaxInput.getComponentElement().click(); // trigger svg refresh + }); + doAndWaitForPreview(() -> { + elementCache().scaleRangeMaxInput.set(max); + elementCache().scaleRangeMinInput.getComponentElement().click(); // trigger svg refresh + }); } clickFieldOptions(label); // close the popover return this; @@ -711,7 +717,7 @@ public QueryChartDialog selectLineColorAndStyleOption(String option, String hexC var seriesDropdown = elementCache().reactSelectByLabel("Line Color and Style"); // series select component uses a custom option renderer seriesDropdown.setOptionLocator((String type) -> Locator.byClass("chart-builder-type-option").withAttribute("data-series-option", type)); - doAndWaitForPreview(() -> seriesDropdown.select(option)); + seriesDropdown.select(option); if (hexColor != null) setColor(elementCache().seriesColorPicker, hexColor);