diff --git a/CHANGELOG.md b/CHANGELOG.md index 2105525e5..3c7702800 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# TBD + +## Enhancements + +- Failed driver guards added to File Manager API [709](https://github.com/bugsnag/maze-runner/pull/709) + # 9.22.0 - 2025/01/08 ## Enhancements diff --git a/bin/maze-runner b/bin/maze-runner index 2fd45007b..04ea972bf 100755 --- a/bin/maze-runner +++ b/bin/maze-runner @@ -11,6 +11,7 @@ require_relative '../lib/utils/deep_merge' require_relative '../lib/maze' require_relative '../lib/maze/appium_server' +require_relative '../lib/maze/api/appium/manager' require_relative '../lib/maze/api/appium/file_manager' require_relative '../lib/maze/api/cucumber/scenario' require_relative '../lib/maze/api/exit_code' diff --git a/lib/maze/api/appium/file_manager.rb b/lib/maze/api/appium/file_manager.rb index ac5ac3ec5..b5132ab6d 100644 --- a/lib/maze/api/appium/file_manager.rb +++ b/lib/maze/api/appium/file_manager.rb @@ -1,29 +1,43 @@ +require_relative '../../helper' +require_relative './manager' + module Maze module Api module Appium # Provides operations for working with files during Appium runs. - class FileManager - # param driver - def initialize - @driver = Maze.driver - end - + class FileManager < Maze::Api::Appium::Manager # Creates a file with the given contents on the device (using Appium). The file will be located in the app's # documents directory for iOS. On Android, it will be /sdcard/Android/data//files unless # Maze.config.android_app_files_directory has been set. # @param contents [String] Content of the file to be written # @param filename [String] Name (with no path) of the file to be written on the device + # @return [Boolean] Whether the file was successfully written to the device def write_app_file(contents, filename) + if failed_driver? + $logger.error 'Cannot write file to device - Appium driver failed.' + return false + end + path = case Maze::Helper.get_current_platform when 'ios' "@#{@driver.app_id}/Documents/#{filename}" when 'android' directory = Maze.config.android_app_files_directory || "/sdcard/Android/data/#{@driver.app_id}/files" "#{directory}/#{filename}" + else + raise 'write_app_file is not supported on this platform' end $logger.trace "Pushing file to '#{path}' with contents: #{contents}" @driver.push_file(path, contents) + true + rescue Selenium::WebDriver::Error::UnknownError => e + $logger.error "Error writing file to device: #{e.message}" + false + rescue Selenium::WebDriver::Error::ServerError => e + # Assume the remote appium session has stopped, so crash out of the session + fail_driver + raise e end # Attempts to retrieve a given file from the device (using Appium). The default location for the file will be @@ -31,9 +45,15 @@ def write_app_file(contents, filename) # Maze.config.android_app_files_directory has been set. # @param filename [String] Name (with no path) of the file to be retrieved from the device # @param directory [String] Directory on the device where the file is located (optional) + # @return [String, nil] The content of the file read, or nil def read_app_file(filename, directory = nil) + if failed_driver? + $logger.error 'Cannot read file from device - Appium driver failed.' + return nil + end + if directory - path = directory + path = "#{directory}/#{filename}" else path = case Maze::Helper.get_current_platform when 'ios' @@ -41,11 +61,20 @@ def read_app_file(filename, directory = nil) when 'android' dir = Maze.config.android_app_files_directory || "/sdcard/Android/data/#{@driver.app_id}/files" "#{dir}/#{filename}" + else + raise 'read_app_file is not supported on this platform' end end $logger.trace "Attempting to read file from '#{path}'" - file = @driver.pull_file(path) + @driver.pull_file(path) + rescue Selenium::WebDriver::Error::UnknownError => e + $logger.error "Error reading file from device: #{e.message}" + nil + rescue Selenium::WebDriver::Error::ServerError => e + # Assume the remote appium session has stopped, so crash out of the session + fail_driver + raise e end end end diff --git a/lib/maze/api/appium/manager.rb b/lib/maze/api/appium/manager.rb new file mode 100644 index 000000000..c52ea8d0f --- /dev/null +++ b/lib/maze/api/appium/manager.rb @@ -0,0 +1,20 @@ +module Maze + module Api + module Appium + # Base class for all Appium managers. + class Manager + def initialize + @driver = Maze.driver + end + + def failed_driver? + @driver.failed? + end + + def fail_driver + @driver.fail_driver + end + end + end + end +end diff --git a/test/api/appium/file_manager_test.rb b/test/api/appium/file_manager_test.rb new file mode 100644 index 000000000..e956638db --- /dev/null +++ b/test/api/appium/file_manager_test.rb @@ -0,0 +1,78 @@ +require 'selenium-webdriver' + +require_relative '../../test_helper' +require_relative '../../../lib/maze' +require_relative '../../../lib/maze/api/appium/file_manager' + +module Maze + module Api + module Appium + class FileManagerTest < Test::Unit::TestCase + + def setup() + $logger = mock('logger') + @mock_driver = mock('driver') + Maze.driver = @mock_driver + @manager = Maze::Api::Appium::FileManager.new + end + + def test_write_app_file_failed_driver + @mock_driver.expects(:failed?).returns(true) + $logger.expects(:error).with("Cannot write file to device - Appium driver failed.") + + @manager.write_app_file('contents', 'filename.json') + end + + def test_write_app_file_success + @mock_driver.expects(:failed?).returns(false) + @mock_driver.expects(:app_id).returns('app1') + Maze::Helper.expects(:get_current_platform).returns('ios') + $logger.expects(:trace).with("Pushing file to '@app1/Documents/filename.json' with contents: contents") + @mock_driver.expects(:push_file).with('@app1/Documents/filename.json', 'contents') + + assert_true(@manager.write_app_file('contents', 'filename.json')) + end + + def test_write_app_file_failure + @mock_driver.expects(:failed?).returns(false) + @mock_driver.expects(:app_id).returns('app1') + Maze::Helper.expects(:get_current_platform).returns('ios') + $logger.expects(:trace).with("Pushing file to '@app1/Documents/filename.json' with contents: contents") + @mock_driver.expects(:push_file).with('@app1/Documents/filename.json', 'contents').raises(Selenium::WebDriver::Error::UnknownError, 'error') + $logger.expects(:error).with("Error writing file to device: error") + + assert_false(@manager.write_app_file('contents', 'filename.json')) + end + + def test_read_app_file_failed_driver + @mock_driver.expects(:failed?).returns(true) + $logger.expects(:error).with("Cannot read file from device - Appium driver failed.") + + @manager.read_app_file('filename.json') + end + + def test_read_app_file_success + @mock_driver.expects(:failed?).returns(false) + @mock_driver.expects(:app_id).returns('app1') + Maze::Helper.expects(:get_current_platform).returns('ios') + $logger.expects(:trace).with("Attempting to read file from '@app1/Documents/filename.json'") + @mock_driver.expects(:pull_file).with('@app1/Documents/filename.json').returns('contents') + + assert_equal('contents', @manager.read_app_file('filename.json')) + end + + def test_read_app_file_failure + @mock_driver.expects(:failed?).returns(false) + @mock_driver.expects(:app_id).returns('app1') + Maze::Helper.expects(:get_current_platform).returns('ios') + $logger.expects(:trace).with("Attempting to read file from '@app1/Documents/filename.json'") + @mock_driver.expects(:pull_file).with('@app1/Documents/filename.json').raises(Selenium::WebDriver::Error::UnknownError, 'error') + + $logger.expects(:error).with("Error reading file from device: error") + + assert_nil(@manager.read_app_file('filename.json')) + end + end + end + end +end