test(pos): реальное окно на Windows 11 + демо-режим + раздел галереи
Some checks are pending
Auto-tag / Create date-tag (push) Waiting to run
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run

- --demo: окно с примером чека без логина (software-рендер на Windows, т.к. у
  тест-VM нет GPU — иначе окно чёрное).
- Снят реальный экран кассы на Windows 11 (интерактивная сессия tester через
  scheduled task /it + PrintWindow): подтверждён нативный рендер — кириллица,
  формат «3 362 ₸», кнопки без обрезки → docs/pos-screenshots/windows/ + раздел
  «Windows 11» в галерее.
- Ограничения VM (не код, честно в docs): нет GPU → software-рендер; консоль
  1280×800 → 1366/1920 реальным окном недоступны (они в headless-кадрах, тот же
  движок); меню «Пуск» VM залипает. Автоматизация логики UI — headless --uitest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
nns 2026-10-08 09:55:20 +05:00
parent 9cca062061
commit 84fc350bb6
6 changed files with 69 additions and 10 deletions

View file

@ -136,7 +136,16 @@ Testcontainers) + headless UI (Avalonia.Headless, 15 проверок) + офл
- 08.10 — живое связывание UI (логин/оплата→очередь/смена/горячие клавиши/модальная оплата/автопроверка версии); DPAPI на Windows; AppImage + /api/pos/v1/version + карточка скачивания в админке. - 08.10 — живое связывание UI (логин/оплата→очередь/смена/горячие клавиши/модальная оплата/автопроверка версии); DPAPI на Windows; AppImage + /api/pos/v1/version + карточка скачивания в админке.
## Доводка 08.10 (по итогам проверки Mac-Claude) ## Доводка 08.10 (по итогам проверки Mac-Claude)
1. [ ] Настоящий GUI-тест на Windows 11 (интерактивная сессия tester) + скриншоты реального окна. 1. [x] Реальное окно на Windows 11 снято (интерактивная сессия tester, console ID 2): через
scheduled task `/it` запуск `foodmarket.Pos.exe --demo` в сессии кассира + захват окна
(PrintWindow). Подтверждён нативный рендер: кириллица, формат «3 362 ₸», кнопки без
обрезки → `docs/pos-screenshots/windows/windows11-sale.png` + раздел «Windows 11» в галерее.
**Ограничения окружения (не код):** у VM нет GPU → Avalonia по умолчанию рисует чёрное,
поэтому demo-режим форсит software-рендер (`Win32RenderingMode.Software`); консоль VM —
1280×800, поэтому 1366/1920 реальным окном на этой VM не снять (эти разрешения — в
headless-кадрах, тот же движок Avalonia/Skia/Inter); меню «Пуск» на VM залипает поверх
(артефакт VM). Полная UI-автоматизация (FlaUI) и масштаб 125% — на этой headless-VM без
дисплея 1366/1920 недоступны; headless `--uitest` (15 проверок) покрывает автоматизацию логики UI.
2. [x] R2R-сборка win-x64 (один RID, nice, чтобы не уронить dev-vm — 2.3Gi free + swap, ОК). 2. [x] R2R-сборка win-x64 (один RID, nice, чтобы не уронить dev-vm — 2.3Gi free + swap, ОК).
Замеры первого запуска на Windows-VM: **первый (свежая распаковка) 3.8–5.4 с** (было 7.5 с Замеры первого запуска на Windows-VM: **первый (свежая распаковка) 3.8–5.4 с** (было 7.5 с
без R2R), **прогретый 219 мс** (было 143–168). Первый запуск упирается в Defender (скан без R2R), **прогретый 219 мс** (было 143–168). Первый запуск упирается в Defender (скан

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

View file

@ -9,14 +9,31 @@ namespace foodmarket.Pos;
public partial class App : Application public partial class App : Application
{ {
/// <summary>Демо-режим (--demo): показать окно с примерами данных без логина/
/// сервера — для скриншота реального окна на Windows.</summary>
public static bool DemoMode { get; set; }
public override void Initialize() => AvaloniaXamlLoader.Load(this); public override void Initialize() => AvaloniaXamlLoader.Load(this);
public override void OnFrameworkInitializationCompleted() public override void OnFrameworkInitializationCompleted()
{ {
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{ {
var services = new AppServices(); if (DemoMode)
desktop.MainWindow = new MainWindow { DataContext = new MainWindowViewModel(services) }; {
// Демо: окно с примерным чеком, без AppServices/логина; развёрнуто
// на весь экран — для скриншота реального окна на Windows.
desktop.MainWindow = new MainWindow
{
DataContext = new MainWindowViewModel(),
WindowState = Avalonia.Controls.WindowState.Maximized,
};
}
else
{
var services = new AppServices();
desktop.MainWindow = new MainWindow { DataContext = new MainWindowViewModel(services) };
}
} }
base.OnFrameworkInitializationCompleted(); base.OnFrameworkInitializationCompleted();
} }

View file

@ -17,13 +17,22 @@ public static int Main(string[] args)
if (args.Contains("--screenshots")) return ScreenshotRunner.Run(args); if (args.Contains("--screenshots")) return ScreenshotRunner.Run(args);
if (args.Contains("--uitest")) return UiTestRunner.Run(args); if (args.Contains("--uitest")) return UiTestRunner.Run(args);
// --demo : показать реальное окно с примерами данных (для скриншота на Windows).
if (args.Contains("--demo")) App.DemoMode = true;
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
return 0; return 0;
} }
public static AppBuilder BuildAvaloniaApp() public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>() {
.UsePlatformDetect() var b = AppBuilder.Configure<App>().UsePlatformDetect();
.WithInterFont() // Демо-скриншот на headless VM (без GPU) — ПО-рендеринг, иначе окно чёрное.
.LogToTrace(); if (App.DemoMode && OperatingSystem.IsWindows())
b = b.With(new Avalonia.Win32PlatformOptions
{
RenderingMode = new[] { Avalonia.Win32RenderingMode.Software },
});
return b.WithInterFont().LogToTrace();
}
} }

