diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-20 18:19:03 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-20 18:19:03 +0300 |
commit | 14bd84b61276ef29b97d23642d698de769bacfd2 (patch) | |
tree | f9eba90140c1bd874211dea17750a0d422c04080 /spec/features | |
parent | 891c388697b2db0d8ee0c8358a9bdbf6dc56d581 (diff) |
Add latest changes from gitlab-org/gitlab@15-10-stable-eev15.10.0-rc42
Diffstat (limited to 'spec/features')
136 files changed, 1960 insertions, 1057 deletions
diff --git a/spec/features/abuse_report_spec.rb b/spec/features/abuse_report_spec.rb index 1267025a7bf..474ab4c7b8e 100644 --- a/spec/features/abuse_report_spec.rb +++ b/spec/features/abuse_report_spec.rb @@ -15,25 +15,6 @@ RSpec.describe 'Abuse reports', :js, feature_category: :insider_threat do 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) 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..9fe72b981f1 100644 --- a/spec/features/admin/admin_abuse_reports_spec.rb +++ b/spec/features/admin/admin_abuse_reports_spec.rb @@ -2,79 +2,210 @@ 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(page).to have_link '', href: admin_user_path(user) + expect_report_shown(open_report, open_report2) + + 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 - 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) + 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) + + 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) + + sign_in(admin) + gitlab_enable_admin_mode_sign_in(admin) + end + + describe 'if a user has been reported for abuse' do + let!(:abuse_report) { create(:abuse_report, user: user) } - it 'shows only single user report' do - visit admin_abuse_reports_path + describe 'in the abuse report view' do + it 'presents information about abuse report' do + visit admin_abuse_reports_path + + 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 + 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) - page.within '.filter-form' do - click_button 'User' - wait_for_requests + expect(page).to have_link '', href: admin_user_path(user) + end + end + end + + describe 'if a many users have been reported for abuse' do + let(:report_count) { AbuseReport.default_per_page + 3 } - page.within '.dropdown-menu-user' do - click_link user2.name + before do + report_count.times do + create(:abuse_report, user: create(:user)) end + end + + describe 'in the abuse report view' do + it 'presents information about abuse report' do + visit admin_abuse_reports_path - wait_for_requests + 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 - expect(page).to have_content(user2.name) - expect(page).not_to have_content(user.name) + 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..fee75496a1e 100644 --- a/spec/features/admin/admin_appearance_spec.rb +++ b/spec/features/admin/admin_appearance_spec.rb @@ -2,7 +2,7 @@ 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) } 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_health_check_spec.rb b/spec/features/admin/admin_health_check_spec.rb index de71a48d2dc..23a9ab74a7a 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: :continuous_verification 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_mode/login_spec.rb b/spec/features/admin/admin_mode/login_spec.rb index 393721fe451..853e4763872 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 @@ -25,13 +25,13 @@ RSpec.describe 'Admin Mode Login', feature_category: :authentication_and_authori 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) - expect(page).to have_content('Two-Factor Authentication') + expect(page).to have_content(_('Enter verification code')) enter_code(repeated_otp) @@ -43,12 +43,12 @@ RSpec.describe 'Admin Mode Login', feature_category: :authentication_and_authori 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) - expect(page).to have_content('Two-Factor Authentication') + expect(page).to have_content(_('Enter verification code')) end it 'allows login with valid code' do @@ -145,11 +145,11 @@ RSpec.describe 'Admin Mode Login', feature_category: :authentication_and_authori it 'signs user in without prompting for second factor' do sign_in_using_saml! - expect(page).not_to have_content('Two-Factor Authentication') + expect(page).not_to have_content(_('Enter verification code')) enable_admin_mode_using_saml! - expect(page).not_to have_content('Two-Factor Authentication') + 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 @@ -159,12 +159,12 @@ RSpec.describe 'Admin Mode Login', feature_category: :authentication_and_authori it 'shows 2FA prompt after omniauth login' do sign_in_using_saml! - expect(page).to have_content('Two-Factor Authentication') + expect(page).to have_content(_('Enter verification code')) enter_code(user.current_otp) 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 @@ -210,12 +210,12 @@ RSpec.describe 'Admin Mode Login', feature_category: :authentication_and_authori 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') + expect(page).to have_content(_('Enter verification code')) 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 diff --git a/spec/features/admin/admin_mode/logout_spec.rb b/spec/features/admin/admin_mode/logout_spec.rb index f4e8941d25a..25f77da4401 100644 --- a/spec/features/admin/admin_mode/logout_spec.rb +++ b/spec/features/admin/admin_mode/logout_spec.rb @@ -2,7 +2,7 @@ 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 diff --git a/spec/features/admin/admin_mode/workers_spec.rb b/spec/features/admin/admin_mode/workers_spec.rb index f3639fd0800..305927663e9 100644 --- a/spec/features/admin/admin_mode/workers_spec.rb +++ b/spec/features/admin/admin_mode/workers_spec.rb @@ -3,7 +3,7 @@ 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 +RSpec.describe 'Admin mode for workers', :request_store, feature_category: :system_access do include Spec::Support::Helpers::Features::AdminUsersHelpers let(:user) { create(:user) } diff --git a/spec/features/admin/admin_mode_spec.rb b/spec/features/admin/admin_mode_spec.rb index 769ff75b5a2..3c47a991fd1 100644 --- a/spec/features/admin/admin_mode_spec.rb +++ b/spec/features/admin/admin_mode_spec.rb @@ -2,7 +2,7 @@ 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 StubENV diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb index f08e6521184..405a254dc84 100644 --- a/spec/features/admin/admin_projects_spec.rb +++ b/spec/features/admin/admin_projects_spec.rb @@ -161,4 +161,29 @@ 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 end diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb index 04dc206f052..d9867c2e704 100644 --- a/spec/features/admin/admin_runners_spec.rb +++ b/spec/features/admin/admin_runners_spec.rb @@ -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 @@ -35,7 +33,7 @@ RSpec.describe "Admin Runners", feature_category: :runner_fleet do describe "runners registration" do before do - stub_feature_flags(create_runner_workflow: false) + stub_feature_flags(create_runner_workflow_for_admin: false) visit admin_runners_path end @@ -493,6 +491,29 @@ 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 + + context 'when runner is saved' do + before do + fill_in s_('Runners|Runner description'), with: 'runner-foo' + fill_in s_('Runners|Tags'), with: 'tag1' + click_on _('Submit') + wait_for_requests + end + + it 'navigates to registration page and opens install instructions drawer' do + expect(page.find('[data-testid="alert-success"]')).to have_content(s_('Runners|Runner created.')) + expect(current_url).to match(register_admin_runner_path(Ci::Runner.last)) + + click_on 'How do I install GitLab Runner?' + expect(page.find('[data-testid="runner-platforms-drawer"]')).to have_content('gitlab-runner install') + end + end + end + describe "Runner show page", :js do let_it_be(:runner) do create( 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..3a1aa36208e 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 @@ -666,11 +666,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 +762,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..342e23d0cab 100644 --- a/spec/features/admin/admin_users_impersonation_tokens_spec.rb +++ b/spec/features/admin/admin_users_impersonation_tokens_spec.rb @@ -2,7 +2,7 @@ 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 diff --git a/spec/features/admin_variables_spec.rb b/spec/features/admin_variables_spec.rb index d1adbf59984..274e62defd9 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: :pipeline_composition 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/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/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/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/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/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/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/dashboard/group_dashboard_with_external_authorization_service_spec.rb b/spec/features/dashboard/group_dashboard_with_external_authorization_service_spec.rb index f5b02a87758..a5d6ba58ffa 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 @@ -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..1fb393e1769 100644 --- a/spec/features/dashboard/groups_list_spec.rb +++ b/spec/features/dashboard/groups_list_spec.rb @@ -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/projects_spec.rb b/spec/features/dashboard/projects_spec.rb index 779fbb48ddb..eafc41c0f40 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 @@ -20,6 +20,12 @@ RSpec.describe 'Dashboard Projects', feature_category: :projects do it_behaves_like "a dashboard page with sidebar", :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 visit dashboard_projects_path @@ -239,7 +245,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 +253,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..f4234b433f8 100644 --- a/spec/features/dashboard/snippets_spec.rb +++ b/spec/features/dashboard/snippets_spec.rb @@ -7,6 +7,13 @@ RSpec.describe 'Dashboard snippets', feature_category: :source_code_management d it_behaves_like 'a dashboard page with sidebar', :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) } let!(:snippets) { create_list(:project_snippet, 2, :public, author: project.first_owner, project: project) } 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/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/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/frequently_visited_projects_and_groups_spec.rb b/spec/features/frequently_visited_projects_and_groups_spec.rb index 50e20910e16..19495230795 100644 --- a/spec/features/frequently_visited_projects_and_groups_spec.rb +++ b/spec/features/frequently_visited_projects_and_groups_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Frequently visited items', :js, feature_category: :not_owned do +RSpec.describe 'Frequently visited items', :js, feature_category: :shared do include Spec::Support::Helpers::Features::TopNavSpecHelpers let_it_be(:user) { create(:user) } diff --git a/spec/features/group_variables_spec.rb b/spec/features/group_variables_spec.rb index 117f50aefc6..8644a15a093 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: :pipeline_composition 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/group_settings_spec.rb b/spec/features/groups/group_settings_spec.rb index 5510e73ef0f..2aa70ec1953 100644 --- a/spec/features/groups/group_settings_spec.rb +++ b/spec/features/groups/group_settings_spec.rb @@ -147,14 +147,14 @@ 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 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 wait_for_requests - click_button target_group_name + click_button(target_group_name || 'No parent group') end click_button s_('GroupSettings|Transfer group') @@ -166,7 +166,10 @@ 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." + within('[data-testid="breadcrumb-links"]') do + expect(page).to have_content(target_group_name) if target_group_name + expect(page).to have_content(selected_group.name) + end expect(current_url).to include(selected_group.reload.full_path) end end @@ -175,7 +178,7 @@ 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_name) { nil } it_behaves_like 'can transfer the group' end 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/members/sort_members_spec.rb b/spec/features/groups/members/sort_members_spec.rb index 5634122ec16..fa5a14f18b4 100644 --- a/spec/features/groups/members/sort_members_spec.rb +++ b/spec/features/groups/members/sort_members_spec.rb @@ -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/new_group_page_spec.rb b/spec/features/groups/new_group_page_spec.rb index a07c27331d9..6d9513ce84f 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 dashboard page with sidebar', :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/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..a6c980f539c 100644 --- a/spec/features/groups/settings/packages_and_registries_spec.rb +++ b/spec/features/groups/settings/packages_and_registries_spec.rb @@ -56,6 +56,14 @@ 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"]' + end + it 'has a Duplicate packages section', :js do visit_settings_page 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/incidents/incident_timeline_events_spec.rb b/spec/features/incidents/incident_timeline_events_spec.rb index 7404ac64cc9..a4449ee2608 100644 --- a/spec/features/incidents/incident_timeline_events_spec.rb +++ b/spec/features/incidents/incident_timeline_events_spec.rb @@ -96,5 +96,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_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/invites_spec.rb b/spec/features/invites_spec.rb index 1091bea1ce3..cb7e933e472 100644 --- a/spec/features/invites_spec.rb +++ b/spec/features/invites_spec.rb @@ -244,9 +244,8 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures, feature_cate context 'the user sign-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 @@ -261,9 +260,9 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures, feature_cate 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/issues/discussion_lock_spec.rb b/spec/features/issues/discussion_lock_spec.rb index 33fc9a6fd96..47865d2b6ba 100644 --- a/spec/features/issues/discussion_lock_spec.rb +++ b/spec/features/issues/discussion_lock_spec.rb @@ -99,7 +99,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/form_spec.rb b/spec/features/issues/form_spec.rb index 585740f7782..fa5dd8c893c 100644 --- a/spec/features/issues/form_spec.rb +++ b/spec/features/issues/form_spec.rb @@ -13,6 +13,7 @@ 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 } @@ -477,14 +478,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' 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 + + describe 'when user has made changes' do + it 'shows a warning and can leave page' do + content = 'new issue content' + find('body').send_keys('e') + fill_in 'issue-description', with: content + + click_link 'Boards' - expect(page).to have_selector('.detail-page-description form') + page.driver.browser.switch_to.alert.accept + + expect(page).not_to have_content(content) + end + end end end diff --git a/spec/features/issues/issue_detail_spec.rb b/spec/features/issues/issue_detail_spec.rb index 20a69c61871..d5f90bb9260 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: '![xss" onload=alert(1);//](a)') diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb index 686074f7412..95277caf0f5 100644 --- a/spec/features/issues/issue_sidebar_spec.rb +++ b/spec/features/issues/issue_sidebar_spec.rb @@ -119,8 +119,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 diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb index ea68f2266b3..e2329e5e287 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,7 +106,6 @@ 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) diff --git a/spec/features/issues/user_comments_on_issue_spec.rb b/spec/features/issues/user_comments_on_issue_spec.rb index 59e1413fc97..145fa3c4a9e 100644 --- a/spec/features/issues/user_comments_on_issue_spec.rb +++ b/spec/features/issues/user_comments_on_issue_spec.rb @@ -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..6325f226ccf 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 @@ -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_edits_issue_spec.rb b/spec/features/issues/user_edits_issue_spec.rb index bf2af918f39..06c1b2afdb0 100644 --- a/spec/features/issues/user_edits_issue_spec.rb +++ b/spec/features/issues/user_edits_issue_spec.rb @@ -111,23 +111,29 @@ 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_on _('Viewing markdown') + click_on _('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_on _('Viewing rich text') + click_on _('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/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/merge_request/user_comments_on_diff_spec.rb b/spec/features/merge_request/user_comments_on_diff_spec.rb index 66b87148eb2..9ab53a00903 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) 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_edits_assignees_sidebar_spec.rb b/spec/features/merge_request/user_edits_assignees_sidebar_spec.rb index cf5024ad59e..becbf0ccfa7 100644 --- a/spec/features/merge_request/user_edits_assignees_sidebar_spec.rb +++ b/spec/features/merge_request/user_edits_assignees_sidebar_spec.rb @@ -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_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_reverts_merge_request_spec.rb b/spec/features/merge_request/user_reverts_merge_request_spec.rb index 43ce473b407..e09a4569caf 100644 --- a/spec/features/merge_request/user_reverts_merge_request_spec.rb +++ b/spec/features/merge_request/user_reverts_merge_request_spec.rb @@ -34,7 +34,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_discussions_navigation_spec.rb b/spec/features/merge_request/user_sees_discussions_navigation_spec.rb index 6e6c2cddfbf..06276d2a933 100644 --- a/spec/features/merge_request/user_sees_discussions_navigation_spec.rb +++ b/spec/features/merge_request/user_sees_discussions_navigation_spec.rb @@ -52,7 +52,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_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_views_open_merge_request_spec.rb b/spec/features/merge_request/user_views_open_merge_request_spec.rb index e481e3f2dfb..afa57cb0f8f 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) } 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/monitor_sidebar_link_spec.rb b/spec/features/monitor_sidebar_link_spec.rb index d5f987d15c2..c4114b28b47 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) } 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/top_nav_responsive_spec.rb b/spec/features/nav/top_nav_responsive_spec.rb index 56f9d373f00..9ac63c26ba0 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 Spec::Support::Helpers::Features::InviteMembersModalHelper let_it_be(:user) { create(:user) } @@ -20,7 +21,7 @@ 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_css('.page-title', text: 'Explore projects') expect(page).to have_link('Dashboard', id: 'logo') expect(page).to have_no_css('.top-nav-responsive') @@ -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_css('.page-title', text: 'Explore projects') expect(page).to have_no_link('Dashboard', 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..d2c0286cb4d 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 Spec::Support::Helpers::Features::InviteMembersModalHelper + 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/populate_new_pipeline_vars_with_params_spec.rb b/spec/features/populate_new_pipeline_vars_with_params_spec.rb index a83b5a81a41..b3ba0a874e9 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: :pipeline_composition 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..9e1bd69a239 100644 --- a/spec/features/profiles/chat_names_spec.rb +++ b/spec/features/profiles/chat_names_spec.rb @@ -3,8 +3,7 @@ require 'spec_helper' RSpec.describe 'Profile > Chat', feature_category: :user_profile do - let(:user) { create(:user) } - let(:integration) { create(:integration) } + let_it_be(:user) { create(:user) } before do sign_in(user) @@ -60,7 +59,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/user_creates_saved_reply_spec.rb b/spec/features/profiles/user_creates_saved_reply_spec.rb new file mode 100644 index 00000000000..1d851b5cea0 --- /dev/null +++ b/spec/features/profiles/user_creates_saved_reply_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Profile > Saved replies > User creates saved reply', :js, + feature_category: :user_profile do + let_it_be(:user) { create(:user) } + + before do + sign_in(user) + + visit profile_saved_replies_path + + wait_for_requests + end + + it 'shows the user a list of their saved replies' do + find('[data-testid="saved-reply-name-input"]').set('test') + find('[data-testid="saved-reply-content-input"]').set('Test content') + + click_button 'Save' + + wait_for_requests + + expect(page).to have_content('My saved replies (1)') + expect(page).to have_content('test') + expect(page).to have_content('Test content') + end +end diff --git a/spec/features/profiles/user_deletes_saved_reply_spec.rb b/spec/features/profiles/user_deletes_saved_reply_spec.rb new file mode 100644 index 00000000000..35bd6018ee3 --- /dev/null +++ b/spec/features/profiles/user_deletes_saved_reply_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Profile > Saved replies > User deletes saved reply', :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 saved replies' do + visit profile_saved_replies_path + + find('[data-testid="saved-reply-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_profile_spec.rb b/spec/features/profiles/user_edit_profile_spec.rb index 3819723cc09..196134a0bda 100644 --- a/spec/features/profiles/user_edit_profile_spec.rb +++ b/spec/features/profiles/user_edit_profile_spec.rb @@ -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 dicord id has a valid length', :js do + valid_dicord_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_dicord_id + + submit_settings + + expect(user.reload).to have_attributes( + discord: valid_dicord_id + ) + end + describe 'when I change my email', :js do before do user.send_reset_password_instructions diff --git a/spec/features/profiles/user_updates_saved_reply_spec.rb b/spec/features/profiles/user_updates_saved_reply_spec.rb new file mode 100644 index 00000000000..e341076ed0a --- /dev/null +++ b/spec/features/profiles/user_updates_saved_reply_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Profile > Saved replies > User updated saved reply', :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_saved_replies_path + + wait_for_requests + end + + it 'shows the user a list of their saved replies' do + find('[data-testid="saved-reply-edit-btn"]').click + find('[data-testid="saved-reply-name-input"]').set('test') + + click_button 'Save' + + wait_for_requests + + expect(page).to have_selector('[data-testid="saved-reply-name"]', text: 'test') + end +end diff --git a/spec/features/profiles/user_uses_saved_reply_spec.rb b/spec/features/profiles/user_uses_saved_reply_spec.rb new file mode 100644 index 00000000000..f9a4f4a7fa6 --- /dev/null +++ b/spec/features/profiles/user_uses_saved_reply_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'User uses saved reply', :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 saved reply' do + visit project_merge_request_path(merge_request.project, merge_request) + + find('[data-testid="saved-replies-dropdown-toggle"]').click + + wait_for_requests + + find('[data-testid="saved-reply-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..8d600edadde 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: :pipeline_composition 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..69b8408dcd6 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: :pipeline_composition 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..d3558af81b8 100644 --- a/spec/features/projects/blobs/blame_spec.rb +++ b/spec/features/projects/blobs/blame_spec.rb @@ -38,7 +38,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 @@ -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,42 @@ 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 disabled' do before do - visit_blob_blame(path) + stub_feature_flags(blame_page_streaming: false) 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' + end - expect(page).to have_css('#L1') - expect(page).to have_css('#L3') - expect(page).not_to have_css('.gl-pagination') - end + context 'when streaming is enabled' do + before do + stub_const('Projects::BlameService::STREAMING_PER_PAGE', 50) + end + + it_behaves_like 'a full blame page' + + it 'shows loading text' do + visit_blob_blame(path) + click_link _('Show full blame') + expect(page).to have_text('Loading full blame...') end end @@ -112,7 +135,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 diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb index 7faf0e1a6b1..f9e3ff1670c 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 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..ed03491d69a 100644 --- a/spec/features/projects/ci/editor_spec.rb +++ b/spec/features/projects/ci/editor_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Pipeline Editor', :js, feature_category: :pipeline_authoring do +RSpec.describe 'Pipeline Editor', :js, feature_category: :pipeline_composition do include Spec::Support::Helpers::Features::SourceEditorSpecHelpers let(:project) { create(:project_empty_repo, :public) } diff --git a/spec/features/projects/ci/lint_spec.rb b/spec/features/projects/ci/lint_spec.rb index 4fea07b18bc..aa9556761c6 100644 --- a/spec/features/projects/ci/lint_spec.rb +++ b/spec/features/projects/ci/lint_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'CI Lint', :js, feature_category: :pipeline_authoring do +RSpec.describe 'CI Lint', :js, feature_category: :pipeline_composition do include Spec::Support::Helpers::Features::SourceEditorSpecHelpers let_it_be(:project) { create(:project, :repository) } 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/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/integrations/apple_app_store_spec.rb b/spec/features/projects/integrations/apple_app_store_spec.rb new file mode 100644 index 00000000000..b6dc6557e20 --- /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(find("input[name='service[app_store_private_key]']", + visible: false).value).to eq(File.read(Rails.root.join('spec/fixtures/auth_key.p8'))) + expect(find("input[name='service[app_store_private_key_file_name]']", visible: false).value).to eq('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..5db4bc8809f --- /dev/null +++ b/spec/features/projects/integrations/google_play_spec.rb @@ -0,0 +1,25 @@ +# 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(find("input[name='service[service_account_key]']", + visible: false).value).to eq(File.read(Rails.root.join('spec/fixtures/service_account.json'))) + expect(find("input[name='service[service_account_key_file_name]']", + visible: false).value).to eq('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/user_activates_mattermost_slash_command_spec.rb b/spec/features/projects/integrations/user_activates_mattermost_slash_command_spec.rb index 16c7a3ff226..07cb138c414 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 @@ -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_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/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..07b8f8339eb 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -1065,16 +1065,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/members/sorting_spec.rb b/spec/features/projects/members/sorting_spec.rb index 6df1e974f42..78fad9b0b55 100644 --- a/spec/features/projects/members/sorting_spec.rb +++ b/spec/features/projects/members/sorting_spec.rb @@ -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/navbar_spec.rb b/spec/features/projects/navbar_spec.rb index 6090d132e3a..03ad5f9a292 100644 --- a/spec/features/projects/navbar_spec.rb +++ b/spec/features/projects/navbar_spec.rb @@ -22,6 +22,7 @@ RSpec.describe 'Project navbar', :with_license, feature_category: :projects do 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 diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb index c6a6ee68185..439ae4275ae 100644 --- a/spec/features/projects/new_project_spec.rb +++ b/spec/features/projects/new_project_spec.rb @@ -578,4 +578,53 @@ RSpec.describe 'New project', :js, feature_category: :projects do it_behaves_like 'has instructions to enable OAuth' end end + + describe 'sidebar' do + let_it_be(:user) { create(:user) } + let_it_be(:parent_group) { create(:group) } + + before do + parent_group.add_owner(user) + sign_in(user) + end + + 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 dashboard page with sidebar', :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 'in the new navigation' do + before do + parent_group.add_owner(user) + user.update!(use_new_navigation: true) + sign_in(user) + end + + 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/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..acb2af07e50 100644 --- a/spec/features/projects/pipeline_schedules_spec.rb +++ b/spec/features/projects/pipeline_schedules_spec.rb @@ -193,7 +193,7 @@ RSpec.describe 'Pipeline Schedules', :js, feature_category: :projects do save_pipeline_schedule end - it 'user sees the new variable in edit window' do + 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') diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index 343c7f53022..098d1201939 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -113,6 +113,50 @@ 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 + + it 'shows pipeline stats with flag off' do + stub_feature_flags(refactor_ci_minutes_consumption: false) + + visit project_pipeline_path(project, finished_pipeline) + + within '.pipeline-info' do + expect(page).to have_content("in #{finished_pipeline.duration} seconds " \ + "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 diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index b5f640f1cca..c46605fa9a8 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -278,6 +278,7 @@ RSpec.describe 'Pipelines', :js, feature_category: :projects do end before do + stub_feature_flags(lazy_load_pipeline_dropdown_actions: false) visit_project_pipelines end @@ -312,6 +313,7 @@ RSpec.describe 'Pipelines', :js, feature_category: :projects do end before do + stub_feature_flags(lazy_load_pipeline_dropdown_actions: false) visit_project_pipelines end @@ -695,7 +697,7 @@ 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') @@ -721,7 +723,7 @@ RSpec.describe 'Pipelines', :js, feature_category: :projects do 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 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/monitor_settings_spec.rb b/spec/features/projects/settings/monitor_settings_spec.rb index 4b553b57331..900f18bf49e 100644 --- a/spec/features/projects/settings/monitor_settings_spec.rb +++ b/spec/features/projects/settings/monitor_settings_spec.rb @@ -3,6 +3,8 @@ 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 } @@ -47,7 +49,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..57aa3a56c6d 100644 --- a/spec/features/projects/settings/registry_settings_cleanup_tags_spec.rb +++ b/spec/features/projects/settings/registry_settings_cleanup_tags_spec.rb @@ -38,6 +38,15 @@ feature_category: :projects do expect(section).to have_text 'Clean up image tags' 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 subject diff --git a/spec/features/projects/settings/registry_settings_spec.rb b/spec/features/projects/settings/registry_settings_spec.rb index 072b5f7f3b0..628fa23afdc 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 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/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..d0d458350b5 --- /dev/null +++ b/spec/features/projects/work_items/work_item_spec.rb @@ -0,0 +1,58 @@ +# 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) } + let_it_be(:milestone) { create(:milestone, project: project) } + let_it_be(:milestones) { create_list(:milestone, 25, project: project) } + + context 'for signed in user' do + before do + project.add_developer(user) + + sign_in(user) + end + + context 'with internal id' do + before do + visit project_work_items_path(project, work_items_path: work_item.iid, iid_path: true) + 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' + it_behaves_like 'work items description' + it_behaves_like 'work items milestone' + end + + context 'with global id' do + before do + stub_feature_flags(use_iid_in_work_items_path: false) + 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 + 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 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/search/user_uses_header_search_field_spec.rb b/spec/features/search/user_uses_header_search_field_spec.rb index 334a192bec4..127176da3fb 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-links') 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..541f94a9340 100644 --- a/spec/features/signed_commits_spec.rb +++ b/spec/features/signed_commits_spec.rb @@ -139,7 +139,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/show_spec.rb b/spec/features/snippets/show_spec.rb index dc2fcdd7305..d6ff8c066c4 100644 --- a/spec/features/snippets/show_spec.rb +++ b/spec/features/snippets/show_spec.rb @@ -28,11 +28,10 @@ RSpec.describe 'Snippet', :js, feature_category: :source_code_management do it_behaves_like 'a dashboard page with sidebar', :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 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/users/login_spec.rb b/spec/features/users/login_spec.rb index 5e683befeec..37b5d80ed61 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' @@ -207,8 +209,89 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_ describe 'with two-factor authentication', :js do def enter_code(code) - fill_in 'user_otp_attempt', with: code - click_button 'Verify code' + if page.has_content?("Sign in via 2FA code") + click_on("Sign in via 2FA code") + enter_code(code) + else + fill_in 'user_otp_attempt', with: code + click_button 'Verify code' + end + end + + shared_examples_for 'can login with recovery codes' do + 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 '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) + expect(page).to have_content('Invalid two-factor code.') + end + end + end end context 'with valid username/password' do @@ -216,8 +299,6 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_ 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 +371,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 + context 'when user with TOTP enabled' do + let(:user) { create(:user, :two_factor) } - 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 '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' end end @@ -379,8 +398,8 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_ 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,7 +413,7 @@ 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 @@ -408,7 +427,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) @@ -928,22 +947,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 +973,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..9aef3ed7cd6 100644 --- a/spec/features/users/show_spec.rb +++ b/spec/features/users/show_spec.rb @@ -149,7 +149,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 +159,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 + + it 'shows 1 followers and 1 following' do + follower.follow(user) + user.follow(followee) - expect(page).to have_content('0 followers') - expect(page).to have_content('0 following') + 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 diff --git a/spec/features/users/signup_spec.rb b/spec/features/users/signup_spec.rb index 11ff318c346..a762198d3c3 100644 --- a/spec/features/users/signup_spec.rb +++ b/spec/features/users/signup_spec.rb @@ -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 @@ -384,7 +383,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', :js 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..fbbc746c0b0 100644 --- a/spec/features/webauthn_spec.rb +++ b/spec/features/webauthn_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Using WebAuthn Devices for Authentication', :js, feature_category: :authentication_and_authorization do +RSpec.describe 'Using WebAuthn Devices for Authentication', :js, feature_category: :system_access do include Spec::Support::Helpers::Features::TwoFactorHelpers let(:app_id) { "http://#{Capybara.current_session.server.host}:#{Capybara.current_session.server.port}" } @@ -10,6 +10,113 @@ RSpec.describe 'Using WebAuthn Devices for Authentication', :js, feature_categor 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 |