pywin32 / pywinauto 在最小化时无法在远程桌面中正常工作

pywin32 / pywinauto not working properly in remote desktop when it is minimized

我有一个 Jenkins 管道,它在使用 pywin 操作应用程序以进行功能测试的远程服务器中执行程序。

我的应用程序在打开远程桌面时运行良好,但是当我关闭远程桌面并从 Jenkins 运行 时,应用程序丢失了。

我所做的是打开应用程序并发送回车键。

这是我的应用程序:

os.startfile("C:\Program Files (x86)\SAP\FrontEnd\SAPgui\saplogon.exe")

time.sleep(5)
handle = win32gui.FindWindow(0, "SAP Logon 740")  

keyboard = Controller()
keyboard.press(Key.enter)

所以我尝试给app加焦点强制对焦没有成功:

os.startfile("C:\Program Files (x86)\SAP\FrontEnd\SAPgui\saplogon.exe")

time.sleep(5)
handle = win32gui.FindWindow(0, "SAP Logon 740")  

win32gui.ShowWindow(handle, 5)           
win32gui.SetForegroundWindow(handle)

keyboard = Controller()
keyboard.press(Key.enter)

我将按键更改为此,结果相同:

shell = win32com.client.Dispatch("WScript.Shell")
shell.SendKeys('{ENTER}')

我尝试更改为 pywinauto,试图点击按钮而不是发送输入,但我发现了更多问题,因为 pywinauto 无法识别我的应用程序标题:

app = Application().start("C:\Program Files (x86)\SAP\FrontEnd\SAPgui\saplogon.exe")

app["SAP Logon 740"] # this doesn't work
app.top_window_()    # this doesn't work either

handle = win32gui.FindWindow(0, "SAP Logon 740")  
sapApp = app.window_(handle = handle)            #Finally this works but...

sapApp["Log &On"].click()                        # This doesn't work
sapApp.log_on.Click()                            # This doesn't work

我得到这个异常:

ctypes.ArgumentError: argument 2: <class 'TypeError'>: expected LP_c_ulong instance instead of pointer to c_long

我知道那是因为我试过:

print(sapApp.descendants(control_type="MenuBar"))

得到这个结果:

[<win32_controls.ButtonWrapper - 'Log &On', Button, 14221798>, ...]

所以我知道这是按钮的名称,但无法点击它。

我也试过将焦点设置到应用程序,结果相同:

sapApp.SetFocus()

远程桌面打开后一切正常,但它关闭了我的应用程序无法进入

所以以前有人遇到过这个问题吗?我 运行 想不出我还能尝试什么?

谢谢

编辑:

这是 ctypes 错误的完整跟踪:

File "e:\Jenkins\workspace\my-project\scripts\test_pywin.py", line 23, in <module> sapApp.log_on.Click()
File "E:\Python_V365\lib\site-packages\pywinauto\controls\hwndwrapper.py", line 725, in click self.verify_actionable()
File "E:\Python_V365\lib\site-packages\pywinauto\base_wrapper.py", line 591, in verify_actionable self.wait_for_idle()
File "E:\Python_V365\lib\site-packages\pywinauto\controls\hwndwrapper.py", line 710, in wait_for_idle win32functions.WaitGuiThreadIdle(self)
File "E:\Python_V365\lib\site-packages\pywinauto\win32functions.py", line 283, in WaitGuiThreadIdle GetWindowThreadProcessId(handle, ctypes.byref(process_id))
ctypes.ArgumentError: argument 2: <class 'TypeError'>: expected LP_c_ulong instance instead of pointer to c_long

还尝试(后端="uia")启动应用程序,结果相同:

app = Application(backend="uia").start("C:\Program Files (x86)\SAP\FrontEnd\SAPgui\saplogon.exe")

启动程序后尝试连接时出现此错误:

app = Application().connect(title="SAP Logon 740", timeout=10)

