├── .github └── CODEOWNERS ├── template ├── new │ ├── system │ │ ├── logs │ │ │ └── README.md │ │ └── evidences │ │ │ ├── README.md │ │ │ └── screen_shots │ │ │ └── README.md │ ├── services │ │ └── README.md │ ├── .bucky_home │ └── config │ │ ├── linkstatus_config.yml │ │ ├── bucky_config.yml │ │ ├── test_db_config.yml │ │ └── e2e_config.yml └── make_page │ ├── sp │ ├── parts │ │ └── sample_page.yml │ └── pageobject │ │ └── sample_page.rb │ ├── pc │ ├── parts │ │ └── sample_page.yml │ └── pageobject │ │ └── sample_page.rb │ └── tablet │ ├── parts │ └── sample_page.yml │ └── pageobject │ └── sample_page.rb ├── example ├── hands-on │ ├── system │ │ ├── logs │ │ │ └── README.md │ │ └── evidences │ │ │ ├── README.md │ │ │ └── screen_shots │ │ │ └── README.md │ ├── services │ │ ├── README.md │ │ └── bucky_hands_on │ │ │ └── pc │ │ │ ├── parts │ │ │ ├── github_search_list.yml │ │ │ └── github_top.yml │ │ │ ├── scenarios │ │ │ ├── linkstatus │ │ │ │ └── github_top.yml │ │ │ └── e2e │ │ │ │ └── search_and_assert.yml │ │ │ └── pageobject │ │ │ ├── github_top.rb │ │ │ └── github_search_list.rb │ ├── .bucky_home │ └── config │ │ ├── linkstatus_config.yml │ │ ├── bucky_config.yml │ │ ├── test_db_config.yml │ │ └── e2e_config.yml ├── bucky-management │ ├── system │ │ ├── logs │ │ │ └── README.md │ │ └── evidences │ │ │ ├── README.md │ │ │ └── screen_shots │ │ │ └── README.md │ ├── services │ │ ├── README.md │ │ └── bucky_example │ │ │ └── pc │ │ │ ├── parts │ │ │ ├── github_search_list.yml │ │ │ └── github_top.yml │ │ │ ├── scenarios │ │ │ ├── linkstatus │ │ │ │ └── github_top.yml │ │ │ └── e2e │ │ │ │ └── search_and_assert.yml │ │ │ └── pageobject │ │ │ ├── github_top.rb │ │ │ └── github_search_list.rb │ ├── .bucky_home │ ├── config │ │ ├── linkstatus_config.yml │ │ ├── test_db_config.yml │ │ ├── bucky_config.yml │ │ └── e2e_config.yml │ └── README.md └── README.md ├── .rspec ├── lib ├── bucky.rb └── bucky │ ├── version.rb │ ├── test_equipment │ ├── verifications │ │ ├── abst_verification.rb │ │ ├── service_verifications.rb │ │ └── e2e_verification.rb │ ├── user_operation │ │ ├── user_operation_logger.rb │ │ ├── user_operator.rb │ │ └── user_operation_helper.rb │ ├── selenium_handler │ │ ├── wait_handler.rb │ │ └── webdriver_handler.rb │ ├── test_case │ │ ├── linkstatus_test_case.rb │ │ ├── abst_test_case.rb │ │ └── e2e_test_case.rb │ ├── evidence │ │ └── evidence_generator.rb │ └── pageobject │ │ ├── pages.rb │ │ └── base_pageobject.rb │ ├── utils │ ├── bucky_output.rb │ ├── yaml_load.rb │ ├── bucky_logger.rb │ ├── requests.rb │ └── config.rb │ ├── core │ ├── test_core │ │ ├── exit_handler.rb │ │ ├── test_result.rb │ │ ├── test_class_generator.rb │ │ ├── test_case_loader.rb │ │ └── test_manager.rb │ ├── report │ │ └── screen_shot_generator.rb │ ├── database │ │ └── db_connector.rb │ └── exception │ │ └── bucky_exception.rb │ └── tools │ └── lint.rb ├── system_testing ├── test_bucky_project │ ├── system │ │ ├── logs │ │ │ └── README.md │ │ └── evidences │ │ │ ├── README.md │ │ │ └── screen_shots │ │ │ └── README.md │ ├── services │ │ ├── README.md │ │ └── service_a │ │ │ ├── sp │ │ │ ├── parts │ │ │ │ └── index.yml │ │ │ ├── scenarios │ │ │ │ ├── linkstatus │ │ │ │ │ └── sp_link.yml │ │ │ │ └── e2e │ │ │ │ │ └── sp_e2e_test.yml │ │ │ └── pageobject │ │ │ │ └── index.rb │ │ │ ├── tablet │ │ │ ├── parts │ │ │ │ └── index.yml │ │ │ ├── pageobject │ │ │ │ └── index.rb │ │ │ └── scenarios │ │ │ │ └── e2e │ │ │ │ └── tablet_e2e_test.yml │ │ │ └── pc │ │ │ ├── parts │ │ │ └── index.yml │ │ │ ├── scenarios │ │ │ ├── linkstatus │ │ │ │ ├── pc_link_exclude_asterisk.yml │ │ │ │ ├── pc_link_exclude_regex.yml │ │ │ │ ├── pc_link_exclude_normal.yml │ │ │ │ └── pc_link.yml │ │ │ └── e2e │ │ │ │ ├── setup_each_pc_e2e.yml │ │ │ │ ├── teardown_each_pc_e2e.yml │ │ │ │ ├── setup_teardown_each_pc_e2e.yml │ │ │ │ └── pc_e2e.yml │ │ │ ├── pageobject │ │ │ └── index.rb │ │ │ └── verifications │ │ │ └── index.rb │ ├── .bucky_home │ └── config │ │ ├── linkstatus_config.yml │ │ ├── bucky_config.yml │ │ ├── test_db_config.yml │ │ └── e2e_config.yml └── testing_code │ ├── command.bats │ ├── linkstatus.bats │ └── e2e.bats ├── Gemfile ├── spec ├── utils │ ├── config_not_in_template.yml │ ├── bucky_config.yml │ ├── linkstatus_config.yml │ ├── test_db_config.yml │ ├── bucky_output_spec.rb │ ├── e2e_config.yml │ ├── yaml_load_spec.rb │ ├── config_spec.rb │ ├── requests_spec.rb │ └── bucky_logger_spec.rb ├── test_config │ ├── test_config.yml │ └── hierarchy_config │ │ └── test_config_hierarchy.yml ├── test_code │ └── services │ │ └── service_a │ │ ├── pc │ │ ├── parts │ │ │ └── sample_page.yml │ │ ├── verifications │ │ │ └── sample_page.rb │ │ ├── scenarios │ │ │ └── e2e │ │ │ │ └── scenario_a.yml │ │ └── pageobject │ │ │ └── sample_page.rb │ │ └── sp │ │ └── scenarios │ │ ├── linkstatus │ │ └── scenario_c.yml │ │ └── e2e │ │ └── scenario_b.yml ├── test_equipment │ ├── verifications │ │ ├── abst_verification_spec.rb │ │ └── service_verifications_spec.rb │ ├── user_operation │ │ ├── user_operation_logger_spec.rb │ │ └── user_operator_spec.rb │ ├── evidence │ │ └── evidence_generator_spec.rb │ ├── selenium_handler │ │ ├── wait_handler_spec.rb │ │ └── webdriver_handler_spec.rb │ ├── pageobject │ │ ├── pages_spec.rb │ │ └── base_pageobject_spec.rb │ └── test_case │ │ ├── abst_test_case_spec.rb │ │ └── e2e_test_case_spec.rb ├── version_spec.rb ├── tools │ ├── config_spec.yml │ ├── rule_config_spec.yml │ └── lint_spec.rb ├── spec_helper.rb └── core │ ├── test_core │ ├── test_result_spec.rb │ ├── exit_handler_spec.rb │ ├── test_case_loader_spec.rb │ ├── test_class_generator_spec.rb │ └── test_manager_spec.rb │ ├── database │ └── db_connector_spec.rb │ ├── report │ └── screen_shot_generator_spec.rb │ └── exception │ └── bucky_exception_spec.rb ├── bin ├── setup └── console ├── Rakefile ├── .circleci ├── setup_rubygems.sh ├── deploy_to_rubygems.sh └── config.yml ├── docker └── nginx │ ├── public │ ├── test_page.html │ └── index.html │ ├── Dockerfile │ └── nginx.conf ├── .dockerignore ├── docker-compose.dev.yml ├── Dockerfile ├── .gitignore ├── docker-compose.dev-with-bm.yml ├── docker-compose.system-test.yml ├── Dockerfile.dev ├── Dockerfile.system-test ├── .codeclimate.yml ├── .rubocop_todo.yml ├── .rubocop.yml ├── bucky-core.gemspec ├── Gemfile.lock └── exe └── bucky /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @lifull-dev/admin-bucky -------------------------------------------------------------------------------- /template/new/system/logs/README.md: -------------------------------------------------------------------------------- 1 | for save logs directory. 2 | -------------------------------------------------------------------------------- /example/hands-on/system/logs/README.md: -------------------------------------------------------------------------------- 1 | for save logs directory. 2 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | --format documentation 4 | -------------------------------------------------------------------------------- /template/new/services/README.md: -------------------------------------------------------------------------------- 1 | for services page and parts objects. 2 | -------------------------------------------------------------------------------- /template/new/system/evidences/README.md: -------------------------------------------------------------------------------- 1 | for save evidence directory. 2 | -------------------------------------------------------------------------------- /example/bucky-management/system/logs/README.md: -------------------------------------------------------------------------------- 1 | for save logs directory. 2 | -------------------------------------------------------------------------------- /example/hands-on/services/README.md: -------------------------------------------------------------------------------- 1 | for services page and parts objects. 2 | -------------------------------------------------------------------------------- /example/hands-on/system/evidences/README.md: -------------------------------------------------------------------------------- 1 | for save evidence directory. 2 | -------------------------------------------------------------------------------- /lib/bucky.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bucky/version' 4 | -------------------------------------------------------------------------------- /template/new/.bucky_home: -------------------------------------------------------------------------------- 1 | # Bucky home directory. 2 | # Do not delete this file. 3 | -------------------------------------------------------------------------------- /example/bucky-management/services/README.md: -------------------------------------------------------------------------------- 1 | for services page and parts objects. 2 | -------------------------------------------------------------------------------- /example/bucky-management/system/evidences/README.md: -------------------------------------------------------------------------------- 1 | for save evidence directory. 2 | -------------------------------------------------------------------------------- /example/hands-on/.bucky_home: -------------------------------------------------------------------------------- 1 | # bucky home directory. 2 | # Do not delete this file. 3 | -------------------------------------------------------------------------------- /system_testing/test_bucky_project/system/logs/README.md: -------------------------------------------------------------------------------- 1 | for save logs directory. 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | gemspec 5 | -------------------------------------------------------------------------------- /example/hands-on/system/evidences/screen_shots/README.md: -------------------------------------------------------------------------------- 1 | for save screen shots directory. 2 | -------------------------------------------------------------------------------- /spec/utils/config_not_in_template.yml: -------------------------------------------------------------------------------- 1 | :config_not_in_template: 'rspec_config_not_in_template' -------------------------------------------------------------------------------- /system_testing/test_bucky_project/services/README.md: -------------------------------------------------------------------------------- 1 | for services page and parts objects. 2 | -------------------------------------------------------------------------------- /system_testing/test_bucky_project/system/evidences/README.md: -------------------------------------------------------------------------------- 1 | for save evidence directory. 2 | -------------------------------------------------------------------------------- /template/new/system/evidences/screen_shots/README.md: -------------------------------------------------------------------------------- 1 | for save screen shots directory. 2 | -------------------------------------------------------------------------------- /example/bucky-management/.bucky_home: -------------------------------------------------------------------------------- 1 | # Bucky home directory. 2 | # Do not delete this file. 3 | -------------------------------------------------------------------------------- /spec/test_config/test_config.yml: -------------------------------------------------------------------------------- 1 | :test_config: 2 | :bucky_test: 3 | :config_test: 'test' 4 | -------------------------------------------------------------------------------- /example/bucky-management/system/evidences/screen_shots/README.md: -------------------------------------------------------------------------------- 1 | for save screen shots directory. 2 | -------------------------------------------------------------------------------- /system_testing/test_bucky_project/.bucky_home: -------------------------------------------------------------------------------- 1 | # bucky home directory. 2 | # Do not delete this file. 3 | -------------------------------------------------------------------------------- /system_testing/test_bucky_project/system/evidences/screen_shots/README.md: -------------------------------------------------------------------------------- 1 | for save screen shots directory. 2 | -------------------------------------------------------------------------------- /spec/utils/bucky_config.yml: -------------------------------------------------------------------------------- 1 | :log_path: './spec/utils/' 2 | :screen_shot_path: './system/screen_shots/' 3 | :bucky_error: 'bucky_error' -------------------------------------------------------------------------------- /spec/test_config/hierarchy_config/test_config_hierarchy.yml: -------------------------------------------------------------------------------- 1 | :test_config: 2 | :bucky_test: 3 | :config_test: 'test_hierarchy' 4 | -------------------------------------------------------------------------------- /lib/bucky/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Bucky 4 | module Version 5 | VERSION = '0.10.25' 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /example/hands-on/services/bucky_hands_on/pc/parts/github_search_list.yml: -------------------------------------------------------------------------------- 1 | bucky_core_a_tag: 2 | - xpath 3 | - //a[contains(text(),'lifull-dev')] 4 | -------------------------------------------------------------------------------- /spec/utils/linkstatus_config.yml: -------------------------------------------------------------------------------- 1 | :linkstatus_open_timeout: 60 2 | :linkstatus_read_timeout: 60 3 | :linkstatus_parallel_num: 1 4 | :linkstatus_thread_num: 4 -------------------------------------------------------------------------------- /example/bucky-management/services/bucky_example/pc/parts/github_search_list.yml: -------------------------------------------------------------------------------- 1 | bucky_core_a_tag: 2 | - xpath 3 | - //a[contains(text(),'lifull-dev')] 4 | -------------------------------------------------------------------------------- /example/hands-on/config/linkstatus_config.yml: -------------------------------------------------------------------------------- 1 | :linkstatus_parallel_num: 1 2 | :linkstatus_thread_num: 4 3 | :linkstatus_open_timeout: 60 4 | :linkstatus_read_timeout: 60 -------------------------------------------------------------------------------- /template/new/config/linkstatus_config.yml: -------------------------------------------------------------------------------- 1 | :linkstatus_parallel_num: 1 2 | :linkstatus_thread_num: 4 3 | :linkstatus_open_timeout: 60 4 | :linkstatus_read_timeout: 60 -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /example/bucky-management/config/linkstatus_config.yml: -------------------------------------------------------------------------------- 1 | :linkstatus_parallel_num: 1 2 | :linkstatus_thread_num: 4 3 | :linkstatus_open_timeout: 60 4 | :linkstatus_read_timeout: 60 -------------------------------------------------------------------------------- /system_testing/test_bucky_project/services/service_a/sp/parts/index.yml: -------------------------------------------------------------------------------- 1 | link: 2 | - xpath 3 | - "//*[@id='test_link']" 4 | ua: 5 | - xpath 6 | - "//*[@id='user_agent']" -------------------------------------------------------------------------------- /system_testing/test_bucky_project/services/service_a/tablet/parts/index.yml: -------------------------------------------------------------------------------- 1 | link: 2 | - xpath 3 | - "//*[@id='test_link']" 4 | ua: 5 | - xpath 6 | - "//*[@id='user_agent']" -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler/gem_tasks' 4 | require 'rspec/core/rake_task' 5 | 6 | RSpec::Core::RakeTask.new(:spec) 7 | 8 | task default: :spec 9 | -------------------------------------------------------------------------------- /system_testing/test_bucky_project/config/linkstatus_config.yml: -------------------------------------------------------------------------------- 1 | :linkstatus_parallel_num: 1 2 | :linkstatus_thread_num: 4 3 | :linkstatus_open_timeout: 60 4 | :linkstatus_read_timeout: 60 -------------------------------------------------------------------------------- /.circleci/setup_rubygems.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | mkdir ~/.gem 5 | echo -e "---\r\n:rubygems_api_key: $RUBYGEMS_API_KEY" > ~/.gem/credentials 6 | chmod 0600 /home/circleci/.gem/credentials 7 | -------------------------------------------------------------------------------- /spec/test_code/services/service_a/pc/parts/sample_page.yml: -------------------------------------------------------------------------------- 1 | # Sample ==================== 2 | freewod_form: 3 | - xpath 4 | - "//*[@id='freeword']" 5 | photos: 6 | - xpaths 7 | - "//*[@class='photo']" 8 | -------------------------------------------------------------------------------- /system_testing/test_bucky_project/services/service_a/pc/parts/index.yml: -------------------------------------------------------------------------------- 1 | link: 2 | - xpath 3 | - "//*[@id='test_link']" 4 | ua: 5 | - xpath 6 | - "//*[@id='user_agent']" 7 | links: 8 | - css 9 | - '.links' -------------------------------------------------------------------------------- /docker/nginx/public/test_page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Test Page 6 | 7 | 8 | 9 |

This is a Test page

