diff --git a/.rubocop.yml b/.rubocop.yml
index 19f0b0b294fbd1413d2b01bac590c37706932d08..c4d26f6176fb64bb383533ae3ad2475f29f55115 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -701,3 +701,11 @@ RSpec/TopLevelDescribePath:
   Exclude:
     - 'spec/fixtures/**/*.rb'
     - 'ee/spec/fixtures/**/*.rb'
+
+QA/SelectorUsage:
+  Enabled: true
+  Include:
+    - 'spec/**/*.rb'
+    - 'ee/spec/**/*.rb'
+  Exclude:
+    - 'spec/rubocop/**/*_spec.rb'
diff --git a/ee/spec/features/admin/admin_audit_logs_spec.rb b/ee/spec/features/admin/admin_audit_logs_spec.rb
index 381259fe5eddeb71762cd7ae936bc513d667f0d1..cda4d39b56bf212c0dbe286daf0a2bf54ed9a55a 100644
--- a/ee/spec/features/admin/admin_audit_logs_spec.rb
+++ b/ee/spec/features/admin/admin_audit_logs_spec.rb
@@ -190,7 +190,7 @@
         click_link 'Impersonate'
 
         visit(new_project_path)
-        find('[data-qa-panel-name="blank_project"]').click
+        find('[data-qa-panel-name="blank_project"]').click # rubocop:disable QA/SelectorUsage
 
         fill_in(:project_name, with: 'Gotham City')
 
diff --git a/ee/spec/features/epics/delete_epic_spec.rb b/ee/spec/features/epics/delete_epic_spec.rb
index 690067bcfcd7db05b58a90ace2707692c8762018..830c684ceb9927cd31d328175715194020087a39 100644
--- a/ee/spec/features/epics/delete_epic_spec.rb
+++ b/ee/spec/features/epics/delete_epic_spec.rb
@@ -31,7 +31,7 @@
     end
 
     it 'deletes the issue and redirect to epic list' do
-      find('.qa-delete-button').click
+      find('.qa-delete-button').click # rubocop:disable QA/SelectorUsage
       wait_for_requests
 
       find('.js-modal-action-primary').click
diff --git a/ee/spec/features/groups/billing/seat_usage_spec.rb b/ee/spec/features/groups/billing/seat_usage_spec.rb
index 08efd2f275cc82c57c80c57e15cf033c09b4a5a8..cb69d1f45bc6cfb934900a788fce510e66936ab1 100644
--- a/ee/spec/features/groups/billing/seat_usage_spec.rb
+++ b/ee/spec/features/groups/billing/seat_usage_spec.rb
@@ -75,13 +75,13 @@
       end
 
       it 'has disabled the remove button' do
-        within '[data-qa-selector="remove_billable_member_modal"]' do
+        within '[data-qa-selector="remove_billable_member_modal"]' do # rubocop:disable QA/SelectorUsage
           expect(page).to have_button('Remove user', disabled: true)
         end
       end
 
       it 'enables the remove button when user enters valid username' do
-        within '[data-qa-selector="remove_billable_member_modal"]' do
+        within '[data-qa-selector="remove_billable_member_modal"]' do # rubocop:disable QA/SelectorUsage
           find('input').fill_in(with: maintainer.username)
           find('input').send_keys(:tab)
 
@@ -90,7 +90,7 @@
       end
 
       it 'does not enable button when user enters invalid username' do
-        within '[data-qa-selector="remove_billable_member_modal"]' do
+        within '[data-qa-selector="remove_billable_member_modal"]' do # rubocop:disable QA/SelectorUsage
           find('input').fill_in(with: 'invalid username')
           find('input').send_keys(:tab)
 
@@ -112,7 +112,7 @@
       end
 
       it 'shows a flash message' do
-        within '[data-qa-selector="remove_billable_member_modal"]' do
+        within '[data-qa-selector="remove_billable_member_modal"]' do # rubocop:disable QA/SelectorUsage
           find('input').fill_in(with: maintainer.username)
           find('input').send_keys(:tab)
 
@@ -138,7 +138,7 @@
 
           click_button('Remove member')
 
-          within '[data-qa-selector="remove_member_modal_content"]' do
+          within '[data-qa-selector="remove_member_modal_content"]' do # rubocop:disable QA/SelectorUsage
             click_button('Remove member')
           end
 
diff --git a/ee/spec/features/groups/navbar_spec.rb b/ee/spec/features/groups/navbar_spec.rb
index 5aaf97b13e60d3d17e6ef76524e342df56705103..17c3eae83da8e660110e8a0ff49221b7dff0730b 100644
--- a/ee/spec/features/groups/navbar_spec.rb
+++ b/ee/spec/features/groups/navbar_spec.rb
@@ -70,7 +70,7 @@
 
     it 'redirects to value stream when Analytics item is clicked' do
       page.within('.sidebar-top-level-items') do
-        find('[data-qa-selector=analytics_anchor]').click
+        find('[data-qa-selector=analytics_anchor]').click # rubocop:disable QA/SelectorUsage
       end
 
       wait_for_requests
diff --git a/ee/spec/features/projects/custom_projects_template_spec.rb b/ee/spec/features/projects/custom_projects_template_spec.rb
index 3e41a8040969647c1988ce9f57758122b7c57303..31ec9f09916159afe819fbcb144016cf88772455 100644
--- a/ee/spec/features/projects/custom_projects_template_spec.rb
+++ b/ee/spec/features/projects/custom_projects_template_spec.rb
@@ -43,7 +43,7 @@
         new_path = 'example-custom-project-template'
         new_name = 'Example Custom Project Template'
 
-        find('[data-qa-panel-name="create_from_template"]').click
+        find('[data-qa-panel-name="create_from_template"]').click # rubocop:disable QA/SelectorUsage
         find('.project-template .custom-instance-project-templates-tab').click
         find("label[for='#{projects.first.name}']").click
 
@@ -68,7 +68,7 @@
         new_path = 'example-custom-project-template'
         new_name = 'Example Custom Project Template'
 
-        find('[data-qa-panel-name="create_from_template"]').click
+        find('[data-qa-panel-name="create_from_template"]').click # rubocop:disable QA/SelectorUsage
         find('.project-template .custom-instance-project-templates-tab').click
         find("label[for='#{projects.first.name}']").click
 
@@ -90,7 +90,7 @@
         new_path = 'example-custom-project-template'
         new_name = 'Example Custom Project Template'
 
-        find('[data-qa-panel-name="create_from_template"]').click
+        find('[data-qa-panel-name="create_from_template"]').click # rubocop:disable QA/SelectorUsage
         find('.project-template .custom-instance-project-templates-tab').click
         find("label[for='#{projects.first.name}']").click
 
@@ -111,7 +111,7 @@
       it 'has a working pagination', :js do
         last_project = "label[for='#{projects.last.name}']"
 
-        find('[data-qa-panel-name="create_from_template"]').click
+        find('[data-qa-panel-name="create_from_template"]').click # rubocop:disable QA/SelectorUsage
         find('.project-template .custom-instance-project-templates-tab').click
 
         expect(page).to have_css('.custom-project-templates .gl-pagination')
diff --git a/ee/spec/features/projects/mirror_spec.rb b/ee/spec/features/projects/mirror_spec.rb
index 1242684b9ea724c1cd32d3f5dee12668c8161169..6a36982a996492b51f3c4cc1c29be360ad01b630 100644
--- a/ee/spec/features/projects/mirror_spec.rb
+++ b/ee/spec/features/projects/mirror_spec.rb
@@ -69,7 +69,7 @@
           import_state.update!(next_execution_timestamp: timestamp - 1.minute)
         end
 
-        let(:disabled_updating_button) { '[data-qa-selector="updating_button"].disabled' }
+        let(:disabled_updating_button) { '[data-qa-selector="updating_button"].disabled' } # rubocop:disable QA/SelectorUsage
 
         it 'disables Update now button' do
           travel_to(timestamp) do
@@ -81,7 +81,7 @@
       end
 
       context 'when the project is archived' do
-        let(:disabled_update_now_button) { '[data-qa-selector="update_now_button"].disabled' }
+        let(:disabled_update_now_button) { '[data-qa-selector="update_now_button"].disabled' } # rubocop:disable QA/SelectorUsage
 
         before do
           project.update!(archived: true)
