Skip to content

Commit 339fbe6

Browse files
committed
ci: automate GUI screenshot capture on Windows runner
1 parent 17bb9db commit 339fbe6

2 files changed

Lines changed: 185 additions & 0 deletions

File tree

‎.github/scripts/screenshot.ps1‎

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
param(
2+
[string]$ExePath = "bin/Release/SciLors Mashed Trainer.exe",
3+
[string]$OutputDir = "screenshots"
4+
)
5+
6+
$ErrorActionPreference = "Continue"
7+
8+
# 1. Adjust resolution so the 1330x940 window fits completely on screen
9+
try {
10+
Set-DisplayResolution -Width 1920 -Height 1080 -Force
11+
Start-Sleep -Seconds 1
12+
} catch {
13+
Write-Host "Set-DisplayResolution not supported or failed: $_"
14+
}
15+
16+
New-Item -ItemType Directory -Force -Path $OutputDir | Out-Null
17+
18+
Add-Type -AssemblyName System.Drawing
19+
Add-Type -AssemblyName System.Windows.Forms
20+
21+
Add-Type @"
22+
using System;
23+
using System.Runtime.InteropServices;
24+
public class Win32 {
25+
[DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr hWnd);
26+
[DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
27+
[DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
28+
[StructLayout(LayoutKind.Sequential)]
29+
public struct RECT {
30+
public int Left; public int Top; public int Right; public int Bottom;
31+
public int Width { get { return Right - Left; } }
32+
public int Height { get { return Bottom - Top; } }
33+
}
34+
}
35+
"@
36+
37+
function Save-Bitmap([System.Drawing.Bitmap]$bmp, [string]$outFile) {
38+
$bmp.Save($outFile, [System.Drawing.Imaging.ImageFormat]::Png)
39+
$bmp.Dispose()
40+
Write-Host "Saved: $outFile"
41+
}
42+
43+
function Capture-FullScreen([string]$outFile) {
44+
$bounds = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds
45+
$bmp = New-Object System.Drawing.Bitmap($bounds.Width, $bounds.Height)
46+
$g = [System.Drawing.Graphics]::FromImage($bmp)
47+
$g.CopyFromScreen($bounds.Location, [System.Drawing.Point]::Empty, $bounds.Size)
48+
$g.Dispose()
49+
Save-Bitmap $bmp $outFile
50+
}
51+
52+
function Capture-Window([IntPtr]$hWnd, [string]$outFile) {
53+
[Win32]::ShowWindow($hWnd, 9) # SW_RESTORE
54+
[Win32]::SetForegroundWindow($hWnd)
55+
Start-Sleep -Milliseconds 600
56+
57+
$rect = New-Object Win32+RECT
58+
[Win32]::GetWindowRect($hWnd, [ref]$rect)
59+
60+
$w = [Math]::Max(100, $rect.Width)
61+
$h = [Math]::Max(100, $rect.Height)
62+
63+
$bmp = New-Object System.Drawing.Bitmap($w, $h)
64+
$g = [System.Drawing.Graphics]::FromImage($bmp)
65+
$g.CopyFromScreen($rect.Left, $rect.Top, 0, 0, (New-Object System.Drawing.Size($w, $h)))
66+
$g.Dispose()
67+
Save-Bitmap $bmp $outFile
68+
}
69+
70+
Write-Host "Launching $ExePath..."
71+
$proc = Start-Process -FilePath $ExePath -PassThru
72+
Start-Sleep -Seconds 6
73+
74+
$hWnd = $proc.MainWindowHandle
75+
$retry = 0
76+
while ($hWnd -eq [IntPtr]::Zero -and $retry -lt 10) {
77+
Start-Sleep -Seconds 1
78+
$proc.Refresh()
79+
$hWnd = $proc.MainWindowHandle
80+
$retry++
81+
}
82+
83+
Write-Host "Window Handle: $hWnd"
84+
Capture-FullScreen "$OutputDir/fullscreen.png"
85+
86+
if ($hWnd -ne [IntPtr]::Zero) {
87+
Capture-Window $hWnd "$OutputDir/mainwindow.png"
88+
89+
# Enumerate and capture all TabItems via UI Automation
90+
try {
91+
Add-Type -AssemblyName UIAutomationClient
92+
Add-Type -AssemblyName UIAutomationTypes
93+
94+
$root = [System.Windows.Automation.AutomationElement]::FromHandle($hWnd)
95+
$tabCondition = New-Object System.Windows.Automation.PropertyCondition(
96+
[System.Windows.Automation.AutomationElement]::ControlTypeProperty,
97+
[System.Windows.Automation.ControlType]::Tab
98+
)
99+
$tabControls = $root.FindAll([System.Windows.Automation.TreeScope]::Descendants, $tabCondition)
100+
Write-Host "Found $($tabControls.Count) TabControl(s)"
101+
102+
foreach ($tabCtrl in $tabControls) {
103+
$itemCondition = New-Object System.Windows.Automation.PropertyCondition(
104+
[System.Windows.Automation.AutomationElement]::ControlTypeProperty,
105+
[System.Windows.Automation.ControlType]::TabItem
106+
)
107+
$tabItems = $tabCtrl.FindAll([System.Windows.Automation.TreeScope]::Children, $itemCondition)
108+
Write-Host "Found $($tabItems.Count) TabItem(s)"
109+
110+
foreach ($tabItem in $tabItems) {
111+
$tabName = $tabItem.Current.Name
112+
Write-Host "Selecting Tab: $tabName"
113+
try {
114+
$selectPattern = $tabItem.GetCurrentPattern([System.Windows.Automation.SelectionItemPattern]::Pattern)
115+
if ($selectPattern) {
116+
$selectPattern.Select()
117+
Start-Sleep -Milliseconds 600
118+
$safeName = $tabName -replace '[^a-zA-Z0-9_-]', '_'
119+
Capture-Window $hWnd "$OutputDir/tab_$safeName.png"
120+
}
121+
} catch {
122+
Write-Host "Failed to select tab $tabName: $_"
123+
}
124+
}
125+
}
126+
} catch {
127+
Write-Host "UI Automation inspection notice: $_"
128+
}
129+
}
130+
131+
# Cleanly close application
132+
try {
133+
$proc.CloseMainWindow()
134+
Start-Sleep -Seconds 1
135+
if (-not $proc.HasExited) {
136+
Stop-Process -Id $proc.Id -Force
137+
}
138+
} catch {}
139+
140+
Write-Host "Screenshot capture complete!"
141+
exit 0
142+

‎.github/workflows/build.yml‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: Build
2+
3+
on:
4+
push:
5+
branches: [ master ]
6+
pull_request:
7+
branches: [ master ]
8+
workflow_dispatch:
9+
10+
jobs:
11+
asm:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v4
15+
- run: Asm/build.sh
16+
- name: Fail if committed .bin files are stale
17+
run: git diff --exit-code -- Asm
18+
19+
build:
20+
needs: asm
21+
runs-on: windows-latest
22+
steps:
23+
- uses: actions/checkout@v4
24+
- uses: microsoft/setup-msbuild@v2
25+
# v4.5 targeting pack is not on the runner image; v4.8 runs the same on Windows 10+
26+
- run: msbuild "SciLors Mashed Trainer.csproj" /p:Configuration=Release /p:TargetFrameworkVersion=v4.8 /restore:false
27+
28+
- name: Upload build artifact
29+
uses: actions/upload-artifact@v4
30+
with:
31+
name: MashedTrainer-Release
32+
path: bin/Release/
33+
34+
- name: Launch App & Capture Screenshots
35+
shell: pwsh
36+
run: pwsh -File .github/scripts/screenshot.ps1 -ExePath "bin/Release/SciLors Mashed Trainer.exe" -OutputDir "screenshots"
37+
38+
- name: Upload screenshots artifact
39+
uses: actions/upload-artifact@v4
40+
if: always()
41+
with:
42+
name: MashedTrainer-Screenshots
43+
path: screenshots/

0 commit comments

Comments
 (0)