summaryrefslogtreecommitdiff
path: root/qa
diff options
context:
space:
mode:
Diffstat (limited to 'qa')
-rw-r--r--qa/qa.rb17
-rw-r--r--qa/qa/factory/README.md445
-rw-r--r--qa/qa/factory/api_fabricator.rb97
-rw-r--r--qa/qa/factory/base.rb140
-rw-r--r--qa/qa/factory/dependency.rb39
-rw-r--r--qa/qa/factory/product.rb27
-rw-r--r--qa/qa/factory/repository/project_push.rb16
-rw-r--r--qa/qa/factory/repository/push.rb12
-rw-r--r--qa/qa/factory/repository/wiki_push.rb10
-rw-r--r--qa/qa/factory/resource/branch.rb10
-rw-r--r--qa/qa/factory/resource/deploy_key.rb22
-rw-r--r--qa/qa/factory/resource/deploy_token.rb20
-rw-r--r--qa/qa/factory/resource/file.rb8
-rw-r--r--qa/qa/factory/resource/fork.rb25
-rw-r--r--qa/qa/factory/resource/group.rb29
-rw-r--r--qa/qa/factory/resource/issue.rb18
-rw-r--r--qa/qa/factory/resource/kubernetes_cluster.rb19
-rw-r--r--qa/qa/factory/resource/label.rb39
-rw-r--r--qa/qa/factory/resource/merge_request.rb48
-rw-r--r--qa/qa/factory/resource/merge_request_from_fork.rb21
-rw-r--r--qa/qa/factory/resource/personal_access_token.rb8
-rw-r--r--qa/qa/factory/resource/project.rb54
-rw-r--r--qa/qa/factory/resource/project_imported_from_github.rb10
-rw-r--r--qa/qa/factory/resource/project_milestone.rb15
-rw-r--r--qa/qa/factory/resource/runner.rb10
-rw-r--r--qa/qa/factory/resource/sandbox.rb37
-rw-r--r--qa/qa/factory/resource/secret_variable.rb10
-rw-r--r--qa/qa/factory/resource/ssh_key.rb20
-rw-r--r--qa/qa/factory/resource/user.rb11
-rw-r--r--qa/qa/factory/resource/wiki.rb25
-rw-r--r--qa/qa/factory/settings/hashed_storage.rb10
-rw-r--r--qa/qa/git/repository.rb136
-rw-r--r--qa/qa/page/README.md2
-rw-r--r--qa/qa/page/base.rb1
-rw-r--r--qa/qa/page/label/index.rb15
-rw-r--r--qa/qa/page/label/new.rb30
-rw-r--r--qa/qa/page/merge_request/new.rb10
-rw-r--r--qa/qa/page/merge_request/show.rb11
-rw-r--r--qa/qa/page/project/job/show.rb25
-rw-r--r--qa/qa/page/project/menu.rb23
-rw-r--r--qa/qa/page/project/pipeline/show.rb2
-rw-r--r--qa/qa/page/project/show.rb10
-rw-r--r--qa/qa/runtime/api/client.rb29
-rw-r--r--qa/qa/runtime/env.rb16
-rw-r--r--qa/qa/runtime/logger.rb25
-rw-r--r--qa/qa/specs/features/api/1_manage/users_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb5
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb8
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb18
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb5
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/secret_variable/add_secret_variable_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb6
-rw-r--r--qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb2
-rw-r--r--qa/qa/support/page/logging.rb100
-rw-r--r--qa/spec/factory/api_fabricator_spec.rb161
-rw-r--r--qa/spec/factory/base_spec.rb262
-rw-r--r--qa/spec/factory/dependency_spec.rb72
-rw-r--r--qa/spec/factory/product_spec.rb30
-rw-r--r--qa/spec/factory/repository/push_spec.rb26
-rw-r--r--qa/spec/git/repository_spec.rb15
-rw-r--r--qa/spec/page/logging_spec.rb94
-rw-r--r--qa/spec/runtime/api/client_spec.rb25
-rw-r--r--qa/spec/runtime/api/request_spec.rb18
-rw-r--r--qa/spec/runtime/api_request_spec.rb0
-rw-r--r--qa/spec/runtime/env_spec.rb67
-rw-r--r--qa/spec/runtime/logger_spec.rb33
-rw-r--r--qa/spec/spec_helper.rb4
91 files changed, 2055 insertions, 559 deletions
diff --git a/qa/qa.rb b/qa/qa.rb
index d6a150fa0b4..f760dd972a7 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -18,6 +18,7 @@ module QA
autoload :Address, 'qa/runtime/address'
autoload :Path, 'qa/runtime/path'
autoload :Fixtures, 'qa/runtime/fixtures'
+ autoload :Logger, 'qa/runtime/logger'
module API
autoload :Client, 'qa/runtime/api/client'
@@ -36,8 +37,8 @@ module QA
# GitLab QA fabrication mechanisms
#
module Factory
+ autoload :ApiFabricator, 'qa/factory/api_fabricator'
autoload :Base, 'qa/factory/base'
- autoload :Dependency, 'qa/factory/dependency'
autoload :Product, 'qa/factory/product'
module Resource
@@ -45,6 +46,7 @@ module QA
autoload :Group, 'qa/factory/resource/group'
autoload :Issue, 'qa/factory/resource/issue'
autoload :Project, 'qa/factory/resource/project'
+ autoload :Label, 'qa/factory/resource/label'
autoload :MergeRequest, 'qa/factory/resource/merge_request'
autoload :ProjectImportedFromGithub, 'qa/factory/resource/project_imported_from_github'
autoload :MergeRequestFromFork, 'qa/factory/resource/merge_request_from_fork'
@@ -240,6 +242,11 @@ module QA
autoload :Banner, 'qa/page/layout/banner'
end
+ module Label
+ autoload :New, 'qa/page/label/new'
+ autoload :Index, 'qa/page/label/index'
+ end
+
module MergeRequest
autoload :New, 'qa/page/merge_request/new'
autoload :Show, 'qa/page/merge_request/show'
@@ -317,6 +324,14 @@ module QA
end
end
end
+
+ # Classes that provide support to other parts of the framework.
+ #
+ module Support
+ module Page
+ autoload :Logging, 'qa/support/page/logging'
+ end
+ end
end
QA::Runtime::Release.extend_autoloads!
diff --git a/qa/qa/factory/README.md b/qa/qa/factory/README.md
new file mode 100644
index 00000000000..cfce096ab39
--- /dev/null
+++ b/qa/qa/factory/README.md
@@ -0,0 +1,445 @@
+# Factory objects in GitLab QA
+
+In GitLab QA we are using factories to create resources.
+
+Factories implementation are primarily done using Browser UI steps, but can also
+be done via the API.
+
+## Why do we need that?
+
+We need factory objects because we need to reduce duplication when creating
+resources for our QA tests.
+
+## How to properly implement a factory object?
+
+All factories should inherit from [`Factory::Base`](./base.rb).
+
+There is only one mandatory method to implement to define a factory. This is the
+`#fabricate!` method, which is used to build a resource via the browser UI.
+Note that you should only use [Page objects](../page/README.md) to interact with
+a Web page in this method.
+
+Here is an imaginary example:
+
+```ruby
+module QA
+ module Factory
+ module Resource
+ class Shirt < Factory::Base
+ attr_accessor :name
+
+ def fabricate!
+ Page::Dashboard::Index.perform do |dashboard_index|
+ dashboard_index.go_to_new_shirt
+ end
+
+ Page::Shirt::New.perform do |shirt_new|
+ shirt_new.set_name(name)
+ shirt_new.create_shirt!
+ end
+ end
+ end
+ end
+ end
+end
+```
+
+### Define API implementation
+
+A factory may also implement the three following methods to be able to create a
+resource via the public GitLab API:
+
+- `#api_get_path`: The `GET` path to fetch an existing resource.
+- `#api_post_path`: The `POST` path to create a new resource.
+- `#api_post_body`: The `POST` body (as a Ruby hash) to create a new resource.
+
+Let's take the `Shirt` factory example, and add these three API methods:
+
+```ruby
+module QA
+ module Factory
+ module Resource
+ class Shirt < Factory::Base
+ attr_accessor :name
+
+ def fabricate!
+ # ... same as before
+ end
+
+ def api_get_path
+ "/shirt/#{name}"
+ end
+
+ def api_post_path
+ "/shirts"
+ end
+
+ def api_post_body
+ {
+ name: name
+ }
+ end
+ end
+ end
+ end
+end
+```
+
+The [`Project` factory](./resource/project.rb) is a good real example of Browser
+UI and API implementations.
+
+### Define attributes
+
+After the resource is fabricated, we would like to access the attributes on
+the resource. We define the attributes with `attribute` method. Suppose
+we want to access the name on the resource, we could change `attr_accessor`
+to `attribute`:
+
+```ruby
+module QA
+ module Factory
+ module Resource
+ class Shirt < Factory::Base
+ attribute :name
+
+ # ... same as before
+ end
+ end
+ end
+end
+```
+
+The difference between `attr_accessor` and `attribute` is that by using
+`attribute` it can also be accessed from the product:
+
+```ruby
+shirt =
+ QA::Factory::Resource::Shirt.fabricate! do |resource|
+ resource.name = "GitLab QA"
+ end
+
+shirt.name # => "GitLab QA"
+```
+
+In the above example, if we use `attr_accessor :name` then `shirt.name` won't
+be available. On the other hand, using `attribute :name` will allow you to use
+`shirt.name`, so most of the time you'll want to use `attribute` instead of
+`attr_accessor` unless we clearly don't need it for the product.
+
+#### Resource attributes
+
+A resource may need another resource to exist first. For instance, a project
+needs a group to be created in.
+
+To define a resource attribute, you can use the `attribute` method with a
+block using the other factory to fabricate the resource.
+
+That will allow access to the other resource from your resource object's
+methods. You would usually use it in `#fabricate!`, `#api_get_path`,
+`#api_post_path`, `#api_post_body`.
+
+Let's take the `Shirt` factory, and add a `project` attribute to it:
+
+```ruby
+module QA
+ module Factory
+ module Resource
+ class Shirt < Factory::Base
+ attribute :name
+
+ attribute :project do
+ Factory::Resource::Project.fabricate! do |resource|
+ resource.name = 'project-to-create-a-shirt'
+ end
+ end
+
+ def fabricate!
+ project.visit!
+
+ Page::Project::Show.perform do |project_show|
+ project_show.go_to_new_shirt
+ end
+
+ Page::Shirt::New.perform do |shirt_new|
+ shirt_new.set_name(name)
+ shirt_new.create_shirt!
+ end
+ end
+
+ def api_get_path
+ "/project/#{project.path}/shirt/#{name}"
+ end
+
+ def api_post_path
+ "/project/#{project.path}/shirts"
+ end
+
+ def api_post_body
+ {
+ name: name
+ }
+ end
+ end
+ end
+ end
+end
+```
+
+**Note that all the attributes are lazily constructed. This means if you want
+a specific attribute to be fabricated first, you'll need to call the
+attribute method first even if you're not using it.**
+
+#### Product data attributes
+
+Once created, you may want to populate a resource with attributes that can be
+found in the Web page, or in the API response.
+For instance, once you create a project, you may want to store its repository
+SSH URL as an attribute.
+
+Again we could use the `attribute` method with a block, using a page object
+to retrieve the data on the page.
+
+Let's take the `Shirt` factory, and define a `:brand` attribute:
+
+```ruby
+module QA
+ module Factory
+ module Resource
+ class Shirt < Factory::Base
+ attribute :name
+
+ attribute :project do
+ Factory::Resource::Project.fabricate! do |resource|
+ resource.name = 'project-to-create-a-shirt'
+ end
+ end
+
+ # Attribute populated from the Browser UI (using the block)
+ attribute :brand do
+ Page::Shirt::Show.perform do |shirt_show|
+ shirt_show.fetch_brand_from_page
+ end
+ end
+
+ # ... same as before
+ end
+ end
+ end
+end
+```
+
+**Note again that all the attributes are lazily constructed. This means if
+you call `shirt.brand` after moving to the other page, it'll not properly
+retrieve the data because we're no longer on the expected page.**
+
+Consider this:
+
+```ruby
+shirt =
+ QA::Factory::Resource::Shirt.fabricate! do |resource|
+ resource.name = "GitLab QA"
+ end
+
+shirt.project.visit!
+
+shirt.brand # => FAIL!
+```
+
+The above example will fail because now we're on the project page, trying to
+construct the brand data from the shirt page, however we moved to the project
+page already. There are two ways to solve this, one is that we could try to
+retrieve the brand before visiting the project again:
+
+```ruby
+shirt =
+ QA::Factory::Resource::Shirt.fabricate! do |resource|
+ resource.name = "GitLab QA"
+ end
+
+shirt.brand # => OK!
+
+shirt.project.visit!
+
+shirt.brand # => OK!
+```
+
+The attribute will be stored in the instance therefore all the following calls
+will be fine, using the data previously constructed. If we think that this
+might be too brittle, we could eagerly construct the data right before
+ending fabrication:
+
+```ruby
+module QA
+ module Factory
+ module Resource
+ class Shirt < Factory::Base
+ # ... same as before
+
+ def fabricate!
+ project.visit!
+
+ Page::Project::Show.perform do |project_show|
+ project_show.go_to_new_shirt
+ end
+
+ Page::Shirt::New.perform do |shirt_new|
+ shirt_new.set_name(name)
+ shirt_new.create_shirt!
+ end
+
+ brand # Eagerly construct the data
+ end
+ end
+ end
+ end
+end
+```
+
+This will make sure we construct the data right after we created the shirt.
+The drawback for this will become we're forced to construct the data even
+if we don't really need to use it.
+
+Alternatively, we could just make sure we're on the right page before
+constructing the brand data:
+
+```ruby
+module QA
+ module Factory
+ module Resource
+ class Shirt < Factory::Base
+ attribute :name
+
+ attribute :project do
+ Factory::Resource::Project.fabricate! do |resource|
+ resource.name = 'project-to-create-a-shirt'
+ end
+ end
+
+ # Attribute populated from the Browser UI (using the block)
+ attribute :brand do
+ back_url = current_url
+ visit!
+
+ Page::Shirt::Show.perform do |shirt_show|
+ shirt_show.fetch_brand_from_page
+ end
+
+ visit(back_url)
+ end
+
+ # ... same as before
+ end
+ end
+ end
+end
+```
+
+This will make sure it's on the shirt page before constructing brand, and
+move back to the previous page to avoid breaking the state.
+
+#### Define an attribute based on an API response
+
+Sometimes, you want to define a resource attribute based on the API response
+from its `GET` or `POST` request. For instance, if the creation of a shirt via
+the API returns
+
+```ruby
+{
+ brand: 'a-brand-new-brand',
+ style: 't-shirt',
+ materials: [[:cotton, 80], [:polyamide, 20]]
+}
+```
+
+you may want to store `style` as-is in the resource, and fetch the first value
+of the first `materials` item in a `main_fabric` attribute.
+
+Let's take the `Shirt` factory, and define a `:style` and a `:main_fabric`
+attributes:
+
+```ruby
+module QA
+ module Factory
+ module Resource
+ class Shirt < Factory::Base
+ # ... same as before
+
+ # Attribute from the Shirt factory if present,
+ # or fetched from the API response if present,
+ # or a QA::Factory::Base::NoValueError is raised otherwise
+ attribute :style
+
+ # If the attribute from the Shirt factory is not present,
+ # and if the API does not contain this field, this block will be
+ # used to construct the value based on the API response.
+ attribute :main_fabric do
+ api_response.&dig(:materials, 0, 0)
+ end
+
+ # ... same as before
+ end
+ end
+ end
+end
+```
+
+**Notes on attributes precedence:**
+
+- attributes from the factory have the highest precedence
+- attributes from the API response take precedence over attributes from the
+ block (usually from Browser UI)
+- attributes without a value will raise a `QA::Factory::Base::NoValueError` error
+
+## Creating resources in your tests
+
+To create a resource in your tests, you can call the `.fabricate!` method on the
+factory class.
+Note that if the factory supports API fabrication, this will use this
+fabrication by default.
+
+Here is an example that will use the API fabrication method under the hood since
+it's supported by the `Shirt` factory:
+
+```ruby
+my_shirt = Factory::Resource::Shirt.fabricate! do |shirt|
+ shirt.name = 'my-shirt'
+end
+
+expect(page).to have_text(my_shirt.name) # => "my-shirt" from the factory's attribute
+expect(page).to have_text(my_shirt.brand) # => "a-brand-new-brand" from the API response
+expect(page).to have_text(my_shirt.style) # => "t-shirt" from the API response
+expect(page).to have_text(my_shirt.main_fabric) # => "cotton" from the API response via the block
+```
+
+If you explicitly want to use the Browser UI fabrication method, you can call
+the `.fabricate_via_browser_ui!` method instead:
+
+```ruby
+my_shirt = Factory::Resource::Shirt.fabricate_via_browser_ui! do |shirt|
+ shirt.name = 'my-shirt'
+end
+
+expect(page).to have_text(my_shirt.name) # => "my-shirt" from the factory's attribute
+expect(page).to have_text(my_shirt.brand) # => the brand name fetched from the `Page::Shirt::Show` page via the block
+expect(page).to have_text(my_shirt.style) # => QA::Factory::Base::NoValueError will be raised because no API response nor a block is provided
+expect(page).to have_text(my_shirt.main_fabric) # => QA::Factory::Base::NoValueError will be raised because no API response and the block didn't provide a value (because it's also based on the API response)
+```
+
+You can also explicitly use the API fabrication method, by calling the
+`.fabricate_via_api!` method:
+
+```ruby
+my_shirt = Factory::Resource::Shirt.fabricate_via_api! do |shirt|
+ shirt.name = 'my-shirt'
+end
+```
+
+In this case, the result will be similar to calling `Factory::Resource::Shirt.fabricate!`.
+
+## Where to ask for help?
+
+If you need more information, ask for help on `#quality` channel on Slack
+(internal, GitLab Team only).
+
+If you are not a Team Member, and you still need help to contribute, please
+open an issue in GitLab CE issue tracker with the `~QA` label.
diff --git a/qa/qa/factory/api_fabricator.rb b/qa/qa/factory/api_fabricator.rb
new file mode 100644
index 00000000000..b1cfb6c9783
--- /dev/null
+++ b/qa/qa/factory/api_fabricator.rb
@@ -0,0 +1,97 @@
+# frozen_string_literal: true
+
+require 'airborne'
+require 'active_support/core_ext/object/deep_dup'
+require 'capybara/dsl'
+
+module QA
+ module Factory
+ module ApiFabricator
+ include Airborne
+ include Capybara::DSL
+
+ HTTP_STATUS_OK = 200
+ HTTP_STATUS_CREATED = 201
+
+ ResourceNotFoundError = Class.new(RuntimeError)
+ ResourceFabricationFailedError = Class.new(RuntimeError)
+ ResourceURLMissingError = Class.new(RuntimeError)
+
+ attr_reader :api_resource, :api_response
+
+ def api_support?
+ respond_to?(:api_get_path) &&
+ respond_to?(:api_post_path) &&
+ respond_to?(:api_post_body)
+ end
+
+ def fabricate_via_api!
+ unless api_support?
+ raise NotImplementedError, "Factory #{self.class.name} does not support fabrication via the API!"
+ end
+
+ resource_web_url(api_post)
+ end
+
+ def eager_load_api_client!
+ api_client.tap do |client|
+ # Eager-load the API client so that the personal token creation isn't
+ # taken in account in the actual resource creation timing.
+ client.personal_access_token
+ end
+ end
+
+ private
+
+ attr_writer :api_resource, :api_response
+
+ def resource_web_url(resource)
+ resource.fetch(:web_url) do
+ raise ResourceURLMissingError, "API resource for #{self.class.name} does not expose a `web_url` property: `#{resource}`."
+ end
+ end
+
+ def api_get
+ url = Runtime::API::Request.new(api_client, api_get_path).url
+ response = get(url)
+
+ unless response.code == HTTP_STATUS_OK
+ raise ResourceNotFoundError, "Resource at #{url} could not be found (#{response.code}): `#{response}`."
+ end
+
+ process_api_response(parse_body(response))
+ end
+
+ def api_post
+ response = post(
+ Runtime::API::Request.new(api_client, api_post_path).url,
+ api_post_body)
+
+ unless response.code == HTTP_STATUS_CREATED
+ raise ResourceFabricationFailedError, "Fabrication of #{self.class.name} using the API failed (#{response.code}) with `#{response}`."
+ end
+
+ process_api_response(parse_body(response))
+ end
+
+ def api_client
+ @api_client ||= begin
+ Runtime::API::Client.new(:gitlab, is_new_session: !current_url.start_with?('http'))
+ end
+ end
+
+ def parse_body(response)
+ JSON.parse(response.body, symbolize_names: true)
+ end
+
+ def process_api_response(parsed_response)
+ self.api_response = parsed_response
+ self.api_resource = transform_api_resource(parsed_response.deep_dup)
+ end
+
+ def transform_api_resource(resource)
+ resource
+ end
+ end
+ end
+end
diff --git a/qa/qa/factory/base.rb b/qa/qa/factory/base.rb
index 7a532ce534b..e82e16f9415 100644
--- a/qa/qa/factory/base.rb
+++ b/qa/qa/factory/base.rb
@@ -1,60 +1,150 @@
+# frozen_string_literal: true
+
require 'forwardable'
+require 'capybara/dsl'
module QA
module Factory
class Base
extend SingleForwardable
+ include ApiFabricator
+ extend Capybara::DSL
+
+ NoValueError = Class.new(RuntimeError)
- def_delegators :evaluator, :dependency, :dependencies
- def_delegators :evaluator, :product, :attributes
+ def_delegators :evaluator, :attribute
def fabricate!(*_args)
raise NotImplementedError
end
- def self.fabricate!(*args)
- new.tap do |factory|
- yield factory if block_given?
+ def visit!
+ visit(web_url)
+ end
+
+ private
- dependencies.each do |name, signature|
- Factory::Dependency.new(name, factory, signature).build!
- end
+ def populate_attribute(name, block)
+ value = attribute_value(name, block)
+
+ raise NoValueError, "No value was computed for product #{name} of factory #{self.class.name}." unless value
+
+ value
+ end
+
+ def attribute_value(name, block)
+ api_value = api_resource&.dig(name)
+
+ if api_value && block
+ log_having_both_api_result_and_block(name, api_value)
+ end
+
+ api_value || (block && instance_exec(&block))
+ end
+
+ def log_having_both_api_result_and_block(name, api_value)
+ QA::Runtime::Logger.info "<#{self.class}> Attribute #{name.inspect} has both API response `#{api_value}` and a block. API response will be picked. Block will be ignored."
+ end
+
+ def self.fabricate!(*args, &prepare_block)
+ fabricate_via_api!(*args, &prepare_block)
+ rescue NotImplementedError
+ fabricate_via_browser_ui!(*args, &prepare_block)
+ end
+
+ def self.fabricate_via_browser_ui!(*args, &prepare_block)
+ options = args.extract_options!
+ factory = options.fetch(:factory) { new }
+ parents = options.fetch(:parents) { [] }
+
+ do_fabricate!(factory: factory, prepare_block: prepare_block, parents: parents) do
+ log_fabrication(:browser_ui, factory, parents, args) { factory.fabricate!(*args) }
+
+ current_url
+ end
+ end
- factory.fabricate!(*args)
+ def self.fabricate_via_api!(*args, &prepare_block)
+ options = args.extract_options!
+ factory = options.fetch(:factory) { new }
+ parents = options.fetch(:parents) { [] }
- break Factory::Product.populate!(factory)
+ raise NotImplementedError unless factory.api_support?
+
+ factory.eager_load_api_client!
+
+ do_fabricate!(factory: factory, prepare_block: prepare_block, parents: parents) do
+ log_fabrication(:api, factory, parents, args) { factory.fabricate_via_api! }
+ end
+ end
+
+ def self.do_fabricate!(factory:, prepare_block:, parents: [])
+ prepare_block.call(factory) if prepare_block
+
+ resource_web_url = yield
+ factory.web_url = resource_web_url
+
+ Factory::Product.new(factory)
+ end
+ private_class_method :do_fabricate!
+
+ def self.log_fabrication(method, factory, parents, args)
+ return yield unless Runtime::Env.debug?
+
+ start = Time.now
+ prefix = "==#{'=' * parents.size}>"
+ msg = [prefix]
+ msg << "Built a #{name}"
+ msg << "as a dependency of #{parents.last}" if parents.any?
+ msg << "via #{method} with args #{args}"
+
+ yield.tap do
+ msg << "in #{Time.now - start} seconds"
+ puts msg.join(' ')
+ puts if parents.empty?
end
end
+ private_class_method :log_fabrication
def self.evaluator
@evaluator ||= Factory::Base::DSL.new(self)
end
+ private_class_method :evaluator
- class DSL
- attr_reader :dependencies, :attributes
+ def self.dynamic_attributes
+ const_get(:DynamicAttributes)
+ rescue NameError
+ mod = const_set(:DynamicAttributes, Module.new)
+
+ include mod
+
+ mod
+ end
+
+ def self.attributes_names
+ dynamic_attributes.instance_methods(false).sort.grep_v(/=$/)
+ end
+ class DSL
def initialize(base)
@base = base
- @dependencies = {}
- @attributes = {}
end
- def dependency(factory, as:, &block)
- as.tap do |name|
- @base.class_eval { attr_accessor name }
+ def attribute(name, &block)
+ @base.dynamic_attributes.module_eval do
+ attr_writer(name)
- Dependency::Signature.new(factory, block).tap do |signature|
- @dependencies.store(name, signature)
+ define_method(name) do
+ instance_variable_get("@#{name}") ||
+ instance_variable_set(
+ "@#{name}",
+ populate_attribute(name, block))
end
end
end
-
- def product(attribute, &block)
- Product::Attribute.new(attribute, block).tap do |signature|
- @attributes.store(attribute, signature)
- end
- end
end
+
+ attribute :web_url
end
end
end
diff --git a/qa/qa/factory/dependency.rb b/qa/qa/factory/dependency.rb
deleted file mode 100644
index fc5dc82ce29..00000000000
--- a/qa/qa/factory/dependency.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-module QA
- module Factory
- class Dependency
- Signature = Struct.new(:factory, :block)
-
- def initialize(name, factory, signature)
- @name = name
- @factory = factory
- @signature = signature
- end
-
- def overridden?
- !!@factory.public_send(@name)
- end
-
- def build!
- return if overridden?
-
- Builder.new(@signature, @factory).fabricate!.tap do |product|
- @factory.public_send("#{@name}=", product)
- end
- end
-
- class Builder
- def initialize(signature, caller_factory)
- @factory = signature.factory
- @block = signature.block
- @caller_factory = caller_factory
- end
-
- def fabricate!
- @factory.fabricate! do |factory|
- @block&.call(factory, @caller_factory)
- end
- end
- end
- end
- end
-end
diff --git a/qa/qa/factory/product.rb b/qa/qa/factory/product.rb
index 996b7f14f61..34df0bda8e5 100644
--- a/qa/qa/factory/product.rb
+++ b/qa/qa/factory/product.rb
@@ -5,23 +5,28 @@ module QA
class Product
include Capybara::DSL
- Attribute = Struct.new(:name, :block)
+ attr_reader :factory
- def initialize
- @location = current_url
+ def initialize(factory)
+ @factory = factory
+
+ define_attributes
end
def visit!
- visit @location
+ visit(web_url)
+ end
+
+ def populate(*attributes)
+ attributes.each(&method(:public_send))
end
- def self.populate!(factory)
- new.tap do |product|
- factory.class.attributes.each_value do |attribute|
- product.instance_exec(factory, attribute.block) do |factory, block|
- value = block.call(factory)
- product.define_singleton_method(attribute.name) { value }
- end
+ private
+
+ def define_attributes
+ factory.class.attributes_names.each do |name|
+ define_singleton_method(name) do
+ factory.public_send(name)
end
end
end
diff --git a/qa/qa/factory/repository/project_push.rb b/qa/qa/factory/repository/project_push.rb
index 167f47c9141..a9dfbc0a783 100644
--- a/qa/qa/factory/repository/project_push.rb
+++ b/qa/qa/factory/repository/project_push.rb
@@ -2,18 +2,14 @@ module QA
module Factory
module Repository
class ProjectPush < Factory::Repository::Push
- dependency Factory::Resource::Project, as: :project do |project|
- project.name = 'project-with-code'
- project.description = 'Project with repository'
- end
-
- product :output do |factory|
- factory.output
+ attribute :project do
+ Factory::Resource::Project.fabricate! do |resource|
+ resource.name = 'project-with-code'
+ resource.description = 'Project with repository'
+ end
end
- product :project do |factory|
- factory.project
- end
+ attribute :output
def initialize
@file_name = 'file.txt'
diff --git a/qa/qa/factory/repository/push.rb b/qa/qa/factory/repository/push.rb
index 6c5088f1da5..703c78daa99 100644
--- a/qa/qa/factory/repository/push.rb
+++ b/qa/qa/factory/repository/push.rb
@@ -30,6 +30,14 @@ module QA
@directory = dir
end
+ def files=(files)
+ if !files.is_a?(Array) || files.empty?
+ raise ArgumentError, "Please provide an array of hashes e.g.: [{name: 'file1', content: 'foo'}]"
+ end
+
+ @files = files
+ end
+
def fabricate!
Git::Repository.perform do |repository|
if ssh_key
@@ -63,6 +71,10 @@ module QA
@directory.each_child do |f|
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
repository.add_file(file_name, file_content)
end
diff --git a/qa/qa/factory/repository/wiki_push.rb b/qa/qa/factory/repository/wiki_push.rb
index ecc6cc18c88..25b6ffe8323 100644
--- a/qa/qa/factory/repository/wiki_push.rb
+++ b/qa/qa/factory/repository/wiki_push.rb
@@ -2,10 +2,12 @@ module QA
module Factory
module Repository
class WikiPush < Factory::Repository::Push
- dependency Factory::Resource::Wiki, as: :wiki do |wiki|
- wiki.title = 'Home'
- wiki.content = '# My First Wiki Content'
- wiki.message = 'Update home'
+ attribute :wiki do
+ Factory::Resource::Wiki.fabricate! do |resource|
+ resource.title = 'Home'
+ resource.content = '# My First Wiki Content'
+ resource.message = 'Update home'
+ end
end
def initialize
diff --git a/qa/qa/factory/resource/branch.rb b/qa/qa/factory/resource/branch.rb
index f3b52565d17..b05d1e252ec 100644
--- a/qa/qa/factory/resource/branch.rb
+++ b/qa/qa/factory/resource/branch.rb
@@ -5,8 +5,10 @@ module QA
attr_accessor :project, :branch_name,
:allow_to_push, :allow_to_merge, :protected
- dependency Factory::Resource::Project, as: :project do |project|
- project.name = 'protected-branch-project'
+ attribute :project do
+ Factory::Resource::Project.fabricate! do |resource|
+ resource.name = 'protected-branch-project'
+ end
end
def initialize
@@ -43,9 +45,7 @@ module QA
# to `allow_to_push` variable.
return branch unless @protected
- Page::Project::Menu.act do
- click_repository_settings
- end
+ Page::Project::Menu.perform(&:click_repository_settings)
Page::Project::Settings::Repository.perform do |setting|
setting.expand_protected_branches do |page|
diff --git a/qa/qa/factory/resource/deploy_key.rb b/qa/qa/factory/resource/deploy_key.rb
index 4c53c500c27..aea99c9f80d 100644
--- a/qa/qa/factory/resource/deploy_key.rb
+++ b/qa/qa/factory/resource/deploy_key.rb
@@ -4,11 +4,11 @@ module QA
class DeployKey < Factory::Base
attr_accessor :title, :key
- product :fingerprint do |resource|
- Page::Project::Settings::Repository.act do
- expand_deploy_keys do |key|
- key_offset = key.key_titles.index do |title|
- title.text == resource.title
+ attribute :fingerprint do
+ Page::Project::Settings::Repository.perform do |setting|
+ setting.expand_deploy_keys do |key|
+ key_offset = key.key_titles.index do |key_title|
+ key_title.text == title
end
key.key_fingerprints[key_offset].text
@@ -16,17 +16,17 @@ module QA
end
end
- dependency Factory::Resource::Project, as: :project do |project|
- project.name = 'project-to-deploy'
- project.description = 'project for adding deploy key test'
+ attribute :project do
+ Factory::Resource::Project.fabricate! do |resource|
+ resource.name = 'project-to-deploy'
+ resource.description = 'project for adding deploy key test'
+ end
end
def fabricate!
project.visit!
- Page::Project::Menu.act do
- click_repository_settings
- end
+ Page::Project::Menu.perform(&:click_repository_settings)
Page::Project::Settings::Repository.perform do |setting|
setting.expand_deploy_keys do |page|
diff --git a/qa/qa/factory/resource/deploy_token.rb b/qa/qa/factory/resource/deploy_token.rb
index 159f79ac50b..68e98f0aa01 100644
--- a/qa/qa/factory/resource/deploy_token.rb
+++ b/qa/qa/factory/resource/deploy_token.rb
@@ -4,25 +4,27 @@ module QA
class DeployToken < Factory::Base
attr_accessor :name, :expires_at
- product :username do |resource|
- Page::Project::Settings::Repository.act do
- expand_deploy_tokens do |token|
+ attribute :username do
+ Page::Project::Settings::Repository.perform do |page|
+ page.expand_deploy_tokens do |token|
token.token_username
end
end
end
- product :password do |password|
- Page::Project::Settings::Repository.act do
- expand_deploy_tokens do |token|
+ attribute :password do
+ Page::Project::Settings::Repository.perform do |page|
+ page.expand_deploy_tokens do |token|
token.token_password
end
end
end
- dependency Factory::Resource::Project, as: :project do |project|
- project.name = 'project-to-deploy'
- project.description = 'project for adding deploy token test'
+ attribute :project do
+ Factory::Resource::Project.fabricate! do |resource|
+ resource.name = 'project-to-deploy'
+ resource.description = 'project for adding deploy token test'
+ end
end
def fabricate!
diff --git a/qa/qa/factory/resource/file.rb b/qa/qa/factory/resource/file.rb
index f8dea06d361..1148876c2d3 100644
--- a/qa/qa/factory/resource/file.rb
+++ b/qa/qa/factory/resource/file.rb
@@ -8,8 +8,10 @@ module QA
:content,
:commit_message
- dependency Factory::Resource::Project, as: :project do |project|
- project.name = 'project-with-new-file'
+ attribute :project do
+ Factory::Resource::Project.fabricate! do |resource|
+ resource.name = 'project-with-new-file'
+ end
end
def initialize
@@ -21,7 +23,7 @@ module QA
def fabricate!
project.visit!
- Page::Project::Show.act { create_new_file! }
+ Page::Project::Show.perform(&:create_new_file!)
Page::File::Form.perform do |page|
page.add_name(@name)
diff --git a/qa/qa/factory/resource/fork.rb b/qa/qa/factory/resource/fork.rb
index 83dd4000f0a..0fac4377040 100644
--- a/qa/qa/factory/resource/fork.rb
+++ b/qa/qa/factory/resource/fork.rb
@@ -2,17 +2,19 @@ module QA
module Factory
module Resource
class Fork < Factory::Base
- dependency Factory::Repository::ProjectPush, as: :push
+ attribute :push do
+ Factory::Repository::ProjectPush.fabricate!
+ end
- dependency Factory::Resource::User, as: :user do |user|
- if Runtime::Env.forker?
- user.username = Runtime::Env.forker_username
- user.password = Runtime::Env.forker_password
+ attribute :user do
+ Factory::Resource::User.fabricate! do |resource|
+ if Runtime::Env.forker?
+ resource.username = Runtime::Env.forker_username
+ resource.password = Runtime::Env.forker_password
+ end
end
end
- product(:user) { |factory| factory.user }
-
def visit_project_with_retry
# The user intermittently fails to stay signed in after visiting the
# project page. The new user is registered and then signs in and a
@@ -48,15 +50,20 @@ module QA
end
def fabricate!
+ push
+ user
+
visit_project_with_retry
- Page::Project::Show.act { fork_project }
+ Page::Project::Show.perform(&:fork_project)
Page::Project::Fork::New.perform do |fork_new|
fork_new.choose_namespace(user.name)
end
- Page::Layout::Banner.act { has_notice?('The project was successfully forked.') }
+ Page::Layout::Banner.perform do |page|
+ page.has_notice?('The project was successfully forked.')
+ end
end
end
end
diff --git a/qa/qa/factory/resource/group.rb b/qa/qa/factory/resource/group.rb
index 033fc48c08f..45e49da86f9 100644
--- a/qa/qa/factory/resource/group.rb
+++ b/qa/qa/factory/resource/group.rb
@@ -4,7 +4,11 @@ module QA
class Group < Factory::Base
attr_accessor :path, :description
- dependency Factory::Resource::Sandbox, as: :sandbox
+ attribute :sandbox do
+ Factory::Resource::Sandbox.fabricate!
+ end
+
+ attribute :id
def initialize
@path = Runtime::Namespace.name
@@ -35,6 +39,29 @@ module QA
end
end
end
+
+ def fabricate_via_api!
+ resource_web_url(api_get)
+ rescue ResourceNotFoundError
+ super
+ end
+
+ def api_get_path
+ "/groups/#{CGI.escape("#{sandbox.path}/#{path}")}"
+ end
+
+ def api_post_path
+ '/groups'
+ end
+
+ def api_post_body
+ {
+ parent_id: sandbox.id,
+ path: path,
+ name: path,
+ visibility: 'public'
+ }
+ end
end
end
end
diff --git a/qa/qa/factory/resource/issue.rb b/qa/qa/factory/resource/issue.rb
index 95f48e20b3e..3a28e0d5aa6 100644
--- a/qa/qa/factory/resource/issue.rb
+++ b/qa/qa/factory/resource/issue.rb
@@ -2,23 +2,21 @@ module QA
module Factory
module Resource
class Issue < Factory::Base
- attr_writer :title, :description, :project
+ attr_writer :description
- dependency Factory::Resource::Project, as: :project do |project|
- project.name = 'project-for-issues'
- project.description = 'project for adding issues'
+ attribute :project do
+ Factory::Resource::Project.fabricate! do |resource|
+ resource.name = 'project-for-issues'
+ resource.description = 'project for adding issues'
+ end
end
- product :title do
- Page::Project::Issue::Show.act { issue_title }
- end
+ attribute :title
def fabricate!
project.visit!
- Page::Project::Show.act do
- go_to_new_issue
- end
+ Page::Project::Show.perform(&:go_to_new_issue)
Page::Project::Issue::New.perform do |page|
page.add_title(@title)
diff --git a/qa/qa/factory/resource/kubernetes_cluster.rb b/qa/qa/factory/resource/kubernetes_cluster.rb
index cdee35c54e3..aac6864f42f 100644
--- a/qa/qa/factory/resource/kubernetes_cluster.rb
+++ b/qa/qa/factory/resource/kubernetes_cluster.rb
@@ -7,24 +7,21 @@ module QA
attr_writer :project, :cluster,
:install_helm_tiller, :install_ingress, :install_prometheus, :install_runner
- product :ingress_ip do
- Page::Project::Operations::Kubernetes::Show.perform do |page|
- page.ingress_ip
- end
+ attribute :ingress_ip do
+ Page::Project::Operations::Kubernetes::Show.perform(&:ingress_ip)
end
def fabricate!
@project.visit!
- Page::Project::Menu.act { click_operations_kubernetes }
+ Page::Project::Menu.perform(
+ &:click_operations_kubernetes)
- Page::Project::Operations::Kubernetes::Index.perform do |page|
- page.add_kubernetes_cluster
- end
+ Page::Project::Operations::Kubernetes::Index.perform(
+ &:add_kubernetes_cluster)
- Page::Project::Operations::Kubernetes::Add.perform do |page|
- page.add_existing_cluster
- end
+ Page::Project::Operations::Kubernetes::Add.perform(
+ &:add_existing_cluster)
Page::Project::Operations::Kubernetes::AddExisting.perform do |page|
page.set_cluster_name(@cluster.cluster_name)
diff --git a/qa/qa/factory/resource/label.rb b/qa/qa/factory/resource/label.rb
new file mode 100644
index 00000000000..32bc519b48c
--- /dev/null
+++ b/qa/qa/factory/resource/label.rb
@@ -0,0 +1,39 @@
+require 'securerandom'
+
+module QA
+ module Factory
+ module Resource
+ class Label < Factory::Base
+ attr_accessor :description, :color
+
+ attribute :title
+
+ attribute :project do
+ Factory::Resource::Project.fabricate! do |resource|
+ resource.name = 'project-with-label'
+ end
+ end
+
+ def initialize
+ @title = "qa-test-#{SecureRandom.hex(8)}"
+ @description = 'This is a test label'
+ @color = '#0033CC'
+ end
+
+ def fabricate!
+ project.visit!
+
+ Page::Project::Menu.perform(&:go_to_labels)
+ Page::Label::Index.perform(&:go_to_new_label)
+
+ Page::Label::New.perform do |page|
+ page.fill_title(@title)
+ page.fill_description(@description)
+ page.fill_color(@color)
+ page.create_label
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/factory/resource/merge_request.rb b/qa/qa/factory/resource/merge_request.rb
index ddb62bd0a68..92b8bdf4a21 100644
--- a/qa/qa/factory/resource/merge_request.rb
+++ b/qa/qa/factory/resource/merge_request.rb
@@ -12,31 +12,33 @@ module QA
:milestone,
:labels
- product :project do |factory|
- factory.project
- end
+ attribute :source_branch
- product :source_branch do |factory|
- factory.source_branch
+ attribute :project do
+ Factory::Resource::Project.fabricate! do |resource|
+ resource.name = 'project-with-merge-request'
+ end
end
- dependency Factory::Resource::Project, as: :project do |project|
- project.name = 'project-with-merge-request'
- end
+ attribute :target do
+ project.visit!
- dependency Factory::Repository::ProjectPush, as: :target do |push, factory|
- factory.project.visit!
- push.project = factory.project
- push.branch_name = 'master'
- push.remote_branch = factory.target_branch
+ Factory::Repository::ProjectPush.fabricate! do |resource|
+ resource.project = project
+ resource.branch_name = 'master'
+ resource.remote_branch = target_branch
+ end
end
- dependency Factory::Repository::ProjectPush, as: :source do |push, factory|
- push.project = factory.project
- push.branch_name = factory.target_branch
- push.remote_branch = factory.source_branch
- push.file_name = "added_file.txt"
- push.file_content = "File Added"
+ attribute :source do
+ Factory::Repository::ProjectPush.fabricate! do |resource|
+ resource.project = project
+ resource.branch_name = target_branch
+ resource.remote_branch = source_branch
+ resource.new_branch = false
+ resource.file_name = "added_file.txt"
+ resource.file_content = "File Added"
+ end
end
def initialize
@@ -50,12 +52,18 @@ module QA
end
def fabricate!
+ target
+ source
project.visit!
- Page::Project::Show.act { new_merge_request }
+ Page::Project::Show.perform(&:new_merge_request)
Page::MergeRequest::New.perform do |page|
page.fill_title(@title)
page.fill_description(@description)
page.choose_milestone(@milestone) if @milestone
+ labels.each do |label|
+ page.select_label(label)
+ end
+
page.create_merge_request
end
end
diff --git a/qa/qa/factory/resource/merge_request_from_fork.rb b/qa/qa/factory/resource/merge_request_from_fork.rb
index 6caaf65f673..fbe062539b9 100644
--- a/qa/qa/factory/resource/merge_request_from_fork.rb
+++ b/qa/qa/factory/resource/merge_request_from_fork.rb
@@ -4,19 +4,24 @@ module QA
class MergeRequestFromFork < MergeRequest
attr_accessor :fork_branch
- dependency Factory::Resource::Fork, as: :fork
+ attribute :fork do
+ Factory::Resource::Fork.fabricate!
+ end
- dependency Factory::Repository::ProjectPush, as: :push do |push, factory|
- push.project = factory.fork
- push.branch_name = factory.fork_branch
- push.file_name = 'file2.txt'
- push.user = factory.fork.user
+ attribute :push do
+ Factory::Repository::ProjectPush.fabricate! do |resource|
+ resource.project = fork
+ resource.branch_name = fork_branch
+ resource.file_name = 'file2.txt'
+ resource.user = fork.user
+ end
end
def fabricate!
+ push
fork.visit!
- Page::Project::Show.act { new_merge_request }
- Page::MergeRequest::New.act { create_merge_request }
+ Page::Project::Show.perform(&:new_merge_request)
+ Page::MergeRequest::New.perform(&:create_merge_request)
end
end
end
diff --git a/qa/qa/factory/resource/personal_access_token.rb b/qa/qa/factory/resource/personal_access_token.rb
index 166054cfcdc..ceb0f1c3d75 100644
--- a/qa/qa/factory/resource/personal_access_token.rb
+++ b/qa/qa/factory/resource/personal_access_token.rb
@@ -7,13 +7,13 @@ module QA
class PersonalAccessToken < Factory::Base
attr_accessor :name
- product :access_token do
- Page::Profile::PersonalAccessTokens.act { created_access_token }
+ attribute :access_token do
+ Page::Profile::PersonalAccessTokens.perform(&:created_access_token)
end
def fabricate!
- Page::Main::Menu.act { go_to_profile_settings }
- Page::Profile::Menu.act { click_access_tokens }
+ Page::Main::Menu.perform(&:go_to_profile_settings)
+ Page::Profile::Menu.perform(&:click_access_tokens)
Page::Profile::PersonalAccessTokens.perform do |page|
page.fill_token_name(name || 'api-test-token')
diff --git a/qa/qa/factory/resource/project.rb b/qa/qa/factory/resource/project.rb
index 90db26ab3ab..f691ae5a342 100644
--- a/qa/qa/factory/resource/project.rb
+++ b/qa/qa/factory/resource/project.rb
@@ -4,26 +4,24 @@ module QA
module Factory
module Resource
class Project < Factory::Base
- attr_writer :description
- attr_reader :name
+ attribute :name
+ attribute :description
- dependency Factory::Resource::Group, as: :group
-
- product :name do |factory|
- factory.name
+ attribute :group do
+ Factory::Resource::Group.fabricate!
end
- product :repository_ssh_location do
- Page::Project::Show.act do
- choose_repository_clone_ssh
- repository_location
+ attribute :repository_ssh_location do
+ Page::Project::Show.perform do |page|
+ page.choose_repository_clone_ssh
+ page.repository_location
end
end
- product :repository_http_location do
- Page::Project::Show.act do
- choose_repository_clone_http
- repository_location
+ attribute :repository_http_location do
+ Page::Project::Show.perform do |page|
+ page.choose_repository_clone_http
+ page.repository_location
end
end
@@ -38,7 +36,7 @@ module QA
def fabricate!
group.visit!
- Page::Group::Show.act { go_to_new_project }
+ Page::Group::Show.perform(&:go_to_new_project)
Page::Project::New.perform do |page|
page.choose_test_namespace
@@ -48,6 +46,32 @@ module QA
page.create_new_project
end
end
+
+ def api_get_path
+ "/projects/#{name}"
+ end
+
+ def api_post_path
+ '/projects'
+ end
+
+ def api_post_body
+ {
+ namespace_id: group.id,
+ path: name,
+ name: name,
+ description: description,
+ visibility: 'public'
+ }
+ end
+
+ private
+
+ def transform_api_resource(resource)
+ resource[:repository_ssh_location] = Git::Location.new(resource[:ssh_url_to_repo])
+ resource[:repository_http_location] = Git::Location.new(resource[:http_url_to_repo])
+ resource
+ end
end
end
end
diff --git a/qa/qa/factory/resource/project_imported_from_github.rb b/qa/qa/factory/resource/project_imported_from_github.rb
index df2a3340d60..f62092ae122 100644
--- a/qa/qa/factory/resource/project_imported_from_github.rb
+++ b/qa/qa/factory/resource/project_imported_from_github.rb
@@ -6,16 +6,16 @@ module QA
class ProjectImportedFromGithub < Resource::Project
attr_writer :personal_access_token, :github_repository_path
- dependency Factory::Resource::Group, as: :group
-
- product :name do |factory|
- factory.name
+ attribute :group do
+ Factory::Resource::Group.fabricate!
end
+ attribute :name
+
def fabricate!
group.visit!
- Page::Group::Show.act { go_to_new_project }
+ Page::Group::Show.perform(&:go_to_new_project)
Page::Project::New.perform do |page|
page.go_to_import_project
diff --git a/qa/qa/factory/resource/project_milestone.rb b/qa/qa/factory/resource/project_milestone.rb
index 1251ae03135..cfda58dc103 100644
--- a/qa/qa/factory/resource/project_milestone.rb
+++ b/qa/qa/factory/resource/project_milestone.rb
@@ -3,11 +3,12 @@ module QA
module Resource
class ProjectMilestone < Factory::Base
attr_accessor :description
- attr_reader :title
- dependency Factory::Resource::Project, as: :project
+ attribute :project do
+ Factory::Resource::Project.fabricate!
+ end
- product(:title) { |factory| factory.title }
+ attribute :title
def title=(title)
@title = "#{title}-#{SecureRandom.hex(4)}"
@@ -17,12 +18,12 @@ module QA
def fabricate!
project.visit!
- Page::Project::Menu.act do
- click_issues
- click_milestones
+ Page::Project::Menu.perform do |page|
+ page.click_issues
+ page.click_milestones
end
- Page::Project::Milestone::Index.act { click_new_milestone }
+ Page::Project::Milestone::Index.perform(&:click_new_milestone)
Page::Project::Milestone::New.perform do |milestone_new|
milestone_new.set_title(@title)
diff --git a/qa/qa/factory/resource/runner.rb b/qa/qa/factory/resource/runner.rb
index 7ac65fe6913..7108db1e55a 100644
--- a/qa/qa/factory/resource/runner.rb
+++ b/qa/qa/factory/resource/runner.rb
@@ -6,9 +6,11 @@ module QA
class Runner < Factory::Base
attr_writer :name, :tags, :image
- dependency Factory::Resource::Project, as: :project do |project|
- project.name = 'project-with-ci-cd'
- project.description = 'Project with CI/CD Pipelines'
+ attribute :project do
+ Factory::Resource::Project.fabricate! do |resource|
+ resource.name = 'project-with-ci-cd'
+ resource.description = 'Project with CI/CD Pipelines'
+ end
end
def name
@@ -26,7 +28,7 @@ module QA
def fabricate!
project.visit!
- Page::Project::Menu.act { click_ci_cd_settings }
+ Page::Project::Menu.perform(&:click_ci_cd_settings)
Service::Runner.new(name).tap do |runner|
Page::Project::Settings::CICD.perform do |settings|
diff --git a/qa/qa/factory/resource/sandbox.rb b/qa/qa/factory/resource/sandbox.rb
index 5249e1755a6..56bcda9e2f3 100644
--- a/qa/qa/factory/resource/sandbox.rb
+++ b/qa/qa/factory/resource/sandbox.rb
@@ -6,21 +6,26 @@ module QA
# creating it if it doesn't yet exist.
#
class Sandbox < Factory::Base
+ attr_reader :path
+
+ attribute :id
+ attribute :path
+
def initialize
- @name = Runtime::Namespace.sandbox_name
+ @path = Runtime::Namespace.sandbox_name
end
def fabricate!
- Page::Main::Menu.act { go_to_groups }
+ Page::Main::Menu.perform(&:go_to_groups)
Page::Dashboard::Groups.perform do |page|
- if page.has_group?(@name)
- page.go_to_group(@name)
+ if page.has_group?(path)
+ page.go_to_group(path)
else
page.go_to_new_group
Page::Group::New.perform do |group|
- group.set_path(@name)
+ group.set_path(path)
group.set_description('GitLab QA Sandbox Group')
group.set_visibility('Public')
group.create
@@ -28,6 +33,28 @@ module QA
end
end
end
+
+ def fabricate_via_api!
+ resource_web_url(api_get)
+ rescue ResourceNotFoundError
+ super
+ end
+
+ def api_get_path
+ "/groups/#{path}"
+ end
+
+ def api_post_path
+ '/groups'
+ end
+
+ def api_post_body
+ {
+ path: path,
+ name: path,
+ visibility: 'public'
+ }
+ end
end
end
end
diff --git a/qa/qa/factory/resource/secret_variable.rb b/qa/qa/factory/resource/secret_variable.rb
index 4084a7fc2cd..24ba3408810 100644
--- a/qa/qa/factory/resource/secret_variable.rb
+++ b/qa/qa/factory/resource/secret_variable.rb
@@ -4,15 +4,17 @@ module QA
class SecretVariable < Factory::Base
attr_accessor :key, :value
- dependency Factory::Resource::Project, as: :project do |project|
- project.name = 'project-with-secret-variables'
- project.description = 'project for adding secret variable test'
+ attribute :project do
+ Factory::Resource::Project.fabricate! do |resource|
+ resource.name = 'project-with-secret-variables'
+ resource.description = 'project for adding secret variable test'
+ end
end
def fabricate!
project.visit!
- Page::Project::Menu.act { click_ci_cd_settings }
+ Page::Project::Menu.perform(&:click_ci_cd_settings)
Page::Project::Settings::CICD.perform do |setting|
setting.expand_secret_variables do |page|
diff --git a/qa/qa/factory/resource/ssh_key.rb b/qa/qa/factory/resource/ssh_key.rb
index 45236f69de9..a48a93fbe65 100644
--- a/qa/qa/factory/resource/ssh_key.rb
+++ b/qa/qa/factory/resource/ssh_key.rb
@@ -6,29 +6,19 @@ module QA
class SSHKey < Factory::Base
extend Forwardable
- attr_accessor :title
- attr_reader :private_key, :public_key, :fingerprint
def_delegators :key, :private_key, :public_key, :fingerprint
- product :private_key do |factory|
- factory.private_key
- end
-
- product :title do |factory|
- factory.title
- end
-
- product :fingerprint do |factory|
- factory.fingerprint
- end
+ attribute :private_key
+ attribute :title
+ attribute :fingerprint
def key
@key ||= Runtime::Key::RSA.new
end
def fabricate!
- Page::Main::Menu.act { go_to_profile_settings }
- Page::Profile::Menu.act { click_ssh_keys }
+ Page::Main::Menu.perform(&:go_to_profile_settings)
+ Page::Profile::Menu.perform(&:click_ssh_keys)
Page::Profile::SSHKeys.perform do |page|
page.add_key(public_key, title)
diff --git a/qa/qa/factory/resource/user.rb b/qa/qa/factory/resource/user.rb
index e8b9ea2e6b4..6e6f46f7a95 100644
--- a/qa/qa/factory/resource/user.rb
+++ b/qa/qa/factory/resource/user.rb
@@ -5,7 +5,6 @@ module QA
module Resource
class User < Factory::Base
attr_reader :unique_id
- attr_writer :username, :password, :name, :email
def initialize
@unique_id = SecureRandom.hex(8)
@@ -31,14 +30,14 @@ module QA
defined?(@username) && defined?(@password)
end
- product(:name) { |factory| factory.name }
- product(:username) { |factory| factory.username }
- product(:email) { |factory| factory.email }
- product(:password) { |factory| factory.password }
+ attribute :name
+ attribute :username
+ attribute :email
+ attribute :password
def fabricate!
# Don't try to log-out if we're not logged-in
- if Page::Main::Menu.act { has_personal_area?(wait: 0) }
+ if Page::Main::Menu.perform { |p| p.has_personal_area?(wait: 0) }
Page::Main::Menu.perform { |main| main.sign_out }
end
diff --git a/qa/qa/factory/resource/wiki.rb b/qa/qa/factory/resource/wiki.rb
index acfe143fa61..769f394e85c 100644
--- a/qa/qa/factory/resource/wiki.rb
+++ b/qa/qa/factory/resource/wiki.rb
@@ -4,19 +4,24 @@ module QA
class Wiki < Factory::Base
attr_accessor :title, :content, :message
- dependency Factory::Resource::Project, as: :project do |project|
- project.name = 'project-for-wikis'
- project.description = 'project for adding wikis'
+ attribute :project do
+ Factory::Resource::Project.fabricate! do |resource|
+ resource.name = 'project-for-wikis'
+ resource.description = 'project for adding wikis'
+ end
end
def fabricate!
- Page::Project::Menu.act { click_wiki }
- Page::Project::Wiki::New.perform do |page|
- page.go_to_create_first_page
- page.set_title(@title)
- page.set_content(@content)
- page.set_message(@message)
- page.create_new_page
+ project.visit!
+
+ Page::Project::Menu.perform { |menu_side| menu_side.click_wiki }
+
+ Page::Project::Wiki::New.perform do |wiki_new|
+ wiki_new.go_to_create_first_page
+ wiki_new.set_title(@title)
+ wiki_new.set_content(@content)
+ wiki_new.set_message(@message)
+ wiki_new.create_new_page
end
end
end
diff --git a/qa/qa/factory/settings/hashed_storage.rb b/qa/qa/factory/settings/hashed_storage.rb
index f2e58a3ea38..4e32382f910 100644
--- a/qa/qa/factory/settings/hashed_storage.rb
+++ b/qa/qa/factory/settings/hashed_storage.rb
@@ -5,18 +5,18 @@ module QA
def fabricate!(*traits)
raise ArgumentError unless traits.include?(:enabled)
- Page::Main::Login.act { sign_in_using_credentials }
- Page::Main::Menu.act { go_to_admin_area }
- Page::Admin::Menu.act { go_to_repository_settings }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+ Page::Main::Menu.perform(&:go_to_admin_area)
+ Page::Admin::Menu.perform(&:go_to_repository_settings)
- Page::Admin::Settings::Main.perform do |setting|
+ Page::Admin::Settings::Repository.perform do |setting|
setting.expand_repository_storage do |page|
page.enable_hashed_storage
page.save_settings
end
end
- QA::Page::Main::Menu.act { sign_out }
+ QA::Page::Main::Menu.perform(&:sign_out)
end
end
end
diff --git a/qa/qa/git/repository.rb b/qa/qa/git/repository.rb
index 14cb8125fdb..27a88534258 100644
--- a/qa/qa/git/repository.rb
+++ b/qa/qa/git/repository.rb
@@ -1,14 +1,24 @@
+# frozen_string_literal: true
+
require 'cgi'
require 'uri'
require 'open3'
+require 'fileutils'
+require 'tmpdir'
module QA
module Git
class Repository
include Scenario::Actable
+ attr_writer :password
+ attr_accessor :env_vars
+
def initialize
- @ssh_cmd = ""
+ # We set HOME to the current working directory (which is a
+ # temporary directory created in .perform()) so the temporarily dropped
+ # .netrc can be utilised
+ self.env_vars = [%Q{HOME="#{File.dirname(netrc_file_path)}"}]
end
def self.perform(*args)
@@ -21,36 +31,27 @@ module QA
@uri = URI(address)
end
- def username=(name)
- @username = name
- @uri.user = name
- end
-
- def password=(pass)
- @password = pass
- @uri.password = CGI.escape(pass).gsub('+', '%20')
+ def username=(username)
+ @username = username
+ @uri.user = username
end
def use_default_credentials
- if ::QA::Runtime::User.ldap_user?
- self.username = Runtime::User.ldap_username
- self.password = Runtime::User.ldap_password
- else
- self.username = Runtime::User.username
- self.password = Runtime::User.password
- end
+ self.username, self.password = default_credentials
+
+ add_credentials_to_netrc unless ssh_key_set?
end
def clone(opts = '')
- run_and_redact_credentials(build_git_command("git clone #{opts} #{@uri} ./"))
+ run("git clone #{opts} #{uri} ./")
end
def checkout(branch_name)
- `git checkout "#{branch_name}"`
+ run(%Q{git checkout "#{branch_name}"})
end
def checkout_new_branch(branch_name)
- `git checkout -b "#{branch_name}"`
+ run(%Q{git checkout -b "#{branch_name}"})
end
def shallow_clone
@@ -58,12 +59,10 @@ module QA
end
def configure_identity(name, email)
- `git config user.name #{name}`
- `git config user.email #{email}`
- end
+ run(%Q{git config user.name #{name}})
+ run(%Q{git config user.email #{email}})
- def configure_ssh_command(command)
- @ssh_cmd = "GIT_SSH_COMMAND='#{command}'"
+ add_credentials_to_netrc
end
def commit_file(name, contents, message)
@@ -74,54 +73,99 @@ module QA
def add_file(name, contents)
::File.write(name, contents)
- `git add #{name}`
+ run(%Q{git add #{name}})
end
def commit(message)
- `git commit -m "#{message}"`
+ run(%Q{git commit -m "#{message}"})
end
def push_changes(branch = 'master')
- output, _ = run_and_redact_credentials(build_git_command("git push #{@uri} #{branch}"))
-
- output
+ run("git push #{uri} #{branch}")
end
def commits
- `git log --oneline`.split("\n")
+ run('git log --oneline').split("\n")
end
def use_ssh_key(key)
@private_key_file = Tempfile.new("id_#{SecureRandom.hex(8)}")
- File.binwrite(@private_key_file, key.private_key)
- File.chmod(0700, @private_key_file)
+ File.binwrite(private_key_file, key.private_key)
+ File.chmod(0700, private_key_file)
@known_hosts_file = Tempfile.new("known_hosts_#{SecureRandom.hex(8)}")
keyscan_params = ['-H']
- keyscan_params << "-p #{@uri.port}" if @uri.port
- keyscan_params << @uri.host
- run_and_redact_credentials("ssh-keyscan #{keyscan_params.join(' ')} >> #{@known_hosts_file.path}")
+ keyscan_params << "-p #{uri.port}" if uri.port
+ keyscan_params << uri.host
+ run("ssh-keyscan #{keyscan_params.join(' ')} >> #{known_hosts_file.path}")
- configure_ssh_command("ssh -i #{@private_key_file.path} -o UserKnownHostsFile=#{@known_hosts_file.path}")
+ self.env_vars << %Q{GIT_SSH_COMMAND="ssh -i #{private_key_file.path} -o UserKnownHostsFile=#{known_hosts_file.path}"}
end
def delete_ssh_key
- return unless @private_key_file
+ return unless ssh_key_set?
- @private_key_file.close(true)
- @known_hosts_file.close(true)
+ private_key_file.close(true)
+ known_hosts_file.close(true)
end
- def build_git_command(command_str)
- [@ssh_cmd, command_str].compact.join(' ')
+ private
+
+ attr_reader :uri, :username, :password, :known_hosts_file, :private_key_file
+
+ def ssh_key_set?
+ !private_key_file.nil?
end
- private
+ def run(command_str)
+ command = [env_vars, command_str, '2>&1'].compact.join(' ')
+ Runtime::Logger.debug "Git: command=[#{command}]"
+
+ output, _ = Open3.capture2(command)
+ output = output.chomp.gsub(/\s+$/, '')
+ Runtime::Logger.debug "Git: output=[#{output}]"
+
+ output
+ end
+
+ def default_credentials
+ if ::QA::Runtime::User.ldap_user?
+ [Runtime::User.ldap_username, Runtime::User.ldap_password]
+ else
+ [Runtime::User.username, Runtime::User.password]
+ end
+ end
+
+ def tmp_netrc_directory
+ @tmp_netrc_directory ||= File.join(Dir.tmpdir, "qa-netrc-credentials", $$.to_s)
+ end
+
+ def netrc_file_path
+ @netrc_file_path ||= File.join(tmp_netrc_directory, '.netrc')
+ end
+
+ def netrc_content
+ "machine #{uri.host} login #{username} password #{password}"
+ end
+
+ def netrc_already_contains_content?
+ File.exist?(netrc_file_path) &&
+ File.readlines(netrc_file_path).grep(/^#{netrc_content}$/).any?
+ end
+
+ def add_credentials_to_netrc
+ # Despite libcurl supporting a custom .netrc location through the
+ # CURLOPT_NETRC_FILE environment variable, git does not support it :(
+ # Info: https://curl.haxx.se/libcurl/c/CURLOPT_NETRC_FILE.html
+ #
+ # This will create a .netrc in the correct working directory, which is
+ # a temporary directory created in .perform()
+ #
+ return if netrc_already_contains_content?
- # Since the remote URL contains the credentials, and git occasionally
- # outputs the URL. Note that stderr is redirected to stdout.
- def run_and_redact_credentials(command)
- Open3.capture2("#{command} 2>&1 | sed -E 's#://[^@]+@#://****@#g'")
+ FileUtils.mkdir_p(tmp_netrc_directory)
+ File.open(netrc_file_path, 'a') { |file| file.puts(netrc_content) }
+ File.chmod(0600, netrc_file_path)
end
end
end
diff --git a/qa/qa/page/README.md b/qa/qa/page/README.md
index 4d58f1a43b7..d0de33892c4 100644
--- a/qa/qa/page/README.md
+++ b/qa/qa/page/README.md
@@ -131,4 +131,4 @@ If you need more information, ask for help on `#quality` channel on Slack
(internal, GitLab Team only).
If you are not a Team Member, and you still need help to contribute, please
-open an issue in GitLab QA issue tracker.
+open an issue in GitLab CE issue tracker with the `~QA` label.
diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb
index 160ec58cf2c..91e229c4c8c 100644
--- a/qa/qa/page/base.rb
+++ b/qa/qa/page/base.rb
@@ -5,6 +5,7 @@ require 'capybara/dsl'
module QA
module Page
class Base
+ prepend Support::Page::Logging if Runtime::Env.debug?
include Capybara::DSL
include Scenario::Actable
extend SingleForwardable
diff --git a/qa/qa/page/label/index.rb b/qa/qa/page/label/index.rb
new file mode 100644
index 00000000000..323acd57743
--- /dev/null
+++ b/qa/qa/page/label/index.rb
@@ -0,0 +1,15 @@
+module QA
+ module Page
+ module Label
+ class Index < Page::Base
+ view 'app/views/projects/labels/index.html.haml' do
+ element :label_create_new
+ end
+
+ def go_to_new_label
+ click_element :label_create_new
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/label/new.rb b/qa/qa/page/label/new.rb
new file mode 100644
index 00000000000..b5422dc9400
--- /dev/null
+++ b/qa/qa/page/label/new.rb
@@ -0,0 +1,30 @@
+module QA
+ module Page
+ module Label
+ class New < Page::Base
+ view 'app/views/shared/labels/_form.html.haml' do
+ element :label_title
+ element :label_description
+ element :label_color
+ element :label_create_button
+ end
+
+ def create_label
+ click_element :label_create_button
+ end
+
+ def fill_title(title)
+ fill_element :label_title, title
+ end
+
+ def fill_description(description)
+ fill_element :label_description, description
+ end
+
+ def fill_color(color)
+ fill_element :label_color, color
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/merge_request/new.rb b/qa/qa/page/merge_request/new.rb
index 83cc4bbbace..1f8f1fbca8e 100644
--- a/qa/qa/page/merge_request/new.rb
+++ b/qa/qa/page/merge_request/new.rb
@@ -22,6 +22,10 @@ module QA
element :issuable_dropdown_menu_milestone
end
+ view 'app/views/shared/issuable/_label_dropdown.html.haml' do
+ element :issuable_label
+ end
+
def create_merge_request
click_element :issuable_create_button
end
@@ -40,6 +44,12 @@ module QA
click_on milestone.title
end
end
+
+ def select_label(label)
+ click_element :issuable_label
+
+ click_link label.title
+ end
end
end
end
diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb
index b40e90ef4ad..376606afb5d 100644
--- a/qa/qa/page/merge_request/show.rb
+++ b/qa/qa/page/merge_request/show.rb
@@ -23,6 +23,10 @@ module QA
element :squash_checkbox
end
+ view 'app/views/shared/issuable/_sidebar.html.haml' do
+ element :labels_block
+ end
+
def fast_forward_possible?
!has_text?('Fast-forward merge is not possible')
end
@@ -64,6 +68,13 @@ module QA
end
end
+ def has_label?(label)
+ page.within(element_selector_css(:labels_block)) do
+ element = find('span', text: label)
+ !element.nil?
+ end
+ end
+
def merge!
# The merge button is disabled on load
wait do
diff --git a/qa/qa/page/project/job/show.rb b/qa/qa/page/project/job/show.rb
index 5baf6439cfc..d688f15914c 100644
--- a/qa/qa/page/project/job/show.rb
+++ b/qa/qa/page/project/job/show.rb
@@ -4,30 +4,39 @@ module QA::Page
COMPLETED_STATUSES = %w[passed failed canceled blocked skipped manual].freeze # excludes created, pending, running
PASSED_STATUS = 'passed'.freeze
- view 'app/views/shared/builds/_build_output.html.haml' do
- element :build_output, '.js-build-output' # rubocop:disable QA/ElementWithPattern
- element :loading_animation, '.js-build-refresh' # rubocop:disable QA/ElementWithPattern
+ view 'app/assets/javascripts/jobs/components/job_app.vue' do
+ element :loading_animation
+ end
+
+ 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, 'ci-status' # rubocop:disable QA/ElementWithPattern
+ element :status_badge
end
def completed?
- COMPLETED_STATUSES.include? find('.ci-status').text
+ COMPLETED_STATUSES.include?(status_badge)
end
def passed?
- find('.ci-status').text == PASSED_STATUS
+ status_badge == PASSED_STATUS
end
def trace_loading?
- has_css?('.js-build-refresh')
+ has_element?(:loading_animation)
end
# Reminder: You may wish to wait for a particular job status before checking output
def output
- find('.js-build-output').text
+ find_element(:build_trace).text
+ end
+
+ private
+
+ def status_badge
+ find_element(:status_badge).text
end
end
end
diff --git a/qa/qa/page/project/menu.rb b/qa/qa/page/project/menu.rb
index b32d5ea772b..cb4a10e1b6a 100644
--- a/qa/qa/page/project/menu.rb
+++ b/qa/qa/page/project/menu.rb
@@ -22,6 +22,7 @@ module QA
element :activity_link, "title: _('Activity')" # rubocop:disable QA/ElementWithPattern
element :wiki_link_text, "Wiki" # rubocop:disable QA/ElementWithPattern
element :milestones_link
+ element :labels_link
end
view 'app/assets/javascripts/fly_out_nav.js' do
@@ -86,6 +87,14 @@ module QA
end
end
+ def go_to_labels
+ hover_issues do
+ within_submenu do
+ click_element(:labels_link)
+ end
+ end
+ end
+
def click_merge_requests
within_sidebar do
click_link('Merge Requests')
@@ -104,8 +113,22 @@ module QA
end
end
+ def click_repository
+ within_sidebar do
+ click_link('Repository')
+ end
+ end
+
private
+ def hover_issues
+ within_sidebar do
+ find_element(:issues_item).hover
+
+ yield
+ end
+ end
+
def hover_settings
within_sidebar do
find('.qa-settings-item').hover
diff --git a/qa/qa/page/project/pipeline/show.rb b/qa/qa/page/project/pipeline/show.rb
index 06df1238738..b22396fd67a 100644
--- a/qa/qa/page/project/pipeline/show.rb
+++ b/qa/qa/page/project/pipeline/show.rb
@@ -9,7 +9,7 @@ module QA::Page
element :pipeline_graph, /class.*pipeline-graph.*/ # rubocop:disable QA/ElementWithPattern
end
- view 'app/assets/javascripts/pipelines/components/graph/job_component.vue' do
+ view 'app/assets/javascripts/pipelines/components/graph/job_item.vue' do
element :job_component, /class.*ci-job-component.*/ # rubocop:disable QA/ElementWithPattern
element :job_link, /class.*js-pipeline-graph-job-link.*/ # rubocop:disable QA/ElementWithPattern
end
diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb
index fcc4bb79c10..d6dddf03ffb 100644
--- a/qa/qa/page/project/show.rb
+++ b/qa/qa/page/project/show.rb
@@ -42,6 +42,10 @@ module QA
element :web_ide_button
end
+ view 'app/views/projects/tree/_tree_content.html.haml' do
+ element :file_tree
+ end
+
def project_name
find('.qa-project-name').text
end
@@ -51,6 +55,12 @@ module QA
click_element :new_file_option
end
+ def go_to_file(filename)
+ within_element(:file_tree) do
+ click_on filename
+ end
+ end
+
def switch_to_branch(branch_name)
find_element(:branches_select).click
diff --git a/qa/qa/runtime/api/client.rb b/qa/qa/runtime/api/client.rb
index 02015e23ad8..0545b500e4c 100644
--- a/qa/qa/runtime/api/client.rb
+++ b/qa/qa/runtime/api/client.rb
@@ -6,33 +6,34 @@ module QA
class Client
attr_reader :address
- def initialize(address = :gitlab, personal_access_token: nil)
+ def initialize(address = :gitlab, personal_access_token: nil, is_new_session: true)
@address = address
@personal_access_token = personal_access_token
+ @is_new_session = is_new_session
end
def personal_access_token
- @personal_access_token ||= get_personal_access_token
- end
-
- def get_personal_access_token
- # you can set the environment variable PERSONAL_ACCESS_TOKEN
- # to use a specific access token rather than create one from the UI
- if Runtime::Env.personal_access_token
- Runtime::Env.personal_access_token
- else
- create_personal_access_token
+ @personal_access_token ||= begin
+ # you can set the environment variable PERSONAL_ACCESS_TOKEN
+ # to use a specific access token rather than create one from the UI
+ Runtime::Env.personal_access_token ||= create_personal_access_token
end
end
private
def create_personal_access_token
- Runtime::Browser.visit(@address, Page::Main::Login) do
- Page::Main::Login.act { sign_in_using_credentials }
- Factory::Resource::PersonalAccessToken.fabricate!.access_token
+ if @is_new_session
+ Runtime::Browser.visit(@address, Page::Main::Login) { do_create_personal_access_token }
+ else
+ do_create_personal_access_token
end
end
+
+ def do_create_personal_access_token
+ Page::Main::Login.act { sign_in_using_credentials }
+ Factory::Resource::PersonalAccessToken.fabricate!.access_token
+ end
end
end
end
diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb
index 4a2109799fa..c7052a9f300 100644
--- a/qa/qa/runtime/env.rb
+++ b/qa/qa/runtime/env.rb
@@ -1,8 +1,20 @@
+# frozen_string_literal: true
+
module QA
module Runtime
module Env
extend self
+ attr_writer :personal_access_token
+
+ def debug?
+ enabled?(ENV['QA_DEBUG'], default: false)
+ end
+
+ def log_destination
+ ENV['QA_LOG_PATH'] || $stdout
+ end
+
# set to 'false' to have Chrome run visibly instead of headless
def chrome_headless?
enabled?(ENV['CHROME_HEADLESS'])
@@ -22,7 +34,7 @@ module QA
# specifies token that can be used for the api
def personal_access_token
- ENV['PERSONAL_ACCESS_TOKEN']
+ @personal_access_token ||= ENV['PERSONAL_ACCESS_TOKEN']
end
def user_username
@@ -42,7 +54,7 @@ module QA
end
def forker?
- forker_username && forker_password
+ !!(forker_username && forker_password)
end
def forker_username
diff --git a/qa/qa/runtime/logger.rb b/qa/qa/runtime/logger.rb
new file mode 100644
index 00000000000..bd5c4fe5bf5
--- /dev/null
+++ b/qa/qa/runtime/logger.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'logger'
+
+module QA
+ module Runtime
+ module Logger
+ extend SingleForwardable
+
+ def_delegators :logger, :debug, :info, :warn, :error, :fatal, :unknown
+
+ singleton_class.module_eval do
+ attr_writer :logger
+
+ def logger
+ return @logger if @logger
+
+ @logger = ::Logger.new Runtime::Env.log_destination
+ @logger.level = Runtime::Env.debug? ? ::Logger::DEBUG : ::Logger::ERROR
+ @logger
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/api/1_manage/users_spec.rb b/qa/qa/specs/features/api/1_manage/users_spec.rb
index 3e3c9e859aa..ba1ba204d24 100644
--- a/qa/qa/specs/features/api/1_manage/users_spec.rb
+++ b/qa/qa/specs/features/api/1_manage/users_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :manage do
+ context 'Manage' do
describe 'Users API' do
before(:context) do
@api_client = Runtime::API::Client.new(:gitlab)
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb
index ae196349c6b..dae2a9e0236 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb
@@ -1,5 +1,5 @@
module QA
- context :manage, :smoke do
+ context 'Manage', :smoke do
describe 'basic user login' do
it 'user logs in using basic credentials' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb
index 217870531da..eb9e0297287 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :manage, :orchestrated, :ldap do
+ context 'Manage', :orchestrated, :ldap do
describe 'LDAP login' do
it 'user logs into GitLab using LDAP credentials' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb
index 6eda2c750d4..b1d641b507f 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :manage, :orchestrated, :mattermost do
+ context 'Manage', :orchestrated, :mattermost do
describe 'Mattermost login' do
it 'user logs into Mattermost using GitLab OAuth' do
Runtime::Browser.visit(:gitlab, Page::Main::Login) do
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb
index 8d5055aab45..87f0e9030d2 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :manage, :orchestrated, :instance_saml do
+ context 'Manage', :orchestrated, :instance_saml do
describe 'Instance wide SAML SSO' do
it 'User logs in to gitlab with SAML SSO' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb
index fb6b4937554..45cb5df8252 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb
@@ -16,13 +16,13 @@ module QA
end
end
- context :manage, :skip_signup_disabled do
+ context 'Manage', :skip_signup_disabled do
describe 'standard' do
it_behaves_like 'registration and login'
end
end
- context :manage, :orchestrated, :ldap, :skip_signup_disabled do
+ context 'Manage', :orchestrated, :ldap, :skip_signup_disabled do
describe 'while LDAP is enabled' do
it_behaves_like 'registration and login'
end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb
index b276c7ee579..7bf26c22fa6 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :manage do
+ context 'Manage' do
describe 'Add project member' do
it 'user adds project member' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
@@ -11,9 +11,10 @@ module QA
Page::Main::Menu.perform { |main| main.sign_out }
Page::Main::Login.act { sign_in_using_credentials }
- Factory::Resource::Project.fabricate! do |resource|
+ project = Factory::Resource::Project.fabricate! do |resource|
resource.name = 'add-member-project'
end
+ project.visit!
Page::Project::Menu.act { click_members_settings }
Page::Project::Settings::Members.perform do |page|
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb
index bb1f3ab26d1..a242f2158da 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb
@@ -1,23 +1,21 @@
# frozen_string_literal: true
module QA
- context :manage, :smoke do
+ context 'Manage', :smoke do
describe 'Project creation' do
it 'user creates a new project' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
- created_project = Factory::Resource::Project.fabricate! do |project|
+ created_project = Factory::Resource::Project.fabricate_via_browser_ui! do |project|
project.name = 'awesome-project'
project.description = 'create awesome project test'
end
- expect(created_project.name).to match /^awesome-project-\h{16}$/
-
+ expect(page).to have_content(created_project.name)
expect(page).to have_content(
/Project \S?awesome-project\S+ was successfully created/
)
-
expect(page).to have_content('create awesome project test')
expect(page).to have_content('The repository for this project is empty')
end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb
index d1cd9865aef..a99b0522e73 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :manage, :orchestrated, :github do
+ context 'Manage', :orchestrated, :github do
describe 'Project import from GitHub' do
let(:imported_project) do
Factory::Resource::ProjectImportedFromGithub.fabricate! do |project|
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb
index 97ac35e8dba..768d40f3acf 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :manage do
+ context 'Manage' do
describe 'Project activity' do
it 'user creates an event in the activity page upon Git push' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
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 49d76f31e3a..e67561b3a39 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
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :plan, :smoke do
+ context 'Plan', :smoke do
describe 'Issue creation' do
let(:issue_title) { 'issue title' }
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb
index bcf55a02a61..037ff5efbd4 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :create do
+ context 'Create' do
describe 'Merge request creation' do
it 'user creates a new merge request' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
@@ -16,16 +16,26 @@ module QA
milestone.project = current_project
end
+ new_label = Factory::Resource::Label.fabricate! do |label|
+ label.project = current_project
+ label.title = 'qa-mr-test-label'
+ label.description = 'Merge Request label'
+ end
+
Factory::Resource::MergeRequest.fabricate! do |merge_request|
merge_request.title = 'This is a merge request with a milestone'
merge_request.description = 'Great feature with milestone'
merge_request.project = current_project
merge_request.milestone = current_milestone
+ merge_request.labels.push(new_label)
end
- expect(page).to have_content('This is a merge request with a milestone')
- expect(page).to have_content('Great feature with milestone')
- expect(page).to have_content(/Opened [\w\s]+ ago/)
+ Page::MergeRequest::Show.perform do |merge_request|
+ expect(merge_request).to have_content('This is a merge request with a milestone')
+ expect(merge_request).to have_content('Great feature with milestone')
+ expect(merge_request).to have_content(/Opened [\w\s]+ ago/)
+ expect(merge_request).to have_label(new_label.title)
+ end
Page::Issuable::Sidebar.perform do |sidebar|
expect(sidebar).to have_milestone(current_milestone.title)
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb
index 922feadb4e1..058af8aebdd 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :create do
+ context 'Create' do
describe 'Merge request creation from fork' do
it 'user forks a project, submits a merge request and maintainer merges it' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
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 984cea8ca10..3bcf086d332 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
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :create do
+ context 'Create' do
describe 'Merge request rebasing' do
it 'user rebases source branch of merge request' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
@@ -10,6 +10,7 @@ module QA
project = Factory::Resource::Project.fabricate! do |project|
project.name = "only-fast-forward"
end
+ project.visit!
Page::Project::Menu.act { go_to_settings }
Page::Project::Settings::MergeRequest.act { enable_ff_only }
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb
index b5b8855a35d..46e1005829d 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :create do
+ context 'Create' do
describe 'Merge request squashing' do
it 'user squashes commits while merging' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
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 c7edcf4c025..7705e12b95e 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
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :create do
+ context 'Create' do
describe 'File templates' do
include Runtime::Fixtures
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb
index b163ca896a7..df70b9608d9 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :create do
+ context 'Create' do
describe 'SSH keys support' do
let(:key_title) { "key for ssh tests #{Time.now.to_f}" }
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb
index 0dcdc6639d1..9c64a9a3439 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :create do
+ context 'Create' do
describe 'Git clone over HTTP', :ldap do
let(:location) do
Page::Project::Show.act do
@@ -14,10 +14,11 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
- Factory::Resource::Project.fabricate! do |scenario|
+ project = Factory::Resource::Project.fabricate! do |scenario|
scenario.name = 'project-with-code'
scenario.description = 'project for git clone tests'
end
+ project.visit!
Git::Repository.perform do |repository|
repository.uri = location.uri
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb
index 82d635065a0..f65a1569fb0 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :create do
+ context 'Create' do
describe 'Files management' do
it 'user creates, edits and deletes a file via the Web' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb
index bf32569b6cb..b9bed39662f 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :create do
+ context 'Create' do
describe 'Git push over HTTP', :ldap do
it 'user pushes code to the repository' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb
index b2da685c477..5f42cb00bd3 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :create do
+ context 'Create' do
describe 'Protected branch support', :ldap do
let(:branch_name) { 'protected-branch' }
let(:commit_message) { 'Protected push commit message' }
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb
index 563393b3d07..36068ffba69 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :create do
+ context 'Create' do
describe 'SSH key support' do
# Note: If you run this test against GDK make sure you've enabled sshd
# See: https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/run_qa_against_gdk.md
diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb
index ab5d97d5b66..07dbf39a8a3 100644
--- a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :create do
+ context 'Create' do
describe 'Web IDE file templates' do
include Runtime::Fixtures
@@ -17,6 +17,7 @@ module QA
project.name = 'file-template-project'
project.description = 'Add file templates via the Web IDE'
end
+ @project.visit!
# Add a file via the regular Files view because the Web IDE isn't
# available unless there is a file present
diff --git a/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb b/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb
index 44dd85c1746..4126fd9fd3e 100644
--- a/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :create do
+ context 'Create' do
describe 'Wiki management' do
def login
Runtime::Browser.visit(:gitlab, Page::Main::Login)
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 e901531b1bf..d66bcce879b 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
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :verify, :orchestrated, :docker do
+ context 'Verify', :orchestrated, :docker do
describe 'Pipeline creation and processing' do
let(:executor) { "qa-runner-#{Time.now.to_i}" }
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 8d83a20f5bf..5d9aa00582f 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
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :verify, :docker do
+ context 'Verify', :docker do
describe 'Runner registration' do
let(:executor) { "qa-runner-#{Time.now.to_i}" }
diff --git a/qa/qa/specs/features/browser_ui/4_verify/secret_variable/add_secret_variable_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/secret_variable/add_secret_variable_spec.rb
index 08a87df5837..292f24d9c0d 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/secret_variable/add_secret_variable_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/secret_variable/add_secret_variable_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :verify do
+ context 'Verify' do
describe 'Secret variable support' do
it 'user adds a secret variable' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb
index 17dfa887434..64b98da8bf5 100644
--- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :release do
+ context 'Release' do
describe 'Deploy key creation' do
it 'user adds a deploy key' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
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 73af24e7f50..caf014c89ea 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
@@ -3,7 +3,7 @@
require 'digest/sha1'
module QA
- context :release, :docker do
+ context 'Release', :docker do
describe 'Git clone using a deploy key' do
def login
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb
index e521597e07f..263ba6a6800 100644
--- a/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :release do
+ context 'Release' do
describe 'Deploy token creation' do
it 'user adds a deploy token' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
index 3735bc00aff..40cae0793dd 100644
--- a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
+++ b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
@@ -3,7 +3,7 @@
require 'pathname'
module QA
- context :configure, :orchestrated, :kubernetes do
+ context 'Configure', :orchestrated, :kubernetes do
describe 'Auto DevOps support' do
after do
@cluster&.remove!
@@ -49,11 +49,13 @@ module QA
cluster.install_prometheus = true
cluster.install_runner = true
end
+ kubernetes_cluster.populate(:ingress_ip)
project.visit!
Page::Project::Menu.act { click_ci_cd_settings }
Page::Project::Settings::CICD.perform do |p|
- p.enable_auto_devops_with_domain("#{kubernetes_cluster.ingress_ip}.nip.io")
+ p.enable_auto_devops_with_domain(
+ "#{kubernetes_cluster.ingress_ip}.nip.io")
end
project.visit!
diff --git a/qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb
index af24b36b734..7096864e011 100644
--- a/qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb
+++ b/qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :configure, :orchestrated, :mattermost do
+ context 'Configure', :orchestrated, :mattermost do
describe 'Mattermost support' do
it 'user creates a group with a mattermost team' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/support/page/logging.rb b/qa/qa/support/page/logging.rb
new file mode 100644
index 00000000000..cf5cd3a79f8
--- /dev/null
+++ b/qa/qa/support/page/logging.rb
@@ -0,0 +1,100 @@
+# frozen_string_literal: true
+
+module QA
+ module Support
+ module Page
+ module Logging
+ def refresh
+ log("refreshing #{current_url}")
+
+ super
+ end
+
+ def wait(max: 60, time: 0.1, reload: true)
+ log("with wait: max #{max}; time #{time}; reload #{reload}")
+ now = Time.now
+
+ element = super
+
+ log("ended wait after #{Time.now - now} seconds")
+
+ element
+ end
+
+ def scroll_to(selector, text: nil)
+ msg = "scrolling to :#{selector}"
+ msg += " with text: #{text}" if text
+ log(msg)
+
+ super
+ end
+
+ def asset_exists?(url)
+ exists = super
+
+ log("asset_exists? #{url} returned #{exists}")
+
+ exists
+ end
+
+ def find_element(name)
+ log("finding :#{name}")
+
+ element = super
+
+ log("found :#{name}") if element
+
+ element
+ end
+
+ def all_elements(name)
+ log("finding all :#{name}")
+
+ elements = super
+
+ log("found #{elements.size} :#{name}") if elements
+
+ elements
+ end
+
+ def click_element(name)
+ log("clicking :#{name}")
+
+ super
+ end
+
+ def fill_element(name, content)
+ masked_content = name.to_s.include?('password') ? '*****' : content
+
+ log(%Q(filling :#{name} with "#{masked_content}"))
+
+ super
+ end
+
+ def has_element?(name)
+ found = super
+
+ log("has_element? :#{name} returned #{found}")
+
+ found
+ end
+
+ def within_element(name)
+ log("within element :#{name}")
+
+ element = super
+
+ log("end within element :#{name}")
+
+ element
+ end
+
+ private
+
+ def log(msg)
+ QA::Runtime::Logger.debug(msg)
+ end
+ end
+ end
+ end
+end
diff --git a/qa/spec/factory/api_fabricator_spec.rb b/qa/spec/factory/api_fabricator_spec.rb
new file mode 100644
index 00000000000..e5fbc064911
--- /dev/null
+++ b/qa/spec/factory/api_fabricator_spec.rb
@@ -0,0 +1,161 @@
+# frozen_string_literal: true
+
+describe QA::Factory::ApiFabricator do
+ let(:factory_without_api_support) do
+ Class.new do
+ def self.name
+ 'FooBarFactory'
+ end
+ end
+ end
+
+ let(:factory_with_api_support) do
+ Class.new do
+ def self.name
+ 'FooBarFactory'
+ end
+
+ def api_get_path
+ '/foo'
+ end
+
+ def api_post_path
+ '/bar'
+ end
+
+ def api_post_body
+ { name: 'John Doe' }
+ end
+ end
+ end
+
+ before do
+ allow(subject).to receive(:current_url).and_return('')
+ end
+
+ subject { factory.tap { |f| f.include(described_class) }.new }
+
+ describe '#api_support?' do
+ let(:api_client) { spy('Runtime::API::Client') }
+ let(:api_client_instance) { double('API Client') }
+
+ context 'when factory does not support fabrication via the API' do
+ let(:factory) { factory_without_api_support }
+
+ it 'returns false' do
+ expect(subject).not_to be_api_support
+ end
+ end
+
+ context 'when factory supports fabrication via the API' do
+ let(:factory) { factory_with_api_support }
+
+ it 'returns false' do
+ expect(subject).to be_api_support
+ end
+ end
+ end
+
+ describe '#fabricate_via_api!' do
+ let(:api_client) { spy('Runtime::API::Client') }
+ let(:api_client_instance) { double('API Client') }
+
+ before do
+ stub_const('QA::Runtime::API::Client', api_client)
+
+ allow(api_client).to receive(:new).and_return(api_client_instance)
+ allow(api_client_instance).to receive(:personal_access_token).and_return('foo')
+ end
+
+ context 'when factory does not support fabrication via the API' do
+ let(:factory) { factory_without_api_support }
+
+ it 'raises a NotImplementedError exception' do
+ expect { subject.fabricate_via_api! }.to raise_error(NotImplementedError, "Factory FooBarFactory does not support fabrication via the API!")
+ end
+ end
+
+ context 'when factory supports fabrication via the API' do
+ let(:factory) { factory_with_api_support }
+ let(:api_request) { spy('Runtime::API::Request') }
+ let(:resource_web_url) { 'http://example.org/api/v4/foo' }
+ let(:resource) { { id: 1, name: 'John Doe', web_url: resource_web_url } }
+ let(:raw_post) { double('Raw POST response', code: 201, body: resource.to_json) }
+
+ before do
+ stub_const('QA::Runtime::API::Request', api_request)
+
+ allow(api_request).to receive(:new).and_return(double(url: resource_web_url))
+ end
+
+ context 'when creating a resource' do
+ before do
+ allow(subject).to receive(:post).with(resource_web_url, subject.api_post_body).and_return(raw_post)
+ end
+
+ it 'returns the resource URL' do
+ expect(api_request).to receive(:new).with(api_client_instance, subject.api_post_path).and_return(double(url: resource_web_url))
+ expect(subject).to receive(:post).with(resource_web_url, subject.api_post_body).and_return(raw_post)
+
+ expect(subject.fabricate_via_api!).to eq(resource_web_url)
+ end
+
+ it 'populates api_resource with the resource' do
+ subject.fabricate_via_api!
+
+ expect(subject.api_resource).to eq(resource)
+ end
+
+ context 'when the POST fails' do
+ let(:post_response) { { error: "Name already taken." } }
+ let(:raw_post) { double('Raw POST response', code: 400, body: post_response.to_json) }
+
+ it 'raises a ResourceFabricationFailedError exception' do
+ expect(api_request).to receive(:new).with(api_client_instance, subject.api_post_path).and_return(double(url: resource_web_url))
+ expect(subject).to receive(:post).with(resource_web_url, subject.api_post_body).and_return(raw_post)
+
+ expect { subject.fabricate_via_api! }.to raise_error(described_class::ResourceFabricationFailedError, "Fabrication of FooBarFactory using the API failed (400) with `#{raw_post}`.")
+ expect(subject.api_resource).to be_nil
+ end
+ end
+ end
+
+ context '#transform_api_resource' do
+ let(:factory) do
+ Class.new do
+ def self.name
+ 'FooBarFactory'
+ end
+
+ def api_get_path
+ '/foo'
+ end
+
+ def api_post_path
+ '/bar'
+ end
+
+ def api_post_body
+ { name: 'John Doe' }
+ end
+
+ def transform_api_resource(resource)
+ resource[:new] = 'foobar'
+ resource
+ end
+ end
+ end
+
+ let(:resource) { { existing: 'foo', web_url: resource_web_url } }
+ let(:transformed_resource) { { existing: 'foo', new: 'foobar', web_url: resource_web_url } }
+
+ it 'transforms the resource' do
+ expect(subject).to receive(:post).with(resource_web_url, subject.api_post_body).and_return(raw_post)
+ expect(subject).to receive(:transform_api_resource).with(resource).and_return(transformed_resource)
+
+ subject.fabricate_via_api!
+ end
+ end
+ end
+ end
+end
diff --git a/qa/spec/factory/base_spec.rb b/qa/spec/factory/base_spec.rb
index 04e04886699..d7b92052894 100644
--- a/qa/spec/factory/base_spec.rb
+++ b/qa/spec/factory/base_spec.rb
@@ -1,132 +1,248 @@
+# frozen_string_literal: true
+
describe QA::Factory::Base do
+ include Support::StubENV
+
let(:factory) { spy('factory') }
let(:product) { spy('product') }
+ let(:product_location) { 'http://product_location' }
- describe '.fabricate!' do
- subject { Class.new(described_class) }
+ shared_context 'fabrication context' do
+ subject do
+ Class.new(described_class) do
+ def self.name
+ 'MyFactory'
+ end
+ end
+ end
before do
- allow(QA::Factory::Product).to receive(:new).and_return(product)
- allow(QA::Factory::Product).to receive(:populate!).and_return(product)
+ allow(subject).to receive(:current_url).and_return(product_location)
+ allow(subject).to receive(:new).and_return(factory)
+ allow(QA::Factory::Product).to receive(:new).with(factory).and_return(product)
end
+ end
- it 'instantiates the factory and calls factory method' do
- expect(subject).to receive(:new).and_return(factory)
+ shared_examples 'fabrication method' do |fabrication_method_called, actual_fabrication_method = nil|
+ let(:fabrication_method_used) { actual_fabrication_method || fabrication_method_called }
+
+ it 'yields factory before calling factory method' do
+ expect(factory).to receive(:something!).ordered
+ expect(factory).to receive(fabrication_method_used).ordered.and_return(product_location)
+
+ subject.public_send(fabrication_method_called, factory: factory) do |factory|
+ factory.something!
+ end
+ end
- subject.fabricate!('something')
+ it 'does not log the factory and build method when QA_DEBUG=false' do
+ stub_env('QA_DEBUG', 'false')
+ expect(factory).to receive(fabrication_method_used).and_return(product_location)
- expect(factory).to have_received(:fabricate!).with('something')
+ expect { subject.public_send(fabrication_method_called, 'something', factory: factory) }
+ .not_to output.to_stdout
end
+ end
- it 'returns fabrication product' do
- allow(subject).to receive(:new).and_return(factory)
+ describe '.fabricate!' do
+ context 'when factory does not support fabrication via the API' do
+ before do
+ expect(described_class).to receive(:fabricate_via_api!).and_raise(NotImplementedError)
+ end
- result = subject.fabricate!('something')
+ it 'calls .fabricate_via_browser_ui!' do
+ expect(described_class).to receive(:fabricate_via_browser_ui!)
- expect(result).to eq product
+ described_class.fabricate!
+ end
end
- it 'yields factory before calling factory method' do
- allow(subject).to receive(:new).and_return(factory)
+ context 'when factory supports fabrication via the API' do
+ it 'calls .fabricate_via_browser_ui!' do
+ expect(described_class).to receive(:fabricate_via_api!)
- subject.fabricate! do |factory|
- factory.something!
+ described_class.fabricate!
end
+ end
+ end
+
+ describe '.fabricate_via_api!' do
+ include_context 'fabrication context'
+
+ it_behaves_like 'fabrication method', :fabricate_via_api!
+
+ it 'instantiates the factory, calls factory method returns fabrication product' do
+ expect(factory).to receive(:fabricate_via_api!).and_return(product_location)
+
+ result = subject.fabricate_via_api!(factory: factory, parents: [])
+
+ expect(result).to eq(product)
+ end
- expect(factory).to have_received(:something!).ordered
- expect(factory).to have_received(:fabricate!).ordered
+ it 'logs the factory and build method when QA_DEBUG=true' do
+ stub_env('QA_DEBUG', 'true')
+ expect(factory).to receive(:fabricate_via_api!).and_return(product_location)
+
+ expect { subject.fabricate_via_api!(factory: factory, parents: []) }
+ .to output(/==> Built a MyFactory via api with args \[\] in [\d\w\.\-]+/)
+ .to_stdout
end
end
- describe '.dependency' do
- let(:dependency) { spy('dependency') }
+ describe '.fabricate_via_browser_ui!' do
+ include_context 'fabrication context'
- before do
- stub_const('Some::MyDependency', dependency)
+ it_behaves_like 'fabrication method', :fabricate_via_browser_ui!, :fabricate!
+
+ it 'instantiates the factory and calls factory method' do
+ subject.fabricate_via_browser_ui!('something', factory: factory, parents: [])
+
+ expect(factory).to have_received(:fabricate!).with('something')
end
+ it 'returns fabrication product' do
+ result = subject.fabricate_via_browser_ui!('something', factory: factory, parents: [])
+
+ expect(result).to eq(product)
+ end
+
+ it 'logs the factory and build method when QA_DEBUG=true' do
+ stub_env('QA_DEBUG', 'true')
+
+ expect { subject.fabricate_via_browser_ui!('something', factory: factory, parents: []) }
+ .to output(/==> Built a MyFactory via browser_ui with args \["something"\] in [\d\w\.\-]+/)
+ .to_stdout
+ end
+ end
+
+ shared_context 'simple factory' do
subject do
- Class.new(described_class) do
- dependency Some::MyDependency, as: :mydep do |factory|
- factory.something!
+ Class.new(QA::Factory::Base) do
+ attribute :test do
+ 'block'
+ end
+
+ attribute :no_block
+
+ def fabricate!
+ 'any'
+ end
+
+ def self.current_url
+ 'http://stub'
end
end
end
- it 'appends a new dependency and accessors' do
- expect(subject.dependencies).to be_one
- end
+ let(:factory) { subject.new }
+ end
+
+ describe '.attribute' do
+ include_context 'simple factory'
- it 'defines dependency accessors' do
- expect(subject.new).to respond_to :mydep, :mydep=
+ it 'appends new product attribute' do
+ expect(subject.attributes_names).to eq([:no_block, :test, :web_url])
end
- describe 'dependencies fabrication' do
- let(:dependency) { double('dependency') }
- let(:instance) { spy('instance') }
+ context 'when the product attribute is populated via a block' do
+ it 'returns a fabrication product and defines factory attributes as its methods' do
+ result = subject.fabricate!(factory: factory)
- subject do
- Class.new(described_class) do
- dependency Some::MyDependency, as: :mydep
- end
+ expect(result).to be_a(QA::Factory::Product)
+ expect(result.test).to eq('block')
end
+ end
+
+ context 'when the product attribute is populated via the api' do
+ let(:api_resource) { { no_block: 'api' } }
before do
- stub_const('Some::MyDependency', dependency)
+ expect(factory).to receive(:api_resource).and_return(api_resource)
+ end
+
+ it 'returns a fabrication product and defines factory attributes as its methods' do
+ result = subject.fabricate!(factory: factory)
- allow(subject).to receive(:new).and_return(instance)
- allow(instance).to receive(:mydep).and_return(nil)
- allow(QA::Factory::Product).to receive(:new)
- allow(QA::Factory::Product).to receive(:populate!)
+ expect(result).to be_a(QA::Factory::Product)
+ expect(result.no_block).to eq('api')
end
- it 'builds all dependencies first' do
- expect(dependency).to receive(:fabricate!).once
+ context 'when the attribute also has a block in the factory' do
+ let(:api_resource) { { test: 'api_with_block' } }
- subject.fabricate!
+ before do
+ allow(QA::Runtime::Logger).to receive(:info)
+ end
+
+ it 'returns the api value and emits an INFO log entry' do
+ result = subject.fabricate!(factory: factory)
+
+ expect(result).to be_a(QA::Factory::Product)
+ expect(result.test).to eq('api_with_block')
+ expect(QA::Runtime::Logger)
+ .to have_received(:info).with(/api_with_block/)
+ end
end
end
- end
- describe '.product' do
- subject do
- Class.new(described_class) do
- def fabricate!
- "any"
- end
+ context 'when the product attribute is populated via a factory attribute' do
+ before do
+ factory.test = 'value'
+ end
+
+ it 'returns a fabrication product and defines factory attributes as its methods' do
+ result = subject.fabricate!(factory: factory)
- # Defined only to be stubbed
- def self.find_page
+ expect(result).to be_a(QA::Factory::Product)
+ expect(result.test).to eq('value')
+ end
+
+ context 'when the api also has such response' do
+ before do
+ allow(factory).to receive(:api_resource).and_return({ test: 'api' })
end
- product :token do
- find_page.do_something_on_page!
- 'resulting value'
+ it 'returns the factory attribute for the product' do
+ result = subject.fabricate!(factory: factory)
+
+ expect(result).to be_a(QA::Factory::Product)
+ expect(result.test).to eq('value')
end
end
end
- it 'appends new product attribute' do
- expect(subject.attributes).to be_one
- expect(subject.attributes).to have_key(:token)
+ context 'when the product attribute has no value' do
+ it 'raises an error because no values could be found' do
+ result = subject.fabricate!(factory: factory)
+
+ expect { result.no_block }
+ .to raise_error(described_class::NoValueError, "No value was computed for product no_block of factory #{factory.class.name}.")
+ end
end
+ end
- describe 'populating fabrication product with data' do
- let(:page) { spy('page') }
+ describe '#web_url' do
+ include_context 'simple factory'
- before do
- allow(factory).to receive(:class).and_return(subject)
- allow(QA::Factory::Product).to receive(:new).and_return(product)
- allow(product).to receive(:page).and_return(page)
- allow(subject).to receive(:find_page).and_return(page)
- end
+ it 'sets #web_url to #current_url after fabrication' do
+ subject.fabricate!(factory: factory)
- it 'populates product after fabrication' do
- subject.fabricate!
+ expect(factory.web_url).to eq(subject.current_url)
+ end
+ end
- expect(product.token).to eq 'resulting value'
- expect(page).to have_received(:do_something_on_page!)
- end
+ describe '#visit!' do
+ include_context 'simple factory'
+
+ before do
+ allow(factory).to receive(:visit)
+ end
+
+ it 'calls #visit with the underlying #web_url' do
+ factory.web_url = subject.current_url
+ factory.visit!
+
+ expect(factory).to have_received(:visit).with(subject.current_url)
end
end
end
diff --git a/qa/spec/factory/dependency_spec.rb b/qa/spec/factory/dependency_spec.rb
deleted file mode 100644
index 8aaa6665a18..00000000000
--- a/qa/spec/factory/dependency_spec.rb
+++ /dev/null
@@ -1,72 +0,0 @@
-describe QA::Factory::Dependency do
- let(:dependency) { spy('dependency' ) }
- let(:factory) { spy('factory') }
- let(:block) { spy('block') }
-
- let(:signature) do
- double('signature', factory: dependency, block: block)
- end
-
- subject do
- described_class.new(:mydep, factory, signature)
- end
-
- describe '#overridden?' do
- it 'returns true if factory has overridden dependency' do
- allow(factory).to receive(:mydep).and_return('something')
-
- expect(subject).to be_overridden
- end
-
- it 'returns false if dependency has not been overridden' do
- allow(factory).to receive(:mydep).and_return(nil)
-
- expect(subject).not_to be_overridden
- end
- end
-
- describe '#build!' do
- context 'when dependency has been overridden' do
- before do
- allow(subject).to receive(:overridden?).and_return(true)
- end
-
- it 'does not fabricate dependency' do
- subject.build!
-
- expect(dependency).not_to have_received(:fabricate!)
- end
- end
-
- context 'when dependency has not been overridden' do
- before do
- allow(subject).to receive(:overridden?).and_return(false)
- end
-
- it 'fabricates dependency' do
- subject.build!
-
- expect(dependency).to have_received(:fabricate!)
- end
-
- it 'sets product in the factory' do
- subject.build!
-
- expect(factory).to have_received(:mydep=).with(dependency)
- end
-
- context 'when receives a caller factory as block argument' do
- let(:dependency) { QA::Factory::Base }
-
- it 'calls given block with dependency factory and caller factory' do
- allow_any_instance_of(QA::Factory::Base).to receive(:fabricate!).and_return(factory)
- allow(QA::Factory::Product).to receive(:populate!).and_return(spy('any'))
-
- subject.build!
-
- expect(block).to have_received(:call).with(an_instance_of(QA::Factory::Base), factory)
- end
- end
- end
- end
-end
diff --git a/qa/spec/factory/product_spec.rb b/qa/spec/factory/product_spec.rb
index f245aabbf43..5b6eaa13e9c 100644
--- a/qa/spec/factory/product_spec.rb
+++ b/qa/spec/factory/product_spec.rb
@@ -1,36 +1,26 @@
describe QA::Factory::Product do
let(:factory) do
- QA::Factory::Base.new
- end
+ Class.new(QA::Factory::Base) do
+ attribute :test do
+ 'block'
+ end
- let(:attributes) do
- { test: QA::Factory::Product::Attribute.new(:test, proc { 'returned' }) }
+ attribute :no_block
+ end.new
end
let(:product) { spy('product') }
+ let(:product_location) { 'http://product_location' }
- before do
- allow(QA::Factory::Base).to receive(:attributes).and_return(attributes)
- end
-
- describe '.populate!' do
- it 'returns a fabrication product and define factory attributes as its methods' do
- expect(described_class).to receive(:new).and_return(product)
+ subject { described_class.new(factory) }
- result = described_class.populate!(factory) do |instance|
- instance.something = 'string'
- end
-
- expect(result).to be product
- expect(result.test).to eq('returned')
- end
+ before do
+ factory.web_url = product_location
end
describe '.visit!' do
it 'makes it possible to visit fabrication product' do
allow_any_instance_of(described_class)
- .to receive(:current_url).and_return('some url')
- allow_any_instance_of(described_class)
.to receive(:visit).and_return('visited some url')
expect(subject.visit!).to eq 'visited some url'
diff --git a/qa/spec/factory/repository/push_spec.rb b/qa/spec/factory/repository/push_spec.rb
new file mode 100644
index 00000000000..2eb6c008248
--- /dev/null
+++ b/qa/spec/factory/repository/push_spec.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+describe QA::Factory::Repository::Push do
+ describe '.files=' do
+ let(:files) do
+ [
+ {
+ name: 'file.txt',
+ content: 'foo'
+ }
+ ]
+ end
+
+ it 'raises an error if files is not an array' do
+ expect { subject.files = '' }.to raise_error(ArgumentError)
+ end
+
+ it 'raises an error if files is an empty array' do
+ expect { subject.files = [] }.to raise_error(ArgumentError)
+ end
+
+ it 'does not raise if files is an array' do
+ expect { subject.files = files }.not_to raise_error
+ end
+ end
+end
diff --git a/qa/spec/git/repository_spec.rb b/qa/spec/git/repository_spec.rb
index 53bff3bf0b3..c629f802aa4 100644
--- a/qa/spec/git/repository_spec.rb
+++ b/qa/spec/git/repository_spec.rb
@@ -1,17 +1,18 @@
describe QA::Git::Repository do
+ include Support::StubENV
+
let(:repository) { described_class.new }
before do
+ stub_env('GITLAB_USERNAME', 'root')
cd_empty_temp_directory
set_bad_uri
repository.use_default_credentials
end
describe '#clone' do
- it 'redacts credentials from the URI in output' do
- output, _ = repository.clone
-
- expect(output).to include("fatal: unable to access 'http://****@foo/bar.git/'")
+ it 'is unable to resolve host' do
+ expect(repository.clone).to include("fatal: unable to access 'http://root@foo/bar.git/'")
end
end
@@ -20,10 +21,8 @@ describe QA::Git::Repository do
`git init` # need a repo to push from
end
- it 'redacts credentials from the URI in output' do
- output, _ = repository.push_changes
-
- expect(output).to include("error: failed to push some refs to 'http://****@foo/bar.git'")
+ it 'fails to push changes' do
+ expect(repository.push_changes).to include("error: failed to push some refs to 'http://root@foo/bar.git'")
end
end
diff --git a/qa/spec/page/logging_spec.rb b/qa/spec/page/logging_spec.rb
new file mode 100644
index 00000000000..9d56353062b
--- /dev/null
+++ b/qa/spec/page/logging_spec.rb
@@ -0,0 +1,94 @@
+# frozen_string_literal: true
+
+require 'capybara/dsl'
+
+describe QA::Support::Page::Logging do
+ include Support::StubENV
+
+ let(:page) { double().as_null_object }
+
+ before do
+ logger = Logger.new $stdout
+ logger.level = ::Logger::DEBUG
+ QA::Runtime::Logger.logger = logger
+
+ allow(Capybara).to receive(:current_session).and_return(page)
+ allow(page).to receive(:current_url).and_return('http://current-url')
+ allow(page).to receive(:has_css?).with(any_args).and_return(true)
+ end
+
+ subject do
+ Class.new(QA::Page::Base) do
+ prepend QA::Support::Page::Logging
+ end.new
+ end
+
+ it 'logs refresh' do
+ expect { subject.refresh }
+ .to output(%r{refreshing http://current-url}).to_stdout_from_any_process
+ end
+
+ it 'logs wait' do
+ expect { subject.wait(max: 0) {} }
+ .to output(/with wait/).to_stdout_from_any_process
+ expect { subject.wait(max: 0) {} }
+ .to output(/ended wait after .* seconds$/).to_stdout_from_any_process
+ end
+
+ it 'logs scroll_to' do
+ expect { subject.scroll_to(:element) }
+ .to output(/scrolling to :element/).to_stdout_from_any_process
+ end
+
+ it 'logs asset_exists?' do
+ expect { subject.asset_exists?('http://asset-url') }
+ .to output(%r{asset_exists\? http://asset-url returned false}).to_stdout_from_any_process
+ end
+
+ it 'logs find_element' do
+ expect { subject.find_element(:element) }
+ .to output(/found :element/).to_stdout_from_any_process
+ end
+
+ it 'logs click_element' do
+ expect { subject.click_element(:element) }
+ .to output(/clicking :element/).to_stdout_from_any_process
+ end
+
+ it 'logs fill_element' do
+ expect { subject.fill_element(:element, 'foo') }
+ .to output(/filling :element with "foo"/).to_stdout_from_any_process
+ end
+
+ it 'logs has_element?' do
+ expect { subject.has_element?(:element) }
+ .to output(/has_element\? :element returned true/).to_stdout_from_any_process
+ end
+
+ it 'logs within_element' do
+ expect { subject.within_element(:element) }
+ .to output(/within element :element/).to_stdout_from_any_process
+ expect { subject.within_element(:element) }
+ .to output(/end within element :element/).to_stdout_from_any_process
+ end
+
+ context 'all_elements' do
+ it 'logs the number of elements found' do
+ allow(page).to receive(:all).and_return([1, 2])
+
+ expect { subject.all_elements(:element) }
+ .to output(/finding all :element/).to_stdout_from_any_process
+ expect { subject.all_elements(:element) }
+ .to output(/found 2 :element/).to_stdout_from_any_process
+ end
+
+ it 'logs 0 if no elements are found' do
+ allow(page).to receive(:all).and_return([])
+
+ expect { subject.all_elements(:element) }
+ .to output(/finding all :element/).to_stdout_from_any_process
+ expect { subject.all_elements(:element) }
+ .not_to output(/found 0 :elements/).to_stdout_from_any_process
+ end
+ end
+end
diff --git a/qa/spec/runtime/api/client_spec.rb b/qa/spec/runtime/api/client_spec.rb
index d497d8839b8..975586b505f 100644
--- a/qa/spec/runtime/api/client_spec.rb
+++ b/qa/spec/runtime/api/client_spec.rb
@@ -13,18 +13,27 @@ describe QA::Runtime::API::Client do
end
end
- describe '#get_personal_access_token' do
- it 'returns specified token from env' do
- stub_env('PERSONAL_ACCESS_TOKEN', 'a_token')
+ describe '#personal_access_token' do
+ context 'when QA::Runtime::Env.personal_access_token is present' do
+ before do
+ allow(QA::Runtime::Env).to receive(:personal_access_token).and_return('a_token')
+ end
- expect(described_class.new.get_personal_access_token).to eq 'a_token'
+ it 'returns specified token from env' do
+ expect(described_class.new.personal_access_token).to eq 'a_token'
+ end
end
- it 'returns a created token' do
- allow_any_instance_of(described_class)
- .to receive(:create_personal_access_token).and_return('created_token')
+ context 'when QA::Runtime::Env.personal_access_token is nil' do
+ before do
+ allow(QA::Runtime::Env).to receive(:personal_access_token).and_return(nil)
+ end
- expect(described_class.new.get_personal_access_token).to eq 'created_token'
+ it 'returns a created token' do
+ expect(subject).to receive(:create_personal_access_token).and_return('created_token')
+
+ expect(subject.personal_access_token).to eq 'created_token'
+ end
end
end
end
diff --git a/qa/spec/runtime/api/request_spec.rb b/qa/spec/runtime/api/request_spec.rb
index 80e3149f32d..08233e3c1d6 100644
--- a/qa/spec/runtime/api/request_spec.rb
+++ b/qa/spec/runtime/api/request_spec.rb
@@ -1,17 +1,23 @@
describe QA::Runtime::API::Request do
- include Support::StubENV
+ let(:client) { QA::Runtime::API::Client.new('http://example.com') }
+ let(:request) { described_class.new(client, '/users') }
before do
- stub_env('PERSONAL_ACCESS_TOKEN', 'a_token')
+ allow(client).to receive(:personal_access_token).and_return('a_token')
end
- let(:client) { QA::Runtime::API::Client.new('http://example.com') }
- let(:request) { described_class.new(client, '/users') }
-
describe '#url' do
- it 'returns the full api request url' do
+ it 'returns the full API request url' do
expect(request.url).to eq 'http://example.com/api/v4/users?private_token=a_token'
end
+
+ context 'when oauth_access_token is passed in the query string' do
+ let(:request) { described_class.new(client, '/users', { oauth_access_token: 'foo' }) }
+
+ it 'does not adds a private_token query string' do
+ expect(request.url).to eq 'http://example.com/api/v4/users?oauth_access_token=foo'
+ end
+ end
end
describe '#request_path' do
diff --git a/qa/spec/runtime/api_request_spec.rb b/qa/spec/runtime/api_request_spec.rb
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/qa/spec/runtime/api_request_spec.rb
+++ /dev/null
diff --git a/qa/spec/runtime/env_spec.rb b/qa/spec/runtime/env_spec.rb
index fda955f6600..c59c415c148 100644
--- a/qa/spec/runtime/env_spec.rb
+++ b/qa/spec/runtime/env_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
describe QA::Runtime::Env do
include Support::StubENV
@@ -38,6 +40,10 @@ describe QA::Runtime::Env do
it_behaves_like 'boolean method', :signup_disabled?, 'SIGNUP_DISABLED', false
end
+ describe '.debug?' do
+ it_behaves_like 'boolean method', :debug?, 'QA_DEBUG', false
+ end
+
describe '.chrome_headless?' do
it_behaves_like 'boolean method', :chrome_headless?, 'CHROME_HEADLESS', true
end
@@ -64,7 +70,54 @@ describe QA::Runtime::Env do
end
end
+ describe '.personal_access_token' do
+ around do |example|
+ described_class.instance_variable_set(:@personal_access_token, nil)
+ example.run
+ described_class.instance_variable_set(:@personal_access_token, nil)
+ end
+
+ context 'when PERSONAL_ACCESS_TOKEN is set' do
+ before do
+ stub_env('PERSONAL_ACCESS_TOKEN', 'a_token')
+ end
+
+ it 'returns specified token from env' do
+ expect(described_class.personal_access_token).to eq 'a_token'
+ end
+ end
+
+ context 'when @personal_access_token is set' do
+ before do
+ described_class.personal_access_token = 'another_token'
+ end
+
+ it 'returns the instance variable value' do
+ expect(described_class.personal_access_token).to eq 'another_token'
+ end
+ end
+ end
+
+ describe '.personal_access_token=' do
+ around do |example|
+ described_class.instance_variable_set(:@personal_access_token, nil)
+ example.run
+ described_class.instance_variable_set(:@personal_access_token, nil)
+ end
+
+ it 'saves the token' do
+ described_class.personal_access_token = 'a_token'
+
+ expect(described_class.personal_access_token).to eq 'a_token'
+ end
+ end
+
describe '.forker?' do
+ before do
+ stub_env('GITLAB_FORKER_USERNAME', nil)
+ stub_env('GITLAB_FORKER_PASSWORD', nil)
+ end
+
it 'returns false if no forker credentials are defined' do
expect(described_class).not_to be_forker
end
@@ -115,4 +168,18 @@ describe QA::Runtime::Env do
expect { described_class.require_github_access_token! }.not_to raise_error
end
end
+
+ describe '.log_destination' do
+ it 'returns $stdout if QA_LOG_PATH is not defined' do
+ stub_env('QA_LOG_PATH', nil)
+
+ expect(described_class.log_destination).to eq($stdout)
+ end
+
+ it 'returns the path if QA_LOG_PATH is defined' do
+ stub_env('QA_LOG_PATH', 'path/to_file')
+
+ expect(described_class.log_destination).to eq('path/to_file')
+ end
+ end
end
diff --git a/qa/spec/runtime/logger_spec.rb b/qa/spec/runtime/logger_spec.rb
new file mode 100644
index 00000000000..44be3381bff
--- /dev/null
+++ b/qa/spec/runtime/logger_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+describe QA::Runtime::Logger do
+ before do
+ logger = Logger.new $stdout
+ logger.level = ::Logger::DEBUG
+ described_class.logger = logger
+ end
+
+ it 'logs debug' do
+ expect { described_class.debug('test') }.to output(/DEBUG -- : test/).to_stdout_from_any_process
+ end
+
+ it 'logs info' do
+ expect { described_class.info('test') }.to output(/INFO -- : test/).to_stdout_from_any_process
+ end
+
+ it 'logs warn' do
+ expect { described_class.warn('test') }.to output(/WARN -- : test/).to_stdout_from_any_process
+ end
+
+ it 'logs error' do
+ expect { described_class.error('test') }.to output(/ERROR -- : test/).to_stdout_from_any_process
+ end
+
+ it 'logs fatal' do
+ expect { described_class.fatal('test') }.to output(/FATAL -- : test/).to_stdout_from_any_process
+ end
+
+ it 'logs unknown' do
+ expect { described_class.unknown('test') }.to output(/ANY -- : test/).to_stdout_from_any_process
+ end
+end
diff --git a/qa/spec/spec_helper.rb b/qa/spec/spec_helper.rb
index 8e6613cd688..8e01da01340 100644
--- a/qa/spec/spec_helper.rb
+++ b/qa/spec/spec_helper.rb
@@ -3,6 +3,10 @@ require_relative '../qa'
Dir[::File.join(__dir__, 'support', '**', '*.rb')].each { |f| require f }
RSpec.configure do |config|
+ config.before do |example|
+ QA::Runtime::Logger.debug("Starting test: #{example.full_description}") if QA::Runtime::Env.debug?
+ end
+
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end