diff --git a/tests/InputBox.Tests/InputBox.Tests.csproj b/tests/InputBox.Tests/InputBox.Tests.csproj
index 4bbd733..ad758fa 100644
--- a/tests/InputBox.Tests/InputBox.Tests.csproj
+++ b/tests/InputBox.Tests/InputBox.Tests.csproj
@@ -20,7 +20,7 @@
all
-
+
diff --git a/tests/InputBox.Tests/MainFormUiSmokeTests.cs b/tests/InputBox.Tests/MainFormUiSmokeTests.cs
index dcb560a..8a004bd 100644
--- a/tests/InputBox.Tests/MainFormUiSmokeTests.cs
+++ b/tests/InputBox.Tests/MainFormUiSmokeTests.cs
@@ -73,7 +73,7 @@ public MainFormUiSmokeTests()
[Trait("Category", "UI")]
public void Launch_ShowsPrimaryControls()
{
- RunWithFailureArtifacts(nameof(Launch_ShowsPrimaryControls), () =>
+ RunWithFailureArtifacts(nameof(Launch_ShowsPrimaryControls), TestContext.Current.CancellationToken, () =>
{
Assert.NotNull(_mainWindow);
@@ -98,7 +98,7 @@ public void Launch_ShowsPrimaryControls()
[Trait("Category", "UI")]
public void ContextMenu_Open_ShowsCoreCommands()
{
- RunWithFailureArtifacts(nameof(ContextMenu_Open_ShowsCoreCommands), () =>
+ RunWithFailureArtifacts(nameof(ContextMenu_Open_ShowsCoreCommands), TestContext.Current.CancellationToken, () =>
{
AutomationElement contextMenu = OpenContextMenu();
AutomationElement privacyModeMenuItem = FindMenuItemByLabel(contextMenu, Strings.Menu_PrivacyMode);
@@ -123,7 +123,7 @@ public void ContextMenu_Open_ShowsCoreCommands()
[Trait("Category", "UI")]
public void HelpDialog_Open_And_CloseSuccessfully()
{
- RunWithFailureArtifacts(nameof(HelpDialog_Open_And_CloseSuccessfully), () =>
+ RunWithFailureArtifacts(nameof(HelpDialog_Open_And_CloseSuccessfully), TestContext.Current.CancellationToken, () =>
{
AutomationElement contextMenu = OpenContextMenu();
AutomationElement helpMenuItem = FindMenuItemByLabel(contextMenu, Strings.Menu_Help);
@@ -159,7 +159,7 @@ public void HelpDialog_Open_And_CloseSuccessfully()
[Trait("Category", "UI")]
public void MinimizeOnReturn_ConfirmDialog_Open_And_CancelSuccessfully()
{
- RunWithFailureArtifacts(nameof(MinimizeOnReturn_ConfirmDialog_Open_And_CancelSuccessfully), () =>
+ RunWithFailureArtifacts(nameof(MinimizeOnReturn_ConfirmDialog_Open_And_CancelSuccessfully), TestContext.Current.CancellationToken, () =>
{
AutomationElement contextMenu = OpenContextMenu();
AutomationElement minimizeOnReturnMenuItem = FindMenuItemByLabel(contextMenu, Strings.Menu_MinimizeOnReturn);
@@ -193,7 +193,7 @@ public void MinimizeOnReturn_ConfirmDialog_Open_And_CancelSuccessfully()
[Trait("Category", "UI")]
public void SettingsGamepadSubMenu_Open_ShowsCoreCommands()
{
- RunWithFailureArtifacts(nameof(SettingsGamepadSubMenu_Open_ShowsCoreCommands), () =>
+ RunWithFailureArtifacts(nameof(SettingsGamepadSubMenu_Open_ShowsCoreCommands), TestContext.Current.CancellationToken, () =>
{
AutomationElement contextMenu = OpenContextMenu();
AutomationElement settingsMenuItem = FindMenuItemByLabel(contextMenu, Strings.Menu_Settings);
@@ -225,7 +225,7 @@ public void SettingsGamepadSubMenu_Open_ShowsCoreCommands()
[Trait("Category", "UI")]
public void GamepadCalibrationDialog_Open_And_CloseSuccessfully()
{
- RunWithFailureArtifacts(nameof(GamepadCalibrationDialog_Open_And_CloseSuccessfully), () =>
+ RunWithFailureArtifacts(nameof(GamepadCalibrationDialog_Open_And_CloseSuccessfully), TestContext.Current.CancellationToken, () =>
{
AutomationElement contextMenu = OpenContextMenu();
AutomationElement settingsMenuItem = FindMenuItemByLabel(contextMenu, Strings.Menu_Settings);
@@ -266,7 +266,7 @@ public void GamepadCalibrationDialog_Open_And_CloseSuccessfully()
[Trait("Category", "UI")]
public void PhraseSubMenu_Open_ShowsManagementCommands()
{
- RunWithFailureArtifacts(nameof(PhraseSubMenu_Open_ShowsManagementCommands), () =>
+ RunWithFailureArtifacts(nameof(PhraseSubMenu_Open_ShowsManagementCommands), TestContext.Current.CancellationToken, () =>
{
AutomationElement contextMenu = OpenContextMenu();
AutomationElement phrasesMenuItem = FindMenuItemByLabel(contextMenu, Strings.Menu_Phrases);
@@ -294,7 +294,7 @@ public void PhraseSubMenu_Open_ShowsManagementCommands()
[Trait("Category", "UI")]
public void PhraseManagerDialog_Open_And_CloseSuccessfully()
{
- RunWithFailureArtifacts(nameof(PhraseManagerDialog_Open_And_CloseSuccessfully), () =>
+ RunWithFailureArtifacts(nameof(PhraseManagerDialog_Open_And_CloseSuccessfully), TestContext.Current.CancellationToken, () =>
{
AutomationElement contextMenu = OpenContextMenu();
AutomationElement phrasesMenuItem = FindMenuItemByLabel(contextMenu, Strings.Menu_Phrases);
@@ -333,7 +333,7 @@ public void PhraseManagerDialog_Open_And_CloseSuccessfully()
[Trait("Category", "UI")]
public void PhraseEditDialog_Open_And_CancelSuccessfully()
{
- RunWithFailureArtifacts(nameof(PhraseEditDialog_Open_And_CancelSuccessfully), () =>
+ RunWithFailureArtifacts(nameof(PhraseEditDialog_Open_And_CancelSuccessfully), TestContext.Current.CancellationToken, () =>
{
AutomationElement contextMenu = OpenContextMenu();
AutomationElement phrasesMenuItem = FindMenuItemByLabel(contextMenu, Strings.Menu_Phrases);
@@ -386,7 +386,7 @@ public void PhraseEditDialog_Open_And_CancelSuccessfully()
[Trait("Category", "UI")]
public void CopyButton_Click_ClearsInput()
{
- RunWithFailureArtifacts(nameof(CopyButton_Click_ClearsInput), () =>
+ RunWithFailureArtifacts(nameof(CopyButton_Click_ClearsInput), TestContext.Current.CancellationToken, () =>
{
FlaUiTextBox inputTextBox = FindInputBox();
FlaUiButton copyButton = FindCopyButton();
@@ -417,7 +417,7 @@ public void CopyButton_Click_ClearsInput()
[Trait("Category", "UI")]
public void RestartPrompt_ConfirmYes_RelaunchedWindowStaysForeground()
{
- RunWithFailureArtifacts(nameof(RestartPrompt_ConfirmYes_RelaunchedWindowStaysForeground), () =>
+ RunWithFailureArtifacts(nameof(RestartPrompt_ConfirmYes_RelaunchedWindowStaysForeground), TestContext.Current.CancellationToken, () =>
{
Assert.NotNull(_application);
Assert.NotNull(_mainWindow);
@@ -1174,9 +1174,12 @@ private static void WaitUntil(
/// 在測試失敗時擷取桌面畫面與診斷文字,方便 GitHub Actions 回傳 Artifact。
///
/// 目前執行中的測試名稱。
+ /// xUnit 測試逾時所提供的取消權杖。
/// 實際要執行的測試邏輯。
- private void RunWithFailureArtifacts(string testName, Action testAction)
+ private void RunWithFailureArtifacts(string testName, CancellationToken cancellationToken, Action testAction)
{
+ cancellationToken.ThrowIfCancellationRequested();
+
try
{
MarkStep($"{testName}: started");
diff --git a/tests/InputBox.Tests/README.md b/tests/InputBox.Tests/README.md
index 94392dc..9f2df4b 100644
--- a/tests/InputBox.Tests/README.md
+++ b/tests/InputBox.Tests/README.md
@@ -142,6 +142,8 @@ xUnit v3 為每個 `[Fact]` 建立獨立的測試類別實例,`IDisposable.Dis
> 為避免 hosted runner 與本機 Agent 因自訂 modal dialog 的偵測不穩而長時間卡住,目前所有 UI 案例皆加上 fail-fast 逾時保護;dialog 案例會在此保護下持續驗證。
+> UI 冒煙測試會將 xUnit 測試逾時的 `TestContext.Current.CancellationToken` 傳入共用執行流程,確保 xUnit v3 MTP v2 4.0.0 能在逾時後及時終止測試。
+
> UI 元素比對一律優先使用資源檔的目前語系字串與 AutomationId,而非寫死繁中或英文標籤,因此 Hosted Runner 的系統語系變動不應成為這組 smoke test 的失敗來源。
### 6. 目標框架