Skip to content

Commit c98b8ec

Browse files
committed
Merge remote-tracking branch 'origin/develop' into fb_mail_transport_via_graph
2 parents 7b70a50 + 230dcfa commit c98b8ec

11 files changed

Lines changed: 192 additions & 42 deletions

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ lookfirstSardineVersion=5.13
1010

1111
jettyVersion=12.1.5
1212

13-
seleniumVersion=4.40.0
13+
seleniumVersion=4.41.0
1414

1515
mockserverNettyVersion=5.15.0
1616

src/org/labkey/test/WebDriverWrapper.java

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
import org.labkey.test.util.TextSearcher;
6464
import org.labkey.test.util.TextSearcher.TextTransformers;
6565
import org.labkey.test.util.Timer;
66+
import org.labkey.test.util.selenium.JavascriptExecutorWrapper;
6667
import org.labkey.test.util.selenium.ScrollUtils;
6768
import org.labkey.test.util.selenium.WebDriverUtils;
6869
import org.openqa.selenium.Alert;
@@ -539,11 +540,7 @@ public Object executeScript(@Language("JavaScript") String script, Object... arg
539540
*/
540541
public <T> T executeScript(@Language("JavaScript") String script, Class<T> expectedResultType, Object... arguments)
541542
{
542-
Object o = executeScript(script, arguments);
543-
if (o != null && !expectedResultType.isAssignableFrom(o.getClass()))
544-
Assert.fail("Script return wrong type. Expected '" + expectedResultType.getSimpleName() + "'. Got: " + o.getClass().getName() + ". Result: " + o);
545-
546-
return (T) o;
543+
return new JavascriptExecutorWrapper(getDriver()).executeScript(script, expectedResultType, arguments);
547544
}
548545

549546
/**
@@ -552,20 +549,12 @@ public <T> T executeScript(@Language("JavaScript") String script, Class<T> expec
552549
*/
553550
public Object executeAsyncScript(@Language("JavaScript") String script, Object... arguments)
554551
{
555-
script = "var callback = arguments[arguments.length - 1];\n" + // See WebDriver documentation for details on injected callback
556-
"try {" +
557-
script +
558-
"} catch (error) { callback(error); }"; // ensure that the callback is invoked when an exception would otherwise prevent it
559-
return ((JavascriptExecutor) getDriver()).executeAsyncScript(script, arguments);
552+
return new JavascriptExecutorWrapper(getDriver()).executeAsyncScript(script, arguments);
560553
}
561554

562555
public <T> T executeAsyncScript(@Language("JavaScript") String script, Class<T> expectedResultType, Object... arguments)
563556
{
564-
Object o = executeAsyncScript(script, arguments);
565-
if (o != null && !expectedResultType.isAssignableFrom(o.getClass()))
566-
Assert.fail("Script return wrong type. Expected '" + expectedResultType.getSimpleName() + "'. Got: " + o.getClass().getName() + ". Result: " + o);
567-
568-
return (T) o;
557+
return new JavascriptExecutorWrapper(getDriver()).executeAsyncScript(script, expectedResultType, arguments);
569558
}
570559

571560
@LogMethod(quiet = true)

src/org/labkey/test/components/domain/DomainFieldRow.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -769,6 +769,8 @@ public DomainFieldRow clickRemoveOntologyConcept()
769769

770770
public void setAllowMultipleSelections(Boolean allowMultipleSelections)
771771
{
772+
WebDriverWrapper.waitFor(() -> elementCache().allowMultipleSelectionsCheckbox.isDisplayed(),
773+
"Allow Multiple Selections checkbox did not become visible", 2000);
772774
elementCache().allowMultipleSelectionsCheckbox.set(allowMultipleSelections);
773775
}
774776

src/org/labkey/test/components/ui/grids/ResponsiveGrid.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public WebElement getComponentElement()
7575

7676
public Boolean isLoaded()
7777
{
78-
return getComponentElement().isDisplayed() &&
78+
return WebElementUtils.checkVisibility(getComponentElement()) &&
7979
!Locators.loadingGrid.existsIn(this) &&
8080
!Locators.spinner.existsIn(this) &&
8181
(Locator.tag("td").existsIn(this) ||

src/org/labkey/test/tests/AuditLogTest.java

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import org.labkey.test.util.Log4jUtils;
4848
import org.labkey.test.util.PermissionsHelper;
4949
import org.labkey.test.util.PortalHelper;
50+
import org.labkey.test.util.SearchHelper;
5051
import org.labkey.test.util.UIUserHelper;
5152

5253
import java.io.BufferedReader;
@@ -64,11 +65,11 @@
6465
import static org.junit.Assert.assertFalse;
6566
import static org.junit.Assert.assertTrue;
6667
import static org.junit.Assert.fail;
68+
import static org.labkey.test.util.PasswordUtil.getUsername;
6769
import static org.labkey.test.util.PermissionsHelper.AUTHOR_ROLE;
6870
import static org.labkey.test.util.PermissionsHelper.EDITOR_ROLE;
6971
import static org.labkey.test.util.PermissionsHelper.FOLDER_ADMIN_ROLE;
7072
import static org.labkey.test.util.PermissionsHelper.PROJECT_ADMIN_ROLE;
71-
import static org.labkey.test.util.PasswordUtil.getUsername;
7273

7374
@Category({Daily.class, Hosting.class})
7475
@BaseWebDriverTest.ClassTimeout(minutes = 9)
@@ -79,21 +80,18 @@ public class AuditLogTest extends BaseWebDriverTest
7980
public static final String QUERY_UPDATE_EVENT = "Query update events";
8081
public static final String PROJECT_AUDIT_EVENT = "Project and Folder events";
8182
public static final String ASSAY_AUDIT_EVENT = "Link to Study events";
83+
public static final String COMMENT_COLUMN = "Comment";
8284

8385
private static final String AUDIT_TEST_USER = "audit_user1@auditlog.test";
8486
private static final String AUDIT_TEST_USER2 = "audit_user2@auditlog.test";
8587
private static final String AUDIT_TEST_USER3 = "audit_user3@auditlog.test";
86-
8788
private static final String AUDIT_SECURITY_GROUP = "Testers";
88-
8989
private static final String AUDIT_TEST_PROJECT = "AuditVerifyTest";
9090
private static final String AUDIT_DETAILED_TEST_PROJECT = "AuditDetailedLogTest";
9191
private static final String AUDIT_TEST_SUBFOLDER = "AuditVerifyTest_Subfolder";
9292
private static final String AUDIT_PROPERTY_EVENTS_PROJECT = "AuditDomainPropertyEvents";
93-
94-
final String DOMAIN_PROPERTY_LOG_NAME = "Domain property events";
95-
96-
public static final String COMMENT_COLUMN = "Comment";
93+
private static final String DOMAIN_PROPERTY_LOG_NAME = "Domain property events";
94+
private static final String SEARCH_TERM = "doesn't matter";
9795

9896
private final ApiPermissionsHelper permissionsHelper = new ApiPermissionsHelper(this);
9997
private final AuditLogHelper _auditLogHelper = new AuditLogHelper(this);
@@ -377,19 +375,37 @@ protected void canSeeAuditLogTest()
377375
createUserWithPermissions(AUDIT_TEST_USER, AUDIT_TEST_PROJECT, EDITOR_ROLE);
378376
createUserWithPermissions(AUDIT_TEST_USER2, AUDIT_TEST_PROJECT, PROJECT_ADMIN_ROLE);
379377

378+
// Do a search to ensure an audit entry in /home
379+
clickProject("Home");
380+
new SearchHelper(this).searchFor(SEARCH_TERM);
381+
goToProjectHome();
382+
380383
// signed in as an admin so we should see rows here
381-
verifyAuditQueries(true);
384+
verifyAuditQueries(true, getProjectName());
382385

383386
// signed in as an editor should not show any rows for audit query links
384387
impersonate(AUDIT_TEST_USER);
385-
verifyAuditQueries(false);
388+
verifyAuditQueries(false, getProjectName());
389+
verifyAuditQueries(false, "Home");
390+
stopImpersonating();
391+
392+
// Grant the "See Audit Log Events" folder role to our audit user in the project and verify we see audit
393+
// information in this project but not /Home. We pass the fully qualified classnames in the next few calls to
394+
// disambiguate the root role from the folder role.
395+
permissionsHelper.addMemberToRole(AUDIT_TEST_USER, "org.labkey.api.security.roles.CanSeeAuditLogFolderRole", PermissionsHelper.MemberType.user, getProjectName());
396+
impersonate(AUDIT_TEST_USER);
397+
verifyAuditQueries(true, getProjectName());
398+
verifyAuditQueries(false, "Home");
386399
stopImpersonating();
400+
permissionsHelper.removeUserRoleAssignment(AUDIT_TEST_USER, "org.labkey.api.security.roles.CanSeeAuditLogFolderRole", getProjectName());
387401

388-
// now grant CanSeeAuditLog permission to our audit user and verify
389-
// we see audit information
390-
permissionsHelper.setSiteRoleUserPermissions(AUDIT_TEST_USER, "See Audit Log Events");
402+
// Grant the "See Audit Log Events" root role to our audit user and verify we see audit information in this
403+
// project and in /Home
404+
permissionsHelper.setSiteRoleUserPermissions(AUDIT_TEST_USER, "org.labkey.api.security.roles.CanSeeAuditLogRole");
391405
impersonate(AUDIT_TEST_USER);
392-
verifyAuditQueries(true);
406+
verifyAuditQueries(true, getProjectName());
407+
ExecuteQueryPage.beginAt(this, "Home", "auditLog", "SearchAuditEvent");
408+
verifyAuditQueryEvent(this, "Query", SEARCH_TERM, 1);
393409

394410
// cleanup
395411
stopImpersonating();
@@ -482,7 +498,7 @@ public void testDetailedQueryUpdateAuditLog() throws IOException, CommandExcepti
482498
//then create model (which has detailed audit log level)
483499
InsertRowsCommand insertCmd2 = new InsertRowsCommand("vehicle", "models");
484500
rowMap = new HashMap<>();
485-
rowMap.put("manufacturerId", resp1.getRows().get(0).get("rowid"));
501+
rowMap.put("manufacturerId", resp1.getRows().getFirst().get("rowid"));
486502
rowMap.put("name", "Soul");
487503
insertCmd2.addRow(rowMap);
488504
insertCmd2.execute(cn, AUDIT_DETAILED_TEST_PROJECT);
@@ -535,17 +551,17 @@ protected void verifyListAuditLogQueries(Visibility v)
535551
verifyAuditQueryEvent(this, "List", "Child List", 1, canSeeChild(v));
536552
}
537553

538-
protected void verifyAuditQueries(boolean canSeeAuditLog)
554+
protected void verifyAuditQueries(boolean canSeeAuditLog, String containerPath)
539555
{
540-
ExecuteQueryPage.beginAt(this, getProjectName(), "auditLog", "ContainerAuditEvent");
556+
ExecuteQueryPage.beginAt(this, containerPath, "auditLog", "ContainerAuditEvent");
541557
if (canSeeAuditLog)
542558
verifyAuditQueryEvent(this, COMMENT_COLUMN, AUDIT_TEST_PROJECT + " was created", 1);
543559
else
544560
assertTextPresent("No data to show.");
545561

546-
ExecuteQueryPage.beginAt(this, getProjectName(), "auditLog", "GroupAuditEvent");
562+
ExecuteQueryPage.beginAt(this, containerPath, "auditLog", "GroupAuditEvent");
547563
if (canSeeAuditLog)
548-
verifyAuditQueryEvent(this, COMMENT_COLUMN, "The user " + AUDIT_TEST_USER + " was assigned to the security role Editor.", 1);
564+
verifyAuditQueryEvent(this, COMMENT_COLUMN, "The user " + AUDIT_TEST_USER + " was assigned to the security role Editor.", 4);
549565
else
550566
assertTextPresent("No data to show.");
551567
}

src/org/labkey/test/tests/GpatAssayTest.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import org.labkey.test.util.EscapeUtil;
4242
import org.labkey.test.util.LogMethod;
4343
import org.labkey.test.util.LoggedParam;
44+
import org.labkey.test.util.RReportHelper;
4445
import org.labkey.test.util.TestDataGenerator;
4546
import org.labkey.test.util.core.webdav.WebDavUploadHelper;
4647
import org.openqa.selenium.WebElement;
@@ -72,11 +73,13 @@ public class GpatAssayTest extends BaseWebDriverTest
7273
private static final String ASSAY_NAME_FNA = "FASTA Assay";
7374
private static final String ASSAY_NAME_FNA_MULTIPLE = "FASTA Assay - Multiple file upload";
7475
private static final String ASSAY_NAME_FNA_MULTIPLE_SINGLE_INPUT = "FASTA Assay - Multiple file single input upload";
76+
private static final File RTRANSFORM_SCRIPT_FILE_NOOP = TestFileUtils.getSampleData("qc/noopTransform.R");
7577

7678
@BeforeClass
7779
public static void doSetup()
7880
{
7981
GpatAssayTest init = getCurrentTest();
82+
new RReportHelper(init).ensureRConfig();
8083
init._containerHelper.createProject(init.getProjectName(), "Assay");
8184
init.goToProjectHome();
8285
}
@@ -256,6 +259,14 @@ private void importFastaGpatAssay(File fnaFile, String assayName)
256259
clickButton("Save and Finish", defaultWaitForPage);
257260
}
258261

262+
// GitHub Issue #875: Optionally add transform scripts in GPAT assay design to test code path with and without transform script
263+
private void randomlyAddTransformScript(ReactAssayDesignerPage assayDesignerPage)
264+
{
265+
boolean shouldAddTransformScript = TestDataGenerator.randomBoolean("whether to add transform script in assay design");
266+
if (shouldAddTransformScript)
267+
assayDesignerPage.addTransformScript(RTRANSFORM_SCRIPT_FILE_NOOP);
268+
}
269+
259270
@LogMethod
260271
private ReactAssayDesignerPage startCreateGpatAssay(File dataFile, @LoggedParam String assayName)
261272
{
@@ -265,9 +276,9 @@ private ReactAssayDesignerPage startCreateGpatAssay(File dataFile, @LoggedParam
265276
_fileBrowserHelper.importFile(dataFile.getName(), "Create New Standard Assay Design");
266277

267278
ReactAssayDesignerPage assayDesignerPage = new ReactAssayDesignerPage(getDriver());
268-
269279
if (assayName != null)
270280
assayDesignerPage.setName(assayName);
281+
randomlyAddTransformScript(assayDesignerPage);
271282
return assayDesignerPage;
272283
}
273284

src/org/labkey/test/util/TestDataGenerator.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -800,7 +800,15 @@ public String randomDateString(String dateFormat, Date min, Date max)
800800

801801
public static boolean randomBoolean()
802802
{
803-
return ThreadLocalRandom.current().nextBoolean();
803+
return randomBoolean(null);
804+
}
805+
806+
public static boolean randomBoolean(@Nullable String message)
807+
{
808+
boolean value = ThreadLocalRandom.current().nextBoolean();
809+
if (message != null)
810+
TestLogger.log("Generated random boolean value for %s: %s".formatted(message, value));
811+
return value;
804812
}
805813

806814
private @NotNull List<String> getFieldsForFile()

src/org/labkey/test/util/data/TestDataUtils.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -581,14 +581,13 @@ public static List<List<String>> readRowsFromFile(File file, CSVFormat format) t
581581
public static List<String> parseMultiValueText(String multiValueString) throws IOException
582582
{
583583
CSVFormat format = CSVFormat.RFC4180.builder()
584-
.setIgnoreSurroundingSpaces(true).get();
584+
.setIgnoreSurroundingSpaces(true).setTrim(true).get();
585585
try (CSVParser parser = format.parse(new StringReader(multiValueString)))
586586
{
587587
List<CSVRecord> records = parser.getRecords();
588-
List<List<String>> list = records.stream().map(CSVRecord::toList).toList();
589-
if (list.size() != 1)
588+
if (records.size() != 1)
590589
throw new IllegalArgumentException("Invalid multi-value text string: " + multiValueString);
591-
return list.getFirst();
590+
return records.getFirst().toList();
592591
}
593592
}
594593

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package org.labkey.test.util.selenium;
2+
3+
import org.intellij.lang.annotations.Language;
4+
import org.jetbrains.annotations.Nullable;
5+
import org.openqa.selenium.JavascriptExecutor;
6+
import org.openqa.selenium.ScriptKey;
7+
import org.openqa.selenium.WebDriver;
8+
9+
import java.util.Set;
10+
11+
public class JavascriptExecutorWrapper implements JavascriptExecutor
12+
{
13+
private final JavascriptExecutor _wrappedExecutor;
14+
15+
public JavascriptExecutorWrapper(WebDriver driver)
16+
{
17+
_wrappedExecutor = (JavascriptExecutor) driver;
18+
}
19+
20+
@Override
21+
public @Nullable Object executeScript(@Language("JavaScript") String script, @Nullable Object... args)
22+
{
23+
return _wrappedExecutor.executeScript(script, args);
24+
}
25+
26+
@Override
27+
public @Nullable Object executeScript(ScriptKey key, @Nullable Object... args)
28+
{
29+
return _wrappedExecutor.executeScript(key, args);
30+
}
31+
32+
/**
33+
* Wrapper for executing JavaScript through WebDriver and verifying return type.
34+
* @param <T> See {@link JavascriptExecutor#executeScript(java.lang.String, java.lang.Object...)} for valid return types
35+
*/
36+
public <T> @Nullable T executeScript(@Language("JavaScript") String script, Class<T> expectedResultType, @Nullable Object... arguments)
37+
{
38+
return verifyType(expectedResultType, executeScript(script, arguments));
39+
}
40+
41+
/**
42+
* Wrapper for synchronous execution of asynchronous JavaScript. This wrapper extracts the 'callback' from the argument list
43+
* See {@link JavascriptExecutor#executeAsyncScript(java.lang.String, java.lang.Object...)} for details
44+
*/
45+
@Override
46+
public @Nullable Object executeAsyncScript(@Language("JavaScript") String script, @Nullable Object... arguments)
47+
{
48+
script = "var callback = arguments[arguments.length - 1];\n" + // See WebDriver documentation for details on injected callback
49+
"try {" +
50+
script +
51+
"} catch (error) { callback(error); }"; // ensure that the callback is invoked when an exception would otherwise prevent it
52+
return _wrappedExecutor.executeAsyncScript(script, arguments);
53+
}
54+
55+
public <T> @Nullable T executeAsyncScript(@Language("JavaScript") String script, Class<T> expectedResultType, @Nullable Object... arguments)
56+
{
57+
return verifyType(expectedResultType, executeAsyncScript(script, arguments));
58+
}
59+
60+
private <T> @Nullable T verifyType(Class<T> expectedResultType, @Nullable Object o)
61+
{
62+
if (o != null && !expectedResultType.isAssignableFrom(o.getClass()))
63+
throw new IllegalStateException("Script return wrong type. Expected '" + expectedResultType.getName() + "'. Got: " + o.getClass().getName() + ". Result: " + o);
64+
65+
return (T) o;
66+
}
67+
68+
@Override
69+
public Set<ScriptKey> getPinnedScripts()
70+
{
71+
return _wrappedExecutor.getPinnedScripts();
72+
}
73+
74+
@Override
75+
public void unpin(ScriptKey key)
76+
{
77+
_wrappedExecutor.unpin(key);
78+
}
79+
80+
@Override
81+
public ScriptKey pin(@Language("JavaScript") String script)
82+
{
83+
return _wrappedExecutor.pin(script);
84+
}
85+
}

src/org/labkey/test/util/selenium/WebDriverUtils.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import org.openqa.selenium.WrapsDriver;
2626
import org.openqa.selenium.WrapsElement;
2727

28+
import java.util.Objects;
29+
2830
public abstract class WebDriverUtils
2931
{
3032
/**
@@ -74,6 +76,17 @@ public static WebDriver extractWrappedDriver(Object peeling)
7476
return null;
7577
}
7678

79+
/**
80+
* Extract a WebDriver instance from an arbitrarily wrapped object and the JavascriptExecutor tied to it.
81+
*
82+
* @param object Object that wraps a WebDriver. Typically, a Component, SearchContext, or WebElement
83+
* @return JavascriptExecutor instance
84+
*/
85+
public static JavascriptExecutorWrapper getJavascriptExecutor(Object object)
86+
{
87+
return Objects.requireNonNull(new JavascriptExecutorWrapper(extractWrappedDriver(object)), () -> "No WebDriver found in " + object.getClass());
88+
}
89+
7790
/**
7891
* Attempts to get alert text from an {@link UnhandledAlertException}. If exception does not supply the alert text,
7992
* attempt to get it from the alert directly (requires {@link org.openqa.selenium.UnexpectedAlertBehaviour#IGNORE}).

0 commit comments

Comments
 (0)