WorkspacesEditingPageTests.cs
1 // Copyright (c) Microsoft Corporation 2 // The Microsoft Corporation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System.Diagnostics; 6 using Microsoft.PowerToys.UITest; 7 using Microsoft.VisualStudio.TestTools.UnitTesting; 8 9 namespace WorkspacesEditorUITest; 10 11 [TestClass] 12 [Ignore("not stable")] 13 public class WorkspacesEditingPageTests : WorkspacesUiAutomationBase 14 { 15 public WorkspacesEditingPageTests() 16 : base() 17 { 18 } 19 20 [TestMethod("WorkspacesEditingPage.RemoveApp")] 21 [TestCategory("Workspaces Editing Page UI")] 22 public void TestRemoveAppFromWorkspace() 23 { 24 // Find app list 25 var appList = Find<Custom>("AppList"); 26 var initialAppCount = appList.FindAll<Custom>(By.ClassName("AppItem")).Count; 27 28 if (initialAppCount == 0) 29 { 30 Assert.Inconclusive("No apps in workspace to remove"); 31 return; 32 } 33 34 // Remove first app 35 var firstApp = appList.FindAll<Custom>(By.ClassName("AppItem"))[0]; 36 var appName = firstApp.GetAttribute("Name"); 37 38 var removeButton = firstApp.Find<Button>("Remove"); 39 removeButton.Click(); 40 Thread.Sleep(500); 41 42 // Verify app removed from list 43 var finalAppCount = appList.FindAll<Custom>(By.ClassName("AppItem")).Count; 44 Assert.AreEqual(initialAppCount - 1, finalAppCount, "App should be removed from list"); 45 46 // Verify preview updated 47 var previewPane = Find<Pane>("Preview"); 48 var windowPreviews = previewPane.FindAll<Custom>(By.ClassName("WindowPreview")); 49 Assert.AreEqual(finalAppCount, windowPreviews.Count, "Preview should show correct number of windows"); 50 } 51 52 [TestMethod("WorkspacesEditingPage.RemoveAndAddBackApp")] 53 [TestCategory("Workspaces Editing Page UI")] 54 public void TestRemoveAndAddBackApp() 55 { 56 // Find app list 57 var appList = Find<Custom>("AppList"); 58 var apps = appList.FindAll<Custom>(By.ClassName("AppItem")); 59 60 if (apps.Count == 0) 61 { 62 Assert.Inconclusive("No apps in workspace to test"); 63 return; 64 } 65 66 var firstApp = apps[0]; 67 var appName = firstApp.GetAttribute("Name"); 68 69 // Remove app 70 var removeButton = firstApp.Find<Button>("Remove"); 71 removeButton.Click(); 72 Thread.Sleep(500); 73 74 // Verify removed app shows in "removed apps" section 75 Assert.IsTrue(Has<Button>("Add back"), "Should have 'Add back' button for removed apps"); 76 77 // Add back the app 78 var addBackButton = Find<Button>("Add back"); 79 addBackButton.Click(); 80 Thread.Sleep(500); 81 82 // Verify app is back in the list 83 var restoredApp = appList.Find<Custom>(By.Name(appName), timeoutMS: 2000); 84 Assert.IsNotNull(restoredApp, "App should be restored to the list"); 85 86 // Verify preview updated 87 var previewPane = Find<Pane>("Preview"); 88 var windowPreviews = previewPane.FindAll<Custom>(By.ClassName("WindowPreview")); 89 var currentAppCount = appList.FindAll<Custom>(By.ClassName("AppItem")).Count; 90 Assert.AreEqual(currentAppCount, windowPreviews.Count, "Preview should show all windows again"); 91 } 92 93 [TestMethod("WorkspacesEditingPage.SetAppMinimized")] 94 [TestCategory("Workspaces Editing Page UI")] 95 public void TestSetAppMinimized() 96 { 97 // Find first app 98 var appList = Find<Custom>("AppList"); 99 var apps = appList.FindAll<Custom>(By.ClassName("AppItem")); 100 101 if (apps.Count == 0) 102 { 103 Assert.Inconclusive("No apps in workspace to test"); 104 return; 105 } 106 107 var firstApp = apps[0]; 108 109 // Find and toggle minimized checkbox 110 var minimizedCheckbox = firstApp.Find<CheckBox>("Minimized"); 111 bool wasMinimized = minimizedCheckbox.IsChecked; 112 113 minimizedCheckbox.Click(); 114 Thread.Sleep(500); 115 116 // Verify state changed 117 Assert.AreNotEqual(wasMinimized, minimizedCheckbox.IsChecked, "Minimized state should toggle"); 118 119 // Verify preview reflects the change 120 var previewPane = Find<Pane>("Preview"); 121 var windowPreviews = previewPane.FindAll<Custom>(By.ClassName("WindowPreview")); 122 123 // The first window preview should indicate minimized state 124 if (minimizedCheckbox.IsChecked && windowPreviews.Count > 0) 125 { 126 var firstPreview = windowPreviews[0]; 127 var opacity = firstPreview.GetAttribute("Opacity"); 128 129 // Minimized windows might have reduced opacity or other visual indicator 130 Assert.IsNotNull(opacity, "Minimized window should have visual indication in preview"); 131 } 132 } 133 134 [TestMethod("WorkspacesEditingPage.SetAppMaximized")] 135 [TestCategory("Workspaces Editing Page UI")] 136 public void TestSetAppMaximized() 137 { 138 // Find first app 139 var appList = Find<Custom>("AppList"); 140 var apps = appList.FindAll<Custom>(By.ClassName("AppItem")); 141 142 if (apps.Count == 0) 143 { 144 Assert.Inconclusive("No apps in workspace to test"); 145 return; 146 } 147 148 var firstApp = apps[0]; 149 150 // Find and toggle maximized checkbox 151 var maximizedCheckbox = firstApp.Find<CheckBox>("Maximized"); 152 bool wasMaximized = maximizedCheckbox.IsChecked; 153 154 maximizedCheckbox.Click(); 155 Thread.Sleep(500); 156 157 // Verify state changed 158 Assert.AreNotEqual(wasMaximized, maximizedCheckbox.IsChecked, "Maximized state should toggle"); 159 160 // Verify preview reflects the change 161 var previewPane = Find<Pane>("Preview"); 162 if (maximizedCheckbox.IsChecked) 163 { 164 // Maximized window should fill the preview area 165 var windowPreviews = previewPane.FindAll<Custom>(By.ClassName("WindowPreview")); 166 if (windowPreviews.Count > 0) 167 { 168 var firstPreview = windowPreviews[0]; 169 170 // Check if preview shows maximized state 171 var width = firstPreview.GetAttribute("Width"); 172 var height = firstPreview.GetAttribute("Height"); 173 Assert.IsNotNull(width, "Maximized window should have width in preview"); 174 Assert.IsNotNull(height, "Maximized window should have height in preview"); 175 } 176 } 177 } 178 179 [TestMethod("WorkspacesEditingPage.LaunchAsAdmin")] 180 [TestCategory("Workspaces Editing Page UI")] 181 public void TestSetLaunchAsAdmin() 182 { 183 // Find app that supports admin launch 184 var appList = Find<Custom>("AppList"); 185 var apps = appList.FindAll<Custom>(By.ClassName("AppItem")); 186 187 bool foundAdminCapableApp = false; 188 foreach (var app in apps) 189 { 190 try 191 { 192 var adminCheckbox = app.Find<CheckBox>("Launch as admin", timeoutMS: 1000); 193 if (adminCheckbox != null && adminCheckbox.IsChecked) 194 { 195 foundAdminCapableApp = true; 196 bool wasAdmin = adminCheckbox.IsChecked; 197 198 adminCheckbox.Click(); 199 Thread.Sleep(500); 200 201 // Verify state changed 202 Assert.AreNotEqual(wasAdmin, adminCheckbox.IsChecked, "Admin launch state should toggle"); 203 break; 204 } 205 } 206 catch 207 { 208 // This app doesn't support admin launch 209 continue; 210 } 211 } 212 213 if (!foundAdminCapableApp) 214 { 215 Assert.Inconclusive("No apps in workspace support admin launch"); 216 } 217 } 218 219 [TestMethod("WorkspacesEditingPage.AddCLIArgs")] 220 [TestCategory("Workspaces Editing Page UI")] 221 public void TestAddCommandLineArguments() 222 { 223 // Find first app 224 var appList = Find<Custom>("AppList"); 225 var apps = appList.FindAll<Custom>(By.ClassName("AppItem")); 226 227 if (apps.Count == 0) 228 { 229 Assert.Inconclusive("No apps in workspace to test"); 230 return; 231 } 232 233 var firstApp = apps[0]; 234 235 // Find CLI args textbox 236 var cliArgsTextBox = firstApp.Find<TextBox>("Command line arguments", timeoutMS: 2000); 237 if (cliArgsTextBox == null) 238 { 239 Assert.Inconclusive("App does not support command line arguments"); 240 return; 241 } 242 243 // Add test arguments 244 string testArgs = "--test-arg value"; 245 cliArgsTextBox.SetText(testArgs); 246 Thread.Sleep(500); 247 248 // Verify arguments were entered 249 Assert.AreEqual(testArgs, cliArgsTextBox.Text, "Command line arguments should be set"); 250 } 251 252 [TestMethod("WorkspacesEditingPage.ChangeAppPosition")] 253 [TestCategory("Workspaces Editing Page UI")] 254 public void TestManuallyChangeAppPosition() 255 { 256 // Find first app 257 var appList = Find<Custom>("AppList"); 258 var apps = appList.FindAll<Custom>(By.ClassName("AppItem")); 259 260 if (apps.Count == 0) 261 { 262 Assert.Inconclusive("No apps in workspace to test"); 263 return; 264 } 265 266 var firstApp = apps[0]; 267 268 // Find position controls 269 var xPositionBox = firstApp.Find<TextBox>("X position", timeoutMS: 2000); 270 var yPositionBox = firstApp.Find<TextBox>("Y position", timeoutMS: 2000); 271 272 if (xPositionBox == null || yPositionBox == null) 273 { 274 // Try alternate approach with spinners 275 var positionSpinners = firstApp.FindAll<Custom>(By.ClassName("SpinBox")); 276 if (positionSpinners.Count >= 2) 277 { 278 xPositionBox = positionSpinners[0].Find<TextBox>(By.ClassName("TextBox")); 279 yPositionBox = positionSpinners[1].Find<TextBox>(By.ClassName("TextBox")); 280 } 281 } 282 283 if (xPositionBox != null && yPositionBox != null) 284 { 285 // Change position 286 xPositionBox.SetText("200"); 287 Thread.Sleep(500); 288 289 yPositionBox.SetText("150"); 290 Thread.Sleep(500); 291 292 // Verify preview updated 293 var previewPane = Find<Pane>("Preview"); 294 var windowPreviews = previewPane.FindAll<Custom>(By.ClassName("WindowPreview")); 295 Assert.IsTrue(windowPreviews.Count > 0, "Preview should show window at new position"); 296 } 297 else 298 { 299 Assert.Inconclusive("Could not find position controls"); 300 } 301 } 302 303 [TestMethod("WorkspacesEditingPage.ChangeWorkspaceName")] 304 [TestCategory("Workspaces Editing Page UI")] 305 public void TestChangeWorkspaceName() 306 { 307 // Find workspace name textbox 308 var nameTextBox = Find<TextBox>("Workspace name"); 309 string originalName = nameTextBox.Text; 310 311 // Change name 312 string newName = "Renamed_Workspace_" + DateTime.Now.Ticks; 313 nameTextBox.SetText(newName); 314 Thread.Sleep(500); 315 316 // Save changes 317 var saveButton = Find<Button>("Save"); 318 saveButton.Click(); 319 Thread.Sleep(1000); 320 321 // Verify we're back at main list 322 Assert.IsTrue(Has<Custom>("WorkspacesList"), "Should return to main list after saving"); 323 324 // Verify workspace was renamed 325 var workspacesList = Find<Custom>("WorkspacesList"); 326 var renamedWorkspace = workspacesList.Find<Custom>(By.Name(newName), timeoutMS: 2000); 327 Assert.IsNotNull(renamedWorkspace, "Workspace should be renamed in the list"); 328 } 329 330 [TestMethod("WorkspacesEditingPage.SaveAndCancelWork")] 331 [TestCategory("Workspaces Editing Page UI")] 332 public void TestSaveAndCancelButtons() 333 { 334 // Make a change 335 var nameTextBox = Find<TextBox>("Workspace name"); 336 string originalName = nameTextBox.Text; 337 string tempName = originalName + "_temp"; 338 339 nameTextBox.SetText(tempName); 340 Thread.Sleep(500); 341 342 // Test Cancel button 343 var cancelButton = Find<Button>("Cancel"); 344 cancelButton.Click(); 345 Thread.Sleep(1000); 346 347 // Verify returned to main list without saving 348 Assert.IsTrue(Has<Custom>("WorkspacesList"), "Should return to main list"); 349 350 // Go back to editing 351 var workspacesList = Find<Custom>("WorkspacesList"); 352 var workspace = workspacesList.FindAll<Custom>(By.ClassName("WorkspaceItem"))[0]; 353 workspace.Click(); 354 Thread.Sleep(1000); 355 356 // Verify name wasn't changed 357 nameTextBox = Find<TextBox>("Workspace name"); 358 Assert.AreEqual(originalName, nameTextBox.Text, "Name should not be changed after cancel"); 359 360 // Now test Save button 361 nameTextBox.SetText(tempName); 362 Thread.Sleep(500); 363 364 var saveButton = Find<Button>("Save"); 365 saveButton.Click(); 366 Thread.Sleep(1000); 367 368 // Verify saved 369 workspacesList = Find<Custom>("WorkspacesList"); 370 var savedWorkspace = workspacesList.Find<Custom>(By.Name(tempName), timeoutMS: 2000); 371 Assert.IsNotNull(savedWorkspace, "Workspace should be saved with new name"); 372 } 373 374 [TestMethod("WorkspacesEditingPage.NavigateWithoutSaving")] 375 [TestCategory("Workspaces Editing Page UI")] 376 public void TestNavigateToMainPageWithoutSaving() 377 { 378 // Make a change 379 var nameTextBox = Find<TextBox>("Workspace name"); 380 string originalName = nameTextBox.Text; 381 382 nameTextBox.SetText(originalName + "_unsaved"); 383 Thread.Sleep(500); 384 385 // Click on "Workspaces" navigation/breadcrumb 386 if (Has<NavigationViewItem>("Workspaces", timeoutMS: 1000)) 387 { 388 var workspacesNav = Find<NavigationViewItem>("Workspaces"); 389 workspacesNav.Click(); 390 Thread.Sleep(1000); 391 } 392 else if (Has<HyperlinkButton>("Workspaces", timeoutMS: 1000)) 393 { 394 var workspacesBreadcrumb = Find<HyperlinkButton>("Workspaces"); 395 workspacesBreadcrumb.Click(); 396 Thread.Sleep(1000); 397 } 398 399 // If there's a confirmation dialog, handle it 400 if (Has<Button>("Discard", timeoutMS: 1000)) 401 { 402 Find<Button>("Discard").Click(); 403 Thread.Sleep(500); 404 } 405 406 // Verify returned to main list 407 Assert.IsTrue(Has<Custom>("WorkspacesList"), "Should return to main list"); 408 409 // Verify changes weren't saved 410 var workspacesList = Find<Custom>("WorkspacesList"); 411 var unsavedWorkspace = workspacesList.Find<Custom>(By.Name(originalName + "_unsaved"), timeoutMS: 1000); 412 Assert.IsNull(unsavedWorkspace, "Unsaved changes should not persist"); 413 } 414 415 [TestMethod("WorkspacesEditingPage.CreateDesktopShortcut")] 416 [TestCategory("Workspaces Editing Page UI")] 417 public void TestCreateDesktopShortcut() 418 { 419 // Find desktop shortcut checkbox 420 var shortcutCheckbox = Find<CheckBox>("Create desktop shortcut"); 421 422 // Get desktop path 423 string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop); 424 425 // Get workspace name to check for shortcut 426 var nameTextBox = Find<TextBox>("Workspace name"); 427 string workspaceName = nameTextBox.Text; 428 string shortcutPath = Path.Combine(desktopPath, $"{workspaceName}.lnk"); 429 430 // Clean up any existing shortcut 431 if (File.Exists(shortcutPath)) 432 { 433 File.Delete(shortcutPath); 434 Thread.Sleep(500); 435 } 436 437 // Check the checkbox 438 if (!shortcutCheckbox.IsChecked) 439 { 440 shortcutCheckbox.Click(); 441 Thread.Sleep(500); 442 } 443 444 // Save 445 var saveButton = Find<Button>("Save"); 446 saveButton.Click(); 447 Thread.Sleep(2000); // Give time for shortcut creation 448 449 // Verify shortcut was created 450 Assert.IsTrue(File.Exists(shortcutPath), "Desktop shortcut should be created"); 451 452 // Clean up 453 if (File.Exists(shortcutPath)) 454 { 455 File.Delete(shortcutPath); 456 } 457 } 458 459 [TestMethod("WorkspacesEditingPage.DesktopShortcutState")] 460 [TestCategory("Workspaces Editing Page UI")] 461 public void TestDesktopShortcutCheckboxState() 462 { 463 // Get workspace name 464 var nameTextBox = Find<TextBox>("Workspace name"); 465 string workspaceName = nameTextBox.Text; 466 string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop); 467 string shortcutPath = Path.Combine(desktopPath, $"{workspaceName}.lnk"); 468 469 // Find checkbox 470 var shortcutCheckbox = Find<CheckBox>("Create desktop shortcut"); 471 472 // Test 1: When shortcut exists 473 if (!File.Exists(shortcutPath)) 474 { 475 // Create shortcut first 476 if (!shortcutCheckbox.IsChecked) 477 { 478 shortcutCheckbox.Click(); 479 Thread.Sleep(500); 480 } 481 482 Find<Button>("Save").Click(); 483 Thread.Sleep(2000); 484 485 // Navigate back to editing 486 NavigateToEditingPage(); 487 } 488 489 shortcutCheckbox = Find<CheckBox>("Create desktop shortcut"); 490 Assert.IsTrue(shortcutCheckbox.IsChecked, "Checkbox should be checked when shortcut exists"); 491 492 // Test 2: Remove shortcut 493 if (File.Exists(shortcutPath)) 494 { 495 File.Delete(shortcutPath); 496 Thread.Sleep(500); 497 } 498 499 // Re-navigate to refresh state 500 Find<Button>("Cancel").Click(); 501 Thread.Sleep(1000); 502 NavigateToEditingPage(); 503 504 shortcutCheckbox = Find<CheckBox>("Create desktop shortcut"); 505 Assert.IsFalse(shortcutCheckbox.IsChecked, "Checkbox should be unchecked when shortcut doesn't exist"); 506 } 507 508 [TestMethod("WorkspacesEditingPage.LaunchAndEdit")] 509 [TestCategory("Workspaces Editing Page UI")] 510 public void TestLaunchAndEditCapture() 511 { 512 // Find Launch and Edit button 513 var launchEditButton = Find<Button>("Launch and Edit"); 514 launchEditButton.Click(); 515 Thread.Sleep(3000); // Wait for apps to launch 516 517 // Open a new application 518 Process.Start("calc.exe"); 519 Thread.Sleep(2000); 520 521 // Click Capture 522 var captureButton = Find<Button>("Capture"); 523 captureButton.Click(); 524 Thread.Sleep(2000); 525 526 // Verify new app was added 527 var appList = Find<Custom>("AppList"); 528 var apps = appList.FindAll<Custom>(By.ClassName("AppItem")); 529 530 bool foundCalculator = false; 531 foreach (var app in apps) 532 { 533 var appName = app.GetAttribute("Name"); 534 if (appName.Contains("Calculator", StringComparison.OrdinalIgnoreCase)) 535 { 536 foundCalculator = true; 537 break; 538 } 539 } 540 541 Assert.IsTrue(foundCalculator, "Newly opened Calculator should be captured and added"); 542 543 // Clean up 544 foreach (var process in Process.GetProcessesByName("CalculatorApp")) 545 { 546 process.Kill(); 547 } 548 549 foreach (var process in Process.GetProcessesByName("Calculator")) 550 { 551 process.Kill(); 552 } 553 } 554 555 // Helper methods 556 private void NavigateToEditingPage() 557 { 558 // Ensure we have at least one workspace 559 if (!Has<Custom>("WorkspacesList", timeoutMS: 1000)) 560 { 561 CreateTestWorkspace(); 562 } 563 564 // Click on first workspace to edit 565 var workspacesList = Find<Custom>("WorkspacesList"); 566 var workspaceItems = workspacesList.FindAll<Custom>(By.ClassName("WorkspaceItem")); 567 568 if (workspaceItems.Count == 0) 569 { 570 CreateTestWorkspace(); 571 workspaceItems = workspacesList.FindAll<Custom>(By.ClassName("WorkspaceItem")); 572 } 573 574 workspaceItems[0].Click(); 575 Thread.Sleep(1000); 576 } 577 578 private void CreateTestWorkspace() 579 { 580 // Open a test app 581 Process.Start("notepad.exe"); 582 Thread.Sleep(1000); 583 584 // Create workspace 585 var createButton = Find<Button>("Create Workspace"); 586 createButton.Click(); 587 Thread.Sleep(1000); 588 589 // Capture 590 var captureButton = Find<Button>("Capture"); 591 captureButton.Click(); 592 Thread.Sleep(2000); 593 594 // Save with default name 595 var saveButton = Find<Button>("Save"); 596 saveButton.Click(); 597 Thread.Sleep(1000); 598 599 // Close test app 600 foreach (var process in Process.GetProcessesByName("notepad")) 601 { 602 process.Kill(); 603 } 604 } 605 }