summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Gemfile1
-rw-r--r--Gemfile.lock26
-rw-r--r--Guardfile43
-rw-r--r--app/assets/javascripts/boards/components/modal/index.vue3
-rw-r--r--app/views/projects/issues/_issue.html.haml2
-rw-r--r--changelogs/unreleased/Update-boards-components-models-index-vue.yml5
-rw-r--r--doc/development/testing_guide/best_practices.md8
-rw-r--r--doc/development/testing_guide/end_to_end/page_objects.md59
-rw-r--r--qa/qa/page/base.rb18
-rw-r--r--qa/qa/page/element.rb10
-rw-r--r--qa/qa/page/project/issue/index.rb4
-rw-r--r--qa/qa/resource/issue.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb7
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/close_issue_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb9
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb3
-rw-r--r--qa/spec/page/element_spec.rb18
21 files changed, 205 insertions, 30 deletions
diff --git a/Gemfile b/Gemfile
index 5dd7688579f..d27bc276088 100644
--- a/Gemfile
+++ b/Gemfile
@@ -411,6 +411,7 @@ group :test do
gem 'concurrent-ruby', '~> 1.1'
gem 'test-prof', '~> 0.10.0'
gem 'rspec_junit_formatter'
+ gem 'guard-rspec'
end
gem 'octokit', '~> 4.9'
diff --git a/Gemfile.lock b/Gemfile.lock
index 5b7785b9475..15465cd6b03 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -446,6 +446,20 @@ GEM
googleapis-common-protos-types (~> 1.0)
gssapi (1.2.0)
ffi (>= 1.0.1)
+ guard (2.15.1)
+ formatador (>= 0.2.4)
+ listen (>= 2.7, < 4.0)
+ lumberjack (>= 1.0.12, < 2.0)
+ nenv (~> 0.1)
+ notiffany (~> 0.0)
+ pry (>= 0.9.12)
+ shellany (~> 0.0)
+ thor (>= 0.18.1)
+ guard-compat (1.2.1)
+ guard-rspec (4.7.3)
+ guard (~> 2.1)
+ guard-compat (~> 1.1)
+ rspec (>= 2.99.0, < 4.0)
haml (5.0.4)
temple (>= 0.8.0)
tilt
@@ -561,6 +575,10 @@ GEM
xml-simple
licensee (8.9.2)
rugged (~> 0.24)
+ listen (3.1.5)
+ rb-fsevent (~> 0.9, >= 0.9.4)
+ rb-inotify (~> 0.9, >= 0.9.7)
+ ruby_dep (~> 1.2)
locale (2.1.2)
lograge (0.10.0)
actionpack (>= 4)
@@ -570,6 +588,7 @@ GEM
loofah (2.3.1)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
+ lumberjack (1.0.13)
mail (2.7.1)
mini_mime (>= 0.1.1)
mail_room (0.9.1)
@@ -598,6 +617,7 @@ GEM
mustermann (~> 1.0.0)
nakayoshi_fork (0.0.4)
nap (1.1.0)
+ nenv (0.3.0)
net-ldap (0.16.0)
net-ntp (2.1.3)
net-ssh (5.2.0)
@@ -608,6 +628,9 @@ GEM
mini_portile2 (~> 2.4.0)
nokogumbo (1.5.0)
nokogiri
+ notiffany (0.1.3)
+ nenv (~> 0.1)
+ shellany (~> 0.0)
numerizer (0.1.1)
oauth (0.5.4)
oauth2 (1.4.1)
@@ -898,6 +921,7 @@ GEM
ruby-progressbar (1.10.1)
ruby-saml (1.7.2)
nokogiri (>= 1.5.10)
+ ruby_dep (1.5.0)
ruby_parser (3.13.1)
sexp_processor (~> 4.9)
rubyntlm (0.6.2)
@@ -939,6 +963,7 @@ GEM
faraday (>= 0.7.6, < 1.0)
settingslogic (2.0.9)
sexp_processor (4.12.0)
+ shellany (0.0.1)
shoulda-matchers (4.0.1)
activesupport (>= 4.2.0)
sidekiq (5.2.7)
@@ -1193,6 +1218,7 @@ DEPENDENCIES
graphql-docs (~> 1.6.0)
grpc (~> 1.24.0)
gssapi
+ guard-rspec
haml_lint (~> 0.31.0)
hamlit (~> 2.8.8)
hangouts-chat (~> 0.0.5)
diff --git a/Guardfile b/Guardfile
new file mode 100644
index 00000000000..8a43f414ca9
--- /dev/null
+++ b/Guardfile
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+# More info at https://github.com/guard/guard#readme
+
+cmd = ENV['SPRING'] ? 'spring rspec' : 'bundle exec rspec'
+
+guard :rspec, cmd: cmd do
+ require "guard/rspec/dsl"
+ dsl = Guard::RSpec::Dsl.new(self)
+
+ directories %w(app ee lib spec)
+
+ # RSpec files
+ rspec = dsl.rspec
+ watch(rspec.spec_helper) { rspec.spec_dir }
+ watch(rspec.spec_support) { rspec.spec_dir }
+ watch(rspec.spec_files)
+
+ # Ruby files
+ ruby = dsl.ruby
+ dsl.watch_spec_files_for(ruby.lib_files)
+
+ # Rails files
+ rails = dsl.rails(view_extensions: %w(erb haml slim))
+ dsl.watch_spec_files_for(rails.app_files)
+ dsl.watch_spec_files_for(rails.views)
+
+ watch(rails.controllers) do |m|
+ [
+ rspec.spec.call("routing/#{m[1]}_routing"),
+ rspec.spec.call("controllers/#{m[1]}_controller")
+ ]
+ end
+
+ # Rails config changes
+ watch(rails.spec_helper) { rspec.spec_dir }
+ watch(rails.routes) { "#{rspec.spec_dir}/routing" }
+ watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
+
+ # Capybara features specs
+ watch(rails.view_dirs) { |m| rspec.spec.call("features/#{m[1]}") }
+ watch(rails.layouts) { |m| rspec.spec.call("features/#{m[1]}") }
+end
diff --git a/app/assets/javascripts/boards/components/modal/index.vue b/app/assets/javascripts/boards/components/modal/index.vue
index defa1f75ba2..618c2ada1f8 100644
--- a/app/assets/javascripts/boards/components/modal/index.vue
+++ b/app/assets/javascripts/boards/components/modal/index.vue
@@ -1,6 +1,7 @@
<script>
/* global ListIssue */
import { urlParamsToObject } from '~/lib/utils/common_utils';
+import boardsStore from '~/boards/stores/boards_store';
import ModalHeader from './header.vue';
import ModalList from './list.vue';
import ModalFooter from './footer.vue';
@@ -109,7 +110,7 @@ export default {
loadIssues(clearIssues = false) {
if (!this.showAddIssuesModal) return false;
- return gl.boardService
+ return boardsStore
.getBacklog({
...urlParamsToObject(this.filter.path),
page: this.page,
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index 1809288311a..c8ab47888d0 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -1,5 +1,5 @@
-# DANGER: Any changes to this file need to be reflected in issuables_list/components/issuable.vue!
-%li{ id: dom_id(issue), class: issue_css_classes(issue), url: issue_path(issue), data: { labels: issue.label_ids, id: issue.id } }
+%li{ id: dom_id(issue), class: issue_css_classes(issue), url: issue_path(issue), data: { labels: issue.label_ids, id: issue.id, qa_selector: 'issue', qa_issue_title: issue.title } }
.issue-box
- if @can_bulk_update
.issue-check.hidden
diff --git a/changelogs/unreleased/Update-boards-components-models-index-vue.yml b/changelogs/unreleased/Update-boards-components-models-index-vue.yml
new file mode 100644
index 00000000000..e328c6ca0da
--- /dev/null
+++ b/changelogs/unreleased/Update-boards-components-models-index-vue.yml
@@ -0,0 +1,5 @@
+---
+title: Remove all references to BoardsService in index.vue
+merge_request: 20152
+author: nuwe1
+type: other
diff --git a/doc/development/testing_guide/best_practices.md b/doc/development/testing_guide/best_practices.md
index c79642d8fed..fe3989474e6 100644
--- a/doc/development/testing_guide/best_practices.md
+++ b/doc/development/testing_guide/best_practices.md
@@ -44,6 +44,14 @@ bundle exec rspec
bundle exec rspec spec/[path]/[to]/[spec].rb
```
+Use [guard](https://github.com/guard/guard) to continuously monitor for changes and only run matching tests:
+
+```sh
+bundle exec guard
+```
+
+When using spring and guard together, use `SPRING=1 bundle exec guard` instead to make use of spring.
+
### General guidelines
- Use a single, top-level `describe ClassName` block.
diff --git a/doc/development/testing_guide/end_to_end/page_objects.md b/doc/development/testing_guide/end_to_end/page_objects.md
index 28111c18378..554995fa2e2 100644
--- a/doc/development/testing_guide/end_to_end/page_objects.md
+++ b/doc/development/testing_guide/end_to_end/page_objects.md
@@ -167,6 +167,65 @@ There are two supported methods of defining elements within a view.
Any existing `.qa-selector` class should be considered deprecated
and we should prefer the `data-qa-selector` method of definition.
+### Dynamic element selection
+
+> Introduced in GitLab 12.5
+
+A common occurrence in automated testing is selecting a single "one-of-many" element.
+In a list of several items, how do you differentiate what you are selecting on?
+The most common workaround for this is via text matching. Instead, a better practice is
+by matching on that specific element by a unique identifier, rather than by text.
+
+We got around this by adding the `data-qa-*` extensible selection mechanism.
+
+#### Examples
+
+**Example 1**
+
+Given the following Rails view (using GitLab Issues as an example):
+
+```haml
+%ul.issues-list
+ - @issues.each do |issue|
+ %li.issue{data: { qa_selector: 'issue', qa_issue_title: issue.title } }= link_to issue
+```
+
+We can select on that specific issue by matching on the Rails model.
+
+```ruby
+class Page::Project::Issues::Index < Page::Base
+ def has_issue?(issue)
+ has_element? :issue, issue_title: issue
+ end
+end
+```
+
+In our test, we can validate that this particular issue exists.
+
+```ruby
+describe 'Issue' do
+ it 'has an issue titled "hello"' do
+ Page::Project::Issues::Index.perform do |index|
+ expect(index).to have_issue('hello')
+ end
+ end
+end
+```
+
+**Example 2**
+
+*By an index...*
+
+```haml
+%ol
+ - @some_model.each_with_index do |model, idx|
+ %li.model{ data: { qa_selector: 'model', qa_index: idx } }
+```
+
+```ruby
+expect(the_page).to have_element(:model, index: 1) #=> select on the first model that appears in the list
+```
+
### Exceptions
In some cases it might not be possible or worthwhile to add a selector.
diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb
index c256a895718..ed4d33dc7a3 100644
--- a/qa/qa/page/base.rb
+++ b/qa/qa/page/base.rb
@@ -111,12 +111,18 @@ module QA
element.select value
end
- def has_element?(name, text: nil, wait: Capybara.default_max_wait_time)
- has_css?(element_selector_css(name), wait: wait, text: text)
+ def has_element?(name, **kwargs)
+ wait = kwargs[:wait] ? kwargs[:wait] && kwargs.delete(:wait) : Capybara.default_max_wait_time
+ text = kwargs[:text] ? kwargs[:text] && kwargs.delete(:text) : nil
+
+ has_css?(element_selector_css(name, kwargs), text: text, wait: wait)
end
- def has_no_element?(name, text: nil, wait: Capybara.default_max_wait_time)
- has_no_css?(element_selector_css(name), wait: wait, text: text)
+ def has_no_element?(name, **kwargs)
+ wait = kwargs[:wait] ? kwargs[:wait] && kwargs.delete(:wait) : Capybara.default_max_wait_time
+ text = kwargs[:text] ? kwargs[:text] && kwargs.delete(:text) : nil
+
+ has_no_css?(element_selector_css(name, kwargs), wait: wait, text: text)
end
def has_text?(text)
@@ -199,8 +205,8 @@ module QA
scroll_to(element_selector_css(name), *args)
end
- def element_selector_css(name)
- Page::Element.new(name).selector_css
+ def element_selector_css(name, *attributes)
+ Page::Element.new(name, *attributes).selector_css
end
def click_link_with_text(text)
diff --git a/qa/qa/page/element.rb b/qa/qa/page/element.rb
index 9e6fd2fdd4f..6bfdf98587b 100644
--- a/qa/qa/page/element.rb
+++ b/qa/qa/page/element.rb
@@ -28,7 +28,7 @@ module QA
end
def selector_css
- %Q([data-qa-selector="#{@name}"],.#{selector})
+ %Q([data-qa-selector="#{@name}"]#{additional_selectors},.#{selector})
end
def expression
@@ -42,6 +42,14 @@ module QA
def matches?(line)
!!(line =~ /["']#{name}['"]|#{expression}/)
end
+
+ private
+
+ def additional_selectors
+ @attributes.dup.delete_if { |attr| attr == :pattern || attr == :required }.map do |key, value|
+ %Q([data-qa-#{key.to_s.tr('_', '-')}="#{value}"])
+ end.join
+ end
end
end
end
diff --git a/qa/qa/page/project/issue/index.rb b/qa/qa/page/project/issue/index.rb
index befee25b37a..a6ccee4353b 100644
--- a/qa/qa/page/project/issue/index.rb
+++ b/qa/qa/page/project/issue/index.rb
@@ -36,6 +36,10 @@ module QA
def click_closed_issues_link
click_element :closed_issues_link
end
+
+ def has_issue?(issue)
+ has_element? :issue, issue_title: issue.to_s
+ end
end
end
end
diff --git a/qa/qa/resource/issue.rb b/qa/qa/resource/issue.rb
index 0817a9de06f..3bcff6a10ac 100644
--- a/qa/qa/resource/issue.rb
+++ b/qa/qa/resource/issue.rb
@@ -38,6 +38,10 @@ module QA
end
end
+ def to_s
+ @title
+ end
+
def api_get_path
"/projects/#{project.id}/issues/#{id}"
end
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb
index 35cd64aa58e..69389672a6d 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb
@@ -7,8 +7,7 @@ module QA
QA::Runtime::Env.personal_access_token = QA::Runtime::Env.admin_personal_access_token
unless QA::Runtime::Env.personal_access_token
- Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.perform(&:sign_in_using_admin_credentials)
+ Flow::Login.sign_in_as_admin
end
user = Resource::User.fabricate_via_api! do |user|
@@ -20,9 +19,7 @@ module QA
Page::Main::Menu.perform(&:sign_out) if Page::Main::Menu.perform { |p| p.has_personal_area?(wait: 0) }
- Runtime::Browser.visit(:gitlab, Page::Main::Login)
-
- Page::Main::Login.perform(&:sign_in_using_credentials)
+ Flow::Login.sign_in
project = Resource::Project.fabricate_via_api! do |resource|
resource.name = 'xss-test-for-mentions-project'
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/close_issue_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/close_issue_spec.rb
index 2bcc89cb338..dc7fa9f3859 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/close_issue_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/close_issue_spec.rb
@@ -7,8 +7,7 @@ module QA
let(:commit_message) { 'Closes' }
before do
- Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.perform(&:sign_in_using_credentials)
+ Flow::Login.sign_in
issue = Resource::Issue.fabricate_via_api! do |issue|
issue.title = issue_title
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb
index ad70f6813fb..77fcc4e9b6a 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb
@@ -6,8 +6,7 @@ module QA
let(:my_first_reply) { 'My first reply' }
before do
- Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.perform(&:sign_in_using_credentials)
+ Flow::Login.sign_in
issue = Resource::Issue.fabricate_via_api! do |issue|
issue.title = 'issue title'
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb
index 0b1bd00ac8d..77489c0ecf5 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb
@@ -4,8 +4,7 @@ module QA
context 'Plan' do
describe 'Issue comments' do
before do
- Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.perform(&:sign_in_using_credentials)
+ Flow::Login.sign_in
issue = Resource::Issue.fabricate_via_api! do |issue|
issue.title = 'issue title'
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb
index 300bf59eba4..254efb741b3 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb
@@ -6,18 +6,19 @@ module QA
let(:issue_title) { 'issue title' }
before do
- Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.perform(&:sign_in_using_credentials)
+ Flow::Login.sign_in
end
it 'user creates an issue' do
- Resource::Issue.fabricate_via_browser_ui! do |issue|
+ issue = Resource::Issue.fabricate_via_browser_ui! do |issue|
issue.title = issue_title
end
Page::Project::Menu.perform(&:click_issues)
- expect(page).to have_content(issue_title)
+ Page::Project::Issue::Index.perform do |index|
+ expect(index).to have_issue(issue)
+ end
end
context 'when using attachments in comments', :object_storage do
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb
index 317e31feea8..a4f6b0bb1bf 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb
@@ -6,8 +6,7 @@ module QA
let(:issue_title) { 'issue title' }
before do
- Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.perform(&:sign_in_using_credentials)
+ Flow::Login.sign_in
issue = Resource::Issue.fabricate_via_api! do |issue|
issue.title = issue_title
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb
index c42c2cedde0..e15afd1f576 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb
@@ -6,8 +6,7 @@ module QA
let(:issue_title) { 'Issue Lists are awesome' }
before do
- Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.perform(&:sign_in_using_credentials)
+ Flow::Login.sign_in
project = Resource::Project.fabricate_via_api! do |resource|
resource.name = 'project-for-issue-suggestions'
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 45c14d0537c..b1a80ad75cd 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
@@ -4,8 +4,7 @@ module QA
context 'Plan', :smoke do
describe 'mention' do
before do
- Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.perform(&:sign_in_using_credentials)
+ Flow::Login.sign_in
@user = Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1)
diff --git a/qa/spec/page/element_spec.rb b/qa/spec/page/element_spec.rb
index ff5e118cefa..3f64743ffac 100644
--- a/qa/spec/page/element_spec.rb
+++ b/qa/spec/page/element_spec.rb
@@ -117,5 +117,23 @@ describe QA::Page::Element do
it 'properly translates to a data-qa-selector' do
expect(subject.selector_css).to include(%q([data-qa-selector="my_element"]))
end
+
+ context 'additional selectors' do
+ let(:element) { described_class.new(:my_element, index: 3, another_match: 'something') }
+ let(:required_element) { described_class.new(:my_element, required: true, index: 3) }
+
+ it 'matches on additional data-qa properties' do
+ expect(element.selector_css).to include(%q([data-qa-selector="my_element"][data-qa-index="3"]))
+ end
+
+ it 'doesnt conflict with element requirement' do
+ expect(required_element).to be_required
+ expect(required_element.selector_css).not_to include(%q(data-qa-required))
+ end
+
+ it 'translates snake_case to kebab-case' do
+ expect(element.selector_css).to include(%q(data-qa-another-match))
+ end
+ end
end
end