diff options
Diffstat (limited to 'qa')
29 files changed, 388 insertions, 48 deletions
diff --git a/qa/README.md b/qa/README.md index a4b4398645e..be4cf89ebbc 100644 --- a/qa/README.md +++ b/qa/README.md @@ -55,7 +55,7 @@ Since the arguments would be passed to `rspec`, you could use all `rspec` options there. For example, passing `--backtrace` and also line number: ``` -bin/qa Test::Instance http://localhost qa/specs/features/login/standard_spec.rb:3 --backtrace +bin/qa Test::Instance http://localhost qa/specs/features/project/create_spec.rb:3 --backtrace ``` ### Overriding the authenticated user @@ -41,13 +41,17 @@ module QA autoload :Project, 'qa/factory/resource/project' autoload :MergeRequest, 'qa/factory/resource/merge_request' autoload :ProjectImportedFromGithub, 'qa/factory/resource/project_imported_from_github' + autoload :MergeRequestFromFork, 'qa/factory/resource/merge_request_from_fork' autoload :DeployKey, 'qa/factory/resource/deploy_key' autoload :Branch, 'qa/factory/resource/branch' autoload :SecretVariable, 'qa/factory/resource/secret_variable' autoload :Runner, 'qa/factory/resource/runner' autoload :PersonalAccessToken, 'qa/factory/resource/personal_access_token' autoload :KubernetesCluster, 'qa/factory/resource/kubernetes_cluster' + autoload :User, 'qa/factory/resource/user' + autoload :ProjectMilestone, 'qa/factory/resource/project_milestone' autoload :Wiki, 'qa/factory/resource/wiki' + autoload :Fork, 'qa/factory/resource/fork' end module Repository @@ -106,6 +110,7 @@ module QA module Main autoload :Login, 'qa/page/main/login' autoload :OAuth, 'qa/page/main/oauth' + autoload :SignUp, 'qa/page/main/sign_up' end module Settings @@ -166,6 +171,15 @@ module QA autoload :Index, 'qa/page/project/issue/index' end + module Fork + autoload :New, 'qa/page/project/fork/new' + end + + module Milestone + autoload :New, 'qa/page/project/milestone/new' + autoload :Index, 'qa/page/project/milestone/index' + end + module Operations module Kubernetes autoload :Index, 'qa/page/project/operations/kubernetes/index' @@ -194,6 +208,10 @@ module QA autoload :Sidebar, 'qa/page/issuable/sidebar' end + module Layout + autoload :Banner, 'qa/page/layout/banner' + end + module MergeRequest autoload :New, 'qa/page/merge_request/new' autoload :Show, 'qa/page/merge_request/show' diff --git a/qa/qa/factory/repository/project_push.rb b/qa/qa/factory/repository/project_push.rb index 48674c08a8d..4f78098d348 100644 --- a/qa/qa/factory/repository/project_push.rb +++ b/qa/qa/factory/repository/project_push.rb @@ -11,6 +11,8 @@ module QA factory.output end + product(:project) { |factory| factory.project } + def initialize @file_name = 'file.txt' @file_content = '# This is test project' diff --git a/qa/qa/factory/repository/push.rb b/qa/qa/factory/repository/push.rb index 4f97e65b091..5b7ebf6c41f 100644 --- a/qa/qa/factory/repository/push.rb +++ b/qa/qa/factory/repository/push.rb @@ -5,7 +5,8 @@ module QA module Repository class Push < Factory::Base attr_accessor :file_name, :file_content, :commit_message, - :branch_name, :new_branch, :output, :repository_uri + :branch_name, :new_branch, :output, :repository_uri, + :user attr_writer :remote_branch @@ -31,9 +32,20 @@ module QA def fabricate! Git::Repository.perform do |repository| repository.uri = repository_uri + repository.use_default_credentials + username = 'GitLab QA' + email = 'root@gitlab.com' + + if user + repository.username = user.username + repository.password = user.password + username = user.name + email = user.email + end + repository.clone - repository.configure_identity('GitLab QA', 'root@gitlab.com') + repository.configure_identity(username, email) if new_branch repository.checkout_new_branch(branch_name) diff --git a/qa/qa/factory/resource/fork.rb b/qa/qa/factory/resource/fork.rb new file mode 100644 index 00000000000..1d0c76a3d30 --- /dev/null +++ b/qa/qa/factory/resource/fork.rb @@ -0,0 +1,24 @@ +module QA + module Factory + module Resource + class Fork < Factory::Base + dependency Factory::Repository::ProjectPush, as: :push + + dependency Factory::Resource::User, as: :user + + product(:user) { |factory| factory.user } + + def fabricate! + push.project.visit! + Page::Project::Show.act { fork_project } + + Page::Project::Fork::New.perform do |fork_new| + fork_new.choose_namespace(user.name) + end + + Page::Layout::Banner.act { has_notice?('The project was successfully forked.') } + end + end + end + end +end diff --git a/qa/qa/factory/resource/merge_request.rb b/qa/qa/factory/resource/merge_request.rb index 24d3597d993..ddb62bd0a68 100644 --- a/qa/qa/factory/resource/merge_request.rb +++ b/qa/qa/factory/resource/merge_request.rb @@ -7,7 +7,10 @@ module QA attr_accessor :title, :description, :source_branch, - :target_branch + :target_branch, + :assignee, + :milestone, + :labels product :project do |factory| factory.project @@ -41,16 +44,18 @@ module QA @description = 'This is a test merge request' @source_branch = "qa-test-feature-#{SecureRandom.hex(8)}" @target_branch = "master" + @assignee = nil + @milestone = nil + @labels = [] end def fabricate! project.visit! - Page::Project::Show.act { new_merge_request } - Page::MergeRequest::New.perform do |page| page.fill_title(@title) page.fill_description(@description) + page.choose_milestone(@milestone) if @milestone page.create_merge_request end end diff --git a/qa/qa/factory/resource/merge_request_from_fork.rb b/qa/qa/factory/resource/merge_request_from_fork.rb new file mode 100644 index 00000000000..6caaf65f673 --- /dev/null +++ b/qa/qa/factory/resource/merge_request_from_fork.rb @@ -0,0 +1,24 @@ +module QA + module Factory + module Resource + class MergeRequestFromFork < MergeRequest + attr_accessor :fork_branch + + dependency Factory::Resource::Fork, as: :fork + + dependency Factory::Repository::ProjectPush, as: :push do |push, factory| + push.project = factory.fork + push.branch_name = factory.fork_branch + push.file_name = 'file2.txt' + push.user = factory.fork.user + end + + def fabricate! + fork.visit! + Page::Project::Show.act { new_merge_request } + Page::MergeRequest::New.act { create_merge_request } + end + end + end + end +end diff --git a/qa/qa/factory/resource/project.rb b/qa/qa/factory/resource/project.rb index 7bc64c6ae5d..7fff22b5468 100644 --- a/qa/qa/factory/resource/project.rb +++ b/qa/qa/factory/resource/project.rb @@ -37,6 +37,7 @@ module QA page.choose_test_namespace page.choose_name(@name) page.add_description(@description) + page.set_visibility('Public') page.create_new_project end end diff --git a/qa/qa/factory/resource/project_milestone.rb b/qa/qa/factory/resource/project_milestone.rb new file mode 100644 index 00000000000..47a5e74204f --- /dev/null +++ b/qa/qa/factory/resource/project_milestone.rb @@ -0,0 +1,36 @@ +module QA + module Factory + module Resource + class ProjectMilestone < Factory::Base + attr_accessor :description + attr_reader :title + + dependency Factory::Resource::Project, as: :project + + product(:title) { |factory| factory.title } + + def title=(title) + @title = "#{title}-#{SecureRandom.hex(4)}" + @description = 'A milestone' + end + + def fabricate! + project.visit! + + Page::Menu::Side.act do + click_issues + click_milestones + end + + Page::Project::Milestone::Index.act { click_new_milestone } + + Page::Project::Milestone::New.perform do |milestone_new| + milestone_new.set_title(@title) + milestone_new.set_description(@description) + milestone_new.create_new_milestone + end + end + end + end + end +end diff --git a/qa/qa/factory/resource/user.rb b/qa/qa/factory/resource/user.rb new file mode 100644 index 00000000000..e08df9e0cd0 --- /dev/null +++ b/qa/qa/factory/resource/user.rb @@ -0,0 +1,34 @@ +require 'securerandom' + +module QA + module Factory + module Resource + class User < Factory::Base + attr_accessor :name, :username, :email, :password + + def initialize + @name = "name-#{SecureRandom.hex(8)}" + @username = "username-#{SecureRandom.hex(8)}" + @email = "mail#{SecureRandom.hex(8)}@mail.com" + @password = 'password' + end + + product(:name) { |factory| factory.name } + + product(:username) { |factory| factory.username } + + product(:email) { |factory| factory.email } + + product(:password) { |factory| factory.password } + + def fabricate! + Page::Menu::Main.act { sign_out } + Page::Main::Login.act { switch_to_register_tab } + Page::Main::SignUp.perform do |page| + page.sign_up!(name: name, username: username, email: email, password: password) + end + end + end + end + end +end diff --git a/qa/qa/page/issuable/sidebar.rb b/qa/qa/page/issuable/sidebar.rb index dec2ce1eab3..f207264e24f 100644 --- a/qa/qa/page/issuable/sidebar.rb +++ b/qa/qa/page/issuable/sidebar.rb @@ -4,6 +4,7 @@ module QA class Sidebar < Page::Base view 'app/views/shared/issuable/_sidebar.html.haml' do element :labels_block, ".issuable-show-labels" + element :milestones_block, '.block.milestone' end def has_label?(label) @@ -11,6 +12,12 @@ module QA !!find('span', text: label) end end + + def has_milestone?(milestone) + page.within('.block.milestone') do + !!find("[href*='/milestones/']", text: milestone) + end + end end end end diff --git a/qa/qa/page/layout/banner.rb b/qa/qa/page/layout/banner.rb new file mode 100644 index 00000000000..e7654bdafc9 --- /dev/null +++ b/qa/qa/page/layout/banner.rb @@ -0,0 +1,17 @@ +module QA + module Page + module Layout + class Banner < Page::Base + view 'app/views/layouts/header/_read_only_banner.html.haml' do + element :flash_notice, ".flash-notice" + end + + def has_notice?(message) + page.within('.flash-notice') do + !!find('span', text: message) + end + end + end + end + end +end diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb index 26c99efc53d..6cdfbd1c125 100644 --- a/qa/qa/page/main/login.rb +++ b/qa/qa/page/main/login.rb @@ -25,19 +25,24 @@ module QA element :standard_tab, "link_to 'Standard'" end + view 'app/views/devise/shared/_tabs_normal.html.haml' do + element :sign_in_tab, /nav-link.*login-pane.*Sign in/ + element :register_tab, /nav-link.*register-pane.*Register/ + end + def initialize # The login page is usually the entry point for all the scenarios so # we need to wait for the instance to start. That said, in some cases # we are already logged-in so we check both cases here. wait(max: 500) do page.has_css?('.login-page') || - Page::Menu::Main.act { has_personal_area? } + Page::Menu::Main.act { has_personal_area?(wait: 0) } end end def sign_in_using_credentials # Don't try to log-in if we're already logged-in - return if Page::Menu::Main.act { has_personal_area? } + return if Page::Menu::Main.act { has_personal_area?(wait: 0) } using_wait_time 0 do set_initial_password_if_present @@ -48,12 +53,22 @@ module QA sign_in_using_gitlab_credentials end end + + Page::Menu::Main.act { has_personal_area? } end def self.path '/users/sign_in' end + def switch_to_sign_in_tab + click_on 'Sign in' + end + + def switch_to_register_tab + click_on 'Register' + end + private def sign_in_using_ldap_credentials diff --git a/qa/qa/page/main/sign_up.rb b/qa/qa/page/main/sign_up.rb new file mode 100644 index 00000000000..9a834e94b81 --- /dev/null +++ b/qa/qa/page/main/sign_up.rb @@ -0,0 +1,27 @@ +module QA + module Page + module Main + class SignUp < Page::Base + view 'app/views/devise/shared/_signup_box.html.haml' do + element :name, 'text_field :name' + element :username, 'text_field :username' + element :email_field, 'email_field :email' + element :email_confirmation, 'email_field :email_confirmation' + element :password, 'password_field :password' + element :register_button, 'submit "Register"' + end + + def sign_up!(name:, username:, email:, password:) + fill_in :new_user_name, with: name + fill_in :new_user_username, with: username + fill_in :new_user_email, with: email + fill_in :new_user_email_confirmation, with: email + fill_in :new_user_password, with: password + click_button 'Register' + + Page::Menu::Main.act { has_personal_area? } + end + end + end + end +end diff --git a/qa/qa/page/menu/main.rb b/qa/qa/page/menu/main.rb index aef5c9f9c82..36e7285f7b7 100644 --- a/qa/qa/page/menu/main.rb +++ b/qa/qa/page/menu/main.rb @@ -60,9 +60,9 @@ module QA end end - def has_personal_area? + def has_personal_area?(wait: Capybara.default_max_wait_time) # No need to wait, either we're logged-in, or not. - using_wait_time(0) { page.has_selector?('.qa-user-avatar') } + using_wait_time(wait) { page.has_selector?('.qa-user-avatar') } end private diff --git a/qa/qa/page/menu/side.rb b/qa/qa/page/menu/side.rb index 333d871c51a..c14a835c2c9 100644 --- a/qa/qa/page/menu/side.rb +++ b/qa/qa/page/menu/side.rb @@ -16,6 +16,7 @@ module QA element :operations_section, "class: 'shortcuts-operations'" element :activity_link, "title: 'Activity'" element :wiki_link_text, "Wiki" + element :milestones_link end view 'app/assets/javascripts/fly_out_nav.js' do @@ -70,6 +71,12 @@ module QA end end + def click_milestones + within_sidebar do + click_element :milestones_link + end + end + def click_wiki within_sidebar do click_link('Wiki') diff --git a/qa/qa/page/merge_request/new.rb b/qa/qa/page/merge_request/new.rb index ec94ff4ac98..83cc4bbbace 100644 --- a/qa/qa/page/merge_request/new.rb +++ b/qa/qa/page/merge_request/new.rb @@ -10,10 +10,18 @@ module QA element :issuable_form_title end + view 'app/views/shared/issuable/form/_metadata.html.haml' do + element :issuable_milestone_dropdown + end + view 'app/views/shared/form_elements/_description.html.haml' do element :issuable_form_description end + view 'app/views/shared/issuable/_milestone_dropdown.html.haml' do + element :issuable_dropdown_menu_milestone + end + def create_merge_request click_element :issuable_create_button end @@ -25,6 +33,13 @@ module QA def fill_description(description) fill_element :issuable_form_description, description end + + def choose_milestone(milestone) + click_element :issuable_milestone_dropdown + within_element(:issuable_dropdown_menu_milestone) do + click_on milestone.title + end + end end end end diff --git a/qa/qa/page/project/fork/new.rb b/qa/qa/page/project/fork/new.rb new file mode 100644 index 00000000000..ed92df956bf --- /dev/null +++ b/qa/qa/page/project/fork/new.rb @@ -0,0 +1,17 @@ +module QA + module Page + module Project + module Fork + class New < Page::Base + view 'app/views/projects/forks/_fork_button.html.haml' do + element :namespace, 'link_to project_forks_path' + end + + def choose_namespace(namespace = Runtime::Namespace.path) + click_on namespace + end + end + end + end + end +end diff --git a/qa/qa/page/project/milestone/index.rb b/qa/qa/page/project/milestone/index.rb new file mode 100644 index 00000000000..a1519c9ef1c --- /dev/null +++ b/qa/qa/page/project/milestone/index.rb @@ -0,0 +1,17 @@ +module QA + module Page + module Project + module Milestone + class Index < Page::Base + view 'app/views/projects/milestones/index.html.haml' do + element :new_project_milestone + end + + def click_new_milestone + click_element :new_project_milestone + end + end + end + end + end +end diff --git a/qa/qa/page/project/milestone/new.rb b/qa/qa/page/project/milestone/new.rb new file mode 100644 index 00000000000..992ef89004b --- /dev/null +++ b/qa/qa/page/project/milestone/new.rb @@ -0,0 +1,27 @@ +module QA + module Page + module Project + module Milestone + class New < Page::Base + view 'app/views/projects/milestones/_form.html.haml' do + element :milestone_create_button + element :milestone_title + element :milestone_description + end + + def set_title(title) + fill_element :milestone_title, title + end + + def set_description(description) + fill_element :milestone_description, description + end + + def create_new_milestone + click_element :milestone_create_button + end + end + end + end + end +end diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb index 7976e96d43b..9e812fa7c74 100644 --- a/qa/qa/page/project/new.rb +++ b/qa/qa/page/project/new.rb @@ -14,6 +14,7 @@ module QA element :project_path, 'text_field :path' element :project_description, 'text_area :description' element :project_create_button, "submit 'Create project'" + element :visibility_radios, 'visibility_level:' end view 'app/views/projects/_import_project_pane.html.haml' do @@ -42,6 +43,10 @@ module QA click_on 'Create project' end + def set_visibility(visibility) + choose visibility + end + def go_to_github_import click_link 'GitHub' end diff --git a/qa/qa/page/project/settings/protected_branches.rb b/qa/qa/page/project/settings/protected_branches.rb index 0bd031e96b5..e572ae12132 100644 --- a/qa/qa/page/project/settings/protected_branches.rb +++ b/qa/qa/page/project/settings/protected_branches.rb @@ -44,6 +44,9 @@ module QA click_allow(:push, 'Developers + Maintainers') end + # @deprecated + alias_method :allow_devs_and_masters_to_push, :allow_devs_and_maintainers_to_push + def allow_no_one_to_merge click_allow(:merge, 'No one') end @@ -52,6 +55,9 @@ module QA click_allow(:merge, 'Developers + Maintainers') end + # @deprecated + alias_method :allow_devs_and_masters_to_merge, :allow_devs_and_maintainers_to_merge + def protect_branch click_on 'Protect' end diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb index 1dcdb59490a..88861d5772d 100644 --- a/qa/qa/page/project/show.rb +++ b/qa/qa/page/project/show.rb @@ -22,6 +22,11 @@ module QA element :branches_dropdown end + view 'app/views/projects/buttons/_fork.html.haml' do + element :fork_label, "%span= s_('GoToYourFork|Fork')" + element :fork_link, "link_to new_project_fork_path(@project)" + end + view 'app/views/projects/_files.html.haml' do element :tree_holder, '.tree-holder' end @@ -61,6 +66,10 @@ module QA click_link 'New issue' end + + def fork_project + click_on 'Fork' + end end end end diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb index cee381f3379..877864fb40c 100644 --- a/qa/qa/runtime/browser.rb +++ b/qa/qa/runtime/browser.rb @@ -85,6 +85,10 @@ module QA driver.browser.save_screenshot(path) end + Capybara::Screenshot.register_filename_prefix_formatter(:rspec) do |example| + File.join(QA::Runtime::Namespace.name, example.file_path.sub('./qa/specs/features/', '')) + end + Capybara.configure do |config| config.default_driver = :chrome config.javascript_driver = :chrome diff --git a/qa/qa/runtime/namespace.rb b/qa/qa/runtime/namespace.rb index ccfa8b44db3..28f17d1160b 100644 --- a/qa/qa/runtime/namespace.rb +++ b/qa/qa/runtime/namespace.rb @@ -8,7 +8,7 @@ module QA end def name - 'qa-test-' + time.strftime('%d-%m-%Y-%H-%M-%S') + "qa-test-#{time.strftime('%Y-%m-%d-%Y-%H-%M-%S')}" end def path diff --git a/qa/qa/specs/features/login/standard_spec.rb b/qa/qa/specs/features/login/standard_spec.rb deleted file mode 100644 index 254f47cf217..00000000000 --- a/qa/qa/specs/features/login/standard_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -module QA - describe 'standard user login', :core do - it 'user logs in using credentials' do - Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.act { sign_in_using_credentials } - - # TODO, since `Signed in successfully` message was removed - # this is the only way to tell if user is signed in correctly. - # - Page::Menu::Main.perform do |menu| - expect(menu).to have_personal_area - end - end - end -end diff --git a/qa/qa/specs/features/merge_request/create_spec.rb b/qa/qa/specs/features/merge_request/create_spec.rb index 18e8c1f35af..36d7efb02e1 100644 --- a/qa/qa/specs/features/merge_request/create_spec.rb +++ b/qa/qa/specs/features/merge_request/create_spec.rb @@ -4,14 +4,29 @@ module QA Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } + current_project = Factory::Resource::Project.fabricate! do |project| + project.name = 'project-with-merge-request-and-milestone' + end + + current_milestone = Factory::Resource::ProjectMilestone.fabricate! do |milestone| + milestone.title = 'unique-milestone' + milestone.project = current_project + end + Factory::Resource::MergeRequest.fabricate! do |merge_request| - merge_request.title = 'This is a merge request' - merge_request.description = 'Great feature' + merge_request.title = 'This is a merge request with a milestone' + merge_request.description = 'Great feature with milestone' + merge_request.project = current_project + merge_request.milestone = current_milestone end - expect(page).to have_content('This is a merge request') - expect(page).to have_content('Great feature') + expect(page).to have_content('This is a merge request with a milestone') + expect(page).to have_content('Great feature with milestone') expect(page).to have_content(/Opened [\w\s]+ ago/) + + Page::Issuable::Sidebar.perform do |sidebar| + expect(sidebar).to have_milestone(current_milestone.title) + end end end end diff --git a/qa/qa/specs/features/project/fork_project_spec.rb b/qa/qa/specs/features/project/fork_project_spec.rb new file mode 100644 index 00000000000..8ad0120305a --- /dev/null +++ b/qa/qa/specs/features/project/fork_project_spec.rb @@ -0,0 +1,23 @@ +module QA + describe 'Project fork', :core do + it 'can submit merge requests to upstream master' do + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.act { sign_in_using_credentials } + + merge_request = Factory::Resource::MergeRequestFromFork.fabricate! do |merge_request| + merge_request.fork_branch = 'feature-branch' + end + + Page::Menu::Main.act { sign_out } + Page::Main::Login.act do + switch_to_sign_in_tab + sign_in_using_credentials + end + + merge_request.visit! + Page::MergeRequest::Show.act { merge! } + + expect(page).to have_content('The changes were merged') + end + end +end diff --git a/qa/qa/specs/features/repository/protected_branches_spec.rb b/qa/qa/specs/features/repository/protected_branches_spec.rb index 29ea2e69ec7..4e593a69aae 100644 --- a/qa/qa/specs/features/repository/protected_branches_spec.rb +++ b/qa/qa/specs/features/repository/protected_branches_spec.rb @@ -13,15 +13,11 @@ module QA Page::Main::Login.act { sign_in_using_credentials } end - after do |example| + after do # We need to clear localStorage because we're using it for the dropdown, # and capybara doesn't do this for us. # https://github.com/teamcapybara/capybara/issues/1702 Capybara.execute_script 'localStorage.clear()' - - # In order to help diagnose a false failure - # https://gitlab.com/gitlab-org/gitlab-ce/issues/48241 - log_push_output if example.exception end context 'when developers and maintainers are allowed to push to a protected branch' do @@ -31,9 +27,9 @@ module QA expect(protected_branch.name).to have_content(branch_name) expect(protected_branch.push_allowance).to have_content('Developers + Maintainers') - @push = push_new_file(branch_name) + push = push_new_file(branch_name) - expect(@push.output).to match(/remote: To create a merge request for protected-branch, visit/) + expect(push.output).to match(/remote: To create a merge request for protected-branch, visit/) end end @@ -41,11 +37,11 @@ module QA it 'user without push rights fails to push to the protected branch' do create_protected_branch(allow_to_push: false) - @push = push_new_file(branch_name) + push = push_new_file(branch_name) - expect(@push.output) + expect(push.output) .to match(/remote\: GitLab\: You are not allowed to push code to protected branches on this project/) - expect(@push.output) + expect(push.output) .to match(/\[remote rejected\] #{branch_name} -> #{branch_name} \(pre-receive hook declined\)/) end end @@ -69,13 +65,5 @@ module QA resource.new_branch = false end end - - def log_push_output - if defined?(@push) - filename = File.join('tmp', "push-output-#{project.name}") - puts "Exception detected. Push output will be saved to #{filename}" - IO.binwrite(filename, @push.output) - end - end end end |