diff --git a/.github/workflows/publish-gem.yml b/.github/workflows/publish-gem.yml index 2120f60..e4fc70c 100644 --- a/.github/workflows/publish-gem.yml +++ b/.github/workflows/publish-gem.yml @@ -49,6 +49,7 @@ jobs: - name: Run checks on the release commit run: | bundle exec rake rubocop + bundle exec rake test bundle exec ruby -Ilib bin/arc version - name: Validate the built gem id: package diff --git a/.github/workflows/release-validation.yml b/.github/workflows/release-validation.yml index 672672f..90d5830 100644 --- a/.github/workflows/release-validation.yml +++ b/.github/workflows/release-validation.yml @@ -23,6 +23,7 @@ jobs: - name: Check console run: | bundle exec rake rubocop + bundle exec rake test bundle exec ruby -Ilib bin/arc version - name: Build gem run: bundle exec rake build diff --git a/Gemfile b/Gemfile index 8b6a597..21b14db 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,6 @@ source 'https://rubygems.org' gemspec +gem 'minitest', '~> 5.0' gem 'rake', '~> 13.0' gem 'rubocop', '1.91.0' diff --git a/Rakefile b/Rakefile index 5809fd7..191b76e 100644 --- a/Rakefile +++ b/Rakefile @@ -2,6 +2,11 @@ require 'bundler/gem_tasks' require 'rake/testtask' require 'rubocop/rake_task' +Rake::TestTask.new(:test) do |t| + t.libs << 'lib' + t.pattern = 'test/**/*_test.rb' +end + desc('Execute RuboCop static code analysis') RuboCop::RakeTask.new(:rubocop) do |t| t.patterns = %w(lib test) diff --git a/lib/appium_console.rb b/lib/appium_console.rb index 2f31977..dfdd40b 100644 --- a/lib/appium_console.rb +++ b/lib/appium_console.rb @@ -43,7 +43,7 @@ def start requires = parsed[:appium_lib][:require] unless requires.empty? - load_files = requires.map { |f| %(require "#{f}";) }.join "\n" + load_files = requires.map { |f| "require #{f.dump};" }.join "\n" cmd += ['-e', load_files] end diff --git a/lib/cli.rb b/lib/cli.rb index 17713cc..9f55ada 100644 --- a/lib/cli.rb +++ b/lib/cli.rb @@ -18,6 +18,7 @@ def template(caps) <<-TEMPLATE.gsub(/skip\s/, '') [caps] platformName = "#{caps[:platform_name]}" +automationName = "#{caps[:automation_name]}" #{caps[:platform_version] ? "platformVersion = \"#{caps[:platform_version]}\"" : 'skip'} #{caps[:device_name] ? "deviceName = \"#{caps[:device_name]}\"" : 'skip'} app = "#{caps[:path_to_app]}" @@ -25,7 +26,7 @@ def template(caps) #{caps[:app_activity] ? "appActivity = \"#{caps[:app_activity]}\"" : 'skip'} [appium_lib] -server_url = "http://127.0.0.1:4723/wd/hub" +server_url = "http://127.0.0.1:4723/" TEMPLATE end end @@ -33,6 +34,7 @@ def template(caps) class Setup < Thor desc 'ios', 'Generates toml for ios' + method_option :force, type: :boolean, default: false, desc: 'Overwrite an existing appium.txt' def ios toml = File.join(Dir.pwd, Config.default_appium_txt_path) template = Config.template( @@ -42,10 +44,11 @@ def ios platform_version: '15.0', path_to_app: '/path/to/app_bundle' ) - File.write(toml, template) + write_config(toml, template) end desc 'android', 'Generates toml for android' + method_option :force, type: :boolean, default: false, desc: 'Overwrite an existing appium.txt' def android toml = File.join(Dir.pwd, Config.default_appium_txt_path) template = Config.template( @@ -56,11 +59,23 @@ def android app_package: 'com.package.example', app_activity: 'com.package.example.ExampleActivity' ) - File.write(toml, template) + write_config(toml, template) + end + + private + + def write_config(path, template) + File.write(path, template, mode: options[:force] ? 'w' : 'wx') + rescue Errno::EEXIST + raise Thor::Error, "#{path} already exists. Use --force to overwrite it." end end class Main < Thor + def self.exit_on_failure? + true + end + desc 'version', 'Prints version of appium_lib and appium_console' def version puts <<-VERSION diff --git a/readme.md b/readme.md index 8a23bae..1419257 100644 --- a/readme.md +++ b/readme.md @@ -19,12 +19,18 @@ - `arc version` prints the current version of appium console and appium lib. - `arc setup android` creates `appium.txt` for android in the current working dir. - `arc setup ios` creates `appium.txt` for ios in the current working dir. + - Setup preserves an existing `appium.txt`. Add `--force` to overwrite it. - `arc toml FILE` starts arc with toml FILE path ## Documentation - [ruby_lib](https://github.com/appium/ruby_lib) +## Development + +Run `bundle install`, then `bundle exec rake test` and `bundle exec rake rubocop`. +Tests run without an Appium server or device. + ## Changelog - 4.0.0 - Drop Ruby 2.7 support diff --git a/test/cli_test.rb b/test/cli_test.rb new file mode 100644 index 0000000..370f56e --- /dev/null +++ b/test/cli_test.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require 'minitest/autorun' +require 'open3' +require 'tmpdir' +require 'tomlrb' + +class CliTest < Minitest::Test + ROOT = File.expand_path('..', __dir__) + + def run_arc(*args) + Open3.capture3(Gem.ruby, '-I', File.join(ROOT, 'lib'), File.join(ROOT, 'bin/arc'), *args) + end + + def test_setup_generates_driver_and_server_settings + { 'ios' => 'XCUITest', 'android' => 'uiautomator2' }.each do |platform, driver| + Dir.mktmpdir do |dir| + Dir.chdir(dir) do + _, stderr, status = run_arc('setup', platform) + assert status.success?, stderr + config = Tomlrb.load_file('appium.txt') + assert_equal driver, config.fetch('caps').fetch('automationName') + assert_equal platform == 'ios' ? 'iOS' : 'Android', config.fetch('caps').fetch('platformName') + assert_equal 'http://127.0.0.1:4723/', config.fetch('appium_lib').fetch('server_url') + end + end + end + end + + def test_setup_preserves_existing_config_unless_forced + %w[ios android].each do |platform| + Dir.mktmpdir do |dir| + Dir.chdir(dir) do + File.write('appium.txt', 'custom configuration') + _, stderr, status = run_arc('setup', platform) + refute status.success? + assert_includes stderr, '--force' + assert_equal 'custom configuration', File.read('appium.txt') + + _, stderr, status = run_arc('setup', platform, '--force') + assert status.success?, stderr + assert Tomlrb.load_file('appium.txt').fetch('caps').key?('automationName') + end + end + end + end + + def test_version_command + stdout, stderr, status = run_arc('version') + assert status.success?, stderr + assert_includes stdout, 'appium_console:' + assert_includes stdout, 'appium_lib:' + end +end diff --git a/test/console_test.rb b/test/console_test.rb new file mode 100644 index 0000000..12c5ab9 --- /dev/null +++ b/test/console_test.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require 'minitest/autorun' +require 'minitest/mock' +require 'open3' +require 'tmpdir' +require 'appium_console' + +class ConsoleTest < Minitest::Test + def test_start_requires_files_with_special_characters_literally + Dir.mktmpdir do |dir| + file = File.join(dir, 'page"\\#{raise "interpolated"}.rb') + File.write(file, 'puts "page loaded"') + parsed = { appium_lib: { require: [file] } } + Appium::Console.setup('appium.txt') + launch = lambda do |args| + assert_equal '-r', args.first + assert_equal File.expand_path('../lib/start.rb', __dir__), args[1] + assert_equal '-e', args[2] + stdout, stderr, status = Open3.capture3(Gem.ruby, '-e', args[3]) + assert status.success?, stderr + assert_equal "page loaded\n", stdout + end + Appium.stub(:load_appium_txt, parsed) do + Pry::CLI.stub(:parse_options, ->(args) { args }) do + Pry::CLI.stub(:start, launch) { capture_io { Appium::Console.start } } + end + end + ensure + Pry.hooks.delete_hook(:after_session, 'Release session hook') + end + end + + def test_reload_loads_changed_file_contents + Dir.mktmpdir do |dir| + file = File.join(dir, 'page.rb') + Appium::Console.setup('appium.txt') + Appium.stub(:load_appium_txt, { appium_lib: { require: [file] } }) do + File.write(file, 'puts "first version"') + assert_output("first version\n") { Pry.reload } + File.write(file, 'puts "second version"') + assert_output("second version\n") { Pry.reload } + end + end + end +end