/// <summary> /// Send a screen dump request to the ui automator server on the android device. /// </summary> /// <returns>The screen content as a xml string.</returns> public string DumpUi() { using (var client = new HttpClient { Timeout = TimeSpan.FromSeconds(_dumpTimeout) }) { try { var repsonse = client.GetAsync(DumpUrl).Result; var dump = repsonse.Content.ReadAsStringAsync().Result; if (string.IsNullOrEmpty(dump)) { DeviceLogger.Log("Empty dump from server!"); DeviceLogger.Log($"Status code: {repsonse.StatusCode}"); DeviceLogger.Log($"Reason phrase: {repsonse.ReasonPhrase}"); throw new UiAutomatorServerException("Could connect to server but the dumped ui was empty"); } return(dump.Replace("\\\"", "\"")); } catch (AggregateException ex) { DeviceLogger.Log("Unexpected error/timed out when trying to dump: "); DeviceLogger.Log(ex.ToString()); throw new UiAutomatorServerException("Failed to dump screen", ex); } } }
/// <summary> /// Start an Activity specified by package and activity name /// </summary> /// <param name="packageName">Name of the package</param> /// <param name="activity">Name of the activity</param> /// <param name="forceStopActivity">Force stop the target application before starting the activity.</param> /// <param name="clearTasks">Clear activity stack</param> /// <exception cref="AdbException">Thrown if we can't find package/activity</exception> public void Start(string packageName, string activity, bool forceStopActivity, bool clearTasks) { if (string.IsNullOrEmpty(packageName)) { throw new ArgumentException("Argument is null or empty", nameof(packageName)); } if (string.IsNullOrWhiteSpace(activity)) { throw new ArgumentException("Argument is null or whitespace", nameof(activity)); } DeviceLogger.Log("Starting a new activity"); var commandBuilder = new StringBuilder($"am start -W -n {packageName}/{activity}"); if (forceStopActivity) { commandBuilder.Append(" -S"); } if (clearTasks) { commandBuilder.Append(" --activity-clear-task"); } var result = Device.Adb.Shell(commandBuilder.ToString()); if (result.Contains("Error") || result.Contains("does not exist") || result.Contains("Exception")) { throw new AdbException(result); } Device.Ui.ClearCache(); }
/// <summary> /// Stop the ui automator server on the android device. /// </summary> public void Stop() { lock (_serverLock) { DeviceLogger.Log("Stopping server"); try { using (var client = new HttpClient() { Timeout = TimeSpan.FromSeconds(5) }) { client.GetAsync(StopUrl); } } catch (Exception ex) when(ex is AggregateException || ex is HttpRequestException || ex is WebException) { DeviceLogger.Log($"Failed stop server, already closed? {ex.Message}"); } if (_currentServerProcess != null) { KillLocalProcess(_currentServerProcess.Process.Id); } // Kill android process just to be safe.. KillAndroidProcess(); } }
/// <summary> /// Kill the local process and it's children. /// </summary> /// <param name="pid">The process id.</param> private void KillLocalProcess(int pid) { var processSearcher = new ManagementObjectSearcher("Select * From Win32_Process Where ParentProcessID=" + pid); var processCollection = processSearcher.Get(); try { var proc = Process.GetProcessById(pid); if (!proc.HasExited) { proc.Kill(); } } catch (ArgumentException) { // Process already exited. } catch (Exception ex) { DeviceLogger.Log($"Something went wrong when trying to kill local process: {ex}"); } if (processCollection != null) { foreach (var mo in processCollection) { KillLocalProcess(Convert.ToInt32(mo["ProcessID"])); } } }
/// <summary> /// Enable or disable bluetooth. /// </summary> /// <param name="state">Wanted state of bluetooth.</param> public void Bluetooth(State state) { DeviceLogger.Log("Changing wifi state"); Device.Adb.Shell(state == State.Enable ? $"am startservice -n {PackageName}/{PackageName}.services.settings.BluetoothService -e enable 1" : $"am startservice -n {PackageName}/{PackageName}.services.settings.BluetoothService -e enable 0"); }
/// <summary> /// Start the ui automator server on the android device. /// </summary> /// <exception cref="UiAutomatorServerException">The exception thrown if we can't server.</exception> public void Start() { lock (_serverLock) { DeviceLogger.Log("Starting server.."); if (_currentServerProcess == null || _currentServerProcess.Process.HasExited) { ForwardPorts(); KillAndroidProcess(); DeviceLogger.Log("Starting instrumental"); _currentServerProcess = _terminal.StartAdbProcess( "shell", $"am instrument -w -r -e debug false -e class {AndroidPackageName}.Start {AndroidPackageName}.test/android.support.test.runner.AndroidJUnitRunner"); } else { DeviceLogger.Log("Server already started"); } if (!Alive(5)) { throw new UiAutomatorServerException("Could not start server"); } } }
/// <summary> /// Enable or disable airplane mode /// </summary> /// <param name="state">Wanted state of airplane mode</param> public void AirplaneMode(State state) { DeviceLogger.Log("Changing airplane mode state"); Device.Adb.Shell(state == State.Enable ? "settings put global airplane_mode_on 1" : "settings put global airplane_mode_on 0"); Device.Adb.Shell("am broadcast -a android.intent.action.AIRPLANE_MODE"); }
/// <summary> /// Perform a swipe motion on the screen. /// </summary> /// <param name="fromX">Start x position on screen</param> /// <param name="fromY">Start y position on screen</param> /// <param name="toX">Final x position on screen</param> /// <param name="toY">Final y position on screen</param> /// <param name="duration">Duration of the swipe in milliseconds</param> public void Swipe(int fromX, int fromY, int toX, int toY, int duration) { if (!_interactionServer.Swipe(fromX, fromY, toX, toY, duration)) { DeviceLogger.Log("Failed to swipe through server, trying through adb."); Device.Adb.Shell($"input swipe {fromX} {fromY} {toX} {toY} {duration}"); } Device.Ui.ClearCache(); }
/// <summary> /// Send a key event to the device. /// </summary> /// <param name="keyEvent">Key event to send to the device</param> public void InputKeyEvent(KeyEvents keyEvent) { if (!_interactionServer.InputKeyEvent(keyEvent)) { DeviceLogger.Log("Failed to input key event through server, trying through adb."); Device.Adb.Shell($"input keyevent {(int)keyEvent}"); } Device.Ui.ClearCache(); }
/// <summary> /// Tap on an x and y position of the screen. /// </summary> /// <param name="x">The x position</param> /// <param name="y">The y position</param> public void Tap(int x, int y) { if (!_interactionServer.Tap(x, y)) { DeviceLogger.Log("Failed to tap through server, trying through adb."); Device.Adb.Shell($"input tap {x} {y}"); } Device.Ui.ClearCache(); }
/// <summary> /// Kill the android process. /// </summary> private void KillAndroidProcess() { try { _terminal.ExecuteAdbCommand("shell", $"ps | grep {AndroidPackageName}"); DeviceLogger.Log("Killing testura helper process on the device."); _terminal.ExecuteAdbCommand("shell", $"pm clear {AndroidPackageName}"); } catch (Exception) { // The terminal throw an exception if we can't find anything with grep. } }
/// <summary> /// Get the current open activity /// </summary> /// <returns>Current open activity</returns> public string GetCurrent() { DeviceLogger.Log("Getting current activity"); var activity = Device.Adb.Shell("dumpsys window windows | grep -E 'mCurrentFocus|mFocusedApp'"); var regex = new Regex(@"(?<=\{)[^}]*(?=\})"); var matches = regex.Matches(activity); if (matches.Count == 0) { return("Unknown activity"); } return(matches[0].Value.Split(' ').Last()); }
/// <summary> /// Start the logcat watcher task. /// </summary> public void Start() { DeviceLogger.Log("Starting logcat watcher.."); if (_flushLogcat) { _terminal.ExecuteAdbCommand(new[] { "logcat", "-c" }); } _cancellationTokenSource = new CancellationTokenSource(); var commands = new List <string> { "shell", "logcat", "-s" }; commands.AddRange(_tags); var process = _terminal.StartAdbProcessWithoutShell(commands.ToArray()); var cancellationToken = _cancellationTokenSource.Token; _task = Task.Run( () => { process.StandardOutput.BaseStream.ReadTimeout = 500; while (true) { try { var output = process.StandardOutput.ReadLine(); if (!string.IsNullOrEmpty(output)) { NewOutput(output); } } catch (TimeoutException) { } if (_cancellationTokenSource.IsCancellationRequested) { DeviceLogger.Log("Logcat watcher cancellation requested, stopping task."); return; } } }, cancellationToken); }
private void SetScreenHeightAndWidth() { DeviceLogger.Log("Getting width and height"); var widthAndHeight = Device.Adb.Shell("wm size"); if (string.IsNullOrEmpty(widthAndHeight)) { throw new AdbException("Could not get screen width and height"); } var split = widthAndHeight.Replace(" ", string.Empty).Split(':', 'x'); DeviceLogger.Log($"Width: {split[split.Length - 2]}, Height: {split[split.Length - 1]}"); _screenBounds = new NodeBounds(int.Parse(split[split.Length - 2]), int.Parse(split[split.Length - 1])); }
/// <summary> /// Input text into the node /// </summary> /// <param name="text">The text to input into the node</param> public void InputText(string text) { if (text == null) { throw new ArgumentNullException(nameof(text)); } if (!_interactionServer.InputText(text)) { DeviceLogger.Log("Failed to input text through server, trying through adb."); Device.Adb.Shell($"input text {text.Replace(" ", "%s")}"); } Device.Ui.ClearCache(); }
/// <summary> /// Check if the ui automator server is alive on the android device. /// </summary> /// <param name="timeout">Timeout in seconds.</param> /// <returns>True if server is a alive, false otherwise.</returns> public bool Alive(int timeout) { var time = DateTime.Now; while ((DateTime.Now - time).TotalSeconds < timeout) { var result = Ping(); if (result) { return(true); } } DeviceLogger.Log("Server is not alive!"); return(false); }
/// <summary> /// Enable or disable gps /// </summary> /// <param name="state">Wanted state of gps</param> public void Gps(State state) { DeviceLogger.Log("Changing gps state"); if (state == State.Enable) { Device.Adb.Shell("settings put secure location_providers_allowed +gps"); Device.Adb.Shell("settings put secure location_providers_allowed +network"); Device.Adb.Shell("settings put secure location_providers_allowed +wifi"); } else { Device.Adb.Shell("settings put secure location_providers_allowed -gps"); Device.Adb.Shell("settings put secure location_providers_allowed -network"); Device.Adb.Shell("settings put secure location_providers_allowed -wifi"); } }
public void InstallDependenciesIfMissing(IAdbService adbService, IActivityService activityService, DeviceConfiguration configuration) { DeviceLogger.Log("Checking if helper is installed.."); if (!activityService.IsPackagedInstalled("com.testura.server")) { DeviceLogger.Log("..not installed."); InstallDependencies(adbService, configuration); } else { DeviceLogger.Log("..already installed."); var latestVersion = Version.Parse(DeviceConfiguration.ServerApkVersion); if (activityService.GetPackageVersion("com.testura.server") < latestVersion) { DeviceLogger.Log("But you don't have the current/latest version. Updating your dependencies"); InstallDependencies(adbService, configuration); } } }
/// <summary> /// Start the adb process and return the command. /// </summary> /// <param name="arguments">Arguments that should be provided to adb.</param> /// <returns>The command that contains the started process.</returns> public Command StartAdbProcess(params string[] arguments) { var allArguments = new List <string> { "/c", GetAdbExe() }; if (!string.IsNullOrEmpty(_deviceConfiguration.Serial)) { allArguments.Add("-s"); allArguments.Add(_deviceConfiguration.Serial); } allArguments.AddRange(arguments); DeviceLogger.Log($"Starting adb process with shell: {string.Join(" ", allArguments)}"); try { var command = Command.Run( "cmd.exe", allArguments.ToArray(), o => { o.StartInfo(si => { si.CreateNoWindow = false; si.UseShellExecute = true; si.RedirectStandardError = false; si.RedirectStandardInput = false; si.RedirectStandardOutput = false; }); o.DisposeOnExit(false); }); return(command); } catch (Win32Exception) { throw new AdbException(AdbNotFoundError); } }
/// <summary> /// Execute a new adb command. /// </summary> /// <param name="arguments">Arguments to send to the adb.</param> /// <returns>Output from adb.</returns> public string ExecuteAdbCommand(params string[] arguments) { var allArguments = new List <string>(); if (!string.IsNullOrEmpty(_deviceConfiguration.Serial)) { allArguments.Add("-s"); allArguments.Add(_deviceConfiguration.Serial); } allArguments.AddRange(arguments); DeviceLogger.Log($"Sending adb command: {string.Join(" ", allArguments)}"); try { using (var command = Command.Run( GetAdbExe(), allArguments, options: o => o.Timeout(TimeSpan.FromMinutes(1)))) { var output = command.StandardOutput.ReadToEnd(); var error = command.StandardError.ReadToEnd(); if (!command.Result.Success) { var message = $"Output: {output}, Error: {error}"; DeviceLogger.Log(message); throw new AdbException(message); } return(output); } } catch (Win32Exception) { throw new AdbException(AdbNotFoundError); } }
private string GetDump() { int tries = _dumpTries; while (true) { try { if (!_server.Alive(2)) { _server.Start(); } return(_server.DumpUi()); } catch (UiAutomatorServerException) { if (tries > 0) { DeviceLogger.Log($"Failed to dump UI, trying {tries} more times"); tries--; /* In some cases we get stuck and the server is alive * but we can't dump the UI. So try a reboot */ if (_server.Alive(2)) { DeviceLogger.Log("Server alive but we can't dump.. trying a reboot."); _server.Stop(); } continue; } DeviceLogger.Log("Tried everything but still can't dump the screen. Glitch in the matrix or did your device freeze?"); throw; } } }
/// <summary> /// Send interaction request to server /// </summary> /// <param name="url">Url of the request</param> /// <param name="timeout">Timeout of request</param> /// <returns>True if we managed to perform interaction, otherwise false.</returns> private bool SendInteractionRequest(string url, TimeSpan timeout) { if (!Alive(5)) { Start(); } DeviceLogger.Log($"Sending interaction request to server: {url}"); using (var client = new HttpClient { Timeout = timeout }) { try { var repsonse = client.GetAsync(url).Result; if (!repsonse.IsSuccessStatusCode) { if (repsonse.StatusCode == HttpStatusCode.NotFound) { throw new UiAutomatorServerException( "Server responded with 404, make sure that you have the latest Testura server app."); } return(false); } var result = repsonse.Content.ReadAsStringAsync().Result; return(result == "success"); } catch (AggregateException) { DeviceLogger.Log("interaction request timed out"); return(false); } } }
public void InstallDependencies(IAdbService adbService, DeviceConfiguration configuration) { DeviceLogger.Log("Installing all dependencies.."); adbService.InstallApp(Path.Combine(configuration.DependenciesDirectory, DeviceConfiguration.ServerApkName)); adbService.InstallApp(Path.Combine(configuration.DependenciesDirectory, DeviceConfiguration.ServerUiAutomatorApkName)); }
/// <summary> /// Forward ports to the android device. /// </summary> private void ForwardPorts() { DeviceLogger.Log("Forwarding ports"); _terminal.ExecuteAdbCommand("forward", $"tcp:{_localPort}", $"tcp:{DevicePort}"); }
/// <summary> /// Stop the logcat watcher task. /// </summary> public void Stop() { DeviceLogger.Log("Request to stop logcat watcher.."); _cancellationTokenSource?.Cancel(); _task?.Wait(2000); }