Skip to content

Commit ef3f7bc

Browse files
committed
Add settings for PUB/SUB port assignments
1 parent 3bbe47f commit ef3f7bc

10 files changed

Lines changed: 202 additions & 16 deletions

File tree

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
profile=selenium4
2-
version=28.0.0-SNAPSHOT
2+
version=34.0.1-SNAPSHOT

src/main/java/com/nordstrom/automation/selenium/AbstractSeleniumConfig.java

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import java.io.File;
44
import java.io.IOException;
5+
import java.lang.reflect.InvocationTargetException;
6+
import java.lang.reflect.Method;
57
import java.net.MalformedURLException;
68
import java.net.URI;
79
import java.net.URISyntaxException;
@@ -29,6 +31,7 @@
2931
import org.apache.http.client.utils.URIBuilder;
3032
import org.openqa.selenium.Capabilities;
3133
import org.openqa.selenium.SearchContext;
34+
import org.openqa.selenium.net.PortProber;
3235
import org.slf4j.Logger;
3336
import org.slf4j.LoggerFactory;
3437

@@ -201,6 +204,22 @@ public enum SeleniumSettings implements SettingsCore.SettingsAPI {
201204
*/
202205
HUB_PORT("selenium.hub.port", null),
203206

207+
/**
208+
* This is the port used by local <b>Selenium Grid</b> components for publishing events.
209+
* <p>
210+
* name: <b>selenium.publish.port</b><br>
211+
* default: <b>4442</b>
212+
*/
213+
PUBLISH_PORT("selenium.publish.port", "4442"),
214+
215+
/**
216+
* This is the port used by local <b>Selenium Grid</b> components for subscribing to events.
217+
* <p>
218+
* name: <b>selenium.subscribe.port</b><br>
219+
* default: <b>4443</b>
220+
*/
221+
SUBSCRIBE_PORT("selenium.subscribe.port", "4443"),
222+
204223
/**
205224
* This setting specifies the slot matcher used by the local <b>Selenium Grid</b> hub server.
206225
*
@@ -354,11 +373,20 @@ public enum SeleniumSettings implements SettingsCore.SettingsAPI {
354373
*/
355374
CONTEXT_PLATFORM("selenium.context.platform", "support"),
356375

376+
/**
377+
* This setting specifies the port that the {@code Appium} server listens on.
378+
* <p>
379+
* name: <b>appium.server.port</b><br>
380+
* default: <b>4723</b>
381+
*/
382+
APPIUM_SERVER_PORT("appium.server.port", "4723"),
383+
357384
/**
358385
* This setting specifies the path to an {@code Appium} configuration file provided to the server
359386
* when it's launched as a local <b>Selenium Grid</b> node server.
360387
* <p>
361-
* <b>NOTE</b>: If specified, this setting
388+
* <b>NOTE</b>: If specified, the config file indicated by this setting provides base values for the options
389+
* it declares. These values can be supplemented or overridden via the {@link #APPIUM_CLI_ARGS} setting.
362390
* <p>
363391
* name: <b>appium.config.path</b><br>
364392
* default: {@code null}
@@ -693,6 +721,22 @@ public synchronized URL getHubUrl() {
693721
return hubUrl;
694722
}
695723

724+
/**
725+
* Get the <b>Selenium Grid</b> event bus 'publish' URL.
726+
*
727+
* @return URL for publishing <b>Grid</b> events
728+
* @see SeleniumSettings#PUBLISH_PORT
729+
*/
730+
public abstract String getPublishUrl();
731+
732+
/**
733+
* Get the <b>Selenium Grid</b> event bus 'subscribe' URL.
734+
*
735+
* @return URL for subscribing to <b>Grid</b> events
736+
* @see SeleniumSettings#SUBSCRIBE_PORT
737+
*/
738+
public abstract String getSubscribeUrl();
739+
696740
/**
697741
* Get object that represents the active Selenium Grid.
698742
*
@@ -822,6 +866,16 @@ public Path getHubConfigPath() {
822866
return hubConfigPath;
823867
}
824868

869+
/**
870+
* Get the port that the {@code Appium} server listens on.
871+
*
872+
* @return {@link SeleniumSettings#APPIUM_SERVER_PORT APPIUM_SERVER_PORT} if defined and available;
873+
* otherwise random available port
874+
*/
875+
public int getAppiumServerPort() {
876+
return getAvailablePort(SeleniumSettings.APPIUM_SERVER_PORT);
877+
}
878+
825879
/**
826880
* Get the path to the Appium configuration.
827881
*
@@ -1117,4 +1171,39 @@ public boolean appiumWithPM2() {
11171171
public String getSettingsPath() {
11181172
return SETTINGS_FILE;
11191173
}
1174+
1175+
/**
1176+
* Get available port for the specified setting.
1177+
*
1178+
* @param setting setting to check for port availability
1179+
* @return value of setting if defined and available; otherwise random available port
1180+
*/
1181+
protected int getAvailablePort(final SeleniumSettings setting) {
1182+
int port = getInt(setting.key(), -1);
1183+
if (port != -1) {
1184+
String portStr = Integer.toString(port);
1185+
port = checkPortIsFree(port);
1186+
if (port == -1) {
1187+
LOGGER.warn("{} port '{}' is unavailable; finding free port", setting.name(), portStr);
1188+
}
1189+
}
1190+
return (port != -1) ? port : PortProber.findFreePort();
1191+
}
1192+
1193+
/**
1194+
* Determine if the specified port is free.
1195+
*
1196+
* @param port port to be evaluated
1197+
* @return the specified port if it's free; otherwise -1
1198+
*/
1199+
private static int checkPortIsFree(int port) {
1200+
try {
1201+
Method method = PortProber.class.getDeclaredMethod("checkPortIsFree", int.class);
1202+
method.setAccessible(true);
1203+
return (int) method.invoke(null, port);
1204+
} catch (NoSuchMethodException | SecurityException | IllegalAccessException |
1205+
IllegalArgumentException | InvocationTargetException e) {
1206+
return -1;
1207+
}
1208+
}
11201209
}

