From 153e9d8805ed13c7af5b1ed2cc677164aefe1392 Mon Sep 17 00:00:00 2001 From: raghulraja Date: Mon, 1 Dec 2025 17:36:34 +0530 Subject: [PATCH 1/3] Auto Generate Testcases for powerapps --- .../SolutionAnalyzer/MsAppAnalyzer.cs | 379 +++++++++++++++ .../SolutionAnalyzer/MsAppUnpacker.cs | 46 ++ .../SolutionAnalyzer/SolutionExtractor.cs | 61 +++ .../TestCaseGenerator/TestCaseGenerator.cs | 456 ++++++++++++++++++ src/PowerAppsTestEngine.sln | 6 + src/TestCaseGenerator/Program.cs | 190 ++++++++ .../TestCaseGenerator.csproj | 15 + 7 files changed, 1153 insertions(+) create mode 100644 src/Microsoft.PowerApps.TestEngine/SolutionAnalyzer/MsAppAnalyzer.cs create mode 100644 src/Microsoft.PowerApps.TestEngine/SolutionAnalyzer/MsAppUnpacker.cs create mode 100644 src/Microsoft.PowerApps.TestEngine/SolutionAnalyzer/SolutionExtractor.cs create mode 100644 src/Microsoft.PowerApps.TestEngine/TestCaseGenerator/TestCaseGenerator.cs create mode 100644 src/TestCaseGenerator/Program.cs create mode 100644 src/TestCaseGenerator/TestCaseGenerator.csproj diff --git a/src/Microsoft.PowerApps.TestEngine/SolutionAnalyzer/MsAppAnalyzer.cs b/src/Microsoft.PowerApps.TestEngine/SolutionAnalyzer/MsAppAnalyzer.cs new file mode 100644 index 00000000..0d20b8e8 --- /dev/null +++ b/src/Microsoft.PowerApps.TestEngine/SolutionAnalyzer/MsAppAnalyzer.cs @@ -0,0 +1,379 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Text.Json; +using System.Text.RegularExpressions; + +namespace Microsoft.PowerApps.TestEngine.SolutionAnalyzer +{ + public class MsAppAnalyzer + { + public AppStructure AnalyzeMsApp(string msappFilePath) + { + var tempExtractPath = Path.Combine(Path.GetTempPath(), $"msapp_{Guid.NewGuid()}"); + + try + { + Console.WriteLine($"DEBUG: Extracting msapp to: {tempExtractPath}"); + Directory.CreateDirectory(tempExtractPath); + ZipFile.ExtractToDirectory(msappFilePath, tempExtractPath); + + Console.WriteLine("DEBUG: Extracted structure:"); + ListDirectory(tempExtractPath, " "); + + var appStructure = new AppStructure + { + AppName = Path.GetFileNameWithoutExtension(msappFilePath), + Screens = new List() + }; + + // First try to parse Src folder (unpacked format with YAML files) + var srcPath = Path.Combine(tempExtractPath, "Src"); + if (Directory.Exists(srcPath)) + { + Console.WriteLine("DEBUG: Found Src folder - parsing YAML files"); + ParseSrcFolder(srcPath, appStructure); + } + + // If no controls found, try CanvasManifest + Controls folder + if (appStructure.Screens.Sum(s => s.Controls.Count) == 0) + { + Console.WriteLine("DEBUG: No controls from Src folder, trying Controls folder"); + ParseFromCanvasManifest(tempExtractPath, appStructure); + ParseControlsFolder(tempExtractPath, appStructure); + } + + Console.WriteLine($"DEBUG: Analysis complete - found {appStructure.Screens.Count} screens with total {appStructure.Screens.Sum(s => s.Controls.Count)} controls"); + return appStructure; + } + finally + { + if (Directory.Exists(tempExtractPath)) + { + try { Directory.Delete(tempExtractPath, true); } + catch { /* Ignore cleanup errors */ } + } + } + } + + private void ListDirectory(string path, string indent) + { + try + { + foreach (var dir in Directory.GetDirectories(path)) + { + Console.WriteLine($"{indent}DIR: {Path.GetFileName(dir)}"); + } + foreach (var file in Directory.GetFiles(path)) + { + Console.WriteLine($"{indent}FILE: {Path.GetFileName(file)}"); + } + } + catch { } + } + + private void ParseFromCanvasManifest(string extractPath, AppStructure appStructure) + { + try + { + var canvasManifestPath = Path.Combine(extractPath, "CanvasManifest.json"); + if (!File.Exists(canvasManifestPath)) + { + Console.WriteLine("DEBUG: CanvasManifest.json not found"); + return; + } + + Console.WriteLine("DEBUG: Parsing CanvasManifest.json"); + var json = File.ReadAllText(canvasManifestPath); + var doc = JsonDocument.Parse(json); + + if (doc.RootElement.TryGetProperty("Screens", out var screens)) + { + Console.WriteLine($"DEBUG: Found {screens.GetArrayLength()} screens in manifest"); + foreach (var screen in screens.EnumerateArray()) + { + if (screen.TryGetProperty("Name", out var screenName)) + { + var name = screenName.GetString(); + Console.WriteLine($"DEBUG: Processing screen: {name}"); + + var screenInfo = new ScreenInfo + { + Name = name, + Controls = new List() + }; + appStructure.Screens.Add(screenInfo); + } + } + } + } + catch (Exception ex) + { + Console.WriteLine($"DEBUG: Error parsing CanvasManifest: {ex.Message}"); + } + } + + private void ParseControlsFolder(string extractPath, AppStructure appStructure) + { + var controlsPath = Path.Combine(extractPath, "Controls"); + + if (!Directory.Exists(controlsPath)) + { + Console.WriteLine("DEBUG: Controls folder not found"); + return; + } + + Console.WriteLine($"DEBUG: Parsing Controls folder"); + + // Get all JSON files in Controls folder + var jsonFiles = Directory.GetFiles(controlsPath, "*.json", SearchOption.AllDirectories); + Console.WriteLine($"DEBUG: Found {jsonFiles.Length} control definition files"); + + foreach (var jsonFile in jsonFiles) + { + var screenName = Path.GetFileNameWithoutExtension(jsonFile).Replace(".json", ""); + Console.WriteLine($"DEBUG: Parsing controls for: {screenName}"); + + // Find the corresponding screen + var screen = appStructure.Screens.FirstOrDefault(s => + s.Name.Equals(screenName, StringComparison.OrdinalIgnoreCase)); + + if (screen == null) + { + // Create screen if not found + screen = new ScreenInfo + { + Name = screenName, + Controls = new List() + }; + appStructure.Screens.Add(screen); + } + + // Parse the JSON to extract controls + ParseControlJson(jsonFile, screen.Controls); + + Console.WriteLine($"DEBUG: Found {screen.Controls.Count} controls in {screenName}"); + } + } + + private void ParseControlJson(string jsonFilePath, List controls) + { + try + { + var json = File.ReadAllText(jsonFilePath); + var doc = JsonDocument.Parse(json); + + // Parse the root element + ParseJsonControl(doc.RootElement, controls); + } + catch (Exception ex) + { + Console.WriteLine($"DEBUG: Error parsing control JSON {jsonFilePath}: {ex.Message}"); + } + } + + private void ParseJsonControl(JsonElement element, List controls) + { + string controlName = null; + string controlType = "Unknown"; + + // Extract control name + if (element.TryGetProperty("Name", out var nameEl)) + controlName = nameEl.GetString(); + + // Extract control type + if (element.TryGetProperty("Template", out var templateEl)) + controlType = templateEl.GetString(); + else if (element.TryGetProperty("Type", out var typeEl)) + controlType = typeEl.GetString(); + else if (element.TryGetProperty("ControlType", out var ctrlTypeEl)) + controlType = ctrlTypeEl.GetString(); + + // Add control if valid + if (!string.IsNullOrEmpty(controlName) && !controlType.Equals("screen", StringComparison.OrdinalIgnoreCase)) + { + Console.WriteLine($"DEBUG: Control: {controlName} ({controlType})"); + controls.Add(new ControlInfo + { + Name = controlName, + Type = controlType, + Properties = new Dictionary() + }); + } + + // Recursively parse child controls + if (element.TryGetProperty("Children", out var children)) + { + foreach (var child in children.EnumerateArray()) + { + ParseJsonControl(child, controls); + } + } + } + + private void ParseSrcFolder(string srcPath, AppStructure appStructure) + { + Console.WriteLine("DEBUG: Listing files in Src folder:"); + var allFiles = Directory.GetFiles(srcPath, "*.*", SearchOption.TopDirectoryOnly); + foreach (var file in allFiles) + { + Console.WriteLine($" - {Path.GetFileName(file)}"); + } + + // Look for .fx.yaml, .pa.yaml, or .yaml files + var yamlFiles = allFiles.Where(f => + f.EndsWith(".fx.yaml", StringComparison.OrdinalIgnoreCase) || + f.EndsWith(".pa.yaml", StringComparison.OrdinalIgnoreCase) || + f.EndsWith(".yaml", StringComparison.OrdinalIgnoreCase) + ).ToList(); + + Console.WriteLine($"DEBUG: Found {yamlFiles.Count} YAML files to parse"); + + foreach (var yamlFile in yamlFiles) + { + var fileName = Path.GetFileNameWithoutExtension(yamlFile); + + // Skip App files + if (fileName.Equals("App", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("App.", StringComparison.OrdinalIgnoreCase)) + { + Console.WriteLine($"DEBUG: Skipping App file: {fileName}"); + continue; + } + + Console.WriteLine($"DEBUG: Parsing YAML file: {fileName}"); + var screenInfo = ParseYamlFile(yamlFile); + if (screenInfo != null && screenInfo.Controls.Count > 0) + { + appStructure.Screens.Add(screenInfo); + Console.WriteLine($"DEBUG: Added screen '{screenInfo.Name}' with {screenInfo.Controls.Count} controls"); + } + } + } + + private ScreenInfo ParseYamlFile(string filePath) + { + try + { + var content = File.ReadAllText(filePath); + var fileName = Path.GetFileNameWithoutExtension(filePath); + fileName = fileName.Replace(".fx", "").Replace(".pa", ""); + + Console.WriteLine($"DEBUG: ========== Parsing {fileName} =========="); + + var screenInfo = new ScreenInfo + { + Name = fileName, + Controls = new List() + }; + + var lines = content.Split(new[] { '\r', '\n' }, StringSplitOptions.None); + + // Parse the new YAML format where controls are under Children + bool inChildrenSection = false; + + for (int i = 0; i < lines.Length; i++) + { + var line = lines[i]; + + // Check if we're entering Children section + if (line.Trim() == "Children:") + { + inChildrenSection = true; + Console.WriteLine($"DEBUG: Found Children section at line {i}"); + continue; + } + + // If we're in Children section, look for control definitions + if (inChildrenSection) + { + // Match pattern: " - ControlName:" + var match = Regex.Match(line, @"^\s+- (\w+):\s*$"); + if (match.Success) + { + var controlName = match.Groups[1].Value; + + // Look ahead for Control type in next lines + string controlType = "Unknown"; + for (int j = i + 1; j < Math.Min(i + 5, lines.Length); j++) + { + var nextLine = lines[j]; + // Match: " Control: FluentV8/Label@1.8.6" + var controlMatch = Regex.Match(nextLine, @"Control:\s+([\w/]+)"); + if (controlMatch.Success) + { + var fullType = controlMatch.Groups[1].Value; + // Extract the control type after the last / + // "FluentV8/Label@1.8.6" -> "Label" + var parts = fullType.Split('/'); + if (parts.Length > 1) + { + controlType = parts[parts.Length - 1].Split('@')[0]; + } + else + { + controlType = fullType.Split('@')[0]; + } + break; + } + } + + Console.WriteLine($"DEBUG: [FOUND] Control: {controlName} | Type: {controlType}"); + + screenInfo.Controls.Add(new ControlInfo + { + Name = controlName, + Type = controlType, + Properties = new Dictionary() + }); + } + + // Exit Children section if indentation decreases significantly + if (!string.IsNullOrWhiteSpace(line) && !line.StartsWith(" ")) + { + inChildrenSection = false; + Console.WriteLine($"DEBUG: Exiting Children section at line {i}"); + } + } + } + + Console.WriteLine($"DEBUG: ========== Summary for {fileName} =========="); + Console.WriteLine($"DEBUG: Total controls found: {screenInfo.Controls.Count}"); + foreach (var ctrl in screenInfo.Controls) + { + Console.WriteLine($"DEBUG: - {ctrl.Name} ({ctrl.Type})"); + } + Console.WriteLine($"DEBUG: =========================================="); + + return screenInfo; + } + catch (Exception ex) + { + Console.WriteLine($"DEBUG: ERROR parsing YAML {filePath}:"); + Console.WriteLine($"DEBUG: {ex.Message}"); + return null; + } + } + } + + public class AppStructure + { + public string AppName { get; set; } + public List Screens { get; set; } + } + + public class ScreenInfo + { + public string Name { get; set; } + public List Controls { get; set; } + } + + public class ControlInfo + { + public string Name { get; set; } + public string Type { get; set; } + public Dictionary Properties { get; set; } + } +} diff --git a/src/Microsoft.PowerApps.TestEngine/SolutionAnalyzer/MsAppUnpacker.cs b/src/Microsoft.PowerApps.TestEngine/SolutionAnalyzer/MsAppUnpacker.cs new file mode 100644 index 00000000..12f4d196 --- /dev/null +++ b/src/Microsoft.PowerApps.TestEngine/SolutionAnalyzer/MsAppUnpacker.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.IO.Compression; + +namespace Microsoft.PowerApps.TestEngine.SolutionAnalyzer +{ + public class MsAppUnpacker + { + public string UnpackMsApp(string msappPath, string outputPath) + { + Console.WriteLine($"DEBUG: Unpacking msapp: {msappPath}"); + + // Create temporary directory for unpacking + var unpackDir = Path.Combine(outputPath, $"unpacked_{Guid.NewGuid()}"); + Directory.CreateDirectory(unpackDir); + + try + { + // Extract msapp as ZIP first + var tempExtract = Path.Combine(Path.GetTempPath(), $"msapp_temp_{Guid.NewGuid()}"); + Directory.CreateDirectory(tempExtract); + ZipFile.ExtractToDirectory(msappPath, tempExtract); + + // Check if already unpacked (has Src folder) + var srcFolder = Path.Combine(tempExtract, "Src"); + if (Directory.Exists(srcFolder)) + { + Console.WriteLine("DEBUG: msapp is already in unpacked format"); + return tempExtract; + } + + // If packed, we need to use PASopa to unpack + // For now, we'll work with the extracted structure + Console.WriteLine("DEBUG: Working with extracted msapp structure"); + return tempExtract; + } + catch (Exception ex) + { + Console.WriteLine($"DEBUG: Error unpacking msapp: {ex.Message}"); + throw; + } + } + } +} diff --git a/src/Microsoft.PowerApps.TestEngine/SolutionAnalyzer/SolutionExtractor.cs b/src/Microsoft.PowerApps.TestEngine/SolutionAnalyzer/SolutionExtractor.cs new file mode 100644 index 00000000..ce1537be --- /dev/null +++ b/src/Microsoft.PowerApps.TestEngine/SolutionAnalyzer/SolutionExtractor.cs @@ -0,0 +1,61 @@ +using System; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Xml.Linq; + +namespace Microsoft.PowerApps.TestEngine.SolutionAnalyzer +{ + public class SolutionExtractor + { + public string ExtractSolution(string solutionZipPath, string extractionPath) + { + if (!File.Exists(solutionZipPath)) + throw new FileNotFoundException($"Solution file not found: {solutionZipPath}"); + + if (Directory.Exists(extractionPath)) + Directory.Delete(extractionPath, true); + + Directory.CreateDirectory(extractionPath); + ZipFile.ExtractToDirectory(solutionZipPath, extractionPath); + + return extractionPath; + } + + public string FindMsAppFile(string extractedSolutionPath) + { + var canvasAppsPath = Path.Combine(extractedSolutionPath, "CanvasApps"); + + if (!Directory.Exists(canvasAppsPath)) + throw new DirectoryNotFoundException("CanvasApps folder not found in solution"); + + var msappFiles = Directory.GetFiles(canvasAppsPath, "*.msapp", SearchOption.AllDirectories); + + if (msappFiles.Length == 0) + throw new FileNotFoundException("No .msapp files found in solution"); + + return msappFiles[0]; + } + + public string GetAppLogicalName(string extractedSolutionPath, string msappFileName) + { + var customizationsPath = Path.Combine(extractedSolutionPath, "customizations.xml"); + + if (!File.Exists(customizationsPath)) + return Path.GetFileNameWithoutExtension(msappFileName); + + try + { + var doc = XDocument.Load(customizationsPath); + var canvasApp = doc.Descendants("CanvasApp") + .FirstOrDefault(x => x.Element("Name")?.Value == Path.GetFileNameWithoutExtension(msappFileName)); + + return canvasApp?.Element("UniqueName")?.Value ?? Path.GetFileNameWithoutExtension(msappFileName); + } + catch + { + return Path.GetFileNameWithoutExtension(msappFileName); + } + } + } +} diff --git a/src/Microsoft.PowerApps.TestEngine/TestCaseGenerator/TestCaseGenerator.cs b/src/Microsoft.PowerApps.TestEngine/TestCaseGenerator/TestCaseGenerator.cs new file mode 100644 index 00000000..940422ab --- /dev/null +++ b/src/Microsoft.PowerApps.TestEngine/TestCaseGenerator/TestCaseGenerator.cs @@ -0,0 +1,456 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.PowerApps.TestEngine.SolutionAnalyzer; + +namespace Microsoft.PowerApps.TestEngine.TestCaseGenerator +{ + public class TestCaseGenerator + { + public string GenerateTestPlan(AppStructure appStructure, string appLogicalName, string environmentId, string tenantId) + { + var sb = new StringBuilder(); + + // Test Suite + sb.AppendLine("testSuite:"); + sb.AppendLine($" testSuiteName: {appStructure.AppName} Complete Tests"); + sb.AppendLine($" testSuiteDescription: Comprehensive test cases for {appStructure.AppName} app controls."); + sb.AppendLine(" persona: User1"); + sb.AppendLine($" appLogicalName: {appLogicalName}"); + sb.AppendLine(); + sb.AppendLine(" testCases:"); + + foreach (var screen in appStructure.Screens) + { + sb.AppendLine($" # {screen.Name} Screen Test Cases"); + GenerateScreenTestCases(sb, screen); + } + + // Test Settings + sb.AppendLine(); + sb.AppendLine("testSettings:"); + sb.AppendLine(" headless: false"); + sb.AppendLine(" locale: \"en-US\""); + sb.AppendLine(" recordVideo: true"); + sb.AppendLine(" extensionModules:"); + sb.AppendLine(" enable: true"); + sb.AppendLine(" browserConfigurations:"); + sb.AppendLine(" - browser: Chromium"); + sb.AppendLine(" channel: msedge"); + sb.AppendLine(); + + // Environment Variables + sb.AppendLine("environmentVariables:"); + sb.AppendLine(" users:"); + sb.AppendLine(" - personaName: User1"); + sb.AppendLine(" emailKey: user1Email"); + sb.AppendLine(" passwordKey: NotNeeded"); + + return sb.ToString(); + } + + private void GenerateScreenTestCases(StringBuilder sb, ScreenInfo screen) + { + foreach (var control in screen.Controls) + { + var controlType = control.Type.ToLower(); + + sb.AppendLine($" # {control.Name} Test Cases"); + + if (controlType.Contains("label")) + { + GenerateComprehensiveLabelTestCases(sb, control, screen.Name); + } + else if (controlType.Contains("textbox") || controlType.Contains("textinput") || controlType.Contains("text")) + { + GenerateComprehensiveTextBoxTestCases(sb, control, screen.Name); + } + else if (controlType.Contains("button")) + { + GenerateComprehensiveButtonTestCases(sb, control, screen.Name); + } + else if (controlType.Contains("combobox") || controlType.Contains("dropdown")) + { + GenerateComprehensiveComboboxTestCases(sb, control, screen.Name); + } + else if (controlType.Contains("checkbox")) + { + GenerateComprehensiveCheckboxTestCases(sb, control, screen.Name); + } + else if (controlType.Contains("datepicker")) + { + GenerateComprehensiveDatePickerTestCases(sb, control, screen.Name); + } + else if (controlType.Contains("radio")) + { + GenerateComprehensiveRadioGroupTestCases(sb, control, screen.Name); + } + else if (controlType.Contains("slider")) + { + GenerateComprehensiveSliderTestCases(sb, control, screen.Name); + } + else if (controlType.Contains("toggle")) + { + GenerateComprehensiveToggleTestCases(sb, control, screen.Name); + } + else if (controlType.Contains("gallery")) + { + GenerateComprehensiveGalleryTestCases(sb, control, screen.Name); + } + else + { + GenerateGenericTestCases(sb, control, screen.Name); + } + } + } + + private void GenerateComprehensiveLabelTestCases(StringBuilder sb, ControlInfo control, string screenName) + { + // Text Property Test + sb.AppendLine($" - testCaseName: Test {control.Name} Text Property"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} Text property can be set and retrieved correctly."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.Text, \"Test Value\");"); + sb.AppendLine($" Assert({control.Name}.Text = \"Test Value\", \"Expected {control.Name}.Text to be 'Test Value'\");"); + sb.AppendLine(); + + // Empty Text Test + sb.AppendLine($" - testCaseName: Test {control.Name} Empty Text"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} Text property can be set to empty string."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.Text, \"\");"); + sb.AppendLine($" Assert({control.Name}.Text = \"\", \"Expected {control.Name}.Text to be empty\");"); + sb.AppendLine(); + + // Long Text Test + sb.AppendLine($" - testCaseName: Test {control.Name} Long Text"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} can handle long text without issues."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.Text, \"This is a very long text to check if the label can handle it without any issues.\");"); + sb.AppendLine($" Assert({control.Name}.Text = \"This is a very long text to check if the label can handle it without any issues.\", \"Expected {control.Name} to display long text\");"); + sb.AppendLine(); + + // Special Characters Test + sb.AppendLine($" - testCaseName: Test {control.Name} Special Characters"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} displays special characters correctly."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.Text, \"Special@#$%^&*!:\");"); + sb.AppendLine($" Assert({control.Name}.Text = \"Special@#$%^&*!:\", \"Expected {control.Name} to display special characters\");"); + sb.AppendLine(); + + // Numeric Text Test + sb.AppendLine($" - testCaseName: Test {control.Name} Numeric Text"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} can display numeric text."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.Text, \"1234567890\");"); + sb.AppendLine($" Assert({control.Name}.Text = \"1234567890\", \"Expected {control.Name}.Text to be numeric text\");"); + sb.AppendLine(); + + // Visible Property True Test + sb.AppendLine($" - testCaseName: Test {control.Name} Visible Property True"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} visibility can be set to true."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.Visible, true);"); + sb.AppendLine($" Assert({control.Name}.Visible = true, \"Expected {control.Name}.Visible to be true\");"); + sb.AppendLine(); + + // Visible Property False Test + sb.AppendLine($" - testCaseName: Test {control.Name} Visible Property False"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} visibility can be set to false."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.Visible, false);"); + sb.AppendLine($" Assert({control.Name}.Visible = false, \"Expected {control.Name}.Visible to be false\");"); + sb.AppendLine(); + + // Visible Toggle Test + sb.AppendLine($" - testCaseName: Test {control.Name} Visible Toggle"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} visibility can be toggled."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.Visible, true);"); + sb.AppendLine($" Assert({control.Name}.Visible = true, \"Expected {control.Name} to be visible\");"); + sb.AppendLine($" SetProperty({control.Name}.Visible, false);"); + sb.AppendLine($" Assert({control.Name}.Visible = false, \"Expected {control.Name} to be hidden\");"); + sb.AppendLine(); + } + + private void GenerateComprehensiveComboboxTestCases(StringBuilder sb, ControlInfo control, string screenName) + { + // Visible Property True + sb.AppendLine($" - testCaseName: Test {control.Name} Visible Property True"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} visibility can be set to true."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.Visible, true);"); + sb.AppendLine($" Assert({control.Name}.Visible = true, \"Expected {control.Name}.Visible to be true\");"); + sb.AppendLine(); + + // Visible Property False + sb.AppendLine($" - testCaseName: Test {control.Name} Visible Property False"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} visibility can be set to false."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.Visible, false);"); + sb.AppendLine($" Assert({control.Name}.Visible = false, \"Expected {control.Name}.Visible to be false\");"); + sb.AppendLine(); + + // Visible Toggle + sb.AppendLine($" - testCaseName: Test {control.Name} Visible Toggle"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} visibility can be toggled correctly."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.Visible, true);"); + sb.AppendLine($" Assert({control.Name}.Visible = true, \"Expected {control.Name} to be visible\");"); + sb.AppendLine($" SetProperty({control.Name}.Visible, false);"); + sb.AppendLine($" Assert({control.Name}.Visible = false, \"Expected {control.Name} to be hidden\");"); + sb.AppendLine(); + + // Items Available + sb.AppendLine($" - testCaseName: Test {control.Name} Items Available"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} has items in its data source."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" Assert(CountRows({control.Name}.Items) >= 0, \"Expected {control.Name}.Items to be available\");"); + sb.AppendLine(); + + // Items Not Empty + sb.AppendLine($" - testCaseName: Test {control.Name} Items Not Empty"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} Items collection is not empty."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" Assert(!IsEmpty({control.Name}.Items), \"Expected {control.Name} to have items available\");"); + sb.AppendLine(); + + // Select Single Item + sb.AppendLine($" - testCaseName: Test {control.Name} Select Single Item"); + sb.AppendLine($" testCaseDescription: Verify that a single item can be selected in {control.Name}."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.SelectedItems, Table({{Value:\"Test Item\", ID:1}}));"); + sb.AppendLine($" Assert(CountRows({control.Name}.SelectedItems) = 1, \"Expected one item to be selected\");"); + sb.AppendLine(); + + // Select Multiple Items + sb.AppendLine($" - testCaseName: Test {control.Name} Select Multiple Items"); + sb.AppendLine($" testCaseDescription: Verify that multiple items can be selected in {control.Name}."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.SelectedItems, Table("); + sb.AppendLine(" {Value:\"Item A\", ID:1},"); + sb.AppendLine(" {Value:\"Item B\", ID:2},"); + sb.AppendLine(" {Value:\"Item C\", ID:3}"); + sb.AppendLine(" ));"); + sb.AppendLine($" Assert(CountRows({control.Name}.SelectedItems) = 3, \"Expected three items to be selected\");"); + sb.AppendLine(); + + // Select Five Items + sb.AppendLine($" - testCaseName: Test {control.Name} Select Five Items"); + sb.AppendLine($" testCaseDescription: Verify that five items can be selected in {control.Name}."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.SelectedItems, Table("); + sb.AppendLine(" {Value:\"Item 1\", ID:1},"); + sb.AppendLine(" {Value:\"Item 2\", ID:2},"); + sb.AppendLine(" {Value:\"Item 3\", ID:3},"); + sb.AppendLine(" {Value:\"Item 4\", ID:4},"); + sb.AppendLine(" {Value:\"Item 5\", ID:5}"); + sb.AppendLine(" ));"); + sb.AppendLine($" Assert(CountRows({control.Name}.SelectedItems) = 5, \"Expected five items to be selected\");"); + sb.AppendLine(); + + // Clear Selection + sb.AppendLine($" - testCaseName: Test {control.Name} Clear Selection"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} selection can be cleared to empty table."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.SelectedItems, Table());"); + sb.AppendLine($" Assert(CountRows({control.Name}.SelectedItems) = 0, \"Expected no items to be selected\");"); + sb.AppendLine(); + + // Select First Item + sb.AppendLine($" - testCaseName: Test {control.Name} Select First Item"); + sb.AppendLine($" testCaseDescription: Verify that the first item can be selected from available items."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.SelectedItems, FirstN({control.Name}.Items, 1));"); + sb.AppendLine($" Assert(CountRows({control.Name}.SelectedItems) <= 1, \"Expected at most one item to be selected\");"); + sb.AppendLine(); + + // Select First Two Items + sb.AppendLine($" - testCaseName: Test {control.Name} Select First Two Items"); + sb.AppendLine($" testCaseDescription: Verify that the first two items can be selected from available items."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.SelectedItems, FirstN({control.Name}.Items, 2));"); + sb.AppendLine($" Assert(CountRows({control.Name}.SelectedItems) <= 2, \"Expected at most two items to be selected\");"); + sb.AppendLine(); + + // Reselect After Clear + sb.AppendLine($" - testCaseName: Test {control.Name} Reselect After Clear"); + sb.AppendLine($" testCaseDescription: Verify selection works after clearing."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.SelectedItems, Table());"); + sb.AppendLine($" Assert(CountRows({control.Name}.SelectedItems) = 0, \"Expected no items selected after clear\");"); + sb.AppendLine($" SetProperty({control.Name}.SelectedItems, Table({{Value:\"New Item\", ID:999}}));"); + sb.AppendLine($" Assert(CountRows({control.Name}.SelectedItems) = 1, \"Expected one item to be selected after reselection\");"); + sb.AppendLine(); + + // Multiple Select Cycles + sb.AppendLine($" - testCaseName: Test {control.Name} Multiple Select Cycles"); + sb.AppendLine($" testCaseDescription: Verify multiple selection and deselection cycles."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.SelectedItems, Table({{Value:\"Item X\", ID:10}}));"); + sb.AppendLine($" Assert(CountRows({control.Name}.SelectedItems) = 1, \"Expected one item selected in first cycle\");"); + sb.AppendLine($" SetProperty({control.Name}.SelectedItems, Table());"); + sb.AppendLine($" Assert(CountRows({control.Name}.SelectedItems) = 0, \"Expected no items after clear\");"); + sb.AppendLine($" SetProperty({control.Name}.SelectedItems, Table({{Value:\"Item Y\", ID:20}}));"); + sb.AppendLine($" Assert(CountRows({control.Name}.SelectedItems) = 1, \"Expected one item selected in second cycle\");"); + sb.AppendLine(); + } + + private void GenerateComprehensiveTextBoxTestCases(StringBuilder sb, ControlInfo control, string screenName) + { + // Sample Text Test - Use .Value for TextBox + sb.AppendLine($" - testCaseName: Test {control.Name} Sample Text"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} accepts and displays input correctly."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.Value, \"Sample Text\");"); + sb.AppendLine($" Assert({control.Name}.Value = \"Sample Text\", \"Verify {control.Name} displays the input text correctly\");"); + sb.AppendLine(); + + // Empty Value Test + sb.AppendLine($" - testCaseName: Test {control.Name} Empty"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} can be set to empty string."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.Value, \"\");"); + sb.AppendLine($" Assert({control.Name}.Value = \"\", \"Expected {control.Name}.Value to be empty\");"); + sb.AppendLine(); + + // Long Text Test + sb.AppendLine($" - testCaseName: Test {control.Name} Long Text"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} can handle long text."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.Value, \"This is a very long text to check if the TextBox can handle it without any issues.\");"); + sb.AppendLine($" Assert({control.Name}.Value = \"This is a very long text to check if the TextBox can handle it without any issues.\", \"Expected {control.Name}.Value to be the long text\");"); + sb.AppendLine(); + + // Special Characters Test + sb.AppendLine($" - testCaseName: Test {control.Name} Special Characters"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} handles special characters."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.Value, \"Special@#$%^&*()!\");"); + sb.AppendLine($" Assert({control.Name}.Value = \"Special@#$%^&*()!\", \"Expected {control.Name} to handle special characters\");"); + sb.AppendLine(); + + // Numeric Text Test + sb.AppendLine($" - testCaseName: Test {control.Name} Numeric Text"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} can handle numeric text."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.Value, \"1234567890\");"); + sb.AppendLine($" Assert({control.Name}.Value = \"1234567890\", \"Expected {control.Name}.Value to be numeric text\");"); + sb.AppendLine(); + + // Visible Property Tests + sb.AppendLine($" - testCaseName: Test {control.Name} Visible Property True"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} visibility can be set to true."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.Visible, true);"); + sb.AppendLine($" Assert({control.Name}.Visible = true, \"Expected {control.Name}.Visible to be true\");"); + sb.AppendLine(); + + sb.AppendLine($" - testCaseName: Test {control.Name} Visible Property False"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} visibility can be set to false."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.Visible, false);"); + sb.AppendLine($" Assert({control.Name}.Visible = false, \"Expected {control.Name}.Visible to be false\");"); + sb.AppendLine(); + + sb.AppendLine($" - testCaseName: Test {control.Name} Visible Toggle"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} visibility can be toggled."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.Visible, true);"); + sb.AppendLine($" Assert({control.Name}.Visible = true, \"Expected {control.Name} to be visible\");"); + sb.AppendLine($" SetProperty({control.Name}.Visible, false);"); + sb.AppendLine($" Assert({control.Name}.Visible = false, \"Expected {control.Name} to be hidden\");"); + sb.AppendLine(); + } + + private void GenerateComprehensiveButtonTestCases(StringBuilder sb, ControlInfo control, string screenName) + { + // Button Click Test + sb.AppendLine($" - testCaseName: Test {control.Name} Click Once"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} performs action when clicked once."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" Select({control.Name});"); + sb.AppendLine($" Assert({control.Name}.Visible = true, \"Verify {control.Name} is functional\");"); + sb.AppendLine(); + + // Button Click Twice Test + sb.AppendLine($" - testCaseName: Test {control.Name} Click Twice"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} performs action when clicked twice."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" Select({control.Name});"); + sb.AppendLine($" Select({control.Name});"); + sb.AppendLine($" Assert({control.Name}.Visible = true, \"Verify {control.Name} handles multiple clicks\");"); + sb.AppendLine(); + } + + private void GenerateComprehensiveCheckboxTestCases(StringBuilder sb, ControlInfo control, string screenName) + { + // Similar comprehensive tests for Checkbox + sb.AppendLine($" - testCaseName: Test {control.Name} Checked Property"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} checked state can be set."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.Checked, true);"); + sb.AppendLine($" Assert({control.Name}.Checked = true, \"Expected {control.Name}.Checked to be true\");"); + sb.AppendLine(); + } + + private void GenerateComprehensiveDatePickerTestCases(StringBuilder sb, ControlInfo control, string screenName) + { + sb.AppendLine($" - testCaseName: Test {control.Name} SelectedDate Property"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} date can be set and retrieved."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.SelectedDate, Date(2024,10,01));"); + sb.AppendLine($" Assert({control.Name}.SelectedDate = Date(2024,10,01), \"Checking the SelectedDate property\");"); + sb.AppendLine(); + } + + private void GenerateComprehensiveRadioGroupTestCases(StringBuilder sb, ControlInfo control, string screenName) + { + sb.AppendLine($" - testCaseName: Test {control.Name} Selection"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} item selection works."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.DefaultSelectedItems, Table({{Value:\"Item 1\"}}));"); + sb.AppendLine($" Assert(CountRows({control.Name}.SelectedItems) = 1, \"Validated Successfully\");"); + sb.AppendLine(); + } + + private void GenerateComprehensiveSliderTestCases(StringBuilder sb, ControlInfo control, string screenName) + { + sb.AppendLine($" - testCaseName: Test {control.Name} Value Property"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} value can be set."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.Value, 50);"); + sb.AppendLine($" Assert({control.Name}.Value = 50, \"Checking the Value property\");"); + sb.AppendLine(); + } + + private void GenerateComprehensiveToggleTestCases(StringBuilder sb, ControlInfo control, string screenName) + { + sb.AppendLine($" - testCaseName: Test {control.Name} Toggle On"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} can be toggled on."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.Checked, true);"); + sb.AppendLine($" Assert({control.Name}.Checked = true, \"User action correctly toggled {control.Name} to on\");"); + sb.AppendLine(); + } + + private void GenerateComprehensiveGalleryTestCases(StringBuilder sb, ControlInfo control, string screenName) + { + sb.AppendLine($" - testCaseName: Test {control.Name} Has Items"); + sb.AppendLine($" testCaseDescription: Verify {control.Name} contains items."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" Assert(CountRows({control.Name}.AllItems) > 0, \"Gallery should have items\");"); + sb.AppendLine(); + } + + private void GenerateGenericTestCases(StringBuilder sb, ControlInfo control, string screenName) + { + sb.AppendLine($" - testCaseName: Test {control.Name} Visible Property"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} is accessible."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" Assert({control.Name}.Visible = true, \"{control.Name} should be accessible\");"); + sb.AppendLine(); + } + } +} diff --git a/src/PowerAppsTestEngine.sln b/src/PowerAppsTestEngine.sln index b073ba25..c8a5f8e2 100644 --- a/src/PowerAppsTestEngine.sln +++ b/src/PowerAppsTestEngine.sln @@ -93,6 +93,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "testengine.provider.powerfx EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "testengine.module.visualcompare", "testengine.module.visualcompare\testengine.module.visualcompare.csproj", "{8431F46D-0269-4E71-BC0D-89438FA99C93}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestCaseGenerator", "TestCaseGenerator\TestCaseGenerator.csproj", "{2929BF6A-623E-8EA9-D3A1-C5E8FBB215CD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -239,6 +241,10 @@ Global {8431F46D-0269-4E71-BC0D-89438FA99C93}.Debug|Any CPU.Build.0 = Debug|Any CPU {8431F46D-0269-4E71-BC0D-89438FA99C93}.Release|Any CPU.ActiveCfg = Release|Any CPU {8431F46D-0269-4E71-BC0D-89438FA99C93}.Release|Any CPU.Build.0 = Release|Any CPU + {2929BF6A-623E-8EA9-D3A1-C5E8FBB215CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2929BF6A-623E-8EA9-D3A1-C5E8FBB215CD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2929BF6A-623E-8EA9-D3A1-C5E8FBB215CD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2929BF6A-623E-8EA9-D3A1-C5E8FBB215CD}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/TestCaseGenerator/Program.cs b/src/TestCaseGenerator/Program.cs new file mode 100644 index 00000000..151732fa --- /dev/null +++ b/src/TestCaseGenerator/Program.cs @@ -0,0 +1,190 @@ +using System; +using System.IO; +using Microsoft.PowerApps.TestEngine.SolutionAnalyzer; +using Microsoft.PowerApps.TestEngine.TestCaseGenerator; + +namespace TestCaseGeneratorTool +{ + class Program + { + static void Main(string[] args) + { +#if DEBUG + // Set default args for debugging when no args provided + if (args.Length == 0) + { + Console.WriteLine("DEBUG MODE: Using default arguments"); + args = new[] + { + @"C:\RR\TestEngine_RR\PowerApps-TestEngine\samples\DemoApp\DemoApp_1_0_0_1.zip", + @"C:\RR\TestEngine_RR\PowerApps-TestEngine\samples\DemoApp\demoapp-tests.fx.yaml", + "fd1a868e-5b14-ef06-b342-3568544a24d9", + "72f988bf-86f1-41af-91ab-2d7cd011db47" + }; + } +#endif + Console.ForegroundColor = ConsoleColor.Cyan; + Console.WriteLine("╔════════════════════════════════════════════════╗"); + Console.WriteLine("║ Power Apps Test Case Generator v1.0 ║"); + Console.WriteLine("╚════════════════════════════════════════════════╝"); + Console.ResetColor(); + Console.WriteLine(); + + if (args.Length < 4) + { + ShowUsage(); + return; + } + + string solutionPath = args[0]; + string outputPath = args[1]; + string environmentId = args[2]; + string tenantId = args[3]; + + try + { + Execute(solutionPath, outputPath, environmentId, tenantId); + } + catch (Exception ex) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"\n✗ Error: {ex.Message}"); + Console.WriteLine($"\nStack Trace:\n{ex.StackTrace}"); + Console.ResetColor(); + Environment.Exit(1); + } + } + + static void ShowUsage() + { + Console.WriteLine("Usage:"); + Console.WriteLine(" TestCaseGenerator.exe "); + Console.WriteLine(); + Console.WriteLine("Parameters:"); + Console.WriteLine(" solution-path : Path to the Power Apps solution ZIP file"); + Console.WriteLine(" output-path : Path where the test plan YAML will be generated"); + Console.WriteLine(" environment-id : GUID of the target Power Apps environment"); + Console.WriteLine(" tenant-id : GUID of the Azure AD tenant"); + Console.WriteLine(); + Console.WriteLine("Example:"); + Console.WriteLine(" TestCaseGenerator.exe ^"); + Console.WriteLine(" C:\\Solutions\\MyApp_1_0_0_1.zip ^"); + Console.WriteLine(" C:\\TestPlans\\myapp-tests.fx.yaml ^"); + Console.WriteLine(" 12345678-1234-1234-1234-123456789012 ^"); + Console.WriteLine(" 87654321-4321-4321-4321-210987654321"); + } + + static void Execute(string solutionPath, string outputPath, string environmentId, string tenantId) + { + Console.WriteLine($"📦 Solution : {Path.GetFileName(solutionPath)}"); + Console.WriteLine($"📄 Output : {Path.GetFileName(outputPath)}"); + Console.WriteLine($"🌐 Environment : {environmentId}"); + Console.WriteLine($"🏢 Tenant : {tenantId}"); + Console.WriteLine(); + + // Step 1: Extract solution ZIP + Console.Write("[1/5] Extracting solution ZIP... "); + var extractor = new SolutionExtractor(); + var tempExtractPath = Path.Combine(Path.GetTempPath(), $"solution_{Guid.NewGuid()}"); + var extractedPath = extractor.ExtractSolution(solutionPath, tempExtractPath); + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine("✓"); + Console.ResetColor(); + + // Step 2: Find msapp file inside the solution + Console.Write("[2/5] Locating .msapp file in solution... "); + var msappPath = extractor.FindMsAppFile(extractedPath); + var appLogicalName = extractor.GetAppLogicalName(extractedPath, Path.GetFileName(msappPath)); + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine("✓"); + Console.ResetColor(); + Console.WriteLine($" Found: {Path.GetFileName(msappPath)}"); + Console.WriteLine($" App Logical Name: {appLogicalName}"); + + // Step 3: Unpack and analyze msapp file + Console.Write("[3/5] Unpacking and analyzing .msapp file... "); + var analyzer = new MsAppAnalyzer(); + var appStructure = analyzer.AnalyzeMsApp(msappPath); + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine("✓"); + Console.ResetColor(); + + Console.WriteLine($" Screens found: {appStructure.Screens.Count}"); + int totalControls = 0; + foreach (var screen in appStructure.Screens) + { + Console.WriteLine($" • {screen.Name}: {screen.Controls.Count} control(s)"); + totalControls += screen.Controls.Count; + } + + // Step 4: Generate test cases + Console.Write("[4/5] Generating comprehensive test cases... "); + var generator = new TestCaseGenerator(); + var testPlan = generator.GenerateTestPlan(appStructure, appLogicalName, environmentId, tenantId); + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine("✓"); + Console.ResetColor(); + + // Step 5: Save test plan + Console.Write("[5/5] Saving test plan file... "); + var outputDir = Path.GetDirectoryName(outputPath); + if (!string.IsNullOrEmpty(outputDir)) + Directory.CreateDirectory(outputDir); + + File.WriteAllText(outputPath, testPlan); + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine("✓"); + Console.ResetColor(); + + // Summary + Console.WriteLine(); + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine("═══════════════════════════════════════════════"); + Console.WriteLine("✓ Test Plan Generated Successfully!"); + Console.WriteLine("═══════════════════════════════════════════════"); + Console.ResetColor(); + Console.WriteLine($"📊 Statistics:"); + Console.WriteLine($" • Screens analyzed : {appStructure.Screens.Count}"); + Console.WriteLine($" • Controls discovered : {totalControls}"); + Console.WriteLine($" • Test cases generated: {EstimateTestCases(appStructure)}"); + Console.WriteLine($" • Output file : {outputPath}"); + Console.WriteLine(); + Console.WriteLine("Next steps:"); + Console.WriteLine(" 1. Review the generated test plan"); + Console.WriteLine(" 2. Customize test cases as needed"); + Console.WriteLine(" 3. Run: dotnet PowerAppsTestEngine.dll -i " + outputPath); + + // Cleanup + try + { + if (Directory.Exists(tempExtractPath)) + Directory.Delete(tempExtractPath, true); + } + catch { /* Ignore cleanup errors */ } + } + + static int EstimateTestCases(AppStructure appStructure) + { + int count = 0; + foreach (var screen in appStructure.Screens) + { + foreach (var control in screen.Controls) + { + var type = control.Type.ToLower(); + if (type.Contains("label")) count += 5; + else if (type.Contains("textinput")) count += 5; + else if (type.Contains("button")) count += 2; + else if (type.Contains("checkbox")) count += 2; + else if (type.Contains("combobox") || type.Contains("dropdown")) count += 1; + else if (type.Contains("datepicker")) count += 1; + else if (type.Contains("radio")) count += 3; + else if (type.Contains("slider")) count += 1; + else if (type.Contains("toggle")) count += 1; + else if (type.Contains("gallery")) count += 1; + else count += 1; + } + } + return count; + } + } +} diff --git a/src/TestCaseGenerator/TestCaseGenerator.csproj b/src/TestCaseGenerator/TestCaseGenerator.csproj new file mode 100644 index 00000000..039d465c --- /dev/null +++ b/src/TestCaseGenerator/TestCaseGenerator.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + TestCaseGenerator + TestCaseGeneratorTool + enable + + + + + + + From e57af620a6c43f90530cc835356c7277f5d60c62 Mon Sep 17 00:00:00 2001 From: raghulraja Date: Tue, 2 Dec 2025 11:13:02 +0530 Subject: [PATCH 2/3] whitespace issue fixed --- .../SolutionAnalyzer/MsAppAnalyzer.cs | 48 +++++++++---------- .../SolutionAnalyzer/MsAppUnpacker.cs | 10 ++-- .../SolutionAnalyzer/SolutionExtractor.cs | 10 ++-- .../TestCaseGenerator/TestCaseGenerator.cs | 10 ++-- 4 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/Microsoft.PowerApps.TestEngine/SolutionAnalyzer/MsAppAnalyzer.cs b/src/Microsoft.PowerApps.TestEngine/SolutionAnalyzer/MsAppAnalyzer.cs index 0d20b8e8..b57ecff1 100644 --- a/src/Microsoft.PowerApps.TestEngine/SolutionAnalyzer/MsAppAnalyzer.cs +++ b/src/Microsoft.PowerApps.TestEngine/SolutionAnalyzer/MsAppAnalyzer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; @@ -13,7 +13,7 @@ public class MsAppAnalyzer public AppStructure AnalyzeMsApp(string msappFilePath) { var tempExtractPath = Path.Combine(Path.GetTempPath(), $"msapp_{Guid.NewGuid()}"); - + try { Console.WriteLine($"DEBUG: Extracting msapp to: {tempExtractPath}"); @@ -36,7 +36,7 @@ public AppStructure AnalyzeMsApp(string msappFilePath) Console.WriteLine("DEBUG: Found Src folder - parsing YAML files"); ParseSrcFolder(srcPath, appStructure); } - + // If no controls found, try CanvasManifest + Controls folder if (appStructure.Screens.Sum(s => s.Controls.Count) == 0) { @@ -98,7 +98,7 @@ private void ParseFromCanvasManifest(string extractPath, AppStructure appStructu { var name = screenName.GetString(); Console.WriteLine($"DEBUG: Processing screen: {name}"); - + var screenInfo = new ScreenInfo { Name = name, @@ -118,7 +118,7 @@ private void ParseFromCanvasManifest(string extractPath, AppStructure appStructu private void ParseControlsFolder(string extractPath, AppStructure appStructure) { var controlsPath = Path.Combine(extractPath, "Controls"); - + if (!Directory.Exists(controlsPath)) { Console.WriteLine("DEBUG: Controls folder not found"); @@ -126,7 +126,7 @@ private void ParseControlsFolder(string extractPath, AppStructure appStructure) } Console.WriteLine($"DEBUG: Parsing Controls folder"); - + // Get all JSON files in Controls folder var jsonFiles = Directory.GetFiles(controlsPath, "*.json", SearchOption.AllDirectories); Console.WriteLine($"DEBUG: Found {jsonFiles.Length} control definition files"); @@ -135,11 +135,11 @@ private void ParseControlsFolder(string extractPath, AppStructure appStructure) { var screenName = Path.GetFileNameWithoutExtension(jsonFile).Replace(".json", ""); Console.WriteLine($"DEBUG: Parsing controls for: {screenName}"); - + // Find the corresponding screen - var screen = appStructure.Screens.FirstOrDefault(s => + var screen = appStructure.Screens.FirstOrDefault(s => s.Name.Equals(screenName, StringComparison.OrdinalIgnoreCase)); - + if (screen == null) { // Create screen if not found @@ -153,7 +153,7 @@ private void ParseControlsFolder(string extractPath, AppStructure appStructure) // Parse the JSON to extract controls ParseControlJson(jsonFile, screen.Controls); - + Console.WriteLine($"DEBUG: Found {screen.Controls.Count} controls in {screenName}"); } } @@ -164,7 +164,7 @@ private void ParseControlJson(string jsonFilePath, List controls) { var json = File.ReadAllText(jsonFilePath); var doc = JsonDocument.Parse(json); - + // Parse the root element ParseJsonControl(doc.RootElement, controls); } @@ -223,7 +223,7 @@ private void ParseSrcFolder(string srcPath, AppStructure appStructure) } // Look for .fx.yaml, .pa.yaml, or .yaml files - var yamlFiles = allFiles.Where(f => + var yamlFiles = allFiles.Where(f => f.EndsWith(".fx.yaml", StringComparison.OrdinalIgnoreCase) || f.EndsWith(".pa.yaml", StringComparison.OrdinalIgnoreCase) || f.EndsWith(".yaml", StringComparison.OrdinalIgnoreCase) @@ -234,7 +234,7 @@ private void ParseSrcFolder(string srcPath, AppStructure appStructure) foreach (var yamlFile in yamlFiles) { var fileName = Path.GetFileNameWithoutExtension(yamlFile); - + // Skip App files if (fileName.Equals("App", StringComparison.OrdinalIgnoreCase) || fileName.StartsWith("App.", StringComparison.OrdinalIgnoreCase)) @@ -260,9 +260,9 @@ private ScreenInfo ParseYamlFile(string filePath) var content = File.ReadAllText(filePath); var fileName = Path.GetFileNameWithoutExtension(filePath); fileName = fileName.Replace(".fx", "").Replace(".pa", ""); - + Console.WriteLine($"DEBUG: ========== Parsing {fileName} =========="); - + var screenInfo = new ScreenInfo { Name = fileName, @@ -270,14 +270,14 @@ private ScreenInfo ParseYamlFile(string filePath) }; var lines = content.Split(new[] { '\r', '\n' }, StringSplitOptions.None); - + // Parse the new YAML format where controls are under Children bool inChildrenSection = false; - + for (int i = 0; i < lines.Length; i++) { var line = lines[i]; - + // Check if we're entering Children section if (line.Trim() == "Children:") { @@ -285,7 +285,7 @@ private ScreenInfo ParseYamlFile(string filePath) Console.WriteLine($"DEBUG: Found Children section at line {i}"); continue; } - + // If we're in Children section, look for control definitions if (inChildrenSection) { @@ -294,7 +294,7 @@ private ScreenInfo ParseYamlFile(string filePath) if (match.Success) { var controlName = match.Groups[1].Value; - + // Look ahead for Control type in next lines string controlType = "Unknown"; for (int j = i + 1; j < Math.Min(i + 5, lines.Length); j++) @@ -319,9 +319,9 @@ private ScreenInfo ParseYamlFile(string filePath) break; } } - + Console.WriteLine($"DEBUG: [FOUND] Control: {controlName} | Type: {controlType}"); - + screenInfo.Controls.Add(new ControlInfo { Name = controlName, @@ -329,7 +329,7 @@ private ScreenInfo ParseYamlFile(string filePath) Properties = new Dictionary() }); } - + // Exit Children section if indentation decreases significantly if (!string.IsNullOrWhiteSpace(line) && !line.StartsWith(" ")) { @@ -346,7 +346,7 @@ private ScreenInfo ParseYamlFile(string filePath) Console.WriteLine($"DEBUG: - {ctrl.Name} ({ctrl.Type})"); } Console.WriteLine($"DEBUG: =========================================="); - + return screenInfo; } catch (Exception ex) diff --git a/src/Microsoft.PowerApps.TestEngine/SolutionAnalyzer/MsAppUnpacker.cs b/src/Microsoft.PowerApps.TestEngine/SolutionAnalyzer/MsAppUnpacker.cs index 12f4d196..4f4c0b4a 100644 --- a/src/Microsoft.PowerApps.TestEngine/SolutionAnalyzer/MsAppUnpacker.cs +++ b/src/Microsoft.PowerApps.TestEngine/SolutionAnalyzer/MsAppUnpacker.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -11,18 +11,18 @@ public class MsAppUnpacker public string UnpackMsApp(string msappPath, string outputPath) { Console.WriteLine($"DEBUG: Unpacking msapp: {msappPath}"); - + // Create temporary directory for unpacking var unpackDir = Path.Combine(outputPath, $"unpacked_{Guid.NewGuid()}"); Directory.CreateDirectory(unpackDir); - + try { // Extract msapp as ZIP first var tempExtract = Path.Combine(Path.GetTempPath(), $"msapp_temp_{Guid.NewGuid()}"); Directory.CreateDirectory(tempExtract); ZipFile.ExtractToDirectory(msappPath, tempExtract); - + // Check if already unpacked (has Src folder) var srcFolder = Path.Combine(tempExtract, "Src"); if (Directory.Exists(srcFolder)) @@ -30,7 +30,7 @@ public string UnpackMsApp(string msappPath, string outputPath) Console.WriteLine("DEBUG: msapp is already in unpacked format"); return tempExtract; } - + // If packed, we need to use PASopa to unpack // For now, we'll work with the extracted structure Console.WriteLine("DEBUG: Working with extracted msapp structure"); diff --git a/src/Microsoft.PowerApps.TestEngine/SolutionAnalyzer/SolutionExtractor.cs b/src/Microsoft.PowerApps.TestEngine/SolutionAnalyzer/SolutionExtractor.cs index ce1537be..6f2f7200 100644 --- a/src/Microsoft.PowerApps.TestEngine/SolutionAnalyzer/SolutionExtractor.cs +++ b/src/Microsoft.PowerApps.TestEngine/SolutionAnalyzer/SolutionExtractor.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.IO.Compression; using System.Linq; @@ -15,7 +15,7 @@ public string ExtractSolution(string solutionZipPath, string extractionPath) if (Directory.Exists(extractionPath)) Directory.Delete(extractionPath, true); - + Directory.CreateDirectory(extractionPath); ZipFile.ExtractToDirectory(solutionZipPath, extractionPath); @@ -25,12 +25,12 @@ public string ExtractSolution(string solutionZipPath, string extractionPath) public string FindMsAppFile(string extractedSolutionPath) { var canvasAppsPath = Path.Combine(extractedSolutionPath, "CanvasApps"); - + if (!Directory.Exists(canvasAppsPath)) throw new DirectoryNotFoundException("CanvasApps folder not found in solution"); var msappFiles = Directory.GetFiles(canvasAppsPath, "*.msapp", SearchOption.AllDirectories); - + if (msappFiles.Length == 0) throw new FileNotFoundException("No .msapp files found in solution"); @@ -40,7 +40,7 @@ public string FindMsAppFile(string extractedSolutionPath) public string GetAppLogicalName(string extractedSolutionPath, string msappFileName) { var customizationsPath = Path.Combine(extractedSolutionPath, "customizations.xml"); - + if (!File.Exists(customizationsPath)) return Path.GetFileNameWithoutExtension(msappFileName); diff --git a/src/Microsoft.PowerApps.TestEngine/TestCaseGenerator/TestCaseGenerator.cs b/src/Microsoft.PowerApps.TestEngine/TestCaseGenerator/TestCaseGenerator.cs index 940422ab..c3bc1cd1 100644 --- a/src/Microsoft.PowerApps.TestEngine/TestCaseGenerator/TestCaseGenerator.cs +++ b/src/Microsoft.PowerApps.TestEngine/TestCaseGenerator/TestCaseGenerator.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -11,7 +11,7 @@ public class TestCaseGenerator public string GenerateTestPlan(AppStructure appStructure, string appLogicalName, string environmentId, string tenantId) { var sb = new StringBuilder(); - + // Test Suite sb.AppendLine("testSuite:"); sb.AppendLine($" testSuiteName: {appStructure.AppName} Complete Tests"); @@ -39,7 +39,7 @@ public string GenerateTestPlan(AppStructure appStructure, string appLogicalName, sb.AppendLine(" - browser: Chromium"); sb.AppendLine(" channel: msedge"); sb.AppendLine(); - + // Environment Variables sb.AppendLine("environmentVariables:"); sb.AppendLine(" users:"); @@ -55,9 +55,9 @@ private void GenerateScreenTestCases(StringBuilder sb, ScreenInfo screen) foreach (var control in screen.Controls) { var controlType = control.Type.ToLower(); - + sb.AppendLine($" # {control.Name} Test Cases"); - + if (controlType.Contains("label")) { GenerateComprehensiveLabelTestCases(sb, control, screen.Name); From e47e4ceb85102a02c083c14ffbcd1a32a338953f Mon Sep 17 00:00:00 2001 From: raghulraja Date: Fri, 5 Dec 2025 15:35:02 +0530 Subject: [PATCH 3/3] rich text editor changes --- .../TestCaseGenerator/TestCaseGenerator.cs | 224 +++++++++++++----- src/TestCaseGenerator/Program.cs | 14 +- 2 files changed, 166 insertions(+), 72 deletions(-) diff --git a/src/Microsoft.PowerApps.TestEngine/TestCaseGenerator/TestCaseGenerator.cs b/src/Microsoft.PowerApps.TestEngine/TestCaseGenerator/TestCaseGenerator.cs index c3bc1cd1..214a9ef4 100644 --- a/src/Microsoft.PowerApps.TestEngine/TestCaseGenerator/TestCaseGenerator.cs +++ b/src/Microsoft.PowerApps.TestEngine/TestCaseGenerator/TestCaseGenerator.cs @@ -58,7 +58,12 @@ private void GenerateScreenTestCases(StringBuilder sb, ScreenInfo screen) sb.AppendLine($" # {control.Name} Test Cases"); - if (controlType.Contains("label")) + // Check RichTextEditor FIRST before checking for generic "text" + if (controlType.Contains("richtexteditor") || controlType.Contains("richedit") || controlType.Contains("richtext")) + { + GenerateComprehensiveRichTextEditorTestCases(sb, control, screen.Name); + } + else if (controlType.Contains("label")) { GenerateComprehensiveLabelTestCases(sb, control, screen.Name); } @@ -70,7 +75,11 @@ private void GenerateScreenTestCases(StringBuilder sb, ScreenInfo screen) { GenerateComprehensiveButtonTestCases(sb, control, screen.Name); } - else if (controlType.Contains("combobox") || controlType.Contains("dropdown")) + else if (controlType.Contains("dropdown")) + { + GenerateComprehensiveDropdownTestCases(sb, control, screen.Name); + } + else if (controlType.Contains("combobox")) { GenerateComprehensiveComboboxTestCases(sb, control, screen.Name); } @@ -147,14 +156,6 @@ private void GenerateComprehensiveLabelTestCases(StringBuilder sb, ControlInfo c sb.AppendLine($" Assert({control.Name}.Text = \"1234567890\", \"Expected {control.Name}.Text to be numeric text\");"); sb.AppendLine(); - // Visible Property True Test - sb.AppendLine($" - testCaseName: Test {control.Name} Visible Property True"); - sb.AppendLine($" testCaseDescription: Verify that {control.Name} visibility can be set to true."); - sb.AppendLine(" testSteps: |"); - sb.AppendLine($" SetProperty({control.Name}.Visible, true);"); - sb.AppendLine($" Assert({control.Name}.Visible = true, \"Expected {control.Name}.Visible to be true\");"); - sb.AppendLine(); - // Visible Property False Test sb.AppendLine($" - testCaseName: Test {control.Name} Visible Property False"); sb.AppendLine($" testCaseDescription: Verify that {control.Name} visibility can be set to false."); @@ -162,16 +163,6 @@ private void GenerateComprehensiveLabelTestCases(StringBuilder sb, ControlInfo c sb.AppendLine($" SetProperty({control.Name}.Visible, false);"); sb.AppendLine($" Assert({control.Name}.Visible = false, \"Expected {control.Name}.Visible to be false\");"); sb.AppendLine(); - - // Visible Toggle Test - sb.AppendLine($" - testCaseName: Test {control.Name} Visible Toggle"); - sb.AppendLine($" testCaseDescription: Verify that {control.Name} visibility can be toggled."); - sb.AppendLine(" testSteps: |"); - sb.AppendLine($" SetProperty({control.Name}.Visible, true);"); - sb.AppendLine($" Assert({control.Name}.Visible = true, \"Expected {control.Name} to be visible\");"); - sb.AppendLine($" SetProperty({control.Name}.Visible, false);"); - sb.AppendLine($" Assert({control.Name}.Visible = false, \"Expected {control.Name} to be hidden\");"); - sb.AppendLine(); } private void GenerateComprehensiveComboboxTestCases(StringBuilder sb, ControlInfo control, string screenName) @@ -236,20 +227,6 @@ private void GenerateComprehensiveComboboxTestCases(StringBuilder sb, ControlInf sb.AppendLine($" Assert(CountRows({control.Name}.SelectedItems) = 3, \"Expected three items to be selected\");"); sb.AppendLine(); - // Select Five Items - sb.AppendLine($" - testCaseName: Test {control.Name} Select Five Items"); - sb.AppendLine($" testCaseDescription: Verify that five items can be selected in {control.Name}."); - sb.AppendLine(" testSteps: |"); - sb.AppendLine($" SetProperty({control.Name}.SelectedItems, Table("); - sb.AppendLine(" {Value:\"Item 1\", ID:1},"); - sb.AppendLine(" {Value:\"Item 2\", ID:2},"); - sb.AppendLine(" {Value:\"Item 3\", ID:3},"); - sb.AppendLine(" {Value:\"Item 4\", ID:4},"); - sb.AppendLine(" {Value:\"Item 5\", ID:5}"); - sb.AppendLine(" ));"); - sb.AppendLine($" Assert(CountRows({control.Name}.SelectedItems) = 5, \"Expected five items to be selected\");"); - sb.AppendLine(); - // Clear Selection sb.AppendLine($" - testCaseName: Test {control.Name} Clear Selection"); sb.AppendLine($" testCaseDescription: Verify that {control.Name} selection can be cleared to empty table."); @@ -263,7 +240,7 @@ private void GenerateComprehensiveComboboxTestCases(StringBuilder sb, ControlInf sb.AppendLine($" testCaseDescription: Verify that the first item can be selected from available items."); sb.AppendLine(" testSteps: |"); sb.AppendLine($" SetProperty({control.Name}.SelectedItems, FirstN({control.Name}.Items, 1));"); - sb.AppendLine($" Assert(CountRows({control.Name}.SelectedItems) <= 1, \"Expected at most one item to be selected\");"); + sb.AppendLine($" Assert(CountRows({control.Name}.SelectedItems) = 1, \"Expected one item to be selected\");"); sb.AppendLine(); // Select First Two Items @@ -271,7 +248,7 @@ private void GenerateComprehensiveComboboxTestCases(StringBuilder sb, ControlInf sb.AppendLine($" testCaseDescription: Verify that the first two items can be selected from available items."); sb.AppendLine(" testSteps: |"); sb.AppendLine($" SetProperty({control.Name}.SelectedItems, FirstN({control.Name}.Items, 2));"); - sb.AppendLine($" Assert(CountRows({control.Name}.SelectedItems) <= 2, \"Expected at most two items to be selected\");"); + sb.AppendLine($" Assert(CountRows({control.Name}.SelectedItems) = 2, \"Expected two items to be selected\");"); sb.AppendLine(); // Reselect After Clear @@ -299,69 +276,53 @@ private void GenerateComprehensiveComboboxTestCases(StringBuilder sb, ControlInf private void GenerateComprehensiveTextBoxTestCases(StringBuilder sb, ControlInfo control, string screenName) { - // Sample Text Test - Use .Value for TextBox + // Sample Text Test - Use .Text for Canvas TextInput sb.AppendLine($" - testCaseName: Test {control.Name} Sample Text"); sb.AppendLine($" testCaseDescription: Verify that {control.Name} accepts and displays input correctly."); sb.AppendLine(" testSteps: |"); - sb.AppendLine($" SetProperty({control.Name}.Value, \"Sample Text\");"); - sb.AppendLine($" Assert({control.Name}.Value = \"Sample Text\", \"Verify {control.Name} displays the input text correctly\");"); + sb.AppendLine($" SetProperty({control.Name}.Text, \"Sample Text\");"); + sb.AppendLine($" Assert({control.Name}.Text = \"Sample Text\", \"Verify {control.Name} displays the input text correctly\");"); sb.AppendLine(); - // Empty Value Test + // Empty Text Test sb.AppendLine($" - testCaseName: Test {control.Name} Empty"); sb.AppendLine($" testCaseDescription: Verify that {control.Name} can be set to empty string."); sb.AppendLine(" testSteps: |"); - sb.AppendLine($" SetProperty({control.Name}.Value, \"\");"); - sb.AppendLine($" Assert({control.Name}.Value = \"\", \"Expected {control.Name}.Value to be empty\");"); + sb.AppendLine($" SetProperty({control.Name}.Text, \"\");"); + sb.AppendLine($" Assert({control.Name}.Text = \"\", \"Expected {control.Name}.Text to be empty\");"); sb.AppendLine(); // Long Text Test sb.AppendLine($" - testCaseName: Test {control.Name} Long Text"); sb.AppendLine($" testCaseDescription: Verify that {control.Name} can handle long text."); sb.AppendLine(" testSteps: |"); - sb.AppendLine($" SetProperty({control.Name}.Value, \"This is a very long text to check if the TextBox can handle it without any issues.\");"); - sb.AppendLine($" Assert({control.Name}.Value = \"This is a very long text to check if the TextBox can handle it without any issues.\", \"Expected {control.Name}.Value to be the long text\");"); + sb.AppendLine($" SetProperty({control.Name}.Text, \"This is a very long text to check if the TextBox can handle it without any issues.\");"); + sb.AppendLine($" Assert({control.Name}.Text = \"This is a very long text to check if the TextBox can handle it without any issues.\", \"Expected {control.Name}.Text to be the long text\");"); sb.AppendLine(); // Special Characters Test sb.AppendLine($" - testCaseName: Test {control.Name} Special Characters"); sb.AppendLine($" testCaseDescription: Verify that {control.Name} handles special characters."); sb.AppendLine(" testSteps: |"); - sb.AppendLine($" SetProperty({control.Name}.Value, \"Special@#$%^&*()!\");"); - sb.AppendLine($" Assert({control.Name}.Value = \"Special@#$%^&*()!\", \"Expected {control.Name} to handle special characters\");"); + sb.AppendLine($" SetProperty({control.Name}.Text, \"Special@#$%^&*()!\");"); + sb.AppendLine($" Assert({control.Name}.Text = \"Special@#$%^&*()!\", \"Expected {control.Name} to handle special characters\");"); sb.AppendLine(); // Numeric Text Test sb.AppendLine($" - testCaseName: Test {control.Name} Numeric Text"); sb.AppendLine($" testCaseDescription: Verify that {control.Name} can handle numeric text."); sb.AppendLine(" testSteps: |"); - sb.AppendLine($" SetProperty({control.Name}.Value, \"1234567890\");"); - sb.AppendLine($" Assert({control.Name}.Value = \"1234567890\", \"Expected {control.Name}.Value to be numeric text\");"); - sb.AppendLine(); - - // Visible Property Tests - sb.AppendLine($" - testCaseName: Test {control.Name} Visible Property True"); - sb.AppendLine($" testCaseDescription: Verify that {control.Name} visibility can be set to true."); - sb.AppendLine(" testSteps: |"); - sb.AppendLine($" SetProperty({control.Name}.Visible, true);"); - sb.AppendLine($" Assert({control.Name}.Visible = true, \"Expected {control.Name}.Visible to be true\");"); + sb.AppendLine($" SetProperty({control.Name}.Text, \"1234567890\");"); + sb.AppendLine($" Assert({control.Name}.Text = \"1234567890\", \"Expected {control.Name}.Text to be numeric text\");"); sb.AppendLine(); + // Visible Property False Test sb.AppendLine($" - testCaseName: Test {control.Name} Visible Property False"); sb.AppendLine($" testCaseDescription: Verify that {control.Name} visibility can be set to false."); sb.AppendLine(" testSteps: |"); sb.AppendLine($" SetProperty({control.Name}.Visible, false);"); sb.AppendLine($" Assert({control.Name}.Visible = false, \"Expected {control.Name}.Visible to be false\");"); sb.AppendLine(); - - sb.AppendLine($" - testCaseName: Test {control.Name} Visible Toggle"); - sb.AppendLine($" testCaseDescription: Verify that {control.Name} visibility can be toggled."); - sb.AppendLine(" testSteps: |"); - sb.AppendLine($" SetProperty({control.Name}.Visible, true);"); - sb.AppendLine($" Assert({control.Name}.Visible = true, \"Expected {control.Name} to be visible\");"); - sb.AppendLine($" SetProperty({control.Name}.Visible, false);"); - sb.AppendLine($" Assert({control.Name}.Visible = false, \"Expected {control.Name} to be hidden\");"); - sb.AppendLine(); } private void GenerateComprehensiveButtonTestCases(StringBuilder sb, ControlInfo control, string screenName) @@ -452,5 +413,138 @@ private void GenerateGenericTestCases(StringBuilder sb, ControlInfo control, str sb.AppendLine($" Assert({control.Name}.Visible = true, \"{control.Name} should be accessible\");"); sb.AppendLine(); } + + private void GenerateComprehensiveRichTextEditorTestCases(StringBuilder sb, ControlInfo control, string screenName) + { + // RichTextEditor uses .HtmlText property, not .Value + sb.AppendLine($" - testCaseName: Test {control.Name} Sample Text"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} accepts and displays rich text correctly."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.HtmlText, \"

Sample Text

\");"); + sb.AppendLine($" Assert(!IsBlank({control.Name}.HtmlText), \"Verify {control.Name} displays the input text correctly\");"); + sb.AppendLine(); + + sb.AppendLine($" - testCaseName: Test {control.Name} Empty"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} can be set to empty."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.HtmlText, \"\");"); + sb.AppendLine($" Assert({control.Name}.HtmlText = \"\", \"Expected {control.Name}.HtmlText to be empty\");"); + sb.AppendLine(); + + sb.AppendLine($" - testCaseName: Test {control.Name} Long Text"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} can handle long text."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.HtmlText, \"

This is a very long text to check if the RichTextEditor can handle it without any issues.

\");"); + sb.AppendLine($" Assert(!IsBlank({control.Name}.HtmlText), \"Expected {control.Name} to handle long text\");"); + sb.AppendLine(); + + sb.AppendLine($" - testCaseName: Test {control.Name} Special Characters"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} handles special characters."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.HtmlText, \"

Special@#$%^&*()!

\");"); + sb.AppendLine($" Assert(!IsBlank({control.Name}.HtmlText), \"Expected {control.Name} to handle special characters\");"); + sb.AppendLine(); + + sb.AppendLine($" - testCaseName: Test {control.Name} Numeric Text"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} can handle numeric text."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.HtmlText, \"

