Skip to content

Commit aea27c3

Browse files
committed
add selenium testing
1 parent ecd42bb commit aea27c3

2 files changed

Lines changed: 266 additions & 0 deletions

File tree

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright (c) 2026 LabKey Corporation
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.labkey.test.components.targetedms;
17+
18+
import org.labkey.test.Locator;
19+
import org.labkey.test.WebDriverWrapper;
20+
import org.labkey.test.components.BodyWebPart;
21+
import org.labkey.test.util.DataRegionTable;
22+
import org.openqa.selenium.WebDriver;
23+
import org.openqa.selenium.WebElement;
24+
25+
/**
26+
* The "Runs Acquired" web part on the Show Instrument page. It shows an instrument's utilization,
27+
* aggregated across all readable folders, as a grid of runs/files by day, with a client-side toggle
28+
* to switch to a by-month summary.
29+
*/
30+
public class InstrumentUtilizationWebPart extends BodyWebPart<InstrumentUtilizationWebPart.Elements>
31+
{
32+
public static final String DEFAULT_TITLE = "Runs Acquired";
33+
public static final String BY_DAY_REGION = "UtilizationByDay";
34+
public static final String BY_MONTH_REGION = "UtilizationByMonth";
35+
public static final String FILES_COLUMN = "Files";
36+
37+
public InstrumentUtilizationWebPart(WebDriver driver)
38+
{
39+
super(driver, DEFAULT_TITLE);
40+
WebDriverWrapper.waitFor(() -> elementCache().byDayToggle.isDisplayed(),
41+
"Runs Acquired web part did not load", getWrapper().defaultWaitForPage);
42+
}
43+
44+
public boolean isByDayVisible()
45+
{
46+
return elementCache().byDayGrid.isDisplayed();
47+
}
48+
49+
public boolean isByMonthVisible()
50+
{
51+
return elementCache().byMonthGrid.isDisplayed();
52+
}
53+
54+
public InstrumentUtilizationWebPart showByDay()
55+
{
56+
if (!isByDayVisible())
57+
elementCache().byDayToggle.click();
58+
WebDriverWrapper.waitFor(this::isByDayVisible, "By Day grid did not become visible", 5000);
59+
return this;
60+
}
61+
62+
public InstrumentUtilizationWebPart showByMonth()
63+
{
64+
if (!isByMonthVisible())
65+
elementCache().byMonthToggle.click();
66+
WebDriverWrapper.waitFor(this::isByMonthVisible, "By Month grid did not become visible", 5000);
67+
return this;
68+
}
69+
70+
public DataRegionTable getByDayTable()
71+
{
72+
return new DataRegionTable(BY_DAY_REGION, getDriver());
73+
}
74+
75+
public DataRegionTable getByMonthTable()
76+
{
77+
return new DataRegionTable(BY_MONTH_REGION, getDriver());
78+
}
79+
80+
/** Sum of the (integer) "Files" column, i.e. the total number of sample files represented by the grid. */
81+
public int getTotalFiles(DataRegionTable table)
82+
{
83+
int total = 0;
84+
for (String value : table.getColumnDataAsText(FILES_COLUMN))
85+
{
86+
total += Integer.parseInt(value.trim());
87+
}
88+
return total;
89+
}
90+
91+
@Override
92+
protected Elements newElementCache()
93+
{
94+
return new Elements();
95+
}
96+
97+
public class Elements extends BodyWebPart<?>.ElementCache
98+
{
99+
final WebElement byDayToggle = Locator.id("utilizationToggleDay").findWhenNeeded(this);
100+
final WebElement byMonthToggle = Locator.id("utilizationToggleMonth").findWhenNeeded(this);
101+
final WebElement byDayGrid = Locator.id("utilizationByDayGrid").findWhenNeeded(this);
102+
final WebElement byMonthGrid = Locator.id("utilizationByMonthGrid").findWhenNeeded(this);
103+
}
104+
}
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/*
2+
* Copyright (c) 2026 LabKey Corporation
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.labkey.test.tests.targetedms;
17+
18+
import org.jetbrains.annotations.Nullable;
19+
import org.junit.BeforeClass;
20+
import org.junit.Test;
21+
import org.junit.experimental.categories.Category;
22+
import org.labkey.remoteapi.CommandException;
23+
import org.labkey.remoteapi.query.Filter;
24+
import org.labkey.remoteapi.query.SelectRowsCommand;
25+
import org.labkey.remoteapi.query.SelectRowsResponse;
26+
import org.labkey.test.BaseWebDriverTest;
27+
import org.labkey.test.Locator;
28+
import org.labkey.test.WebTestHelper;
29+
import org.labkey.test.components.targetedms.InstrumentUtilizationWebPart;
30+
import org.labkey.test.util.DataRegionTable;
31+
32+
import java.io.IOException;
33+
import java.util.List;
34+
import java.util.Map;
35+
36+
import static org.junit.Assert.assertEquals;
37+
import static org.junit.Assert.assertFalse;
38+
import static org.junit.Assert.assertTrue;
39+
40+
/**
41+
* Verifies the instrument-scoped, cross-folder utilization views on the Show Instrument page: the
42+
* "Utilization Calendar" web part and the "Runs Acquired" by-day/by-month grids. The same Skyline
43+
* document is imported into two folders so we can assert that the views aggregate across every folder
44+
* the user can read, not just the current one.
45+
*/
46+
@Category({})
47+
@BaseWebDriverTest.ClassTimeout(minutes = 5)
48+
public class TargetedMSInstrumentUtilizationTest extends TargetedMSTest
49+
{
50+
private static final String QC_SUBFOLDER = "QC Subfolder";
51+
52+
private String _instrumentName;
53+
54+
@Override
55+
protected @Nullable String getProjectName()
56+
{
57+
return getClass().getSimpleName() + " Project";
58+
}
59+
60+
@BeforeClass
61+
public static void initProject()
62+
{
63+
TargetedMSInstrumentUtilizationTest init = getCurrentTest();
64+
init.doInit();
65+
}
66+
67+
private void doInit()
68+
{
69+
setupFolder(FolderType.QC);
70+
setupSubfolder(getProjectName(), QC_SUBFOLDER, FolderType.QC);
71+
72+
// Import the same document into two folders so the same instrument has data in both
73+
goToProjectHome();
74+
importData(SProCoP_FILE);
75+
76+
clickFolder(QC_SUBFOLDER);
77+
importData(SProCoP_FILE);
78+
}
79+
80+
@Test
81+
public void testCrossFolderInstrumentUtilization() throws IOException, CommandException
82+
{
83+
_instrumentName = getInstrumentNickname();
84+
85+
// The two folders hold identical imports, so the cross-folder total should be exactly double
86+
// the count in a single folder.
87+
int singleFolderFileCount = getFileCount(getProjectName());
88+
int expectedCrossFolderFileCount = singleFolderFileCount * 2;
89+
assertTrue("Expected the single folder to contain sample files with acquisition times", singleFolderFileCount > 0);
90+
91+
beginAt(WebTestHelper.buildURL("targetedms", getProjectName(), "showInstrument", Map.of("name", _instrumentName)));
92+
93+
verifyCalendar();
94+
verifyRunsAcquiredGrids(expectedCrossFolderFileCount);
95+
}
96+
97+
private void verifyCalendar()
98+
{
99+
log("Verifying the cross-folder Utilization Calendar renders");
100+
assertElementPresent(Locator.tagWithClass("span", "labkey-wp-title-text").withText("Utilization Calendar"));
101+
102+
// Wait for the async selectRows call to populate the calendar with day cells
103+
waitForElement(Locator.tagWithClassContaining("div", "day-content"));
104+
assertEquals("Calendar should default to a single month",
105+
"1 month", getSelectedOptionText(Locator.id("utilizationMonthNumberSelect")));
106+
}
107+
108+
private void verifyRunsAcquiredGrids(int expectedCrossFolderFileCount)
109+
{
110+
InstrumentUtilizationWebPart utilization = new InstrumentUtilizationWebPart(getDriver());
111+
112+
log("Verifying the By Day grid is shown by default and aggregates across folders");
113+
assertTrue("By Day grid should be visible by default", utilization.isByDayVisible());
114+
assertFalse("By Month grid should be hidden by default", utilization.isByMonthVisible());
115+
116+
DataRegionTable byDay = utilization.getByDayTable();
117+
assertTrue("By Day grid is missing expected columns",
118+
byDay.getColumnLabels().containsAll(List.of("Date", "Runs", "Files")));
119+
assertEquals("By Day grid should sum files across both folders",
120+
expectedCrossFolderFileCount, utilization.getTotalFiles(byDay));
121+
122+
log("Toggling to the By Month summary");
123+
utilization.showByMonth();
124+
assertTrue("By Month grid should be visible after toggling", utilization.isByMonthVisible());
125+
assertFalse("By Day grid should be hidden after toggling", utilization.isByDayVisible());
126+
127+
DataRegionTable byMonth = utilization.getByMonthTable();
128+
assertTrue("By Month grid is missing expected columns",
129+
byMonth.getColumnLabels().containsAll(List.of("Month", "Runs", "Files")));
130+
// Grouping by month rather than day changes the row count but not the overall file total
131+
assertEquals("By Month grid should sum to the same cross-folder file total",
132+
expectedCrossFolderFileCount, utilization.getTotalFiles(byMonth));
133+
134+
log("Toggling back to the By Day grid");
135+
utilization.showByDay();
136+
assertTrue("By Day grid should be visible after toggling back", utilization.isByDayVisible());
137+
assertFalse("By Month grid should be hidden after toggling back", utilization.isByMonthVisible());
138+
}
139+
140+
/** @return the default nickname (model - serial number) for the instrument that acquired the imported data */
141+
private String getInstrumentNickname() throws IOException, CommandException
142+
{
143+
SelectRowsCommand command = new SelectRowsCommand("targetedms", "SampleFile");
144+
command.setColumns(List.of("InstrumentNickname"));
145+
command.setMaxRows(1);
146+
SelectRowsResponse response = command.execute(createDefaultConnection(), getProjectName());
147+
assertFalse("No sample files were imported", response.getRows().isEmpty());
148+
return (String) response.getRows().get(0).get("InstrumentNickname");
149+
}
150+
151+
/** @return the number of sample files (with an acquisition time) for the instrument in a single container */
152+
private int getFileCount(String containerPath) throws IOException, CommandException
153+
{
154+
SelectRowsCommand command = new SelectRowsCommand("targetedms", "SampleFile");
155+
command.setColumns(List.of("Id"));
156+
command.setFilters(List.of(
157+
new Filter("InstrumentNickname", _instrumentName),
158+
new Filter("AcquiredTime", null, Filter.Operator.NONBLANK)));
159+
SelectRowsResponse response = command.execute(createDefaultConnection(), containerPath);
160+
return response.getRowCount().intValue();
161+
}
162+
}

0 commit comments

Comments
 (0)