From 80568b8b3171cddc6745f69f42a987d3247aee64 Mon Sep 17 00:00:00 2001 From: Dor Blayzer <59066376+Dor-bl@users.noreply.github.com> Date: Tue, 14 Nov 2023 00:00:51 +0200 Subject: [PATCH] fix: Update Node path to compile with appium 2 and make him cross-platform (#659) * fix: Update WindowsNode path to compile with appium 2 refactor: Move Appium.js path retrieval to helpers.paths class * refactor!: Retrieve npm prefix path and use cross-platform commands to get appium script * fix: npm install prefix and npm install path mixup * fix: make PathToNode compatible with both Windows and Unix-based systems * chore: Remove redundant NodePaths from the resources * chore: Remove comment section * chore: Add exception if npm path cannot be detected * chore: Rename _pathToCustomizedAppiumJs * chore: Add timeout for RunCommand chore: Remove private var envPlatform * chore: Optimize code performance * chore: Optimize path readability on Windows platforms * chore: Switch from `npm list -g --depth=0` to `npm -g root` for better performance * chore: Remove comment section * chore: Throw specific exception for npm commands * chore: Move declaration near reference * chore: Rename GetAppiumJsPath * chore: Improve NPM path not found exception * chore: Rename resource file name * chore: Remove path replacement and add XML DOC * chore: Verify exit code on RunCommand() from Npm * chore: Use GetNpmExecutablePath() on all platforms * chore: log command upon errorin RunCommand() * chore: Improve NpmNotFoundException log * chore: Rename GetAppiumPackageIndexPath() * chore: Simplify InitAppiumPackageIndexPath() * chore: Simplify paths creation * chore: More exceptions mapping for RunCommand * fix: More exceptions improvements in RunCommand() * fix: Exceptions makeover pt. 3 * fix: npm typo * chore: Minimise try-catch * chore: Remove unnecessary using --- .../Properties/Resources.Designer.cs | 40 +------- test/integration/Properties/Resources.resx | 9 -- .../AppiumLocalServerLaunchingTest.cs | 32 +------ test/integration/helpers/Npm.cs | 93 +++++++++++++++++++ .../helpers/NpmNotFoundException.cs | 22 +++++ .../helpers/NpmUnknownCommandException.cs | 17 ++++ test/integration/helpers/PathToLinuxNode | 1 - test/integration/helpers/PathToMacOSNode | 1 - test/integration/helpers/PathToWindowsNode | 1 - test/integration/helpers/Paths.cs | 36 +++++++ 10 files changed, 178 insertions(+), 74 deletions(-) create mode 100644 test/integration/helpers/Npm.cs create mode 100644 test/integration/helpers/NpmNotFoundException.cs create mode 100644 test/integration/helpers/NpmUnknownCommandException.cs delete mode 100644 test/integration/helpers/PathToLinuxNode delete mode 100644 test/integration/helpers/PathToMacOSNode delete mode 100644 test/integration/helpers/PathToWindowsNode create mode 100644 test/integration/helpers/Paths.cs diff --git a/test/integration/Properties/Resources.Designer.cs b/test/integration/Properties/Resources.Designer.cs index 400c7e3e..02421c41 100644 --- a/test/integration/Properties/Resources.Designer.cs +++ b/test/integration/Properties/Resources.Designer.cs @@ -8,10 +8,10 @@ // //------------------------------------------------------------------------------ -namespace Appium.Net.Integration.Tests.Properties -{ - - +namespace Appium.Net.Integration.Tests.Properties { + using System; + + /// /// A strongly-typed resource class, for looking up localized strings, etc. /// @@ -19,7 +19,7 @@ namespace Appium.Net.Integration.Tests.Properties // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] public class Resources { @@ -70,36 +70,6 @@ public static byte[] ApiDemos_debug { } } - /// - /// Looks up a localized resource of type System.Byte[]. - /// - public static byte[] PathToLinuxNode { - get { - object obj = ResourceManager.GetObject("PathToLinuxNode", resourceCulture); - return ((byte[])(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Byte[]. - /// - public static byte[] PathToMacOSNode { - get { - object obj = ResourceManager.GetObject("PathToMacOSNode", resourceCulture); - return ((byte[])(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Byte[]. - /// - public static byte[] PathToWindowsNode { - get { - object obj = ResourceManager.GetObject("PathToWindowsNode", resourceCulture); - return ((byte[])(obj)); - } - } - /// /// Looks up a localized resource of type System.Byte[]. /// diff --git a/test/integration/Properties/Resources.resx b/test/integration/Properties/Resources.resx index f6ac0653..a1013a48 100644 --- a/test/integration/Properties/Resources.resx +++ b/test/integration/Properties/Resources.resx @@ -118,15 +118,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - ..\helpers\PathToLinuxNode;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - ..\helpers\PathToMacOSNode;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - ..\helpers\PathToWindowsNode;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - ..\apps\ApiDemos-debug.apk;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 diff --git a/test/integration/ServerTests/AppiumLocalServerLaunchingTest.cs b/test/integration/ServerTests/AppiumLocalServerLaunchingTest.cs index 14bb79a3..91d0aa38 100644 --- a/test/integration/ServerTests/AppiumLocalServerLaunchingTest.cs +++ b/test/integration/ServerTests/AppiumLocalServerLaunchingTest.cs @@ -4,9 +4,8 @@ using System.Net; using System.Text; using System.Threading; -using Appium.Net.Integration.Tests.Properties; +using Appium.Net.Integration.Tests.Helpers; using NUnit.Framework; -using OpenQA.Selenium; using OpenQA.Selenium.Appium; using OpenQA.Selenium.Appium.Enums; using OpenQA.Selenium.Appium.Service; @@ -18,15 +17,12 @@ namespace Appium.Net.Integration.Tests.ServerTests [TestFixture] public class AppiumLocalServerLaunchingTest { - private string _pathToCustomizedAppiumJs; + private string _pathToAppiumPackageIndex; private string _testIp; [OneTimeSetUp] public void BeforeAll() { - var isWindows = Platform.CurrentPlatform.IsPlatformType(PlatformType.Windows); - var isMacOs = Platform.CurrentPlatform.IsPlatformType(PlatformType.Mac); - var isLinux = Platform.CurrentPlatform.IsPlatformType(PlatformType.Linux); IPHostEntry host; var hostName = Dns.GetHostName(); @@ -41,25 +37,7 @@ public void BeforeAll() } Console.WriteLine(_testIp); - byte[] bytes; - if (isWindows) - { - bytes = Resources.PathToWindowsNode; - _pathToCustomizedAppiumJs = Encoding.UTF8.GetString(bytes); - return; - } - if (isMacOs) - { - bytes = Resources.PathToMacOSNode; - _pathToCustomizedAppiumJs = Encoding.UTF8.GetString(bytes); - return; - } - if (isLinux) - { - bytes = Resources.PathToLinuxNode; - _pathToCustomizedAppiumJs = Encoding.UTF8.GetString(bytes); - return; - } + _pathToAppiumPackageIndex = new Paths().PathToAppiumPackageIndex; } [Test] @@ -105,7 +83,7 @@ public void CheckAbilityToBuildServiceUsingNodeDefinedInProperties() AppiumLocalService service = null; try { - var definedNode = _pathToCustomizedAppiumJs; + var definedNode = _pathToAppiumPackageIndex; Environment.SetEnvironmentVariable(AppiumServiceConstants.AppiumBinaryPath, definedNode); service = AppiumLocalService.BuildDefaultService(); service.Start(); @@ -124,7 +102,7 @@ public void CheckAbilityToBuildServiceUsingNodeDefinedExplicitly() AppiumLocalService service = null; try { - service = new AppiumServiceBuilder().WithAppiumJS(new FileInfo(_pathToCustomizedAppiumJs)).Build(); + service = new AppiumServiceBuilder().WithAppiumJS(new FileInfo(_pathToAppiumPackageIndex)).Build(); service.Start(); Assert.AreEqual(true, service.IsRunning); } diff --git a/test/integration/helpers/Npm.cs b/test/integration/helpers/Npm.cs new file mode 100644 index 00000000..d3605116 --- /dev/null +++ b/test/integration/helpers/Npm.cs @@ -0,0 +1,93 @@ +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; + +namespace Appium.Net.Integration.Tests.helpers +{ + internal class Npm + { + + public static string GetNpmPrefixPath() + { + string npmPath = GetNpmExecutablePath(); + string npmPrefixPath = RunCommand(npmPath, "-g root"); + + return npmPrefixPath.Trim(); + } + + private static string RunCommand(string command, string arguments, int timeoutMilliseconds = 30000) + { + int exitCode; + string output; + try + { + using (Process process = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = command, + Arguments = arguments, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true, + } + }) + { + process.Start(); + + output = process.StandardOutput.ReadToEnd(); + string errorOutput = process.StandardError.ReadToEnd(); + _ = process.WaitForExit(timeoutMilliseconds); + + exitCode = process.ExitCode; + } + if ((exitCode == 1) && command.Contains("npm")) + { + Console.WriteLine($"npm Error upon command: `{arguments}`. {output}"); + throw new NpmUnknownCommandException($"Command: `{arguments}` exited with code {exitCode}. Error: {output}"); + } + + return output; + } + + catch (Win32Exception ex) when (command.Contains("npm")) + { + Console.WriteLine(ex.Message); + throw new NpmNotFoundException($"npm not found under {command}", ex); + } + } + + private static string GetNpmExecutablePath() + { + string commandName = IsWindows() ? "where" : "which"; + string result = RunCommand(commandName, "npm"); + + string npmPath; + + string[] lines = result?.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); + + if (IsWindows()) + { + npmPath = lines?.FirstOrDefault(line => !string.IsNullOrWhiteSpace(line) && line.EndsWith("npm.cmd")); + } + else + { + npmPath = lines?.FirstOrDefault(line => !string.IsNullOrWhiteSpace(line)); + } + + if (string.IsNullOrWhiteSpace(npmPath)) + { + throw new NpmNotFoundException("NPM executable not found. Please make sure the NPM executable is installed and check the configured PATH environment variable."); + } + + return npmPath; + } + + private static bool IsWindows() + { + return Environment.OSVersion.Platform == PlatformID.Win32NT; + } + } +} diff --git a/test/integration/helpers/NpmNotFoundException.cs b/test/integration/helpers/NpmNotFoundException.cs new file mode 100644 index 00000000..71521797 --- /dev/null +++ b/test/integration/helpers/NpmNotFoundException.cs @@ -0,0 +1,22 @@ +using System; + +namespace Appium.Net.Integration.Tests.helpers +{ + public class NpmNotFoundException : Exception + { + public NpmNotFoundException() + : base("Node Package Manager (npm) cannot be found. Make sure Node.js is installed and present in PATH.") + { + } + + public NpmNotFoundException(string message) + : base(message) + { + } + + public NpmNotFoundException(string message, Exception innerException) + : base(message, innerException) + { + } + } +} diff --git a/test/integration/helpers/NpmUnknownCommandException.cs b/test/integration/helpers/NpmUnknownCommandException.cs new file mode 100644 index 00000000..492dbf98 --- /dev/null +++ b/test/integration/helpers/NpmUnknownCommandException.cs @@ -0,0 +1,17 @@ +using System; + +namespace Appium.Net.Integration.Tests.helpers +{ + public class NpmUnknownCommandException : Exception + { + public NpmUnknownCommandException() + : base("Unknown npm command encountered. ") + { + } + + public NpmUnknownCommandException(string message) + : base(message) + { + } + } +} diff --git a/test/integration/helpers/PathToLinuxNode b/test/integration/helpers/PathToLinuxNode deleted file mode 100644 index 1932de20..00000000 --- a/test/integration/helpers/PathToLinuxNode +++ /dev/null @@ -1 +0,0 @@ -/Applications/Appium.app/Contents/Resources/node_modules/appium/bin/appium.js \ No newline at end of file diff --git a/test/integration/helpers/PathToMacOSNode b/test/integration/helpers/PathToMacOSNode deleted file mode 100644 index 1932de20..00000000 --- a/test/integration/helpers/PathToMacOSNode +++ /dev/null @@ -1 +0,0 @@ -/Applications/Appium.app/Contents/Resources/node_modules/appium/bin/appium.js \ No newline at end of file diff --git a/test/integration/helpers/PathToWindowsNode b/test/integration/helpers/PathToWindowsNode deleted file mode 100644 index ed0acb16..00000000 --- a/test/integration/helpers/PathToWindowsNode +++ /dev/null @@ -1 +0,0 @@ -C:/Program Files (x86)/Appium/node_modules/appium/bin/appium.js \ No newline at end of file diff --git a/test/integration/helpers/Paths.cs b/test/integration/helpers/Paths.cs new file mode 100644 index 00000000..a61d2225 --- /dev/null +++ b/test/integration/helpers/Paths.cs @@ -0,0 +1,36 @@ +using Appium.Net.Integration.Tests.helpers; +using System.IO; + +namespace Appium.Net.Integration.Tests.Helpers +{ + internal class Paths + { + private string _pathToAppiumPackageIndex; + + public string PathToAppiumPackageIndex + { + get + { + if (_pathToAppiumPackageIndex == null) + { + InitAppiumPackageIndexPath(); + } + return _pathToAppiumPackageIndex; + } + } + + /// + /// Initializes the Appium package index path by combining the components "appium" and "index.js" with the npm prefix path. + /// + /// + /// This method sets the _pathToAppiumPackageIndex variable by combining the specified components with the npm prefix path. + /// + private void InitAppiumPackageIndexPath() + { + string[] appiumJsPathComponents = { "appium", "index.js" }; + string npmPath = Npm.GetNpmPrefixPath(); + + _pathToAppiumPackageIndex = Path.Combine(npmPath, Path.Combine(appiumJsPathComponents)); + } + } +}