diff --git a/ee/spec/features/projects/new_project_spec.rb b/ee/spec/features/projects/new_project_spec.rb
index 82520c6109b1ca35a54a81c45c64575459b00428..6aac37afdb2ba62ba52bc1560629c505ebfb8a5f 100644
--- a/ee/spec/features/projects/new_project_spec.rb
+++ b/ee/spec/features/projects/new_project_spec.rb
@@ -17,7 +17,7 @@
 
       it 'shows mirror repository checkbox enabled', :js do
         visit new_project_path
-        find('[data-qa-panel-name="import_project"]').click
+        find('[data-qa-panel-name="import_project"]').click # rubocop:disable QA/SelectorUsage
         first('.js-import-git-toggle-button').click
 
         expect(page).to have_unchecked_field('Mirror repository', disabled: false)
@@ -31,7 +31,7 @@
 
       it 'does not show mirror repository option' do
         visit new_project_path
-        find('[data-qa-panel-name="import_project"]').click
+        find('[data-qa-panel-name="import_project"]').click # rubocop:disable QA/SelectorUsage
         first('.js-import-git-toggle-button').click
 
         expect(page).not_to have_content('Mirror repository')
@@ -60,16 +60,16 @@
       it 'shows CI/CD tab and pane' do
         visit new_project_path
 
-        expect(page).to have_css('[data-qa-panel-name="cicd_for_external_repo"]')
+        expect(page).to have_css('[data-qa-panel-name="cicd_for_external_repo"]') # rubocop:disable QA/SelectorUsage
 
-        find('[data-qa-panel-name="cicd_for_external_repo"]').click
+        find('[data-qa-panel-name="cicd_for_external_repo"]').click # rubocop:disable QA/SelectorUsage
 
         expect(page).to have_css('#ci-cd-project-pane')
       end
 
       it '"Import project" tab creates projects with features enabled' do
         visit new_project_path
-        find('[data-qa-panel-name="import_project"]').click
+        find('[data-qa-panel-name="import_project"]').click # rubocop:disable QA/SelectorUsage
 
         page.within '#import-project-pane' do
           first('.js-import-git-toggle-button').click
@@ -89,7 +89,7 @@
 
       it 'creates CI/CD project from repo URL', :sidekiq_might_not_need_inline do
         visit new_project_path
-        find('[data-qa-panel-name="cicd_for_external_repo"]').click
+        find('[data-qa-panel-name="cicd_for_external_repo"]').click # rubocop:disable QA/SelectorUsage
 
         page.within '#ci-cd-project-pane' do
           find('.js-import-git-toggle-button').click
@@ -109,7 +109,7 @@
 
       it 'creates CI/CD project from GitHub' do
         visit new_project_path
-        find('[data-qa-panel-name="cicd_for_external_repo"]').click
+        find('[data-qa-panel-name="cicd_for_external_repo"]').click # rubocop:disable QA/SelectorUsage
 
         page.within '#ci-cd-project-pane' do
           find('.js-import-github').click
@@ -146,7 +146,7 @@
 
       it 'stays on GitHub import page after access token failure' do
         visit new_project_path
-        find('[data-qa-panel-name="cicd_for_external_repo"]').click
+        find('[data-qa-panel-name="cicd_for_external_repo"]').click # rubocop:disable QA/SelectorUsage
 
         page.within '#ci-cd-project-pane' do
           find('.js-import-github').click
@@ -170,7 +170,7 @@
       it 'does not show CI/CD only tab' do
         visit new_project_path
 
-        expect(page).not_to have_css('[data-qa-panel-name="cicd_for_external_repo"]')
+        expect(page).not_to have_css('[data-qa-panel-name="cicd_for_external_repo"]') # rubocop:disable QA/SelectorUsage
       end
     end
   end
@@ -458,7 +458,7 @@ def visit_create_from_group_template_tab
     def visit_create_from_built_in_templates_tab
       visit new_project_path
 
-      find('[data-qa-panel-name="create_from_template"]').click
+      find('[data-qa-panel-name="create_from_template"]').click # rubocop:disable QA/SelectorUsage
     end
   end
 end
diff --git a/ee/spec/features/projects_spec.rb b/ee/spec/features/projects_spec.rb
index 2fa257e22a656f2d575852290a9c368f0cc619f6..7a01822ec5b54202467b534a4d55b1529b88f753 100644
--- a/ee/spec/features/projects_spec.rb
+++ b/ee/spec/features/projects_spec.rb
@@ -19,7 +19,7 @@
 
     it "defaults to correct namespace" do
       visit new_project_path
-      find('[data-qa-selector="create_from_template_link"]').click
+      find('[data-qa-selector="create_from_template_link"]').click # rubocop:disable QA/SelectorUsage
       find('.custom-group-project-templates-tab').click
       find("label[for=#{template.name}]").click
 
@@ -28,7 +28,7 @@
 
     it "uses supplied namespace" do
       visit new_project_path(namespace_id: other_subgroup.id)
-      find('[data-qa-selector="create_from_template_link"]').click
+      find('[data-qa-selector="create_from_template_link"]').click # rubocop:disable QA/SelectorUsage
       find('.custom-group-project-templates-tab').click
       find("label[for=#{template.name}]").click
 
diff --git a/ee/spec/features/protected_branches_spec.rb b/ee/spec/features/protected_branches_spec.rb
index d3157a6e98b7d796efe1416d91cf47169c9ca4ee..6a716420e0e21c77dea523dd111418ba928f2111 100644
--- a/ee/spec/features/protected_branches_spec.rb
+++ b/ee/spec/features/protected_branches_spec.rb
@@ -98,7 +98,7 @@ def submit_form
           it 'displays toggle off' do
             visit project_settings_repository_path(project)
 
-            page.within '.qa-protected-branches-list' do
+            page.within '.qa-protected-branches-list' do # rubocop:disable QA/SelectorUsage
               expect(page).not_to have_css('.js-code-owner-toggle.is-checked')
             end
           end
diff --git a/ee/spec/support/shared_examples/features/protected_branches_access_control_shared_examples.rb b/ee/spec/support/shared_examples/features/protected_branches_access_control_shared_examples.rb
index b190bc743e83205535a4d2d6528e41b2f910b5dd..98431236cc890918d6191856edc4ca8df281eeda 100644
--- a/ee/spec/support/shared_examples/features/protected_branches_access_control_shared_examples.rb
+++ b/ee/spec/support/shared_examples/features/protected_branches_access_control_shared_examples.rb
@@ -179,9 +179,9 @@ def last_access_levels(git_operation)
     it 'unprotect/delete can be performed by a maintainer' do
       visit project_protected_branches_path(project)
 
-      expect(page).to have_selector('.qa-protected-branch')
+      expect(page).to have_selector('.qa-protected-branch') # rubocop:disable QA/SelectorUsage
       accept_alert { click_on 'Unprotect' }
-      expect(page).not_to have_selector('.qa-protected-branch')
+      expect(page).not_to have_selector('.qa-protected-branch') # rubocop:disable QA/SelectorUsage
     end
 
     context 'with unprotect access levels' do
diff --git a/ee/spec/views/groups/settings/_transfer.html.haml_spec.rb b/ee/spec/views/groups/settings/_transfer.html.haml_spec.rb
index 81a69ba4805dfcf1c4e90b2725e640367bd9986c..a7ea421dafb471ebb041d95e029b0875778bf91a 100644
--- a/ee/spec/views/groups/settings/_transfer.html.haml_spec.rb
+++ b/ee/spec/views/groups/settings/_transfer.html.haml_spec.rb
@@ -9,8 +9,8 @@
     it 'enables the Select parent group dropdown and does not show an alert for a group' do
       render 'groups/settings/transfer', group: group
 
-      expect(rendered).to have_selector '[data-qa-selector="select_group_dropdown"]'
-      expect(rendered).not_to have_selector '[data-qa-selector="select_group_dropdown"][disabled]'
+      expect(rendered).to have_selector '[data-qa-selector="select_group_dropdown"]' # rubocop:disable QA/SelectorUsage
+      expect(rendered).not_to have_selector '[data-qa-selector="select_group_dropdown"][disabled]' # rubocop:disable QA/SelectorUsage
       expect(rendered).not_to have_selector '[data-testid="group-to-transfer-has-linked-subscription-alert"]'
     end
 
@@ -19,7 +19,7 @@
 
       render 'groups/settings/transfer', group: group
 
-      expect(rendered).to have_selector '[data-qa-selector="select_group_dropdown"][disabled]'
+      expect(rendered).to have_selector '[data-qa-selector="select_group_dropdown"][disabled]' # rubocop:disable QA/SelectorUsage
       expect(rendered).to have_selector '[data-testid="group-to-transfer-has-linked-subscription-alert"]'
     end
 
