microsoft / WinAppDriver

Windows Application Driver
MIT License
3.67k stars 1.4k forks source link

Connect to already running application. #534

Open ewancombe opened 5 years ago

ewancombe commented 5 years ago

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);

}

moonkey124 commented 5 years ago

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);
ewancombe commented 5 years ago

Thank you #moonkey124, that's absolutely amazing! I have just tweaked your answer to give it a wpf app flavour by swapping "WindowsDriver" for "WindowsDriver". 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.

yanan58 commented 5 years ago

@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?

avinashs15 commented 5 years ago

@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.

arnonax commented 5 years ago

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.

Shakevg commented 4 years ago

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:

fenchu commented 4 years ago

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.

Shakevg commented 4 years ago

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/

sachinDV commented 4 years ago

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 commented 4 years ago

@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?

bswhb commented 4 years ago

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!

Shakevg commented 4 years ago

@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;

HenningL commented 3 years ago

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?

Shivin89 commented 3 years ago

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

huster-songtao commented 2 years ago
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 commented 2 years ago

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

samuelfreiberg commented 2 years ago

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

HQidea commented 3 months ago

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);