src/main/java/com/nordstrom/automation/selenium/DriverPlugin.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ default LocalGridServer create(SeleniumConfig config, String launcherClassName,
109109
* @throws IOException if an I/O error occurs
110110
*/
111111
LocalGridServer create(SeleniumConfig config, String launcherClassName, String[] dependencyContexts,
112-
URL hubUrl, final Path workingPath, final Path outputPath) throws IOException;
112+
URL hubUrl, Path workingPath, Path outputPath) throws IOException;
113113

114114
/**
115115
* Get constructor for this driver's {@link RemoteWebDriver} implementation.

src/main/java/com/nordstrom/automation/selenium/plugins/RemoteWebDriverPlugin.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,14 @@ protected RemoteWebDriverPlugin(String browserName) {
4949
*/
5050
@Override
5151
public LocalGridServer create(SeleniumConfig config, String launcherClassName, String[] dependencyContexts,
52-
URL hubUrl, final Path workingPath, final Path outputPath) throws IOException {
52+
URL hubUrl, Path workingPath, Path outputPath) throws IOException {
5353

5454
String[] combinedContexts = combineDependencyContexts(dependencyContexts, this);
5555
String capabilities = getCapabilities(config);
5656
Path nodeConfigPath = config.createNodeConfig(capabilities, hubUrl);
5757
String[] propertyNames = getPropertyNames(capabilities);
58-
return LocalSeleniumGrid.create(config, launcherClassName, combinedContexts,
59-
false, -1, nodeConfigPath, workingPath, outputPath, propertyNames);
58+
return LocalSeleniumGrid.createNode(config, launcherClassName, combinedContexts,
59+
-1, nodeConfigPath, workingPath, outputPath, propertyNames);
6060
}
6161