@@ -29,8 +29,8 @@
 
       render 'groups/settings/transfer', group: subgroup
 
-      expect(rendered).to have_selector '[data-qa-selector="select_group_dropdown"]'
-      expect(rendered).not_to have_selector '[data-qa-selector="select_group_dropdown"][disabled]'
+      expect(rendered).to have_selector '[data-qa-selector="select_group_dropdown"]' # rubocop:disable QA/SelectorUsage
+      expect(rendered).not_to have_selector '[data-qa-selector="select_group_dropdown"][disabled]' # rubocop:disable QA/SelectorUsage
       expect(rendered).not_to have_selector '[data-testid="group-to-transfer-has-linked-subscription-alert"]'
     end
   end
diff --git a/rubocop/cop/qa/selector_usage.rb b/rubocop/cop/qa/selector_usage.rb
new file mode 100644
index 0000000000000000000000000000000000000000..568b1c308518657a1318cae9d8a8ccb9bffc36a5
--- /dev/null
+++ b/rubocop/cop/qa/selector_usage.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require_relative '../../qa_helpers'
+require_relative '../../code_reuse_helpers'
+
+module RuboCop
+  module Cop
+    module QA
+      # This cop checks for the usage of data-qa-selectors or .qa-* classes in non-QA files
+      #
+      # @example
+      #   # bad
+      #   find('[data-qa-selector="the_selector"]')
+      #   find('.qa-selector')
+      #
+      #   # good
+      #   find('[data-testid="the_selector"]')
+      #   find('#selector')
+      class SelectorUsage < RuboCop::Cop::Cop
+        include QAHelpers
+        include CodeReuseHelpers
+
+        SELECTORS = /\.qa-\w+|data-qa-\w+/.freeze
+        MESSAGE = %(Do not use `%s` as this is reserved for the end-to-end specs. Use a different selector or a data-testid instead.)
+
+        def on_str(node)
+          return if in_qa_file?(node)
+          return unless in_spec?(node)
+
+          add_offense(node, message: MESSAGE % node.value) if SELECTORS =~ node.value
+        rescue StandardError
+          # catch all errors and ignore them.
+          # without this catch-all rescue, rubocop will fail
+          # because of non-UTF-8 characters in some Strings
+        end
+      end
+    end
+  end
+end
diff --git a/spec/features/admin/admin_appearance_spec.rb b/spec/features/admin/admin_appearance_spec.rb
index b96762ec6ad9ebe7122c98220e8980c289b2e62e..cd148642b9075184bb8ab827616bd41e7aafd88d 100644
--- a/spec/features/admin/admin_appearance_spec.rb
+++ b/spec/features/admin/admin_appearance_spec.rb
@@ -90,7 +90,7 @@
     sign_in(admin)
     gitlab_enable_admin_mode_sign_in(admin)
     visit new_project_path
-    find('[data-qa-panel-name="blank_project"]').click
+    find('[data-qa-panel-name="blank_project"]').click # rubocop:disable QA/SelectorUsage
 
     expect_custom_new_project_appearance(appearance)
   end
diff --git a/spec/features/admin/admin_groups_spec.rb b/spec/features/admin/admin_groups_spec.rb
index 7d7b2baf9413337147ade79225d51118900730d1..8315b8f44b02225030d5a8a441180abb788cf0d1 100644
--- a/spec/features/admin/admin_groups_spec.rb
+++ b/spec/features/admin/admin_groups_spec.rb
@@ -256,7 +256,7 @@
 
       visit group_group_members_path(group)
 
-      page.within '[data-qa-selector="members_list"]' do
+      page.within '[data-qa-selector="members_list"]' do # rubocop:disable QA/SelectorUsage
         expect(page).to have_content(current_user.name)
         expect(page).to have_content('Developer')
       end
@@ -265,7 +265,7 @@
 
       visit group_group_members_path(group)
 
-      page.within '[data-qa-selector="members_list"]' do
+      page.within '[data-qa-selector="members_list"]' do # rubocop:disable QA/SelectorUsage
         expect(page).not_to have_content(current_user.name)
         expect(page).not_to have_content('Developer')
       end
diff --git a/spec/features/admin/users/user_spec.rb b/spec/features/admin/users/user_spec.rb
index c2033913a6766886762fb2e12450dd855e816f06..624bfde7359823bd7b106b5368aeab32caed6d04 100644
--- a/spec/features/admin/users/user_spec.rb
+++ b/spec/features/admin/users/user_spec.rb
@@ -184,7 +184,7 @@
         it 'logs in as the user when impersonate is clicked' do
           subject
 
-          find('[data-qa-selector="user_menu"]').click
+          find('[data-qa-selector="user_menu"]').click # rubocop:disable QA/SelectorUsage
 
           expect(page.find(:css, '[data-testid="user-profile-link"]')['data-user']).to eql(another_user.username)
         end
@@ -220,7 +220,7 @@
         it 'logs out of impersonated user back to original user' do
           subject
 
-          find('[data-qa-selector="user_menu"]').click
+          find('[data-qa-selector="user_menu"]').click # rubocop:disable QA/SelectorUsage
 
           expect(page.find(:css, '[data-testid="user-profile-link"]')['data-user']).to eq(current_user.username)
         end
diff --git a/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb b/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb
index a4c0a84af7dea84f21600cc494f0d2883c71d92c..077c363f78b5022215ae2bfaad45e68f2b0b6750 100644
--- a/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb
+++ b/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb
@@ -33,7 +33,7 @@ def resolve_all_discussions_link_selector(title: "")
 
     context 'resolving the thread' do
       before do
-        find('button[data-qa-selector="resolve_discussion_button"]').click
+        find('button[data-qa-selector="resolve_discussion_button"]').click # rubocop:disable QA/SelectorUsage
       end
 
       it 'hides the link for creating a new issue' do
diff --git a/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb b/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb
index ac3471e8401e57470e5aaed012dd3464cc6059ef..3ff8fc5ecca64ee59c0b1e2e065ec2c06eb7dbff 100644
--- a/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb
+++ b/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb
@@ -35,7 +35,7 @@ def resolve_discussion_selector
 
     context 'resolving the thread' do
       before do
-        find('button[data-qa-selector="resolve_discussion_button"]').click
+        find('button[data-qa-selector="resolve_discussion_button"]').click # rubocop:disable QA/SelectorUsage
       end
 
       it 'hides the link for creating a new issue' do
diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb
index 0e2ef5cc6ebbc19bea0073b2119d730a3d0a31c1..e198d9d4ebb6e055c9aae25a114376e7643a7aa9 100644
--- a/spec/features/issues/issue_sidebar_spec.rb
+++ b/spec/features/issues/issue_sidebar_spec.rb
@@ -408,7 +408,7 @@
 
       context 'sidebar', :js do
         it 'finds issue copy forwarding email' do
-          expect(find('[data-qa-selector="copy-forward-email"]').text).to eq "Issue email: #{issue.creatable_note_email_address(user)}"
+          expect(find('[data-qa-selector="copy-forward-email"]').text).to eq "Issue email: #{issue.creatable_note_email_address(user)}" # rubocop:disable QA/SelectorUsage
         end
       end
 
@@ -444,7 +444,7 @@
       end
 
       it 'does not find issue email' do
-        expect(page).not_to have_selector('[data-qa-selector="copy-forward-email"]')
+        expect(page).not_to have_selector('[data-qa-selector="copy-forward-email"]') # rubocop:disable QA/SelectorUsage
       end
     end
   end
diff --git a/spec/features/issues/user_creates_issue_spec.rb b/spec/features/issues/user_creates_issue_spec.rb
index 4a77e850d5178190f7d910aeae41b9683eb32218..f46aa5c21b65ff1af4e457be6022090e1e88c074 100644
--- a/spec/features/issues/user_creates_issue_spec.rb
+++ b/spec/features/issues/user_creates_issue_spec.rb
@@ -182,7 +182,7 @@
       end
 
       it 'does not hide the milestone select' do
-        expect(page).to have_selector('.qa-issuable-milestone-dropdown')
+        expect(page).to have_selector('.qa-issuable-milestone-dropdown') # rubocop:disable QA/SelectorUsage
       end
     end
 
@@ -202,11 +202,11 @@
       end
 
       it 'shows the milestone select' do
-        expect(page).to have_selector('.qa-issuable-milestone-dropdown')
+        expect(page).to have_selector('.qa-issuable-milestone-dropdown') # rubocop:disable QA/SelectorUsage
       end
 
       it 'hides the weight input' do
