Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
143f196f8b
commit
aaf124b0f7
21 changed files with 205 additions and 30 deletions
1
Gemfile
1
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'
|
||||
|
|
26
Gemfile.lock
26
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)
|
||||
|
|
43
Guardfile
Normal file
43
Guardfile
Normal file
|
@ -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
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Remove all references to BoardsService in index.vue
|
||||
merge_request: 20152
|
||||
author: nuwe1
|
||||
type: other
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -38,6 +38,10 @@ module QA
|
|||
end
|
||||
end
|
||||
|
||||
def to_s
|
||||
@title
|
||||
end
|
||||
|
||||
def api_get_path
|
||||
"/projects/#{project.id}/issues/#{id}"
|
||||
end
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue