diff --git a/.gitignore b/.gitignore index ea8c4bf..3d339d6 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ +.DS_Store /target +surveydriver.properties diff --git a/pom.xml b/pom.xml index d3a405d..b87f70e 100644 --- a/pom.xml +++ b/pom.xml @@ -25,12 +25,7 @@ org.seleniumhq.selenium selenium-java - 4.3.0 - - - com.google.code.gson - gson - 2.10.1 + 4.16.1 org.apache.logging.log4j @@ -40,12 +35,12 @@ org.apache.logging.log4j log4j-core - 2.17.2 + 2.20.0 org.apache.logging.log4j log4j-slf4j-impl - 2.17.2 + 2.20.0 diff --git a/scripts/cldr-add-webdrivers.sql b/scripts/cldr-add-webdrivers.sql deleted file mode 100644 index 492cccd..0000000 --- a/scripts/cldr-add-webdrivers.sql +++ /dev/null @@ -1,11 +0,0 @@ --- Add Survey Tool users to the database, needed for running SurveyDriver. It must be consistent with SurveyDriver.getNodeLoginQuery. --- Usage: mysql cldrdb < scripts/cldr-add-webdrivers.sql -insert into cldr_users(userlevel, name, email, org, password) values(1, 'SundayDriver_TESTER_', 'sundaydriver.ta9emn2f.@czca.bangladesh.example.com', 'Bangladesh Computer Council', 'ME0BtTx7J'); -insert into cldr_users(userlevel, name, email, org, password) values(1, 'MondayDriver_TESTER_', 'mondaydriver.fvuisg2in@sisi.sil.example.com', 'SIL', 'OjATx0fTt'); -insert into cldr_users(userlevel, name, email, org, password) values(1, 'TuesdayDriver_TESTER_', 'tuesdaydriver.smw4grsg0@ork0.netflix.example.com', 'Netflix', 'QEuNcNCvi'); -insert into cldr_users(userlevel, name, email, org, password) values(1, 'WednesdayDriver_TESTER_', 'wednesdaydriver.kesjczv8q@8sye.afghan-csa.example.com', 'Afghan_csa', 'MjpHbYuJY'); -insert into cldr_users(userlevel, name, email, org, password) values(1, 'ThursdayDriver_TESTER_', 'thursdaydriver.klxizrpyc@p9mn.welsh-lc.example.com', 'Welsh LC', 'cMkLuCab1'); -insert into cldr_users(userlevel, name, email, org, password) values(1, 'FridayDriver_TESTER_', 'fridaydriver.kclabyoxi@fgkg.mozilla.example.com', 'Mozilla', 'qSR.KZ57V'); -insert into cldr_users(userlevel, name, email, org, password) values(1, 'SaturdayDriver_TESTER_', 'saturdaydriver.oelbvfn0x@smiz.cherokee.example.com', 'Cherokee', 'r3Lim3OFL'); -insert into cldr_users(userlevel, name, email, org, password) values(1, 'BackseatDriver_TESTER_', 'backseatdriver.cogihy42h@jqs9.india.example.com', 'India', 'LenA3VJSK'); -insert into cldr_users(userlevel, name, email, org, password) values(1, 'StudentDriver_TESTER_', 'studentdriver.h.ze76.2p@nd3e.government of pakistan - national language authority.example.com', 'Government of Pakistan - National Language Authority', 'S5fpuRqHW'); diff --git a/scripts/selenium-grid-start.sh b/scripts/selenium-grid-start.sh index 4bd6392..adbcab8 100644 --- a/scripts/selenium-grid-start.sh +++ b/scripts/selenium-grid-start.sh @@ -1,8 +1,2 @@ #!/bin/bash - -HUB_URL=http://localhost:4444/grid/register java -jar selenium-server-4.16.1.jar standalone & - -for ((PORT = 5555; PORT <= 5563; PORT++)) -do - HUB_URL=http://localhost:4444/grid/register java -jar selenium-server-4.16.1.jar standalone --port $PORT & -done +java -jar selenium-server-4.16.1.jar standalone --selenium-manager true & diff --git a/src/test/java/org/unicode/cldr/surveydriver/SurveyDriver.java b/src/test/java/org/unicode/cldr/surveydriver/SurveyDriver.java index a9a6816..eea67db 100644 --- a/src/test/java/org/unicode/cldr/surveydriver/SurveyDriver.java +++ b/src/test/java/org/unicode/cldr/surveydriver/SurveyDriver.java @@ -2,14 +2,13 @@ import static org.junit.Assert.assertTrue; -import com.google.gson.Gson; import java.net.MalformedURLException; import java.net.URL; import java.time.Duration; import java.util.List; +import java.util.Objects; +import java.util.Random; import java.util.logging.Level; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import org.openqa.selenium.By; import org.openqa.selenium.Keys; import org.openqa.selenium.NoSuchElementException; @@ -48,10 +47,6 @@ * 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. *