-        expect(page).not_to have_selector('.qa-issuable-weight-input')
+        expect(page).not_to have_selector('.qa-issuable-weight-input') # rubocop:disable QA/SelectorUsage
       end
 
       it 'shows the incident help text' do
diff --git a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
index 73e628bda98221c4b1af3d8b31f4020b6f420b7f..8343e04aef159a871e21bfcbdd4400f985f56697 100644
--- a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
+++ b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
@@ -63,7 +63,7 @@
 
       it 'allows user to mark thread as resolved' do
         page.within '.diff-content' do
-          find('button[data-qa-selector="resolve_discussion_button"]').click
+          find('button[data-qa-selector="resolve_discussion_button"]').click # rubocop:disable QA/SelectorUsage
         end
 
         expect(page).to have_selector('.discussion-body', visible: false)
@@ -80,7 +80,7 @@
 
       it 'allows user to unresolve thread' do
         page.within '.diff-content' do
-          find('button[data-qa-selector="resolve_discussion_button"]').click
+          find('button[data-qa-selector="resolve_discussion_button"]').click # rubocop:disable QA/SelectorUsage
           click_button 'Unresolve thread'
         end
 
@@ -92,7 +92,7 @@
       describe 'resolved thread' do
         before do
           page.within '.diff-content' do
-            find('button[data-qa-selector="resolve_discussion_button"]').click
+            find('button[data-qa-selector="resolve_discussion_button"]').click # rubocop:disable QA/SelectorUsage
           end
 
           visit_merge_request
@@ -193,7 +193,7 @@
 
       it 'allows user to resolve from reply form without a comment' do
         page.within '.diff-content' do
-          find('button[data-qa-selector="resolve_discussion_button"]').click
+          find('button[data-qa-selector="resolve_discussion_button"]').click # rubocop:disable QA/SelectorUsage
         end
 
         page.within '.line-resolve-all-container' do
@@ -230,7 +230,7 @@
 
       it 'hides jump to next button when all resolved' do
         page.within '.diff-content' do
-          find('button[data-qa-selector="resolve_discussion_button"]').click
+          find('button[data-qa-selector="resolve_discussion_button"]').click # rubocop:disable QA/SelectorUsage
         end
 
         expect(page).to have_selector('.discussion-next-btn', visible: false)
@@ -326,7 +326,7 @@
       it 'allows user to mark all threads as resolved' do
         page.all('.discussion-reply-holder', count: 2).each do |reply_holder|
           page.within reply_holder do
-            find('button[data-qa-selector="resolve_discussion_button"]').click
+            find('button[data-qa-selector="resolve_discussion_button"]').click # rubocop:disable QA/SelectorUsage
           end
         end
 
@@ -338,7 +338,7 @@
 
       it 'allows user to quickly scroll to next unresolved thread' do
         page.within('.discussion-reply-holder', match: :first) do
-          find('button[data-qa-selector="resolve_discussion_button"]').click
+          find('button[data-qa-selector="resolve_discussion_button"]').click # rubocop:disable QA/SelectorUsage
         end
 
         page.within '.line-resolve-all-container' do
@@ -410,7 +410,7 @@
 
       it 'allows user to mark thread as resolved' do
         page.within '.diff-content' do
-          find('button[data-qa-selector="resolve_discussion_button"]').click
+          find('button[data-qa-selector="resolve_discussion_button"]').click # rubocop:disable QA/SelectorUsage
         end
 
         page.within '.diff-content .note' do
@@ -425,7 +425,7 @@
 
       it 'allows user to unresolve thread' do
         page.within '.diff-content' do
-          find('button[data-qa-selector="resolve_discussion_button"]').click
+          find('button[data-qa-selector="resolve_discussion_button"]').click # rubocop:disable QA/SelectorUsage
           click_button 'Unresolve thread'
         end
 
@@ -453,7 +453,7 @@
 
       it 'allows user to comment & unresolve thread' do
         page.within '.diff-content' do
-          find('button[data-qa-selector="resolve_discussion_button"]').click
+          find('button[data-qa-selector="resolve_discussion_button"]').click # rubocop:disable QA/SelectorUsage
 
           find_field('Reply…').click
 
diff --git a/spec/features/project_variables_spec.rb b/spec/features/project_variables_spec.rb
index 62565eaabe1fa7446d0ce1ff4b78a870196e8a28..5139c724d826c454372135ef9db751bcc822f234 100644
--- a/spec/features/project_variables_spec.rb
+++ b/spec/features/project_variables_spec.rb
@@ -21,7 +21,7 @@
     click_button('Add variable')
 
     page.within('#add-ci-variable') do
-      find('[data-qa-selector="ci_variable_key_field"] input').set('akey')
+      find('[data-qa-selector="ci_variable_key_field"] input').set('akey') # rubocop:disable QA/SelectorUsage
       find('#ci-variable-value').set('akey_value')
       find('[data-testid="environment-scope"]').click
       find('[data-testid="ci-environment-search"]').set('review/*')
diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb
index b6b9a8ae96270ae6e654259893d8bdab7ba1f5d2..8281e82959ba4c5d377dd9377282ad7b7df57e5e 100644
--- a/spec/features/projects/blobs/blob_show_spec.rb
+++ b/spec/features/projects/blobs/blob_show_spec.rb
@@ -137,7 +137,7 @@ def create_file(file_name, content)
 
     context 'when ref switch' do
       def switch_ref_to(ref_name)
-        first('.qa-branches-select').click
+        first('.qa-branches-select').click # rubocop:disable QA/SelectorUsage
 
         page.within '.project-refs-form' do
           click_link ref_name
diff --git a/spec/features/projects/environments/environment_metrics_spec.rb b/spec/features/projects/environments/environment_metrics_spec.rb
index ee5afb22109922c2385f5ea36f6a8cf0d380ede5..0f858c627bcaff216961388291681a8b9598effd 100644
--- a/spec/features/projects/environments/environment_metrics_spec.rb
+++ b/spec/features/projects/environments/environment_metrics_spec.rb
@@ -30,9 +30,9 @@
       click_link 'Monitoring'
 
       expect(page).to have_current_path(project_metrics_dashboard_path(project, environment: environment.id))
-      expect(page).to have_css('[data-qa-selector="environments_dropdown"]')
+      expect(page).to have_css('[data-qa-selector="environments_dropdown"]') # rubocop:disable QA/SelectorUsage
 
-      within('[data-qa-selector="environments_dropdown"]') do
+      within('[data-qa-selector="environments_dropdown"]') do # rubocop:disable QA/SelectorUsage
         # Click on the dropdown
         click_on(environment.name)
 
@@ -58,7 +58,7 @@
     it 'shows metrics', :js do
       click_link 'Monitoring'
 
-      expect(page).to have_css('[data-qa-selector="prometheus_graphs"]')
+      expect(page).to have_css('[data-qa-selector="prometheus_graphs"]') # rubocop:disable QA/SelectorUsage
     end
 
     it_behaves_like 'has environment selector'
diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb
index 0dd4bd55d46cf4db82d9d64e787a2b1089bdb84f..9413fae02e0f5403d8e5b0812600688377cea965 100644
--- a/spec/features/projects/environments/environments_spec.rb
+++ b/spec/features/projects/environments/environments_spec.rb
@@ -455,10 +455,10 @@ def upcoming_deployment_content_selector
         expect(page).to have_content 'review-1'
         expect(page).to have_content 'review-2'
         within('.ci-table') do
-          within('[data-qa-selector="environment_item"]', text: 'review-1') do
+          within('[data-qa-selector="environment_item"]', text: 'review-1') do # rubocop:disable QA/SelectorUsage
             expect(find('.js-auto-stop').text).not_to be_empty
           end
-          within('[data-qa-selector="environment_item"]', text: 'review-2') do
+          within('[data-qa-selector="environment_item"]', text: 'review-2') do # rubocop:disable QA/SelectorUsage
             expect(find('.js-auto-stop').text).not_to be_empty
           end
         end
diff --git a/spec/features/projects/environments_pod_logs_spec.rb b/spec/features/projects/environments_pod_logs_spec.rb
index 5019e45593c5fe57019a55d422c8708fa590ff52..7d31de2b4184d1d3619584ac6f00c4b608e444e2 100644
--- a/spec/features/projects/environments_pod_logs_spec.rb
+++ b/spec/features/projects/environments_pod_logs_spec.rb
@@ -50,7 +50,7 @@
 
       wait_for_requests
 
