maven-clean-plugin
diff --git a/scripts/cldrHarStats.mjs b/scripts/cldrHarStats.mjs
new file mode 100644
index 0000000..1b355aa
--- /dev/null
+++ b/scripts/cldrHarStats.mjs
@@ -0,0 +1,130 @@
+import { readFileSync } from "fs";
+
+const URL_PREFIX = "http://localhost:9080/cldr-apps/";
+const FILTER_REGEX = "api/voting/[a-zA-Z_]+/row";
+
+/*
+ * Run with three args: the HAR file name, the start time, and the end time. For example:
+ * node scripts/cldrHarStats.mjs ../HAR/2024-01-12-a.har 2024-01-12T17:15:55Z 2024-01-12T17:18:55Z > ../HAR/2024-01-12-a.html
+ *
+ * Summary is written to stderr (console.warn); detailed HTML is written to stdout
+ */
+const harFileName = process.argv[2];
+const startTimeStamp = process.argv[3];
+const endTimeStamp = process.argv[4];
+/*
+ * Read the input HAR file into an array and filter it to include only entries
+ * matching FILTER_REGEX and within the given start/end times
+ */
+const obj = JSON.parse(readFileSync(harFileName, "utf8"));
+const allEntries = obj.log.entries;
+const filteredEntries = allEntries.filter(function (entry) {
+ return (
+ entry.startedDateTime.localeCompare(startTimeStamp) >= 0 &&
+ entry.startedDateTime.localeCompare(endTimeStamp) < 0 &&
+ entry.request.url.match(FILTER_REGEX)
+ );
+});
+console.warn("allEntries.length = " + allEntries.length);
+console.warn("filteredEntries.length = " + filteredEntries.length);
+
+writeHtmlThruTableStart();
+writeHtmlTableHeader();
+writeHtmlTableBody(filteredEntries);
+writeHtmlFromTableEnd();
+
+/**
+ * Write the HTML up to and including the opening table tag
+ */
+function writeHtmlThruTableStart() {
+ write("");
+ write("");
+ write(
+ ''
+ );
+ write("");
+ write("");
+ write("");
+ write("");
+}
+
+function writeHtmlFromTableEnd() {
+ write("
");
+ write("");
+ write("");
+}
+
+function writeHtmlTableHeader() {
+ const info = {
+ isHeader: true,
+ requestNumber: "request",
+ startedDateTime: "start",
+ time: "ms",
+ size: "size",
+ method: "method",
+ url: "URL",
+ postData: "POST data (if applicable)",
+ };
+ putEntryInfo(info);
+}
+
+function writeHtmlTableBody(entries) {
+ let postTime = 0,
+ getTime = 0,
+ postCount = 0,
+ getCount = 0;
+ for (let i = 0; i < entries.length; i++) {
+ const entry = entries[i];
+ let url = entry.request.url;
+ if (url.indexOf(URL_PREFIX) === 0) {
+ url = url.slice(URL_PREFIX.length);
+ }
+ if (entry.request.method === "POST") {
+ postCount++;
+ postTime += entry.time;
+ } else {
+ getCount++;
+ getTime += entry.time;
+ }
+ const postData =
+ entry.request.method === "POST" && entry.request?.postData?.text
+ ? entry.request.postData.text
+ : "";
+ const info = {
+ isHeader: false,
+ requestNumber: i + 1,
+ startedDateTime: entry.startedDateTime,
+ time: Math.round(parseFloat(entry.time)),
+ size: Math.round(entry.response.content.size / 1000) + "k",
+ method: entry.request.method,
+ url: url,
+ postData: postData,
+ };
+ putEntryInfo(info);
+ }
+ console.warn("Average POST time = " + postTime / postCount);
+ console.warn("Average GET time = " + getTime / getCount);
+}
+
+function putEntryInfo(info) {
+ const cellStart = info.isHeader ? "" : " | ";
+ const cellEnd = info.isHeader ? "" : " | ";
+ write("");
+ write(cellStart + info.requestNumber + cellEnd);
+ write(cellStart + info.startedDateTime + cellEnd);
+ write(cellStart + info.time + cellEnd);
+ write(cellStart + info.size + cellEnd);
+ write(cellStart + info.method + cellEnd);
+ write(cellStart + info.url + cellEnd);
+ write(cellStart + info.postData + cellEnd);
+ write("
");
+}
+
+function write(string) {
+ process.stdout.write(string + "\n");
+}
diff --git a/scripts/selenium-server-4.16.1.jar b/scripts/selenium-server-4.16.1.jar
new file mode 100644
index 0000000..8f18c72
Binary files /dev/null and b/scripts/selenium-server-4.16.1.jar differ
diff --git a/src/test/java/org/unicode/cldr/surveydriver/SurveyDriver.java b/src/test/java/org/unicode/cldr/surveydriver/SurveyDriver.java
index eea67db..118bb96 100644
--- a/src/test/java/org/unicode/cldr/surveydriver/SurveyDriver.java
+++ b/src/test/java/org/unicode/cldr/surveydriver/SurveyDriver.java
@@ -30,28 +30,27 @@
/**
* Perform automated testing of the CLDR Survey Tool using Selenium WebDriver.
- *
- * This test has been used with the cldr-apps-webdriver project running in IntelliJ. At the same time,
- * cldr-apps can be running either on localhost or on SmokeTest.
- *
- * This code requires installing an implementation of WebDriver, such as chromedriver for Chrome.
- * On macOS, chromedriver can be installed from Terminal with brew as follows:
- * brew install chromedriver
- * Then, right-click chromedriver, choose Open, and authorize to avoid the macOS error,
- * "“chromedriver” cannot be opened because the developer cannot be verified".
- * Press Ctrl+C to stop this instance of chromedriver.
- *
- * (Testing with geckodriver for Firefox was unsuccessful, but has not been tried recently.)
- *
- * Go to selenium.dev/downloads and scroll down
- * to "Selenium Server (Grid)" and follow the link to download a file like selenium-server-4.16.1.jar
- * and save it in the parent directory of cldr-apps-webdriver.
- *
- * Start selenium grid:
- *
- * sh cldr-apps-webdriver/scripts/selenium-grid-start.sh &
- *
- * Open this file (SurveyDriver.java) in IntelliJ, right-click cldr-apps-webdriver in the Project
+ *
+ *
This test has been used with the cldr-apps-webdriver project running in IntelliJ. At the same
+ * time, cldr-apps can be running either on localhost or on SmokeTest.
+ *
+ *
This code requires installing an implementation of WebDriver, such as chromedriver for Chrome.
+ * On macOS, chromedriver can be installed from Terminal with brew as follows: brew install
+ * chromedriver Then, right-click chromedriver, choose Open, and authorize to avoid the macOS error,
+ * "“chromedriver” cannot be opened because the developer cannot be verified". Press Ctrl+C to stop
+ * this instance of chromedriver.
+ *
+ *
(Testing with geckodriver for Firefox was unsuccessful, but has not been tried recently.)
+ *
+ *
Go to selenium.dev/downloads and scroll down
+ * to "Selenium Server (Grid)" and follow the link to download a file like
+ * selenium-server-4.16.1.jar and save it in the parent directory of cldr-apps-webdriver.
+ *
+ *
Start selenium grid:
+ *
+ *
cd cldr-apps-webdriver/scripts; sh selenium-grid-start.sh
+ *
+ *
Open this file (SurveyDriver.java) in IntelliJ, right-click cldr-apps-webdriver in the Project
* panel, and choose "Debug All Tests". You can do this repeatedly to start multiple browsers with
* simulated vetters vetting at the same time.
*/
@@ -61,10 +60,11 @@ public class SurveyDriver {
* Enable/disable specific tests using these booleans
*/
static final boolean TEST_VETTING_TABLE = false;
- static final boolean TEST_FAST_VOTING = true;
+ static final boolean TEST_FAST_VOTING = false;
static final boolean TEST_LOCALES_AND_PAGES = false;
static final boolean TEST_ANNOTATION_VOTING = false;
static final boolean TEST_XML_UPLOADER = false;
+ static final boolean TEST_DASHBOARD = true;
/*
* Configure for Survey Tool server, which can be localhost, cldr-smoke, cldr-staging, ...
@@ -81,9 +81,7 @@ public class SurveyDriver {
* the WebDriver interface). Otherwise, the driver could be a ChromeDriver, or FirefoxDriver, EdgeDriver,
* or SafariDriver (all subclasses of RemoteWebDriver) if we add options for those.
* While much of the code in this class works either way, Selenium Grid needs the driver to be a
- * RemoteWebDriver and requires installation of a hub and one or more "slots", which can be done by
- *
- * sh scripts/selenium-grid-start.sh
+ * RemoteWebDriver and requires installation, see above comment about selenium-grid-start.sh
*/
static final boolean USE_REMOTE_WEBDRIVER = true;
static final String REMOTE_WEBDRIVER_URL = "http://localhost:4444";
@@ -93,10 +91,9 @@ public class SurveyDriver {
private SessionId sessionId = null;
/**
- * A nonnegative integer associated with a particular simulated user of Survey Tool,
- * and with a particular instance of a browser in the selenium grid. The user's
- * email address will be like "driver-123@cldr-apps-webdriver.org", where 123
- * would be the userIndex.
+ * A nonnegative integer associated with a particular simulated user of Survey Tool, and with a
+ * particular instance of a browser in the selenium grid. The user's email address will be like
+ * "driver-123@cldr-apps-webdriver.org", where 123 would be the userIndex.
*/
private int userIndex = 0; // possibly changed below, see getUserIndexFromGrid
@@ -120,12 +117,13 @@ public static void runTests() {
if (TEST_XML_UPLOADER) {
assertTrue(new SurveyDriverXMLUploader(s).testXMLUploader());
}
+ if (TEST_DASHBOARD) {
+ assertTrue(new SurveyDriverDashboard(s).test());
+ }
s.tearDown();
}
- /**
- * Set up the driver and its "wait" object.
- */
+ /** Set up the driver and its "wait" object. */
private void setUp() {
LoggingPreferences logPrefs = new LoggingPreferences();
logPrefs.enable(LogType.BROWSER, Level.ALL);
@@ -151,9 +149,7 @@ private void setUp() {
}
}
- /**
- * Clean up when finished testing.
- */
+ /** Clean up when finished testing. */
private void tearDown() {
SurveyDriverLog.println("cldr-apps-webdriver is quitting, goodbye from sessionId " + sessionId);
if (driver != null) {
@@ -171,14 +167,12 @@ private void tearDown() {
}
/**
- * Test "fast" voting, that is, voting for several items on a page, and measuring
- * the time of response.
- *
- * Purposes:
- * (1) study the sequence of events, especially client-server traffic,
- * when multiple voting events (maybe by a single user) are being handled;
- * (2) simulate simultaneous input from multiple vetters, for integration and
- * performance testing under high load.
+ * Test "fast" voting, that is, voting for several items on a page, and measuring the time of
+ * response.
+ *
+ *
Purposes: (1) study the sequence of events, especially client-server traffic, when
+ * multiple voting events (maybe by a single user) are being handled; (2) simulate simultaneous
+ * input from multiple vetters, for integration and performance testing under high load.
*/
private boolean testFastVoting() {
if (!login()) {
@@ -435,9 +429,7 @@ private boolean testFastVotingInner(String page, String url) {
return true;
}
- /**
- * Log into Survey Tool.
- */
+ /** Log into Survey Tool. */
public boolean login() {
final String url = BASE_URL;
SurveyDriverLog.println("Logging in to " + url);
@@ -484,7 +476,7 @@ private boolean loginWithButton(String url) {
return clickButtonByXpath(loginXpath, url);
}
- private boolean clickButtonByXpath(String xpath, String url) {
+ public boolean clickButtonByXpath(String xpath, String url) {
WebElement el = getClickableElementByXpath(xpath, url);
if (el == null) {
return false;
@@ -519,12 +511,12 @@ private boolean inputTextByXpath(String xpath, String text, String url) {
private WebElement getClickableElementByXpath(String xpath, String url) {
try {
- wait.until(
- (ExpectedCondition) webDriver -> driver.findElement(By.xpath(xpath)) != null
- );
+ wait.until((ExpectedCondition) webDriver -> driver.findElement(By.xpath(xpath)) != null);
} catch (Exception e) {
SurveyDriverLog.println(e);
- SurveyDriverLog.println("❌ Test failed, timed out waiting for element to be found by xpath " + xpath + " in url " + url);
+ SurveyDriverLog.println(
+ "❌ Test failed, timed out waiting for element to be found by xpath " + xpath + " in url " + url
+ );
return null;
}
WebElement el;
@@ -534,7 +526,8 @@ private WebElement getClickableElementByXpath(String xpath, String url) {
} catch (Exception e) {
SurveyDriverLog.println(e);
SurveyDriverLog.println(
- "❌ Test failed, timed out waiting for " + xpath + " button to be clickable in " + url);
+ "❌ Test failed, timed out waiting for " + xpath + " button to be clickable in " + url
+ );
return null;
}
return el;
@@ -576,9 +569,7 @@ private boolean chooseComprehensiveCoverage(String url) {
return true;
}
- /**
- * Test all the locales and pages we're interested in.
- */
+ /** Test all the locales and pages we're interested in. */
private boolean testAllLocalesAndPages() {
String[] locales = SurveyDriverData.getLocales();
String[] pages = SurveyDriverData.getPages();
@@ -586,7 +577,8 @@ private boolean testAllLocalesAndPages() {
* Reference: https://unicode.org/cldr/trac/ticket/11238 "browser console shows error message,
* there is INHERITANCE_MARKER without inheritedValue"
*/
- String searchString = "INHERITANCE_MARKER without inheritedValue"; // formerly, "there is no Bailey Target item"
+ String searchString = "INHERITANCE_MARKER without inheritedValue"; // formerly, "there is no Bailey Target
+ // item"
for (String loc : locales) {
// for (PathHeader.PageId page : PathHeader.PageId.values()) {
@@ -602,10 +594,8 @@ private boolean testAllLocalesAndPages() {
/**
* Test the given locale and page.
*
- * @param loc
- * the locale string, like "pt_PT"
- * @param page
- * the page name, like "Alphabetic_Information"
+ * @param loc the locale string, like "pt_PT"
+ * @param page the page name, like "Alphabetic_Information"
* @return true if all parts of the test pass, else false
*/
private boolean testOneLocationAndPage(String loc, String page, String searchString) {
@@ -686,8 +676,7 @@ private boolean testAnnotationVoting() {
/**
* Count how many log entries contain the given string.
*
- * @param searchString
- * the string for which to search
+ * @param searchString the string for which to search
* @return the number of occurrences
*/
private int countLogEntriesContainingString(String searchString) {
@@ -738,7 +727,12 @@ public boolean waitUntilLoadingMessageDone(String url) {
String loadingId = "LoadingMessageSection";
try {
wait.until(
- (ExpectedCondition) webDriver -> Objects.requireNonNull(webDriver).findElement(By.id(loadingId)).getCssValue("display").contains("none")
+ (ExpectedCondition) webDriver ->
+ Objects
+ .requireNonNull(webDriver)
+ .findElement(By.id(loadingId))
+ .getCssValue("display")
+ .contains("none")
);
} catch (Exception e) {
SurveyDriverLog.println(e);
@@ -749,8 +743,8 @@ public boolean waitUntilLoadingMessageDone(String url) {
}
/**
- * Hide the element whose id is "left-sidebar", by simulating the appropriate mouse action if it's
- * visible.
+ * Hide the element whose id is "left-sidebar", by simulating the appropriate mouse action if
+ * it's visible.
*
* @param url the url we're loading
* @return true for success, false for failure
@@ -872,6 +866,29 @@ public WebElement waitInputBoxAppears(WebElement rowEl, String url) {
return inputEl;
}
+ /**
+ * Wait until the element with the given class name is clickable, then click it.
+ *
+ * @param className the class name
+ * @param url the url we're loading
+ * @return true for success, false for failure
+ */ public boolean clickButtonByClassName(String className, String url) {
+ WebElement button = driver.findElement(By.className(className));
+ if (!waitUntilElementClickable(button, url)) {
+ return false;
+ }
+ try {
+ button.click();
+ } catch (Exception e) {
+ SurveyDriverLog.println(e);
+ SurveyDriverLog.println(
+ "❌ Test failed, maybe timed out, waiting button with class " + className + " to be clickable in " + url
+ );
+ return false;
+ }
+ return true;
+ }
+
/**
* Wait until the element is clickable.
*
@@ -1013,7 +1030,8 @@ public void clickOnRowCellTagElement(
* Wait until an element with class with the given name exists, or wait until one doesn't.
*
* @param className the class name
- * @param checking true to wait until such an element exists, or false to wait until no such element exists
+ * @param checking true to wait until such an element exists, or false to wait until no such
+ * element exists
* @param url the url we're loading
* @return true for success, false for failure
*/
@@ -1043,7 +1061,8 @@ public boolean waitUntilClassExists(String className, boolean checking, String u
* Wait until an element with id with the given name exists, or wait until one doesn't.
*
* @param idName the id name
- * @param checking true to wait until such an element exists, or false to wait until no such element exists
+ * @param checking true to wait until such an element exists, or false to wait until no such
+ * element exists
* @param url the url we're loading
* @return true for success, false for failure
*/
@@ -1070,34 +1089,34 @@ public boolean waitUntilIdExists(String idName, boolean checking, String url) {
}
/**
- * Supposing there are n slots in the selenium grid, get a number
- * in the range [0, ..., n - 1], representing the particular
- * slot we are using. This number will be used as the "user index"
- * identifying a unique Survey Tool simulated user, with a fictitious
- * email address like "driver-123@cldr-apps-webdriver.org", where
- * 123 would be the user index.
+ * Supposing there are n slots in the selenium grid, get a number in the range [0, ..., n - 1],
+ * representing the particular slot we are using. This number will be used as the "user index"
+ * identifying a unique Survey Tool simulated user, with a fictitious email address like
+ * "driver-123@cldr-apps-webdriver.org", where 123 would be the user index.
*
* @param sessionId the session id associated with our slot
- *
* @return the user index, a nonnegative integer
*/
private int getUserIndexFromGrid(SessionId sessionId) {
String url = REMOTE_WEBDRIVER_URL + "/status"; // http://localhost:4444/status
driver.get(url);
String jsonString = driver.findElement(By.tagName("body")).getText();
- SurveyDriverLog.println("jsonString = " + jsonString);
+ // SurveyDriverLog.println("jsonString = " + jsonString);
// Ideally, at this point we could convert the json into a NodeStatus object
// NodeStatus nodeStatus = NodeStatus.fromJson(jsonString);
- // https://www.selenium.dev/selenium/docs/api/java/org/openqa/selenium/grid/data/NodeStatus.html
+ //
+ // https://www.selenium.dev/selenium/docs/api/java/org/openqa/selenium/grid/data/NodeStatus.html
// Unfortunately it's not clear how to do that...
// Another way would be with gson:
- // SurveyDriverTestSession obj = new Gson().fromJson(jsonString, SurveyDriverNodeStatus.class);
+ // SurveyDriverTestSession obj = new Gson().fromJson(jsonString,
+ // SurveyDriverNodeStatus.class);
// -- with our own imitation of NodeStatus, but that seems too difficult and error-prone.
// Another way would be to forget about NodeStatus, and simply assign a different user index
// each time we launch SurveyDriver, using our own mechanism independent of the grid...
- // Crude work-around: each slot has "lastStarted" (1970 if never), sometimes followed by sessionId.
+ // Crude work-around: each slot has "lastStarted" (1970 if never), sometimes followed by
+ // sessionId.
// For example:
// "lastStarted": "2023-12-21T03:24:37.017561Z",
// "sessionId": "19049a348716f4dd825e330c47787573",
diff --git a/src/test/java/org/unicode/cldr/surveydriver/SurveyDriverDashboard.java b/src/test/java/org/unicode/cldr/surveydriver/SurveyDriverDashboard.java
new file mode 100644
index 0000000..5deeabc
--- /dev/null
+++ b/src/test/java/org/unicode/cldr/surveydriver/SurveyDriverDashboard.java
@@ -0,0 +1,66 @@
+package org.unicode.cldr.surveydriver;
+
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+public class SurveyDriverDashboard {
+
+ private final SurveyDriver s;
+ private final WebDriver driver;
+
+ public SurveyDriverDashboard(SurveyDriver s) {
+ this.s = s;
+ this.driver = s.driver;
+ }
+
+ private static final String[] locales = {
+ // Czech, German, Spanish, French, Hindi, Japanese, Russian, Chinese
+ "cs", "de", "es", "fr", "hi", "ja", "ru", "zh"
+ };
+
+ /** Test the Dashboard interface */
+ public boolean test() {
+ if (!s.login()) {
+ return false;
+ }
+ final int REPETITION_COUNT = 10000;
+ for (int i = 0; i < REPETITION_COUNT; i++) {
+ SurveyDriverLog.println("SurveyDriverDashboard.test i = " + i);
+ if (!testOne(i)) {
+ return false;
+ }
+ }
+ SurveyDriverLog.println("✅ Dashboard test passed");
+ return true;
+ }
+
+ private boolean testOne(int i) {
+ String loc = locales[i % locales.length];
+ String url = SurveyDriver.BASE_URL + "v#/" + loc + "//";
+ driver.get(url);
+ if (!s.hideLeftSidebar(url)) {
+ return false;
+ }
+ if (!s.waitUntilElementInactive("left-sidebar", url)) {
+ return false;
+ }
+ if (!s.waitUntilElementInactive("overlay", url)) {
+ return false;
+ }
+ // If we're on a locale's "General Info" page (rather than a specific
+ // page such as "Alphabetic_Information"), then the "Open Dashboard"
+ // button is from GeneralInfo.vue, and its class includes "general-open-dash".
+ // There are other "Open Dashboard" buttons produced in cldrGui.mjs, and a
+ // "Dashboard" item in the left-sidebar.
+ // For unknown reasons, clickButtonByXpath with "//button[contains(., 'Open Dashboard')]" fails here.
+ if (!s.clickButtonByClassName("general-open-dash", url)) {
+ return false;
+ }
+ if (!s.waitUntilIdExists("DashboardScroller", true, url)) {
+ return false;
+ }
+ SurveyDriverLog.println("✅ Dashboard: tested locale " + loc);
+ return true;
+ }
+}
diff --git a/src/test/java/org/unicode/cldr/surveydriver/SurveyDriverXMLUploader.java b/src/test/java/org/unicode/cldr/surveydriver/SurveyDriverXMLUploader.java
index 7042db0..8da6177 100644
--- a/src/test/java/org/unicode/cldr/surveydriver/SurveyDriverXMLUploader.java
+++ b/src/test/java/org/unicode/cldr/surveydriver/SurveyDriverXMLUploader.java
@@ -14,9 +14,7 @@ public SurveyDriverXMLUploader(SurveyDriver s) {
this.s = s;
}
- /**
- * Test the XMLUploader interface ("Upload XML" in the gear menu).
- */
+ /** Test the XMLUploader interface ("Upload XML" in the gear menu). */
public boolean testXMLUploader() {
if (!s.login()) {
return false;
@@ -39,6 +37,14 @@ public boolean testXMLUploader() {
return false;
}
} while (!clickOnUploadXMLElement(url));
+
+ // The interface has changed.
+ // There is still a separate tab for bulk upload, but now there's a new step, choosing
+ // between these options:
+ // 1. Convert XLSX to XML
+ // 2. Upload XML as your vote (Bulk Upload)
+ SurveyDriverLog.println("❌ XML-Upload test needs revision for changed interface in Survey Tool!");
+
switchToNewTabOrWindow();
if (!specifyXmlFileToUpload(url)) {
@@ -50,8 +56,8 @@ public boolean testXMLUploader() {
}
/**
- * After new tab or window is created, switch WebDriver to it.
- * Otherwise, our actions would still operate on the old window.
+ * After new tab or window is created, switch WebDriver to it. Otherwise, our actions would
+ * still operate on the old window.
*/
private void switchToNewTabOrWindow() {
WebDriver driver = s.driver;
@@ -81,7 +87,7 @@ private void switchToNewTabOrWindow() {
}
/**
- * Click on the gear menu.
+ * Click on the main menu.
*
* @param url the url we're loading
* @return true for success, false for failure
@@ -120,13 +126,13 @@ private boolean clickOnMainMenu(String url) {
}
/**
- * Click on the "Upload XML" item in the gear menu.
+ * Click on the "Upload (Bulk Import)" item in the gear menu.
*
* @param url the url we're loading
* @return true for success, false for failure
*/
private boolean clickOnUploadXMLElement(String url) {
- String linkText = "Upload XML";
+ String linkText = "Upload (Bulk Import)";
WebElement clickEl = null;
try {
clickEl = s.driver.findElement(By.partialLinkText(linkText));