diff --git a/src/Microsoft.PowerApps.TestEngine/SolutionAnalyzer/MsAppAnalyzer.cs b/src/Microsoft.PowerApps.TestEngine/SolutionAnalyzer/MsAppAnalyzer.cs new file mode 100644 index 00000000..b57ecff1 --- /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..4f4c0b4a --- /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..6f2f7200 --- /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..214a9ef4 --- /dev/null +++ b/src/Microsoft.PowerApps.TestEngine/TestCaseGenerator/TestCaseGenerator.cs @@ -0,0 +1,550 @@ +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"); + + // 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); + } + 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("dropdown")) + { + GenerateComprehensiveDropdownTestCases(sb, control, screen.Name); + } + else if (controlType.Contains("combobox")) + { + 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 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(); + } + + 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(); + + // 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 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 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 .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}.Text, \"Sample Text\");"); + sb.AppendLine($" Assert({control.Name}.Text = \"Sample Text\", \"Verify {control.Name} displays the input text correctly\");"); + sb.AppendLine(); + + // 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}.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}.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}.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}.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(); + } + + 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(); + } + + 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/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..1ae8495e --- /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 += 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 += 1; + else if (type.Contains("combobox") || type.Contains("dropdown")) count += 11; + else if (type.Contains("datepicker")) count += 1; + 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; + 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 + + + + + + +