-      page.within('.qa-pods-dropdown') do
+      page.within('.qa-pods-dropdown') do # rubocop:disable QA/SelectorUsage
         find(".dropdown-toggle:not([disabled])").click
 
         dropdown_items = find(".dropdown-menu").all(".dropdown-item:not([disabled])")
diff --git a/spec/features/projects/feature_flags/user_sees_feature_flag_list_spec.rb b/spec/features/projects/feature_flags/user_sees_feature_flag_list_spec.rb
index 30bfcb645f4dbb3469acc4de1bae1907a6f182f7..221f07a2f75a67d9e3120666104c4f435bcbd5f0 100644
--- a/spec/features/projects/feature_flags/user_sees_feature_flag_list_spec.rb
+++ b/spec/features/projects/feature_flags/user_sees_feature_flag_list_spec.rb
@@ -91,7 +91,7 @@
     it 'shows the empty page' do
       expect(page).to have_text 'Get started with feature flags'
       expect(page).to have_selector('.btn-confirm', text: 'New feature flag')
-      expect(page).to have_selector('[data-qa-selector="configure_feature_flags_button"]', text: 'Configure')
+      expect(page).to have_selector('[data-qa-selector="configure_feature_flags_button"]', text: 'Configure') # rubocop:disable QA/SelectorUsage
     end
   end
 end
diff --git a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
index 42f8daf9d5e9a4d881c23c6c71d1c55218a2ad34..37583870cfd857b814ce8b93e3fc5d49576255ac 100644
--- a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
+++ b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
@@ -18,7 +18,7 @@
 
     expect(current_path).to eq("/-/ide/project/#{project.full_path}/edit/master/-/LICENSE")
 
-    expect(page).to have_selector('.qa-file-templates-bar')
+    expect(page).to have_selector('.qa-file-templates-bar') # rubocop:disable QA/SelectorUsage
 
     select_template('MIT License')
 
diff --git a/spec/features/projects/fork_spec.rb b/spec/features/projects/fork_spec.rb
index 9a6d1961a022c3c1b1b0581834306384474ec712..69e4303cce7c5d52068909746f763d749dd643f5 100644
--- a/spec/features/projects/fork_spec.rb
+++ b/spec/features/projects/fork_spec.rb
@@ -181,8 +181,8 @@ def submit_form
         it 'allows user to fork only to the group on fork page', :js do
           visit new_project_fork_path(project)
 
-          to_personal_namespace = find('[data-qa-selector=fork_namespace_button].disabled')
-          to_group = find(".fork-groups button[data-qa-name=#{group.name}]")
+          to_personal_namespace = find('[data-qa-selector=fork_namespace_button].disabled') # rubocop:disable QA/SelectorUsage
+          to_group = find(".fork-groups button[data-qa-name=#{group.name}]") # rubocop:disable QA/SelectorUsage
 
           expect(to_personal_namespace).not_to be_nil
           expect(to_group).not_to be_disabled
diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb
index a4c57e83bdd1895d673ade2b533ea367604a8b85..302187917b79faa8c5a4a0860999918eb463deba 100644
--- a/spec/features/projects/import_export/import_file_spec.rb
+++ b/spec/features/projects/import_export/import_file_spec.rb
@@ -62,6 +62,6 @@
   end
 
   def click_import_project
-    find('[data-qa-panel-name="import_project"]').click
+    find('[data-qa-panel-name="import_project"]').click # rubocop:disable QA/SelectorUsage
   end
 end
diff --git a/spec/features/projects/members/invite_group_spec.rb b/spec/features/projects/members/invite_group_spec.rb
index 6ce6834b5d5314fcd96bcea29bb81915ccda0d9b..8c3646125a50f8f097f7fbc36ae474a543de9977 100644
--- a/spec/features/projects/members/invite_group_spec.rb
+++ b/spec/features/projects/members/invite_group_spec.rb
@@ -13,7 +13,7 @@
   using RSpec::Parameterized::TableSyntax
 
   where(:invite_members_group_modal_enabled, :expected_invite_group_selector) do
-    true  | 'button[data-qa-selector="invite_a_group_button"]'
+    true  | 'button[data-qa-selector="invite_a_group_button"]' # rubocop:disable QA/SelectorUsage
     false | '#invite-group-tab'
   end
 
@@ -43,7 +43,7 @@
   end
 
   describe 'Share with group lock' do
-    let(:invite_group_selector) { 'button[data-qa-selector="invite_a_group_button"]' }
+    let(:invite_group_selector) { 'button[data-qa-selector="invite_a_group_button"]' } # rubocop:disable QA/SelectorUsage
 
     shared_examples 'the project can be shared with groups' do
       it 'the "Invite a group" button exists' do
diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb
index b0134c848c72cbefe95b7af6a77f52b22f984a85..0b293970703016d60823c75379929265fc2b3a8b 100644
--- a/spec/features/projects/new_project_spec.rb
+++ b/spec/features/projects/new_project_spec.rb
@@ -19,7 +19,7 @@
       )
 
       visit new_project_path
-      find('[data-qa-panel-name="blank_project"]').click
+      find('[data-qa-panel-name="blank_project"]').click # rubocop:disable QA/SelectorUsage
 
       expect(page).to have_content 'Other visibility settings have been disabled by the administrator.'
     end
@@ -30,7 +30,7 @@
       )
 
       visit new_project_path
-      find('[data-qa-panel-name="blank_project"]').click
+      find('[data-qa-panel-name="blank_project"]').click # rubocop:disable QA/SelectorUsage
 
       expect(page).to have_content 'Visibility settings have been disabled by the administrator.'
     end
@@ -45,14 +45,14 @@
 
     it 'shows "New project" page', :js do
       visit new_project_path
-      find('[data-qa-panel-name="blank_project"]').click
+      find('[data-qa-panel-name="blank_project"]').click # rubocop:disable QA/SelectorUsage
 
       expect(page).to have_content('Project name')
       expect(page).to have_content('Project URL')
       expect(page).to have_content('Project slug')
 
       click_link('New project')
-      find('[data-qa-panel-name="import_project"]').click
+      find('[data-qa-panel-name="import_project"]').click # rubocop:disable QA/SelectorUsage
 
       expect(page).to have_link('GitHub')
       expect(page).to have_link('Bitbucket')
@@ -65,7 +65,7 @@
       before do
         visit new_project_path
 
-        find('[data-qa-panel-name="import_project"]').click
+        find('[data-qa-panel-name="import_project"]').click # rubocop:disable QA/SelectorUsage
       end
 
       it 'has Manifest file' do
@@ -79,7 +79,7 @@
           stub_application_setting(default_project_visibility: level)
 
           visit new_project_path
-          find('[data-qa-panel-name="blank_project"]').click
+          find('[data-qa-panel-name="blank_project"]').click # rubocop:disable QA/SelectorUsage
           page.within('#blank-project-pane') do
             expect(find_field("project_visibility_level_#{level}")).to be_checked
           end
@@ -87,7 +87,7 @@
 
         it "saves visibility level #{level} on validation error" do
           visit new_project_path
-          find('[data-qa-panel-name="blank_project"]').click
+          find('[data-qa-panel-name="blank_project"]').click # rubocop:disable QA/SelectorUsage
 
           choose(key)
           click_button('Create project')
@@ -107,7 +107,7 @@
         context 'when admin mode is enabled', :enable_admin_mode do
           it 'has private selected' do
             visit new_project_path(namespace_id: group.id)
