summaryrefslogtreecommitdiff
path: root/qa/qa/resource/README.md
diff options
context:
space:
mode:
Diffstat (limited to 'qa/qa/resource/README.md')
-rw-r--r--qa/qa/resource/README.md392
1 files changed, 0 insertions, 392 deletions
diff --git a/qa/qa/resource/README.md b/qa/qa/resource/README.md
deleted file mode 100644
index 4cdeb3f42a2..00000000000
--- a/qa/qa/resource/README.md
+++ /dev/null
@@ -1,392 +0,0 @@
-# Resource class in GitLab QA
-
-Resources are primarily created using Browser UI steps, but can also
-be created via the API.
-
-## How to properly implement a resource class?
-
-All resource classes should inherit from [`Resource::Base`](./base.rb).
-
-There is only one mandatory method to implement to define a resource class.
-This is the `#fabricate!` method, which is used to build the 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 Resource
- class Shirt < 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
-```
-
-### Define API implementation
-
-A resource class may also implement the three following methods to be able to
-create the 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` resource class, and add these three API methods:
-
-```ruby
-module QA
- module Resource
- class Shirt < 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
-```
-
-The [`Project` resource](./project.rb) is a good real example of Browser
-UI and API implementations.
-
-#### 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 resource class 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` resource class, and add a `project` attribute to it:
-
-```ruby
-module QA
- module Resource
- class Shirt < Base
- attr_accessor :name
-
- attribute :project do
- 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
-```
-
-**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` resource class, and define a `:brand` attribute:
-
-```ruby
-module QA
- module Resource
- class Shirt < Base
- attr_accessor :name
-
- attribute :project do
- 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
-```
-
-**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::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::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 Resource
- class Shirt < 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
-
- populate(:brand) # Eagerly construct the data
- end
- end
- end
-end
-```
-
-The `populate` method will iterate through its arguments and call each
-attribute respectively. Here `populate(:brand)` has the same effect as
-just `brand`. Using the populate method makes the intention clearer.
-
-With this, it will make sure we construct the data right after we create the
-shirt. The drawback is that this will always construct the data when the
-resource is fabricated even if we don't need to use the data.
-
-Alternatively, we could just make sure we're on the right page before
-constructing the brand data:
-
-```ruby
-module QA
- module Resource
- class Shirt < Base
- attr_accessor :name
-
- attribute :project do
- 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
-```
-
-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` resource class, and define a `:style` and a
-`:main_fabric` attributes:
-
-```ruby
-module QA
- module Resource
- class Shirt < Base
- # ... same as before
-
- # @style from the instance if present,
- # or fetched from the API response if present,
- # or a QA::Resource::Base::NoValueError is raised otherwise
- attribute :style
-
- # If @main_fabric 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, and
- # store the result in @main_fabric
- attribute :main_fabric do
- api_response.&dig(:materials, 0, 0)
- end
-
- # ... same as before
- end
- end
-end
-```
-
-**Notes on attributes precedence:**
-
-- resource instance variables 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::Resource::Base::NoValueError` error
-
-## Creating resources in your tests
-
-To create a resource in your tests, you can call the `.fabricate!` method on
-the resource class.
-Note that if the resource class 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` resource class:
-
-```ruby
-my_shirt = Resource::Shirt.fabricate! do |shirt|
- shirt.name = 'my-shirt'
-end
-
-expect(page).to have_text(my_shirt.name) # => "my-shirt" from the resource's instance variable
-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 = 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 resource's instance variable
-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::Resource::Base::NoValueError will be raised because no API response nor a block is provided
-expect(page).to have_text(my_shirt.main_fabric) # => QA::Resource::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 = Resource::Shirt.fabricate_via_api! do |shirt|
- shirt.name = 'my-shirt'
-end
-```
-
-In this case, the result will be similar to calling
-`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.