6262
/**

src/main/java/com/nordstrom/automation/selenium/support/TestNgPlatformBase.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public String[] getSubPath() {
5858
* {@inheritDoc}
5959
*/
6060
@Override
61-
@SuppressWarnings({ "unchecked", "serial" })
61+
@SuppressWarnings("unchecked")
6262
public P getTargetPlatform() {
6363
P platform = null;
6464
ITestResult testResult = Reporter.getCurrentTestResult();

src/selenium3/java/com/nordstrom/automation/selenium/SeleniumConfig.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,16 @@ protected Map<String, String> getDefaults() {
230230
return defaults;
231231
}
232232

233+
@Override
234+
public String getPublishUrl() {
235+
return null;
236+
}
237+
238+
@Override
239+
public String getSubscribeUrl() {
240+
return null;
241+
}
242+
233243
/**
234244
* {@inheritDoc}
235245
*/

src/selenium3/java/com/nordstrom/automation/selenium/core/LocalSeleniumGrid.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,35 @@ public static SeleniumGrid create(SeleniumConfig config, final URL hubUrl) throw
203203
}
204204
}
205205

206+
/**
207+
* Create an object that represents a Selenium Grid server with the specified arguments.
208+
* <p>
209+
* <b>NOTE</b>: The created object defines a separate process for managing the local server, but does <b>NOT</b>
210+
* start this process.
211+
*
212+
* @param config {@link SeleniumConfig} object
213+
* @param launcherClassName fully-qualified name of {@code GridLauncher} class
214+
* @param dependencyContexts fully-qualified names of context classes for Selenium Grid dependencies
215+
* @param port port that Grid server should use; -1 to specify auto-configuration
216+
* @param configPath {@link Path} to server configuration file
217+
* @param workingPath {@link Path} of working directory for server process; {@code null} for default
218+
* @param outputPath {@link Path} to output log file; {@code null} to decline log-to-file
219+
* @param propertyNames optional array of property names to propagate to server process
220+
* @return {@link LocalGridServer} object for managing the server process
221+
* @throws GridServerLaunchFailedException if a Grid component process failed to start
222+
* @see #activate()
223+
* @see LocalGridServer#start()
224+
* @see <a href="http://www.seleniumhq.org/docs/07_selenium_grid.jsp#getting-command-line-help">
225+
* Getting Command-Line Help</a>
226+
*/
227+
public static LocalGridServer createNode(final SeleniumConfig config, final String launcherClassName,
228+
final String[] dependencyContexts, final Integer port, final Path configPath,
229+
final Path workingPath, final Path outputPath, final String... propertyNames) {
230+
231+
return create(config, launcherClassName, dependencyContexts, false,
232+
port, configPath, workingPath, outputPath, propertyNames);
233+
}
234+
206235
/**
207236
* Create an object that represents a Selenium Grid server with the specified arguments.
208237
* <p>

src/selenium4/java/com/nordstrom/automation/selenium/SeleniumConfig.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
import org.openqa.selenium.MutableCapabilities;
3030
import org.openqa.selenium.grid.config.ConfigException;
3131
import org.openqa.selenium.json.Json;
32-
import org.openqa.selenium.net.PortProber;
3332

3433
import com.nordstrom.automation.selenium.core.GridServer;
3534
import com.nordstrom.automation.selenium.core.GridUtility;
@@ -47,6 +46,7 @@ public class SeleniumConfig extends AbstractSeleniumConfig {
4746
private static final String DEFAULT_HUB_PORT = "4446";
4847
private static final String DEFAULT_HUB_CONFIG = "hubConfig-s4.json";
4948
private static final String DEFAULT_NODE_CONFIG = "nodeConfig-s4.json";
49+
private static final String EVENT_HOST = "tcp://*:";
5050

5151
/**
5252
* <b>org.openqa.selenium.grid.Main</b>
@@ -391,6 +391,18 @@ protected Map<String, String> getDefaults() {
391391
return defaults;
392392
}
393393

394+
@Override
395+
public String getPublishUrl() {
396+
int port = getAvailablePort(SeleniumSettings.PUBLISH_PORT);
397+
return (port != -1) ? EVENT_HOST + port : null;
398+
}
399+
400+
@Override
401+
public String getSubscribeUrl() {
402+
int port = getAvailablePort(SeleniumSettings.SUBSCRIBE_PORT);
403+
return (port != -1) ? EVENT_HOST + port : null;
404+
}
405+
394406
/**
395407
* {@inheritDoc}
396408
*/
@@ -450,7 +462,7 @@ public Path createNodeConfig(String capabilities, URL hubUrl) throws IOException
450462
// create relay configuration template if absent
451463
Map<String, Object> relayOptions = (Map<String, Object>) nodeConfig.computeIfAbsent("relay", k -> new HashMap<>());
452464
relayOptions.computeIfAbsent("host", k -> HostUtils.getLocalHost());
453-
relayOptions.computeIfAbsent("port", k -> PortProber.findFreePort());
465+
relayOptions.computeIfAbsent("port", k -> SeleniumConfig.getConfig().getAppiumServerPort());
454466
relayOptions.computeIfAbsent("configs", k -> new ArrayList<>());
455467
// otherwise (not Appium)
456468
} else {

src/selenium4/java/com/nordstrom/automation/selenium/core/LocalSeleniumGrid.java

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ public class LocalSeleniumGrid extends SeleniumGrid {
4242
private static final String OPT_HOST = "--host";
4343
private static final String OPT_PORT = "--port";
4444
private static final String OPT_CONFIG = "--config";
45+
private static final String OPT_PUB_EVENTS = "--publish-events";
46+
private static final String OPT_SUB_EVENTS = "--subscribe-events";
4547

4648
/**
4749
* Constructor for models of local Selenium Grid instances from hub URL.
@@ -139,6 +141,8 @@ public static SeleniumGrid create(SeleniumConfig config, final URL hubUrl) throw
139141
Objects.requireNonNull(config, "[config] must be non-null");
140142

141143
String launcherClassName = config.getString(SeleniumSettings.GRID_LAUNCHER.key());
144+
String publishUrl = config.getPublishUrl();
145+
String subscribeUrl = config.getSubscribeUrl();
142146
String[] dependencyContexts = config.getDependencyContexts();
143147
String workingDir = config.getString(SeleniumSettings.GRID_WORKING_DIR.key());
144148
Path workingPath = (workingDir == null || workingDir.isEmpty()) ? null : Paths.get(workingDir);
@@ -157,8 +161,8 @@ public static SeleniumGrid create(SeleniumConfig config, final URL hubUrl) throw
157161
Integer hubPort = (hubUrl != null) ?
158162
hubUrl.getPort() : config.getInteger(SeleniumSettings.HUB_PORT.key(), -1);
159163
Path outputPath = GridUtility.getOutputPath(config, true);
160-
hubServer = create(config, launcherClassName, dependencyContexts,
161-
true, hubPort, hubConfigPath, workingPath, outputPath);
164+
hubServer = create(config, launcherClassName, dependencyContexts, true, hubPort,
165+
publishUrl, subscribeUrl, hubConfigPath, workingPath, outputPath);
162166
}
163167

164168
// store hub host and hub port in system properties for subsequent retrieval
@@ -178,10 +182,10 @@ public static SeleniumGrid create(SeleniumConfig config, final URL hubUrl) throw
178182
nodeServers.add(nodeServer);
179183
// if this is an Appium Grid server
180184
if (nodeServer instanceof AppiumGridServer) {
181-
// get path to relay configuration path from Appium process environment
185+
// get path to relay configuration from Appium process environment
182186
Path nodeConfigPath = ((AppiumGridServer) nodeServer).getNodeConfigPath();
183187
// add relay node for Appium Grid server to nodes list
184-
nodeServers.add(create(config, launcherClassName, dependencyContexts, false, -1, nodeConfigPath,
188+
nodeServers.add(createNode(config, launcherClassName, dependencyContexts, -1, nodeConfigPath,
185189
workingPath, GridUtility.getOutputPath(config, null)));
186190
LOGGER.debug("Adding local Grid relay for Appium server providing personalities: {}",
187191
nodeServer.getPersonalities().keySet());
@@ -218,6 +222,35 @@ public static SeleniumGrid create(SeleniumConfig config, final URL hubUrl) throw
218222
}
219223
}
220224

225+
/**
226+
* Create an object that represents a Selenium Grid server with the specified arguments.
227+
* <p>
228+
* <b>NOTE</b>: The created object defines a separate process for managing the local server, but does <b>NOT</b>
229+
* start this process.
230+
*
231+
* @param config {@link SeleniumConfig} object
232+
* @param launcherClassName fully-qualified name of {@code GridLauncher} class
233+
* @param dependencyContexts fully-qualified names of context classes for Selenium Grid dependencies
234+
* @param port port that Grid server should use; -1 to specify auto-configuration
235+
* @param configPath {@link Path} to server configuration file
236+
* @param workingPath {@link Path} of working directory for server process; {@code null} for default
237+
* @param outputPath {@link Path} to output log file; {@code null} to decline log-to-file
238+
* @param propertyNames optional array of property names to propagate to server process
239+
* @return {@link LocalGridServer} object for managing the server process
240+
* @throws GridServerLaunchFailedException if a Grid component process failed to start
241+
* @see #activate()
242+
* @see LocalGridServer#start()
243+
* @see <a href="http://www.seleniumhq.org/docs/07_selenium_grid.jsp#getting-command-line-help">
244+
* Getting Command-Line Help</a>
245+
*/
246+
public static LocalGridServer createNode(final SeleniumConfig config, final String launcherClassName,
247+
final String[] dependencyContexts, final Integer port, final Path configPath,
248+
final Path workingPath, final Path outputPath, final String... propertyNames) {
249+
250+
return create(config, launcherClassName, dependencyContexts, false, port,
251+
null, null, configPath, workingPath, outputPath, propertyNames);
252+
}
253+
221254
/**
222255
* Create an object that represents a Selenium Grid server with the specified arguments.
223256
* <p>
@@ -229,19 +262,22 @@ public static SeleniumGrid create(SeleniumConfig config, final URL hubUrl) throw
229262
* @param dependencyContexts fully-qualified names of context classes for Selenium Grid dependencies
230263
* @param isHub role of Grid server being started ({@code true} = hub; {@code false} = node)
231264
* @param port port that Grid server should use; -1 to specify auto-configuration
265+
* @param publishUrl URL for publishing Grid events
266+
* @param subscribeUrl URL for subscribing to Grid events
232267
* @param configPath {@link Path} to server configuration file
233268
* @param workingPath {@link Path} of working directory for server process; {@code null} for default
234269
* @param outputPath {@link Path} to output log file; {@code null} to decline log-to-file
235270
* @param propertyNames optional array of property names to propagate to server process
236271
* @return {@link LocalGridServer} object for managing the server process
237-
* @throws GridServerLaunchFailedException If a Grid component process failed to start
272+
* @throws GridServerLaunchFailedException if a Grid component process failed to start
238273
* @see #activate()
239274
* @see LocalGridServer#start()
240275
* @see <a href="http://www.seleniumhq.org/docs/07_selenium_grid.jsp#getting-command-line-help">
241276
* Getting Command-Line Help</a>
242277
*/
243278
public static LocalGridServer create(final SeleniumConfig config, final String launcherClassName,
244-
final String[] dependencyContexts, final boolean isHub, final Integer port, final Path configPath,
279+
final String[] dependencyContexts, final boolean isHub, final Integer port,
280+
final String publishUrl, final String subscribeUrl, final Path configPath,
245281
final Path workingPath, final Path outputPath, final String... propertyNames) {
246282

247283
List<String> argsList = new ArrayList<>();
@@ -272,6 +308,16 @@ public static LocalGridServer create(final SeleniumConfig config, final String l
272308
argsList.add(OPT_PORT);
273309
argsList.add(portNum.toString());
274310

311+
if (publishUrl != null) {
312+
argsList.add(OPT_PUB_EVENTS);
313+
argsList.add(publishUrl);
314+
}
315+
316+
if (subscribeUrl != null) {
317+
argsList.add(OPT_SUB_EVENTS);
318+
argsList.add(subscribeUrl);
319+
}
320+
275321
// specify server configuration file
276322
argsList.add(OPT_CONFIG);
277323
argsList.add(configPath.toString());

0 commit comments

Comments
 (0)