summaryrefslogtreecommitdiff
path: root/qa/qa/factory
diff options
context:
space:
mode:
Diffstat (limited to 'qa/qa/factory')
-rw-r--r--qa/qa/factory/README.md474
-rw-r--r--qa/qa/factory/api_fabricator.rb97
-rw-r--r--qa/qa/factory/base.rb81
-rw-r--r--qa/qa/factory/dependency.rb31
-rw-r--r--qa/qa/factory/product.rb40
-rw-r--r--qa/qa/factory/repository/project_push.rb9
-rw-r--r--qa/qa/factory/repository/push.rb12
-rw-r--r--qa/qa/factory/resource/fork.rb2
-rw-r--r--qa/qa/factory/resource/group.rb27
-rw-r--r--qa/qa/factory/resource/issue.rb7
-rw-r--r--qa/qa/factory/resource/label.rb39
-rw-r--r--qa/qa/factory/resource/merge_request.rb14
-rw-r--r--qa/qa/factory/resource/project.rb33
-rw-r--r--qa/qa/factory/resource/project_imported_from_github.rb4
-rw-r--r--qa/qa/factory/resource/project_milestone.rb2
-rw-r--r--qa/qa/factory/resource/sandbox.rb37
-rw-r--r--qa/qa/factory/resource/ssh_key.rb14
-rw-r--r--qa/qa/factory/resource/user.rb8
-rw-r--r--qa/qa/factory/resource/wiki.rb17
-rw-r--r--qa/qa/factory/settings/hashed_storage.rb2
20 files changed, 852 insertions, 98 deletions
diff --git a/qa/qa/factory/README.md b/qa/qa/factory/README.md
new file mode 100644
index 00000000000..10140e39510
--- /dev/null
+++ b/qa/qa/factory/README.md
@@ -0,0 +1,474 @@
+# 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, :size
+
+ def initialize(name)
+ @name = name
+ end
+
+ 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, :size
+
+ def initialize(name)
+ @name = name
+ end
+
+ 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
+
+ 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 dependencies
+
+A resource may need an other resource to exist first. For instance, a project
+needs a group to be created in.
+
+To define a dependency, you can use the `dependency` DSL method.
+The first argument is a factory class, then you should pass `as: <name>` to give
+a name to the dependency.
+That will allow access to the dependency 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` dependency to it:
+
+```ruby
+module QA
+ module Factory
+ module Resource
+ class Shirt < Factory::Base
+ attr_accessor :name, :size
+
+ dependency Factory::Resource::Project, as: :project do |project|
+ project.name = 'project-to-create-a-shirt'
+ end
+
+ def initialize(name)
+ @name = name
+ 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 dependencies are always built via the API fabrication method if
+supported by their factories.**
+
+### Define attributes on the created resource
+
+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.
+
+To define an attribute, you can use the `product` DSL method.
+The first argument is the attribute name, then you should define a name for the
+dependency to be accessible from your resource object's methods.
+
+Let's take the `Shirt` factory, and define a `:brand` attribute:
+
+```ruby
+module QA
+ module Factory
+ module Resource
+ class Shirt < Factory::Base
+ attr_accessor :name, :size
+
+ dependency Factory::Resource::Project, as: :project do |project|
+ project.name = 'project-to-create-a-shirt'
+ end
+
+ # Attribute populated from the Browser UI (using the block)
+ product :brand do
+ Page::Shirt::Show.perform do |shirt_show|
+ shirt_show.fetch_brand_from_page
+ end
+ end
+
+ def initialize(name)
+ @name = name
+ 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
+```
+
+#### Inherit a factory's attribute
+
+Sometimes, you want a resource to inherit its factory attributes. For instance,
+it could be useful to pass the `size` attribute from the `Shirt` factory to the
+created resource.
+You can do that by defining `product :attribute_name` without a block.
+
+Let's take the `Shirt` factory, and define a `:name` and a `:size` attributes:
+
+```ruby
+module QA
+ module Factory
+ module Resource
+ class Shirt < Factory::Base
+ attr_accessor :name, :size
+
+ dependency Factory::Resource::Project, as: :project do |project|
+ project.name = 'project-to-create-a-shirt'
+ end
+
+ # Attribute from the Browser UI (using the block)
+ product :brand do
+ Page::Shirt::Show.perform do |shirt_show|
+ shirt_show.fetch_brand_from_page
+ end
+ end
+
+ # Attribute inherited from the Shirt factory if present,
+ # or a QA::Factory::Product::NoValueError is raised otherwise
+ product :name
+ product :size
+
+ def initialize(name)
+ @name = name
+ 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
+```
+
+#### 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',
+ size: 'extra-small',
+ 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.
+
+For both attributes, you will need to define an inherited attribute, as shown
+in "Inherit a factory's attribute" above, but in the case of `main_fabric`, you
+will need to implement the
+`#transform_api_resource` method to first populate the `:main_fabric` key in the
+API response so that it can be used later to automatically populate the
+attribute on your resource.
+
+If an attribute can only be retrieved from the API response, you should define
+a block to give it a default value, otherwise you could get a
+`QA::Factory::Product::NoValueError` when creating your resource via the
+Browser UI.
+
+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
+ attr_accessor :name, :size
+
+ dependency Factory::Resource::Project, as: :project do |project|
+ project.name = 'project-to-create-a-shirt'
+ end
+
+ # Attribute fetched from the API response if present,
+ # or from the Browser UI otherwise (using the block)
+ product :brand do
+ Page::Shirt::Show.perform do |shirt_show|
+ shirt_show.fetch_brand_from_page
+ end
+ end
+
+ # Attribute fetched from the API response if present,
+ # or from the Shirt factory if present,
+ # or a QA::Factory::Product::NoValueError is raised otherwise
+ product :name
+ product :size
+ product :style do
+ 'unknown'
+ end
+ product :main_fabric do
+ 'unknown'
+ end
+
+ def initialize(name)
+ @name = name
+ 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
+
+ private
+
+ def transform_api_resource(api_response)
+ api_response[:main_fabric] = api_response[:materials][0][0]
+ api_response
+ end
+ end
+ end
+ end
+end
+```
+
+**Notes on attributes precedence:**
+
+- attributes from the API response take precedence over attributes from the
+ Browser UI
+- attributes from the Browser UI take precedence over attributes from the
+ factory (i.e inherited)
+- attributes without a value will raise a `QA::Factory::Product::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!('my-shirt') do |shirt|
+ shirt.size = 'small'
+end
+
+expect(page).to have_text(my_shirt.brand) # => "a-brand-new-brand" from the API response
+expect(page).to have_text(my_shirt.name) # => "my-shirt" from the inherited factory's attribute
+expect(page).to have_text(my_shirt.size) # => "extra-small" 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 (transformed) API response
+```
+
+If you explicitely 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!('my-shirt') do |shirt|
+ shirt.size = 'small'
+end
+
+expect(page).to have_text(my_shirt.brand) # => the brand name fetched from the `Page::Shirt::Show` page
+expect(page).to have_text(my_shirt.name) # => "my-shirt" from the inherited factory's attribute
+expect(page).to have_text(my_shirt.size) # => "small" from the inherited factory's attribute
+expect(page).to have_text(my_shirt.style) # => "unknown" from the attribute block
+expect(page).to have_text(my_shirt.main_fabric) # => "unknown" from the attribute block
+```
+
+You can also explicitely use the API fabrication method, by calling the
+`.fabricate_via_api!` method:
+
+```ruby
+my_shirt = Factory::Resource::Shirt.fabricate_via_api!('my-shirt') do |shirt|
+ shirt.size = 'small'
+end
+```
+
+In this case, the result will be similar to calling `Factory::Resource::Shirt.fabricate!('my-shirt')`.
+
+## 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..e1dc23d350d 100644
--- a/qa/qa/factory/base.rb
+++ b/qa/qa/factory/base.rb
@@ -1,9 +1,14 @@
+# frozen_string_literal: true
+
require 'forwardable'
+require 'capybara/dsl'
module QA
module Factory
class Base
extend SingleForwardable
+ include ApiFabricator
+ extend Capybara::DSL
def_delegators :evaluator, :dependency, :dependencies
def_delegators :evaluator, :product, :attributes
@@ -12,46 +17,96 @@ module QA
raise NotImplementedError
end
- def self.fabricate!(*args)
- new.tap do |factory|
- yield factory if block_given?
+ def self.fabricate!(*args, &prepare_block)
+ fabricate_via_api!(*args, &prepare_block)
+ rescue NotImplementedError
+ fabricate_via_browser_ui!(*args, &prepare_block)
+ end
- dependencies.each do |name, signature|
- Factory::Dependency.new(name, factory, signature).build!
- 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
+
+ def self.fabricate_via_api!(*args, &prepare_block)
+ options = args.extract_options!
+ factory = options.fetch(:factory) { new }
+ parents = options.fetch(:parents) { [] }
+
+ 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
+
+ dependencies.each do |signature|
+ Factory::Dependency.new(factory, signature).build!(parents: parents + [self])
+ end
+
+ resource_web_url = yield
+
+ Factory::Product.populate!(factory, resource_web_url)
+ end
+ private_class_method :do_fabricate!
+
+ def self.log_fabrication(method, factory, parents, args)
+ return yield unless Runtime::Env.debug?
- factory.fabricate!(*args)
+ 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}"
- break Factory::Product.populate!(factory)
+ 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 initialize(base)
@base = base
- @dependencies = {}
- @attributes = {}
+ @dependencies = []
+ @attributes = []
end
def dependency(factory, as:, &block)
as.tap do |name|
@base.class_eval { attr_accessor name }
- Dependency::Signature.new(factory, block).tap do |signature|
- @dependencies.store(name, signature)
+ Dependency::Signature.new(name, factory, block).tap do |signature|
+ @dependencies << signature
end
end
end
def product(attribute, &block)
Product::Attribute.new(attribute, block).tap do |signature|
- @attributes.store(attribute, signature)
+ @attributes << signature
end
end
end
diff --git a/qa/qa/factory/dependency.rb b/qa/qa/factory/dependency.rb
index fc5dc82ce29..655e2677db0 100644
--- a/qa/qa/factory/dependency.rb
+++ b/qa/qa/factory/dependency.rb
@@ -1,37 +1,26 @@
module QA
module Factory
class Dependency
- Signature = Struct.new(:factory, :block)
+ Signature = Struct.new(:name, :factory, :block)
- def initialize(name, factory, signature)
- @name = name
- @factory = factory
- @signature = signature
+ def initialize(caller_factory, dependency_signature)
+ @caller_factory = caller_factory
+ @dependency_signature = dependency_signature
end
def overridden?
- !!@factory.public_send(@name)
+ !!@caller_factory.public_send(@dependency_signature.name)
end
- def build!
+ def build!(parents: [])
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
+ dependency = @dependency_signature.factory.fabricate!(parents: parents) do |factory|
+ @dependency_signature.block&.call(factory, @caller_factory)
end
- def fabricate!
- @factory.fabricate! do |factory|
- @block&.call(factory, @caller_factory)
- end
+ dependency.tap do |dependency|
+ @caller_factory.public_send("#{@dependency_signature.name}=", dependency)
end
end
end
diff --git a/qa/qa/factory/product.rb b/qa/qa/factory/product.rb
index 996b7f14f61..17fe908eaa2 100644
--- a/qa/qa/factory/product.rb
+++ b/qa/qa/factory/product.rb
@@ -5,26 +5,46 @@ module QA
class Product
include Capybara::DSL
+ NoValueError = Class.new(RuntimeError)
+
+ attr_reader :factory, :web_url
+
Attribute = Struct.new(:name, :block)
- def initialize
- @location = current_url
+ def initialize(factory, web_url)
+ @factory = factory
+ @web_url = web_url
+
+ populate_attributes!
end
def visit!
- visit @location
+ visit(web_url)
+ end
+
+ def self.populate!(factory, web_url)
+ new(factory, web_url)
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 populate_attributes!
+ factory.class.attributes.each do |attribute|
+ instance_exec(factory, attribute.block) do |factory, block|
+ value = attribute_value(attribute, block)
+
+ raise NoValueError, "No value was computed for product #{attribute.name} of factory #{factory.class.name}." unless value
+
+ define_singleton_method(attribute.name) { value }
end
end
end
+
+ def attribute_value(attribute, block)
+ factory.api_resource&.dig(attribute.name) ||
+ (block && block.call(factory)) ||
+ (factory.respond_to?(attribute.name) && factory.public_send(attribute.name))
+ end
end
end
end
diff --git a/qa/qa/factory/repository/project_push.rb b/qa/qa/factory/repository/project_push.rb
index 167f47c9141..6f878396f0e 100644
--- a/qa/qa/factory/repository/project_push.rb
+++ b/qa/qa/factory/repository/project_push.rb
@@ -7,13 +7,8 @@ module QA
project.description = 'Project with repository'
end
- product :output do |factory|
- factory.output
- end
-
- product :project do |factory|
- factory.project
- end
+ product :output
+ product :project
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/resource/fork.rb b/qa/qa/factory/resource/fork.rb
index 83dd4000f0a..6e2a668df64 100644
--- a/qa/qa/factory/resource/fork.rb
+++ b/qa/qa/factory/resource/fork.rb
@@ -11,7 +11,7 @@ module QA
end
end
- product(:user) { |factory| factory.user }
+ product :user
def visit_project_with_retry
# The user intermittently fails to stay signed in after visiting the
diff --git a/qa/qa/factory/resource/group.rb b/qa/qa/factory/resource/group.rb
index 033fc48c08f..2688328df92 100644
--- a/qa/qa/factory/resource/group.rb
+++ b/qa/qa/factory/resource/group.rb
@@ -6,6 +6,10 @@ module QA
dependency Factory::Resource::Sandbox, as: :sandbox
+ product :id do
+ true # We don't retrieve the Group ID when using the Browser UI
+ end
+
def initialize
@path = Runtime::Namespace.name
@description = "QA test run at #{Runtime::Namespace.time}"
@@ -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..9b444cb0bf1 100644
--- a/qa/qa/factory/resource/issue.rb
+++ b/qa/qa/factory/resource/issue.rb
@@ -2,16 +2,15 @@ module QA
module Factory
module Resource
class Issue < Factory::Base
- attr_writer :title, :description, :project
+ attr_accessor :title, :description, :project
dependency Factory::Resource::Project, as: :project do |project|
project.name = 'project-for-issues'
project.description = 'project for adding issues'
end
- product :title do
- Page::Project::Issue::Show.act { issue_title }
- end
+ product :project
+ product :title
def fabricate!
project.visit!
diff --git a/qa/qa/factory/resource/label.rb b/qa/qa/factory/resource/label.rb
new file mode 100644
index 00000000000..4080f15bf66
--- /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 :title,
+ :description,
+ :color
+
+ product(:title) { |factory| factory.title }
+
+ dependency Factory::Resource::Project, as: :project do |project|
+ project.name = 'project-with-label'
+ 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.act { go_to_labels }
+ Page::Label::Index.act { 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..d30da8a3db0 100644
--- a/qa/qa/factory/resource/merge_request.rb
+++ b/qa/qa/factory/resource/merge_request.rb
@@ -12,13 +12,8 @@ module QA
:milestone,
:labels
- product :project do |factory|
- factory.project
- end
-
- product :source_branch do |factory|
- factory.source_branch
- end
+ product :project
+ product :source_branch
dependency Factory::Resource::Project, as: :project do |project|
project.name = 'project-with-merge-request'
@@ -35,6 +30,7 @@ module QA
push.project = factory.project
push.branch_name = factory.target_branch
push.remote_branch = factory.source_branch
+ push.new_branch = false
push.file_name = "added_file.txt"
push.file_content = "File Added"
end
@@ -56,6 +52,10 @@ module QA
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/project.rb b/qa/qa/factory/resource/project.rb
index 90db26ab3ab..105e42b23ec 100644
--- a/qa/qa/factory/resource/project.rb
+++ b/qa/qa/factory/resource/project.rb
@@ -4,14 +4,13 @@ module QA
module Factory
module Resource
class Project < Factory::Base
- attr_writer :description
+ attr_accessor :description
attr_reader :name
dependency Factory::Resource::Group, as: :group
- product :name do |factory|
- factory.name
- end
+ product :group
+ product :name
product :repository_ssh_location do
Page::Project::Show.act do
@@ -48,6 +47,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..a45e7fee03b 100644
--- a/qa/qa/factory/resource/project_imported_from_github.rb
+++ b/qa/qa/factory/resource/project_imported_from_github.rb
@@ -8,9 +8,7 @@ module QA
dependency Factory::Resource::Group, as: :group
- product :name do |factory|
- factory.name
- end
+ product :name
def fabricate!
group.visit!
diff --git a/qa/qa/factory/resource/project_milestone.rb b/qa/qa/factory/resource/project_milestone.rb
index 1251ae03135..35383842142 100644
--- a/qa/qa/factory/resource/project_milestone.rb
+++ b/qa/qa/factory/resource/project_milestone.rb
@@ -7,7 +7,7 @@ module QA
dependency Factory::Resource::Project, as: :project
- product(:title) { |factory| factory.title }
+ product :title
def title=(title)
@title = "#{title}-#{SecureRandom.hex(4)}"
diff --git a/qa/qa/factory/resource/sandbox.rb b/qa/qa/factory/resource/sandbox.rb
index 5249e1755a6..e592f4e0dd2 100644
--- a/qa/qa/factory/resource/sandbox.rb
+++ b/qa/qa/factory/resource/sandbox.rb
@@ -6,21 +6,28 @@ module QA
# creating it if it doesn't yet exist.
#
class Sandbox < Factory::Base
+ attr_reader :path
+
+ product :id do
+ true # We don't retrieve the Group ID when using the Browser UI
+ end
+ product :path
+
def initialize
- @name = Runtime::Namespace.sandbox_name
+ @path = Runtime::Namespace.sandbox_name
end
def fabricate!
Page::Main::Menu.act { 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 +35,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/ssh_key.rb b/qa/qa/factory/resource/ssh_key.rb
index 45236f69de9..a512d071dd4 100644
--- a/qa/qa/factory/resource/ssh_key.rb
+++ b/qa/qa/factory/resource/ssh_key.rb
@@ -10,17 +10,9 @@ module QA
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
+ product :private_key
+ product :title
+ product :fingerprint
def key
@key ||= Runtime::Key::RSA.new
diff --git a/qa/qa/factory/resource/user.rb b/qa/qa/factory/resource/user.rb
index e8b9ea2e6b4..36edf787b64 100644
--- a/qa/qa/factory/resource/user.rb
+++ b/qa/qa/factory/resource/user.rb
@@ -31,10 +31,10 @@ 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 }
+ product :name
+ product :username
+ product :email
+ product :password
def fabricate!
# Don't try to log-out if we're not logged-in
diff --git a/qa/qa/factory/resource/wiki.rb b/qa/qa/factory/resource/wiki.rb
index acfe143fa61..d697433736e 100644
--- a/qa/qa/factory/resource/wiki.rb
+++ b/qa/qa/factory/resource/wiki.rb
@@ -10,13 +10,16 @@ module QA
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..5e8f883e25f 100644
--- a/qa/qa/factory/settings/hashed_storage.rb
+++ b/qa/qa/factory/settings/hashed_storage.rb
@@ -9,7 +9,7 @@ module QA
Page::Main::Menu.act { go_to_admin_area }
Page::Admin::Menu.act { 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