提交 52cacdb8 编写于 作者: G GitLab Bot

Add latest changes from gitlab-org/gitlab@master

上级 9398d718
...@@ -111,6 +111,7 @@ export default { ...@@ -111,6 +111,7 @@ export default {
v-else v-else
:class="fileClass" :class="fileClass"
:title="textForTitle" :title="textForTitle"
:data-level="level"
class="file-row" class="file-row"
role="button" role="button"
@click="clickFile" @click="clickFile"
......
...@@ -736,6 +736,7 @@ module Ci ...@@ -736,6 +736,7 @@ module Ci
MergeRequest.where(id: merge_request_id) MergeRequest.where(id: merge_request_id)
else else
MergeRequest.where(source_project_id: project_id, source_branch: ref) MergeRequest.where(source_project_id: project_id, source_branch: ref)
.by_commit_sha(sha)
end end
end end
......
...@@ -153,6 +153,6 @@ class PrometheusService < MonitoringService ...@@ -153,6 +153,6 @@ class PrometheusService < MonitoringService
def create_default_alerts def create_default_alerts
return unless project_id return unless project_id
Prometheus::CreateDefaultAlertsWorker.perform_async(project_id: project_id) Prometheus::CreateDefaultAlertsWorker.perform_async(project_id)
end end
end end
---
title: Prevent false positives in Ci::Pipeline#all_merge_requests
merge_request: 28800
author:
type: fixed
...@@ -4,6 +4,13 @@ Check this document if it includes instructions for the version you are updating ...@@ -4,6 +4,13 @@ Check this document if it includes instructions for the version you are updating
These steps go together with the [general steps](updating_the_geo_nodes.md#general-update-steps) These steps go together with the [general steps](updating_the_geo_nodes.md#general-update-steps)
for updating Geo nodes. for updating Geo nodes.
## Updating to GitLab 12.9
CAUTION: **Warning:**
GitLab 12.9.0 through GitLab 12.9.3 are affected by [a bug that stops
repository verification](https://gitlab.com/gitlab-org/gitlab/-/issues/213523).
The issue is fixed in GitLab 12.9.4. Please upgrade to GitLab 12.9.4 or later.
## Updating to GitLab 12.7 ## Updating to GitLab 12.7
DANGER: **Danger:** DANGER: **Danger:**
......
...@@ -20,29 +20,27 @@ from where content is sourced, the `gitlab-docs` project, and the published outp ...@@ -20,29 +20,27 @@ from where content is sourced, the `gitlab-docs` project, and the published outp
```mermaid ```mermaid
graph LR graph LR
A[gitlab-foss/doc] A[gitlab/doc]
B[gitlab/doc] B[gitlab-runner/docs]
C[gitlab-runner/docs] C[omnibus-gitlab/doc]
D[omnibus-gitlab/doc] D[charts/doc]
E[charts/doc] E[gitlab-docs]
F[gitlab-docs] A --> E
A --> F B --> E
B --> F C --> E
C --> F D --> E
D --> F E -- Build pipeline --> F
E --> F F[docs.gitlab.com]
F -- Build pipeline --> G G[/ce/]
G[docs.gitlab.com] H[/ee/]
H[/ce/] I[/runner/]
I[/ee/] J[/omnibus/]
J[/runner/] K[/charts/]
K[/omnibus/] F --> H
L[/charts/] F --> I
G --> H F --> J
G --> I F --> K
G --> J H -- symlink --> G
G --> K
G --> L
``` ```
You will not find any GitLab docs content in the `gitlab-docs` repository. You will not find any GitLab docs content in the `gitlab-docs` repository.
......
...@@ -60,12 +60,15 @@ Please see the `sha_tokenizer` explanation later below for an example. ...@@ -60,12 +60,15 @@ Please see the `sha_tokenizer` explanation later below for an example.
#### `code_analyzer` #### `code_analyzer`
Used when indexing a blob's filename and content. Uses the `whitespace` tokenizer and the filters: `code`, `edgeNGram_filter`, `lowercase`, and `asciifolding` Used when indexing a blob's filename and content. Uses the `whitespace` tokenizer and the filters: [`code`](#code), [`edgeNGram_filter`](#edgengram_filter), `lowercase`, and `asciifolding`
The `whitespace` tokenizer was selected in order to have more control over how tokens are split. For example the string `Foo::bar(4)` needs to generate tokens like `Foo` and `bar(4)` in order to be properly searched. The `whitespace` tokenizer was selected in order to have more control over how tokens are split. For example the string `Foo::bar(4)` needs to generate tokens like `Foo` and `bar(4)` in order to be properly searched.
Please see the `code` filter for an explanation on how tokens are split. Please see the `code` filter for an explanation on how tokens are split.
NOTE: **Known Issues**:
Currently the [Elasticsearch code_analyzer doesn't account for all code cases](../integration/elasticsearch.md#known-issues).
#### `code_search_analyzer` #### `code_search_analyzer`
Not directly used for indexing, but rather used to transform a search input. Uses the `whitespace` tokenizer and the `lowercase` and `asciifolding` filters. Not directly used for indexing, but rather used to transform a search input. Uses the `whitespace` tokenizer and the `lowercase` and `asciifolding` filters.
......
...@@ -634,6 +634,14 @@ Here are some common pitfalls and how to overcome them: ...@@ -634,6 +634,14 @@ Here are some common pitfalls and how to overcome them:
You probably have not used either `http://` or `https://` as part of your value in the **"URL"** field of the Elasticseach Integration Menu. Please make sure you are using either `http://` or `https://` in this field as the [Elasticsearch client for Go](https://github.com/olivere/elastic) that we are using [needs the prefix for the URL to be accepted as valid](https://github.com/olivere/elastic/commit/a80af35aa41856dc2c986204e2b64eab81ccac3a). You probably have not used either `http://` or `https://` as part of your value in the **"URL"** field of the Elasticseach Integration Menu. Please make sure you are using either `http://` or `https://` in this field as the [Elasticsearch client for Go](https://github.com/olivere/elastic) that we are using [needs the prefix for the URL to be accepted as valid](https://github.com/olivere/elastic/commit/a80af35aa41856dc2c986204e2b64eab81ccac3a).
Once you have corrected the formatting of the URL, delete the index (via the [dedicated Rake task](#gitlab-elasticsearch-rake-tasks)) and [reindex the content of your instance](#adding-gitlabs-data-to-the-elasticsearch-index). Once you have corrected the formatting of the URL, delete the index (via the [dedicated Rake task](#gitlab-elasticsearch-rake-tasks)) and [reindex the content of your instance](#adding-gitlabs-data-to-the-elasticsearch-index).
### Known Issues
- **[Elasticsearch `code_analyzer` doesn't account for all code cases](https://gitlab.com/gitlab-org/gitlab/issues/10693)**
The `code_analyzer` pattern and filter configuration is being evaluated for improvement. We have noticed [several edge cases](https://gitlab.com/gitlab-org/gitlab/-/issues/10693#note_158382332) that are not returning expected search results due to our pattern and filter configuration.
An improved strategy for the `code_analyzer` pattern and filters are being discussed in [issue 29443](https://gitlab.com/gitlab-org/gitlab/-/issues/29443).
### Reverting to basic search ### Reverting to basic search
Sometimes there may be issues with your Elasticsearch index data and as such Sometimes there may be issues with your Elasticsearch index data and as such
......
...@@ -448,7 +448,7 @@ of the group/namespace. You can [purchase additional CI minutes](#purchasing-add ...@@ -448,7 +448,7 @@ of the group/namespace. You can [purchase additional CI minutes](#purchasing-add
If you're using GitLab.com, you can purchase additional CI minutes so your If you're using GitLab.com, you can purchase additional CI minutes so your
pipelines won't be blocked after you have used all your CI minutes from your pipelines won't be blocked after you have used all your CI minutes from your
main quota. Additional minutes: main quota. You can find pricing for additional CI/CD minutes in the [GitLab Customers Portal](https://customers.gitlab.com/plans). Additional minutes:
- Are only used once the shared quota included in your subscription runs out. - Are only used once the shared quota included in your subscription runs out.
- Roll over month to month. - Roll over month to month.
......
...@@ -148,6 +148,9 @@ The results will be saved as a ...@@ -148,6 +148,9 @@ The results will be saved as a
that you can later download and analyze. that you can later download and analyze.
Due to implementation limitations, we always take the latest DAST artifact available. Due to implementation limitations, we always take the latest DAST artifact available.
DANGER: **Danger:**
**DO NOT** run an authenticated scan against a production server. When an authenticated scan is run, it may perform *any* function that the authenticated user can. This includes modifying and deleting data, submitting forms, following links, and so on. Only run an authenticated scan against a test server.
### Full scan ### Full scan
DAST can be configured to perform [ZAP Full Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Full-Scan), which DAST can be configured to perform [ZAP Full Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Full-Scan), which
......
...@@ -64,7 +64,9 @@ module Gitlab ...@@ -64,7 +64,9 @@ module Gitlab
else else
redis.del(*keys) redis.del(*keys)
end end
rescue ::Redis::CommandError rescue ::Redis::CommandError => e
Gitlab::ErrorTracking.log_exception(e)
redis.del(*keys) redis.del(*keys)
end end
end end
......
...@@ -5,7 +5,7 @@ FactoryBot.define do ...@@ -5,7 +5,7 @@ FactoryBot.define do
factory :ci_empty_pipeline, class: 'Ci::Pipeline' do factory :ci_empty_pipeline, class: 'Ci::Pipeline' do
source { :push } source { :push }
ref { 'master' } ref { 'master' }
sha { '97de212e80737a608d939f648d959671fb0a0142' } sha { 'b83d6e391c22777fca1ed3012fce84f633d7fed0' }
status { 'pending' } status { 'pending' }
add_attribute(:protected) { false } add_attribute(:protected) { false }
......
...@@ -244,13 +244,15 @@ describe 'Dashboard Projects' do ...@@ -244,13 +244,15 @@ describe 'Dashboard Projects' do
ActiveRecord::QueryRecorder.new { visit dashboard_projects_path }.count ActiveRecord::QueryRecorder.new { visit dashboard_projects_path }.count
# There are three known N+1 queries: # There are seven known N+1 queries: https://gitlab.com/gitlab-org/gitlab/-/issues/214037
# 1. Project#open_issues_count # 1. Project#open_issues_count
# 2. Project#open_merge_requests_count # 2. Project#open_merge_requests_count
# 3. Project#forks_count # 3. Project#forks_count
# # 4. ProjectsHelper#load_pipeline_status
# In addition, ProjectsHelper#load_pipeline_status also adds an # 5. RendersMemberAccess#preload_max_member_access_for_collection
# additional query. # 6. User#max_member_access_for_project_ids
expect { visit dashboard_projects_path }.not_to exceed_query_limit(control_count + 4) # 7. CommitWithPipeline#last_pipeline
expect { visit dashboard_projects_path }.not_to exceed_query_limit(control_count + 7)
end end
end end
# frozen_string_literal: true
require 'spec_helper'
describe 'IDE user commits changes', :js do
include WebIdeSpecHelpers
let(:project) { create(:project, :public, :repository) }
let(:user) { project.owner }
before do
sign_in(user)
ide_visit(project)
end
it 'user updates nested files' do
content = <<~HEREDOC
Lorem ipsum
Dolar sit
Amit
HEREDOC
ide_create_new_file('foo/bar/lorem_ipsum.md', content: content)
ide_delete_file('foo/bar/.gitkeep')
ide_commit
expect(page).to have_content('All changes are committed')
expect(project.repository.blob_at('master', 'foo/bar/.gitkeep')).to be_nil
expect(project.repository.blob_at('master', 'foo/bar/lorem_ipsum.md').data).to eql(content)
end
end
...@@ -133,15 +133,8 @@ describe 'Pipeline', :js do ...@@ -133,15 +133,8 @@ describe 'Pipeline', :js do
context 'when there are two related merge requests' do context 'when there are two related merge requests' do
before do before do
create(:merge_request, create(:merge_request, source_project: project, source_branch: pipeline.ref)
source_project: project, create(:merge_request, source_project: project, source_branch: pipeline.ref, target_branch: 'fix')
source_branch: pipeline.ref,
target_branch: 'feature-1')
create(:merge_request,
source_project: project,
source_branch: pipeline.ref,
target_branch: 'feature-2')
end end
it 'links to the most recent related merge request' do it 'links to the most recent related merge request' do
......
...@@ -14,7 +14,7 @@ describe Resolvers::MergeRequestPipelinesResolver do ...@@ -14,7 +14,7 @@ describe Resolvers::MergeRequestPipelinesResolver do
sha: merge_request.diff_head_sha sha: merge_request.diff_head_sha
) )
end end
let_it_be(:other_project_pipeline) { create(:ci_pipeline, project: merge_request.source_project) } let_it_be(:other_project_pipeline) { create(:ci_pipeline, project: merge_request.source_project, ref: 'other-ref') }
let_it_be(:other_pipeline) { create(:ci_pipeline) } let_it_be(:other_pipeline) { create(:ci_pipeline) }
let(:current_user) { create(:user) } let(:current_user) { create(:user) }
......
...@@ -103,6 +103,12 @@ describe Gitlab::RepositorySetCache, :clean_gitlab_redis_cache do ...@@ -103,6 +103,12 @@ describe Gitlab::RepositorySetCache, :clean_gitlab_redis_cache do
expect(cache.expire(:foo)).to eq(1) expect(cache.expire(:foo)).to eq(1)
expect(cache.read(:foo)).to be_empty expect(cache.read(:foo)).to be_empty
end end
it 'logs the failure' do
expect(Gitlab::ErrorTracking).to receive(:log_exception)
cache.expire(:foo)
end
end end
end end
......
...@@ -2367,18 +2367,31 @@ describe Ci::Pipeline, :mailer do ...@@ -2367,18 +2367,31 @@ describe Ci::Pipeline, :mailer do
end end
end end
describe "#all_merge_requests" do describe '#all_merge_requests' do
let(:project) { create(:project) } let(:project) { create(:project) }
shared_examples 'a method that returns all merge requests for a given pipeline' do shared_examples 'a method that returns all merge requests for a given pipeline' do
let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: pipeline_project, ref: 'master') } let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: pipeline_project, ref: 'master') }
it "returns all merge requests having the same source branch" do it 'returns all merge requests having the same source branch and the pipeline sha' do
merge_request = create(:merge_request, source_project: pipeline_project, target_project: project, source_branch: pipeline.ref) merge_request = create(:merge_request, source_project: pipeline_project, target_project: project, source_branch: pipeline.ref)
create(:merge_request_diff, merge_request: merge_request).tap do |diff|
create(:merge_request_diff_commit, merge_request_diff: diff, sha: pipeline.sha)
end
expect(pipeline.all_merge_requests).to eq([merge_request]) expect(pipeline.all_merge_requests).to eq([merge_request])
end end
it "doesn't return merge requests having the same source branch without the pipeline sha" do
merge_request = create(:merge_request, source_project: pipeline_project, target_project: project, source_branch: pipeline.ref)
create(:merge_request_diff, merge_request: merge_request).tap do |diff|
create(:merge_request_diff_commit, merge_request_diff: diff, sha: 'unrelated')
end
expect(pipeline.all_merge_requests).to be_empty
end
it "doesn't return merge requests having a different source branch" do it "doesn't return merge requests having a different source branch" do
create(:merge_request, source_project: pipeline_project, target_project: project, source_branch: 'feature', target_branch: 'master') create(:merge_request, source_project: pipeline_project, target_project: project, source_branch: 'feature', target_branch: 'master')
......
...@@ -133,7 +133,7 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do ...@@ -133,7 +133,7 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do
it 'creates default alerts' do it 'creates default alerts' do
expect(Prometheus::CreateDefaultAlertsWorker) expect(Prometheus::CreateDefaultAlertsWorker)
.to receive(:perform_async) .to receive(:perform_async)
.with(project_id: project.id) .with(project.id)
create_service create_service
end end
......
...@@ -236,7 +236,7 @@ describe Ci::PipelinePresenter do ...@@ -236,7 +236,7 @@ describe Ci::PipelinePresenter do
context 'for a branch pipeline with two open MRs' do context 'for a branch pipeline with two open MRs' do
let!(:one) { create(:merge_request, source_project: project, source_branch: pipeline.ref) } let!(:one) { create(:merge_request, source_project: project, source_branch: pipeline.ref) }
let!(:two) { create(:merge_request, source_project: project, source_branch: pipeline.ref, target_branch: 'wip') } let!(:two) { create(:merge_request, source_project: project, source_branch: pipeline.ref, target_branch: 'fix') }
it { is_expected.to contain_exactly(one, two) } it { is_expected.to contain_exactly(one, two) }
end end
......
...@@ -15,7 +15,7 @@ describe BuildDetailsEntity do ...@@ -15,7 +15,7 @@ describe BuildDetailsEntity do
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
let(:pipeline) { create(:ci_pipeline, project: project) } let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, :failed, pipeline: pipeline) } let(:build) { create(:ci_build, :failed, pipeline: pipeline) }
let(:request) { double('request') } let(:request) { double('request', project: project) }
let(:entity) do let(:entity) do
described_class.new(build, request: request, described_class.new(build, request: request,
......
...@@ -368,10 +368,12 @@ describe Issues::CreateService do ...@@ -368,10 +368,12 @@ describe Issues::CreateService do
end end
context 'checking spam' do context 'checking spam' do
let(:title) { 'Legit issue' }
let(:description) { 'please fix' }
let(:opts) do let(:opts) do
{ {
title: 'Awesome issue', title: title,
description: 'please fix', description: description,
request: double(:request, env: {}) request: double(:request, env: {})
} }
end end
...@@ -382,7 +384,7 @@ describe Issues::CreateService do ...@@ -382,7 +384,7 @@ describe Issues::CreateService do
context 'when recaptcha was verified' do context 'when recaptcha was verified' do
let(:log_user) { user } let(:log_user) { user }
let(:spam_logs) { create_list(:spam_log, 2, user: log_user, title: 'Awesome issue') } let(:spam_logs) { create_list(:spam_log, 2, user: log_user, title: title) }
let(:target_spam_log) { spam_logs.last } let(:target_spam_log) { spam_logs.last }
before do before do
...@@ -396,7 +398,7 @@ describe Issues::CreateService do ...@@ -396,7 +398,7 @@ describe Issues::CreateService do
expect(issue).not_to be_spam expect(issue).not_to be_spam
end end
it 'issue is valid ' do it 'creates a valid issue' do
expect(issue).to be_valid expect(issue).to be_valid
end end
...@@ -405,14 +407,14 @@ describe Issues::CreateService do ...@@ -405,14 +407,14 @@ describe Issues::CreateService do
end end
it 'marks related spam_log as recaptcha_verified' do it 'marks related spam_log as recaptcha_verified' do
expect { issue }.to change {SpamLog.last.recaptcha_verified}.from(false).to(true) expect { issue }.to change { target_spam_log.reload.recaptcha_verified }.from(false).to(true)
end end
context 'when spam log does not belong to a user' do context 'when spam log does not belong to a user' do
let(:log_user) { create(:user) } let(:log_user) { create(:user) }
it 'does not mark spam_log as recaptcha_verified' do it 'does not mark spam_log as recaptcha_verified' do
expect { issue }.not_to change {SpamLog.last.recaptcha_verified} expect { issue }.not_to change { target_spam_log.reload.recaptcha_verified }
end end
end end
end end
...@@ -431,8 +433,8 @@ describe Issues::CreateService do ...@@ -431,8 +433,8 @@ describe Issues::CreateService do
end end
end end
context 'when issuables_recaptcha_enabled feature flag is true' do context 'when allow_possible_spam feature flag is false' do
it 'marks an issue as spam' do it 'marks the issue as spam' do
expect(issue).to be_spam expect(issue).to be_spam
end end
...@@ -442,34 +444,26 @@ describe Issues::CreateService do ...@@ -442,34 +444,26 @@ describe Issues::CreateService do
it 'creates a new spam_log' do it 'creates a new spam_log' do
expect { issue } expect { issue }
.to have_spam_log(title: issue.title, description: issue.description, user_id: user.id, noteable_type: 'Issue') .to have_spam_log(title: title, description: description, user_id: user.id, noteable_type: 'Issue')
end
it 'assigns a spam_log to the issue' do
expect(issue.spam_log).to eq(SpamLog.last)
end end
end end
context 'when issuable_recaptcha_enabled feature flag is false' do context 'when allow_possible_spam feature flag is true' do
before do before do
stub_feature_flags(allow_possible_spam: true) stub_feature_flags(allow_possible_spam: true)
end end
it 'does not mark an issue as spam' do it 'does not mark the issue as spam' do
expect(issue).not_to be_spam expect(issue).not_to be_spam
end end
it 'accepts the ​issue as valid' do it '​creates a valid issue' do
expect(issue).to be_valid expect(issue).to be_valid
end end
it 'creates a new spam_log' do it 'creates a new spam_log' do
expect { issue } expect { issue }
.to have_spam_log(title: issue.title, description: issue.description, user_id: user.id, noteable_type: 'Issue') .to have_spam_log(title: title, description: description, user_id: user.id, noteable_type: 'Issue')
end
it 'assigns a spam_log to an issue' do
expect(issue.spam_log).to eq(SpamLog.last)
end end
end end
end end
...@@ -485,8 +479,8 @@ describe Issues::CreateService do ...@@ -485,8 +479,8 @@ describe Issues::CreateService do
expect(issue).not_to be_spam expect(issue).not_to be_spam
end end
it 'an issue is valid ' do it 'creates a valid issue' do
expect(issue.valid?).to be_truthy expect(issue).to be_valid
end end
it 'does not assign a spam_log to an issue' do it 'does not assign a spam_log to an issue' do
......
# frozen_string_literal: true
# These helpers help you interact within the Web IDE.
#
# Usage:
# describe "..." do
# include WebIdeSpecHelpers
# ...
#
# ide_visit(project)
# ide_create_new_file('path/to/file.txt', content: 'Lorem ipsum')
# ide_commit
#
module WebIdeSpecHelpers
include ActionView::Helpers::JavaScriptHelper
def ide_visit(project)
visit project_path(project)
wait_for_requests
click_link('Web IDE')
wait_for_requests
end
def ide_tree_body
page.find('.ide-tree-body')
end
def ide_tree_actions
page.find('.ide-tree-actions')
end
def ide_file_row_open?(row)
row.matches_css?('.is-open')
end
# Creates a file in the IDE by expanding directories
# then using the dropdown next to the parent directory
#
# - Throws an error if the parent directory is not found
def ide_create_new_file(path, content: '')
parent_path = path.split('/')[0...-1].join('/')
container = ide_traverse_to_file(parent_path)
if container
click_file_action(container, 'New file')
else
ide_tree_actions.click_button('New file')
end
within '#ide-new-entry' do
find('input').fill_in(with: path)
click_button('Create file')
end
ide_set_editor_value(content)
end
# Deletes a file by traversing to `path`
# then clicking the 'Delete' action.
#
# - Throws an error if the file is not found
def ide_delete_file(path)
container = ide_traverse_to_file(path)
click_file_action(container, 'Delete')
end
# Opens parent directories until the file at `path`
# is exposed.
#
# - Returns a reference to the file row at `path`
# - Throws an error if the file is not found
def ide_traverse_to_file(path)
paths = path.split('/')
container = nil
paths.each_with_index do |path, index|
ide_open_file_row(container) if container
container = find_file_child(container, path, level: index)
end
container
end
def ide_open_file_row(row)
return if ide_file_row_open?(row)
row.click
end
def ide_set_editor_value(value)
editor = find('.monaco-editor')
uri = editor['data-uri']
execute_script("monaco.editor.getModel('#{uri}').setValue('#{escape_javascript(value)}')")
end
def ide_editor_value
editor = find('.monaco-editor')
uri = editor['data-uri']
evaluate_script("monaco.editor.getModel('#{uri}').getValue()")
end
def ide_commit
ide_switch_mode('commit')
commit_to_current_branch
end
def ide_switch_mode(mode)
find(".js-ide-#{mode}-mode").click
end
private
def file_row_container(row)
row ? row.find(:xpath, '..') : ide_tree_body
end
def find_file_child(row, name, level: nil)
container = file_row_container(row)
container.find(".file-row[data-level=\"#{level}\"]", text: name)
end
def click_file_action(row, text)
row.hover
dropdown = row.find('.ide-new-btn')
dropdown.find('button').click
dropdown.find('button', text: text).click
end
def commit_to_current_branch(option: 'Commit to master branch', message: '')
within '.multi-file-commit-form' do
fill_in('commit-message', with: message) if message
choose(option)
click_button('Commit')
wait_for_requests
end
end
end
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册