1234567890

\");"); + sb.AppendLine($" Assert(!IsBlank({control.Name}.HtmlText), \"Expected {control.Name} to handle numeric text\");"); + sb.AppendLine(); + + sb.AppendLine($" - testCaseName: Test {control.Name} Visible Property True"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} visibility can be set to true."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.Visible, true);"); + sb.AppendLine($" Assert({control.Name}.Visible = true, \"Expected {control.Name}.Visible to be true\");"); + sb.AppendLine(); + + sb.AppendLine($" - testCaseName: Test {control.Name} Visible Property False"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} visibility can be set to false."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.Visible, false);"); + sb.AppendLine($" Assert({control.Name}.Visible = false, \"Expected {control.Name}.Visible to be false\");"); + sb.AppendLine(); + + sb.AppendLine($" - testCaseName: Test {control.Name} Visible Toggle"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} visibility can be toggled."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.Visible, true);"); + sb.AppendLine($" Assert({control.Name}.Visible = true, \"Expected {control.Name} to be visible\");"); + sb.AppendLine($" SetProperty({control.Name}.Visible, false);"); + sb.AppendLine($" Assert({control.Name}.Visible = false, \"Expected {control.Name} to be hidden\");"); + sb.AppendLine(); + } + + private void GenerateComprehensiveDropdownTestCases(StringBuilder sb, ControlInfo control, string screenName) + { + // Dropdown is typically single-select only + sb.AppendLine($" - testCaseName: Test {control.Name} Visible Property True"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} visibility can be set to true."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.Visible, true);"); + sb.AppendLine($" Assert({control.Name}.Visible = true, \"Expected {control.Name}.Visible to be true\");"); + sb.AppendLine(); + + sb.AppendLine($" - testCaseName: Test {control.Name} Visible Property False"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} visibility can be set to false."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.Visible, false);"); + sb.AppendLine($" Assert({control.Name}.Visible = false, \"Expected {control.Name}.Visible to be false\");"); + sb.AppendLine(); + + sb.AppendLine($" - testCaseName: Test {control.Name} Visible Toggle"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} visibility can be toggled correctly."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.Visible, true);"); + sb.AppendLine($" Assert({control.Name}.Visible = true, \"Expected {control.Name} to be visible\");"); + sb.AppendLine($" SetProperty({control.Name}.Visible, false);"); + sb.AppendLine($" Assert({control.Name}.Visible = false, \"Expected {control.Name} to be hidden\");"); + sb.AppendLine(); + + sb.AppendLine($" - testCaseName: Test {control.Name} Items Available"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} has items in its data source."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" Assert(CountRows({control.Name}.Items) >= 0, \"Expected {control.Name}.Items to be available\");"); + sb.AppendLine(); + + sb.AppendLine($" - testCaseName: Test {control.Name} Items Not Empty"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} Items collection is not empty."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" Assert(!IsEmpty({control.Name}.Items), \"Expected {control.Name} to have items available\");"); + sb.AppendLine(); + + // Dropdown is single-select - use SelectedItems with single item + sb.AppendLine($" - testCaseName: Test {control.Name} Select Single Item"); + sb.AppendLine($" testCaseDescription: Verify that a single item can be selected in {control.Name}."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.SelectedItems, Table({{Value:\"Test Item\", ID:1}}));"); + sb.AppendLine($" Assert(CountRows({control.Name}.SelectedItems) = 1, \"Expected one item to be selected\");"); + sb.AppendLine(); + + // Remove multi-select tests for Dropdown (it's single-select) + sb.AppendLine($" - testCaseName: Test {control.Name} Clear Selection"); + sb.AppendLine($" testCaseDescription: Verify that {control.Name} selection can be cleared."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.SelectedItems, Table());"); + sb.AppendLine($" Assert(CountRows({control.Name}.SelectedItems) = 0, \"Expected no items to be selected\");"); + sb.AppendLine(); + + sb.AppendLine($" - testCaseName: Test {control.Name} Select First Item"); + sb.AppendLine($" testCaseDescription: Verify that the first item can be selected from available items."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.SelectedItems, FirstN({control.Name}.Items, 1));"); + sb.AppendLine($" Assert(CountRows({control.Name}.SelectedItems) = 1, \"Expected one item to be selected\");"); + sb.AppendLine(); + + sb.AppendLine($" - testCaseName: Test {control.Name} Reselect After Clear"); + sb.AppendLine($" testCaseDescription: Verify selection works after clearing."); + sb.AppendLine(" testSteps: |"); + sb.AppendLine($" SetProperty({control.Name}.SelectedItems, Table());"); + sb.AppendLine($" Assert(CountRows({control.Name}.SelectedItems) = 0, \"Expected no items selected after clear\");"); + sb.AppendLine($" SetProperty({control.Name}.SelectedItems, Table({{Value:\"New Item\", ID:999}}));"); + sb.AppendLine($" Assert(CountRows({control.Name}.SelectedItems) = 1, \"Expected one item to be selected after reselection\");"); + sb.AppendLine(); + } } } diff --git a/src/TestCaseGenerator/Program.cs b/src/TestCaseGenerator/Program.cs index 151732fa..1ae8495e 100644 --- a/src/TestCaseGenerator/Program.cs +++ b/src/TestCaseGenerator/Program.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using Microsoft.PowerApps.TestEngine.SolutionAnalyzer; using Microsoft.PowerApps.TestEngine.TestCaseGenerator; @@ -108,7 +108,6 @@ static void Execute(string solutionPath, string outputPath, string environmentId Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("✓"); Console.ResetColor(); - Console.WriteLine($" Screens found: {appStructure.Screens.Count}"); int totalControls = 0; foreach (var screen in appStructure.Screens) @@ -171,13 +170,14 @@ static int EstimateTestCases(AppStructure appStructure) foreach (var control in screen.Controls) { var type = control.Type.ToLower(); - if (type.Contains("label")) count += 5; - else if (type.Contains("textinput")) count += 5; + if (type.Contains("label")) count += 8; + else if (type.Contains("richtexteditor") || type.Contains("richtext")) count += 8; + else if (type.Contains("textinput") || type.Contains("textbox")) count += 8; else if (type.Contains("button")) count += 2; - else if (type.Contains("checkbox")) count += 2; - else if (type.Contains("combobox") || type.Contains("dropdown")) count += 1; + else if (type.Contains("checkbox")) count += 1; + else if (type.Contains("combobox") || type.Contains("dropdown")) count += 11; else if (type.Contains("datepicker")) count += 1; - else if (type.Contains("radio")) count += 3; + else if (type.Contains("radio")) count += 1; else if (type.Contains("slider")) count += 1; else if (type.Contains("toggle")) count += 1; else if (type.Contains("gallery")) count += 1;