diff options
Diffstat (limited to 'spec/features')
333 files changed, 5908 insertions, 2680 deletions
diff --git a/spec/features/abuse_report_spec.rb b/spec/features/abuse_report_spec.rb index 1267025a7bf..82b7379b67c 100644 --- a/spec/features/abuse_report_spec.rb +++ b/spec/features/abuse_report_spec.rb @@ -12,28 +12,10 @@ RSpec.describe 'Abuse reports', :js, feature_category: :insider_threat do before do sign_in(reporter1) + stub_feature_flags(moved_mr_sidebar: false) end describe 'report abuse to administrator' do - shared_examples 'reports the user with an abuse category' do - it do - fill_and_submit_abuse_category_form - fill_and_submit_report_abuse_form - - expect(page).to have_content 'Thank you for your report' - end - end - - shared_examples 'reports the user without an abuse category' do - it do - click_link 'Report abuse to administrator' - - fill_and_submit_report_abuse_form - - expect(page).to have_content 'Thank you for your report' - end - end - context 'when reporting an issue for abuse' do before do visit project_issue_path(project, issue) @@ -133,7 +115,7 @@ RSpec.describe 'Abuse reports', :js, feature_category: :insider_threat do before do visit project_issue_path(project, issue) - click_button 'More actions' + find('.more-actions-toggle button').click end it_behaves_like 'reports the user with an abuse category' @@ -143,7 +125,7 @@ RSpec.describe 'Abuse reports', :js, feature_category: :insider_threat do private def fill_and_submit_abuse_category_form(category = "They're posting spam.") - click_button 'Report abuse to administrator' + click_button 'Report abuse' choose category click_button 'Next' diff --git a/spec/features/action_cable_logging_spec.rb b/spec/features/action_cable_logging_spec.rb index c02a41c4c59..c8a4e1efb7a 100644 --- a/spec/features/action_cable_logging_spec.rb +++ b/spec/features/action_cable_logging_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'ActionCable logging', :js, feature_category: :not_owned do +RSpec.describe 'ActionCable logging', :js, feature_category: :shared do let_it_be(:project) { create(:project, :public) } let_it_be(:issue) { create(:issue, project: project) } let_it_be(:user) { create(:user) } diff --git a/spec/features/admin/admin_abuse_reports_spec.rb b/spec/features/admin/admin_abuse_reports_spec.rb index 10f12d7116f..9739ea53f81 100644 --- a/spec/features/admin/admin_abuse_reports_spec.rb +++ b/spec/features/admin/admin_abuse_reports_spec.rb @@ -2,79 +2,247 @@ require 'spec_helper' -RSpec.describe "Admin::AbuseReports", :js, feature_category: :not_owned do - let(:user) { create(:user) } +RSpec.describe "Admin::AbuseReports", :js, feature_category: :shared do + let_it_be(:user) { create(:user) } + let_it_be(:admin) { create(:admin) } context 'as an admin' do - before do - admin = create(:admin) - sign_in(admin) - gitlab_enable_admin_mode_sign_in(admin) - end + describe 'displayed reports' do + include FilteredSearchHelpers - describe 'if a user has been reported for abuse' do - let!(:abuse_report) { create(:abuse_report, user: user) } + let_it_be(:open_report) { create(:abuse_report, created_at: 5.days.ago, updated_at: 2.days.ago) } + let_it_be(:open_report2) { create(:abuse_report, created_at: 4.days.ago, updated_at: 3.days.ago, category: 'phishing') } + let_it_be(:closed_report) { create(:abuse_report, :closed) } - describe 'in the abuse report view' do - it 'presents information about abuse report' do - visit admin_abuse_reports_path + let(:abuse_report_row_selector) { '[data-testid="abuse-report-row"]' } - expect(page).to have_content('Abuse Reports') - expect(page).to have_content(abuse_report.message) - expect(page).to have_link(user.name, href: user_path(user)) - expect(page).to have_link('Remove user') - end + before do + sign_in(admin) + gitlab_enable_admin_mode_sign_in(admin) + + visit admin_abuse_reports_path end - describe 'in the profile page of the user' do - it 'shows a link to the admin view of the user' do - visit user_path(user) + it 'only includes open reports by default' do + expect_displayed_reports_count(2) + + expect_report_shown(open_report, open_report2) - expect(page).to have_link '', href: admin_user_path(user) + within '[data-testid="abuse-reports-filtered-search-bar"]' do + expect(page).to have_content 'Status = Open' end end - end - describe 'if a many users have been reported for abuse' do - let(:report_count) { AbuseReport.default_per_page + 3 } + it 'can be filtered by status, user, reporter, and category', :aggregate_failures do + # filter by status + filter %w[Status Closed] + expect_displayed_reports_count(1) + expect_report_shown(closed_report) + expect_report_not_shown(open_report, open_report2) - before do - report_count.times do - create(:abuse_report, user: create(:user)) + filter %w[Status Open] + expect_displayed_reports_count(2) + expect_report_shown(open_report, open_report2) + expect_report_not_shown(closed_report) + + # filter by user + filter(['User', open_report2.user.username]) + + expect_displayed_reports_count(1) + expect_report_shown(open_report2) + expect_report_not_shown(open_report, closed_report) + + # filter by reporter + filter(['Reporter', open_report.reporter.username]) + + expect_displayed_reports_count(1) + expect_report_shown(open_report) + expect_report_not_shown(open_report2, closed_report) + + # filter by category + filter(['Category', open_report2.category]) + + expect_displayed_reports_count(1) + expect_report_shown(open_report2) + expect_report_not_shown(open_report, closed_report) + end + + it 'can be sorted by created_at and updated_at in desc and asc order', :aggregate_failures do + # created_at desc (default) + expect(report_rows[0].text).to include(report_text(open_report2)) + expect(report_rows[1].text).to include(report_text(open_report)) + + # created_at asc + toggle_sort_direction + + expect(report_rows[0].text).to include(report_text(open_report)) + expect(report_rows[1].text).to include(report_text(open_report2)) + + # updated_at ascending + sort_by 'Updated date' + + expect(report_rows[0].text).to include(report_text(open_report2)) + expect(report_rows[1].text).to include(report_text(open_report)) + + # updated_at descending + toggle_sort_direction + + expect(report_rows[0].text).to include(report_text(open_report)) + expect(report_rows[1].text).to include(report_text(open_report2)) + end + + def report_rows + page.all(abuse_report_row_selector) + end + + def report_text(report) + "#{report.user.name} reported for #{report.category}" + end + + def expect_report_shown(*reports) + reports.each do |r| + expect(page).to have_content(report_text(r)) end end - describe 'in the abuse report view' do - it 'presents information about abuse report' do - visit admin_abuse_reports_path + def expect_report_not_shown(*reports) + reports.each do |r| + expect(page).not_to have_content(report_text(r)) + end + end + + def expect_displayed_reports_count(count) + expect(page).to have_css(abuse_report_row_selector, count: count) + end + + def filter(tokens) + # remove all existing filters first + page.find_all('.gl-token-close').each(&:click) - expect(page).to have_selector('.pagination') - expect(page).to have_selector('.pagination .js-pagination-page', count: (report_count.to_f / AbuseReport.default_per_page).ceil) + select_tokens(*tokens, submit: true, input_text: 'Filter reports') + end + + def sort_by(sort) + page.within('.vue-filtered-search-bar-container .sort-dropdown-container') do + page.find('.gl-dropdown-toggle').click + + page.within('.dropdown-menu') do + click_button sort + wait_for_requests + end end end end - describe 'filtering by user' do - let!(:user2) { create(:user) } - let!(:abuse_report) { create(:abuse_report, user: user) } - let!(:abuse_report_2) { create(:abuse_report, user: user2) } + context 'when abuse_reports_list feature flag is disabled' do + before do + stub_feature_flags(abuse_reports_list: false) - it 'shows only single user report' do - visit admin_abuse_reports_path + sign_in(admin) + gitlab_enable_admin_mode_sign_in(admin) + end + + describe 'if a user has been reported for abuse' do + let_it_be(:abuse_report) { create(:abuse_report, user: user) } + + describe 'in the abuse report view' do + before do + visit admin_abuse_reports_path + end + + it 'presents information about abuse report' do + expect(page).to have_content('Abuse Reports') + + expect(page).to have_content(user.name) + expect(page).to have_content(abuse_report.reporter.name) + expect(page).to have_content(abuse_report.message) + expect(page).to have_link(user.name, href: user_path(user)) + end + + it 'present actions items' do + expect(page).to have_link('Remove user & report') + expect(page).to have_link('Block user') + expect(page).to have_link('Remove user') + end + end + + describe 'in the profile page of the user' do + it 'shows a link to view user in the admin area' do + visit user_path(user) + + expect(page).to have_link 'View user in admin area', href: admin_user_path(user) + end + end + end + + describe 'if an admin has been reported for abuse' do + let_it_be(:admin_abuse_report) { create(:abuse_report, user: admin) } + + describe 'in the abuse report view' do + before do + visit admin_abuse_reports_path + end - page.within '.filter-form' do - click_button 'User' - wait_for_requests + it 'presents information about abuse report' do + page.within(:table_row, { "User" => admin.name }) do + expect(page).to have_content(admin.name) + expect(page).to have_content(admin_abuse_report.reporter.name) + expect(page).to have_content(admin_abuse_report.message) + expect(page).to have_link(admin.name, href: user_path(admin)) + end + end - page.within '.dropdown-menu-user' do - click_link user2.name + it 'does not present actions items' do + page.within(:table_row, { "User" => admin.name }) do + expect(page).not_to have_link('Remove user & report') + expect(page).not_to have_link('Block user') + expect(page).not_to have_link('Remove user') + end end + end + end + + describe 'if a many users have been reported for abuse' do + let(:report_count) { AbuseReport.default_per_page + 3 } - wait_for_requests + before do + report_count.times do + create(:abuse_report, user: create(:user)) + end end - expect(page).to have_content(user2.name) - expect(page).not_to have_content(user.name) + describe 'in the abuse report view' do + it 'presents information about abuse report' do + visit admin_abuse_reports_path + + expect(page).to have_selector('.pagination') + expect(page).to have_selector('.pagination .js-pagination-page', count: (report_count.to_f / AbuseReport.default_per_page).ceil) + end + end + end + + describe 'filtering by user' do + let!(:user2) { create(:user) } + let!(:abuse_report) { create(:abuse_report, user: user) } + let!(:abuse_report_2) { create(:abuse_report, user: user2) } + + it 'shows only single user report' do + visit admin_abuse_reports_path + + page.within '.filter-form' do + click_button 'User' + wait_for_requests + + page.within '.dropdown-menu-user' do + click_link user2.name + end + + wait_for_requests + end + + expect(page).to have_content(user2.name) + expect(page).not_to have_content(user.name) + end end end end diff --git a/spec/features/admin/admin_appearance_spec.rb b/spec/features/admin/admin_appearance_spec.rb index 252d9ac5bac..db0ae79c9c4 100644 --- a/spec/features/admin/admin_appearance_spec.rb +++ b/spec/features/admin/admin_appearance_spec.rb @@ -2,190 +2,192 @@ require 'spec_helper' -RSpec.describe 'Admin Appearance', feature_category: :not_owned do +RSpec.describe 'Admin Appearance', feature_category: :shared do let!(:appearance) { create(:appearance) } let(:admin) { create(:admin) } flag_values = [true, false] flag_values.each do |val| - before do - stub_feature_flags(restyle_login_page: val) - end - - it 'create new appearance' do - sign_in(admin) - gitlab_enable_admin_mode_sign_in(admin) - visit admin_application_settings_appearances_path - - fill_in 'appearance_title', with: 'MyCompany' - fill_in 'appearance_description', with: 'dev server' - fill_in 'appearance_pwa_name', with: 'GitLab PWA' - fill_in 'appearance_pwa_short_name', with: 'GitLab' - fill_in 'appearance_pwa_description', with: 'GitLab as PWA' - fill_in 'appearance_new_project_guidelines', with: 'Custom project guidelines' - fill_in 'appearance_profile_image_guidelines', with: 'Custom profile image guidelines' - click_button 'Update appearance settings' - - expect(page).to have_current_path admin_application_settings_appearances_path, ignore_query: true - expect(page).to have_content 'Appearance' - - expect(page).to have_field('appearance_title', with: 'MyCompany') - expect(page).to have_field('appearance_description', with: 'dev server') - expect(page).to have_field('appearance_pwa_name', with: 'GitLab PWA') - expect(page).to have_field('appearance_pwa_short_name', with: 'GitLab') - expect(page).to have_field('appearance_pwa_description', with: 'GitLab as PWA') - expect(page).to have_field('appearance_new_project_guidelines', with: 'Custom project guidelines') - expect(page).to have_field('appearance_profile_image_guidelines', with: 'Custom profile image guidelines') - expect(page).to have_content 'Last edit' - end + context "with #{val}" do + before do + stub_feature_flags(restyle_login_page: val) + end - it 'preview sign-in page appearance' do - sign_in(admin) - gitlab_enable_admin_mode_sign_in(admin) + it 'create new appearance' do + sign_in(admin) + gitlab_enable_admin_mode_sign_in(admin) + visit admin_application_settings_appearances_path - visit admin_application_settings_appearances_path - click_link "Sign-in page" + fill_in 'appearance_title', with: 'MyCompany' + fill_in 'appearance_description', with: 'dev server' + fill_in 'appearance_pwa_name', with: 'GitLab PWA' + fill_in 'appearance_pwa_short_name', with: 'GitLab' + fill_in 'appearance_pwa_description', with: 'GitLab as PWA' + fill_in 'appearance_new_project_guidelines', with: 'Custom project guidelines' + fill_in 'appearance_profile_image_guidelines', with: 'Custom profile image guidelines' + click_button 'Update appearance settings' - expect(find('#login')).to be_disabled - expect(find('#password')).to be_disabled - expect(find('button')).to be_disabled + expect(page).to have_current_path admin_application_settings_appearances_path, ignore_query: true + expect(page).to have_content 'Appearance' + + expect(page).to have_field('appearance_title', with: 'MyCompany') + expect(page).to have_field('appearance_description', with: 'dev server') + expect(page).to have_field('appearance_pwa_name', with: 'GitLab PWA') + expect(page).to have_field('appearance_pwa_short_name', with: 'GitLab') + expect(page).to have_field('appearance_pwa_description', with: 'GitLab as PWA') + expect(page).to have_field('appearance_new_project_guidelines', with: 'Custom project guidelines') + expect(page).to have_field('appearance_profile_image_guidelines', with: 'Custom profile image guidelines') + expect(page).to have_content 'Last edit' + end - expect_custom_sign_in_appearance(appearance) - end + it 'preview sign-in page appearance' do + sign_in(admin) + gitlab_enable_admin_mode_sign_in(admin) - it 'preview new project page appearance', :js do - sign_in(admin) - gitlab_enable_admin_mode_sign_in(admin) + visit admin_application_settings_appearances_path + click_link "Sign-in page" - visit admin_application_settings_appearances_path - click_link "New project page" + expect(find('#login')).to be_disabled + expect(find('#password')).to be_disabled + expect(find('button')).to be_disabled - expect_custom_new_project_appearance(appearance) - end + expect_custom_sign_in_appearance(appearance) + end - context 'Custom system header and footer' do - before do + it 'preview new project page appearance', :js do sign_in(admin) gitlab_enable_admin_mode_sign_in(admin) - end - context 'when system header and footer messages are empty' do - it 'shows custom system header and footer fields' do - visit admin_application_settings_appearances_path + visit admin_application_settings_appearances_path + click_link "New project page" - expect(page).to have_field('appearance_header_message', with: '') - expect(page).to have_field('appearance_footer_message', with: '') - expect(page).to have_field('appearance_message_background_color') - expect(page).to have_field('appearance_message_font_color') - end + expect_custom_new_project_appearance(appearance) end - context 'when system header and footer messages are not empty' do + context 'Custom system header and footer' do before do - appearance.update!(header_message: 'Foo', footer_message: 'Bar') + sign_in(admin) + gitlab_enable_admin_mode_sign_in(admin) end - it 'shows custom system header and footer fields' do - visit admin_application_settings_appearances_path + context 'when system header and footer messages are empty' do + it 'shows custom system header and footer fields' do + visit admin_application_settings_appearances_path - expect(page).to have_field('appearance_header_message', with: appearance.header_message) - expect(page).to have_field('appearance_footer_message', with: appearance.footer_message) - expect(page).to have_field('appearance_message_background_color') - expect(page).to have_field('appearance_message_font_color') + expect(page).to have_field('appearance_header_message', with: '') + expect(page).to have_field('appearance_footer_message', with: '') + expect(page).to have_field('appearance_message_background_color') + expect(page).to have_field('appearance_message_font_color') + end end - end - end - it 'custom sign-in page' do - visit new_user_session_path + context 'when system header and footer messages are not empty' do + before do + appearance.update!(header_message: 'Foo', footer_message: 'Bar') + end - expect_custom_sign_in_appearance(appearance) - end + it 'shows custom system header and footer fields' do + visit admin_application_settings_appearances_path - it 'custom new project page', :js do - sign_in(admin) - gitlab_enable_admin_mode_sign_in(admin) - visit new_project_path - click_link 'Create blank project' + expect(page).to have_field('appearance_header_message', with: appearance.header_message) + expect(page).to have_field('appearance_footer_message', with: appearance.footer_message) + expect(page).to have_field('appearance_message_background_color') + expect(page).to have_field('appearance_message_font_color') + end + end + end - expect_custom_new_project_appearance(appearance) - end + it 'custom sign-in page' do + visit new_user_session_path - context 'Profile page with custom profile image guidelines' do - before do + expect_custom_sign_in_appearance(appearance) + end + + it 'custom new project page', :js do sign_in(admin) gitlab_enable_admin_mode_sign_in(admin) - visit admin_application_settings_appearances_path - fill_in 'appearance_profile_image_guidelines', with: 'Custom profile image guidelines, please :smile:!' - click_button 'Update appearance settings' + visit new_project_path + click_link 'Create blank project' + + expect_custom_new_project_appearance(appearance) end - it 'renders guidelines when set' do - sign_in create(:user) - visit profile_path + context 'Profile page with custom profile image guidelines' do + before do + sign_in(admin) + gitlab_enable_admin_mode_sign_in(admin) + visit admin_application_settings_appearances_path + fill_in 'appearance_profile_image_guidelines', with: 'Custom profile image guidelines, please :smile:!' + click_button 'Update appearance settings' + end + + it 'renders guidelines when set' do + sign_in create(:user) + visit profile_path - expect(page).to have_content 'Custom profile image guidelines, please 😄!' + expect(page).to have_content 'Custom profile image guidelines, please 😄!' + end end - end - it 'appearance logo' do - sign_in(admin) - gitlab_enable_admin_mode_sign_in(admin) - visit admin_application_settings_appearances_path + it 'appearance logo' do + sign_in(admin) + gitlab_enable_admin_mode_sign_in(admin) + visit admin_application_settings_appearances_path - attach_file(:appearance_logo, logo_fixture) - click_button 'Update appearance settings' - expect(page).to have_css(logo_selector) + attach_file(:appearance_logo, logo_fixture) + click_button 'Update appearance settings' + expect(page).to have_css(logo_selector) - click_link 'Remove logo' - expect(page).not_to have_css(logo_selector) - end + click_link 'Remove logo' + expect(page).not_to have_css(logo_selector) + end - it 'appearance pwa icon' do - sign_in(admin) - gitlab_enable_admin_mode_sign_in(admin) - visit admin_application_settings_appearances_path + it 'appearance pwa icon' do + sign_in(admin) + gitlab_enable_admin_mode_sign_in(admin) + visit admin_application_settings_appearances_path - attach_file(:appearance_pwa_icon, logo_fixture) - click_button 'Update appearance settings' - expect(page).to have_css(pwa_icon_selector) + attach_file(:appearance_pwa_icon, logo_fixture) + click_button 'Update appearance settings' + expect(page).to have_css(pwa_icon_selector) - click_link 'Remove icon' - expect(page).not_to have_css(pwa_icon_selector) - end + click_link 'Remove icon' + expect(page).not_to have_css(pwa_icon_selector) + end - it 'header logos' do - sign_in(admin) - gitlab_enable_admin_mode_sign_in(admin) - visit admin_application_settings_appearances_path + it 'header logos' do + sign_in(admin) + gitlab_enable_admin_mode_sign_in(admin) + visit admin_application_settings_appearances_path - attach_file(:appearance_header_logo, logo_fixture) - click_button 'Update appearance settings' - expect(page).to have_css(header_logo_selector) + attach_file(:appearance_header_logo, logo_fixture) + click_button 'Update appearance settings' + expect(page).to have_css(header_logo_selector) - click_link 'Remove header logo' - expect(page).not_to have_css(header_logo_selector) - end + click_link 'Remove header logo' + expect(page).not_to have_css(header_logo_selector) + end - it 'Favicon' do - sign_in(admin) - gitlab_enable_admin_mode_sign_in(admin) - visit admin_application_settings_appearances_path + it 'Favicon' do + sign_in(admin) + gitlab_enable_admin_mode_sign_in(admin) + visit admin_application_settings_appearances_path - attach_file(:appearance_favicon, logo_fixture) - click_button 'Update appearance settings' + attach_file(:appearance_favicon, logo_fixture) + click_button 'Update appearance settings' - expect(page).to have_css('.appearance-light-logo-preview') + expect(page).to have_css('.appearance-light-logo-preview') - click_link 'Remove favicon' + click_link 'Remove favicon' - expect(page).not_to have_css('.appearance-light-logo-preview') + expect(page).not_to have_css('.appearance-light-logo-preview') - # allowed file types - attach_file(:appearance_favicon, Rails.root.join('spec', 'fixtures', 'sanitized.svg')) - click_button 'Update appearance settings' + # allowed file types + attach_file(:appearance_favicon, Rails.root.join('spec', 'fixtures', 'sanitized.svg')) + click_button 'Update appearance settings' - expect(page).to have_content 'Favicon You are not allowed to upload "svg" files, allowed types: png, ico' + expect(page).to have_content 'Favicon You are not allowed to upload "svg" files, allowed types: png, ico' + end end end diff --git a/spec/features/admin/admin_browse_spam_logs_spec.rb b/spec/features/admin/admin_browse_spam_logs_spec.rb index 461c9d08273..c272a8630b7 100644 --- a/spec/features/admin/admin_browse_spam_logs_spec.rb +++ b/spec/features/admin/admin_browse_spam_logs_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Admin browse spam logs', feature_category: :not_owned do +RSpec.describe 'Admin browse spam logs', feature_category: :shared do let!(:spam_log) { create(:spam_log, description: 'abcde ' * 20) } before do diff --git a/spec/features/admin/admin_deploy_keys_spec.rb b/spec/features/admin/admin_deploy_keys_spec.rb index e55e1cce6b9..f59b4db5cc2 100644 --- a/spec/features/admin/admin_deploy_keys_spec.rb +++ b/spec/features/admin/admin_deploy_keys_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'admin deploy keys', :js, feature_category: :authentication_and_authorization do +RSpec.describe 'admin deploy keys', :js, feature_category: :system_access do include Spec::Support::Helpers::ModalHelpers let_it_be(:admin) { create(:admin) } diff --git a/spec/features/admin/admin_groups_spec.rb b/spec/features/admin/admin_groups_spec.rb index a07a5c48713..34fe98d22bd 100644 --- a/spec/features/admin/admin_groups_spec.rb +++ b/spec/features/admin/admin_groups_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' RSpec.describe 'Admin Groups', feature_category: :subgroups do - include Spec::Support::Helpers::Features::MembersHelpers - include Spec::Support::Helpers::Features::InviteMembersModalHelper + include Features::MembersHelpers + include Features::InviteMembersModalHelpers include Spec::Support::Helpers::ModalHelpers let(:internal) { Gitlab::VisibilityLevel::INTERNAL } diff --git a/spec/features/admin/admin_health_check_spec.rb b/spec/features/admin/admin_health_check_spec.rb index de71a48d2dc..66014e676d5 100644 --- a/spec/features/admin/admin_health_check_spec.rb +++ b/spec/features/admin/admin_health_check_spec.rb @@ -2,8 +2,9 @@ require 'spec_helper' -RSpec.describe "Admin Health Check", feature_category: :continuous_verification do +RSpec.describe "Admin Health Check", :js, feature_category: :error_budgets do include StubENV + include Spec::Support::Helpers::ModalHelpers let_it_be(:admin) { create(:admin) } before do @@ -30,7 +31,8 @@ RSpec.describe "Admin Health Check", feature_category: :continuous_verification describe 'reload access token' do it 'changes the access token' do orig_token = Gitlab::CurrentSettings.health_check_access_token - click_button 'Reset health check access token' + click_link 'Reset health check access token' + accept_gl_confirm('Are you sure you want to reset the health check token?') expect(page).to have_content('New health check access token has been generated!') expect(find('#health-check-token').text).not_to eq orig_token diff --git a/spec/features/admin/admin_hook_logs_spec.rb b/spec/features/admin/admin_hook_logs_spec.rb index d6507e68692..34208cca113 100644 --- a/spec/features/admin/admin_hook_logs_spec.rb +++ b/spec/features/admin/admin_hook_logs_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Admin::HookLogs', feature_category: :continuous_verification do +RSpec.describe 'Admin::HookLogs', feature_category: :integrations do let_it_be(:system_hook) { create(:system_hook) } let_it_be(:hook_log) { create(:web_hook_log, web_hook: system_hook, internal_error_message: 'some error') } let_it_be(:admin) { create(:admin) } diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb index 363c152371e..a8aa2680b55 100644 --- a/spec/features/admin/admin_hooks_spec.rb +++ b/spec/features/admin/admin_hooks_spec.rb @@ -106,7 +106,7 @@ RSpec.describe 'Admin::Hooks', feature_category: :integrations do visit admin_hooks_path click_button 'Test' - click_button 'Push events' + click_link 'Push events' end it { expect(page).to have_current_path(admin_hooks_path, ignore_query: true) } @@ -142,7 +142,7 @@ RSpec.describe 'Admin::Hooks', feature_category: :integrations do visit admin_hooks_path click_button 'Test' - click_button 'Merge request events' + click_link 'Merge request events' expect(page).to have_content 'Hook executed successfully' end diff --git a/spec/features/admin/admin_labels_spec.rb b/spec/features/admin/admin_labels_spec.rb index 8d2813d26f7..68d63ac321e 100644 --- a/spec/features/admin/admin_labels_spec.rb +++ b/spec/features/admin/admin_labels_spec.rb @@ -37,8 +37,11 @@ RSpec.describe 'admin issues labels', feature_category: :team_planning do end it 'deletes all labels', :js do - page.all('.labels .js-remove-label').each do |remove| - accept_gl_confirm(button_text: 'Delete label') { remove.click } + page.all('.labels .label-actions-list').each do |label| + label.click + accept_gl_confirm(button_text: 'Delete label') do + click_link 'Delete' + end wait_for_requests end diff --git a/spec/features/admin/admin_mode/login_spec.rb b/spec/features/admin/admin_mode/login_spec.rb index 393721fe451..c0c8b12342a 100644 --- a/spec/features/admin/admin_mode/login_spec.rb +++ b/spec/features/admin/admin_mode/login_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Admin Mode Login', feature_category: :authentication_and_authorization do +RSpec.describe 'Admin Mode Login', feature_category: :system_access do include TermsHelper include UserLoginHelper include LdapHelpers @@ -15,249 +15,251 @@ RSpec.describe 'Admin Mode Login', feature_category: :authentication_and_authori flag_values = [true, false] flag_values.each do |val| - before do - stub_feature_flags(restyle_login_page: val) - end - context 'with valid username/password' do - let(:user) { create(:admin, :two_factor) } + context "with #{val}" do + before do + stub_feature_flags(restyle_login_page: val) + end + context 'with valid username/password' do + let(:user) { create(:admin, :two_factor) } - context 'using one-time code' do - it 'blocks login if we reuse the same code immediately' do - gitlab_sign_in(user, remember: true) + context 'using one-time code' do + it 'blocks login if we reuse the same code immediately' do + gitlab_sign_in(user, remember: true) - expect(page).to have_content('Two-Factor Authentication') + expect(page).to have_content(_('Enter verification code')) - repeated_otp = user.current_otp - enter_code(repeated_otp) - gitlab_enable_admin_mode_sign_in(user) + repeated_otp = user.current_otp + enter_code(repeated_otp) + gitlab_enable_admin_mode_sign_in(user) - expect(page).to have_content('Two-Factor Authentication') + expect(page).to have_content(_('Enter verification code')) - enter_code(repeated_otp) + enter_code(repeated_otp) - expect(page).to have_current_path admin_session_path, ignore_query: true - expect(page).to have_content('Invalid two-factor code') - end + expect(page).to have_current_path admin_session_path, ignore_query: true + expect(page).to have_content('Invalid two-factor code') + end - context 'not re-using codes' do - before do - gitlab_sign_in(user, remember: true) + context 'not re-using codes' do + before do + gitlab_sign_in(user, remember: true) - expect(page).to have_content('Two-factor authentication code') + expect(page).to have_content('Enter verification code') - enter_code(user.current_otp) - gitlab_enable_admin_mode_sign_in(user) + enter_code(user.current_otp) + gitlab_enable_admin_mode_sign_in(user) - expect(page).to have_content('Two-Factor Authentication') - end + expect(page).to have_content(_('Enter verification code')) + end - it 'allows login with valid code' do - # Cannot reuse the TOTP - travel_to(30.seconds.from_now) do - enter_code(user.current_otp) + it 'allows login with valid code' do + # Cannot reuse the TOTP + travel_to(30.seconds.from_now) do + enter_code(user.current_otp) - expect(page).to have_current_path admin_root_path, ignore_query: true - expect(page).to have_content('Admin mode enabled') + expect(page).to have_current_path admin_root_path, ignore_query: true + expect(page).to have_content('Admin mode enabled') + end end - end - it 'blocks login with invalid code' do - # Cannot reuse the TOTP - travel_to(30.seconds.from_now) do - enter_code('foo') + it 'blocks login with invalid code' do + # Cannot reuse the TOTP + travel_to(30.seconds.from_now) do + enter_code('foo') - expect(page).to have_content('Invalid two-factor code') + expect(page).to have_content('Invalid two-factor code') + end end - end - it 'allows login with invalid code, then valid code' do - # Cannot reuse the TOTP - travel_to(30.seconds.from_now) do - enter_code('foo') + it 'allows login with invalid code, then valid code' do + # Cannot reuse the TOTP + travel_to(30.seconds.from_now) do + enter_code('foo') - expect(page).to have_content('Invalid two-factor code') + expect(page).to have_content('Invalid two-factor code') - enter_code(user.current_otp) + enter_code(user.current_otp) - expect(page).to have_current_path admin_root_path, ignore_query: true - expect(page).to have_content('Admin mode enabled') + expect(page).to have_current_path admin_root_path, ignore_query: true + expect(page).to have_content('Admin mode enabled') + end end - end - context 'using backup code' do - let(:codes) { user.generate_otp_backup_codes! } + context 'using backup code' do + let(:codes) { user.generate_otp_backup_codes! } - before do - expect(codes.size).to eq 10 + before do + expect(codes.size).to eq 10 - # Ensure the generated codes get saved - user.save! - end + # Ensure the generated codes get saved + user.save! + end - context 'with valid code' do - it 'allows login' do - enter_code(codes.sample) + context 'with valid code' do + it 'allows login' do + enter_code(codes.sample) - expect(page).to have_current_path admin_root_path, ignore_query: true - expect(page).to have_content('Admin mode enabled') - end + expect(page).to have_current_path admin_root_path, ignore_query: true + expect(page).to have_content('Admin mode enabled') + end - it 'invalidates the used code' do - expect { enter_code(codes.sample) } - .to change { user.reload.otp_backup_codes.size }.by(-1) + it 'invalidates the used code' do + expect { enter_code(codes.sample) } + .to change { user.reload.otp_backup_codes.size }.by(-1) + end end - end - context 'with invalid code' do - it 'blocks login' do - code = codes.sample - expect(user.invalidate_otp_backup_code!(code)).to eq true + context 'with invalid code' do + it 'blocks login' do + code = codes.sample + expect(user.invalidate_otp_backup_code!(code)).to eq true - user.save! - expect(user.reload.otp_backup_codes.size).to eq 9 + user.save! + expect(user.reload.otp_backup_codes.size).to eq 9 - enter_code(code) + enter_code(code) - expect(page).to have_content('Invalid two-factor code.') + expect(page).to have_content('Invalid two-factor code.') + end end end end end - end - - context 'when logging in via omniauth' do - let(:user) { create(:omniauth_user, :admin, :two_factor, extern_uid: 'my-uid', provider: 'saml', password_automatically_set: false) } - let(:mock_saml_response) do - File.read('spec/fixtures/authentication/saml_response.xml') - end - before do - stub_omniauth_saml_config(enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'], providers: [mock_saml_config_with_upstream_two_factor_authn_contexts]) - end - - context 'when authn_context is worth two factors' do + context 'when logging in via omniauth' do + let(:user) { create(:omniauth_user, :admin, :two_factor, extern_uid: 'my-uid', provider: 'saml', password_automatically_set: false) } let(:mock_saml_response) do File.read('spec/fixtures/authentication/saml_response.xml') - .gsub('urn:oasis:names:tc:SAML:2.0:ac:classes:Password', - 'urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorOTPSMS') end - it 'signs user in without prompting for second factor' do - sign_in_using_saml! + before do + stub_omniauth_saml_config(enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'], providers: [mock_saml_config_with_upstream_two_factor_authn_contexts]) + end + + context 'when authn_context is worth two factors' do + let(:mock_saml_response) do + File.read('spec/fixtures/authentication/saml_response.xml') + .gsub('urn:oasis:names:tc:SAML:2.0:ac:classes:Password', + 'urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorOTPSMS') + end - expect(page).not_to have_content('Two-Factor Authentication') + it 'signs user in without prompting for second factor' do + sign_in_using_saml! - enable_admin_mode_using_saml! + expect(page).not_to have_content(_('Enter verification code')) - expect(page).not_to have_content('Two-Factor Authentication') - expect(page).to have_current_path admin_root_path, ignore_query: true - expect(page).to have_content('Admin mode enabled') + enable_admin_mode_using_saml! + + expect(page).not_to have_content(_('Enter verification code')) + expect(page).to have_current_path admin_root_path, ignore_query: true + expect(page).to have_content('Admin mode enabled') + end end - end - context 'when two factor authentication is required' do - it 'shows 2FA prompt after omniauth login' do - sign_in_using_saml! + context 'when two factor authentication is required' do + it 'shows 2FA prompt after omniauth login' do + sign_in_using_saml! - expect(page).to have_content('Two-Factor Authentication') - enter_code(user.current_otp) + expect(page).to have_content(_('Enter verification code')) + enter_code(user.current_otp) - enable_admin_mode_using_saml! + enable_admin_mode_using_saml! - expect(page).to have_content('Two-Factor Authentication') + expect(page).to have_content(_('Enter verification code')) - # Cannot reuse the TOTP - travel_to(30.seconds.from_now) do - enter_code(user.current_otp) + # Cannot reuse the TOTP + travel_to(30.seconds.from_now) do + enter_code(user.current_otp) - expect(page).to have_current_path admin_root_path, ignore_query: true - expect(page).to have_content('Admin mode enabled') + expect(page).to have_current_path admin_root_path, ignore_query: true + expect(page).to have_content('Admin mode enabled') + end end end - end - def sign_in_using_saml! - gitlab_sign_in_via('saml', user, 'my-uid', mock_saml_response) - end + def sign_in_using_saml! + gitlab_sign_in_via('saml', user, 'my-uid', mock_saml_response) + end - def enable_admin_mode_using_saml! - gitlab_enable_admin_mode_sign_in_via('saml', user, 'my-uid', mock_saml_response) + def enable_admin_mode_using_saml! + gitlab_enable_admin_mode_sign_in_via('saml', user, 'my-uid', mock_saml_response) + end end - end - context 'when logging in via ldap' do - let(:uid) { 'my-uid' } - let(:provider_label) { 'Main LDAP' } - let(:provider_name) { 'main' } - let(:provider) { "ldap#{provider_name}" } - let(:ldap_server_config) do - { - 'label' => provider_label, - 'provider_name' => provider, - 'attributes' => {}, - 'encryption' => 'plain', - 'uid' => 'uid', - 'base' => 'dc=example,dc=com' - } - end + context 'when logging in via ldap' do + let(:uid) { 'my-uid' } + let(:provider_label) { 'Main LDAP' } + let(:provider_name) { 'main' } + let(:provider) { "ldap#{provider_name}" } + let(:ldap_server_config) do + { + 'label' => provider_label, + 'provider_name' => provider, + 'attributes' => {}, + 'encryption' => 'plain', + 'uid' => 'uid', + 'base' => 'dc=example,dc=com' + } + end - let(:user) { create(:omniauth_user, :admin, :two_factor, extern_uid: uid, provider: provider) } + let(:user) { create(:omniauth_user, :admin, :two_factor, extern_uid: uid, provider: provider) } - before do - setup_ldap(provider, user, uid, ldap_server_config) - end + before do + setup_ldap(provider, user, uid, ldap_server_config) + end - context 'when two factor authentication is required' do - it 'shows 2FA prompt after ldap login' do - sign_in_using_ldap!(user, provider_label) - expect(page).to have_content('Two-Factor Authentication') + context 'when two factor authentication is required' do + it 'shows 2FA prompt after ldap login' do + sign_in_using_ldap!(user, provider_label) + expect(page).to have_content(_('Enter verification code')) - enter_code(user.current_otp) - enable_admin_mode_using_ldap!(user) + enter_code(user.current_otp) + enable_admin_mode_using_ldap!(user) - expect(page).to have_content('Two-Factor Authentication') + expect(page).to have_content(_('Enter verification code')) - # Cannot reuse the TOTP - travel_to(30.seconds.from_now) do - enter_code(user.current_otp) + # Cannot reuse the TOTP + travel_to(30.seconds.from_now) do + enter_code(user.current_otp) - expect(page).to have_current_path admin_root_path, ignore_query: true - expect(page).to have_content('Admin mode enabled') + expect(page).to have_current_path admin_root_path, ignore_query: true + expect(page).to have_content('Admin mode enabled') + end end end - end - def setup_ldap(provider, user, uid, ldap_server_config) - stub_ldap_setting(enabled: true) + def setup_ldap(provider, user, uid, ldap_server_config) + stub_ldap_setting(enabled: true) - allow(::Gitlab::Auth::Ldap::Config).to receive_messages(enabled: true, servers: [ldap_server_config]) - allow(Gitlab::Auth::OAuth::Provider).to receive_messages(providers: [provider.to_sym]) + allow(::Gitlab::Auth::Ldap::Config).to receive_messages(enabled: true, servers: [ldap_server_config]) + allow(Gitlab::Auth::OAuth::Provider).to receive_messages(providers: [provider.to_sym]) - Ldap::OmniauthCallbacksController.define_providers! - Rails.application.reload_routes! + Ldap::OmniauthCallbacksController.define_providers! + Rails.application.reload_routes! - mock_auth_hash(provider, uid, user.email) - allow(Gitlab::Auth::Ldap::Access).to receive(:allowed?).with(user).and_return(true) + mock_auth_hash(provider, uid, user.email) + allow(Gitlab::Auth::Ldap::Access).to receive(:allowed?).with(user).and_return(true) - allow_any_instance_of(ActionDispatch::Routing::RoutesProxy) - .to receive(:"user_#{provider}_omniauth_callback_path") - .and_return("/users/auth/#{provider}/callback") - end + allow_any_instance_of(ActionDispatch::Routing::RoutesProxy) + .to receive(:"user_#{provider}_omniauth_callback_path") + .and_return("/users/auth/#{provider}/callback") + end - def sign_in_using_ldap!(user, provider_label) - visit new_user_session_path - click_link provider_label - fill_in 'username', with: user.username - fill_in 'password', with: user.password - click_button 'Sign in' - end + def sign_in_using_ldap!(user, provider_label) + visit new_user_session_path + click_link provider_label + fill_in 'username', with: user.username + fill_in 'password', with: user.password + click_button 'Sign in' + end - def enable_admin_mode_using_ldap!(user) - visit new_admin_session_path - click_link provider_label - fill_in 'username', with: user.username - fill_in 'password', with: user.password - click_button 'Enter Admin Mode' + def enable_admin_mode_using_ldap!(user) + visit new_admin_session_path + click_link provider_label + fill_in 'username', with: user.username + fill_in 'password', with: user.password + click_button 'Enter admin mode' + end end end end diff --git a/spec/features/admin/admin_mode/logout_spec.rb b/spec/features/admin/admin_mode/logout_spec.rb index f4e8941d25a..a64d3f241f6 100644 --- a/spec/features/admin/admin_mode/logout_spec.rb +++ b/spec/features/admin/admin_mode/logout_spec.rb @@ -2,10 +2,10 @@ require 'spec_helper' -RSpec.describe 'Admin Mode Logout', :js, feature_category: :authentication_and_authorization do +RSpec.describe 'Admin Mode Logout', :js, feature_category: :system_access do include TermsHelper include UserLoginHelper - include Spec::Support::Helpers::Features::TopNavSpecHelpers + include Features::TopNavSpecHelpers let(:user) { create(:admin) } diff --git a/spec/features/admin/admin_mode/workers_spec.rb b/spec/features/admin/admin_mode/workers_spec.rb index f3639fd0800..124c43eef9d 100644 --- a/spec/features/admin/admin_mode/workers_spec.rb +++ b/spec/features/admin/admin_mode/workers_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' # Test an operation that triggers background jobs requiring administrative rights -RSpec.describe 'Admin mode for workers', :request_store, feature_category: :authentication_and_authorization do - include Spec::Support::Helpers::Features::AdminUsersHelpers +RSpec.describe 'Admin mode for workers', :request_store, feature_category: :system_access do + include Features::AdminUsersHelpers let(:user) { create(:user) } let(:user_to_delete) { create(:user) } diff --git a/spec/features/admin/admin_mode_spec.rb b/spec/features/admin/admin_mode_spec.rb index 769ff75b5a2..65249fa0235 100644 --- a/spec/features/admin/admin_mode_spec.rb +++ b/spec/features/admin/admin_mode_spec.rb @@ -2,9 +2,9 @@ require 'spec_helper' -RSpec.describe 'Admin mode', :js, feature_category: :not_owned do +RSpec.describe 'Admin mode', :js, feature_category: :shared do include MobileHelpers - include Spec::Support::Helpers::Features::TopNavSpecHelpers + include Features::TopNavSpecHelpers include StubENV let(:admin) { create(:admin) } @@ -50,7 +50,7 @@ RSpec.describe 'Admin mode', :js, feature_category: :not_owned do fill_in 'user_password', with: admin.password - click_button 'Enter Admin Mode' + click_button 'Enter admin mode' expect(page).to have_current_path(admin_root_path) end @@ -65,7 +65,7 @@ RSpec.describe 'Admin mode', :js, feature_category: :not_owned do fill_in 'user_password', with: admin.password - click_button 'Enter Admin Mode' + click_button 'Enter admin mode' expect(page).to have_current_path(admin_root_path) end @@ -111,7 +111,7 @@ RSpec.describe 'Admin mode', :js, feature_category: :not_owned do open_top_nav expect(page).to have_link(text: 'Admin', href: admin_root_path, visible: true) - expect(page).to have_link(text: 'Leave Admin Mode', href: destroy_admin_session_path, visible: true) + expect(page).to have_link(text: 'Leave admin mode', href: destroy_admin_session_path, visible: true) end end diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb index f08e6521184..ac2e9de7aee 100644 --- a/spec/features/admin/admin_projects_spec.rb +++ b/spec/features/admin/admin_projects_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' RSpec.describe "Admin::Projects", feature_category: :projects do - include Spec::Support::Helpers::Features::MembersHelpers - include Spec::Support::Helpers::Features::InviteMembersModalHelper + include Features::MembersHelpers + include Features::InviteMembersModalHelpers include Spec::Support::Helpers::ModalHelpers include ListboxHelpers @@ -161,4 +161,44 @@ RSpec.describe "Admin::Projects", feature_category: :projects do expect(page).to have_current_path(dashboard_projects_path, ignore_query: true, url: false) end end + + describe 'project edit' do + it 'updates project details' do + project = create(:project, :private, name: 'Garfield', description: 'Funny Cat') + + visit edit_admin_namespace_project_path({ id: project.to_param, namespace_id: project.namespace.to_param }) + + aggregate_failures do + expect(page).to have_content(project.name) + expect(page).to have_content(project.description) + end + + fill_in 'Project name', with: 'Scooby-Doo' + fill_in 'Project description (optional)', with: 'Funny Dog' + + click_button 'Save changes' + + visit edit_admin_namespace_project_path({ id: project.to_param, namespace_id: project.namespace.to_param }) + + aggregate_failures do + expect(page).to have_content('Scooby-Doo') + expect(page).to have_content('Funny Dog') + end + end + end + + describe 'project runner registration edit' do + it 'updates runner registration' do + visit edit_admin_namespace_project_path({ id: project.to_param, namespace_id: project.namespace.to_param }) + + expect(find_field('New project runners can be registered')).to be_checked + + uncheck 'New project runners can be registered' + click_button 'Save changes' + + visit edit_admin_namespace_project_path({ id: project.to_param, namespace_id: project.namespace.to_param }) + + expect(find_field('New project runners can be registered')).not_to be_checked + end + end end diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb index 04dc206f052..582535790bd 100644 --- a/spec/features/admin/admin_runners_spec.rb +++ b/spec/features/admin/admin_runners_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe "Admin Runners", feature_category: :runner_fleet do - include Spec::Support::Helpers::Features::RunnersHelpers + include Features::RunnersHelpers include Spec::Support::Helpers::ModalHelpers let_it_be(:admin) { create(:admin) } @@ -23,8 +23,6 @@ RSpec.describe "Admin Runners", feature_category: :runner_fleet do describe "runners creation" do before do - stub_feature_flags(create_runner_workflow: true) - visit admin_runners_path end @@ -34,15 +32,30 @@ RSpec.describe "Admin Runners", feature_category: :runner_fleet do end describe "runners registration" do - before do - stub_feature_flags(create_runner_workflow: false) + context 'when create_runner_workflow_for_namespace is enabled' do + before do + stub_feature_flags(create_runner_workflow_for_admin: true) - visit admin_runners_path + visit admin_runners_path + end + + it_behaves_like "shows and resets runner registration token" do + let(:dropdown_text) { s_('Runners|Register an instance runner') } + let(:registration_token) { Gitlab::CurrentSettings.runners_registration_token } + end end - it_behaves_like "shows and resets runner registration token" do - let(:dropdown_text) { s_('Runners|Register an instance runner') } - let(:registration_token) { Gitlab::CurrentSettings.runners_registration_token } + context 'when create_runner_workflow_for_namespace is disabled' do + before do + stub_feature_flags(create_runner_workflow_for_admin: false) + + visit admin_runners_path + end + + it_behaves_like "shows and resets runner registration token" do + let(:dropdown_text) { s_('Runners|Register an instance runner') } + let(:registration_token) { Gitlab::CurrentSettings.runners_registration_token } + end end end @@ -373,11 +386,9 @@ RSpec.describe "Admin Runners", feature_category: :runner_fleet do it_behaves_like 'shows no runners found' - it 'shows active tab' do + it 'shows active tab with no runner' do expect(page).to have_link('Instance', class: 'active') - end - it 'shows no runner' do expect(page).not_to have_content 'runner-project' expect(page).not_to have_content 'runner-group' end @@ -471,10 +482,12 @@ RSpec.describe "Admin Runners", feature_category: :runner_fleet do it_behaves_like 'shows no runners registered' it 'shows tabs with total counts equal to 0' do - expect(page).to have_link('All 0') - expect(page).to have_link('Instance 0') - expect(page).to have_link('Group 0') - expect(page).to have_link('Project 0') + aggregate_failures do + expect(page).to have_link('All 0') + expect(page).to have_link('Instance 0') + expect(page).to have_link('Group 0') + expect(page).to have_link('Project 0') + end end end @@ -493,6 +506,16 @@ RSpec.describe "Admin Runners", feature_category: :runner_fleet do end end + describe "Runner create page", :js do + before do + visit new_admin_runner_path + end + + it_behaves_like 'creates runner and shows register page' do + let(:register_path_pattern) { register_admin_runner_path('.*') } + end + end + describe "Runner show page", :js do let_it_be(:runner) do create( @@ -546,11 +569,8 @@ RSpec.describe "Admin Runners", feature_category: :runner_fleet do end end - it 'deletes runner' do + it 'deletes runner and redirects to runner list' do expect(page.find('[data-testid="alert-success"]')).to have_content('deleted') - end - - it 'redirects to runner list' do expect(current_url).to match(admin_runners_path) end end @@ -593,12 +613,9 @@ RSpec.describe "Admin Runners", feature_category: :runner_fleet do wait_for_requests end - it 'show success alert' do - expect(page.find('[data-testid="alert-success"]')).to have_content('saved') - end - - it 'redirects to runner page' do + it 'show success alert and redirects to runner page' do expect(current_url).to match(admin_runner_path(project_runner)) + expect(page.find('[data-testid="alert-success"]')).to have_content('saved') end end @@ -632,12 +649,12 @@ RSpec.describe "Admin Runners", feature_category: :runner_fleet do assigned_project = page.find('[data-testid="assigned-projects"]') expect(page).to have_content('Runner assigned to project.') - expect(assigned_project).to have_content(project2.path) + expect(assigned_project).to have_content(project2.name) end end context 'with project runner' do - let(:project_runner) { create(:ci_runner, :project, projects: [project1]) } + let_it_be(:project_runner) { create(:ci_runner, :project, projects: [project1]) } before do visit edit_admin_runner_path(project_runner) @@ -647,7 +664,7 @@ RSpec.describe "Admin Runners", feature_category: :runner_fleet do end context 'with locked runner' do - let(:locked_runner) { create(:ci_runner, :project, projects: [project1], locked: true) } + let_it_be(:locked_runner) { create(:ci_runner, :project, projects: [project1], locked: true) } before do visit edit_admin_runner_path(locked_runner) @@ -658,7 +675,7 @@ RSpec.describe "Admin Runners", feature_category: :runner_fleet do end describe 'disable/destroy' do - let(:runner) { create(:ci_runner, :project, projects: [project1]) } + let_it_be(:runner) { create(:ci_runner, :project, projects: [project1]) } before do visit edit_admin_runner_path(runner) @@ -672,7 +689,7 @@ RSpec.describe "Admin Runners", feature_category: :runner_fleet do new_runner_project = page.find('[data-testid="unassigned-projects"]') expect(page).to have_content('Runner unassigned from project.') - expect(new_runner_project).to have_content(project1.path) + expect(new_runner_project).to have_content(project1.name) end end end diff --git a/spec/features/admin/admin_sees_background_migrations_spec.rb b/spec/features/admin/admin_sees_background_migrations_spec.rb index cad1bf74d2e..77266e65e4c 100644 --- a/spec/features/admin/admin_sees_background_migrations_spec.rb +++ b/spec/features/admin/admin_sees_background_migrations_spec.rb @@ -191,7 +191,7 @@ RSpec.describe "Admin > Admin sees background migrations", feature_category: :da visit admin_background_migrations_path within '#content-body' do - expect(page).to have_link('Learn more', href: help_page_path('user/admin_area/monitoring/background_migrations')) + expect(page).to have_link('Learn more', href: help_page_path('update/background_migrations')) end end diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index 26efed85513..1f43caf37e7 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Admin updates settings', feature_category: :not_owned do +RSpec.describe 'Admin updates settings', feature_category: :shared do include StubENV include TermsHelper include UsageDataHelpers @@ -53,17 +53,6 @@ RSpec.describe 'Admin updates settings', feature_category: :not_owned do end it 'modify import sources' do - expect(current_settings.import_sources).not_to be_empty - - page.within('[data-testid="admin-visibility-access-settings"]') do - Gitlab::ImportSources.options.map do |name, _| - uncheck name - end - - click_button 'Save changes' - end - - expect(page).to have_content "Application settings saved successfully" expect(current_settings.import_sources).to be_empty page.within('[data-testid="admin-visibility-access-settings"]') do @@ -157,7 +146,7 @@ RSpec.describe 'Admin updates settings', feature_category: :not_owned do expect(user_internal_regex['placeholder']).to eq 'Regex pattern' end - context 'Dormant users' do + context 'Dormant users', feature_category: :user_management do context 'when Gitlab.com' do let(:dot_com?) { true } @@ -182,7 +171,7 @@ RSpec.describe 'Admin updates settings', feature_category: :not_owned do expect(page).to have_field('Days of inactivity before deactivation') end - it 'changes dormant users' do + it 'changes dormant users', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/408224' do expect(page).to have_unchecked_field('Deactivate dormant users after a period of inactivity') expect(current_settings.deactivate_dormant_users).to be_falsey @@ -199,7 +188,7 @@ RSpec.describe 'Admin updates settings', feature_category: :not_owned do expect(page).to have_checked_field('Deactivate dormant users after a period of inactivity') end - it 'change dormant users period' do + it 'change dormant users period', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/408224' do expect(page).to have_field _('Days of inactivity before deactivation') page.within(find('[data-testid="account-limit"]')) do @@ -360,8 +349,8 @@ RSpec.describe 'Admin updates settings', feature_category: :not_owned do end end - context 'GitLab for Jira App settings' do - it 'changes the setting' do + context 'GitLab for Jira App settings', feature_category: :integrations do + it 'changes the settings' do page.within('#js-jira_connect-settings') do fill_in 'Jira Connect Application ID', with: '1234' fill_in 'Jira Connect Proxy URL', with: 'https://example.com' @@ -375,6 +364,28 @@ RSpec.describe 'Admin updates settings', feature_category: :not_owned do expect(page).to have_content "Application settings saved successfully" end end + + context 'GitLab for Slack app settings', feature_category: :integrations do + it 'changes the settings' do + page.within('.as-slack') do + check 'Enable Slack application' + fill_in 'Client ID', with: 'slack_app_id' + fill_in 'Client secret', with: 'slack_app_secret' + fill_in 'Signing secret', with: 'slack_app_signing_secret' + fill_in 'Verification token', with: 'slack_app_verification_token' + click_button 'Save changes' + end + + expect(current_settings).to have_attributes( + slack_app_enabled: true, + slack_app_id: 'slack_app_id', + slack_app_secret: 'slack_app_secret', + slack_app_signing_secret: 'slack_app_signing_secret', + slack_app_verification_token: 'slack_app_verification_token' + ) + expect(page).to have_content 'Application settings saved successfully' + end + end end context 'Integrations page' do @@ -426,6 +437,7 @@ RSpec.describe 'Admin updates settings', feature_category: :not_owned do fill_in 'application_setting_auto_devops_domain', with: 'domain.com' uncheck 'Keep the latest artifacts for all jobs in the latest successful pipelines' uncheck 'Enable pipeline suggestion banner' + fill_in 'application_setting_ci_max_includes', with: 200 click_button 'Save changes' end @@ -433,6 +445,7 @@ RSpec.describe 'Admin updates settings', feature_category: :not_owned do expect(current_settings.auto_devops_domain).to eq('domain.com') expect(current_settings.keep_latest_artifact).to be false expect(current_settings.suggest_pipeline_enabled).to be false + expect(current_settings.ci_max_includes).to be 200 expect(page).to have_content "Application settings saved successfully" end @@ -442,7 +455,6 @@ RSpec.describe 'Admin updates settings', feature_category: :not_owned do page.within('.as-ci-cd') do fill_in 'plan_limits_ci_pipeline_size', with: 10 fill_in 'plan_limits_ci_active_jobs', with: 20 - fill_in 'plan_limits_ci_active_pipelines', with: 25 fill_in 'plan_limits_ci_project_subscriptions', with: 30 fill_in 'plan_limits_ci_pipeline_schedules', with: 40 fill_in 'plan_limits_ci_needs_size_limit', with: 50 @@ -454,7 +466,6 @@ RSpec.describe 'Admin updates settings', feature_category: :not_owned do limits = default_plan.reload.limits expect(limits.ci_pipeline_size).to eq(10) expect(limits.ci_active_jobs).to eq(20) - expect(limits.ci_active_pipelines).to eq(25) expect(limits.ci_project_subscriptions).to eq(30) expect(limits.ci_pipeline_schedules).to eq(40) expect(limits.ci_needs_size_limit).to eq(50) @@ -487,7 +498,7 @@ RSpec.describe 'Admin updates settings', feature_category: :not_owned do container_registry_delete_tags_service_timeout: 'Container Registry delete tags service execution timeout', container_registry_expiration_policies_worker_capacity: 'Cleanup policy maximum workers running concurrently', container_registry_cleanup_tags_service_max_list_size: 'Cleanup policy maximum number of tags to be deleted', - container_registry_expiration_policies_caching: 'Enable container expiration caching' + container_registry_expiration_policies_caching: 'Enable cleanup policy caching' } end @@ -638,6 +649,8 @@ RSpec.describe 'Admin updates settings', feature_category: :not_owned do end it 'loads togglable usage ping payload on click', :js do + allow(Gitlab::Usage::ServicePingReport).to receive(:for).and_return({ uuid: '12345678', hostname: '127.0.0.1' }) + stub_usage_data_connections stub_database_flavor_check @@ -666,11 +679,11 @@ RSpec.describe 'Admin updates settings', feature_category: :not_owned do visit network_admin_application_settings_path page.within('.as-outbound') do - check 'Allow requests to the local network from web hooks and services' + check 'Allow requests to the local network from webhooks and integrations' # Enabled by default uncheck 'Allow requests to the local network from system hooks' # Enabled by default - uncheck 'Enforce DNS rebinding attack protection' + uncheck 'Enforce DNS-rebinding attack protection' click_button 'Save changes' end @@ -762,6 +775,18 @@ RSpec.describe 'Admin updates settings', feature_category: :not_owned do expect(current_settings.users_get_by_id_limit_allowlist).to eq(%w[someone someone_else]) end + it 'changes Projects API rate limits settings' do + visit network_admin_application_settings_path + + page.within('.as-projects-api-limits') do + fill_in 'Maximum requests per 10 minutes per IP address', with: 100 + click_button 'Save changes' + end + + expect(page).to have_content "Application settings saved successfully" + expect(current_settings.projects_api_rate_limit_unauthenticated).to eq(100) + end + shared_examples 'regular throttle rate limit settings' do it 'changes rate limit settings' do visit network_admin_application_settings_path diff --git a/spec/features/admin/admin_system_info_spec.rb b/spec/features/admin/admin_system_info_spec.rb index 6c4a316ae77..21a001f12c3 100644 --- a/spec/features/admin/admin_system_info_spec.rb +++ b/spec/features/admin/admin_system_info_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Admin System Info', feature_category: :not_owned do +RSpec.describe 'Admin System Info', feature_category: :shared do before do admin = create(:admin) sign_in(admin) diff --git a/spec/features/admin/admin_users_impersonation_tokens_spec.rb b/spec/features/admin/admin_users_impersonation_tokens_spec.rb index 5e6cc206883..0350c8ab066 100644 --- a/spec/features/admin/admin_users_impersonation_tokens_spec.rb +++ b/spec/features/admin/admin_users_impersonation_tokens_spec.rb @@ -2,9 +2,9 @@ require 'spec_helper' -RSpec.describe 'Admin > Users > Impersonation Tokens', :js, feature_category: :authentication_and_authorization do +RSpec.describe 'Admin > Users > Impersonation Tokens', :js, feature_category: :system_access do include Spec::Support::Helpers::ModalHelpers - include Spec::Support::Helpers::AccessTokenHelpers + include Features::AccessTokenHelpers let(:admin) { create(:admin) } let!(:user) { create(:user) } diff --git a/spec/features/admin/admin_uses_repository_checks_spec.rb b/spec/features/admin/admin_uses_repository_checks_spec.rb index 318572a7664..d9d36ec3bae 100644 --- a/spec/features/admin/admin_uses_repository_checks_spec.rb +++ b/spec/features/admin/admin_uses_repository_checks_spec.rb @@ -19,7 +19,7 @@ RSpec.describe 'Admin uses repository checks', :request_store, feature_category: visit_admin_project_page(project) expect(page).not_to have_css('.repository-check') - expect(page).to have_content('Enter Admin Mode') + expect(page).to have_content('Enter admin mode') end end diff --git a/spec/features/admin/broadcast_messages_spec.rb b/spec/features/admin/broadcast_messages_spec.rb new file mode 100644 index 00000000000..fca4cdb0ff4 --- /dev/null +++ b/spec/features/admin/broadcast_messages_spec.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Admin Broadcast Messages', :js, feature_category: :onboarding do + context 'when creating and editing' do + it 'previews, creates and edits a broadcast message' do + admin = create(:admin) + sign_in(admin) + gitlab_enable_admin_mode_sign_in(admin) + + # create + visit admin_broadcast_messages_path + + fill_in 'Message', with: 'test message' + + wait_for_requests + + page.within(preview_container) do + expect(page).to have_content('test message') + end + + click_button 'Add broadcast message' + + wait_for_requests + + page.within(preview_container) do + expect(page).to have_content('Your message here') + end + + page.within(first_message_container) do + expect(page).to have_content('test message') + end + + # edit + page.within(first_message_container) do + find('[data-testid="edit-message"]').click + end + + wait_for_requests + + expect(find('[data-testid="message-input"]').value).to eq('test message') + + fill_in 'Message', with: 'changed test message' + + wait_for_requests + + page.within(preview_container) do + expect(page).to have_content('changed test message') + end + + click_button 'Update broadcast message' + + wait_for_requests + + page.within(preview_container) do + expect(page).to have_content('Your message here') + end + + page.within(first_message_container) do + expect(page).to have_content('changed test message') + end + end + + def preview_container + find('[data-testid="preview-broadcast-message"]') + end + + def first_message_container + find('[data-testid="message-row"]', match: :first) + end + end +end diff --git a/spec/features/admin/integrations/instance_integrations_spec.rb b/spec/features/admin/integrations/instance_integrations_spec.rb index 3b2ed1d9810..d963aa700eb 100644 --- a/spec/features/admin/integrations/instance_integrations_spec.rb +++ b/spec/features/admin/integrations/instance_integrations_spec.rb @@ -5,6 +5,10 @@ require 'spec_helper' RSpec.describe 'Instance integrations', :js, feature_category: :integrations do include_context 'instance integration activation' + before do + stub_feature_flags(remove_monitor_metrics: false) + end + it_behaves_like 'integration settings form' do let(:integrations) { Integration.find_or_initialize_all_non_project_specific(Integration.for_instance) } diff --git a/spec/features/admin/users/user_spec.rb b/spec/features/admin/users/user_spec.rb index 66129617220..18d62cb585f 100644 --- a/spec/features/admin/users/user_spec.rb +++ b/spec/features/admin/users/user_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'Admin::Users::User', feature_category: :user_management do - include Spec::Support::Helpers::Features::AdminUsersHelpers + include Features::AdminUsersHelpers include Spec::Support::Helpers::ModalHelpers let_it_be(:user) { create(:omniauth_user, provider: 'twitter', extern_uid: '123456') } @@ -273,8 +273,11 @@ RSpec.describe 'Admin::Users::User', feature_category: :user_management do end context 'when viewing the confirm email warning', :js do - let_it_be(:another_user) { create(:user, :unconfirmed) } + before do + stub_application_setting_enum('email_confirmation_setting', 'soft') + end + let_it_be(:another_user) { create(:user, :unconfirmed) } let(:warning_alert) { page.find(:css, '[data-testid="alert-warning"]') } let(:expected_styling) { { 'pointer-events' => 'none', 'cursor' => 'default' } } diff --git a/spec/features/admin/users/users_spec.rb b/spec/features/admin/users/users_spec.rb index 07db0750074..8e80ce5edd9 100644 --- a/spec/features/admin/users/users_spec.rb +++ b/spec/features/admin/users/users_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'Admin::Users', feature_category: :user_management do - include Spec::Support::Helpers::Features::AdminUsersHelpers + include Features::AdminUsersHelpers include Spec::Support::Helpers::ModalHelpers include ListboxHelpers @@ -311,6 +311,40 @@ RSpec.describe 'Admin::Users', feature_category: :user_management do end end + describe 'users pending approval' do + it 'sends a welcome email and a password reset email to the user upon admin approval', :sidekiq_inline do + user = create(:user, :blocked_pending_approval, created_by_id: current_user.id) + + visit admin_users_path + + click_link 'Pending approval' + + click_user_dropdown_toggle(user.id) + + find('[data-testid="approve"]').click + + expect(page).to have_content("Approve user #{user.name}?") + + within_modal do + perform_enqueued_jobs do + click_button 'Approve' + end + end + + expect(page).to have_content('Successfully approved') + + welcome_email = ActionMailer::Base.deliveries.find { |m| m.subject == 'Welcome to GitLab!' } + expect(welcome_email.to).to eq([user.email]) + expect(welcome_email.text_part.body).to have_content('Your GitLab account request has been approved!') + + password_reset_email = ActionMailer::Base.deliveries.find { |m| m.subject == 'Account was created for you' } + expect(password_reset_email.to).to eq([user.email]) + expect(password_reset_email.text_part.body).to have_content('Click here to set your password') + + expect(ActionMailer::Base.deliveries.count).to eq(2) + end + end + describe 'internal users' do context 'when showing a `Ghost User`' do let_it_be(:ghost_user) { create(:user, :ghost) } diff --git a/spec/features/admin_variables_spec.rb b/spec/features/admin_variables_spec.rb index d1adbf59984..744d18a3b6d 100644 --- a/spec/features/admin_variables_spec.rb +++ b/spec/features/admin_variables_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Instance variables', :js, feature_category: :pipeline_authoring do +RSpec.describe 'Instance variables', :js, feature_category: :secrets_management do let(:admin) { create(:admin) } let(:page_path) { ci_cd_admin_application_settings_path } @@ -12,9 +12,21 @@ RSpec.describe 'Instance variables', :js, feature_category: :pipeline_authoring stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') sign_in(admin) gitlab_enable_admin_mode_sign_in(admin) + visit page_path wait_for_requests end - it_behaves_like 'variable list', isAdmin: true + context 'when ci_variables_pages FF is enabled' do + it_behaves_like 'variable list', is_admin: true + it_behaves_like 'variable list pagination', :ci_instance_variable + end + + context 'when ci_variables_pages FF is disabled' do + before do + stub_feature_flags(ci_variables_pages: false) + end + + it_behaves_like 'variable list', is_admin: true + end end diff --git a/spec/features/alerts_settings/user_views_alerts_settings_spec.rb b/spec/features/alerts_settings/user_views_alerts_settings_spec.rb index 70223b2c0d4..94f5cf8f5b2 100644 --- a/spec/features/alerts_settings/user_views_alerts_settings_spec.rb +++ b/spec/features/alerts_settings/user_views_alerts_settings_spec.rb @@ -19,6 +19,7 @@ RSpec.describe 'Alert integrations settings form', :js, feature_category: :incid describe 'when viewing alert integrations as a maintainer' do context 'with the default page permissions' do before do + stub_feature_flags(remove_monitor_metrics: false) visit project_settings_operations_path(project, anchor: 'js-alert-management-settings') wait_for_requests end diff --git a/spec/features/boards/board_filters_spec.rb b/spec/features/boards/board_filters_spec.rb index dee63be8119..006b7ce45d4 100644 --- a/spec/features/boards/board_filters_spec.rb +++ b/spec/features/boards/board_filters_spec.rb @@ -50,7 +50,7 @@ RSpec.describe 'Issue board filters', :js, feature_category: :team_planning do set_filter('assignee') end - it_behaves_like 'loads all the users when opened' do + it_behaves_like 'loads all the users when opened', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/351426' do let(:issue) { issue_2 } end end diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index 3e2e391d060..1ea6e079104 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -150,8 +150,7 @@ RSpec.describe 'Project issue boards', :js, feature_category: :team_planning do find('.board .board-list') inspect_requests(inject_headers: { 'X-GITLAB-DISABLE-SQL-QUERY-LIMIT' => 'https://gitlab.com/gitlab-org/gitlab/-/issues/323426' }) do - evaluate_script("window.scrollTo(0, document.body.scrollHeight)") - evaluate_script("document.querySelectorAll('.board .board-list')[1].scrollTop = document.querySelectorAll('.board .board-list')[1].scrollHeight") + evaluate_script("[...document.querySelectorAll('.board:nth-child(2) .board-list [data-testid=\"board-card-gl-io\"]')].pop().scrollIntoView()") end expect(page).to have_selector('.board-card', count: 20) @@ -160,8 +159,7 @@ RSpec.describe 'Project issue boards', :js, feature_category: :team_planning do find('.board .board-list') inspect_requests(inject_headers: { 'X-GITLAB-DISABLE-SQL-QUERY-LIMIT' => 'https://gitlab.com/gitlab-org/gitlab/-/issues/323426' }) do - evaluate_script("window.scrollTo(0, document.body.scrollHeight)") - evaluate_script("document.querySelectorAll('.board .board-list')[1].scrollTop = document.querySelectorAll('.board .board-list')[1].scrollHeight") + evaluate_script("[...document.querySelectorAll('.board:nth-child(2) .board-list [data-testid=\"board-card-gl-io\"]')].pop().scrollIntoView()") end expect(page).to have_selector('.board-card', count: 30) @@ -170,8 +168,7 @@ RSpec.describe 'Project issue boards', :js, feature_category: :team_planning do find('.board .board-list') inspect_requests(inject_headers: { 'X-GITLAB-DISABLE-SQL-QUERY-LIMIT' => 'https://gitlab.com/gitlab-org/gitlab/-/issues/323426' }) do - evaluate_script("window.scrollTo(0, document.body.scrollHeight)") - evaluate_script("document.querySelectorAll('.board .board-list')[1].scrollTop = document.querySelectorAll('.board .board-list')[1].scrollHeight") + evaluate_script("[...document.querySelectorAll('.board:nth-child(2) .board-list [data-testid=\"board-card-gl-io\"]')].pop().scrollIntoView()") end expect(page).to have_selector('.board-card', count: 38) @@ -594,7 +591,9 @@ RSpec.describe 'Project issue boards', :js, feature_category: :team_planning do def remove_list page.within(find('.board:nth-child(2)')) do - find('button[title="List settings"]').click + dropdown = first("[data-testid='header-list-actions']") + dropdown.click + click_button('Edit list settings') end page.within(find('.js-board-settings-sidebar')) do diff --git a/spec/features/boards/issue_ordering_spec.rb b/spec/features/boards/issue_ordering_spec.rb index 8aecaab42c2..b6196fa6a1d 100644 --- a/spec/features/boards/issue_ordering_spec.rb +++ b/spec/features/boards/issue_ordering_spec.rb @@ -138,7 +138,7 @@ RSpec.describe 'Issue Boards', :js, feature_category: :team_planning do wait_for_requests end - it 'moves to end of list' do + it 'moves to end of list', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/410100' do expect(all('.board-card').first).to have_content(issue3.title) page.within(find('.board:nth-child(2)')) do @@ -151,7 +151,7 @@ RSpec.describe 'Issue Boards', :js, feature_category: :team_planning do expect(all('.board-card').last).to have_content(issue3.title) end - it 'moves to start of list' do + it 'moves to start of list', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/410100' do expect(all('.board-card').last).to have_content(issue1.title) page.within(find('.board:nth-child(2)')) do diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb index d597c57ac1c..6753f0ea009 100644 --- a/spec/features/boards/new_issue_spec.rb +++ b/spec/features/boards/new_issue_spec.rb @@ -32,18 +32,23 @@ RSpec.describe 'Issue Boards new issue', :js, feature_category: :team_planning d end it 'displays new issue button' do - expect(first('.board')).to have_button('New issue', count: 1) + dropdown = first("[data-testid='header-list-actions']") + dropdown.click + expect(first('.board')).to have_button('Create new issue', count: 1) end it 'does not display new issue button in closed list' do page.within('.board:nth-child(3)') do - expect(page).not_to have_button('New issue') + expect(page).not_to have_selector("[data-testid='header-list-actions']") + expect(page).not_to have_button('Create new issue') end end it 'shows form when clicking button' do page.within(first('.board')) do - click_button 'New issue' + dropdown = first("[data-testid='header-list-actions']") + dropdown.click + click_button 'Create new issue' expect(page).to have_selector('.board-new-issue-form') end @@ -51,7 +56,9 @@ RSpec.describe 'Issue Boards new issue', :js, feature_category: :team_planning d it 'hides form when clicking cancel' do page.within(first('.board')) do - click_button 'New issue' + dropdown = first("[data-testid='header-list-actions']") + dropdown.click + click_button 'Create new issue' expect(page).to have_selector('.board-new-issue-form') @@ -63,7 +70,9 @@ RSpec.describe 'Issue Boards new issue', :js, feature_category: :team_planning d it 'creates new issue, places it on top of the list, and opens sidebar' do page.within(first('.board')) do - click_button 'New issue' + dropdown = first("[data-testid='header-list-actions']") + dropdown.click + click_button 'Create new issue' end page.within(first('.board-new-issue-form')) do @@ -91,7 +100,9 @@ RSpec.describe 'Issue Boards new issue', :js, feature_category: :team_planning d it 'successfuly loads labels to be added to newly created issue' do page.within(first('.board')) do - click_button 'New issue' + dropdown = first("[data-testid='header-list-actions']") + dropdown.click + click_button 'Create new issue' end page.within(first('.board-new-issue-form')) do @@ -121,7 +132,9 @@ RSpec.describe 'Issue Boards new issue', :js, feature_category: :team_planning d wait_for_all_requests page.within('.board:nth-child(2)') do - click_button('New issue') + dropdown = first("[data-testid='header-list-actions']") + dropdown.click + click_button('Create new issue') page.within(first('.board-new-issue-form')) do find('.form-control').set('new issue') @@ -144,12 +157,14 @@ RSpec.describe 'Issue Boards new issue', :js, feature_category: :team_planning d end it 'does not display new issue button in open list' do - expect(first('.board')).not_to have_button('New issue') + expect(page).not_to have_selector("[data-testid='header-list-actions']") + expect(first('.board')).not_to have_button('Create new issue') end it 'does not display new issue button in label list' do page.within('.board:nth-child(2)') do - expect(page).not_to have_button('New issue') + expect(page).not_to have_selector("[data-testid='header-list-actions']") + expect(page).not_to have_button('Create new issue') end end end @@ -173,7 +188,8 @@ RSpec.describe 'Issue Boards new issue', :js, feature_category: :team_planning d context 'when backlog does not exist' do it 'does not display new issue button in label list' do page.within('.board.is-draggable') do - expect(page).not_to have_button('New issue') + expect(page).not_to have_selector("[data-testid='header-list-actions']") + expect(page).not_to have_button('Create new issue') end end end @@ -182,12 +198,14 @@ RSpec.describe 'Issue Boards new issue', :js, feature_category: :team_planning d let_it_be(:backlog_list) { create(:backlog_list, board: group_board) } it 'does not display new issue button in open list' do - expect(first('.board')).not_to have_button('New issue') + expect(page).not_to have_selector("[data-testid='header-list-actions']") + expect(first('.board')).not_to have_button('Create new issue') end it 'does not display new issue button in label list' do page.within('.board.is-draggable') do - expect(page).not_to have_button('New issue') + expect(page).not_to have_selector("[data-testid='header-list-actions']") + expect(page).not_to have_button('Create new issue') end end end @@ -205,7 +223,9 @@ RSpec.describe 'Issue Boards new issue', :js, feature_category: :team_planning d context 'when backlog does not exist' do it 'display new issue button in label list' do - expect(board_list_header).to have_button('New issue') + dropdown = first("[data-testid='header-list-actions']") + dropdown.click + expect(board_list_header).to have_button('Create new issue') end end @@ -214,7 +234,9 @@ RSpec.describe 'Issue Boards new issue', :js, feature_category: :team_planning d before do page.within(board_list_header) do - click_button 'New issue' + dropdown = first("[data-testid='header-list-actions']") + dropdown.click + click_button 'Create new issue' end project_select_dropdown.click diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb index 0a16e95c0bf..358da1e1279 100644 --- a/spec/features/boards/sidebar_spec.rb +++ b/spec/features/boards/sidebar_spec.rb @@ -9,7 +9,7 @@ RSpec.describe 'Project issue boards sidebar', :js, feature_category: :team_plan let_it_be(:group) { create(:group, :public) } let_it_be(:project) { create(:project, :public, namespace: group) } let_it_be(:board) { create(:board, project: project) } - let_it_be(:label) { create(:label, project: project, name: 'Label') } + let_it_be(:label) { create(:label, project: project, name: 'Label') } let_it_be(:list) { create(:list, board: board, label: label, position: 0) } let_it_be(:issue, reload: true) { create(:issue, project: project, relative_position: 1) } diff --git a/spec/features/breadcrumbs_schema_markup_spec.rb b/spec/features/breadcrumbs_schema_markup_spec.rb index d924423c9a9..6610519cd24 100644 --- a/spec/features/breadcrumbs_schema_markup_spec.rb +++ b/spec/features/breadcrumbs_schema_markup_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Breadcrumbs schema markup', :aggregate_failures, feature_category: :not_owned do +RSpec.describe 'Breadcrumbs schema markup', :aggregate_failures, feature_category: :shared do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project, :public, namespace: user.namespace) } let_it_be(:issue) { create(:issue, project: project) } diff --git a/spec/features/broadcast_messages_spec.rb b/spec/features/broadcast_messages_spec.rb index 3e4289347e3..2fad15c8a1f 100644 --- a/spec/features/broadcast_messages_spec.rb +++ b/spec/features/broadcast_messages_spec.rb @@ -7,7 +7,7 @@ RSpec.describe 'Broadcast Messages', feature_category: :onboarding do shared_examples 'a Broadcast Messages' do |type| it 'shows broadcast message' do - visit root_path + visit explore_projects_path expect(page).to have_content 'SampleMessage' end @@ -15,17 +15,17 @@ RSpec.describe 'Broadcast Messages', feature_category: :onboarding do it 'renders styled links' do create(:broadcast_message, type, message: "<a href='gitlab.com' style='color: purple'>click me</a>") - visit root_path + visit explore_projects_path expected_html = "<p><a href=\"gitlab.com\" style=\"color: purple\">click me</a></p>" expect(page.body).to include(expected_html) end end - shared_examples 'a dismissable Broadcast Messages' do + shared_examples 'a dismissible Broadcast Messages' do it 'hides broadcast message after dismiss', :js, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/390900' do - visit root_path + visit explore_projects_path find('.js-dismiss-current-broadcast-notification').click @@ -34,25 +34,25 @@ RSpec.describe 'Broadcast Messages', feature_category: :onboarding do it 'broadcast message is still hidden after refresh', :js, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/391406' do - visit root_path + visit explore_projects_path find('.js-dismiss-current-broadcast-notification').click wait_for_cookie_set("hide_broadcast_message_#{broadcast_message.id}") - visit root_path + visit explore_projects_path expect(page).not_to have_content 'SampleMessage' end end describe 'banner type' do - let!(:broadcast_message) { create(:broadcast_message, message: 'SampleMessage') } + let_it_be(:broadcast_message) { create(:broadcast_message, message: 'SampleMessage') } it_behaves_like 'a Broadcast Messages' - it 'is not dismissable' do - visit root_path + it 'is not dismissible' do + visit explore_projects_path expect(page).not_to have_selector('.js-dismiss-current-broadcast-notification') end @@ -62,33 +62,33 @@ RSpec.describe 'Broadcast Messages', feature_category: :onboarding do sign_in(user) - visit root_path + visit explore_projects_path expect(page).to have_content 'Hi {{name}}' end end - describe 'dismissable banner type' do - let!(:broadcast_message) { create(:broadcast_message, dismissable: true, message: 'SampleMessage') } + describe 'dismissible banner type' do + let_it_be(:broadcast_message) { create(:broadcast_message, dismissable: true, message: 'SampleMessage') } it_behaves_like 'a Broadcast Messages' - it_behaves_like 'a dismissable Broadcast Messages' + it_behaves_like 'a dismissible Broadcast Messages' end describe 'notification type' do - let!(:broadcast_message) { create(:broadcast_message, :notification, message: 'SampleMessage') } + let_it_be(:broadcast_message) { create(:broadcast_message, :notification, message: 'SampleMessage') } it_behaves_like 'a Broadcast Messages', :notification - it_behaves_like 'a dismissable Broadcast Messages' + it_behaves_like 'a dismissible Broadcast Messages' it 'replaces placeholders' do create(:broadcast_message, :notification, message: 'Hi {{name}}') sign_in(user) - visit root_path + visit explore_projects_path expect(page).to have_content "Hi #{user.name}" end diff --git a/spec/features/calendar_spec.rb b/spec/features/calendar_spec.rb index b2a29c88b68..67baed5dc91 100644 --- a/spec/features/calendar_spec.rb +++ b/spec/features/calendar_spec.rb @@ -44,15 +44,25 @@ RSpec.describe 'Contributions Calendar', :js, feature_category: :user_profile do "#{get_cell_level_selector(contributions)}[title='#{contribution_text}<br /><span class=\"gl-text-gray-300\">#{date}</span>']" end + def get_days_of_week + page.all('[data-testid="user-contrib-cell-group"]')[1] + .all('[data-testid="user-contrib-cell"]') + .map do |node| + node[:title].match(/(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)/)[0] + end + end + def push_code_contribution event = create(:push_event, project: contributed_project, author: user) - create(:push_event_payload, - event: event, - commit_from: '11f9ac0a48b62cef25eedede4c1819964f08d5ce', - commit_to: '1cf19a015df3523caf0a1f9d40c98a267d6a2fc2', - commit_count: 3, - ref: 'master') + create( + :push_event_payload, + event: event, + commit_from: '11f9ac0a48b62cef25eedede4c1819964f08d5ce', + commit_to: '1cf19a015df3523caf0a1f9d40c98a267d6a2fc2', + commit_count: 3, + ref: 'master' + ) end def note_comment_contribution @@ -70,162 +80,331 @@ RSpec.describe 'Contributions Calendar', :js, feature_category: :user_profile do find('#js-overview .user-calendar-activities', visible: visible).text end - before do - stub_feature_flags(profile_tabs_vue: false) - sign_in user - end - - describe 'calendar day selection' do + shared_context 'when user page is visited' do before do visit user.username - page.find('.js-overview-tab a').click + page.click_link('Overview') wait_for_requests end + end - it 'displays calendar' do - expect(find('#js-overview')).to have_css('.js-contrib-calendar') + context 'with `profile_tabs_vue` feature flag disabled' do + before do + stub_feature_flags(profile_tabs_vue: false) + sign_in user end - describe 'select calendar day' do - let(:cells) { page.all('#js-overview .user-contrib-cell') } + describe 'calendar day selection' do + include_context 'when user page is visited' - before do - cells[0].click - wait_for_requests - @first_day_activities = selected_day_activities + it 'displays calendar' do + expect(find('#js-overview')).to have_css('.js-contrib-calendar') end - it 'displays calendar day activities' do - expect(selected_day_activities).not_to be_empty - end + describe 'select calendar day' do + let(:cells) { page.all('#js-overview .user-contrib-cell') } - describe 'select another calendar day' do before do - cells[1].click + cells[0].click wait_for_requests end - it 'displays different calendar day activities' do - expect(selected_day_activities).not_to eq(@first_day_activities) + it 'displays calendar day activities' do + expect(selected_day_activities).not_to be_empty end - end - describe 'deselect calendar day' do - before do - cells[0].click - wait_for_requests - cells[0].click + describe 'select another calendar day' do + it 'displays different calendar day activities' do + first_day_activities = selected_day_activities + + cells[1].click + wait_for_requests + + expect(selected_day_activities).not_to eq(first_day_activities) + end end - it 'hides calendar day activities' do - expect(selected_day_activities(visible: false)).to be_empty + describe 'deselect calendar day' do + before do + cells[0].click + wait_for_requests + cells[0].click + end + + it 'hides calendar day activities' do + expect(selected_day_activities(visible: false)).to be_empty + end end end end - end - shared_context 'visit user page' do - before do - visit user.username - page.find('.js-overview-tab a').click - wait_for_requests - end - end + describe 'calendar daily activities' do + shared_examples 'a day with activity' do |contribution_count:| + include_context 'when user page is visited' + + it 'displays calendar activity square for 1 contribution', :sidekiq_inline do + expect(find('#js-overview')).to have_selector(get_cell_level_selector(contribution_count), count: 1) + + today = Date.today.strftime(date_format) + expect(find('#js-overview')).to have_selector(get_cell_date_selector(contribution_count, today), count: 1) + end + end - describe 'calendar daily activities' do - shared_examples 'a day with activity' do |contribution_count:| - include_context 'visit user page' + describe '1 issue and 1 work item creation calendar activity' do + before do + Issues::CreateService.new( + container: contributed_project, + current_user: user, + params: issue_params, + spam_params: nil + ).execute + WorkItems::CreateService.new( + container: contributed_project, + current_user: user, + params: { title: 'new task' }, + spam_params: nil + ).execute + end - it 'displays calendar activity square for 1 contribution', :sidekiq_might_not_need_inline do - expect(find('#js-overview')).to have_selector(get_cell_level_selector(contribution_count), count: 1) + it_behaves_like 'a day with activity', contribution_count: 2 - today = Date.today.strftime(date_format) - expect(find('#js-overview')).to have_selector(get_cell_date_selector(contribution_count, today), count: 1) + describe 'issue title is shown on activity page' do + include_context 'when user page is visited' + + it 'displays calendar activity log', :sidekiq_inline do + expect(all('#js-overview .overview-content-list .event-target-title').map(&:text)).to contain_exactly( + match(/#{issue_title}/), + match(/new task/) + ) + end + end end - end - describe '1 issue and 1 work item creation calendar activity' do - before do - Issues::CreateService.new(container: contributed_project, current_user: user, params: issue_params, spam_params: nil).execute - WorkItems::CreateService.new( - container: contributed_project, - current_user: user, - params: { title: 'new task' }, - spam_params: nil - ).execute + describe '1 comment calendar activity' do + before do + note_comment_contribution + end + + it_behaves_like 'a day with activity', contribution_count: 1 end - it_behaves_like 'a day with activity', contribution_count: 2 + describe '10 calendar activities' do + before do + 10.times { push_code_contribution } + end + + it_behaves_like 'a day with activity', contribution_count: 10 + end + + describe 'calendar activity on two days' do + before do + push_code_contribution + + travel_to(Date.yesterday) do + Issues::CreateService.new( + container: contributed_project, + current_user: user, + params: issue_params, + spam_params: nil + ).execute + end + end + + include_context 'when user page is visited' - describe 'issue title is shown on activity page' do - include_context 'visit user page' + it 'displays calendar activity squares for both days', :sidekiq_inline do + expect(find('#js-overview')).to have_selector(get_cell_level_selector(1), count: 2) + end - it 'displays calendar activity log', :sidekiq_inline do - expect(all('#js-overview .overview-content-list .event-target-title').map(&:text)).to contain_exactly( - match(/#{issue_title}/), - match(/new task/) - ) + it 'displays calendar activity square for yesterday', :sidekiq_inline do + yesterday = Date.yesterday.strftime(date_format) + expect(find('#js-overview')).to have_selector(get_cell_date_selector(1, yesterday), count: 1) + end + + it 'displays calendar activity square for today' do + today = Date.today.strftime(date_format) + expect(find('#js-overview')).to have_selector(get_cell_date_selector(1, today), count: 1) end end end - describe '1 comment calendar activity' do - before do - note_comment_contribution + describe 'on smaller screens' do + shared_examples 'hidden activity calendar' do + include_context 'when user page is visited' + + it 'hides the activity calender' do + expect(find('#js-overview')).not_to have_css('.js-contrib-calendar') + end end - it_behaves_like 'a day with activity', contribution_count: 1 + context 'when screen size is xs' do + before do + resize_screen_xs + end + + it_behaves_like 'hidden activity calendar' + end end - describe '10 calendar activities' do - before do - 10.times { push_code_contribution } + describe 'first_day_of_week setting' do + context 'when first day of the week is set to Monday' do + before do + stub_application_setting(first_day_of_week: 1) + end + + include_context 'when user page is visited' + + it 'shows calendar with Monday as the first day of the week' do + expect(get_days_of_week).to eq(%w[Monday Tuesday Wednesday Thursday Friday Saturday Sunday]) + end + end + + context 'when first day of the week is set to Sunday' do + before do + stub_application_setting(first_day_of_week: 0) + end + + include_context 'when user page is visited' + + it 'shows calendar with Sunday as the first day of the week' do + expect(get_days_of_week).to eq(%w[Sunday Monday Tuesday Wednesday Thursday Friday Saturday]) + end end + end + end + + context 'with `profile_tabs_vue` feature flag enabled' do + before do + sign_in user + end - it_behaves_like 'a day with activity', contribution_count: 10 + include_context 'when user page is visited' + + it 'displays calendar' do + expect(page).to have_css('[data-testid="contrib-calendar"]') end - describe 'calendar activity on two days' do - before do - push_code_contribution + describe 'calendar daily activities' do + shared_examples 'a day with activity' do |contribution_count:| + include_context 'when user page is visited' - travel_to(Date.yesterday) do - Issues::CreateService.new(container: contributed_project, current_user: user, params: issue_params, spam_params: nil).execute + it 'displays calendar activity square for 1 contribution', :sidekiq_inline do + expect(page).to have_selector(get_cell_level_selector(contribution_count), count: 1) + + today = Date.today.strftime(date_format) + expect(page).to have_selector(get_cell_date_selector(contribution_count, today), count: 1) end end - include_context 'visit user page' - it 'displays calendar activity squares for both days', :sidekiq_might_not_need_inline do - expect(find('#js-overview')).to have_selector(get_cell_level_selector(1), count: 2) + describe '1 issue and 1 work item creation calendar activity' do + before do + Issues::CreateService.new( + container: contributed_project, + current_user: user, + params: issue_params, + spam_params: nil + ).execute + WorkItems::CreateService.new( + container: contributed_project, + current_user: user, + params: { title: 'new task' }, + spam_params: nil + ).execute + end + + it_behaves_like 'a day with activity', contribution_count: 2 end - it 'displays calendar activity square for yesterday', :sidekiq_might_not_need_inline do - yesterday = Date.yesterday.strftime(date_format) - expect(find('#js-overview')).to have_selector(get_cell_date_selector(1, yesterday), count: 1) + describe '1 comment calendar activity' do + before do + note_comment_contribution + end + + it_behaves_like 'a day with activity', contribution_count: 1 end - it 'displays calendar activity square for today' do - today = Date.today.strftime(date_format) - expect(find('#js-overview')).to have_selector(get_cell_date_selector(1, today), count: 1) + describe '10 calendar activities' do + before do + 10.times { push_code_contribution } + end + + it_behaves_like 'a day with activity', contribution_count: 10 + end + + describe 'calendar activity on two days' do + before do + push_code_contribution + + travel_to(Date.yesterday) do + Issues::CreateService.new( + container: contributed_project, + current_user: user, + params: issue_params, + spam_params: nil + ).execute + end + end + + include_context 'when user page is visited' + + it 'displays calendar activity squares for both days', :sidekiq_inline do + expect(page).to have_selector(get_cell_level_selector(1), count: 2) + end + + it 'displays calendar activity square for yesterday', :sidekiq_inline do + yesterday = Date.yesterday.strftime(date_format) + expect(page).to have_selector(get_cell_date_selector(1, yesterday), count: 1) + end + + it 'displays calendar activity square for today' do + today = Date.today.strftime(date_format) + expect(page).to have_selector(get_cell_date_selector(1, today), count: 1) + end end end - end - describe 'on smaller screens' do - shared_examples 'hidden activity calendar' do - include_context 'visit user page' + describe 'on smaller screens' do + shared_examples 'hidden activity calendar' do + include_context 'when user page is visited' - it 'hides the activity calender' do - expect(find('#js-overview')).not_to have_css('.js-contrib-calendar') + it 'hides the activity calender' do + expect(page).not_to have_css('[data-testid="contrib-calendar"]') + end + end + + context 'when screen size is xs' do + before do + resize_screen_xs + end + + it_behaves_like 'hidden activity calendar' end end - context 'size xs' do - before do - resize_screen_xs + describe 'first_day_of_week setting' do + context 'when first day of the week is set to Monday' do + before do + stub_application_setting(first_day_of_week: 1) + end + + include_context 'when user page is visited' + + it 'shows calendar with Monday as the first day of the week' do + expect(get_days_of_week).to eq(%w[Monday Tuesday Wednesday Thursday Friday Saturday Sunday]) + end end - it_behaves_like 'hidden activity calendar' + context 'when first day of the week is set to Sunday' do + before do + stub_application_setting(first_day_of_week: 0) + end + + include_context 'when user page is visited' + + it 'shows calendar with Sunday as the first day of the week' do + expect(get_days_of_week).to eq(%w[Sunday Monday Tuesday Wednesday Thursday Friday Saturday]) + end + end end end end diff --git a/spec/features/callouts/registration_enabled_spec.rb b/spec/features/callouts/registration_enabled_spec.rb index 15c900592a1..3282a40854d 100644 --- a/spec/features/callouts/registration_enabled_spec.rb +++ b/spec/features/callouts/registration_enabled_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Registration enabled callout', feature_category: :authentication_and_authorization do +RSpec.describe 'Registration enabled callout', feature_category: :system_access do let_it_be(:admin) { create(:admin) } let_it_be(:non_admin) { create(:user) } let_it_be(:project) { create(:project) } diff --git a/spec/features/canonical_link_spec.rb b/spec/features/canonical_link_spec.rb index d8f9a7584e7..0ed76c30ce4 100644 --- a/spec/features/canonical_link_spec.rb +++ b/spec/features/canonical_link_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'Canonical link', feature_category: :remote_development do - include Spec::Support::Helpers::Features::CanonicalLinkHelpers + include Features::CanonicalLinkHelpers let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project, :public, namespace: user.namespace) } diff --git a/spec/features/clusters/cluster_detail_page_spec.rb b/spec/features/clusters/cluster_detail_page_spec.rb index e8fb5f4105d..31dec5e38da 100644 --- a/spec/features/clusters/cluster_detail_page_spec.rb +++ b/spec/features/clusters/cluster_detail_page_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Clusterable > Show page', feature_category: :kubernetes_management do +RSpec.describe 'Clusterable > Show page', feature_category: :deployment_management do include KubernetesHelpers let(:current_user) { create(:user) } diff --git a/spec/features/clusters/cluster_health_dashboard_spec.rb b/spec/features/clusters/cluster_health_dashboard_spec.rb index b557f803a99..e932f8c6b98 100644 --- a/spec/features/clusters/cluster_health_dashboard_spec.rb +++ b/spec/features/clusters/cluster_health_dashboard_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'Cluster Health board', :js, :kubeclient, :use_clean_rails_memory_store_caching, :sidekiq_inline, -feature_category: :kubernetes_management do +feature_category: :deployment_management do include KubernetesHelpers include PrometheusHelpers @@ -13,6 +13,8 @@ feature_category: :kubernetes_management do let_it_be(:cluster_path) { project_cluster_path(clusterable, cluster) } before do + stub_feature_flags(remove_monitor_metrics: false) + clusterable.add_maintainer(current_user) sign_in(current_user) @@ -28,6 +30,24 @@ feature_category: :kubernetes_management do expect(page).to have_css('.cluster-health-graphs') end + context 'feature remove_monitor_metrics enabled' do + before do + stub_feature_flags(remove_monitor_metrics: true) + end + + it 'does not show the cluster health tab' do + visit cluster_path + + expect(page).not_to have_text('Health') + end + + it 'does not show the cluster health section' do + visit project_cluster_path(clusterable, cluster, { tab: 'health' }) + + expect(page).not_to have_text('you must first enable Prometheus in the Integrations tab') + end + end + context 'no prometheus available' do it 'shows enable Prometheus message' do visit cluster_path diff --git a/spec/features/clusters/create_agent_spec.rb b/spec/features/clusters/create_agent_spec.rb index 01902c36e99..93a49151978 100644 --- a/spec/features/clusters/create_agent_spec.rb +++ b/spec/features/clusters/create_agent_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Cluster agent registration', :js, feature_category: :kubernetes_management do +RSpec.describe 'Cluster agent registration', :js, feature_category: :deployment_management do let_it_be(:project) { create(:project, :custom_repo, files: { '.gitlab/agents/example-agent-1/config.yaml' => '' }) } let_it_be(:current_user) { create(:user, maintainer_projects: [project]) } let_it_be(:token) { Devise.friendly_token } diff --git a/spec/features/commit_spec.rb b/spec/features/commit_spec.rb index a9672569a4a..dd96b763e55 100644 --- a/spec/features/commit_spec.rb +++ b/spec/features/commit_spec.rb @@ -6,7 +6,7 @@ RSpec.describe 'Commit', feature_category: :source_code_management do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { create(:user) } - describe "single commit view" do + shared_examples "single commit view" do let(:commit) do project.repository.commits(nil, limit: 100).find do |commit| commit.diffs.size > 1 @@ -16,7 +16,6 @@ RSpec.describe 'Commit', feature_category: :source_code_management do let(:files) { commit.diffs.diff_files.to_a } before do - stub_feature_flags(async_commit_diff_files: false) project.add_maintainer(user) sign_in(user) end @@ -28,15 +27,9 @@ RSpec.describe 'Commit', feature_category: :source_code_management do visit project_commit_path(project, commit) end - it "shows the short commit message" do + it "shows the short commit message, number of total changes and stats", :js, :aggregate_failures do expect(page).to have_content(commit.title) - end - - it "reports the correct number of total changes" do expect(page).to have_content("Changes #{commit.diffs.size}") - end - - it 'renders diff stats', :js do expect(page).to have_selector(".diff-stats") end @@ -50,23 +43,36 @@ RSpec.describe 'Commit', feature_category: :source_code_management do visit project_commit_path(project, commit) end - it "shows an adjusted count for changed files on this page", :js do - expect(page).to have_content("Showing 1 changed file") + def diff_files_on_page + page.all('.files .diff-file').pluck(:id) end - it "shows only the first diff on the first page" do - expect(page).to have_selector(".files ##{files[0].file_hash}") - expect(page).not_to have_selector(".files ##{files[1].file_hash}") - end + it "shows paginated content and controls to navigate", :js, :aggregate_failures do + expect(page).to have_content("Showing 1 changed file") + + wait_for_requests + + expect(diff_files_on_page).to eq([files[0].file_hash]) - it "can navigate to the second page" do within(".files .gl-pagination") do click_on("2") end - expect(page).not_to have_selector(".files ##{files[0].file_hash}") - expect(page).to have_selector(".files ##{files[1].file_hash}") + wait_for_requests + + expect(diff_files_on_page).to eq([files[1].file_hash]) end end end + + it_behaves_like "single commit view" + + context "when super sidebar is enabled" do + before do + user.update!(use_new_navigation: true) + stub_feature_flags(super_sidebar_nav: true) + end + + it_behaves_like "single commit view" + end end diff --git a/spec/features/commits/user_uses_quick_actions_spec.rb b/spec/features/commits/user_uses_quick_actions_spec.rb index 6d043a0bb2f..c83a30c99c3 100644 --- a/spec/features/commits/user_uses_quick_actions_spec.rb +++ b/spec/features/commits/user_uses_quick_actions_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'Commit > User uses quick actions', :js, feature_category: :source_code_management do - include Spec::Support::Helpers::Features::NotesHelpers + include Features::NotesHelpers include RepoHelpers let(:project) { create(:project, :public, :repository) } diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb index eafe74f4b0b..c38ae0c2b0d 100644 --- a/spec/features/commits_spec.rb +++ b/spec/features/commits_spec.rb @@ -107,7 +107,7 @@ RSpec.describe 'Commits', feature_category: :source_code_management do describe 'Cancel all builds' do it 'cancels commit', :js, :sidekiq_might_not_need_inline do visit pipeline_path(pipeline) - click_on 'Cancel running' + click_on 'Cancel pipeline' expect(page).to have_content 'canceled' end end @@ -133,7 +133,7 @@ RSpec.describe 'Commits', feature_category: :source_code_management do expect(page).to have_content pipeline.sha[0..7] expect(page).to have_content pipeline.git_commit_message.gsub!(/\s+/, ' ') expect(page).to have_content pipeline.user.name - expect(page).not_to have_link('Cancel running') + expect(page).not_to have_link('Cancel pipeline') expect(page).not_to have_link('Retry') end @@ -156,7 +156,7 @@ RSpec.describe 'Commits', feature_category: :source_code_management do expect(page).to have_content pipeline.git_commit_message.gsub!(/\s+/, ' ') expect(page).to have_content pipeline.user.name - expect(page).not_to have_link('Cancel running') + expect(page).not_to have_link('Cancel pipeline') expect(page).not_to have_link('Retry') end end @@ -165,10 +165,24 @@ RSpec.describe 'Commits', feature_category: :source_code_management do context 'viewing commits for a branch' do let(:branch_name) { 'master' } + let(:ref_selector) { '.ref-selector' } + let(:ref_with_hash) { 'ref-#-hash' } + + def switch_ref_to(ref_name) + first(ref_selector).click + wait_for_requests + + page.within ref_selector do + fill_in 'Search by Git revision', with: ref_name + wait_for_requests + find('li', text: ref_name, match: :prefer_exact).click + end + end before do project.add_maintainer(user) sign_in(user) + project.repository.create_branch(ref_with_hash, branch_name) visit project_commits_path(project, branch_name) end @@ -180,11 +194,17 @@ RSpec.describe 'Commits', feature_category: :source_code_management do end end + it 'switches ref to ref containing a hash', :js do + switch_ref_to(ref_with_hash) + + expect(page).to have_selector ref_selector, text: ref_with_hash + end + it 'shows the ref switcher with the multi-file editor enabled', :js do set_cookie('new_repo', 'true') visit project_commits_path(project, branch_name) - expect(find('.ref-selector')).to have_content branch_name + expect(find(ref_selector)).to have_content branch_name end end diff --git a/spec/features/contextual_sidebar_spec.rb b/spec/features/contextual_sidebar_spec.rb index 2b671d4b3f1..132c8eb7192 100644 --- a/spec/features/contextual_sidebar_spec.rb +++ b/spec/features/contextual_sidebar_spec.rb @@ -39,74 +39,5 @@ RSpec.describe 'Contextual sidebar', :js, feature_category: :remote_development expect(page).to have_selector('.is-showing-fly-out') end end - - context 'with invite_members_in_side_nav experiment', :experiment do - it 'allows opening of modal for the candidate experience' do - stub_experiments(invite_members_in_side_nav: :candidate) - expect(experiment(:invite_members_in_side_nav)).to track(:assignment) - .with_context(group: project.group) - .on_next_instance - - visit project_path(project) - - page.within '[data-test-id="side-nav-invite-members"' do - find('[data-test-id="invite-members-button"').click - end - - expect(page).to have_content("You're inviting members to the") - end - - it 'does not have invite members link in side nav for the control experience' do - stub_experiments(invite_members_in_side_nav: :control) - expect(experiment(:invite_members_in_side_nav)).to track(:assignment) - .with_context(group: project.group) - .on_next_instance - - visit project_path(project) - - expect(page).not_to have_css('[data-test-id="side-nav-invite-members"') - end - end - end - - context 'when context is a group' do - let_it_be(:user) { create(:user) } - let_it_be(:group) do - create(:group).tap do |g| - g.add_owner(user) - end - end - - before do - sign_in(user) - end - - context 'with invite_members_in_side_nav experiment', :experiment do - it 'allows opening of modal for the candidate experience' do - stub_experiments(invite_members_in_side_nav: :candidate) - expect(experiment(:invite_members_in_side_nav)).to track(:assignment) - .with_context(group: group) - .on_next_instance - - visit group_path(group) - - page.within '[data-test-id="side-nav-invite-members"' do - find('[data-test-id="invite-members-button"').click - end - - expect(page).to have_content("You're inviting members to the") - end - - it 'does not have invite members link in side nav for the control experience' do - stub_experiments(invite_members_in_side_nav: :control) - expect(experiment(:invite_members_in_side_nav)).to track(:assignment) - .with_context(group: group) - .on_next_instance - - visit group_path(group) - - expect(page).not_to have_css('[data-test-id="side-nav-invite-members"') - end - end end end diff --git a/spec/features/cycle_analytics_spec.rb b/spec/features/cycle_analytics_spec.rb index 55bf77d00b1..56272f58e0d 100644 --- a/spec/features/cycle_analytics_spec.rb +++ b/spec/features/cycle_analytics_spec.rb @@ -47,12 +47,12 @@ RSpec.describe 'Value Stream Analytics', :js, feature_category: :value_stream_ma end it 'displays metrics with relevant values' do - expect(metrics_values).to eq(['-'] * 4) + expect(metrics_values).to eq(['-'] * 3) end it 'shows active stage with empty message' do expect(page).to have_selector('.gl-path-active-item-indigo', text: 'Issue') - expect(page).to have_content("We don't have enough data to show this stage.") + expect(page).to have_content("There are 0 items to show in this stage, for these filters, within this time range.") end end @@ -98,7 +98,6 @@ RSpec.describe 'Value Stream Analytics', :js, feature_category: :value_stream_ma aggregate_failures 'with relevant values' do expect(metrics_tiles).to have_content('Commit') expect(metrics_tiles).to have_content('Deploy') - expect(metrics_tiles).to have_content('Deployment Frequency') expect(metrics_tiles).to have_content('New Issue') end end @@ -132,11 +131,11 @@ RSpec.describe 'Value Stream Analytics', :js, feature_category: :value_stream_ma end it 'can filter the metrics by date' do - expect(metrics_values).to match_array(%w[21 2 1 0]) + expect(metrics_values).to match_array(%w[21 2 1]) set_daterange(from, to) - expect(metrics_values).to eq(['-'] * 4) + expect(metrics_values).to eq(['-'] * 3) end it 'can sort records', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/338332' do diff --git a/spec/features/dashboard/activity_spec.rb b/spec/features/dashboard/activity_spec.rb index 2f9b7bb7e0f..2345e4be722 100644 --- a/spec/features/dashboard/activity_spec.rb +++ b/spec/features/dashboard/activity_spec.rb @@ -9,7 +9,7 @@ RSpec.describe 'Dashboard > Activity', feature_category: :user_profile do sign_in(user) end - it_behaves_like 'a dashboard page with sidebar', :activity_dashboard_path, :activity + it_behaves_like 'a "Your work" page with sidebar and breadcrumbs', :activity_dashboard_path, :activity context 'tabs' do it 'shows Your Activity' do diff --git a/spec/features/dashboard/group_dashboard_with_external_authorization_service_spec.rb b/spec/features/dashboard/group_dashboard_with_external_authorization_service_spec.rb index f5b02a87758..3040c97a16f 100644 --- a/spec/features/dashboard/group_dashboard_with_external_authorization_service_spec.rb +++ b/spec/features/dashboard/group_dashboard_with_external_authorization_service_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe 'The group dashboard', :js, feature_category: :subgroups do include ExternalAuthorizationServiceHelpers - include Spec::Support::Helpers::Features::TopNavSpecHelpers + include Features::TopNavSpecHelpers let(:user) { create(:user) } @@ -21,9 +21,8 @@ RSpec.describe 'The group dashboard', :js, feature_category: :subgroups do within_top_nav do expect(page).to have_button('Projects') expect(page).to have_button('Groups') - expect(page).to have_link('Activity') - expect(page).to have_link('Milestones') - expect(page).to have_link('Snippets') + expect(page).to have_link('Your work') + expect(page).to have_link('Explore') end end @@ -36,9 +35,8 @@ RSpec.describe 'The group dashboard', :js, feature_category: :subgroups do within_top_nav do expect(page).to have_button('Projects') expect(page).to have_button('Groups') - expect(page).not_to have_link('Activity') - expect(page).not_to have_link('Milestones') - expect(page).to have_link('Snippets') + expect(page).to have_link('Your work') + expect(page).to have_link('Explore') end end end diff --git a/spec/features/dashboard/groups_list_spec.rb b/spec/features/dashboard/groups_list_spec.rb index a45e0a58ed6..7112b30957a 100644 --- a/spec/features/dashboard/groups_list_spec.rb +++ b/spec/features/dashboard/groups_list_spec.rb @@ -19,7 +19,7 @@ RSpec.describe 'Dashboard Groups page', :js, feature_category: :subgroups do page.find("[data-testid='group-#{group.id}-dropdown-button'").click end - it_behaves_like 'a dashboard page with sidebar', :dashboard_groups_path, :groups + it_behaves_like 'a "Your work" page with sidebar and breadcrumbs', :dashboard_groups_path, :groups it 'shows groups user is member of' do group.add_owner(user) @@ -230,4 +230,11 @@ RSpec.describe 'Dashboard Groups page', :js, feature_category: :subgroups do expect(page).not_to have_content(another_group.name) end end + + it 'links to the "Explore groups" page' do + sign_in(user) + visit dashboard_groups_path + + expect(page).to have_link("Explore groups", href: explore_groups_path) + end end diff --git a/spec/features/dashboard/issuables_counter_spec.rb b/spec/features/dashboard/issuables_counter_spec.rb index 5dc59cfa841..5e6ec007569 100644 --- a/spec/features/dashboard/issuables_counter_spec.rb +++ b/spec/features/dashboard/issuables_counter_spec.rb @@ -14,7 +14,7 @@ RSpec.describe 'Navigation bar counter', :use_clean_rails_memory_store_caching, sign_in(user) end - it 'reflects dashboard issues count' do + it 'reflects dashboard issues count', :js do visit issues_path expect_counters('issues', '1', n_("%d assigned issue", "%d assigned issues", 1) % 1) diff --git a/spec/features/dashboard/issues_filter_spec.rb b/spec/features/dashboard/issues_filter_spec.rb index a7734ed50c2..964ac2f714d 100644 --- a/spec/features/dashboard/issues_filter_spec.rb +++ b/spec/features/dashboard/issues_filter_spec.rb @@ -3,48 +3,60 @@ require 'spec_helper' RSpec.describe 'Dashboard Issues filtering', :js, feature_category: :team_planning do - include Spec::Support::Helpers::Features::SortingHelpers + include Features::SortingHelpers include FilteredSearchHelpers - let(:user) { create(:user) } - let(:project) { create(:project) } - let(:milestone) { create(:milestone, project: project) } + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project) } + let_it_be(:milestone) { create(:milestone, project: project) } - let!(:issue) { create(:issue, project: project, author: user, assignees: [user]) } - let!(:issue2) { create(:issue, project: project, author: user, assignees: [user], milestone: milestone) } + let_it_be(:issue) { create(:issue, project: project, author: user, assignees: [user]) } + let_it_be(:issue2) { create(:issue, project: project, author: user, assignees: [user], milestone: milestone) } + let_it_be(:label) { create(:label, project: project, title: 'bug') } + let_it_be(:label_link) { create(:label_link, label: label, target: issue) } + + let_it_be(:project2) { create(:project, namespace: user.namespace) } + let_it_be(:label2) { create(:label, title: 'bug') } before do + project.labels << label + project2.labels << label2 project.add_maintainer(user) sign_in(user) - - visit_issues end context 'without any filter' do it 'shows error message' do + visit issues_dashboard_path + expect(page).to have_content 'Please select at least one filter to see results' end end context 'filtering by milestone' do it 'shows all issues with no milestone' do - input_filtered_search("milestone:=none") + visit issues_dashboard_path + + select_tokens 'Milestone', '=', 'None', submit: true expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) expect(page).to have_selector('.issue', count: 1) end it 'shows all issues with the selected milestone' do - input_filtered_search("milestone:=%\"#{milestone.title}\"") + visit issues_dashboard_path + + select_tokens 'Milestone', '=', milestone.title, submit: true expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) expect(page).to have_selector('.issue', count: 1) end it 'updates atom feed link' do - visit_issues(milestone_title: '', assignee_username: user.username) + visit issues_dashboard_path(milestone_title: '', assignee_username: user.username) + click_button 'Actions' - link = find('[data-testid="rss-feed-link"]') + link = find_link('Subscribe to RSS feed') params = CGI.parse(URI.parse(link[:href]).query) auto_discovery_link = find('link[type="application/atom+xml"]', visible: false) auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query) @@ -59,40 +71,47 @@ RSpec.describe 'Dashboard Issues filtering', :js, feature_category: :team_planni end context 'filtering by label' do - let(:label) { create(:label, project: project) } - let!(:label_link) { create(:label_link, label: label, target: issue) } + before do + visit issues_dashboard_path + end it 'shows all issues with the selected label' do - input_filtered_search("label:=~#{label.title}") + select_tokens 'Label', '=', label.title, submit: true - page.within 'ul.content-list' do - expect(page).to have_content issue.title - expect(page).not_to have_content issue2.title - end + expect(page).to have_content issue.title + expect(page).not_to have_content issue2.title + end + + it 'removes duplicate labels' do + select_tokens 'Label', '=' + send_keys 'bu' + + expect_suggestion('bug') + expect_suggestion_count(3) # Expect None, Any, and bug end end context 'sorting' do before do - visit_issues(assignee_username: user.username) + visit issues_dashboard_path(assignee_username: user.username) end - it 'remembers last sorting value' do - pajamas_sort_by(s_('SortOptions|Created date')) - visit_issues(assignee_username: user.username) + it 'remembers last sorting value', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/408749' do + click_button 'Created date' + click_button 'Updated date' + + visit issues_dashboard_path(assignee_username: user.username) - expect(page).to have_button('Created date') + expect(page).to have_button('Updated date') end it 'keeps sorting issues after visiting Projects Issues page' do - pajamas_sort_by(s_('SortOptions|Created date')) + click_button 'Created date' + click_button 'Due date' + visit project_issues_path(project) - expect(page).to have_button('Created date') + expect(page).to have_button('Due date') end end - - def visit_issues(...) - visit issues_dashboard_path(...) - end end diff --git a/spec/features/dashboard/issues_spec.rb b/spec/features/dashboard/issues_spec.rb index 654cc9978a7..70d9f7e5137 100644 --- a/spec/features/dashboard/issues_spec.rb +++ b/spec/features/dashboard/issues_spec.rb @@ -5,15 +5,15 @@ require 'spec_helper' RSpec.describe 'Dashboard Issues', feature_category: :team_planning do include FilteredSearchHelpers - let(:current_user) { create :user } - let(:user) { current_user } # Shared examples depend on this being available - let!(:public_project) { create(:project, :public) } - let(:project) { create(:project) } - let(:project_with_issues_disabled) { create(:project, :issues_disabled) } - let!(:authored_issue) { create :issue, author: current_user, project: project } - let!(:authored_issue_on_public_project) { create :issue, author: current_user, project: public_project } - let!(:assigned_issue) { create :issue, assignees: [current_user], project: project } - let!(:other_issue) { create :issue, project: project } + let_it_be(:current_user) { create :user } + let_it_be(:user) { current_user } # Shared examples depend on this being available + let_it_be(:public_project) { create(:project, :public) } + let_it_be(:project) { create(:project) } + let_it_be(:project_with_issues_disabled) { create(:project, :issues_disabled) } + let_it_be(:authored_issue) { create :issue, author: current_user, project: project } + let_it_be(:authored_issue_on_public_project) { create :issue, author: current_user, project: public_project } + let_it_be(:assigned_issue) { create :issue, assignees: [current_user], project: project } + let_it_be(:other_issue) { create :issue, project: project } before do [project, project_with_issues_disabled].each { |project| project.add_maintainer(current_user) } @@ -21,18 +21,18 @@ RSpec.describe 'Dashboard Issues', feature_category: :team_planning do visit issues_dashboard_path(assignee_username: current_user.username) end - it_behaves_like 'a dashboard page with sidebar', :issues_dashboard_path, :issues + it_behaves_like 'a "Your work" page with sidebar and breadcrumbs', :issues_dashboard_path, :issues - describe 'issues' do + describe 'issues', :js do it 'shows issues assigned to current user' do expect(page).to have_content(assigned_issue.title) expect(page).not_to have_content(authored_issue.title) expect(page).not_to have_content(other_issue.title) end - it 'shows issues when current user is author', :js do - reset_filters - input_filtered_search("author:=#{current_user.to_reference}") + it 'shows issues when current user is author' do + click_button 'Clear' + select_tokens 'Author', '=', current_user.to_reference, submit: true expect(page).to have_content(authored_issue.title) expect(page).to have_content(authored_issue_on_public_project.title) @@ -41,12 +41,21 @@ RSpec.describe 'Dashboard Issues', feature_category: :team_planning do end it 'state filter tabs work' do - find('#state-closed').click - expect(page).to have_current_path(issues_dashboard_url(assignee_username: current_user.username, state: 'closed'), url: true) + click_link 'Closed' + + expect(page).not_to have_content(assigned_issue.title) + expect(page).not_to have_content(authored_issue.title) + expect(page).not_to have_content(other_issue.title) end - it_behaves_like "it has an RSS button with current_user's feed token" - it_behaves_like "an autodiscoverable RSS feed with current_user's feed token" + describe 'RSS link' do + before do + click_button 'Actions' + end + + it_behaves_like "it has an RSS link with current_user's feed token" + it_behaves_like "an autodiscoverable RSS feed with current_user's feed token" + end end describe 'new issue dropdown' do diff --git a/spec/features/dashboard/label_filter_spec.rb b/spec/features/dashboard/label_filter_spec.rb deleted file mode 100644 index f116c84ff40..00000000000 --- a/spec/features/dashboard/label_filter_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'Dashboard > label filter', :js, feature_category: :team_planning do - include FilteredSearchHelpers - - let(:filtered_search) { find('.filtered-search') } - let(:filter_dropdown) { find("#js-dropdown-label .filter-dropdown") } - - let(:user) { create(:user) } - let(:project) { create(:project, name: 'test', namespace: user.namespace) } - let(:project2) { create(:project, name: 'test2', path: 'test2', namespace: user.namespace) } - let(:label) { create(:label, title: 'bug', color: '#ff0000') } - let(:label2) { create(:label, title: 'bug') } - - before do - project.labels << label - project2.labels << label2 - - sign_in(user) - visit issues_dashboard_path - - init_label_search - end - - context 'duplicate labels' do - it 'removes duplicate labels' do - filtered_search.send_keys('bu') - - expect(filter_dropdown).to have_selector('.filter-dropdown-item', text: 'bug', count: 1) - end - end -end diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb index 34bab9dffd0..d53f5affe64 100644 --- a/spec/features/dashboard/merge_requests_spec.rb +++ b/spec/features/dashboard/merge_requests_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'Dashboard Merge Requests', feature_category: :code_review_workflow do - include Spec::Support::Helpers::Features::SortingHelpers + include Features::SortingHelpers include FilteredSearchHelpers include ProjectForksHelper @@ -19,7 +19,7 @@ RSpec.describe 'Dashboard Merge Requests', feature_category: :code_review_workfl sign_in(current_user) end - it_behaves_like 'a dashboard page with sidebar', :merge_requests_dashboard_path, :merge_requests + it_behaves_like 'a "Your work" page with sidebar and breadcrumbs', :merge_requests_dashboard_path, :merge_requests it 'disables target branch filter' do visit merge_requests_dashboard_path diff --git a/spec/features/dashboard/milestones_spec.rb b/spec/features/dashboard/milestones_spec.rb index 3b197bbf009..0dd25ffaa94 100644 --- a/spec/features/dashboard/milestones_spec.rb +++ b/spec/features/dashboard/milestones_spec.rb @@ -26,7 +26,7 @@ RSpec.describe 'Dashboard > Milestones', feature_category: :team_planning do visit dashboard_milestones_path end - it_behaves_like 'a dashboard page with sidebar', :dashboard_milestones_path, :milestones + it_behaves_like 'a "Your work" page with sidebar and breadcrumbs', :dashboard_milestones_path, :milestones it 'sees milestones' do expect(page).to have_current_path dashboard_milestones_path, ignore_query: true diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb index 779fbb48ddb..32bce32ec6c 100644 --- a/spec/features/dashboard/projects_spec.rb +++ b/spec/features/dashboard/projects_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe 'Dashboard Projects', feature_category: :projects do let_it_be(:user) { create(:user) } - let_it_be(:project, reload: true) { create(:project, :repository) } + let_it_be(:project, reload: true) { create(:project, :repository, creator: build(:user)) } # ensure creator != owner to avoid N+1 false-positive let_it_be(:project2) { create(:project, :public) } before do @@ -18,7 +18,13 @@ RSpec.describe 'Dashboard Projects', feature_category: :projects do end end - it_behaves_like "a dashboard page with sidebar", :dashboard_projects_path, :projects + it_behaves_like 'a "Your work" page with sidebar and breadcrumbs', :dashboard_projects_path, :projects + + it 'links to the "Explore projects" page' do + visit dashboard_projects_path + + expect(page).to have_link("Explore projects", href: explore_projects_path) + end context 'when user has access to the project' do it 'shows role badge' do @@ -106,6 +112,8 @@ RSpec.describe 'Dashboard Projects', feature_category: :projects do end context 'when on Starred projects tab', :js do + it_behaves_like 'a "Your work" page with sidebar and breadcrumbs', :starred_dashboard_projects_path, :projects + it 'shows the empty state when there are no starred projects' do visit(starred_dashboard_projects_path) @@ -239,7 +247,7 @@ RSpec.describe 'Dashboard Projects', feature_category: :projects do create(:ci_pipeline, :with_job, status: :success, project: project, ref: project.default_branch, sha: project.commit.sha) visit dashboard_projects_path - control_count = ActiveRecord::QueryRecorder.new { visit dashboard_projects_path }.count + control = ActiveRecord::QueryRecorder.new { visit dashboard_projects_path } new_project = create(:project, :repository, name: 'new project') create(:ci_pipeline, :with_job, status: :success, project: new_project, ref: new_project.commit.sha) @@ -247,15 +255,11 @@ RSpec.describe 'Dashboard Projects', feature_category: :projects do ActiveRecord::QueryRecorder.new { visit dashboard_projects_path }.count - # There are seven known N+1 queries: https://gitlab.com/gitlab-org/gitlab/-/issues/214037 - # 1. Project#open_issues_count - # 2. Project#open_merge_requests_count - # 3. Project#forks_count - # 4. ProjectsHelper#load_pipeline_status - # 5. RendersMemberAccess#preload_max_member_access_for_collection - # 6. User#max_member_access_for_project_ids - # 7. Ci::CommitWithPipeline#last_pipeline + # There are a few known N+1 queries: https://gitlab.com/gitlab-org/gitlab/-/issues/214037 + # - User#max_member_access_for_project_ids + # - ProjectsHelper#load_pipeline_status / Ci::CommitWithPipeline#last_pipeline + # - Ci::Pipeline#detailed_status - expect { visit dashboard_projects_path }.not_to exceed_query_limit(control_count + 7) + expect { visit dashboard_projects_path }.not_to exceed_query_limit(control).with_threshold(4) end end diff --git a/spec/features/dashboard/root_explore_spec.rb b/spec/features/dashboard/root_explore_spec.rb index a232ebec68e..9e844f81a29 100644 --- a/spec/features/dashboard/root_explore_spec.rb +++ b/spec/features/dashboard/root_explore_spec.rb @@ -2,16 +2,12 @@ require 'spec_helper' -RSpec.describe 'Root explore', feature_category: :not_owned do +RSpec.describe 'Root explore', :saas, feature_category: :shared do let_it_be(:public_project) { create(:project, :public) } let_it_be(:archived_project) { create(:project, :archived) } let_it_be(:internal_project) { create(:project, :internal) } let_it_be(:private_project) { create(:project, :private) } - before do - allow(Gitlab).to receive(:com?).and_return(true) - end - context 'when logged in' do let_it_be(:user) { create(:user) } diff --git a/spec/features/dashboard/shortcuts_spec.rb b/spec/features/dashboard/shortcuts_spec.rb index 30587756505..155f7e93961 100644 --- a/spec/features/dashboard/shortcuts_spec.rb +++ b/spec/features/dashboard/shortcuts_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Dashboard shortcuts', :js, feature_category: :not_owned do +RSpec.describe 'Dashboard shortcuts', :js, feature_category: :shared do context 'logged in' do let(:user) { create(:user) } let(:project) { create(:project) } diff --git a/spec/features/dashboard/snippets_spec.rb b/spec/features/dashboard/snippets_spec.rb index ba40290d866..da985c6dc07 100644 --- a/spec/features/dashboard/snippets_spec.rb +++ b/spec/features/dashboard/snippets_spec.rb @@ -5,7 +5,14 @@ require 'spec_helper' RSpec.describe 'Dashboard snippets', feature_category: :source_code_management do let_it_be(:user) { create(:user) } - it_behaves_like 'a dashboard page with sidebar', :dashboard_snippets_path, :snippets + it_behaves_like 'a "Your work" page with sidebar and breadcrumbs', :dashboard_snippets_path, :snippets + + it 'links to the "Explore snippets" page' do + sign_in(user) + visit dashboard_snippets_path + + expect(page).to have_link("Explore snippets", href: explore_snippets_path) + end context 'when the project has snippets' do let(:project) { create(:project, :public, creator: user) } @@ -37,7 +44,8 @@ RSpec.describe 'Dashboard snippets', feature_category: :source_code_management d element = page.find('.row.empty-state') expect(element).to have_content("Code snippets") - expect(element.find('.svg-content img.js-lazy-loaded')['src']).to have_content('illustrations/snippets_empty') + expect(element.find('.svg-content img.js-lazy-loaded')['src']) + .to have_content('illustrations/empty-state/empty-snippets-md') end it 'shows new snippet button in main content area' do diff --git a/spec/features/dashboard/todos/todos_spec.rb b/spec/features/dashboard/todos/todos_spec.rb index 59bb1a452c9..d0003b69415 100644 --- a/spec/features/dashboard/todos/todos_spec.rb +++ b/spec/features/dashboard/todos/todos_spec.rb @@ -15,7 +15,7 @@ RSpec.describe 'Dashboard Todos', feature_category: :team_planning do project.add_developer(user) end - it_behaves_like 'a dashboard page with sidebar', :dashboard_todos_path, :todos + it_behaves_like 'a "Your work" page with sidebar and breadcrumbs', :dashboard_todos_path, :todos context 'User does not have todos' do before do diff --git a/spec/features/display_system_header_and_footer_bar_spec.rb b/spec/features/display_system_header_and_footer_bar_spec.rb index 22fd0987418..9b2bf0ef1fa 100644 --- a/spec/features/display_system_header_and_footer_bar_spec.rb +++ b/spec/features/display_system_header_and_footer_bar_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Display system header and footer bar', feature_category: :not_owned do +RSpec.describe 'Display system header and footer bar', feature_category: :shared do let(:header_message) { "Foo" } let(:footer_message) { "Bar" } diff --git a/spec/features/emails/issues_spec.rb b/spec/features/emails/issues_spec.rb new file mode 100644 index 00000000000..c425dad88aa --- /dev/null +++ b/spec/features/emails/issues_spec.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe "E-Mails > Issues", :js, feature_category: :team_planning do + let_it_be(:project) { create(:project_empty_repo, :public, name: 'Long Earth') } + let_it_be(:author) { create(:user, username: 'author', name: 'Sally Linsay') } + let_it_be(:current_user) { create(:user, username: 'current_user', name: 'Shi-mi') } + + before do + project.add_developer(current_user) + sign_in(current_user) + end + + describe 'assignees' do + let_it_be(:assignee) { create(:user, username: 'assignee', name: 'Joshua Valienté') } + let_it_be(:issue_without_assignee) { create(:issue, project: project, author: author, title: 'No milk today!') } + + let_it_be(:issue_with_assignee) do + create( + :issue, project: project, author: author, assignees: [assignee], + title: 'All your base are belong to us') + end + + it 'sends confirmation e-mail for assigning' do + synchronous_notifications + expect(Notify).to receive(:reassigned_issue_email) + .with(author.id, issue_without_assignee.id, [], current_user.id, nil) + .once + .and_call_original + expect(Notify).to receive(:reassigned_issue_email) + .with(assignee.id, issue_without_assignee.id, [], current_user.id, NotificationReason::ASSIGNED) + .once + .and_call_original + + visit issue_path(issue_without_assignee) + assign_to(assignee) + + expect(find('#notes-list')).to have_text("Shi-mi assigned to @assignee just now") + end + + it 'sends confirmation e-mail for reassigning' do + synchronous_notifications + expect(Notify).to receive(:reassigned_issue_email) + .with(author.id, issue_with_assignee.id, [assignee.id], current_user.id, NotificationReason::ASSIGNED) + .once + .and_call_original + expect(Notify).to receive(:reassigned_issue_email) + .with(assignee.id, issue_with_assignee.id, [assignee.id], current_user.id, nil) + .once + .and_call_original + + visit issue_path(issue_with_assignee) + assign_to(author) + + expect(find('#notes-list')).to have_text("Shi-mi assigned to @author and unassigned @assignee just now") + end + + it 'sends confirmation e-mail for unassigning' do + synchronous_notifications + expect(Notify).to receive(:reassigned_issue_email) + .with(author.id, issue_with_assignee.id, [assignee.id], current_user.id, nil) + .once + .and_call_original + expect(Notify).to receive(:reassigned_issue_email) + .with(assignee.id, issue_with_assignee.id, [assignee.id], current_user.id, nil) + .once + .and_call_original + + visit issue_path(issue_with_assignee) + quick_action('/unassign') + + expect(find('#notes-list')).to have_text("Shi-mi unassigned @assignee just now") + end + end + + describe 'closing' do + let_it_be(:issue) { create(:issue, project: project, author: author, title: 'Public Holiday') } + + it 'sends confirmation e-mail for closing' do + synchronous_notifications + expect(Notify).to receive(:closed_issue_email) + .with(author.id, issue.id, current_user.id, { closed_via: nil, reason: nil }) + .once + .and_call_original + + visit issue_path(issue) + quick_action("/close") + + expect(find('#notes-list')).to have_text("Shi-mi closed just now") + end + end + + private + + def assign_to(user) + quick_action("/assign @#{user.username}") + end + + def quick_action(command) + fill_in 'note[note]', with: command + click_button 'Comment' + end + + def synchronous_notifications + expect_next_instance_of(NotificationService) do |service| + expect(service).to receive(:async).and_return(service) + end + end +end diff --git a/spec/features/expand_collapse_diffs_spec.rb b/spec/features/expand_collapse_diffs_spec.rb index 1f09b01ddec..43dd80187ce 100644 --- a/spec/features/expand_collapse_diffs_spec.rb +++ b/spec/features/expand_collapse_diffs_spec.rb @@ -19,6 +19,8 @@ RSpec.describe 'Expand and collapse diffs', :js, feature_category: :source_code_ # Ensure that undiffable.md is in .gitattributes project.repository.copy_gitattributes(branch) visit project_commit_path(project, project.commit(branch)) + + wait_for_requests end def file_container(filename) @@ -222,10 +224,16 @@ RSpec.describe 'Expand and collapse diffs', :js, feature_category: :source_code_ let(:branch) { 'expand-collapse-files' } # safe-files -> 100 | safe-lines -> 5000 | commit-files -> 105 - it 'does collapsing from the safe number of files to the end on small files' do - expect(page).to have_link('Expand all') + it 'does collapsing from the safe number of files to the end on small files', :aggregate_failures do + expect(page).not_to have_link('Expand all') + expect(page).to have_selector('.diff-content', count: 20) + expect(page).to have_selector('.diff-collapsed', count: 0) - expect(page).to have_selector('.diff-content', count: 105) + visit project_commit_path(project, project.commit(branch), page: 6) + wait_for_requests + + expect(page).to have_link('Expand all') + expect(page).to have_selector('.diff-content', count: 5) expect(page).to have_selector('.diff-collapsed', count: 5) %w(file-95.txt file-96.txt file-97.txt file-98.txt file-99.txt).each do |filename| diff --git a/spec/features/explore/groups_spec.rb b/spec/features/explore/groups_spec.rb index 458f83dffb4..57a7e8ea523 100644 --- a/spec/features/explore/groups_spec.rb +++ b/spec/features/explore/groups_spec.rb @@ -29,8 +29,8 @@ RSpec.describe 'Explore Groups', :js, feature_category: :subgroups do shared_examples 'renders public and internal projects' do it do visit_page - expect(page).to have_content(public_project.name) - expect(page).to have_content(internal_project.name) + expect(page).to have_content(public_project.name).or(have_content(public_project.path)) + expect(page).to have_content(internal_project.name).or(have_content(internal_project.path)) expect(page).not_to have_content(private_project.name) end end @@ -38,7 +38,7 @@ RSpec.describe 'Explore Groups', :js, feature_category: :subgroups do shared_examples 'renders only public project' do it do visit_page - expect(page).to have_content(public_project.name) + expect(page).to have_content(public_project.name).or(have_content(public_project.path)) expect(page).not_to have_content(internal_project.name) expect(page).not_to have_content(private_project.name) end @@ -47,7 +47,7 @@ RSpec.describe 'Explore Groups', :js, feature_category: :subgroups do shared_examples 'renders group in public groups area' do it do visit explore_groups_path - expect(page).to have_content(group.name) + expect(page).to have_content(group.path) end end @@ -56,32 +56,44 @@ RSpec.describe 'Explore Groups', :js, feature_category: :subgroups do sign_in(user) end - it_behaves_like 'renders public and internal projects' do - subject(:visit_page) { visit group_path(group) } + context 'for group_path' do + it_behaves_like 'renders public and internal projects' do + subject(:visit_page) { visit group_path(group) } + end end - it_behaves_like 'renders public and internal projects' do - subject(:visit_page) { visit issues_group_path(group) } + context 'for issues_group_path' do + it_behaves_like 'renders public and internal projects' do + subject(:visit_page) { visit issues_group_path(group) } + end end - it_behaves_like 'renders public and internal projects' do - subject(:visit_page) { visit merge_requests_group_path(group) } + context 'for merge_requests_group_path' do + it_behaves_like 'renders public and internal projects' do + subject(:visit_page) { visit merge_requests_group_path(group) } + end end it_behaves_like 'renders group in public groups area' end context 'when signed out' do - it_behaves_like 'renders only public project' do - subject(:visit_page) { visit group_path(group) } + context 'for group_path' do + it_behaves_like 'renders only public project' do + subject(:visit_page) { visit group_path(group) } + end end - it_behaves_like 'renders only public project' do - subject(:visit_page) { visit issues_group_path(group) } + context 'for issues_group_path' do + it_behaves_like 'renders only public project' do + subject(:visit_page) { visit issues_group_path(group) } + end end - it_behaves_like 'renders only public project' do - subject(:visit_page) { visit merge_requests_group_path(group) } + context 'for merge_requests_group_path' do + it_behaves_like 'renders only public project' do + subject(:visit_page) { visit merge_requests_group_path(group) } + end end it_behaves_like 'renders group in public groups area' diff --git a/spec/features/explore/navbar_spec.rb b/spec/features/explore/navbar_spec.rb new file mode 100644 index 00000000000..8f281abe6a7 --- /dev/null +++ b/spec/features/explore/navbar_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe '"Explore" navbar', feature_category: :navigation do + include_context '"Explore" navbar structure' + + it_behaves_like 'verified navigation bar' do + before do + visit explore_projects_path + end + end +end diff --git a/spec/features/explore/user_explores_projects_spec.rb b/spec/features/explore/user_explores_projects_spec.rb index 14fddf5d84c..f259ba6a167 100644 --- a/spec/features/explore/user_explores_projects_spec.rb +++ b/spec/features/explore/user_explores_projects_spec.rb @@ -3,6 +3,18 @@ require 'spec_helper' RSpec.describe 'User explores projects', feature_category: :user_profile do + describe '"All" tab' do + it_behaves_like 'an "Explore" page with sidebar and breadcrumbs', :explore_projects_path, :projects + end + + describe '"Most starred" tab' do + it_behaves_like 'an "Explore" page with sidebar and breadcrumbs', :starred_explore_projects_path, :projects + end + + describe '"Trending" tab' do + it_behaves_like 'an "Explore" page with sidebar and breadcrumbs', :trending_explore_projects_path, :projects + end + context 'when some projects exist' do let_it_be(:archived_project) { create(:project, :archived) } let_it_be(:internal_project) { create(:project, :internal) } diff --git a/spec/features/file_uploads/project_import_spec.rb b/spec/features/file_uploads/project_import_spec.rb index c261834206d..3934e0319ad 100644 --- a/spec/features/file_uploads/project_import_spec.rb +++ b/spec/features/file_uploads/project_import_spec.rb @@ -22,6 +22,10 @@ RSpec.describe 'Upload a project export archive', :api, :js, feature_category: : ) end + before do + stub_application_setting(import_sources: ['gitlab_project']) + end + RSpec.shared_examples 'for a project export archive' do it { expect { subject }.to change { Project.count }.by(1) } diff --git a/spec/features/frequently_visited_projects_and_groups_spec.rb b/spec/features/frequently_visited_projects_and_groups_spec.rb index 50e20910e16..514b642a2d4 100644 --- a/spec/features/frequently_visited_projects_and_groups_spec.rb +++ b/spec/features/frequently_visited_projects_and_groups_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' -RSpec.describe 'Frequently visited items', :js, feature_category: :not_owned do - include Spec::Support::Helpers::Features::TopNavSpecHelpers +RSpec.describe 'Frequently visited items', :js, feature_category: :shared do + include Features::TopNavSpecHelpers let_it_be(:user) { create(:user) } diff --git a/spec/features/global_search_spec.rb b/spec/features/global_search_spec.rb index 15393ec4cd6..f94f0288f99 100644 --- a/spec/features/global_search_spec.rb +++ b/spec/features/global_search_spec.rb @@ -13,64 +13,8 @@ RSpec.describe 'Global search', :js, feature_category: :global_search do sign_in(user) end - describe 'when new_header_search feature is disabled' do + describe 'when header search' do before do - # TODO: Remove this along with feature flag #339348 - stub_feature_flags(new_header_search: false) - visit dashboard_projects_path - end - - it 'increases usage ping searches counter' do - expect(Gitlab::UsageDataCounters::SearchCounter).to receive(:count).with(:navbar_searches) - expect(Gitlab::UsageDataCounters::SearchCounter).to receive(:count).with(:all_searches) - - submit_search('foobar') - end - - describe 'I search through the issues and I see pagination' do - before do - allow_next(SearchService).to receive(:per_page).and_return(1) - create_list(:issue, 2, project: project, title: 'initial') - end - - it "has a pagination" do - submit_search('initial') - select_search_scope('Issues') - - expect(page).to have_selector('.gl-pagination .next') - end - end - - it 'closes the dropdown on blur' do - find('#search').click - fill_in 'search', with: "a" - - expect(page).to have_selector("div[data-testid='dashboard-search-options'].show") - - find('#search').send_keys(:backspace) - find('body').click - - expect(page).to have_no_selector("div[data-testid='dashboard-search-options'].show") - end - - it 'renders legacy search bar' do - expect(page).to have_selector('.search-form') - expect(page).to have_no_selector('#js-header-search') - end - - it 'focuses search input when shortcut "s" is pressed' do - expect(page).not_to have_selector('#search:focus') - - find('body').native.send_key('s') - - expect(page).to have_selector('#search:focus') - end - end - - describe 'when new_header_search feature is enabled' do - before do - # TODO: Remove this along with feature flag #339348 - stub_feature_flags(new_header_search: true) visit dashboard_projects_path end diff --git a/spec/features/group_variables_spec.rb b/spec/features/group_variables_spec.rb index 117f50aefc6..3e87c90e7dc 100644 --- a/spec/features/group_variables_spec.rb +++ b/spec/features/group_variables_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Group variables', :js, feature_category: :pipeline_authoring do +RSpec.describe 'Group variables', :js, feature_category: :secrets_management do let(:user) { create(:user) } let(:group) { create(:group) } let!(:variable) { create(:ci_group_variable, key: 'test_key', value: 'test_value', masked: true, group: group) } @@ -15,5 +15,16 @@ RSpec.describe 'Group variables', :js, feature_category: :pipeline_authoring do wait_for_requests end - it_behaves_like 'variable list' + context 'when ci_variables_pages FF is enabled' do + it_behaves_like 'variable list' + it_behaves_like 'variable list pagination', :ci_group_variable + end + + context 'when ci_variables_pages FF is disabled' do + before do + stub_feature_flags(ci_variables_pages: false) + end + + it_behaves_like 'variable list' + end end diff --git a/spec/features/groups/board_spec.rb b/spec/features/groups/board_spec.rb index c451a97bed5..8acf3ffe441 100644 --- a/spec/features/groups/board_spec.rb +++ b/spec/features/groups/board_spec.rb @@ -25,8 +25,10 @@ RSpec.describe 'Group Boards', feature_category: :team_planning do it 'adds an issue to the backlog' do page.within(find('.board', match: :first)) do - issue_title = 'New Issue' - click_button 'New issue' + dropdown = first("[data-testid='header-list-actions']") + dropdown.click + issue_title = 'Create new issue' + click_button issue_title wait_for_requests diff --git a/spec/features/groups/container_registry_spec.rb b/spec/features/groups/container_registry_spec.rb index 11f94967aaf..ab8d8238bdc 100644 --- a/spec/features/groups/container_registry_spec.rb +++ b/spec/features/groups/container_registry_spec.rb @@ -95,7 +95,11 @@ RSpec.describe 'Container Registry', :js, feature_category: :container_registry first('[data-testid="additional-actions"]').click first('[data-testid="single-delete-button"]').click expect(find('.modal .modal-title')).to have_content _('Remove tag') + stub_container_registry_tags(repository: %r{my/image}, tags: [], with_manifest: true) find('.modal .modal-footer .btn-danger').click + + expect(page).to have_content '0 tags' + expect(page).not_to have_content '1 tag' end end end diff --git a/spec/features/groups/crm/contacts/create_spec.rb b/spec/features/groups/crm/contacts/create_spec.rb index 860cadd322d..aa05ef82a8b 100644 --- a/spec/features/groups/crm/contacts/create_spec.rb +++ b/spec/features/groups/crm/contacts/create_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe 'Create a CRM contact', :js, feature_category: :service_desk do let(:user) { create(:user) } let(:group) { create(:group, :crm_enabled) } - let!(:organization) { create(:organization, group: group, name: 'GitLab') } + let!(:crm_organization) { create(:crm_organization, group: group, name: 'GitLab') } before do group.add_owner(user) diff --git a/spec/features/groups/dependency_proxy_spec.rb b/spec/features/groups/dependency_proxy_spec.rb index 05984d40ea6..60922f813df 100644 --- a/spec/features/groups/dependency_proxy_spec.rb +++ b/spec/features/groups/dependency_proxy_spec.rb @@ -52,6 +52,12 @@ RSpec.describe 'Group Dependency Proxy', feature_category: :dependency_proxy do expect(find('input[data-testid="proxy-url"]').value).to have_content('/dependency_proxy/containers') end + it 'has link to settings' do + visit path + + expect(page).to have_link s_('DependencyProxy|Configure in settings') + end + it 'hides the proxy URL when feature is disabled' do visit settings_path wait_for_requests @@ -80,6 +86,10 @@ RSpec.describe 'Group Dependency Proxy', feature_category: :dependency_proxy do it 'does not show the feature toggle but shows the proxy URL' do expect(find('input[data-testid="proxy-url"]').value).to have_content('/dependency_proxy/containers') end + + it 'does not have link to settings' do + expect(page).not_to have_link s_('DependencyProxy|Configure in settings') + end end end diff --git a/spec/features/groups/group_runners_spec.rb b/spec/features/groups/group_runners_spec.rb index ae757e04716..514110d78ae 100644 --- a/spec/features/groups/group_runners_spec.rb +++ b/spec/features/groups/group_runners_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe "Group Runners", feature_category: :runner_fleet do - include Spec::Support::Helpers::Features::RunnersHelpers + include Features::RunnersHelpers include Spec::Support::Helpers::ModalHelpers let_it_be(:group_owner) { create(:user) } @@ -16,10 +16,12 @@ RSpec.describe "Group Runners", feature_category: :runner_fleet do end describe "Group runners page", :js do - let!(:group_registration_token) { group.runners_token } + describe "legacy runners registration" do + let_it_be(:group_registration_token) { group.runners_token } - describe "runners registration" do before do + stub_feature_flags(create_runner_workflow_for_namespace: false) + visit group_runners_path(group) end @@ -60,15 +62,11 @@ RSpec.describe "Group Runners", feature_category: :runner_fleet do let(:runner) { group_runner } end - it 'shows a group badge' do - within_runner_row(group_runner.id) do - expect(page).to have_selector '.badge', text: s_('Runners|Group') - end - end - - it 'can edit runner information' do + it 'shows an editable group badge' do within_runner_row(group_runner.id) do expect(find_link('Edit')[:href]).to end_with(edit_group_runner_path(group, group_runner)) + + expect(page).to have_selector '.badge', text: s_('Runners|Group') end end @@ -102,15 +100,11 @@ RSpec.describe "Group Runners", feature_category: :runner_fleet do let(:runner) { project_runner } end - it 'shows a project badge' do - within_runner_row(project_runner.id) do - expect(page).to have_selector '.badge', text: s_('Runners|Project') - end - end - - it 'can edit runner information' do + it 'shows an editable project runner' do within_runner_row(project_runner.id) do expect(find_link('Edit')[:href]).to end_with(edit_group_runner_path(group, project_runner)) + + expect(page).to have_selector '.badge', text: s_('Runners|Project') end end end @@ -202,6 +196,16 @@ RSpec.describe "Group Runners", feature_category: :runner_fleet do end end + describe "Group runner create page", :js do + before do + visit new_group_runner_path(group) + end + + it_behaves_like 'creates runner and shows register page' do + let(:register_path_pattern) { register_group_runner_path(group, '.*') } + end + end + describe "Group runner show page", :js do let_it_be(:group_runner) do create(:ci_runner, :group, groups: [group], description: 'runner-foo') diff --git a/spec/features/groups/group_settings_spec.rb b/spec/features/groups/group_settings_spec.rb index 5510e73ef0f..6443f4a6c38 100644 --- a/spec/features/groups/group_settings_spec.rb +++ b/spec/features/groups/group_settings_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe 'Edit group settings', feature_category: :subgroups do + include Spec::Support::Helpers::ModalHelpers + let(:user) { create(:user) } let(:group) { create(:group, path: 'foo') } @@ -72,7 +74,7 @@ RSpec.describe 'Edit group settings', feature_category: :subgroups do visit new_project_full_path expect(page).to have_current_path(new_project_full_path, ignore_query: true) - expect(find('.breadcrumbs')).to have_content(project.path) + expect(find('.breadcrumbs')).to have_content(project.name) end it 'the old project path redirects to the new path' do @@ -80,7 +82,7 @@ RSpec.describe 'Edit group settings', feature_category: :subgroups do visit old_project_full_path expect(page).to have_current_path(new_project_full_path, ignore_query: true) - expect(find('.breadcrumbs')).to have_content(project.path) + expect(find('.breadcrumbs')).to have_content(project.name) end end end @@ -147,14 +149,16 @@ RSpec.describe 'Edit group settings', feature_category: :subgroups do selected_group.add_owner(user) end - it 'can successfully transfer the group', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/384966' do + it 'can successfully transfer the group' do + selected_group_path = selected_group.path + visit edit_group_path(selected_group) page.within('[data-testid="transfer-locations-dropdown"]') do click_button _('Select parent group') - fill_in _('Search'), with: target_group_name + fill_in _('Search'), with: target_group&.name || '' wait_for_requests - click_button target_group_name + click_button(target_group&.name || 'No parent group') end click_button s_('GroupSettings|Transfer group') @@ -166,8 +170,16 @@ RSpec.describe 'Edit group settings', feature_category: :subgroups do click_button 'Confirm' end - expect(page).to have_text "Group '#{selected_group.name}' was successfully transferred." - expect(current_url).to include(selected_group.reload.full_path) + within('[data-testid="breadcrumb-links"]') do + expect(page).to have_content(target_group.name) if target_group + expect(page).to have_content(selected_group.name) + end + + if target_group + expect(current_url).to include("#{target_group.path}/#{selected_group_path}") + else + expect(current_url).to include(selected_group_path) + end end end @@ -175,14 +187,13 @@ RSpec.describe 'Edit group settings', feature_category: :subgroups do let(:selected_group) { create(:group, path: 'foo-subgroup', parent: group) } context 'when transfering to no parent group' do - let(:target_group_name) { 'No parent group' } + let(:target_group) { nil } it_behaves_like 'can transfer the group' end context 'when transfering to a parent group' do let(:target_group) { create(:group, path: 'foo-parentgroup') } - let(:target_group_name) { target_group.name } before do target_group.add_owner(user) @@ -194,7 +205,7 @@ RSpec.describe 'Edit group settings', feature_category: :subgroups do context 'when transfering from a root group to a parent group' do let(:selected_group) { create(:group, path: 'foo-rootgroup') } - let(:target_group_name) { group.name } + let(:target_group) { group } it_behaves_like 'can transfer the group' end @@ -235,6 +246,67 @@ RSpec.describe 'Edit group settings', feature_category: :subgroups do end end + describe 'group README', :js do + let_it_be(:group) { create(:group) } + + context 'with gitlab-profile project and README.md' do + let_it_be(:project) { create(:project, :readme, namespace: group) } + + it 'renders link to Group README and navigates to it on click' do + visit edit_group_path(group) + wait_for_requests + + click_link('README') + wait_for_requests + + expect(page).to have_current_path(project_blob_path(project, "#{project.default_branch}/README.md")) + expect(page).to have_text('README.md') + end + end + + context 'with gitlab-profile project and no README.md' do + let_it_be(:project) { create(:project, path: 'gitlab-profile', namespace: group) } + + it 'renders Add README button and allows user to create a README via the IDE' do + visit edit_group_path(group) + wait_for_requests + + expect(page).not_to have_selector('.ide') + + click_button('Add README') + + accept_gl_confirm("This will create a README.md for project #{group.readme_project.present.path_with_namespace}.", button_text: 'Add README') + wait_for_requests + + expect(page).to have_current_path("/-/ide/project/#{group.readme_project.present.path_with_namespace}/edit/main/-/README.md/") + + page.within('.ide') do + expect(page).to have_text('README.md') + end + end + end + + context 'with no gitlab-profile project and no README.md' do + it 'renders Add README button and allows user to create both the gitlab-profile project and README via the IDE' do + visit edit_group_path(group) + wait_for_requests + + expect(page).not_to have_selector('.ide') + + click_button('Add README') + + accept_gl_confirm("This will create a project #{group.full_path}/gitlab-profile and add a README.md.", button_text: 'Create and add README') + wait_for_requests + + expect(page).to have_current_path("/-/ide/project/#{group.full_path}/gitlab-profile/edit/main/-/README.md/") + + page.within('.ide') do + expect(page).to have_text('README.md') + end + end + end + end + def update_path(new_group_path) visit edit_group_path(group) diff --git a/spec/features/groups/import_export/connect_instance_spec.rb b/spec/features/groups/import_export/connect_instance_spec.rb index 8aea18a268b..f6548c035f0 100644 --- a/spec/features/groups/import_export/connect_instance_spec.rb +++ b/spec/features/groups/import_export/connect_instance_spec.rb @@ -26,7 +26,7 @@ RSpec.describe 'Import/Export - Connect to another instance', :js, feature_categ pat = 'demo-pat' expect(page).to have_content 'Import groups by direct transfer' - expect(page).to have_content 'Not all related objects are migrated' + expect(page).to have_content 'Not all group items are migrated' fill_in :bulk_import_gitlab_url, with: source_url fill_in :bulk_import_gitlab_access_token, with: pat diff --git a/spec/features/groups/integrations/group_integrations_spec.rb b/spec/features/groups/integrations/group_integrations_spec.rb index 0d65fa5964b..8cddda91e89 100644 --- a/spec/features/groups/integrations/group_integrations_spec.rb +++ b/spec/features/groups/integrations/group_integrations_spec.rb @@ -5,6 +5,10 @@ require 'spec_helper' RSpec.describe 'Group integrations', :js do include_context 'group integration activation' + before do + stub_feature_flags(remove_monitor_metrics: false) + end + it_behaves_like 'integration settings form' do let(:integrations) { Integration.find_or_initialize_all_non_project_specific(Integration.for_group(group)) } @@ -12,4 +16,16 @@ RSpec.describe 'Group integrations', :js do visit_group_integration(integration.title) end end + + context 'with remove_monitor_metrics flag enabled' do + before do + stub_feature_flags(remove_monitor_metrics: true) + end + + it 'returns a 404 for the prometheus edit page' do + visit edit_group_settings_integration_path(group, :prometheus) + + expect(page).to have_content "Page Not Found" + end + end end diff --git a/spec/features/groups/issues_spec.rb b/spec/features/groups/issues_spec.rb index 00c0d4c3ebe..6d0d768d356 100644 --- a/spec/features/groups/issues_spec.rb +++ b/spec/features/groups/issues_spec.rb @@ -23,6 +23,10 @@ RSpec.describe 'Group issues page', feature_category: :subgroups do context 'rss feed' do let(:access_level) { ProjectFeature::ENABLED } + before do + click_button 'Actions' + end + context 'when signed in' do let(:user) do user_in_group.ensure_feed_token @@ -30,29 +34,15 @@ RSpec.describe 'Group issues page', feature_category: :subgroups do user_in_group end + it_behaves_like "it has an RSS link with current_user's feed token" it_behaves_like "an autodiscoverable RSS feed with current_user's feed token" - - # Note: The one from rss_shared_example.rb uses a css pseudo-class `:has` - # which is VERY experimental and only supported in Nokogiri used by Capybara - # However,`:js` option forces Capybara to use Selenium that doesn't support`:has` - context "it has an RSS button with current_user's feed token" do - it "shows the RSS button with current_user's feed token" do - expect(page).to have_link 'Subscribe to RSS feed', href: /feed_token=#{user.feed_token}/ - end - end end context 'when signed out' do let(:user) { nil } + it_behaves_like "it has an RSS link without a feed token" it_behaves_like "an autodiscoverable RSS feed without a feed token" - - # Note: please see the above - context "it has an RSS button without a feed token" do - it "shows the RSS button without a feed token" do - expect(page).not_to have_link 'Subscribe to RSS feed', href: /feed_token/ - end - end end end diff --git a/spec/features/groups/labels/index_spec.rb b/spec/features/groups/labels/index_spec.rb index ea27fa2c5d9..7b0a38a83db 100644 --- a/spec/features/groups/labels/index_spec.rb +++ b/spec/features/groups/labels/index_spec.rb @@ -24,6 +24,7 @@ RSpec.describe 'Group labels', feature_category: :team_planning do end it 'shows an edit label button', :js do - expect(page).to have_selector('.edit') + click_button 'Label actions dropdown' + expect(page).to have_link('Edit') end end diff --git a/spec/features/groups/members/filter_members_spec.rb b/spec/features/groups/members/filter_members_spec.rb index dc33bb11bea..c2ec709576b 100644 --- a/spec/features/groups/members/filter_members_spec.rb +++ b/spec/features/groups/members/filter_members_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'Groups > Members > Filter members', :js, feature_category: :subgroups do - include Spec::Support::Helpers::Features::MembersHelpers + include Features::MembersHelpers let(:user) { create(:user) } let(:nested_group_user) { create(:user) } diff --git a/spec/features/groups/members/leave_group_spec.rb b/spec/features/groups/members/leave_group_spec.rb index cfb1b24bccb..e1c2d8c0547 100644 --- a/spec/features/groups/members/leave_group_spec.rb +++ b/spec/features/groups/members/leave_group_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'Groups > Members > Leave group', feature_category: :subgroups do - include Spec::Support::Helpers::Features::MembersHelpers + include Features::MembersHelpers include Spec::Support::Helpers::ModalHelpers let(:user) { create(:user) } diff --git a/spec/features/groups/members/list_members_spec.rb b/spec/features/groups/members/list_members_spec.rb index 1aea5a76b41..6e20f92c16b 100644 --- a/spec/features/groups/members/list_members_spec.rb +++ b/spec/features/groups/members/list_members_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'Groups > Members > List members', :js, feature_category: :subgroups do - include Spec::Support::Helpers::Features::MembersHelpers + include Features::MembersHelpers let(:user1) { create(:user, name: 'John Doe') } let(:user2) { create(:user, name: 'Mary Jane') } diff --git a/spec/features/groups/members/manage_groups_spec.rb b/spec/features/groups/members/manage_groups_spec.rb index ee8786a2e36..f9c11dd0183 100644 --- a/spec/features/groups/members/manage_groups_spec.rb +++ b/spec/features/groups/members/manage_groups_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' RSpec.describe 'Groups > Members > Manage groups', :js, feature_category: :subgroups do - include Spec::Support::Helpers::Features::MembersHelpers - include Spec::Support::Helpers::Features::InviteMembersModalHelper + include Features::MembersHelpers + include Features::InviteMembersModalHelpers include Spec::Support::Helpers::ModalHelpers let_it_be(:user) { create(:user) } diff --git a/spec/features/groups/members/manage_members_spec.rb b/spec/features/groups/members/manage_members_spec.rb index 5cd5908b359..2d5a3dbb8f8 100644 --- a/spec/features/groups/members/manage_members_spec.rb +++ b/spec/features/groups/members/manage_members_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' RSpec.describe 'Groups > Members > Manage members', feature_category: :subgroups do - include Spec::Support::Helpers::Features::MembersHelpers - include Spec::Support::Helpers::Features::InviteMembersModalHelper + include Features::MembersHelpers + include Features::InviteMembersModalHelpers include Spec::Support::Helpers::ModalHelpers let_it_be(:user1) { create(:user, name: 'John Doe') } diff --git a/spec/features/groups/members/master_adds_member_with_expiration_date_spec.rb b/spec/features/groups/members/master_adds_member_with_expiration_date_spec.rb index e9f80b05fa7..4f56c807ec8 100644 --- a/spec/features/groups/members/master_adds_member_with_expiration_date_spec.rb +++ b/spec/features/groups/members/master_adds_member_with_expiration_date_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' RSpec.describe 'Groups > Members > Owner adds member with expiration date', :js, feature_category: :subgroups do - include Spec::Support::Helpers::Features::MembersHelpers - include Spec::Support::Helpers::Features::InviteMembersModalHelper + include Features::MembersHelpers + include Features::InviteMembersModalHelpers let_it_be(:user1) { create(:user, name: 'John Doe') } let_it_be(:group) { create(:group) } diff --git a/spec/features/groups/members/search_members_spec.rb b/spec/features/groups/members/search_members_spec.rb index 6b2896b194c..80de1cabd1e 100644 --- a/spec/features/groups/members/search_members_spec.rb +++ b/spec/features/groups/members/search_members_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'Search group member', :js, feature_category: :subgroups do - include Spec::Support::Helpers::Features::MembersHelpers + include Features::MembersHelpers let(:user) { create :user } let(:member) { create :user } diff --git a/spec/features/groups/members/sort_members_spec.rb b/spec/features/groups/members/sort_members_spec.rb index 5634122ec16..d2e5445deae 100644 --- a/spec/features/groups/members/sort_members_spec.rb +++ b/spec/features/groups/members/sort_members_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'Groups > Members > Sort members', :js, feature_category: :subgroups do - include Spec::Support::Helpers::Features::MembersHelpers + include Features::MembersHelpers let(:owner) { create(:user, name: 'John Doe', created_at: 5.days.ago, last_activity_on: Date.today) } let(:developer) { create(:user, name: 'Mary Jane', created_at: 1.day.ago, last_sign_in_at: 5.days.ago, last_activity_on: Date.today - 5) } @@ -18,7 +18,7 @@ RSpec.describe 'Groups > Members > Sort members', :js, feature_category: :subgro def expect_sort_by(text, sort_direction) within('[data-testid="members-sort-dropdown"]') do - expect(page).to have_css('button[aria-haspopup="true"]', text: text) + expect(page).to have_css('button[aria-haspopup="menu"]', text: text) expect(page).to have_button("Sorting Direction: #{sort_direction == :asc ? 'Ascending' : 'Descending'}") end end diff --git a/spec/features/groups/milestone_spec.rb b/spec/features/groups/milestone_spec.rb index a70a1e2e70b..376e1e6063f 100644 --- a/spec/features/groups/milestone_spec.rb +++ b/spec/features/groups/milestone_spec.rb @@ -25,17 +25,17 @@ RSpec.describe 'Group milestones', feature_category: :subgroups do description.native.send_keys('') - click_button('Preview') + click_button("Preview") preview = find('.js-md-preview') expect(preview).to have_content('Nothing to preview.') - click_button('Write') + click_button("Continue editing") description.native.send_keys(':+1: Nice') - click_button('Preview') + click_button("Preview") expect(preview).to have_css('gl-emoji') expect(find('#milestone_description', visible: false)).not_to be_visible diff --git a/spec/features/groups/milestones/milestone_editing_spec.rb b/spec/features/groups/milestones/milestone_editing_spec.rb new file mode 100644 index 00000000000..b3c7cfe88af --- /dev/null +++ b/spec/features/groups/milestones/milestone_editing_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe "Milestone editing", feature_category: :team_planning do + let_it_be(:group) { create(:group, owner: user) } + let_it_be(:user) { create(:group_member, :maintainer, user: create(:user), group: group).user } + + let(:milestone) { create(:milestone, group: group, title: "12345676543") } + + before do + sign_in(user) + + visit(edit_group_milestone_path(group, milestone)) + end + + it_behaves_like 'milestone handling version conflicts' +end diff --git a/spec/features/groups/new_group_page_spec.rb b/spec/features/groups/new_group_page_spec.rb index a07c27331d9..1efdc3fff07 100644 --- a/spec/features/groups/new_group_page_spec.rb +++ b/spec/features/groups/new_group_page_spec.rb @@ -3,15 +3,14 @@ require 'spec_helper' RSpec.describe 'New group page', :js, feature_category: :subgroups do - let(:user) { create(:user) } - let(:group) { create(:group) } + let_it_be(:user) { create(:user) } + let_it_be(:parent_group) { create(:group) } before do + parent_group.add_owner(user) sign_in(user) end - it_behaves_like 'a dashboard page with sidebar', :new_group_path, :groups - describe 'new top level group alert' do context 'when a user visits the new group page' do it 'shows the new top level group alert' do @@ -22,8 +21,6 @@ RSpec.describe 'New group page', :js, feature_category: :subgroups do end context 'when a user visits the new sub group page' do - let(:parent_group) { create(:group) } - it 'does not show the new top level group alert' do visit new_group_path(parent_id: parent_group.id, anchor: 'create-group-pane') @@ -31,4 +28,45 @@ RSpec.describe 'New group page', :js, feature_category: :subgroups do end end end + + describe 'sidebar' do + context 'in the current navigation' do + before do + user.update!(use_new_navigation: false) + end + + context 'for a new top-level group' do + it_behaves_like 'a "Your work" page with sidebar and breadcrumbs', :new_group_path, :groups + end + + context 'for a new subgroup' do + it 'shows the group sidebar of the parent group' do + visit new_group_path(parent_id: parent_group.id, anchor: 'create-group-pane') + expect(page).to have_selector( + ".nav-sidebar[aria-label=\"Group navigation\"] .context-header[title=\"#{parent_group.name}\"]" + ) + end + end + end + + context 'in the new navigation' do + before do + user.update!(use_new_navigation: true) + end + + context 'for a new top-level group' do + it 'shows the "Your work" navigation' do + visit new_group_path + expect(page).to have_selector(".super-sidebar .context-switcher-toggle", text: "Your work") + end + end + + context 'for a new subgroup' do + it 'shows the group navigation of the parent group' do + visit new_group_path(parent_id: parent_group.id, anchor: 'create-group-pane') + expect(page).to have_selector(".super-sidebar .context-switcher-toggle", text: parent_group.name) + end + end + end + end end diff --git a/spec/features/groups/packages_spec.rb b/spec/features/groups/packages_spec.rb index dd238657fbc..b7f9cd3e93a 100644 --- a/spec/features/groups/packages_spec.rb +++ b/spec/features/groups/packages_spec.rb @@ -37,8 +37,8 @@ RSpec.describe 'Group Packages', feature_category: :package_registry do end context 'when there are packages' do - let_it_be(:second_project) { create(:project, name: 'second-project', group: group) } - let_it_be(:npm_package) { create(:npm_package, project: project, name: 'zzz', created_at: 1.day.ago, version: '1.0.0') } + let_it_be(:second_project) { create(:project, group: group) } + let_it_be(:npm_package) { create(:npm_package, :with_build, project: project, name: 'zzz', created_at: 1.day.ago, version: '1.0.0') } let_it_be(:maven_package) { create(:maven_package, project: second_project, name: 'aaa', created_at: 2.days.ago, version: '2.0.0') } let_it_be(:packages) { [npm_package, maven_package] } @@ -47,10 +47,12 @@ RSpec.describe 'Group Packages', feature_category: :package_registry do it_behaves_like 'packages list', check_project_name: true + it_behaves_like 'pipelines on packages list' + it_behaves_like 'package details link' it 'allows you to navigate to the project page' do - find('[data-testid="root-link"]', text: project.name).click + find('[data-testid="root-link"]', text: project.path).click expect(page).to have_current_path(project_path(project)) expect(page).to have_content(project.name) diff --git a/spec/features/groups/settings/access_tokens_spec.rb b/spec/features/groups/settings/access_tokens_spec.rb index 1bee3be1ddb..cb92f9abdf5 100644 --- a/spec/features/groups/settings/access_tokens_spec.rb +++ b/spec/features/groups/settings/access_tokens_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Group > Settings > Access Tokens', :js, feature_category: :authentication_and_authorization do +RSpec.describe 'Group > Settings > Access Tokens', :js, feature_category: :system_access do include Spec::Support::Helpers::ModalHelpers let_it_be(:user) { create(:user) } diff --git a/spec/features/groups/settings/packages_and_registries_spec.rb b/spec/features/groups/settings/packages_and_registries_spec.rb index 80e2dcd5174..8ea8dc9219a 100644 --- a/spec/features/groups/settings/packages_and_registries_spec.rb +++ b/spec/features/groups/settings/packages_and_registries_spec.rb @@ -56,6 +56,15 @@ RSpec.describe 'Group Package and registry settings', feature_category: :package expect(sidebar).to have_link _('Packages and registries') end + it 'passes axe automated accessibility testing', :js do + visit_settings_page + + wait_for_requests + + expect(page).to be_axe_clean.within('[data-testid="packages-and-registries-group-settings"]') + .skipping :'link-in-text-block' + end + it 'has a Duplicate packages section', :js do visit_settings_page diff --git a/spec/features/groups/show_spec.rb b/spec/features/groups/show_spec.rb index 5cab79b40cf..0f936173e5d 100644 --- a/spec/features/groups/show_spec.rb +++ b/spec/features/groups/show_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'Group show page', feature_category: :subgroups do - include Spec::Support::Helpers::Features::InviteMembersModalHelper + include Features::InviteMembersModalHelpers let_it_be(:user) { create(:user) } let_it_be(:group) { create(:group) } diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb index 8806d1c2219..088b5b11a9a 100644 --- a/spec/features/groups_spec.rb +++ b/spec/features/groups_spec.rb @@ -510,6 +510,47 @@ RSpec.describe 'Group', feature_category: :subgroups do end end end + + context 'when in a private group' do + before do + group.update!( + visibility_level: Gitlab::VisibilityLevel::PRIVATE, + project_creation_level: Gitlab::Access::MAINTAINER_PROJECT_ACCESS + ) + end + + context 'when visibility levels have been restricted to private only by an administrator' do + before do + stub_application_setting( + restricted_visibility_levels: [ + Gitlab::VisibilityLevel::PRIVATE + ] + ) + end + + it 'does not display the "New project" button' do + visit group_path(group) + + page.within '[data-testid="group-buttons"]' do + expect(page).not_to have_link('New project') + end + end + end + end + end + + describe 'group README', :js do + context 'with gitlab-profile project and README.md' do + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, :readme, namespace: group) } + + it 'renders README block on group page' do + visit group_path(group) + wait_for_requests + + expect(page).to have_text('README.md') + end + end end def remove_with_confirm(button_text, confirm_with) diff --git a/spec/features/help_dropdown_spec.rb b/spec/features/help_dropdown_spec.rb index a5c9221ad26..5f1d3a5e2b7 100644 --- a/spec/features/help_dropdown_spec.rb +++ b/spec/features/help_dropdown_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe "Help Dropdown", :js, feature_category: :not_owned do +RSpec.describe "Help Dropdown", :js, feature_category: :shared do let_it_be(:user) { create(:user) } let_it_be(:admin) { create(:admin) } diff --git a/spec/features/help_pages_spec.rb b/spec/features/help_pages_spec.rb index 6c0901d6169..905c5e25f6e 100644 --- a/spec/features/help_pages_spec.rb +++ b/spec/features/help_pages_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Help Pages', feature_category: :not_owned do +RSpec.describe 'Help Pages', feature_category: :shared do describe 'Get the main help page' do before do allow(File).to receive(:read).and_call_original diff --git a/spec/features/ide/user_opens_merge_request_spec.rb b/spec/features/ide/user_opens_merge_request_spec.rb index 0074b4b1eb0..dc280133a20 100644 --- a/spec/features/ide/user_opens_merge_request_spec.rb +++ b/spec/features/ide/user_opens_merge_request_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe 'IDE merge request', :js, feature_category: :web_ide do + include CookieHelper + let(:merge_request) { create(:merge_request, :simple, source_project: project) } let(:project) { create(:project, :public, :repository) } let(:user) { project.first_owner } @@ -12,6 +14,8 @@ RSpec.describe 'IDE merge request', :js, feature_category: :web_ide do sign_in(user) + set_cookie('new-actions-popover-viewed', 'true') + visit(merge_request_path(merge_request)) end diff --git a/spec/features/ide_spec.rb b/spec/features/ide_spec.rb index 2ca8d3f7156..bdf8be95415 100644 --- a/spec/features/ide_spec.rb +++ b/spec/features/ide_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'IDE', :js, feature_category: :web_ide do - include WebIdeSpecHelpers + include Features::WebIdeSpecHelpers let_it_be(:ide_iframe_selector) { '#ide iframe' } let_it_be(:normal_project) { create(:project, :repository) } @@ -48,17 +48,6 @@ RSpec.describe 'IDE', :js, feature_category: :web_ide do it_behaves_like 'legacy Web IDE' end - context 'with vscode feature flag on and use_legacy_web_ide=true' do - let(:vscode_ff) { true } - let(:user) { create(:user, use_legacy_web_ide: true) } - - before do - ide_visit(project) - end - - it_behaves_like 'legacy Web IDE' - end - describe 'sub-groups' do let_it_be(:group) { create(:group) } let_it_be(:subgroup) { create(:group, parent: group) } diff --git a/spec/features/import/manifest_import_spec.rb b/spec/features/import/manifest_import_spec.rb index bb3eb34637b..aba38eb0196 100644 --- a/spec/features/import/manifest_import_spec.rb +++ b/spec/features/import/manifest_import_spec.rb @@ -7,6 +7,8 @@ RSpec.describe 'Import multiple repositories by uploading a manifest file', :js, let(:group) { create(:group) } before do + stub_application_setting(import_sources: ['manifest']) + sign_in(user) group.add_owner(user) diff --git a/spec/features/incidents/incident_details_spec.rb b/spec/features/incidents/incident_details_spec.rb index 709919d0196..a166ff46177 100644 --- a/spec/features/incidents/incident_details_spec.rb +++ b/spec/features/incidents/incident_details_spec.rb @@ -94,6 +94,7 @@ RSpec.describe 'Incident details', :js, feature_category: :incident_management d end it 'routes the user to the incident details page when the `issue_type` is set to incident' do + set_cookie('new-actions-popover-viewed', 'true') visit project_issue_path(project, issue) wait_for_requests @@ -113,6 +114,7 @@ RSpec.describe 'Incident details', :js, feature_category: :incident_management d end it 'routes the user to the issue details page when the `issue_type` is set to issue' do + set_cookie('new-actions-popover-viewed', 'true') visit incident_project_issues_path(project, incident) wait_for_requests diff --git a/spec/features/incidents/incident_timeline_events_spec.rb b/spec/features/incidents/incident_timeline_events_spec.rb index 7404ac64cc9..4d51ed652c9 100644 --- a/spec/features/incidents/incident_timeline_events_spec.rb +++ b/spec/features/incidents/incident_timeline_events_spec.rb @@ -43,9 +43,7 @@ RSpec.describe 'Incident timeline events', :js, feature_category: :incident_mana expect(page).to have_content(s_('Incident|No timeline items have been added yet.')) end - it 'submits event data on save with feature flag on' do - stub_feature_flags(incident_event_tags: true) - + it 'submits event data on save' do # Add event click_button(s_('Incident|Add new timeline event')) @@ -96,5 +94,6 @@ RSpec.describe 'Incident timeline events', :js, feature_category: :incident_mana it_behaves_like 'for each incident details route', 'add, edit, and delete timeline events', - tab_text: s_('Incident|Timeline') + tab_text: s_('Incident|Timeline'), + tab: 'timeline' end diff --git a/spec/features/incidents/user_uses_quick_actions_spec.rb b/spec/features/incidents/user_uses_quick_actions_spec.rb index 3740f2fca47..27facbcafe8 100644 --- a/spec/features/incidents/user_uses_quick_actions_spec.rb +++ b/spec/features/incidents/user_uses_quick_actions_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'Incidents > User uses quick actions', :js, feature_category: :incident_management do - include Spec::Support::Helpers::Features::NotesHelpers + include Features::NotesHelpers describe 'incident-only commands' do let_it_be(:user) { create(:user) } diff --git a/spec/features/incidents/user_views_alert_details_spec.rb b/spec/features/incidents/user_views_alert_details_spec.rb new file mode 100644 index 00000000000..f3d0273071c --- /dev/null +++ b/spec/features/incidents/user_views_alert_details_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'User uploads alerts to incident', :js, feature_category: :incident_management do + let_it_be(:incident) { create(:incident) } + let_it_be(:project) { incident.project } + let_it_be(:user) { create(:user, developer_projects: [project]) } + + context 'with alert' do + let_it_be(:alert) { create(:alert_management_alert, issue_id: incident.id, project: project) } + + shared_examples 'shows alert tab with details' do + specify do + expect(page).to have_link(s_('Incident|Alert details')) + expect(page).to have_content(alert.title) + end + end + + it_behaves_like 'for each incident details route', + 'shows alert tab with details', + tab_text: s_('Incident|Alert details'), + tab: 'alerts' + end + + context 'with no alerts' do + it 'hides the Alert details tab' do + sign_in(user) + visit project_issue_path(project, incident) + + expect(page).not_to have_link(s_('Incident|Alert details')) + end + end +end diff --git a/spec/features/integrations_settings_spec.rb b/spec/features/integrations_settings_spec.rb new file mode 100644 index 00000000000..70ce2f55161 --- /dev/null +++ b/spec/features/integrations_settings_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Integration settings', feature_category: :integrations do + let_it_be(:project) { create(:project) } + let_it_be(:user) { create(:user) } + + before do + project.add_maintainer(user) + sign_in(user) + end + + context 'with Shimo Zentao integration records' do + before do + create(:integration, project: project, type_new: 'Integrations::Shimo', category: 'issue_tracker') + create(:integration, project: project, type_new: 'Integrations::Zentao', category: 'issue_tracker') + end + + it 'shows settings without Shimo Zentao', :js do + visit namespace_project_settings_integrations_path(namespace_id: project.namespace.full_path, + project_id: project.path) + + expect(page).to have_content('Add an integration') + expect(page).not_to have_content('ZenTao') + expect(page).not_to have_content('Shimo') + end + end +end diff --git a/spec/features/invites_spec.rb b/spec/features/invites_spec.rb index 1091bea1ce3..a3d4b30b59c 100644 --- a/spec/features/invites_spec.rb +++ b/spec/features/invites_spec.rb @@ -28,14 +28,10 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures, feature_cate fill_in 'new_user_username', with: new_user.username fill_in 'new_user_email', with: new_user.email fill_in 'new_user_password', with: new_user.password - click_button submit_button_text - end - def fill_in_sign_in_form(user) - fill_in 'user_login', with: user.email - fill_in 'user_password', with: user.password - check 'user_remember_me' - click_button 'Sign in' + wait_for_all_requests + + click_button submit_button_text end def fill_in_welcome_form @@ -73,7 +69,7 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures, feature_cate end end - context 'when invite is sent before account is created - ldap or service sign in for manual acceptance edge case' do + context 'when invite is sent before account is created;ldap or service sign in for manual acceptance edge case' do let(:user) { create(:user, email: 'user@example.com') } context 'when invite clicked and not signed in' do @@ -84,7 +80,7 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures, feature_cate it 'sign in, grants access and redirects to group activity page' do click_link 'Sign in' - fill_in_sign_in_form(user) + gitlab_sign_in(user, remember: true, visit: false) expect(page).to have_current_path(activity_group_path(group), ignore_query: true) end @@ -151,7 +147,7 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures, feature_cate end end - context 'when inviting an unregistered user' do + context 'when inviting an unregistered user', :js do let(:new_user) { build_stubbed(:user) } let(:invite_email) { new_user.email } let(:group_invite) { create(:group_member, :invited, group: group, invite_email: invite_email, created_by: owner) } @@ -175,17 +171,19 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures, feature_cate fill_in_sign_up_form(new_user) expect(page).to have_current_path(new_user_session_path, ignore_query: true) - expect(page).to have_content('You have signed up successfully. However, we could not sign you in because your account is awaiting approval from your GitLab administrator') + sign_up_message = 'You have signed up successfully. However, we could not sign you in because your account ' \ + 'is awaiting approval from your GitLab administrator.' + expect(page).to have_content(sign_up_message) end end - context 'email confirmation disabled' do + context 'with email confirmation disabled' do before do stub_application_setting_enum('email_confirmation_setting', 'off') end - context 'the user signs up for an account with the invitation email address' do - it 'redirects to the most recent membership activity page with all the projects/groups invitations automatically accepted' do + context 'when the user signs up for an account with the invitation email address' do + it 'redirects to the most recent membership activity page with all invitations automatically accepted' do fill_in_sign_up_form(new_user) fill_in_welcome_form @@ -194,7 +192,7 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures, feature_cate end end - context 'the user sign-up using a different email address' do + context 'when the user sign-up using a different email address' do let(:invite_email) { build_stubbed(:user).email } it 'signs up and redirects to the activity page' do @@ -206,9 +204,9 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures, feature_cate end end - context 'email confirmation enabled' do + context 'with email confirmation enabled' do context 'when user is not valid in sign up form' do - let(:new_user) { build_stubbed(:user, first_name: '', last_name: '') } + let(:new_user) { build_stubbed(:user, password: '11111111') } it 'fails sign up and redirects back to sign up', :aggregate_failures do expect { fill_in_sign_up_form(new_user) }.not_to change { User.count } @@ -232,8 +230,8 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures, feature_cate end end - context 'the user signs up for an account with the invitation email address' do - it 'redirects to the most recent membership activity page with all the projects/groups invitations automatically accepted' do + context 'when the user signs up for an account with the invitation email address' do + it 'redirects to the most recent membership activity page with all invitations automatically accepted' do fill_in_sign_up_form(new_user) fill_in_welcome_form @@ -241,12 +239,11 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures, feature_cate end end - context 'the user sign-up using a different email address' do + context 'when the user signs up using a different email address' do let(:invite_email) { build_stubbed(:user).email } - context 'when soft email confirmation is not enabled' do + context 'when email confirmation is not set to `soft`' do before do - stub_feature_flags(soft_email_confirmation: false) allow(User).to receive(:allow_unconfirmed_access_for).and_return 0 stub_feature_flags(identity_verification: false) end @@ -254,16 +251,16 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures, feature_cate it 'signs up and redirects to the group activity page' do fill_in_sign_up_form(new_user) confirm_email(new_user) - fill_in_sign_in_form(new_user) + gitlab_sign_in(new_user, remember: true, visit: false) fill_in_welcome_form expect(page).to have_current_path(activity_group_path(group), ignore_query: true) end end - context 'when soft email confirmation is enabled' do + context 'when email confirmation setting is set to `soft`' do before do - stub_feature_flags(soft_email_confirmation: true) + stub_application_setting_enum('email_confirmation_setting', 'soft') allow(User).to receive(:allow_unconfirmed_access_for).and_return 2.days end diff --git a/spec/features/issuables/issuable_list_spec.rb b/spec/features/issuables/issuable_list_spec.rb index 350b0582565..c979aff2147 100644 --- a/spec/features/issuables/issuable_list_spec.rb +++ b/spec/features/issuables/issuable_list_spec.rb @@ -48,7 +48,7 @@ RSpec.describe 'issuable list', :js, feature_category: :team_planning do end end - it 'displays a warning if counting the number of issues times out' do + it 'displays a warning if counting the number of issues times out', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/393344' do allow_any_instance_of(IssuesFinder).to receive(:count_by_state).and_raise(ActiveRecord::QueryCanceled) visit_issuable_list(:issue) diff --git a/spec/features/issuables/markdown_references/internal_references_spec.rb b/spec/features/issuables/markdown_references/internal_references_spec.rb index aeae76b1b77..04950c7c7d4 100644 --- a/spec/features/issuables/markdown_references/internal_references_spec.rb +++ b/spec/features/issuables/markdown_references/internal_references_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe "Internal references", :js, feature_category: :team_planning do - include Spec::Support::Helpers::Features::NotesHelpers + include Features::NotesHelpers let(:private_project_user) { private_project.first_owner } let(:private_project) { create(:project, :private, :repository) } diff --git a/spec/features/issuables/markdown_references/jira_spec.rb b/spec/features/issuables/markdown_references/jira_spec.rb index 52464c6be8b..887bc7d0c87 100644 --- a/spec/features/issuables/markdown_references/jira_spec.rb +++ b/spec/features/issuables/markdown_references/jira_spec.rb @@ -29,7 +29,7 @@ RSpec.describe "Jira", :js, feature_category: :team_planning do end it "creates a link to the referenced issue on the preview" do - find(".js-md-preview-button").click + click_button("Preview") wait_for_requests diff --git a/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb b/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb index 0bdb5930f30..c982052fc0e 100644 --- a/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb +++ b/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb @@ -20,6 +20,7 @@ RSpec.describe 'Resolving all open threads in a merge request from an issue', :j before do stub_feature_flags(moved_mr_sidebar: false) + stub_feature_flags(hide_create_issue_resolve_all: false) end describe 'as a user with access to the project' do @@ -33,7 +34,7 @@ RSpec.describe 'Resolving all open threads in a merge request from an issue', :j find('.discussions-counter .dropdown-toggle').click within('.discussions-counter') do - expect(page).to have_link(_("Create issue to resolve all threads"), href: new_project_issue_path(project, merge_request_to_resolve_discussions_of: merge_request.iid)) + expect(page).to have_link(_("Resolve all with new issue"), href: new_project_issue_path(project, merge_request_to_resolve_discussions_of: merge_request.iid)) end end @@ -44,7 +45,7 @@ RSpec.describe 'Resolving all open threads in a merge request from an issue', :j it 'hides the link for creating a new issue' do expect(page).not_to have_selector resolve_all_discussions_link_selector - expect(page).not_to have_content "Create issue to resolve all threads" + expect(page).not_to have_content "Resolve all with new issue" end end @@ -69,7 +70,7 @@ RSpec.describe 'Resolving all open threads in a merge request from an issue', :j end it 'does not show a link to create a new issue' do - expect(page).not_to have_link 'Create issue to resolve all threads' + expect(page).not_to have_link 'Resolve all with new issue' end end @@ -83,13 +84,13 @@ RSpec.describe 'Resolving all open threads in a merge request from an issue', :j end it 'has a link to resolve all threads by creating an issue' do - expect(page).to have_link 'Create issue to resolve all threads', href: new_project_issue_path(project, merge_request_to_resolve_discussions_of: merge_request.iid) + expect(page).to have_link 'Resolve all with new issue', href: new_project_issue_path(project, merge_request_to_resolve_discussions_of: merge_request.iid) end context 'creating an issue for threads' do before do page.within '.mr-state-widget' do - page.click_link 'Create issue to resolve all threads', href: new_project_issue_path(project, merge_request_to_resolve_discussions_of: merge_request.iid) + page.click_link 'Resolve all with new issue', href: new_project_issue_path(project, merge_request_to_resolve_discussions_of: merge_request.iid) wait_for_all_requests end diff --git a/spec/features/issues/csv_spec.rb b/spec/features/issues/csv_spec.rb index 8629201459f..21d5041b210 100644 --- a/spec/features/issues/csv_spec.rb +++ b/spec/features/issues/csv_spec.rb @@ -16,6 +16,7 @@ RSpec.describe 'Issues csv', :js, feature_category: :team_planning do def request_csv(params = {}) visit project_issues_path(project, params) + click_button 'Actions' click_button 'Export as CSV' click_on 'Export issues' end diff --git a/spec/features/issues/discussion_lock_spec.rb b/spec/features/issues/discussion_lock_spec.rb index 33fc9a6fd96..fb9addff1a2 100644 --- a/spec/features/issues/discussion_lock_spec.rb +++ b/spec/features/issues/discussion_lock_spec.rb @@ -9,6 +9,7 @@ RSpec.describe 'Discussion Lock', :js, feature_category: :team_planning do before do sign_in(user) + stub_feature_flags(moved_mr_sidebar: false) end context 'when a user is a team member' do @@ -99,7 +100,7 @@ RSpec.describe 'Discussion Lock', :js, feature_category: :team_planning do it 'the user can not create a comment' do page.within('#notes') do expect(page).not_to have_selector('js-main-target-form') - expect(page.find('.disabled-comment')) + expect(page.find('.disabled-comments')) .to have_content('This issue is locked. Only project members can comment.') end end diff --git a/spec/features/issues/filtered_search/dropdown_hint_spec.rb b/spec/features/issues/filtered_search/dropdown_hint_spec.rb index 39034a40b1f..1cd8326c5fe 100644 --- a/spec/features/issues/filtered_search/dropdown_hint_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_hint_spec.rb @@ -52,8 +52,8 @@ RSpec.describe 'Dropdown hint', :js, feature_category: :team_planning do click_filtered_search_bar send_keys 'as' - # Expect Assignee and Release - expect_suggestion_count 2 + # Expect Assignee, Release, Search for this text + expect_suggestion_count 3 end end diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb index f67d5c40efd..a65befc3115 100644 --- a/spec/features/issues/filtered_search/filter_issues_spec.rb +++ b/spec/features/issues/filtered_search/filter_issues_spec.rb @@ -61,7 +61,7 @@ RSpec.describe 'Filter issues', :js, feature_category: :team_planning do it 'filters by all available tokens' do search_term = 'issue' select_tokens 'Assignee', '=', user.username, 'Author', '=', user.username, 'Label', '=', caps_sensitive_label.title, 'Milestone', '=', milestone.title - send_keys search_term, :enter + send_keys search_term, :enter, :enter expect_assignee_token(user.name) expect_author_token(user.name) @@ -261,7 +261,7 @@ RSpec.describe 'Filter issues', :js, feature_category: :team_planning do it 'filters issues by searched label, label2, author, assignee, milestone and text' do search_term = 'bug' select_tokens 'Label', '=', bug_label.title, 'Label', '=', caps_sensitive_label.title, 'Author', '=', user.username, 'Assignee', '=', user.username, 'Milestone', '=', milestone.title - send_keys search_term, :enter + send_keys search_term, :enter, :enter expect_label_token(bug_label.title) expect_label_token(caps_sensitive_label.title) @@ -275,7 +275,7 @@ RSpec.describe 'Filter issues', :js, feature_category: :team_planning do it 'filters issues by searched label, label2, author, assignee, not included in a milestone' do search_term = 'bug' select_tokens 'Label', '=', bug_label.title, 'Label', '=', caps_sensitive_label.title, 'Author', '=', user.username, 'Assignee', '=', user.username, 'Milestone', '!=', milestone.title - send_keys search_term, :enter + send_keys search_term, :enter, :enter expect_label_token(bug_label.title) expect_label_token(caps_sensitive_label.title) @@ -488,13 +488,13 @@ RSpec.describe 'Filter issues', :js, feature_category: :team_planning do context 'searched text with other filters' do it 'filters issues by searched text, author, text, assignee, text, label1, text, label2, text, milestone and text' do click_filtered_search_bar - send_keys 'bug ' + send_keys 'bug', :enter select_tokens 'Author', '=', user.username - send_keys 'report ' + send_keys 'report', :enter select_tokens 'Label', '=', bug_label.title select_tokens 'Label', '=', caps_sensitive_label.title select_tokens 'Milestone', '=', milestone.title - send_keys 'foo', :enter + send_keys 'foo', :enter, :enter expect_issues_list_count(1) expect_search_term('bug report foo') diff --git a/spec/features/issues/filtered_search/recent_searches_spec.rb b/spec/features/issues/filtered_search/recent_searches_spec.rb index 2d9c73f2756..0efa2f49e36 100644 --- a/spec/features/issues/filtered_search/recent_searches_spec.rb +++ b/spec/features/issues/filtered_search/recent_searches_spec.rb @@ -100,7 +100,7 @@ RSpec.describe 'Recent searches', :js, feature_category: :team_planning do def submit_then_clear_search(search) click_filtered_search_bar - send_keys(search, :enter) + send_keys(search, :enter, :enter) click_button 'Clear' end end diff --git a/spec/features/issues/filtered_search/search_bar_spec.rb b/spec/features/issues/filtered_search/search_bar_spec.rb index c975df2a531..35c099b29aa 100644 --- a/spec/features/issues/filtered_search/search_bar_spec.rb +++ b/spec/features/issues/filtered_search/search_bar_spec.rb @@ -70,7 +70,8 @@ RSpec.describe 'Search bar', :js, feature_category: :team_planning do original_size = get_suggestion_count send_keys 'autho' - expect_suggestion_count 1 + # Expect Author, Search for this text + expect_suggestion_count 2 click_button 'Clear' click_filtered_search_bar diff --git a/spec/features/issues/filtered_search/visual_tokens_spec.rb b/spec/features/issues/filtered_search/visual_tokens_spec.rb index f25925ed33d..3031b20eb7c 100644 --- a/spec/features/issues/filtered_search/visual_tokens_spec.rb +++ b/spec/features/issues/filtered_search/visual_tokens_spec.rb @@ -79,10 +79,10 @@ RSpec.describe 'Visual tokens', :js, feature_category: :team_planning do describe 'editing a search term while editing another filter token' do before do click_filtered_search_bar - send_keys 'foo ' + send_keys 'foo', :enter select_tokens 'Assignee', '=' click_token_segment 'foo' - send_keys ' ' + send_keys :enter end it 'opens author dropdown' do @@ -98,44 +98,6 @@ RSpec.describe 'Visual tokens', :js, feature_category: :team_planning do end end - describe 'add new token after editing existing token' do - before do - select_tokens 'Assignee', '=', user.username, 'Label', '=', 'None' - click_token_segment(user.name) - send_keys ' ' - end - - describe 'opens dropdowns' do - it 'opens hint dropdown' do - expect_visible_suggestions_list - end - - it 'opens token dropdown' do - click_on 'Author' - - expect_visible_suggestions_list - end - end - - describe 'visual tokens' do - it 'creates visual token' do - click_on 'Author' - click_on '= is' - click_on 'The Rock' - - expect_author_token 'The Rock' - end - end - - it 'does not tokenize incomplete token' do - click_on 'Author' - find('.js-navbar').click - - expect_empty_search_term - expect_token_segment 'Assignee' - end - end - describe 'search using incomplete visual tokens' do before do select_tokens 'Author', '=', user.username, 'Assignee', '=', 'None' diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb index 585740f7782..9702e43a559 100644 --- a/spec/features/issues/form_spec.rb +++ b/spec/features/issues/form_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe 'New/edit issue', :js, feature_category: :team_planning do include ActionView::Helpers::JavaScriptHelper + include ListboxHelpers let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { create(:user) } @@ -13,9 +14,11 @@ RSpec.describe 'New/edit issue', :js, feature_category: :team_planning do let_it_be(:label) { create(:label, project: project) } let_it_be(:label2) { create(:label, project: project) } let_it_be(:issue) { create(:issue, project: project, assignees: [user], milestone: milestone) } + let_it_be(:issue2) { create(:issue, project: project, assignees: [user], milestone: milestone) } let_it_be(:confidential_issue) { create(:issue, project: project, assignees: [user], milestone: milestone, confidential: true) } let(:current_user) { user } + let(:visible_label_selection_on_metadata) { false } before_all do project.add_maintainer(user) @@ -25,6 +28,7 @@ RSpec.describe 'New/edit issue', :js, feature_category: :team_planning do before do stub_licensed_features(multiple_issue_assignees: false, issue_weights: false) + stub_feature_flags(visible_label_selection_on_metadata: visible_label_selection_on_metadata) sign_in(current_user) end @@ -112,110 +116,240 @@ RSpec.describe 'New/edit issue', :js, feature_category: :team_planning do end end - it 'allows user to create new issue' do - fill_in 'issue_title', with: 'title' - fill_in 'issue_description', with: 'title' + context 'with the visible_label_selection_on_metadata feature flag enabled' do + let(:visible_label_selection_on_metadata) { true } - expect(find('a', text: 'Assign to me')).to be_visible - click_button 'Unassigned' + it 'allows user to create new issue' do + fill_in 'issue_title', with: 'title' + fill_in 'issue_description', with: 'title' - wait_for_requests + expect(find('a', text: 'Assign to me')).to be_visible + click_button 'Unassigned' - page.within '.dropdown-menu-user' do - click_link user2.name - end - expect(find('input[name="issue[assignee_ids][]"]', visible: false).value).to match(user2.id.to_s) - page.within '.js-assignee-search' do - expect(page).to have_content user2.name - end - expect(find('a', text: 'Assign to me')).to be_visible + wait_for_requests + + page.within '.dropdown-menu-user' do + click_link user2.name + end + expect(find('input[name="issue[assignee_ids][]"]', visible: false).value).to match(user2.id.to_s) + page.within '.js-assignee-search' do + expect(page).to have_content user2.name + end + expect(find('a', text: 'Assign to me')).to be_visible + + click_link 'Assign to me' + assignee_ids = page.all('input[name="issue[assignee_ids][]"]', visible: false) + + expect(assignee_ids[0].value).to match(user.id.to_s) + + page.within '.js-assignee-search' do + expect(page).to have_content user.name + end + expect(find('a', text: 'Assign to me', visible: false)).not_to be_visible + + click_button 'Select milestone' + click_button milestone.title + expect(find('input[name="issue[milestone_id]"]', visible: false).value).to match(milestone.id.to_s) + expect(page).to have_button milestone.title + + click_button _('Select label') + wait_for_all_requests + page.within '[data-testid="sidebar-labels"]' do + click_button label.title + click_button label2.title + click_button _('Close') + wait_for_requests + page.within('[data-testid="embedded-labels-list"]') do + expect(page).to have_content(label.title) + expect(page).to have_content(label2.title) + end + end + + click_button 'Create issue' + + page.within '.issuable-sidebar' do + page.within '.assignee' do + expect(page).to have_content "Assignee" + end - click_link 'Assign to me' - assignee_ids = page.all('input[name="issue[assignee_ids][]"]', visible: false) + page.within '.milestone' do + expect(page).to have_content milestone.title + end + + page.within '.labels' do + expect(page).to have_content label.title + expect(page).to have_content label2.title + end + end - expect(assignee_ids[0].value).to match(user.id.to_s) + page.within '.breadcrumbs' do + issue = Issue.find_by(title: 'title') - page.within '.js-assignee-search' do - expect(page).to have_content user.name + expect(page).to have_text("Issues #{issue.to_reference}") + end end - expect(find('a', text: 'Assign to me', visible: false)).not_to be_visible - click_button 'Select milestone' - click_button milestone.title - expect(find('input[name="issue[milestone_id]"]', visible: false).value).to match(milestone.id.to_s) - expect(page).to have_button milestone.title + it 'correctly updates the dropdown toggle when removing a label' do + click_button _('Select label') + + wait_for_all_requests - click_button 'Labels' - page.within '.dropdown-menu-labels' do - click_link label.title - click_link label2.title + page.within '[data-testid="sidebar-labels"]' do + click_button label.title + click_button _('Close') + + wait_for_requests + + page.within('[data-testid="embedded-labels-list"]') do + expect(page).to have_content(label.title) + end + + expect(page.find('.gl-dropdown-button-text')).to have_content(label.title) + end + + click_button label.title, class: 'gl-dropdown-toggle' + + wait_for_all_requests + + page.within '[data-testid="sidebar-labels"]' do + click_button label.title, class: 'dropdown-item' + click_button _('Close') + + wait_for_requests + + expect(page).not_to have_selector('[data-testid="embedded-labels-list"]') + expect(page.find('.gl-dropdown-button-text')).to have_content(_('Select label')) + end end - find('.js-issuable-form-dropdown.js-label-select').click + it 'clears label search input field when a label is selected', :js do + click_button _('Select label') - page.within '.js-label-select' do - expect(page).to have_content label.title + wait_for_all_requests + + page.within '[data-testid="sidebar-labels"]' do + search_field = find('input[type="search"]') + + search_field.native.send_keys(label.title) + + expect(page).to have_css('.gl-search-box-by-type-clear') + + click_button label.title, class: 'dropdown-item' + + expect(page).not_to have_css('.gl-search-box-by-type-clear') + expect(search_field.value).to eq '' + end end - expect(page.all('input[name="issue[label_ids][]"]', visible: false)[1].value).to match(label.id.to_s) - expect(page.all('input[name="issue[label_ids][]"]', visible: false)[2].value).to match(label2.id.to_s) + end - click_button 'Create issue' + context 'with the visible_label_selection_on_metadata feature flag disabled' do + let(:visible_label_selection_on_metadata) { false } + + it 'allows user to create new issue' do + fill_in 'issue_title', with: 'title' + fill_in 'issue_description', with: 'title' - page.within '.issuable-sidebar' do - page.within '.assignee' do - expect(page).to have_content "Assignee" + expect(find('a', text: 'Assign to me')).to be_visible + click_button 'Unassigned' + + wait_for_requests + + page.within '.dropdown-menu-user' do + click_link user2.name end + expect(find('input[name="issue[assignee_ids][]"]', visible: false).value).to match(user2.id.to_s) + page.within '.js-assignee-search' do + expect(page).to have_content user2.name + end + expect(find('a', text: 'Assign to me')).to be_visible + + click_link 'Assign to me' + assignee_ids = page.all('input[name="issue[assignee_ids][]"]', visible: false) + + expect(assignee_ids[0].value).to match(user.id.to_s) + + page.within '.js-assignee-search' do + expect(page).to have_content user.name + end + expect(find('a', text: 'Assign to me', visible: false)).not_to be_visible - page.within '.milestone' do - expect(page).to have_content milestone.title + click_button 'Select milestone' + click_button milestone.title + expect(find('input[name="issue[milestone_id]"]', visible: false).value).to match(milestone.id.to_s) + expect(page).to have_button milestone.title + + click_button 'Labels' + page.within '.dropdown-menu-labels' do + click_link label.title + click_link label2.title end - page.within '.labels' do + find('.js-issuable-form-dropdown.js-label-select').click + + page.within '.js-label-select' do expect(page).to have_content label.title - expect(page).to have_content label2.title end - end + expect(page.all('input[name="issue[label_ids][]"]', visible: false)[1].value).to match(label.id.to_s) + expect(page.all('input[name="issue[label_ids][]"]', visible: false)[2].value).to match(label2.id.to_s) - page.within '.breadcrumbs' do - issue = Issue.find_by(title: 'title') + click_button 'Create issue' - expect(page).to have_text("Issues #{issue.to_reference}") - end - end + page.within '.issuable-sidebar' do + page.within '.assignee' do + expect(page).to have_content "Assignee" + end - it 'displays an error message when submitting an invalid form' do - click_button 'Create issue' + page.within '.milestone' do + expect(page).to have_content milestone.title + end - page.within('[data-testid="issue-title-input-field"]') do - expect(page).to have_text(_('This field is required.')) - end - end + page.within '.labels' do + expect(page).to have_content label.title + expect(page).to have_content label2.title + end + end - it 'correctly updates the dropdown toggle when removing a label' do - click_button 'Labels' + page.within '.breadcrumbs' do + issue = Issue.find_by(title: 'title') - page.within '.dropdown-menu-labels' do - click_link label.title + expect(page).to have_text("Issues #{issue.to_reference}") + end end - expect(find('.js-label-select')).to have_content(label.title) + it 'correctly updates the dropdown toggle when removing a label' do + click_button 'Labels' + + page.within '.dropdown-menu-labels' do + click_link label.title + end + + expect(find('.js-label-select')).to have_content(label.title) + + page.within '.dropdown-menu-labels' do + click_link label.title + end - page.within '.dropdown-menu-labels' do - click_link label.title + expect(find('.js-label-select')).to have_content('Labels') end - expect(find('.js-label-select')).to have_content('Labels') - end + it 'clears label search input field when a label is selected' do + click_button 'Labels' + + page.within '.dropdown-menu-labels' do + search_field = find('input[type="search"]') - it 'clears label search input field when a label is selected' do - click_button 'Labels' + search_field.set(label2.title) + click_link label2.title + expect(search_field.value).to eq '' + end + end + end - page.within '.dropdown-menu-labels' do - search_field = find('input[type="search"]') + it 'displays an error message when submitting an invalid form' do + click_button 'Create issue' - search_field.set(label2.title) - click_link label2.title - expect(search_field.value).to eq '' + page.within('[data-testid="issue-title-input-field"]') do + expect(page).to have_text(_('This field is required.')) end end @@ -248,19 +382,15 @@ RSpec.describe 'New/edit issue', :js, feature_category: :team_planning do describe 'displays issue type options in the dropdown' do shared_examples 'type option is visible' do |label:, identifier:| it "shows #{identifier} option", :aggregate_failures do - page.within('[data-testid="issue-type-select-dropdown"]') do - expect(page).to have_selector(%([data-testid="issue-type-#{identifier}-icon"])) - expect(page).to have_content(label) - end + wait_for_requests + expect_listbox_item(label) end end shared_examples 'type option is missing' do |label:, identifier:| it "does not show #{identifier} option", :aggregate_failures do - page.within('[data-testid="issue-type-select-dropdown"]') do - expect(page).not_to have_selector(%([data-testid="issue-type-#{identifier}-icon"])) - expect(page).not_to have_content(label) - end + wait_for_requests + expect_no_listbox_item(label) end end @@ -428,42 +558,100 @@ RSpec.describe 'New/edit issue', :js, feature_category: :team_planning do visit edit_project_issue_path(project, issue) end - it 'allows user to update issue' do - expect(find('input[name="issue[assignee_ids][]"]', visible: false).value).to match(user.id.to_s) - expect(find('input[name="issue[milestone_id]"]', visible: false).value).to match(milestone.id.to_s) - expect(find('a', text: 'Assign to me', visible: false)).not_to be_visible + context 'with the visible_label_selection_on_metadata feature flag enabled' do + let(:visible_label_selection_on_metadata) { true } - page.within '.js-user-search' do - expect(page).to have_content user.name - end + it 'allows user to update issue' do + expect(find('input[name="issue[assignee_ids][]"]', visible: false).value).to match(user.id.to_s) + expect(find('input[name="issue[milestone_id]"]', visible: false).value).to match(milestone.id.to_s) + expect(find('a', text: 'Assign to me', visible: false)).not_to be_visible + + page.within '.js-user-search' do + expect(page).to have_content user.name + end - expect(page).to have_button milestone.title + expect(page).to have_button milestone.title - click_button 'Labels' - page.within '.dropdown-menu-labels' do - click_link label.title - click_link label2.title - end - page.within '.js-label-select' do - expect(page).to have_content label.title + click_button _('Select label') + + wait_for_all_requests + + page.within '[data-testid="sidebar-labels"]' do + click_button label.title + click_button label2.title + click_button _('Close') + + wait_for_requests + + page.within('[data-testid="embedded-labels-list"]') do + expect(page).to have_content(label.title) + expect(page).to have_content(label2.title) + end + end + + expect(page.all('input[name="issue[label_ids][]"]', visible: false) + .map(&:value)) + .to contain_exactly(label.id.to_s, label2.id.to_s) + + click_button 'Save changes' + + page.within '.issuable-sidebar' do + page.within '.assignee' do + expect(page).to have_content user.name + end + + page.within '.milestone' do + expect(page).to have_content milestone.title + end + + page.within '.labels' do + expect(page).to have_content label.title + expect(page).to have_content label2.title + end + end end - expect(page.all('input[name="issue[label_ids][]"]', visible: false)[1].value).to match(label.id.to_s) - expect(page.all('input[name="issue[label_ids][]"]', visible: false)[2].value).to match(label2.id.to_s) + end + + context 'with the visible_label_selection_on_metadata feature flag disabled' do + let(:visible_label_selection_on_metadata) { false } - click_button 'Save changes' + it 'allows user to update issue' do + expect(find('input[name="issue[assignee_ids][]"]', visible: false).value).to match(user.id.to_s) + expect(find('input[name="issue[milestone_id]"]', visible: false).value).to match(milestone.id.to_s) + expect(find('a', text: 'Assign to me', visible: false)).not_to be_visible - page.within '.issuable-sidebar' do - page.within '.assignee' do + page.within '.js-user-search' do expect(page).to have_content user.name end - page.within '.milestone' do - expect(page).to have_content milestone.title - end + expect(page).to have_button milestone.title - page.within '.labels' do + click_button 'Labels' + page.within '.dropdown-menu-labels' do + click_link label.title + click_link label2.title + end + page.within '.js-label-select' do expect(page).to have_content label.title - expect(page).to have_content label2.title + end + expect(page.all('input[name="issue[label_ids][]"]', visible: false)[1].value).to match(label.id.to_s) + expect(page.all('input[name="issue[label_ids][]"]', visible: false)[2].value).to match(label2.id.to_s) + + click_button 'Save changes' + + page.within '.issuable-sidebar' do + page.within '.assignee' do + expect(page).to have_content user.name + end + + page.within '.milestone' do + expect(page).to have_content milestone.title + end + + page.within '.labels' do + expect(page).to have_content label.title + expect(page).to have_content label2.title + end end end end @@ -477,14 +665,69 @@ RSpec.describe 'New/edit issue', :js, feature_category: :team_planning do end describe 'inline edit' do - before do - visit project_issue_path(project, issue) + context 'within issue 1' do + before do + visit project_issue_path(project, issue) + wait_for_requests + end + + it 'opens inline edit form with shortcut' do + find('body').send_keys('e') + + expect(page).to have_selector('.detail-page-description form') + end + + describe 'when user has made no changes' do + it 'let user leave the page without warnings' do + expected_content = 'Issue created' + expect(page).to have_content(expected_content) + + find('body').send_keys('e') + + click_link 'Boards' + + expect(page).not_to have_content(expected_content) + end + end + + describe 'when user has made changes' do + it 'shows a warning and can stay on page', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/397683' do + content = 'new issue content' + + find('body').send_keys('e') + fill_in 'issue-description', with: content + + click_link 'Boards' + + page.driver.browser.switch_to.alert.dismiss + + click_button 'Save changes' + wait_for_requests + + expect(page).to have_content(content) + end + end end - it 'opens inline edit form with shortcut' do - find('body').send_keys('e') + context 'within issue 2' do + before do + visit project_issue_path(project, issue2) + wait_for_requests + end - expect(page).to have_selector('.detail-page-description form') + describe 'when user has made changes' do + it 'shows a warning and can leave page', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/410497' do + content = 'new issue content' + find('body').send_keys('e') + fill_in 'issue-description', with: content + + click_link 'Boards' + + page.driver.browser.switch_to.alert.accept + + expect(page).not_to have_content(content) + end + end end end @@ -499,22 +742,55 @@ RSpec.describe 'New/edit issue', :js, feature_category: :team_planning do visit new_project_issue_path(sub_group_project) end - it 'creates project label from dropdown' do - click_button 'Labels' + context 'with the visible_label_selection_on_metadata feature flag enabled', :js do + let(:visible_label_selection_on_metadata) { true } - click_link 'Create project label' + it 'creates project label from dropdown' do + find('[data-testid="labels-select-dropdown-contents"] button').click - page.within '.dropdown-new-label' do - fill_in 'new_label_name', with: 'test label' - first('.suggest-colors-dropdown a').click + wait_for_all_requests - click_button 'Create' + page.within '[data-testid="sidebar-labels"]' do + click_button _('Create project label') - wait_for_requests + wait_for_requests + end + + page.within '.js-labels-create' do + find('[data-testid="label-title-input"]').fill_in with: 'test label' + first('.suggest-colors-dropdown a').click + + click_button 'Create' + + wait_for_all_requests + end + + page.within '.js-labels-list' do + expect(page).to have_button 'test label' + end end + end + + context 'with the visible_label_selection_on_metadata feature flag disabled' do + let(:visible_label_selection_on_metadata) { false } + + it 'creates project label from dropdown' do + click_button 'Labels' + + click_link 'Create project label' - page.within '.dropdown-menu-labels' do - expect(page).to have_link 'test label' + page.within '.dropdown-new-label' do + fill_in 'new_label_name', with: 'test label' + first('.suggest-colors-dropdown a').click + + click_button 'Create' + + wait_for_requests + end + + page.within '.dropdown-menu-labels' do + expect(page).to have_link 'test label' + end end end end diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb index 2bd5373b715..665c7307231 100644 --- a/spec/features/issues/gfm_autocomplete_spec.rb +++ b/spec/features/issues/gfm_autocomplete_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe 'GFM autocomplete', :js, feature_category: :team_planning do + include CookieHelper + let_it_be(:user) { create(:user, name: '💃speciąl someone💃', username: 'someone.special') } let_it_be(:user2) { create(:user, name: 'Marge Simpson', username: 'msimpson') } @@ -45,6 +47,7 @@ RSpec.describe 'GFM autocomplete', :js, feature_category: :team_planning do before do sign_in(user) + set_cookie('new-actions-popover-viewed', 'true') visit project_issue_path(project, issue_to_edit) wait_for_requests diff --git a/spec/features/issues/incident_issue_spec.rb b/spec/features/issues/incident_issue_spec.rb index 41bbd79202f..145b51d207a 100644 --- a/spec/features/issues/incident_issue_spec.rb +++ b/spec/features/issues/incident_issue_spec.rb @@ -51,8 +51,8 @@ RSpec.describe 'Incident Detail', :js, feature_category: :team_planning do aggregate_failures 'when on summary tab (default tab)' do hidden_items = find_all('.js-issue-widgets') - # Linked Issues/MRs and comment box and emoji block - expect(hidden_items.count).to eq(3) + # Description footer + Linked Issues/MRs + comment box + emoji block + expect(hidden_items.count).to eq(4) expect(hidden_items).to all(be_visible) edit_button = find_all('[aria-label="Edit title and description"]') diff --git a/spec/features/issues/issue_detail_spec.rb b/spec/features/issues/issue_detail_spec.rb index 20a69c61871..29a61d584ee 100644 --- a/spec/features/issues/issue_detail_spec.rb +++ b/spec/features/issues/issue_detail_spec.rb @@ -48,6 +48,30 @@ RSpec.describe 'Issue Detail', :js, feature_category: :team_planning do end end + context 'when issue description has task list items' do + before do + description = '- [ ] I am a task + +| Table | +|-------| +| <ul><li>[ ] I am inside a table</li><ul> |' + issue.update!(description: description) + + sign_in(user) + visit project_issue_path(project, issue) + end + + it 'shows task actions ellipsis button when hovering over the task list item, but not within a table', :aggregate_failures do + find('li', text: 'I am a task').hover + + expect(page).to have_button 'Task actions' + + find('li', text: 'I am inside a table').hover + + expect(page).not_to have_button 'Task actions' + end + end + context 'when issue description has xss snippet' do before do issue.update!(description: '') @@ -74,6 +98,7 @@ RSpec.describe 'Issue Detail', :js, feature_category: :team_planning do project.add_developer(user_to_be_deleted) sign_in(user_to_be_deleted) + stub_feature_flags(moved_mr_sidebar: false) visit project_issue_path(project, issue) wait_for_requests @@ -105,7 +130,7 @@ RSpec.describe 'Issue Detail', :js, feature_category: :team_planning do describe 'when an issue `issue_type` is edited' do before do sign_in(user) - + set_cookie('new-actions-popover-viewed', 'true') visit project_issue_path(project, issue) wait_for_requests end @@ -139,7 +164,7 @@ RSpec.describe 'Issue Detail', :js, feature_category: :team_planning do describe 'when an incident `issue_type` is edited' do before do sign_in(user) - + set_cookie('new-actions-popover-viewed', 'true') visit project_issue_path(project, incident) wait_for_requests end diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb index 686074f7412..ee71181fba2 100644 --- a/spec/features/issues/issue_sidebar_spec.rb +++ b/spec/features/issues/issue_sidebar_spec.rb @@ -4,7 +4,8 @@ require 'spec_helper' RSpec.describe 'Issue Sidebar', feature_category: :team_planning do include MobileHelpers - include Spec::Support::Helpers::Features::InviteMembersModalHelper + include Features::InviteMembersModalHelpers + include CookieHelper let_it_be(:group) { create(:group, :nested) } let_it_be(:project) { create(:project, :public, namespace: group) } @@ -20,6 +21,7 @@ RSpec.describe 'Issue Sidebar', feature_category: :team_planning do context 'when signed in' do before do sign_in(user) + set_cookie('new-actions-popover-viewed', 'true') end context 'when concerning the assignee', :js do @@ -86,12 +88,12 @@ RSpec.describe 'Issue Sidebar', feature_category: :team_planning do end within '.js-right-sidebar' do - find('.block.assignee').click(x: 0, y: 0) + find('.block.assignee').click(x: 0, y: 0, offset: 0) find('.block.assignee .edit-link').click end - expect(page.all('.dropdown-menu-user li').length).to eq(1) - expect(find('.dropdown-input-field').value).to eq(user2.name) + expect(page.all('.dropdown-menu-user li').length).to eq(6) + expect(find('.dropdown-input-field').value).to eq('') end it 'shows label text as "Apply" when assignees are changed' do @@ -119,8 +121,6 @@ RSpec.describe 'Issue Sidebar', feature_category: :team_planning do page.within '.dropdown-menu-user' do expect(page).to have_link('Invite members') - expect(page).to have_selector('[data-track-action="click_invite_members"]') - expect(page).to have_selector('[data-track-label="edit_assignee"]') click_link 'Invite members' end @@ -207,6 +207,7 @@ RSpec.describe 'Issue Sidebar', feature_category: :team_planning do context 'as an allowed user' do before do + stub_feature_flags(moved_mr_sidebar: false) project.add_developer(user) visit_issue(project, issue) end @@ -295,6 +296,7 @@ RSpec.describe 'Issue Sidebar', feature_category: :team_planning do context 'as a guest' do before do + stub_feature_flags(moved_mr_sidebar: false) project.add_guest(user) visit_issue(project, issue) end diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb index ea68f2266b3..4512e88ae72 100644 --- a/spec/features/issues/move_spec.rb +++ b/spec/features/issues/move_spec.rb @@ -97,7 +97,7 @@ RSpec.describe 'issue move to another project', feature_category: :team_planning end end - context 'service desk issue moved to a project with service desk disabled', :js do + context 'service desk issue moved to a project with service desk disabled', :saas, :js do let(:project_title) { 'service desk disabled project' } let(:warning_selector) { '.js-alert-moved-from-service-desk-warning' } let(:namespace) { create(:namespace) } @@ -106,9 +106,8 @@ RSpec.describe 'issue move to another project', feature_category: :team_planning let(:service_desk_issue) { create(:issue, project: service_desk_project, author: ::User.support_bot) } before do - allow(Gitlab).to receive(:com?).and_return(true) - allow(Gitlab::IncomingEmail).to receive(:enabled?).and_return(true) - allow(Gitlab::IncomingEmail).to receive(:supports_wildcard?).and_return(true) + allow(Gitlab::Email::IncomingEmail).to receive(:enabled?).and_return(true) + allow(Gitlab::Email::IncomingEmail).to receive(:supports_wildcard?).and_return(true) regular_project.add_reporter(user) service_desk_project.add_reporter(user) diff --git a/spec/features/issues/rss_spec.rb b/spec/features/issues/rss_spec.rb index 36dffeded50..eb45d3c8d8b 100644 --- a/spec/features/issues/rss_spec.rb +++ b/spec/features/issues/rss_spec.rb @@ -23,24 +23,20 @@ RSpec.describe 'Project Issues RSS', :js, feature_category: :team_planning do before do sign_in(user) visit path + click_button 'Actions' end - it "shows the RSS button with current_user's feed token" do - expect(page).to have_link 'Subscribe to RSS feed', href: /feed_token=#{user.feed_token}/ - end - + it_behaves_like "it has an RSS link with current_user's feed token" it_behaves_like "an autodiscoverable RSS feed with current_user's feed token" end context 'when signed out' do before do visit path + click_button 'Actions' end - it "shows the RSS button without a feed token" do - expect(page).not_to have_link 'Subscribe to RSS feed', href: /feed_token/ - end - + it_behaves_like "it has an RSS link without a feed token" it_behaves_like "an autodiscoverable RSS feed without a feed token" end diff --git a/spec/features/issues/service_desk_spec.rb b/spec/features/issues/service_desk_spec.rb index 922ab95538b..0cadeb62fa2 100644 --- a/spec/features/issues/service_desk_spec.rb +++ b/spec/features/issues/service_desk_spec.rb @@ -10,8 +10,8 @@ RSpec.describe 'Service Desk Issue Tracker', :js, feature_category: :team_planni before do # The following two conditions equate to Gitlab::ServiceDesk.supported == true - allow(Gitlab::IncomingEmail).to receive(:enabled?).and_return(true) - allow(Gitlab::IncomingEmail).to receive(:supports_wildcard?).and_return(true) + allow(Gitlab::Email::IncomingEmail).to receive(:enabled?).and_return(true) + allow(Gitlab::Email::IncomingEmail).to receive(:supports_wildcard?).and_return(true) project.add_maintainer(user) sign_in(user) diff --git a/spec/features/issues/user_bulk_edits_issues_labels_spec.rb b/spec/features/issues/user_bulk_edits_issues_labels_spec.rb index 1fc6609d1f5..a01ae9ae0c2 100644 --- a/spec/features/issues/user_bulk_edits_issues_labels_spec.rb +++ b/spec/features/issues/user_bulk_edits_issues_labels_spec.rb @@ -406,7 +406,7 @@ RSpec.describe 'Issues > Labels bulk assignment', feature_category: :team_planni context 'cannot bulk assign labels' do it do - expect(page).not_to have_button 'Edit issues' + expect(page).not_to have_button 'Bulk edit' expect(page).not_to have_unchecked_field 'Select all' expect(page).not_to have_unchecked_field issue1.title end @@ -462,7 +462,7 @@ RSpec.describe 'Issues > Labels bulk assignment', feature_category: :team_planni def enable_bulk_update visit project_issues_path(project) wait_for_requests - click_button 'Edit issues' + click_button 'Bulk edit' end def disable_bulk_update diff --git a/spec/features/issues/user_bulk_edits_issues_spec.rb b/spec/features/issues/user_bulk_edits_issues_spec.rb index 5696bde4069..3e119d86c05 100644 --- a/spec/features/issues/user_bulk_edits_issues_spec.rb +++ b/spec/features/issues/user_bulk_edits_issues_spec.rb @@ -16,7 +16,7 @@ RSpec.describe 'Multiple issue updating from issues#index', :js, feature_categor it 'sets to closed', :js do visit project_issues_path(project) - click_button 'Edit issues' + click_button 'Bulk edit' check 'Select all' click_button 'Select status' click_button 'Closed' @@ -29,7 +29,7 @@ RSpec.describe 'Multiple issue updating from issues#index', :js, feature_categor create_closed visit project_issues_path(project, state: 'closed') - click_button 'Edit issues' + click_button 'Bulk edit' check 'Select all' click_button 'Select status' click_button 'Open' @@ -43,7 +43,7 @@ RSpec.describe 'Multiple issue updating from issues#index', :js, feature_categor it 'updates to current user' do visit project_issues_path(project) - click_button 'Edit issues' + click_button 'Bulk edit' check 'Select all' click_update_assignee_button click_button user.username @@ -61,7 +61,7 @@ RSpec.describe 'Multiple issue updating from issues#index', :js, feature_categor expect(find('.issue:first-of-type')).to have_link "Assigned to #{user.name}" - click_button 'Edit issues' + click_button 'Bulk edit' check 'Select all' click_update_assignee_button click_button 'Unassigned' @@ -77,7 +77,7 @@ RSpec.describe 'Multiple issue updating from issues#index', :js, feature_categor it 'updates milestone' do visit project_issues_path(project) - click_button 'Edit issues' + click_button 'Bulk edit' check 'Select all' click_button 'Select milestone' click_button milestone.title @@ -94,7 +94,7 @@ RSpec.describe 'Multiple issue updating from issues#index', :js, feature_categor expect(find('.issue:first-of-type')).to have_text milestone.title - click_button 'Edit issues' + click_button 'Bulk edit' check 'Select all' click_button 'Select milestone' click_button 'No milestone' @@ -110,7 +110,7 @@ RSpec.describe 'Multiple issue updating from issues#index', :js, feature_categor it 'after selecting all issues, unchecking one issue only unselects that one issue' do visit project_issues_path(project) - click_button 'Edit issues' + click_button 'Bulk edit' check 'Select all' uncheck issue.title diff --git a/spec/features/issues/user_comments_on_issue_spec.rb b/spec/features/issues/user_comments_on_issue_spec.rb index 59e1413fc97..3ace560fb40 100644 --- a/spec/features/issues/user_comments_on_issue_spec.rb +++ b/spec/features/issues/user_comments_on_issue_spec.rb @@ -3,7 +3,7 @@ require "spec_helper" RSpec.describe "User comments on issue", :js, feature_category: :team_planning do - include Spec::Support::Helpers::Features::NotesHelpers + include Features::NotesHelpers let_it_be(:project) { create(:project, :public) } let_it_be(:issue) { create(:issue, project: project) } @@ -32,6 +32,8 @@ RSpec.describe "User comments on issue", :js, feature_category: :team_planning d end end + it_behaves_like 'edits content using the content editor' + it "adds comment with code block" do code_block_content = "Command [1]: /usr/local/bin/git , see [text](doc/text)" comment = "```\n#{code_block_content}\n```" diff --git a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb index bbc14368d82..6d9eb3a7191 100644 --- a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb +++ b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb @@ -73,8 +73,8 @@ RSpec.describe 'User creates branch and merge request on issue page', :js, featu expect(page).to have_content('New merge request') expect(page).to have_content("From #{issue.to_branch_name} into #{project.default_branch}") - expect(page).to have_content("Closes ##{issue.iid}") expect(page).to have_field("Title", with: "Draft: Resolve \"Cherry-Coloured Funk\"") + expect(page).to have_field("Description", with: "Closes ##{issue.iid}") expect(page).to have_current_path(project_new_merge_request_path(project, merge_request: { source_branch: issue.to_branch_name, target_branch: project.default_branch, issue_iid: issue.iid })) end end @@ -98,8 +98,8 @@ RSpec.describe 'User creates branch and merge request on issue page', :js, featu expect(page).to have_content('New merge request') expect(page).to have_content("From #{branch_name} into #{project.default_branch}") - expect(page).to have_content("Closes ##{issue.iid}") expect(page).to have_field("Title", with: "Draft: Resolve \"Cherry-Coloured Funk\"") + expect(page).to have_field("Description", with: "Closes ##{issue.iid}") expect(page).to have_current_path(project_new_merge_request_path(project, merge_request: { source_branch: branch_name, target_branch: project.default_branch, issue_iid: issue.iid })) end end @@ -113,6 +113,26 @@ RSpec.describe 'User creates branch and merge request on issue page', :js, featu expect(page).to have_current_path project_tree_path(project, branch_name), ignore_query: true end end + + context 'when branch name is invalid' do + shared_examples 'has error message' do |dropdown| + it 'has error message' do + select_dropdown_option(dropdown, 'custom-branch-name w~th ^bad chars?') + + wait_for_requests + + expect(page).to have_text("Can't contain spaces, ~, ^, ?") + end + end + + context 'when creating a merge request', :sidekiq_might_not_need_inline do + it_behaves_like 'has error message', 'create-mr' + end + + context 'when creating a branch', :sidekiq_might_not_need_inline do + it_behaves_like 'has error message', 'create-branch' + end + end end context "when there is a referenced merge request" do diff --git a/spec/features/issues/user_creates_issue_spec.rb b/spec/features/issues/user_creates_issue_spec.rb index c5d0791dc57..d4148717f0a 100644 --- a/spec/features/issues/user_creates_issue_spec.rb +++ b/spec/features/issues/user_creates_issue_spec.rb @@ -8,16 +8,20 @@ RSpec.describe "User creates issue", feature_category: :team_planning do let_it_be(:project) { create(:project_empty_repo, :public) } let_it_be(:user) { create(:user) } + let(:visible_label_selection_on_metadata) { false } + context "when unauthenticated" do before do sign_out(:user) end - it "redirects to signin then back to new issue after signin", :js do + it "redirects to signin then back to new issue after signin", :js, quarantine: 'https://gitlab.com/gitlab-org/quality/engineering-productivity/master-broken-incidents/-/issues/1486' do create(:issue, project: project) visit project_issues_path(project) + wait_for_all_requests + page.within ".nav-controls" do click_link "New issue" end @@ -32,6 +36,7 @@ RSpec.describe "User creates issue", feature_category: :team_planning do context "when signed in as guest", :js do before do + stub_feature_flags(visible_label_selection_on_metadata: visible_label_selection_on_metadata) project.add_guest(user) sign_in(user) @@ -61,18 +66,18 @@ RSpec.describe "User creates issue", feature_category: :team_planning do page.within(form) do click_button("Preview") - preview = find(".js-md-preview") # this element is findable only when the "Preview" link is clicked. + preview = find(".js-vue-md-preview") # this element is findable only when the "Preview" link is clicked. expect(preview).to have_content("Nothing to preview.") - click_button("Write") + click_button("Continue editing") fill_in("Description", with: "Bug fixed :smile:") click_button("Preview") expect(preview).to have_css("gl-emoji") expect(textarea).not_to be_visible - click_button("Write") + click_button("Continue editing") fill_in("Description", with: "/confidential") click_button("Preview") @@ -90,18 +95,50 @@ RSpec.describe "User creates issue", feature_category: :team_planning do end end - it "creates issue" do - issue_title = "500 error on profile" + context 'with the visible_label_selection_on_metadata feature flag enabled' do + let(:visible_label_selection_on_metadata) { true } + + it "creates issue" do + issue_title = "500 error on profile" + + fill_in("Title", with: issue_title) + + click_button _('Select label') + + wait_for_all_requests + + page.within '[data-testid="sidebar-labels"]' do + click_button label_titles.first + click_button _('Close') + + wait_for_requests + end + + click_button("Create issue") + + expect(page).to have_content(issue_title) + .and have_content(user.name) + .and have_content(project.name) + .and have_content(label_titles.first) + end + end + + context 'with the visible_label_selection_on_metadata feature flag disabled' do + let(:visible_label_selection_on_metadata) { false } + + it "creates issue" do + issue_title = "500 error on profile" - fill_in("Title", with: issue_title) - click_button("Label") - click_link(label_titles.first) - click_button("Create issue") + fill_in("Title", with: issue_title) + click_button("Label") + click_link(label_titles.first) + click_button("Create issue") - expect(page).to have_content(issue_title) - .and have_content(user.name) - .and have_content(project.name) - .and have_content(label_titles.first) + expect(page).to have_content(issue_title) + .and have_content(user.name) + .and have_content(project.name) + .and have_content(label_titles.first) + end end end @@ -127,6 +164,8 @@ RSpec.describe "User creates issue", feature_category: :team_planning do end end + it_behaves_like 'edits content using the content editor' + context 'dropzone upload file', :js do before do visit new_project_issue_path(project) @@ -184,7 +223,7 @@ RSpec.describe "User creates issue", feature_category: :team_planning do end it 'pre-fills the issue type dropdown with issue type' do - expect(find('.js-issuable-type-filter-dropdown-wrap .dropdown-toggle-text')).to have_content('Issue') + expect(find('.js-issuable-type-filter-dropdown-wrap .gl-button-text')).to have_content('Issue') end it 'does not hide the milestone select' do @@ -200,7 +239,7 @@ RSpec.describe "User creates issue", feature_category: :team_planning do end it 'does not pre-fill the issue type dropdown with incident type' do - expect(find('.js-issuable-type-filter-dropdown-wrap .dropdown-toggle-text')).not_to have_content('Incident') + expect(find('.js-issuable-type-filter-dropdown-wrap .gl-button-text')).not_to have_content('Incident') end it 'shows the milestone select' do @@ -231,6 +270,7 @@ RSpec.describe "User creates issue", feature_category: :team_planning do wait_for_requests expect(page).to have_field('Title', with: '') + expect(page).to have_field('Description', with: '') fill_in 'issue_title', with: 'bug 345' fill_in 'issue_description', with: 'bug description' @@ -238,6 +278,21 @@ RSpec.describe "User creates issue", feature_category: :team_planning do click_button 'Create issue' end end + + it 'clears local storage after cancelling a new issue creation', :js do + 2.times do + visit new_project_issue_path(project) + wait_for_requests + + expect(page).to have_field('Title', with: '') + expect(page).to have_field('Description', with: '') + + fill_in 'issue_title', with: 'bug 345' + fill_in 'issue_description', with: 'bug description' + + click_link 'Cancel' + end + end end context 'when signed in as reporter', :js do @@ -257,7 +312,7 @@ RSpec.describe "User creates issue", feature_category: :team_planning do end it 'pre-fills the issue type dropdown with incident type' do - expect(find('.js-issuable-type-filter-dropdown-wrap .dropdown-toggle-text')).to have_content('Incident') + expect(find('.js-issuable-type-filter-dropdown-wrap .gl-button-text')).to have_content('Incident') end it 'hides the epic select' do diff --git a/spec/features/issues/user_edits_issue_spec.rb b/spec/features/issues/user_edits_issue_spec.rb index bf2af918f39..bc20660d2a0 100644 --- a/spec/features/issues/user_edits_issue_spec.rb +++ b/spec/features/issues/user_edits_issue_spec.rb @@ -3,6 +3,8 @@ require "spec_helper" RSpec.describe "Issues > User edits issue", :js, feature_category: :team_planning do + include CookieHelper + let_it_be(:project) { create(:project_empty_repo, :public) } let_it_be(:project_with_milestones) { create(:project_empty_repo, :public) } let_it_be(:user) { create(:user) } @@ -18,6 +20,7 @@ RSpec.describe "Issues > User edits issue", :js, feature_category: :team_plannin project.add_developer(user) project_with_milestones.add_developer(user) sign_in(user) + set_cookie('new-actions-popover-viewed', 'true') end context "from edit page" do @@ -26,6 +29,8 @@ RSpec.describe "Issues > User edits issue", :js, feature_category: :team_plannin visit edit_project_issue_path(project, issue) end + it_behaves_like 'edits content using the content editor' + it "previews content", quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/391757' do form = first(".gfm-form") @@ -34,9 +39,7 @@ RSpec.describe "Issues > User edits issue", :js, feature_category: :team_plannin click_button("Preview") end - expect(form).to have_button("Write") - - click_button("Write") + click_button("Continue editing") fill_in("Description", with: "/confidential") click_button("Preview") @@ -51,7 +54,7 @@ RSpec.describe "Issues > User edits issue", :js, feature_category: :team_plannin first('.js-user-search').click click_link 'Unassigned' - click_button 'Save changes' + click_button _('Save changes') page.within('.assignee') do expect(page).to have_content 'None - assign yourself' @@ -76,7 +79,7 @@ RSpec.describe "Issues > User edits issue", :js, feature_category: :team_plannin expect(find('#issuable-due-date').value).to eq date.to_s - click_button 'Save changes' + click_button _('Save changes') page.within '.issuable-sidebar' do expect(page).to have_content date.to_s(:medium) @@ -89,9 +92,15 @@ RSpec.describe "Issues > User edits issue", :js, feature_category: :team_plannin fill_in 'issue_title', with: 'bug 345' fill_in 'issue_description', with: 'bug description' - click_button 'Save changes' + click_button _('Save changes') - expect(page).to have_content 'Someone edited the issue the same time you did' + expect(page).to have_content( + format( + _("Someone edited this %{model_name} at the same time you did. Please check out the %{link_to_model} and make sure your changes will not unintentionally remove theirs."), # rubocop:disable Layout/LineLength + model_name: _('issue'), + link_to_model: _('issue') + ) + ) end end end @@ -111,23 +120,27 @@ RSpec.describe "Issues > User edits issue", :js, feature_category: :team_plannin markdown_field_focused_selector = 'textarea:focus' click_edit_issue_description - expect(page).to have_selector(markdown_field_focused_selector) + issuable_form = find('[data-testid="issuable-form"]') - click_on _('View rich text') - click_on _('Rich text') + expect(issuable_form).to have_selector(markdown_field_focused_selector) - expect(page).not_to have_selector(content_editor_focused_selector) + page.within issuable_form do + click_button("Switch to rich text") + end + + expect(issuable_form).not_to have_selector(content_editor_focused_selector) refresh click_edit_issue_description - expect(page).to have_selector(content_editor_focused_selector) + expect(issuable_form).to have_selector(content_editor_focused_selector) - click_on _('View markdown') - click_on _('Markdown') + page.within issuable_form do + click_button("Switch to Markdown") + end - expect(page).not_to have_selector(markdown_field_focused_selector) + expect(issuable_form).not_to have_selector(markdown_field_focused_selector) end end diff --git a/spec/features/issues/user_sorts_issue_comments_spec.rb b/spec/features/issues/user_sorts_issue_comments_spec.rb index ca52e620ea7..153066343f2 100644 --- a/spec/features/issues/user_sorts_issue_comments_spec.rb +++ b/spec/features/issues/user_sorts_issue_comments_spec.rb @@ -16,9 +16,8 @@ RSpec.describe 'Comment sort direction', feature_category: :team_planning do it 'saves sort order' do # open dropdown, and select 'Newest first' page.within('.issuable-details') do - find('#discussion-preferences-dropdown').click + click_button('Sort or filter') click_button('Oldest first') - find('#discussion-preferences-dropdown').click click_button('Newest first') end diff --git a/spec/features/issues/user_toggles_subscription_spec.rb b/spec/features/issues/user_toggles_subscription_spec.rb index 904fafdf56a..00b04c10d33 100644 --- a/spec/features/issues/user_toggles_subscription_spec.rb +++ b/spec/features/issues/user_toggles_subscription_spec.rb @@ -10,6 +10,7 @@ RSpec.describe "User toggles subscription", :js, feature_category: :team_plannin context 'user is not logged in' do before do + stub_feature_flags(moved_mr_sidebar: false) visit(project_issue_path(project, issue)) end @@ -20,9 +21,9 @@ RSpec.describe "User toggles subscription", :js, feature_category: :team_plannin context 'user is logged in' do before do + stub_feature_flags(moved_mr_sidebar: false) project.add_developer(user) sign_in(user) - visit(project_issue_path(project, issue)) end @@ -52,6 +53,7 @@ RSpec.describe "User toggles subscription", :js, feature_category: :team_plannin context 'user is logged in without edit permission' do before do + stub_feature_flags(moved_mr_sidebar: false) sign_in(user2) visit(project_issue_path(project, issue)) diff --git a/spec/features/issues/user_uses_quick_actions_spec.rb b/spec/features/issues/user_uses_quick_actions_spec.rb index 963f1c56fef..e85a521e242 100644 --- a/spec/features/issues/user_uses_quick_actions_spec.rb +++ b/spec/features/issues/user_uses_quick_actions_spec.rb @@ -8,7 +8,7 @@ require 'spec_helper' # Because this kind of spec takes more time to run there is no need to add new ones # for each existing quick action unless they test something not tested by existing tests. RSpec.describe 'Issues > User uses quick actions', :js, feature_category: :team_planning do - include Spec::Support::Helpers::Features::NotesHelpers + include Features::NotesHelpers context "issuable common quick actions" do let(:new_url_opts) { {} } diff --git a/spec/features/jira_connect/branches_spec.rb b/spec/features/jira_connect/branches_spec.rb index c90c0d2dda9..25dc14a1dc9 100644 --- a/spec/features/jira_connect/branches_spec.rb +++ b/spec/features/jira_connect/branches_spec.rb @@ -75,7 +75,7 @@ RSpec.describe 'Create GitLab branches from Jira', :js, feature_category: :integ select_listbox_item(source_branch) fill_in 'Branch name', with: new_branch - click_on 'Create branch' + click_button 'Create branch' expect(page).to have_text('New branch was successfully created. You can now close this window and return to Jira.') diff --git a/spec/features/jira_oauth_provider_authorize_spec.rb b/spec/features/jira_oauth_provider_authorize_spec.rb index a542aaa7619..e873d9c219f 100644 --- a/spec/features/jira_oauth_provider_authorize_spec.rb +++ b/spec/features/jira_oauth_provider_authorize_spec.rb @@ -4,18 +4,39 @@ require 'spec_helper' RSpec.describe 'JIRA OAuth Provider', feature_category: :integrations do describe 'JIRA DVCS OAuth Authorization' do - let(:application) { create(:oauth_application, redirect_uri: oauth_jira_dvcs_callback_url, scopes: 'read_user') } + let_it_be(:application) do + create(:oauth_application, redirect_uri: oauth_jira_dvcs_callback_url, scopes: 'read_user') + end + + let(:authorize_path) do + oauth_jira_dvcs_authorize_path(client_id: application.uid, + redirect_uri: oauth_jira_dvcs_callback_url, + response_type: 'code', + state: 'my_state', + scope: 'read_user') + end before do sign_in(user) + end - visit oauth_jira_dvcs_authorize_path(client_id: application.uid, - redirect_uri: oauth_jira_dvcs_callback_url, - response_type: 'code', - state: 'my_state', - scope: 'read_user') + it_behaves_like 'Secure OAuth Authorizations' do + before do + visit authorize_path + end end - it_behaves_like 'Secure OAuth Authorizations' + context 'when the flag is disabled' do + let_it_be(:user) { create(:user) } + + before do + stub_feature_flags(jira_dvcs_end_of_life_amnesty: false) + visit authorize_path + end + + it 'presents as an endpoint that does not exist' do + expect(page).to have_gitlab_http_status(:not_found) + end + end end end diff --git a/spec/features/labels_hierarchy_spec.rb b/spec/features/labels_hierarchy_spec.rb index d6e607e80df..e8f40a1ceab 100644 --- a/spec/features/labels_hierarchy_spec.rb +++ b/spec/features/labels_hierarchy_spec.rb @@ -157,28 +157,71 @@ RSpec.describe 'Labels Hierarchy', :js, feature_category: :team_planning do end end - context 'when creating new issuable' do + context 'with the visible_label_selection_on_metadata feature flag enabled' do before do - visit new_project_issue_path(project_1) + stub_feature_flags(visible_label_selection_on_metadata: true) end - it 'is able to assign ancestor group labels' do - fill_in 'issue_title', with: 'new created issue' - fill_in 'issue_description', with: 'new issue description' + context 'when creating new issuable' do + before do + visit new_project_issue_path(project_1) + end + + it 'is able to assign ancestor group labels' do + fill_in 'issue_title', with: 'new created issue' + fill_in 'issue_description', with: 'new issue description' + + click_button _('Select label') + + wait_for_all_requests + + page.within '[data-testid="sidebar-labels"]' do + click_button grandparent_group_label.title + click_button parent_group_label.title + click_button project_label_1.title + click_button _('Close') + + wait_for_requests + end + + find('.btn-confirm').click + + expect(page.find('.issue-details h1.title')).to have_content('new created issue') + expect(page).to have_selector('span.gl-label-text', text: grandparent_group_label.title) + expect(page).to have_selector('span.gl-label-text', text: parent_group_label.title) + expect(page).to have_selector('span.gl-label-text', text: project_label_1.title) + end + end + end - find(".js-label-select").click - wait_for_requests + context 'with the visible_label_selection_on_metadata feature flag disabled' do + before do + stub_feature_flags(visible_label_selection_on_metadata: false) + end - find('a.label-item', text: grandparent_group_label.title).click - find('a.label-item', text: parent_group_label.title).click - find('a.label-item', text: project_label_1.title).click + context 'when creating new issuable' do + before do + visit new_project_issue_path(project_1) + end + + it 'is able to assign ancestor group labels' do + fill_in 'issue_title', with: 'new created issue' + fill_in 'issue_description', with: 'new issue description' + + find(".js-label-select").click + wait_for_requests - find('.btn-confirm').click + find('a.label-item', text: grandparent_group_label.title).click + find('a.label-item', text: parent_group_label.title).click + find('a.label-item', text: project_label_1.title).click - expect(page.find('.issue-details h1.title')).to have_content('new created issue') - expect(page).to have_selector('span.gl-label-text', text: grandparent_group_label.title) - expect(page).to have_selector('span.gl-label-text', text: parent_group_label.title) - expect(page).to have_selector('span.gl-label-text', text: project_label_1.title) + find('.btn-confirm').click + + expect(page.find('.issue-details h1.title')).to have_content('new created issue') + expect(page).to have_selector('span.gl-label-text', text: grandparent_group_label.title) + expect(page).to have_selector('span.gl-label-text', text: parent_group_label.title) + expect(page).to have_selector('span.gl-label-text', text: project_label_1.title) + end end end diff --git a/spec/features/markdown/markdown_spec.rb b/spec/features/markdown/markdown_spec.rb index 6e62aa892bb..a31ad5a868e 100644 --- a/spec/features/markdown/markdown_spec.rb +++ b/spec/features/markdown/markdown_spec.rb @@ -68,7 +68,7 @@ RSpec.describe 'GitLab Markdown', :aggregate_failures, feature_category: :team_p end aggregate_failures 'parses mermaid code block' do - expect(doc).to have_selector('pre[lang=mermaid] > code.js-render-mermaid') + expect(doc).to have_selector('pre[data-canonical-lang=mermaid] > code.js-render-mermaid') end aggregate_failures 'parses strikethroughs' do @@ -250,6 +250,7 @@ RSpec.describe 'GitLab Markdown', :aggregate_failures, feature_category: :team_p aggregate_failures 'all reference filters' do expect(doc).to reference_users expect(doc).to reference_issues + expect(doc).to reference_work_items expect(doc).to reference_merge_requests expect(doc).to reference_snippets expect(doc).to reference_commit_ranges @@ -345,6 +346,7 @@ RSpec.describe 'GitLab Markdown', :aggregate_failures, feature_category: :team_p aggregate_failures 'all reference filters' do expect(doc).to reference_users expect(doc).to reference_issues + expect(doc).to reference_work_items expect(doc).to reference_merge_requests expect(doc).to reference_snippets expect(doc).to reference_commit_ranges diff --git a/spec/features/markdown/metrics_spec.rb b/spec/features/markdown/metrics_spec.rb index b5e42b16f87..1b68f78e993 100644 --- a/spec/features/markdown/metrics_spec.rb +++ b/spec/features/markdown/metrics_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Metrics rendering', :js, :kubeclient, :use_clean_rails_memory_store_caching, :sidekiq_inline, feature_category: :team_planning do +RSpec.describe 'Metrics rendering', :js, :kubeclient, :use_clean_rails_memory_store_caching, :sidekiq_inline, feature_category: :metrics do include PrometheusHelpers include KubernetesHelpers include GrafanaApiHelpers @@ -17,6 +17,7 @@ RSpec.describe 'Metrics rendering', :js, :kubeclient, :use_clean_rails_memory_st let(:metrics_url) { urls.metrics_project_environment_url(project, environment) } before do + stub_feature_flags(remove_monitor_metrics: false) clear_host_from_memoized_variables stub_gitlab_domain @@ -28,6 +29,20 @@ RSpec.describe 'Metrics rendering', :js, :kubeclient, :use_clean_rails_memory_st clear_host_from_memoized_variables end + shared_examples_for 'metrics dashboard unavailable' do + context 'when metrics dashboard feature is unavailable' do + before do + stub_feature_flags(remove_monitor_metrics: true) + end + + it 'shows no embedded metrics' do + visit project_issue_path(project, issue) + + expect(page).to have_no_css('div.prometheus-graph') + end + end + end + context 'internal metrics embeds' do before do import_common_metrics @@ -36,6 +51,8 @@ RSpec.describe 'Metrics rendering', :js, :kubeclient, :use_clean_rails_memory_st allow(Prometheus::ProxyService).to receive(:new).and_call_original end + include_examples 'metrics dashboard unavailable' + it 'shows embedded metrics' do visit project_issue_path(project, issue) @@ -50,6 +67,20 @@ RSpec.describe 'Metrics rendering', :js, :kubeclient, :use_clean_rails_memory_st .at_least(:once) end + context 'with remove_monitor_metrics flag enabled' do + before do + stub_feature_flags(remove_monitor_metrics: true) + end + + it 'does not show embedded metrics' do + visit project_issue_path(project, issue) + + expect(page).not_to have_css('div.prometheus-graph') + expect(page).not_to have_text('Memory Usage (Total)') + expect(page).not_to have_text('Core Usage (Total)') + end + end + context 'when dashboard params are in included the url' do let(:metrics_url) { urls.metrics_project_environment_url(project, environment, **chart_params) } @@ -120,7 +151,9 @@ RSpec.describe 'Metrics rendering', :js, :kubeclient, :use_clean_rails_memory_st allow(Grafana::ProxyService).to receive(:new).and_call_original end - it 'shows embedded metrics' do + include_examples 'metrics dashboard unavailable' + + it 'shows embedded metrics', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/402973' do visit project_issue_path(project, issue) expect(page).to have_css('div.prometheus-graph') @@ -157,6 +190,8 @@ RSpec.describe 'Metrics rendering', :js, :kubeclient, :use_clean_rails_memory_st stub_any_prometheus_request_with_response end + include_examples 'metrics dashboard unavailable' + it 'shows embedded metrics' do visit project_issue_path(project, issue) @@ -186,6 +221,8 @@ RSpec.describe 'Metrics rendering', :js, :kubeclient, :use_clean_rails_memory_st let(:metrics_url) { urls.namespace_project_cluster_url(*params, **query_params) } let(:description) { "# Summary \n[](#{metrics_url})" } + include_examples 'metrics dashboard unavailable' + it 'shows embedded metrics' do visit project_issue_path(project, issue) diff --git a/spec/features/markdown/observability_spec.rb b/spec/features/markdown/observability_spec.rb index 86caf3eb1b1..ec414d4396e 100644 --- a/spec/features/markdown/observability_spec.rb +++ b/spec/features/markdown/observability_spec.rb @@ -2,82 +2,44 @@ require 'spec_helper' -RSpec.describe 'Observability rendering', :js do +RSpec.describe 'Observability rendering', :js, feature_category: :metrics do let_it_be(:group) { create(:group, :public) } let_it_be(:project) { create(:project, :repository, group: group) } let_it_be(:user) { create(:user) } - let_it_be(:observable_url) { "https://observe.gitlab.com/" } - - let_it_be(:expected) do - %(<iframe src="#{observable_url}?theme=light&kiosk" frameborder="0") - end + let_it_be(:observable_url) { "https://www.gitlab.com/groups/#{group.path}/-/observability/explore?observability_path=/explore?foo=bar" } + let_it_be(:expected_observable_url) { "https://observe.gitlab.com/-/#{group.id}/explore?foo=bar" } before do - project.add_maintainer(user) + stub_config_setting(url: "https://www.gitlab.com") + group.add_developer(user) sign_in(user) end - context 'when embedding in an issue' do - let(:issue) do - create(:issue, project: project, description: observable_url) - end - - before do - visit project_issue_path(project, issue) - wait_for_requests - end - - it 'renders iframe in description' do - page.within('.description') do - expect(page.html).to include(expected) - end - end - - it 'renders iframe in comment' do - expect(page).not_to have_css('.note-text') - - page.within('.js-main-target-form') do - fill_in('note[note]', with: observable_url) - click_button('Comment') + context 'when user is a developer of the embedded group' do + context 'when embedding in an issue' do + let(:issue) do + create(:issue, project: project, description: observable_url) end - wait_for_requests - - page.within('.note-text') do - expect(page.html).to include(expected) + before do + visit project_issue_path(project, issue) + wait_for_requests end - end - end - - context 'when embedding in an MR' do - let(:merge_request) do - create(:merge_request, source_project: project, target_project: project, description: observable_url) - end - before do - visit merge_request_path(merge_request) - wait_for_requests + it_behaves_like 'embeds observability' end - it 'renders iframe in description' do - page.within('.description') do - expect(page.html).to include(expected) + context 'when embedding in an MR' do + let(:merge_request) do + create(:merge_request, source_project: project, target_project: project, description: observable_url) end - end - it 'renders iframe in comment' do - expect(page).not_to have_css('.note-text') - - page.within('.js-main-target-form') do - fill_in('note[note]', with: observable_url) - click_button('Comment') + before do + visit merge_request_path(merge_request) + wait_for_requests end - wait_for_requests - - page.within('.note-text') do - expect(page.html).to include(expected) - end + it_behaves_like 'embeds observability' end end @@ -96,28 +58,7 @@ RSpec.describe 'Observability rendering', :js do wait_for_requests end - it 'does not render iframe in description' do - page.within('.description') do - expect(page.html).not_to include(expected) - expect(page.html).to include(observable_url) - end - end - - it 'does not render iframe in comment' do - expect(page).not_to have_css('.note-text') - - page.within('.js-main-target-form') do - fill_in('note[note]', with: observable_url) - click_button('Comment') - end - - wait_for_requests - - page.within('.note-text') do - expect(page.html).not_to include(expected) - expect(page.html).to include(observable_url) - end - end + it_behaves_like 'does not embed observability' end context 'when embedding in an MR' do @@ -130,28 +71,7 @@ RSpec.describe 'Observability rendering', :js do wait_for_requests end - it 'does not render iframe in description' do - page.within('.description') do - expect(page.html).not_to include(expected) - expect(page.html).to include(observable_url) - end - end - - it 'does not render iframe in comment' do - expect(page).not_to have_css('.note-text') - - page.within('.js-main-target-form') do - fill_in('note[note]', with: observable_url) - click_button('Comment') - end - - wait_for_requests - - page.within('.note-text') do - expect(page.html).not_to include(expected) - expect(page.html).to include(observable_url) - end - end + it_behaves_like 'does not embed observability' end end end diff --git a/spec/features/markdown/sandboxed_mermaid_spec.rb b/spec/features/markdown/sandboxed_mermaid_spec.rb index 0282d02d809..f8a535191da 100644 --- a/spec/features/markdown/sandboxed_mermaid_spec.rb +++ b/spec/features/markdown/sandboxed_mermaid_spec.rb @@ -53,4 +53,28 @@ RSpec.describe 'Sandboxed Mermaid rendering', :js, feature_category: :team_plann end end end + + context 'in a project milestone' do + let(:milestone) { create(:project_milestone, project: project, description: description) } + + it 'includes mermaid frame correctly', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/408560' do + visit(project_milestone_path(project, milestone)) + + wait_for_requests + + expect(page.html).to include(expected) + end + end + + context 'in a group milestone' do + let(:group_milestone) { create(:group_milestone, description: description) } + + it 'includes mermaid frame correctly' do + visit(group_milestone_path(group_milestone.group, group_milestone)) + + wait_for_requests + + expect(page.html).to include(expected) + end + end end diff --git a/spec/features/merge_request/batch_comments_spec.rb b/spec/features/merge_request/batch_comments_spec.rb index ddbcb04fa80..6e98812753a 100644 --- a/spec/features/merge_request/batch_comments_spec.rb +++ b/spec/features/merge_request/batch_comments_spec.rb @@ -52,6 +52,8 @@ RSpec.describe 'Merge request > Batch comments', :js, feature_category: :code_re find('.js-note-delete').click + wait_for_requests + page.within('.modal') do click_button('Delete comment', match: :first) end @@ -66,6 +68,8 @@ RSpec.describe 'Merge request > Batch comments', :js, feature_category: :code_re find('.js-note-edit').click + wait_for_requests + # make sure comment form is in view execute_script("window.scrollBy(0, 200)") diff --git a/spec/features/merge_request/maintainer_edits_fork_spec.rb b/spec/features/merge_request/maintainer_edits_fork_spec.rb index b8dc3af8a6a..c9aa22e396b 100644 --- a/spec/features/merge_request/maintainer_edits_fork_spec.rb +++ b/spec/features/merge_request/maintainer_edits_fork_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe 'a maintainer edits files on a source-branch of an MR from a fork', :js, :sidekiq_might_not_need_inline, feature_category: :code_review_workflow do - include Spec::Support::Helpers::Features::SourceEditorSpecHelpers + include Features::SourceEditorSpecHelpers include ProjectForksHelper let(:user) { create(:user, username: 'the-maintainer') } let(:target_project) { create(:project, :public, :repository) } diff --git a/spec/features/merge_request/user_accepts_merge_request_spec.rb b/spec/features/merge_request/user_accepts_merge_request_spec.rb index 8ff0c294b24..e3989a8a192 100644 --- a/spec/features/merge_request/user_accepts_merge_request_spec.rb +++ b/spec/features/merge_request/user_accepts_merge_request_spec.rb @@ -16,7 +16,7 @@ RSpec.describe 'User accepts a merge request', :js, :sidekiq_might_not_need_inli it 'when merge method is set to merge commit' do visit(merge_request_path(merge_request)) - click_button('Merge') + click_merge_button puts merge_request.short_merged_commit_sha @@ -31,7 +31,7 @@ RSpec.describe 'User accepts a merge request', :js, :sidekiq_might_not_need_inli visit(merge_request_path(merge_request)) - click_button('Merge') + click_merge_button expect(page).to have_content("Changes merged into #{merge_request.target_branch} with #{merge_request.short_merged_commit_sha}") end @@ -41,7 +41,7 @@ RSpec.describe 'User accepts a merge request', :js, :sidekiq_might_not_need_inli visit(merge_request_path(merge_request)) - click_button('Merge') + click_merge_button expect(page).to have_content("Changes merged into #{merge_request.target_branch} with #{merge_request.short_merged_commit_sha}") end @@ -55,7 +55,7 @@ RSpec.describe 'User accepts a merge request', :js, :sidekiq_might_not_need_inli it 'accepts a merge request' do check('Delete source branch') - click_button('Merge') + click_merge_button expect(page).to have_content('Changes merged into') expect(page).not_to have_selector('.js-remove-branch-button') @@ -72,7 +72,7 @@ RSpec.describe 'User accepts a merge request', :js, :sidekiq_might_not_need_inli end it 'accepts a merge request' do - click_button('Merge') + click_merge_button expect(page).to have_content('Changes merged into') expect(page).to have_selector('.js-remove-branch-button') @@ -90,7 +90,7 @@ RSpec.describe 'User accepts a merge request', :js, :sidekiq_might_not_need_inli it 'accepts a merge request' do check('Delete source branch') - click_button('Merge') + click_merge_button expect(page).to have_content('Changes merged into') expect(page).not_to have_selector('.js-remove-branch-button') @@ -112,9 +112,15 @@ RSpec.describe 'User accepts a merge request', :js, :sidekiq_might_not_need_inli find('[data-testid="widget_edit_commit_message"]').click fill_in('merge-message-edit', with: 'wow such merge') - click_button('Merge') + click_merge_button expect(page).to have_selector('.gl-badge', text: 'Merged') end end + + def click_merge_button + page.within('.mr-state-widget') do + click_button 'Merge' + end + end end diff --git a/spec/features/merge_request/user_comments_on_diff_spec.rb b/spec/features/merge_request/user_comments_on_diff_spec.rb index 66b87148eb2..35e2fa2f89c 100644 --- a/spec/features/merge_request/user_comments_on_diff_spec.rb +++ b/spec/features/merge_request/user_comments_on_diff_spec.rb @@ -44,7 +44,7 @@ RSpec.describe 'User comments on a diff', :js, feature_category: :code_review_wo end context 'in multiple files' do - it 'toggles comments' do + it 'toggles comments', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/393518' do first_line_element = find_by_scrolling("[id='#{sample_compare.changes[0][:line_code]}']").find(:xpath, "..") first_root_element = first_line_element.ancestor('[data-path]') click_diff_line(first_line_element) @@ -248,7 +248,7 @@ RSpec.describe 'User comments on a diff', :js, feature_category: :code_review_wo page.within('.diff-file:nth-of-type(1) .discussion .note') do find('.more-actions').click - find('.more-actions .dropdown-menu li', match: :first) + find('.more-actions li', match: :first) find('.js-note-delete').click end diff --git a/spec/features/merge_request/user_comments_on_merge_request_spec.rb b/spec/features/merge_request/user_comments_on_merge_request_spec.rb index 9335615b4c7..e113e305af5 100644 --- a/spec/features/merge_request/user_comments_on_merge_request_spec.rb +++ b/spec/features/merge_request/user_comments_on_merge_request_spec.rb @@ -30,6 +30,8 @@ RSpec.describe 'User comments on a merge request', :js, feature_category: :code_ end end + it_behaves_like 'edits content using the content editor' + it 'replys to a new comment' do page.within('.js-main-target-form') do fill_in('note[note]', with: 'comment 1') diff --git a/spec/features/merge_request/user_creates_image_diff_notes_spec.rb b/spec/features/merge_request/user_creates_image_diff_notes_spec.rb index 1d7a3fae371..6d3268ffe3a 100644 --- a/spec/features/merge_request/user_creates_image_diff_notes_spec.rb +++ b/spec/features/merge_request/user_creates_image_diff_notes_spec.rb @@ -174,7 +174,7 @@ RSpec.describe 'Merge request > User creates image diff notes', :js, feature_cat end shared_examples 'onion skin' do - it 'resets opacity when toggling between view modes' do + it 'resets opacity when toggling between view modes', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/393331' do # Simulate dragging onion-skin slider drag_and_drop_by(find('.dragger'), -30, 0) diff --git a/spec/features/merge_request/user_creates_mr_spec.rb b/spec/features/merge_request/user_creates_mr_spec.rb index 6ee20a08a47..f48315a1636 100644 --- a/spec/features/merge_request/user_creates_mr_spec.rb +++ b/spec/features/merge_request/user_creates_mr_spec.rb @@ -9,35 +9,158 @@ RSpec.describe 'Merge request > User creates MR', feature_category: :code_review stub_licensed_features(multiple_merge_request_assignees: false) end - context 'non-fork merge request' do - include_context 'merge request create context' - it_behaves_like 'a creatable merge request' - end + shared_examples 'a creatable merge request with visible selected labels' do + include WaitForRequests + include ListboxHelpers + + it 'creates new merge request', :js do + find('[data-testid="assignee-ids-dropdown-toggle"]').click + page.within '.dropdown-menu-user' do + click_link user2.name + end + + expect(find('input[name="merge_request[assignee_ids][]"]', visible: false).value).to match(user2.id.to_s) + page.within '[data-testid="assignee-ids-dropdown-toggle"]' do + expect(page).to have_content user2.name + end + + click_link 'Assign to me' + + expect(find('input[name="merge_request[assignee_ids][]"]', visible: false).value).to match(user.id.to_s) + page.within '[data-testid="assignee-ids-dropdown-toggle"]' do + expect(page).to have_content user.name + end + + click_button 'Select milestone' + click_button milestone.title + expect(find('input[name="merge_request[milestone_id]"]', visible: false).value).to match(milestone.id.to_s) + expect(page).to have_button milestone.title + + click_button _('Select label') + wait_for_all_requests + page.within '[data-testid="sidebar-labels"]' do + click_button label.title + click_button label2.title + click_button _('Close') + wait_for_requests + page.within('[data-testid="embedded-labels-list"]') do + expect(page).to have_content(label.title) + expect(page).to have_content(label2.title) + end + end + + click_button 'Create merge request' + + page.within '.issuable-sidebar' do + page.within '.assignee' do + expect(page).to have_content user.name + end - context 'from a forked project' do - let(:canonical_project) { create(:project, :public, :repository) } + page.within '.milestone' do + expect(page).to have_content milestone.title + end - let(:source_project) do - fork_project(canonical_project, user, - repository: true, - namespace: user.namespace) + page.within '.labels' do + expect(page).to have_content label.title + expect(page).to have_content label2.title + end + end end - context 'to canonical project' do + it 'updates the branches when selecting a new target project', :js do + target_project_member = target_project.first_owner + ::Branches::CreateService.new(target_project, target_project_member) + .execute('a-brand-new-branch-to-test', 'master') + + visit project_new_merge_request_path(source_project) + + first('.js-target-project').click + select_listbox_item(target_project.full_path) + + wait_for_requests + + first('.js-target-branch').click + + find('.gl-listbox-search-input').set('a-brand-new-branch-to-test') + + wait_for_requests + + expect_listbox_item('a-brand-new-branch-to-test') + end + end + + context 'with the visible_label_selection_on_metadata feature flag enabled' do + before do + stub_feature_flags(visible_label_selection_on_metadata: true) + end + + context 'non-fork merge request' do include_context 'merge request create context' - it_behaves_like 'a creatable merge request' + it_behaves_like 'a creatable merge request with visible selected labels' end - context 'to another forked project' do - let(:target_project) do + context 'from a forked project' do + let(:canonical_project) { create(:project, :public, :repository) } + + let(:source_project) do fork_project(canonical_project, user, repository: true, namespace: user.namespace) end + context 'to canonical project' do + include_context 'merge request create context' + it_behaves_like 'a creatable merge request with visible selected labels' + end + + context 'to another forked project' do + let(:target_project) do + fork_project(canonical_project, user, + repository: true, + namespace: user.namespace) + end + + include_context 'merge request create context' + it_behaves_like 'a creatable merge request with visible selected labels' + end + end + end + + context 'with the visible_label_selection_on_metadata feature flag disabled' do + before do + stub_feature_flags(visible_label_selection_on_metadata: false) + end + + context 'non-fork merge request' do include_context 'merge request create context' it_behaves_like 'a creatable merge request' end + + context 'from a forked project' do + let(:canonical_project) { create(:project, :public, :repository) } + + let(:source_project) do + fork_project(canonical_project, user, + repository: true, + namespace: user.namespace) + end + + context 'to canonical project' do + include_context 'merge request create context' + it_behaves_like 'a creatable merge request' + end + + context 'to another forked project' do + let(:target_project) do + fork_project(canonical_project, user, + repository: true, + namespace: user.namespace) + end + + include_context 'merge request create context' + it_behaves_like 'a creatable merge request' + end + end end context 'source project', :js do diff --git a/spec/features/merge_request/user_edits_assignees_sidebar_spec.rb b/spec/features/merge_request/user_edits_assignees_sidebar_spec.rb index cf5024ad59e..faef4f6f517 100644 --- a/spec/features/merge_request/user_edits_assignees_sidebar_spec.rb +++ b/spec/features/merge_request/user_edits_assignees_sidebar_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'Merge request > User edits assignees sidebar', :js, feature_category: :code_review_workflow do - include Spec::Support::Helpers::Features::InviteMembersModalHelper + include Features::InviteMembersModalHelpers let(:project) { create(:project, :public, :repository) } let(:protected_branch) { create(:protected_branch, :maintainers_can_push, name: 'master', project: project) } @@ -162,8 +162,6 @@ RSpec.describe 'Merge request > User edits assignees sidebar', :js, feature_cate page.within '.dropdown-menu-user' do expect(page).to have_link('Invite members') - expect(page).to have_selector('[data-track-action="click_invite_members"]') - expect(page).to have_selector('[data-track-label="edit_assignee"]') click_link 'Invite members' end diff --git a/spec/features/merge_request/user_edits_merge_request_spec.rb b/spec/features/merge_request/user_edits_merge_request_spec.rb index 839081d00dc..584a17ae33d 100644 --- a/spec/features/merge_request/user_edits_merge_request_spec.rb +++ b/spec/features/merge_request/user_edits_merge_request_spec.rb @@ -108,4 +108,6 @@ RSpec.describe 'User edits a merge request', :js, feature_category: :code_review end end end + + it_behaves_like 'edits content using the content editor' end diff --git a/spec/features/merge_request/user_edits_mr_spec.rb b/spec/features/merge_request/user_edits_mr_spec.rb index 6fcbfd309e2..76588832ee1 100644 --- a/spec/features/merge_request/user_edits_mr_spec.rb +++ b/spec/features/merge_request/user_edits_mr_spec.rb @@ -5,19 +5,233 @@ require 'spec_helper' RSpec.describe 'Merge request > User edits MR', feature_category: :code_review_workflow do include ProjectForksHelper + shared_examples 'an editable merge request with visible selected labels' do + it 'updates merge request', :js do + find('.js-assignee-search').click + page.within '.dropdown-menu-user' do + click_link user.name + end + expect(find('input[name="merge_request[assignee_ids][]"]', visible: false).value).to match(user.id.to_s) + page.within '.js-assignee-search' do + expect(page).to have_content user.name + end + + find('.js-reviewer-search').click + page.within '.dropdown-menu-user' do + click_link user.name + end + expect(find('input[name="merge_request[reviewer_ids][]"]', visible: false).value).to match(user.id.to_s) + page.within '.js-reviewer-search' do + expect(page).to have_content user.name + end + + click_button 'Select milestone' + click_button milestone.title + expect(find('input[name="merge_request[milestone_id]"]', visible: false).value).to match(milestone.id.to_s) + expect(page).to have_button milestone.title + + click_button _('Select label') + wait_for_all_requests + page.within '[data-testid="sidebar-labels"]' do + click_button label.title + click_button label2.title + click_button _('Close') + wait_for_requests + page.within('[data-testid="embedded-labels-list"]') do + expect(page).to have_content(label.title) + expect(page).to have_content(label2.title) + end + end + + click_button 'Save changes' + + page.within '.issuable-sidebar' do + page.within '.assignee' do + expect(page).to have_content user.name + end + + page.within '.reviewer' do + expect(page).to have_content user.name + end + + page.within '.milestone' do + expect(page).to have_content milestone.title + end + + page.within '.labels' do + expect(page).to have_content label.title + expect(page).to have_content label2.title + end + end + end + + it 'description has autocomplete', :js do + find('#merge_request_description').native.send_keys('') + fill_in 'merge_request_description', with: user.to_reference[0..4] + + page.within('.atwho-view') do + expect(page).to have_content(user2.name) + end + end + + it 'description has quick action autocomplete', :js do + find('#merge_request_description').native.send_keys('/') + + expect(page).to have_selector('.atwho-container') + end + + it 'has class js-quick-submit in form' do + expect(page).to have_selector('.js-quick-submit') + end + + it 'warns about version conflict', :js do + merge_request.update!(title: "New title") + + fill_in 'merge_request_title', with: 'bug 345' + fill_in 'merge_request_description', with: 'bug description' + + click_button _('Save changes') + + expect(page).to have_content( + format( + _("Someone edited this %{model_name} at the same time you did. Please check out the %{link_to_model} and make sure your changes will not unintentionally remove theirs."), # rubocop:disable Layout/LineLength + model_name: _('merge request'), + link_to_model: _('merge request') + ) + ) + end + + it 'preserves description textarea height', :js do + long_description = %q( + Lorem ipsum dolor sit amet, consectetur adipiscing elit. + Etiam ac ornare ligula, ut tempus arcu. + Etiam ultricies accumsan dolor vitae faucibus. + Donec at elit lacus. + Mauris orci ante, aliquam quis lorem eget, convallis faucibus arcu. + Aenean at pulvinar lacus. + Ut viverra quam massa, molestie ornare tortor dignissim a. + Suspendisse tristique pellentesque tellus, id lacinia metus elementum id. + Nam tristique, arcu rhoncus faucibus viverra, lacus ipsum sagittis ligula, vitae convallis odio lacus a nibh. + Ut tincidunt est purus, ac vestibulum augue maximus in. + Suspendisse vel erat et mi ultricies semper. + Pellentesque volutpat pellentesque consequat. + + Cras congue nec ligula tristique viverra. + Curabitur fringilla fringilla fringilla. + Donec rhoncus dignissim orci ut accumsan. + Ut rutrum urna a rhoncus varius. + Maecenas blandit, mauris nec accumsan gravida, augue nibh finibus magna, sed maximus turpis libero nec neque + Suspendisse at semper est. + Nunc imperdiet dapibus dui, varius sollicitudin erat luctus non. + Sed pellentesque ligula eget posuere facilisis. + Donec dictum commodo volutpat. + Donec egestas dui ac magna sollicitudin bibendum. + Vivamus purus neque, ullamcorper ac feugiat et, tempus sit amet metus. + Praesent quis viverra neque. + Sed bibendum viverra est, eu aliquam mi ornare vitae. + Proin et dapibus ipsum. + Nunc tortor diam, malesuada nec interdum vel, placerat quis justo. + Ut viverra at erat eu laoreet. + + Pellentesque commodo, diam sit amet dignissim condimentum, tortor justo pretium est, + non venenatis metus eros ut nunc. + Etiam ut neque eget sem dapibus aliquam. + Curabitur vel elit lorem. + Nulla nec enim elit. + Sed ut ex id justo facilisis convallis at ac augue. + Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; + Nullam cursus egestas turpis non tristique. + Suspendisse in erat sem. + Fusce libero elit, fermentum gravida mauris id, auctor iaculis felis. + Nullam vulputate tempor laoreet. + + Nam tempor et magna sed convallis. + Fusce sit amet sollicitudin risus, a ullamcorper lacus. + Morbi gravida quis sem eget porttitor. + Donec eu egestas mauris, in elementum tortor. + Sed eget ex mi. + Mauris iaculis tortor ut est auctor, nec dignissim quam sagittis. + Suspendisse vel metus non quam suscipit tincidunt. + Cras molestie lacus non justo finibus sodales quis vitae erat. + In a porttitor nisi, id sollicitudin urna. + Ut at felis tellus. + Suspendisse potenti. + + Maecenas leo ligula, varius at neque vitae, ornare maximus justo. + Nullam convallis luctus risus et vulputate. + Duis suscipit faucibus iaculis. + Etiam quis tortor faucibus, tristique tellus sit amet, sodales neque. + Nulla dapibus nisi vel aliquet consequat. + Etiam faucibus, metus eget condimentum iaculis, enim urna lobortis sem, id efficitur eros sapien nec nisi. + Aenean ut finibus ex. + ) + + fill_in 'merge_request_description', with: long_description + + height = get_textarea_height + click_button("Preview") + click_button("Continue editing") + new_height = get_textarea_height + + expect(height).to eq(new_height) + end + + context 'when "Remove source branch" is set' do + before do + merge_request.update!(merge_params: { 'force_remove_source_branch' => '1' }) + end + + it 'allows to unselect "Remove source branch"', :js do + expect(merge_request.merge_params['force_remove_source_branch']).to be_truthy + + visit edit_project_merge_request_path(target_project, merge_request) + uncheck 'Delete source branch when merge request is accepted' + + click_button 'Save changes' + + expect(page).to have_unchecked_field 'remove-source-branch-input' + expect(page).to have_content 'Delete source branch' + end + end + end + before do stub_licensed_features(multiple_merge_request_assignees: false) end - context 'non-fork merge request' do - include_context 'merge request edit context' - it_behaves_like 'an editable merge request' + context 'with the visible_label_selection_on_metadata feature flag enabled' do + before do + stub_feature_flags(visible_label_selection_on_metadata: true) + end + + context 'non-fork merge request' do + include_context 'merge request edit context' + it_behaves_like 'an editable merge request with visible selected labels' + end + + context 'for a forked project' do + let(:source_project) { fork_project(target_project, nil, repository: true) } + + include_context 'merge request edit context' + it_behaves_like 'an editable merge request with visible selected labels' + end end - context 'for a forked project' do - let(:source_project) { fork_project(target_project, nil, repository: true) } + context 'with the visible_label_selection_on_metadata feature flag disabled' do + before do + stub_feature_flags(visible_label_selection_on_metadata: false) + end + + context 'non-fork merge request' do + include_context 'merge request edit context' + it_behaves_like 'an editable merge request' + end + + context 'for a forked project' do + let(:source_project) { fork_project(target_project, nil, repository: true) } - include_context 'merge request edit context' - it_behaves_like 'an editable merge request' + include_context 'merge request edit context' + it_behaves_like 'an editable merge request' + end end end diff --git a/spec/features/merge_request/user_edits_reviewers_sidebar_spec.rb b/spec/features/merge_request/user_edits_reviewers_sidebar_spec.rb index 26a9b955e2d..52d058aeabc 100644 --- a/spec/features/merge_request/user_edits_reviewers_sidebar_spec.rb +++ b/spec/features/merge_request/user_edits_reviewers_sidebar_spec.rb @@ -26,8 +26,6 @@ RSpec.describe 'Merge request > User edits reviewers sidebar', :js, feature_cate page.within '.dropdown-menu-user' do expect(page).to have_link('Invite Members') - expect(page).to have_selector('[data-track-action="click_invite_members"]') - expect(page).to have_selector('[data-track-label="edit_reviewer"]') end click_link 'Invite Members' diff --git a/spec/features/merge_request/user_manages_subscription_spec.rb b/spec/features/merge_request/user_manages_subscription_spec.rb index d4ccc4a93b5..3bcc8255ab7 100644 --- a/spec/features/merge_request/user_manages_subscription_spec.rb +++ b/spec/features/merge_request/user_manages_subscription_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe 'User manages subscription', :js, feature_category: :code_review_workflow do + include CookieHelper + let(:project) { create(:project, :public, :repository) } let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } let(:user) { create(:user) } @@ -10,7 +12,7 @@ RSpec.describe 'User manages subscription', :js, feature_category: :code_review_ before do stub_feature_flags(moved_mr_sidebar: moved_mr_sidebar_enabled) - + set_cookie('new-actions-popover-viewed', 'true') project.add_maintainer(user) sign_in(user) diff --git a/spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb b/spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb index cdc00017ab3..19b5ad0fa84 100644 --- a/spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb +++ b/spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb @@ -13,11 +13,29 @@ RSpec.describe 'Merge request > User merges only if pipeline succeeds', :js, fea context 'project does not have CI enabled' do it 'allows MR to be merged' do + stub_feature_flags(auto_merge_labels_mr_widget: false) + visit project_merge_request_path(project, merge_request) wait_for_requests - expect(page).to have_button 'Merge' + page.within('.mr-state-widget') do + expect(page).to have_button 'Merge' + end + end + end + + context 'project does not have CI enabled and auto_merge_labels_mr_widget on' do + it 'allows MR to be merged' do + stub_feature_flags(auto_merge_labels_mr_widget: true) + + visit project_merge_request_path(project, merge_request) + + wait_for_requests + + page.within('.mr-state-widget') do + expect(page).to have_button 'Merge' + end end end @@ -33,6 +51,8 @@ RSpec.describe 'Merge request > User merges only if pipeline succeeds', :js, fea context 'when merge requests can only be merged if the pipeline succeeds' do before do project.update_attribute(:only_allow_merge_if_pipeline_succeeds, true) + + stub_feature_flags(auto_merge_labels_mr_widget: false) end context 'when CI is running' do @@ -56,7 +76,78 @@ RSpec.describe 'Merge request > User merges only if pipeline succeeds', :js, fea wait_for_requests - expect(page).not_to have_button('Merge') + expect(page).not_to have_button('Merge', exact: true) + expect(page).to have_content('Merge blocked: pipeline must succeed. Push a commit that fixes the failure or learn about other solutions.') + end + end + + context 'when CI canceled' do + let(:status) { :canceled } + + it 'does not allow MR to be merged' do + visit project_merge_request_path(project, merge_request) + + wait_for_requests + + expect(page).not_to have_button('Merge', exact: true) + expect(page).to have_content('Merge blocked: pipeline must succeed. Push a commit that fixes the failure or learn about other solutions.') + end + end + + context 'when CI succeeded' do + let(:status) { :success } + + it 'allows MR to be merged' do + visit project_merge_request_path(project, merge_request) + + wait_for_requests + + expect(page).to have_button('Merge', exact: true) + end + end + + context 'when CI skipped' do + let(:status) { :skipped } + + it 'does not allow MR to be merged' do + visit project_merge_request_path(project, merge_request) + + wait_for_requests + + expect(page).not_to have_button('Merge', exact: true) + end + end + end + + context 'when merge requests can only be merged if the pipeline succeeds with auto_merge_labels_mr_widget on' do + before do + project.update_attribute(:only_allow_merge_if_pipeline_succeeds, true) + + stub_feature_flags(auto_merge_labels_mr_widget: true) + end + + context 'when CI is running' do + let(:status) { :running } + + it 'does not allow to merge immediately' do + visit project_merge_request_path(project, merge_request) + + wait_for_requests + + expect(page).to have_button 'Set to auto-merge' + expect(page).not_to have_button '.js-merge-moment' + end + end + + context 'when CI failed' do + let(:status) { :failed } + + it 'does not allow MR to be merged' do + visit project_merge_request_path(project, merge_request) + + wait_for_requests + + expect(page).not_to have_button('Merge', exact: true) expect(page).to have_content('Merge blocked: pipeline must succeed. Push a commit that fixes the failure or learn about other solutions.') end end @@ -69,7 +160,7 @@ RSpec.describe 'Merge request > User merges only if pipeline succeeds', :js, fea wait_for_requests - expect(page).not_to have_button 'Merge' + expect(page).not_to have_button('Merge', exact: true) expect(page).to have_content('Merge blocked: pipeline must succeed. Push a commit that fixes the failure or learn about other solutions.') end end @@ -82,7 +173,7 @@ RSpec.describe 'Merge request > User merges only if pipeline succeeds', :js, fea wait_for_requests - expect(page).to have_button 'Merge' + expect(page).to have_button('Merge', exact: true) end end @@ -94,7 +185,7 @@ RSpec.describe 'Merge request > User merges only if pipeline succeeds', :js, fea wait_for_requests - expect(page).not_to have_button 'Merge' + expect(page).not_to have_button('Merge', exact: true) end end end @@ -102,6 +193,8 @@ RSpec.describe 'Merge request > User merges only if pipeline succeeds', :js, fea context 'when merge requests can be merged when the build failed' do before do project.update_attribute(:only_allow_merge_if_pipeline_succeeds, false) + + stub_feature_flags(auto_merge_labels_mr_widget: false) end context 'when CI is running' do @@ -126,8 +219,59 @@ RSpec.describe 'Merge request > User merges only if pipeline succeeds', :js, fea visit project_merge_request_path(project, merge_request) wait_for_requests + page.within('.mr-state-widget') do + expect(page).to have_button 'Merge' + end + end + end - expect(page).to have_button 'Merge' + context 'when CI succeeded' do + let(:status) { :success } + + it 'allows MR to be merged' do + visit project_merge_request_path(project, merge_request) + + wait_for_requests + + page.within('.mr-state-widget') do + expect(page).to have_button 'Merge' + end + end + end + end + + context 'when merge requests can be merged when the build failed with auto_merge_labels_mr_widget on' do + before do + project.update_attribute(:only_allow_merge_if_pipeline_succeeds, false) + + stub_feature_flags(auto_merge_labels_mr_widget: true) + end + + context 'when CI is running' do + let(:status) { :running } + + it 'allows MR to be merged immediately' do + visit project_merge_request_path(project, merge_request) + + wait_for_requests + + expect(page).to have_button 'Set to auto-merge' + + page.find('.js-merge-moment').click + expect(page).to have_content 'Merge immediately' + end + end + + context 'when CI failed' do + let(:status) { :failed } + + it 'allows MR to be merged' do + visit project_merge_request_path(project, merge_request) + + wait_for_requests + page.within('.mr-state-widget') do + expect(page).to have_button 'Merge' + end end end @@ -139,7 +283,9 @@ RSpec.describe 'Merge request > User merges only if pipeline succeeds', :js, fea wait_for_requests - expect(page).to have_button 'Merge' + page.within('.mr-state-widget') do + expect(page).to have_button 'Merge' + end end end end diff --git a/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb index 6d2c8f15a82..5c00da1f569 100644 --- a/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb +++ b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb @@ -26,13 +26,15 @@ RSpec.describe 'Merge request > User merges when pipeline succeeds', :js, featur context 'when there is active pipeline for merge request' do before do create(:ci_build, pipeline: pipeline) + stub_feature_flags(auto_merge_labels_mr_widget: false) + sign_in(user) visit project_merge_request_path(project, merge_request) end describe 'enabling Merge when pipeline succeeds' do shared_examples 'Merge when pipeline succeeds activator' do - it 'activates the Merge when pipeline succeeds feature' do + it 'activates the Merge when pipeline succeeds feature', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/410105' do click_button "Merge when pipeline succeeds" expect(page).to have_content "Set by #{user.name} to be merged automatically when the pipeline succeeds" @@ -98,6 +100,69 @@ RSpec.describe 'Merge request > User merges when pipeline succeeds', :js, featur end end + context 'when there is active pipeline for merge request with auto_merge_labels_mr_widget on' do + before do + create(:ci_build, pipeline: pipeline) + stub_feature_flags(auto_merge_labels_mr_widget: true) + + sign_in(user) + visit project_merge_request_path(project, merge_request) + end + + describe 'enabling Merge when pipeline succeeds' do + shared_examples 'Set to auto-merge activator' do + it 'activates the Merge when pipeline succeeds feature' do + click_button "Set to auto-merge" + + expect(page).to have_content "Set by #{user.name} to be merged automatically when the pipeline succeeds" + expect(page).to have_content "Source branch will not be deleted" + expect(page).to have_selector ".js-cancel-auto-merge" + visit project_merge_request_path(project, merge_request) # Needed to refresh the page + expect(page).to have_content /enabled an automatic merge when the pipeline for \h{8} succeeds/i + end + end + + context "when enabled immediately" do + it_behaves_like 'Set to auto-merge activator' + end + + context 'when enabled after it was previously canceled' do + before do + click_button "Set to auto-merge" + + wait_for_requests + + click_button "Cancel auto-merge" + + wait_for_requests + + expect(page).to have_content 'Set to auto-merge' + end + + it_behaves_like 'Set to auto-merge activator' + end + + context 'when it was enabled and then canceled' do + let(:merge_request) do + create(:merge_request_with_diffs, + :merge_when_pipeline_succeeds, + source_project: project, + title: 'Bug NS-04', + author: user, + merge_user: user) + end + + before do + merge_request.merge_params['force_remove_source_branch'] = '0' + merge_request.save! + click_button "Cancel auto-merge" + end + + it_behaves_like 'Set to auto-merge activator' + end + end + end + context 'when merge when pipeline succeeds is enabled' do let(:merge_request) do create(:merge_request_with_diffs, :simple, :merge_when_pipeline_succeeds, @@ -112,6 +177,7 @@ RSpec.describe 'Merge request > User merges when pipeline succeeds', :js, featur end before do + stub_feature_flags(auto_merge_labels_mr_widget: false) sign_in user visit project_merge_request_path(project, merge_request) end @@ -177,11 +243,53 @@ RSpec.describe 'Merge request > User merges when pipeline succeeds', :js, featur end end + context 'when merge when pipeline succeeds is enabled and auto_merge_labels_mr_widget on' do + let(:merge_request) do + create(:merge_request_with_diffs, :simple, :merge_when_pipeline_succeeds, + source_project: project, + author: user, + merge_user: user, + title: 'MepMep') + end + + let!(:build) do + create(:ci_build, pipeline: pipeline) + end + + before do + stub_feature_flags(auto_merge_labels_mr_widget: true) + sign_in user + visit project_merge_request_path(project, merge_request) + end + + it 'allows to cancel the automatic merge', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/410494' do + click_button "Cancel auto-merge" + + expect(page).to have_button "Set to auto-merge" + + refresh + + expect(page).to have_content "canceled the automatic merge" + end + end + context 'when pipeline is not active' do it 'does not allow to enable merge when pipeline succeeds' do + stub_feature_flags(auto_merge_labels_mr_widget: false) + visit project_merge_request_path(project, merge_request) expect(page).not_to have_link 'Merge when pipeline succeeds' end end + + context 'when pipeline is not active and auto_merge_labels_mr_widget on' do + it 'does not allow to enable merge when pipeline succeeds' do + stub_feature_flags(auto_merge_labels_mr_widget: true) + + visit project_merge_request_path(project, merge_request) + + expect(page).not_to have_link 'Set to auto-merge' + end + end end diff --git a/spec/features/merge_request/user_opens_checkout_branch_modal_spec.rb b/spec/features/merge_request/user_opens_checkout_branch_modal_spec.rb index 7cb1c95f6dc..601310cbacf 100644 --- a/spec/features/merge_request/user_opens_checkout_branch_modal_spec.rb +++ b/spec/features/merge_request/user_opens_checkout_branch_modal_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe 'Merge request > User opens checkout branch modal', :js, feature_category: :code_review_workflow do include ProjectForksHelper + include CookieHelper let(:project) { create(:project, :public, :repository) } let(:user) { project.creator } @@ -11,6 +12,7 @@ RSpec.describe 'Merge request > User opens checkout branch modal', :js, feature_ before do project.add_maintainer(user) sign_in(user) + set_cookie('new-actions-popover-viewed', 'true') end describe 'for fork' do diff --git a/spec/features/merge_request/user_posts_diff_notes_spec.rb b/spec/features/merge_request/user_posts_diff_notes_spec.rb index a74a8b1cd5a..f13c68a60ee 100644 --- a/spec/features/merge_request/user_posts_diff_notes_spec.rb +++ b/spec/features/merge_request/user_posts_diff_notes_spec.rb @@ -103,7 +103,7 @@ RSpec.describe 'Merge request > User posts diff notes', :js, feature_category: : should_allow_commenting(find_by_scrolling('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]')) accept_gl_confirm(button_text: 'Delete comment') do - first('button.more-actions-toggle').click + first('.more-actions-toggle button').click first('.js-note-delete').click end diff --git a/spec/features/merge_request/user_posts_notes_spec.rb b/spec/features/merge_request/user_posts_notes_spec.rb index f167ab8fe8a..03b01ef4b7a 100644 --- a/spec/features/merge_request/user_posts_notes_spec.rb +++ b/spec/features/merge_request/user_posts_notes_spec.rb @@ -62,7 +62,7 @@ RSpec.describe 'Merge request > User posts notes', :js, feature_category: :code_ before do page.within('.js-main-target-form') do fill_in 'note[note]', with: 'This is awesome!' - find('.js-md-preview-button').click + click_button("Preview") click_button 'Comment' end end @@ -138,7 +138,7 @@ RSpec.describe 'Merge request > User posts notes', :js, feature_category: :code_ it 'hides the toolbar buttons when previewing a note' do wait_for_requests - find('.js-md-preview-button').click + click_button("Preview") page.within('.js-main-target-form') do expect(page).not_to have_css('.md-header-toolbar') end diff --git a/spec/features/merge_request/user_resolves_conflicts_spec.rb b/spec/features/merge_request/user_resolves_conflicts_spec.rb index 7b1afd786f7..0f283f1194f 100644 --- a/spec/features/merge_request/user_resolves_conflicts_spec.rb +++ b/spec/features/merge_request/user_resolves_conflicts_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'Merge request > User resolves conflicts', :js, feature_category: :code_review_workflow do - include Spec::Support::Helpers::Features::SourceEditorSpecHelpers + include Features::SourceEditorSpecHelpers let(:project) { create(:project, :repository) } let(:user) { project.creator } diff --git a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb index c3b9068d708..5da9f4a1f19 100644 --- a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb +++ b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb @@ -218,7 +218,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js, feat end end - it 'allows user to quickly scroll to next unresolved thread' do + it 'allows user to quickly scroll to next unresolved thread', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/410109' do page.within '.discussions-counter' do page.find('.discussion-next-btn').click end diff --git a/spec/features/merge_request/user_reverts_merge_request_spec.rb b/spec/features/merge_request/user_reverts_merge_request_spec.rb index 43ce473b407..8c782056aa4 100644 --- a/spec/features/merge_request/user_reverts_merge_request_spec.rb +++ b/spec/features/merge_request/user_reverts_merge_request_spec.rb @@ -8,12 +8,15 @@ RSpec.describe 'User reverts a merge request', :js, feature_category: :code_revi let(:user) { create(:user) } before do + stub_feature_flags(unbatch_graphql_queries: false) project.add_developer(user) sign_in(user) visit(merge_request_path(merge_request)) - click_button('Merge') + page.within('.mr-state-widget') do + click_button 'Merge' + end wait_for_requests @@ -34,7 +37,7 @@ RSpec.describe 'User reverts a merge request', :js, feature_category: :code_revi revert_commit - expect(page).to have_content('Sorry, we cannot revert this merge request automatically.') + expect(page).to have_content('Merge request revert failed:') end it 'reverts a merge request in a new merge request', :sidekiq_might_not_need_inline do diff --git a/spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb b/spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb index ad2ceeb23e2..21c62b0d0d8 100644 --- a/spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb +++ b/spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe 'Merge request > User sees check out branch modal', :js, feature_category: :code_review_workflow do + include CookieHelper + let(:project) { create(:project, :public, :repository) } let(:user) { project.creator } let(:merge_request) { create(:merge_request, source_project: project) } @@ -10,6 +12,7 @@ RSpec.describe 'Merge request > User sees check out branch modal', :js, feature_ before do sign_in(user) + set_cookie('new-actions-popover-viewed', 'true') visit project_merge_request_path(project, merge_request) wait_for_requests diff --git a/spec/features/merge_request/user_sees_diff_spec.rb b/spec/features/merge_request/user_sees_diff_spec.rb index 12fdcf4859e..3fb3ef12fcc 100644 --- a/spec/features/merge_request/user_sees_diff_spec.rb +++ b/spec/features/merge_request/user_sees_diff_spec.rb @@ -60,7 +60,7 @@ RSpec.describe 'Merge request > User sees diff', :js, feature_category: :code_re visit diffs_project_merge_request_path(project, merge_request) page.within('.gl-alert') do - expect(page).to have_text("Too many changes to show. To preserve performance only 3 of 3+ files are displayed. Plain diff Email patch") + expect(page).to have_text("Some changes are not shown. For a faster browsing experience, only 3 of 3+ files are shown. Download one of the files below to see all changes. Plain diff Patches") end end end diff --git a/spec/features/merge_request/user_sees_discussions_navigation_spec.rb b/spec/features/merge_request/user_sees_discussions_navigation_spec.rb index 6e6c2cddfbf..5f815bffb22 100644 --- a/spec/features/merge_request/user_sees_discussions_navigation_spec.rb +++ b/spec/features/merge_request/user_sees_discussions_navigation_spec.rb @@ -2,7 +2,9 @@ require 'spec_helper' -RSpec.describe 'Merge request > User sees discussions navigation', :js, feature_category: :code_review_workflow do +RSpec.describe 'Merge request > User sees discussions navigation', + :js, feature_category: :code_review_workflow, + quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/410678' do let_it_be(:project) { create(:project, :public, :repository) } let_it_be(:user) { project.creator } let_it_be(:merge_request) { create(:merge_request, source_project: project) } @@ -42,7 +44,7 @@ RSpec.describe 'Merge request > User sees discussions navigation', :js, feature_ shared_examples 'a page with a thread navigation' do context 'with active threads' do - it 'navigates to the first thread' do + it 'navigates to the first thread', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/410144' do goto_next_thread expect(page).to have_selector(first_discussion_selector, obscured: false) end @@ -52,7 +54,7 @@ RSpec.describe 'Merge request > User sees discussions navigation', :js, feature_ expect(page).to have_selector(second_discussion_selector, obscured: false) end - it 'navigates through active threads' do + it 'navigates through active threads', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/391912' do goto_next_thread goto_next_thread expect(page).to have_selector(second_discussion_selector, obscured: false) diff --git a/spec/features/merge_request/user_sees_merge_button_depending_on_unresolved_discussions_spec.rb b/spec/features/merge_request/user_sees_merge_button_depending_on_unresolved_discussions_spec.rb index b83580565e4..476be5ab599 100644 --- a/spec/features/merge_request/user_sees_merge_button_depending_on_unresolved_discussions_spec.rb +++ b/spec/features/merge_request/user_sees_merge_button_depending_on_unresolved_discussions_spec.rb @@ -21,7 +21,7 @@ feature_category: :code_review_workflow do context 'with unresolved threads' do it 'does not allow to merge' do - expect(page).not_to have_button 'Merge' + expect(page).not_to have_button('Merge', exact: true) expect(page).to have_content('all threads must be resolved') end end @@ -33,7 +33,7 @@ feature_category: :code_review_workflow do end it 'allows MR to be merged' do - expect(page).to have_button 'Merge' + expect(page).to have_button('Merge', exact: true) end end end @@ -46,7 +46,7 @@ feature_category: :code_review_workflow do context 'with unresolved threads' do it 'does not allow to merge' do - expect(page).to have_button 'Merge' + expect(page).to have_button('Merge', exact: true) end end @@ -57,7 +57,7 @@ feature_category: :code_review_workflow do end it 'allows MR to be merged' do - expect(page).to have_button 'Merge' + expect(page).to have_button('Merge', exact: true) end end end diff --git a/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb b/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb index 458746f0854..7d024103943 100644 --- a/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb +++ b/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb @@ -56,6 +56,8 @@ RSpec.describe 'Merge request > User sees pipelines triggered by merge request', end before do + stub_feature_flags(auto_merge_labels_mr_widget: false) + visit project_merge_request_path(project, merge_request) page.within('.merge-request-tabs') do @@ -185,6 +187,48 @@ RSpec.describe 'Merge request > User sees pipelines triggered by merge request', end end + context 'when a user created a merge request in the parent project with auto_merge_labels_mr_widget on' do + before do + stub_feature_flags(auto_merge_labels_mr_widget: true) + + visit project_merge_request_path(project, merge_request) + + page.within('.merge-request-tabs') do + click_link('Pipelines') + end + end + + context 'when a user merges a merge request in the parent project', :sidekiq_might_not_need_inline do + before do + click_link 'Overview' + click_button 'Set to auto-merge' + + wait_for_requests + end + + context 'when detached merge request pipeline is pending' do + it 'waits the head pipeline' do + expect(page).to have_content('to be merged automatically when the pipeline succeeds') + expect(page).to have_button('Cancel auto-merge') + end + end + + context 'when branch pipeline succeeds' do + before do + click_link 'Overview' + push_pipeline.reload.succeed! + + wait_for_requests + end + + it 'waits the head pipeline' do + expect(page).to have_content('to be merged automatically when the pipeline succeeds') + expect(page).to have_button('Cancel auto-merge') + end + end + end + end + context 'when there are no `merge_requests` keyword in .gitlab-ci.yml' do let(:config) do { @@ -244,6 +288,8 @@ RSpec.describe 'Merge request > User sees pipelines triggered by merge request', before do forked_project.add_maintainer(user2) + stub_feature_flags(auto_merge_labels_mr_widget: false) + visit project_merge_request_path(project, merge_request) page.within('.merge-request-tabs') do diff --git a/spec/features/merge_request/user_sees_merge_widget_spec.rb b/spec/features/merge_request/user_sees_merge_widget_spec.rb index acf2893b513..cb56e79fcc0 100644 --- a/spec/features/merge_request/user_sees_merge_widget_spec.rb +++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb @@ -53,6 +53,7 @@ RSpec.describe 'Merge request > User sees merge widget', :js, feature_category: let!(:deployment) { build.deployment } before do + stub_feature_flags(unbatch_graphql_queries: false) merge_request.update!(head_pipeline: pipeline) deployment.update!(status: :success) visit project_merge_request_path(project, merge_request) @@ -396,7 +397,9 @@ RSpec.describe 'Merge request > User sees merge widget', :js, feature_category: end it 'updates the MR widget', :sidekiq_might_not_need_inline do - click_button 'Merge' + page.within('.mr-state-widget') do + click_button 'Merge' + end expect(page).to have_content('An error occurred while merging') end @@ -452,7 +455,7 @@ RSpec.describe 'Merge request > User sees merge widget', :js, feature_category: wait_for_requests - expect(page).not_to have_button('Merge') + expect(page).not_to have_button('Merge', exact: true) expect(page).to have_content('Merging!') end end diff --git a/spec/features/merge_request/user_sees_pipelines_spec.rb b/spec/features/merge_request/user_sees_pipelines_spec.rb index cab940ba704..f92ce3865a9 100644 --- a/spec/features/merge_request/user_sees_pipelines_spec.rb +++ b/spec/features/merge_request/user_sees_pipelines_spec.rb @@ -15,27 +15,40 @@ RSpec.describe 'Merge request > User sees pipelines', :js, feature_category: :co context 'with pipelines' do let!(:pipeline) do - create(:ci_empty_pipeline, + create(:ci_pipeline, + :success, project: merge_request.source_project, ref: merge_request.source_branch, sha: merge_request.diff_head_sha) end + let!(:manual_job) { create(:ci_build, :manual, name: 'job1', stage: 'deploy', pipeline: pipeline) } + + let!(:job) { create(:ci_build, :success, name: 'job2', stage: 'test', pipeline: pipeline) } + before do merge_request.update_attribute(:head_pipeline_id, pipeline.id) end - it 'user visits merge request pipelines tab' do + it 'pipelines table displays correctly' do visit project_merge_request_path(project, merge_request) - expect(page.find('.ci-widget')).to have_content('pending') + expect(page.find('.ci-widget')).to have_content('passed') page.within('.merge-request-tabs') do click_link('Pipelines') end + wait_for_requests - expect(page).to have_css('[data-testid="pipeline-mini-graph"]') + page.within('[data-testid="pipeline-table-row"]') do + expect(page).to have_selector('.ci-success') + expect(page).to have_content(pipeline.id) + expect(page).to have_content('API') + expect(page).to have_css('[data-testid="pipeline-mini-graph"]') + expect(page).to have_css('[data-testid="pipelines-manual-actions-dropdown"]') + expect(page).to have_css('[data-testid="pipeline-multi-actions-dropdown"]') + end end context 'with a detached merge request pipeline' do diff --git a/spec/features/merge_request/user_sees_real_time_reviewers_spec.rb b/spec/features/merge_request/user_sees_real_time_reviewers_spec.rb new file mode 100644 index 00000000000..e967787d2c7 --- /dev/null +++ b/spec/features/merge_request/user_sees_real_time_reviewers_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Merge request > Real-time reviewers', feature_category: :code_review_workflow do + let_it_be(:project) { create(:project, :public, :repository) } + let(:user) { project.creator } + let(:merge_request) { create(:merge_request, :simple, source_project: project, author: user) } + + before do + sign_in(user) + visit project_merge_request_path(project, merge_request) + end + + it 'updates in real-time', :js do + wait_for_requests + + # Simulate a real-time update of reviewers + merge_request.update!(reviewer_ids: [user.id]) + GraphqlTriggers.merge_request_reviewers_updated(merge_request) + + expect(find('.reviewer')).to have_content(user.name) + end +end diff --git a/spec/features/merge_request/user_sees_versions_spec.rb b/spec/features/merge_request/user_sees_versions_spec.rb index f94b288300a..91f8fd13681 100644 --- a/spec/features/merge_request/user_sees_versions_spec.rb +++ b/spec/features/merge_request/user_sees_versions_spec.rb @@ -232,7 +232,7 @@ RSpec.describe 'Merge request > User sees versions', :js, feature_category: :cod end it 'only shows diffs from the commit' do - diff_commit_ids = find_all('.diff-file [data-commit-id]').map { |diff| diff['data-commit-id'] } + diff_commit_ids = find_all('.diff-file [data-commit-id]').pluck('data-commit-id') expect(diff_commit_ids).not_to be_empty expect(diff_commit_ids).to all(eq(params[:commit_id])) diff --git a/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb b/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb index 0de59ea21c5..dae28cbb05c 100644 --- a/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb +++ b/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe 'Merge request > User selects branches for new MR', :js, feature_category: :code_review_workflow do include ListboxHelpers + include CookieHelper let(:project) { create(:project, :public, :repository) } let(:user) { project.creator } @@ -17,6 +18,7 @@ RSpec.describe 'Merge request > User selects branches for new MR', :js, feature_ before do project.add_maintainer(user) sign_in(user) + set_cookie('new-actions-popover-viewed', 'true') end it 'selects the source branch sha when a tag with the same name exists' do diff --git a/spec/features/merge_request/user_uses_quick_actions_spec.rb b/spec/features/merge_request/user_uses_quick_actions_spec.rb index 1a88918da65..1ec86948065 100644 --- a/spec/features/merge_request/user_uses_quick_actions_spec.rb +++ b/spec/features/merge_request/user_uses_quick_actions_spec.rb @@ -9,7 +9,7 @@ require 'spec_helper' # for each existing quick action unless they test something not tested by existing tests. RSpec.describe 'Merge request > User uses quick actions', :js, :use_clean_rails_redis_caching, feature_category: :code_review_workflow do - include Spec::Support::Helpers::Features::NotesHelpers + include Features::NotesHelpers let(:project) { create(:project, :public, :repository) } let(:user) { project.creator } diff --git a/spec/features/merge_request/user_views_open_merge_request_spec.rb b/spec/features/merge_request/user_views_open_merge_request_spec.rb index e481e3f2dfb..1a9d40ae926 100644 --- a/spec/features/merge_request/user_views_open_merge_request_spec.rb +++ b/spec/features/merge_request/user_views_open_merge_request_spec.rb @@ -7,6 +7,18 @@ RSpec.describe 'User views an open merge request', feature_category: :code_revie create(:merge_request, source_project: project, target_project: project, description: '# Description header') end + context 'feature flags' do + let_it_be(:project) { create(:project, :public, :repository) } + + it 'pushes content_editor_on_issues feature flag to frontend' do + stub_feature_flags(content_editor_on_issues: true) + + visit merge_request_path(merge_request) + + expect(page).to have_pushed_frontend_feature_flags(contentEditorOnIssues: true) + end + end + context 'when a merge request does not have repository' do let(:project) { create(:project, :public, :repository) } @@ -44,25 +56,25 @@ RSpec.describe 'User views an open merge request', feature_category: :code_revie end it 'renders empty description preview' do - find('.gfm-form').fill_in(:merge_request_description, with: '') + fill_in(:merge_request_description, with: '') - page.within('.gfm-form') do - click_button('Preview') + page.within('.js-vue-markdown-field') do + click_button("Preview") - expect(find('.js-md-preview')).to have_content('Nothing to preview.') + expect(find('.js-vue-md-preview')).to have_content('Nothing to preview.') end end it 'renders description preview' do - find('.gfm-form').fill_in(:merge_request_description, with: ':+1: Nice') + fill_in(:merge_request_description, with: ':+1: Nice') - page.within('.gfm-form') do - click_button('Preview') + page.within('.js-vue-markdown-field') do + click_button("Preview") - expect(find('.js-md-preview')).to have_css('gl-emoji') + expect(find('.js-vue-md-preview')).to have_css('gl-emoji') end - expect(find('.gfm-form')).to have_css('.js-md-preview').and have_button('Write') + expect(find('.js-vue-markdown-field')).to have_css('.js-md-preview-button') expect(find('#merge_request_description', visible: false)).not_to be_visible end end @@ -92,7 +104,7 @@ RSpec.describe 'User views an open merge request', feature_category: :code_revie visit(merge_request_path(merge_request)) end - it 'shows diverged commits count' do + it 'shows diverged commits count', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/408223' do expect(page).not_to have_content(/([0-9]+ commits? behind)/) end end diff --git a/spec/features/merge_requests/filters_generic_behavior_spec.rb b/spec/features/merge_requests/filters_generic_behavior_spec.rb index 197b9fa770d..4dbbde5168b 100644 --- a/spec/features/merge_requests/filters_generic_behavior_spec.rb +++ b/spec/features/merge_requests/filters_generic_behavior_spec.rb @@ -71,7 +71,7 @@ RSpec.describe 'Merge Requests > Filters generic behavior', :js, feature_categor context 'filter dropdown' do it 'filters by label name' do - init_label_search + filtered_search.set('label:=') filtered_search.send_keys('~bug') page.within '.filter-dropdown' do diff --git a/spec/features/merge_requests/rss_spec.rb b/spec/features/merge_requests/rss_spec.rb index 9c9f46278f6..5f697a4f79d 100644 --- a/spec/features/merge_requests/rss_spec.rb +++ b/spec/features/merge_requests/rss_spec.rb @@ -25,7 +25,7 @@ RSpec.describe 'Project Merge Requests RSS', feature_category: :code_review_work visit path end - it_behaves_like "it has an RSS button with current_user's feed token" + it_behaves_like "it has an RSS link with current_user's feed token" it_behaves_like "an autodiscoverable RSS feed with current_user's feed token" end @@ -34,7 +34,7 @@ RSpec.describe 'Project Merge Requests RSS', feature_category: :code_review_work visit path end - it_behaves_like "it has an RSS button without a feed token" + it_behaves_like "it has an RSS link without a feed token" it_behaves_like "an autodiscoverable RSS feed without a feed token" end diff --git a/spec/features/merge_requests/user_exports_as_csv_spec.rb b/spec/features/merge_requests/user_exports_as_csv_spec.rb index 23ac1b264ad..57d6ee69923 100644 --- a/spec/features/merge_requests/user_exports_as_csv_spec.rb +++ b/spec/features/merge_requests/user_exports_as_csv_spec.rb @@ -14,6 +14,7 @@ RSpec.describe 'Merge Requests > Exports as CSV', :js, feature_category: :code_r context 'button is clicked' do before do + click_button 'Actions' click_button 'Export as CSV' end diff --git a/spec/features/merge_requests/user_lists_merge_requests_spec.rb b/spec/features/merge_requests/user_lists_merge_requests_spec.rb index 3171ae89fe6..371c40b40a5 100644 --- a/spec/features/merge_requests/user_lists_merge_requests_spec.rb +++ b/spec/features/merge_requests/user_lists_merge_requests_spec.rb @@ -23,7 +23,7 @@ RSpec.describe 'Merge requests > User lists merge requests', feature_category: : milestone: create(:milestone, project: project, due_date: '2013-12-11'), created_at: 1.minute.ago, updated_at: 1.minute.ago) - @fix.metrics.update!(merged_at: 10.seconds.ago, latest_closed_at: 10.seconds.ago) + @fix.metrics.update!(merged_at: 10.seconds.ago, latest_closed_at: 20.seconds.ago) @markdown = create(:merge_request, title: 'markdown', @@ -33,7 +33,8 @@ RSpec.describe 'Merge requests > User lists merge requests', feature_category: : reviewers: [user, user2, user3, user4], milestone: create(:milestone, project: project, due_date: '2013-12-12'), created_at: 2.minutes.ago, - updated_at: 2.minutes.ago) + updated_at: 2.minutes.ago, + state: 'merged') @markdown.metrics.update!(merged_at: 10.minutes.ago, latest_closed_at: 10.seconds.ago) @merge_test = create(:merge_request, @@ -49,7 +50,8 @@ RSpec.describe 'Merge requests > User lists merge requests', feature_category: : source_project: project, source_branch: 'feautre', created_at: 2.minutes.ago, - updated_at: 1.minute.ago) + updated_at: 1.minute.ago, + state: 'merged') @feature.metrics.update!(merged_at: 10.seconds.ago, latest_closed_at: 10.minutes.ago) end @@ -79,10 +81,9 @@ RSpec.describe 'Merge requests > User lists merge requests', feature_category: : expect(page).to have_current_path(project_merge_requests_path(project), ignore_query: true) expect(page).to have_content 'merge-test' - expect(page).to have_content 'feature' expect(page).not_to have_content 'fix' expect(page).not_to have_content 'markdown' - expect(count_merge_requests).to eq(2) + expect(count_merge_requests).to eq(1) end it 'filters on a specific assignee' do @@ -90,8 +91,7 @@ RSpec.describe 'Merge requests > User lists merge requests', feature_category: : expect(page).not_to have_content 'merge-test' expect(page).to have_content 'fix' - expect(page).to have_content 'markdown' - expect(count_merge_requests).to eq(2) + expect(count_merge_requests).to eq(1) end it 'sorts by newest' do @@ -99,35 +99,35 @@ RSpec.describe 'Merge requests > User lists merge requests', feature_category: : expect(first_merge_request).to include('fix') expect(last_merge_request).to include('merge-test') - expect(count_merge_requests).to eq(4) + expect(count_merge_requests).to eq(2) end it 'sorts by last updated' do visit_merge_requests(project, sort: sort_value_recently_updated) expect(first_merge_request).to include('merge-test') - expect(count_merge_requests).to eq(4) + expect(count_merge_requests).to eq(2) end it 'sorts by milestone due date' do visit_merge_requests(project, sort: sort_value_milestone) expect(first_merge_request).to include('fix') - expect(count_merge_requests).to eq(4) + expect(count_merge_requests).to eq(2) end - it 'sorts by merged at' do + it 'ignores sorting by merged at' do visit_merge_requests(project, sort: sort_value_merged_date) - expect(first_merge_request).to include('markdown') - expect(count_merge_requests).to eq(4) + expect(first_merge_request).to include('fix') + expect(count_merge_requests).to eq(2) end it 'sorts by closed at' do visit_merge_requests(project, sort: sort_value_closed_date) - expect(first_merge_request).to include('feature') - expect(count_merge_requests).to eq(4) + expect(first_merge_request).to include('fix') + expect(count_merge_requests).to eq(2) end it 'filters on one label and sorts by milestone due date' do @@ -141,6 +141,15 @@ RSpec.describe 'Merge requests > User lists merge requests', feature_category: : expect(count_merge_requests).to eq(1) end + context 'when viewing merged merge requests' do + it 'sorts by merged at' do + visit_merge_requests(project, state: 'merged', sort: sort_value_merged_date) + + expect(first_merge_request).to include('markdown') + expect(count_merge_requests).to eq(2) + end + end + context 'while filtering on two labels' do let(:label) { create(:label, project: project) } let(:label2) { create(:label, project: project) } diff --git a/spec/features/merge_requests/user_mass_updates_spec.rb b/spec/features/merge_requests/user_mass_updates_spec.rb index b0be76d386a..45d57cf8374 100644 --- a/spec/features/merge_requests/user_mass_updates_spec.rb +++ b/spec/features/merge_requests/user_mass_updates_spec.rb @@ -44,7 +44,7 @@ RSpec.describe 'Merge requests > User mass updates', :js, feature_category: :cod merge_request.close visit project_merge_requests_path(project, state: 'merged') - click_button 'Edit merge requests' + click_button 'Bulk edit' expect(page).not_to have_button 'Select status' end @@ -108,7 +108,7 @@ RSpec.describe 'Merge requests > User mass updates', :js, feature_category: :cod end def change_status(text) - click_button 'Edit merge requests' + click_button 'Bulk edit' check 'Select all' click_button 'Select status' click_button text @@ -116,7 +116,7 @@ RSpec.describe 'Merge requests > User mass updates', :js, feature_category: :cod end def change_assignee(text) - click_button 'Edit merge requests' + click_button 'Bulk edit' check 'Select all' within 'aside[aria-label="Bulk update"]' do click_button 'Select assignee' @@ -127,7 +127,7 @@ RSpec.describe 'Merge requests > User mass updates', :js, feature_category: :cod end def change_milestone(text) - click_button 'Edit merge requests' + click_button 'Bulk edit' check 'Select all' click_button 'Select milestone' click_button text diff --git a/spec/features/merge_requests/user_sorts_merge_requests_spec.rb b/spec/features/merge_requests/user_sorts_merge_requests_spec.rb index 58d796f8288..5ccc24ebca1 100644 --- a/spec/features/merge_requests/user_sorts_merge_requests_spec.rb +++ b/spec/features/merge_requests/user_sorts_merge_requests_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe 'User sorts merge requests', :js, feature_category: :code_review_workflow do include CookieHelper - include Spec::Support::Helpers::Features::SortingHelpers + include Features::SortingHelpers let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) } let!(:merge_request2) do diff --git a/spec/features/milestones/user_deletes_milestone_spec.rb b/spec/features/milestones/user_deletes_milestone_spec.rb index 141e626c6f3..a7f2457de04 100644 --- a/spec/features/milestones/user_deletes_milestone_spec.rb +++ b/spec/features/milestones/user_deletes_milestone_spec.rb @@ -18,6 +18,7 @@ RSpec.describe "User deletes milestone", :js, feature_category: :team_planning d project.add_developer(user) visit(project_milestones_path(project)) click_link(milestone.title) + click_button("Milestone actions") click_button("Delete") click_button("Delete milestone") @@ -38,6 +39,7 @@ RSpec.describe "User deletes milestone", :js, feature_category: :team_planning d visit(group_milestones_path(group)) click_link(milestone_to_be_deleted.title) + click_button("Milestone actions") click_button("Delete") click_button("Delete milestone") diff --git a/spec/features/monitor_sidebar_link_spec.rb b/spec/features/monitor_sidebar_link_spec.rb index d5f987d15c2..6a1413c04f6 100644 --- a/spec/features/monitor_sidebar_link_spec.rb +++ b/spec/features/monitor_sidebar_link_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Monitor dropdown sidebar', :aggregate_failures, feature_category: :not_owned do +RSpec.describe 'Monitor dropdown sidebar', :aggregate_failures, feature_category: :shared do let_it_be_with_reload(:project) { create(:project, :internal, :repository) } let_it_be(:user) { create(:user) } @@ -11,6 +11,7 @@ RSpec.describe 'Monitor dropdown sidebar', :aggregate_failures, feature_category before do project.add_role(user, role) if role sign_in(user) + stub_feature_flags(remove_monitor_metrics: false) end shared_examples 'shows Monitor menu based on the access level' do diff --git a/spec/features/nav/new_nav_invite_members_spec.rb b/spec/features/nav/new_nav_invite_members_spec.rb new file mode 100644 index 00000000000..4c37d6b4760 --- /dev/null +++ b/spec/features/nav/new_nav_invite_members_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'new navigation toggle', :js, feature_category: :navigation do + include Features::InviteMembersModalHelpers + + let_it_be(:user) { create(:user, use_new_navigation: true) } + + before do + sign_in(user) + end + + context 'when inside a group' do + let_it_be(:group) { create(:group).tap { |record| record.add_owner(user) } } + + before do + visit group_path(group) + end + + it 'the add menu contains invite members dropdown option and opens invite modal' do + invite_members_from_menu + + page.within invite_modal_selector do + expect(page).to have_content("You're inviting members to the #{group.name} group") + end + end + end + + context 'when inside a project' do + let_it_be(:project) { create(:project, :repository).tap { |record| record.add_owner(user) } } + + before do + visit project_path(project) + end + + it 'the add menu contains invite members dropdown option and opens invite modal' do + invite_members_from_menu + + page.within invite_modal_selector do + expect(page).to have_content("You're inviting members to the #{project.name} project") + end + end + end + + def invite_members_from_menu + page.find('[data-testid="new-menu-toggle"] button').click + click_button('Invite team members') + end +end diff --git a/spec/features/nav/new_nav_toggle_spec.rb b/spec/features/nav/new_nav_toggle_spec.rb index 8e5cc7df053..2cdaf12bb15 100644 --- a/spec/features/nav/new_nav_toggle_spec.rb +++ b/spec/features/nav/new_nav_toggle_spec.rb @@ -60,7 +60,7 @@ RSpec.describe 'new navigation toggle', :js, feature_category: :navigation do it 'allows to disable new nav', :aggregate_failures do within '[data-testid="super-sidebar"] [data-testid="user-dropdown"]' do - find('button').click + click_button "#{user.name} user’s menu" expect(page).to have_content('Navigation redesign') toggle = page.find('.gl-toggle.is-checked') diff --git a/spec/features/nav/pinned_nav_items_spec.rb b/spec/features/nav/pinned_nav_items_spec.rb new file mode 100644 index 00000000000..308350d5166 --- /dev/null +++ b/spec/features/nav/pinned_nav_items_spec.rb @@ -0,0 +1,195 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Navigation menu item pinning', :js, feature_category: :navigation do + let_it_be(:user) { create(:user, use_new_navigation: true) } + + before do + sign_in(user) + end + + describe 'non-pinnable navigation menu' do + before do + visit explore_projects_path + end + + it 'does not show the Pinned section' do + within '#super-sidebar' do + expect(page).not_to have_content 'Pinned' + end + end + + it 'does not show the buttons to pin items' do + within '#super-sidebar' do + expect(page).not_to have_css 'button svg[data-testid="thumbtack-icon"]' + end + end + end + + describe 'pinnable navigation menu' do + let_it_be(:project) { create(:project) } + + before do + project.add_member(user, :owner) + visit project_path(project) + end + + it 'adds sensible defaults' do + within '[data-testid="pinned-nav-items"]' do + expect(page).to have_link 'Issues' + end + end + + it 'shows the Pinned section' do + within '#super-sidebar' do + expect(page).to have_content 'Pinned' + end + end + + it 'allows to pin items' do + within '#super-sidebar' do + click_on 'Manage' + add_pin('Activity') + add_pin('Members') + end + + within '[data-testid="pinned-nav-items"]' do + expect(page).to have_link 'Issues' + expect(page).to have_link 'Activity' + expect(page).to have_link 'Members' + end + end + + describe 'when all pins are removed' do + before do + remove_pin('Issues') + end + + it 'shows the Pinned section as expanded by default' do + within '#super-sidebar' do + expect(page).to have_content 'Your pinned items appear here.' + end + end + + it 'maintains the collapsed/expanded state between page loads' do + within '#super-sidebar' do + click_on 'Pinned' + visit project_path(project) + expect(page).not_to have_content 'Your pinned items appear here.' + + click_on 'Pinned' + visit project_path(project) + expect(page).to have_content 'Your pinned items appear here.' + end + end + end + + describe 'pinned items' do + before do + within '#super-sidebar' do + click_on 'Operate' + add_pin('Package Registry') + add_pin('Terraform modules') + wait_for_requests + end + end + + it 'can be unpinned from within the pinned section' do + within '[data-testid="pinned-nav-items"]' do + remove_pin('Package Registry') + expect(page).not_to have_content 'Package Registry' + end + end + + it 'can be unpinned from within its section' do + section = find("button", text: 'Operate') + + within(section.sibling('ul')) do + remove_pin('Terraform modules') + end + + within '[data-testid="pinned-nav-items"]' do + expect(page).not_to have_content 'Terraform modules' + end + end + + it 'can be reordered' do + within '[data-testid="pinned-nav-items"]' do + pinned_items = page.find_all('a').map(&:text) + item2 = page.find('a', text: 'Package Registry') + item3 = page.find('a', text: 'Terraform modules') + expect(pinned_items[1..2]).to eq [item2.text, item3.text] + drag_item(item3, to: item2) + + pinned_items = page.find_all('a').map(&:text) + expect(pinned_items[1..2]).to eq [item3.text, item2.text] + end + end + end + end + + describe 'reordering pins with hidden pins from non-available features' do + let_it_be(:project_with_repo) { create(:project, :repository) } + let_it_be(:project_without_repo) { create(:project, :repository_disabled) } + + before do + project_with_repo.add_member(user, :owner) + project_without_repo.add_member(user, :owner) + + visit project_path(project_with_repo) + within '#super-sidebar' do + click_on 'Code' + add_pin('Commits') + click_on 'Manage' + add_pin('Activity') + add_pin('Members') + end + + visit project_path(project_without_repo) + within '[data-testid="pinned-nav-items"]' do + activity_item = page.find('a', text: 'Activity') + members_item = page.find('a', text: 'Members') + drag_item(members_item, to: activity_item) + end + + visit project_path(project_with_repo) + end + + it 'keeps pins of non-available features' do + within '[data-testid="pinned-nav-items"]' do + pinned_items = page.find_all('a') + .map(&:text) + .map { |text| text.split("\n").first } # to drop the counter badge text from "Issues\n0" + expect(pinned_items).to eq ["Issues", "Merge requests", "Commits", "Members", "Activity"] + end + end + end + + private + + def add_pin(menu_item_title) + menu_item = find("[data-testid=\"nav-item-link\"]", text: menu_item_title) + menu_item.hover + menu_item.find("[data-testid=\"thumbtack-icon\"]").click + wait_for_requests + end + + def remove_pin(menu_item_title) + menu_item = find("[data-testid=\"nav-item-link\"]", text: menu_item_title) + menu_item.hover + menu_item.find("[data-testid=\"thumbtack-solid-icon\"]").click + wait_for_requests + end + + def drag_item(item, to:) + item.hover + drag_handle = item.find('[data-testid="grip-icon"]') + + # Reduce delay to make it less likely for draggables to + # change position during drag operation, which reduces + # flakiness. + drag_handle.drag_to(to, delay: 0.01) + wait_for_requests + end +end diff --git a/spec/features/nav/top_nav_responsive_spec.rb b/spec/features/nav/top_nav_responsive_spec.rb index 56f9d373f00..ff8132dc087 100644 --- a/spec/features/nav/top_nav_responsive_spec.rb +++ b/spec/features/nav/top_nav_responsive_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe 'top nav responsive', :js, feature_category: :navigation do include MobileHelpers + include Features::InviteMembersModalHelpers let_it_be(:user) { create(:user) } @@ -20,8 +21,8 @@ RSpec.describe 'top nav responsive', :js, feature_category: :navigation do context 'when menu is closed' do it 'has page content and hides responsive menu', :aggregate_failures do - expect(page).to have_css('.page-title', text: 'Projects') - expect(page).to have_link('Dashboard', id: 'logo') + expect(page).to have_css('.page-title', text: 'Explore projects') + expect(page).to have_link('Homepage', id: 'logo') expect(page).to have_no_css('.top-nav-responsive') end @@ -33,14 +34,15 @@ RSpec.describe 'top nav responsive', :js, feature_category: :navigation do end it 'hides everything and shows responsive menu', :aggregate_failures do - expect(page).to have_no_css('.page-title', text: 'Projects') - expect(page).to have_no_link('Dashboard', id: 'logo') + expect(page).to have_no_css('.page-title', text: 'Explore projects') + expect(page).to have_no_link('Homepage', id: 'logo') within '.top-nav-responsive' do expect(page).to have_link(nil, href: search_path) expect(page).to have_button('Projects') expect(page).to have_button('Groups') - expect(page).to have_link('Snippets', href: dashboard_snippets_path) + expect(page).to have_link('Your work', href: dashboard_projects_path) + expect(page).to have_link('Explore', href: explore_projects_path) end end @@ -61,10 +63,12 @@ RSpec.describe 'top nav responsive', :js, feature_category: :navigation do visit project_path(project) end - it 'the add menu contains invite members dropdown option and goes to the members page' do + it 'the add menu contains invite members dropdown option and opens invite modal' do invite_members_from_menu - expect(page).to have_current_path(project_project_members_path(project)) + page.within invite_modal_selector do + expect(page).to have_content("You're inviting members to the #{project.name} project") + end end end @@ -75,10 +79,12 @@ RSpec.describe 'top nav responsive', :js, feature_category: :navigation do visit group_path(group) end - it 'the add menu contains invite members dropdown option and goes to the members page' do + it 'the add menu contains invite members dropdown option and opens invite modal' do invite_members_from_menu - expect(page).to have_current_path(group_group_members_path(group)) + page.within invite_modal_selector do + expect(page).to have_content("You're inviting members to the #{group.name} group") + end end end @@ -86,7 +92,7 @@ RSpec.describe 'top nav responsive', :js, feature_category: :navigation do click_button('Menu') create_new_button.click - click_link('Invite members') + click_button('Invite members') end def create_new_button diff --git a/spec/features/nav/top_nav_spec.rb b/spec/features/nav/top_nav_spec.rb index cc20b626e30..74022a4a976 100644 --- a/spec/features/nav/top_nav_spec.rb +++ b/spec/features/nav/top_nav_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe 'top nav responsive', :js, feature_category: :navigation do + include Features::InviteMembersModalHelpers + let_it_be(:user) { create(:user) } before do @@ -16,10 +18,12 @@ RSpec.describe 'top nav responsive', :js, feature_category: :navigation do visit project_path(project) end - it 'the add menu contains invite members dropdown option and goes to the members page' do + it 'the add menu contains invite members dropdown option and opens invite modal' do invite_members_from_menu - expect(page).to have_current_path(project_project_members_path(project)) + page.within invite_modal_selector do + expect(page).to have_content("You're inviting members to the #{project.name} project") + end end end @@ -30,10 +34,12 @@ RSpec.describe 'top nav responsive', :js, feature_category: :navigation do visit group_path(group) end - it 'the add menu contains invite members dropdown option and goes to the members page' do + it 'the add menu contains invite members dropdown option and opens invite modal' do invite_members_from_menu - expect(page).to have_current_path(group_group_members_path(group)) + page.within invite_modal_selector do + expect(page).to have_content("You're inviting members to the #{group.name} group") + end end end diff --git a/spec/features/oauth_login_spec.rb b/spec/features/oauth_login_spec.rb index bd96d65f984..ca20a1cd81b 100644 --- a/spec/features/oauth_login_spec.rb +++ b/spec/features/oauth_login_spec.rb @@ -16,7 +16,7 @@ RSpec.describe 'OAuth Login', :allow_forgery_protection, feature_category: :syst end providers = [:github, :twitter, :bitbucket, :gitlab, :google_oauth2, - :facebook, :cas3, :auth0, :salesforce, :dingtalk, :alicloud] + :facebook, :auth0, :salesforce, :dingtalk, :alicloud] around do |example| with_omniauth_full_host { example.run } diff --git a/spec/features/oauth_registration_spec.rb b/spec/features/oauth_registration_spec.rb index 3c1004e452f..c88a018a592 100644 --- a/spec/features/oauth_registration_spec.rb +++ b/spec/features/oauth_registration_spec.rb @@ -21,7 +21,6 @@ RSpec.describe 'OAuth Registration', :js, :allow_forgery_protection, feature_cat :gitlab | {} :google_oauth2 | {} :facebook | {} - :cas3 | {} :auth0 | {} :salesforce | { extra: { email_verified: true } } :dingtalk | {} diff --git a/spec/features/populate_new_pipeline_vars_with_params_spec.rb b/spec/features/populate_new_pipeline_vars_with_params_spec.rb index a83b5a81a41..bcda30ccb84 100644 --- a/spec/features/populate_new_pipeline_vars_with_params_spec.rb +++ b/spec/features/populate_new_pipeline_vars_with_params_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe "Populate new pipeline CI variables with url params", :js, feature_category: :pipeline_authoring do +RSpec.describe "Populate new pipeline CI variables with url params", :js, feature_category: :secrets_management do let(:user) { create(:user) } let(:project) { create(:project) } let(:page_path) { new_project_pipeline_path(project) } diff --git a/spec/features/profiles/chat_names_spec.rb b/spec/features/profiles/chat_names_spec.rb index 299ecdb6032..aa3f4a90298 100644 --- a/spec/features/profiles/chat_names_spec.rb +++ b/spec/features/profiles/chat_names_spec.rb @@ -2,9 +2,8 @@ require 'spec_helper' -RSpec.describe 'Profile > Chat', feature_category: :user_profile do - let(:user) { create(:user) } - let(:integration) { create(:integration) } +RSpec.describe 'Profile > Chat', feature_category: :integrations do + let_it_be(:user) { create(:user) } before do sign_in(user) @@ -12,7 +11,12 @@ RSpec.describe 'Profile > Chat', feature_category: :user_profile do describe 'uses authorization link' do let(:params) do - { team_id: 'T00', team_domain: 'my_chat_team', user_id: 'U01', user_name: 'my_chat_user' } + { + team_id: 'f1924a8db44ff3bb41c96424cdc20676', + team_domain: 'my_chat_team', + user_id: 'ay5sq51sebfh58ktrce5ijtcwy', + user_name: 'my_chat_user' + } end let!(:authorize_url) { ChatNames::AuthorizeUserService.new(params).execute } @@ -22,6 +26,36 @@ RSpec.describe 'Profile > Chat', feature_category: :user_profile do visit authorize_path end + it 'names the Mattermost integration correctly' do + expect(page).to have_content( + 'An application called Mattermost slash commands is requesting access to your GitLab account' + ) + expect(page).to have_content('Authorize Mattermost slash commands') + end + + context 'when params are of the GitLab for Slack app' do + let(:params) do + { team_id: 'T00', team_domain: 'my_chat_team', user_id: 'U01', user_name: 'my_chat_user' } + end + + shared_examples 'names the GitLab for Slack app integration correctly' do + specify do + expect(page).to have_content( + 'An application called GitLab for Slack app is requesting access to your GitLab account' + ) + expect(page).to have_content('Authorize GitLab for Slack app') + end + end + + include_examples 'names the GitLab for Slack app integration correctly' + + context 'with a Slack enterprise-enabled team' do + let(:params) { super().merge(user_id: 'W01') } + + include_examples 'names the GitLab for Slack app integration correctly' + end + end + context 'clicks authorize' do before do click_button 'Authorize' @@ -60,7 +94,7 @@ RSpec.describe 'Profile > Chat', feature_category: :user_profile do end describe 'visits chat accounts' do - let!(:chat_name) { create(:chat_name, user: user, integration: integration) } + let_it_be(:chat_name) { create(:chat_name, user: user) } before do visit profile_chat_names_path diff --git a/spec/features/profiles/gpg_keys_spec.rb b/spec/features/profiles/gpg_keys_spec.rb index 0fc59f21489..f39d9ddaf56 100644 --- a/spec/features/profiles/gpg_keys_spec.rb +++ b/spec/features/profiles/gpg_keys_spec.rb @@ -37,12 +37,13 @@ RSpec.describe 'Profile > GPG Keys', feature_category: :user_profile do end it 'user sees their key' do - create(:gpg_key, user: user, key: GpgHelpers::User2.public_key) + gpg_key = create(:gpg_key, user: user, key: GpgHelpers::User2.public_key) visit profile_gpg_keys_path expect(page).to have_content('bette.cartwright@example.com Verified') expect(page).to have_content('bette.cartwright@example.net Unverified') expect(page).to have_content(GpgHelpers::User2.fingerprint) + expect(page).to have_selector('time.js-timeago', text: gpg_key.created_at.strftime('%b %d, %Y')) end it 'user removes a key via the key index' do diff --git a/spec/features/profiles/list_users_saved_replies_spec.rb b/spec/features/profiles/list_users_comment_template_spec.rb index 4f3678f8051..85e455ba988 100644 --- a/spec/features/profiles/list_users_saved_replies_spec.rb +++ b/spec/features/profiles/list_users_comment_template_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Profile > Notifications > List users saved replies', :js, +RSpec.describe 'Profile > Comment templates > List users comment templates', :js, feature_category: :user_profile do let_it_be(:user) { create(:user) } let_it_be(:saved_reply) { create(:saved_reply, user: user) } @@ -11,10 +11,10 @@ RSpec.describe 'Profile > Notifications > List users saved replies', :js, sign_in(user) end - it 'shows the user a list of their saved replies' do - visit profile_saved_replies_path + it 'shows the user a list of their comment templates' do + visit profile_comment_templates_path - expect(page).to have_content('My saved replies (1)') + expect(page).to have_content('My comment templates (1)') expect(page).to have_content(saved_reply.name) expect(page).to have_content(saved_reply.content) end diff --git a/spec/features/profiles/personal_access_tokens_spec.rb b/spec/features/profiles/personal_access_tokens_spec.rb index a050e87241b..65fe1330be2 100644 --- a/spec/features/profiles/personal_access_tokens_spec.rb +++ b/spec/features/profiles/personal_access_tokens_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe 'Profile > Personal Access Tokens', :js, feature_category: :user_profile do include Spec::Support::Helpers::ModalHelpers - include Spec::Support::Helpers::AccessTokenHelpers + include Features::AccessTokenHelpers let(:user) { create(:user) } let(:pat_create_service) { double('PersonalAccessTokens::CreateService', execute: ServiceResponse.error(message: 'error', payload: { personal_access_token: PersonalAccessToken.new })) } diff --git a/spec/features/profiles/user_creates_comment_template_spec.rb b/spec/features/profiles/user_creates_comment_template_spec.rb new file mode 100644 index 00000000000..44e2b932c00 --- /dev/null +++ b/spec/features/profiles/user_creates_comment_template_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Profile > Comment templates > User creates comment template', :js, + feature_category: :user_profile do + let_it_be(:user) { create(:user) } + + before do + sign_in(user) + + visit profile_comment_templates_path + + wait_for_requests + end + + it 'shows the user a list of their saved replies' do + find('[data-testid="comment-template-name-input"]').set('test') + find('[data-testid="comment-template-content-input"]').set('Test content') + + click_button 'Save' + + wait_for_requests + + expect(page).to have_content('My comment templates (1)') + expect(page).to have_content('test') + expect(page).to have_content('Test content') + end +end diff --git a/spec/features/profiles/user_deletes_comment_template_spec.rb b/spec/features/profiles/user_deletes_comment_template_spec.rb new file mode 100644 index 00000000000..7ef857e9622 --- /dev/null +++ b/spec/features/profiles/user_deletes_comment_template_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Profile > Comment templates > User deletes comment template', :js, + feature_category: :user_profile do + let_it_be(:user) { create(:user) } + let_it_be(:saved_reply) { create(:saved_reply, user: user) } + + before do + sign_in(user) + end + + it 'shows the user a list of their comment template' do + visit profile_comment_templates_path + + click_button 'Comment template actions' + find('[data-testid="comment-template-delete-btn"]').click + + page.within('.gl-modal') do + click_button 'Delete' + end + + wait_for_requests + + expect(page).not_to have_content(saved_reply.name) + end +end diff --git a/spec/features/profiles/user_edit_preferences_spec.rb b/spec/features/profiles/user_edit_preferences_spec.rb index f7a9850355a..6cc926ed017 100644 --- a/spec/features/profiles/user_edit_preferences_spec.rb +++ b/spec/features/profiles/user_edit_preferences_spec.rb @@ -12,22 +12,11 @@ RSpec.describe 'User edit preferences profile', :js, feature_category: :user_pro before do stub_languages_translation_percentage(language_percentage_levels) - stub_feature_flags(user_time_settings: true) stub_feature_flags(vscode_web_ide: vscode_web_ide) sign_in(user) visit(profile_preferences_path) end - it 'allows the user to toggle their time format preference' do - field = page.find_field("user[time_format_in_24h]") - - expect(field).not_to be_checked - - field.click - - expect(field).to be_checked - end - it 'allows the user to toggle their time display preference' do field = page.find_field("user[time_display_relative]") @@ -38,24 +27,6 @@ RSpec.describe 'User edit preferences profile', :js, feature_category: :user_pro expect(field).not_to be_checked end - it 'allows the user to toggle using the legacy web ide' do - field = page.find_field("user[use_legacy_web_ide]") - - expect(field).not_to be_checked - - field.click - - expect(field).to be_checked - end - - describe 'when vscode_web_ide feature flag is disabled' do - let(:vscode_web_ide) { false } - - it 'does not display the legacy web ide user preference' do - expect(page).not_to have_field("user[use_legacy_web_ide]") - end - end - describe 'User changes tab width to acceptable value' do it 'shows success message' do fill_in 'Tab width', with: 9 diff --git a/spec/features/profiles/user_edit_profile_spec.rb b/spec/features/profiles/user_edit_profile_spec.rb index 3819723cc09..a6dcbc31dc4 100644 --- a/spec/features/profiles/user_edit_profile_spec.rb +++ b/spec/features/profiles/user_edit_profile_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'User edit profile', feature_category: :user_profile do - include Spec::Support::Helpers::Features::NotesHelpers + include Features::NotesHelpers let_it_be(:user) { create(:user) } @@ -97,6 +97,26 @@ RSpec.describe 'User edit profile', feature_category: :user_profile do expect(page).to have_content('Website url is not a valid URL') end + it 'validates that the discord id has a valid length', :js do + valid_discord_id = '123456789123456789' + too_short_discord_id = '123456' + too_long_discord_id = '123456789abcdefghijkl' + + fill_in 'user_discord', with: too_short_discord_id + expect(page).to have_content('Discord ID is too short') + + fill_in 'user_discord', with: too_long_discord_id + expect(page).to have_content('Discord ID is too long') + + fill_in 'user_discord', with: valid_discord_id + + submit_settings + + expect(user.reload).to have_attributes( + discord: valid_discord_id + ) + end + describe 'when I change my email', :js do before do user.send_reset_password_instructions @@ -277,7 +297,7 @@ RSpec.describe 'User edit profile', feature_category: :user_profile do end page.within '.dropdown-menu-user' do - expect(page).to have_content("#{user.name} (Busy)") + expect(page).to have_content("#{user.name} Busy") end end @@ -288,7 +308,7 @@ RSpec.describe 'User edit profile', feature_category: :user_profile do visit project_issue_path(project, issue) wait_for_requests - expect(page.find('.issuable-assignees')).to have_content("#{user.name} (Busy)") + expect(page.find('.issuable-assignees')).to have_content("#{user.name} Busy") end end end @@ -504,10 +524,6 @@ RSpec.describe 'User edit profile', feature_category: :user_profile do let(:issue) { create(:issue, project: project) } let(:project) { create(:project) } - before do - stub_feature_flags(user_time_settings: true) - end - it 'shows the user time preferences form' do expect(page).to have_content('Time settings') end diff --git a/spec/features/profiles/user_updates_comment_template_spec.rb b/spec/features/profiles/user_updates_comment_template_spec.rb new file mode 100644 index 00000000000..2e6bfdcc407 --- /dev/null +++ b/spec/features/profiles/user_updates_comment_template_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Profile > Comment templates > User updated comment template', :js, + feature_category: :user_profile do + let_it_be(:user) { create(:user) } + let_it_be(:saved_reply) { create(:saved_reply, user: user) } + + before do + sign_in(user) + + visit profile_comment_templates_path + + wait_for_requests + end + + it 'shows the user a list of their comment template' do + click_button 'Comment template actions' + + find('[data-testid="comment-template-edit-btn"]').click + find('[data-testid="comment-template-name-input"]').set('test') + + click_button 'Save' + + wait_for_requests + + expect(page).to have_selector('[data-testid="comment-template-name"]', text: 'test') + end +end diff --git a/spec/features/profiles/user_uses_comment_template_spec.rb b/spec/features/profiles/user_uses_comment_template_spec.rb new file mode 100644 index 00000000000..704d02e94f4 --- /dev/null +++ b/spec/features/profiles/user_uses_comment_template_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'User uses comment template', :js, + feature_category: :user_profile do + let_it_be(:project) { create(:project, :repository) } + let_it_be(:merge_request) { create(:merge_request, source_project: project) } + let_it_be(:user) { create(:user) } + let_it_be(:saved_reply) { create(:saved_reply, user: user) } + + before do + project.add_owner(user) + + sign_in(user) + end + + it 'applies comment template' do + visit project_merge_request_path(merge_request.project, merge_request) + + find('.js-comment-template-toggle').click + + wait_for_requests + + find('.gl-new-dropdown-item').click + + expect(find('.note-textarea').value).to eq(saved_reply.content) + end +end diff --git a/spec/features/profiles/user_visits_profile_authentication_log_spec.rb b/spec/features/profiles/user_visits_profile_authentication_log_spec.rb index 90f24c5b866..ac0ed91468c 100644 --- a/spec/features/profiles/user_visits_profile_authentication_log_spec.rb +++ b/spec/features/profiles/user_visits_profile_authentication_log_spec.rb @@ -13,7 +13,7 @@ RSpec.describe 'User visits the authentication log', feature_category: :user_pro it 'shows correct menu item' do visit(audit_log_profile_path) - expect(page).to have_active_navigation('Authentication log') + expect(page).to have_active_navigation('Authentication Log') end end diff --git a/spec/features/project_group_variables_spec.rb b/spec/features/project_group_variables_spec.rb index 0e1e6e49c6d..966c05bb4cb 100644 --- a/spec/features/project_group_variables_spec.rb +++ b/spec/features/project_group_variables_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Project group variables', :js, feature_category: :pipeline_authoring do +RSpec.describe 'Project group variables', :js, feature_category: :secrets_management do let(:user) { create(:user) } let(:group) { create(:group) } let(:subgroup) { create(:group, parent: group) } diff --git a/spec/features/project_variables_spec.rb b/spec/features/project_variables_spec.rb index 1a951980141..c4f78bf4ea3 100644 --- a/spec/features/project_variables_spec.rb +++ b/spec/features/project_variables_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Project variables', :js, feature_category: :pipeline_authoring do +RSpec.describe 'Project variables', :js, feature_category: :secrets_management do let(:user) { create(:user) } let(:project) { create(:project) } let(:variable) { create(:ci_variable, key: 'test_key', value: 'test_value', masked: true) } @@ -16,7 +16,18 @@ RSpec.describe 'Project variables', :js, feature_category: :pipeline_authoring d wait_for_requests end - it_behaves_like 'variable list' + context 'when ci_variables_pages FF is enabled' do + it_behaves_like 'variable list' + it_behaves_like 'variable list pagination', :ci_variable + end + + context 'when ci_variables_pages FF is disabled' do + before do + stub_feature_flags(ci_variables_pages: false) + end + + it_behaves_like 'variable list' + end it 'adds a new variable with an environment scope' do click_button('Add variable') diff --git a/spec/features/projects/badges/list_spec.rb b/spec/features/projects/badges/list_spec.rb index e6bd4b22b0a..c9e4aabe72a 100644 --- a/spec/features/projects/badges/list_spec.rb +++ b/spec/features/projects/badges/list_spec.rb @@ -43,13 +43,46 @@ RSpec.describe 'list of badges', feature_category: :continuous_integration do it 'user changes current ref of build status badge', :js do page.within('.pipeline-status') do - first('.js-project-refs-dropdown').click + find('.ref-selector').click + wait_for_requests - page.within '.project-refs-form' do - click_link 'improve/awesome' + page.within('.ref-selector') do + fill_in 'Search by Git revision', with: 'improve/awesome' + wait_for_requests + find('li', text: 'improve/awesome', match: :prefer_exact).click end expect(page).to have_content 'badges/improve/awesome/pipeline.svg' end end + + it 'user changes current ref of coverage status badge', :js do + page.within('.coverage-report') do + find('.ref-selector').click + wait_for_requests + + page.within('.ref-selector') do + fill_in 'Search by Git revision', with: 'improve/awesome' + wait_for_requests + find('li', text: 'improve/awesome', match: :prefer_exact).click + end + + expect(page).to have_content 'badges/improve/awesome/coverage.svg' + end + end + + it 'user changes current ref of latest release status badge', :js do + page.within('.Latest-Release') do + find('.ref-selector').click + wait_for_requests + + page.within('.ref-selector') do + fill_in 'Search by Git revision', with: 'improve/awesome' + wait_for_requests + find('li', text: 'improve/awesome', match: :prefer_exact).click + end + + expect(page).to have_content '-/badges/release.svg' + end + end end diff --git a/spec/features/projects/blobs/blame_spec.rb b/spec/features/projects/blobs/blame_spec.rb index 27b7c6ef2d5..9f061a2ff14 100644 --- a/spec/features/projects/blobs/blame_spec.rb +++ b/spec/features/projects/blobs/blame_spec.rb @@ -38,13 +38,13 @@ RSpec.describe 'File blame', :js, feature_category: :projects do within '[data-testid="blob-content-holder"]' do expect(page).to have_css('.blame-commit') expect(page).not_to have_css('.gl-pagination') - expect(page).not_to have_link _('View entire blame') + expect(page).not_to have_link _('Show full blame') end end context 'when blob length is over the blame range limit' do before do - stub_const('Projects::BlameService::PER_PAGE', 2) + stub_const('Gitlab::Git::BlamePagination::PAGINATION_PER_PAGE', 2) end it 'displays two first lines of the file with pagination' do @@ -53,7 +53,7 @@ RSpec.describe 'File blame', :js, feature_category: :projects do within '[data-testid="blob-content-holder"]' do expect(page).to have_css('.blame-commit') expect(page).to have_css('.gl-pagination') - expect(page).to have_link _('View entire blame') + expect(page).to have_link _('Show full blame') expect(page).to have_css('#L1') expect(page).not_to have_css('#L3') @@ -85,19 +85,34 @@ RSpec.describe 'File blame', :js, feature_category: :projects do end end - context 'when user clicks on View entire blame button' do + shared_examples 'a full blame page' do + context 'when user clicks on Show full blame button' do + before do + visit_blob_blame(path) + click_link _('Show full blame') + end + + it 'displays the blame page without pagination' do + within '[data-testid="blob-content-holder"]' do + expect(page).to have_css('#L1') + expect(page).to have_css('#L667') + expect(page).not_to have_css('.gl-pagination') + end + end + end + end + + context 'when streaming is enabled' do before do - visit_blob_blame(path) + stub_const('Gitlab::Git::BlamePagination::STREAMING_PER_PAGE', 50) end - it 'displays the blame page without pagination' do - within '[data-testid="blob-content-holder"]' do - click_link _('View entire blame') + it_behaves_like 'a full blame page' - expect(page).to have_css('#L1') - expect(page).to have_css('#L3') - expect(page).not_to have_css('.gl-pagination') - end + it 'shows loading text', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/410499' do + visit_blob_blame(path) + click_link _('Show full blame') + expect(page).to have_text('Loading full blame...') end end @@ -112,7 +127,7 @@ RSpec.describe 'File blame', :js, feature_category: :projects do within '[data-testid="blob-content-holder"]' do expect(page).to have_css('.blame-commit') expect(page).not_to have_css('.gl-pagination') - expect(page).not_to have_link _('View entire blame') + expect(page).not_to have_link _('Show full blame') end end end @@ -120,7 +135,7 @@ RSpec.describe 'File blame', :js, feature_category: :projects do context 'when blob length is over global max page limit' do before do - stub_const('Projects::BlameService::PER_PAGE', 200) + stub_const('Gitlab::Git::BlamePagination::PAGINATION_PER_PAGE', 200) end let(:path) { 'files/markdown/ruby-style-guide.md' } diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb index 7faf0e1a6b1..cd1dde55e30 100644 --- a/spec/features/projects/blobs/blob_show_spec.rb +++ b/spec/features/projects/blobs/blob_show_spec.rb @@ -137,11 +137,13 @@ RSpec.describe 'File blob', :js, feature_category: :projects do context 'when ref switch' do def switch_ref_to(ref_name) - first('[data-testid="branches-select"]').click + find('.ref-selector').click + wait_for_requests - page.within '.project-refs-form' do - click_link ref_name + page.within('.ref-selector') do + fill_in 'Search by Git revision', with: ref_name wait_for_requests + find('li', text: ref_name, match: :prefer_exact).click end end @@ -193,10 +195,11 @@ RSpec.describe 'File blob', :js, feature_category: :projects do end end - it 'successfully changes ref when the ref name matches the project name' do - project.repository.create_branch(project.name) + # Regression test for https://gitlab.com/gitlab-org/gitlab/-/issues/330947 + it 'successfully changes ref when the ref name matches the project path' do + project.repository.create_branch(project.path) - visit_blob('files/js/application.js', ref: project.name) + visit_blob('files/js/application.js', ref: project.path) switch_ref_to('master') aggregate_failures do @@ -577,7 +580,11 @@ RSpec.describe 'File blob', :js, feature_category: :projects do end describe '.gitlab/dashboards/custom-dashboard.yml' do + let(:remove_monitor_metrics) { false } + before do + stub_feature_flags(remove_monitor_metrics: remove_monitor_metrics) + project.add_maintainer(project.creator) Files::CreateService.new( @@ -605,6 +612,15 @@ RSpec.describe 'File blob', :js, feature_category: :projects do expect(page).to have_link('Learn more') end end + + context 'when metrics dashboard feature is unavailable' do + let(:remove_monitor_metrics) { true } + + it 'displays the blob without an auxiliary viewer' do + expect(page).to have_content('Environment metrics') + expect(page).not_to have_content('Metrics Dashboard YAML definition', wait: 0) + end + end end context 'invalid dashboard file' do diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb index 144b4ed85cd..6e335871ed1 100644 --- a/spec/features/projects/blobs/edit_spec.rb +++ b/spec/features/projects/blobs/edit_spec.rb @@ -3,9 +3,9 @@ require 'spec_helper' RSpec.describe 'Editing file blob', :js, feature_category: :projects do - include Spec::Support::Helpers::Features::SourceEditorSpecHelpers + include Features::SourceEditorSpecHelpers include TreeHelper - include BlobSpecHelpers + include Features::BlobSpecHelpers let_it_be(:project) { create(:project, :public, :repository) } let_it_be(:merge_request) { create(:merge_request, source_project: project, source_branch: 'feature', target_branch: 'master') } @@ -142,7 +142,7 @@ RSpec.describe 'Editing file blob', :js, feature_category: :projects do it 'renders content with CommonMark' do visit project_edit_blob_path(project, tree_join(branch, readme_file_path)) fill_editor(content: '1. one\\n - sublist\\n') - click_link 'Preview' + click_on "Preview" wait_for_requests # the above generates two separate lists (not embedded) in CommonMark diff --git a/spec/features/projects/blobs/user_views_pipeline_editor_button_spec.rb b/spec/features/projects/blobs/user_views_pipeline_editor_button_spec.rb index 2f67e909543..3b383793de2 100644 --- a/spec/features/projects/blobs/user_views_pipeline_editor_button_spec.rb +++ b/spec/features/projects/blobs/user_views_pipeline_editor_button_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'User views pipeline editor button on root ci config file', :js, feature_category: :projects do - include BlobSpecHelpers + include Features::BlobSpecHelpers let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project, :public, :repository) } diff --git a/spec/features/projects/branches/user_creates_branch_spec.rb b/spec/features/projects/branches/user_creates_branch_spec.rb index 60bd77393e9..5aa10a8d4b0 100644 --- a/spec/features/projects/branches/user_creates_branch_spec.rb +++ b/spec/features/projects/branches/user_creates_branch_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'User creates branch', :js, feature_category: :projects do - include Spec::Support::Helpers::Features::BranchesHelpers + include Features::BranchesHelpers let_it_be(:group) { create(:group, :public) } let_it_be(:user) { create(:user) } @@ -81,7 +81,9 @@ RSpec.describe 'User creates branch', :js, feature_category: :projects do it 'does not create new branch' do invalid_branch_name = '1.0 stable' - create_branch(invalid_branch_name) + fill_in("branch_name", with: invalid_branch_name) + find('body').click + click_button("Create branch") expect(page).to have_content('Branch name is invalid') expect(page).to have_content("can't contain spaces") diff --git a/spec/features/projects/branches_spec.rb b/spec/features/projects/branches_spec.rb index fc7833809b3..e1f1a63565c 100644 --- a/spec/features/projects/branches_spec.rb +++ b/spec/features/projects/branches_spec.rb @@ -201,6 +201,12 @@ RSpec.describe 'Branches', feature_category: :projects do end end + describe 'Link to branch rules' do + it 'does not have possibility to navigate to branch rules', :js do + expect(page).not_to have_content(s_("Branches|View branch rules")) + end + end + context 'on project with 0 branch' do let(:project) { create(:project, :public, :empty_repo) } let(:repository) { project.repository } @@ -239,6 +245,17 @@ RSpec.describe 'Branches', feature_category: :projects do expect(page).not_to have_content 'Merge request' end end + + describe 'Navigate to branch rules from branches page' do + it 'shows repository settings page with Branch rules section expanded' do + visit project_branches_path(project) + + view_branch_rules + + expect(page).to have_content( + _('Define rules for who can push, merge, and the required approvals for each branch.')) + end + end end end @@ -353,4 +370,11 @@ RSpec.describe 'Branches', feature_category: :projects do click_button 'Yes, delete branch' end end + + def view_branch_rules + page.within('.nav-controls') do + click_link s_("Branches|View branch rules") + end + wait_for_requests + end end diff --git a/spec/features/projects/ci/editor_spec.rb b/spec/features/projects/ci/editor_spec.rb index 536152626af..9851194bd3c 100644 --- a/spec/features/projects/ci/editor_spec.rb +++ b/spec/features/projects/ci/editor_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' -RSpec.describe 'Pipeline Editor', :js, feature_category: :pipeline_authoring do - include Spec::Support::Helpers::Features::SourceEditorSpecHelpers +RSpec.describe 'Pipeline Editor', :js, feature_category: :pipeline_composition do + include Features::SourceEditorSpecHelpers let(:project) { create(:project_empty_repo, :public) } let(:user) { create(:user) } @@ -101,7 +101,7 @@ RSpec.describe 'Pipeline Editor', :js, feature_category: :pipeline_authoring do end end - it 'user who tries to navigate away can cancel the action and keep their changes' do + it 'user who tries to navigate away can cancel the action and keep their changes', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/410496' do click_link 'Pipelines' page.driver.browser.switch_to.alert.dismiss @@ -113,7 +113,7 @@ RSpec.describe 'Pipeline Editor', :js, feature_category: :pipeline_authoring do end end - it 'user who tries to navigate away can confirm the action and discard their change' do + it 'user who tries to navigate away can confirm the action and discard their change', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/410496' do click_link 'Pipelines' page.driver.browser.switch_to.alert.accept diff --git a/spec/features/projects/ci/lint_spec.rb b/spec/features/projects/ci/lint_spec.rb index 4fea07b18bc..bc370a296e4 100644 --- a/spec/features/projects/ci/lint_spec.rb +++ b/spec/features/projects/ci/lint_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' -RSpec.describe 'CI Lint', :js, feature_category: :pipeline_authoring do - include Spec::Support::Helpers::Features::SourceEditorSpecHelpers +RSpec.describe 'CI Lint', :js, feature_category: :pipeline_composition do + include Features::SourceEditorSpecHelpers let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { create(:user) } diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb index 114182982e2..f9195904ea3 100644 --- a/spec/features/projects/clusters/gcp_spec.rb +++ b/spec/features/projects/clusters/gcp_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Gcp Cluster', :js, feature_category: :kubernetes_management do +RSpec.describe 'Gcp Cluster', :js, feature_category: :deployment_management do include GoogleApi::CloudPlatformHelpers let(:project) { create(:project) } diff --git a/spec/features/projects/clusters/user_spec.rb b/spec/features/projects/clusters/user_spec.rb index 34fc0a76c7f..eb2601bb85f 100644 --- a/spec/features/projects/clusters/user_spec.rb +++ b/spec/features/projects/clusters/user_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User Cluster', :js, feature_category: :kubernetes_management do +RSpec.describe 'User Cluster', :js, feature_category: :deployment_management do include GoogleApi::CloudPlatformHelpers let(:project) { create(:project) } diff --git a/spec/features/projects/commit/cherry_pick_spec.rb b/spec/features/projects/commit/cherry_pick_spec.rb index 93ce851521f..b608fc953f3 100644 --- a/spec/features/projects/commit/cherry_pick_spec.rb +++ b/spec/features/projects/commit/cherry_pick_spec.rb @@ -56,7 +56,7 @@ RSpec.describe 'Cherry-pick Commits', :js, feature_category: :source_code_manage cherry_pick_commit - expect(page).to have_content('Sorry, we cannot cherry-pick this commit automatically.') + expect(page).to have_content('Commit cherry-pick failed:') end end diff --git a/spec/features/projects/commit/comments/user_adds_comment_spec.rb b/spec/features/projects/commit/comments/user_adds_comment_spec.rb index c53ac27bb5f..b0cb57f158d 100644 --- a/spec/features/projects/commit/comments/user_adds_comment_spec.rb +++ b/spec/features/projects/commit/comments/user_adds_comment_spec.rb @@ -3,7 +3,7 @@ require "spec_helper" RSpec.describe "User adds a comment on a commit", :js, feature_category: :source_code_management do - include Spec::Support::Helpers::Features::NotesHelpers + include Features::NotesHelpers include RepoHelpers let(:comment_text) { "XML attached" } @@ -36,7 +36,7 @@ RSpec.describe "User adds a comment on a commit", :js, feature_category: :source expect(page).not_to have_css(".js-note-text") # Check on the `Write` tab - click_button("Write") + click_button("Continue editing") expect(page).to have_field("note[note]", with: "#{comment_text} #{emoji}") @@ -107,7 +107,7 @@ RSpec.describe "User adds a comment on a commit", :js, feature_category: :source # Test UI elements, then submit. page.within("form[data-line-code='#{sample_commit.line_code}']") do expect(find(".js-note-text", visible: false).text).to eq("") - expect(page).to have_css('.js-md-write-button') + expect(page).to have_css('.js-md-preview') click_button("Comment") end diff --git a/spec/features/projects/commit/comments/user_deletes_comments_spec.rb b/spec/features/projects/commit/comments/user_deletes_comments_spec.rb index a1e7ddb4d6e..e265756f930 100644 --- a/spec/features/projects/commit/comments/user_deletes_comments_spec.rb +++ b/spec/features/projects/commit/comments/user_deletes_comments_spec.rb @@ -3,7 +3,7 @@ require "spec_helper" RSpec.describe "User deletes comments on a commit", :js, feature_category: :source_code_management do - include Spec::Support::Helpers::Features::NotesHelpers + include Features::NotesHelpers include Spec::Support::Helpers::ModalHelpers include RepoHelpers @@ -30,7 +30,7 @@ RSpec.describe "User deletes comments on a commit", :js, feature_category: :sour note.hover find(".more-actions").click - find(".more-actions .dropdown-menu li", match: :first) + find(".more-actions li", match: :first) find(".js-note-delete").click end diff --git a/spec/features/projects/commit/comments/user_edits_comments_spec.rb b/spec/features/projects/commit/comments/user_edits_comments_spec.rb index 9019a981a18..b0b963de91b 100644 --- a/spec/features/projects/commit/comments/user_edits_comments_spec.rb +++ b/spec/features/projects/commit/comments/user_edits_comments_spec.rb @@ -3,7 +3,7 @@ require "spec_helper" RSpec.describe "User edits a comment on a commit", :js, feature_category: :source_code_management do - include Spec::Support::Helpers::Features::NotesHelpers + include Features::NotesHelpers include RepoHelpers let(:project) { create(:project, :repository) } diff --git a/spec/features/projects/commit/diff_notes_spec.rb b/spec/features/projects/commit/diff_notes_spec.rb index f29e0803f61..1f4358db9cd 100644 --- a/spec/features/projects/commit/diff_notes_spec.rb +++ b/spec/features/projects/commit/diff_notes_spec.rb @@ -8,18 +8,15 @@ RSpec.describe 'Commit diff', :js, feature_category: :source_code_management do let(:user) { create(:user) } let(:project) { create(:project, :public, :repository) } - using RSpec::Parameterized::TableSyntax - - where(:view, :async_diff_file_loading) do - 'inline' | true - 'inline' | false - 'parallel' | true - 'parallel' | false + where(:view) do + [ + ['inline'], + ['parallel'] + ] end with_them do before do - stub_feature_flags(async_commit_diff_files: async_diff_file_loading) project.add_maintainer(user) sign_in user visit project_commit_path(project, sample_commit.id, view: view) diff --git a/spec/features/projects/commit/user_comments_on_commit_spec.rb b/spec/features/projects/commit/user_comments_on_commit_spec.rb index 66a407b5ff6..709914434e7 100644 --- a/spec/features/projects/commit/user_comments_on_commit_spec.rb +++ b/spec/features/projects/commit/user_comments_on_commit_spec.rb @@ -3,7 +3,7 @@ require "spec_helper" RSpec.describe "User comments on commit", :js, feature_category: :source_code_management do - include Spec::Support::Helpers::Features::NotesHelpers + include Features::NotesHelpers include Spec::Support::Helpers::ModalHelpers include RepoHelpers @@ -38,7 +38,7 @@ RSpec.describe "User comments on commit", :js, feature_category: :source_code_ma expect(page).not_to have_css(".js-note-text") # Check on `Write` tab - click_button("Write") + click_button("Continue editing") expect(page).to have_field("note[note]", with: "#{comment_text} #{emoji_code}") @@ -109,7 +109,7 @@ RSpec.describe "User comments on commit", :js, feature_category: :source_code_ma note.hover find(".more-actions").click - find(".more-actions .dropdown-menu li", match: :first) + find(".more-actions li", match: :first) find(".js-note-delete").click end diff --git a/spec/features/projects/commit/user_reverts_commit_spec.rb b/spec/features/projects/commit/user_reverts_commit_spec.rb index 8c7b8e6ba32..4d2abf55675 100644 --- a/spec/features/projects/commit/user_reverts_commit_spec.rb +++ b/spec/features/projects/commit/user_reverts_commit_spec.rb @@ -47,7 +47,7 @@ RSpec.describe 'User reverts a commit', :js, feature_category: :source_code_mana revert_commit - expect(page).to have_content('Sorry, we cannot revert this commit automatically.') + expect(page).to have_content('Commit revert failed:') end end diff --git a/spec/features/projects/commit/user_sees_pipelines_tab_spec.rb b/spec/features/projects/commit/user_sees_pipelines_tab_spec.rb new file mode 100644 index 00000000000..da83bbcb63a --- /dev/null +++ b/spec/features/projects/commit/user_sees_pipelines_tab_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Commit > Pipelines tab', :js, feature_category: :source_code_management do + let_it_be_with_reload(:project) { create(:project, :repository) } + let_it_be(:user) { create(:user) } + + before do + project.add_maintainer(user) + sign_in(user) + end + + context 'when commit has pipelines' do + let_it_be(:pipeline) do + create(:ci_pipeline, + :success, + project: project, + ref: project.default_branch, + sha: project.commit.sha) + end + + let_it_be(:job) { create(:ci_build, :success, pipeline: pipeline) } + let_it_be(:manual_job) { create(:ci_build, :manual, pipeline: pipeline) } + + before do + visit project_commit_path(project, project.commit.id) + wait_for_requests + end + + it 'displays pipelines table' do + page.within('.commit-ci-menu') do + click_link('Pipelines') + end + + wait_for_requests + + page.within('[data-testid="pipeline-table-row"]') do + expect(page).to have_selector('.ci-success') + expect(page).to have_content(pipeline.id) + expect(page).to have_content('API') + expect(page).to have_css('[data-testid="pipeline-mini-graph"]') + expect(page).to have_css('[data-testid="pipelines-manual-actions-dropdown"]') + expect(page).to have_css('[data-testid="pipeline-multi-actions-dropdown"]') + end + end + end + + context 'when commit does not have pipelines' do + before do + visit project_commit_path(project, project.commit.id) + end + + it 'does not display pipelines tab link' do + expect(page).not_to have_link('Pipelines') + end + end +end diff --git a/spec/features/projects/compare_spec.rb b/spec/features/projects/compare_spec.rb index 8284299443f..4c13d23559b 100644 --- a/spec/features/projects/compare_spec.rb +++ b/spec/features/projects/compare_spec.rb @@ -11,7 +11,7 @@ RSpec.describe "Compare", :js, feature_category: :projects do sign_in user end - describe "branches" do + shared_examples "compare view of branches" do shared_examples 'compares branches' do it 'compares branches' do visit project_compare_index_path(project, from: 'master', to: 'master') @@ -114,7 +114,7 @@ RSpec.describe "Compare", :js, feature_category: :projects do click_button('Compare') page.within('[data-testid="too-many-changes-alert"]') do - expect(page).to have_text("Too many changes to show. To preserve performance only 3 of 3+ files are displayed.") + expect(page).to have_text("Some changes are not shown. For a faster browsing experience, only 3 of 3+ files are shown. Download one of the files below to see all changes.") end end end @@ -148,7 +148,7 @@ RSpec.describe "Compare", :js, feature_category: :projects do end end - describe "tags" do + shared_examples "compare view of tags" do it "compares tags" do visit project_compare_index_path(project, from: "master", to: "master") @@ -182,4 +182,17 @@ RSpec.describe "Compare", :js, feature_category: :projects do dropdown.all(".js-compare-#{dropdown_type}-dropdown .dropdown-item", text: selection).first.click end end + + it_behaves_like "compare view of branches" + it_behaves_like "compare view of tags" + + context "when super sidebar is enabled" do + before do + user.update!(use_new_navigation: true) + stub_feature_flags(super_sidebar_nav: true) + end + + it_behaves_like "compare view of branches" + it_behaves_like "compare view of tags" + end end diff --git a/spec/features/projects/container_registry_spec.rb b/spec/features/projects/container_registry_spec.rb index 98cf024afa8..5306a9f15c6 100644 --- a/spec/features/projects/container_registry_spec.rb +++ b/spec/features/projects/container_registry_spec.rb @@ -30,6 +30,20 @@ RSpec.describe 'Container Registry', :js, feature_category: :projects do expect(page).to have_title _('Container Registry') end + it 'does not have link to settings' do + visit_container_registry + + expect(page).not_to have_link _('Configure in settings') + end + + it 'has link to settings when user is maintainer' do + project.add_maintainer(user) + + visit_container_registry + + expect(page).to have_link _('Configure in settings') + end + context 'when there are no image repositories' do it 'list page has no container title' do visit_container_registry @@ -63,7 +77,8 @@ RSpec.describe 'Container Registry', :js, feature_category: :projects do expect(DeleteContainerRepositoryWorker).not_to receive(:perform_async) find('[title="Remove repository"]').click - expect(find('.modal .modal-title')).to have_content _('Remove repository') + expect(find('.modal .modal-title')).to have_content _('Delete image repository?') + find('.modal .modal-body input').set('my/image') find('.modal .modal-footer .btn-danger').click end @@ -101,7 +116,11 @@ RSpec.describe 'Container Registry', :js, feature_category: :projects do first('[data-testid="additional-actions"]').click first('[data-testid="single-delete-button"]').click expect(find('.modal .modal-title')).to have_content _('Remove tag') + stub_container_registry_tags(repository: %r{my/image}, tags: ('1'..'19').to_a, with_manifest: true) find('.modal .modal-footer .btn-danger').click + + expect(page).to have_content '19 tags' + expect(page).not_to have_content '20 tags' end it('pagination navigate to the second page') do diff --git a/spec/features/projects/environments/environment_metrics_spec.rb b/spec/features/projects/environments/environment_metrics_spec.rb index 4a112445ab9..e212d464029 100644 --- a/spec/features/projects/environments/environment_metrics_spec.rb +++ b/spec/features/projects/environments/environment_metrics_spec.rb @@ -14,10 +14,13 @@ RSpec.describe 'Environment > Metrics', feature_category: :projects do let!(:staging) { create(:environment, name: 'staging', project: project) } before do + stub_feature_flags(remove_monitor_metrics: false) + project.add_developer(user) stub_any_prometheus_request sign_in(user) + stub_feature_flags(remove_monitor_metrics: false) end around do |example| @@ -65,6 +68,18 @@ RSpec.describe 'Environment > Metrics', feature_category: :projects do it_behaves_like 'has environment selector' end + context 'when metrics dashboard feature is unavailable' do + before do + stub_feature_flags(remove_monitor_metrics: true) + end + + it 'does not provide a link to the monitoring dashboard' do + visit_environment(environment) + + expect(page).not_to have_link('Monitoring') + end + end + def visit_environment(environment) visit project_environment_path(environment.project, environment) end diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb index 91401d19fd1..527a146ff73 100644 --- a/spec/features/projects/environments/environment_spec.rb +++ b/spec/features/projects/environments/environment_spec.rb @@ -94,6 +94,36 @@ RSpec.describe 'Environment', feature_category: :projects do expect(page).to have_link("#{build.name} (##{build.id})") end end + + context 'with related deployable present' do + let_it_be(:previous_pipeline) { create(:ci_pipeline, project: project) } + + let_it_be(:previous_build) do + create(:ci_build, :success, pipeline: previous_pipeline, environment: environment.name) + end + + let_it_be(:previous_deployment) do + create(:deployment, :success, environment: environment, deployable: previous_build) + end + + let_it_be(:pipeline) { create(:ci_pipeline, project: project) } + let_it_be(:build) { create(:ci_build, pipeline: pipeline, environment: environment.name) } + + let_it_be(:deployment) do + create(:deployment, :success, environment: environment, deployable: build) + end + + before do + visit_environment(environment) + end + + it 'shows deployment information and buttons', :js do + wait_for_requests + expect(page).to have_button('Re-deploy to environment') + expect(page).to have_button('Rollback environment') + expect(page).to have_link("#{build.name} (##{build.id})") + end + end end end diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb index 788bf6477b1..b50fc59ac32 100644 --- a/spec/features/projects/environments/environments_spec.rb +++ b/spec/features/projects/environments/environments_spec.rb @@ -31,16 +31,16 @@ RSpec.describe 'Environments page', :js, feature_category: :projects do end describe 'page tabs' do - it 'shows "Available" and "Stopped" tab with links' do - visit_environments(project) - - expect(page).to have_link(_('Available')) - expect(page).to have_link(_('Stopped')) - end - describe 'with one available environment' do let!(:environment) { create(:environment, project: project, state: :available) } + it 'shows "Available" and "Stopped" tab with links' do + visit_environments(project) + + expect(page).to have_link(_('Available')) + expect(page).to have_link(_('Stopped')) + end + describe 'in available tab page' do it 'shows one environment' do visit_environments(project, scope: 'available') @@ -70,7 +70,7 @@ RSpec.describe 'Environments page', :js, feature_category: :projects do it 'shows no environments' do visit_environments(project, scope: 'stopped') - expect(page).to have_content(s_('Environments|You don\'t have any stopped environments.')) + expect(page).to have_content(s_('Environments|Get started with environments')) end end @@ -99,7 +99,7 @@ RSpec.describe 'Environments page', :js, feature_category: :projects do it 'shows no environments' do visit_environments(project, scope: 'available') - expect(page).to have_content(s_('Environments|You don\'t have any environments.')) + expect(page).to have_content(s_('Environments|Get started with environments')) end end @@ -119,11 +119,11 @@ RSpec.describe 'Environments page', :js, feature_category: :projects do visit_environments(project) end - it 'does not show environments and counters are set to zero' do - expect(page).to have_content(s_('Environments|You don\'t have any environments.')) + it 'does not show environments and tabs' do + expect(page).to have_content(s_('Environments|Get started with environments')) - expect(page).to have_link("#{_('Available')} 0") - expect(page).to have_link("#{_('Stopped')} 0") + expect(page).not_to have_link(_('Available')) + expect(page).not_to have_link(_('Stopped')) end end @@ -175,7 +175,7 @@ RSpec.describe 'Environments page', :js, feature_category: :projects do context 'when builds and manual actions are present' do let!(:pipeline) { create(:ci_pipeline, project: project) } - let!(:build) { create(:ci_build, pipeline: pipeline) } + let!(:build) { create(:ci_build, :success, pipeline: pipeline) } let!(:action) do create(:ci_build, :manual, pipeline: pipeline, name: 'deploy to production') @@ -207,8 +207,14 @@ RSpec.describe 'Environments page', :js, feature_category: :projects do .not_to change { Ci::Pipeline.count } end - it 'shows a stop button' do + it 'shows a stop button and dialog' do expect(page).to have_selector(stop_button_selector) + + click_button(_('Stop')) + + within('.modal-body') do + expect(page).to have_css('.warning_message') + end end it 'does not show external link button' do @@ -216,7 +222,6 @@ RSpec.describe 'Environments page', :js, feature_category: :projects do end it 'does not show terminal button' do - expect(page).not_to have_button(_('More actions')) expect(page).not_to have_terminal_button end @@ -242,8 +247,14 @@ RSpec.describe 'Environments page', :js, feature_category: :projects do on_stop: 'close_app') end - it 'shows a stop button' do + it 'shows a stop button and dialog' do expect(page).to have_selector(stop_button_selector) + + click_button(_('Stop')) + + within('.modal-body') do + expect(page).not_to have_css('.warning_message') + end end context 'when user is a reporter' do @@ -273,7 +284,6 @@ RSpec.describe 'Environments page', :js, feature_category: :projects do let(:role) { :developer } it 'does not show terminal button' do - expect(page).not_to have_button(_('More actions')) expect(page).not_to have_terminal_button end end @@ -344,7 +354,7 @@ RSpec.describe 'Environments page', :js, feature_category: :projects do wait_for_requests end - it 'enqueues the delayed job', :js do + it 'enqueues the delayed job', :js, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/409990' do expect(delayed_job.reload).to be_pending end end @@ -360,7 +370,7 @@ RSpec.describe 'Environments page', :js, feature_category: :projects do sha: project.commit.id) end - it 'does not show deployments' do + it 'does not show deployments', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/409990' do visit_environments(project) page.click_button _('Expand') @@ -393,7 +403,7 @@ RSpec.describe 'Environments page', :js, feature_category: :projects do it 'does have a new environment button' do visit_environments(project) - expect(page).to have_link('New environment') + expect(page).to have_link('Create an environment') end describe 'creating a new environment' do @@ -405,7 +415,7 @@ RSpec.describe 'Environments page', :js, feature_category: :projects do let(:role) { :developer } it 'developer creates a new environment with a valid name' do - click_link 'New environment' + click_link 'Create an environment' fill_in('Name', with: 'production') click_on 'Save' @@ -413,7 +423,7 @@ RSpec.describe 'Environments page', :js, feature_category: :projects do end it 'developer creates a new environment with invalid name' do - click_link 'New environment' + click_link 'Create an environment' fill_in('Name', with: 'name,with,commas') click_on 'Save' diff --git a/spec/features/projects/files/dockerfile_dropdown_spec.rb b/spec/features/projects/files/dockerfile_dropdown_spec.rb index 1e05bdae204..ec1f03570d9 100644 --- a/spec/features/projects/files/dockerfile_dropdown_spec.rb +++ b/spec/features/projects/files/dockerfile_dropdown_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'Projects > Files > User wants to add a Dockerfile file', :js, feature_category: :projects do - include Spec::Support::Helpers::Features::SourceEditorSpecHelpers + include Features::SourceEditorSpecHelpers before do project = create(:project, :repository) diff --git a/spec/features/projects/files/editing_a_file_spec.rb b/spec/features/projects/files/editing_a_file_spec.rb index 04f45de42cc..1f928da0427 100644 --- a/spec/features/projects/files/editing_a_file_spec.rb +++ b/spec/features/projects/files/editing_a_file_spec.rb @@ -3,7 +3,8 @@ require 'spec_helper' RSpec.describe 'Projects > Files > User wants to edit a file', feature_category: :projects do - let(:project) { create(:project, :repository) } + include ProjectForksHelper + let(:project) { create(:project, :repository, :public) } let(:user) { project.first_owner } let(:commit_params) do { @@ -17,17 +18,48 @@ RSpec.describe 'Projects > Files > User wants to edit a file', feature_category: } end - before do - sign_in user - visit project_edit_blob_path(project, - File.join(project.default_branch, '.gitignore')) + context 'when the user has write access' do + before do + sign_in user + visit project_edit_blob_path(project, + File.join(project.default_branch, '.gitignore')) + end + + it 'file has been updated since the user opened the edit page' do + Files::UpdateService.new(project, user, commit_params).execute + + click_button 'Commit changes' + + expect(page).to have_content 'Someone edited the file the same time you did.' + end end - it 'file has been updated since the user opened the edit page' do - Files::UpdateService.new(project, user, commit_params).execute + context 'when the user does not have write access' do + let(:user) { create(:user) } + + context 'and the user has a fork of the project' do + let(:forked_project) { fork_project(project, user, namespace: user.namespace, repository: true) } + + before do + forked_project + sign_in user + visit project_edit_blob_path(project, + File.join(project.default_branch, '.gitignore')) + end + + context 'and the forked project is ahead of the upstream project' do + before do + Files::UpdateService.new(forked_project, user, commit_params).execute + end - click_button 'Commit changes' + it 'renders an error message' do + click_button 'Commit changes' - expect(page).to have_content 'Someone edited the file the same time you did.' + expect(page).to have_content( + %(Error: Can't edit this file. The fork and upstream project have diverged. Edit the file on the fork) + ) + end + end + end end end diff --git a/spec/features/projects/files/gitignore_dropdown_spec.rb b/spec/features/projects/files/gitignore_dropdown_spec.rb index 5e11a94e65b..eedb79167bd 100644 --- a/spec/features/projects/files/gitignore_dropdown_spec.rb +++ b/spec/features/projects/files/gitignore_dropdown_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'Projects > Files > User wants to add a .gitignore file', :js, feature_category: :projects do - include Spec::Support::Helpers::Features::SourceEditorSpecHelpers + include Features::SourceEditorSpecHelpers before do project = create(:project, :repository) diff --git a/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb index 67678a937e5..f2d657b3513 100644 --- a/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb +++ b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'Projects > Files > User wants to add a .gitlab-ci.yml file', :js, feature_category: :projects do - include Spec::Support::Helpers::Features::SourceEditorSpecHelpers + include Features::SourceEditorSpecHelpers let(:params) { {} } let(:filename) { '.gitlab-ci.yml' } diff --git a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb index 8d64151e680..cfa55eba188 100644 --- a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb +++ b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe 'Projects > Files > Project owner sees a link to create a license file in empty project', :js, feature_category: :projects do - include WebIdeSpecHelpers + include Features::WebIdeSpecHelpers let(:project) { create(:project_empty_repo) } let(:project_maintainer) { project.first_owner } diff --git a/spec/features/projects/files/user_browses_files_spec.rb b/spec/features/projects/files/user_browses_files_spec.rb index 8082d1bdf63..9b9c2158432 100644 --- a/spec/features/projects/files/user_browses_files_spec.rb +++ b/spec/features/projects/files/user_browses_files_spec.rb @@ -11,8 +11,7 @@ RSpec.describe "User browses files", :js, feature_category: :projects do "A fork of this project has been created that you can make changes in, so you can submit a merge request." end - let(:project) { create(:project, :repository, name: "Shop") } - let(:project2) { create(:project, :repository, name: "Another Project", path: "another-project") } + let_it_be(:project) { create(:project, :repository) } let(:tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) } let(:user) { project.first_owner } @@ -142,7 +141,9 @@ RSpec.describe "User browses files", :js, feature_category: :projects do expect(page).to have_current_path(project_blob_path(project, "markdown/doc/raketasks/maintenance.md"), ignore_query: true) expect(page).to have_content("bundle exec rake gitlab:env:info RAILS_ENV=production") - click_link("shop") + page.within(".tree-ref-container") do + click_link(project.path) + end page.within(".tree-table") do click_link("README.md") diff --git a/spec/features/projects/files/user_creates_files_spec.rb b/spec/features/projects/files/user_creates_files_spec.rb index 97ccb45dfc6..42aceef256a 100644 --- a/spec/features/projects/files/user_creates_files_spec.rb +++ b/spec/features/projects/files/user_creates_files_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' RSpec.describe 'Projects > Files > User creates files', :js, feature_category: :projects do - include Spec::Support::Helpers::Features::SourceEditorSpecHelpers - include BlobSpecHelpers + include Features::SourceEditorSpecHelpers + include Features::BlobSpecHelpers let(:fork_message) do "You're not allowed to make changes to this project directly. "\ diff --git a/spec/features/projects/files/user_edits_files_spec.rb b/spec/features/projects/files/user_edits_files_spec.rb index 5a61aa146a2..779257b2e2b 100644 --- a/spec/features/projects/files/user_edits_files_spec.rb +++ b/spec/features/projects/files/user_edits_files_spec.rb @@ -3,9 +3,9 @@ require 'spec_helper' RSpec.describe 'Projects > Files > User edits files', :js, feature_category: :projects do - include Spec::Support::Helpers::Features::SourceEditorSpecHelpers + include Features::SourceEditorSpecHelpers include ProjectForksHelper - include BlobSpecHelpers + include Features::BlobSpecHelpers let(:project) { create(:project, :repository, name: 'Shop') } let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') } diff --git a/spec/features/projects/fork_spec.rb b/spec/features/projects/fork_spec.rb index 8b484141a95..39cdc8faa85 100644 --- a/spec/features/projects/fork_spec.rb +++ b/spec/features/projects/fork_spec.rb @@ -119,16 +119,6 @@ RSpec.describe 'Project fork', feature_category: :projects do end end - shared_examples "increments the fork counter on the source project's page" do - specify :sidekiq_might_not_need_inline do - create_forks - - visit project_path(project) - - expect(page).to have_css('.fork-count', text: 2) - end - end - it_behaves_like 'fork button on project page' it_behaves_like 'create fork page', 'Fork project' @@ -185,25 +175,17 @@ RSpec.describe 'Project fork', feature_category: :projects do end end - context 'with cache_home_panel feature flag' do + context 'when user is a maintainer in multiple groups' do before do create(:group_member, :maintainer, user: user, group: group2) end - context 'when caching is enabled' do - before do - stub_feature_flags(cache_home_panel: project) - end - - it_behaves_like "increments the fork counter on the source project's page" - end + it "increments the fork counter on the source project's page", :sidekiq_might_not_need_inline do + create_forks - context 'when caching is disabled' do - before do - stub_feature_flags(cache_home_panel: false) - end + visit project_path(project) - it_behaves_like "increments the fork counter on the source project's page" + expect(page).to have_css('.fork-count', text: 2) end end end diff --git a/spec/features/projects/import_export/export_file_spec.rb b/spec/features/projects/import_export/export_file_spec.rb index 6630956f835..3c39d8745a4 100644 --- a/spec/features/projects/import_export/export_file_spec.rb +++ b/spec/features/projects/import_export/export_file_spec.rb @@ -40,59 +40,28 @@ RSpec.describe 'Import/Export - project export integration test', :js, feature_c sign_in(user) end - context "with streaming serializer" do - before do - stub_feature_flags(project_export_as_ndjson: false) - end - - it 'exports a project successfully', :sidekiq_inline do - export_project_and_download_file(page, project) - - in_directory_with_expanded_export(project) do |exit_status, tmpdir| - expect(exit_status).to eq(0) + it 'exports a project successfully', :sidekiq_inline do + export_project_and_download_file(page, project) - project_json_path = File.join(tmpdir, 'project.json') - expect(File).to exist(project_json_path) + in_directory_with_expanded_export(project) do |exit_status, tmpdir| + expect(exit_status).to eq(0) - project_hash = Gitlab::Json.parse(File.read(project_json_path)) - - sensitive_words.each do |sensitive_word| - found = find_sensitive_attributes(sensitive_word, project_hash) + project_json_path = File.join(tmpdir, 'tree', 'project.json') + expect(File).to exist(project_json_path) - expect(found).to be_nil, failure_message(found.try(:key_found), found.try(:parent), sensitive_word) + relations = [] + relations << Gitlab::Json.parse(File.read(project_json_path)) + Dir.glob(File.join(tmpdir, 'tree/project', '*.ndjson')) do |rb_filename| + File.foreach(rb_filename) do |line| + relations << Gitlab::Json.parse(line) end end - end - end - context "with ndjson" do - before do - stub_feature_flags(project_export_as_ndjson: true) - end - - it 'exports a project successfully', :sidekiq_inline do - export_project_and_download_file(page, project) - - in_directory_with_expanded_export(project) do |exit_status, tmpdir| - expect(exit_status).to eq(0) - - project_json_path = File.join(tmpdir, 'tree', 'project.json') - expect(File).to exist(project_json_path) - - relations = [] - relations << Gitlab::Json.parse(File.read(project_json_path)) - Dir.glob(File.join(tmpdir, 'tree/project', '*.ndjson')) do |rb_filename| - File.foreach(rb_filename) do |line| - relations << Gitlab::Json.parse(line) - end - end - - relations.each do |relation_hash| - sensitive_words.each do |sensitive_word| - found = find_sensitive_attributes(sensitive_word, relation_hash) + relations.each do |relation_hash| + sensitive_words.each do |sensitive_word| + found = find_sensitive_attributes(sensitive_word, relation_hash) - expect(found).to be_nil, failure_message(found.try(:key_found), found.try(:parent), sensitive_word) - end + expect(found).to be_nil, failure_message(found.try(:key_found), found.try(:parent), sensitive_word) end end end diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb index 8fb11f06cdd..f4ed0728402 100644 --- a/spec/features/projects/import_export/import_file_spec.rb +++ b/spec/features/projects/import_export/import_file_spec.rb @@ -8,6 +8,7 @@ RSpec.describe 'Import/Export - project import integration test', :js, feature_c let(:export_path) { "#{Dir.tmpdir}/import_file_spec" } before do + stub_application_setting(import_sources: ['gitlab_project']) stub_uploads_object_storage(FileUploader) allow_next_instance_of(Gitlab::ImportExport) do |instance| allow(instance).to receive(:storage_path).and_return(export_path) diff --git a/spec/features/projects/import_export/test_project_export.tar.gz b/spec/features/projects/import_export/test_project_export.tar.gz Binary files differindex b93da033aea..d34d72920dd 100644 --- a/spec/features/projects/import_export/test_project_export.tar.gz +++ b/spec/features/projects/import_export/test_project_export.tar.gz diff --git a/spec/features/projects/integrations/apple_app_store_spec.rb b/spec/features/projects/integrations/apple_app_store_spec.rb new file mode 100644 index 00000000000..a5ae7df4a89 --- /dev/null +++ b/spec/features/projects/integrations/apple_app_store_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Upload Dropzone Field', feature_category: :integrations do + include_context 'project integration activation' + + it 'uploads the file data to the correct form fields and updates the messaging correctly', :js, :aggregate_failures do + visit_project_integration('Apple App Store Connect') + + expect(page).to have_content('Drag your Private Key file here or click to upload.') + expect(page).not_to have_content('auth_key.p8') + + find("input[name='service[dropzone_file_name]']", + visible: false).set(Rails.root.join('spec/fixtures/auth_key.p8')) + + expect(page).to have_field("service[app_store_private_key]", type: :hidden, + with: File.read(Rails.root.join('spec/fixtures/auth_key.p8'))) + expect(page).to have_field("service[app_store_private_key_file_name]", type: :hidden, with: 'auth_key.p8') + + expect(page).not_to have_content('Drag your Private Key file here or click to upload.') + expect(page).to have_content('auth_key.p8') + end +end diff --git a/spec/features/projects/integrations/google_play_spec.rb b/spec/features/projects/integrations/google_play_spec.rb new file mode 100644 index 00000000000..db867fc40d7 --- /dev/null +++ b/spec/features/projects/integrations/google_play_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Upload Dropzone Field', feature_category: :integrations do + include_context 'project integration activation' + + it 'uploads the file data to the correct form fields and updates the messaging correctly', :js, :aggregate_failures do + visit_project_integration('Google Play') + + expect(page).to have_content('Drag your key file here or click to upload.') + expect(page).not_to have_content('service_account.json') + + find("input[name='service[dropzone_file_name]']", + visible: false).set(Rails.root.join('spec/fixtures/service_account.json')) + + expect(page).to have_field("service[service_account_key]", type: :hidden, + with: File.read(Rails.root.join('spec/fixtures/service_account.json'))) + expect(page).to have_field("service[service_account_key_file_name]", type: :hidden, with: 'service_account.json') + + expect(page).not_to have_content('Drag your key file here or click to upload.') + expect(page).to have_content('service_account.json') + end +end diff --git a/spec/features/projects/integrations/project_integrations_spec.rb b/spec/features/projects/integrations/project_integrations_spec.rb index d99b6ca9092..94dbc401726 100644 --- a/spec/features/projects/integrations/project_integrations_spec.rb +++ b/spec/features/projects/integrations/project_integrations_spec.rb @@ -12,4 +12,16 @@ RSpec.describe 'Project integrations', :js, feature_category: :integrations do visit_project_integration(integration.title) end end + + context 'with remove_monitor_metrics flag enabled' do + before do + stub_feature_flags(remove_monitor_metrics: true) + end + + it 'returns a 404 for the prometheus edit page' do + visit edit_project_settings_integration_path(project, :prometheus) + + expect(page).to have_content "Page Not Found" + end + end end diff --git a/spec/features/projects/integrations/user_activates_mattermost_slash_command_spec.rb b/spec/features/projects/integrations/user_activates_mattermost_slash_command_spec.rb index 16c7a3ff226..aea76944c7f 100644 --- a/spec/features/projects/integrations/user_activates_mattermost_slash_command_spec.rb +++ b/spec/features/projects/integrations/user_activates_mattermost_slash_command_spec.rb @@ -79,7 +79,7 @@ RSpec.describe 'Set up Mattermost slash commands', :js, feature_category: :integ select_element = find('#mattermost_team_id') - expect(select_element['disabled']).to be_falsey + expect(select_element['disabled']).to eq('false') expect(select_element.all('option').count).to eq(3) end @@ -99,7 +99,7 @@ RSpec.describe 'Set up Mattermost slash commands', :js, feature_category: :integ click_link 'Add to Mattermost' - expect(find('input[type="submit"]')['disabled']).not_to eq("true") + expect(find('button[type="submit"]')['disabled']).not_to eq("true") end it 'disables the submit button if the required fields are not provided', :js do @@ -109,7 +109,7 @@ RSpec.describe 'Set up Mattermost slash commands', :js, feature_category: :integ fill_in('mattermost_trigger', with: '') - expect(find('input[type="submit"]')['disabled']).to eq("true") + expect(find('button[type="submit"]')['disabled']).to eq("true") end def stub_teams(count: 0) @@ -145,7 +145,7 @@ RSpec.describe 'Set up Mattermost slash commands', :js, feature_category: :integ it 'shows a token placeholder' do token_placeholder = find_field('service_token')['placeholder'] - expect(token_placeholder).to eq('XXxxXXxxXXxxXXxxXXxxXXxx') + expect(token_placeholder).to eq('') end end end diff --git a/spec/features/projects/integrations/user_activates_prometheus_spec.rb b/spec/features/projects/integrations/user_activates_prometheus_spec.rb index 5b2d885410f..a47000672ca 100644 --- a/spec/features/projects/integrations/user_activates_prometheus_spec.rb +++ b/spec/features/projects/integrations/user_activates_prometheus_spec.rb @@ -6,6 +6,7 @@ RSpec.describe 'User activates Prometheus', feature_category: :integrations do include_context 'project integration activation' before do + stub_feature_flags(remove_monitor_metrics: false) stub_request(:get, /.*prometheus.example.com.*/) end diff --git a/spec/features/projects/integrations/user_activates_slack_notifications_spec.rb b/spec/features/projects/integrations/user_activates_slack_notifications_spec.rb index ec00dcaf046..01c202baf70 100644 --- a/spec/features/projects/integrations/user_activates_slack_notifications_spec.rb +++ b/spec/features/projects/integrations/user_activates_slack_notifications_spec.rb @@ -7,7 +7,6 @@ RSpec.describe 'User activates Slack notifications', :js, feature_category: :int context 'when integration is not configured yet' do before do - stub_feature_flags(integration_slack_app_notifications: false) visit_project_integration('Slack notifications') end diff --git a/spec/features/projects/integrations/user_activates_slack_slash_command_spec.rb b/spec/features/projects/integrations/user_activates_slack_slash_command_spec.rb index 0f6d721565e..38491501c65 100644 --- a/spec/features/projects/integrations/user_activates_slack_slash_command_spec.rb +++ b/spec/features/projects/integrations/user_activates_slack_slash_command_spec.rb @@ -12,7 +12,7 @@ RSpec.describe 'Slack slash commands', :js, feature_category: :integrations do it 'shows a token placeholder' do token_placeholder = find_field('Token')['placeholder'] - expect(token_placeholder).to eq('XXxxXXxxXXxxXXxxXXxxXXxx') + expect(token_placeholder).to eq('') end it 'shows a help message' do diff --git a/spec/features/projects/integrations/user_uses_inherited_settings_spec.rb b/spec/features/projects/integrations/user_uses_inherited_settings_spec.rb index e0063a9c733..9ff344bcc88 100644 --- a/spec/features/projects/integrations/user_uses_inherited_settings_spec.rb +++ b/spec/features/projects/integrations/user_uses_inherited_settings_spec.rb @@ -22,17 +22,17 @@ RSpec.describe 'User uses inherited settings', :js, feature_category: :integrati expect(page).not_to have_button('Use custom settings') expect(page).to have_field('Web URL', with: parent_settings[:url], readonly: true) - expect(page).to have_field('Enter new password or API token', with: '', readonly: true) + expect(page).to have_field('New API token, password, or Jira personal access token', with: '', readonly: true) click_on 'Use default settings' click_on 'Use custom settings' expect(page).not_to have_button('Use default settings') expect(page).to have_field('Web URL', with: project_settings[:url], readonly: false) - expect(page).to have_field('Enter new password or API token', with: '', readonly: false) + expect(page).to have_field('New API token, password, or Jira personal access token', with: '', readonly: false) fill_in 'Web URL', with: 'http://custom.com' - fill_in 'Enter new password or API token', with: 'custom' + fill_in 'New API token, password, or Jira personal access token', with: 'custom' click_save_integration @@ -53,14 +53,14 @@ RSpec.describe 'User uses inherited settings', :js, feature_category: :integrati expect(page).not_to have_button('Use default settings') expect(page).to have_field('URL', with: project_settings[:url], readonly: false) - expect(page).to have_field('Enter new password or API token', with: '', readonly: false) + expect(page).to have_field('New API token, password, or Jira personal access token', with: '', readonly: false) click_on 'Use custom settings' click_on 'Use default settings' expect(page).not_to have_button('Use custom settings') expect(page).to have_field('URL', with: parent_settings[:url], readonly: true) - expect(page).to have_field('Enter new password or API token', with: '', readonly: true) + expect(page).to have_field('New API token, password, or Jira personal access token', with: '', readonly: true) click_save_integration diff --git a/spec/features/projects/issuable_templates_spec.rb b/spec/features/projects/issuable_templates_spec.rb index adf410ce6e8..77f88994bfb 100644 --- a/spec/features/projects/issuable_templates_spec.rb +++ b/spec/features/projects/issuable_templates_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe 'issuable templates', :js, feature_category: :projects do include ProjectForksHelper + include CookieHelper let(:user) { create(:user) } let(:project) { create(:project, :public, :repository) } @@ -12,6 +13,7 @@ RSpec.describe 'issuable templates', :js, feature_category: :projects do before do project.add_maintainer(user) sign_in user + set_cookie('new-actions-popover-viewed', 'true') end context 'user creates an issue using templates' do diff --git a/spec/features/projects/issues/viewing_relocated_issues_spec.rb b/spec/features/projects/issues/viewing_relocated_issues_spec.rb index abd36b3ceef..f86f7bfacbd 100644 --- a/spec/features/projects/issues/viewing_relocated_issues_spec.rb +++ b/spec/features/projects/issues/viewing_relocated_issues_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'issues canonical link', feature_category: :team_planning do - include Spec::Support::Helpers::Features::CanonicalLinkHelpers + include Features::CanonicalLinkHelpers let_it_be(:original_project) { create(:project, :public) } let_it_be(:original_issue) { create(:issue, project: original_project) } diff --git a/spec/features/projects/jobs/user_triggers_manual_job_with_variables_spec.rb b/spec/features/projects/jobs/user_triggers_manual_job_with_variables_spec.rb index a9e0fce1a1c..e4394010e8c 100644 --- a/spec/features/projects/jobs/user_triggers_manual_job_with_variables_spec.rb +++ b/spec/features/projects/jobs/user_triggers_manual_job_with_variables_spec.rb @@ -24,7 +24,7 @@ RSpec.describe 'User triggers manual job with variables', :js, feature_category: find("[data-testid='ci-variable-value']").set('key_value') end - find("[data-testid='trigger-manual-job-btn']").click + find("[data-testid='run-manual-job-btn']").click wait_for_requests diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index 67389fdda8a..796bac2e8e7 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -232,7 +232,7 @@ RSpec.describe 'Jobs', :clean_gitlab_redis_shared_state, feature_category: :proj expect(page).to have_link('New issue') end - it 'links to issues/new with the title and description filled in' do + it 'links to issues/new with the title and description filled in', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/408222' do button_title = "Job Failed ##{job.id}" job_url = project_job_url(project, job, host: page.server.host, port: page.server.port) options = { issue: { title: button_title, description: "Job [##{job.id}](#{job_url}) failed for #{job.sha}:\n" } } @@ -269,13 +269,15 @@ RSpec.describe 'Jobs', :clean_gitlab_redis_shared_state, feature_category: :proj let(:resource_group) { create(:ci_resource_group, project: project) } before do + resource_group.assign_resource_to(create(:ci_build)) + visit project_job_path(project, job) wait_for_requests end it 'shows correct UI components' do expect(page).to have_content("This job is waiting for resource: #{resource_group.key}") - expect(page).to have_link("Cancel this job") + expect(page).to have_link("View job currently using resource") end end @@ -1065,16 +1067,19 @@ RSpec.describe 'Jobs', :clean_gitlab_redis_shared_state, feature_category: :proj end context "Build from other project" do + let(:other_job_download_path) { download_project_job_artifacts_path(project, job2) } + before do create(:ci_job_artifact, :archive, file: artifacts_file, job: job2) end - it do - requests = inspect_requests do - visit download_project_job_artifacts_path(project, job2) - end + it 'receive 404 from download request', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/391632' do + requests = inspect_requests { visit other_job_download_path } + + request = requests.find { |request| request.url == other_job_download_path } - expect(requests.first.status_code).to eq(404) + expect(request).to be_present + expect(request.status_code).to eq(404) end end end diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb index b527b8926a0..4af5dd380c1 100644 --- a/spec/features/projects/labels/update_prioritization_spec.rb +++ b/spec/features/projects/labels/update_prioritization_spec.rb @@ -101,7 +101,7 @@ RSpec.describe 'Prioritize labels', feature_category: :team_planning do expect(page).to have_content 'wontfix' # Sort labels - drag_to(selector: '.label-list-item', from_index: 1, to_index: 2) + drag_to(selector: '.label-list-item .label-content', from_index: 1, to_index: 2) page.within('.prioritized-labels') do expect(first('.label-list-item')).to have_content('feature') diff --git a/spec/features/projects/members/anonymous_user_sees_members_spec.rb b/spec/features/projects/members/anonymous_user_sees_members_spec.rb index 6b92581d704..0b8661cce82 100644 --- a/spec/features/projects/members/anonymous_user_sees_members_spec.rb +++ b/spec/features/projects/members/anonymous_user_sees_members_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'Projects > Members > Anonymous user sees members' do - include Spec::Support::Helpers::Features::MembersHelpers + include Features::MembersHelpers let(:user) { create(:user) } let(:group) { create(:group, :public) } diff --git a/spec/features/projects/members/group_members_spec.rb b/spec/features/projects/members/group_members_spec.rb index 416b96ab668..c0257446a37 100644 --- a/spec/features/projects/members/group_members_spec.rb +++ b/spec/features/projects/members/group_members_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'Projects members', :js, feature_category: :subgroups do - include Spec::Support::Helpers::Features::MembersHelpers + include Features::MembersHelpers let(:user) { create(:user) } let(:developer) { create(:user) } diff --git a/spec/features/projects/members/groups_with_access_list_spec.rb b/spec/features/projects/members/groups_with_access_list_spec.rb index 51acba246c5..8238f95fd47 100644 --- a/spec/features/projects/members/groups_with_access_list_spec.rb +++ b/spec/features/projects/members/groups_with_access_list_spec.rb @@ -3,9 +3,9 @@ require 'spec_helper' RSpec.describe 'Projects > Members > Groups with access list', :js, feature_category: :subgroups do - include Spec::Support::Helpers::Features::MembersHelpers + include Features::MembersHelpers include Spec::Support::Helpers::ModalHelpers - include Spec::Support::Helpers::Features::InviteMembersModalHelper + include Features::InviteMembersModalHelpers let_it_be(:user) { create(:user) } let_it_be(:group) { create(:group, :public) } diff --git a/spec/features/projects/members/manage_groups_spec.rb b/spec/features/projects/members/manage_groups_spec.rb index b78bfacf171..5efb5abefc6 100644 --- a/spec/features/projects/members/manage_groups_spec.rb +++ b/spec/features/projects/members/manage_groups_spec.rb @@ -4,8 +4,8 @@ require 'spec_helper' RSpec.describe 'Project > Members > Manage groups', :js, feature_category: :subgroups do include ActionView::Helpers::DateHelper - include Spec::Support::Helpers::Features::MembersHelpers - include Spec::Support::Helpers::Features::InviteMembersModalHelper + include Features::MembersHelpers + include Features::InviteMembersModalHelpers let_it_be(:maintainer) { create(:user) } diff --git a/spec/features/projects/members/manage_members_spec.rb b/spec/features/projects/members/manage_members_spec.rb index 615ef1b03dd..5ae6eb83b6b 100644 --- a/spec/features/projects/members/manage_members_spec.rb +++ b/spec/features/projects/members/manage_members_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' RSpec.describe 'Projects > Members > Manage members', :js, feature_category: :onboarding do - include Spec::Support::Helpers::Features::MembersHelpers - include Spec::Support::Helpers::Features::InviteMembersModalHelper + include Features::MembersHelpers + include Features::InviteMembersModalHelpers include Spec::Support::Helpers::ModalHelpers let_it_be(:user1) { create(:user, name: 'John Doe') } diff --git a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb index 31c8237aacc..be778def833 100644 --- a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb +++ b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb @@ -4,8 +4,8 @@ require 'spec_helper' RSpec.describe 'Projects > Members > Maintainer adds member with expiration date', :js, feature_category: :subgroups do include ActiveSupport::Testing::TimeHelpers - include Spec::Support::Helpers::Features::MembersHelpers - include Spec::Support::Helpers::Features::InviteMembersModalHelper + include Features::MembersHelpers + include Features::InviteMembersModalHelpers let_it_be(:maintainer) { create(:user) } let_it_be(:project) { create(:project, :with_namespace_settings) } diff --git a/spec/features/projects/members/member_leaves_project_spec.rb b/spec/features/projects/members/member_leaves_project_spec.rb index 2632bc2f5bd..91e30b3396e 100644 --- a/spec/features/projects/members/member_leaves_project_spec.rb +++ b/spec/features/projects/members/member_leaves_project_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'Projects > Members > Member leaves project', feature_category: :subgroups do - include Spec::Support::Helpers::Features::MembersHelpers + include Features::MembersHelpers include Spec::Support::Helpers::ModalHelpers let(:user) { create(:user) } diff --git a/spec/features/projects/members/sorting_spec.rb b/spec/features/projects/members/sorting_spec.rb index 6df1e974f42..85bf381404c 100644 --- a/spec/features/projects/members/sorting_spec.rb +++ b/spec/features/projects/members/sorting_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'Projects > Members > Sorting', :js, feature_category: :subgroups do - include Spec::Support::Helpers::Features::MembersHelpers + include Features::MembersHelpers let(:maintainer) { create(:user, name: 'John Doe', created_at: 5.days.ago, last_activity_on: Date.today) } let(:developer) { create(:user, name: 'Mary Jane', created_at: 1.day.ago, last_sign_in_at: 5.days.ago, last_activity_on: Date.today - 5) } @@ -148,7 +148,7 @@ RSpec.describe 'Projects > Members > Sorting', :js, feature_category: :subgroups def expect_sort_by(text, sort_direction) within('[data-testid="members-sort-dropdown"]') do - expect(page).to have_css('button[aria-haspopup="true"]', text: text) + expect(page).to have_css('button[aria-haspopup="menu"]', text: text) expect(page).to have_button("Sorting Direction: #{sort_direction == :asc ? 'Ascending' : 'Descending'}") end end diff --git a/spec/features/projects/members/tabs_spec.rb b/spec/features/projects/members/tabs_spec.rb index 232420224fc..9ee06edc0c1 100644 --- a/spec/features/projects/members/tabs_spec.rb +++ b/spec/features/projects/members/tabs_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'Projects > Members > Tabs', :js, feature_category: :subgroups do - include Spec::Support::Helpers::Features::MembersHelpers + include Features::MembersHelpers using RSpec::Parameterized::TableSyntax let_it_be(:user) { create(:user) } diff --git a/spec/features/projects/milestones/milestone_editing_spec.rb b/spec/features/projects/milestones/milestone_editing_spec.rb new file mode 100644 index 00000000000..8a03683eb35 --- /dev/null +++ b/spec/features/projects/milestones/milestone_editing_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe "Milestone editing", :js, feature_category: :team_planning do + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, name: 'test', namespace: user.namespace) } + + let(:milestone) { create(:milestone, project: project, start_date: Date.today, due_date: 5.days.from_now) } + + before do + sign_in(user) + + visit(edit_project_milestone_path(project, milestone)) + end + + it_behaves_like 'milestone handling version conflicts' +end diff --git a/spec/features/projects/navbar_spec.rb b/spec/features/projects/navbar_spec.rb index 6090d132e3a..532dd7d0a84 100644 --- a/spec/features/projects/navbar_spec.rb +++ b/spec/features/projects/navbar_spec.rb @@ -19,9 +19,12 @@ RSpec.describe 'Project navbar', :with_license, feature_category: :projects do stub_config(registry: { enabled: false }) stub_feature_flags(harbor_registry_integration: false) + stub_feature_flags(ml_experiment_tracking: false) + stub_feature_flags(remove_monitor_metrics: false) insert_package_nav(_('Deployments')) insert_infrastructure_registry_nav insert_infrastructure_google_cloud_nav + insert_infrastructure_aws_nav end it_behaves_like 'verified navigation bar' do @@ -90,7 +93,19 @@ RSpec.describe 'Project navbar', :with_license, feature_category: :projects do before do stub_feature_flags(harbor_registry_integration: true) - insert_harbor_registry_nav(_('Infrastructure Registry')) + insert_harbor_registry_nav(_('Terraform modules')) + + visit project_path(project) + end + + it_behaves_like 'verified navigation bar' + end + + context 'when models experiments is available' do + before do + stub_feature_flags(ml_experiment_tracking: true) + + insert_model_experiments_nav(_('Terraform modules')) visit project_path(project) end diff --git a/spec/features/projects/network_graph_spec.rb b/spec/features/projects/network_graph_spec.rb index a29c9f58195..af976b8ffb0 100644 --- a/spec/features/projects/network_graph_spec.rb +++ b/spec/features/projects/network_graph_spec.rb @@ -6,10 +6,13 @@ RSpec.describe 'Project Network Graph', :js, feature_category: :projects do let(:user) { create :user } let(:project) { create :project, :repository, namespace: user.namespace } let(:ref_selector) { '.ref-selector' } + let(:ref_with_hash) { 'ref-#-hash' } before do sign_in(user) + project.repository.create_branch(ref_with_hash, 'master') + # Stub Graph max_size to speed up test (10 commits vs. 650) allow(Network::Graph).to receive(:max_count).and_return(10) end @@ -52,6 +55,12 @@ RSpec.describe 'Project Network Graph', :js, feature_category: :projects do end end + it 'switches ref to branch containing a hash' do + switch_ref_to(ref_with_hash) + + expect(page).to have_selector ref_selector, text: ref_with_hash + end + it 'switches ref to tag' do switch_ref_to('v1.0.0') diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb index c6a6ee68185..351662af217 100644 --- a/spec/features/projects/new_project_spec.rb +++ b/spec/features/projects/new_project_spec.rb @@ -3,7 +3,11 @@ require 'spec_helper' RSpec.describe 'New project', :js, feature_category: :projects do - include Spec::Support::Helpers::Features::TopNavSpecHelpers + include Features::TopNavSpecHelpers + + before do + stub_application_setting(import_sources: Gitlab::ImportSources.values) + end context 'as a user' do let_it_be(:user) { create(:user) } @@ -67,26 +71,38 @@ RSpec.describe 'New project', :js, feature_category: :projects do context 'as an admin' do let(:user) { create(:admin) } - before do - sign_in(user) - end + shared_examples '"New project" page' do + before do + sign_in(user) + end - it 'shows "New project" page', :js do - visit new_project_path - click_link 'Create blank project' + it 'shows "New project" page', :js do + visit new_project_path + click_link 'Create blank project' - expect(page).to have_content('Project name') - expect(page).to have_content('Project URL') - expect(page).to have_content('Project slug') + expect(page).to have_content('Project name') + expect(page).to have_content('Project URL') + expect(page).to have_content('Project slug') - click_link('New project') - click_link 'Import project' + click_link('New project') + click_link 'Import project' + + expect(page).to have_link('GitHub') + expect(page).to have_link('Bitbucket') + expect(page).to have_button('Repository by URL') + expect(page).to have_link('GitLab export') + end + end + + include_examples '"New project" page' - expect(page).to have_link('GitHub') - expect(page).to have_link('Bitbucket') - expect(page).to have_link('GitLab.com') - expect(page).to have_button('Repository by URL') - expect(page).to have_link('GitLab export') + context 'when the new navigation is enabled' do + before do + user.update!(use_new_navigation: true) + stub_feature_flags(super_sidebar_nav: true) + end + + include_examples '"New project" page' end shared_examples 'renders importer link' do |params| @@ -123,11 +139,9 @@ RSpec.describe 'New project', :js, feature_category: :projects do 'github': :new_import_github_path, 'bitbucket': :status_import_bitbucket_path, 'bitbucket server': :status_import_bitbucket_server_path, - 'gitlab.com': :status_import_gitlab_path, 'fogbugz': :new_import_fogbugz_path, 'gitea': :new_import_gitea_path, - 'manifest': :new_import_manifest_path, - 'phabricator': :new_import_phabricator_path + 'manifest': :new_import_manifest_path } end @@ -560,22 +574,52 @@ RSpec.describe 'New project', :js, feature_category: :projects do end end - context 'from GitLab.com', :js do - let(:target_link) { 'GitLab.com' } - let(:provider) { :gitlab } + describe 'sidebar' do + let_it_be(:user) { create(:user) } + let_it_be(:parent_group) { create(:group) } - context 'as a user' do - let(:user) { create(:user) } - let(:oauth_config_instructions) { 'To enable importing projects from GitLab.com, ask your GitLab administrator to configure OAuth integration' } + before do + parent_group.add_owner(user) + sign_in(user) + end - it_behaves_like 'has instructions to enable OAuth' + context 'in the current navigation' do + before do + user.update!(use_new_navigation: false) + end + + context 'for a new top-level project' do + it_behaves_like 'a "Your work" page with sidebar and breadcrumbs', :new_project_path, :projects + end + + context 'for a new group project' do + it 'shows the group sidebar of the parent group' do + visit new_project_path(namespace_id: parent_group.id) + expect(page).to have_selector(".nav-sidebar[aria-label=\"Group navigation\"] .context-header[title=\"#{parent_group.name}\"]") + end + end end - context 'as an admin', :do_not_mock_admin_mode_setting do - let(:user) { create(:admin) } - let(:oauth_config_instructions) { 'To enable importing projects from GitLab.com, as administrator you need to configure OAuth integration' } + context 'in the new navigation' do + before do + parent_group.add_owner(user) + user.update!(use_new_navigation: true) + sign_in(user) + end - it_behaves_like 'has instructions to enable OAuth' + context 'for a new top-level project' do + it 'shows the "Your work" navigation' do + visit new_project_path + expect(page).to have_selector(".super-sidebar .context-switcher-toggle", text: "Your work") + end + end + + context 'for a new group project' do + it 'shows the group sidebar of the parent group' do + visit new_project_path(namespace_id: parent_group.id) + expect(page).to have_selector(".super-sidebar .context-switcher-toggle", text: parent_group.name) + end + end end end end diff --git a/spec/features/projects/packages_spec.rb b/spec/features/projects/packages_spec.rb index 31ff455d0df..5d3ebd8bec6 100644 --- a/spec/features/projects/packages_spec.rb +++ b/spec/features/projects/packages_spec.rb @@ -31,7 +31,7 @@ RSpec.describe 'Packages', feature_category: :projects do end context 'when there are packages' do - let_it_be(:npm_package) { create(:npm_package, project: project, name: 'zzz', created_at: 1.day.ago, version: '1.0.0') } + let_it_be(:npm_package) { create(:npm_package, :with_build, project: project, name: 'zzz', created_at: 1.day.ago, version: '1.0.0') } let_it_be(:maven_package) { create(:maven_package, project: project, name: 'aaa', created_at: 2.days.ago, version: '2.0.0') } let_it_be(:packages) { [npm_package, maven_package] } @@ -40,6 +40,8 @@ RSpec.describe 'Packages', feature_category: :projects do it_behaves_like 'packages list' + it_behaves_like 'pipelines on packages list' + it_behaves_like 'package details link' context 'deleting a package' do diff --git a/spec/features/projects/pages/user_edits_lets_encrypt_settings_spec.rb b/spec/features/projects/pages/user_edits_lets_encrypt_settings_spec.rb index a7da59200e9..16e64ade665 100644 --- a/spec/features/projects/pages/user_edits_lets_encrypt_settings_spec.rb +++ b/spec/features/projects/pages/user_edits_lets_encrypt_settings_spec.rb @@ -48,13 +48,13 @@ RSpec.describe "Pages with Let's Encrypt", :https_pages_enabled, feature_categor expect(domain.auto_ssl_enabled).to eq false expect(find("#pages_domain_auto_ssl_enabled", visible: false).value).to eq 'false' - expect(page).to have_selector '.card-header', text: 'Certificate' + expect(page).to have_selector '.gl-card-header', text: 'Certificate' expect(page).to have_text domain.subject find('.js-auto-ssl-toggle-container .js-project-feature-toggle button').click expect(find("#pages_domain_auto_ssl_enabled", visible: false).value).to eq 'true' - expect(page).not_to have_selector '.card-header', text: 'Certificate' + expect(page).not_to have_selector '.gl-card-header', text: 'Certificate' expect(page).not_to have_text domain.subject click_on 'Save Changes' @@ -108,7 +108,7 @@ RSpec.describe "Pages with Let's Encrypt", :https_pages_enabled, feature_categor it 'user do not see private key' do visit project_pages_domain_path(project, domain) - expect(page).not_to have_selector '.card-header', text: 'Certificate' + expect(page).not_to have_selector '.gl-card-header', text: 'Certificate' expect(page).not_to have_text domain.subject end end @@ -131,16 +131,16 @@ RSpec.describe "Pages with Let's Encrypt", :https_pages_enabled, feature_categor it 'user sees certificate subject' do visit project_pages_domain_path(project, domain) - expect(page).to have_selector '.card-header', text: 'Certificate' + expect(page).to have_selector '.gl-card-header', text: 'Certificate' expect(page).to have_text domain.subject end it 'user can delete the certificate', :js do visit project_pages_domain_path(project, domain) - expect(page).to have_selector '.card-header', text: 'Certificate' + expect(page).to have_selector '.gl-card-header', text: 'Certificate' expect(page).to have_text domain.subject - within('.card') { click_on 'Remove' } + within('.gl-card') { click_on 'Remove' } accept_gl_confirm(button_text: 'Remove certificate') expect(page).to have_field 'Certificate (PEM)', with: '' expect(page).to have_field 'Key (PEM)', with: '' diff --git a/spec/features/projects/pipeline_schedules_spec.rb b/spec/features/projects/pipeline_schedules_spec.rb index 3ede76d3360..81e003d7d1c 100644 --- a/spec/features/projects/pipeline_schedules_spec.rb +++ b/spec/features/projects/pipeline_schedules_spec.rb @@ -7,295 +7,394 @@ RSpec.describe 'Pipeline Schedules', :js, feature_category: :projects do let!(:project) { create(:project, :repository) } let!(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly, project: project) } - let!(:pipeline) { create(:ci_pipeline, pipeline_schedule: pipeline_schedule) } + let!(:pipeline) { create(:ci_pipeline, pipeline_schedule: pipeline_schedule, project: project) } let(:scope) { nil } let!(:user) { create(:user) } + let!(:maintainer) { create(:user) } - before do - stub_feature_flags(pipeline_schedules_vue: false) - end - - context 'logged in as the pipeline schedule owner' do + context 'with pipeline_schedules_vue feature flag turned off' do before do - project.add_developer(user) - pipeline_schedule.update!(owner: user) - gitlab_sign_in(user) + stub_feature_flags(pipeline_schedules_vue: false) end - describe 'GET /projects/pipeline_schedules' do + context 'logged in as the pipeline schedule owner' do before do - visit_pipelines_schedules + project.add_developer(user) + pipeline_schedule.update!(owner: user) + gitlab_sign_in(user) end - it 'edits the pipeline' do - page.within('.pipeline-schedule-table-row') do - click_link 'Edit' + describe 'GET /projects/pipeline_schedules' do + before do + visit_pipelines_schedules end - expect(page).to have_content('Edit Pipeline Schedule') - end - end + it 'edits the pipeline' do + page.within('.pipeline-schedule-table-row') do + click_link 'Edit' + end - describe 'PATCH /projects/pipelines_schedules/:id/edit' do - before do - edit_pipeline_schedule + expect(page).to have_content('Edit Pipeline Schedule') + end end - it 'displays existing properties' do - description = find_field('schedule_description').value - expect(description).to eq('pipeline schedule') - expect(page).to have_button('master') - expect(page).to have_button('Select timezone') - end + describe 'PATCH /projects/pipelines_schedules/:id/edit' do + before do + edit_pipeline_schedule + end - it 'edits the scheduled pipeline' do - fill_in 'schedule_description', with: 'my brand new description' + it 'displays existing properties' do + description = find_field('schedule_description').value + expect(description).to eq('pipeline schedule') + expect(page).to have_button('master') + expect(page).to have_button('Select timezone') + end - save_pipeline_schedule + it 'edits the scheduled pipeline' do + fill_in 'schedule_description', with: 'my brand new description' - expect(page).to have_content('my brand new description') - end + save_pipeline_schedule - context 'when ref is nil' do - before do - pipeline_schedule.update_attribute(:ref, nil) - edit_pipeline_schedule + expect(page).to have_content('my brand new description') end - it 'shows the pipeline schedule with default ref' do - page.within('[data-testid="schedule-target-ref"]') do - expect(first('.gl-button-text').text).to eq('master') + context 'when ref is nil' do + before do + pipeline_schedule.update_attribute(:ref, nil) + edit_pipeline_schedule end - end - end - context 'when ref is empty' do - before do - pipeline_schedule.update_attribute(:ref, '') - edit_pipeline_schedule + it 'shows the pipeline schedule with default ref' do + page.within('[data-testid="schedule-target-ref"]') do + expect(first('.gl-button-text').text).to eq('master') + end + end end - it 'shows the pipeline schedule with default ref' do - page.within('[data-testid="schedule-target-ref"]') do - expect(first('.gl-button-text').text).to eq('master') + context 'when ref is empty' do + before do + pipeline_schedule.update_attribute(:ref, '') + edit_pipeline_schedule + end + + it 'shows the pipeline schedule with default ref' do + page.within('[data-testid="schedule-target-ref"]') do + expect(first('.gl-button-text').text).to eq('master') + end end end end end - end - context 'logged in as a project maintainer' do - before do - project.add_maintainer(user) - gitlab_sign_in(user) - end - - describe 'GET /projects/pipeline_schedules' do + context 'logged in as a project maintainer' do before do - visit_pipelines_schedules + project.add_maintainer(user) + gitlab_sign_in(user) end - describe 'The view' do - it 'displays the required information description' do - page.within('.pipeline-schedule-table-row') do - expect(page).to have_content('pipeline schedule') - expect(find("[data-testid='next-run-cell'] time")['title']) - .to include(pipeline_schedule.real_next_run.strftime('%b %-d, %Y')) - expect(page).to have_link('master') - expect(page).to have_link("##{pipeline.id}") - end + describe 'GET /projects/pipeline_schedules' do + before do + visit_pipelines_schedules end - it 'creates a new scheduled pipeline' do - click_link 'New schedule' + describe 'The view' do + it 'displays the required information description' do + page.within('.pipeline-schedule-table-row') do + expect(page).to have_content('pipeline schedule') + expect(find("[data-testid='next-run-cell'] time")['title']) + .to include(pipeline_schedule.real_next_run.strftime('%b %-d, %Y')) + expect(page).to have_link('master') + expect(page).to have_link("##{pipeline.id}") + end + end - expect(page).to have_content('Schedule a new pipeline') - end + it 'creates a new scheduled pipeline' do + click_link 'New schedule' + + expect(page).to have_content('Schedule a new pipeline') + end - it 'changes ownership of the pipeline' do - click_button 'Take ownership' + it 'changes ownership of the pipeline' do + click_button 'Take ownership' - page.within('#pipeline-take-ownership-modal') do - click_link 'Take ownership' + page.within('#pipeline-take-ownership-modal') do + click_link 'Take ownership' + end + + page.within('.pipeline-schedule-table-row') do + expect(page).not_to have_content('No owner') + expect(page).to have_link('Sidney Jones') + end end - page.within('.pipeline-schedule-table-row') do - expect(page).not_to have_content('No owner') - expect(page).to have_link('Sidney Jones') + it 'deletes the pipeline' do + click_link 'Delete' + + accept_gl_confirm(button_text: 'Delete pipeline schedule') + + expect(page).not_to have_css(".pipeline-schedule-table-row") end end - it 'deletes the pipeline' do - click_link 'Delete' + context 'when ref is nil' do + before do + pipeline_schedule.update_attribute(:ref, nil) + visit_pipelines_schedules + end + + it 'shows a list of the pipeline schedules with empty ref column' do + expect(first('.branch-name-cell').text).to eq('') + end + end - accept_gl_confirm(button_text: 'Delete pipeline schedule') + context 'when ref is empty' do + before do + pipeline_schedule.update_attribute(:ref, '') + visit_pipelines_schedules + end - expect(page).not_to have_css(".pipeline-schedule-table-row") + it 'shows a list of the pipeline schedules with empty ref column' do + expect(first('.branch-name-cell').text).to eq('') + end end end - context 'when ref is nil' do + describe 'POST /projects/pipeline_schedules/new' do before do - pipeline_schedule.update_attribute(:ref, nil) - visit_pipelines_schedules + visit_new_pipeline_schedule + end + + it 'sets defaults for timezone and target branch' do + expect(page).to have_button('master') + expect(page).to have_button('Select timezone') end - it 'shows a list of the pipeline schedules with empty ref column' do - expect(first('.branch-name-cell').text).to eq('') + it 'creates a new scheduled pipeline' do + fill_in_schedule_form + save_pipeline_schedule + + expect(page).to have_content('my fancy description') + end + + it 'prevents an invalid form from being submitted' do + save_pipeline_schedule + + expect(page).to have_content('This field is required') end end - context 'when ref is empty' do + context 'when user creates a new pipeline schedule with variables' do before do - pipeline_schedule.update_attribute(:ref, '') visit_pipelines_schedules + click_link 'New schedule' + fill_in_schedule_form + all('[name="schedule[variables_attributes][][key]"]')[0].set('AAA') + all('[name="schedule[variables_attributes][][secret_value]"]')[0].set('AAA123') + all('[name="schedule[variables_attributes][][key]"]')[1].set('BBB') + all('[name="schedule[variables_attributes][][secret_value]"]')[1].set('BBB123') + save_pipeline_schedule end - it 'shows a list of the pipeline schedules with empty ref column' do - expect(first('.branch-name-cell').text).to eq('') + it 'user sees the new variable in edit window', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/397040' do + find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click + page.within('.ci-variable-list') do + expect(find(".ci-variable-row:nth-child(1) .js-ci-variable-input-key").value).to eq('AAA') + expect(find(".ci-variable-row:nth-child(1) .js-ci-variable-input-value", visible: false).value).to eq('AAA123') + expect(find(".ci-variable-row:nth-child(2) .js-ci-variable-input-key").value).to eq('BBB') + expect(find(".ci-variable-row:nth-child(2) .js-ci-variable-input-value", visible: false).value).to eq('BBB123') + end end end - end - describe 'POST /projects/pipeline_schedules/new' do - before do - visit_new_pipeline_schedule - end + context 'when user edits a variable of a pipeline schedule' do + before do + create(:ci_pipeline_schedule, project: project, owner: user).tap do |pipeline_schedule| + create(:ci_pipeline_schedule_variable, key: 'AAA', value: 'AAA123', pipeline_schedule: pipeline_schedule) + end - it 'sets defaults for timezone and target branch' do - expect(page).to have_button('master') - expect(page).to have_button('Select timezone') + visit_pipelines_schedules + find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click + find('.js-ci-variable-list-section .js-secret-value-reveal-button').click + first('.js-ci-variable-input-key').set('foo') + first('.js-ci-variable-input-value').set('bar') + click_button 'Save pipeline schedule' + end + + it 'user sees the updated variable in edit window' do + find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click + page.within('.ci-variable-list') do + expect(find(".ci-variable-row:nth-child(1) .js-ci-variable-input-key").value).to eq('foo') + expect(find(".ci-variable-row:nth-child(1) .js-ci-variable-input-value", visible: false).value).to eq('bar') + end + end end - it 'creates a new scheduled pipeline' do - fill_in_schedule_form - save_pipeline_schedule + context 'when user removes a variable of a pipeline schedule' do + before do + create(:ci_pipeline_schedule, project: project, owner: user).tap do |pipeline_schedule| + create(:ci_pipeline_schedule_variable, key: 'AAA', value: 'AAA123', pipeline_schedule: pipeline_schedule) + end - expect(page).to have_content('my fancy description') + visit_pipelines_schedules + find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click + find('.ci-variable-list .ci-variable-row-remove-button').click + click_button 'Save pipeline schedule' + end + + it 'user does not see the removed variable in edit window' do + find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click + page.within('.ci-variable-list') do + expect(find(".ci-variable-row:nth-child(1) .js-ci-variable-input-key").value).to eq('') + expect(find(".ci-variable-row:nth-child(1) .js-ci-variable-input-value", visible: false).value).to eq('') + end + end end - it 'prevents an invalid form from being submitted' do - save_pipeline_schedule + context 'when active is true and next_run_at is NULL' do + before do + create(:ci_pipeline_schedule, project: project, owner: user).tap do |pipeline_schedule| + pipeline_schedule.update_attribute(:next_run_at, nil) # Consequently next_run_at will be nil + end + end + + it 'user edit and recover the problematic pipeline schedule' do + visit_pipelines_schedules + find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click + fill_in 'schedule_cron', with: '* 1 2 3 4' + click_button 'Save pipeline schedule' - expect(page).to have_content('This field is required') + page.within('.pipeline-schedule-table-row:nth-child(1)') do + expect(page).to have_css("[data-testid='next-run-cell'] time") + end + end end end - context 'when user creates a new pipeline schedule with variables' do + context 'logged in as non-member' do before do - visit_pipelines_schedules - click_link 'New schedule' - fill_in_schedule_form - all('[name="schedule[variables_attributes][][key]"]')[0].set('AAA') - all('[name="schedule[variables_attributes][][secret_value]"]')[0].set('AAA123') - all('[name="schedule[variables_attributes][][key]"]')[1].set('BBB') - all('[name="schedule[variables_attributes][][secret_value]"]')[1].set('BBB123') - save_pipeline_schedule + gitlab_sign_in(user) end - it 'user sees the new variable in edit window' do - find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click - page.within('.ci-variable-list') do - expect(find(".ci-variable-row:nth-child(1) .js-ci-variable-input-key").value).to eq('AAA') - expect(find(".ci-variable-row:nth-child(1) .js-ci-variable-input-value", visible: false).value).to eq('AAA123') - expect(find(".ci-variable-row:nth-child(2) .js-ci-variable-input-key").value).to eq('BBB') - expect(find(".ci-variable-row:nth-child(2) .js-ci-variable-input-value", visible: false).value).to eq('BBB123') + describe 'GET /projects/pipeline_schedules' do + before do + visit_pipelines_schedules + end + + describe 'The view' do + it 'does not show create schedule button' do + expect(page).not_to have_link('New schedule') + end end end end - context 'when user edits a variable of a pipeline schedule' do - before do - create(:ci_pipeline_schedule, project: project, owner: user).tap do |pipeline_schedule| - create(:ci_pipeline_schedule_variable, key: 'AAA', value: 'AAA123', pipeline_schedule: pipeline_schedule) + context 'not logged in' do + describe 'GET /projects/pipeline_schedules' do + before do + visit_pipelines_schedules end - visit_pipelines_schedules - find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click - - find('.js-ci-variable-list-section .js-secret-value-reveal-button').click - first('.js-ci-variable-input-key').set('foo') - first('.js-ci-variable-input-value').set('bar') - click_button 'Save pipeline schedule' - end - - it 'user sees the updated variable in edit window' do - find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click - page.within('.ci-variable-list') do - expect(find(".ci-variable-row:nth-child(1) .js-ci-variable-input-key").value).to eq('foo') - expect(find(".ci-variable-row:nth-child(1) .js-ci-variable-input-value", visible: false).value).to eq('bar') + describe 'The view' do + it 'does not show create schedule button' do + expect(page).not_to have_link('New schedule') + end end end end + end - context 'when user removes a variable of a pipeline schedule' do + context 'with pipeline_schedules_vue feature flag turned on' do + context 'logged in as a project maintainer' do before do - create(:ci_pipeline_schedule, project: project, owner: user).tap do |pipeline_schedule| - create(:ci_pipeline_schedule_variable, key: 'AAA', value: 'AAA123', pipeline_schedule: pipeline_schedule) - end - - visit_pipelines_schedules - find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click - find('.ci-variable-list .ci-variable-row-remove-button').click - click_button 'Save pipeline schedule' + project.add_maintainer(maintainer) + pipeline_schedule.update!(owner: user) + gitlab_sign_in(maintainer) end - it 'user does not see the removed variable in edit window' do - find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click - page.within('.ci-variable-list') do - expect(find(".ci-variable-row:nth-child(1) .js-ci-variable-input-key").value).to eq('') - expect(find(".ci-variable-row:nth-child(1) .js-ci-variable-input-value", visible: false).value).to eq('') - end - end - end + describe 'GET /projects/pipeline_schedules' do + before do + visit_pipelines_schedules - context 'when active is true and next_run_at is NULL' do - before do - create(:ci_pipeline_schedule, project: project, owner: user).tap do |pipeline_schedule| - pipeline_schedule.update_attribute(:next_run_at, nil) # Consequently next_run_at will be nil + wait_for_requests end - end - it 'user edit and recover the problematic pipeline schedule' do - visit_pipelines_schedules - find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click - fill_in 'schedule_cron', with: '* 1 2 3 4' - click_button 'Save pipeline schedule' + describe 'The view' do + it 'displays the required information description' do + page.within('[data-testid="pipeline-schedule-table-row"]') do + expect(page).to have_content('pipeline schedule') + expect(find("[data-testid='next-run-cell'] time")['title']) + .to include(pipeline_schedule.real_next_run.strftime('%b %-d, %Y')) + expect(page).to have_link('master') + expect(find("[data-testid='last-pipeline-status'] a")['href']).to include(pipeline.id.to_s) + end + end + + it 'changes ownership of the pipeline' do + click_button 'Take ownership' + + page.within('#pipeline-take-ownership-modal') do + click_button 'Take ownership' + + wait_for_requests + end + + page.within('[data-testid="pipeline-schedule-table-row"]') do + expect(page).not_to have_content('No owner') + expect(page).to have_link('Sidney Jones') + end + end - page.within('.pipeline-schedule-table-row:nth-child(1)') do - expect(page).to have_css("[data-testid='next-run-cell'] time") + it 'runs the pipeline' do + click_button 'Run pipeline schedule' + + wait_for_requests + + expect(page).to have_content("Successfully scheduled a pipeline to run. Go to the Pipelines page for details.") + end + + it 'deletes the pipeline' do + click_button 'Delete pipeline schedule' + + accept_gl_confirm(button_text: 'Delete pipeline schedule') + + expect(page).not_to have_css('[data-testid="pipeline-schedule-table-row"]') + end end end end - end - - context 'logged in as non-member' do - before do - gitlab_sign_in(user) - end - describe 'GET /projects/pipeline_schedules' do + context 'logged in as non-member' do before do - visit_pipelines_schedules + gitlab_sign_in(user) end - describe 'The view' do - it 'does not show create schedule button' do - expect(page).not_to have_link('New schedule') + describe 'GET /projects/pipeline_schedules' do + before do + visit_pipelines_schedules + + wait_for_requests + end + + describe 'The view' do + it 'does not show create schedule button' do + expect(page).not_to have_link('New schedule') + end end end end - end - context 'not logged in' do - describe 'GET /projects/pipeline_schedules' do - before do - visit_pipelines_schedules - end + context 'not logged in' do + describe 'GET /projects/pipeline_schedules' do + before do + visit_pipelines_schedules + + wait_for_requests + end - describe 'The view' do - it 'does not show create schedule button' do - expect(page).not_to have_link('New schedule') + describe 'The view' do + it 'does not show create schedule button' do + expect(page).not_to have_link('New schedule') + end end end end @@ -332,5 +431,6 @@ RSpec.describe 'Pipeline Schedules', :js, feature_category: :projects do select_timezone select_target_branch + find('body').click # close dropdown end end diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index 343c7f53022..7167581eedf 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -80,7 +80,7 @@ RSpec.describe 'Pipeline', :js, feature_category: :projects do expect(page).to have_content('test') expect(page).to have_content('deploy') expect(page).to have_content('Retry') - expect(page).to have_content('Cancel running') + expect(page).to have_content('Cancel pipeline') end it 'shows link to the pipeline ref' do @@ -113,6 +113,39 @@ RSpec.describe 'Pipeline', :js, feature_category: :projects do end end + describe 'pipeline stats text' do + let(:finished_pipeline) do + create(:ci_pipeline, :success, project: project, + ref: 'master', sha: project.commit.id, user: user) + end + + before do + finished_pipeline.update!(started_at: "2023-01-01 01:01:05", created_at: "2023-01-01 01:01:01", + finished_at: "2023-01-01 01:01:10", duration: 9) + end + + context 'pipeline has finished' do + it 'shows pipeline stats with flag on' do + visit project_pipeline_path(project, finished_pipeline) + + within '.pipeline-info' do + expect(page).to have_content("in #{finished_pipeline.duration} seconds") + expect(page).to have_content("and was queued for #{finished_pipeline.queued_duration} seconds") + end + end + end + + context 'pipeline has not finished' do + it 'does not show pipeline stats' do + visit_pipeline + + within '.pipeline-info' do + expect(page).not_to have_selector('[data-testid="pipeline-stats-text"]') + end + end + end + end + describe 'related merge requests' do context 'when there are no related merge requests' do it 'shows a "no related merge requests" message' do @@ -592,11 +625,11 @@ RSpec.describe 'Pipeline', :js, feature_category: :projects do context 'when canceling' do before do - click_on 'Cancel running' + click_on 'Cancel pipeline' end - it 'does not show a "Cancel running" button', :sidekiq_might_not_need_inline do - expect(page).not_to have_content('Cancel running') + it 'does not show a "Cancel pipeline" button', :sidekiq_might_not_need_inline do + expect(page).not_to have_content('Cancel pipeline') end end end @@ -814,10 +847,10 @@ RSpec.describe 'Pipeline', :js, feature_category: :projects do expect(page).to have_content('test') expect(page).to have_content('deploy') expect(page).to have_content('Retry') - expect(page).to have_content('Cancel running') + expect(page).to have_content('Cancel pipeline') end - it 'does not link to job' do + it 'does not link to job', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/408215' do expect(page).not_to have_selector('.js-pipeline-graph-job-link') end end @@ -880,7 +913,7 @@ RSpec.describe 'Pipeline', :js, feature_category: :projects do expect(page).to have_selector('table.ci-table > tbody > tr > td', text: 'blocked user schedule') end - it 'does not create a new Pipeline' do + it 'does not create a new Pipeline', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/408215' do visit project_pipelines_path(project) expect(page).not_to have_selector('.ci-table') @@ -1060,7 +1093,7 @@ RSpec.describe 'Pipeline', :js, feature_category: :projects do expect(page).to have_content(build_running.id) expect(page).to have_content(build_external.id) expect(page).to have_content('Retry') - expect(page).to have_content('Cancel running') + expect(page).to have_content('Cancel pipeline') expect(page).to have_button('Play') end @@ -1095,11 +1128,11 @@ RSpec.describe 'Pipeline', :js, feature_category: :projects do context 'when canceling' do before do - click_on 'Cancel running' + click_on 'Cancel pipeline' end - it 'does not show a "Cancel running" button', :sidekiq_might_not_need_inline do - expect(page).not_to have_content('Cancel running') + it 'does not show a "Cancel pipeline" button', :sidekiq_might_not_need_inline do + expect(page).not_to have_content('Cancel pipeline') end end end diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index b5f640f1cca..d3ccde3d2e1 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -288,16 +288,21 @@ RSpec.describe 'Pipelines', :js, feature_category: :projects do it 'has link to the manual action' do find('[data-testid="pipelines-manual-actions-dropdown"]').click + wait_for_requests + expect(page).to have_button('manual build') end context 'when manual action was played' do before do find('[data-testid="pipelines-manual-actions-dropdown"]').click + + wait_for_requests + click_button('manual build') end - it 'enqueues manual action job' do + it 'enqueues manual action job', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/409984' do expect(page).to have_selector('[data-testid="pipelines-manual-actions-dropdown"] .gl-dropdown-toggle:disabled') end end @@ -308,7 +313,8 @@ RSpec.describe 'Pipelines', :js, feature_category: :projects do create(:ci_build, :scheduled, pipeline: pipeline, name: 'delayed job 1', - stage: 'test') + stage: 'test', + scheduled_at: 2.hours.since + 2.minutes) end before do @@ -322,9 +328,12 @@ RSpec.describe 'Pipelines', :js, feature_category: :projects do it "has link to the delayed job's action" do find('[data-testid="pipelines-manual-actions-dropdown"]').click - time_diff = [0, delayed_job.scheduled_at - Time.zone.now].max + wait_for_requests + expect(page).to have_button('delayed job 1') - expect(page).to have_content(Time.at(time_diff).utc.strftime("%H:%M:%S")) + + time_diff = [0, delayed_job.scheduled_at - Time.zone.now].max + expect(page).to have_content(Time.at(time_diff).utc.strftime("%H:%M")) end context 'when delayed job is expired already' do @@ -338,6 +347,8 @@ RSpec.describe 'Pipelines', :js, feature_category: :projects do it "shows 00:00:00 as the remaining time" do find('[data-testid="pipelines-manual-actions-dropdown"]').click + wait_for_requests + expect(page).to have_content("00:00:00") end end @@ -358,7 +369,7 @@ RSpec.describe 'Pipelines', :js, feature_category: :projects do wait_for_requests end - it 'enqueues the delayed job', :js do + it 'enqueues the delayed job', :js, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/410129' do expect(delayed_job.reload).to be_pending end end @@ -675,7 +686,7 @@ RSpec.describe 'Pipelines', :js, feature_category: :projects do click_button project.default_branch wait_for_requests - find('.gl-new-dropdown-item', text: 'master').click + find('.gl-new-dropdown-item', text: '2-mb-file').click wait_for_requests end @@ -686,7 +697,7 @@ RSpec.describe 'Pipelines', :js, feature_category: :projects do it 'creates a new pipeline' do expect do - click_on 'Run pipeline' + find('[data-testid="run_pipeline_button"]', text: 'Run pipeline').click wait_for_requests end .to change { Ci::Pipeline.count }.by(1) @@ -695,14 +706,14 @@ RSpec.describe 'Pipelines', :js, feature_category: :projects do end context 'when variables are specified' do - it 'creates a new pipeline with variables', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/375552' do + it 'creates a new pipeline with variables' do page.within(find("[data-testid='ci-variable-row']")) do find("[data-testid='pipeline-form-ci-variable-key']").set('key_name') find("[data-testid='pipeline-form-ci-variable-value']").set('value') end expect do - click_on 'Run pipeline' + find('[data-testid="run_pipeline_button"]', text: 'Run pipeline').click wait_for_requests end .to change { Ci::Pipeline.count }.by(1) @@ -715,17 +726,17 @@ RSpec.describe 'Pipelines', :js, feature_category: :projects do context 'without gitlab-ci.yml' do before do - click_on 'Run pipeline' + find('[data-testid="run_pipeline_button"]', text: 'Run pipeline').click wait_for_requests end it { expect(page).to have_content('Missing CI config file') } - it 'creates a pipeline after first request failed and a valid gitlab-ci.yml file is available when trying again', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/375552' do + it 'creates a pipeline after first request failed and a valid gitlab-ci.yml file is available when trying again' do stub_ci_pipeline_to_return_yaml_file expect do - click_on 'Run pipeline' + find('[data-testid="run_pipeline_button"]', text: 'Run pipeline').click wait_for_requests end .to change { Ci::Pipeline.count }.by(1) @@ -787,9 +798,9 @@ RSpec.describe 'Pipelines', :js, feature_category: :projects do describe 'find pipelines' do it 'shows filtered pipelines', :js do click_button project.default_branch - send_keys('fix') + send_keys('2-mb-file') - expect_listbox_item('fix') + expect_listbox_item('2-mb-file') end end end diff --git a/spec/features/projects/releases/user_creates_release_spec.rb b/spec/features/projects/releases/user_creates_release_spec.rb index f678d77b002..678c8df666f 100644 --- a/spec/features/projects/releases/user_creates_release_spec.rb +++ b/spec/features/projects/releases/user_creates_release_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'User creates release', :js, feature_category: :continuous_delivery do - include Spec::Support::Helpers::Features::ReleasesHelpers + include Features::ReleasesHelpers let_it_be(:project) { create(:project, :repository) } let_it_be(:milestone_1) { create(:milestone, project: project, title: '1.1') } @@ -25,17 +25,19 @@ RSpec.describe 'User creates release', :js, feature_category: :continuous_delive it 'renders the breadcrumbs', :aggregate_failures do within('.breadcrumbs') do - expect(page).to have_content("#{project.creator.name} #{project.name} New Release") + expect(page).to have_content("#{project.creator.name} #{project.name} Releases New") expect(page).to have_link(project.creator.name, href: user_path(project.creator)) expect(page).to have_link(project.name, href: project_path(project)) - expect(page).to have_link('New Release', href: new_project_release_path(project)) + expect(page).to have_link('Releases', href: project_releases_path(project)) + expect(page).to have_link('New', href: new_project_release_path(project)) end end it 'defaults the "Create from" dropdown to the project\'s default branch' do select_new_tag_name(tag_name) + expect(page).to have_button(project.default_branch) expect(page.find('[data-testid="create-from-field"] .ref-selector button')).to have_content(project.default_branch) end @@ -107,7 +109,7 @@ RSpec.describe 'User creates release', :js, feature_category: :continuous_delive fill_release_notes('**some** _markdown_ [content](https://example.com)') - click_on 'Preview' + click_button("Preview") wait_for_all_requests end @@ -123,13 +125,12 @@ RSpec.describe 'User creates release', :js, feature_category: :continuous_delive let(:new_page_url) { new_project_release_path(project, tag_name: 'v1.1.0') } it 'creates release with preselected tag' do - page.within '[data-testid="tag-name-field"]' do - expect(page).to have_text('v1.1.0') - end + expect(page).to have_button 'v1.1.0' + + open_tag_popover 'v1.1.0' expect(page).not_to have_selector('[data-testid="create-from-field"]') - fill_release_title("test release") click_button('Create release') wait_for_all_requests diff --git a/spec/features/projects/settings/access_tokens_spec.rb b/spec/features/projects/settings/access_tokens_spec.rb index 12e14f5193f..a38c10c6bab 100644 --- a/spec/features/projects/settings/access_tokens_spec.rb +++ b/spec/features/projects/settings/access_tokens_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Project > Settings > Access Tokens', :js, feature_category: :credential_management do +RSpec.describe 'Project > Settings > Access Tokens', :js, feature_category: :user_management do include Spec::Support::Helpers::ModalHelpers let_it_be(:user) { create(:user) } diff --git a/spec/features/projects/settings/branch_rules_settings_spec.rb b/spec/features/projects/settings/branch_rules_settings_spec.rb index 71d9c559b77..59609fecd93 100644 --- a/spec/features/projects/settings/branch_rules_settings_spec.rb +++ b/spec/features/projects/settings/branch_rules_settings_spec.rb @@ -28,6 +28,17 @@ RSpec.describe 'Projects > Settings > Repository > Branch rules settings', featu let(:role) { :maintainer } context 'Branch rules', :js do + it 'renders breadcrumbs' do + request + + page.within '.breadcrumbs' do + expect(page).to have_link('Repository Settings', href: project_settings_repository_path(project)) + expect(page).to have_link('Branch rules', + href: project_settings_repository_path(project, anchor: 'branch-rules')) + expect(page).to have_link('Details', href: '#') + end + end + it 'renders branch rules page' do request diff --git a/spec/features/projects/settings/forked_project_settings_spec.rb b/spec/features/projects/settings/forked_project_settings_spec.rb index 28d5c080db9..6b646bcf7d3 100644 --- a/spec/features/projects/settings/forked_project_settings_spec.rb +++ b/spec/features/projects/settings/forked_project_settings_spec.rb @@ -22,7 +22,7 @@ RSpec.describe 'Projects > Settings > For a forked project', :js, feature_catego wait_for_requests - fill_in('confirm_name_input', with: forked_project.name) + fill_in('confirm_name_input', with: forked_project.path) click_button('Confirm') wait_for_requests diff --git a/spec/features/projects/settings/monitor_settings_spec.rb b/spec/features/projects/settings/monitor_settings_spec.rb index 4b553b57331..1367ffb0009 100644 --- a/spec/features/projects/settings/monitor_settings_spec.rb +++ b/spec/features/projects/settings/monitor_settings_spec.rb @@ -3,12 +3,15 @@ require 'spec_helper' RSpec.describe 'Projects > Settings > For a forked project', :js, feature_category: :projects do + include ListboxHelpers + let_it_be(:project) { create(:project, :repository, create_templates: :issue) } let(:user) { project.first_owner } before do sign_in(user) + stub_feature_flags(remove_monitor_metrics: false) end describe 'Sidebar > Monitor' do @@ -47,7 +50,7 @@ RSpec.describe 'Projects > Settings > For a forked project', :js, feature_catego check(create_issue) uncheck(send_email) click_on('No template selected') - click_on('bug') + select_listbox_item('bug') save_form click_settings_tab diff --git a/spec/features/projects/settings/registry_settings_cleanup_tags_spec.rb b/spec/features/projects/settings/registry_settings_cleanup_tags_spec.rb index d4c1fe4d43e..bdfe6a06dd1 100644 --- a/spec/features/projects/settings/registry_settings_cleanup_tags_spec.rb +++ b/spec/features/projects/settings/registry_settings_cleanup_tags_spec.rb @@ -32,10 +32,19 @@ feature_category: :projects do it 'shows available section' do subject - expect(find('.breadcrumbs')).to have_content('Clean up image tags') + expect(find('.breadcrumbs')).to have_content('Cleanup policies') section = find('[data-testid="container-expiration-policy-project-settings"]') - expect(section).to have_text 'Clean up image tags' + expect(section).to have_text 'Cleanup policies' + end + + it 'passes axe automated accessibility testing' do + subject + + wait_for_requests + + expect(page).to be_axe_clean.within('[data-testid="container-expiration-policy-project-settings"]') + .skipping :'link-in-text-block' end it 'saves cleanup policy submit the form' do diff --git a/spec/features/projects/settings/registry_settings_spec.rb b/spec/features/projects/settings/registry_settings_spec.rb index 072b5f7f3b0..68e9b0225ea 100644 --- a/spec/features/projects/settings/registry_settings_spec.rb +++ b/spec/features/projects/settings/registry_settings_spec.rb @@ -21,6 +21,15 @@ feature_category: :projects do end context 'as owner', :js do + it 'passes axe automated accessibility testing' do + subject + + wait_for_requests + + expect(page).to be_axe_clean.within('[data-testid="packages-and-registries-project-settings"]') + .skipping :'link-in-text-block' + end + it 'shows active tab on sidebar' do subject @@ -33,10 +42,10 @@ feature_category: :projects do subject settings_block = find('[data-testid="container-expiration-policy-project-settings"]') - expect(settings_block).to have_text 'Clean up image tags' + expect(settings_block).to have_text 'Cleanup policies' end - it 'contains link to clean up image tags page' do + it 'contains link to cleanup policies page' do subject expect(page).to have_link('Edit cleanup rules', href: cleanup_image_tags_project_settings_packages_and_registries_path(project)) diff --git a/spec/features/projects/settings/repository_settings_spec.rb b/spec/features/projects/settings/repository_settings_spec.rb index a0625c93b1a..08abade7d18 100644 --- a/spec/features/projects/settings/repository_settings_spec.rb +++ b/spec/features/projects/settings/repository_settings_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe 'Projects > Settings > Repository settings', feature_category: :projects do + include Features::MirroringHelpers + let(:project) { create(:project_empty_repo) } let(:user) { create(:user) } let(:role) { :developer } @@ -61,6 +63,10 @@ RSpec.describe 'Projects > Settings > Repository settings', feature_category: :p let(:new_ssh_key) { attributes_for(:key)[:key] } + around do |example| + travel_to Time.zone.local(2022, 3, 1, 1, 0, 0) { example.run } + end + it 'get list of keys' do project.deploy_keys << private_deploy_key project.deploy_keys << public_deploy_key @@ -83,6 +89,21 @@ RSpec.describe 'Projects > Settings > Repository settings', feature_category: :p expect(page).to have_content('Grant write permissions to this key') end + it 'add a new deploy key with expiration' do + one_month = Time.zone.local(2022, 4, 1, 1, 0, 0) + visit project_settings_repository_path(project) + + fill_in 'deploy_key_title', with: 'new_deploy_key_with_expiry' + fill_in 'deploy_key_key', with: new_ssh_key + fill_in 'deploy_key_expires_at', with: one_month.to_s + check 'deploy_key_deploy_keys_projects_attributes_0_can_push' + click_button 'Add key' + + expect(page).to have_content('new_deploy_key_with_expiry') + expect(page).to have_content('in 1 month') + expect(page).to have_content('Grant write permissions to this key') + end + it 'edit an existing deploy key' do project.deploy_keys << private_deploy_key visit project_settings_repository_path(project) @@ -152,10 +173,8 @@ RSpec.describe 'Projects > Settings > Repository settings', feature_category: :p end it 'creates a push mirror that mirrors all branches', :js do - expect(page).to have_css('.js-mirror-protected-hidden[value="0"]', visible: false) - - fill_in 'url', with: ssh_url - expect(page).to have_css(".js-mirror-url-hidden[value=\"#{ssh_url}\"]", visible: false) + wait_for_mirror_field_javascript('protected', '0') + fill_and_wait_for_mirror_url_javascript('url', ssh_url) select 'SSH public key', from: 'Authentication method' select_direction @@ -172,10 +191,8 @@ RSpec.describe 'Projects > Settings > Repository settings', feature_category: :p it 'creates a push mirror that only mirrors protected branches', :js do find('#only_protected_branches').click - expect(page).to have_css('.js-mirror-protected-hidden[value="1"]', visible: false) - - fill_in 'url', with: ssh_url - expect(page).to have_css(".js-mirror-url-hidden[value=\"#{ssh_url}\"]", visible: false) + wait_for_mirror_field_javascript('protected', '1') + fill_and_wait_for_mirror_url_javascript('url', ssh_url) select 'SSH public key', from: 'Authentication method' select_direction @@ -192,8 +209,7 @@ RSpec.describe 'Projects > Settings > Repository settings', feature_category: :p it 'creates a push mirror that keeps divergent refs', :js do select_direction - fill_in 'url', with: ssh_url - expect(page).to have_css(".js-mirror-url-hidden[value=\"#{ssh_url}\"]", visible: false) + fill_and_wait_for_mirror_url_javascript('url', ssh_url) fill_in 'Password', with: 'password' check 'Keep divergent refs' @@ -212,8 +228,7 @@ RSpec.describe 'Projects > Settings > Repository settings', feature_category: :p end it 'generates an SSH public key on submission', :js do - fill_in 'url', with: ssh_url - expect(page).to have_css(".js-mirror-url-hidden[value=\"#{ssh_url}\"]", visible: false) + fill_and_wait_for_mirror_url_javascript('url', ssh_url) select 'SSH public key', from: 'Authentication method' diff --git a/spec/features/projects/settings/service_desk_setting_spec.rb b/spec/features/projects/settings/service_desk_setting_spec.rb index 859c738731b..74139aa0d7f 100644 --- a/spec/features/projects/settings/service_desk_setting_spec.rb +++ b/spec/features/projects/settings/service_desk_setting_spec.rb @@ -12,8 +12,8 @@ RSpec.describe 'Service Desk Setting', :js, :clean_gitlab_redis_cache, feature_c sign_in(user) allow_any_instance_of(Project).to receive(:present).with(current_user: user).and_return(presenter) - allow(::Gitlab::IncomingEmail).to receive(:enabled?) { true } - allow(::Gitlab::IncomingEmail).to receive(:supports_wildcard?) { true } + allow(::Gitlab::Email::IncomingEmail).to receive(:enabled?) { true } + allow(::Gitlab::Email::IncomingEmail).to receive(:supports_wildcard?) { true } end it 'shows activation checkbox' do @@ -24,7 +24,7 @@ RSpec.describe 'Service Desk Setting', :js, :clean_gitlab_redis_cache, feature_c context 'when service_desk_email is disabled' do before do - allow(::Gitlab::ServiceDeskEmail).to receive(:enabled?).and_return(false) + allow(::Gitlab::Email::ServiceDeskEmail).to receive(:enabled?).and_return(false) visit edit_project_path(project) end @@ -43,8 +43,8 @@ RSpec.describe 'Service Desk Setting', :js, :clean_gitlab_redis_cache, feature_c context 'when service_desk_email is enabled' do before do - allow(::Gitlab::ServiceDeskEmail).to receive(:enabled?) { true } - allow(::Gitlab::ServiceDeskEmail).to receive(:address_for_key) { 'address-suffix@example.com' } + allow(::Gitlab::Email::ServiceDeskEmail).to receive(:enabled?) { true } + allow(::Gitlab::Email::ServiceDeskEmail).to receive(:address_for_key) { 'address-suffix@example.com' } visit edit_project_path(project) end diff --git a/spec/features/projects/settings/user_manages_project_members_spec.rb b/spec/features/projects/settings/user_manages_project_members_spec.rb index 159a83a261d..b7463537fb2 100644 --- a/spec/features/projects/settings/user_manages_project_members_spec.rb +++ b/spec/features/projects/settings/user_manages_project_members_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'Projects > Settings > User manages project members', feature_category: :projects do - include Spec::Support::Helpers::Features::MembersHelpers + include Features::MembersHelpers include Spec::Support::Helpers::ModalHelpers include ListboxHelpers diff --git a/spec/features/projects/settings/user_renames_a_project_spec.rb b/spec/features/projects/settings/user_renames_a_project_spec.rb index 2da6e760fbf..a6b72e7a297 100644 --- a/spec/features/projects/settings/user_renames_a_project_spec.rb +++ b/spec/features/projects/settings/user_renames_a_project_spec.rb @@ -37,7 +37,7 @@ RSpec.describe 'Projects > Settings > User renames a project', feature_category: it 'shows errors for invalid project path' do change_path(project, 'foo&bar') - expect(page).to have_field 'Path', with: 'gitlab' + expect(page).to have_field 'Path', with: project.path expect(page).to have_content "Path can contain only letters, digits, '_', '-' and '.'. Cannot start with '-', end in '.git' or end in '.atom'" end end @@ -67,7 +67,7 @@ RSpec.describe 'Projects > Settings > User renames a project', feature_category: end context 'when changing project path' do - let(:project) { create(:project, :repository, namespace: user.namespace, name: 'gitlabhq') } + let(:project) { create(:project, :repository, namespace: user.namespace, path: 'gitlabhq') } before(:context) do TestEnv.clean_test_path diff --git a/spec/features/projects/settings/webhooks_settings_spec.rb b/spec/features/projects/settings/webhooks_settings_spec.rb index 3b8b982b621..e527d0c9c74 100644 --- a/spec/features/projects/settings/webhooks_settings_spec.rb +++ b/spec/features/projects/settings/webhooks_settings_spec.rb @@ -83,7 +83,7 @@ RSpec.describe 'Projects > Settings > Webhook Settings', feature_category: :proj visit webhooks_path click_button 'Test' - click_button 'Push events' + click_link 'Push events' expect(page).to have_current_path(webhooks_path, ignore_query: true) end diff --git a/spec/features/projects/snippets/create_snippet_spec.rb b/spec/features/projects/snippets/create_snippet_spec.rb index 06e48bc82c0..a28416f3ca3 100644 --- a/spec/features/projects/snippets/create_snippet_spec.rb +++ b/spec/features/projects/snippets/create_snippet_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe 'Projects > Snippets > Create Snippet', :js, feature_category: :source_code_management do include DropzoneHelper - include Spec::Support::Helpers::Features::SnippetSpecHelpers + include Features::SnippetSpecHelpers let_it_be(:user) { create(:user) } let_it_be(:project) do diff --git a/spec/features/projects/snippets/user_updates_snippet_spec.rb b/spec/features/projects/snippets/user_updates_snippet_spec.rb index 014bf63c696..dda9a556d17 100644 --- a/spec/features/projects/snippets/user_updates_snippet_spec.rb +++ b/spec/features/projects/snippets/user_updates_snippet_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'Projects > Snippets > User updates a snippet', :js, feature_category: :source_code_management do - include Spec::Support::Helpers::Features::SnippetSpecHelpers + include Features::SnippetSpecHelpers let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project, namespace: user.namespace) } diff --git a/spec/features/projects/terraform_spec.rb b/spec/features/projects/terraform_spec.rb index 5e2f65165c2..b7500b0cfb7 100644 --- a/spec/features/projects/terraform_spec.rb +++ b/spec/features/projects/terraform_spec.rb @@ -33,7 +33,7 @@ RSpec.describe 'Terraform', :js, feature_category: :projects do end it 'displays a tab with states count' do - expect(page).to have_content("States #{project.terraform_states.size}") + expect(page).to have_content("Terraform states #{project.terraform_states.size}") end it 'displays a table with terraform states' do diff --git a/spec/features/projects/tree/create_directory_spec.rb b/spec/features/projects/tree/create_directory_spec.rb index 58f572bc021..8fae8f38025 100644 --- a/spec/features/projects/tree/create_directory_spec.rb +++ b/spec/features/projects/tree/create_directory_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'Multi-file editor new directory', :js, feature_category: :web_ide do - include WebIdeSpecHelpers + include Features::WebIdeSpecHelpers let(:user) { create(:user) } let(:project) { create(:project, :repository) } diff --git a/spec/features/projects/tree/create_file_spec.rb b/spec/features/projects/tree/create_file_spec.rb index 674aef8e6f4..2f8935b9ce3 100644 --- a/spec/features/projects/tree/create_file_spec.rb +++ b/spec/features/projects/tree/create_file_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'Multi-file editor new file', :js, feature_category: :web_ide do - include WebIdeSpecHelpers + include Features::WebIdeSpecHelpers let(:user) { create(:user) } let(:project) { create(:project, :repository) } diff --git a/spec/features/projects/tree/tree_show_spec.rb b/spec/features/projects/tree/tree_show_spec.rb index 52c6cb2192b..3becc48d450 100644 --- a/spec/features/projects/tree/tree_show_spec.rb +++ b/spec/features/projects/tree/tree_show_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'Projects tree', :js, feature_category: :web_ide do - include WebIdeSpecHelpers + include Features::WebIdeSpecHelpers include RepoHelpers include ListboxHelpers diff --git a/spec/features/projects/tree/upload_file_spec.rb b/spec/features/projects/tree/upload_file_spec.rb index 42fa88a0d3e..6ec57af2590 100644 --- a/spec/features/projects/tree/upload_file_spec.rb +++ b/spec/features/projects/tree/upload_file_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'Multi-file editor upload file', :js, feature_category: :web_ide do - include WebIdeSpecHelpers + include Features::WebIdeSpecHelpers let(:user) { create(:user) } let(:project) { create(:project, :repository) } diff --git a/spec/features/projects/user_changes_project_visibility_spec.rb b/spec/features/projects/user_changes_project_visibility_spec.rb index 5daa5b98b6e..64af25aea28 100644 --- a/spec/features/projects/user_changes_project_visibility_spec.rb +++ b/spec/features/projects/user_changes_project_visibility_spec.rb @@ -91,23 +91,4 @@ RSpec.describe 'User changes public project visibility', :js, feature_category: it_behaves_like 'does not require confirmation' end - - context 'with unlink_fork_network_upon_visibility_decrease = false' do - let(:project) { create(:project, :empty_repo, :public) } - - before do - stub_feature_flags(unlink_fork_network_upon_visibility_decrease: false) - - fork_project(project, project.first_owner) - - sign_in(project.first_owner) - - visit edit_project_path(project) - - # https://gitlab.com/gitlab-org/gitlab/-/issues/381259 - allow(Gitlab::QueryLimiting::Transaction).to receive(:threshold).and_return(110) - end - - it_behaves_like 'does not require confirmation' - end end diff --git a/spec/features/projects/user_sees_user_popover_spec.rb b/spec/features/projects/user_sees_user_popover_spec.rb index 5badcd99dff..9d8d06c514e 100644 --- a/spec/features/projects/user_sees_user_popover_spec.rb +++ b/spec/features/projects/user_sees_user_popover_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'User sees user popover', :js, feature_category: :projects do - include Spec::Support::Helpers::Features::NotesHelpers + include Features::NotesHelpers let_it_be(:user) { create(:user, pronouns: 'they/them') } let_it_be(:project) { create(:project, :repository, creator: user) } diff --git a/spec/features/projects/user_uses_shortcuts_spec.rb b/spec/features/projects/user_uses_shortcuts_spec.rb index 05d79ea3b1b..1d4ab242308 100644 --- a/spec/features/projects/user_uses_shortcuts_spec.rb +++ b/spec/features/projects/user_uses_shortcuts_spec.rb @@ -9,6 +9,7 @@ RSpec.describe 'User uses shortcuts', :js, feature_category: :projects do before do sign_in(user) + stub_feature_flags(remove_monitor_metrics: false) visit(project_path(project)) @@ -68,11 +69,11 @@ RSpec.describe 'User uses shortcuts', :js, feature_category: :projects do end context 'when navigating to the Project pages' do - it 'redirects to the project page' do + it 'redirects to the project overview page' do visit project_issues_path(project) find('body').native.send_key('g') - find('body').native.send_key('p') + find('body').native.send_key('o') expect(page).to have_active_navigation(project.name) end @@ -155,6 +156,14 @@ RSpec.describe 'User uses shortcuts', :js, feature_category: :projects do end context 'when navigating to the CI/CD pages' do + it 'redirects to the Pipelines page' do + find('body').native.send_key('g') + find('body').native.send_key('p') + + expect(page).to have_active_navigation('CI/CD') + expect(page).to have_active_sub_navigation('Pipelines') + end + it 'redirects to the Jobs page' do find('body').native.send_key('g') find('body').native.send_key('j') diff --git a/spec/features/projects/user_views_empty_project_spec.rb b/spec/features/projects/user_views_empty_project_spec.rb index e2b56e8ced6..e38cfc2273a 100644 --- a/spec/features/projects/user_views_empty_project_spec.rb +++ b/spec/features/projects/user_views_empty_project_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'User views an empty project', feature_category: :projects do - include Spec::Support::Helpers::Features::InviteMembersModalHelper + include Features::InviteMembersModalHelpers let_it_be(:project) { create(:project, :empty_repo) } let_it_be(:user) { create(:user) } diff --git a/spec/features/work_items/work_item_children_spec.rb b/spec/features/projects/work_items/work_item_children_spec.rb index f41fb86d13c..43a6b2771f6 100644 --- a/spec/features/work_items/work_item_children_spec.rb +++ b/spec/features/projects/work_items/work_item_children_spec.rb @@ -132,5 +132,48 @@ RSpec.describe 'Work item children', :js, feature_category: :team_planning do end end end + + context 'in work item metadata' do + let_it_be(:label) { create(:label, title: 'Label 1', project: project) } + let_it_be(:milestone) { create(:milestone, project: project, title: 'v1') } + let_it_be(:task) do + create( + :work_item, + :task, + project: project, + labels: [label], + assignees: [user], + milestone: milestone + ) + end + + before do + visit project_issue_path(project, issue) + + wait_for_requests + end + + it 'displays labels, milestone and assignee for work item children', :aggregate_failures do + page.within('[data-testid="work-item-links"]') do + click_button 'Add' + click_button 'Existing task' + + find('[data-testid="work-item-token-select-input"]').set(task.title) + wait_for_all_requests + click_button task.title + + click_button 'Add task' + + wait_for_all_requests + end + + page.within('[data-testid="links-child"]') do + expect(page).to have_content(task.title) + expect(page).to have_content(label.title) + expect(page).to have_link(user.name) + expect(page).to have_content(milestone.title) + end + end + end end end diff --git a/spec/features/projects/work_items/work_item_spec.rb b/spec/features/projects/work_items/work_item_spec.rb new file mode 100644 index 00000000000..b706a624fc5 --- /dev/null +++ b/spec/features/projects/work_items/work_item_spec.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Work item', :js, feature_category: :team_planning do + let_it_be_with_reload(:user) { create(:user) } + + let_it_be(:project) { create(:project, :public) } + let_it_be(:work_item) { create(:work_item, project: project) } + let_it_be(:milestone) { create(:milestone, project: project) } + let_it_be(:milestones) { create_list(:milestone, 25, project: project) } + let_it_be(:note) { create(:note, noteable: work_item, project: work_item.project) } + let(:work_items_path) { project_work_items_path(project, work_items_path: work_item.iid) } + + context 'for signed in user' do + before do + project.add_developer(user) + + sign_in(user) + + visit work_items_path + end + + it 'shows project issues link in breadcrumbs' do + within('[data-testid="breadcrumb-links"]') do + expect(page).to have_link('Issues', href: project_issues_path(project)) + end + end + + it 'uses IID path in breadcrumbs' do + within('[data-testid="breadcrumb-current-link"]') do + expect(page).to have_link("##{work_item.iid}", href: work_items_path) + end + end + + it_behaves_like 'work items title' + it_behaves_like 'work items status' + it_behaves_like 'work items assignees' + it_behaves_like 'work items labels' + it_behaves_like 'work items comments', :issue + it_behaves_like 'work items description' + it_behaves_like 'work items milestone' + it_behaves_like 'work items notifications' + it_behaves_like 'work items todos' + it_behaves_like 'work items award emoji' + end + + context 'for signed in owner' do + before do + project.add_owner(user) + + sign_in(user) + + visit work_items_path + end + + it_behaves_like 'work items invite members' + end + + context 'for guest users' do + before do + project.add_guest(user) + + sign_in(user) + + visit work_items_path + end + + it_behaves_like 'work items comment actions for guest users' + end + + context 'for user not signed in' do + before do + visit work_items_path + end + + it 'actions dropdown is not displayed' do + expect(page).not_to have_selector('[data-testid="work-item-actions-dropdown"]') + end + + it 'todos action is not displayed' do + expect(page).not_to have_selector('[data-testid="work-item-todos-action"]') + end + + it 'award button is disabled and add reaction is not displayed' do + within('[data-testid="work-item-award-list"]') do + expect(page).not_to have_selector('[data-testid="emoji-picker"]') + expect(page).to have_selector('[data-testid="award-button"].disabled') + end + end + end +end diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb index 04096b3e4f9..e4a64d391b0 100644 --- a/spec/features/protected_branches_spec.rb +++ b/spec/features/protected_branches_spec.rb @@ -96,6 +96,15 @@ RSpec.describe 'Protected Branches', :js, feature_category: :source_code_managem expect(ProtectedBranch.last.name).to eq('some->branch') end + it "shows success alert once protected branch is created" do + visit project_protected_branches_path(project) + set_defaults + set_protected_branch_name('some->branch') + click_on "Protect" + wait_for_requests + expect(page).to have_content(s_('ProtectedBranch|View protected branches as branch rules')) + end + it "displays the last commit on the matching branch if it exists" do commit = create(:commit, project: project) project.repository.add_branch(admin, 'some-branch', commit.id) diff --git a/spec/features/protected_tags_spec.rb b/spec/features/protected_tags_spec.rb index c2058a5c345..45315f53fd6 100644 --- a/spec/features/protected_tags_spec.rb +++ b/spec/features/protected_tags_spec.rb @@ -16,8 +16,8 @@ RSpec.describe 'Protected Tags', :js, :with_license, feature_category: :source_c it "allows creating explicit protected tags" do visit project_protected_tags_path(project) set_protected_tag_name('some-tag') - set_allowed_to('create') if Gitlab.ee? - click_on "Protect" + set_allowed_to('create') + click_on_protect within(".protected-tags-list") { expect(page).to have_content('some-tag') } expect(ProtectedTag.count).to eq(1) @@ -30,8 +30,8 @@ RSpec.describe 'Protected Tags', :js, :with_license, feature_category: :source_c visit project_protected_tags_path(project) set_protected_tag_name('some-tag') - set_allowed_to('create') if Gitlab.ee? - click_on "Protect" + set_allowed_to('create') + click_on_protect within(".protected-tags-list") { expect(page).to have_content(commit.id[0..7]) } end @@ -39,8 +39,8 @@ RSpec.describe 'Protected Tags', :js, :with_license, feature_category: :source_c it "displays an error message if the named tag does not exist" do visit project_protected_tags_path(project) set_protected_tag_name('some-tag') - set_allowed_to('create') if Gitlab.ee? - click_on "Protect" + set_allowed_to('create') + click_on_protect within(".protected-tags-list") { expect(page).to have_content('tag was removed') } end @@ -50,8 +50,8 @@ RSpec.describe 'Protected Tags', :js, :with_license, feature_category: :source_c it "allows creating protected tags with a wildcard" do visit project_protected_tags_path(project) set_protected_tag_name('*-stable') - set_allowed_to('create') if Gitlab.ee? - click_on "Protect" + set_allowed_to('create') + click_on_protect within(".protected-tags-list") { expect(page).to have_content('*-stable') } expect(ProtectedTag.count).to eq(1) @@ -64,8 +64,8 @@ RSpec.describe 'Protected Tags', :js, :with_license, feature_category: :source_c visit project_protected_tags_path(project) set_protected_tag_name('*-stable') - set_allowed_to('create') if Gitlab.ee? - click_on "Protect" + set_allowed_to('create') + click_on_protect within(".protected-tags-list") do expect(page).to have_content("Protected tags (2)") @@ -80,8 +80,8 @@ RSpec.describe 'Protected Tags', :js, :with_license, feature_category: :source_c visit project_protected_tags_path(project) set_protected_tag_name('*-stable') - set_allowed_to('create') if Gitlab.ee? - click_on "Protect" + set_allowed_to('create') + click_on_protect visit project_protected_tags_path(project) click_on "2 matching tags" @@ -101,4 +101,14 @@ RSpec.describe 'Protected Tags', :js, :with_license, feature_category: :source_c include_examples "protected tags > access control > CE" end + + context 'when the users for protected tags feature is off' do + before do + stub_licensed_features(protected_refs_for_users: false) + end + + include_examples 'Deploy keys with protected tags' do + let(:all_dropdown_sections) { ['Roles', 'Deploy Keys'] } + end + end end diff --git a/spec/features/reportable_note/issue_spec.rb b/spec/features/reportable_note/issue_spec.rb index 55e7f5897bc..a18cdf27294 100644 --- a/spec/features/reportable_note/issue_spec.rb +++ b/spec/features/reportable_note/issue_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe 'Reportable note on issue', :js, feature_category: :team_planning do + include CookieHelper + let(:user) { create(:user) } let(:project) { create(:project) } let(:issue) { create(:issue, project: project) } @@ -11,7 +13,7 @@ RSpec.describe 'Reportable note on issue', :js, feature_category: :team_planning before do project.add_maintainer(user) sign_in(user) - + set_cookie('new-actions-popover-viewed', 'true') visit project_issue_path(project, issue) end diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb index b2ddf427c0d..452a5700e08 100644 --- a/spec/features/runners_spec.rb +++ b/spec/features/runners_spec.rb @@ -14,23 +14,58 @@ RSpec.describe 'Runners', feature_category: :runner_fleet do stub_feature_flags(project_runners_vue_ui: false) end - context 'when user opens runners page' do - let(:project) { create(:project) } + context 'when user views runners page' do + let_it_be(:project) { create(:project) } before do project.add_maintainer(user) end - it 'user can see a link with instructions on how to install GitLab Runner' do - visit project_runners_path(project) + context 'when create_runner_workflow_for_namespace is enabled', :js do + before do + stub_feature_flags(create_runner_workflow_for_namespace: [project.namespace]) + + visit project_runners_path(project) + end + + it 'user can see a link with instructions on how to install GitLab Runner' do + expect(page).to have_link(s_('Runners|New project runner'), href: new_project_runner_path(project)) + end + + it_behaves_like "shows and resets runner registration token" do + let(:dropdown_text) { s_('Runners|Register a project runner') } + let(:registration_token) { project.runners_token } + end + end + + context 'when user views new runner page' do + context 'when create_runner_workflow_for_namespace is enabled', :js do + before do + stub_feature_flags(create_runner_workflow_for_namespace: [project.namespace]) + + visit new_project_runner_path(project) + end - expect(page).to have_link('Install GitLab Runner and ensure it\'s running.', href: "https://docs.gitlab.com/runner/install/") + it_behaves_like 'creates runner and shows register page' do + let(:register_path_pattern) { register_project_runner_path(project, '.*') } + end + end end - describe 'runners registration token' do - let!(:token) { project.runners_token } + context 'when create_runner_workflow_for_namespace is disabled' do + before do + stub_feature_flags(create_runner_workflow_for_namespace: false) + end + + it 'user can see a link with instructions on how to install GitLab Runner' do + visit project_runners_path(project) + + expect(page).to have_link('Install GitLab Runner and ensure it\'s running.', href: "https://docs.gitlab.com/runner/install/") + end + + describe 'runners registration token' do + let!(:token) { project.runners_token } - context 'when project_runners_vue_ui is disabled' do before do visit project_runners_path(project) end diff --git a/spec/features/search/user_searches_for_code_spec.rb b/spec/features/search/user_searches_for_code_spec.rb index b7d06a3a962..976324a5032 100644 --- a/spec/features/search/user_searches_for_code_spec.rb +++ b/spec/features/search/user_searches_for_code_spec.rb @@ -99,64 +99,11 @@ RSpec.describe 'User searches for code', :js, :disable_rate_limiter, feature_cat end end - context 'when :new_header_search is true' do + context 'when header search' do context 'search code within refs' do let(:ref_name) { 'v1.0.0' } before do - # This feature is disabled by default in spec_helper.rb. - # We missed a feature breaking bug, so to prevent this regression, testing both scenarios for this spec. - # This can be removed as part of closing https://gitlab.com/gitlab-org/gitlab/-/issues/339348. - stub_feature_flags(new_header_search: true) - visit(project_tree_path(project, ref_name)) - - submit_search('gitlab-grack') - select_search_scope('Code') - end - - it 'shows ref switcher in code result summary' do - expect(find('.ref-selector')).to have_text(ref_name) - end - - it 'persists branch name across search' do - find('.gl-search-box-by-click-search-button').click - expect(find('.ref-selector')).to have_text(ref_name) - end - - # this example is use to test the design that the refs is not - # only represent the branch as well as the tags. - it 'ref switcher list all the branches and tags' do - find('.ref-selector').click - wait_for_requests - - page.within('.ref-selector') do - expect(page).to have_selector('li', text: 'add-ipython-files') - expect(page).to have_selector('li', text: 'v1.0.0') - end - end - - it 'search result changes when refs switched' do - expect(find('.results')).not_to have_content('path = gitlab-grack') - - find('.ref-selector').click - wait_for_requests - - select_listbox_item('add-ipython-files') - - expect(page).to have_selector('.results', text: 'path = gitlab-grack') - end - end - end - - context 'when :new_header_search is false' do - context 'search code within refs' do - let(:ref_name) { 'v1.0.0' } - - before do - # This feature is disabled by default in spec_helper.rb. - # We missed a feature breaking bug, so to prevent this regression, testing both scenarios for this spec. - # This can be removed as part of closing https://gitlab.com/gitlab-org/gitlab/-/issues/339348. - stub_feature_flags(new_header_search: false) visit(project_tree_path(project, ref_name)) submit_search('gitlab-grack') diff --git a/spec/features/search/user_uses_header_search_field_spec.rb b/spec/features/search/user_uses_header_search_field_spec.rb index 334a192bec4..71d0f8d6d7f 100644 --- a/spec/features/search/user_uses_header_search_field_spec.rb +++ b/spec/features/search/user_uses_header_search_field_spec.rb @@ -26,11 +26,21 @@ RSpec.describe 'User uses header search field', :js, :disable_rate_limiter, feat wait_for_all_requests end - it 'starts searching by pressing the enter key' do - submit_search('gitlab') + context 'when searching by pressing the enter key' do + before do + submit_search('gitlab') + end + + it 'renders page title' do + page.within('.page-title') do + expect(page).to have_content('Search') + end + end - page.within('.page-title') do - expect(page).to have_content('Search') + it 'renders breadcrumbs' do + page.within('.breadcrumbs') do + expect(page).to have_content('Search') + end end end diff --git a/spec/features/security/admin_access_spec.rb b/spec/features/security/admin_access_spec.rb index de81444ed71..d162b24175f 100644 --- a/spec/features/security/admin_access_spec.rb +++ b/spec/features/security/admin_access_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe "Admin::Projects", feature_category: :permissions do +RSpec.describe "Admin::Projects", feature_category: :system_access do include AccessMatchers describe "GET /admin/projects" do diff --git a/spec/features/security/dashboard_access_spec.rb b/spec/features/security/dashboard_access_spec.rb index 948a4567624..0d60f1b1d11 100644 --- a/spec/features/security/dashboard_access_spec.rb +++ b/spec/features/security/dashboard_access_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe "Dashboard access", feature_category: :permissions do +RSpec.describe "Dashboard access", feature_category: :system_access do include AccessMatchers describe "GET /dashboard" do diff --git a/spec/features/security/group/internal_access_spec.rb b/spec/features/security/group/internal_access_spec.rb index ad2df4a1882..49f81600ac2 100644 --- a/spec/features/security/group/internal_access_spec.rb +++ b/spec/features/security/group/internal_access_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Internal Group access', feature_category: :permissions do +RSpec.describe 'Internal Group access', feature_category: :system_access do include AccessMatchers let(:group) { create(:group, :internal) } diff --git a/spec/features/security/group/private_access_spec.rb b/spec/features/security/group/private_access_spec.rb index 2e7b7512b45..5206667427e 100644 --- a/spec/features/security/group/private_access_spec.rb +++ b/spec/features/security/group/private_access_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Private Group access', feature_category: :permissions do +RSpec.describe 'Private Group access', feature_category: :system_access do include AccessMatchers let(:group) { create(:group, :private) } diff --git a/spec/features/security/group/public_access_spec.rb b/spec/features/security/group/public_access_spec.rb index 513c5710c8f..5c5580908aa 100644 --- a/spec/features/security/group/public_access_spec.rb +++ b/spec/features/security/group/public_access_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Public Group access', feature_category: :permissions do +RSpec.describe 'Public Group access', feature_category: :system_access do include AccessMatchers let(:group) { create(:group, :public) } diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb index e35e7ed742b..8ad4bedfdf8 100644 --- a/spec/features/security/project/internal_access_spec.rb +++ b/spec/features/security/project/internal_access_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe "Internal Project Access", feature_category: :permissions do +RSpec.describe "Internal Project Access", feature_category: :system_access do include AccessMatchers let_it_be(:project, reload: true) { create(:project, :internal, :repository, :with_namespace_settings) } diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb index 59ddb18ae8a..d2d74ecf5c9 100644 --- a/spec/features/security/project/private_access_spec.rb +++ b/spec/features/security/project/private_access_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe "Private Project Access", feature_category: :permissions do +RSpec.describe "Private Project Access", feature_category: :system_access do include AccessMatchers let_it_be(:project, reload: true) do diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb index 425691001f2..916f289b0b8 100644 --- a/spec/features/security/project/public_access_spec.rb +++ b/spec/features/security/project/public_access_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe "Public Project Access", feature_category: :permissions do +RSpec.describe "Public Project Access", feature_category: :system_access do include AccessMatchers let_it_be(:project, reload: true) do diff --git a/spec/features/security/project/snippet/internal_access_spec.rb b/spec/features/security/project/snippet/internal_access_spec.rb index b7dcc5f31d3..6ed0ec20210 100644 --- a/spec/features/security/project/snippet/internal_access_spec.rb +++ b/spec/features/security/project/snippet/internal_access_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe "Internal Project Snippets Access", feature_category: :permissions do +RSpec.describe "Internal Project Snippets Access", feature_category: :system_access do include AccessMatchers let_it_be(:project) { create(:project, :internal) } diff --git a/spec/features/security/project/snippet/private_access_spec.rb b/spec/features/security/project/snippet/private_access_spec.rb index 0ae45abb7ec..ef61f79a1b5 100644 --- a/spec/features/security/project/snippet/private_access_spec.rb +++ b/spec/features/security/project/snippet/private_access_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe "Private Project Snippets Access", feature_category: :permissions do +RSpec.describe "Private Project Snippets Access", feature_category: :system_access do include AccessMatchers let_it_be(:project) { create(:project, :private) } diff --git a/spec/features/security/project/snippet/public_access_spec.rb b/spec/features/security/project/snippet/public_access_spec.rb index b98f665c0dc..27fee745635 100644 --- a/spec/features/security/project/snippet/public_access_spec.rb +++ b/spec/features/security/project/snippet/public_access_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe "Public Project Snippets Access", feature_category: :permissions do +RSpec.describe "Public Project Snippets Access", feature_category: :system_access do include AccessMatchers let_it_be(:project) { create(:project, :public) } diff --git a/spec/features/signed_commits_spec.rb b/spec/features/signed_commits_spec.rb index 5d9b451cdf6..0268c8ad0d4 100644 --- a/spec/features/signed_commits_spec.rb +++ b/spec/features/signed_commits_spec.rb @@ -98,7 +98,8 @@ RSpec.describe 'GPG signed commits', feature_category: :source_code_management d end end - it 'unverified signature: gpg key email does not match the committer_email when the committer_email belongs to the user as a unconfirmed secondary email' do + it 'unverified signature: gpg key email does not match the committer_email when the committer_email belongs to the user as a unconfirmed secondary email', + quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/408233' do user_2_key visit project_commit_path(project, GpgHelpers::SIGNED_COMMIT_SHA) @@ -112,7 +113,7 @@ RSpec.describe 'GPG signed commits', feature_category: :source_code_management d end end - it 'unverified signature: commit contains multiple GPG signatures' do + it 'unverified signature: commit contains multiple GPG signatures', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/408233' do user_1_key visit project_commit_path(project, GpgHelpers::MULTIPLE_SIGNATURES_SHA) @@ -125,7 +126,7 @@ RSpec.describe 'GPG signed commits', feature_category: :source_code_management d end end - it 'verified and the gpg user has a gitlab profile' do + it 'verified and the gpg user has a gitlab profile', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/408233' do user_1_key visit project_commit_path(project, GpgHelpers::SIGNED_AND_AUTHORED_SHA) @@ -139,7 +140,7 @@ RSpec.describe 'GPG signed commits', feature_category: :source_code_management d end end - it "verified and the gpg user's profile doesn't exist anymore" do + it "verified and the gpg user's profile doesn't exist anymore", quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/395802' do user_1_key visit project_commit_path(project, GpgHelpers::SIGNED_AND_AUTHORED_SHA) diff --git a/spec/features/snippets/notes_on_personal_snippets_spec.rb b/spec/features/snippets/notes_on_personal_snippets_spec.rb index c281e5906ad..5c1ee729346 100644 --- a/spec/features/snippets/notes_on_personal_snippets_spec.rb +++ b/spec/features/snippets/notes_on_personal_snippets_spec.rb @@ -81,6 +81,7 @@ RSpec.describe 'Comments on personal snippets', :js, feature_category: :source_c it 'previews a note' do fill_in 'note[note]', with: 'This is **awesome**!' + find('.js-md-preview-button').click page.within('.new-note .md-preview-holder') do @@ -119,20 +120,36 @@ RSpec.describe 'Comments on personal snippets', :js, feature_category: :source_c end context 'when editing a note' do - it 'changes the text' do - find('.js-note-edit').click + context 'when note is empty' do + before do + find('.js-note-edit').click - page.within('.current-note-edit-form') do - fill_in 'note[note]', with: 'new content' - find('.btn-confirm').click + page.within('.current-note-edit-form') do + fill_in 'note[note]', with: '' + end end - page.within("#notes-list li#note_#{snippet_notes[0].id}") do - edited_text = find('.edited-text') + it 'disables save button' do + expect(page).to have_button('Save comment', disabled: true) + end + end + + context 'when note is not empty' do + it 'changes the text' do + find('.js-note-edit').click + + page.within('.current-note-edit-form') do + fill_in 'note[note]', with: 'new content' + find('.btn-confirm').click + end + + page.within("#notes-list li#note_#{snippet_notes[0].id}") do + edited_text = find('.edited-text') - expect(page).to have_css('.note_edited_ago') - expect(page).to have_content('new content') - expect(edited_text).to have_selector('.note_edited_ago') + expect(page).to have_css('.note_edited_ago') + expect(page).to have_content('new content') + expect(edited_text).to have_selector('.note_edited_ago') + end end end end diff --git a/spec/features/snippets/show_spec.rb b/spec/features/snippets/show_spec.rb index dc2fcdd7305..2673ad5e1d7 100644 --- a/spec/features/snippets/show_spec.rb +++ b/spec/features/snippets/show_spec.rb @@ -25,14 +25,13 @@ RSpec.describe 'Snippet', :js, feature_category: :source_code_management do subject { visit snippet_path(snippet) } end - it_behaves_like 'a dashboard page with sidebar', :dashboard_snippets_path, :snippets + it_behaves_like 'a "Your work" page with sidebar and breadcrumbs', :dashboard_snippets_path, :snippets context 'when unauthenticated' do - it 'does not have the sidebar' do + it 'shows the "Explore" sidebar' do visit snippet_path(snippet) - expect(page).to have_title _('Snippets') - expect(page).not_to have_css('aside.nav-sidebar') + expect(page).to have_css('aside.nav-sidebar[aria-label="Explore"]') end end @@ -43,6 +42,6 @@ RSpec.describe 'Snippet', :js, feature_category: :source_code_management do sign_in(different_user) end - it_behaves_like 'a dashboard page with sidebar', :dashboard_snippets_path, :snippets + it_behaves_like 'a "Your work" page with sidebar and breadcrumbs', :dashboard_snippets_path, :snippets end end diff --git a/spec/features/snippets/user_creates_snippet_spec.rb b/spec/features/snippets/user_creates_snippet_spec.rb index 03f569fe4b0..f4b6b552d46 100644 --- a/spec/features/snippets/user_creates_snippet_spec.rb +++ b/spec/features/snippets/user_creates_snippet_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe 'User creates snippet', :js, feature_category: :source_code_management do include DropzoneHelper - include Spec::Support::Helpers::Features::SnippetSpecHelpers + include Features::SnippetSpecHelpers let_it_be(:user) { create(:user) } @@ -21,7 +21,7 @@ RSpec.describe 'User creates snippet', :js, feature_category: :source_code_manag visit new_snippet_path end - it_behaves_like 'a dashboard page with sidebar', :new_snippet_path, :snippets + it_behaves_like 'a "Your work" page with sidebar and breadcrumbs', :new_snippet_path, :snippets def fill_form snippet_fill_in_form(title: title, content: file_content, description: md_description) @@ -128,7 +128,7 @@ RSpec.describe 'User creates snippet', :js, feature_category: :source_code_manag expect(page).not_to have_content(files_validation_message) end - it 'previews a snippet with file' do + it 'previews a snippet with file', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/408203' do # Click placeholder first to expand full description field snippet_fill_in_description('My Snippet') dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif') diff --git a/spec/features/snippets/user_edits_snippet_spec.rb b/spec/features/snippets/user_edits_snippet_spec.rb index 5096472ebe1..f58fda67b59 100644 --- a/spec/features/snippets/user_edits_snippet_spec.rb +++ b/spec/features/snippets/user_edits_snippet_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe 'User edits snippet', :js, feature_category: :source_code_management do include DropzoneHelper - include Spec::Support::Helpers::Features::SnippetSpecHelpers + include Features::SnippetSpecHelpers let_it_be(:file_name) { 'test.rb' } let_it_be(:content) { 'puts "test"' } diff --git a/spec/features/tags/developer_creates_tag_spec.rb b/spec/features/tags/developer_creates_tag_spec.rb index 6a1db051e87..cb59ee17514 100644 --- a/spec/features/tags/developer_creates_tag_spec.rb +++ b/spec/features/tags/developer_creates_tag_spec.rb @@ -62,7 +62,7 @@ RSpec.describe 'Developer creates tag', :js, feature_category: :source_code_mana expect(ref_input.value).to eq 'master' expect(find('.gl-button-text')).to have_content 'master' find('.ref-selector').click - expect(find('.gl-new-dropdown-inner')).to have_content 'test' + expect(find('.gl-new-dropdown-inner')).to have_content 'spooky-stuff' end end end diff --git a/spec/features/tags/developer_deletes_tag_spec.rb b/spec/features/tags/developer_deletes_tag_spec.rb index 76cf3aa691d..19feb5b21bc 100644 --- a/spec/features/tags/developer_deletes_tag_spec.rb +++ b/spec/features/tags/developer_deletes_tag_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe 'Developer deletes tag', :js, feature_category: :source_code_management do + include Spec::Support::Helpers::ModalHelpers + let(:user) { create(:user) } let(:group) { create(:group) } let(:project) { create(:project, :repository, namespace: group) } @@ -18,7 +20,7 @@ RSpec.describe 'Developer deletes tag', :js, feature_category: :source_code_mana it 'deletes the tag' do expect(page).to have_content 'v1.1.0' - container = page.find('.content .flex-row', text: 'v1.1.0') + container = page.find('[data-testid="tag-row"]', text: 'v1.1.0') delete_tag container expect(page).not_to have_content 'v1.1.0' @@ -28,7 +30,7 @@ RSpec.describe 'Developer deletes tag', :js, feature_category: :source_code_mana it 'can not delete protected tags' do expect(page).to have_content 'v1.1.1' - container = page.find('.content .flex-row', text: 'v1.1.1') + container = page.find('[data-testid="tag-row"]', text: 'v1.1.1') expect(container).to have_button('Only a project maintainer or owner can delete a protected tag', disabled: true) end @@ -41,8 +43,7 @@ RSpec.describe 'Developer deletes tag', :js, feature_category: :source_code_mana expect(page).to have_current_path( project_tag_path(project, 'v1.0.0'), ignore_query: true) - container = page.find('.nav-controls') - delete_tag container + delete_tag expect(page).to have_current_path(project_tags_path(project), ignore_query: true) expect(page).not_to have_content 'v1.0.0' @@ -58,17 +59,22 @@ RSpec.describe 'Developer deletes tag', :js, feature_category: :source_code_mana end it 'shows the error message' do - container = page.find('.content .flex-row', text: 'v1.1.0') + container = page.find('[data-testid="tag-row"]', text: 'v1.1.0') delete_tag container expect(page).to have_content('Do not delete tags') end end - def delete_tag(container) - container.find('.js-delete-tag-button').click + def delete_tag(container = page.document) + within container do + click_button('Delete tag') + end + + within_modal do + click_button('Yes, delete tag') + end - page.within('.modal') { click_button('Yes, delete tag') } wait_for_requests end end diff --git a/spec/features/tags/developer_views_tags_spec.rb b/spec/features/tags/developer_views_tags_spec.rb index dc9f38f1d83..81a41951377 100644 --- a/spec/features/tags/developer_views_tags_spec.rb +++ b/spec/features/tags/developer_views_tags_spec.rb @@ -60,7 +60,6 @@ RSpec.describe 'Developer views tags', feature_category: :source_code_management expect(page).to have_current_path( project_tag_path(project, 'v1.0.0'), ignore_query: true) expect(page).to have_content 'v1.0.0' - expect(page).to have_content 'This tag has no release notes.' end describe 'links on the tag page' do diff --git a/spec/features/tags/maintainer_deletes_protected_tag_spec.rb b/spec/features/tags/maintainer_deletes_protected_tag_spec.rb index ce518b962cd..67f6862502c 100644 --- a/spec/features/tags/maintainer_deletes_protected_tag_spec.rb +++ b/spec/features/tags/maintainer_deletes_protected_tag_spec.rb @@ -19,7 +19,10 @@ RSpec.describe 'Maintainer deletes protected tag', :js, feature_category: :sourc it 'deletes the tag' do expect(page).to have_content "#{tag_name} protected" - page.find('.content .flex-row', text: tag_name).find('.js-delete-tag-button').click + page.within('[data-testid="tag-row"]', text: tag_name) do + click_button('Delete tag') + end + assert_modal_content(tag_name) confirm_delete_tag(tag_name) @@ -35,7 +38,7 @@ RSpec.describe 'Maintainer deletes protected tag', :js, feature_category: :sourc it 'deletes the tag' do expect(page).to have_current_path(project_tag_path(project, tag_name), ignore_query: true) - page.find('.js-delete-tag-button').click + click_button('Delete tag') assert_modal_content(tag_name) confirm_delete_tag(tag_name) diff --git a/spec/features/topic_show_spec.rb b/spec/features/topic_show_spec.rb index d640e4e4edb..39b8782ea58 100644 --- a/spec/features/topic_show_spec.rb +++ b/spec/features/topic_show_spec.rb @@ -23,7 +23,7 @@ RSpec.describe 'Topic show page', feature_category: :projects do it 'shows title, avatar and description as markdown' do expect(page).to have_content(topic.title) expect(page).not_to have_content(topic.name) - expect(page).to have_selector('.avatar-container > img.topic-avatar') + expect(page).to have_selector('.gl-avatar.gl-avatar-s64') expect(find('.topic-description')).to have_selector('p > strong') expect(find('.topic-description')).to have_selector('p > a[rel]') expect(find('.topic-description')).to have_selector('p > gl-emoji') diff --git a/spec/features/u2f_spec.rb b/spec/features/u2f_spec.rb deleted file mode 100644 index 9ef0626b2b2..00000000000 --- a/spec/features/u2f_spec.rb +++ /dev/null @@ -1,216 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js, -feature_category: :authentication_and_authorization do - include Spec::Support::Helpers::Features::TwoFactorHelpers - - before do - stub_feature_flags(webauthn: false) - end - - it_behaves_like 'hardware device for 2fa', 'U2F' - - describe "registration" do - let(:user) { create(:user) } - - before do - gitlab_sign_in(user) - user.update_attribute(:otp_required_for_login, true) - end - - describe 'when 2FA via OTP is enabled' do - it 'allows registering more than one device' do - visit profile_account_path - - # First device - manage_two_factor_authentication - first_device = register_u2f_device - expect(page).to have_content('Your U2F device was registered') - - # Second device - second_device = register_u2f_device(name: 'My other device') - expect(page).to have_content('Your U2F device was registered') - - expect(page).to have_content(first_device.name) - expect(page).to have_content(second_device.name) - expect(U2fRegistration.count).to eq(2) - end - end - - it 'allows the same device to be registered for multiple users' do - # U2f specs will be removed after WebAuthn migration completed - pending('FakeU2fDevice has static key handle, '\ - 'leading to duplicate credential_xid for WebAuthn during migration, '\ - 'resulting in unique constraint violation') - - # First user - visit profile_account_path - manage_two_factor_authentication - u2f_device = register_u2f_device - expect(page).to have_content('Your U2F device was registered') - gitlab_sign_out - - # Second user - user = gitlab_sign_in(:user) - user.update_attribute(:otp_required_for_login, true) - visit profile_account_path - manage_two_factor_authentication - register_u2f_device(u2f_device, name: 'My other device') - expect(page).to have_content('Your U2F device was registered') - - expect(U2fRegistration.count).to eq(2) - end - - context "when there are form errors" do - it "doesn't register the device if there are errors" do - visit profile_account_path - manage_two_factor_authentication - - # Have the "u2f device" respond with bad data - page.execute_script("u2f.register = function(_,_,_,callback) { callback('bad response'); };") - click_on 'Set up new device' - expect(page).to have_content('Your device was successfully set up') - click_on 'Register device' - - expect(U2fRegistration.count).to eq(0) - expect(page).to have_content("The form contains the following error") - expect(page).to have_content("did not send a valid JSON response") - end - - it "allows retrying registration" do - visit profile_account_path - manage_two_factor_authentication - - # Failed registration - page.execute_script("u2f.register = function(_,_,_,callback) { callback('bad response'); };") - click_on 'Set up new device' - expect(page).to have_content('Your device was successfully set up') - click_on 'Register device' - expect(page).to have_content("The form contains the following error") - - # Successful registration - register_u2f_device - - expect(page).to have_content('Your U2F device was registered') - expect(U2fRegistration.count).to eq(1) - end - end - end - - describe "authentication" do - let(:user) { create(:user) } - - before do - # Register and logout - gitlab_sign_in(user) - user.update_attribute(:otp_required_for_login, true) - visit profile_account_path - manage_two_factor_authentication - @u2f_device = register_u2f_device - gitlab_sign_out - end - - describe "when 2FA via OTP is disabled" do - it "allows logging in with the U2F device" do - user.update_attribute(:otp_required_for_login, false) - gitlab_sign_in(user) - - @u2f_device.respond_to_u2f_authentication - - expect(page).to have_css('.sign-out-link', visible: false) - end - end - - describe "when 2FA via OTP is enabled" do - it "allows logging in with the U2F device" do - user.update_attribute(:otp_required_for_login, true) - gitlab_sign_in(user) - - @u2f_device.respond_to_u2f_authentication - - expect(page).to have_css('.sign-out-link', visible: false) - end - end - - describe "when a given U2F device has already been registered by another user" do - describe "but not the current user" do - it "does not allow logging in with that particular device" do - # Register current user with the different U2F device - current_user = gitlab_sign_in(:user) - current_user.update_attribute(:otp_required_for_login, true) - visit profile_account_path - manage_two_factor_authentication - register_u2f_device(name: 'My other device') - gitlab_sign_out - - # Try authenticating user with the old U2F device - gitlab_sign_in(current_user) - @u2f_device.respond_to_u2f_authentication - expect(page).to have_content('Authentication via U2F device failed') - end - end - - describe "and also the current user" do - it "allows logging in with that particular device" do - # U2f specs will be removed after WebAuthn migration completed - pending('FakeU2fDevice has static key handle, '\ - 'leading to duplicate credential_xid for WebAuthn during migration, '\ - 'resulting in unique constraint violation') - - # Register current user with the same U2F device - current_user = gitlab_sign_in(:user) - current_user.update_attribute(:otp_required_for_login, true) - visit profile_account_path - manage_two_factor_authentication - register_u2f_device(@u2f_device) - gitlab_sign_out - - # Try authenticating user with the same U2F device - gitlab_sign_in(current_user) - @u2f_device.respond_to_u2f_authentication - - expect(page).to have_css('.sign-out-link', visible: false) - end - end - end - - describe "when a given U2F device has not been registered" do - it "does not allow logging in with that particular device" do - unregistered_device = FakeU2fDevice.new(page, 'My device') - gitlab_sign_in(user) - unregistered_device.respond_to_u2f_authentication - - expect(page).to have_content('Authentication via U2F device failed') - end - end - - describe "when more than one device has been registered by the same user" do - it "allows logging in with either device" do - # Register first device - user = gitlab_sign_in(:user) - user.update_attribute(:otp_required_for_login, true) - visit profile_two_factor_auth_path - expect(page).to have_content("Your device needs to be set up.") - first_device = register_u2f_device - - # Register second device - visit profile_two_factor_auth_path - expect(page).to have_content("Your device needs to be set up.") - second_device = register_u2f_device(name: 'My other device') - gitlab_sign_out - - # Authenticate as both devices - [first_device, second_device].each do |device| - gitlab_sign_in(user) - device.respond_to_u2f_authentication - - expect(page).to have_css('.sign-out-link', visible: false) - - gitlab_sign_out - end - end - end - end -end diff --git a/spec/features/unsubscribe_links_spec.rb b/spec/features/unsubscribe_links_spec.rb index 23fa6261bd5..28699bc2c24 100644 --- a/spec/features/unsubscribe_links_spec.rb +++ b/spec/features/unsubscribe_links_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Unsubscribe links', :sidekiq_inline, feature_category: :not_owned do +RSpec.describe 'Unsubscribe links', :sidekiq_inline, feature_category: :shared do include Warden::Test::Helpers let_it_be(:project) { create(:project, :public) } diff --git a/spec/features/user_can_display_performance_bar_spec.rb b/spec/features/user_can_display_performance_bar_spec.rb index 4f6ce6e8f71..caf13c4111b 100644 --- a/spec/features/user_can_display_performance_bar_spec.rb +++ b/spec/features/user_can_display_performance_bar_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User can display performance bar', :js, feature_category: :continuous_verification do +RSpec.describe 'User can display performance bar', :js, feature_category: :application_performance do shared_examples 'performance bar cannot be displayed' do it 'does not show the performance bar by default' do expect(page).not_to have_css('#js-peek') diff --git a/spec/features/user_sees_revert_modal_spec.rb b/spec/features/user_sees_revert_modal_spec.rb index ae3158e4270..aca32d26bdb 100644 --- a/spec/features/user_sees_revert_modal_spec.rb +++ b/spec/features/user_sees_revert_modal_spec.rb @@ -19,9 +19,12 @@ feature_category: :code_review_workflow do end before do + stub_feature_flags(unbatch_graphql_queries: false) sign_in(user) visit(project_merge_request_path(project, merge_request)) - click_button('Merge') + page.within('.mr-state-widget') do + click_button 'Merge' + end wait_for_requests end diff --git a/spec/features/user_sorts_things_spec.rb b/spec/features/user_sorts_things_spec.rb index b45de88832c..bc377fb1f8f 100644 --- a/spec/features/user_sorts_things_spec.rb +++ b/spec/features/user_sorts_things_spec.rb @@ -7,7 +7,7 @@ require "spec_helper" # The `it`s are named here by convention `starting point -> some pages -> final point`. # All those specs are moved out to this spec intentionally to keep them all in one place. RSpec.describe "User sorts things", :js do - include Spec::Support::Helpers::Features::SortingHelpers + include Features::SortingHelpers include DashboardHelper let_it_be(:project) { create(:project_empty_repo, :public) } diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb index 5e683befeec..5529f0fa49e 100644 --- a/spec/features/users/login_spec.rb +++ b/spec/features/users/login_spec.rb @@ -109,6 +109,10 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_ end context 'within the grace period' do + before do + stub_application_setting_enum('email_confirmation_setting', 'soft') + end + it 'allows to login' do expect(authentication_metrics).to increment(:user_authenticated_counter) @@ -137,11 +141,9 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_ end context 'when resending the confirmation email' do - it 'redirects to the "almost there" page' do - stub_feature_flags(soft_email_confirmation: false) - - user = create(:user) + let_it_be(:user) { create(:user) } + it 'redirects to the "almost there" page' do visit new_user_confirmation_path fill_in 'user_email', with: user.email click_button 'Resend' @@ -206,18 +208,96 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_ end describe 'with two-factor authentication', :js do - def enter_code(code) + def enter_code(code, only_two_factor_webauthn_enabled: false) + click_on("Sign in via 2FA code") if only_two_factor_webauthn_enabled + fill_in 'user_otp_attempt', with: code click_button 'Verify code' end - context 'with valid username/password' do + shared_examples_for 'can login with recovery codes' do |only_two_factor_webauthn_enabled: false| + context 'using backup code' do + let(:codes) { user.generate_otp_backup_codes! } + + before do + expect(codes.size).to eq 10 + + # Ensure the generated codes get saved + user.save!(touch: false) + end + + context 'with valid code' do + it 'allows login' do + expect(authentication_metrics) + .to increment(:user_authenticated_counter) + .and increment(:user_two_factor_authenticated_counter) + + enter_code(codes.sample, only_two_factor_webauthn_enabled: only_two_factor_webauthn_enabled) + + expect(page).to have_current_path root_path, ignore_query: true + end + + it 'invalidates the used code' do + expect(authentication_metrics) + .to increment(:user_authenticated_counter) + .and increment(:user_two_factor_authenticated_counter) + + expect { enter_code(codes.sample, only_two_factor_webauthn_enabled: only_two_factor_webauthn_enabled) } + .to change { user.reload.otp_backup_codes.size }.by(-1) + end + + it 'invalidates backup codes twice in a row' do + expect(authentication_metrics) + .to increment(:user_authenticated_counter).twice + .and increment(:user_two_factor_authenticated_counter).twice + .and increment(:user_session_destroyed_counter) + + random_code = codes.delete(codes.sample) + expect { enter_code(random_code, only_two_factor_webauthn_enabled: only_two_factor_webauthn_enabled) } + .to change { user.reload.otp_backup_codes.size }.by(-1) + + gitlab_sign_out + gitlab_sign_in(user) + + expect { enter_code(codes.sample, only_two_factor_webauthn_enabled: only_two_factor_webauthn_enabled) } + .to change { user.reload.otp_backup_codes.size }.by(-1) + end + + it 'triggers ActiveSession.cleanup for the user' do + expect(authentication_metrics) + .to increment(:user_authenticated_counter) + .and increment(:user_two_factor_authenticated_counter) + expect(ActiveSession).to receive(:cleanup).with(user).once.and_call_original + + enter_code(codes.sample, only_two_factor_webauthn_enabled: only_two_factor_webauthn_enabled) + end + end + + context 'with invalid code' do + it 'blocks login' do + # TODO, invalid two factor authentication does not increment + # metrics / counters, see gitlab-org/gitlab-ce#49785 + + code = codes.sample + expect(user.invalidate_otp_backup_code!(code)).to eq true + + user.save!(touch: false) + expect(user.reload.otp_backup_codes.size).to eq 9 + + enter_code(code, only_two_factor_webauthn_enabled: only_two_factor_webauthn_enabled) + expect(page).to have_content('Invalid two-factor code.') + end + end + end + end + + # Freeze time to prevent failures when time between code being entered and + # validated greater than otp_allowed_drift + context 'with valid username/password', :freeze_time do let(:user) { create(:user, :two_factor) } before do gitlab_sign_in(user, remember: true) - - expect(page).to have_content('Two-factor authentication code') end it 'does not show a "You are already signed in." error message' do @@ -290,78 +370,16 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_ end end - context 'using backup code' do - let(:codes) { user.generate_otp_backup_codes! } - - before do - expect(codes.size).to eq 10 - - # Ensure the generated codes get saved - user.save!(touch: false) - end - - context 'with valid code' do - it 'allows login' do - expect(authentication_metrics) - .to increment(:user_authenticated_counter) - .and increment(:user_two_factor_authenticated_counter) - - enter_code(codes.sample) - - expect(page).to have_current_path root_path, ignore_query: true - end - - it 'invalidates the used code' do - expect(authentication_metrics) - .to increment(:user_authenticated_counter) - .and increment(:user_two_factor_authenticated_counter) - - expect { enter_code(codes.sample) } - .to change { user.reload.otp_backup_codes.size }.by(-1) - end - - it 'invalidates backup codes twice in a row' do - expect(authentication_metrics) - .to increment(:user_authenticated_counter).twice - .and increment(:user_two_factor_authenticated_counter).twice - .and increment(:user_session_destroyed_counter) - - random_code = codes.delete(codes.sample) - expect { enter_code(random_code) } - .to change { user.reload.otp_backup_codes.size }.by(-1) - - gitlab_sign_out - gitlab_sign_in(user) - - expect { enter_code(codes.sample) } - .to change { user.reload.otp_backup_codes.size }.by(-1) - end - - it 'triggers ActiveSession.cleanup for the user' do - expect(authentication_metrics) - .to increment(:user_authenticated_counter) - .and increment(:user_two_factor_authenticated_counter) - expect(ActiveSession).to receive(:cleanup).with(user).once.and_call_original - - enter_code(codes.sample) - end - end + context 'when user with TOTP enabled' do + let(:user) { create(:user, :two_factor) } - context 'with invalid code' do - it 'blocks login' do - # TODO, invalid two factor authentication does not increment - # metrics / counters, see gitlab-org/gitlab-ce#49785 - - code = codes.sample - expect(user.invalidate_otp_backup_code!(code)).to eq true + include_examples 'can login with recovery codes' + end - user.save!(touch: false) - expect(user.reload.otp_backup_codes.size).to eq 9 + context 'when user with only Webauthn enabled' do + let(:user) { create(:user, :two_factor_via_webauthn, registrations_count: 1) } - enter_code(code) - expect(page).to have_content('Invalid two-factor code.') - end - end + include_examples 'can login with recovery codes', only_two_factor_webauthn_enabled: true end end @@ -376,11 +394,29 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_ providers: [mock_saml_config_with_upstream_two_factor_authn_contexts]) end + it 'displays the remember me checkbox' do + visit new_user_session_path + + expect(page).to have_field('remember_me_omniauth') + end + + context 'when remember me is not enabled' do + before do + stub_application_setting(remember_me_enabled: false) + end + + it 'does not display the remember me checkbox' do + visit new_user_session_path + + expect(page).not_to have_field('remember_me_omniauth') + end + end + context 'when authn_context is worth two factors' do let(:mock_saml_response) do File.read('spec/fixtures/authentication/saml_response.xml') - .gsub('urn:oasis:names:tc:SAML:2.0:ac:classes:Password', - 'urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorOTPSMS') + .gsub('urn:oasis:names:tc:SAML:2.0:ac:classes:Password', + 'urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorOTPSMS') end it 'signs user in without prompting for second factor' do @@ -394,12 +430,14 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_ sign_in_using_saml! expect_single_session_with_authenticated_ttl - expect(page).not_to have_content('Two-Factor Authentication') + expect(page).not_to have_content(_('Enter verification code')) expect(page).to have_current_path root_path, ignore_query: true end end - context 'when two factor authentication is required' do + # Freeze time to prevent failures when time between code being entered and + # validated greater than otp_allowed_drift + context 'when two factor authentication is required', :freeze_time do it 'shows 2FA prompt after OAuth login' do expect(authentication_metrics) .to increment(:user_authenticated_counter) @@ -408,7 +446,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_ sign_in_using_saml! - expect(page).to have_content('Two-factor authentication code') + expect(page).to have_content('Enter verification code') enter_code(user.current_otp) @@ -424,6 +462,24 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_ end describe 'without two-factor authentication' do + it 'displays the remember me checkbox' do + visit new_user_session_path + + expect(page).to have_content(_('Remember me')) + end + + context 'when remember me is not enabled' do + before do + stub_application_setting(remember_me_enabled: false) + end + + it 'does not display the remember me checkbox' do + visit new_user_session_path + + expect(page).not_to have_content(_('Remember me')) + end + end + context 'with correct username and password' do let(:user) { create(:user) } @@ -457,6 +513,43 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_ gitlab_sign_in(user) end + context 'when the session expires' do + it 'signs the user out' do + expect(authentication_metrics) + .to increment(:user_authenticated_counter) + + gitlab_sign_in(user) + expire_session + visit root_path + + expect(page).to have_current_path new_user_session_path + end + + it 'extends the session when using remember me' do + expect(authentication_metrics) + .to increment(:user_authenticated_counter).twice + + gitlab_sign_in(user, remember: true) + expire_session + visit root_path + + expect(page).to have_current_path root_path + end + + it 'does not extend the session when remember me is not enabled' do + expect(authentication_metrics) + .to increment(:user_authenticated_counter) + + gitlab_sign_in(user, remember: true) + expire_session + stub_application_setting(remember_me_enabled: false) + + visit root_path + + expect(page).to have_current_path new_user_session_path + end + end + context 'when the users password is expired' do before do user.update!(password_expires_at: Time.zone.parse('2018-05-08 11:29:46 UTC')) @@ -591,23 +684,21 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_ end context 'within the grace period' do - it 'redirects to two-factor configuration page' do - freeze_time do - expect(authentication_metrics) - .to increment(:user_authenticated_counter) - - gitlab_sign_in(user) - - expect(page).to have_current_path profile_two_factor_auth_path, ignore_query: true - expect(page).to have_content( - 'The group settings for Group 1 and Group 2 require you to enable '\ - 'Two-Factor Authentication for your account. '\ - 'You can leave Group 1 and leave Group 2. '\ - 'You need to do this '\ - 'before '\ - "#{(Time.zone.now + 2.days).strftime("%a, %d %b %Y %H:%M:%S %z")}" - ) - end + it 'redirects to two-factor configuration page', :freeze_time do + expect(authentication_metrics) + .to increment(:user_authenticated_counter) + + gitlab_sign_in(user) + + expect(page).to have_current_path profile_two_factor_auth_path, ignore_query: true + expect(page).to have_content( + 'The group settings for Group 1 and Group 2 require you to enable '\ + 'Two-Factor Authentication for your account. '\ + 'You can leave Group 1 and leave Group 2. '\ + 'You need to do this '\ + 'before '\ + "#{(Time.zone.now + 2.days).strftime("%a, %d %b %Y %H:%M:%S %z")}" + ) end it 'allows skipping two-factor configuration', :js do @@ -732,17 +823,37 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_ allow(instance).to receive(:"user_#{provider}_omniauth_callback_path") .and_return("/users/auth/#{provider}/callback") end - - visit new_user_session_path end it 'correctly renders tabs and panes' do + visit new_user_session_path + ensure_tab_pane_correctness(['Main LDAP', 'Standard']) end it 'renders link to sign up path' do + visit new_user_session_path + expect(page.body).to have_link('Register now', href: new_user_registration_path) end + + it 'displays the remember me checkbox' do + visit new_user_session_path + + ensure_remember_me_in_tab(ldap_server_config['label']) + end + + context 'when remember me is not enabled' do + before do + stub_application_setting(remember_me_enabled: false) + end + + it 'does not display the remember me checkbox' do + visit new_user_session_path + + ensure_remember_me_not_in_tab(ldap_server_config['label']) + end + end end context 'when crowd is enabled' do @@ -757,13 +868,31 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_ allow(instance).to receive(:user_crowd_omniauth_authorize_path) .and_return("/users/auth/crowd/callback") end - - visit new_user_session_path end it 'correctly renders tabs and panes' do + visit new_user_session_path + ensure_tab_pane_correctness(%w(Crowd Standard)) end + + it 'displays the remember me checkbox' do + visit new_user_session_path + + ensure_remember_me_in_tab(_('Crowd')) + end + + context 'when remember me is not enabled' do + before do + stub_application_setting(remember_me_enabled: false) + end + + it 'does not display the remember me checkbox' do + visit new_user_session_path + + ensure_remember_me_not_in_tab(_('Crowd')) + end + end end end @@ -928,22 +1057,22 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_ it 'asks the user to accept the terms before setting an email', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/388049', type: :flaky } do - expect(authentication_metrics) - .to increment(:user_authenticated_counter) + expect(authentication_metrics) + .to increment(:user_authenticated_counter) - gitlab_sign_in_via('saml', user, 'my-uid') + gitlab_sign_in_via('saml', user, 'my-uid') - expect_to_be_on_terms_page - click_button 'Accept terms' + expect_to_be_on_terms_page + click_button 'Accept terms' - expect(page).to have_current_path(profile_path, ignore_query: true) + expect(page).to have_current_path(profile_path, ignore_query: true) - fill_in 'Email', with: 'hello@world.com' + fill_in 'Email', with: 'hello@world.com' - click_button 'Update profile settings' + click_button 'Update profile settings' - expect(page).to have_content('Profile was successfully updated') - end + expect(page).to have_content('Profile was successfully updated') + end end end @@ -954,8 +1083,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_ let(:alert_message) { "To continue, you need to select the link in the confirmation email we sent to verify your email address. If you didn't get our email, select Resend confirmation email" } before do - stub_application_setting_enum('email_confirmation_setting', 'hard') - stub_feature_flags(soft_email_confirmation: true) + stub_application_setting_enum('email_confirmation_setting', 'soft') stub_feature_flags(identity_verification: false) allow(User).to receive(:allow_unconfirmed_access_for).and_return grace_period end diff --git a/spec/features/users/show_spec.rb b/spec/features/users/show_spec.rb index 88b2d918976..9c4a1b36ecc 100644 --- a/spec/features/users/show_spec.rb +++ b/spec/features/users/show_spec.rb @@ -15,6 +15,14 @@ RSpec.describe 'User page', feature_category: :user_profile do expect(page).to have_content("User ID: #{user.id}") end + it 'shows name on breadcrumbs' do + subject + + page.within '.breadcrumbs' do + expect(page).to have_content(user.name) + end + end + context 'with public profile' do context 'with `profile_tabs_vue` feature flag disabled' do before do @@ -149,7 +157,7 @@ RSpec.describe 'User page', feature_category: :user_profile do end end - context 'follow/unfollow and followers/following' do + context 'follow/unfollow and followers/following', :js do let_it_be(:followee) { create(:user) } let_it_be(:follower) { create(:user) } @@ -159,21 +167,33 @@ RSpec.describe 'User page', feature_category: :user_profile do expect(page).not_to have_button(text: 'Follow', class: 'gl-button') end - it 'shows 0 followers and 0 following' do - subject + shared_examples 'follower tabs with count badges' do + it 'shows 0 followers and 0 following' do + subject + + expect(page).to have_content('Followers 0') + expect(page).to have_content('Following 0') + end - expect(page).to have_content('0 followers') - expect(page).to have_content('0 following') + it 'shows 1 followers and 1 following' do + follower.follow(user) + user.follow(followee) + + subject + + expect(page).to have_content('Followers 1') + expect(page).to have_content('Following 1') + end end - it 'shows 1 followers and 1 following' do - follower.follow(user) - user.follow(followee) + it_behaves_like 'follower tabs with count badges' - subject + context 'with profile_tabs_vue feature flag disabled' do + before_all do + stub_feature_flags(profile_tabs_vue: false) + end - expect(page).to have_content('1 follower') - expect(page).to have_content('1 following') + it_behaves_like 'follower tabs with count badges' end it 'does show button to follow' do @@ -526,4 +546,36 @@ RSpec.describe 'User page', feature_category: :user_profile do end end end + + context 'achievements' do + it 'renders the user achievements mount point' do + subject + + expect(page).to have_selector('#js-user-achievements') + end + + context 'when the user has chosen not to display achievements' do + let(:user) { create(:user) } + + before do + user.update!(achievements_enabled: false) + end + + it 'does not render the user achievements mount point' do + subject + + expect(page).not_to have_selector('#js-user-achievements') + end + end + + context 'when the profile is private' do + let(:user) { create(:user, private_profile: true) } + + it 'does not render the user achievements mount point' do + subject + + expect(page).not_to have_selector('#js-user-achievements') + end + end + end end diff --git a/spec/features/users/signup_spec.rb b/spec/features/users/signup_spec.rb index 11ff318c346..d65eea3671c 100644 --- a/spec/features/users/signup_spec.rb +++ b/spec/features/users/signup_spec.rb @@ -10,7 +10,7 @@ RSpec.shared_examples 'Signup name validation' do |field, max_length, label| visit new_user_registration_path end - describe "#{field} validation", :js do + describe "#{field} validation" do it "does not show an error border if the user's fullname length is not longer than #{max_length} characters" do fill_in field, with: 'u' * max_length @@ -44,7 +44,7 @@ RSpec.shared_examples 'Signup name validation' do |field, max_length, label| end end -RSpec.describe 'Signup', feature_category: :user_profile do +RSpec.describe 'Signup', :js, feature_category: :user_profile do include TermsHelper let(:new_user) { build_stubbed(:user) } @@ -71,7 +71,7 @@ RSpec.describe 'Signup', feature_category: :user_profile do stub_application_setting(require_admin_approval_after_user_signup: false) end - describe 'username validation', :js do + describe 'username validation' do before do visit new_user_registration_path end @@ -200,9 +200,8 @@ RSpec.describe 'Signup', feature_category: :user_profile do stub_application_setting_enum('email_confirmation_setting', 'hard') end - context 'when soft email confirmation is not enabled' do + context 'when email confirmation setting is not `soft`' do before do - stub_feature_flags(soft_email_confirmation: false) stub_feature_flags(identity_verification: false) end @@ -221,9 +220,9 @@ RSpec.describe 'Signup', feature_category: :user_profile do end end - context 'when soft email confirmation is enabled' do + context 'when email confirmation setting is `soft`' do before do - stub_feature_flags(soft_email_confirmation: true) + stub_application_setting_enum('email_confirmation_setting', 'soft') end it 'creates the user account and sends a confirmation email' do @@ -338,6 +337,7 @@ RSpec.describe 'Signup', feature_category: :user_profile do expect { click_button 'Register' }.not_to change { User.count } expect(page).to have_content(_('There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.')) + expect(page).to have_content("Minimum length is #{Gitlab::CurrentSettings.minimum_password_length} characters") end end @@ -357,6 +357,8 @@ RSpec.describe 'Signup', feature_category: :user_profile do visit new_user_registration_path fill_in_signup_form + wait_for_all_requests + click_button 'Register' visit new_project_path @@ -384,7 +386,7 @@ RSpec.describe 'Signup', feature_category: :user_profile do expect(page.body).not_to match(/#{new_user.password}/) end - context 'with invalid email', :saas, :js do + context 'with invalid email' do it_behaves_like 'user email validation' do let(:path) { new_user_registration_path } end diff --git a/spec/features/webauthn_spec.rb b/spec/features/webauthn_spec.rb index 859793d1353..5c42facfa8b 100644 --- a/spec/features/webauthn_spec.rb +++ b/spec/features/webauthn_spec.rb @@ -2,14 +2,121 @@ require 'spec_helper' -RSpec.describe 'Using WebAuthn Devices for Authentication', :js, feature_category: :authentication_and_authorization do - include Spec::Support::Helpers::Features::TwoFactorHelpers +RSpec.describe 'Using WebAuthn Devices for Authentication', :js, feature_category: :system_access do + include Features::TwoFactorHelpers let(:app_id) { "http://#{Capybara.current_session.server.host}:#{Capybara.current_session.server.port}" } before do WebAuthn.configuration.origin = app_id end + context 'when the webauth_without_totp feature flag is enabled' do + # Some of the shared tests don't apply. After removing U2F support and the `webauthn_without_totp` feature flag, refactor the shared tests. + # TODO: it_behaves_like 'hardware device for 2fa', 'WebAuthn' + + describe 'registration' do + let(:user) { create(:user) } + + before do + gitlab_sign_in(user) + end + + it 'shows an error when using a wrong password' do + visit profile_account_path + + # First device + enable_two_factor_authentication + webauthn_device_registration(password: 'fake') + expect(page).to have_content(_('You must provide a valid current password.')) + end + + it 'allows registering more than one device' do + visit profile_account_path + + # First device + enable_two_factor_authentication + first_device = webauthn_device_registration(password: user.password) + expect(page).to have_content('Your WebAuthn device was registered!') + copy_recovery_codes + manage_two_factor_authentication + + # Second device + second_device = webauthn_device_registration(name: 'My other device', password: user.password) + expect(page).to have_content('Your WebAuthn device was registered!') + + expect(page).to have_content(first_device.name) + expect(page).to have_content(second_device.name) + expect(WebauthnRegistration.count).to eq(2) + end + + it 'allows the same device to be registered for multiple users' do + # First user + visit profile_account_path + enable_two_factor_authentication + webauthn_device = webauthn_device_registration(password: user.password) + expect(page).to have_content('Your WebAuthn device was registered!') + gitlab_sign_out + + # Second user + user = gitlab_sign_in(:user) + visit profile_account_path + enable_two_factor_authentication + webauthn_device_registration(webauthn_device: webauthn_device, name: 'My other device', password: user.password) + expect(page).to have_content('Your WebAuthn device was registered!') + + expect(WebauthnRegistration.count).to eq(2) + end + + context 'when there are form errors' do + let(:mock_register_js) do + <<~JS + const mockResponse = { + type: 'public-key', + id: '', + rawId: '', + response: { + clientDataJSON: '', + attestationObject: '', + }, + getClientExtensionResults: () => {}, + }; + navigator.credentials.create = () => Promise.resolve(mockResponse); + JS + end + + it "doesn't register the device if there are errors" do + visit profile_account_path + enable_two_factor_authentication + + # Have the "webauthn device" respond with bad data + page.execute_script(mock_register_js) + click_on _('Set up new device') + webauthn_fill_form_and_submit(password: user.password) + expect(page).to have_content(_('Your WebAuthn device did not send a valid JSON response.')) + + expect(WebauthnRegistration.count).to eq(0) + end + + it 'allows retrying registration' do + visit profile_account_path + enable_two_factor_authentication + + # Failed registration + page.execute_script(mock_register_js) + click_on _('Set up new device') + webauthn_fill_form_and_submit(password: user.password) + expect(page).to have_content(_('Your WebAuthn device did not send a valid JSON response.')) + + # Successful registration + webauthn_device_registration(password: user.password) + + expect(page).to have_content('Your WebAuthn device was registered!') + expect(WebauthnRegistration.count).to eq(1) + end + end + end + end + context 'when the webauth_without_totp feature flag is disabled' do before do stub_feature_flags(webauthn_without_totp: false) @@ -114,99 +221,99 @@ RSpec.describe 'Using WebAuthn Devices for Authentication', :js, feature_categor end end end + end - describe 'authentication' do - let(:otp_required_for_login) { true } - let(:user) { create(:user, webauthn_xid: WebAuthn.generate_user_id, otp_required_for_login: otp_required_for_login) } - let!(:webauthn_device) do - add_webauthn_device(app_id, user) - end + describe 'authentication' do + let(:otp_required_for_login) { true } + let(:user) { create(:user, webauthn_xid: WebAuthn.generate_user_id, otp_required_for_login: otp_required_for_login) } + let!(:webauthn_device) do + add_webauthn_device(app_id, user) + end - describe 'when 2FA via OTP is disabled' do - let(:otp_required_for_login) { false } + describe 'when 2FA via OTP is disabled' do + let(:otp_required_for_login) { false } - it 'allows logging in with the WebAuthn device' do - gitlab_sign_in(user) + it 'allows logging in with the WebAuthn device' do + gitlab_sign_in(user) - webauthn_device.respond_to_webauthn_authentication + webauthn_device.respond_to_webauthn_authentication - expect(page).to have_css('.sign-out-link', visible: false) - end + expect(page).to have_css('.sign-out-link', visible: false) end + end - describe 'when 2FA via OTP is enabled' do - it 'allows logging in with the WebAuthn device' do - gitlab_sign_in(user) + describe 'when 2FA via OTP is enabled' do + it 'allows logging in with the WebAuthn device' do + gitlab_sign_in(user) - webauthn_device.respond_to_webauthn_authentication + webauthn_device.respond_to_webauthn_authentication - expect(page).to have_css('.sign-out-link', visible: false) - end + expect(page).to have_css('.sign-out-link', visible: false) end + end - describe 'when a given WebAuthn device has already been registered by another user' do - describe 'but not the current user' do - let(:other_user) { create(:user, webauthn_xid: WebAuthn.generate_user_id, otp_required_for_login: otp_required_for_login) } + describe 'when a given WebAuthn device has already been registered by another user' do + describe 'but not the current user' do + let(:other_user) { create(:user, webauthn_xid: WebAuthn.generate_user_id, otp_required_for_login: otp_required_for_login) } - it 'does not allow logging in with that particular device' do - # Register other user with a different WebAuthn device - other_device = add_webauthn_device(app_id, other_user) + it 'does not allow logging in with that particular device' do + # Register other user with a different WebAuthn device + other_device = add_webauthn_device(app_id, other_user) - # Try authenticating user with the old WebAuthn device - gitlab_sign_in(user) - other_device.respond_to_webauthn_authentication - expect(page).to have_content('Authentication via WebAuthn device failed') - end + # Try authenticating user with the old WebAuthn device + gitlab_sign_in(user) + other_device.respond_to_webauthn_authentication + expect(page).to have_content('Authentication via WebAuthn device failed') end + end + + describe "and also the current user" do + # TODO Uncomment once WebAuthn::FakeClient supports passing credential options + # (especially allow_credentials, as this is needed to specify which credential the + # fake client should use. Currently, the first credential is always used). + # There is an issue open for this: https://github.com/cedarcode/webauthn-ruby/issues/259 + it "allows logging in with that particular device" do + pending("support for passing credential options in FakeClient") + # Register current user with the same WebAuthn device + current_user = gitlab_sign_in(:user) + visit profile_account_path + manage_two_factor_authentication + register_webauthn_device(webauthn_device) + gitlab_sign_out + + # Try authenticating user with the same WebAuthn device + gitlab_sign_in(current_user) + webauthn_device.respond_to_webauthn_authentication - describe "and also the current user" do - # TODO Uncomment once WebAuthn::FakeClient supports passing credential options - # (especially allow_credentials, as this is needed to specify which credential the - # fake client should use. Currently, the first credential is always used). - # There is an issue open for this: https://github.com/cedarcode/webauthn-ruby/issues/259 - it "allows logging in with that particular device" do - pending("support for passing credential options in FakeClient") - # Register current user with the same WebAuthn device - current_user = gitlab_sign_in(:user) - visit profile_account_path - manage_two_factor_authentication - register_webauthn_device(webauthn_device) - gitlab_sign_out - - # Try authenticating user with the same WebAuthn device - gitlab_sign_in(current_user) - webauthn_device.respond_to_webauthn_authentication - - expect(page).to have_css('.sign-out-link', visible: false) - end + expect(page).to have_css('.sign-out-link', visible: false) end end + end - describe 'when a given WebAuthn device has not been registered' do - it 'does not allow logging in with that particular device' do - unregistered_device = FakeWebauthnDevice.new(page, 'My device') - gitlab_sign_in(user) - unregistered_device.respond_to_webauthn_authentication + describe 'when a given WebAuthn device has not been registered' do + it 'does not allow logging in with that particular device' do + unregistered_device = FakeWebauthnDevice.new(page, 'My device') + gitlab_sign_in(user) + unregistered_device.respond_to_webauthn_authentication - expect(page).to have_content('Authentication via WebAuthn device failed') - end + expect(page).to have_content('Authentication via WebAuthn device failed') end + end - describe 'when more than one device has been registered by the same user' do - it 'allows logging in with either device' do - first_device = add_webauthn_device(app_id, user) - second_device = add_webauthn_device(app_id, user) + describe 'when more than one device has been registered by the same user' do + it 'allows logging in with either device' do + first_device = add_webauthn_device(app_id, user) + second_device = add_webauthn_device(app_id, user) - # Authenticate as both devices - [first_device, second_device].each do |device| - gitlab_sign_in(user) - # register_webauthn_device(device) - device.respond_to_webauthn_authentication + # Authenticate as both devices + [first_device, second_device].each do |device| + gitlab_sign_in(user) + # register_webauthn_device(device) + device.respond_to_webauthn_authentication - expect(page).to have_css('.sign-out-link', visible: false) + expect(page).to have_css('.sign-out-link', visible: false) - gitlab_sign_out - end + gitlab_sign_out end end end diff --git a/spec/features/whats_new_spec.rb b/spec/features/whats_new_spec.rb index 6b19ab28b44..3668d90f2e9 100644 --- a/spec/features/whats_new_spec.rb +++ b/spec/features/whats_new_spec.rb @@ -2,13 +2,11 @@ require "spec_helper" -RSpec.describe "renders a `whats new` dropdown item", feature_category: :not_owned do +RSpec.describe "renders a `whats new` dropdown item", feature_category: :onboarding do let_it_be(:user) { create(:user) } context 'when not logged in' do - it 'and on .com it renders' do - allow(Gitlab).to receive(:com?).and_return(true) - + it 'and on SaaS it renders', :saas do visit user_path(user) page.within '.header-help' do diff --git a/spec/features/work_items/work_item_spec.rb b/spec/features/work_items/work_item_spec.rb deleted file mode 100644 index 3c71a27ff82..00000000000 --- a/spec/features/work_items/work_item_spec.rb +++ /dev/null @@ -1,37 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'Work item', :js, feature_category: :team_planning do - let_it_be(:project) { create(:project, :public) } - let_it_be(:user) { create(:user) } - let_it_be(:work_item) { create(:work_item, project: project) } - - context 'for signed in user' do - before do - project.add_developer(user) - - sign_in(user) - - visit project_work_items_path(project, work_items_path: work_item.id) - end - - it_behaves_like 'work items status' - it_behaves_like 'work items assignees' - it_behaves_like 'work items labels' - it_behaves_like 'work items comments' - it_behaves_like 'work items description' - end - - context 'for signed in owner' do - before do - project.add_owner(user) - - sign_in(user) - - visit project_work_items_path(project, work_items_path: work_item.id) - end - - it_behaves_like 'work items invite members' - end -end |