-            find('[data-qa-panel-name="blank_project"]').click
+            find('[data-qa-panel-name="blank_project"]').click # rubocop:disable QA/SelectorUsage
 
             page.within('#blank-project-pane') do
               expect(find_field("project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).to be_checked
@@ -134,7 +134,7 @@
         context 'when admin mode is enabled', :enable_admin_mode do
           it 'has private selected' do
             visit new_project_path(namespace_id: group.id, project: { visibility_level: Gitlab::VisibilityLevel::PRIVATE })
-            find('[data-qa-panel-name="blank_project"]').click
+            find('[data-qa-panel-name="blank_project"]').click # rubocop:disable QA/SelectorUsage
 
             page.within('#blank-project-pane') do
               expect(find_field("project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).to be_checked
@@ -155,7 +155,7 @@
     context 'Readme selector' do
       it 'shows the initialize with Readme checkbox on "Blank project" tab' do
         visit new_project_path
-        find('[data-qa-panel-name="blank_project"]').click
+        find('[data-qa-panel-name="blank_project"]').click # rubocop:disable QA/SelectorUsage
 
         expect(page).to have_css('input#project_initialize_with_readme')
         expect(page).to have_content('Initialize repository with a README')
@@ -163,7 +163,7 @@
 
       it 'does not show the initialize with Readme checkbox on "Create from template" tab' do
         visit new_project_path
-        find('[data-qa-panel-name="create_from_template"]').click
+        find('[data-qa-panel-name="create_from_template"]').click # rubocop:disable QA/SelectorUsage
         first('.choose-template').click
 
         page.within '.project-fields-form' do
@@ -174,7 +174,7 @@
 
       it 'does not show the initialize with Readme checkbox on "Import project" tab' do
         visit new_project_path
-        find('[data-qa-panel-name="import_project"]').click
+        find('[data-qa-panel-name="import_project"]').click # rubocop:disable QA/SelectorUsage
         first('.js-import-git-toggle-button').click
 
         page.within '#import-project-pane' do
@@ -188,7 +188,7 @@
       context 'with user namespace' do
         before do
           visit new_project_path
-          find('[data-qa-panel-name="blank_project"]').click
+          find('[data-qa-panel-name="blank_project"]').click # rubocop:disable QA/SelectorUsage
         end
 
         it 'selects the user namespace' do
@@ -204,7 +204,7 @@
         before do
           group.add_owner(user)
           visit new_project_path(namespace_id: group.id)
-          find('[data-qa-panel-name="blank_project"]').click
+          find('[data-qa-panel-name="blank_project"]').click # rubocop:disable QA/SelectorUsage
         end
 
         it 'selects the group namespace' do
@@ -221,7 +221,7 @@
         before do
           group.add_maintainer(user)
           visit new_project_path(namespace_id: subgroup.id)
-          find('[data-qa-panel-name="blank_project"]').click
+          find('[data-qa-panel-name="blank_project"]').click # rubocop:disable QA/SelectorUsage
         end
 
         it 'selects the group namespace' do
@@ -241,7 +241,7 @@
           internal_group.add_owner(user)
           private_group.add_owner(user)
           visit new_project_path(namespace_id: public_group.id)
-          find('[data-qa-panel-name="blank_project"]').click
+          find('[data-qa-panel-name="blank_project"]').click # rubocop:disable QA/SelectorUsage
         end
 
         it 'enables the correct visibility options' do
@@ -271,7 +271,7 @@
     context 'Import project options', :js do
       before do
         visit new_project_path
-        find('[data-qa-panel-name="import_project"]').click
+        find('[data-qa-panel-name="import_project"]').click # rubocop:disable QA/SelectorUsage
       end
 
       context 'from git repository url, "Repo by URL"' do
@@ -343,7 +343,7 @@
         before do
           group.add_developer(user)
           visit new_project_path(namespace_id: group.id)
-          find('[data-qa-panel-name="blank_project"]').click
+          find('[data-qa-panel-name="blank_project"]').click # rubocop:disable QA/SelectorUsage
         end
 
         it 'selects the group namespace' do
diff --git a/spec/features/projects/show/user_sees_collaboration_links_spec.rb b/spec/features/projects/show/user_sees_collaboration_links_spec.rb
index 613033373e86d745d193d4b36f13953914db31d7..552f068ecc777bf3b01c0a475f16150914c644ce 100644
--- a/spec/features/projects/show/user_sees_collaboration_links_spec.rb
+++ b/spec/features/projects/show/user_sees_collaboration_links_spec.rb
@@ -39,7 +39,7 @@ def find_new_menu_toggle
 
       # The dropdown above the tree
       page.within('.repo-breadcrumb') do
-        find('.qa-add-to-tree').click
+        find('.qa-add-to-tree').click # rubocop:disable QA/SelectorUsage
 
         aggregate_failures 'dropdown links above the repo tree' do
           expect(page).to have_link('New file')
@@ -71,7 +71,7 @@ def find_new_menu_toggle
         find_new_menu_toggle.click
       end
 
-      expect(page).not_to have_selector('.qa-add-to-tree')
+      expect(page).not_to have_selector('.qa-add-to-tree') # rubocop:disable QA/SelectorUsage
 
       expect(page).not_to have_link('Web IDE')
     end
diff --git a/spec/features/projects/tree/create_directory_spec.rb b/spec/features/projects/tree/create_directory_spec.rb
index e2ae858cb9bfbad5381cf5a7e57e7a78403bdfc1..f6127b38bd69607eff33ea9cd2410c50816cc8ae 100644
--- a/spec/features/projects/tree/create_directory_spec.rb
+++ b/spec/features/projects/tree/create_directory_spec.rb
@@ -49,8 +49,8 @@
     # Compact mode depends on the size of window. If it is shorter than MAX_WINDOW_HEIGHT_COMPACT,
     # (as it is with WEBDRIVER_HEADLESS=0), this initial commit button will exist. Otherwise, if it is
     # taller (as it is by default with chrome headless) then the button will not exist.
-    if page.has_css?('.qa-begin-commit-button')
-      find('.qa-begin-commit-button').click
+    if page.has_css?('.qa-begin-commit-button') # rubocop:disable QA/SelectorUsage
+      find('.qa-begin-commit-button').click # rubocop:disable QA/SelectorUsage
     end
 
     fill_in('commit-message', with: 'commit message ide')
diff --git a/spec/features/projects/tree/create_file_spec.rb b/spec/features/projects/tree/create_file_spec.rb
index 956b88988544e8bddecdba878a0c6f1f5997454e..33be02a9121073ff8778ded7352e0480175c95a4 100644
--- a/spec/features/projects/tree/create_file_spec.rb
+++ b/spec/features/projects/tree/create_file_spec.rb
@@ -39,8 +39,8 @@
     # Compact mode depends on the size of window. If it is shorter than MAX_WINDOW_HEIGHT_COMPACT,
     # (as it is with WEBDRIVER_HEADLESS=0), this initial commit button will exist. Otherwise, if it is
     # taller (as it is by default with chrome headless) then the button will not exist.
-    if page.has_css?('.qa-begin-commit-button')
-      find('.qa-begin-commit-button').click
+    if page.has_css?('.qa-begin-commit-button') # rubocop:disable QA/SelectorUsage
+      find('.qa-begin-commit-button').click # rubocop:disable QA/SelectorUsage
     end
 
     fill_in('commit-message', with: 'commit message ide')
diff --git a/spec/features/projects/tree/tree_show_spec.rb b/spec/features/projects/tree/tree_show_spec.rb
index ca9e0a238884450c8cf5574c4dd7a8d80d9329e5..f8bbaa9535be836b2fbca7d9a2155fa15e207763 100644
--- a/spec/features/projects/tree/tree_show_spec.rb
+++ b/spec/features/projects/tree/tree_show_spec.rb
@@ -26,7 +26,7 @@
     expect(page).to have_selector('.tree-item')
     expect(page).to have_content('add tests for .gitattributes custom highlighting')
     expect(page).not_to have_selector('.flash-alert')
-    expect(page).not_to have_selector('[data-qa-selector="label-lfs"]', text: 'LFS')
+    expect(page).not_to have_selector('[data-qa-selector="label-lfs"]', text: 'LFS') # rubocop:disable QA/SelectorUsage
   end
 
   it 'renders tree table for a subtree without errors' do
@@ -35,7 +35,7 @@
 
     expect(page).to have_selector('.tree-item')
     expect(page).to have_content('add spaces in whitespace file')
-    expect(page).not_to have_selector('[data-qa-selector="label-lfs"]', text: 'LFS')
+    expect(page).not_to have_selector('[data-qa-selector="label-lfs"]', text: 'LFS') # rubocop:disable QA/SelectorUsage
     expect(page).not_to have_selector('.flash-alert')
   end
 
@@ -112,7 +112,7 @@
     it 'renders LFS badge on blob item' do
       visit project_tree_path(project, File.join('master', 'files/lfs'))
 
-      expect(page).to have_selector('[data-qa-selector="label-lfs"]', text: 'LFS')
+      expect(page).to have_selector('[data-qa-selector="label-lfs"]', text: 'LFS') # rubocop:disable QA/SelectorUsage
     end
   end
 
diff --git a/spec/features/projects/user_creates_project_spec.rb b/spec/features/projects/user_creates_project_spec.rb
index 96051e8d9f9661f5d4c4f263a2f4393288d9095e..2dc2f16889657f009d057bb9d2b6587efcd6fa65 100644
--- a/spec/features/projects/user_creates_project_spec.rb
+++ b/spec/features/projects/user_creates_project_spec.rb
@@ -13,7 +13,7 @@
   it 'creates a new project' do
     visit(new_project_path)
 
-    find('[data-qa-panel-name="blank_project"]').click
+    find('[data-qa-panel-name="blank_project"]').click # rubocop:disable QA/SelectorUsage
     fill_in(:project_name, with: 'Empty')
 
     expect(page).to have_checked_field 'Initialize repository with a README'
@@ -43,7 +43,7 @@
     it 'creates a new project' do
       visit(new_project_path)
 
-      find('[data-qa-panel-name="blank_project"]').click
+      find('[data-qa-panel-name="blank_project"]').click # rubocop:disable QA/SelectorUsage
       fill_in :project_name, with: 'A Subgroup Project'
       fill_in :project_path, with: 'a-subgroup-project'
 
@@ -72,7 +72,7 @@
     it 'creates a new project' do
       visit(new_project_path)
 
-      find('[data-qa-panel-name="blank_project"]').click
+      find('[data-qa-panel-name="blank_project"]').click # rubocop:disable QA/SelectorUsage
       fill_in :project_name, with: 'a-new-project'
       fill_in :project_path, with: 'a-new-project'
 
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index a3d134d49ebd93e1fba2c7d7d51abb04818cee70..afdd2f0abde6256910d38824b084a853d65dddce 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -16,7 +16,7 @@
 
     shared_examples 'creates from template' do |template, sub_template_tab = nil|
       it "is created from template", :js do
-        find('[data-qa-panel-name="create_from_template"]').click
+        find('[data-qa-panel-name="create_from_template"]').click # rubocop:disable QA/SelectorUsage
         find(".project-template #{sub_template_tab}").click if sub_template_tab
         find("label[for=#{template.name}]").click
         fill_in("project_name", with: template.name)
@@ -290,7 +290,7 @@
     it 'has working links to submodules' do
       click_link('645f6c4c')
 
-      expect(page).to have_selector('.qa-branches-select', text: '645f6c4c82fd3f5e06f67134450a570b795e55a6')
+      expect(page).to have_selector('.qa-branches-select', text: '645f6c4c82fd3f5e06f67134450a570b795e55a6') # rubocop:disable QA/SelectorUsage
     end
 
     context 'for signed commit on default branch', :js do
diff --git a/spec/rubocop/cop/qa/selector_usage_spec.rb b/spec/rubocop/cop/qa/selector_usage_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b40c57f89916e08712f069a5a4f820e85b911dfb
--- /dev/null
+++ b/spec/rubocop/cop/qa/selector_usage_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+require_relative '../../../../rubocop/cop/qa/selector_usage'
+
+RSpec.describe RuboCop::Cop::QA::SelectorUsage do
+  subject(:cop) { described_class.new }
+
+  shared_examples 'non-qa file usage' do
+    it 'reports an offense' do
+      expect_offense(<<-RUBY)
+        find('#{selector}').click
+             #{'^' * (selector.size + 2)} Do not use `#{selector}` as this is reserved for the end-to-end specs. Use a different selector or a data-testid instead.
+      RUBY
+    end
+  end
+
+  context 'in a QA file' do
+    before do
+      allow(cop).to receive(:in_qa_file?).and_return(true)
+    end
+
+    it 'has no error' do
+      expect_no_offenses(<<-RUBY)
+        has_element?('[data-qa-selector="my_selector"]')
+      RUBY
+    end
+  end
+
+  context 'outside of QA' do
+    before do
+      allow(cop).to receive(:in_qa_file?).and_return(false)
+      allow(cop).to receive(:in_spec?).and_return(true)
+    end
+
+    context 'data-qa-selector' do
+      let(:selector) { '[data-qa-selector="my_selector"]' }
+
+      it_behaves_like 'non-qa file usage'
+    end
+
+    context 'qa class' do
+      let(:selector) { '.qa-selector' }
+
+      it_behaves_like 'non-qa file usage'
+    end
+  end
+end
diff --git a/spec/support/shared_examples/features/discussion_comments_shared_example.rb b/spec/support/shared_examples/features/discussion_comments_shared_example.rb
index ff2878f77b4c47b09a22ad929ad4c1d1e1f87ac0..fb2e422559d506ae9636b5f24f9ecfd8134d7e5f 100644
--- a/spec/support/shared_examples/features/discussion_comments_shared_example.rb
+++ b/spec/support/shared_examples/features/discussion_comments_shared_example.rb
@@ -308,7 +308,7 @@ def submit_reply(text)
           let(:reply_id) { find("#{comments_selector} .note:last-of-type", match: :first)['data-note-id'] }
 
           it 'can be replied to after resolving' do
-            find('button[data-qa-selector="resolve_discussion_button"]').click
+            find('button[data-qa-selector="resolve_discussion_button"]').click # rubocop:disable QA/SelectorUsage
             wait_for_requests
 
             refresh
@@ -320,7 +320,7 @@ def submit_reply(text)
           it 'shows resolved thread when toggled' do
             submit_reply('a')
 
-            find('button[data-qa-selector="resolve_discussion_button"]').click
+            find('button[data-qa-selector="resolve_discussion_button"]').click # rubocop:disable QA/SelectorUsage
             wait_for_requests
 
             expect(page).to have_selector(".note-row-#{note_id}", visible: true)
diff --git a/spec/support/shared_examples/features/packages_shared_examples.rb b/spec/support/shared_examples/features/packages_shared_examples.rb
index 9e88db2e1c0a9ff9e4e235349cd60950a337aee1..8df9c8098c05a8037bcf72038458a84dfb8af1b0 100644
--- a/spec/support/shared_examples/features/packages_shared_examples.rb
+++ b/spec/support/shared_examples/features/packages_shared_examples.rb
@@ -14,7 +14,7 @@
   end
 
   def package_table_row(index)
-    page.all("#{packages_table_selector} > [data-qa-selector=\"package_row\"]")[index].text
+    page.all("#{packages_table_selector} > [data-qa-selector=\"package_row\"]")[index].text # rubocop:disable QA/SelectorUsage
   end
 end
 
@@ -92,7 +92,7 @@ def package_table_row(index)
 end
 
 def packages_table_selector
-  '[data-qa-selector="packages-table"]'
+  '[data-qa-selector="packages-table"]' # rubocop:disable QA/SelectorUsage
 end
 
 def click_sort_option(option, ascending)
diff --git a/spec/support/shared_examples/features/protected_branches_with_deploy_keys_examples.rb b/spec/support/shared_examples/features/protected_branches_with_deploy_keys_examples.rb
index 28fe198c9c388a82dc91aef60ceb426721461eee..14142793a0d04d004aedb127fe5a2905c5ae8e53 100644
--- a/spec/support/shared_examples/features/protected_branches_with_deploy_keys_examples.rb
+++ b/spec/support/shared_examples/features/protected_branches_with_deploy_keys_examples.rb
@@ -23,7 +23,7 @@
         find(".js-allowed-to-push").click
         wait_for_requests
 
-        within('.qa-allowed-to-push-dropdown') do
+        within('.qa-allowed-to-push-dropdown') do # rubocop:disable QA/SelectorUsage
           dropdown_headers = page.all('.dropdown-header').map(&:text)
 
           expect(dropdown_headers).to contain_exactly(*all_dropdown_sections)
@@ -38,7 +38,7 @@
         find(".js-allowed-to-merge").click
         wait_for_requests
 
-        within('.qa-allowed-to-merge-dropdown') do
+        within('.qa-allowed-to-merge-dropdown') do # rubocop:disable QA/SelectorUsage
           dropdown_headers = page.all('.dropdown-header').map(&:text)
 
           expect(dropdown_headers).to contain_exactly(*dropdown_sections_minus_deploy_keys)
@@ -68,7 +68,7 @@
         find(".js-allowed-to-push").click
         wait_for_requests
 
-        within('.qa-allowed-to-push-dropdown') do
+        within('.qa-allowed-to-push-dropdown') do # rubocop:disable QA/SelectorUsage
           dropdown_headers = page.all('.dropdown-header').map(&:text)
 
           expect(dropdown_headers).to contain_exactly(*dropdown_sections_minus_deploy_keys)
diff --git a/spec/support/shared_examples/features/rss_shared_examples.rb b/spec/support/shared_examples/features/rss_shared_examples.rb
index 1b0d3f9605ad3c106406b8944accdfaf4dc8d47a..c7c2aeea358dd417e4fe127a7bd783745b3b530d 100644
--- a/spec/support/shared_examples/features/rss_shared_examples.rb
+++ b/spec/support/shared_examples/features/rss_shared_examples.rb
@@ -9,7 +9,7 @@
 RSpec.shared_examples "it has an RSS button with current_user's feed token" do
   it "shows the RSS button with current_user's feed token" do
     expect(page)
-      .to have_css("a:has(.qa-rss-icon)[href*='feed_token=#{user.feed_token}']")
+      .to have_css("a:has(.qa-rss-icon)[href*='feed_token=#{user.feed_token}']") # rubocop:disable QA/SelectorUsage
   end
 end
 
@@ -22,6 +22,6 @@
 RSpec.shared_examples "it has an RSS button without a feed token" do
   it "shows the RSS button without a feed token" do
     expect(page)
-      .to have_css("a:has(.qa-rss-icon):not([href*='feed_token'])")
+      .to have_css("a:has(.qa-rss-icon):not([href*='feed_token'])") # rubocop:disable QA/SelectorUsage
   end
 end
diff --git a/spec/support/shared_examples/features/variable_list_shared_examples.rb b/spec/support/shared_examples/features/variable_list_shared_examples.rb
index 997500415a90e6fc0cd93bb7b35ba935dce14c5c..524518392814169fb8fcd3f2ba553b4be763f27c 100644
--- a/spec/support/shared_examples/features/variable_list_shared_examples.rb
+++ b/spec/support/shared_examples/features/variable_list_shared_examples.rb
@@ -91,7 +91,7 @@
     end
 
     page.within('#add-ci-variable') do
-      find('[data-qa-selector="ci_variable_key_field"] input').set('new_key')
+      find('[data-qa-selector="ci_variable_key_field"] input').set('new_key') # rubocop:disable QA/SelectorUsage
 
       click_button('Update variable')
     end
@@ -173,7 +173,7 @@
     click_button('Add variable')
 
     page.within('#add-ci-variable') do
-      find('[data-qa-selector="ci_variable_key_field"] input').set('empty_mask_key')
+      find('[data-qa-selector="ci_variable_key_field"] input').set('empty_mask_key') # rubocop:disable QA/SelectorUsage
       find('[data-testid="ci-variable-protected-checkbox"]').click
       find('[data-testid="ci-variable-masked-checkbox"]').click
 
@@ -286,8 +286,8 @@ def fill_variable(key, value, protected: false, masked: false)
     wait_for_requests
 
     page.within('#add-ci-variable') do
-      find('[data-qa-selector="ci_variable_key_field"] input').set(key)
-      find('[data-qa-selector="ci_variable_value_field"]').set(value) if value.present?
+      find('[data-qa-selector="ci_variable_key_field"] input').set(key) # rubocop:disable QA/SelectorUsage
+      find('[data-qa-selector="ci_variable_value_field"]').set(value) if value.present? # rubocop:disable QA/SelectorUsage
       find('[data-testid="ci-variable-protected-checkbox"]').click if protected
       find('[data-testid="ci-variable-masked-checkbox"]').click if masked
 
diff --git a/spec/support/shared_examples/features/wiki/user_views_asciidoc_page_with_includes_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_views_asciidoc_page_with_includes_shared_examples.rb
index 3b2fda4e05b2ef1e781708ad13bb4c96568b19f3..6fdc5ecae736ea242acb70eb1726c7cd7cfe98e9 100644
--- a/spec/support/shared_examples/features/wiki/user_views_asciidoc_page_with_includes_shared_examples.rb
+++ b/spec/support/shared_examples/features/wiki/user_views_asciidoc_page_with_includes_shared_examples.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 RSpec.shared_examples 'User views AsciiDoc page with includes' do
-  let_it_be(:wiki_content_selector) { '[data-qa-selector=wiki_page_content]' }
+  let_it_be(:wiki_content_selector) { '[data-qa-selector=wiki_page_content]' } # rubocop:disable QA/SelectorUsage
   let!(:included_wiki_page) { create_wiki_page('included_page', content: 'Content from the included page')}
   let!(:wiki_page) { create_wiki_page('home', content: "Content from the main page.\ninclude::included_page.asciidoc[]") }
 
diff --git a/spec/views/admin/sessions/new.html.haml_spec.rb b/spec/views/admin/sessions/new.html.haml_spec.rb
index 94870f0bdba88d4a3051b23a9a08a76b4ceeb305..97528b6e782806ef65c3d8f749c1f333d5af6e19 100644
--- a/spec/views/admin/sessions/new.html.haml_spec.rb
+++ b/spec/views/admin/sessions/new.html.haml_spec.rb
@@ -19,9 +19,9 @@
     it 'shows enter password form' do
       render
 
-      expect(rendered).to have_selector('[data-qa-selector="sign_in_tab"]')
+      expect(rendered).to have_selector('[data-qa-selector="sign_in_tab"]') # rubocop:disable QA/SelectorUsage
       expect(rendered).to have_css('#login-pane.active')
-      expect(rendered).to have_selector('[data-qa-selector="password_field"]')
+      expect(rendered).to have_selector('[data-qa-selector="password_field"]') # rubocop:disable QA/SelectorUsage
     end
 
     it 'warns authentication not possible if password not set' do
@@ -60,7 +60,7 @@
     it 'is shown when enabled' do
       render
 
-      expect(rendered).to have_selector('[data-qa-selector="ldap_tab"]')
+      expect(rendered).to have_selector('[data-qa-selector="ldap_tab"]') # rubocop:disable QA/SelectorUsage
       expect(rendered).to have_css('.login-box#ldapmain')
       expect(rendered).to have_field('LDAP Username')
       expect(rendered).not_to have_content('No authentication methods configured')
@@ -71,7 +71,7 @@
 
       render
 
-      expect(rendered).not_to have_selector('[data-qa-selector="ldap_tab"]')
+      expect(rendered).not_to have_selector('[data-qa-selector="ldap_tab"]') # rubocop:disable QA/SelectorUsage
       expect(rendered).not_to have_field('LDAP Username')
       expect(rendered).to have_content('No authentication methods configured')
     end
diff --git a/spec/views/devise/sessions/new.html.haml_spec.rb b/spec/views/devise/sessions/new.html.haml_spec.rb
index d3552bf2e5a722b921671a57d988f778bdd51cd1..0109d05abe4a07fecef58504ff088d8f9d127cb0 100644
--- a/spec/views/devise/sessions/new.html.haml_spec.rb
+++ b/spec/views/devise/sessions/new.html.haml_spec.rb
@@ -48,7 +48,7 @@
       render
 
       expect(rendered).to have_selector('.new-session-tabs')
-      expect(rendered).to have_selector('[data-qa-selector="ldap_tab"]')
+      expect(rendered).to have_selector('[data-qa-selector="ldap_tab"]') # rubocop:disable QA/SelectorUsage
       expect(rendered).to have_field('LDAP Username')
     end
 
@@ -58,7 +58,7 @@
       render
 
       expect(rendered).to have_content('No authentication methods configured')
-      expect(rendered).not_to have_selector('[data-qa-selector="ldap_tab"]')
+      expect(rendered).not_to have_selector('[data-qa-selector="ldap_tab"]') # rubocop:disable QA/SelectorUsage
       expect(rendered).not_to have_field('LDAP Username')
     end
   end
diff --git a/spec/views/groups/settings/_transfer.html.haml_spec.rb b/spec/views/groups/settings/_transfer.html.haml_spec.rb
index aeb70251a623950fc4c21693a23ccfcdd8059250..b557c989eaeffdb00cf722cc644aebf4e4060a50 100644
--- a/spec/views/groups/settings/_transfer.html.haml_spec.rb
+++ b/spec/views/groups/settings/_transfer.html.haml_spec.rb
@@ -9,8 +9,8 @@
 
       render 'groups/settings/transfer', group: group
 
-      expect(rendered).to have_selector '[data-qa-selector="select_group_dropdown"]'
-      expect(rendered).not_to have_selector '[data-qa-selector="select_group_dropdown"][disabled]'
+      expect(rendered).to have_selector '[data-qa-selector="select_group_dropdown"]' # rubocop:disable QA/SelectorUsage
+      expect(rendered).not_to have_selector '[data-qa-selector="select_group_dropdown"][disabled]' # rubocop:disable QA/SelectorUsage
       expect(rendered).not_to have_selector '[data-testid="group-to-transfer-has-linked-subscription-alert"]'
     end
   end