WinAppDriver icon indicating copy to clipboard operation
WinAppDriver copied to clipboard

Connect to already running application.

Open ewancombe opened this issue 6 years ago • 18 comments

You can connect to an application that's already running. There's an example of how to connect to Cortana (which has been launched) here.

My question is, how do I connect an app that's not a microsoft application? I have tried the example in the link but I can't get it to work for my application. In my application if I follow that example, it launches my application twice which is not what I want. I assume it's not possible to run 2 instances of Cortana so the only reason that example works is that it launches Cortana but that fails so it uses the existing running instance.

I am therefore trying to get the handle another way, by using GetProcess to get my process and get the handle that way (because it's already running). Does anyone have any idea what I need to do?

Anyway, here's my code and it always fails on the line as commented...

[TestMethod()] public void Common_CreateSession_ForAlreadyRunningmyApp() { string WindowsApplicationDriverUrl = "http://127.0.0.1:4723";

    IntPtr myAppTopLevelWindowHandle = new IntPtr();
    foreach (Process clsProcess in Process.GetProcesses())
    {
        if (clsProcess.ProcessName.Contains("MyApp.Client.Shell"))
        {
            myAppTopLevelWindowHandle = clsProcess.Handle;
        }
    }

    DesiredCapabilities appCapabilities = new DesiredCapabilities();
    appCapabilities.SetCapability("appTopLevelWindow", myAppTopLevelWindowHandle);

    //Create session for app that's already running (THIS LINE FAILS, ERROR: : 'Could not find any recognizable digits.')               
    session = new WindowsDriver<RemoteWebElement>(new Uri(WindowsApplicationDriverUrl), appCapabilities);
    session.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(10);

}

ewancombe avatar Dec 03 '18 11:12 ewancombe

My solution to this problem was to first create a desktop session. In that desktop session I searched for all open Windows with the class name "Window". After that I checked if one of those Windows has my Application Name and if yes, I took the Window Handle from it with the GetAttribute Method. Once I had the correct window handle I started a new session with this window handle. You can see my code is below. I hope this helps.

DesiredCapabilities desktopCapabilities = new DesiredCapabilities();
	desktopCapabilities.SetCapability("platformName", "Windows");
	desktopCapabilities.SetCapability("app", "Root");
	desktopCapabilities.SetCapability("deviceName", "WindowsPC");
Session = new WindowsDriver<WindowsElement>(new Uri("http://127.0.0.1:4723"), desktopCapabilities);

WindowsElement applicationWindow = null;
var openWindows = Session.FindElementsByClassName("Window");
foreach (var window in openWindows)
{					
	if (window.GetAttribute("Name").StartsWith("yourApplicationName"))
	{
		applicationWindow = window;
		break;
	}
}

// Attaching to existing Application Window
var topLevelWindowHandle = applicationWindow.GetAttribute("NativeWindowHandle");
topLevelWindowHandle = int.Parse(topLevelWindowHandle).ToString("X");

DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.SetCapability("deviceName", "WindowsPC");
capabilities.SetCapability("appTopLevelWindow", topLevelWindowHandle);
Session = new WindowsDriver<WindowsElement>(new Uri("http://127.0.0.1:4723"), capabilities);

moonkey124 avatar Dec 04 '18 08:12 moonkey124

Thank you #moonkey124, that's absolutely amazing! I have just tweaked your answer to give it a wpf app flavour by swapping "WindowsDriver<WindowsElement>" for "WindowsDriver<RemoteWebElement>". I also used my own application name (obviously) and for some reason "Wait.Seconds(1);" wouldn't build, I'm not sure what library that's from so I used Thread.Sleep(500);

All in all though your solution was exactly what I was looking for. You have helped me and Hopefully other people too!

My test development will now be more fluid as I can work on say step 15 of my orderedtest without having to run the first 14 steps and hit my debug point, I can simply leave the application in the state it would be in at the end of step 14 and then used this approach to connect to the existing session.

I will also have the option of joining ordered tests together if we want longer running tests without logging out, closing the app, launching and logging in again. I'll do with cautiously though as I know shorter, self contained tests are easier to problem solve and fix.

ewancombe avatar Dec 04 '18 10:12 ewancombe

@ewancombe
I couldn't make it work. Here is my code: DesiredCapabilities capabilities = new DesiredCapabilities(); capabilities.setCapability("platformName", "Windows"); capabilities.setCapability("deviceName", "WindowsPC"); capabilities.setCapability("app", "Root"); deskTopSession = new WindowsDriver<WindowsElement>(new URL(winAppDriverUrl), capabilities);
List<WindowsElement> openWindows = deskTopSession.findElementsByClassName("Window");

The openWindows.size is always 0.

Could you please share your tweaked code?

yanan58 avatar Jan 06 '19 20:01 yanan58

@yanan58 Replace "Window" in deskTopSession.findElementsByClassName("Window"); with your application class name.. You can get this using Inspect exe and checking out the root of your application.

avinashs15 avatar Apr 04 '19 10:04 avinashs15

I suspect that the problem in your original question was the line: myAppTopLevelWindowHandle = clsProcess.Handle. I believe that if you change it to myAppTopLevelWindowHandle = clsProcess.MainWindowHandle it should work. You also have to convert that number to hex string (using .ToString("x") when you add it to the capabilities.

arnonax avatar May 05 '19 14:05 arnonax

Final working method:

        /// <summary>
        /// Create session for already running app
        /// </summary>
        /// <param name="appText">Part text of app process name or app title to search in running processes</param>
        /// <returns>Session for already running app</returns>
        /// <example>CreateSessionForAlreadyRunningApp("calc");</example>
        private static WindowsDriver<WindowsElement> CreateSessionForAlreadyRunningApp(string appText)
        {
            IntPtr appTopLevelWindowHandle = new IntPtr();
            foreach (Process clsProcess in Process.GetProcesses())
            {
                if (clsProcess.ProcessName.IndexOf(appText, StringComparison.OrdinalIgnoreCase) >= 0 ||
                    clsProcess.MainWindowTitle.IndexOf(appText, StringComparison.OrdinalIgnoreCase) >= 0)
                {
                    appTopLevelWindowHandle = clsProcess.MainWindowHandle;
                    break;
                }
            }
            var appTopLevelWindowHandleHex = appTopLevelWindowHandle.ToString("x"); //convert number to hex string

            DesiredCapabilities appCapabilities = new DesiredCapabilities();
            appCapabilities.SetCapability("appTopLevelWindow", appTopLevelWindowHandleHex);
            var appSession = new WindowsDriver<WindowsElement>(new Uri(WindowsApplicationDriverUrl), appCapabilities);
            return appSession;
        }

Call as:

  • by process name CreateSessionForAlreadyRunningApp("WINWORD.exe")
  • by document title CreateSessionForAlreadyRunningApp("title")

Shakevg avatar Dec 05 '19 08:12 Shakevg

I'm using python, any idea how to do do this there?, if I can get a list of application ids I can connect but how do we get this?, a simple comand line app that can list this.

fenchu avatar Dec 20 '19 12:12 fenchu

I think something like that: https://superuser.com/questions/914782/how-do-you-list-all-processes-on-the-command-line-in-windows https://thispointer.com/python-get-list-of-all-running-processes-and-sort-by-highest-memory-usage/

Shakevg avatar Dec 20 '19 13:12 Shakevg

My solution to this problem was to first create a desktop session. In that desktop session I searched for all open Windows with the class name "Window". After that I checked if one of those Windows has my Application Name and if yes, I took the Window Handle from it with the GetAttribute Method. Once I had the correct window handle I started a new session with this window handle. You can see my code is below. I hope this helps.

DesiredCapabilities desktopCapabilities = new DesiredCapabilities();
	desktopCapabilities.SetCapability("platformName", "Windows");
	desktopCapabilities.SetCapability("app", "Root");
	desktopCapabilities.SetCapability("deviceName", "WindowsPC");
Session = new WindowsDriver<WindowsElement>(new Uri("http://127.0.0.1:4723"), desktopCapabilities);

WindowsElement applicationWindow = null;
var openWindows = Session.FindElementsByClassName("Window");
foreach (var window in openWindows)
{					
	if (window.GetAttribute("Name").StartsWith("yourApplicationName"))
	{
		applicationWindow = window;
		break;
	}
}

// Attaching to existing Application Window
var topLevelWindowHandle = applicationWindow.GetAttribute("NativeWindowHandle");
topLevelWindowHandle = int.Parse(topLevelWindowHandle).ToString("X");

DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.SetCapability("deviceName", "WindowsPC");
capabilities.SetCapability("appTopLevelWindow", topLevelWindowHandle);
Session = new WindowsDriver<WindowsElement>(new Uri("http://127.0.0.1:4723"), capabilities);

var openWindows = Session.FindElementsByClassName("my app class name");

This take a lot of time.Do you guys face this problem?

sachinDV avatar Apr 27 '20 13:04 sachinDV

@yanan58 Replace "Window" in deskTopSession.findElementsByClassName("Window"); with your application class name.. You can get this using Inspect exe and checking out the root of your application.

var openWindows = Session.FindElementsByClassName("my app class name");

This take a lot of time.Do you guys face this problem?

sachinDV avatar Apr 27 '20 13:04 sachinDV

Hi @Shakevg , thanks for your comment, it works attaching the existing window.

I have one more question to leave this window there, after all the testing stuff completed and session closed. If i call session.quit(), the window will be closed. If I did not quit, it seems the session will be timeout and window also closed as well.

Is there any way out to leave the window just open while safely closing the session? Thanks a lot!

bswhb avatar Sep 18 '20 10:09 bswhb

@bswhb You can do not call session.quit(), the app should continue to work (it works for my app). But I don't know safely closing session method, maybe: session=null;

Shakevg avatar Sep 26 '20 19:09 Shakevg

Hi. I cant connect to existing app with the capaility. I tried 0x671A34 and 671A34 as handle-hex.

The WAD log:

POST /wd/hub/session HTTP/1.1 Accept-Encoding: gzip Connection: Keep-Alive Content-Length: 746 Content-Type: application/json; charset=utf-8 Host: 127.0.0.1:4723 User-Agent: selenium/3.141.59 (java windows)

{ "desiredCapabilities": { "ms:waitForAppLaunch": "5", "appWorkingDir": "C:\u002fUsers\u002fBesitzer\u002feclipse-workspace\u002fjavafx-client\u002fout\u002f", "ms:experimental-webdriver": true, "platformName": "WINDOWS", "appTopLevelWindow": "0x671A34", "deviceName": "WindowsPC", "platform": "WINDOWS" }, "capabilities": { "firstMatch": [ { "appTopLevelWindow": "0x671A34", "appWorkingDir": "C:\u002fUsers\u002fBesitzer\u002feclipse-workspace\u002fjavafx-client\u002fout\u002f", "appium:deviceName": "WindowsPC", "ms:experimental-webdriver": true, "ms:waitForAppLaunch": "5", "platform": "WINDOWS", "platformName": "windows" } ] } } HTTP/1.1 404 Not Found

And here the inspect: How found: Selected from tree... Name: "My First JavaFX App" ControlType: UIA_WindowControlTypeId (0xC370) LocalizedControlType: "Fenster" BoundingRectangle: {l:321 t:260 r:1521 b:560} IsEnabled: true IsKeyboardFocusable: true HasKeyboardFocus: false ProcessId: 36688 RuntimeId: [2A.671A34] AutomationId: "JavaFX1" FrameworkId: "Win32" ClassName: "GlassWndClass-GlassWindowClass-2" NativeWindowHandle: 0x671A34 IsControlElement: true IsContentElement: true ProviderDescription: "[pid:43852,providerId:0x671A34 Main:Nested [pid:36688,providerId:0x671A34 Main(parent link):JavaFXProvider (unmanaged:glass.dll)]; Nonclient:Microsoft: Non-Client Proxy (unmanaged:uiautomationcore.dll); Hwnd(parent link):Microsoft: HWND Proxy (unmanaged:uiautomationcore.dll)]" IsPassword: false IsDialog: false

Is there anything i can do to debug that more?

HenningL avatar Nov 26 '20 19:11 HenningL

I'm using javascript, any idea how to do this there?

Shivin89 avatar Aug 26 '21 23:08 Shivin89

if (clsProcess.ProcessName.IndexOf(appText, StringComparison.OrdinalIgnoreCase) >= 0 ||
                    clsProcess.MainWindowTitle.IndexOf(appText, StringComparison.OrdinalIgnoreCase) >= 0)
                {
                    appTopLevelWindowHandle = clsProcess.MainWindowHandle;
                    break;
                }
               if (clsProcess.ProcessName.IndexOf(appText, StringComparison.OrdinalIgnoreCase) >= 0 ||
                    clsProcess.MainWindowTitle.IndexOf(appText, StringComparison.OrdinalIgnoreCase) >= 0)
                {
                    if (clsProcess.MainWindowHandle.ToInt32() == 0)
                        continue;

                    appTopLevelWindowHandle = clsProcess.MainWindowHandle;
                    break;
                }

The ability to minimize the application to the Taskbar is a standard behavior of the vast majority of Windows applications. we have an option to minimize (or close) the program to the system tray (officially known as the Notification Area) that is usually located next to the Taskbar in the bottom-right corner.

process.MainWindowHandle is 0x0000000000000000

https://github.com/microsoft/WinAppDriver/issues/1091#issuecomment-1001198171

huster-songtao avatar Dec 26 '21 18:12 huster-songtao

My solution to this problem was to first create a desktop session. In that desktop session I searched for all open Windows with the class name "Window". After that I checked if one of those Windows has my Application Name and if yes, I took the Window Handle from it with the GetAttribute Method. Once I had the correct window handle I started a new session with this window handle. You can see my code is below. I hope this helps.

DesiredCapabilities desktopCapabilities = new DesiredCapabilities();
	desktopCapabilities.SetCapability("platformName", "Windows");
	desktopCapabilities.SetCapability("app", "Root");
	desktopCapabilities.SetCapability("deviceName", "WindowsPC");
Session = new WindowsDriver<WindowsElement>(new Uri("http://127.0.0.1:4723"), desktopCapabilities);

WindowsElement applicationWindow = null;
var openWindows = Session.FindElementsByClassName("Window");
foreach (var window in openWindows)
{					
	if (window.GetAttribute("Name").StartsWith("yourApplicationName"))
	{
		applicationWindow = window;
		break;
	}
}

// Attaching to existing Application Window
var topLevelWindowHandle = applicationWindow.GetAttribute("NativeWindowHandle");
topLevelWindowHandle = int.Parse(topLevelWindowHandle).ToString("X");

DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.SetCapability("deviceName", "WindowsPC");
capabilities.SetCapability("appTopLevelWindow", topLevelWindowHandle);
Session = new WindowsDriver<WindowsElement>(new Uri("http://127.0.0.1:4723"), capabilities);

var openWindows = Session.FindElementsByClassName("my app class name");

This take a lot of time.Do you guys face this problem?

It is inefficient above. Implementation of High Performance Based on Windows API. see https://github.com/microsoft/WinAppDriver/issues/1091#issuecomment-1001198171

huster-songtao avatar Dec 26 '21 18:12 huster-songtao

I'm using javascript, any idea how to do this there?

Hey, did you have any luck figuring this out? Using React-Native/JS, I'm trying to figure this out as well

samuelfreiberg avatar Sep 13 '22 19:09 samuelfreiberg

I'm using javascript, any idea how to do this there?↳

Hey, did you have any luck figuring this out? Using React-Native/JS, I'm trying to figure this out as well↳

import { remote } from 'webdriverio';

async function main() {
  const caps = {
    'platformName': 'windows',
    'appium:automationName': 'windows',
    'appium:app': 'Root',
    'appium:newCommandTimeout': 60
  };
  const driver = await remote({
    protocol: 'http',
    hostname: '127.0.0.1',
    port: 4723,
    path: '/',
    capabilities: caps
  });
  const window = await driver.findElement('name', 'your window name');
  const handle = await driver.$(window).getAttribute('NativeWindowHandle');

  await driver.deleteSession();

  const actualCaps = {
    'platformName': 'windows',
    'appium:automationName': 'windows',
    'appium:appTopLevelWindow': (+handle).toString(16),
    'appium:newCommandTimeout': 60
  };
  const actualDriver = await remote({
    protocol: 'http',
    hostname: '127.0.0.1',
    port: 4723,
    path: '/',
    capabilities: actualCaps
  });
}

main().catch(console.log);

HQidea avatar Jul 01 '24 08:07 HQidea