diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2019-10-22 11:31:16 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2019-10-22 11:31:16 +0000 |
commit | 905c1110b08f93a19661cf42a276c7ea90d0a0ff (patch) | |
tree | 756d138db422392c00471ab06acdff92c5a9b69c /qa | |
parent | 50d93f8d1686950fc58dda4823c4835fd0d8c14b (diff) | |
download | gitlab-ce-905c1110b08f93a19661cf42a276c7ea90d0a0ff.tar.gz |
Add latest changes from gitlab-org/gitlab@12-4-stable-ee
Diffstat (limited to 'qa')
79 files changed, 1265 insertions, 298 deletions
diff --git a/qa/Dockerfile b/qa/Dockerfile index 84dbfae1008..4845938b57e 100644 --- a/qa/Dockerfile +++ b/qa/Dockerfile @@ -50,12 +50,13 @@ RUN export CLOUD_SDK_REPO="cloud-sdk-$(lsb_release -c -s)" && \ WORKDIR /home/gitlab/qa COPY ./qa/Gemfile* /home/gitlab/qa/ COPY ./config/initializers/0_inject_enterprise_edition_module.rb /home/gitlab/config/initializers/ -# Copy VERSION to ensure the COPY succeeds to copy at least one file since ee/app/models/license.rb isn't present in CE +# Copy VERSION to ensure the COPY succeeds to copy at least one file since ee/app/models/license.rb isn't present in FOSS +# The [b] part makes ./ee/app/models/license.r[b] a pattern that is allowed to return no files (which is the case in FOSS) COPY VERSION ./ee/app/models/license.r[b] /home/gitlab/ee/app/models/ COPY ./lib/gitlab.rb /home/gitlab/lib/ -COPY ./INSTALLATION_TYPE /home/gitlab/ -COPY ./VERSION /home/gitlab/ -RUN cd /home/gitlab/qa/ && bundle install +COPY ./lib/gitlab/utils.rb /home/gitlab/lib/gitlab/ +COPY ./INSTALLATION_TYPE ./VERSION /home/gitlab/ +RUN cd /home/gitlab/qa/ && bundle install --jobs=$(nproc) --retry=3 --quiet COPY ./qa /home/gitlab/qa ENTRYPOINT ["bin/test"] diff --git a/qa/knapsack/gitlab-ce/review-qa-all_master_report.json b/qa/knapsack/gitlab-ce/review-qa-all_master_report.json deleted file mode 100644 index f147346ba0f..00000000000 --- a/qa/knapsack/gitlab-ce/review-qa-all_master_report.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb": 9.697327613830566, - "qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb": 46.54227638244629, - "qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb": 10.214765310287476, - "qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb": 7.882027864456177, - "qa/specs/features/api/3_create/repository/files_spec.rb": 5.015859127044678, - "qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb": 12.772682905197144, - "qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb": 29.76174831390381, - "qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb": 22.800872802734375, - "qa/specs/features/browser_ui/1_manage/login/register_spec.rb": 22.320587396621704, - "qa/specs/features/api/1_manage/users_spec.rb": 0.6089541912078857, - "qa/specs/features/browser_ui/3_create/repository/clone_spec.rb": 0.9618203639984131, - "qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb": 13.403101205825806, - "qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb": 8.810423135757446, - "qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb": 7.730542182922363, - "qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb": 16.18057894706726, - "qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb": 8.31815505027771, - "qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb": 9.48607873916626, - "qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb": 19.552733182907104, - "qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb": 17.273863554000854, - "qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb": 8.281434059143066, - "qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb": 18.047621726989746, - "qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb": 7.422840595245361, - "qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb": 3.438166856765747, - "qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb": 18.679633855819702, - "qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb": 27.943300485610962, - "qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb": 39.17585229873657, - "qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb": 40.09336972236633, - "qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb": 3.705310821533203, - "qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb": 5.812374591827393, - "qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb": 92.46774697303772, - "qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb": 100.28881478309631, - "qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb": 23.710937023162842, - "qa/specs/features/browser_ui/1_manage/login/login_via_oauth_spec.rb": 20.58603596687317, - "qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb": 25.460349321365356, - "qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb": 19.459370374679565, - "qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb": 6.731764793395996, - "qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb": 15.342933893203735, - "qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb": 11.280649185180664, - "qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb": 57.48992609977722, - "qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb": 32.5517954826355 -}
\ No newline at end of file diff --git a/qa/qa/ce/knapsack/nightly_master_report.json b/qa/knapsack/master_report.json index 08694f706de..467150e84c7 100644 --- a/qa/qa/ce/knapsack/nightly_master_report.json +++ b/qa/knapsack/master_report.json @@ -1,4 +1,12 @@ { + "qa/specs/features/ee/api/2_plan/epics_milestone_dates_spec.rb": 4.835599899291992, + "qa/specs/features/ee/browser_ui/2_plan/epic/epics_management_spec.rb": 69.85551619529724, + "qa/specs/features/ee/browser_ui/2_plan/epic/promote_issue_to_epic_spec.rb": 14.649160623550415, + "qa/specs/features/ee/browser_ui/2_plan/scoped_labels/editing_scoped_labels_spec.rb": 12.790381908416748, + "qa/specs/features/ee/browser_ui/3_create/merge_request/add_batch_comments_in_merge_request_spec.rb": 0.00018262863159179688, + "qa/specs/features/ee/browser_ui/3_create/repository/assign_code_owners_spec.rb": 59.73394823074341, + "qa/specs/features/ee/browser_ui/3_create/repository/code_owners_spec.rb": 26.39240026473999, + "qa/specs/features/ee/browser_ui/secure/create_project_with_secure_spec.rb": 46.76790499687195, "qa/specs/features/api/1_manage/users_spec.rb": 0.6089541912078857, "qa/specs/features/api/3_create/repository/files_spec.rb": 5.015859127044678, "qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb": 1.0199065208435059, @@ -5,6 +5,7 @@ $: << File.expand_path(File.dirname(__FILE__)) Encoding.default_external = 'UTF-8' require_relative '../lib/gitlab' +require_relative '../lib/gitlab/utils' require_relative '../config/initializers/0_inject_enterprise_edition_module' module QA @@ -23,6 +24,7 @@ module QA autoload :Feature, 'qa/runtime/feature' autoload :Fixtures, 'qa/runtime/fixtures' autoload :Logger, 'qa/runtime/logger' + autoload :GPG, 'qa/runtime/gpg' module API autoload :Client, 'qa/runtime/api/client' @@ -67,7 +69,9 @@ module QA autoload :Fork, 'qa/resource/fork' autoload :SSHKey, 'qa/resource/ssh_key' autoload :Snippet, 'qa/resource/snippet' + autoload :Tag, 'qa/resource/tag' autoload :ProjectMember, 'qa/resource/project_member' + autoload :UserGPG, 'qa/resource/user_gpg' module Events autoload :Base, 'qa/resource/events/base' @@ -111,6 +115,7 @@ module QA module Integration autoload :Github, 'qa/scenario/test/integration/github' autoload :LDAPNoTLS, 'qa/scenario/test/integration/ldap_no_tls' + autoload :LDAPNoServer, 'qa/scenario/test/integration/ldap_no_server' autoload :LDAPTLS, 'qa/scenario/test/integration/ldap_tls' autoload :InstanceSAML, 'qa/scenario/test/integration/instance_saml' autoload :OAuth, 'qa/scenario/test/integration/oauth' @@ -287,6 +292,8 @@ module QA autoload :Menu, 'qa/page/profile/menu' autoload :PersonalAccessTokens, 'qa/page/profile/personal_access_tokens' autoload :SSHKeys, 'qa/page/profile/ssh_keys' + autoload :Emails, 'qa/page/profile/emails' + autoload :Password, 'qa/page/profile/password' autoload :TwoFactorAuth, 'qa/page/profile/two_factor_auth' end @@ -329,6 +336,13 @@ module QA autoload :PerformanceBar, 'qa/page/admin/settings/component/performance_bar' end end + + module Overview + module Users + autoload :Index, 'qa/page/admin/overview/users/index' + autoload :Show, 'qa/page/admin/overview/users/show' + end + end end module Mattermost @@ -344,6 +358,7 @@ module QA # Classes describing components that are used by several pages. # module Component + autoload :CiBadgeLink, 'qa/page/component/ci_badge_link' autoload :ClonePanel, 'qa/page/component/clone_panel' autoload :LazyLoader, 'qa/page/component/lazy_loader' autoload :LegacyClonePanel, 'qa/page/component/legacy_clone_panel' @@ -381,7 +396,6 @@ module QA autoload :Shellout, 'qa/service/shellout' autoload :KubernetesCluster, 'qa/service/kubernetes_cluster' autoload :Omnibus, 'qa/service/omnibus' - autoload :Runner, 'qa/service/runner' module ClusterProvider autoload :Base, 'qa/service/cluster_provider/base' @@ -389,6 +403,13 @@ module QA autoload :Minikube, 'qa/service/cluster_provider/minikube' autoload :K3d, 'qa/service/cluster_provider/k3d' end + + module DockerRun + autoload :Base, 'qa/service/docker_run/base' + autoload :LDAP, 'qa/service/docker_run/ldap' + autoload :NodeJs, 'qa/service/docker_run/node_js' + autoload :GitlabRunner, 'qa/service/docker_run/gitlab_runner' + end end ## @@ -421,6 +442,10 @@ module QA autoload :Login, 'qa/vendor/github/page/login' end end + + module OnePassword + autoload :CLI, 'qa/vendor/one_password/cli' + end end # Classes that provide support to other parts of the framework. @@ -430,6 +455,7 @@ module QA autoload :Logging, 'qa/support/page/logging' end autoload :Api, 'qa/support/api' + autoload :Dates, 'qa/support/dates' autoload :Waiter, 'qa/support/waiter' autoload :Retrier, 'qa/support/retrier' end diff --git a/qa/qa/fixtures/ldap/admin/1_add_nodes.ldif b/qa/qa/fixtures/ldap/admin/1_add_nodes.ldif new file mode 100644 index 00000000000..1476f53a0b9 --- /dev/null +++ b/qa/qa/fixtures/ldap/admin/1_add_nodes.ldif @@ -0,0 +1,7 @@ +dn: ou=Global Groups,dc=example,dc=org +objectClass: organizationalUnit +ou: Global Groups + +dn: ou=People,ou=Global Groups,dc=example,dc=org +objectClass: organizationalUnit +ou: People diff --git a/qa/qa/fixtures/ldap/admin/2_add_users.ldif b/qa/qa/fixtures/ldap/admin/2_add_users.ldif new file mode 100644 index 00000000000..00ba2fe8397 --- /dev/null +++ b/qa/qa/fixtures/ldap/admin/2_add_users.ldif @@ -0,0 +1,63 @@ + +# 1. hruser1 + +dn: uid=hruser1,ou=People,ou=Global Groups,dc=example,dc=org +cn: HR User 1 +givenName: HR +sn: User1 +uid: hruser1 +uidNumber: 5000 +gidNumber: 10000 +homeDirectory: /home/hruser1 +mail: hruser1@example.org +objectClass: top +objectClass: posixAccount +objectClass: shadowAccount +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +loginShell: /bin/bash +# hashed value for 'password' +userPassword: {SSHA}ICMhr6Jxt5bk2awD7HL7GxRTM3BZ1pFI + +# 2. adminuser1 + +dn: uid=adminuser1,ou=People,ou=Global Groups,dc=example,dc=org +cn: Admin User 1 +givenName: Admin +sn: User1 +uid: adminuser1 +uidNumber: 5009 +gidNumber: 10009 +homeDirectory: /home/adminuser1 +mail: adminuser1@example.org +objectClass: top +objectClass: posixAccount +objectClass: shadowAccount +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +loginShell: /bin/bash +# hashed value for 'password' +userPassword: {SSHA}ICMhr6Jxt5bk2awD7HL7GxRTM3BZ1pFI + +# 2. adminuser2 + +dn: uid=adminuser2,ou=People,ou=Global Groups,dc=example,dc=org +cn: Admin User 2 +givenName: Admin +sn: User1 +uid: adminuser2 +uidNumber: 5010 +gidNumber: 10010 +homeDirectory: /home/adminuser2 +mail: adminuser2@example.org +objectClass: top +objectClass: posixAccount +objectClass: shadowAccount +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +loginShell: /bin/bash +# hashed value for 'password' +userPassword: {SSHA}ICMhr6Jxt5bk2awD7HL7GxRTM3BZ1pFI diff --git a/qa/qa/fixtures/ldap/admin/3_add_groups.ldif b/qa/qa/fixtures/ldap/admin/3_add_groups.ldif new file mode 100644 index 00000000000..db8b15bc9ea --- /dev/null +++ b/qa/qa/fixtures/ldap/admin/3_add_groups.ldif @@ -0,0 +1,16 @@ +# 1. Human Resources + +dn: cn=Human Resources,ou=Global Groups,dc=example,dc=org +objectClass: groupofnames +cn: Human Resources +description: Human Resources +member: uid=hruser1,ou=People,ou=Global Groups,dc=example,dc=org + +# 2. Admin + +dn: cn=AdminGroup,ou=Global Groups,dc=example,dc=org +objectClass: groupofnames +cn: AdminGroup +description: Human Resources +member: uid=adminuser1,ou=People,ou=Global Groups,dc=example,dc=org +member: uid=adminuser2,ou=People,ou=Global Groups,dc=example,dc=org diff --git a/qa/qa/fixtures/ldap/non_admin/1_add_nodes.ldif b/qa/qa/fixtures/ldap/non_admin/1_add_nodes.ldif new file mode 100644 index 00000000000..1476f53a0b9 --- /dev/null +++ b/qa/qa/fixtures/ldap/non_admin/1_add_nodes.ldif @@ -0,0 +1,7 @@ +dn: ou=Global Groups,dc=example,dc=org +objectClass: organizationalUnit +ou: Global Groups + +dn: ou=People,ou=Global Groups,dc=example,dc=org +objectClass: organizationalUnit +ou: People diff --git a/qa/qa/fixtures/ldap/non_admin/2_add_users.ldif b/qa/qa/fixtures/ldap/non_admin/2_add_users.ldif new file mode 100644 index 00000000000..cf8accc97e1 --- /dev/null +++ b/qa/qa/fixtures/ldap/non_admin/2_add_users.ldif @@ -0,0 +1,61 @@ + +# 1. Human Resources + +dn: uid=hruser1,ou=People,ou=Global Groups,dc=example,dc=org +cn: HR User 1 +givenName: HR +sn: User1 +uid: hruser1 +uidNumber: 5000 +gidNumber: 10000 +homeDirectory: /home/hruser1 +mail: hruser1@example.org +objectClass: top +objectClass: posixAccount +objectClass: shadowAccount +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +loginShell: /bin/bash +# hashed value for 'password' +userPassword: {SSHA}ICMhr6Jxt5bk2awD7HL7GxRTM3BZ1pFI + +# 2. Admin + +dn: uid=adminuser1,ou=People,ou=Global Groups,dc=example,dc=org +cn: Admin User 1 +givenName: Admin +sn: User1 +uid: adminuser1 +uidNumber: 5009 +gidNumber: 10009 +homeDirectory: /home/adminuser1 +mail: adminuser1@example.org +objectClass: top +objectClass: posixAccount +objectClass: shadowAccount +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +loginShell: /bin/bash +# hashed value for 'password' +userPassword: {SSHA}ICMhr6Jxt5bk2awD7HL7GxRTM3BZ1pFI + +dn: uid=adminuser2,ou=People,ou=Global Groups,dc=example,dc=org +cn: Admin User 2 +givenName: Admin +sn: User1 +uid: adminuser2 +uidNumber: 5010 +gidNumber: 10010 +homeDirectory: /home/adminuser2 +mail: adminuser2@example.org +objectClass: top +objectClass: posixAccount +objectClass: shadowAccount +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +loginShell: /bin/bash +# hashed value for 'password' +userPassword: {SSHA}ICMhr6Jxt5bk2awD7HL7GxRTM3BZ1pFI diff --git a/qa/qa/fixtures/ldap/non_admin/3_add_groups.ldif b/qa/qa/fixtures/ldap/non_admin/3_add_groups.ldif new file mode 100644 index 00000000000..65487a3b2c6 --- /dev/null +++ b/qa/qa/fixtures/ldap/non_admin/3_add_groups.ldif @@ -0,0 +1,16 @@ +# 1. Human Resources + +dn: cn=Human Resources,ou=Global Groups,dc=example,dc=org +objectClass: groupofnames +cn: Human Resources +description: Human Resources +member: uid=hruser1,ou=People,ou=Global Groups,dc=example,dc=org +member: uid=adminuser1,ou=People,ou=Global Groups,dc=example,dc=org + +# 2. Admin + +dn: cn=AdminGroup,ou=Global Groups,dc=example,dc=org +objectClass: groupofnames +cn: AdminGroup +description: Human Resources +member: uid=adminuser2,ou=People,ou=Global Groups,dc=example,dc=org diff --git a/qa/qa/git/repository.rb b/qa/qa/git/repository.rb index 24b9fc67dd9..f56fab63198 100644 --- a/qa/qa/git/repository.rb +++ b/qa/qa/git/repository.rb @@ -14,7 +14,7 @@ module QA include Scenario::Actable RepositoryCommandError = Class.new(StandardError) - attr_writer :use_lfs + attr_writer :use_lfs, :gpg_key_id attr_accessor :env_vars InvalidCredentialsError = Class.new(RuntimeError) @@ -25,6 +25,7 @@ module QA # .netrc can be utilised self.env_vars = [%Q{HOME="#{tmp_home_dir}"}] @use_lfs = false + @gpg_key_id = nil end def self.perform(*args) @@ -75,7 +76,7 @@ module QA end def configure_identity(name, email) - run(%Q{git config user.name #{name}}) + run(%Q{git config user.name "#{name}"}) run(%Q{git config user.email #{email}}) end @@ -99,10 +100,18 @@ module QA git_lfs_track_result.to_s + git_add_result.to_s end + def delete_tag(tag_name) + run(%Q{git push origin --delete #{tag_name}}).to_s + end + def commit(message) run(%Q{git commit -m "#{message}"}).to_s end + def commit_with_gpg(message) + run(%Q{git config user.signingkey #{@gpg_key_id} && git config gpg.program $(which gpg) && git commit -S -m "#{message}"}).to_s + end + def push_changes(branch = 'master') run("git push #{uri} #{branch}").to_s end diff --git a/qa/qa/page/admin/menu.rb b/qa/qa/page/admin/menu.rb index 5a18ebd7af8..2d1af78046d 100644 --- a/qa/qa/page/admin/menu.rb +++ b/qa/qa/page/admin/menu.rb @@ -6,12 +6,16 @@ module QA class Menu < Page::Base view 'app/views/layouts/nav/sidebar/_admin.html.haml' do element :admin_sidebar - element :admin_sidebar_submenu + element :admin_sidebar_settings_submenu element :admin_settings_item element :admin_settings_repository_item element :admin_settings_general_item element :admin_settings_metrics_and_profiling_item element :admin_settings_preferences_link + element :admin_monitoring_link + element :admin_sidebar_monitoring_submenu_content + element :admin_sidebar_overview_submenu_content + element :users_overview_link end view 'app/views/layouts/nav/sidebar/_admin.html.haml' do @@ -19,59 +23,65 @@ module QA end def go_to_preferences_settings - hover_settings do - within_submenu do + hover_element(:admin_settings_item) do + within_submenu(:admin_sidebar_settings_submenu) do click_element :admin_settings_preferences_link end end end def go_to_repository_settings - hover_settings do - within_submenu do + hover_element(:admin_settings_item) do + within_submenu(:admin_sidebar_settings_submenu) do click_element :admin_settings_repository_item end end end def go_to_integration_settings - hover_settings do - within_submenu do + hover_element(:admin_settings_item) do + within_submenu(:admin_sidebar_settings_submenu) do click_element :integration_settings_link end end end def go_to_general_settings - hover_settings do - within_submenu do + hover_element(:admin_settings_item) do + within_submenu(:admin_sidebar_settings_submenu) do click_element :admin_settings_general_item end end end def go_to_metrics_and_profiling_settings - hover_settings do - within_submenu do + hover_element(:admin_settings_item) do + within_submenu(:admin_sidebar_settings_submenu) do click_element :admin_settings_metrics_and_profiling_item end end end def go_to_network_settings - hover_settings do - within_submenu do + hover_element(:admin_settings_item) do + within_submenu(:admin_sidebar_settings_submenu) do click_element :admin_settings_network_item end end end + def go_to_users_overview + within_submenu(:admin_sidebar_overview_submenu_content) do + click_element :users_overview_link + end + end + private - def hover_settings + def hover_element(element) within_sidebar do - scroll_to_element(:admin_settings_item) - find_element(:admin_settings_item).hover + scroll_to_element(element) + find_element(element).hover yield end @@ -83,8 +93,8 @@ module QA end end - def within_submenu - within_element(:admin_sidebar_submenu) do + def within_submenu(element) + within_element(element) do yield end end diff --git a/qa/qa/page/admin/overview/users/index.rb b/qa/qa/page/admin/overview/users/index.rb new file mode 100644 index 00000000000..e374c1bf281 --- /dev/null +++ b/qa/qa/page/admin/overview/users/index.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module QA + module Page + module Admin + module Overview + module Users + class Index < QA::Page::Base + view 'app/views/admin/users/index.html.haml' do + element :user_search_field + end + + view 'app/views/admin/users/_user.html.haml' do + element :user_row_content + end + + view 'app/views/admin/users/_user_detail.html.haml' do + element :username_link + end + + def search_user(username) + find_element(:user_search_field).set(username).send_keys(:return) + end + + def click_user(username) + within_element(:user_row_content, text: username) do + click_element(:username_link) + end + end + end + end + end + end + end +end diff --git a/qa/qa/page/admin/overview/users/show.rb b/qa/qa/page/admin/overview/users/show.rb new file mode 100644 index 00000000000..11ea7bcabc8 --- /dev/null +++ b/qa/qa/page/admin/overview/users/show.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module QA + module Page + module Admin + module Overview + module Users + class Show < QA::Page::Base + view 'app/views/admin/users/_head.html.haml' do + element :impersonate_user_link + end + + def click_impersonate_user + click_element(:impersonate_user_link) + end + end + end + end + end + end +end diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb index 45496c6b245..71df90f2f42 100644 --- a/qa/qa/page/base.rb +++ b/qa/qa/page/base.rb @@ -15,6 +15,10 @@ module QA def_delegators :evaluator, :view, :views + def assert_no_element(name) + assert_no_selector(element_selector_css(name)) + end + def refresh page.refresh end @@ -90,8 +94,8 @@ module QA end # replace with (..., page = self.class) - def click_element(name, page = nil) - find_element(name).click + def click_element(name, page = nil, text: nil) + find_element(name, text: text).click page.validate_elements_present! if page end @@ -102,9 +106,9 @@ module QA def select_element(name, value) element = find_element(name) - return if element.text.downcase.to_s == value.to_s + return if element.text == value - element.select value.to_s.capitalize + element.select value end def has_element?(name, text: nil, wait: Capybara.default_max_wait_time) @@ -127,6 +131,10 @@ module QA has_no_css?('.fa-spinner', wait: Capybara.default_max_wait_time) end + def finished_loading_block? + has_no_css?('.fa-spinner.block-loading', wait: Capybara.default_max_wait_time) + end + def wait_for_animated_element(name) # It would be ideal if we could detect when the animation is complete # but in some cases there's nothing we can easily access via capybara @@ -143,6 +151,11 @@ module QA end def within_element_by_index(name, index) + # Finding all elements can be flaky if the elements don't all load + # immediately. So we wait for any to appear before trying to find a + # specific one. + has_element?(name) + page.within all_elements(name)[index] do yield end diff --git a/qa/qa/page/component/ci_badge_link.rb b/qa/qa/page/component/ci_badge_link.rb new file mode 100644 index 00000000000..aad8dc1d3df --- /dev/null +++ b/qa/qa/page/component/ci_badge_link.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module QA + module Page + module Component + module CiBadgeLink + COMPLETED_STATUSES = %w[passed failed canceled blocked skipped manual].freeze # excludes created, pending, running + PASSED_STATUS = 'passed'.freeze + + def self.included(base) + base.view 'app/assets/javascripts/vue_shared/components/ci_badge_link.vue' do + element :status_badge + end + end + + def status_badge + find_element(:status_badge).text + end + + def successful?(timeout: 60) + raise "Timed out waiting for the status to be a valid completed state" unless completed?(timeout: timeout) + + status_badge == PASSED_STATUS + end + + private + + def completed?(timeout: 60) + wait(reload: false, max: timeout) do + COMPLETED_STATUSES.include?(status_badge) + end + end + end + end + end +end diff --git a/qa/qa/page/component/note.rb b/qa/qa/page/component/note.rb index 34fde045091..c85fa690d6c 100644 --- a/qa/qa/page/component/note.rb +++ b/qa/qa/page/component/note.rb @@ -10,6 +10,10 @@ module QA element :discussion_option end + base.view 'app/assets/javascripts/notes/components/noteable_discussion.vue' do + element :discussion_content + end + base.view 'app/assets/javascripts/notes/components/note_actions.vue' do element :note_edit_button end @@ -21,6 +25,7 @@ module QA base.view 'app/assets/javascripts/notes/components/discussion_actions.vue' do element :discussion_reply_tab + element :resolve_discussion_button end base.view 'app/assets/javascripts/notes/components/toggle_replies_widget.vue' do @@ -54,6 +59,12 @@ module QA click_element :reply_comment_button end + def resolve_discussion_at_index(index) + within_element_by_index(:discussion_content, index) do + click_element :resolve_discussion_button + end + end + def collapse_replies click_element :collapse_replies end diff --git a/qa/qa/page/group/new.rb b/qa/qa/page/group/new.rb index 9e37d14fbf3..e01b8754d55 100644 --- a/qa/qa/page/group/new.rb +++ b/qa/qa/page/group/new.rb @@ -11,7 +11,7 @@ module QA end view 'app/views/groups/new.html.haml' do - element :create_group_button, "submit 'Create group'" # rubocop:disable QA/ElementWithPattern + element :create_group_button, "submit _('Create group')" # rubocop:disable QA/ElementWithPattern element :visibility_radios, 'visibility_level:' # rubocop:disable QA/ElementWithPattern end diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb index ca93663dba2..6e266e26d78 100644 --- a/qa/qa/page/main/login.rb +++ b/qa/qa/page/main/login.rb @@ -42,7 +42,7 @@ module QA element :login_page, required: true end - def sign_in_using_credentials(user = nil) + def sign_in_using_credentials(user: nil, skip_page_validation: false) # Don't try to log-in if we're already logged-in return if Page::Main::Menu.perform(&:signed_in?) @@ -52,9 +52,9 @@ module QA raise NotImplementedError if Runtime::User.ldap_user? && user&.credentials_given? if Runtime::User.ldap_user? - sign_in_using_ldap_credentials(user || Runtime::User) + sign_in_using_ldap_credentials(user: user || Runtime::User) else - sign_in_using_gitlab_credentials(user || Runtime::User) + sign_in_using_gitlab_credentials(user: user || Runtime::User, skip_page_validation: skip_page_validation) end end end @@ -68,13 +68,13 @@ module QA using_wait_time 0 do set_initial_password_if_present - sign_in_using_gitlab_credentials(admin) + sign_in_using_gitlab_credentials(user: admin) end Page::Main::Menu.perform(&:has_personal_area?) end - def sign_in_using_ldap_credentials(user) + def sign_in_using_ldap_credentials(user:) Page::Main::Menu.perform(&:sign_out_if_signed_in) using_wait_time 0 do @@ -87,7 +87,7 @@ module QA click_element :sign_in_button end - Page::Main::Menu.perform(&:has_personal_area?) + Page::Main::Menu.perform(&:signed_in?) end def self.path @@ -148,18 +148,18 @@ module QA def sign_out_and_sign_in_as(user:) Menu.perform(&:sign_out_if_signed_in) has_sign_in_tab? - sign_in_using_credentials(user) + sign_in_using_credentials(user: user) end private - def sign_in_using_gitlab_credentials(user) + def sign_in_using_gitlab_credentials(user:, skip_page_validation: false) switch_to_sign_in_tab if has_sign_in_tab? switch_to_standard_tab if has_standard_tab? fill_element :login_field, user.username fill_element :password_field, user.password - click_element :sign_in_button, Page::Main::Menu + click_element :sign_in_button, !skip_page_validation && Page::Main::Menu end def set_initial_password_if_present diff --git a/qa/qa/page/main/menu.rb b/qa/qa/page/main/menu.rb index 751b67d7695..024f56db8e2 100644 --- a/qa/qa/page/main/menu.rb +++ b/qa/qa/page/main/menu.rb @@ -13,12 +13,14 @@ module QA element :navbar, required: true element :user_avatar, required: true element :user_menu, required: true + element :stop_impersonation_link end view 'app/views/layouts/nav/_dashboard.html.haml' do element :admin_area_link element :projects_dropdown, required: true element :groups_dropdown, required: true + element :more_dropdown, required: true element :snippets_link end @@ -51,6 +53,13 @@ module QA end end + def go_to_snippets + within_top_menu do + click_element :more_dropdown + click_element :snippets_link + end + end + def click_admin_area within_top_menu { click_element :admin_area_link } end @@ -79,10 +88,6 @@ module QA end end - def click_snippets_link - click_element :snippets_link - end - def search_for(term) fill_element :search_term_field, "#{term}\n" end @@ -95,6 +100,14 @@ module QA has_element?(:admin_area_link, wait: wait) end + def has_no_admin_area_link?(wait: Capybara.default_max_wait_time) + has_no_element?(:admin_area_link, wait: wait) + end + + def click_stop_impersonation_link + click_element(:stop_impersonation_link) + end + private def within_top_menu diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb index 6e550805f9f..14b8c420b16 100644 --- a/qa/qa/page/merge_request/show.rb +++ b/qa/qa/page/merge_request/show.rb @@ -6,6 +6,17 @@ module QA class Show < Page::Base include Page::Component::Note + view 'app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue' do + element :dropdown_toggle + element :download_email_patches + element :download_plain_diff + end + + view 'app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue' do + element :merge_request_pipeline_info_content + element :pipeline_link + end + view 'app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue' do element :merge_button element :fast_forward_message, 'Fast-forward merge without a merge commit' # rubocop:disable QA/ElementWithPattern @@ -27,12 +38,6 @@ module QA element :squash_checkbox end - view 'app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue' do - element :dropdown_toggle - element :download_email_patches - element :download_plain_diff - end - view 'app/views/projects/merge_requests/show.html.haml' do element :notes_tab element :diffs_tab @@ -55,6 +60,18 @@ module QA element :edit_button end + def click_discussions_tab + click_element :notes_tab + end + + def click_diffs_tab + click_element :diffs_tab + end + + def click_pipeline_link + click_element :pipeline_link + end + def fast_forward_possible? has_no_text?('Fast-forward merge is not possible') end @@ -111,6 +128,11 @@ module QA end end + def has_pipeline_status?(text) + # Pipelines can be slow, so we wait a bit longer than the usual 10 seconds + has_element?(:merge_request_pipeline_info_content, text: text, wait: 30) + end + def has_title?(title) has_element?(:title, text: title) end @@ -120,17 +142,7 @@ module QA end def try_to_merge! - # The merge button is disabled on load - wait do - has_element?(:merge_button) - end - - # The merge button is enabled via JS - wait(reload: false) do - !find_element(:merge_button).disabled? - end - - merge_immediately + merge_immediately if ready_to_merge? end def merge! @@ -157,14 +169,6 @@ module QA click_element :squash_checkbox end - def click_discussions_tab - click_element :notes_tab - end - - def click_diffs_tab - click_element :diffs_tab - end - def add_comment_to_diff(text) wait(interval: 5) do has_text?("No newline at end of file") @@ -178,6 +182,18 @@ module QA click_element :edit_button end + def ready_to_merge? + # The merge button is disabled on load + wait do + has_element?(:merge_button) + end + + # The merge button is enabled via JS + wait(reload: false) do + !find_element(:merge_button).disabled? + end + end + def view_email_patches click_element :dropdown_toggle visit_link_in_element(:download_email_patches) diff --git a/qa/qa/page/profile/emails.rb b/qa/qa/page/profile/emails.rb new file mode 100644 index 00000000000..c20bc6a5c57 --- /dev/null +++ b/qa/qa/page/profile/emails.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module QA + module Page + module Profile + class Emails < Page::Base + view 'app/views/profiles/emails/index.html.haml' do + element :email_address_field + element :add_email_address_button + element :email_row_content + element :delete_email_link + end + + def add_email_address(email_address) + find_element(:email_address_field).set email_address + click_element(:add_email_address_button) + end + + def delete_email_address(email_address) + page.accept_alert do + within_element(:email_row_content, text: email_address) do + click_element(:delete_email_link) + end + end + end + end + end + end +end diff --git a/qa/qa/page/profile/menu.rb b/qa/qa/page/profile/menu.rb index 99a795a23ef..e7baaf3d40a 100644 --- a/qa/qa/page/profile/menu.rb +++ b/qa/qa/page/profile/menu.rb @@ -9,6 +9,8 @@ module QA element :access_token_title, 'Access Tokens' # rubocop:disable QA/ElementWithPattern element :top_level_items, '.sidebar-top-level-items' # rubocop:disable QA/ElementWithPattern element :ssh_keys, 'SSH Keys' # rubocop:disable QA/ElementWithPattern + element :profile_emails_link + element :profile_password_link end def click_access_tokens @@ -23,6 +25,18 @@ module QA end end + def click_emails + within_sidebar do + click_element(:profile_emails_link) + end + end + + def click_password + within_sidebar do + click_element(:profile_password_link) + end + end + private def within_sidebar diff --git a/qa/qa/page/profile/password.rb b/qa/qa/page/profile/password.rb new file mode 100644 index 00000000000..ce062f39edb --- /dev/null +++ b/qa/qa/page/profile/password.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module QA + module Page + module Profile + class Password < Page::Base + view 'app/views/profiles/passwords/edit.html.haml' do + element :current_password_field + element :new_password_field + element :confirm_password_field + element :save_password_button + end + + def update_password(new_password, current_password) + find_element(:current_password_field).set current_password + find_element(:new_password_field).set new_password + find_element(:confirm_password_field).set new_password + click_element(:save_password_button) + end + end + end + end +end diff --git a/qa/qa/page/project/issue/index.rb b/qa/qa/page/project/issue/index.rb index f74366f6967..befee25b37a 100644 --- a/qa/qa/page/project/issue/index.rb +++ b/qa/qa/page/project/issue/index.rb @@ -5,14 +5,30 @@ module QA module Project module Issue class Index < Page::Base + view 'app/helpers/projects_helper.rb' do + element :assignee_link + end + view 'app/views/projects/issues/_issue.html.haml' do element :issue_link, 'link_to issue.title' # rubocop:disable QA/ElementWithPattern end + view 'app/views/shared/issuable/_assignees.html.haml' do + element :avatar_counter + end + view 'app/views/shared/issuable/_nav.html.haml' do element :closed_issues_link end + def assignee_link_count + all_elements(:assignee_link).count + end + + def avatar_counter + find_element(:avatar_counter) + end + def click_issue_link(title) click_link(title) end diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb index e5e26b1864b..d2732eb7dd2 100644 --- a/qa/qa/page/project/issue/show.rb +++ b/qa/qa/page/project/issue/show.rb @@ -22,30 +22,68 @@ module QA element :noteable_note_item end + view 'app/assets/javascripts/sidebar/components/assignees/assignee_avatar.vue' do + element :avatar_image + end + + view 'app/assets/javascripts/sidebar/components/assignees/assignee_title.vue' do + element :assignee_edit_link + element :assignee_title + end + + view 'app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue' do + element :more_assignees_link + end + + view 'app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue' do + element :remove_related_issue_button + end + view 'app/helpers/dropdowns_helper.rb' do element :dropdown_input_field end - view 'app/views/shared/notes/_form.html.haml' do - element :new_note_form, 'new-note' # rubocop:disable QA/ElementWithPattern - element :new_note_form, 'attr: :note' # rubocop:disable QA/ElementWithPattern + view 'app/views/shared/issuable/_close_reopen_button.html.haml' do + element :reopen_issue_button end view 'app/views/shared/issuable/_sidebar.html.haml' do + element :assignee_block element :labels_block element :edit_link_labels element :dropdown_menu_labels element :milestone_link end - view 'app/views/shared/issuable/_close_reopen_button.html.haml' do - element :reopen_issue_button + view 'app/views/shared/notes/_form.html.haml' do + element :new_note_form, 'new-note' # rubocop:disable QA/ElementWithPattern + element :new_note_form, 'attr: :note' # rubocop:disable QA/ElementWithPattern + end + + def assign(user) + click_element(:assignee_edit_link) + select_user(user.username) + click_body + end + + def assignee_title + find_element(:assignee_title) + end + + def avatar_image_count + wait_assignees_block_finish_loading do + all_elements(:avatar_image).count + end end def click_milestone_link click_element(:milestone_link) end + def click_remove_related_issue_button + click_element(:remove_related_issue_button) + end + # Adds a comment to an issue # attachment option should be an absolute path def comment(text, attachment: nil, filter: :all_activities) @@ -66,6 +104,10 @@ module QA end end + def more_assignees_link + find_element(:more_assignees_link) + end + def select_all_activities_filter select_filter_with_text('Show all activity') end @@ -103,6 +145,10 @@ module QA find_element(:labels_block) end + def toggle_more_assignees_link + click_element(:more_assignees_link) + end + private def select_filter_with_text(text) @@ -112,6 +158,20 @@ module QA find_element(:filter_options, text: text).click end end + + def select_user(username) + find("#{element_selector_css(:assignee_block)} input").set(username) + find('.dropdown-menu-user-link', text: "@#{username}").click + end + + def wait_assignees_block_finish_loading + within_element(:assignee_block) do + wait(reload: false, max: 10, interval: 1) do + finished_loading_block? + yield + end + end + end end end end diff --git a/qa/qa/page/project/job/show.rb b/qa/qa/page/project/job/show.rb index 5853f487f0b..cf847710024 100644 --- a/qa/qa/page/project/job/show.rb +++ b/qa/qa/page/project/job/show.rb @@ -3,17 +3,12 @@ module QA::Page module Project::Job class Show < QA::Page::Base - COMPLETED_STATUSES = %w[passed failed canceled blocked skipped manual].freeze # excludes created, pending, running - PASSED_STATUS = 'passed'.freeze + include Component::CiBadgeLink view 'app/assets/javascripts/jobs/components/job_log.vue' do element :build_trace end - view 'app/assets/javascripts/vue_shared/components/ci_badge_link.vue' do - element :status_badge - end - view 'app/assets/javascripts/jobs/components/stages_dropdown.vue' do element :pipeline_path end @@ -26,8 +21,16 @@ module QA::Page end # Reminder: You may wish to wait for a particular job status before checking output - def output - find_element(:build_trace).text + def output(wait: 5) + result = '' + + wait(reload: false, max: wait, interval: 1) do + result = find_element(:build_trace).text + + !result.empty? + end + + result end private @@ -37,16 +40,6 @@ module QA::Page has_element?(:build_trace, wait: 1) end end - - def completed?(timeout: 60) - wait(reload: false, max: timeout) do - COMPLETED_STATUSES.include?(status_badge) - end - end - - def status_badge - find_element(:status_badge).text - end end end end diff --git a/qa/qa/page/project/milestone/index.rb b/qa/qa/page/project/milestone/index.rb index 8ad7689ce70..6895c44f72f 100644 --- a/qa/qa/page/project/milestone/index.rb +++ b/qa/qa/page/project/milestone/index.rb @@ -17,5 +17,3 @@ module QA end end end - -QA::Page::Project::Milestone::Index.prepend_if_ee('QA::EE::Page::Project::Milestone::Index') diff --git a/qa/qa/page/project/pipeline/show.rb b/qa/qa/page/project/pipeline/show.rb index 3dca47a57e9..fd29c5eacdc 100644 --- a/qa/qa/page/project/pipeline/show.rb +++ b/qa/qa/page/project/pipeline/show.rb @@ -3,6 +3,8 @@ module QA::Page module Project::Pipeline class Show < QA::Page::Base + include Component::CiBadgeLink + view 'app/assets/javascripts/vue_shared/components/header_ci_component.vue' do element :pipeline_header, /header class.*ci-header-container.*/ # rubocop:disable QA/ElementWithPattern end @@ -16,6 +18,10 @@ module QA::Page element :job_link end + view 'app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue' do + element :linked_pipeline_button + end + view 'app/assets/javascripts/vue_shared/components/ci_icon.vue' do element :status_icon, 'ci-status-icon-${status}' # rubocop:disable QA/ElementWithPattern end @@ -38,6 +44,14 @@ module QA::Page end end + def has_job?(job_name) + has_element?(:job_link, text: job_name) + end + + def has_no_job?(job_name) + has_no_element?(:job_link, text: job_name) + end + def has_tag?(tag_name) within_element(:pipeline_badges) do has_selector?('.badge', text: tag_name) @@ -45,7 +59,11 @@ module QA::Page end def click_job(job_name) - find_element(:job_link, text: job_name).click + click_element(:job_link, text: job_name) + end + + def click_linked_job(project_name) + click_element(:linked_pipeline_button, text: /#{project_name}/) end def click_on_first_job diff --git a/qa/qa/page/project/settings/main.rb b/qa/qa/page/project/settings/main.rb index 6b26c82a46f..18d55598d90 100644 --- a/qa/qa/page/project/settings/main.rb +++ b/qa/qa/page/project/settings/main.rb @@ -11,6 +11,7 @@ module QA view 'app/views/projects/edit.html.haml' do element :advanced_settings + element :merge_request_settings end view 'app/views/projects/settings/_general.html.haml' do @@ -41,6 +42,12 @@ module QA end end + def expand_merge_requests_settings(&block) + expand_section(:merge_request_settings) do + MergeRequest.perform(&block) + end + end + def expand_visibility_project_features_permissions(&block) expand_section(:visibility_features_permissions_content) do VisibilityFeaturesPermissions.perform(&block) @@ -51,5 +58,3 @@ module QA end end end - -QA::Page::Project::Settings::Main.prepend_if_ee('QA::EE::Page::Project::Settings::Main') diff --git a/qa/qa/page/project/settings/merge_request.rb b/qa/qa/page/project/settings/merge_request.rb index f92528c4262..7da2c9d168c 100644 --- a/qa/qa/page/project/settings/merge_request.rb +++ b/qa/qa/page/project/settings/merge_request.rb @@ -8,7 +8,6 @@ module QA include Common view 'app/views/projects/edit.html.haml' do - element :merge_request_settings element :save_merge_request_changes end @@ -16,14 +15,18 @@ module QA element :radio_button_merge_ff end + def click_save_changes + click_element :save_merge_request_changes + end + def enable_ff_only - expand_section(:merge_request_settings) do - click_element :radio_button_merge_ff - click_element :save_merge_request_changes - end + click_element :radio_button_merge_ff + click_save_changes end end end end end end + +QA::Page::Project::Settings::MergeRequest.prepend_if_ee("QA::EE::Page::Project::Settings::MergeRequest") diff --git a/qa/qa/page/project/settings/mirroring_repositories.rb b/qa/qa/page/project/settings/mirroring_repositories.rb index 441235afca8..4afe042d9fb 100644 --- a/qa/qa/page/project/settings/mirroring_repositories.rb +++ b/qa/qa/page/project/settings/mirroring_repositories.rb @@ -15,7 +15,9 @@ module QA element :mirror_repository_button element :mirror_repository_url_cell element :mirror_last_update_at_cell + element :mirror_error_badge element :mirrored_repository_row + element :copy_public_key_button end view 'app/views/projects/mirrors/_mirror_repos_form.html.haml' do @@ -24,6 +26,17 @@ module QA view 'app/views/shared/_remote_mirror_update_button.html.haml' do element :update_now_button + element :updating_button + end + + view 'app/views/projects/mirrors/_ssh_host_keys.html.haml' do + element :detect_host_keys + element :fingerprints_list + end + + view 'app/views/projects/mirrors/_authentication_method.html.haml' do + element :authentication_method + element :password end def repository_url=(value) @@ -35,17 +48,40 @@ module QA end def mirror_direction=(value) - raise ArgumentError, "Mirror direction must be :push or :pull" unless [:push, :pull].include? value + raise ArgumentError, "Mirror direction must be 'Push' or 'Pull'" unless %w(Push Pull).include? value select_element(:mirror_direction, value) + + # Changing the mirror direction causes the fields below to change, + # and that change is animated, so we need to wait for the animation + # to complete otherwise changes to those fields could fail + wait_for_animated_element :authentication_method end def authentication_method=(value) - raise ArgumentError, "Authentication method must be :password or :none" unless [:password, :none].include? value + raise ArgumentError, "Authentication method must be 'SSH public key', 'Password', or 'None'" unless %w(Password None SSH\ public\ key).include? value select_element(:authentication_method, value) end + def public_key(url) + row_index = find_repository_row_index url + + within_element_by_index(:mirrored_repository_row, row_index) do + find_element(:copy_public_key_button)['data-clipboard-text'] + end + end + + def detect_host_keys + click_element :detect_host_keys + + # The host key detection process is interrupted if we navigate away + # from the page before the fingerprint appears. + wait(max: 5) do + find_element(:fingerprints_list).has_text? /.*/ + end + end + def mirror_repository click_element :mirror_repository_button end @@ -54,7 +90,9 @@ module QA row_index = find_repository_row_index url within_element_by_index(:mirrored_repository_row, row_index) do - click_element :update_now_button + # When a repository is first mirrored, the update process might + # already be started, so the button is already "clicked" + click_element :update_now_button unless has_element? :updating_button end # Wait a few seconds for the sync to occur and then refresh the page @@ -72,16 +110,19 @@ module QA # Fail early if the page still shows that there has been no update within_element_by_index(:mirrored_repository_row, row_index) do find_element(:mirror_last_update_at_cell, wait: 0).assert_no_text('Never') + assert_no_element(:mirror_error_badge) end end private def find_repository_row_index(target_url) - all_elements(:mirror_repository_url_cell).index do |url| - # The url might be a sanitized url but the target_url won't be so - # we compare just the paths instead of the full url - URI.parse(url.text).path == target_url.path + wait(max: 5, reload: false) do + all_elements(:mirror_repository_url_cell).index do |url| + # The url might be a sanitized url but the target_url won't be so + # we compare just the paths instead of the full url + URI.parse(url.text).path == target_url.path + end end end end diff --git a/qa/qa/page/project/settings/repository.rb b/qa/qa/page/project/settings/repository.rb index 437a945aceb..58ed37870b7 100644 --- a/qa/qa/page/project/settings/repository.rb +++ b/qa/qa/page/project/settings/repository.rb @@ -16,7 +16,7 @@ module QA end view 'app/views/projects/mirrors/_mirror_repos.html.haml' do - element :mirroring_repositories_settings + element :mirroring_repositories_settings_section end def expand_deploy_keys(&block) @@ -38,7 +38,7 @@ module QA end def expand_mirroring_repositories(&block) - expand_section(:mirroring_repositories_settings) do + expand_section(:mirroring_repositories_settings_section) do MirroringRepositories.perform(&block) end end @@ -47,3 +47,5 @@ module QA end end end + +QA::Page::Project::Settings::Repository.prepend_if_ee('QA::EE::Page::Project::Settings::Repository') diff --git a/qa/qa/page/validator.rb b/qa/qa/page/validator.rb index 9b2d0a1a41d..75e48b5785e 100644 --- a/qa/qa/page/validator.rb +++ b/qa/qa/page/validator.rb @@ -17,7 +17,7 @@ module QA def constants @consts ||= @module.constants.map do |const| - @module.const_get(const) + @module.const_get(const, false) end end diff --git a/qa/qa/resource/fork.rb b/qa/qa/resource/fork.rb index 54b0c2d0059..d203e8eb264 100644 --- a/qa/qa/resource/fork.rb +++ b/qa/qa/resource/fork.rb @@ -30,7 +30,7 @@ module QA Page::Main::Menu.perform(&:sign_out) Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.perform do |login| - login.sign_in_using_credentials(user) + login.sign_in_using_credentials(user: user) end upstream.project.visit! diff --git a/qa/qa/resource/issue.rb b/qa/qa/resource/issue.rb index a894e5c2033..0817a9de06f 100644 --- a/qa/qa/resource/issue.rb +++ b/qa/qa/resource/issue.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'securerandom' + module QA module Resource class Issue < Base @@ -13,11 +15,15 @@ module QA end attribute :id + attribute :iid + attribute :assignee_ids attribute :labels attribute :title def initialize + @assignee_ids = [] @labels = [] + @title = "Issue title #{SecureRandom.hex(8)}" end def fabricate! @@ -25,10 +31,10 @@ module QA Page::Project::Show.perform(&:go_to_new_issue) - Page::Project::Issue::New.perform do |page| # rubocop:disable QA/AmbiguousPageObjectName - page.add_title(@title) - page.add_description(@description) - page.create_new_issue + Page::Project::Issue::New.perform do |new_page| + new_page.add_title(@title) + new_page.add_description(@description) + new_page.create_new_issue end end @@ -42,6 +48,7 @@ module QA def api_post_body { + assignee_ids: assignee_ids, labels: labels, title: title }.tap do |hash| diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb index a0389390c83..caaa766e982 100644 --- a/qa/qa/resource/project.rb +++ b/qa/qa/resource/project.rb @@ -88,6 +88,10 @@ module QA "#{api_get_path}/members" end + def api_runners_path + "#{api_get_path}/runners" + end + def api_post_path '/projects' end @@ -108,6 +112,11 @@ module QA post_body end + def runners + response = get Runtime::API::Request.new(api_client, api_runners_path).url + parse_body(response) + end + def share_with_group(invitee, access_level = Resource::Members::AccessLevel::DEVELOPER) post Runtime::API::Request.new(api_client, "/projects/#{id}/share").url, { group_id: invitee.id, group_access: access_level } end diff --git a/qa/qa/resource/repository/commit.rb b/qa/qa/resource/repository/commit.rb index 61c2ad6bfc0..4b5e8535ade 100644 --- a/qa/qa/resource/repository/commit.rb +++ b/qa/qa/resource/repository/commit.rb @@ -21,14 +21,16 @@ module QA @commit_message = 'QA Test - Commit message' end - def files=(files) - if !files.is_a?(Array) || - files.empty? || - files.any? { |file| !file.has_key?(:file_path) || !file.has_key?(:content) } - raise ArgumentError, "Please provide an array of hashes e.g.: [{file_path: 'file1', content: 'foo'}]" - end + def add_files(files) + validate_files!(files) + + @add_files = files + end + + def update_files(files) + validate_files!(files) - @files = files + @update_files = files end def resource_web_url(resource) @@ -56,8 +58,19 @@ module QA end def actions - @files.map do |file| - file.merge({ action: "create" }) + pending_actions = [] + pending_actions << @add_files.map { |file| file.merge({ action: "create" }) } if @add_files + pending_actions << @update_files.map { |file| file.merge({ action: "update" }) } if @update_files + pending_actions.flatten + end + + private + + def validate_files!(files) + if !files.is_a?(Array) || + files.empty? || + files.any? { |file| !file.has_key?(:file_path) || !file.has_key?(:content) } + raise ArgumentError, "Please provide an array of hashes e.g.: [{file_path: 'file1', content: 'foo'}]" end end end diff --git a/qa/qa/resource/repository/push.rb b/qa/qa/resource/repository/push.rb index a5827fb6e73..68674248be2 100644 --- a/qa/qa/resource/repository/push.rb +++ b/qa/qa/resource/repository/push.rb @@ -8,9 +8,9 @@ module QA class Push < Base attr_accessor :file_name, :file_content, :commit_message, :branch_name, :new_branch, :output, :repository_http_uri, - :repository_ssh_uri, :ssh_key, :user, :use_lfs + :repository_ssh_uri, :ssh_key, :user, :use_lfs, :tag_name - attr_writer :remote_branch + attr_writer :remote_branch, :gpg_key_id def initialize @file_name = 'file.txt' @@ -21,6 +21,8 @@ module QA @repository_http_uri = "" @ssh_key = nil @use_lfs = false + @tag_name = nil + @gpg_key_id = nil end def remote_branch @@ -67,29 +69,43 @@ module QA email = user.email end + unless @gpg_key_id.nil? + repository.gpg_key_id = @gpg_key_id + end + @output += repository.clone repository.configure_identity(username, email) @output += repository.checkout(branch_name, new_branch: new_branch) - if @directory - @directory.each_child do |f| - @output += repository.add_file(f.basename, f.read) if f.file? - end - elsif @files - @files.each do |f| - repository.add_file(f[:name], f[:content]) - end + if @tag_name + @output += repository.delete_tag(@tag_name) else - @output += repository.add_file(file_name, file_content) - end + if @directory + @directory.each_child do |f| + @output += repository.add_file(f.basename, f.read) if f.file? + end + elsif @files + @files.each do |f| + repository.add_file(f[:name], f[:content]) + end + else + @output += repository.add_file(file_name, file_content) + end - @output += repository.commit(commit_message) - @output += repository.push_changes("#{branch_name}:#{remote_branch}") + @output += commit_to repository + @output += repository.push_changes("#{branch_name}:#{remote_branch}") + end repository.delete_ssh_key end end + + private + + def commit_to(repository) + @gpg_key_id.nil? ? repository.commit(@commit_message) : repository.commit_with_gpg(@commit_message) + end end end end diff --git a/qa/qa/resource/repository/wiki_push.rb b/qa/qa/resource/repository/wiki_push.rb index 8edaff70ac6..e926c00d380 100644 --- a/qa/qa/resource/repository/wiki_push.rb +++ b/qa/qa/resource/repository/wiki_push.rb @@ -25,14 +25,7 @@ module QA end def repository_ssh_uri - @repository_ssh_uri ||= begin - wiki.visit! - Page::Project::Wiki::Show.act do - click_clone_repository - choose_repository_clone_ssh - repository_location.uri - end - end + @repository_ssh_uri ||= wiki.repository_ssh_location.uri end def fabricate! diff --git a/qa/qa/resource/runner.rb b/qa/qa/resource/runner.rb index 9c2e138bade..1be2429bc04 100644 --- a/qa/qa/resource/runner.rb +++ b/qa/qa/resource/runner.rb @@ -6,8 +6,9 @@ module QA module Resource class Runner < Base attr_writer :name, :tags, :image - attr_accessor :config + attr_accessor :config, :token + attribute :id attribute :project do Project.fabricate_via_api! do |resource| resource.name = 'project-with-ci-cd' @@ -28,9 +29,9 @@ module QA end def fabricate_via_api! - Service::Runner.new(name).tap do |runner| + Service::DockerRun::GitlabRunner.new(name).tap do |runner| runner.pull - runner.token = project.runners_token + runner.token = @token ||= project.runners_token runner.address = Runtime::Scenario.gitlab_address runner.tags = tags runner.image = image @@ -40,6 +41,18 @@ module QA end end + def remove_via_api! + @id = project.runners.find { |runner| runner[:description] == name }[:id] + + super + + Service::DockerRun::GitlabRunner.new(name).remove! + end + + def api_delete_path + "/runners/#{id}" + end + def api_get_path end diff --git a/qa/qa/resource/sandbox.rb b/qa/qa/resource/sandbox.rb index 47bd86440a0..6ee3dcf350f 100644 --- a/qa/qa/resource/sandbox.rb +++ b/qa/qa/resource/sandbox.rb @@ -10,6 +10,7 @@ module QA attr_accessor :path attribute :id + attribute :runners_token def initialize @path = Runtime::Namespace.sandbox_name diff --git a/qa/qa/resource/tag.rb b/qa/qa/resource/tag.rb new file mode 100644 index 00000000000..ac4fccec525 --- /dev/null +++ b/qa/qa/resource/tag.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module QA + module Resource + class Tag < Base + attr_accessor :project, :name, :ref + + def resource_web_url(resource) + super + rescue ResourceURLMissingError + # this particular resource does not expose a web_url property + end + + def api_get_path + "/projects/#{project.id}/repository/tags/#{name}" + end + + def api_post_path + "/projects/#{project.id}/repository/tags" + end + + def api_post_body + { + tag_name: name, + ref: ref + } + end + end + end +end diff --git a/qa/qa/resource/user.rb b/qa/qa/resource/user.rb index 911d2b2f506..dcf145c9882 100644 --- a/qa/qa/resource/user.rb +++ b/qa/qa/resource/user.rb @@ -26,7 +26,7 @@ module QA end def name - @name ||= api_resource&.dig(:name) || username + @name ||= api_resource&.dig(:name) || "QA User #{unique_id}" end def email @@ -53,7 +53,7 @@ module QA if credentials_given? Page::Main::Login.perform do |login| - login.sign_in_using_credentials(self) + login.sign_in_using_credentials(user: self) end else Page::Main::Login.perform do |login| @@ -91,9 +91,8 @@ module QA def self.fabricate_or_use(username = nil, password = nil) if Runtime::Env.signup_disabled? - self.new.tap do |user| + self.fabricate_via_api! do |user| user.username = username - user.password = password end else self.fabricate! diff --git a/qa/qa/resource/user_gpg.rb b/qa/qa/resource/user_gpg.rb new file mode 100644 index 00000000000..25d74ad8ce5 --- /dev/null +++ b/qa/qa/resource/user_gpg.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module QA + module Resource + class UserGPG < Base + attr_accessor :id, :gpg + attr_reader :key_id + + def initialize + @gpg = Runtime::GPG.new + @key_id = @gpg.key_id + end + + def fabricate_via_api! + super + @id = self.api_response[:id] + rescue ResourceFabricationFailedError => error + if error.message.include? 'has already been taken' + self + else + raise ResourceFabricationFailedError error + end + end + + def resource_web_url(resource) + super + rescue ResourceURLMissingError + # this particular resource does not expose a web_url property + end + + def api_get_path + "/user/gpg_keys/#{@id}" + end + + def api_post_path + '/user/gpg_keys' + end + + def api_post_body + { + key: @gpg.key + } + end + end + end +end diff --git a/qa/qa/resource/wiki.rb b/qa/qa/resource/wiki.rb index 6e3648dba0b..45d5da9346d 100644 --- a/qa/qa/resource/wiki.rb +++ b/qa/qa/resource/wiki.rb @@ -21,6 +21,15 @@ module QA end end + attribute :repository_ssh_location do + Page::Project::Wiki::Show.perform(&:click_clone_repository) + + Page::Project::Wiki::GitAccess.perform do |git_access| + git_access.choose_repository_clone_ssh + git_access.repository_location + end + end + def fabricate! project.visit! diff --git a/qa/qa/runtime/api/client.rb b/qa/qa/runtime/api/client.rb index 594913c757a..1b0adbc9053 100644 --- a/qa/qa/runtime/api/client.rb +++ b/qa/qa/runtime/api/client.rb @@ -50,7 +50,7 @@ module QA unless Page::Main::Menu.perform { |p| p.has_personal_area?(wait: 0) } Runtime::Browser.visit(@address, Page::Main::Login) - Page::Main::Login.perform { |login| login.sign_in_using_credentials(@user) } + Page::Main::Login.perform { |login| login.sign_in_using_credentials(user: @user) } end token = Resource::PersonalAccessToken.fabricate!.access_token diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb index 77d04aa7594..4789b380377 100644 --- a/qa/qa/runtime/browser.rb +++ b/qa/qa/runtime/browser.rb @@ -65,7 +65,7 @@ module QA # QA::Runtime::Env.browser.capitalize will work for every driver type except PhantomJS. # We will have no use to use PhantomJS so this shouldn't be a problem. - options = Selenium::WebDriver.const_get(QA::Runtime::Env.browser.capitalize)::Options.new + options = Selenium::WebDriver.const_get(QA::Runtime::Env.browser.capitalize, false)::Options.new if QA::Runtime::Env.browser == :chrome options.add_argument("window-size=1480,2200") diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb index 594e5712ab2..b4047ef5088 100644 --- a/qa/qa/runtime/env.rb +++ b/qa/qa/runtime/env.rb @@ -145,6 +145,54 @@ module QA ENV['GITLAB_QA_PASSWORD_2'] end + def gitlab_qa_username_3 + ENV['GITLAB_QA_USERNAME_3'] || 'gitlab-qa-user3' + end + + def gitlab_qa_password_3 + ENV['GITLAB_QA_PASSWORD_3'] + end + + def gitlab_qa_username_4 + ENV['GITLAB_QA_USERNAME_4'] || 'gitlab-qa-user4' + end + + def gitlab_qa_password_4 + ENV['GITLAB_QA_PASSWORD_4'] + end + + def gitlab_qa_username_5 + ENV['GITLAB_QA_USERNAME_5'] || 'gitlab-qa-user5' + end + + def gitlab_qa_password_5 + ENV['GITLAB_QA_PASSWORD_5'] + end + + def gitlab_qa_username_6 + ENV['GITLAB_QA_USERNAME_6'] || 'gitlab-qa-user6' + end + + def gitlab_qa_password_6 + ENV['GITLAB_QA_PASSWORD_6'] + end + + def gitlab_qa_1p_email + ENV['GITLAB_QA_1P_EMAIL'] + end + + def gitlab_qa_1p_password + ENV['GITLAB_QA_1P_PASSWORD'] + end + + def gitlab_qa_1p_secret + ENV['GITLAB_QA_1P_SECRET'] + end + + def gitlab_qa_1p_github_uuid + ENV['GITLAB_QA_1P_GITHUB_UUID'] + end + def knapsack? !!(ENV['KNAPSACK_GENERATE_REPORT'] || ENV['KNAPSACK_REPORT_PATH'] || ENV['KNAPSACK_TEST_FILE_PATTERN']) end diff --git a/qa/qa/runtime/fixtures.rb b/qa/qa/runtime/fixtures.rb index 02cecffd4df..f91218ea0b5 100644 --- a/qa/qa/runtime/fixtures.rb +++ b/qa/qa/runtime/fixtures.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'tmpdir' + module QA module Runtime module Fixtures @@ -18,6 +20,19 @@ module QA parse_body(response)[:content] end + def with_fixtures(fixtures) + dir = Dir.mktmpdir + fixtures.each do |file_def| + path = File.join(dir, file_def[:file_path]) + FileUtils.mkdir_p(File.dirname(path)) + File.write(path, file_def[:content]) + end + + yield dir + ensure + FileUtils.remove_entry(dir) + end + private def api_client diff --git a/qa/qa/runtime/gpg.rb b/qa/qa/runtime/gpg.rb new file mode 100644 index 00000000000..9f8baf7e580 --- /dev/null +++ b/qa/qa/runtime/gpg.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module QA + module Runtime + class GPG + attr_reader :key, :key_id + + def initialize + @key_id = 'B8358D73048DACC4' + import_key(File.expand_path('qa/ee/fixtures/gpg/admin.asc')) + @key = collect_key.first + end + + private + + def import_key(path) + import_key = "gpg --import #{path}" + execute(import_key) + end + + def collect_key + get_ascii_format = "gpg --armor --export #{@key_id}" + execute(get_ascii_format) + end + + def execute(command) + Open3.capture2e(*command) do |stdin, out, wait| + out.each_char { |char| print char } + + if wait.value.exited? && wait.value.exitstatus.nonzero? + raise CommandError, "Command `#{command}` failed!" + end + end + end + end + end +end diff --git a/qa/qa/runtime/release.rb b/qa/qa/runtime/release.rb index 4f96e0cf44b..18a6736afcf 100644 --- a/qa/qa/runtime/release.rb +++ b/qa/qa/runtime/release.rb @@ -19,7 +19,7 @@ module QA end def strategy - QA.const_get("QA::#{version}::Strategy") + Object.const_get("QA::#{version}::Strategy", false) end def self.method_missing(name, *args) diff --git a/qa/qa/runtime/user.rb b/qa/qa/runtime/user.rb index 011e4a548a5..3c26a3ad691 100644 --- a/qa/qa/runtime/user.rb +++ b/qa/qa/runtime/user.rb @@ -25,6 +25,10 @@ module QA Runtime::Env.user_password || default_password end + def email + default_email + end + def ldap_user? Runtime::Env.ldap_username && Runtime::Env.ldap_password end diff --git a/qa/qa/scenario/test/integration/ldap_no_server.rb b/qa/qa/scenario/test/integration/ldap_no_server.rb new file mode 100644 index 00000000000..be71fc0ef6d --- /dev/null +++ b/qa/qa/scenario/test/integration/ldap_no_server.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module QA + module Scenario + module Test + module Integration + class LDAPNoServer < Test::Instance::All + tags :ldap_no_server + end + end + end + end +end diff --git a/qa/qa/service/docker_run/base.rb b/qa/qa/service/docker_run/base.rb new file mode 100644 index 00000000000..3f42c09ad2c --- /dev/null +++ b/qa/qa/service/docker_run/base.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module QA + module Service + module DockerRun + class Base + include Service::Shellout + + def initialize + @network = Runtime::Scenario.attributes[:network] || 'test' + end + + def network + shell "docker network inspect #{@network}" + rescue CommandError + 'bridge' + else + @network + end + + def pull + shell "docker pull #{@image}" + end + + def host_name + "#{@name}.#{network}" + end + + def register! + raise NotImplementedError + end + + def remove! + shell "docker rm -f #{@name}" if running? + end + + def running? + `docker ps -f name=#{@name}`.include?(@name) + end + end + end + end +end diff --git a/qa/qa/service/docker_run/gitlab_runner.rb b/qa/qa/service/docker_run/gitlab_runner.rb new file mode 100644 index 00000000000..6856a5a8399 --- /dev/null +++ b/qa/qa/service/docker_run/gitlab_runner.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require 'securerandom' + +module QA + module Service + module DockerRun + class GitlabRunner < Base + attr_accessor :token, :address, :tags, :image, :run_untagged + attr_writer :config + + def initialize(name) + @image = 'gitlab/gitlab-runner:alpine' + @name = name || "qa-runner-#{SecureRandom.hex(4)}" + @tags = %w[qa test] + @run_untagged = false + + super() + end + + def config + @config ||= <<~END + concurrent = 1 + check_interval = 0 + + [session_server] + session_timeout = 1800 + END + end + + def register! + shell <<~CMD.tr("\n", ' ') + docker run -d --rm --entrypoint=/bin/sh + --network #{network} --name #{@name} + -p 8093:8093 + -e CI_SERVER_URL=#{@address} + -e REGISTER_NON_INTERACTIVE=true + -e REGISTRATION_TOKEN=#{@token} + -e RUNNER_EXECUTOR=shell + -e RUNNER_TAG_LIST=#{@tags.join(',')} + -e RUNNER_NAME=#{@name} + #{@image} -c "#{register_command}" + CMD + end + + private + + def register_command + <<~CMD + printf '#{config.chomp.gsub(/\n/, "\\n").gsub('"', '\"')}' > /etc/gitlab-runner/config.toml && + gitlab-runner register --run-untagged=#{@run_untagged} && + gitlab-runner run + CMD + end + end + end + end +end diff --git a/qa/qa/service/docker_run/ldap.rb b/qa/qa/service/docker_run/ldap.rb new file mode 100644 index 00000000000..c33d75ff640 --- /dev/null +++ b/qa/qa/service/docker_run/ldap.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module QA + module Service + module DockerRun + class LDAP < Base + def initialize(volume) + @image = 'osixia/openldap:latest' + @name = 'ldap-server' + @volume = volume + + super() + end + + def register! + shell <<~CMD.tr("\n", ' ') + docker run -d --rm + --network #{network} + --hostname #{host_name} + --name #{@name} + -p 389:389 + --volume #{volume_or_fixture(@volume)}:/container/service/slapd/assets/config/bootstrap/ldif/custom + #{@image} --copy-service + CMD + end + + def volume_or_fixture(volume_name) + if volume_exists?(volume_name) + volume_name + else + File.expand_path("../fixtures/ldap/#{volume_name}", __dir__) + end + end + + def volume_exists?(volume_name) + `docker volume ls -q -f name=#{volume_name}`.include?(volume_name) + end + end + end + end +end diff --git a/qa/qa/service/docker_run/node_js.rb b/qa/qa/service/docker_run/node_js.rb new file mode 100644 index 00000000000..642f1d1a33a --- /dev/null +++ b/qa/qa/service/docker_run/node_js.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module QA + module Service + module DockerRun + class NodeJs < Base + def initialize(volume_host_path) + @image = 'node:12.11.1-alpine' + @name = "qa-node-#{SecureRandom.hex(8)}" + @volume_host_path = volume_host_path + + super() + end + + def publish! + # When we run the tests via gitlab-qa, we use docker-in-docker + # which means that host of a volume mount would be the host that + # started the gitlab-qa QA container (e.g., the CI runner), + # not the gitlab-qa container itself. That means we can't + # mount a volume from the file system inside the gitlab-qa + # container. + # + # Instead, we copy the files into the container. + shell <<~CMD.tr("\n", ' ') + docker run -d --rm + --network #{network} + --hostname #{host_name} + --name #{@name} + --volume #{@volume_host_path}:/home/node + #{@image} sh -c "sleep 60" + CMD + shell "docker cp #{@volume_host_path}/. #{@name}:/home/node" + shell "docker exec -t #{@name} sh -c 'cd /home/node && npm publish'" + end + end + end + end +end diff --git a/qa/qa/service/runner.rb b/qa/qa/service/runner.rb deleted file mode 100644 index 6fc5984b12a..00000000000 --- a/qa/qa/service/runner.rb +++ /dev/null @@ -1,73 +0,0 @@ -# frozen_string_literal: true - -require 'securerandom' - -module QA - module Service - class Runner - include Service::Shellout - - attr_accessor :token, :address, :tags, :image, :run_untagged - attr_writer :config - - def initialize(name) - @image = 'gitlab/gitlab-runner:alpine' - @name = name || "qa-runner-#{SecureRandom.hex(4)}" - @network = Runtime::Scenario.attributes[:network] || 'test' - @tags = %w[qa test] - @run_untagged = false - end - - def config - @config ||= <<~END - concurrent = 1 - check_interval = 0 - - [session_server] - session_timeout = 1800 - END - end - - def network - shell "docker network inspect #{@network}" - rescue CommandError - 'bridge' - else - @network - end - - def pull - shell "docker pull #{@image}" - end - - def register! - shell <<~CMD.tr("\n", ' ') - docker run -d --rm --entrypoint=/bin/sh - --network #{network} --name #{@name} - -p 8093:8093 - -e CI_SERVER_URL=#{@address} - -e REGISTER_NON_INTERACTIVE=true - -e REGISTRATION_TOKEN=#{@token} - -e RUNNER_EXECUTOR=shell - -e RUNNER_TAG_LIST=#{@tags.join(',')} - -e RUNNER_NAME=#{@name} - #{@image} -c "#{register_command}" - CMD - end - - def remove! - shell "docker rm -f #{@name}" - end - - private - - def register_command - <<~CMD - printf '#{config.chomp.gsub(/\n/, "\\n").gsub('"', '\"')}' > /etc/gitlab-runner/config.toml && - gitlab-runner register --run-untagged=#{@run_untagged} && - gitlab-runner run - CMD - end - end - end -end diff --git a/qa/qa/specs/features/api/1_manage/rate_limits_spec.rb b/qa/qa/specs/features/api/1_manage/rate_limits_spec.rb index 44c5e0b4196..819739ac535 100644 --- a/qa/qa/specs/features/api/1_manage/rate_limits_spec.rb +++ b/qa/qa/specs/features/api/1_manage/rate_limits_spec.rb @@ -3,11 +3,8 @@ module QA context 'Manage with IP rate limits', :requires_admin do describe 'Users API' do - before(:context) do - @api_client = Runtime::API::Client.new(:gitlab, ip_limits: true) - end - - let(:request) { Runtime::API::Request.new(@api_client, '/users') } + let(:api_client) { Runtime::API::Client.new(:gitlab, ip_limits: true) } + let(:request) { Runtime::API::Request.new(api_client, '/users') } it 'GET /users' do 5.times do diff --git a/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb b/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb index ce8425cb3d1..5ba434a7781 100644 --- a/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb +++ b/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb @@ -8,7 +8,7 @@ module QA describe 'Compare archives of different user projects with the same name and check they\'re different' do include Support::Api - before(:all) do + before do @project_name = "project-archive-download-#{SecureRandom.hex(8)}" @archive_types = %w(tar.gz tar.bz2 tar zip) @users = { @@ -46,7 +46,7 @@ module QA project.standalone = true project.add_name_uuid = false project.name = project_name - project.path_with_namespace = "#{user.name}/#{project_name}" + project.path_with_namespace = "#{user.username}/#{project_name}" project.user = user project.api_client = api_client end diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oauth_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oauth_spec.rb index db99488160b..a118176eb8a 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oauth_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oauth_spec.rb @@ -1,8 +1,7 @@ # frozen_string_literal: true module QA - # Failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/121 - context 'Manage', :orchestrated, :oauth, :quarantine do + context 'Manage', :orchestrated, :oauth do describe 'OAuth login' do it 'User logs in to GitLab with GitHub OAuth' do Runtime::Browser.visit(:gitlab, Page::Main::Login) diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb index e3fd835bb01..45c14d0537c 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb @@ -11,6 +11,7 @@ module QA project = Resource::Project.fabricate_via_api! do |resource| resource.name = 'project-to-test-mention' + resource.visibility = 'private' end project.visit! diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb index 3ce291bf8bc..c7b5e40d0be 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb @@ -13,8 +13,12 @@ module QA end project.visit! - Page::Project::Menu.perform(&:click_settings) - Page::Project::Settings::MergeRequest.perform(&:enable_ff_only) + Page::Project::Menu.perform(&:go_to_general_settings) + Page::Project::Settings::Main.perform do |main| + main.expand_merge_requests_settings do |settings| + settings.enable_ff_only + end + end merge_request = Resource::MergeRequest.fabricate! do |merge_request| merge_request.project = project diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb index c4a6ce13f4c..e42d538fdf8 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb @@ -6,8 +6,10 @@ module QA include Runtime::Fixtures def login - Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.perform(&:sign_in_using_credentials) + unless Page::Main::Menu.perform(&:signed_in?) + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.perform(&:sign_in_using_credentials) + end end before(:all) do @@ -57,12 +59,10 @@ module QA @project.visit! Page::Project::Show.perform(&:create_new_file!) - Page::File::Form.perform do |page| # rubocop:disable QA/AmbiguousPageObjectName - page.select_template template[:file_name], template[:name] + Page::File::Form.perform do |form| + form.select_template template[:file_name], template[:name] end - expect(page).to have_content('Template applied') - expect(page).to have_button('Undo') expect(page).to have_content(content[0..100]) Page::File::Form.perform(&:commit_changes) diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb index 448d4980727..059362704b4 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb @@ -25,8 +25,8 @@ module QA settings.expand_mirroring_repositories do |mirror_settings| # Configure the source project to push to the target project mirror_settings.repository_url = target_project_uri - mirror_settings.mirror_direction = :push - mirror_settings.authentication_method = :password + mirror_settings.mirror_direction = 'Push' + mirror_settings.authentication_method = 'Password' mirror_settings.password = Runtime::User.password mirror_settings.mirror_repository mirror_settings.update target_project_uri diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb index 796de44a012..cbc9f63f772 100644 --- a/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb @@ -7,7 +7,7 @@ module QA Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.perform(&:sign_in_using_credentials) - Page::Main::Menu.perform(&:click_snippets_link) + Page::Main::Menu.perform(&:go_to_snippets) Resource::Snippet.fabricate_via_browser_ui! do |snippet| snippet.title = 'Snippet title' diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb index 2952a54ff5d..5d91b70082c 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb @@ -6,7 +6,7 @@ module QA let(:executor) { "qa-runner-#{Time.now.to_i}" } after do - Service::Runner.new(executor).remove! + Service::DockerRun::GitlabRunner.new(executor).remove! end it 'users creates a pipeline which gets processed' do diff --git a/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb index 900ddcb7f59..58f129b846d 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb @@ -6,7 +6,7 @@ module QA let(:executor) { "qa-runner-#{Time.now.to_i}" } after do - Service::Runner.new(executor).remove! + Service::DockerRun::GitlabRunner.new(executor).remove! end it 'user registers a new specific runner' do diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb index 141166f6971..e45ce438fc2 100644 --- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb +++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb @@ -26,7 +26,7 @@ module QA end after do - Service::Runner.new(@runner_name).remove! + Service::DockerRun::GitlabRunner.new(@runner_name).remove! end keys = [ diff --git a/qa/qa/support/dates.rb b/qa/qa/support/dates.rb new file mode 100644 index 00000000000..47fc721afc1 --- /dev/null +++ b/qa/qa/support/dates.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module QA + module Support + module Dates + def current_date_yyyy_mm_dd + current_date.strftime("%Y/%m/%d") + end + + def next_month_yyyy_mm_dd + current_date.next_month.strftime("%Y/%m/%d") + end + + private + + def current_date + DateTime.now + end + end + end +end diff --git a/qa/qa/support/page/logging.rb b/qa/qa/support/page/logging.rb index 93d8fa99c0a..6b6e12f86de 100644 --- a/qa/qa/support/page/logging.rb +++ b/qa/qa/support/page/logging.rb @@ -4,6 +4,12 @@ module QA module Support module Page module Logging + def assert_no_element(name) + log("asserting no element :#{name}") + + super + end + def refresh log("refreshing #{current_url}") @@ -53,9 +59,10 @@ module QA elements end - def click_element(name, page = nil) + def click_element(name, page = nil, **kwargs) msg = ["clicking :#{name}"] msg << ", expecting to be at #{page.class}" if page + msg << "with args #{kwargs}" log(msg.compact.join(' ')) diff --git a/qa/qa/vendor/github/page/login.rb b/qa/qa/vendor/github/page/login.rb index 120ba6e6c06..f6e72bb01f9 100644 --- a/qa/qa/vendor/github/page/login.rb +++ b/qa/qa/vendor/github/page/login.rb @@ -12,6 +12,12 @@ module QA fill_in 'password', with: QA::Runtime::Env.github_password click_on 'Sign in' + otp = OnePassword::CLI.new.otp + + fill_in 'otp', with: otp + + click_on 'Verify' + click_on 'Authorize gitlab-qa' if has_button?('Authorize gitlab-qa') end end diff --git a/qa/qa/vendor/one_password/cli.rb b/qa/qa/vendor/one_password/cli.rb new file mode 100644 index 00000000000..3cb69391783 --- /dev/null +++ b/qa/qa/vendor/one_password/cli.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module QA + module Vendor + module OnePassword + class CLI + def initialize + @email = QA::Runtime::Env.gitlab_qa_1p_email + @password = QA::Runtime::Env.gitlab_qa_1p_password + @secret = QA::Runtime::Env.gitlab_qa_1p_secret + @github_uuid = QA::Runtime::Env.gitlab_qa_1p_github_uuid + end + + def otp + `#{op_path} get totp #{@github_uuid} --session=#{session_token}`.to_i + end + + private + + def session_token + `echo '#{@password}' | #{op_path} signin gitlab.1password.com #{@email} #{@secret} --output=raw --shorthand=gitlab_qa` + end + + def op_path + File.expand_path(File.join(%W[qa vendor one_password #{os} op])) + end + + def os + RUBY_PLATFORM.include?("darwin") ? "darwin" : "linux" + end + end + end + end +end diff --git a/qa/qa/vendor/one_password/darwin/op b/qa/qa/vendor/one_password/darwin/op Binary files differnew file mode 100755 index 00000000000..0f646522834 --- /dev/null +++ b/qa/qa/vendor/one_password/darwin/op diff --git a/qa/qa/vendor/one_password/linux/op b/qa/qa/vendor/one_password/linux/op Binary files differnew file mode 100755 index 00000000000..47ce87731be --- /dev/null +++ b/qa/qa/vendor/one_password/linux/op diff --git a/qa/spec/page/element_spec.rb b/qa/spec/page/element_spec.rb index 20d4a00c020..ff5e118cefa 100644 --- a/qa/spec/page/element_spec.rb +++ b/qa/spec/page/element_spec.rb @@ -113,6 +113,7 @@ describe QA::Page::Element do describe 'data-qa selectors' do subject { described_class.new(:my_element) } + it 'properly translates to a data-qa-selector' do expect(subject.selector_css).to include(%q([data-qa-selector="my_element"])) end diff --git a/qa/spec/resource/user_spec.rb b/qa/spec/resource/user_spec.rb index d612dfc530e..5845f7996a3 100644 --- a/qa/spec/resource/user_spec.rb +++ b/qa/spec/resource/user_spec.rb @@ -35,8 +35,8 @@ describe QA::Resource::User do end describe '#name' do - it 'defaults to the username' do - expect(subject.name).to eq(subject.username) + it 'defaults to a name based on the username' do + expect(subject.name).to match(/#{subject.username.tr('-', ' ')}/i) end it 'retrieves the name from the api_resource if present' do diff --git a/qa/spec/scenario/test/integration/ldap_spec.rb b/qa/spec/scenario/test/integration/ldap_spec.rb index b6d798bf504..86747cd8eb7 100644 --- a/qa/spec/scenario/test/integration/ldap_spec.rb +++ b/qa/spec/scenario/test/integration/ldap_spec.rb @@ -8,6 +8,14 @@ describe QA::Scenario::Test::Integration::LDAPNoTLS do end end +describe QA::Scenario::Test::Integration::LDAPNoServer do + context '#perform' do + it_behaves_like 'a QA scenario class' do + let(:tags) { [:ldap_no_server] } + end + end +end + describe QA::Scenario::Test::Integration::LDAPTLS do context '#perform' do it_behaves_like 'a QA scenario class' do |