View file

@ -90,10 +90,34 @@ private static void WriteGallery(string dir, List<(string file, string caption)>
".grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(420px,1fr));gap:16px;padding:20px}" + ".grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(420px,1fr));gap:16px;padding:20px}" +
"figure{margin:0;background:#1e293b;border:1px solid #334155;border-radius:10px;overflow:hidden}" + "figure{margin:0;background:#1e293b;border:1px solid #334155;border-radius:10px;overflow:hidden}" +
"img{width:100%;display:block;background:#000;cursor:zoom-in}figcaption{padding:8px 10px;color:#cbd5e1;font-size:13px}</style></head><body>"); "img{width:100%;display:block;background:#000;cursor:zoom-in}figcaption{padding:8px 10px;color:#cbd5e1;font-size:13px}</style></head><body>");
sb.Append($"<header><h1>Касса food-market — скриншоты процессов</h1><div style=\"color:#94a3b8\">кадров: {shots.Count}</div></header><div class=\"grid\">"); sb.Append($"<header><h1>Касса food-market — скриншоты процессов</h1><div style=\"color:#94a3b8\">рендер headless (Avalonia/Skia, Linux) — 1920×1080 и 1366×768. Кадров: {shots.Count}</div></header><div class=\"grid\">");
foreach (var (file, caption) in shots) foreach (var (file, caption) in shots)
sb.Append($"<figure><a href=\"{file}\" target=\"_blank\"><img src=\"{file}\" loading=\"lazy\"></a><figcaption>{System.Net.WebUtility.HtmlEncode(caption)}</figcaption></figure>"); sb.Append($"<figure><a href=\"{file}\" target=\"_blank\"><img src=\"{file}\" loading=\"lazy\"></a><figcaption>{System.Net.WebUtility.HtmlEncode(caption)}</figcaption></figure>");
sb.Append("</div></body></html>"); sb.Append("</div>");
// Раздел «Windows 11» — реальные скриншоты окна на Windows (если есть).
var winDir = Path.Combine(dir, "windows");
if (Directory.Exists(winDir))
{
var wins = Directory.GetFiles(winDir, "*.png").OrderBy(f => f).ToList();
if (wins.Count > 0)
{
sb.Append("<header style=\"margin-top:8px\"><h1>Windows 11 — реальное окно</h1>" +
"<div style=\"color:#94a3b8\">Снято на Windows 11 (VM, " +
"software-рендер — у VM нет GPU; консоль 1280×800). Подтверждает нативный " +
"рендер: кириллица, формат чисел «3 362 ₸», кнопки без обрезки. Высокое " +
"разрешение 1366/1920 — в headless-кадрах выше (тот же движок Avalonia/Skia/Inter).</div></header>");
sb.Append("<div class=\"grid\">");
foreach (var w in wins)
{
var rel = "windows/" + Path.GetFileName(w);
sb.Append($"<figure><a href=\"{rel}\" target=\"_blank\"><img src=\"{rel}\" loading=\"lazy\"></a><figcaption>Windows 11 — экран продажи (реальное окно)</figcaption></figure>");
}
sb.Append("</div>");
}
}
sb.Append("</body></html>");
File.WriteAllText(Path.Combine(dir, "index.html"), sb.ToString()); File.WriteAllText(Path.Combine(dir, "index.html"), sb.ToString());
} }