10 | 11 | 12 | -------------------------------------------------------------------------------- /example/bucky-management/config/test_db_config.yml: -------------------------------------------------------------------------------- 1 | :test_db: 2 | :bucky_test: 3 | :username: root 4 | :password: password 5 | :database: bucky_test 6 | :host: 127.0.0.1 7 | :port: 3306 8 | :adapter: :mysql 9 | -------------------------------------------------------------------------------- /spec/utils/test_db_config.yml: -------------------------------------------------------------------------------- 1 | :test_db: 2 | :bucky_test: 3 | :username: 'db_username' 4 | :password: 'db_password' 5 | :database: 'bucky_development' 6 | :host: 'loaclhost' 7 | :port: 3306 8 | :adapter: :mysql -------------------------------------------------------------------------------- /example/hands-on/services/bucky_hands_on/pc/parts/github_top.yml: -------------------------------------------------------------------------------- 1 | search_bar: 2 | - xpath 3 | - //input[contains(@class,'header-search-input')] 4 | search_resault: 5 | - xpath 6 | - //div[contains(@class,'jump-to-suggestions')] 7 | -------------------------------------------------------------------------------- /example/bucky-management/services/bucky_example/pc/parts/github_top.yml: -------------------------------------------------------------------------------- 1 | search_bar: 2 | - xpath 3 | - //input[contains(@class,'header-search-input')] 4 | search_resault: 5 | - xpath 6 | - //div[contains(@class,'jump-to-suggestions')] 7 | -------------------------------------------------------------------------------- /docker/nginx/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:1.13 2 | RUN rm -f /etc/nginx/conf.d/* 3 | RUN rm -f /usr/share/nginx/html/* 4 | 5 | COPY ./public /app/public 6 | ADD ./nginx.conf /etc/nginx/nginx.conf 7 | CMD /usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.conf 8 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | ## You can check the example code in each directory 2 | * [hands-on](https://github.com/lifull-dev/bucky-core/blob/master/example/hands-on) 3 | * [bucky-management](https://github.com/lifull-dev/bucky-core/blob/master/example/bucky-management) 4 | -------------------------------------------------------------------------------- /template/new/config/bucky_config.yml: -------------------------------------------------------------------------------- 1 | :test_result_path: './system/test_results/' 2 | :log_path: './system/logs/' 3 | :screen_shot_path: './system/evidences/screen_shots/' 4 | :bucky_error: 'bucky_error' 5 | :test_code_repo: 'https://github.com/hogehoge/fugafuga/' 6 | -------------------------------------------------------------------------------- /example/hands-on/config/bucky_config.yml: -------------------------------------------------------------------------------- 1 | :test_result_path: './system/test_results/' 2 | :log_path: './system/logs/' 3 | :screen_shot_path: './system/evidences/screen_shots/' 4 | :bucky_error: 'bucky_error' 5 | :test_code_repo: 'https://github.com/hogehoge/fugafuga/' 6 | -------------------------------------------------------------------------------- /spec/test_equipment/verifications/abst_verification_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../../../lib/bucky/test_equipment/verifications/abst_verification' 4 | 5 | describe Bucky::TestEquipment::Verifications::AbstVerification do 6 | end 7 | -------------------------------------------------------------------------------- /example/bucky-management/config/bucky_config.yml: -------------------------------------------------------------------------------- 1 | :test_result_path: './system/test_results/' 2 | :log_path: './system/logs/' 3 | :screen_shot_path: './system/evidences/screen_shots/' 4 | :bucky_error: 'bucky_error' 5 | :test_code_repo: 'https://github.com/hogehoge/fugafuga/' 6 | -------------------------------------------------------------------------------- /spec/version_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | require_relative '../lib/bucky/version' 5 | 6 | describe Bucky::Version do 7 | it 'has a version number' do 8 | expect(Bucky::Version::VERSION).not_to be_empty 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /system_testing/test_bucky_project/config/bucky_config.yml: -------------------------------------------------------------------------------- 1 | :test_result_path: './system/test_results/' 2 | :log_path: './system/logs/' 3 | :screen_shot_path: './system/evidences/screen_shots/' 4 | :bucky_error: 'bucky_error' 5 | :test_code_repo: 'https://github.com/hogehoge/fugafuga/' 6 | -------------------------------------------------------------------------------- /template/make_page/sp/parts/sample_page.yml: -------------------------------------------------------------------------------- 1 | # add some parts for this page 2 | 3 | # Sample ==================== 4 | # freewod_form: 5 | # - xpath 6 | # - "//*[@id='freeword']" 7 | # photos: 8 | # - css 9 | # - '.photos' 10 | # icon: 11 | # - id 12 | # - icon -------------------------------------------------------------------------------- /template/make_page/pc/parts/sample_page.yml: -------------------------------------------------------------------------------- 1 | # Add some parts for this page 2 | 3 | # Sample ==================== 4 | # freewod_form: 5 | # - xpath 6 | # - "//*[@id='freeword']" 7 | # photos: 8 | # - css 9 | # - '.photos' 10 | # icon: 11 | # - id 12 | # - icon 13 | -------------------------------------------------------------------------------- /template/make_page/tablet/parts/sample_page.yml: -------------------------------------------------------------------------------- 1 | # Add some parts for this page 2 | 3 | # Sample ==================== 4 | # freewod_form: 5 | # - xpath 6 | # - "//*[@id='freeword']" 7 | # photos: 8 | # - css 9 | # - '.photos' 10 | # icon: 11 | # - id 12 | # - icon -------------------------------------------------------------------------------- /spec/test_code/services/service_a/sp/scenarios/linkstatus/scenario_c.yml: -------------------------------------------------------------------------------- 1 | desc: status check 2 | device: sp 3 | service: service_c 4 | priority: high 5 | test_category: linkstatus 6 | cases: 7 | - desc: top page 8 | urls: 9 | - http://example.com 10 | - http://example.com/fugafuga/ -------------------------------------------------------------------------------- /template/new/config/test_db_config.yml: -------------------------------------------------------------------------------- 1 | :test_db: 2 | :bucky_test: 3 | :username: <%= ENV['BUCKY_DB_USERNAME'] %> 4 | :password: <%= ENV['BUCKY_DB_PASSWORD'] %> 5 | :database: 'bucky_development' 6 | :host: <%= ENV['BUCKY_DB_HOSTNAME'] %> 7 | :port: 3306 8 | :adapter: :mysql 9 | -------------------------------------------------------------------------------- /example/hands-on/config/test_db_config.yml: -------------------------------------------------------------------------------- 1 | :test_db: 2 | :bucky_test: 3 | :username: <%= ENV['BUCKY_DB_USERNAME'] %> 4 | :password: <%= ENV['BUCKY_DB_PASSWORD'] %> 5 | :database: 'bucky_development' 6 | :host: <%= ENV['BUCKY_DB_HOSTNAME'] %> 7 | :port: 3306 8 | :adapter: :mysql 9 | -------------------------------------------------------------------------------- /system_testing/test_bucky_project/config/test_db_config.yml: -------------------------------------------------------------------------------- 1 | :test_db: 2 | :bucky_test: 3 | :username: <%= ENV['BUCKY_DB_USERNAME'] %> 4 | :password: <%= ENV['BUCKY_DB_PASSWORD'] %> 5 | :database: 'bucky_development' 6 | :host: <%= ENV['BUCKY_DB_HOST'] %> 7 | :port: 3306 8 | :adapter: :mysql 9 | -------------------------------------------------------------------------------- /lib/bucky/test_equipment/verifications/abst_verification.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'test/unit' 4 | 5 | module Bucky 6 | module TestEquipment 7 | module Verifications 8 | class AbstVerification 9 | include Test::Unit::Assertions 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /system_testing/test_bucky_project/services/service_a/sp/scenarios/linkstatus/sp_link.yml: -------------------------------------------------------------------------------- 1 | desc: sp linkstatus test suites 2 | device: sp 3 | service: service_a 4 | priority: high 5 | test_category: linkstatus 6 | cases: 7 | - case_name: sp_link_1 8 | desc: sp linkstatus test 1 9 | urls: 10 | - http://bucky.net -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Git 2 | .gitignore 3 | 4 | # CI 5 | .codeclimate.yml 6 | .circleci 7 | .rubocop* 8 | 9 | # Docker 10 | # docker-compose* 11 | 12 | # Test 13 | spec/ 14 | .rspec 15 | system-testing/ 16 | docker/ 17 | 18 | # Log 19 | *.log 20 | 21 | # Others 22 | .DS_Store 23 | .bundle 24 | .vscode 25 | .yardoc 26 | coverage/ 27 | example/ 28 | doc 29 | tags 30 | tmp 31 | .sample/ 32 | -------------------------------------------------------------------------------- /lib/bucky/test_equipment/user_operation/user_operation_logger.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Bucky 4 | module TestEquipment 5 | module UserOperation 6 | class UserOperationLogger 7 | class << self 8 | def get_user_opr_log(op_args) 9 | op_args.to_s << "\n" 10 | end 11 | end 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /docker/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | 3 | events { 4 | worker_connections 128; 5 | } 6 | 7 | http { 8 | access_log /var/log/nginx/access.log; 9 | error_log /var/log/nginx/error.log; 10 | 11 | server_tokens off; 12 | index index.html; 13 | 14 | server { 15 | listen 80; 16 | server_name localhost; 17 | 18 | location /{ 19 | root /app/public; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /spec/test_code/services/service_a/pc/verifications/sample_page.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bucky/test_equipment/verifications/e2e_verification' 4 | 5 | module Services 6 | module ServiceA 7 | module Pc 8 | module Verifications 9 | class SamplePage < Bucky::TestEquipment::Verifications::E2eVerification 10 | end 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /system_testing/test_bucky_project/services/service_a/pc/scenarios/linkstatus/pc_link_exclude_asterisk.yml: -------------------------------------------------------------------------------- 1 | desc: pc linkstatus test suites 2 | device: pc 3 | service: service_a 4 | priority: high 5 | test_category: linkstatus 6 | exclude_urls: 7 | - http://bucky.net/test* 8 | cases: 9 | - case_name: pc_link_exclude_asterisk_1 10 | desc: pc linkstatus test 1 11 | urls: 12 | - http://bucky.net 13 | -------------------------------------------------------------------------------- /system_testing/test_bucky_project/services/service_a/pc/scenarios/linkstatus/pc_link_exclude_regex.yml: -------------------------------------------------------------------------------- 1 | desc: pc linkstatus test suites 2 | device: pc 3 | service: service_a 4 | priority: high 5 | test_category: linkstatus 6 | exclude_urls: 7 | - /http://bucky.net/.*_page.*/ 8 | cases: 9 | - case_name: pc_link_exclude_regex_1 10 | desc: pc linkstatus test 1 11 | urls: 12 | - http://bucky.net 13 | -------------------------------------------------------------------------------- /system_testing/test_bucky_project/services/service_a/pc/scenarios/linkstatus/pc_link_exclude_normal.yml: -------------------------------------------------------------------------------- 1 | desc: pc linkstatus test suites 2 | device: pc 3 | service: service_a 4 | priority: high 5 | test_category: linkstatus 6 | exclude_urls: 7 | - http://bucky.net/test_page.html 8 | cases: 9 | - case_name: pc_link_exclude_normal_1 10 | desc: pc linkstatus test 1 11 | urls: 12 | - http://bucky.net 13 | -------------------------------------------------------------------------------- /system_testing/test_bucky_project/services/service_a/sp/pageobject/index.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require '/bucky-core/lib/bucky/test_equipment/pageobject/base_pageobject' 4 | 5 | module Services 6 | module ServiceA 7 | module Sp 8 | module PageObject 9 | class Index < Bucky::TestEquipment::PageObject::BasePageObject 10 | end 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /system_testing/test_bucky_project/services/service_a/tablet/pageobject/index.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require '/bucky-core/lib/bucky/test_equipment/pageobject/base_pageobject' 4 | 5 | module Services 6 | module ServiceA 7 | module Tablet 8 | module PageObject 9 | class Index < Bucky::TestEquipment::PageObject::BasePageObject 10 | end 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require 'bundler/setup' 5 | require 'bucky/core' 6 | 7 | # You can add fixtures and/or initialization code here to make experimenting 8 | # with your gem easier. You can also use a different console, if you like. 9 | 10 | # (If you use this, don't forget to add pry to your Gemfile!) 11 | # require "pry" 12 | # Pry.start 13 | 14 | require 'irb' 15 | IRB.start 16 | -------------------------------------------------------------------------------- /system_testing/test_bucky_project/services/service_a/pc/scenarios/linkstatus/pc_link.yml: -------------------------------------------------------------------------------- 1 | desc: pc linkstatus test suites 2 | device: pc 3 | service: service_a 4 | priority: high 5 | test_category: linkstatus 6 | cases: 7 | - case_name: pc_link_1 8 | desc: pc linkstatus test 1 9 | urls: 10 | - http://bucky.net 11 | - case_name: pc_link_2 12 | desc: pc linkstatus test 2 13 | urls: 14 | - http://bucky-error.net -------------------------------------------------------------------------------- /docker-compose.dev.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | bucky-core: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile.dev 7 | container_name: bucky-core 8 | volumes: 9 | - .:/bucky-core 10 | - .sample:/app 11 | tty: true 12 | chrome: 13 | image: selenium/standalone-chrome-debug:3.141.59-oxygen 14 | container_name: chrome 15 | ports: 16 | - '4444:4444' 17 | - '5901:5900' 18 | shm_size: 1G -------------------------------------------------------------------------------- /spec/test_code/services/service_a/pc/scenarios/e2e/scenario_a.yml: -------------------------------------------------------------------------------- 1 | desc: suite description 2 | device: pc 3 | service: service_a 4 | priority: high 5 | test_category: e2e 6 | cases: 7 | - desc: open toppage and title check 8 | procs: 9 | - proc: open toppage 10 | exec: 11 | operate: go 12 | url: http://example.com/ 13 | - proc: check title 14 | exec: 15 | verify: assert_title 16 | expect: 'TItle : hogehoge' -------------------------------------------------------------------------------- /spec/test_code/services/service_a/sp/scenarios/e2e/scenario_b.yml: -------------------------------------------------------------------------------- 1 | desc: suite description 2 | device: sp 3 | service: service_b 4 | priority: middle 5 | test_category: e2e 6 | cases: 7 | - desc: open toppage and title check 8 | procs: 9 | - proc: open toppage 10 | exec: 11 | operate: go 12 | url: http://example.com/ 13 | - proc: check title 14 | exec: 15 | verify: assert_title 16 | expect: 'TItle : hogehoge' -------------------------------------------------------------------------------- /example/hands-on/services/bucky_hands_on/pc/scenarios/linkstatus/github_top.yml: -------------------------------------------------------------------------------- 1 | # Describe for this test suite 2 | desc: Check all a tag in githib top 3 | device: pc 4 | service: bucky_hands_on 5 | priority: high 6 | test_category: linkstatus 7 | labels: 8 | - example 9 | # You can exclude url that you don't want to check 10 | exclude_urls: 11 | - https://github.com/customer-stories/* 12 | cases: 13 | - case_name: github_top_1 14 | desc: Check github top 15 | urls: 16 | - https://github.com/ 17 | -------------------------------------------------------------------------------- /lib/bucky/utils/bucky_output.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Bucky 4 | module Utils 5 | module BuckyOutput 6 | module StringColorize 7 | refine String do 8 | def black 9 | "\e[30m#{self}\e[0m" 10 | end 11 | 12 | def bg_red 13 | "\e[41m#{self}\e[0m" 14 | end 15 | 16 | def bg_green 17 | "\e[42m#{self}\e[0m" 18 | end 19 | end 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /example/bucky-management/services/bucky_example/pc/scenarios/linkstatus/github_top.yml: -------------------------------------------------------------------------------- 1 | # Describe for this test suite 2 | desc: Check all a tag in githib top 3 | device: pc 4 | service: bucky_example 5 | priority: high 6 | test_category: linkstatus 7 | labels: 8 | - example 9 | # You can exclude url that you don't want to check 10 | exclude_urls: 11 | - https://github.com/customer-stories/* 12 | cases: 13 | - case_name: github_top_1 14 | desc: Check github top 15 | urls: 16 | - https://github.com/ 17 | -------------------------------------------------------------------------------- /spec/tools/config_spec.yml: -------------------------------------------------------------------------------- 1 | :selenium_ip: 'docker_host_ip' # Default selenium ip. please set ip or 'docker_host_ip'. ip route | awk 'NR==1 {print $3}' 2 | :selenium_port: <%= ENV['SELENIUM_PORT'] || 4444 %> 3 | :browser: :chrome 4 | :sp_device_name: :iphone 5 | :tablet_device_name: :ipad 6 | :device_name_on_chrome: 7 | :iphone: 'iPhone X' 8 | :android: 'Galaxy S5' 9 | # :ipad: 'iPad' 10 | :user_agent: 'QA-E2E (X11; Linux x86_64)' 11 | :driver_read_timeout: 120 12 | :driver_open_timeout: 90 13 | :find_element_timeout: 60 -------------------------------------------------------------------------------- /spec/tools/rule_config_spec.yml: -------------------------------------------------------------------------------- 1 | :selenium_ip: 'docker_host_ip' # default selenium ip. please set ip or 'docker_host_ip'. ip route | awk 'NR==1 {print $3}' 2 | :selenium_port: <%= ENV['SELENIUM_PORT'] || 4444 %> 3 | :browser: :chrome 4 | :sp_device_name: :iphone 5 | :tablet_device_name: :ipad 6 | :device_name_on_chrome: 7 | :iphone: 'iPhone X' 8 | :android: 'Galaxy S5' 9 | :ipad: 'iPad' 10 | :user_agent: 'QA-E2E (X11; Linux x86_64)' 11 | :driver_read_timeout: 120 12 | :driver_open_timeout: 90 13 | :find_element_timeout: 60 -------------------------------------------------------------------------------- /lib/bucky/core/test_core/exit_handler.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'singleton' 4 | 5 | module Bucky 6 | module Core 7 | module TestCore 8 | class ExitHandler 9 | include Singleton 10 | 11 | def initialize 12 | @exit_code = 0 13 | end 14 | 15 | def reset 16 | @exit_code = 0 17 | end 18 | 19 | def raise 20 | @exit_code = 1 21 | end 22 | 23 | def bucky_exit 24 | exit @exit_code 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:3.2-alpine 2 | ENV LANG ja_JP.UTF-8 3 | ENV PAGER busybox less 4 | 5 | RUN apk update && \ 6 | apk upgrade && \ 7 | apk add --update\ 8 | bash \ 9 | build-base \ 10 | curl-dev \ 11 | git \ 12 | iproute2 \ 13 | libxml2-dev \ 14 | libxslt-dev \ 15 | linux-headers \ 16 | mysql-dev \ 17 | openssh \ 18 | ruby-dev \ 19 | ruby-json \ 20 | tzdata \ 21 | yaml \ 22 | yaml-dev \ 23 | zlib-dev 24 | 25 | RUN gem install bucky-core 26 | 27 | WORKDIR /app 28 | RUN chown -R nobody:nobody /app 29 | USER nobody 30 | -------------------------------------------------------------------------------- /spec/test_equipment/user_operation/user_operation_logger_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../../../lib/bucky/test_equipment/user_operation/user_operation_logger' 4 | 5 | describe Bucky::TestEquipment::UserOperation::UserOperationLogger do 6 | subject { Bucky::TestEquipment::UserOperation::UserOperationLogger } 7 | 8 | describe '.get_user_opr_log' do 9 | let(:operation_args) { { operation: :go } } 10 | it 'change to string' do 11 | expect(subject.get_user_opr_log(operation_args)).to be_a_kind_of(String) 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /system_testing/test_bucky_project/services/service_a/pc/pageobject/index.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require '/bucky-core/lib/bucky/test_equipment/pageobject/base_pageobject' 4 | module Services 5 | module ServiceA 6 | module Pc 7 | module PageObject 8 | class Index < Bucky::TestEquipment::PageObject::BasePageObject 9 | def click_single_element(_) 10 | links.click 11 | end 12 | 13 | def click_multiple_element(_) 14 | links[1].click 15 | end 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /system_testing/test_bucky_project/services/service_a/pc/scenarios/e2e/setup_each_pc_e2e.yml: -------------------------------------------------------------------------------- 1 | desc: setup each pc e2e suits 2 | device: pc 3 | service: service_a 4 | priority: high 5 | test_category: e2e 6 | setup_each: 7 | procs: 8 | - proc: open index 9 | exec: 10 | operate: go 11 | url: http://bucky.net 12 | cases: 13 | - case_name: setup_each_pc_e2e_1 14 | func: setup each pc e2e 1 func 15 | desc: setup each pc e2e 1 func 16 | procs: 17 | - proc: check title 18 | exec: 19 | verify: assert_title 20 | expect: Test Index -------------------------------------------------------------------------------- /spec/utils/bucky_output_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../../lib/bucky/utils/bucky_output' 4 | 5 | describe Bucky::Utils::BuckyOutput do 6 | describe 'StringColorize' do 7 | using Bucky::Utils::BuckyOutput::StringColorize 8 | let(:string) { 'Hoge' } 9 | 10 | it '.black' do 11 | expect(string.black).to eql "\e[30m#{string}\e[0m" 12 | end 13 | 14 | it '.bg_green' do 15 | expect(string.bg_green).to eql "\e[42m#{string}\e[0m" 16 | end 17 | 18 | it '.bg_red' do 19 | expect(string.bg_red).to eql "\e[41m#{string}\e[0m" 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/utils/e2e_config.yml: -------------------------------------------------------------------------------- 1 | :selenium_ip: 'docker_host_ip' # Default selenium ip. please set ip or 'my_ssh_ip'. 2 | :selenium_port: '4444' # Default selenium port 3 | :selenium_mode: :remote # Other selection: :local(only PC mode), :headless 4 | :browser: :chrome # Other selection: :firefox (only PC mode) 5 | :headless: false 6 | :e2e_parallel_num: 1 7 | :sp_device_name: :iphone6 # Other selection: nexus5 8 | :device_chrome_name: 9 | :iphone6: 'Apple iPhone 6' 10 | :nexus5: 'Google Nexus 5' 11 | :chromedriver_flags: # If you want to use chromedriver options, please add like below 12 | # - '--disable-dev-shm-usage' -------------------------------------------------------------------------------- /system_testing/test_bucky_project/services/service_a/pc/scenarios/e2e/teardown_each_pc_e2e.yml: -------------------------------------------------------------------------------- 1 | desc: teardown each pc e2e suits 2 | device: pc 3 | service: service_a 4 | priority: high 5 | test_category: e2e 6 | teardown_each: 7 | procs: 8 | - proc: open test_page.html 9 | exec: 10 | operate: go 11 | url: http://bucky.net/test_page.html 12 | cases: 13 | - case_name: teardown_each_pc_e2e_1 14 | func: teardown each pc e2e 1 func 15 | desc: teardown each pc e2e 1 func 16 | procs: 17 | - proc: open page 18 | exec: 19 | operate: go 20 | url: http://bucky.net -------------------------------------------------------------------------------- /.circleci/deploy_to_rubygems.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | VERSION=$(git describe --tags | grep -o -E "([0-9]+\.){1}[0-9]+(\.[0-9]+)?" | head -n1) 5 | git config user.email "bucky-operator@users.noreply.github.com" 6 | git config user.name "bucky-operator" 7 | # Update version.rb 8 | sed -i -e "s/VERSION = '[0-9]\+\.[0-9]\+\.[0-9]\+'/VERSION = '$VERSION'/" lib/bucky/version.rb 9 | git diff 10 | git checkout master 11 | git add lib/bucky/version.rb 12 | git commit -m "Version $VERSION" 13 | # Build and release gem 14 | gem build bucky-core.gemspec 15 | gem push "bucky-core-$VERSION.gem" 16 | # Push to master 17 | git push origin master 18 | -------------------------------------------------------------------------------- /system_testing/test_bucky_project/services/service_a/pc/verifications/index.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require '/bucky-core/lib/bucky/test_equipment/verifications/e2e_verification' 4 | module Services 5 | module ServiceA 6 | module Pc 7 | module Verifications 8 | class Index < Bucky::TestEquipment::Verifications::E2eVerification 9 | def click_single_element(_) 10 | @pages.index.links.click 11 | end 12 | 13 | def click_multiple_element(_) 14 | @pages.index.links[1].click 15 | end 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Dir[File.join(File.dirname(__FILE__), '../lib/**/*.rb')].each { |f| require f } 4 | require 'simplecov' 5 | require 'simplecov-console' 6 | 7 | # Save to CircleCI's artifacts directory if we're on CircleCI 8 | if ENV['CIRCLE_ARTIFACTS'] 9 | dir = File.join(ENV['CIRCLE_ARTIFACTS'], 'coverage') 10 | SimpleCov.coverage_dir(dir) 11 | end 12 | 13 | SimpleCov::Formatter::Console.table_options = { max_width: 170 } 14 | SimpleCov.formatter = SimpleCov::Formatter::Console 15 | 16 | SimpleCov.start do 17 | add_filter 'spec/' 18 | end 19 | 20 | $LOAD_PATH.unshift File.expand_path('../../lib', __dir__) 21 | -------------------------------------------------------------------------------- /template/make_page/pc/pageobject/sample_page.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bucky/test_equipment/pageobject/base_pageobject' 4 | module Services 5 | module {SampleService} 6 | module Pc 7 | module PageObject 8 | class {SamplePage} < Bucky::TestEquipment::PageObject::BasePageObject 9 | protected 10 | 11 | # Add some user operations for this page 12 | # Sample ==================== 13 | # @param [Hash] args 14 | # def search_freeword(**args) 15 | # freeword_form.send_keys(args[:word]) 16 | # freeword_form.submit 17 | # end 18 | end 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/test_code/services/service_a/pc/pageobject/sample_page.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bucky/test_equipment/pageobject/base_pageobject' 4 | module Services 5 | module ServiceA 6 | module Pc 7 | module PageObject 8 | class SamplePage < Bucky::TestEquipment::PageObject::BasePageObject 9 | protected 10 | 11 | # Add some user operations for this page 12 | # Sample ==================== 13 | # @param [Hash] args 14 | # def search_freeword(**args) 15 | # freeword_form.send_keys(args[:word]) 16 | # freeword_form.submit 17 | # end 18 | end 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /template/make_page/sp/pageobject/sample_page.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bucky/test_equipment/pageobject/base_pageobject' 4 | 5 | module Services 6 | module {SampleService} 7 | module Sp 8 | module PageObject 9 | class {SamplePage} < Bucky::TestEquipment::PageObject::BasePageObject 10 | protected 11 | 12 | # Add some user operations for this page 13 | # Sample ==================== 14 | # @param [Hash] args 15 | # def search_freeword(**args) 16 | # freeword_form.send_keys(args[:word]) 17 | # freeword_form.submit 18 | # end 19 | end 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /example/hands-on/services/bucky_hands_on/pc/pageobject/github_top.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bucky/test_equipment/pageobject/base_pageobject' 4 | module Services 5 | module BuckyHandsOn 6 | module Pc 7 | module PageObject 8 | class GithubTop < Bucky::TestEquipment::PageObject::BasePageObject 9 | protected 10 | 11 | # add some user operations for this page 12 | # sample ==================== 13 | # @param [Hash] args 14 | # def search_freeword(**args) 15 | # freeword_form.send_keys(args[:word]) 16 | # freeword_form.submit 17 | # end 18 | end 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/utils/yaml_load_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../../lib/bucky/utils/yaml_load' 4 | 5 | describe Bucky::Utils::YamlLoad do 6 | let(:yaml_load) { Class.new { extend Bucky::Utils::YamlLoad } } 7 | let(:yaml_file) { './spec/test_config/test_config.yml' } 8 | let(:yaml_dir) { './spec/test_config//**/*yml' } 9 | 10 | describe '#load_yaml' do 11 | it 'load target yaml' do 12 | expect(yaml_load.load_yaml(yaml_file)).to be_a(Hash) 13 | end 14 | end 15 | 16 | describe '#file_sort_hierarchy' do 17 | it 'sort and deepest file last' do 18 | expect(yaml_load.file_sort_hierarchy(yaml_dir).last).to include('hierarchy_config') 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /template/make_page/tablet/pageobject/sample_page.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bucky/test_equipment/pageobject/base_pageobject' 4 | 5 | module Services 6 | module {SampleService} 7 | module Tablet 8 | module PageObject 9 | class {SamplePage} < Bucky::TestEquipment::PageObject::BasePageObject 10 | protected 11 | 12 | # Add some user operations for this page 13 | # Sample ==================== 14 | # @param [Hash] args 15 | # def search_freeword(**args) 16 | # freeword_form.send_keys(args[:word]) 17 | # freeword_form.submit 18 | # end 19 | end 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.sw[pon] 2 | tags 3 | bak_* 4 | tmp_* 5 | *.log 6 | *.png 7 | *.json 8 | vendor/ 9 | .bundle 10 | 11 | ### https://raw.github.com/github/gitignore/cc542de017c606138a87ee4880e5f06b3a306def/ruby.gitignore 12 | 13 | *.gem 14 | *.rbc 15 | /.config 16 | /coverage/ 17 | /InstalledFiles 18 | /pkg/ 19 | /spec/reports/ 20 | /spec/examples.txt 21 | /test/tmp/ 22 | /test/version_tmp/ 23 | /tmp/ 24 | 25 | ## Documentation cache and generated files: 26 | /.yardoc/ 27 | /_yardoc/ 28 | /doc/ 29 | /rdoc/ 30 | 31 | ## Environment normalization: 32 | /.bundle/ 33 | /vendor/bundle 34 | /lib/bundler/man/ 35 | 36 | # Unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 37 | .rvmrc 38 | 39 | .DS_Store 40 | .sample 41 | -------------------------------------------------------------------------------- /docker/nginx/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Test Index 6 | 7 | 12 | 13 | 14 | Go to test page 15 |
16 |
17 | class1-1 18 |
19 |
20 | class1-2 21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /example/bucky-management/services/bucky_example/pc/pageobject/github_top.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bucky/test_equipment/pageobject/base_pageobject' 4 | module Services 5 | module BuckyExample 6 | module Pc 7 | module PageObject 8 | class GithubTop < Bucky::TestEquipment::PageObject::BasePageObject 9 | protected 10 | 11 | # Add some user operations for this page 12 | # Sample ==================== 13 | # @param [Hash] args 14 | # def search_freeword(**args) 15 | # freeword_form.send_keys(args[:word]) 16 | # freeword_form.submit 17 | # end 18 | end 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /example/hands-on/config/e2e_config.yml: -------------------------------------------------------------------------------- 1 | :selenium_ip: localhost # default selenium ip. please set ip or 'docker_host_ip'. ip route | awk 'NR==1 {print $3}' 2 | :selenium_port: '4444' # default selenium port 3 | :browser: :chrome # Only chrome 4 | :headless: false 5 | :e2e_parallel_num: 1 6 | :sp_device_name: :iphone 7 | :tablet_device_name: :ipad 8 | :device_name_on_chrome: 9 | :iphone: 'iPhone X' 10 | :android: 'Galaxy S5' 11 | :ipad: 'iPad' 12 | :chromedriver_flags: # If you want to use chromedriver options, please add like below 13 | # - '--disable-dev-shm-usage' 14 | :user_agent: 'E2ETest (X11; Linux x86_64)' 15 | :driver_read_timeout: 60 # sec 16 | :driver_open_timeout: 60 # sec 17 | :find_element_timeout: 10 # sec -------------------------------------------------------------------------------- /example/hands-on/services/bucky_hands_on/pc/pageobject/github_search_list.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bucky/test_equipment/pageobject/base_pageobject' 4 | module Services 5 | module BuckyHandsOn 6 | module Pc 7 | module PageObject 8 | class GithubSearchList < Bucky::TestEquipment::PageObject::BasePageObject 9 | protected 10 | 11 | # add some user operations for this page 12 | # sample ==================== 13 | # @param [Hash] args 14 | # def search_freeword(**args) 15 | # freeword_form.send_keys(args[:word]) 16 | # freeword_form.submit 17 | # end 18 | end 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /template/new/config/e2e_config.yml: -------------------------------------------------------------------------------- 1 | :selenium_ip: 'docker_host_ip' # Default selenium ip. please set ip or 'docker_host_ip'. ip route | awk 'NR==1 {print $3}' 2 | :selenium_port: '4444' # Default selenium port 3 | :browser: :chrome # Only chrome 4 | :headless: false 5 | :e2e_parallel_num: 1 6 | :sp_device_name: :iphone 7 | :tablet_device_name: :ipad 8 | :device_name_on_chrome: 9 | :iphone: 'iPhone X' 10 | :android: 'Galaxy S5' 11 | :ipad: 'iPad' 12 | :chromedriver_flags: # If you want to use chromedriver options, please add like below 13 | # - '--disable-dev-shm-usage' 14 | :user_agent: 'E2ETest (X11; Linux x86_64)' 15 | :driver_read_timeout: 60 # sec 16 | :driver_open_timeout: 60 # sec 17 | :find_element_timeout: 10 # sec -------------------------------------------------------------------------------- /example/bucky-management/config/e2e_config.yml: -------------------------------------------------------------------------------- 1 | :selenium_ip: localhost # Default selenium ip. please set ip or 'docker_host_ip'. ip route | awk 'NR==1 {print $3}' 2 | :selenium_port: '4444' # Default selenium port 3 | :browser: :chrome # Only chrome 4 | :headless: false 5 | :e2e_parallel_num: 1 6 | :sp_device_name: :iphone 7 | :tablet_device_name: :ipad 8 | :device_name_on_chrome: 9 | :iphone: 'iPhone X' 10 | :android: 'Galaxy S5' 11 | :ipad: 'iPad' 12 | :chromedriver_flags: # If you want to use chromedriver options, please add like below 13 | # - '--disable-dev-shm-usage' 14 | :user_agent: 'E2ETest (X11; Linux x86_64)' 15 | :driver_read_timeout: 60 # sec 16 | :driver_open_timeout: 60 # sec 17 | :find_element_timeout: 10 # sec -------------------------------------------------------------------------------- /example/bucky-management/services/bucky_example/pc/pageobject/github_search_list.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bucky/test_equipment/pageobject/base_pageobject' 4 | module Services 5 | module BuckyExample 6 | module Pc 7 | module PageObject 8 | class GithubSearchList < Bucky::TestEquipment::PageObject::BasePageObject 9 | protected 10 | 11 | # Add some user operations for this page 12 | # Sample ==================== 13 | # @param [Hash] args 14 | # def search_freeword(**args) 15 | # freeword_form.send_keys(args[:word]) 16 | # freeword_form.submit 17 | # end 18 | end 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/bucky/test_equipment/selenium_handler/wait_handler.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'selenium-webdriver' 4 | require 'English' 5 | 6 | module Bucky 7 | module TestEquipment 8 | module SeleniumHandler 9 | module WaitHandler 10 | module_function 11 | 12 | def wait_until_helper(timeout, interval, ignore, &block) 13 | wait = Selenium::WebDriver::Wait.new(timeout: timeout, interval: interval, ignore: [ignore]) 14 | wait.until { block.call } 15 | rescue Selenium::WebDriver::Error::TimeoutError 16 | raise ignore, "Wait until the limit times for #{caller[1][/`([^']*)'/, 1]}\n #{$ERROR_INFO.message}" 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /system_testing/test_bucky_project/config/e2e_config.yml: -------------------------------------------------------------------------------- 1 | :selenium_ip: 'docker_host_ip' # Default selenium ip. please set ip or 'docker_host_ip'. ip route | awk 'NR==1 {print $3}' 2 | :selenium_port: '4444' # Default selenium port 3 | :browser: :chrome # Only chrome 4 | :headless: true 5 | :e2e_parallel_num: 1 6 | :sp_device_name: :iphone 7 | :tablet_device_name: :ipad 8 | :device_name_on_chrome: 9 | :iphone: 'iPhone X' 10 | :android: 'Galaxy S5' 11 | :ipad: 'iPad' 12 | :chromedriver_flags: # If you want to use chromedriver options, please add like below 13 | # - '--disable-dev-shm-usage' 14 | :user_agent: 'E2ETest (X11; Linux x86_64)' 15 | :driver_read_timeout: 60 # sec 16 | :driver_open_timeout: 60 # sec 17 | :find_element_timeout: 10 # sec -------------------------------------------------------------------------------- /lib/bucky/test_equipment/test_case/linkstatus_test_case.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'net/http' 4 | require_relative '../verifications/status_checker' 5 | require_relative './abst_test_case' 6 | 7 | module Bucky 8 | module TestEquipment 9 | module TestCase 10 | class LinkstatusTestCase < Bucky::TestEquipment::TestCase::AbstTestCase 11 | include Bucky::TestEquipment::Verifications::StatusChecker 12 | 13 | class << self 14 | def startup; end 15 | 16 | def shutdown; end 17 | end 18 | 19 | def setup; end 20 | 21 | def teardown 22 | # Call abst_test_case.teardown to get elappsed time of every test case 23 | super 24 | end 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /system_testing/test_bucky_project/services/service_a/sp/scenarios/e2e/sp_e2e_test.yml: -------------------------------------------------------------------------------- 1 | desc: sp e2e suits 2 | device: sp 3 | service: service_a 4 | priority: high 5 | test_category: e2e 6 | cases: 7 | - case_name: sp_e2e_1 8 | func: sp e2e 1 func UserAgent 9 | desc: sp e2e 1 func UserAgent 10 | procs: 11 | - proc: open index 12 | exec: 13 | operate: go 14 | url: http://bucky.net 15 | - proc: check UserAgent(DeviceName) 16 | exec: 17 | verify: assert_contained_text 18 | expect: iPhone 19 | page: index 20 | part: ua 21 | - proc: check UserAgent(Browser) 22 | exec: 23 | verify: assert_contained_text 24 | expect: Safari 25 | page: index 26 | part: ua -------------------------------------------------------------------------------- /docker-compose.dev-with-bm.yml: -------------------------------------------------------------------------------- 1 | services: 2 | bucky-core: 3 | build: 4 | context: . 5 | dockerfile: Dockerfile.dev 6 | container_name: bucky-core 7 | volumes: 8 | - .:/bucky-core 9 | - .sample:/app 10 | tty: true 11 | environment: 12 | - BUCKY_DB_USERNAME=root 13 | - BUCKY_DB_PASSWORD=password 14 | - BUCKY_DB_HOSTNAME=bm-mysql 15 | - BUCKY_DB_NAME=bucky_development 16 | networks: 17 | - bucky-management_default 18 | chrome: 19 | image: selenium/standalone-chromium:128.0 20 | container_name: chrome 21 | ports: 22 | - '4444:4444' 23 | - '5901:5900' 24 | shm_size: 1G 25 | networks: 26 | - bucky-management_default 27 | networks: 28 | bucky-management_default: 29 | external: true 30 | -------------------------------------------------------------------------------- /system_testing/test_bucky_project/services/service_a/tablet/scenarios/e2e/tablet_e2e_test.yml: -------------------------------------------------------------------------------- 1 | desc: tablet e2e suits 2 | device: tablet 3 | service: service_a 4 | priority: high 5 | test_category: e2e 6 | cases: 7 | - case_name: tablet_e2e_1 8 | func: tablet e2e 1 func UserAgent 9 | desc: tablet e2e 1 func UserAgent 10 | procs: 11 | - proc: open index 12 | exec: 13 | operate: go 14 | url: http://bucky.net 15 | - proc: check UserAgent(DeviceName) 16 | exec: 17 | verify: assert_contained_text 18 | expect: iPad 19 | page: index 20 | part: ua 21 | - proc: check UserAgent(Browser) 22 | exec: 23 | verify: assert_contained_text 24 | expect: Safari 25 | page: index 26 | part: ua -------------------------------------------------------------------------------- /lib/bucky/utils/yaml_load.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'yaml' 4 | require 'erb' 5 | 6 | module Bucky 7 | module Utils 8 | module YamlLoad 9 | # Load yaml(include erb) 10 | # @param [File] yaml file 11 | # @return [Hash] hashed yaml contents 12 | def load_yaml(file) 13 | YAML.safe_load( 14 | ERB.new(File.read(file)).result, 15 | permitted_classes: [Array, Hash, String, Numeric, Symbol, TrueClass, FalseClass], 16 | aliases: true 17 | ) 18 | end 19 | 20 | # Sort files to hierarchy 21 | # @param [String] path of directory 22 | # @return [Array] sorted files 23 | def file_sort_hierarchy(path) 24 | Dir.glob(path).sort_by { |f| f.split('/').size } 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /docker-compose.system-test.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | bucky-core: 4 | container_name: bucky-core 5 | build: 6 | context: . 7 | dockerfile: Dockerfile.system-test 8 | volumes: 9 | - .:/bucky-core 10 | tty: true 11 | depends_on: 12 | chrome: 13 | condition: service_healthy 14 | web: 15 | condition: service_started 16 | chrome: 17 | container_name: bucky-chrome 18 | image: selenium/standalone-chromium:128.0 19 | healthcheck: 20 | test: ["CMD-SHELL", "curl -f http://localhost:4444 || exit 1"] 21 | interval: 3s 22 | timeout: 5s 23 | retries: 2 24 | ports: 25 | - '4444:4444' 26 | # For local debug 27 | - '5901:5900' 28 | web: 29 | container_name: bucky.net 30 | build: 31 | context: docker/nginx 32 | -------------------------------------------------------------------------------- /spec/core/test_core/test_result_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../../../lib/bucky/core/test_core/test_result' 4 | 5 | describe Bucky::Core::TestCore::TestResult do 6 | let(:test_suite_result) { [{ test_case_1: 1.234 }, { test_case_2: 2.257 }] } 7 | let(:tdo_double) { double('double of Core::Database::TestDataOperator') } 8 | let(:instance) { Bucky::Core::TestCore::TestResult.instance } 9 | before :each do 10 | allow(Bucky::Core::Database::TestDataOperator).to receive(:new).and_return(tdo_double) 11 | end 12 | 13 | describe '#save' do 14 | it 'call test_data_operator#save_test_result' do 15 | allow(instance).to receive(:format_result_summary) 16 | expect(tdo_double).to receive(:save_test_result) 17 | instance.save(test_suite_result) 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/bucky/utils/bucky_logger.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'logger' 4 | require_relative './config' 5 | 6 | module Bucky 7 | module Utils 8 | module BuckyLogger 9 | LogFileDir = Bucky::Utils::Config.instance.data[:log_path] 10 | # Write following logs 11 | # - user opareation (e.g. test_sample_app_pc_e2e_1_1.log) 12 | # - verification (e.g. test_sample_app_pc_e2e_1_1.log) 13 | # - Error of Bucky core (bucky_error.log) 14 | # @param [String] file_name 15 | # @param [String] or [Execption] content 16 | def write(file_name, content) 17 | puts " #{content}" 18 | logger = Logger.new("#{LogFileDir}#{file_name}.log", 1) 19 | logger.info content 20 | logger.close 21 | end 22 | module_function :write 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/bucky/core/report/screen_shot_generator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../../utils/config' 4 | require_relative '../exception/bucky_exception' 5 | 6 | module Bucky 7 | module Core 8 | module Report 9 | module ScreenShotGenerator 10 | # Save screen shot 11 | # @param [Webdriver] driver 12 | # @param [String] test_case e.g.) test_sample_app_pc_e2e_1_1 13 | def generate_screen_shot(driver, test_case) 14 | timestamp = Time.now.strftime('%Y%m%d_%H%M%S') 15 | driver.save_screenshot( 16 | Bucky::Utils::Config.instance[:screen_shot_path] + test_case << "_#{timestamp}.png" 17 | ) 18 | rescue StandardError => e 19 | Bucky::Core::Exception::BuckyException.handle(e) 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/bucky/core/database/db_connector.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'sequel' 4 | require_relative '../exception/bucky_exception' 5 | require_relative '../../utils/config' 6 | 7 | module Bucky 8 | module Core 9 | module Database 10 | class DbConnector 11 | attr_reader :con 12 | 13 | def initialize 14 | @test_db_config = Bucky::Utils::Config.instance[:test_db] 15 | end 16 | 17 | # Connect to database 18 | # @param [String] db_name database name 19 | def connect(db_name = 'bucky_test') 20 | @con = if $debug 21 | @test_db_config[db_name.to_sym] 22 | else 23 | Sequel.connect(@test_db_config[db_name.to_sym], encoding: 'utf8') 24 | end 25 | end 26 | 27 | # Disconnect to database 28 | def disconnect 29 | @con.disconnect 30 | end 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/test_equipment/evidence/evidence_generator_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../../../lib/bucky/test_equipment/evidence/evidence_generator' 4 | 5 | describe Bucky::TestEquipment::Evidence::EvidenceGenerator do 6 | describe '#report' do 7 | it 'call BuckyLogger.write' do 8 | expect(Bucky::Utils::BuckyLogger).to receive(:write) 9 | subject.report('file_path', 'error_class') 10 | end 11 | end 12 | end 13 | 14 | describe Bucky::TestEquipment::Evidence::E2eEvidence do 15 | describe '#save_evidence' do 16 | it 'call #generate_screen_shot' do 17 | allow(subject).to receive(:report) 18 | expect(subject).to receive(:generate_screen_shot) 19 | subject.save_evidence('error_class') 20 | end 21 | it 'call #report' do 22 | allow(subject).to receive(:generate_screen_shot) 23 | expect(subject).to receive(:report) 24 | subject.save_evidence('error_class') 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/test_equipment/selenium_handler/wait_handler_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../../../lib/bucky/test_equipment/selenium_handler/wait_handler' 4 | require 'selenium-webdriver' 5 | require 'English' 6 | 7 | describe Bucky::TestEquipment::SeleniumHandler::WaitHandler do 8 | subject { Bucky::TestEquipment::SeleniumHandler::WaitHandler } 9 | describe '#wait_until_helper' do 10 | let(:timeout) { 0.5 } 11 | let(:interval) { 0.1 } 12 | let(:ignore) { StandardError } 13 | let(:test_block) { p 'test_block' } 14 | let(:test_block_raise_error) { raise StandardError } 15 | it 'not raise exeption' do 16 | expect { subject.wait_until_helper(timeout, interval, ignore) { test_block } }.not_to raise_error 17 | end 18 | 19 | it 'raise ignore exeption after timeout' do 20 | expect { subject.wait_until_helper(timeout, interval, ignore) { test_block_error } }.to raise_error(StandardError) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /Dockerfile.dev: -------------------------------------------------------------------------------- 1 | FROM ruby:3.2-alpine 2 | ENV LANG ja_JP.UTF-8 3 | ENV PAGER busybox less 4 | 5 | RUN apk update && \ 6 | apk upgrade && \ 7 | apk add --update\ 8 | bash \ 9 | build-base \ 10 | curl-dev \ 11 | iproute2 \ 12 | git \ 13 | libxml2-dev \ 14 | libxslt-dev \ 15 | linux-headers \ 16 | mysql-dev \ 17 | openssh \ 18 | ruby-dev \ 19 | ruby-json \ 20 | tzdata \ 21 | yaml \ 22 | yaml-dev \ 23 | zlib-dev 24 | 25 | ENV BC_DIR /bucky-core/ 26 | ENV PATH /bucky-core/exe/:$PATH 27 | WORKDIR $BC_DIR 28 | COPY . $BC_DIR 29 | RUN \ 30 | gem install bundler -v 2.5.18 && \ 31 | echo 'gem: --no-document' >> ~/.gemrc && \ 32 | cp ~/.gemrc /etc/gemrc && \ 33 | chmod uog+r /etc/gemrc && \ 34 | bundle config --global build.nokogiri --use-system-libraries && \ 35 | bundle config --global jobs 4 && \ 36 | bundle install && \ 37 | rm -rf ~/.gem 38 | 39 | WORKDIR /app 40 | RUN chown -R nobody:nobody /app 41 | USER nobody 42 | -------------------------------------------------------------------------------- /lib/bucky/test_equipment/evidence/evidence_generator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../../core/report/screen_shot_generator' 4 | require_relative '../../utils/bucky_logger' 5 | 6 | module Bucky 7 | module TestEquipment 8 | module Evidence 9 | class EvidenceGenerator 10 | include Bucky::Utils::BuckyLogger 11 | 12 | # Save log 13 | # @param [String] file 14 | # @param [Exception Object] err 15 | def report(file, err) 16 | Bucky::Utils::BuckyLogger.write(file, err) 17 | end 18 | end 19 | 20 | # Create evidence for each test category 21 | class E2eEvidence < EvidenceGenerator 22 | include Bucky::Core::Report::ScreenShotGenerator 23 | 24 | def initialize(**evid_args) 25 | @driver = evid_args[:driver] 26 | @tc = evid_args[:test_case] 27 | end 28 | 29 | def save_evidence(err) 30 | generate_screen_shot(@driver, @tc) 31 | report("#{@tc}_error", err) 32 | end 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /system_testing/test_bucky_project/services/service_a/pc/scenarios/e2e/setup_teardown_each_pc_e2e.yml: -------------------------------------------------------------------------------- 1 | desc: setup teardown each pc e2e suits 2 | device: pc 3 | service: service_a 4 | priority: high 5 | test_category: e2e 6 | setup_each: 7 | procs: 8 | - proc: open index 9 | exec: 10 | operate: go 11 | url: http://bucky.net 12 | teardown_each: 13 | procs: 14 | - proc: open test_page.html 15 | exec: 16 | operate: go 17 | url: http://bucky.net/test_page.html 18 | 19 | cases: 20 | - case_name: setup_teardown_each_pc_e2e_1 21 | func: setup teardown each pc e2e 1 func 22 | desc: setup teardown each pc e2e 1 func 23 | procs: 24 | - proc: check title 25 | exec: 26 | verify: assert_title 27 | expect: Test Index 28 | - case_name: setup_teardown_each_pc_e2e_2 29 | func: setup teardown each pc e2e 2 func 30 | desc: setup teardown each pc e2e 2 func 31 | procs: 32 | - proc: check title 33 | exec: 34 | verify: assert_title 35 | expect: Test Index -------------------------------------------------------------------------------- /Dockerfile.system-test: -------------------------------------------------------------------------------- 1 | FROM ruby:3.2-alpine 2 | ENV LANG ja_JP.UTF-8 3 | ENV PAGER busybox less 4 | 5 | RUN apk update && \ 6 | apk upgrade && \ 7 | apk add --update \ 8 | bash \ 9 | build-base \ 10 | curl-dev \ 11 | git \ 12 | iproute2 \ 13 | libxml2-dev \ 14 | libxslt-dev \ 15 | linux-headers \ 16 | mysql-dev \ 17 | openssh \ 18 | ruby-dev \ 19 | ruby-json \ 20 | tzdata \ 21 | yaml \ 22 | yaml-dev \ 23 | zlib-dev \ 24 | curl \ 25 | parallel 26 | 27 | RUN git clone https://github.com/bats-core/bats-core.git /tmp/bats-core && \ 28 | cd /tmp/bats-core && \ 29 | ./install.sh /usr/local && \ 30 | rm -rf /tmp/bats-core 31 | 32 | ENV BC_DIR /bucky-core/ 33 | ENV PATH /bucky-core/exe/:$PATH 34 | WORKDIR $BC_DIR 35 | COPY . $BC_DIR 36 | 37 | RUN gem install bundler -v 2.5.18 && \ 38 | echo 'gem: --no-document' >> ~/.gemrc && \ 39 | cp ~/.gemrc /etc/gemrc && \ 40 | chmod uog+r /etc/gemrc && \ 41 | bundle config --global build.nokogiri --use-system-libraries && \ 42 | bundle config --global jobs 4 && \ 43 | bundle install && \ 44 | rm -rf ~/.gem 45 | 46 | WORKDIR /app 47 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | checks: 3 | argument-count: 4 | enabled: true 5 | config: 6 | threshold: 4 7 | complex-logic: 8 | enabled: true 9 | config: 10 | threshold: 4 11 | file-lines: 12 | enabled: true 13 | config: 14 | threshold: 250 15 | method-complexity: 16 | enabled: true 17 | config: 18 | threshold: 10 19 | method-count: 20 | enabled: true 21 | config: 22 | threshold: 20 23 | method-lines: 24 | enabled: true 25 | config: 26 | threshold: 30 27 | nested-control-flow: 28 | enabled: true 29 | config: 30 | threshold: 4 31 | return-statements: 32 | enabled: true 33 | config: 34 | threshold: 4 35 | similar-code: 36 | enabled: true 37 | config: 38 | threshold: #language-specific defaults. overrides affect all languages. 39 | identical-code: 40 | enabled: true 41 | config: 42 | threshold: #language-specific defaults. overrides affect all languages. 43 | # plugins: 44 | # rubocop: # run by CircleCI 45 | # enabled: true 46 | # channel: rubocop-0-58 47 | exclude_patterns: 48 | - "spec/" 49 | -------------------------------------------------------------------------------- /spec/core/database/db_connector_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../../../lib/bucky/core/database/db_connector' 4 | 5 | describe Bucky::Core::Database::DbConnector do 6 | let(:config_double) { double('double of Config') } 7 | 8 | before do 9 | # Config mock 10 | allow(Bucky::Utils::Config).to receive(:instance).and_return(config_double) 11 | allow(config_double).to receive('[]').and_return(bucky_test: 'test') 12 | end 13 | 14 | describe '#connect' do 15 | it 'Call Sequel.connect' do 16 | allow(Sequel).to receive(:connect).and_return('call Sequel.connect') 17 | expect(Sequel).to receive(:connect) 18 | subject.connect('bucky_test') 19 | end 20 | end 21 | 22 | describe '#disconnect' do 23 | let(:sequel_double) { double('double of Sequel') } 24 | 25 | it 'Call Sequel.disconnect' do 26 | allow(Sequel).to receive(:connect).and_return(sequel_double) 27 | allow(sequel_double).to receive(:disconnect).and_return('call Sequel.disconnect') 28 | 29 | subject.connect 30 | 31 | expect(sequel_double).to receive(:disconnect) 32 | subject.disconnect 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/core/report/screen_shot_generator_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../../../lib/bucky/core/report/screen_shot_generator' 4 | 5 | describe Bucky::Core::Report::ScreenShotGenerator do 6 | let(:test_class) { Struct.new(:test_class) { include Bucky::Core::Report::ScreenShotGenerator } } 7 | let(:driver) { double('double of driver') } 8 | let(:config_double) { double('double of Config') } 9 | let(:error) { StandardError } 10 | subject { test_class.new } 11 | 12 | before do 13 | allow(config_double).to receive('[]').and_return('screen_shot_path') 14 | allow(Bucky::Utils::Config).to receive(:instance).and_return(config_double) 15 | end 16 | 17 | describe '.generate_screen_shot' do 18 | it 'call Selenium::WebDriver::DriverExtensions::TakesScreenshot#save_screenshot' do 19 | expect(driver).to receive(:save_screenshot) 20 | subject.generate_screen_shot(driver, 'test_case_name') 21 | end 22 | context 'get error when calling save_screenshot' do 23 | it 'call BuckyException.handle' do 24 | allow(driver).to receive(:save_screenshot).and_raise(error) 25 | expect(Bucky::Core::Exception::BuckyException).to receive(:handle) 26 | subject.generate_screen_shot(driver, 'test_case_name') 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/core/test_core/exit_handler_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../../../lib/bucky/core/test_core/exit_handler' 4 | 5 | describe Bucky::Core::TestCore::ExitHandler do 6 | let(:instance) { Bucky::Core::TestCore::ExitHandler.instance } 7 | 8 | describe '#initialize' do 9 | it 'initialize exit handler' do 10 | expect(instance.instance_variable_get(:@exit_code)).to eq 0 11 | end 12 | end 13 | 14 | describe '#reset' do 15 | it 'reset exit_code to 0' do 16 | instance.instance_variable_set(:@exit_code, 1) 17 | instance.reset 18 | expect(instance.instance_variable_get(:@exit_code)).to eq 0 19 | end 20 | end 21 | 22 | describe '#raise' do 23 | it 'raise exit code to 1' do 24 | instance.raise 25 | expect(instance.instance_variable_get(:@exit_code)).to eq 1 26 | end 27 | end 28 | 29 | describe '#bucky_exit' do 30 | it 'the exit code should be 1' do 31 | instance.instance_variable_set(:@exit_code, 1) 32 | instance.bucky_exit 33 | rescue SystemExit => e 34 | expect(e.status).to eq(1) 35 | end 36 | 37 | it 'the exit code should be 0' do 38 | instance.instance_variable_set(:@exit_code, 0) 39 | instance.bucky_exit 40 | rescue SystemExit => e 41 | expect(e.status).to eq(0) 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /spec/utils/config_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../../lib/bucky/utils/config' 4 | 5 | describe Bucky::Utils::Config do 6 | let(:config) { Bucky::Utils::Config.instance } 7 | let(:ssh_ip) { '123.123.123.123' } 8 | 9 | before do 10 | Singleton.__init__(Bucky::Utils::Config) 11 | Bucky::Utils::Config.class_variable_set(:@@dir, "#{__dir__}/*yml") 12 | allow_any_instance_of(described_class).to receive(:`).and_return(ssh_ip) 13 | end 14 | 15 | describe '#initialize' do 16 | it 'no error, when load yaml' do 17 | expect { config }.not_to raise_error 18 | end 19 | end 20 | 21 | describe '#switch_specified_word' do 22 | context 'when selenium_ip is "docker_host_ip"' do 23 | it '@data[:selenium_ip] is host server ip' do 24 | expect(config[:selenium_ip]).to eq(ssh_ip) 25 | end 26 | end 27 | end 28 | 29 | describe '#[]' do 30 | it 'no error, get data from multiple layers' do 31 | expect { config[:test_db][:bucky_test] }.not_to raise_error 32 | end 33 | it 'raise exception, when there is no specified key' do 34 | not_found_key = :whoamai 35 | expect { config[not_found_key] }.to raise_error("Undefined Config : #{not_found_key}\nKey doesn't match in config file. Please check config file in config/*") 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/utils/requests_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../../lib/bucky/utils/requests' 4 | 5 | describe Bucky::Utils::Requests do 6 | let(:test_class) { Struct.new(:test_class) { include Bucky::Utils::Requests } } 7 | subject { test_class.new } 8 | let(:uri) { 'http://example.com' } 9 | let(:device) { 'pc' } 10 | let(:open_timeout) { 60 } 11 | let(:read_timeout) { 60 } 12 | let(:http) { Net::HTTP.new('test') } 13 | 14 | describe '#get_response' do 15 | context 'Valid URL is given' do 16 | let(:uri) { 'http://example.com' } 17 | it 'call Net::HTTP.get' do 18 | allow(Net::HTTP).to receive(:start).and_yield(http) 19 | Net::HTTP.start do |http| 20 | expect(http).to receive(:get) 21 | end 22 | subject.get_response(uri, device, open_timeout, read_timeout) 23 | end 24 | end 25 | context 'Unusual URI is given' do 26 | ['https://example.com/path/query[]=1/', 'http://例.com?query=[]'].each do |uri| 27 | it "#{uri} is given, call Net::HTTP.get" do 28 | allow(Net::HTTP).to receive(:start).and_yield(http) 29 | Net::HTTP.start do |http| 30 | expect(http).to receive(:get) 31 | end 32 | subject.get_response(uri, device, open_timeout, read_timeout) 33 | end 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/bucky/core/exception/bucky_exception.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../../utils/bucky_logger' 4 | require_relative '../../utils/config' 5 | 6 | module Bucky 7 | module Core 8 | module Exception 9 | class BuckyException 10 | include Bucky::Utils::BuckyLogger 11 | class << self 12 | # Error handling on bucky framework 13 | # @param [Object] err exception object 14 | def handle(err) 15 | Bucky::Utils::BuckyLogger.write(Bucky::Utils::Config.instance[:bucky_error], err) 16 | end 17 | end 18 | end 19 | 20 | class DbConnectorException < Bucky::Core::Exception::BuckyException 21 | class << self 22 | def handle(err) 23 | super 24 | raise err 25 | end 26 | end 27 | end 28 | 29 | # Error handling on webdriver 30 | # @param [Object] err exception object 31 | # @param [String] proc_name 32 | class WebdriverException < Bucky::Core::Exception::BuckyException 33 | class << self 34 | def handle(err, proc_name = nil) 35 | super(err) 36 | raise err if proc_name.nil? 37 | 38 | raise(err.class, "#{err.message}\nFail in proc: ##{proc_name}") 39 | end 40 | end 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /example/hands-on/services/bucky_hands_on/pc/scenarios/e2e/search_and_assert.yml: -------------------------------------------------------------------------------- 1 | # Describe for this test suite 2 | desc: search in github and check page transition 3 | device: pc 4 | service: bucky_hands_on 5 | priority: high 6 | test_category: e2e 7 | labels: 8 | - example 9 | cases: 10 | # You should create test case name as {test suite name + _ + number} 11 | - case_name: search_and_assert_1 12 | func: transition 13 | desc: Should be able to search bucky-core in github, and move to bucky-core page. 14 | # Procedures to do in this case 15 | procs: 16 | - proc: Open github top page 17 | exec: 18 | operate: go 19 | url: https://github.com/ 20 | - proc: Input 'bucky-core' in search bar 21 | exec: 22 | operate: input 23 | page: github_top 24 | part: search_bar 25 | word: 'bucky-core' 26 | - proc: Click search result 27 | exec: 28 | operate: click 29 | page: github_top 30 | part: search_resault 31 | - proc: Click target result 32 | exec: 33 | operate: click 34 | page: github_search_list 35 | part: bucky_core_a_tag 36 | - proc: assert_text 37 | exec: 38 | verify: assert_title 39 | expect: 'GitHub - lifull-dev/bucky-core: System testing framework for web application.' 40 | -------------------------------------------------------------------------------- /example/bucky-management/services/bucky_example/pc/scenarios/e2e/search_and_assert.yml: -------------------------------------------------------------------------------- 1 | # Describe for this test suite 2 | desc: search in github and check page transition 3 | device: pc 4 | service: bucky_example 5 | priority: high 6 | test_category: e2e 7 | labels: 8 | - example 9 | cases: 10 | # You should create test case name as {test suite name + _ + number} 11 | - case_name: search_and_assert_1 12 | func: transition 13 | desc: Should be able to search bucky-core in github, and move to bucky-core page. 14 | # Procedures to do in this case 15 | procs: 16 | - proc: Open github top page 17 | exec: 18 | operate: go 19 | url: https://github.com/ 20 | - proc: Input 'bucky-core' in search bar 21 | exec: 22 | operate: input 23 | page: github_top 24 | part: search_bar 25 | word: 'bucky-core' 26 | - proc: Click search result 27 | exec: 28 | operate: click 29 | page: github_top 30 | part: search_resault 31 | - proc: Click target result 32 | exec: 33 | operate: click 34 | page: github_search_list 35 | part: bucky_core_a_tag 36 | - proc: assert_text 37 | exec: 38 | verify: assert_title 39 | expect: 'GitHub - lifull-dev/bucky-core: System testing framework for web application.' 40 | -------------------------------------------------------------------------------- /spec/utils/bucky_logger_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../../lib/bucky/utils/bucky_logger' 4 | require 'test/unit' 5 | Test::Unit::AutoRunner.need_auto_run = false 6 | 7 | describe Bucky::Utils::BuckyLogger do 8 | describe '#write' do 9 | let(:klass) { Bucky::Utils::BuckyLogger } 10 | let(:file_name) { 'test' } 11 | let(:logger) { double('logger double') } 12 | subject { klass.write(file_name, content) } 13 | before do 14 | allow(Logger).to receive(:new).and_return(logger) 15 | end 16 | 17 | context 'when args is string object' do 18 | let(:content) { [:operation, 'go', :url, 'http://example.com/'].to_s } 19 | it 'call logger.info' do 20 | expect(logger).to receive(:info) 21 | allow(logger).to receive(:close) 22 | subject 23 | end 24 | it 'call logger.close' do 25 | allow(logger).to receive(:info) 26 | expect(logger).to receive(:close) 27 | subject 28 | end 29 | end 30 | context 'when args is error object' do 31 | let(:content) { Test::Unit::AssertionFailedError } 32 | it 'call logger.info' do 33 | expect(logger).to receive(:info) 34 | allow(logger).to receive(:close) 35 | subject 36 | end 37 | it 'call logger.close' do 38 | allow(logger).to receive(:info) 39 | expect(logger).to receive(:close) 40 | subject 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /system_testing/testing_code/command.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | BUCKY_PROJECT_NAME=bats_test 4 | TEST_SERVICE=test_service 5 | 6 | setup() { 7 | rm -rf /tmp/$BUCKY_PROJECT_NAME 8 | cd /tmp/ 9 | bucky new $BUCKY_PROJECT_NAME 10 | } 11 | 12 | teardown() { 13 | rm -rf /tmp/$BUCKY_PROJECT_NAME 14 | } 15 | 16 | @test "[command] #1 After executing 'new' command, expected files and directories are created" { 17 | run diff /bucky-core/template/new/ /tmp/$BUCKY_PROJECT_NAME 18 | [ $status -eq 0 ] 19 | } 20 | 21 | @test "[command] #2 After executing 'make service' command, expected directory is created" { 22 | cd /tmp/$BUCKY_PROJECT_NAME 23 | bucky make service $TEST_SERVICE 24 | run ls /tmp/$BUCKY_PROJECT_NAME/services/$TEST_SERVICE 25 | [ $status -eq 0 ] 26 | } 27 | 28 | @test "[command] #3 After executing 'make page' command, expected page and parts file are created" { 29 | cd /tmp/$BUCKY_PROJECT_NAME 30 | bucky make service $TEST_SERVICE 31 | bucky make page test_page --service $TEST_SERVICE --device pc 32 | run ls /tmp/$BUCKY_PROJECT_NAME/services/$TEST_SERVICE/pc/pageobject/test_page.rb 33 | [ $status -eq 0 ] 34 | run ls /tmp/$BUCKY_PROJECT_NAME/services/$TEST_SERVICE/pc/parts/test_page.yml 35 | [ $status -eq 0 ] 36 | } 37 | 38 | @test "[command] #4 After executing undefined command, show error message and exit" { 39 | cd /tmp/$BUCKY_PROJECT_NAME 40 | run bucky hoge fuga 41 | [ $(expr "$output" : ".*Invalid command error.*") -ne 0 ] 42 | } 43 | -------------------------------------------------------------------------------- /spec/core/test_core/test_case_loader_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../../../lib/bucky/core/test_core/test_case_loader' 4 | 5 | describe Bucky::Core::TestCore::TestCaseLoader do 6 | describe '.load_testcode' do 7 | subject(:subject) { Bucky::Core::TestCore::TestCaseLoader.load_testcode(test_cond) } 8 | let(:bucky_home) { './spec/test_code' } 9 | 10 | before do 11 | $bucky_home_dir = bucky_home 12 | end 13 | 14 | context 'In case there are some arguments' do 15 | context 'when give args of test suite' do 16 | let(:test_cond) { { suite_name: [expect_scenario], test_category: 'e2e' } } 17 | let(:expect_scenario) { 'scenario_a' } 18 | it 'return test code object' do 19 | expect(subject).not_to be_empty 20 | end 21 | it 'return test code specified by test id' do 22 | subject.each do |code| 23 | expect(code[:test_suite_name]).to eq expect_scenario 24 | end 25 | end 26 | end 27 | 28 | context 'When give args of priority' do 29 | let(:test_cond) { { priority: [expect_priority], test_category: 'e2e' } } 30 | let(:expect_priority) { 'middle' } 31 | it 'return test code object' do 32 | expect(subject).not_to be_empty 33 | end 34 | it 'return test code specified by priority' do 35 | subject.each do |code| 36 | expect(code[:suite][:priority]).to eq expect_priority 37 | end 38 | end 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/bucky/test_equipment/test_case/abst_test_case.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'test/unit' 4 | require 'json' 5 | require_relative '../../core/test_core/test_result' 6 | 7 | module Bucky 8 | module TestEquipment 9 | module TestCase 10 | class AbstTestCase < Test::Unit::TestCase 11 | class << self 12 | def startup 13 | return if $debug 14 | 15 | @@this_result = Bucky::Core::TestCore::TestResult.instance 16 | @@added_result_info = {} 17 | end 18 | 19 | def shutdown 20 | @@this_result.save(@@added_result_info) unless $debug 21 | end 22 | end 23 | 24 | # Override Test::Unit::TestCase#run 25 | # Save test result to own test result object. 26 | def run(result) 27 | super 28 | @@this_result.result = result unless $debug 29 | w_pipe.puts({ 30 | test_class_name: self.class.name, 31 | cases_count: result.run_count, 32 | success_count: result.pass_count, 33 | failure_count: result.run_count - result.pass_count 34 | }.to_json) 35 | end 36 | 37 | def setup 38 | # To make it easy to read 39 | puts "\n" 40 | end 41 | 42 | def teardown 43 | return if $debug 44 | 45 | @@added_result_info[method_name.to_sym] = { 46 | test_suite_id: suite_id, 47 | elapsed_time: Time.now - start_time, 48 | case_name: description 49 | } 50 | end 51 | 52 | def cleanup; end 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/bucky/utils/requests.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'net/http' 4 | require 'addressable/uri' 5 | 6 | module Bucky 7 | module Utils 8 | module Requests 9 | USER_AGENT_STRING = { 10 | pc: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36', 11 | sp: 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_3_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13E238 Safari/601.1' 12 | }.freeze 13 | 14 | # @param [String] uri 15 | # @param [String] device 16 | # @param [Integer/Float] open_timeout max wait time until open page 17 | # @param [Integer/Float] read_timeout max wait time until recieve response 18 | # @return [Net::HTTP] HttpStatusCode 19 | def get_response(uri, device, open_timeout, read_timeout) 20 | parsed_uri = Addressable::URI.parse(uri.to_str.strip) 21 | query = if parsed_uri.query.nil? 22 | '' 23 | else 24 | '?' + parsed_uri.query_values.map { |k, v| "#{CGI.escape(k)}=#{CGI.escape(v)}" }.join('&') 25 | end 26 | # If path is empty, add "/" e.g) http://example.com 27 | path = parsed_uri.path.empty? ? '/' : parsed_uri.path 28 | 29 | Net::HTTP.start(parsed_uri.host, parsed_uri.port, use_ssl: parsed_uri.scheme == 'https') do |http| 30 | http.open_timeout = open_timeout 31 | http.read_timeout = read_timeout 32 | http.get("#{path}#{query}", 'User-Agent' => USER_AGENT_STRING[device.to_sym]) 33 | end 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/core/exception/bucky_exception_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../../../lib/bucky/core/exception/bucky_exception' 4 | 5 | describe Bucky::Core::Exception do 6 | let(:error) { StandardError.new('error test') } 7 | let(:config_double) { double('double of Config') } 8 | before do 9 | allow(Bucky::Utils::Config).to receive(:instance).and_return(config_double) 10 | allow(config_double).to receive('[]').and_return(bucky_error: 'test') 11 | end 12 | 13 | describe Bucky::Core::Exception::BuckyException do 14 | let(:klass) { Bucky::Core::Exception::BuckyException } 15 | describe '.handle' do 16 | it 'call BuckyLogger.write' do 17 | expect(Bucky::Utils::BuckyLogger).to receive(:write) 18 | klass.handle(error) 19 | end 20 | end 21 | end 22 | 23 | describe Bucky::Core::Exception::DbConnectorException do 24 | let(:klass) { Bucky::Core::Exception::DbConnectorException } 25 | describe '.handle' do 26 | it 'raise error' do 27 | allow(Bucky::Core::Exception::BuckyException).to receive(:handle) 28 | expect { klass.handle(error) }.to raise_error(error) 29 | end 30 | end 31 | end 32 | 33 | describe Bucky::Core::Exception::WebdriverException do 34 | let(:klass) { Bucky::Core::Exception::WebdriverException } 35 | describe '.handle' do 36 | it 'raise error' do 37 | allow(Bucky::Core::Exception::BuckyException).to receive(:handle) 38 | expect { klass.handle(error) }.to raise_error(error) 39 | end 40 | 41 | let(:proc_name) { '1:test proc' } 42 | it 'raise error with proc_name' do 43 | allow(Bucky::Core::Exception::BuckyException).to receive(:handle) 44 | expect { klass.handle(error, proc_name) }.to raise_error(StandardError, "error test\nFail in proc: #1:test proc") 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by 2 | # `rubocop --auto-gen-config` 3 | # on 2018-07-30 18:33:29 +0900 using RuboCop version 0.58.2. 4 | # The point is for the user to remove these configuration records 5 | # one by one as the offenses are removed from the code base. 6 | # Note that changes in the inspected code, or installation of new 7 | # versions of RuboCop, may require this file to be generated again. 8 | 9 | # Offense count: 1 10 | # Configuration parameters: CountComments. 11 | Metrics/ClassLength: 12 | Max: 105 13 | Exclude: 14 | - 'lib/bucky/core/database/test_data_operator.rb' 15 | 16 | # Offense count: 3 17 | Security/Eval: 18 | Exclude: 19 | - 'lib/bucky/core/test_core/test_class_generator.rb' 20 | - 'lib/bucky/test_equipment/pageobject/pages.rb' 21 | - 'lib/bucky/test_equipment/verifications/service_verifications.rb' 22 | 23 | # Offense count: 2 24 | # Configuration parameters: EnforcedStyle. 25 | # SupportedStyles: inline, group 26 | Style/AccessModifierDeclarations: 27 | Exclude: 28 | - 'lib/bucky/test_equipment/selenium_handler/webdriver_handler.rb' 29 | - 'lib/bucky/utils/bucky_logger.rb' 30 | 31 | # Offense count: 2 32 | Lint/MissingSuper: 33 | Exclude: 34 | - 'lib/bucky/test_equipment/user_operation/user_operator.rb' 35 | - 'lib/bucky/test_equipment/verifications/service_verifications.rb' 36 | 37 | # Offense count: 2 38 | Style/MissingRespondToMissing: 39 | Exclude: 40 | - 'lib/bucky/test_equipment/user_operation/user_operator.rb' 41 | - 'lib/bucky/test_equipment/verifications/service_verifications.rb' 42 | 43 | # Offense count: 229 44 | # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. 45 | # URISchemes: http, https 46 | Layout/LineLength: 47 | Max: 225 48 | 49 | Style/ExpandPathArguments: 50 | Exclude: 51 | - 'bucky-core.gemspec' # for lifull gem 52 | -------------------------------------------------------------------------------- /lib/bucky/utils/config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'singleton' 4 | require_relative './yaml_load' 5 | 6 | module Bucky 7 | module Utils 8 | class Config 9 | include Bucky::Utils::YamlLoad 10 | include Singleton 11 | @@dir = "#{$bucky_home_dir}/config/**/*yml" 12 | 13 | attr_reader :data 14 | 15 | # @param [String] *.yml or hoge/fuga.yml 16 | def initialize 17 | @data = {} 18 | @resources = [] 19 | @default_config_dir = File.expand_path('../../../template/new/config', __dir__) 20 | # Read from a file of shallow hierarchy, then overwrite it if there is same key in deep hierarchy 21 | file_sort_hierarchy(@@dir).each do |file| 22 | file_name = file.split('/').last 23 | default_config_file = "#{@default_config_dir}/#{file_name}" 24 | data = load_yaml(file) 25 | next if data.empty? 26 | 27 | if File.exist?(default_config_file) 28 | default_config_data = load_yaml(default_config_file) 29 | data = default_config_data.merge(data) 30 | end 31 | @data = @data.merge(data) 32 | @resources << file 33 | end 34 | 35 | set_selenium_ip 36 | end 37 | 38 | # Get data by [] 39 | def [](column) 40 | return @data[column] if @data.key?(column) 41 | 42 | # If there is no key, raise exeption 43 | raise "Undefined Config : #{column}\nKey doesn't match in config file. Please check config file in config/*" 44 | end 45 | 46 | private 47 | 48 | def set_selenium_ip 49 | return unless @data[:selenium_ip] == 'docker_host_ip' 50 | 51 | selenium_ip = `ip route | awk 'NR==1 {print $3}'`.chomp 52 | raise StandardError, 'Could not load docker host ip.' if selenium_ip.empty? 53 | 54 | @data[:selenium_ip] = selenium_ip 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: .rubocop_todo.yml 2 | 3 | AllCops: 4 | NewCops: disable 5 | Exclude: 6 | - 'lib/bucky/test_equipment/user_operation/user_operation_helper.rb' 7 | - 'spec/fixtures/**/*' 8 | - 'spec/test_code/**/*' 9 | - 'template/**/*' 10 | - 'tmp/**/*' 11 | - 'vendor/**/*' 12 | TargetRubyVersion: 3.2 13 | 14 | Metrics/AbcSize: 15 | Enabled: false 16 | 17 | # Allow method prefix start with 'set_' and 'get_' 18 | Naming/AccessorMethodName: 19 | Enabled: false 20 | 21 | Metrics/BlockLength: 22 | Exclude: 23 | - 'Rakefile' 24 | - '**/*.rake' 25 | - 'spec/**/*.rb' 26 | - 'bucky-core.gemspec' 27 | - 'lib/bucky/core/database/test_data_operator.rb' 28 | - 'lib/bucky/core/test_core/test_class_generator.rb' 29 | 30 | Style/ClassVars: 31 | Exclude: 32 | - 'lib/bucky/test_equipment/selenium_handler/webdriver_handler.rb' 33 | - 'lib/bucky/test_equipment/test_case/abst_test_case.rb' 34 | - 'lib/bucky/utils/config.rb' 35 | - 'lib/bucky/tools/lint.rb' 36 | 37 | Metrics/CyclomaticComplexity: 38 | Enabled: false 39 | 40 | # Allow missing top-level documentation comment of class 41 | Style/Documentation: 42 | Enabled: false 43 | 44 | Style/FrozenStringLiteralComment: 45 | EnforcedStyle: always 46 | 47 | Style/GlobalVars: 48 | AllowedVariables: [$bucky_home_dir, $debug, $job_id, $round] 49 | 50 | # Not allow using '->' for multi-line lambdas. 51 | Style/Lambda: 52 | Enabled: false 53 | 54 | Layout/LineLength: 55 | Enabled: false 56 | 57 | Lint/AmbiguousBlockAssociation: 58 | Exclude: 59 | - 'spec/**/*.rb' 60 | 61 | Metrics/MethodLength: 62 | Enabled: false 63 | 64 | Metrics/ModuleLength: 65 | Max: 140 66 | Exclude: 67 | - 'spec/**/*.rb' 68 | 69 | Metrics/PerceivedComplexity: 70 | Enabled: false 71 | 72 | Lint/UselessAssignment: 73 | Exclude: 74 | - 'lib/bucky/core/test_core/test_result.rb' 75 | 76 | Metrics/ClassLength: 77 | Max: 110 78 | 79 | Lint/UselessAccessModifier: 80 | Exclude: 81 | - 'example/**/*' 82 | -------------------------------------------------------------------------------- /example/bucky-management/README.md: -------------------------------------------------------------------------------- 1 | # Bucky-management 2 | 3 | ## What is this example doing 4 | This example will show you how to connect to Bucky-management and how to get test report automatically. 5 | 6 | ## Before starting 7 | Start Bucky-management with docker-compose.yml. 8 | See [READEME](https://github.com/lifull-dev/bucky-management) in Bucky-management if you start it at first time. 9 | ```bash 10 | # Execute this command in Bucky-management directory 11 | docker-compose -f docker-compose.yml up --build -d 12 | ``` 13 | ## 1. Move to you test project directory 14 | - You can see how to make test project in [example/hands-on](https://github.com/lifull-dev/bucky-core/tree/master/example/hands-on) 15 | ```bash 16 | # In this example, you can just stay in example/bucky-management 17 | cd {test project name} 18 | ``` 19 | 20 | ## 2. Sets connect information to DB 21 | - Use default value as following if you didn't change anything in Bucky-management's docker-compose.yml 22 | - Database name is according to what you set in RAILS_ENV when startup Bucky-management 23 | ``` 24 | ## config/test_db_config.yml ## 25 | 26 | :test_db: 27 | :bucky_test: 28 | :username: root 29 | :password: password 30 | :database: bucky_test # When $RAILS_ENV=test 31 | :host: 127.0.0.1 32 | :port: 3306 33 | :adapter: :mysql 34 | ``` 35 | 36 | ## 3. Execute test case without -d option 37 | It will save test result into DB executing without debug option. 38 | ```bash 39 | # These test case is already made. You can just execute them in example/bucky-management 40 | bucky run -t e2e -D pc -c search_and_assert_1 41 | bucky run -t linkstatus -D pc -c github_top_1 42 | ``` 43 | 44 | ## 4. Check your test report 45 | Open your browser http://localhost 46 | 47 | You will see two result shown in top page. 48 | 49 | ## At last 50 | Congratulations!! You have just made your test report. 51 | 52 | There are lots of information shown in reports. 53 | It shows more information in each report's detail, e.g. pass rate, NG cases name, NG error message and so on. 54 | 55 | Enjoy your test with Bucky!! 56 | -------------------------------------------------------------------------------- /system_testing/testing_code/linkstatus.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | setup() { 4 | cd /bucky-core/system_testing/test_bucky_project 5 | } 6 | 7 | @test "[linkstatus] #1, #3 After executing linkstatus on pc, results contain target base url, no failures/errors and exit code is 0." { 8 | run bucky run -t linkstatus -d -D pc -c pc_link_1 9 | [ $status -eq 0 ] 10 | [ $(expr "$output" : ".*http://bucky\.net.*") -ne 0 ] 11 | [ $(expr "$output" : ".*0 failures, 0 errors,.*") -ne 0 ] 12 | } 13 | 14 | @test "[linkstatus] #2 After executing linkstatus on pc, results contain target link url and no failures/errors." { 15 | run bucky run -t linkstatus -d -D pc -c pc_link_1 16 | [ $(expr "$output" : ".*http://bucky\.net/test_page.html.*") -ne 0 ] 17 | [ $(expr "$output" : ".*0 failures, 0 errors,.*") -ne 0 ] 18 | } 19 | 20 | @test "[linkstatus] #4 After executing linkstatus failed test, exit code is 1" { 21 | run bucky run -t linkstatus -d -D pc -c pc_link_2 22 | [ $status -eq 1 ] 23 | } 24 | 25 | @test "[linkstatus] #5 After executing linkstatus on sp, results have no failures nor errors" { 26 | run bucky run -t linkstatus -d -D sp -c sp_link_1 27 | [ $(expr "$output" : ".*0 failures, 0 errors,.*") -ne 0 ] 28 | } 29 | 30 | @test "[linkstatus] #6 After executing linkstatus with excluding by normal, results not contain target link url and no failures/errors." { 31 | run bucky run -t linkstatus -d -D pc -c pc_link_exclude_normal_1 32 | [ $status -eq 0 ] 33 | [ $(expr "$output" : ".*http://bucky\.net/test_page.html.*") -eq 0 ] 34 | } 35 | 36 | @test "[linkstatus] #7 After executing linkstatus with excluding by asterisk, results not contain target link url and no failures/errors." { 37 | run bucky run -t linkstatus -d -D pc -c pc_link_exclude_asterisk_1 38 | [ $status -eq 0 ] 39 | [ $(expr "$output" : ".*http://bucky\.net/test_page.html.*") -eq 0 ] 40 | } 41 | 42 | @test "[linkstatus] #8 After executing linkstatus with excluding by regex, results not contain target link url and no failures/errors." { 43 | run bucky run -t linkstatus -d -D pc -c pc_link_exclude_regex_1 44 | [ $status -eq 0 ] 45 | [ $(expr "$output" : ".*http://bucky\.net/test_page.html.*") -eq 0 ] 46 | } 47 | 48 | -------------------------------------------------------------------------------- /lib/bucky/test_equipment/pageobject/pages.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Bucky 4 | module TestEquipment 5 | module PageObject 6 | class Pages 7 | def initialize(service, device, driver) 8 | collect_pageobjects(service, device, driver) 9 | end 10 | 11 | # Load page class and define page method 12 | # @param [String] service 13 | # @param [String] device (pc, sp) 14 | # @param [Object] driver Webdriver object 15 | def collect_pageobjects(service, device, driver) 16 | # Create module name 17 | module_service_name = service.split('_').map(&:capitalize).join 18 | Dir.glob("#{$bucky_home_dir}/services/#{service}/#{device}/pageobject/*.rb").each do |file| 19 | require file 20 | 21 | page_name = file.split('/')[-1].sub('.rb', '') 22 | page_class_name = page_name.split('_').map(&:capitalize).join 23 | 24 | # Get instance of page class 25 | page_class = eval(format('Services::%s::%s::PageObject::%s', module_service_name: module_service_name, device: device.capitalize, page_class_name: page_class_name)) 26 | page_instance = page_class.new(service, device, page_name, driver) 27 | 28 | # Define method by page name 29 | self.class.class_eval do 30 | define_method(page_name) { page_instance } 31 | end 32 | end 33 | end 34 | 35 | # Get Web element by page, part, num 36 | # @param [Hash] args 37 | def get_part(args) 38 | return send(args[:page]).send(args[:part][:locate])[args[:part][:num]] if part_plural?(args) 39 | 40 | send(args[:page]).send(args[:part]) 41 | end 42 | 43 | # @param [Hash] args 44 | # @return [Boolean] 45 | def part_plural?(args) 46 | # If the part is hash and has 'num' key, it has plural elements. 47 | args[:part].class == Hash && args[:part].key?(:num) 48 | end 49 | 50 | # @param [Hash] args 51 | # @return [Bool] 52 | def part_exist?(args) 53 | get_part(args) 54 | true 55 | rescue Selenium::WebDriver::Error::NoSuchElementError 56 | false 57 | end 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/bucky/tools/lint.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../utils/yaml_load' 4 | 5 | module Bucky 6 | module Tools 7 | class Lint 8 | class << self 9 | include Bucky::Utils::YamlLoad 10 | @@config_dir = "#{$bucky_home_dir}/config/**/*yml" 11 | @@rule_config_dir = File.expand_path('../../../template/new/config', __dir__) + '/*yml' 12 | 13 | def check(category) 14 | method = "check_#{category}".to_sym 15 | respond_to?(method) ? send(method) : raise(StandardError, "no such a category #{category}") 16 | end 17 | 18 | # If you want to add new category, please make new method 19 | def check_config 20 | data = merge_yaml_data(@@config_dir) 21 | @rule_data = merge_yaml_data(@@rule_config_dir) 22 | actual = make_key_chain(data) 23 | expect = make_key_chain(@rule_data) 24 | diff = diff_arr(expect, actual) 25 | make_message(diff) 26 | end 27 | 28 | private 29 | 30 | # Merge yaml in target directory. 31 | def merge_yaml_data(dir) 32 | data_output = {} 33 | file_sort_hierarchy(dir).each do |file| 34 | data = load_yaml(file) 35 | data_output.merge!(data) unless data.empty? 36 | end 37 | data_output.any? ? data_output : (raise StandardError, "No key! please check the directory existence [#{dir}]") 38 | end 39 | 40 | def make_message(diff) 41 | if diff.empty? 42 | puts "\e[32mok\e[0m" 43 | else 44 | puts "\e[31m[ERROR] The following configures are undefined. Tests can still be executed with default value automatically." 45 | diff.each do |key| 46 | puts "- #{key}" 47 | puts "{#{key}: #{@rule_data[:"#{key}"]}}\e[0m" 48 | end 49 | end 50 | end 51 | 52 | def diff_arr(expect, actual) 53 | expect.delete_if do |i| 54 | actual.include?(i) 55 | end 56 | end 57 | 58 | def make_key_chain(hash) 59 | hash.map do |k, v| 60 | if v.is_a? Hash 61 | make_key_chain(v).map { |item| "#{k}-#{item}" } 62 | else 63 | k.to_s 64 | end 65 | end.flatten 66 | end 67 | end 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /lib/bucky/test_equipment/user_operation/user_operator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../../core/exception/bucky_exception' 4 | require_relative './user_operation_helper' 5 | require_relative '../../utils/bucky_logger' 6 | 7 | module Bucky 8 | module TestEquipment 9 | module UserOperation 10 | class UserOperator 11 | include Bucky::Utils::BuckyLogger 12 | 13 | def initialize(args) 14 | @operation_helper = Bucky::TestEquipment::UserOperation::UserOperationHelper.new(args) 15 | @pages = args[:pages] 16 | end 17 | 18 | # Call user operation by argument 19 | # @param [String] operation 20 | # @param [String] test_case_name 21 | # @param [Hash] args 22 | def method_missing(operation, test_case_name, **args) 23 | @operation = operation 24 | @test_case_name = test_case_name 25 | Bucky::Utils::BuckyLogger.write(test_case_name, args[:exec]) 26 | 27 | # Call method of UserOperationHelper 28 | return @operation_helper.send(@operation, args[:exec]) if @operation_helper.methods.include?(@operation) 29 | 30 | # Call method of page object 31 | # e.g) {page: 'top', operation: 'input_freeword', word: 'testing word'} 32 | return page_method(args[:exec]) if args[:exec].key?(:page) && !args[:exec].key?(:part) 33 | 34 | # Call method of part 35 | part_mothod(args[:exec]) if args[:exec].key?(:part) 36 | rescue StandardError => e 37 | Bucky::Core::Exception::WebdriverException.handle(e, "#{args[:step_number]}:#{args[:proc_name]}") 38 | end 39 | 40 | private 41 | 42 | def page_method(args) 43 | @pages.send(args[:page]).send(@operation, args) 44 | end 45 | 46 | def part_mothod(args) 47 | # Multiple parts is saved as hash 48 | # e.g){page: 'top', part: {locate: 'rosen_tokyo', num: 1}, operate: 'click'} 49 | if args[:part].class == Hash 50 | part_name = args[:part][:locate] 51 | num = args[:part][:num] 52 | @pages.send(args[:page]).send(part_name)[num].send(@operation) 53 | # e.g.){page: 'top', part: 'rosen_tokyo', operate: 'click'} 54 | else 55 | @pages.send(args[:page]).send(args[:part]).send(@operation) 56 | end 57 | end 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/bucky/test_equipment/test_case/e2e_test_case.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'selenium-webdriver' 4 | require_relative './abst_test_case' 5 | require_relative '../user_operation/user_operator' 6 | require_relative '../pageobject/pages' 7 | require_relative '../selenium_handler/webdriver_handler' 8 | require_relative '../verifications/service_verifications' 9 | 10 | module Bucky 11 | module TestEquipment 12 | module TestCase 13 | class E2eTestCase < Bucky::TestEquipment::TestCase::AbstTestCase 14 | include Bucky::TestEquipment::SeleniumHandler::WebdriverHandler 15 | 16 | TEST_CATEGORY = 'e2e' 17 | 18 | class << self 19 | def startup; end 20 | 21 | def shutdown; end 22 | end 23 | 24 | # Initialize the following class 25 | # - webdriver 26 | # - page object 27 | # - user oparation 28 | # - verification 29 | # @param [Hash] suite 30 | def t_equip_setup 31 | @driver = create_webdriver(suite_data[:device]) 32 | @pages = Bucky::TestEquipment::PageObject::Pages.new(suite_data[:service], suite_data[:device], @driver) 33 | service_verifications_args = { service: suite_data[:service], device: suite_data[:device], driver: @driver, pages: @pages, method_name: } 34 | @service_verifications = Bucky::TestEquipment::Verifications::ServiceVerifications.new(service_verifications_args) 35 | user_operator_args = { app: suite_data[:service], device: suite_data[:device], driver: @driver, pages: @pages } 36 | @user_operator = Bucky::TestEquipment::UserOperation::UserOperator.new(user_operator_args) 37 | end 38 | 39 | # Call mothod of verification 40 | # @param [Hash] verify_args e.g.) {:exec=>{verify: "assert_title", expect: "page title"}, :step_number=> 1, :proc_name=> "test proc"} 41 | def verify(**verify_args) 42 | @service_verifications.send(verify_args[:exec][:verify], **verify_args) 43 | end 44 | 45 | # Call method of user operation 46 | # @param [Hash] op_args e.g.) {:exec=>{:operate=>"click", :page=>"top_page", :part=>"fizz_button"}, :step_number=> 1, :proc_name=> "test proc"} 47 | def operate(**op_args) 48 | @user_operator.send(op_args[:exec][:operate], method_name, **op_args) 49 | end 50 | 51 | def setup 52 | super 53 | t_equip_setup 54 | end 55 | 56 | def teardown 57 | @driver.quit 58 | ensure 59 | super 60 | end 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/bucky/test_equipment/verifications/service_verifications.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../verifications/e2e_verification' 4 | 5 | module Bucky 6 | module TestEquipment 7 | module Verifications 8 | class ServiceVerifications 9 | attr_reader :e2e_verification 10 | 11 | # @param [String] @service 12 | # @param [String] @device (pc, sp) 13 | # @param [Selenium::WebDriver::Remote::Driver] @driver 14 | # @param [Bucky::TestEquipment::PageObject::Pages] @pages 15 | # @param [String] @test_case_name 16 | def initialize(args) 17 | @service = args[:service] 18 | @device = args[:device] 19 | @driver = args[:driver] 20 | @pages = args[:pages] 21 | @test_case_name = args[:method_name] 22 | collect_verifications 23 | @e2e_verification = Bucky::TestEquipment::Verifications::E2eVerification.new(@driver, @pages, @test_case_name) 24 | end 25 | 26 | def method_missing(verification, **args) 27 | if e2e_verification.respond_to? verification 28 | puts " #{verification} is defined in E2eVerificationClass." 29 | e2e_verification.send(verification, **args[:exec]) 30 | elsif args[:exec].key?(:page) 31 | send(args[:exec][:page]).send(verification, **args[:exec]) 32 | else 33 | raise StandardError, "Undefined verification method or invalid arguments. #{verification},#{args[:exec]}" 34 | end 35 | rescue StandardError => e 36 | Bucky::Core::Exception::WebdriverException.handle(e, "#{args[:step_number]}:#{args[:proc_name]}") 37 | end 38 | 39 | private 40 | 41 | # Load page and define page verification method 42 | def collect_verifications 43 | module_service_name = @service.split('_').map(&:capitalize).join 44 | Dir.glob("#{$bucky_home_dir}/services/#{@service}/#{@device}/verifications/*.rb").each do |file| 45 | require file 46 | 47 | page_name = file.split('/')[-1].sub('.rb', '') 48 | page_class_name = page_name.split('_').map(&:capitalize).join 49 | 50 | # Get instance of page object 51 | page_class = eval(format('Services::%s::%s::Verifications::%s', module_service_name:, device: @device.capitalize, page_class_name:)) 52 | page_instance = page_class.new(@driver, @pages, @test_case_name) 53 | 54 | self.class.class_eval do 55 | define_method(page_name) { page_instance } 56 | end 57 | end 58 | end 59 | end 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /spec/test_equipment/pageobject/pages_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../../../lib/bucky/test_equipment/pageobject/pages' 4 | require 'selenium-webdriver' 5 | 6 | describe Bucky::TestEquipment::PageObject::Pages do 7 | let(:service) { 'service_a' } 8 | let(:device) { 'pc' } 9 | let(:driver) { 'driver' } 10 | let(:page_name) { :sample_page } 11 | let(:bucky_home) { './spec/test_code' } 12 | 13 | before { $bucky_home_dir = bucky_home } 14 | subject { Bucky::TestEquipment::PageObject::Pages.new(service, device, driver) } 15 | 16 | describe '#initialize' do 17 | it 'define instance method with pageobject name' do 18 | expect(subject).to respond_to page_name 19 | end 20 | end 21 | 22 | describe '#get_part' do 23 | let(:page_double) { double('page double') } 24 | let(:part_double) { double('part double') } 25 | context 'in case single part' do 26 | let(:operation_args) { { page: 'test_page', part: 'rosen_tokyo' } } 27 | it 'call send on partobject' do 28 | allow(subject).to receive(:send).and_return(page_double) 29 | expect(page_double).to receive(:send).with(operation_args[:part]) 30 | subject.get_part(operation_args) 31 | end 32 | end 33 | context 'in case operate one part of multiple parts' do 34 | let(:operation_args) { { page: 'top', part: { locate: 'rosen_tokyo', num: 1 } } } 35 | let(:parts_double) { double('parts double') } 36 | it 'call [] on partobject' do 37 | allow(subject).to receive(:send).and_return(parts_double) 38 | allow(parts_double).to receive(:send).and_return(part_double) 39 | expect(part_double).to receive(:[]) 40 | subject.get_part(operation_args) 41 | end 42 | end 43 | end 44 | 45 | describe '#part_plural?' do 46 | let(:args) { { page: 'bukken_detail', part: { locate: 'buttons', num: 0 } } } 47 | it 'if part have "num", return true' do 48 | expect(subject.part_plural?(args)).to be true 49 | end 50 | end 51 | 52 | describe '#part_exist?' do 53 | let(:get_part_double) { double('get_part double') } 54 | let(:operation_args) { { page: 'test_page', part: 'rosen_tokyo' } } 55 | let(:error) { Selenium::WebDriver::Error::NoSuchElementError } 56 | context 'get part' do 57 | it 'if part exists, return true' do 58 | allow(subject).to receive(:get_part).and_return(get_part_double) 59 | expect(subject.part_exist?(operation_args)).to be true 60 | end 61 | it 'if raise NoSuchElementError, return false' do 62 | allow(subject).to receive(:get_part).and_raise(error) 63 | expect(subject.part_exist?(operation_args)).to be false 64 | end 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /bucky-core.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | lib = File.expand_path('../lib', __FILE__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | require 'bucky/version' 6 | 7 | Gem::Specification.new do |spec| 8 | spec.name = 'bucky-core' 9 | spec.version = Bucky::Version::VERSION 10 | spec.authors = %w[NaotoKishino HikaruFukuzawa SeiyaSato NaokiNakano RikiyaHikimochi JyeRuey] 11 | spec.email = ['KishinoNaoto@lifull.com'] 12 | 13 | spec.summary = 'System testing framework for web application.' 14 | spec.description = <<-DESCRIPTION 15 | Bucky-core can run test code which is written in YAML. End-to-End test (working with Selenium) and Linkstatus test (HTTP status check) are supported in default. Page object model pattern and page based element management is the main concept in Bucky-core. You can create scenarios and execute it easily by using Bucky-core. 16 | 17 | When working with Bucky-management, Bucky-core can also record test results. You can make test results visualization by using Bucky-management. 18 | DESCRIPTION 19 | spec.required_ruby_version = '>= 2.5' 20 | spec.homepage = 'https://github.com/lifull-dev/bucky-core' 21 | spec.license = 'Apache License 2.0' 22 | 23 | spec.files = `git ls-files -z`.split("\x0").reject do |f| 24 | f.match(%r{^(test|spec|features)/}) 25 | end 26 | spec.bindir = 'exe' 27 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 28 | spec.require_paths = ['lib'] 29 | 30 | spec.add_development_dependency 'awesome_print', '~> 1.8' 31 | spec.add_development_dependency 'bundler', '2.5.18' 32 | spec.add_development_dependency 'hirb', '~> 0.7' 33 | spec.add_development_dependency 'pry', '~> 0.10' 34 | spec.add_development_dependency 'pry-byebug', '~> 3.4' 35 | spec.add_development_dependency 'pry-stack_explorer', '~> 0.4' 36 | spec.add_development_dependency 'rake', '~> 13' 37 | spec.add_development_dependency 'rspec', '~> 3.6' 38 | spec.add_development_dependency 'rspec_junit_formatter', '~> 0.3' 39 | spec.add_development_dependency 'rubocop', '1.66.1' 40 | spec.add_development_dependency 'simplecov', '~> 0.15.1' 41 | spec.add_development_dependency 'simplecov-console', '~> 0.4.2' 42 | 43 | spec.add_runtime_dependency 'addressable', '~> 2.5' 44 | spec.add_runtime_dependency 'color_echo', '~> 3.1' 45 | spec.add_runtime_dependency 'json', '~> 2.3.0' 46 | spec.add_runtime_dependency 'nokogiri', '1.18.2' 47 | spec.add_runtime_dependency 'parallel', '~> 1.11' 48 | spec.add_runtime_dependency 'ruby-mysql', '~> 2.9' 49 | spec.add_runtime_dependency 'selenium-webdriver', '4.24' 50 | spec.add_runtime_dependency 'sequel', '5.84' 51 | spec.add_runtime_dependency 'test-unit', '~> 3.2' 52 | end 53 | -------------------------------------------------------------------------------- /spec/test_equipment/verifications/service_verifications_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../../../lib/bucky/test_equipment/verifications/service_verifications' 4 | 5 | describe Bucky::TestEquipment::Verifications::ServiceVerifications do 6 | let(:pages) { double('page') } 7 | let(:e2e_verification) { double('e2e_verification') } 8 | let(:assert_title_mock) { double('assert_title_mock') } 9 | let(:page_name) { :sample_page } 10 | let(:args) { { service: 'service_a', device: 'pc', driver: 'driver', method_name: 'test_case_name', pages: } } 11 | let(:bucky_home) { './spec/test_code' } 12 | 13 | subject { Bucky::TestEquipment::Verifications::ServiceVerifications.new(args) } 14 | before do 15 | $bucky_home_dir = bucky_home 16 | allow(pages).to receive(:send).and_return(page_name) 17 | end 18 | 19 | describe '#initialize' do 20 | it 'define all instance valiables' do 21 | expect(subject.instance_variable_get(:@service)).to eq 'service_a' 22 | expect(subject.instance_variable_get(:@device)).to eq 'pc' 23 | expect(subject.instance_variable_get(:@driver)).to eq 'driver' 24 | expect(subject.instance_variable_get(:@test_case_name)).to eq 'test_case_name' 25 | expect(subject.instance_variable_get(:@pages)).to eq pages 26 | end 27 | end 28 | 29 | describe '#method_missing' do 30 | let(:verify_args) { { exec: { verify: 'assert_title', expect: 'page title' }, step_number: 1, proc_name: 'test proc' } } 31 | let(:verify_page_args) { { exec: { page: page_name, verify: 'assert_sample', expect: 'page title' }, step_number: 1, proc_name: 'test proc' } } 32 | let(:dummy_verify_args) { { exec: { verify: 'hoge', expect: 'hoge' }, step_number: 1, proc_name: 'test proc' } } 33 | let(:page_method_double) { double('page method') } 34 | before do 35 | allow(Bucky::TestEquipment::Verifications::E2eVerification).to receive(:new).and_return(e2e_verification) 36 | end 37 | it 'if common e2e method, call send to e2e_verification' do 38 | allow(e2e_verification).to receive(:respond_to?).and_return(true) 39 | expect(e2e_verification).to receive(:send) 40 | subject.send('assert_title', **verify_args) 41 | end 42 | it 'if page verify method, call method of page instance' do 43 | allow(e2e_verification).to receive(:respond_to?).and_return(false) 44 | allow(args).to receive(:key?).and_return(true) 45 | allow(subject).to receive(page_name).and_return(page_method_double) 46 | expect(page_method_double).to receive(:assert_sample) 47 | subject.send('assert_sample', **verify_page_args) 48 | end 49 | let(:args_mock) { double('args_mock') } 50 | it 'if call undefined method, raise exception' do 51 | allow(e2e_verification).to receive(:respond_to?).and_return(false) 52 | allow(args).to receive(:key?).and_return(false) 53 | expect { subject.send('undefined method', dummy_verify_args) }.to raise_error(StandardError) 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/bucky/test_equipment/selenium_handler/webdriver_handler.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'selenium-webdriver' 4 | require_relative '../../core/exception/bucky_exception' 5 | require_relative '../../utils/config' 6 | 7 | module Bucky 8 | module TestEquipment 9 | module SeleniumHandler 10 | module WebdriverHandler 11 | # Create and return webdriver object 12 | # @param [String] device_type e.g.) sp, pc, tablet 13 | # @return [Selenium::WebDriver] 14 | def create_webdriver(device_type) 15 | @@config = Bucky::Utils::Config.instance 16 | driver_args = create_driver_args(device_type) 17 | # Correctly create an options object 18 | options = generate_desire_caps(device_type) 19 | driver = Selenium::WebDriver.for :remote, url: driver_args[:url], options:, http_client: driver_args[:http_client] 20 | driver.manage.window.resize_to(1920, 1080) 21 | driver.manage.timeouts.implicit_wait = @@config[:find_element_timeout] 22 | driver 23 | rescue StandardError => e 24 | Bucky::Core::Exception::BuckyException.handle(e) 25 | end 26 | module_function :create_webdriver 27 | 28 | private 29 | 30 | # @param [String] device_type e.g.) sp, pc, tablet 31 | # @return [Hash] driver_args 32 | def create_driver_args(_device_type) 33 | { 34 | url: format('http://%s:%s/wd/hub', ip: @@config[:selenium_ip], port: @@config[:selenium_port]), 35 | http_client: create_http_client 36 | } 37 | end 38 | 39 | def create_http_client 40 | client = Selenium::WebDriver::Remote::Http::Default.new 41 | client.open_timeout = @@config[:driver_open_timeout] 42 | client.read_timeout = @@config[:driver_read_timeout] 43 | client 44 | end 45 | 46 | # Generate the desired capabilities 47 | # @param [String] device_type e.g.) sp, pc, tablet 48 | # @return [Selenium::WebDriver::Options] 49 | def generate_desire_caps(device_type) 50 | case @@config[:browser] 51 | when :chrome 52 | set_chrome_option(device_type) 53 | else 54 | raise 'Currently only supports chrome. Sorry.' 55 | end 56 | end 57 | 58 | def set_chrome_option(device_type) 59 | options = Selenium::WebDriver::Options.chrome 60 | if %w[sp tablet].include?(device_type) 61 | device_type = "#{device_type}_device_name".to_sym 62 | options.add_emulation(device_name: @@config[:device_name_on_chrome][@@config[device_type]]) 63 | end 64 | options.add_argument("--user-agent=#{@@config[:user_agent]}") if @@config[:user_agent] 65 | options.add_argument('--headless') if @@config[:headless] 66 | @@config[:chromedriver_flags]&.each do |flag| 67 | options.add_argument(flag) 68 | end 69 | options 70 | end 71 | end 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /spec/test_equipment/test_case/abst_test_case_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../../../lib/bucky/test_equipment/test_case/abst_test_case' 4 | 5 | describe Bucky::TestEquipment::TestCase::AbstTestCase do 6 | Test::Unit::AutoRunner.need_auto_run = false 7 | 8 | let(:this_class) { Bucky::TestEquipment::TestCase::AbstTestCase } 9 | context 'not debug mode' do 10 | describe '.startup' do 11 | it 'call Bucky::Core::TestCore::TestResult.instance' do 12 | expect(Bucky::Core::TestCore::TestResult).to receive(:instance) 13 | this_class.startup 14 | end 15 | end 16 | describe '.shutdown' do 17 | let(:test_result_double) { double('test result double') } 18 | before do 19 | allow(Bucky::Core::TestCore::TestResult).to receive(:instance).and_return(test_result_double) 20 | this_class.startup 21 | end 22 | it 'call @@this_result.save' do 23 | expect(test_result_double).to receive(:save) 24 | this_class.shutdown 25 | end 26 | end 27 | describe '#teardown' do 28 | subject { this_class.new('test_method_name') } 29 | let(:added_result_info) { double('added_result_info_double') } 30 | let(:test_result_double) { double('this_result_double') } 31 | before do 32 | allow(Bucky::Core::TestCore::TestResult).to receive(:instance).and_return(test_result_double) 33 | allow(subject).to receive(:suite_id).and_return(1) 34 | allow(subject).to receive(:start_time).and_return(Time.now) 35 | this_class.startup 36 | this_class.class_variable_set('@@added_result_info', added_result_info) 37 | end 38 | it 'call @@added_result_info[]' do 39 | expect(added_result_info).to receive(:[]=) 40 | subject.teardown 41 | end 42 | end 43 | end 44 | context 'debug mode' do 45 | before do 46 | $debug = true 47 | end 48 | 49 | describe '.startup' do 50 | it 'not call Bucky::Core::TestCore::TestResult.instance' do 51 | expect(Bucky::Core::TestCore::TestResult).not_to receive(:instance) 52 | this_class.startup 53 | end 54 | end 55 | describe '.shutdown' do 56 | let(:test_result_double) { double('test result double') } 57 | before do 58 | allow(Bucky::Core::TestCore::TestResult).to receive(:instance).and_return(test_result_double) 59 | this_class.startup 60 | end 61 | it 'not call @@this_result.save' do 62 | expect(test_result_double).not_to receive(:save) 63 | this_class.shutdown 64 | end 65 | end 66 | describe '#teardown' do 67 | subject { this_class.new('test_method_name') } 68 | let(:elapsed_time_each_test_double) { double('elapsed_time_each_test_double') } 69 | before do 70 | this_class.startup 71 | allow(subject).to receive(:start_time).and_return(Time.now) 72 | this_class.class_variable_set('@@elapsed_time_each_test', elapsed_time_each_test_double) 73 | end 74 | it 'not call @@elapsed_time_each_test.push' do 75 | expect(elapsed_time_each_test_double).not_to receive(:push) 76 | subject.teardown 77 | end 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /spec/tools/lint_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../../lib/bucky/tools/lint' 4 | 5 | describe Bucky::Tools::Lint do 6 | let(:lint) { Bucky::Tools::Lint } 7 | let(:data_double) { { k1: 'v1', k2: { k3: 'v2' }, k4: { k5: { k6: 'v3', k7: 'v4' } } } } 8 | let(:rule_data_double) { { k1: 'v1', k2: { k3: 'v2' }, k4: { k5: { k6: 'v3', k8: 'v4' } } } } 9 | let(:config_dir) { "#{File.dirname(__FILE__)}/config_spec.yml" } 10 | let(:rule_config_dir) { "#{File.dirname(__FILE__)}/rule_config_spec.yml" } 11 | let(:dummy_dir) { '/hoge/*yml' } 12 | 13 | before do 14 | lint.class_variable_set(:@@config_dir, config_dir) 15 | lint.class_variable_set(:@@rule_config_dir, config_dir) 16 | end 17 | 18 | # [TODO] call method dynamically 19 | describe '#self.check' do 20 | it 'not raise exception, when argument is config' do 21 | lint.class_variable_set(:@@config_dir, config_dir) 22 | lint.class_variable_set(:@@rule_config_dir, config_dir) 23 | expect { lint.check('config') }.not_to raise_error 24 | end 25 | it 'raise exception, when call not exist method' do 26 | expect { lint.check('hoge') }.to raise_error(StandardError) 27 | end 28 | end 29 | 30 | describe '#check_config' do 31 | it 'print error message, when there is difference of hash' do 32 | lint.class_variable_set(:@@rule_config_dir, rule_config_dir) 33 | expect { lint.check_config }.to output("\e[31m[ERROR] The following configures are undefined. Tests can still be executed with default value automatically.\n- device_name_on_chrome-ipad\n{device_name_on_chrome-ipad: }\e[0m\n").to_stdout 34 | end 35 | it 'print ok, when there is no difference of hash' do 36 | lint.class_variable_set(:@@dir, config_dir) 37 | lint.class_variable_set(:@@rule_config_dir, config_dir) 38 | expect { lint.check_config }.to output("\e[32mok\e[0m\n").to_stdout 39 | end 40 | end 41 | 42 | describe '#merge_yaml_data' do 43 | it 'load yaml and make hash' do 44 | expect(lint.send(:merge_yaml_data, config_dir)).to be_a(Hash) 45 | end 46 | it 'when there is no key in yaml, raise exception' do 47 | expect { lint.send(:merge_yaml_data, dummy_dir) }.to raise_error(StandardError, "No key! please check the directory existence [#{dummy_dir}]") 48 | end 49 | end 50 | 51 | describe '#make_message' do 52 | it 'when there are element in given array, print error message' do 53 | expect { lint.send(:make_message, ['foo']) }.to output("\e[31m[ERROR] The following configures are undefined. Tests can still be executed with default value automatically.\n- foo\n{foo: }\e[0m\n").to_stdout 54 | end 55 | it 'when there are no element in given array, print ok' do 56 | expect { lint.send(:make_message, []) }.to output("\e[32mok\e[0m\n").to_stdout 57 | end 58 | end 59 | 60 | describe '#diff_arr' do 61 | it 'return diff array' do 62 | arr1 = %w[k1 k2-k3 k4-k5-k6 k4-k5-k7] 63 | arr2 = %w[k1 k2-k3] 64 | expect(lint.send(:diff_arr, arr1, arr2)).to eq %w[k4-k5-k6 k4-k5-k7] 65 | end 66 | end 67 | 68 | describe '#make_key_chain' do 69 | it 'return array containing elements of chain case' do 70 | expected_list = %w[k1 k2-k3 k4-k5-k6 k4-k5-k7] 71 | expect(lint.send(:make_key_chain, data_double)).to eq expected_list 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /lib/bucky/core/test_core/test_result.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'singleton' 4 | require 'json' 5 | require 'test/unit/testresult' 6 | 7 | require_relative '../database/test_data_operator' 8 | 9 | module Bucky 10 | module Core 11 | module TestCore 12 | class TestResult 13 | include Singleton 14 | 15 | attr_accessor :result 16 | 17 | def initialize 18 | @result = Test::Unit::TestResult.new 19 | @tdo = Bucky::Core::Database::TestDataOperator.new 20 | end 21 | 22 | # Save test result 23 | # @param [Array] added_result_info 24 | def save(added_result_info) 25 | # Format data before saving to database. 26 | test_suite_result = format_result_summary(added_result_info) 27 | @tdo.save_test_result(test_suite_result) 28 | end 29 | 30 | private 31 | 32 | # Return Bucky::Test::Unit::TestResult Object 33 | # @param [Array] added_result_info 34 | # @return [Array] Array data for Sequel 35 | def format_result_summary(added_result_info) 36 | # For sequel 37 | data_set = {} 38 | error_test_case_name = [] 39 | 40 | ############################################## 41 | # Store failed cases in data set. # 42 | ############################################## 43 | @result.faults.each do |fault| 44 | case_name = fault.method_name 45 | case_id = @tdo.get_test_case_id(added_result_info[case_name.to_sym][:test_suite_id], added_result_info[case_name.to_sym][:case_name]) 46 | error_test_case_name.push(case_name.to_sym) 47 | data_set[case_name] = 48 | [ 49 | id = nil, 50 | test_case_id = case_id, 51 | error_title = fault.message, 52 | error_message = fault.location.join("\n"), 53 | elapsed_time = added_result_info[case_name.to_sym][:elapsed_time], 54 | is_error = 1, 55 | job_id = $job_id, 56 | round = $round 57 | ] 58 | end 59 | 60 | ############################################ 61 | # Store passed cases in data set. # 62 | ############################################ 63 | added_result_info.delete_if { |k, _v| error_test_case_name.include?(k) } 64 | added_result_info.each do |case_name, added_info| 65 | case_id = @tdo.get_test_case_id(added_info[:test_suite_id], added_info[:case_name]) 66 | data_set[case_name] = 67 | [ 68 | id = nil, 69 | test_case_id = case_id, 70 | error_title = '', 71 | error_message = '', 72 | elapsed_time = added_info[:elapsed_time], 73 | is_error = 0, 74 | job_id = $job_id, 75 | round = $round 76 | ] 77 | end 78 | 79 | # Data set for sequel 80 | data_set_ary = data_set.map { |_, v| v } 81 | 82 | # Table colomn 83 | column_name_ary = %i[id test_case_id error_title error_message elapsed_time is_error job_id round] 84 | { 85 | column: column_name_ary, 86 | data_set: data_set_ary 87 | } 88 | end 89 | end 90 | end 91 | end 92 | end 93 | -------------------------------------------------------------------------------- /spec/core/test_core/test_class_generator_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../../../lib/bucky/core/test_core/test_class_generator' 4 | require 'test/unit' 5 | Test::Unit::AutoRunner.need_auto_run = false 6 | 7 | describe Bucky::Core::TestCore::TestClassGenerator.new(test_cond: 'for_test') do 8 | describe '#generate_test_class' do 9 | before do 10 | allow(Bucky::Utils::Config).to receive(:instance).and_return(linkstatus_open_timeout: 60) 11 | allow(Bucky::Utils::Config).to receive(:instance).and_return(linkstatus_read_timeout: 60) 12 | end 13 | let(:linkstatus_url_log) { { url: { code: 200, count: 1 } } } 14 | let(:linkstatus_data) do 15 | { 16 | test_class_name: 'SearchflowAreaTop', 17 | test_suite_name: 'searchflow_area_top', 18 | test_category: 'linkstatus', 19 | suite: { 20 | desc: 'check link on top page', 21 | device: device, 22 | service: 'foo', 23 | priority: 'high', 24 | test_category: 'linkstatus', 25 | cases: [ 26 | { 27 | desc: 'top page', 28 | urls: ['http://localhost/hoge'] 29 | } 30 | ] 31 | }, 32 | test_suite_id: 6 33 | } 34 | end 35 | let(:e2e_data) do 36 | { 37 | test_class_name: 'SearchflowAreaTop', 38 | test_suite_name: 'searchflow_area_top', 39 | test_category: 'e2e', 40 | suite: { 41 | desc: 'description', 42 | device: 'pc', 43 | service: 'foo', 44 | priority: :high, 45 | test_category: :e2e, 46 | cases: [ 47 | { 48 | desc: 'case_description', 49 | procs: [ 50 | { 51 | proc: 'open toppage', 52 | exec: 53 | { 54 | operate: :go, 55 | url: 'http://localhost/' 56 | } 57 | } 58 | ] 59 | } 60 | ] 61 | } 62 | } 63 | end 64 | 65 | describe 'generate class' do 66 | let(:device) { 'pc' } 67 | it 'generate test class according to test suite data' do 68 | subject.generate_test_class(data: linkstatus_data, linkstatus_url_log: linkstatus_url_log) 69 | expect(subject.instance_variable_get(:@test_classes).const_get(:TestFooPcLinkstatusSearchflowAreaTop).name).to eq('Bucky::Core::TestCore::TestClasses::TestFooPcLinkstatusSearchflowAreaTop') 70 | end 71 | end 72 | 73 | describe 'generate method' do 74 | let(:device) { 'sp' } 75 | context 'in case of linkstatus' do 76 | it 'generate test method according to test suite data' do 77 | subject.generate_test_class(data: linkstatus_data, linkstatus_url_log: linkstatus_url_log) 78 | expect(subject.instance_variable_get(:@test_classes).const_get(:TestFooSpLinkstatusSearchflowAreaTop).instance_methods).to include :test_foo_sp_linkstatus_searchflow_area_top_0 79 | end 80 | end 81 | 82 | context 'in case of e2e' do 83 | it 'generate test method according to test suite data' do 84 | subject.generate_test_class(data: e2e_data) 85 | expect(subject.instance_variable_get(:@test_classes).const_get(:TestFooPcE2eSearchflowAreaTop).instance_methods).to include :test_foo_pc_e2e_searchflow_area_top_0 86 | end 87 | end 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /lib/bucky/test_equipment/pageobject/base_pageobject.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../../utils/yaml_load' 4 | require_relative '../../core/exception/bucky_exception' 5 | require_relative '../selenium_handler/wait_handler' 6 | 7 | module Bucky 8 | module TestEquipment 9 | module PageObject 10 | class BasePageObject 11 | include Bucky::Utils::YamlLoad 12 | include Bucky::TestEquipment::SeleniumHandler::WaitHandler 13 | 14 | # https://seleniumhq.github.io/selenium/docs/api/rb/Selenium/WebDriver/SearchContext.html#find_element-instance_method 15 | FINDERS = { 16 | class: 'class name', 17 | class_name: 'class name', 18 | css: 'css selector', 19 | id: 'id', 20 | link: 'link text', 21 | link_text: 'link text', 22 | name: 'name', 23 | partial_link_text: 'partial link text', 24 | tag_name: 'tag name', 25 | xpath: 'xpath' 26 | }.freeze 27 | 28 | def initialize(service, device, page_name, driver) 29 | @driver = driver 30 | generate_parts(service, device, page_name) 31 | end 32 | 33 | private 34 | 35 | # Load parts file and define parts method 36 | # @param [String] service 37 | # @param [String] device (pc, sp) 38 | # @param [String] page_name 39 | def generate_parts(service, device, page_name) 40 | Dir.glob("./services/#{service}/#{device}/parts/#{page_name}.yml").each do |file| 41 | parts_data = load_yaml(file) 42 | if parts_data.nil? 43 | warn("Warning: #{file} is empty") 44 | next 45 | end 46 | # Define part method e.g.) page.parts 47 | parts_data.each do |part_name, value| 48 | self.class.class_eval { define_method(part_name) { find_elem(value[0], value[1]) } } 49 | end 50 | end 51 | end 52 | 53 | # @param [String] method Method name for searching WebElement 54 | # http://www.rubydoc.info/gems/selenium-webdriver/0.0.28/Selenium/WebDriver/Find 55 | # @param [String] Condition of search (xpath/id) 56 | # @return [Selenium::WebDriver::Element] 57 | def find_elem(method, value) 58 | method_name = method.downcase.to_sym 59 | raise StandardError, "Invalid finder. #{method_name}" unless FINDERS.key? method_name 60 | 61 | # wait until driver find element 62 | elements = wait_until_helper(3, 0.1, Selenium::WebDriver::Error::NoSuchElementError) { @driver.find_elements(method_name, value) } 63 | raise_if_elements_empty(elements, method_name, value) 64 | 65 | get_element_or_attribute = lambda do |elems, arg| 66 | # return WebElement 67 | return elems[arg] if arg.is_a? Integer 68 | # return String(Value of WebElement`s attribute) 69 | return elems.first.attribute(arg) if [String, Symbol].include? arg.class 70 | 71 | raise StandardError, "Invalid argument type. Expected type is Integer/String/Symbol.\n\ 72 | | Got argument:#{arg}, type:#{arg.class}." 73 | end 74 | 75 | elements.first.instance_eval do 76 | define_singleton_method('[]') { |arg| get_element_or_attribute.call(elements, arg) } 77 | %w[each map length].each { |m| define_singleton_method(m) { |&block| elements.send(m, &block) } } 78 | end 79 | 80 | elements.first 81 | end 82 | 83 | def raise_if_elements_empty(elements, method, value) 84 | raise Selenium::WebDriver::Error::NoSuchElementError, "#{method} #{value}" if elements.empty? 85 | end 86 | end 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /spec/test_equipment/pageobject/base_pageobject_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../../../lib/bucky/test_equipment/pageobject/base_pageobject' 4 | require 'selenium-webdriver' 5 | 6 | describe Bucky::TestEquipment::PageObject::BasePageObject do 7 | subject { Bucky::TestEquipment::PageObject::BasePageObject.new('a', 'b', 'c', driver) } 8 | let(:driver) { double('driver double') } 9 | describe '#initialize' do 10 | it 'not raise exeption' do 11 | expect { subject }.not_to raise_error 12 | end 13 | end 14 | describe '#find_elem' do 15 | let(:value) { 'content-1' } 16 | let(:method_mock) { 'id' } 17 | let(:config_double) { double('double of Config') } 18 | let(:element) do 19 | class Element end 20 | return Element.new 21 | end 22 | let(:elem_objects) { [element, element.dup] } 23 | context 'If given method name is included in FINDERS' do 24 | %w[class class_name css id link link_text name partial_link_text tag_name xpath].each do |method| 25 | it "If #{method} is given, find_elements(:#{method}, value) is call" do 26 | expect(driver).to receive(:find_elements).with(method.to_sym, value).and_return(elem_objects) 27 | subject.send(:find_elem, method, value) 28 | end 29 | end 30 | context 'Redefine Array methods in WebElement' do 31 | let(:method) { :xpath } 32 | before do 33 | allow(driver).to receive(:find_elements).with(method, value).and_return(elem_objects) 34 | end 35 | %w[[] each map length].each do |redefined_method| 36 | it "#{redefined_method} method is redefined in WebElement instance" do 37 | elem = subject.send(:find_elem, method, value) 38 | expect(elem).to respond_to(redefined_method.to_sym) 39 | end 40 | end 41 | context '[] method' do 42 | it '[] method is called in multipule WebElements object, if [] method called with integers.' do 43 | elem = subject.send(:find_elem, method, value) 44 | expect(elem_objects).to receive(:[]) 45 | elem[1] 46 | end 47 | it 'Attribute method is called in a single WebElement, if [] method called with string.' do 48 | elem = subject.send(:find_elem, method, value) 49 | expect(element).to receive(:attribute) 50 | elem['attribute_name'] 51 | end 52 | it 'Attribute method is called in a single WebElement, if [] method called with symbol.' do 53 | elem = subject.send(:find_elem, method, value) 54 | expect(element).to receive(:attribute) 55 | elem[:attribute_name] 56 | end 57 | it 'Raise invalid argument type error, if [] method called with anything other than integers, strings, or symbols.' do 58 | elem = subject.send(:find_elem, method, value) 59 | expect { elem[[]] }.to raise_error(StandardError) 60 | end 61 | end 62 | end 63 | end 64 | it 'If given method name is not included in FINDERS, raise error' do 65 | method = 'invalid method name' 66 | allow(Bucky::Core::Exception::BuckyException).to receive(:handle) 67 | expect { subject.send(:find_elem, method, value) }.to raise_error(StandardError, "Invalid finder. #{method}") 68 | end 69 | it 'raise StandardError when exception' do 70 | allow(Bucky::Utils::Config).to receive(:instance).and_return(config_double) 71 | allow(config_double).to receive('[]').and_return(bucky_error: 'test') 72 | allow(driver).to receive(:find_elements).and_raise(Selenium::WebDriver::Error::NoSuchElementError.new) 73 | expect { subject.send(:find_elem, method_mock, value) }.to raise_error(StandardError) 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | bucky-core (0.10.25) 5 | addressable (~> 2.5) 6 | color_echo (~> 3.1) 7 | json (~> 2.3.0) 8 | nokogiri (~> 1.11.1) 9 | parallel (~> 1.11) 10 | ruby-mysql (~> 2.9) 11 | selenium-webdriver (= 4.24) 12 | sequel (= 5.84) 13 | test-unit (~> 3.2) 14 | 15 | GEM 16 | remote: https://rubygems.org/ 17 | specs: 18 | addressable (2.8.7) 19 | public_suffix (>= 2.0.2, < 7.0) 20 | ansi (1.5.0) 21 | ast (2.4.2) 22 | awesome_print (1.9.2) 23 | base64 (0.2.0) 24 | bigdecimal (3.1.8) 25 | binding_of_caller (1.0.1) 26 | debug_inspector (>= 1.2.0) 27 | byebug (11.1.3) 28 | coderay (1.1.3) 29 | color_echo (3.1.1) 30 | debug_inspector (1.2.0) 31 | diff-lcs (1.5.1) 32 | docile (1.1.5) 33 | hirb (0.7.3) 34 | json (2.3.1) 35 | language_server-protocol (3.17.0.3) 36 | logger (1.6.1) 37 | method_source (1.1.0) 38 | mini_portile2 (2.5.3) 39 | nokogiri (1.11.7) 40 | mini_portile2 (~> 2.5.0) 41 | racc (~> 1.4) 42 | parallel (1.26.3) 43 | parser (3.3.5.0) 44 | ast (~> 2.4.1) 45 | racc 46 | power_assert (2.0.3) 47 | pry (0.14.2) 48 | coderay (~> 1.1) 49 | method_source (~> 1.0) 50 | pry-byebug (3.10.1) 51 | byebug (~> 11.0) 52 | pry (>= 0.13, < 0.15) 53 | pry-stack_explorer (0.6.1) 54 | binding_of_caller (~> 1.0) 55 | pry (~> 0.13) 56 | public_suffix (6.0.1) 57 | racc (1.8.1) 58 | rainbow (3.1.1) 59 | rake (13.2.1) 60 | regexp_parser (2.9.2) 61 | rexml (3.3.7) 62 | rspec (3.13.0) 63 | rspec-core (~> 3.13.0) 64 | rspec-expectations (~> 3.13.0) 65 | rspec-mocks (~> 3.13.0) 66 | rspec-core (3.13.1) 67 | rspec-support (~> 3.13.0) 68 | rspec-expectations (3.13.3) 69 | diff-lcs (>= 1.2.0, < 2.0) 70 | rspec-support (~> 3.13.0) 71 | rspec-mocks (3.13.1) 72 | diff-lcs (>= 1.2.0, < 2.0) 73 | rspec-support (~> 3.13.0) 74 | rspec-support (3.13.1) 75 | rspec_junit_formatter (0.6.0) 76 | rspec-core (>= 2, < 4, != 2.12.0) 77 | rubocop (1.66.1) 78 | json (~> 2.3) 79 | language_server-protocol (>= 3.17.0) 80 | parallel (~> 1.10) 81 | parser (>= 3.3.0.2) 82 | rainbow (>= 2.2.2, < 4.0) 83 | regexp_parser (>= 2.4, < 3.0) 84 | rubocop-ast (>= 1.32.2, < 2.0) 85 | ruby-progressbar (~> 1.7) 86 | unicode-display_width (>= 2.4.0, < 3.0) 87 | rubocop-ast (1.32.3) 88 | parser (>= 3.3.1.0) 89 | ruby-mysql (2.11.1) 90 | ruby-progressbar (1.13.0) 91 | rubyzip (2.3.2) 92 | selenium-webdriver (4.24.0) 93 | base64 (~> 0.2) 94 | logger (~> 1.4) 95 | rexml (~> 3.2, >= 3.2.5) 96 | rubyzip (>= 1.2.2, < 3.0) 97 | websocket (~> 1.0) 98 | sequel (5.84.0) 99 | bigdecimal 100 | simplecov (0.15.1) 101 | docile (~> 1.1.0) 102 | json (>= 1.8, < 3) 103 | simplecov-html (~> 0.10.0) 104 | simplecov-console (0.4.2) 105 | ansi 106 | hirb 107 | simplecov 108 | simplecov-html (0.10.2) 109 | test-unit (3.6.2) 110 | power_assert 111 | unicode-display_width (2.6.0) 112 | websocket (1.2.11) 113 | 114 | PLATFORMS 115 | ruby 116 | 117 | DEPENDENCIES 118 | awesome_print (~> 1.8) 119 | bucky-core! 120 | bundler (= 2.5.18) 121 | hirb (~> 0.7) 122 | pry (~> 0.10) 123 | pry-byebug (~> 3.4) 124 | pry-stack_explorer (~> 0.4) 125 | rake (~> 13) 126 | rspec (~> 3.6) 127 | rspec_junit_formatter (~> 0.3) 128 | rubocop (= 1.66.1) 129 | simplecov (~> 0.15.1) 130 | simplecov-console (~> 0.4.2) 131 | 132 | BUNDLED WITH 133 | 2.5.18 134 | -------------------------------------------------------------------------------- /system_testing/test_bucky_project/services/service_a/pc/scenarios/e2e/pc_e2e.yml: -------------------------------------------------------------------------------- 1 | desc: pc e2e suits 2 | device: pc 3 | service: service_a 4 | priority: high 5 | test_category: e2e 6 | cases: 7 | - case_name: pc_e2e_1 8 | func: pc e2e 1 func 9 | desc: pc e2e 1 func 10 | procs: 11 | - proc: open index 12 | exec: 13 | operate: go 14 | url: http://bucky.net 15 | - proc: open index 16 | exec: 17 | operate: click 18 | page: index 19 | part: link 20 | timeout: 1 21 | - proc: check title 22 | exec: 23 | verify: assert_title 24 | expect: Test Page 25 | - case_name: pc_e2e_2 26 | func: pc e2e 2 func 27 | desc: pc e2e 2 func 28 | procs: 29 | - proc: open index 30 | exec: 31 | operate: go 32 | url: http://bucky.net 33 | - proc: open index 34 | exec: 35 | operate: click 36 | page: index 37 | part: link 38 | - proc: check title(NG) 39 | exec: 40 | verify: assert_title 41 | expect: Test Page Wrong 42 | - case_name: pc_e2e_3 43 | func: pc e2e 3 func UserAgent 44 | desc: pc e2e 3 func UserAgent 45 | procs: 46 | - proc: open index 47 | exec: 48 | operate: go 49 | url: http://bucky.net 50 | - proc: check UserAgent(OS) 51 | exec: 52 | verify: assert_contained_text 53 | page: index 54 | part: ua 55 | expect: Linux 56 | - case_name: pc_e2e_4 57 | func: pc e2e 4 func when 58 | desc: pc e2e 4 func when 59 | procs: 60 | - proc: open index 61 | exec: 62 | operate: go 63 | url: http://bucky.net 64 | - proc: index 65 | exec: 66 | operate: click 67 | page: index 68 | part: link 69 | when: <%= ENV['STAGE'] == 'development' %> 70 | - case_name: pc_e2e_5 71 | func: pc e2e 5 multi elements 72 | desc: pc e2e 5 multi elements 73 | procs: 74 | - proc: open index 75 | exec: 76 | operate: go 77 | url: http://bucky.net 78 | - proc: index 79 | exec: 80 | operate: click 81 | page: index 82 | part: 83 | locate: links 84 | num: 1 85 | - case_name: pc_e2e_6 86 | func: pc e2e 6 get element from PageObject 87 | desc: pc e2e 6 click the single element from PageObject 88 | procs: 89 | - proc: open index 90 | exec: 91 | operate: go 92 | url: http://bucky.net 93 | - proc: index 94 | exec: 95 | operate: click_single_element 96 | page: index 97 | - case_name: pc_e2e_7 98 | func: pc e2e 7 get multiple element from PageObject 99 | desc: pc e2e 7 click the second element in multiple element from PageObject 100 | procs: 101 | - proc: open index 102 | exec: 103 | operate: go 104 | url: http://bucky.net 105 | - proc: index 106 | exec: 107 | operate: click_multiple_element 108 | page: index 109 | - case_name: pc_e2e_8 110 | func: pc e2e 8 get element from Verifications 111 | desc: pc e2e 8 click the single element from Verifications 112 | procs: 113 | - proc: open index 114 | exec: 115 | operate: go 116 | url: http://bucky.net 117 | - proc: index 118 | exec: 119 | verify: click_single_element 120 | page: index 121 | - case_name: pc_e2e_9 122 | func: pc e2e 9 get multiple element from Verifications 123 | desc: pc e2e 9 click the second element in multiple element from Verifications 124 | procs: 125 | - proc: open index 126 | exec: 127 | operate: go 128 | url: http://bucky.net 129 | - proc: index 130 | exec: 131 | verify: click_multiple_element 132 | page: index 133 | -------------------------------------------------------------------------------- /spec/test_equipment/test_case/e2e_test_case_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../../../lib/bucky/test_equipment/test_case/e2e_test_case' 4 | 5 | describe Bucky::TestEquipment::TestCase::E2eTestCase do 6 | Test::Unit::AutoRunner.need_auto_run = false 7 | let(:this_class) { Bucky::TestEquipment::TestCase::E2eTestCase } 8 | subject { this_class.new('test_method_name') } 9 | 10 | before do 11 | allow(subject).to receive(:suite_data).and_return(service: 'service_a', device: 'pc') 12 | end 13 | 14 | describe '#t_equip_setup' do 15 | it 'call create_webdriver' do 16 | allow(Bucky::TestEquipment::PageObject::Pages).to receive(:new) 17 | allow(Bucky::TestEquipment::Verifications::ServiceVerifications).to receive(:new) 18 | allow(Bucky::TestEquipment::UserOperation::UserOperator).to receive(:new) 19 | expect(subject).to receive(:create_webdriver) 20 | subject.t_equip_setup 21 | end 22 | it 'create instance of Bucky::TestEquipment::PageObject::Pages' do 23 | allow(subject).to receive(:create_webdriver) 24 | allow(Bucky::TestEquipment::Verifications::ServiceVerifications).to receive(:new) 25 | allow(Bucky::TestEquipment::UserOperation::UserOperator).to receive(:new) 26 | expect(Bucky::TestEquipment::PageObject::Pages).to receive(:new) 27 | subject.t_equip_setup 28 | end 29 | it 'create instance of Bucky::TestEquipment::UserOperation::UserOperator' do 30 | allow(subject).to receive(:create_webdriver) 31 | allow(Bucky::TestEquipment::Verifications::ServiceVerifications).to receive(:new) 32 | allow(Bucky::TestEquipment::PageObject::Pages).to receive(:new) 33 | expect(Bucky::TestEquipment::UserOperation::UserOperator).to receive(:new) 34 | subject.t_equip_setup 35 | end 36 | it 'create instance of Bucky::TestEquipment::Verifications::ServiceVerifications' do 37 | allow(subject).to receive(:create_webdriver) 38 | allow(Bucky::TestEquipment::PageObject::Pages).to receive(:new) 39 | allow(Bucky::TestEquipment::UserOperation::UserOperator).to receive(:new) 40 | expect(Bucky::TestEquipment::Verifications::ServiceVerifications).to receive(:new) 41 | subject.t_equip_setup 42 | end 43 | end 44 | 45 | describe '#verify' do 46 | let(:verification_double) { double('verification double') } 47 | let(:verify_args) { { exec: { verify: 'assert_title', expect: 'page title' }, step_number: 1, proc_name: 'test proc' } } 48 | before do 49 | allow(subject).to receive(:create_webdriver) 50 | allow(Bucky::TestEquipment::Verifications::ServiceVerifications).to receive(:new).and_return(verification_double) 51 | allow(Bucky::TestEquipment::PageObject::Pages).to receive(:new) 52 | allow(Bucky::TestEquipment::UserOperation::UserOperator).to receive(:new) 53 | subject.t_equip_setup 54 | end 55 | it 'call service_verifications.send' do 56 | expect(verification_double).to receive(:send) 57 | subject.verify(**verify_args) 58 | end 59 | end 60 | 61 | describe '#operate' do 62 | let(:user_operator_double) { double('user_operator double') } 63 | let(:op_args) { { exec: { operate: 'click', page: 'top', part: { locate: 'rosen_tokyo', num: 1 } }, step_number: 1, proc_name: 'test proc' } } 64 | before do 65 | allow(subject).to receive(:create_webdriver) 66 | allow(Bucky::TestEquipment::Verifications::ServiceVerifications).to receive(:new) 67 | allow(Bucky::TestEquipment::PageObject::Pages).to receive(:new) 68 | allow(Bucky::TestEquipment::UserOperation::UserOperator).to receive(:new).and_return(user_operator_double) 69 | allow(Bucky::TestEquipment::Verifications::E2eVerification).to receive(:new) 70 | subject.t_equip_setup 71 | end 72 | it 'call user_operator.send' do 73 | expect(user_operator_double).to receive(:send) 74 | subject.operate(**op_args) 75 | end 76 | end 77 | 78 | describe '#setup' do 79 | it 'call t_equip_setup' do 80 | expect(subject).to receive(:t_equip_setup) 81 | subject.setup 82 | end 83 | end 84 | describe '#teardown' do 85 | # Not implement, because implementing super mock is difficault 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /lib/bucky/test_equipment/user_operation/user_operation_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../selenium_handler/wait_handler' 4 | 5 | module Bucky 6 | module TestEquipment 7 | module UserOperation 8 | class UserOperationHelper 9 | include Bucky::TestEquipment::SeleniumHandler::WaitHandler 10 | 11 | def initialize(args) 12 | @app = args[:app] 13 | @device = args[:device] 14 | @driver = args[:driver] 15 | @pages = args[:pages] 16 | end 17 | 18 | # Open url 19 | # @param [Hash] 20 | def go(args) 21 | @driver.navigate.to args[:url] 22 | end 23 | 24 | def back(_) 25 | @driver.navigate.back 26 | end 27 | 28 | def input(args) 29 | # when input successfully, return of click is nil. 30 | wait_until_helper((args || {}).fetch(:timeout, 5), 0.1, Selenium::WebDriver::Error::StaleElementReferenceError) { @pages.get_part(args).send_keys(args[:word]).nil? } 31 | end 32 | 33 | # Clear textbox 34 | def clear(args) 35 | @pages.get_part(args).clear 36 | end 37 | 38 | def click(args) 39 | elem = @pages.get_part(args) 40 | # when click successfully, return of click is nil. 41 | wait_until_helper((args || {}).fetch(:timeout, 5), 0.1, Selenium::WebDriver::Error::WebDriverError) { elem.click.nil? } 42 | end 43 | 44 | def refresh(_) 45 | @driver.navigate.refresh 46 | end 47 | 48 | def switch_to_next_window(_) 49 | window_index = @driver.window_handles.index(@driver.window_handle) 50 | windows_number = @driver.window_handles.size 51 | return if window_index + 1 == windows_number 52 | 53 | @driver.switch_to.window(@driver.window_handles[window_index + 1]) 54 | end 55 | 56 | def switch_to_previous_window(_) 57 | window_index = @driver.window_handles.index(@driver.window_handle) 58 | @driver.switch_to.window(@driver.window_handles[window_index - 1]) 59 | end 60 | 61 | def switch_to_newest_window(_) 62 | @driver.switch_to.window(@driver.window_handles.last) 63 | end 64 | 65 | def switch_to_oldest_window(_) 66 | @driver.switch_to.window(@driver.window_handles.first) 67 | end 68 | 69 | def switch_to_the_window(args) 70 | # when the window successfully switched, return of switch_to.window is nil. 71 | wait_until_helper((args || {}).fetch(:timeout, 5), 0.1, Selenium::WebDriver::Error::NoSuchWindowError) { @driver.switch_to.window(args[:window_name]).nil? } 72 | end 73 | 74 | # Close window 75 | def close(_) 76 | window_index = @driver.window_handles.index(@driver.window_handle) 77 | @driver.close 78 | @driver.switch_to.window(@driver.window_handles[window_index - 1]) 79 | end 80 | 81 | def stop(_) 82 | puts 'stop. press enter to continue' 83 | gets 84 | end 85 | 86 | def choose(args) 87 | option = wait_until_helper((args || {}).fetch(:timeout, 5), 0.1, Selenium::WebDriver::Error::StaleElementReferenceError) { Selenium::WebDriver::Support::Select.new(@pages.get_part(args)) } 88 | if args.key?(:text) 89 | type = :text 90 | selected = args[type].to_s 91 | elsif args.key?(:value) 92 | type = :value 93 | selected = args[type].to_s 94 | elsif args.key?(:index) 95 | type = :index 96 | selected = args[type].to_i 97 | else 98 | raise StandardError, "Included invalid key #{args.keys}" 99 | end 100 | option.select_by(type, selected) 101 | end 102 | 103 | # Alert accept 104 | def accept_alert(args) 105 | alert = wait_until_helper((args || {}).fetch(:timeout, 5), 0.1, Selenium::WebDriver::Error::NoSuchAlertError) { @driver.switch_to.alert } 106 | alert.accept 107 | end 108 | 109 | def wait(args) 110 | # Indent 111 | print ' ' * 6 112 | args[:sec].times do |count| 113 | print "#{count + 1} " 114 | sleep 1 115 | end 116 | puts '' 117 | end 118 | end 119 | end 120 | end 121 | end 122 | -------------------------------------------------------------------------------- /spec/test_equipment/user_operation/user_operator_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../../../lib/bucky/test_equipment/user_operation/user_operator' 4 | 5 | describe Bucky::TestEquipment::UserOperation::UserOperator do 6 | let(:pages_double) { double('pages double') } 7 | let(:user_operation_helper_double) { double('user_operation_helper double') } 8 | let(:uset_operator_args) { { service: 'sample_app', device: 'pc', driver: { driver: 'mock driver' }, pages: pages_double } } 9 | 10 | subject { Bucky::TestEquipment::UserOperation::UserOperator.new(uset_operator_args) } 11 | before do 12 | allow(Bucky::Utils::BuckyLogger).to receive(:write) 13 | end 14 | 15 | describe '#method_missing' do 16 | before do 17 | allow(Bucky::TestEquipment::UserOperation::UserOperationHelper).to receive(:new).and_return(user_operation_helper_double) 18 | allow(user_operation_helper_double).to receive_message_chain(:methods, :include?).and_return(operation_helper_methods?) 19 | end 20 | context 'when call method of operation helper' do 21 | let(:operation_helper_methods?) { true } 22 | let(:operation) { :go } 23 | let(:operation_args) { { exec: { operation: 'go' }, step_number: 1, proc_name: 'test proc' } } 24 | it 'call operation_helper.send' do 25 | expect(user_operation_helper_double).to receive(:send) 26 | subject.send(operation, 'test_method_name', **operation_args) 27 | end 28 | end 29 | context 'when call method of other than operation helper' do 30 | let(:page_double) { double('page double') } 31 | let(:part_double) { double('part double') } 32 | let(:operation_helper_methods?) { false } 33 | let(:operation) { :go } 34 | let(:operation_args) { { exec: { operation: 'go' }, step_number: 1, proc_name: 'test proc' } } 35 | 36 | it 'not call operation_helper.send' do 37 | expect(user_operation_helper_double).not_to receive(:send) 38 | subject.send(operation, 'test_method_name', **operation_args) 39 | end 40 | 41 | context 'when call method of pageobject' do 42 | let(:operation) { :input_inquire_info } 43 | let(:operation_args) { { exec: { page: 'top', operate: 'input_inquire_info' }, step_number: 1, proc_name: 'test proc' } } 44 | it 'call pageobject.send' do 45 | allow(pages_double).to receive(:send).and_return(page_double) 46 | expect(page_double).to receive(:send) 47 | subject.send(operation, 'test_method_name', **operation_args) 48 | end 49 | end 50 | context 'when call method of part' do 51 | let(:operation) { :click } 52 | context 'in case single part' do 53 | let(:operation_args) { { exec: { page: 'top', part: 'rosen_tokyo' }, step_number: 1, proc_name: 'test proc' } } 54 | it 'call send of part object' do 55 | allow(pages_double).to receive(:send).and_return(page_double) 56 | allow(page_double).to receive(:send).and_return(part_double) 57 | expect(part_double).to receive(:send).with(operation) 58 | subject.send(operation, 'test_method_name', **operation_args) 59 | end 60 | end 61 | context 'in case operate one part of multiple parts' do 62 | let(:operation_args) { { exec: { page: 'top', part: { locate: 'rosen_tokyo', num: 1 } }, step_number: 1, proc_name: 'test proc' } } 63 | let(:parts_double) { double('parts double') } 64 | it 'call send of part object' do 65 | allow(pages_double).to receive(:send).and_return(page_double) 66 | allow(page_double).to receive(:send).and_return(parts_double) 67 | allow(parts_double).to receive(:[]).and_return(part_double) 68 | expect(part_double).to receive(:send) 69 | subject.send(operation, 'test_method_name', **operation_args) 70 | end 71 | end 72 | end 73 | end 74 | context 'when raise exception' do 75 | let(:operation_helper_methods?) { true } 76 | let(:operation) { :go } 77 | let(:operation_args) { { exec: { operation: 'go' }, step_number: 1, proc_name: 'test proc' } } 78 | let(:exception) { StandardError } 79 | it 'call WebdrverException.handle' do 80 | allow(user_operation_helper_double).to receive(:send).and_raise(exception) 81 | expect { subject.send(operation, 'test_method_name', operation_args) }.to raise_error(exception) 82 | end 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /system_testing/testing_code/e2e.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | setup() { 4 | cd /bucky-core/system_testing/test_bucky_project 5 | } 6 | 7 | @test "[e2e] #01 After executing e2e operate go, results have no failures nor errors" { 8 | run bucky run -t e2e -d -D pc -c pc_e2e_1 9 | [ $(expr "$output" : ".*0 failures, 0 errors,.*") -ne 0 ] 10 | } 11 | 12 | @test "[e2e] #02 After executing e2e verify assert_title, results contain verify words, no failures/errors and exit code is 0" { 13 | run bucky run -t e2e -d -D pc -c pc_e2e_1 14 | [ $status -eq 0 ] 15 | [ $(expr "$output" : ".*:verify.*assert_title.*") -ne 0 ] 16 | [ $(expr "$output" : ".*0 failures, 0 errors,.*") -ne 0 ] 17 | } 18 | 19 | @test "[e2e] #03 After executing e2e verify assert_title, results contain verify words and 1 failure and exit code is 1" { 20 | run bucky run -t e2e -d -D pc -c pc_e2e_2 21 | [ $status -eq 1 ] 22 | [ $(expr "$output" : ".*:verify.*assert_title.*") -ne 0 ] 23 | [ $(expr "$output" : ".*1 failures.*") -ne 0 ] 24 | } 25 | 26 | @test "[e2e] #04 After executing e2e on pc , results contain 'Linux' and no failures/errors." { 27 | run bucky run -t e2e -d -D pc -c pc_e2e_3 28 | [ $(expr "$output" : ".*Linux.*") -ne 0 ] 29 | [ $(expr "$output" : ".*0 failures, 0 errors,.*") -ne 0 ] 30 | } 31 | 32 | @test "[e2e] #05 After executing e2e on sp , results contain 'iPhone' and no failures/errors." { 33 | run bucky run -t e2e -d -D sp -c sp_e2e_1 34 | [ $(expr "$output" : ".*iPhone.*") -ne 0 ] 35 | [ $(expr "$output" : ".*0 failures, 0 errors,.*") -ne 0 ] 36 | } 37 | 38 | @test "[e2e] #06 After executing e2e on tablet , results contain 'iPad' and no failures/errors." { 39 | run bucky run -t e2e -d -D tablet -c tablet_e2e_1 40 | [ $(expr "$output" : ".*iPad.*") -ne 0 ] 41 | [ $(expr "$output" : ".*0 failures, 0 errors,.*") -ne 0 ] 42 | } 43 | 44 | @test "[e2e] #07 After executing e2e operate go with setup each, results have no failures nor errors" { 45 | run bucky run -d -c setup_each_pc_e2e_1 46 | [ $(expr "$output" : ".*0 failures, 0 errors,.*") -ne 0 ] 47 | } 48 | 49 | @test "[e2e] #08 After executing e2e operate go with teardown each, results have no failures nor errors" { 50 | run bucky run -d -c teardown_each_pc_e2e_1 51 | [ $(expr "$output" : ".*0 failures, 0 errors,.*") -ne 0 ] 52 | } 53 | 54 | @test "[e2e] #09 When setup and teardown is added, multiple test cases execute normally, results have no failures nor errors" { 55 | run bucky run -d -s setup_teardown_each_pc_e2e 56 | [ $(expr "$output" : ".*0 failures, 0 errors,.*") -ne 0 ] 57 | } 58 | 59 | @test "[e2e] #10 When 'when condition' is added, results have no failures nor errors" { 60 | export STAGE=development 61 | run bucky run -t e2e -d -D pc -c pc_e2e_4 62 | [ $(expr "$output" : ".*click.*") -ne 0 ] 63 | [ $(expr "$output" : ".*0 failures, 0 errors,.*") -ne 0 ] 64 | } 65 | 66 | @test "[e2e] #11 When 'when condition' is not added, results have no click procedure" { 67 | run bucky run -t e2e -d -D pc -c pc_e2e_4 68 | [ $(expr "$output" : ".*click.*") -eq 0 ] 69 | [ $(expr "$output" : ".*0 failures, 0 errors,.*") -ne 0 ] 70 | } 71 | 72 | @test "[e2e] #12 When 'different when condition' is added, results have no click procedure" { 73 | export STAGE=staging 74 | run bucky run -t e2e -d -D pc -c pc_e2e_4 75 | [ $(expr "$output" : ".*click.*") -eq 0 ] 76 | [ $(expr "$output" : ".*0 failures, 0 errors,.*") -ne 0 ] 77 | } 78 | 79 | @test "[e2e] #13 When click second element from scenario, results have no failures nor errors" { 80 | run bucky run -t e2e -d -D pc -c pc_e2e_5 81 | [ $(expr "$output" : ".*0 failures, 0 errors,.*") -ne 0 ] 82 | } 83 | 84 | @test "[e2e] #14 When click element from PageObject file, results have no failures nor errors" { 85 | run bucky run -t e2e -d -D pc -c pc_e2e_6 86 | [ $(expr "$output" : ".*0 failures, 0 errors,.*") -ne 0 ] 87 | } 88 | 89 | @test "[e2e] #15 When click second element from PageObject file, results have no failures nor errors" { 90 | run bucky run -t e2e -d -D pc -c pc_e2e_7 91 | [ $(expr "$output" : ".*0 failures, 0 errors,.*") -ne 0 ] 92 | } 93 | 94 | @test "[e2e] #16 When click element from Verification file, results have no failures nor errors" { 95 | run bucky run -t e2e -d -D pc -c pc_e2e_8 96 | [ $(expr "$output" : ".*0 failures, 0 errors,.*") -ne 0 ] 97 | } 98 | 99 | @test "[e2e] #17 When click second element from Verification file, results have no failures nor errors" { 100 | run bucky run -t e2e -d -D pc -c pc_e2e_9 101 | [ $(expr "$output" : ".*0 failures, 0 errors,.*") -ne 0 ] 102 | } 103 | -------------------------------------------------------------------------------- /spec/core/test_core/test_manager_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../../../lib/bucky/core/test_core/test_manager' 4 | require 'test/unit' 5 | Test::Unit::AutoRunner.need_auto_run = false 6 | 7 | describe Bucky::Core::TestCore::TestManager do 8 | # Class Double 9 | let(:tdo) { double('tdo-double') } 10 | before do 11 | allow(Bucky::Core::Database::TestDataOperator).to receive(:new).and_return(tdo) 12 | allow(tdo).to receive(:save_job_record_and_get_job_id) 13 | allow(tdo).to receive(:get_ng_test_cases_at_last_execution).and_return(ng_case_data) 14 | allow(tm).to receive(:do_test_suites).and_return({}) 15 | end 16 | 17 | describe '#run' do 18 | let(:tm) { Bucky::Core::TestCore::TestManager.new(re_test_count: re_test_count) } 19 | 20 | describe '@re_test_count: run untill max round' do 21 | context '@re_test_count is 1' do 22 | let(:re_test_count) { 1 } 23 | let(:ng_case_data) { { test_case: 1 } } 24 | it 'call load_test_suites once' do 25 | expect(tm).to receive(:load_test_suites).once 26 | tm.run 27 | end 28 | end 29 | 30 | context '@re_test_count is 2' do 31 | let(:re_test_count) { 2 } 32 | let(:ng_case_data) { { test_case: 1 } } 33 | it 'call load_test_suites twice' do 34 | expect(tm).to receive(:load_test_suites).twice 35 | tm.run 36 | end 37 | end 38 | end 39 | 40 | describe '@tdo.get_ng_test_cases_at_last_execution' do 41 | context 'there is no failure' do 42 | let(:re_test_count) { 2 } 43 | let(:ng_case_data) { {} } 44 | it 'call load_test_suites once' do 45 | expect(tm).to receive(:load_test_suites).once 46 | tm.run 47 | end 48 | end 49 | end 50 | end 51 | 52 | describe '#rerun' do 53 | let(:tm) { Bucky::Core::TestCore::TestManager.new(re_test_count: re_test_count, job: rerun_job_id) } 54 | before do 55 | allow(tdo).to receive(:get_last_round_from_job_id) 56 | end 57 | 58 | describe 'call execute_test on rerun method' do 59 | context 'there is no failure in round 2' do 60 | let(:re_test_count) { 2 } 61 | let(:rerun_job_id) { 10 } 62 | let(:ng_case_data) { {} } 63 | it 'call execute_test' do 64 | expect(tm).to receive(:execute_test) 65 | tm.rerun 66 | end 67 | end 68 | end 69 | end 70 | 71 | describe '#parallel_helper' do 72 | let(:tm) { Bucky::Core::TestCore::TestManager.new(re_test_count: 1) } 73 | let(:parallel_helper) { Class.new { extend Bucky::Core::TestCore::ParallelHelper } } 74 | let(:block_mock) { proc { 'Block mock' } } 75 | let(:ng_case_data) { {} } 76 | before do 77 | $debug = true 78 | end 79 | after do 80 | $debug = false 81 | end 82 | context 'parallel_new_worker_each' do 83 | let(:e2e_parallel_num) { 2 } 84 | let(:test_suite_data_e2e) do 85 | [ 86 | { test_class_name: 'test', 87 | test_suite_name: 'test', 88 | test_category: 'e2e', 89 | suite: { 90 | device: 'pc', 91 | service: 'spec', 92 | test_category: 'e2e', 93 | cases: [ 94 | { case_name: 'test_1' } 95 | ] 96 | } } 97 | ] 98 | end 99 | 100 | it 'call target block in fork' do 101 | allow(parallel_helper).to receive(:fork) do |&block| 102 | expect(block.call).to eq('Block mock') 103 | end 104 | parallel_helper.send(:parallel_new_worker_each, test_suite_data_e2e, e2e_parallel_num, &block_mock) 105 | end 106 | end 107 | 108 | context 'parallel_distribute_into_workers' do 109 | let(:linkstatus_parallel_num) { 2 } 110 | let(:test_suite_data_linkstatus) do 111 | [ 112 | { test_class_name: 'test', 113 | test_suite_name: 'test', 114 | test_category: 'linkstatus', 115 | suite: { 116 | device: 'pc', 117 | service: 'spec', 118 | test_category: 'linkstatus', 119 | cases: [ 120 | { case_name: 'test_1' } 121 | ] 122 | } } 123 | ] 124 | end 125 | it 'make workers in fork then call block in each' do 126 | allow(parallel_helper).to receive(:fork) do |&block_fork| 127 | allow(block_fork.call).to receive(:each) do |&block_each| 128 | expect(block_each.call).to eq('Block mock') 129 | end 130 | end 131 | parallel_helper.send(:parallel_distribute_into_workers, test_suite_data_linkstatus, linkstatus_parallel_num, &block_mock) 132 | end 133 | end 134 | end 135 | end 136 | -------------------------------------------------------------------------------- /lib/bucky/test_equipment/verifications/e2e_verification.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../../utils/bucky_logger' 4 | require_relative '../../utils/bucky_output' 5 | require_relative './abst_verification' 6 | require_relative '../evidence/evidence_generator' 7 | 8 | module Bucky 9 | module TestEquipment 10 | module Verifications 11 | class E2eVerification < Bucky::TestEquipment::Verifications::AbstVerification 12 | include Bucky::Utils::BuckyLogger 13 | include Bucky::Utils::BuckyOutput 14 | using StringColorize 15 | 16 | def initialize(driver, pages, test_case_name) 17 | @driver = driver 18 | @pages = pages 19 | @evidence = Bucky::TestEquipment::Evidence::E2eEvidence.new(driver: driver, test_case: test_case_name) 20 | end 21 | 22 | def pages_getter 23 | @pages 24 | end 25 | 26 | # Check whether title of web page matches expected value 27 | # @param [Hash] 28 | def assert_title(**args) 29 | Bucky::Utils::BuckyLogger.write('assert_title', args) 30 | verify_rescue { assert_equal(args[:expect]&.to_s, @driver.title, 'Not Expected Title.') } 31 | end 32 | 33 | # Check whether text of web element matches expected value 34 | # @param [Hash] 35 | def assert_text(**args) 36 | Bucky::Utils::BuckyLogger.write('assert_text', args) 37 | part = @pages.get_part(args) 38 | verify_rescue { assert_equal(args[:expect]&.to_s, part.text, 'Not Expected Text.') } 39 | end 40 | 41 | # Check whether text of web element contains expected value 42 | # @param [Hash] 43 | def assert_contained_text(**args) 44 | Bucky::Utils::BuckyLogger.write('assert_contained_text', args) 45 | part = @pages.get_part(args) 46 | verify_rescue { assert(part.text.include?(args[:expect]&.to_s), "Not Contain Expected Text.\nexpect: #{args[:expect].to_s.bg_green.black}\nactual: #{part.text.to_s.bg_red.black}") } 47 | end 48 | 49 | # Check whether url contains excepted value 50 | # @param [Hash] 51 | def assert_contained_url(**args) 52 | Bucky::Utils::BuckyLogger.write('assert_contained_url', args) 53 | verify_rescue { assert(@driver.current_url.include?(args[:expect]&.to_s), "Not Contain Expected URL.\nexpect: #{args[:expect].to_s.bg_green.black}\nactual: #{@driver.current_url.to_s.bg_red.black}") } 54 | end 55 | 56 | # Check whether attribute of web element contains excepted value. 57 | # @param [Hash] 58 | def assert_contained_attribute(**args) 59 | Bucky::Utils::BuckyLogger.write('assert_contained_attribute', args) 60 | part = @pages.get_part(args) 61 | verify_rescue { assert(part[args[:attribute]].include?(args[:expect]&.to_s), "Not Contain Expected Attribute.\nexpect: #{args[:expect].to_s.bg_green.black}\nactual: #{part[args[:attribute]].to_s.bg_red.black}") } 62 | end 63 | 64 | # Check whether text of part is number 65 | # @param [Hash] 66 | def assert_is_number(**args) 67 | Bucky::Utils::BuckyLogger.write('assert is number', args) 68 | text = @pages.get_part(args).text.sub(',', '') 69 | verify_rescue { assert(text.to_i.to_s == text.to_s, "Not number.\nactual: #{text.bg_red.black}") } 70 | end 71 | 72 | # Check whether style property includes display:block 73 | # @param [Hash] 74 | def assert_display(**args) 75 | Bucky::Utils::BuckyLogger.write('assert display', args) 76 | verify_rescue { assert_true(@pages.get_part(args).displayed?, "No display this parts.\nURL: #{@driver.current_url}\npage: #{args[:page]}\npart: #{args[:part]}") } 77 | end 78 | 79 | # Check whether web element exists 80 | # @param [Hash] 81 | def assert_exist_part(**args) 82 | Bucky::Utils::BuckyLogger.write('assert_exist_part', args) 83 | verify_rescue { assert_true(@pages.part_exist?(args), "This part is not exist.\nURL: #{@driver.current_url}\npage: #{args[:page]}\npart: #{args[:part]}") } 84 | end 85 | 86 | # Check whether web element don't exist 87 | # @param [Hash] 88 | def assert_not_exist_part(**args) 89 | Bucky::Utils::BuckyLogger.write('assert_not_exist_part', args) 90 | verify_rescue { assert_false(@pages.part_exist?(args), "This part is exist.\nURL: #{@driver.current_url}\npage: #{args[:page]}\npart: #{args[:part]}") } 91 | end 92 | 93 | private 94 | 95 | # Common exception method 96 | # @param [Method] &assert 97 | def verify_rescue 98 | yield 99 | rescue StandardError => e 100 | @evidence.save_evidence(e) 101 | raise e 102 | end 103 | end 104 | end 105 | end 106 | end 107 | -------------------------------------------------------------------------------- /spec/test_equipment/selenium_handler/webdriver_handler_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../../../lib/bucky/test_equipment/selenium_handler/webdriver_handler' 4 | 5 | describe Bucky::TestEquipment::SeleniumHandler::WebdriverHandler do 6 | include Bucky::TestEquipment::SeleniumHandler::WebdriverHandler 7 | let(:config_double) { double('double of Config') } 8 | let(:webdriver_double) { double('double of Webdriver') } 9 | let(:webdriver_manage_double) { double('double of Webdriver.manage') } 10 | let(:webdriver_manage_window_double) { double('double of Webdriver.manage.window') } 11 | let(:webdriver_manage_timeouts_double) { double('double of Webdriver.manage.timeouts') } 12 | let(:webdriver_manage_timeouts_double_implicit_wait) { double('double of Webdriver.manage.timeouts.implicit_wait') } 13 | 14 | before do 15 | allow(Bucky::Utils::Config).to receive(:instance).and_return(config_double) 16 | allow(config_double).to receive('[]').with(:browser).and_return(browser) 17 | allow(config_double).to receive('[]').with(:selenium_ip).and_return(selenium_ip) 18 | allow(config_double).to receive('[]').with(:selenium_port).and_return(selenium_port) 19 | allow(config_double).to receive('[]').with(:device_name_on_chrome).and_return(device_name_on_chrome) 20 | allow(config_double).to receive('[]').with(:sp_device_name).and_return(sp_device_name) 21 | allow(config_double).to receive('[]').with(:tablet_device_name).and_return(sp_device_name) 22 | allow(config_double).to receive('[]').with(:bucky_error).and_return(bucky_error) 23 | allow(config_double).to receive('[]').with(:driver_open_timeout).and_return(10) 24 | allow(config_double).to receive('[]').with(:driver_read_timeout).and_return(10) 25 | allow(config_double).to receive('[]').with(:user_agent).and_return(10) 26 | allow(config_double).to receive('[]').with(:find_element_timeout).and_return(10) 27 | allow(config_double).to receive('[]').with(:headless).and_return(headless) 28 | allow(config_double).to receive('[]').with(:chromedriver_flags).and_return(chromedriver_flags) 29 | allow(Selenium::WebDriver).to receive(:for).and_return(webdriver_double) 30 | allow(webdriver_double).to receive(:manage).and_return(webdriver_manage_double) 31 | allow(webdriver_manage_double).to receive(:window).and_return(webdriver_manage_window_double) 32 | allow(webdriver_manage_window_double).to receive(:resize_to) 33 | allow(webdriver_manage_double).to receive(:timeouts).and_return(webdriver_manage_timeouts_double) 34 | allow(webdriver_manage_timeouts_double).to receive(:implicit_wait=) 35 | end 36 | 37 | describe '#create_webdriver' do 38 | let(:selenium_ip) { '11.22.33.44' } 39 | let(:selenium_port) { '4444' } 40 | let(:device_name_on_chrome) { { iphone6: 'Apple iPhone 6' } } 41 | let(:sp_device_name) { :iphone6 } 42 | let(:sp_device_name) { :ipad } 43 | let(:bucky_error) { 'bucky error' } 44 | let(:headless) { false } 45 | let(:chromedriver_flags) { ['--foo', '--bar', '--baz'] } 46 | 47 | context 'pc' do 48 | let(:device_type) { 'pc' } 49 | context 'browser is chrome' do 50 | let(:browser) { :chrome } 51 | it 'call Selenium::WebDriver.for' do 52 | expect(Selenium::WebDriver).to receive(:for) 53 | create_webdriver(device_type) 54 | end 55 | end 56 | context 'browser is not chrome' do 57 | let(:browser) { :firefox } 58 | it 'raise exception' do 59 | expect(Bucky::Core::Exception::BuckyException).to receive(:handle) 60 | create_webdriver(device_type) 61 | end 62 | end 63 | end 64 | context 'sp' do 65 | let(:device_type) { 'sp' } 66 | context 'browser is chrome' do 67 | let(:browser) { :chrome } 68 | it 'call Selenium::WebDriver.for' do 69 | expect(Selenium::WebDriver).to receive(:for) 70 | create_webdriver(device_type) 71 | end 72 | end 73 | context 'browser is not chrome' do 74 | let(:browser) { :firefox } 75 | let(:selenium_mode) { :remote } 76 | it 'raise exception' do 77 | expect(Bucky::Core::Exception::BuckyException).to receive(:handle) 78 | create_webdriver(device_type) 79 | end 80 | end 81 | end 82 | context 'tablet' do 83 | let(:device_type) { 'tablet' } 84 | context 'browser is chrome' do 85 | let(:browser) { :chrome } 86 | it 'call Selenium::WebDriver.for' do 87 | expect(Selenium::WebDriver).to receive(:for) 88 | create_webdriver(device_type) 89 | end 90 | end 91 | context 'browser is not chrome' do 92 | let(:browser) { :firefox } 93 | let(:selenium_mode) { :remote } 94 | it 'raise exception' do 95 | expect(Bucky::Core::Exception::BuckyException).to receive(:handle) 96 | create_webdriver(device_type) 97 | end 98 | end 99 | end 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /lib/bucky/core/test_core/test_class_generator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../../test_equipment/test_case/e2e_test_case' 4 | require_relative '../../test_equipment/test_case/linkstatus_test_case' 5 | 6 | module Bucky 7 | module Core 8 | module TestCore 9 | # For creating class dynamically. 10 | class TestClasses 11 | end 12 | 13 | module TestClassGeneratorHelper 14 | private 15 | 16 | def add_test_procedure(procedures) 17 | procedures.each.with_index(1) do |procedure, step_number| 18 | procedure[:proc] ||= ''.dup 19 | puts " #{step_number}:#{procedure[:proc]}" 20 | method = procedure[:exec].key?(:operate) ? :operate : :verify 21 | send(method, exec: procedure[:exec], step_number:, proc_name: procedure[:proc]) 22 | end 23 | end 24 | 25 | def make_test_method_name(data, test_case, index) 26 | if test_case[:case_name].nil? 27 | return [ 28 | 'test', 29 | data[:suite][:service], 30 | data[:suite][:device], 31 | data[:test_category], 32 | data[:test_suite_name], 33 | index.to_s 34 | ].join('_') 35 | end 36 | "test_#{test_case[:case_name]}" 37 | end 38 | end 39 | 40 | class TestClassGenerator 41 | attr_accessor :test_classes 42 | 43 | def initialize(test_cond) 44 | @test_classes = TestClasses 45 | @test_cond = test_cond 46 | end 47 | 48 | # Genrate test class by test suite and test case data 49 | def generate_test_class(data: [], linkstatus_url_log: {}, w_pipe: {}) 50 | test_cond = @test_cond 51 | # Common proccessing 52 | # e.g.) TestSampleAppPcE2e1, TestSampleAppPcHttpstatus1 53 | test_class_name = make_test_class_name(data) 54 | # Select super class by test category 55 | super_suite_class = eval format('Bucky::TestEquipment::TestCase::%sTestCase', test_category: data[:test_category].capitalize) 56 | # Define test suite class 57 | test_classes.const_set(test_class_name.to_sym, Class.new(super_suite_class) do |_klass| 58 | extend TestClassGeneratorHelper 59 | include TestClassGeneratorHelper 60 | define_method(:suite_data, proc { data[:suite] }) 61 | define_method(:suite_id, proc { data[:test_suite_id] }) 62 | define_method(:simple_test_class_name) do |original_name| 63 | match_obj = /\Atest_(.+)\(.+::(Test.+)\)\z/.match(original_name) 64 | "#{match_obj[1]}(#{match_obj[2]})" 65 | end 66 | define_method(:w_pipe, proc { w_pipe }) 67 | 68 | # Class structure is different for each test category 69 | case data[:test_category] 70 | when 'linkstatus' 71 | data[:suite][:cases].each_with_index do |t_case, i| 72 | method_name = make_test_method_name(data, t_case, i) 73 | description( 74 | t_case[:case_name], 75 | define_method(method_name) do 76 | puts "\n#{simple_test_class_name(name)}" 77 | t_case[:urls].each do |url| 78 | linkstatus_check_args = { url:, device: data[:suite][:device], exclude_urls: data[:suite][:exclude_urls], link_check_max_times: test_cond[:link_check_max_times], url_log: linkstatus_url_log } 79 | linkstatus_check(linkstatus_check_args) 80 | end 81 | end 82 | ) 83 | end 84 | 85 | when 'e2e' 86 | if data[:suite][:setup_each] 87 | def setup 88 | super 89 | puts "[setup]#{simple_test_class_name(name)}" 90 | add_test_procedure(suite_data[:setup_each][:procs]) 91 | end 92 | end 93 | 94 | if data[:suite][:teardown_each] 95 | def teardown 96 | puts "[teardown]#{simple_test_class_name(name)}" 97 | add_test_procedure(suite_data[:teardown_each][:procs]) 98 | super 99 | end 100 | end 101 | 102 | # Generate test case method 103 | data[:suite][:cases].each_with_index do |t_case, i| 104 | # e.g.) test_sample_app_pc_e2e_1_2 105 | method_name = make_test_method_name(data, t_case, i) 106 | method_obj = proc do 107 | puts "\n#{simple_test_class_name(name)}\n #{t_case[:desc]} ...." 108 | add_test_procedure(t_case[:procs]) 109 | end 110 | description(t_case[:case_name], define_method(method_name, method_obj)) 111 | end 112 | end 113 | end) 114 | end 115 | 116 | private 117 | 118 | def make_test_class_name(data) 119 | [ 120 | 'Test', 121 | data[:suite][:service].split(/_|-/).map(&:capitalize).join.to_s, 122 | data[:suite][:device].capitalize.to_s, 123 | data[:test_category].to_s.capitalize.to_s, 124 | (data[:test_class_name]).to_s 125 | ].join 126 | end 127 | end 128 | end 129 | end 130 | end 131 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | generate_cache_for_system_test: 4 | machine: true 5 | steps: 6 | - checkout 7 | - restore_cache: 8 | key: docker-{{ checksum ".circleci/config.yml" }}-{{ checksum "docker-compose.system-test.yml" }}-{{ checksum "Dockerfile.system-test" }}-{{ checksum "bucky-core.gemspec" }}-{{ checksum ".dockerignore" }} 9 | - run: 10 | command: | 11 | if [ ! -f ~/caches/images.tar ]; then 12 | docker-compose -f docker-compose.system-test.yml build && \ 13 | mkdir -p ~/caches && \ 14 | docker save $(docker images | awk 'NR>=2 && ! /^/{print $1}') -o ~/caches/images.tar 15 | fi 16 | - save_cache: 17 | key: docker-{{ checksum ".circleci/config.yml" }}-{{ checksum "docker-compose.system-test.yml" }}-{{ checksum "Dockerfile.system-test" }}-{{ checksum "bucky-core.gemspec" }}-{{ checksum ".dockerignore" }} 18 | paths: ~/caches/images.tar 19 | system_test: 20 | machine: true 21 | parallelism: 2 22 | steps: 23 | - checkout 24 | - restore_cache: 25 | key: docker-{{ checksum ".circleci/config.yml" }}-{{ checksum "docker-compose.system-test.yml" }}-{{ checksum "Dockerfile.system-test" }}-{{ checksum "bucky-core.gemspec" }}-{{ checksum ".dockerignore" }} 26 | - run: 27 | command: docker load -q -i ~/caches/images.tar 28 | - run: 29 | name: docker up 30 | command: docker-compose -f docker-compose.system-test.yml up -d 31 | - run: 32 | name: execute system testing 33 | command: 'circleci tests glob system_testing/testing_code/*.bats | xargs -n 1 -I {} docker exec bucky-core bats "/bucky-core/"{} | circleci tests split' 34 | - run: 35 | name: docker down 36 | command: docker-compose -f docker-compose.system-test.yml down 37 | unit_test: 38 | working_directory: ~/bucky-core 39 | docker: 40 | - image: cimg/ruby:3.2.0 41 | environment: 42 | CC_TEST_REPORTER_ID: fd7bd9d517bdf8953c4d4803ca4ad7539d12d5c760048b8daf80cbc7d54fb262 43 | steps: 44 | - checkout 45 | - type: cache-restore 46 | key: unit-test-{{ checksum "Gemfile.lock" }} 47 | - run: bundle install --path vendor/bundle --quiet 48 | - type: cache-save 49 | key: unit-test-{{ checksum "Gemfile.lock" }} 50 | paths: 51 | - vendor/bundle 52 | # Download test-reporter 53 | - run: 54 | name: Setup Code Climate test-reporter 55 | command: | 56 | curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter 57 | chmod +x ./cc-test-reporter 58 | # Run rspec and show on code climate 59 | - run: | 60 | ./cc-test-reporter before-build 61 | bundle exec rspec 62 | ./cc-test-reporter after-build --coverage-input-type simplecov --exit-code $? 63 | static_code_analysis: 64 | docker: 65 | - image: cimg/ruby:3.2.0 66 | steps: 67 | - checkout 68 | - type: cache-restore 69 | key: syntax-check-{{ checksum "Gemfile.lock" }} 70 | - run: bundle install --path vendor/bundle --quiet 71 | - type: cache-save 72 | key: syntax-check-{{ checksum "Gemfile.lock" }} 73 | paths: 74 | - vendor/bundle 75 | - run: 76 | name: Run RuboCop on changed files 77 | command: | 78 | git diff --name-only origin/master...HEAD -- '*.rb' | xargs -r bundle exec rubocop -f html --out report.html 79 | - store_artifacts: 80 | path: report.html 81 | publish_to_rubygems: 82 | docker: 83 | - image: cimg/ruby:3.2.0 84 | steps: 85 | - add_ssh_keys: 86 | finerprints: 87 | - "6a:f3:d3:b5:a5:da:ce:e0:9f:22:f8:4a:2f:51:67:2b" 88 | - checkout 89 | - run: 90 | name: Setup Rubygems 91 | command: bash .circleci/setup_rubygems.sh 92 | - run: 93 | name: Update version.rb and publish on RubyGems 94 | command: bash .circleci/deploy_to_rubygems.sh 95 | publish_to_docker_hub: 96 | machine: true 97 | environment: 98 | DOCKER_REPO: lifullsetg/bucky-core 99 | steps: 100 | - checkout 101 | - run: 102 | name: Build and tagged image 103 | command: | 104 | docker build -t "$DOCKER_REPO":"${CIRCLE_TAG/v/}" . 105 | docker tag "$DOCKER_REPO":"${CIRCLE_TAG/v/}" "$DOCKER_REPO":latest 106 | - run: 107 | name: Login and push image 108 | command: | 109 | docker login -u "$DOCKER_HUB_USER" -p "$DOCKER_HUB_TOKEN" 110 | docker push "$DOCKER_REPO":"${CIRCLE_TAG/v/}" 111 | docker push "$DOCKER_REPO":latest 112 | docker logout 113 | workflows: 114 | version: 2 115 | test: 116 | jobs: 117 | - static_code_analysis 118 | - unit_test 119 | - generate_cache_for_system_test 120 | - system_test: 121 | requires: 122 | - generate_cache_for_system_test 123 | deploy: 124 | jobs: 125 | - publish_to_rubygems: 126 | filters: 127 | tags: 128 | only: /^v[0-9]{1,}(\.[0-9]{1,}){2}$/ 129 | branches: 130 | ignore: /.*/ 131 | - publish_to_docker_hub: 132 | filters: 133 | tags: 134 | only: /^v[0-9]{1,}(\.[0-9]{1,}){2}$/ 135 | branches: 136 | ignore: /.*/ 137 | requires: 138 | - publish_to_rubygems 139 | -------------------------------------------------------------------------------- /lib/bucky/core/test_core/test_case_loader.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../database/test_data_operator' 4 | require_relative '../../utils/yaml_load' 5 | 6 | class Hash 7 | def deep_symbolize_keys! 8 | deep_transform_keys! do |key| 9 | key unless key.respond_to? :to_sym 10 | key.to_sym 11 | end 12 | end 13 | 14 | def deep_transform_keys!(&block) 15 | keys.each do |key| 16 | value = delete(key) 17 | self[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys!(&block) : value 18 | end 19 | self 20 | end 21 | end 22 | 23 | module Bucky 24 | module Core 25 | module TestCore 26 | class TestCaseLoader 27 | class << self 28 | include Bucky::Utils::YamlLoad 29 | # Load test code files and return test suite data. 30 | # @return [Array] test suite 31 | def load_testcode(test_cond) 32 | return load_re_test_testcode(test_cond[:re_test_cond]) if test_cond.key? :re_test_cond 33 | 34 | testcodes = [] 35 | service = (test_cond[:service] || ['*']).first 36 | device = (test_cond[:device] || ['*']).first 37 | 38 | Dir.glob("#{$bucky_home_dir}/services/#{service}/#{device}/scenarios/#{test_cond[:test_category]}/*.yml").each do |testcode_file| 39 | testcodes << load_testcode_in_file(testcode_file, test_cond) 40 | end 41 | # Delete nil element 42 | testcodes.compact 43 | end 44 | 45 | private 46 | 47 | def load_testcode_in_file(testcode_file, test_cond) 48 | testcode_info = testcode_file.split(%r{/|\.}).reverse 49 | test_suite_name, test_category = testcode_info[1..3] 50 | test_class_name = test_suite_name.split('_').map(&:capitalize).join 51 | test_suite = load_suite_file(testcode_file) 52 | return nil unless target_in_suite_options?(test_suite, test_cond, test_suite_name) 53 | 54 | test_suite = set_suite_option_to_case(test_suite) 55 | refined_suite = refine_case_with_option(test_cond, test_suite) 56 | return nil if refined_suite[:cases].empty? 57 | 58 | { test_class_name: test_class_name, test_suite_name: test_suite_name, test_category: test_category, suite: refined_suite } 59 | end 60 | 61 | def load_re_test_testcode(re_test_cond) 62 | testcodes = [] 63 | re_test_cond.each_value do |cond| 64 | testcode_info = cond[:file_path].split(%r{/|\.}).reverse 65 | test_suite_name, test_category = testcode_info[1..3] 66 | test_class_name = test_suite_name.split('_').map(&:capitalize).join 67 | test_suite = load_suite_file(cond[:file_path]) 68 | test_suite[:cases].delete_if { |c| !cond[:case_names].include? c[:case_name] } 69 | testcodes << { test_class_name: test_class_name, test_suite_name: test_suite_name, test_category: test_category, suite: test_suite } 70 | end 71 | testcodes 72 | end 73 | 74 | def load_suite_file(file) 75 | test_suite = load_yaml(file) 76 | test_suite.deep_symbolize_keys! 77 | test_suite[:setup_each][:procs].each(&:deep_symbolize_keys!) if test_suite[:setup_each] 78 | test_suite[:teardown_each][:procs].each(&:deep_symbolize_keys!) if test_suite[:teardown_each] 79 | test_suite[:cases].each do |c| 80 | c.deep_symbolize_keys! 81 | next unless c.key? :procs 82 | 83 | c[:procs].each do |p| 84 | c[:procs].delete(p) unless when_match?(p) 85 | p.deep_symbolize_keys! 86 | end 87 | end 88 | test_suite 89 | rescue StandardError => e 90 | raise e, "loading #{file}, #{e.message}" 91 | end 92 | 93 | def when_match?(proc) 94 | return true unless proc.key?('when') 95 | raise StandardError, 'Please input correct condition' unless [true, false].include?(proc['when']) 96 | 97 | proc['when'] 98 | end 99 | 100 | # Return true if match suite condition 101 | # Only priority and suite name 102 | def target_in_suite_options?(test_suite, test_cond, test_suite_name) 103 | # priority 104 | return false unless check_option_include_target?(test_cond[:priority], test_suite[:priority]) 105 | # suite_name 106 | return false unless check_option_include_target?(test_cond[:suite_name], test_suite_name) 107 | 108 | true 109 | end 110 | 111 | # Return true, if target includes specified option 112 | def check_option_include_target?(option, target) 113 | # If there is no option, return nil. 114 | if option.nil? 115 | nil 116 | else 117 | return false unless option.include?(target) 118 | end 119 | true 120 | end 121 | 122 | # set suite option to case e.g.) suite's labels are inherited by test cases 123 | # only label 124 | def set_suite_option_to_case(test_suite) 125 | return test_suite unless test_suite.key?(:labels) 126 | 127 | # Pattern of value is different depending on how to write (string/array) 128 | # change to array on all pattern 129 | test_suite[:labels] = [test_suite[:labels]].flatten 130 | test_suite[:cases].each do |c| 131 | c[:labels] = if c.key?(:labels) 132 | [c[:labels]].flatten | test_suite[:labels] 133 | else 134 | test_suite[:labels] 135 | end 136 | end 137 | test_suite 138 | end 139 | 140 | # Filter with option 141 | def refine_case_with_option(test_cond, suite) 142 | # Filtering by label 143 | if test_cond.key? :label 144 | # Delete test case that have no label 145 | # Pattern of value is different depending on how to write (string/array) 146 | # Change to array on all pattern 147 | suite[:cases].delete_if { |c| c[:labels].nil? } 148 | suite[:cases].delete_if { |c| !(test_cond[:label].sort - [c[:labels]].flatten.sort).empty? } 149 | end 150 | # Exclude by label 151 | if test_cond.key? :xlabel 152 | # Delete test case that have specify label 153 | suite[:cases].delete_if { |c| (test_cond[:xlabel].sort - [c[:labels]].flatten.sort).empty? } 154 | end 155 | # If there is no option, do nothing. 156 | return suite unless test_cond.key? :case 157 | 158 | # Delete test case doesn't match 159 | suite[:cases].delete_if { |c| !test_cond[:case].include? c[:case_name] } 160 | suite 161 | end 162 | end 163 | end 164 | end 165 | end 166 | end 167 | -------------------------------------------------------------------------------- /lib/bucky/core/test_core/test_manager.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'json' 4 | require_relative '../test_core/test_case_loader' 5 | require_relative '../../utils/config' 6 | require_relative './test_class_generator' 7 | require_relative '../test_core/exit_handler' 8 | 9 | module Bucky 10 | module Core 11 | module TestCore 12 | module ParallelHelper 13 | parent_pid = Process.pid 14 | 15 | # Terminate parent and child process when getting interrupt signal 16 | Signal.trap('INT') do 17 | Process.kill('TERM', -1 * parent_pid) 18 | end 19 | 20 | private 21 | 22 | def parallel_new_worker_each(data_set, max_processes, &block) 23 | # Max parallel workers number 24 | available_workers = max_processes 25 | 26 | # If child process dead, available workers increase 27 | Signal.trap('CLD') { available_workers += 1 } 28 | 29 | r_pipe, w_pipe = IO.pipe 30 | 31 | data_set.each do |data| 32 | # Wait until worker is available and handle exit code from previous process 33 | unless available_workers.positive? 34 | Process.wait 35 | Bucky::Core::TestCore::ExitHandler.instance.raise unless $CHILD_STATUS.exitstatus.zero? 36 | end 37 | # Workers decrease when start working 38 | available_workers -= 1 39 | fork { block.call(data, w_pipe) } 40 | end 41 | # Handle all exit code in waitall 42 | Process.waitall.each do |child| 43 | Bucky::Core::TestCore::ExitHandler.instance.raise unless child[1].exitstatus.zero? 44 | end 45 | 46 | w_pipe.close 47 | results_set = collect_results_set(r_pipe) 48 | r_pipe.close 49 | 50 | results_set 51 | end 52 | 53 | def parallel_distribute_into_workers(data_set, max_processes, &block) 54 | # Group the data by remainder of index 55 | data_set_grouped = data_set.group_by.with_index { |_elem, index| index % max_processes } 56 | r_pipe, w_pipe = IO.pipe 57 | # Use 'values' method to get only hash's key into an array 58 | data_set_grouped.values.each do |data_for_pre_worker| 59 | # Number of child process is equal to max_processes (or equal to data_set length when data_set length is less than max_processes) 60 | fork do 61 | data_for_pre_worker.each { |data| block.call(data, w_pipe) } 62 | end 63 | end 64 | # Handle all exit code in waitall 65 | Process.waitall.each do |child| 66 | Bucky::Core::TestCore::ExitHandler.instance.raise unless child[1].exitstatus.zero? 67 | end 68 | 69 | w_pipe.close 70 | results_set = collect_results_set(r_pipe) 71 | r_pipe.close 72 | 73 | results_set 74 | end 75 | 76 | def collect_results_set(r_pipe) 77 | results_set = {} 78 | r_pipe.each_line do |line| 79 | r = JSON.parse(line) 80 | results_set[r['test_class_name']] = r 81 | end 82 | 83 | results_set 84 | end 85 | end 86 | 87 | class TestManager 88 | include ParallelHelper 89 | 90 | # Keep test conditions and round number 91 | def initialize(test_cond) 92 | @test_cond = test_cond 93 | @re_test_count = @test_cond[:re_test_count] 94 | @tdo = Bucky::Core::Database::TestDataOperator.new 95 | @start_time = Time.now 96 | $job_id = @tdo.save_job_record_and_get_job_id(@start_time, @test_cond[:command_and_option], @test_cond[:base_fqdn]) 97 | @json_report = { 98 | summary: { 99 | cases_count: 0, 100 | success_count: 0, 101 | failure_count: 0, 102 | job_id: $job_id, 103 | test_category: test_cond[:test_category], 104 | device: test_cond[:device], 105 | labels: test_cond[:label], 106 | exclude_labels: test_cond[:xlabel], 107 | rerun_job_id: test_cond[:job], 108 | round_count: 0 109 | } 110 | } 111 | end 112 | 113 | def run 114 | execute_test 115 | end 116 | 117 | # Rerun by job id 118 | def rerun 119 | rerun_job_id = @test_cond[:job] 120 | $round = @tdo.get_last_round_from_job_id(rerun_job_id) 121 | @test_cond[:re_test_cond] = @tdo.get_ng_test_cases_at_last_execution( 122 | is_error: 1, job_id: rerun_job_id, round: $round 123 | ) 124 | execute_test 125 | end 126 | 127 | private 128 | 129 | # Load test suite from test code. 130 | def load_test_suites 131 | test_suite_data = Bucky::Core::TestCore::TestCaseLoader.load_testcode(@test_cond) 132 | raise StandardError, "\nThere is no test case!\nPlease check test condition." if test_suite_data.empty? 133 | 134 | @tdo.update_test_suites_data(test_suite_data) 135 | @tdo.add_suite_id_to_loaded_suite_data(test_suite_data) 136 | end 137 | 138 | # Generate and execute test 139 | def do_test_suites(test_suite_data) 140 | # For checking on linkstatus 141 | e2e_parallel_num = Bucky::Utils::Config.instance[:e2e_parallel_num] 142 | linkstatus_parallel_num = Bucky::Utils::Config.instance[:linkstatus_parallel_num] 143 | tcg = Bucky::Core::TestCore::TestClassGenerator.new(@test_cond) 144 | case @test_cond[:test_category] 145 | when 'e2e' then results_set = parallel_new_worker_each(test_suite_data, e2e_parallel_num) { |data, w_pipe| tcg.generate_test_class(data: data, w_pipe: w_pipe) } 146 | when 'linkstatus' then 147 | linkstatus_url_log = {} 148 | results_set = parallel_distribute_into_workers(test_suite_data, linkstatus_parallel_num) { |data, w_pipe| tcg.generate_test_class(data: data, linkstatus_url_log: linkstatus_url_log, w_pipe: w_pipe) } 149 | end 150 | 151 | results_set 152 | end 153 | 154 | def execute_test 155 | all_round_results = [] 156 | @re_test_count.times do |i| 157 | Bucky::Core::TestCore::ExitHandler.instance.reset 158 | $round = i + 1 159 | @json_report[:summary][:round_count] = $round 160 | test_suite_data = load_test_suites 161 | all_round_results.append(do_test_suites(test_suite_data)) 162 | @test_cond[:re_test_cond] = @tdo.get_ng_test_cases_at_last_execution( 163 | is_error: 1, job_id: $job_id, round: $round 164 | ) 165 | break if @test_cond[:re_test_cond].empty? 166 | end 167 | 168 | return unless @test_cond[:out] 169 | 170 | @json_report[:summary][:cases_count] = all_round_results[0].sum { |_case, res| res['cases_count'] } 171 | @json_report[:summary][:failure_count] = all_round_results[-1].sum { |_case, res| res['failure_count'] } 172 | @json_report[:summary][:success_count] = @json_report[:summary][:cases_count] - @json_report[:summary][:failure_count] 173 | 174 | File.open(@test_cond[:out], 'w') do |f| 175 | f.puts(@json_report.to_json) 176 | puts "\nSave report : #{@test_cond[:out]}\n" 177 | end 178 | end 179 | end 180 | end 181 | end 182 | end 183 | -------------------------------------------------------------------------------- /exe/bucky: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require 'rubygems' 5 | require 'optparse' 6 | require 'color_echo' 7 | require 'fileutils' 8 | require_relative '../lib/bucky/version' 9 | require_relative '../lib/bucky/core/test_core/exit_handler' 10 | 11 | # Color Settings 12 | CE.fg(:cyan) 13 | 14 | NEW_COMMAND = %w[new].freeze 15 | MAKE_SERVICE_COMMAND = %w[make service].freeze 16 | MAKE_PAGE_COMMAND = %w[make page].freeze 17 | RUN_COMMAND = %w[run].freeze 18 | RERUN_COMMAND = %w[rerun].freeze 19 | LINT_COMMAND = %w[lint].freeze 20 | 21 | # =================================== 22 | # Parse options ===================== 23 | # =================================== 24 | opts = OptionParser.new 25 | opts.banner = 'Bucky: A test framework that supports Life Cycle of System Testing.' 26 | opts.define_head 'Usage: bucky [task]' 27 | opts.separator '' 28 | opts.separator 'Examples:' 29 | opts.separator " bucky #{RUN_COMMAND.join(' ')} --test_category e2e --suite_name test_suite --case_name test_case_1" 30 | opts.separator " bucky #{RUN_COMMAND.join(' ')} -t e2e -s 1 -c 1" 31 | opts.separator " bucky #{RUN_COMMAND.join(' ')} --test_category linkstatus --re_test_count 3" 32 | opts.separator " bucky #{RUN_COMMAND.join(' ')} --test_category e2e --suite_name test_suite --case_name test_case_1 --device sp --priority high --re_test_count 3" 33 | opts.separator '' 34 | opts.separator 'For more information see https://github.com/lifull-dev/bucky-core' 35 | opts.separator '' 36 | opts.separator 'Options:' 37 | 38 | opts.on_tail('-h', '-?', '--help', 'Show this message') do 39 | puts opts 40 | exit 41 | end 42 | 43 | opts.on_tail('-v', '--version', 'Show version') do 44 | puts "BuckyCore: #{Bucky::Version::VERSION}" 45 | exit 46 | end 47 | 48 | test_cond = {} 49 | # Save command and option 50 | test_cond[:command_and_option] = 'bucky ' + ARGV.join(' ') 51 | opts.on('-t', '--test_category TEST_CATEGORY', 'e.g. --test_category e2e, -t e2e') do |v| 52 | test_cond[:test_category] = v 53 | end 54 | opts.on('-s', '--suite_name SUITE_NAME') do |v| 55 | test_cond[:suite_name] = v 56 | end 57 | # opts.on('-c', '--case_id CASE_ID') do |v| 58 | # test_cond[:case_id] = v 59 | # end 60 | opts.on('-S', '--service SERVICE') do |v| 61 | test_cond[:service] = v 62 | end 63 | opts.on('-D', '--device DEVICE') do |v| 64 | test_cond[:device] = v 65 | end 66 | opts.on('-p', '--priority PRIORITY') do |v| 67 | test_cond[:priority] = v 68 | end 69 | opts.on('-d', 'Not Insert TestReport') do |_| 70 | $debug = true 71 | end 72 | 73 | # TODO: Add error handling, if the argument of '-r' is not greater than 0 74 | opts.on('-r', '--re_test_count RE_TEST_COUNT') do |v| 75 | test_cond[:re_test_count] = v 76 | end 77 | opts.on('-c', '--case CASE_NAME') do |v| 78 | test_cond[:case] = v 79 | end 80 | opts.on('-j', '--job_id JOB_ID') do |v| 81 | test_cond[:job] = v 82 | end 83 | opts.on('-l', '--label LABEL_NAME') do |v| 84 | test_cond[:label] = v 85 | end 86 | opts.on('--xl', '--exclude_label EXCLUDE_LABEL_NAME') do |v| 87 | test_cond[:xlabel] = v 88 | end 89 | opts.on('-m', '--link_check_max_times') do |v| 90 | test_cond[:link_check_max_times] = v.to_i 91 | end 92 | opts.on('-o', '--out JSON_OUTPUT_FILE_PATH') do |v| 93 | test_cond[:out] = v 94 | end 95 | lint_cond = {} 96 | opts.on('-C', '--category CATEGORY_NAME') do |v| 97 | lint_cond[:lint_category] = v 98 | end 99 | 100 | opts.parse! 101 | 102 | # =================================== 103 | # Select by command ================= 104 | # =================================== 105 | 106 | # Output error message and exit 107 | # @param [String] msg error message 108 | def error_and_exit(msg = nil) 109 | CE.fg(:yellow) 110 | puts 'Invalid command error.' 111 | CE.fg(:red) 112 | puts " [Error] #{msg}" if msg 113 | CE.fg(:yellow) 114 | puts " your command : bucky #{ARGV.join(' ')}" 115 | puts ' Please input some commands.' 116 | puts " e.g. bucky #{NEW_COMMAND.join(' ')}" 117 | puts " bucky #{RUN_COMMAND.join(' ')}" 118 | puts " bucky #{MAKE_SERVICE_COMMAND.join(' ')}" 119 | puts " bucky #{MAKE_PAGE_COMMAND.join(' ')}" 120 | puts " bucky #{RERUN_COMMAND.join(' ')}" 121 | puts " bucky #{LINT_COMMAND.join(' ')}" 122 | exit 123 | end 124 | 125 | def bucky_home? 126 | File.exist?('.bucky_home') 127 | end 128 | 129 | def setup_test_cond(test_cond) 130 | # Default conditions setting conditions setting 131 | test_cond[:test_category] ||= 'e2e' 132 | test_cond[:re_test_count] = test_cond[:re_test_count] ? test_cond[:re_test_count].to_i : 1 133 | test_cond[:link_check_max_times] ||= 3 134 | # Change to array e.g.--suite_id 1,2,3 -> :suite_id=>[1,2,3] 135 | %i[suite_name case label xlabel device].each do |k| 136 | test_cond[k] = test_cond[k].split(',') unless test_cond[k].nil? 137 | end 138 | test_cond[:base_fqdn] = ENV['BASE_FQDN'] ||= '' 139 | test_cond 140 | end 141 | 142 | current_dir = Dir.pwd 143 | gem_script_dir = __dir__ 144 | 145 | if ARGV[0].end_with? 'run' 146 | error_and_exit('Not bucky project dirctory here.') unless bucky_home? 147 | $bucky_home_dir = Dir.pwd 148 | require_relative '../lib/bucky/core/test_core/test_manager' 149 | Bucky::Core::TestCore::TestManager.new(setup_test_cond(test_cond)).send(ARGV[0]) 150 | Bucky::Core::TestCore::ExitHandler.instance.bucky_exit 151 | elsif ARGV == LINT_COMMAND 152 | $bucky_home_dir = Dir.pwd 153 | # Default conditions setting 154 | lint_cond[:lint_category] ||= 'config' 155 | require_relative '../lib/bucky/tools/lint' 156 | Bucky::Tools::Lint.check(lint_cond[:lint_category]) 157 | elsif ARGV[0..0] == NEW_COMMAND 158 | error_and_exit('No test app name.') if ARGV.length < 2 159 | 160 | # Copy template 161 | FileUtils.cp_r("#{gem_script_dir}/../template/new/", current_dir, verbose: true) 162 | # Rename dir 163 | FileUtils.mv("#{current_dir}/new", "#{current_dir}/#{ARGV[1]}", verbose: true) 164 | 165 | elsif ARGV[0..1] == MAKE_SERVICE_COMMAND 166 | error_and_exit('Not bucky project dirctory here.') unless bucky_home? 167 | error_and_exit('No service name.') if ARGV.length < 3 168 | 169 | service_name = ARGV[2] 170 | 171 | # Check if there is any directory in same service name. 172 | error_and_exit("Already exist #{service_name} directory.") if File.exist?("#{current_dir}/services/#{service_name}") 173 | FileUtils.mkdir("#{current_dir}/services/#{service_name}", verbose: true) 174 | 175 | elsif ARGV[0..1] == MAKE_PAGE_COMMAND 176 | support_device = %w[pc sp tablet] 177 | error_and_exit('Not bucky project dirctory here.') unless bucky_home? 178 | error_and_exit('No page name.') if ARGV.length < 3 179 | error_and_exit('No service name.') unless test_cond[:service] 180 | 181 | # Default conditions setting 182 | test_cond[:device] ||= 'pc' 183 | 184 | error_and_exit("#{test_cond[:device]} device is not supported. (only pc/sp/tablet.)") unless support_device.include?(test_cond[:device]) 185 | 186 | page_name = ARGV[2] 187 | pageobject_dir = "#{current_dir}/services/#{test_cond[:service]}/#{test_cond[:device]}/pageobject" 188 | pageobject_rb = "#{pageobject_dir}/#{page_name}.rb" 189 | parts_dir = "#{current_dir}/services/#{test_cond[:service]}/#{test_cond[:device]}/parts" 190 | parts_file = "#{parts_dir}/#{page_name}.yml" 191 | 192 | error_and_exit("Already exist pageobject file: #{pageobject_rb}.") if File.exist?(pageobject_rb) 193 | error_and_exit("Already exist parts file: #{parts_file}.") if File.exist?(parts_file) 194 | FileUtils.mkdir_p("#{current_dir}/services/#{test_cond[:service]}/#{test_cond[:device]}") 195 | FileUtils.cp_r( 196 | "#{gem_script_dir}/../template/make_page/#{test_cond[:device]}", 197 | "#{current_dir}/services/#{test_cond[:service]}", 198 | verbose: true 199 | ) 200 | 201 | FileUtils.mv("#{pageobject_dir}/sample_page.rb", pageobject_rb, verbose: true) 202 | FileUtils.mv("#{parts_dir}/sample_page.yml", parts_file, verbose: true) 203 | 204 | # Change service name and page name on pageobject 205 | # | {SampleService} -> ServiceName 206 | # | {SamplePage} -> PageName 207 | script = File.open(pageobject_rb).read 208 | split_and_capitalize = proc { |str| str.split(/_|-/).map(&:capitalize).join } 209 | script.sub!('{SampleService}', split_and_capitalize.call(test_cond[:service])) 210 | script.sub!('{SamplePage}', split_and_capitalize.call(page_name)) 211 | File.open(pageobject_rb, 'w+') { |f| f.write script } 212 | else 213 | error_and_exit 214 | end 215 | --------------------------------------------------------------------------------