11package com .nordstrom .automation .selenium .plugins ;
22
3+ import java .io .BufferedReader ;
4+ import java .io .InputStreamReader ;
5+ import java .lang .reflect .Type ;
6+ import java .nio .file .Files ;
7+ import java .nio .file .Paths ;
8+ import java .util .List ;
39import java .util .Map ;
10+ import java .util .Objects ;
11+
12+ import org .openqa .selenium .json .Json ;
13+ import org .openqa .selenium .json .TypeToken ;
14+ import org .openqa .selenium .manager .SeleniumManager ;
15+ import org .openqa .selenium .manager .SeleniumManagerOutput .Result ;
416
517import com .nordstrom .automation .selenium .SeleniumConfig ;
18+ import com .nordstrom .automation .selenium .core .JsUtility ;
19+ import com .nordstrom .automation .selenium .exceptions .DriverExecutableNotFoundException ;
20+ import com .nordstrom .common .file .OSInfo ;
21+ import com .nordstrom .common .file .OSInfo .OSType ;
622
723/**
824 * This class is the plug-in for <b>OperaDriver</b>.
925 */
10- public class OperaPlugin extends RemoteWebDriverPlugin {
26+ public class OperaPlugin extends ChromePlugin {
27+
28+ private static final String TEMPLATE =
29+ "{\" browserName\" :\" chrome\" ," +
30+ "\" goog:chromeOptions\" :{\" binary\" :\" <binary-path>\" }," +
31+ "\" nord:options\" :{\" personality\" :\" opera\" ," +
32+ "\" driverPath\" :\" <driver-path>\" }}" ;
33+
34+ private static final String BASELINE =
35+ "{\" browserName\" :\" chrome\" ," +
36+ "\" goog:chromeOptions\" :{\" args\" :[\" --disable-infobars\" ,\" --disable-dev-shm-usage\" ," +
37+ "\" --remote-debugging-port=9222\" ,\" --no-sandbox\" ," +
38+ "\" --disable-gpu\" ,\" --disable-logging\" ]," +
39+ "\" prefs\" :{\" credentials_enable_service\" :false}}," +
40+ "\" nord:options\" :{\" personality\" :\" opera\" ," +
41+ "\" pluginClass\" :\" com.nordstrom.automation.selenium.plugins.OperaPlugin\" }}" ;
42+
43+ private static final OSType OS_TYPE ;
44+ private static final Map <String , String > OPERA_TO_CHROMIUM ;
45+ private static final String BINARY_PATH ;
46+ private static final String DRIVER_PATH ;
47+ private static final String CAPABILITIES ;
48+ private static final Map <String , String > PERSONALITIES ;
49+
50+ private static final String VERSION_MAPPINGS = "operaChromiumVersions.json" ;
51+ private static final Type MAP_TYPE = new TypeToken <Map <String , String >>() {}.getType ();
52+
53+ static {
54+ OS_TYPE = OSInfo .getDefault ().getType ();
55+ String mappings = JsUtility .getScriptResource (VERSION_MAPPINGS );
56+ OPERA_TO_CHROMIUM = new Json ().toType (mappings , MAP_TYPE );
57+
58+ BINARY_PATH = Objects .requireNonNull (findOperaBinary (), "Failed finding the Opera browser binary" );
59+ DRIVER_PATH = Objects .requireNonNull (findDriverBinary (), "Failed finding the 'chromedriver' binary" );
60+
61+ CAPABILITIES = TEMPLATE
62+ .replace ("<binary-path>" , BINARY_PATH .replaceAll ("\\ \\ " , "/" ))
63+ .replace ("<driver-path>" , DRIVER_PATH .replaceAll ("\\ \\ " , "/" ));
64+ PERSONALITIES = Map .of (OperaCaps .DRIVER_NAME , BASELINE );
65+ }
1166
1267 /**
1368 * Constructor for <b>OperaPlugin</b> objects.
@@ -16,58 +71,146 @@ public OperaPlugin() {
1671 super (OperaCaps .DRIVER_NAME );
1772 }
1873
19- /**
20- * <b>org.openqa.selenium.opera.OperaDriver</b>
21- *
22- * <pre><dependency>
23- * <groupId>org.seleniumhq.selenium</groupId>
24- * <artifactId>selenium-opera-driver</artifactId>
25- * <version>4.30.0</version>
26- *</dependency></pre>
27- *
28- * <b>net.bytebuddy.matcher.ElementMatcher</b>
29- *
30- * <pre><dependency>
31- * <groupId>net.bytebuddy</groupId>
32- * <artifactId>byte-buddy</artifactId>
33- * <version>1.17.5</version>
34- *</dependency></pre>
35- */
36- private static final String [] DEPENDENCY_CONTEXTS = {
37- "org.openqa.selenium.opera.OperaDriver" ,
38- "net.bytebuddy.matcher.ElementMatcher"
39- };
40-
4174 /**
4275 * {@inheritDoc}
4376 */
4477 @ Override
45- public String [] getDependencyContexts ( ) {
46- return DEPENDENCY_CONTEXTS ;
78+ public String getCapabilities ( SeleniumConfig config ) {
79+ return CAPABILITIES ;
4780 }
4881
4982 /**
5083 * {@inheritDoc}
5184 */
5285 @ Override
53- public String getCapabilities ( SeleniumConfig config ) {
54- return OperaCaps . getCapabilities () ;
86+ public Map < String , String > getPersonalities ( ) {
87+ return PERSONALITIES ;
5588 }
5689
5790 /**
58- * {@inheritDoc}
91+ * Get list of system property names recognized by the driver associated with this plug-in.
92+ *
93+ * @param capabilities JSON {@link org.openqa.selenium.Capabilities Capabilities} object
94+ * @return list of system property names
5995 */
60- @ Override
61- public Map <String , String > getPersonalities () {
62- return OperaCaps .getPersonalities ();
96+ public String [] getPropertyNames (String capabilities ) {
97+ return ChromeCaps .PROPERTY_NAMES ;
6398 }
6499
65100 /**
66- * {@inheritDoc}
101+ * Get path to installed <b>Opera</b> browser.
102+ *
103+ * @return path to installed <b>Opera</b> browser; {@code null} if browser not found
67104 */
68- @ Override
69- public String [] getPropertyNames (String capabilities ) {
70- return OperaCaps .getPropertyNames (capabilities );
105+ public static String findOperaBinary () {
106+ switch (OS_TYPE ) {
107+ case MACINTOSH :
108+ return findOnMac ();
109+ case UNIX :
110+ return findOnLinux ();
111+ case WINDOWS :
112+ return findOnWindows ();
113+ default :
114+ return null ;
115+ }
71116 }
72117
118+ private static String findOnWindows () {
119+ String [] possiblePaths = { "C:\\ Program Files\\ Opera\\ opera.exe" ,
120+ System .getenv ("LOCALAPPDATA" ) + "\\ Programs\\ Opera\\ opera.exe" };
121+
122+ return firstExistingPath (possiblePaths );
123+ }
124+
125+ private static String findOnMac () {
126+ String [] possiblePaths = { "/Applications/Opera.app/Contents/MacOS/Opera" ,
127+ System .getProperty ("user.home" ) + "/Applications/Opera.app/Contents/MacOS/Opera" };
128+
129+ return firstExistingPath (possiblePaths );
130+ }
131+
132+ private static String findOnLinux () {
133+ String [] possiblePaths = { "/usr/bin/opera" , "/usr/local/bin/opera" , "/opt/opera/opera" };
134+
135+ return firstExistingPath (possiblePaths );
136+ }
137+
138+ private static String firstExistingPath (String [] paths ) {
139+ for (String path : paths ) {
140+ if (path != null && Files .exists (Paths .get (path ))) {
141+ return path ;
142+ }
143+ }
144+ return null ;
145+ }
146+
147+ /**
148+ * Detect version of installed <b>Opera</b> browser.
149+ *
150+ * @return version of installed <b>Opera</b> browser; {@code null} if detection fails
151+ */
152+ public static String detectOperaVersion () {
153+ try {
154+ switch (OS_TYPE ) {
155+ case MACINTOSH :
156+ return detectMac ();
157+ case UNIX :
158+ return detectLinux ();
159+ case WINDOWS :
160+ return detectWindows ();
161+ default :
162+ return null ;
163+ }
164+ } catch (Exception e ) {
165+ e .printStackTrace ();
166+ return null ;
167+ }
168+ }
169+
170+ private static String detectWindows () throws Exception {
171+ // Use PowerShell to get ProductVersion from file metadata
172+ String [] cmd = { "powershell.exe" , "-Command" , "\" (Get-Item '" + BINARY_PATH + "').VersionInfo.ProductVersion\" " };
173+ return runCommand (cmd ).trim ();
174+ }
175+
176+ private static String detectMac () throws Exception {
177+ String [] cmd = { "/usr/libexec/PlistBuddy" , "-c" , "Print :CFBundleShortVersionString" , BINARY_PATH + "/Contents/Info.plist" };
178+ return runCommand (cmd ).trim ();
179+ }
180+
181+ private static String detectLinux () throws Exception {
182+ String [] cmd = { "/bin/sh" , "-c" , "cat " + BINARY_PATH + "/resources/version" };
183+ return runCommand (cmd ).trim ();
184+ }
185+
186+ private static String runCommand (String [] command ) throws Exception {
187+ Process process = new ProcessBuilder (command ).redirectErrorStream (true ).start ();
188+ try (BufferedReader reader = new BufferedReader (new InputStreamReader (process .getInputStream ()))) {
189+ String line = reader .readLine ();
190+ return (line != null ) ? line : null ;
191+ }
192+ }
193+
194+ /**
195+ * Find 'chromedriver' binary that works with the active installation of Opera.
196+ *
197+ * @return path to 'chromedriver' binary; {@code null} if driver not found
198+ */
199+ public static String findDriverBinary () {
200+ String operaVersion = Objects .requireNonNull (detectOperaVersion (),
201+ "Failed detecting Opera browser version" );
202+ String operaMajor = operaVersion .split ("\\ ." )[0 ];
203+ String chromiumMajor = Objects .requireNonNull (OPERA_TO_CHROMIUM .get (operaMajor ),
204+ "No Chromium version mapping found for Opera major version: " + operaMajor );
205+
206+ try {
207+ SeleniumManager manager = SeleniumManager .getInstance ();
208+ Result result = manager .getBinaryPaths (List .of (
209+ "--driver" , "chromedriver" ,
210+ "--driver-version" , chromiumMajor ));
211+ return result .getDriverPath ();
212+ } catch (IllegalStateException e ) {
213+ throw new DriverExecutableNotFoundException (ChromeCaps .DRIVER_PATH );
214+ }
215+ }
73216}
0 commit comments