feat: choose stock and resolution for warehouse test prints

Co-authored-by: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <james@kof22.com>
This commit is contained in:
james.maesandCodex committed 2026-10-30 17:42:29 -05:00
1 parent 4b6b6bccde
commit 93dd5fc8d2
17 files changed
+291 -21

No files matched your search

+5 -5
View File
@@ -9,10 +9,10 @@ on:
inputs:
product-version:
type: string
default: '0.1.6'
default: '0.1.7'
release-version:
type: string
default: '0.1.6-rc.1'
default: '0.1.7-rc.1'
permissions:
contents: read
@@ -39,7 +39,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: QRun-IO/qqq-print-protocol
ref: a0de93b00e602abde9be77c1ec9103b670bf3c09
ref: f4f977ca4aa6b749e6a52760eed5e53c99a7704d
path: qqq-print-protocol
persist-credentials: false
- name: Set up .NET
@@ -52,8 +52,8 @@ jobs:
run: ./build/Test-BrandedInstaller.ps1
- name: Build unsigned prerelease MSI
env:
PRODUCT_VERSION: ${{ inputs.product-version || '0.1.6' }}
RELEASE_VERSION: ${{ inputs.release-version || '0.1.6-rc.1' }}
PRODUCT_VERSION: ${{ inputs.product-version || '0.1.7' }}
RELEASE_VERSION: ${{ inputs.release-version || '0.1.7-rc.1' }}
run: |
./build/Build-Installer.ps1 -ProductVersion $env:PRODUCT_VERSION -ReleaseVersion $env:RELEASE_VERSION
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+1 -1
View File
@@ -2,7 +2,7 @@
This is QRun-IO/qqq-print-agent, built on KofTwentyTwo AppKit's Windows service variant. Follow the current user request and repository .editorconfig/central package versions.
- Verify with `./build/Verify.ps1`; package with `./build/Build-Installer.ps1 -ProductVersion 0.1.6 -ReleaseVersion 0.1.6-rc.1`.
- Verify with `./build/Verify.ps1`; package with `./build/Build-Installer.ps1 -ProductVersion 0.1.7 -ReleaseVersion 0.1.7-rc.1`.
- Core owns protocol/state/policy; Windows owns OS adapters/storage/runtime; Service owns hosting; UI owns native views/tray plumbing.
- Zero compiler/analyzer warnings; locked dependencies. Coverage is reported for the initial prerelease under docs/adr/0002-prerelease-quality-and-packaging.md.
- Keep the service alive independently of the companion. Use AppKit update UX with the machine MSI adapter; update service, companion and CLI together. Do not install a per-user-only replacement over the machine package.
+8
View File
@@ -1,5 +1,13 @@
# Changelog
## 0.1.7-rc.1 — 2026-10-30
- Built-in QQQ-branded warehouse diagnostics use the server's bundled legible font across PDF and ZPL.
- Native test controls offer Letter, A4 and 4 × 6 labels with explicit 203/300/600-dpi ZPL choices.
- Prefer ZPL for label-capable queues and suggest resolution from an explicit driver hint; operators verify stock and resolution before printing.
- Existing API callers keep compatible defaults when testProfile is omitted.
## 0.1.6-rc.1 — 2026-10-30
- Expand selected printer information within its row, with actions first, labeled fields, a bounded description preview and expandable Windows diagnostics.
+1 -1
View File
@@ -55,7 +55,7 @@
commit as "+<sha>" on AssemblyInformationalVersion, which the About dialog and the
CLI's version output show as a short hash. -->
<PropertyGroup>
<VersionPrefix>0.1.6</VersionPrefix>
<VersionPrefix>0.1.7</VersionPrefix>
<VersionSuffix>rc.1</VersionSuffix>
</PropertyGroup>
+2 -2
View File
@@ -1,9 +1,9 @@
[CmdletBinding()]
param(
[ValidatePattern('^\d+\.\d+\.\d+$')]
[string] $ProductVersion = '0.1.6',
[string] $ProductVersion = '0.1.7',
[ValidatePattern('^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$')]
[string] $ReleaseVersion = '0.1.6-rc.1',
[string] $ReleaseVersion = '0.1.7-rc.1',
[string] $PayloadDirectory,
[switch] $SkipPublish,
[string] $CertificateThumbprint,
+3
View File
@@ -1,5 +1,8 @@
# Session state — 2026-10-30
## Branded warehouse diagnostics in progress — 2026-10-30
User requested strongly QQQ-branded built-in test documents with warehouse-readable characters. Protocol 0.1.3 adds optional media/resolution profiles; Windows 0.1.7 adds explicit native choices and prefers ZPL on compatible label queues. Windows gate passed 227 tests (57 Core, 170 Windows/CLI), zero warnings/errors; the final UI acceptance test compiles. QQQ generator uses the existing QQQ logo and embedded JetBrains Mono Bold (dotted zero, distinct 1/I/l), rendered into bounded graphics for ZPL. Independent review caught and corrected the Zebra per-field graphic-size limit before any new built-in physical print. QQQ's repeated gate, final previews, installation and actual built-in print acceptance are in progress. The prior installed 0.1.6 and published artifacts remain available.
## Current checkpoint: 0.1.6 published and Zebra submissions accepted
Public v0.1.6-rc.1 release assets are verified. The original paired QQQ database was restored on loopback port 18081 without changing device credentials; read-only inspection found no pending jobs before startup. The connection is healthy. A Zebra ZT411 at 300 dpi, loaded with confirmed 4 × 6-inch stock, is exposed locally, enabled in QQQ and READY. Exactly one ZPL shipping test and one PDF barcode/alignment test reached SPOOLED. Physical output and barcode scanning are pending user confirmation. The service remained Running throughout this session. This is local end-to-end protocol evidence, not a deployed remote cloud test. No production code changed.
+8
View File
@@ -63,3 +63,11 @@ Settings shows actual Windows service state and provides **Start service**, **Re
Closing the window hides it to the tray. Reopening from Start restores the existing instance. **Show the tray companion when I sign in** explicitly enables a task for the current administrator and the installed executable; it is off by default. Upgrades retain this preference, and full uninstall removes verified application tasks. See [installation and security details](INSTALLATION.md).
Administrators can apply [customer branding](BRANDING.md) without rebuilding. Appearance follows Windows or your selected light/dark theme; unsafe light accents fall back for readable AppKit text.
## Branded warehouse test prints
Select a printer and choose **Print test**. Choose a document format and stock from the native option boxes, then confirm **Print one test**. PDF offers Letter, A4 or a 4 × 6-inch label. ZPL offers 4 × 6-inch labels at 203, 300 or 600 dpi. Match the loaded stock and actual device resolution; the driver-name suggestion is only a hint. PDF stock choices do not change Windows queue defaults.
QQQ generates the same branded diagnostics for desktop requests and its own printer record/table actions. The pages use the QQQ logo, large warehouse identifiers, dotted zeros and distinct 1/I/l glyphs from embedded JetBrains Mono Bold. PDF embeds the font; ZPL carries rasterized glyphs in bounded document graphics, so neither depends on a resident printer font. Label diagnostics include a Code 128 barcode on the ZPL path, and all layouts include size/alignment checks. Paper output and scanner readability must be checked physically.
Use qbit-printing 0.1.3-rc.1 or later for these branded profiles. Older clients can omit the profile, defaulting to Letter PDF or 203-dpi 4 × 6 ZPL. Native QQQ actions can process selected printers; group devices with the same stock and label resolution. Connecting or discovering a device never automatically sends a test.
+1 -1
View File
@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Package</OutputType>
<InstallerPlatform>x64</InstallerPlatform>
<ProductVersion Condition="'$(ProductVersion)' == ''">0.1.6</ProductVersion>
<ProductVersion Condition="'$(ProductVersion)' == ''">0.1.7</ProductVersion>
<PayloadRoot Condition="'$(PayloadRoot)' == ''">$(MSBuildThisFileDirectory)..\artifacts\package</PayloadRoot>
<OutputName>QqqPrintAgent-$(ProductVersion)-x64</OutputName>
<DefineConstants>ProductVersion=$(ProductVersion);PayloadRoot=$(PayloadRoot)</DefineConstants>
@@ -116,7 +116,10 @@ public sealed class PrintApiClient : IDisposable
}
/// <summary>Request an explicit operator action without retaining document bytes locally.</summary>
public async Task<string> OperatorActionAsync(string operation, string? jobId, string? printerId, string? format, bool confirmDuplicateRisk, CancellationToken token)
public Task<string> OperatorActionAsync(string operation, string? jobId, string? printerId, string? format, bool confirmDuplicateRisk, CancellationToken token)
=> OperatorActionAsync(operation, jobId, printerId, format, confirmDuplicateRisk, null, token);
public async Task<string> OperatorActionAsync(string operation, string? jobId, string? printerId, string? format, bool confirmDuplicateRisk, string? testProfile, CancellationToken token)
{
string path = operation switch
{
@@ -125,7 +128,14 @@ public sealed class PrintApiClient : IDisposable
"cancel-job" => $"device/jobs/{Uri.EscapeDataString(jobId ?? throw new ArgumentException("Job is required.", nameof(jobId)))}/cancel",
_ => throw new ArgumentException("Unsupported operator action.", nameof(operation)),
};
using HttpResponseMessage response = await _http.PostAsJsonAsync(path, new { printerId, format, confirmDuplicateRisk, requestId = Guid.NewGuid().ToString("N") }, JsonOptions, token).ConfigureAwait(false);
using HttpResponseMessage response = await _http.PostAsJsonAsync(path, new
{
printerId,
format,
confirmDuplicateRisk,
testProfile = string.Equals(operation, "test-print", StringComparison.Ordinal) ? testProfile : null,
requestId = Guid.NewGuid().ToString("N"),
}, JsonOptions, token).ConfigureAwait(false);
if(!response.IsSuccessStatusCode)
{
if(response.StatusCode == System.Net.HttpStatusCode.NotFound)
@@ -0,0 +1,23 @@
namespace Qqq.PrintAgent.Core.Printing;
/// <summary>Suggested diagnostic defaults; the operator must still verify installed stock and resolution.</summary>
public static class TestPrintDefaults
{
public static string Format(IEnumerable<string> supportedFormats)
=> supportedFormats.Contains("ZPL", StringComparer.Ordinal) ? "ZPL" : "PDF";
public static string Profile(string format, string? driverName)
{
if(!string.Equals(format, "ZPL", StringComparison.Ordinal))
{
return "LETTER";
}
string driver = driverName ?? string.Empty;
if(driver.Contains("600dpi", StringComparison.OrdinalIgnoreCase) || driver.Contains("600 dpi", StringComparison.OrdinalIgnoreCase))
{
return "LABEL_4X6_600";
}
return driver.Contains("300dpi", StringComparison.OrdinalIgnoreCase) || driver.Contains("300 dpi", StringComparison.OrdinalIgnoreCase)
? "LABEL_4X6_300" : "LABEL_4X6_203";
}
}
@@ -53,7 +53,8 @@ public sealed partial class AgentRuntime
}
}
using PrintApiClient api = CreateApi(configuration);
string queuedJob = await api.OperatorActionAsync(request.Operation, request.JobId, request.PrinterId, request.Format, request.ConfirmDuplicateRisk, token).ConfigureAwait(false);
string? testProfile = string.Equals(request.Operation, "test-print", StringComparison.Ordinal) ? request.TestProfile : null;
string queuedJob = await api.OperatorActionAsync(request.Operation, request.JobId, request.PrinterId, request.Format, request.ConfirmDuplicateRisk, testProfile, token).ConfigureAwait(false);
if(string.Equals(request.Operation, "cancel-job", StringComparison.Ordinal))
{
lock(_stateLock)
+1 -1
View File
@@ -7,7 +7,7 @@ namespace Qqq.PrintAgent.Windows;
public sealed record PipeRequest(string Operation, string? ServerUrl = null, string? EnrollmentCode = null, string? PrinterId = null, bool? Exposed = null,
string? JobId = null, string? Format = null, bool ConfirmDuplicateRisk = false, JobQuery? JobQuery = null, LogSettings? LogSettings = null,
Qqq.PrintAgent.Core.Printing.DeviceProfile? DeviceProfile = null, PrinterProfile? PrinterProfile = null);
Qqq.PrintAgent.Core.Printing.DeviceProfile? DeviceProfile = null, PrinterProfile? PrinterProfile = null, string? TestProfile = null);
+7 -7
View File
@@ -76,14 +76,14 @@ public sealed partial class MainWindow
{
return;
}
ComboBox format = new() { Header = "Test format", ItemsSource = printer.Formats, SelectedIndex = 0, MinWidth = 220 };
StackPanel content = new() { Spacing = 12 };
content.Children.Add(new TextBlock { Text = $"Send one test to {PrinterLabel(printer)} through the server? This uses real paper or a label. The printer must be shared here and approved on the server.", TextWrapping = TextWrapping.Wrap });
content.Children.Add(format);
var dialog = new ContentDialog { Title = "Print a test", Content = content, PrimaryButtonText = "Print one test", CloseButtonText = "Back", DefaultButton = ContentDialogButton.Close, XamlRoot = Content.XamlRoot };
if(await DialogGuard.ShowAsync(dialog) == ContentDialogResult.Primary && format.SelectedItem is string selectedFormat)
var editor = new TestPrintEditor(printer, PrinterLabel(printer));
ContentDialog dialog = CreateWideDialog("Print a test", editor);
dialog.PrimaryButtonText = "Print one test";
dialog.CloseButtonText = "Back";
dialog.DefaultButton = ContentDialogButton.Close;
if(await DialogGuard.ShowAsync(dialog) == ContentDialogResult.Primary && editor.SelectedProfile is string profile)
{
await RunActionAsync(new PipeRequest("test-print", PrinterId: printer.PrinterId, Format: selectedFormat));
await RunActionAsync(new PipeRequest("test-print", PrinterId: printer.PrinterId, Format: editor.SelectedFormat, TestProfile: profile));
}
}
+66
View File
@@ -0,0 +1,66 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Qqq.PrintAgent.Core.Printing;
using Qqq.PrintAgent.Windows;
namespace Qqq.PrintAgent;
/// <summary>Explicit stock and resolution choices for one server-generated diagnostic document.</summary>
internal sealed class TestPrintEditor : StackPanel
{
private readonly ComboBox _format = new() { Header = "Print format", HorizontalAlignment = HorizontalAlignment.Stretch };
private readonly ComboBox _profile = new() { Header = "Paper or label stock", HorizontalAlignment = HorizontalAlignment.Stretch };
private readonly TextBlock _guidance = new() { TextWrapping = TextWrapping.Wrap };
private readonly string _driverName;
internal string SelectedFormat => (_format.SelectedItem as ComboBoxItem)?.Tag as string ?? "PDF";
internal string? SelectedProfile => (_profile.SelectedItem as ComboBoxItem)?.Tag as string;
internal TestPrintEditor(PrinterSnapshot printer, string printerLabel)
{
_driverName = printer.DriverName;
Spacing = 16;
Children.Add(new TextBlock { Text = $"Print one high-contrast QQQ diagnostic page or label on {printerLabel}. This uses real stock and requires local sharing and server approval.", TextWrapping = TextWrapping.Wrap });
foreach(string format in printer.Formats)
{
_format.Items.Add(new ComboBoxItem { Content = string.Equals(format, "ZPL", StringComparison.Ordinal) ? "Printer-ready label (ZPL)" : "Document (PDF)", Tag = format });
}
string preferred = TestPrintDefaults.Format(printer.Formats);
_format.SelectedItem = _format.Items.Cast<ComboBoxItem>().First(item => string.Equals(item.Tag as string, preferred, StringComparison.Ordinal));
_format.SelectionChanged += FormatChanged;
Microsoft.UI.Xaml.Automation.AutomationProperties.SetAutomationId(_format, "TestPrintFormat");
Microsoft.UI.Xaml.Automation.AutomationProperties.SetAutomationId(_profile, "TestPrintMedia");
Children.Add(_format);
Children.Add(_profile);
Children.Add(_guidance);
UpdateProfiles();
}
private void FormatChanged(object sender, SelectionChangedEventArgs e) => UpdateProfiles();
private void UpdateProfiles()
{
_profile.Items.Clear();
bool zpl = string.Equals(SelectedFormat, "ZPL", StringComparison.Ordinal);
if(zpl)
{
AddProfile("4 × 6 in label — 203 dpi", "LABEL_4X6_203");
AddProfile("4 × 6 in label — 300 dpi", "LABEL_4X6_300");
AddProfile("4 × 6 in label — 600 dpi", "LABEL_4X6_600");
}
else
{
AddProfile("US Letter — 8.5 × 11 in", "LETTER");
AddProfile("A4 — 210 × 297 mm", "A4");
AddProfile("4 × 6 in label", "LABEL_4X6_300");
}
string preferred = TestPrintDefaults.Profile(SelectedFormat, _driverName);
_profile.SelectedItem = _profile.Items.Cast<ComboBoxItem>().First(item => string.Equals(item.Tag as string, preferred, StringComparison.Ordinal));
_profile.Header = zpl ? "Label stock and resolution" : "Paper or label stock";
_guidance.Text = zpl
? "Load 4 × 6 in labels and verify the printer's actual resolution before printing. The default is only a driver-name suggestion (203 dpi when unknown); a wrong DPI changes the printed size. This diagnostic does not calibrate the printer or save device configuration."
: "Load stock matching the selected size and set the Windows queue to the same paper or label size. The diagnostic prints one copy at actual size; the stock selection does not change Windows printer defaults.";
}
private void AddProfile(string label, string value) => _profile.Items.Add(new ComboBoxItem { Content = label, Tag = value });
}
@@ -0,0 +1,21 @@
using System.Text.Json;
using Qqq.PrintAgent.Core.Printing;
using Qqq.PrintAgent.Windows;
namespace Qqq.PrintAgent.Cli.Tests;
public sealed class TestPrintPipeTests
{
[Fact]
public void TestMedia_RoundTripsThroughLocalPipeRequestWithoutChangingLegacyDefaults()
{
const string legacy = "{\"operation\":\"test-print\",\"printerId\":\"queue\",\"format\":\"PDF\"}";
PipeRequest previous = JsonSerializer.Deserialize<PipeRequest>(legacy, PrintApiClient.JsonOptions)!;
Assert.Null(previous.TestProfile);
using var oldBody = JsonDocument.Parse(JsonSerializer.Serialize(previous, PrintApiClient.JsonOptions));
Assert.False(oldBody.RootElement.TryGetProperty("testProfile", out _));
PipeRequest selected = previous with { Format = "ZPL", TestProfile = "LABEL_4X6_300" };
PipeRequest restored = JsonSerializer.Deserialize<PipeRequest>(JsonSerializer.Serialize(selected, PrintApiClient.JsonOptions), PrintApiClient.JsonOptions)!;
Assert.Equal(selected, restored);
}
}
@@ -0,0 +1,82 @@
using System.Net;
using System.Text.Json;
using Qqq.PrintAgent.Core.Printing;
namespace Qqq.PrintAgent.Core.Tests;
public sealed class TestPrintProfileTests
{
[Theory]
[InlineData("PDF", "LETTER")]
[InlineData("PDF", "A4")]
[InlineData("PDF", "LABEL_4X6_300")]
[InlineData("ZPL", "LABEL_4X6_203")]
[InlineData("ZPL", "LABEL_4X6_300")]
[InlineData("ZPL", "LABEL_4X6_600")]
public async Task SelectedMediaProfile_IsIncludedInActualTestPrintRequest(string format, string profile)
{
using CaptureHandler handler = new();
using PrintApiClient api = new("https://synthetic.invalid", "synthetic-token", handler);
string job = await api.OperatorActionAsync("test-print", null, "queue-1", format, false, profile, CancellationToken.None);
Assert.Equal("new-test-job", job);
Assert.Equal("/printing/v1/device/test-print", handler.Path);
JsonElement body = handler.Body;
Assert.Equal("queue-1", body.GetProperty("printerId").GetString());
Assert.Equal(format, body.GetProperty("format").GetString());
Assert.Equal(profile, body.GetProperty("testProfile").GetString());
Assert.True(Guid.TryParseExact(body.GetProperty("requestId").GetString(), "N", out _));
}
[Fact]
public async Task ExistingCaller_KeepsLegacyShapeWithoutNullTestProfile()
{
using CaptureHandler handler = new();
using PrintApiClient api = new("https://synthetic.invalid", "synthetic-token", handler);
await api.OperatorActionAsync("test-print", null, "queue-1", "PDF", false, CancellationToken.None);
Assert.False(handler.Body.TryGetProperty("testProfile", out _));
Assert.Equal(4, handler.Body.EnumerateObject().Count());
}
[Theory]
[InlineData("retry-job")]
[InlineData("cancel-job")]
public async Task Profile_IsNeverSentForOtherOperatorActions(string operation)
{
using CaptureHandler handler = new();
using PrintApiClient api = new("https://synthetic.invalid", "synthetic-token", handler);
await api.OperatorActionAsync(operation, "existing-job", null, null, true, "LABEL_4X6_600", CancellationToken.None);
Assert.False(handler.Body.TryGetProperty("testProfile", out _));
Assert.True(handler.Body.GetProperty("confirmDuplicateRisk").GetBoolean());
}
[Fact]
public void LabelCapability_TakesPrecedenceOverPdfEnumerationOrder()
{
Assert.Equal("ZPL", TestPrintDefaults.Format(["PDF", "ZPL"]));
Assert.Equal("ZPL", TestPrintDefaults.Format(["ZPL"]));
Assert.Equal("PDF", TestPrintDefaults.Format(["PDF"]));
}
[Theory]
[InlineData("ZPL", "ZDesigner ZT411-300dpi", "LABEL_4X6_300")]
[InlineData("ZPL", "Thermal 600 DPI", "LABEL_4X6_600")]
[InlineData("ZPL", "Generic thermal", "LABEL_4X6_203")]
[InlineData("ZPL", "Model 600 with unknown resolution", "LABEL_4X6_203")]
[InlineData("PDF", "Laser 600dpi", "LETTER")]
public void ResolutionSuggestion_UsesExplicitDpiHintsOnly(string format, string driver, string expected)
=> Assert.Equal(expected, TestPrintDefaults.Profile(format, driver));
private sealed class CaptureHandler : HttpMessageHandler
{
internal JsonElement Body { get; private set; }
internal string? Path { get; private set; }
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
Path = request.RequestUri!.AbsolutePath;
using var document = JsonDocument.Parse(await request.Content!.ReadAsStringAsync(cancellationToken));
Body = document.RootElement.Clone();
return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("{\"jobId\":\"new-test-job\"}") };
}
}
}
+48
View File
@@ -185,6 +185,54 @@ public class AppTests
session.WaitFor(() => session.FindInApp(cf => cf.ByAutomationId("SortJobTitle")) is { Name: "Sort by document, ascending" } button ? button : null, "document sort query to finish");
}
/// <summary>Requires an exposed PDF/ZPL queue; opens diagnostic choices and cancels without submitting any job.</summary>
[Fact]
public void LabelTestDialog_OffersResolutionAndStockChoicesWithoutPrinting()
{
using var session = new AppSession();
AutomationElement inventory = session.WaitForElement("PrinterInventory");
AutomationElement row = session.WaitFor(() => inventory.FindAllDescendants(cf => cf.ByControlType(ControlType.ListItem))
.FirstOrDefault(IsSharedLabelRow), "an exposed PDF/ZPL queue for the read-only diagnostic dialog fixture");
row.Patterns.SelectionItem.Pattern.Select();
AutomationElement test = session.WaitFor(() => row.FindFirstDescendant(cf => cf.ByAutomationId("PrintTest")) is { IsEnabled: true } button ? button : null, "the selected queue's diagnostic button");
test.Patterns.Invoke.Pattern.Invoke();
ComboBox format = session.WaitForElement("TestPrintFormat").AsComboBox();
ComboBox media = session.WaitForElement("TestPrintMedia").AsComboBox();
Assert.Equal("Printer-ready label (ZPL)", format.SelectedItem?.Text);
string[] resolutions = ["4 × 6 in label — 203 dpi", "4 × 6 in label — 300 dpi", "4 × 6 in label — 600 dpi"];
foreach(string resolution in resolutions)
{
media.Select(resolution);
Assert.Equal(resolution, media.SelectedItem?.Text);
}
format.Select("Document (PDF)");
session.WaitFor(() => string.Equals(media.SelectedItem?.Text, "US Letter — 8.5 × 11 in", StringComparison.Ordinal) ? media : null, "PDF stock choices");
string[] stocks = ["US Letter — 8.5 × 11 in", "A4 — 210 × 297 mm", "4 × 6 in label"];
foreach(string stock in stocks)
{
media.Select(stock);
Assert.Equal(stock, media.SelectedItem?.Text);
}
session.ClickButton("Back");
session.WaitForElementGone("TestPrintFormat");
Assert.False(session.App.HasExited);
}
private static bool IsSharedLabelRow(AutomationElement row)
{
AutomationElement? exposure = row.FindFirstDescendant(cf => cf.ByControlType(ControlType.CheckBox));
if(row.IsOffscreen || exposure?.Patterns.Toggle.Pattern.ToggleState.Value != ToggleState.On)
{
return false;
}
return row.FindAllDescendants(cf => cf.ByControlType(ControlType.Text)).Any(element =>
{
string[] formats = element.Name.Split(',', StringSplitOptions.TrimEntries);
return element.BoundingRectangle.Bottom <= row.BoundingRectangle.Top + 44
&& formats.Contains("ZPL", StringComparer.Ordinal) && formats.Contains("PDF", StringComparer.Ordinal);
});
}
/// <summary>The close button hides details while the tray process stays alive.</summary>