File "e:\Jenkins\workspace\tacion_BehaveImplementation-637TPHZXXSFG4MVWWWJCBSJOWSAVPZMPOYFKFKNYKRT5XRBIZFBQ\scripts\test_pywin.py", line 12, in <module> app = Application().connect(title="SAP Logon 740", timeout=10)
File "E:\Python_V365\lib\site-packages\pywinauto\application.py", line 944, in connect self.process = findwindows.find_element(**kwargs).process_id
File "E:\Python_V365\lib\site-packages\pywinauto\findwindows.py", line 84, in find_element elements = find_elements(**kwargs)
TypeError: find_elements() got an unexpected keyword argument 'timeout'

这终于奏效了:

app = Application().connect(title="SAP Logon 740", backend="uia")
sapApp = app["SAP Logon 740"]

但发现问题实际上是我需要等待应用程序完全加载,所以这也有效:

app = Application().start("C:\Program Files (x86)\SAP\FrontEnd\SAPgui\saplogon.exe")
time.sleep(5)
app["SAP Logon 740"]

但是按钮还是一样的问题

UPD:下面列出的所有实践都已编译到 Remote Execution Guide 中,可能是最新的。

远程桌面的问题与工具无关。当 RDP 最小化或断开连接时,RDP 本身不会保留 GUI 上下文(当 OS 被锁定时会发生相同的效果)。但症状通常涉及 .click_input().type_keys() / keyboard.SendKeys() 方法,这些方法在没有 GUI 上下文的情况下无法工作。

最小化 RDP 问题的解决方法:

  1. 将 RDP 切换到 windowed 模式(非全屏),运行 那里的脚本并快速切换到本地机器。现在你可以正常工作了。这是手动 运行.

  2. 最简单的方法
  3. 在远程机器上安装 VNC 服务器软件(我使用的是 TightVNC),在本地机器上安装 VNC 客户端(也是 TightVNC)。如果您看到黑屏,您可能需要更新远程计算机上的视频卡驱动程序。如果至少使用过一次 RDP,您还必须重新启动远程主机。主要好处:您甚至可以断开与远程主机的连接,但 TightVNC 将始终保持 GUI 上下文。这更适合自动 运行s(Jenkins 代理必须在这个活动桌面中 运行,它根本不能 运行 作为服务)。另一个虚拟桌面环境(如 Citrix)可能适合此目的,但我没有这方面的个人经验。

  4. RDP(mstsc 命令)有一些参数可以从当前连接中解除虚拟远程桌面的绑定(现在不记得了)。用于自动远程 运行 的其他工具可能包括 psexec 或带有 psexec 插件的 Ansible。


如果找不到此应用程序(进程 PID)的主要 window,可能 saplogon.exe 会生成另一个目标 window 的进程。然后你必须做 app = Application().connect(title="SAP Logon 740", timeout=10) 来绑定正确的进程 ID。对于此类启动器来说,这是很常见的问题。


ctypes.ArgumentError比较有意思。请提供错误的完整回溯。我怀疑这可以在 pywinauto 端修复。这种错误的发生可能是因为其他 Python 库使用 ctypes 不正确的方式,但它可能是可以解决的。


如果使用默认 "win32" 后端无法找到登录按钮,您可以尝试 Application(backend="uia").connect(...)Getting Started Guide.

中解释了差异

如果我理解你的问题,这也是我的问题,那么可以在那里找到正确的答案:

https://support.smartbear.com/testcomplete/docs/testing-with/running/via-rdp/keeping-computer-unlocked.html

(这个link来自这个页面:Remote Execution Guide in pywinauto

以及要遵循的确切流程,如页面中所述:

To disconnect from Remote Desktop, run the following command on the remote computer (in the Remote Desktop window) as an Administrator:

%windir%\System32\tscon.exe RDP-Tcp#NNN /dest:console

where RDP-Tcp#NNN is the ID of your current Remote Desktop session, for example, RDP-Tcp#0. You can see it in the Windows Task Manager on the Users tab, in the Session column.

You will see the “Your remote desktop session has ended” message, and the Remote Desktop client will close. But all programs and tests on the remote computer will continue running normally.

我只是在测试它并且没有问题。