microsoft / WinAppDriver

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

Implement wait for the app to launch the main window, same as winium #330

Open anunayaabb opened 6 years ago

anunayaabb commented 6 years ago

The APP which I am testing takes at least 7 to 8 sec to launch the Main Window, because of which the test fails , I can switch to the opened window but that will only be possible if the app supports multiple instances, if the app supports only one instance, the app will crash (that is what happens in my case), and that seems like a real bottle neck in automation for those apps.

timotiusmargo commented 6 years ago

Hi @anunayaabb,

Thanks for the change proposal. To be clear, this proposal is to enable specifying a delay in the startup as @CryilJ suggested in issue #244, isn't it? In such scenario Windows Application Driver would wait for the specified duration after launching the application before attempting to locate the main application window.

This change proposal makes total sense and will very likely benefit many applications that require more time to start. Thus, we have marked this as an enhancement. For the time being, can you try attaching to already opened application window mechanism?

In this workaround, you should not need to re-launch the app after the failed session creation (that actually launched the application but failed to locate the window). Your session creation will then look like:

  1. Create a new session with the app identifier (App will launch but session is not created since it can't find the application window in time)
  2. Create a new Desktop Session
  3. Wait until the application main window is launched
  4. Use the newly created DesktopSession to locate the main application window element
  5. Use the main application window element NativeWindowHandle attribute to start a new session as shown in the example below. https://github.com/Microsoft/WinAppDriver/tree/v1.0#attaching-to-an-existing-app-window
anunayaabb commented 6 years ago

@timotiusmargo Thanks,I tried it already it works, but that does not solve my issue and my UWP app does not support multiple instances. So if I switch to an existing window, the app crashes.

anunayaabb commented 6 years ago

@timotiusmargo Below is the code snippet which I have implemented for my testing.Please do let me know if I am doing anything wrong.

try { session = new WindowsDriver(new Uri(WindowsApplicationDriverUrl), appCapabilities); Assert.IsNotNull(session); }

            catch (Exception Ex)
            {
                if (Ex.Message.Contains("Failed to locate opened application window with appId: XXXXX_xygkxtyvxheec!App"))
                {
                    Thread.Sleep(10000);
                    DesiredCapabilities desktopCapabilities = new DesiredCapabilities();
                    desktopCapabilities.SetCapability("app", "Root");
                    desktopSession = new WindowsDriver<WindowsElement>(new Uri(WindowsApplicationDriverUrl), desktopCapabilities);
                    Assert.IsNotNull(desktopSession);

                    WindowsElement XXXShellWindow = desktopSession.FindElementByName("XXX");
                    string XXXShellTopLevelWindowHandle = (int.Parse(XXXShellWindow.GetAttribute("NativeWindowHandle"))).ToString("x");

                    appCapabilities.SetCapability("appToplevelWindow", XXXShellTopLevelWindowHandle);
                    session = new WindowsDriver<WindowsElement>(new Uri(WindowsApplicationDriverUrl), appCapabilities, TimeSpan.FromSeconds(20));
                    Assert.IsNotNull(session);
                }
                else
                {
                    Assert.Fail("Unexpected error occured: {0}", Ex.Message);
                }
anunayaabb commented 6 years ago

Can you please let me know any tentative date for implementation of this enhancement. I will be of great help even if you can share and exe with only this fix(or a daily build in case if it is there )

timotiusmargo commented 6 years ago

Hi @anunayaabb,

I don't have any tentative date at the moment and we can get back to you when we do. For the time being, the given workaround should work fine.

The code snippet you pasted above looks fine to me. At what point did your application crash? Can you include the HTTP request command logs that you find in WinAppDriver.exe console? There's no code above that re-launch the application or requiring it to support multi instance. So the fact that your application doesn't support multi instance should not be the cause of the crash.

anunayaabb commented 6 years ago

Hi @timotiusmargo :please find the http server logs: C:\Automation\WindowsApplicationDriver>WinAppDriver.exe 192.168.125.204 4725 Windows Application Driver listening for requests at: http://192.168.125.204:4725/ Press ENTER to exit.

========================================== POST /session HTTP/1.1 Accept: application/json, image/png Connection: Keep-Alive Content-Length: 125 Content-Type: application/json;charset=utf-8 Host: 192.168.125.204:4725

HTTP/1.1 500 Internal Error Content-Length: 177 Content-Type: application/json

{"status":13,"value":{"error":"unknown error","message":"Failed to locate opened application window with appId: XXX_xygkxtyvxheec!App, and processId: 2900"}}

========================================== POST /session HTTP/1.1 Accept: application/json, image/png Content-Length: 63 Content-Type: application/json;charset=utf-8 Host: 192.168.125.204:4725

HTTP/1.1 200 OK Content-Length: 111 Content-Type: application/json

{"sessionId":"33CC4C55-6245-4B4D-B8F0-8C356852C64A","status":0,"value":{"app":"Root","platformName":"Windows"}}

========================================== POST /session/33CC4C55-6245-4B4D-B8F0-8C356852C64A/element HTTP/1.1 Accept: application/json, image/png Content-Length: 50 Content-Type: application/json;charset=utf-8 Host: 192.168.125.204:4725

HTTP/1.1 200 OK Content-Length: 95 Content-Type: application/json

{"sessionId":"33CC4C55-6245-4B4D-B8F0-8C356852C64A","status":0,"value":{"ELEMENT":"42.393694"}}

========================================== GET /session/33CC4C55-6245-4B4D-B8F0-8C356852C64A/element/42.393694/attribute/NativeWindowHandle HTTP/1.1 Accept: application/json, image/png Host: 192.168.125.204:4725

HTTP/1.1 200 OK Content-Length: 80 Content-Type: application/json

{"sessionId":"33CC4C55-6245-4B4D-B8F0-8C356852C64A","status":0,"value":"393694"}

========================================== POST /session HTTP/1.1 Accept: application/json, image/png Content-Length: 153 Content-Type: application/json;charset=utf-8 Host: 192.168.125.204:4725

HTTP/1.1 500 Internal Error Content-Length: 177 Content-Type: application/json

{"status":13,"value":{"error":"unknown error","message":"Failed to locate opened application window with appId: XXX_xygkxtyvxheec!App, and processId: 2900"}}

anunayaabb commented 6 years ago

Any update on the implementation you can share.

timotiusmargo commented 6 years ago

Hi @anunayaabb,

The last session creation command below taken from the log above is where the problem lies:

==========================================
POST /session HTTP/1.1
Accept: application/json, image/png
Content-Length: 153
Content-Type: application/json;charset=utf-8
Host: 192.168.125.204:4725
HTTP/1.1 500 Internal Error
Content-Length: 177
Content-Type: application/json
{"status":13,"value":{"error":"unknown error","message":"Failed to locate opened application window with appId: XXX_xygkxtyvxheec!App, and processId: 2900"}}

This command is still trying to create a session by launching the app with the given XXX_xygkxtyvxheec!App appId. At this point you should be creating the session using the given appTopLevelWindow capability instead. This could be the reason why you see the application is being launched twice. Can you make sure that the new session is created from the appTopLevelWindow by using a new DesiredCapabilities instance as shown below?

// Create session by attaching to Cortana top level window
DesiredCapabilities appCapabilities = new DesiredCapabilities();
appCapabilities.SetCapability("appTopLevelWindow", CortanaTopLevelWindowHandle);
CortanaSession = new WindowsDriver<WindowsElement>(new Uri(WindowsApplicationDriverUrl), appCapabilities);
anunayaabb commented 6 years ago

@timotiusmargo Thanks, I tried that and I am able to switch to the opened window, but after switching , my app crashes and that is an expected behaviour of my app.

Please dole me know by when will you be available with this implementation.

anunayaabb commented 6 years ago

@timotiusmargo Any update on this issue.

hassanuz commented 6 years ago

Thanks @anunayaabb. We're looking into this.

anunayaabb commented 6 years ago

Do we have any ETA for this issue

Maohan1989 commented 5 years ago

I am trying to inspect the Element in a desktop application. I am unable to inspect some of the elements. The desktop application is developed using VB. when i asked our developers they told some of the pages are jsp kind of pages which i am unable to inspect. I tried with inpect.exe and other tools also. Can anyone help me on this?

Arekssio commented 5 years ago

Any update on this issue? We are trying to test an app that takes like 6-7 seconds to open.

cbigurra commented 5 years ago

Hi @timotiusmargo, do you have any news about this issue?

kicks321 commented 5 years ago

Option 1: Your best bet, is that you are not waiting an implicit amount of seconds:

`DesiredCapabilities tempAppLauncherCapabilities = new DesiredCapabilities();
tempAppLauncherCapabilities.SetCapability("app", "AppName");
tempAppLauncherCapabilities.SetCapability("deviceName", "WindowsPC");
session = new WindowsDriver<WindowsElement>(new Uri(WindowsApplicationDriverUrl), tempAppLauncherCapabilities);

// This is what you want to do, wait FROM SECONDS, not minutes or anything else, I think there is a bug in the conversion of minutes to seconds, because waiting in minutes never works for me
**session.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(600);`**

Option 2: just to explicitly throw a Thread.Sleep() method after you instantiate your session before you start searching for the elements. Just have it wait an arbitrary amount of seconds like 10 seconds in case if it takes longer than 6-7 seconds. For this, you are telling WinAppDriver "Hey, stop looking x amount of seconds," then WinAppDriver says, hey okay 👍!

`DesiredCapabilities tempAppLauncherCapabilities = new DesiredCapabilities();
tempAppLauncherCapabilities.SetCapability("app", "AppName");
tempAppLauncherCapabilities.SetCapability("deviceName", "WindowsPC");
session = new WindowsDriver<WindowsElement>(new Uri(WindowsApplicationDriverUrl), tempAppLauncherCapabilities);
session.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(60);

// Put it right here!  After you create your session, because at this moment, your application is launching, so telling WinAppDriver to wait is what is going on here
**System.Threading.Thread.Sleep(10000);**

Option 3: If you create a timer method that waits for the main screen to be in view, you're going to have a verbose method waiting like so:

[SetUp]
public static void SetUp()
{
   // Instantiate the session here
   // Then call the WaitForMainScreen() method which I have return the main screen element when searching for it and waiting for it to appear
   Assert.IsNotNull(WaitForMainScreen());
}
// Wrapper for waiting for the main based on timeouts
private static readonly Stopwatch timer = new Stopwatch();
private static int waitTime = 60000;

public static WindowsElement WaitForMainScreen()
{
   Stopwatch timer = new Stopwatch();
   int waitTime = 60000;
   bool isElementVisible = false;
   WindowsElement element = null;

   timer.Start();

   while (timer.Elapsed <= TimeSpan.FromMilliseconds(waitTime) && isElementVisible == false)
   {
       try
      {
          element = session.FindElement(identifier);
         if (element == session.FindElement(identifier))
         {
            isElementVisible = true;
         }
      }
      catch
      {
         continue;
      }
   }
   timer.Stop();
   Assert.IsFalse(timer.Elapsed > TimeSpan.FromMilliseconds(waitTime), "Timeout Elapsed, element with accessibility of | " + identifier + " | not found.");
   return element;
}