3030import org .openqa .selenium .SessionNotCreatedException ;
3131import org .openqa .selenium .UnsupportedCommandException ;
3232import org .openqa .selenium .WebDriverException ;
33+ import org .openqa .selenium .bidi .BiDi ;
34+ import org .openqa .selenium .bidi .HasBiDi ;
3335import org .openqa .selenium .remote .CapabilityType ;
3436import org .openqa .selenium .remote .DriverCommand ;
3537import org .openqa .selenium .remote .ErrorHandler ;
4244import org .openqa .selenium .remote .http .HttpClient ;
4345import org .openqa .selenium .remote .http .HttpMethod ;
4446
47+ import java .net .URI ;
48+ import java .net .URISyntaxException ;
4549import java .net .URL ;
4650import java .util .Arrays ;
4751import java .util .Collections ;
@@ -66,7 +70,8 @@ public class AppiumDriver extends RemoteWebDriver implements
6670 LogsEvents ,
6771 HasBrowserCheck ,
6872 CanRememberExtensionPresence ,
69- HasSettings {
73+ HasSettings ,
74+ HasBiDi {
7075
7176 private static final ErrorHandler ERROR_HANDLER = new ErrorHandler (new ErrorCodesMobile (), true );
7277 // frequently used command parameters
@@ -76,6 +81,8 @@ public class AppiumDriver extends RemoteWebDriver implements
7681 protected final RemoteLocationContext locationContext ;
7782 private final ExecuteMethod executeMethod ;
7883 private final Set <String > absentExtensionNames = new HashSet <>();
84+ protected URI biDiUri ;
85+ protected BiDi biDi ;
7986
8087 /**
8188 * Creates a new instance based on command {@code executor} and {@code capabilities}.
@@ -171,54 +178,6 @@ public AppiumDriver(URL remoteSessionAddress, String platformName, String automa
171178 setSessionId (sessionAddress .getId ());
172179 }
173180
174- /**
175- * Changes platform name if it is not set and returns merged capabilities.
176- *
177- * @param originalCapabilities the given {@link Capabilities}.
178- * @param defaultName a platformName value which has to be set up
179- * @return {@link Capabilities} with changed platform name value or the original capabilities
180- */
181- protected static Capabilities ensurePlatformName (
182- Capabilities originalCapabilities , String defaultName ) {
183- return originalCapabilities .getPlatformName () == null
184- ? originalCapabilities .merge (new ImmutableCapabilities (PLATFORM_NAME , defaultName ))
185- : originalCapabilities ;
186- }
187-
188- /**
189- * Changes automation name if it is not set and returns merged capabilities.
190- *
191- * @param originalCapabilities the given {@link Capabilities}.
192- * @param defaultName a platformName value which has to be set up
193- * @return {@link Capabilities} with changed mobile automation name value or the original capabilities
194- */
195- protected static Capabilities ensureAutomationName (
196- Capabilities originalCapabilities , String defaultName ) {
197- String currentAutomationName = CapabilityHelpers .getCapability (
198- originalCapabilities , AUTOMATION_NAME_OPTION , String .class );
199- if (isNullOrEmpty (currentAutomationName )) {
200- String capabilityName = originalCapabilities .getCapabilityNames ()
201- .contains (AUTOMATION_NAME_OPTION ) ? AUTOMATION_NAME_OPTION : APPIUM_PREFIX + AUTOMATION_NAME_OPTION ;
202- return originalCapabilities .merge (new ImmutableCapabilities (capabilityName , defaultName ));
203- }
204- return originalCapabilities ;
205- }
206-
207- /**
208- * Changes platform and automation names if they are not set
209- * and returns merged capabilities.
210- *
211- * @param originalCapabilities the given {@link Capabilities}.
212- * @param defaultPlatformName a platformName value which has to be set up
213- * @param defaultAutomationName The default automation name to set up for this class
214- * @return {@link Capabilities} with changed platform/automation name value or the original capabilities
215- */
216- protected static Capabilities ensurePlatformAndAutomationNames (
217- Capabilities originalCapabilities , String defaultPlatformName , String defaultAutomationName ) {
218- Capabilities capsWithPlatformFixed = ensurePlatformName (originalCapabilities , defaultPlatformName );
219- return ensureAutomationName (capsWithPlatformFixed , defaultAutomationName );
220- }
221-
222181 @ Override
223182 public ExecuteMethod getExecuteMethod () {
224183 return executeMethod ;
@@ -260,39 +219,6 @@ public void addCommand(HttpMethod httpMethod, String url, String methodName) {
260219 ((AppiumCommandExecutor ) getCommandExecutor ()).refreshAdditionalCommands ();
261220 }
262221
263- @ Override
264- protected void startSession (Capabilities capabilities ) {
265- var response = Optional .ofNullable (
266- execute (DriverCommand .NEW_SESSION (singleton (capabilities )))
267- ).orElseThrow (() -> new SessionNotCreatedException (
268- "The underlying command executor returned a null response."
269- ));
270-
271- var rawCapabilities = Optional .ofNullable (response .getValue ())
272- .map (value -> {
273- if (!(value instanceof Map )) {
274- throw new SessionNotCreatedException (String .format (
275- "The underlying command executor returned a response "
276- + "with a non well formed payload: %s" , response )
277- );
278- }
279- //noinspection unchecked
280- return (Map <String , Object >) value ;
281- })
282- .orElseThrow (() -> new SessionNotCreatedException (
283- "The underlying command executor returned a response without payload: " + response )
284- );
285-
286- // TODO: remove this workaround for Selenium API enforcing some legacy capability values in major version
287- rawCapabilities .remove ("platform" );
288- if (rawCapabilities .containsKey (CapabilityType .BROWSER_NAME )
289- && isNullOrEmpty ((String ) rawCapabilities .get (CapabilityType .BROWSER_NAME ))) {
290- rawCapabilities .remove (CapabilityType .BROWSER_NAME );
291- }
292- this .capabilities = new BaseOptions <>(rawCapabilities );
293- setSessionId (response .getSessionId ());
294- }
295-
296222 @ Override
297223 public Response execute (String driverCommand , Map <String , ?> parameters ) {
298224 return super .execute (driverCommand , parameters );
@@ -337,7 +263,118 @@ public AppiumDriver markExtensionAbsence(String extName) {
337263 return this ;
338264 }
339265
266+ @ Override
267+ public Optional <BiDi > maybeGetBiDi () {
268+ return Optional .ofNullable (this .biDi );
269+ }
270+
340271 protected HttpClient getHttpClient () {
341272 return ((HttpCommandExecutor ) getCommandExecutor ()).client ;
342273 }
274+
275+ @ Override
276+ protected void startSession (Capabilities capabilities ) {
277+ var response = Optional .ofNullable (
278+ execute (DriverCommand .NEW_SESSION (singleton (capabilities )))
279+ ).orElseThrow (() -> new SessionNotCreatedException (
280+ "The underlying command executor returned a null response."
281+ ));
282+
283+ var rawCapabilities = Optional .ofNullable (response .getValue ())
284+ .map (value -> {
285+ if (!(value instanceof Map )) {
286+ throw new SessionNotCreatedException (String .format (
287+ "The underlying command executor returned a response "
288+ + "with a non well formed payload: %s" , response )
289+ );
290+ }
291+ //noinspection unchecked
292+ return (Map <String , Object >) value ;
293+ })
294+ .orElseThrow (() -> new SessionNotCreatedException (
295+ "The underlying command executor returned a response without payload: " + response )
296+ );
297+
298+ // TODO: remove this workaround for Selenium API enforcing some legacy capability values in major version
299+ rawCapabilities .remove ("platform" );
300+ if (rawCapabilities .containsKey (CapabilityType .BROWSER_NAME )
301+ && isNullOrEmpty ((String ) rawCapabilities .get (CapabilityType .BROWSER_NAME ))) {
302+ rawCapabilities .remove (CapabilityType .BROWSER_NAME );
303+ }
304+ this .capabilities = new BaseOptions <>(rawCapabilities );
305+ this .initBiDi (capabilities );
306+ setSessionId (response .getSessionId ());
307+ }
308+
309+ /**
310+ * Changes platform name if it is not set and returns merged capabilities.
311+ *
312+ * @param originalCapabilities the given {@link Capabilities}.
313+ * @param defaultName a platformName value which has to be set up
314+ * @return {@link Capabilities} with changed platform name value or the original capabilities
315+ */
316+ protected static Capabilities ensurePlatformName (
317+ Capabilities originalCapabilities , String defaultName ) {
318+ return originalCapabilities .getPlatformName () == null
319+ ? originalCapabilities .merge (new ImmutableCapabilities (PLATFORM_NAME , defaultName ))
320+ : originalCapabilities ;
321+ }
322+
323+ /**
324+ * Changes automation name if it is not set and returns merged capabilities.
325+ *
326+ * @param originalCapabilities the given {@link Capabilities}.
327+ * @param defaultName a platformName value which has to be set up
328+ * @return {@link Capabilities} with changed mobile automation name value or the original capabilities
329+ */
330+ protected static Capabilities ensureAutomationName (
331+ Capabilities originalCapabilities , String defaultName ) {
332+ String currentAutomationName = CapabilityHelpers .getCapability (
333+ originalCapabilities , AUTOMATION_NAME_OPTION , String .class );
334+ if (isNullOrEmpty (currentAutomationName )) {
335+ String capabilityName = originalCapabilities .getCapabilityNames ()
336+ .contains (AUTOMATION_NAME_OPTION ) ? AUTOMATION_NAME_OPTION : APPIUM_PREFIX + AUTOMATION_NAME_OPTION ;
337+ return originalCapabilities .merge (new ImmutableCapabilities (capabilityName , defaultName ));
338+ }
339+ return originalCapabilities ;
340+ }
341+
342+ /**
343+ * Changes platform and automation names if they are not set
344+ * and returns merged capabilities.
345+ *
346+ * @param originalCapabilities the given {@link Capabilities}.
347+ * @param defaultPlatformName a platformName value which has to be set up
348+ * @param defaultAutomationName The default automation name to set up for this class
349+ * @return {@link Capabilities} with changed platform/automation name value or the original capabilities
350+ */
351+ protected static Capabilities ensurePlatformAndAutomationNames (
352+ Capabilities originalCapabilities , String defaultPlatformName , String defaultAutomationName ) {
353+ Capabilities capsWithPlatformFixed = ensurePlatformName (originalCapabilities , defaultPlatformName );
354+ return ensureAutomationName (capsWithPlatformFixed , defaultAutomationName );
355+ }
356+
357+ private void initBiDi (Capabilities responseCaps ) {
358+ var webSocketUrl = CapabilityHelpers .getCapability (responseCaps , "webSocketUrl" , String .class );
359+ if (webSocketUrl == null ) {
360+ return ;
361+ }
362+ try {
363+ this .biDiUri = new URI (webSocketUrl );
364+ } catch (URISyntaxException e ) {
365+ // no valid url -> no BiDi
366+ return ;
367+ }
368+ var executor = getCommandExecutor ();
369+ final HttpClient wsClient ;
370+ if (executor instanceof AppiumCommandExecutor ) {
371+ var wsConfig = ((AppiumCommandExecutor ) executor ).getAppiumClientConfig ().baseUri (biDiUri );
372+ wsClient = ((AppiumCommandExecutor ) executor ).getHttpClientFactory ().createClient (wsConfig );
373+ } else {
374+ var wsConfig = AppiumClientConfig .defaultConfig ().baseUri (biDiUri );
375+ wsClient = HttpClient .Factory .createDefault ().createClient (wsConfig );
376+ }
377+ var biDiConnection = new org .openqa .selenium .bidi .Connection (wsClient , biDiUri .toString ());
378+ this .biDi = new BiDi (biDiConnection );
379+ }
343380}
0 commit comments