Files

98 lines
4.6 KiB
C#

using Microsoft.Playwright;
using ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Cluster;
using Xunit;
namespace ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Design;
[Collection("Playwright")]
public class SharedScriptCrudTests
{
private readonly PlaywrightFixture _pw;
public SharedScriptCrudTests(PlaywrightFixture pw)
{
_pw = pw;
}
/// <summary>
/// CLI-create a shared script, then exercise the Central UI list render + UI delete
/// round-trip. The create/edit form is driven by a Monaco editor (brittle to type),
/// so authoring happens via the CLI; the UI delete is the behavior under test. A
/// best-effort CLI delete in finally guarantees no zztest-script-* definition leaks
/// if the UI path fails mid-way (the happy path already deletes via the UI).
/// </summary>
[SkippableFact]
public async Task CliCreated_Script_DeletesViaCard()
{
Skip.IfNot(await ClusterAvailability.IsAvailableAsync(), ClusterAvailability.SkipReason);
var name = CliRunner.UniqueName("script");
int id = await CliRunner.CreateSharedScriptAsync(name);
try
{
var page = await _pw.NewAuthenticatedPageAsync();
await page.GotoAsync($"{PlaywrightFixture.BaseUrl}/design/shared-scripts");
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
await Assertions.Expect(page.Locator("h4:has-text('Shared Scripts')")).ToBeVisibleAsync();
// Narrow the list so the card locator stays unambiguous even with many scripts.
await page.Locator("input[placeholder='Filter by name or code…']").FillAsync(name);
var card = page.Locator("div.card").Filter(new() { HasText = name });
await Assertions.Expect(card).ToHaveCountAsync(1, new() { Timeout = 10_000 });
// Scope the kebab + delete item to the card's .dropdown for strict-mode safety.
var dropdown = card.Locator(".dropdown");
await dropdown.Locator("button[aria-label^='More actions']").ClickAsync();
await dropdown.Locator(".dropdown-menu button.dropdown-item.text-danger").ClickAsync();
await Assertions.Expect(page.Locator(".modal-title:has-text('Delete Shared Script')")).ToBeVisibleAsync();
await page.Locator(".modal-footer .btn-danger").ClickAsync();
// Single web-first assertion on the success toast — toasts auto-dismiss, so we
// do NOT chase the toast body in a second sequential check.
await Assertions.Expect(page.Locator(".toast", new() { HasText = "deleted" }))
.ToHaveCountAsync(1, new() { Timeout = 15_000 });
// The card must be gone.
await Assertions.Expect(page.Locator("div.card").Filter(new() { HasText = name }))
.ToHaveCountAsync(0, new() { Timeout = 10_000 });
}
finally
{
// Best-effort teardown: the happy path deletes via the UI, so this finds
// nothing; it only fires if the UI path failed mid-way.
await CliRunner.DeleteSharedScriptAsync(id);
}
}
/// <summary>
/// The create form renders: heading, the (enabled) Name input, the editor tabs, and
/// the Monaco editor surface. Asserts render only — does NOT click Save, Check Syntax,
/// or Test Run (Test Run fires real I/O). Mutates nothing.
/// </summary>
[SkippableFact]
public async Task CreateForm_Renders()
{
Skip.IfNot(await ClusterAvailability.IsAvailableAsync(), ClusterAvailability.SkipReason);
var page = await _pw.NewAuthenticatedPageAsync();
await page.GotoAsync($"{PlaywrightFixture.BaseUrl}/design/shared-scripts/create");
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
await Assertions.Expect(page.Locator("h4:has-text('New Shared Script')")).ToBeVisibleAsync();
var nameInput = page.Locator("input[type=text].form-control.form-control-sm");
await Assertions.Expect(nameInput).ToBeVisibleAsync();
await Assertions.Expect(nameInput).ToBeEnabledAsync();
await Assertions.Expect(page.Locator("button.nav-link:has-text('Code')")).ToBeVisibleAsync();
await Assertions.Expect(page.Locator("button.nav-link:has-text('Parameters')")).ToBeVisibleAsync();
await Assertions.Expect(page.Locator("button.nav-link:has-text('Return type')")).ToBeVisibleAsync();
// Monaco mounts asynchronously via JS interop — give it a generous timeout.
await Assertions.Expect(page.Locator(".monaco-editor")).ToBeVisibleAsync(new() { Timeout = 15_000 });
}
}