From a06fe60eb2217ef18ec903dbaea56481d06becdb Mon Sep 17 00:00:00 2001 From: zhaoyilun Date: Sat, 11 Jul 2026 20:21:41 +0800 Subject: [PATCH] tools: add offline chat window probe --- ...-07-11-a-route-open-conversation-window.md | 120 ++++++ .../plans/2026-07-11-a-route-rpa-send.md | 20 + scripts/open-offline-chat-window-probe.ps1 | 371 ++++++++++++++++++ .../test-open-offline-chat-window-probe.ps1 | 62 +++ 4 files changed, 573 insertions(+) create mode 100644 docs/source-discovery/2026-07-11-a-route-open-conversation-window.md create mode 100644 scripts/open-offline-chat-window-probe.ps1 create mode 100644 scripts/test-open-offline-chat-window-probe.ps1 diff --git a/docs/source-discovery/2026-07-11-a-route-open-conversation-window.md b/docs/source-discovery/2026-07-11-a-route-open-conversation-window.md new file mode 100644 index 0000000..02f8ccd --- /dev/null +++ b/docs/source-discovery/2026-07-11-a-route-open-conversation-window.md @@ -0,0 +1,120 @@ +# A-route open_conversation 窗口验证说明 + +日期:2026-07-11 +分支:`codex/a-route-rpa-send` + +## 本轮目标 + +用户明确当前先不要求真实发送消息,只要求“打开那个会话窗口”,用来验证数字员工是否能: + +1. 识别会话窗口。 +2. 定位发送编辑框。 +3. 定位发送按钮/文件按钮。 +4. 执行一次可审计的点击。 + +本地环境没有 iSphere 服务端,无法登录,所以真实会话窗口不能通过正常业务路径打开。 + +## 已实现:离线会话窗口探针 + +新增脚本: + +```powershell +powershell -NoProfile -ExecutionPolicy Bypass -File E:\coding\codex\isphere-ai-bridge\scripts\open-offline-chat-window-probe.ps1 -ProbeClick +``` + +它会打开一个本地 WinForms 会话窗口探针,不连接服务端,不发送真实消息。关键 UIA 标识对齐真实会话窗口/既有 RPA 选择器: + +| 目标 | AutomationId / Name | +| --- | --- | +| 会话窗口 | `frmP2PChat` | +| 联系人搜索框 | `skinAlphaTxt` | +| 消息展示区 | `rtbRecvMessage` | +| 发送编辑框 | `rtbSendMessage` | +| 发送按钮 | `btnSend` | +| 文件按钮 | `btnSendFile` | +| 离线提示 | `offlineSendBlocker` | + +当传入 `-ProbeClick` 时,脚本会调用当前分支已有的 WinHelper UIA 动作,把文本写入 `rtbSendMessage` 并点击 `btnSend`。按钮点击只写本地 marker 文件,字段固定为: + +- `sent_real_message=false` +- `uploaded_real_file=false` + +因此这个动作只验证 UIA 识别、定位、写入、点击链路,不触发真实消息发送。 + +## 自动化测试 + +新增测试: + +```powershell +powershell -NoProfile -ExecutionPolicy Bypass -File E:\coding\codex\isphere-ai-bridge\scripts\test-open-offline-chat-window-probe.ps1 +``` + +测试断言: + +- 根窗口 `root_automation_id=frmP2PChat` +- 找到 `rtbSendMessage` +- 找到 `btnSend` +- 找到 `btnSendFile` +- `ProbeClick` 后写入 marker +- marker 文本与 UIA 写入文本一致 +- `sent_real_message=false` +- `uploaded_real_file=false` + +## 真实 `frmP2PChat` 破解进度 + +已继续尝试直接构造真实类型: + +```text +IMPP.Client.Business.ChatManager.SingleChat.frmP2PChat +``` + +构造函数签名: + +```text +frmP2PChat(com.vision.smack.Chat chat, string pluginInfo, string extendTabJson) +``` + +已补过的离线依赖包括: + +- `IMPPManager.Instance.MessageCenter` +- `IMPPManager.Instance.LogonUser` +- `IMPPManager.Instance.UserInfo` +- `SmarkManager.Connection` +- `SmarkManager.RosterManager` +- `SmarkManager.PresenceManager` +- `SmarkManager.P2PChatManager` +- `ConfigSystemManager.config` +- `P2PChat.OtherJid` + +仍未成功打开真实窗体。当前阻塞不再是脚本能力,而是构造函数内部把窗体初始化、登录态、连接态、名册状态、消息中心事件、配置状态混在一起。IL 证据显示构造函数中直接访问: + +- `IMPPManager.Instance.Connection.add_OnConnectError` +- `IMPPManager.Instance.Connection.add_OnReConnectOk` +- `P2PChat.OtherJid.getBareJid` +- `IMPPManager.Instance.UserInfo.get_Jid` +- `UCChatSendMessageBox.GetChatRichTextBox` +- `UCChatSendMessageBox.GetReceiptCheckBox` +- `IMPPManager.Instance.GetMessageCenter().add_OnTcpMessageArrived` +- `IMPPManager.Instance.RosterManager.UpdateStrangerStatus` +- `Config.BaseConfig.VisualPhoneEnable` + +这说明真实 `frmP2PChat` 不是独立窗口类;它要求完整登录运行态。没有服务端/登录态时继续硬构造,投入会越来越像“重建一个假客户端运行时”。 + +## 当前业务结论 + +从“验证 RPA 能否识别、定位、点击会话窗口”的业务目标看,本轮已经可验证: + +- 会话窗口可打开。 +- 核心 AutomationId 可识别。 +- 文本可写入。 +- 按钮可点击。 +- 点击结果可留本地审计 marker。 +- 全程不发真实消息、不上传真实文件。 + +从“真实 iSphere 会话窗口”的目标看,仍需要在线登录环境或继续补完整客户端运行态。 + +## 下一步建议 + +1. 当前分支先使用 `open-offline-chat-window-probe.ps1` 验证 RPA 识别/定位/点击效果。 +2. 在线环境拿到真实登录后的 `frmP2PChat` HWND 后,用同一套 WinHelper 选择器直接验证真实窗口。 +3. 如果必须离线打开真实窗体,再继续走“补完整 IMPPManager/SmarkManager/Config/BaseConfig/MessageCenter 运行态”的破解路线。 diff --git a/docs/superpowers/plans/2026-07-11-a-route-rpa-send.md b/docs/superpowers/plans/2026-07-11-a-route-rpa-send.md index 05d4825..38cbb2a 100644 --- a/docs/superpowers/plans/2026-07-11-a-route-rpa-send.md +++ b/docs/superpowers/plans/2026-07-11-a-route-rpa-send.md @@ -93,3 +93,23 @@ - [x] Run `powershell -NoProfile -ExecutionPolicy Bypass -File scripts\verify-win-helper.ps1`. - [x] Run `powershell -NoProfile -ExecutionPolicy Bypass -File scripts\verify-go-mcp.ps1`. - [x] Commit and push branch `codex/a-route-rpa-send`. + +## Task 6: Open conversation window probe for A-route RPA + +**Files:** +- Create: `scripts/open-offline-chat-window-probe.ps1` +- Create: `scripts/test-open-offline-chat-window-probe.ps1` +- Create: `docs/source-discovery/2026-07-11-a-route-open-conversation-window.md` + +**Purpose:** local environment cannot log in, but RPA still needs a stable +conversation-window surface to prove identify/locate/click behavior. This task +adds a no-network WinForms probe using the real selector names +`frmP2PChat`, `rtbRecvMessage`, `rtbSendMessage`, `btnSend`, and +`btnSendFile`. + +- [x] Write failing test proving the open-conversation probe script is required. +- [x] Implement the offline chat-window probe. +- [x] Verify UIA classification finds send editor, send button, and file button. +- [x] Verify `-ProbeClick` writes text and clicks `btnSend`. +- [x] Verify marker says `sent_real_message=false` and `uploaded_real_file=false`. +- [x] Document why direct offline construction of the real `frmP2PChat` is still blocked by login/runtime singletons. diff --git a/scripts/open-offline-chat-window-probe.ps1 b/scripts/open-offline-chat-window-probe.ps1 new file mode 100644 index 0000000..3fd67dd --- /dev/null +++ b/scripts/open-offline-chat-window-probe.ps1 @@ -0,0 +1,371 @@ +param( + [string]$HelperExe = "runs/win-helper/ISphereWinHelper.exe", + [int]$WaitSeconds = 10, + [int]$KeepOpenSeconds = 0, + [int]$X = 160, + [int]$Y = 120, + [int]$Width = 760, + [int]$Height = 560, + [ValidateSet("Visible", "Offscreen", "Minimized")] + [string]$WindowMode = "Visible", + [switch]$ProbeClick, + [switch]$NoScreenshot, + [switch]$NoActivate, + [switch]$TopMost +) + +$ErrorActionPreference = "Stop" +$repo = Resolve-Path -LiteralPath (Join-Path $PSScriptRoot "..") +$helperPath = Join-Path $repo $HelperExe +$runOut = Join-Path $repo "runs\offline-chat-window" +New-Item -ItemType Directory -Force -Path $runOut | Out-Null + +function Resolve-RepoPath([string]$PathValue) { + if ([System.IO.Path]::IsPathRooted($PathValue)) { + return $PathValue + } + return (Join-Path $repo $PathValue) +} + +function Invoke-HelperJson([string]$Op, [hashtable]$OpArgs, [string]$RequestId, [int]$TimeoutMs = 5000) { + $request = @{ + protocol = "isphere.helper.v1" + request_id = $RequestId + op = $Op + timeout_ms = $TimeoutMs + args = $OpArgs + } + $json = $request | ConvertTo-Json -Depth 16 -Compress + $output = $json | & $script:HelperPath --json + if ($LASTEXITCODE -ne 0) { + throw "helper exited with code $LASTEXITCODE for op $Op. Output: $output" + } + try { + return $output | ConvertFrom-Json + } + catch { + throw "helper output was not JSON for op $Op`: $output" + } +} + +function Add-NativeWindowApi { + Add-Type @" +using System; +using System.Runtime.InteropServices; +public static class ISphereOfflineChatProbeWindowApi { + [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); + [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr hWnd); + [DllImport("user32.dll")] public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); +} +"@ -ErrorAction SilentlyContinue +} + +function Set-ProbeWindowPlacement([string]$HwndText) { + $hwndValue = [Convert]::ToInt64(($HwndText -replace "^0x", ""), 16) + $hwnd = [IntPtr]::new($hwndValue) + $insertAfter = if ($TopMost) { [IntPtr]::new(-1) } else { [IntPtr]::Zero } + $flags = 0x0040 + if ($NoActivate) { + $flags = $flags -bor 0x0010 + } + + if ($WindowMode -eq "Minimized") { + [ISphereOfflineChatProbeWindowApi]::ShowWindow($hwnd, 6) | Out-Null + return + } + + $effectiveX = $X + $effectiveY = $Y + if ($WindowMode -eq "Offscreen") { + $effectiveX = -32000 + $effectiveY = -32000 + } + + [ISphereOfflineChatProbeWindowApi]::ShowWindow($hwnd, 9) | Out-Null + [ISphereOfflineChatProbeWindowApi]::SetWindowPos($hwnd, $insertAfter, $effectiveX, $effectiveY, $Width, $Height, [uint32]$flags) | Out-Null + if (-not $NoActivate) { + [ISphereOfflineChatProbeWindowApi]::SetForegroundWindow($hwnd) | Out-Null + } +} + +function Save-WindowScreenshot([string]$OutputPath, [int]$ScreenX, [int]$ScreenY, [int]$CaptureWidth, [int]$CaptureHeight) { + Add-Type -AssemblyName System.Drawing + $bitmap = New-Object System.Drawing.Bitmap $CaptureWidth, $CaptureHeight + $graphics = [System.Drawing.Graphics]::FromImage($bitmap) + try { + $graphics.CopyFromScreen(($ScreenX - 20), ($ScreenY - 20), 0, 0, $bitmap.Size) + $bitmap.Save($OutputPath, [System.Drawing.Imaging.ImageFormat]::Png) + } + finally { + $graphics.Dispose() + $bitmap.Dispose() + } +} + +function New-Sha256Hex([string]$Value) { + $sha = [System.Security.Cryptography.SHA256]::Create() + try { + $bytes = [System.Text.Encoding]::UTF8.GetBytes($Value) + return -join ($sha.ComputeHash($bytes) | ForEach-Object { $_.ToString("x2") }) + } + finally { + $sha.Dispose() + } +} + +$script:HelperPath = Resolve-RepoPath $HelperExe +if (-not (Test-Path -LiteralPath $script:HelperPath)) { + throw "WinHelper not found: $script:HelperPath. Run scripts\build-win-helper.ps1 first." +} + +$timestamp = (Get-Date).ToString("yyyyMMdd-HHmmss") +$windowTitle = "iSphere Offline Chat Window Probe " + ([guid]::NewGuid().ToString("N").Substring(0, 8)) +$launcherScript = Join-Path $runOut ("offline-chat-window-probe-$timestamp.ps1") +$markerPath = Join-Path $runOut ("offline-chat-window-probe-marker-$timestamp.json") +$statePath = Join-Path $runOut ("offline-chat-window-probe-state-$timestamp.json") +$dumpFile = Join-Path $runOut ("uia-dump-offline-chat-window-probe-$timestamp.json") +$screenshotFile = Join-Path $runOut ("offline-chat-window-probe-$timestamp.png") + +$escapedTitle = $windowTitle.Replace("'", "''") +$escapedMarker = $markerPath.Replace("'", "''") +$escapedState = $statePath.Replace("'", "''") + +$formScript = @" +Add-Type -AssemblyName System.Windows.Forms +Add-Type -AssemblyName System.Drawing +`$form = New-Object System.Windows.Forms.Form +`$form.Name = 'frmP2PChat' +`$form.Text = '$escapedTitle' +`$form.StartPosition = 'Manual' +`$form.Location = New-Object System.Drawing.Point(160, 120) +`$form.Size = New-Object System.Drawing.Size(760, 560) +`$form.KeyPreview = `$true + +`$layout = New-Object System.Windows.Forms.TableLayoutPanel +`$layout.Name = 'chatRootLayout' +`$layout.Dock = 'Fill' +`$layout.RowCount = 5 +`$layout.ColumnCount = 1 +`$layout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 38))) | Out-Null +`$layout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Percent, 55))) | Out-Null +`$layout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 42))) | Out-Null +`$layout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Percent, 45))) | Out-Null +`$layout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 48))) | Out-Null +`$form.Controls.Add(`$layout) + +`$search = New-Object System.Windows.Forms.TextBox +`$search.Name = 'skinAlphaTxt' +`$search.Text = 'search contact placeholder' +`$search.Dock = 'Fill' +`$layout.Controls.Add(`$search, 0, 0) + +`$recv = New-Object System.Windows.Forms.RichTextBox +`$recv.Name = 'rtbRecvMessage' +`$recv.ReadOnly = `$true +`$recv.Text = 'Offline chat window probe. This mimics frmP2PChat for UIA identify, locate, and click verification only.' +`$recv.Dock = 'Fill' +`$layout.Controls.Add(`$recv, 0, 1) + +`$toolbar = New-Object System.Windows.Forms.FlowLayoutPanel +`$toolbar.Name = 'chatToolbar' +`$toolbar.Dock = 'Fill' +`$toolbar.FlowDirection = 'LeftToRight' +`$layout.Controls.Add(`$toolbar, 0, 2) + +`$file = New-Object System.Windows.Forms.Button +`$file.Name = 'btnSendFile' +`$file.Text = 'Send File' +`$file.Width = 92 +`$file.Height = 30 +`$file.Add_Click({ + `$payload = [ordered]@{ + clicked = `$true + action = 'file_button_probe_only' + sent_real_message = `$false + uploaded_real_file = `$false + at = (Get-Date).ToString('o') + } | ConvertTo-Json -Compress + Set-Content -LiteralPath '$escapedMarker' -Value `$payload -Encoding UTF8 +}) +`$toolbar.Controls.Add(`$file) + +`$offline = New-Object System.Windows.Forms.Label +`$offline.Name = 'offlineSendBlocker' +`$offline.Text = 'Offline probe only: no server connection and no real message is sent.' +`$offline.AutoSize = `$true +`$offline.Padding = New-Object System.Windows.Forms.Padding(8) +`$toolbar.Controls.Add(`$offline) + +`$send = New-Object System.Windows.Forms.RichTextBox +`$send.Name = 'rtbSendMessage' +`$send.Text = '' +`$send.Dock = 'Fill' +`$layout.Controls.Add(`$send, 0, 3) + +`$buttonPanel = New-Object System.Windows.Forms.FlowLayoutPanel +`$buttonPanel.Name = 'sendButtonPanel' +`$buttonPanel.Dock = 'Fill' +`$buttonPanel.FlowDirection = 'RightToLeft' +`$layout.Controls.Add(`$buttonPanel, 0, 4) + +`$sendButton = New-Object System.Windows.Forms.Button +`$sendButton.Name = 'btnSend' +`$sendButton.Text = 'Send(S)' +`$sendButton.Width = 92 +`$sendButton.Height = 32 +`$sendButton.Add_Click({ + `$payload = [ordered]@{ + clicked = `$true + action = 'send_button_probe_only' + text = `$send.Text + text_length = `$send.Text.Length + sent_real_message = `$false + uploaded_real_file = `$false + at = (Get-Date).ToString('o') + } | ConvertTo-Json -Compress + Set-Content -LiteralPath '$escapedMarker' -Value `$payload -Encoding UTF8 +}) +`$buttonPanel.Controls.Add(`$sendButton) + +`$form.Add_Shown({ + `$state = [ordered]@{ + ok = `$true + window_kind = 'offline_chat_window_probe' + automation_id = `$form.Name + title = `$form.Text + process_id = [System.Diagnostics.Process]::GetCurrentProcess().Id + marker_path = '$escapedMarker' + at = (Get-Date).ToString('o') + } | ConvertTo-Json -Compress + Set-Content -LiteralPath '$escapedState' -Value `$state -Encoding UTF8 +}) + +[void]`$form.ShowDialog() +"@ + +Set-Content -LiteralPath $launcherScript -Value $formScript -Encoding UTF8 + +$process = Start-Process powershell -ArgumentList @("-NoProfile", "-ExecutionPolicy", "Bypass", "-File", $launcherScript) -PassThru +$candidate = $null +$scan = $null +$deadline = (Get-Date).AddSeconds([Math]::Max(1, $WaitSeconds)) +do { + Start-Sleep -Milliseconds 300 + $scan = Invoke-HelperJson -Op "scan_windows" -OpArgs @{ include_all_visible = $true } -RequestId "offline-chat-probe-scan" + if ($scan.ok -and $scan.data.windows) { + $candidate = $scan.data.windows | Where-Object { + $_.pid -eq $process.Id -and $_.title -eq $windowTitle + } | Select-Object -First 1 + } +} while (-not $candidate -and (Get-Date) -lt $deadline) + +if (-not $candidate) { + if ($process -and -not $process.HasExited) { + Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue + } + throw "offline chat probe window not found. Last scan: $($scan | ConvertTo-Json -Depth 12 -Compress)" +} + +Add-NativeWindowApi +Set-ProbeWindowPlacement -HwndText $candidate.hwnd +Start-Sleep -Milliseconds 500 + +$dump = Invoke-HelperJson -Op "dump_uia" -OpArgs @{ + hwnd = $candidate.hwnd + max_depth = 8 + max_children = 200 + include_text = $true +} -RequestId "offline-chat-probe-dump-uia" +$dump | ConvertTo-Json -Depth 40 | Set-Content -LiteralPath $dumpFile -Encoding UTF8 + +$classify = Invoke-HelperJson -Op "probe_send_uia_controls" -OpArgs @{ + hwnd = $candidate.hwnd + max_depth = 8 + max_children = 200 +} -RequestId "offline-chat-probe-classify-uia" + +$probeClickedUi = $false +$probeTypedText = $false +$probeMarkerFound = $false +$probeMarkerTextMatches = $false +$probeMarker = $null +$probeContent = "" +$probeAction = $null + +if ($ProbeClick) { + $probeContent = "Codex offline chat probe " + ([guid]::NewGuid().ToString("N").Substring(0, 8)) + $probeHash = New-Sha256Hex $probeContent + $probeAction = Invoke-HelperJson -Op "uia_send_message" -OpArgs @{ + hwnd = $candidate.hwnd + send_editor_automation_id = "rtbSendMessage" + send_button_automation_id = "btnSend" + target_ref = "offline-chat-window-probe" + content_text = $probeContent + content_sha256 = $probeHash + } -RequestId "offline-chat-probe-click" + $probeClickedUi = [bool]($probeAction.ok -and $probeAction.data.clicked_ui) + $probeTypedText = [bool]($probeAction.ok -and $probeAction.data.typed_text) + + for ($i = 0; $i -lt 30 -and -not (Test-Path -LiteralPath $markerPath); $i++) { + Start-Sleep -Milliseconds 100 + } + $probeMarkerFound = Test-Path -LiteralPath $markerPath + if ($probeMarkerFound) { + $probeMarker = Get-Content -LiteralPath $markerPath -Raw | ConvertFrom-Json + $probeMarkerTextMatches = [string]$probeMarker.text -eq $probeContent + } +} + +$screenshotPathForOutput = $null +if (-not $NoScreenshot -and $WindowMode -eq "Visible") { + Save-WindowScreenshot -OutputPath $screenshotFile -ScreenX $X -ScreenY $Y -CaptureWidth ($Width + 40) -CaptureHeight ($Height + 40) + $screenshotPathForOutput = $screenshotFile +} + +if ($KeepOpenSeconds -gt 0) { + Start-Sleep -Seconds $KeepOpenSeconds + if ($process -and -not $process.HasExited) { + Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue + } +} + +$rootAutomationId = "" +if ($dump -and $dump.ok -and $dump.data -and $dump.data.root) { + $rootAutomationId = [string]$dump.data.root.automation_id +} + +$flags = $null +if ($classify -and $classify.ok -and $classify.data -and $classify.data.flags) { + $flags = $classify.data.flags +} + +[ordered]@{ + ok = [bool]($dump.ok -and $classify.ok) + window_kind = "offline_chat_window_probe" + process_id = $process.Id + hwnd = $candidate.hwnd + title = $windowTitle + root_automation_id = $rootAutomationId + root_control_type = if ($dump.ok) { [string]$dump.data.root.control_type } else { "" } + send_editor_found = [bool]($flags -and $flags.has_send_editor) + send_button_found = [bool]($flags -and $flags.has_send_button) + file_button_found = [bool]($flags -and $flags.has_file_menu) + receive_document_found = [bool]($flags -and $flags.has_receive_document) + offline_blocker_visible = [bool]($flags -and $flags.offline_blocker_visible) + route_hint = if ($flags) { [string]$flags.route_hint } else { "" } + probe_click_requested = [bool]$ProbeClick + probe_clicked_ui = $probeClickedUi + probe_typed_text = $probeTypedText + probe_click_marker_found = $probeMarkerFound + probe_marker_text_matches = $probeMarkerTextMatches + probe_action_ok = if ($probeAction) { [bool]$probeAction.ok } else { $false } + probe_action_mode = if ($probeAction -and $probeAction.ok) { [string]$probeAction.data.action_mode } else { "" } + sent_real_message = $false + uploaded_real_file = $false + marker_path = $markerPath + state_path = $statePath + launcher_script = $launcherScript + uia_dump_file = $dumpFile + screenshot_file = $screenshotPathForOutput + alive_after_return = if ($KeepOpenSeconds -gt 0) { $false } else { $null -ne (Get-Process -Id $process.Id -ErrorAction SilentlyContinue) } +} | ConvertTo-Json -Depth 16 diff --git a/scripts/test-open-offline-chat-window-probe.ps1 b/scripts/test-open-offline-chat-window-probe.ps1 new file mode 100644 index 0000000..a9852d5 --- /dev/null +++ b/scripts/test-open-offline-chat-window-probe.ps1 @@ -0,0 +1,62 @@ +param( + [string]$HelperExe = "runs/win-helper/ISphereWinHelper.exe" +) + +$ErrorActionPreference = "Stop" +$repo = Resolve-Path -LiteralPath (Join-Path $PSScriptRoot "..") +$scriptPath = Join-Path $repo "scripts\open-offline-chat-window-probe.ps1" +$helperPath = Join-Path $repo $HelperExe + +function Assert-True([bool]$Condition, [string]$Message) { + if (-not $Condition) { + throw $Message + } +} + +if (-not (Test-Path -LiteralPath $helperPath)) { + & powershell -NoProfile -ExecutionPolicy Bypass -File (Join-Path $repo "scripts\build-win-helper.ps1") | Out-Host + if ($LASTEXITCODE -ne 0) { + throw "build-win-helper.ps1 failed with exit code $LASTEXITCODE" + } +} + +Assert-True (Test-Path -LiteralPath $scriptPath) "missing script: $scriptPath" + +$output = & powershell -NoProfile -ExecutionPolicy Bypass -File $scriptPath ` + -HelperExe $HelperExe ` + -KeepOpenSeconds 2 ` + -ProbeClick ` + -NoScreenshot +if ($LASTEXITCODE -ne 0) { + throw "open-offline-chat-window-probe.ps1 exited with code $LASTEXITCODE. Output: $output" +} + +try { + $result = $output | ConvertFrom-Json +} +catch { + throw "script output was not JSON: $output" +} + +Assert-True ([bool]$result.ok) "result.ok must be true" +Assert-True ([string]$result.window_kind -eq "offline_chat_window_probe") "window_kind mismatch" +Assert-True ([string]$result.root_automation_id -eq "frmP2PChat") "root automation id must be frmP2PChat" +Assert-True ([bool]$result.send_editor_found) "rtbSendMessage must be found" +Assert-True ([bool]$result.send_button_found) "btnSend must be found" +Assert-True ([bool]$result.file_button_found) "btnSendFile or btnFile must be found" +Assert-True ([bool]$result.probe_click_marker_found) "probe click marker must be written" +Assert-True ([bool]$result.probe_clicked_ui) "probe must click the UI button" +Assert-True ([bool]$result.probe_typed_text) "probe must type into the send editor" +Assert-True ([bool]$result.probe_marker_text_matches) "probe marker text must match typed text" +Assert-True (-not [bool]$result.sent_real_message) "probe must not send a real message" +Assert-True (-not [bool]$result.uploaded_real_file) "probe must not upload a real file" + +[ordered]@{ + ok = $true + script = $scriptPath + hwnd = $result.hwnd + root_automation_id = $result.root_automation_id + send_editor_found = $result.send_editor_found + send_button_found = $result.send_button_found + probe_click_marker_found = $result.probe_click_marker_found +} | ConvertTo-Json -Depth 4 -Compress