- * Add a specific set of simulated test users to the db, consistent with the method getNodeLoginQuery below: - *

- * mysql cldrdb < cldr-apps-webdriver/scripts/cldr-add-webdrivers.sql - *

* Start selenium grid: *

* sh cldr-apps-webdriver/scripts/selenium-grid-start.sh & @@ -72,10 +67,11 @@ public class SurveyDriver { static final boolean TEST_XML_UPLOADER = false; /* - * Configure for Survey Tool server, which can be localhost, SmokeTest, or other + * Configure for Survey Tool server, which can be localhost, cldr-smoke, cldr-staging, ... */ static final String BASE_URL = "http://localhost:9080/cldr-apps/"; - // static final String BASE_URL = "http://cldr-smoke.unicode.org/smoketest/"; + // static final String BASE_URL = "https://cldr-smoke.unicode.org/cldr-apps/"; + // static final String BASE_URL = "https://cldr-staging.unicode.org/cldr-apps/"; static final long TIME_OUT_SECONDS = 30; static final long SLEEP_MILLISECONDS = 100; @@ -85,19 +81,9 @@ 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 nodes, which can be done by + * RemoteWebDriver and requires installation of a hub and one or more "slots", which can be done by * * sh scripts/selenium-grid-start.sh - * - * It contains commands of these types (where x.x.x stands for the Selenium version number): - * - * HUB_URL=http://localhost:4444/grid/register java -jar selenium-server-x.x.x.jar standalone - * HUB_URL=http://localhost:4444/grid/register java -jar selenium-server-x.x.x.jar standalone --port 5555 - * HUB_URL=http://localhost:4444/grid/register java -jar selenium-server-x.x.x.jar standalone --port 5556 - * ... - * - * The port numbers 5555, ... in selenium-grid-start.sh must be consistent with the method - * SurveyDriver.getNodeLoginQuery. */ static final boolean USE_REMOTE_WEBDRIVER = true; static final String REMOTE_WEBDRIVER_URL = "http://localhost:4444"; @@ -105,7 +91,15 @@ public class SurveyDriver { public WebDriver driver; public WebDriverWait wait; private SessionId sessionId = null; - private int nodePort = 5555; // default, may be changed + + /** + * 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 + private boolean gotComprehensiveCoverage = false; public static void runTests() { @@ -138,7 +132,7 @@ private void setUp() { ChromeOptions options = new ChromeOptions(); options.setCapability("goog:loggingPrefs", logPrefs); - // options.addArguments("start-maximized"); // doesn't work + // options.addArguments("start-maximized"); // this works, but inconvenient especially if more than one // options.addArguments("auto-open-devtools-for-tabs"); // this works, but makes window too narrow if (USE_REMOTE_WEBDRIVER) { try { @@ -150,41 +144,10 @@ private void setUp() { SurveyDriverLog.println("Session id = " + sessionId); // e.g., 9c0d7d317d64cb53b6eaefc70427d4d8 } else { driver = new ChromeDriver(options); - // driver.manage().window().maximize(); // doesn't work } wait = new WebDriverWait(driver, Duration.ofSeconds(TIME_OUT_SECONDS), Duration.ofMillis(SLEEP_MILLISECONDS)); if (USE_REMOTE_WEBDRIVER) { - /* - * http://localhost:4444/grid/api/testsession?session= - * That returns json such as: - * {"msg":"slot found !", - * "success":true, - * "session":"9fb6ca4b0548bc4708bfd4708732bdd6", - * "internalKey":"1ae1fb96-8188-4c0e-9f49-051993962939", - * "inactivityTime":108, - * "proxyId":"http://192.168.2.10:5556"} - * - * The proxyId tells the port (like 5556) which in turn tells us which node we're on - */ - String url = REMOTE_WEBDRIVER_URL + "/grid/api/testsession?session=" + sessionId; - // String url = REMOTE_WEBDRIVER_URL + "/grid/api/hub/status?" + sessionId; - - driver.get(url); - String jsonString = driver.findElement(By.tagName("body")).getText(); - SurveyDriverLog.println("jsonString = " + jsonString); - Gson gson = new Gson(); - SurveyDriverTestSession obj = gson.fromJson(jsonString, SurveyDriverTestSession.class); - if (obj == null) { - SurveyDriverLog.println("Null SurveyDriverTestSession in setUp"); - } else { - String proxyId = obj.proxyId; - if (proxyId != null) { - Matcher m = Pattern.compile(":(\\d+)$").matcher(proxyId); - if (m.find()) { - nodePort = Integer.parseInt(m.group(1)); // e.g., 5556 - } - } - } + userIndex = getUserIndexFromGrid(sessionId); } } @@ -192,7 +155,7 @@ private void setUp() { * Clean up when finished testing. */ private void tearDown() { - SurveyDriverLog.println("cldr-apps-webdriver is quitting, goodbye from node on port " + nodePort); + SurveyDriverLog.println("cldr-apps-webdriver is quitting, goodbye from sessionId " + sessionId); if (driver != null) { /* * This five-second sleep may not always be appropriate. It can help to see the browser for a few seconds @@ -222,12 +185,10 @@ private boolean testFastVoting() { return false; } /* - * TODO: configure the locale and page on a per-node basis, to enable multiple simulated + * TODO: configure the locale and page on a per-slot basis, to enable multiple simulated * users to be voting in multiple locales and/or pages. */ String loc = "sr"; - // String loc = "ar"; - // String loc = "en_CA"; String page = "Languages_A_D"; String url = BASE_URL + "v#/" + loc + "/" + page; @@ -442,15 +403,11 @@ private boolean testFastVotingInner(String page, String url) { if (inputEl == null) { SurveyDriverLog.println("Warning: continuing, didn't see input box for " + url); continue; - // SurveyDriverLog.println("❌ Fast vote test failed, didn't see input box for " + url); - // return false; } inputEl = waitUntilRowCellTagElementClickable(inputEl, rowId, cellClass, "input", url); if (inputEl == null) { SurveyDriverLog.println("Warning: continuing, input box not clickable for " + url); continue; - // SurveyDriverLog.println("❌ Fast vote test failed, input box not clickable for " + url); - // return false; } inputEl.clear(); inputEl.click(); @@ -464,15 +421,6 @@ private boolean testFastVotingInner(String page, String url) { } } } - /* - * TODO: Wait for tr_checking2 element (temporary green background) to exist. - * Problem: sometimes server is too fast and/or our polling isn't frequent enough, - * and then the tr_checking2 element(s) are already gone before we get here. - * For now, skip this call. - */ - if (false && !waitUntilClassExists("tr_checking2", true, url)) { - return false; - } /* * Wait for tr_checking2 element (temporary green background) NOT to exist. * The temporary green background indicates that the client is waiting for a response @@ -491,7 +439,7 @@ private boolean testFastVotingInner(String page, String url) { * Log into Survey Tool. */ public boolean login() { - String url = BASE_URL + getNodeLoginQuery(); + final String url = BASE_URL; SurveyDriverLog.println("Logging in to " + url); driver.get(url); @@ -506,6 +454,9 @@ public boolean login() { if (!waitUntilLoadingMessageDone(url)) { return false; } + if (!loginWithButton(url)) { + return false; + } /* * To make sure we're really logged in, find an element with class "glyphicon-user". */ @@ -516,49 +467,77 @@ public boolean login() { return true; } - /** - * Get a query string for logging in as a particular user. It may depend on which Selenium node - * we're running on. It could also depend on BASE_URL if we're running on SmokeTest rather than - * localhost. - *

- * Currently, this set of users depends on running a mysql script on localhost or SmokeTest. - * See scripts/cldr-add-webdrivers.sql, usage "mysql cldrdb < cldr-apps-webdriver/scripts/cldr-add-webdrivers.sql". - * The usernames and passwords here need to agree with that script. - *

- * Make sure users have permission to vote in their locales. TC users can vote in all locales, - * so an easy way is to make them all TC. - *

- * The range of port numbers 5555, ..., here needs to match selenium-grid-start.sh - */ - private String getNodeLoginQuery() { - if (nodePort == 5555) { - return "survey?email=sundaydriver.ta9emn2f.@czca.bangladesh.example.com&uid=ME0BtTx7J"; + private boolean loginWithButton(String url) { + final String loginXpath = "//span[text()='Log In']"; + final String usernameXpath = "//input[@placeholder='Username']"; + final String passwordXpath = "//input[@placeholder='Password']"; + final SurveyDriverCredentials cred = SurveyDriverCredentials.getForUser(userIndex); + if (!clickButtonByXpath(loginXpath, url)) { + return false; } - if (nodePort == 5556) { - return "survey?email=mondaydriver.fvuisg2in@sisi.sil.example.com&uid=OjATx0fTt"; + if (!inputTextByXpath(usernameXpath, cred.getEmail(), url)) { + return false; } - if (nodePort == 5557) { - return "survey?email=tuesdaydriver.smw4grsg0@ork0.netflix.example.com&uid=QEuNcNCvi"; + if (!inputTextByXpath(passwordXpath, cred.getPassword(), url)) { + return false; } - if (nodePort == 5558) { - return "survey?email=wednesdaydriver.kesjczv8q@8sye.afghan-csa.example.com&uid=MjpHbYuJY"; + return clickButtonByXpath(loginXpath, url); + } + + private boolean clickButtonByXpath(String xpath, String url) { + WebElement el = getClickableElementByXpath(xpath, url); + if (el == null) { + return false; } - if (nodePort == 5559) { - return "survey?email=thursdaydriver.klxizrpyc@p9mn.welsh-lc.example.com&uid=cMkLuCab1"; + try { + el.click(); + } catch (Exception e) { + SurveyDriverLog.println("Exception caught while trying to click " + xpath + " element"); + SurveyDriverLog.println(e); + return false; } - if (nodePort == 5560) { - return "survey?email=fridaydriver.kclabyoxi@fgkg.mozilla.example.com&uid=qSR.KZ57V"; + return true; + } + + private boolean inputTextByXpath(String xpath, String text, String url) { + WebElement el = getClickableElementByXpath(xpath, url); + if (el == null) { + return false; } - if (nodePort == 5561) { - return "survey?email=saturdaydriver.oelbvfn0x@smiz.cherokee.example.com&uid=r3Lim3OFL"; + try { + el.clear(); + el.click(); + el.sendKeys(text); + el.sendKeys(Keys.RETURN); + } catch (Exception e) { + SurveyDriverLog.println("Exception caught while trying to input text " + text); + SurveyDriverLog.println(e); + return false; } - if (nodePort == 5562) { - return "survey?email=backseatdriver.cogihy42h@jqs9.india.example.com&uid=LenA3VJSK"; + return true; + } + + private WebElement getClickableElementByXpath(String xpath, String url) { + try { + 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); + return null; } - if (nodePort == 5563) { - return "survey?email=studentdriver.h.ze76.2p@nd3e.government%20of%20pakistan%20-%20national%20language%20authority.example.com&uid=S5fpuRqHW"; + WebElement el; + try { + el = driver.findElement(By.xpath(xpath)); + wait.until(ExpectedConditions.elementToBeClickable(el)); + } catch (Exception e) { + SurveyDriverLog.println(e); + SurveyDriverLog.println( + "❌ Test failed, timed out waiting for " + xpath + " button to be clickable in " + url); + return null; } - throw new RuntimeException("Unexpected node port " + nodePort); + return el; } /** @@ -737,12 +716,7 @@ private int countLogEntriesContainingString(String searchString) { public boolean waitForTitle(String s, String url) { try { wait.until( - new ExpectedCondition() { - @Override - public Boolean apply(WebDriver webDriver) { - return (webDriver.getTitle().contains(s)); - } - } + (ExpectedCondition) webDriver -> (Objects.requireNonNull(webDriver).getTitle().contains(s)) ); } catch (Exception e) { SurveyDriverLog.println(e); @@ -764,12 +738,7 @@ public boolean waitUntilLoadingMessageDone(String url) { String loadingId = "LoadingMessageSection"; try { wait.until( - new ExpectedCondition() { - @Override - public Boolean apply(WebDriver webDriver) { - return 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); @@ -825,12 +794,9 @@ public boolean hideLeftSidebar(String url) { public boolean waitUntilElementActive(String id, String url) { try { wait.until( - new ExpectedCondition() { - @Override - public Boolean apply(WebDriver webDriver) { - WebElement el = webDriver.findElement(By.id(id)); - return el != null && el.getAttribute("class").contains("active"); - } + (ExpectedCondition) webDriver -> { + WebElement el = Objects.requireNonNull(webDriver).findElement(By.id(id)); + return el != null && el.getAttribute("class").contains("active"); } ); } catch (Exception e) { @@ -851,12 +817,9 @@ public Boolean apply(WebDriver webDriver) { public boolean waitUntilElementInactive(String id, String url) { try { wait.until( - new ExpectedCondition() { - @Override - public Boolean apply(WebDriver webDriver) { - WebElement el = webDriver.findElement(By.id(id)); - return el == null || !el.getAttribute("class").contains("active"); - } + (ExpectedCondition) webDriver -> { + WebElement el = Objects.requireNonNull(webDriver).findElement(By.id(id)); + return el == null || !el.getAttribute("class").contains("active"); } ); } catch (Exception e) { @@ -1057,14 +1020,9 @@ public void clickOnRowCellTagElement( public boolean waitUntilClassExists(String className, boolean checking, String url) { try { wait.until( - new ExpectedCondition() { - @Override - public Boolean apply(WebDriver webDriver) { - // WebElement el = webDriver.findElement(By.className(className)); - // return checking ? (el != null) : (el == null); - int elCount = webDriver.findElements(By.className(className)).size(); - return checking ? (elCount > 0) : (elCount == 0); - } + (ExpectedCondition) webDriver -> { + int elCount = Objects.requireNonNull(webDriver).findElements(By.className(className)).size(); + return checking ? (elCount > 0) : (elCount == 0); } ); } catch (Exception e) { @@ -1092,12 +1050,9 @@ public Boolean apply(WebDriver webDriver) { public boolean waitUntilIdExists(String idName, boolean checking, String url) { try { wait.until( - new ExpectedCondition() { - @Override - public Boolean apply(WebDriver webDriver) { - int elCount = webDriver.findElements(By.id(idName)).size(); - return checking ? (elCount > 0) : (elCount == 0); - } + (ExpectedCondition) webDriver -> { + int elCount = Objects.requireNonNull(webDriver).findElements(By.id(idName)).size(); + return checking ? (elCount > 0) : (elCount == 0); } ); } catch (Exception e) { @@ -1114,14 +1069,54 @@ public Boolean apply(WebDriver webDriver) { return true; } - static class SurveyDriverTestSession { - String proxyId = null; - /* Additional fields currently unused: - String msg = null; - String success = null; - String session = null; - String internalKey = null; - String inactivityTime = null; - */ + /** + * 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); + + // 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 + // Unfortunately it's not clear how to do that... + // Another way would be with gson: + // 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. + // For example: + // "lastStarted": "2023-12-21T03:24:37.017561Z", + // "sessionId": "19049a348716f4dd825e330c47787573", + int uIndex = -1; + String sessionIdString = sessionId.toString(); + int slotCount = 0; + for (String line : jsonString.split("\\n")) { + if (line.contains("lastStarted")) { + ++slotCount; + } else if (line.contains("sessionId") && line.contains(sessionIdString)) { + uIndex = slotCount - 1; + System.out.println("Setting user index to " + uIndex + "; based on " + url); + break; + } + } + if (uIndex < 0) { + uIndex = new Random().nextInt(9); + System.out.println("Setting user index randomly to " + uIndex + "; unable to determine from " + url); + } + return uIndex; } } diff --git a/src/test/java/org/unicode/cldr/surveydriver/SurveyDriverCredentials.java b/src/test/java/org/unicode/cldr/surveydriver/SurveyDriverCredentials.java new file mode 100644 index 0000000..6437f83 --- /dev/null +++ b/src/test/java/org/unicode/cldr/surveydriver/SurveyDriverCredentials.java @@ -0,0 +1,62 @@ +package org.unicode.cldr.surveydriver; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +public class SurveyDriverCredentials { + /** + * Fictitious domain identifying fictitious email addresses for simulated users like + * "driver-123@cldr-apps-webdriver.org" + */ + private static final String EMAIL_AT_DOMAIN = "@cldr-apps-webdriver.org"; + private static final String EMAIL_PREFIX = "driver-"; + + /** + * cldr-apps-webdriver/src/test/resources/org/unicode/cldr/surveydriver/surveydriver.properties + * -- not in version control; contains a line WEBDRIVER_PASSWORD=... + */ + private static final String PROPS_FILENAME = "surveydriver.properties"; + private static final String PROPS_PASSWORD_KEY = "WEBDRIVER_PASSWORD"; + private static String webdriverPassword = null; + + private final String email; + + private SurveyDriverCredentials(String email) { + this.email = email; + } + + /** + * Get credentials for logging in as a particular user depending on which Selenium slot + * we're running on. + */ + public static SurveyDriverCredentials getForUser(int userIndex) { + String email = EMAIL_PREFIX + userIndex + EMAIL_AT_DOMAIN; + return new SurveyDriverCredentials(email); + } + + public String getEmail() { + return email; + } + + public String getPassword() { + if (webdriverPassword != null) { + return webdriverPassword; + } + final InputStream stream = SurveyDriverCredentials.class.getResourceAsStream(PROPS_FILENAME); + if (stream == null) { + throw new RuntimeException("File not found: " + PROPS_FILENAME); + } + final Properties props = new java.util.Properties(); + try { + props.load(stream); + } catch (IOException e) { + throw new RuntimeException(e); + } + webdriverPassword = (String) props.get(PROPS_PASSWORD_KEY); + if (webdriverPassword == null || webdriverPassword.isBlank()) { + throw new RuntimeException("WEBDRIVER_PASSWORD not found in " + PROPS_FILENAME); + } + return webdriverPassword; + } +}