diff --git a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Cluster/CliRunner.Helpers.cs b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Cluster/CliRunner.Helpers.cs
index 6ca4ffee..bf8b1725 100644
--- a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Cluster/CliRunner.Helpers.cs
+++ b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Cluster/CliRunner.Helpers.cs
@@ -283,6 +283,32 @@ public static partial class CliRunner
/// Best-effort delete of an API method via api-method delete for teardown.
public static Task DeleteApiMethodAsync(int id) => BestEffortAsync("api-method", "delete", id);
+ ///
+ /// Creates an API key via security api-key create and returns its opaque string
+ /// keyId (resolved by name from security api-key list).
+ ///
+ ///
+ /// security api-key create prints a human-readable block (not JSON) even under
+ /// --format json, so this uses (never
+ /// RunJsonAsync, which would throw a JsonException on that output) and then
+ /// resolves the new key's id by its unique name. Use this for tests that need a
+ /// pre-existing key to act on (enable/disable/delete); pair with
+ /// for teardown.
+ ///
+ /// Unique key name (typically from ).
+ /// Comma-separated API method names the key is scoped to.
+ /// The new key's opaque keyId.
+ ///
+ /// The CLI failed, or the key could not be resolved by name after creation.
+ ///
+ public static async Task CreateApiKeyAsync(string name, string methods)
+ {
+ await RunAsync("security", "api-key", "create", "--name", name, "--methods", methods);
+ return await ResolveApiKeyIdByNameAsync(name)
+ ?? throw new InvalidOperationException(
+ $"API key '{name}' was created but could not be resolved by name in 'security api-key list'.");
+ }
+
///
/// Resolves an API key's opaque string keyId from its display name via
/// security api-key list; returns if no key matches.
@@ -327,9 +353,13 @@ public static partial class CliRunner
///
/// Reads an instance's full configuration via instance get; the returned document exposes
/// connectionBindings, attributeOverrides, and areaId for persistence read-back.
- /// Caller owns the returned .
///
- public static Task GetInstanceAsync(int id) =>
+ ///
+ /// This is the only helper that hands back a live (the rest return
+ /// scalars). The caller OWNS it and MUST wrap the call in using var doc = …; the
+ /// Document suffix is the signal that this returns a disposable resource, not plain data.
+ ///
+ public static Task GetInstanceDocumentAsync(int id) =>
RunJsonAsync("instance", "get", "--id", id.ToString(System.Globalization.CultureInfo.InvariantCulture));
///