641.md 11.5 KB
Newer Older
Lab机器人's avatar
readme  
Lab机器人 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
# Page objects in GitLab QA

> 原文:[https://docs.gitlab.com/ee/development/testing_guide/end_to_end/page_objects.html](https://docs.gitlab.com/ee/development/testing_guide/end_to_end/page_objects.html)

*   [Why do we need that?](#why-do-we-need-that)
*   [What problems did we have in the past?](#what-problems-did-we-have-in-the-past)
*   [How did we solve fragile tests problem?](#how-did-we-solve-fragile-tests-problem)
*   [How to properly implement a page object?](#how-to-properly-implement-a-page-object)
    *   [Defining Elements](#defining-elements)
    *   [Adding Elements to a View](#adding-elements-to-a-view)
    *   [`data-qa-selector` vs `.qa-selector`](#data-qa-selector-vs-qa-selector)
    *   [Dynamic element selection](#dynamic-element-selection)
        *   [Examples](#examples)
    *   [Exceptions](#exceptions)
    *   [Define Page concerns](#define-page-concerns)
*   [Running the test locally](#running-the-test-locally)
*   [Where to ask for help?](#where-to-ask-for-help)

# Page objects in GitLab QA[](#page-objects-in-gitlab-qa "Permalink")

在 GitLab 质量检查中,我们使用一种称为*Page Objects*的已知模式.

这意味着我们已经为用于驱动 GitLab 质量检查方案的 GitLab 中的所有页面建立了抽象. 每当我们在页面上执行某项操作(例如填写表单或单击按钮)时,我们仅通过与该 GitLab 区域相关联的页面对象来执行此操作.

例如,当 GitLab QA 测试工具登录到 GitLab 时,它需要填写用户登录名和用户密码. 为此,我们有一个名为`Page::Main::Login``sign_in_using_credentials`方法的类,这是代码中仅有的一部分,它具有有关`user_login``user_password`字段的知识.

## Why do we need that?[](#why-do-we-need-that "Permalink")

我们需要页面对象,因为只要有人在 GitLab 的源代码中更改某些选择器,我们就需要减少重复并避免出现问题.

想象一下,我们在 GitLab 质量检查中有 100 个规格,并且在每次声明之前我们都需要登录 GitLab. 如果没有页面对象,则需要依靠易失性助手或直接调用 Capybara 方法. 想象一下在每个`*_spec.rb`文件/测试示例中调用`fill_in :user_login` .

以后有人在与此页面关联的视图`t.text_field :login`更改为`t.text_field :username` ,它将生成一个不同的字段标识符,这将有效地破坏所有测试.

因为我们到处都在使用`Page::Main::Login.perform(&:sign_in_using_credentials)` ,所以当我们要登录到 GitLab 时,页面对象是唯一的事实来源,我们需要将`fill_in :user_login`更新为`fill_in :user_username`只能放在一个位置.

## What problems did we have in the past?[](#what-problems-did-we-have-in-the-past "Permalink")

由于性能原因以及构建软件包和测试所有内容所花费的时间,我们不会针对每次提交都运行 QA 测试.

这就是为什么当有人在*新的会话*视图中将`t.text_field :login`更改为`t.text_field :username` ,直到我们的 GitLab QA 夜间管道失败或有人触发了他们的`package-and-qa`操作,我们才知道这一更改.合并请求.

显然,这样的更改将破坏所有测试. 我们称这个问题为*脆弱的测试问题* .

为了使 GitLab QA 更加可靠和健壮,我们必须通过在 GitLab CE / EE 视图与 GitLab QA 之间引入耦合来解决此问题.

## How did we solve fragile tests problem?[](#how-did-we-solve-fragile-tests-problem "Permalink")

当前,当您添加新的`Page::Base`派生类时,还需要定义页面对象所依赖的所有选择器.

Whenever you push your code to CE / EE repository, `qa:selectors` sanity test job is going to be run as a part of a CI pipeline.

此测试将验证我们在`qa/page`目录中实现的所有页面对象. 失败时,将通知您有关丢失的或无效的视图/选择器定义的信息.

## How to properly implement a page object?[](#how-to-properly-implement-a-page-object "Permalink")

我们建立了一个 DSL 来定义页面对象和它实际实现的 GitLab 视图之间的耦合. 请参阅下面的示例.

```
module Page
  module Main
    class Login < Page::Base
      view 'app/views/devise/passwords/edit.html.haml' do
        element :password_field
        element :password_confirmation
        element :change_password_button
      end

      view 'app/views/devise/sessions/_new_base.html.haml' do
        element :login_field
        element :password_field
        element :sign_in_button
      end

      # ...
    end
  end
end 
```

### Defining Elements[](#defining-elements "Permalink")

`view` DSL 方法将对应于渲染元素的 rails View,partial 或 Vue 组件.

`element` DSL 方法依次声明一个元素,需要将其相应的`data-qa-selector=element_name_snaked`数据属性添加到视图文件中.

您还可以定义一个值(字符串或正则表达式)以匹配实际的视图代码,但是出于两个原因, **不**建议使用上述方法,建议使用该值:

*   一致性:只有一种定义元素的方法
*   关注点分离:QA 使用专用的`data-qa-*`属性,而不是重用其他组件使用的代码或类(例如`js-*`类等)

```
view 'app/views/my/view.html.haml' do

  ### Good ###

  # Implicitly require the CSS selector `[data-qa-selector="logout_button"]` to be present in the view
  element :logout_button

  ### Bad ###

  ## This is deprecated and forbidden by the `QA/ElementWithPattern` RuboCop cop.
  # Require `f.submit "Sign in"` to be present in `my/view.html.haml
  element :my_button, 'f.submit "Sign in"' # rubocop:disable QA/ElementWithPattern

  ## This is deprecated and forbidden by the `QA/ElementWithPattern` RuboCop cop.
  # Match every line in `my/view.html.haml` against
  # `/link_to .* "My Profile"/` regexp.
  element :profile_link, /link_to .* "My Profile"/ # rubocop:disable QA/ElementWithPattern
end 
```

### Adding Elements to a View[](#adding-elements-to-a-view "Permalink")

鉴于以下要素...

```
view 'app/views/my/view.html.haml' do
  element :login_field
  element :password_field
  element :sign_in_button
end 
```

要将这些元素添加到视图,必须通过为定义的每个元素添加`data-qa-selector`属性来更改 rails View,Partial 或 Vue 组件.

在我们的示例中, `data-qa-selector="login_field"``data-qa-selector="password_field"``data-qa-selector="sign_in_button"`

**app/views/my/view.html.haml**

```
= f.text_field :login, class: "form-control top", autofocus: "autofocus", autocapitalize: "off", autocorrect: "off", required: true, title: "This field is required.", data: { qa_selector: 'login_field' }
= f.password_field :password, class: "form-control bottom", required: true, title: "This field is required.", data: { qa_selector: 'password_field' }
= f.submit "Sign in", class: "btn btn-success", data: { qa_selector: 'sign_in_button' } 
```

注意事项:

*   元素的名称和`qa_selector`必须匹配并使用 snake_cased
*   如果该元素无条件显示在页面上,请向该元素添加`required: true` . 请参阅[动态元素验证](dynamic_element_validation.html)
*   您可能会在现有的页面对象中看到`.qa-selector`类. 我们应该使用[`data-qa-selector`](#data-qa-selector-vs-qa-selector)定义方法,而不是`.qa-selector` CSS 类.

### `data-qa-selector` vs `.qa-selector`[](#data-qa-selector-vs-qa-selector "Permalink")

在 GitLab 12.1 中引入

在视图中定义元素有两种支持的方法.

1.  `data-qa-selector` attribute
2.  `.qa-selector` class

任何现有的`.qa-selector`类都应视为已弃用,我们应该更喜欢`data-qa-selector`定义方法.

### Dynamic element selection[](#dynamic-element-selection "Permalink")

在 GitLab 12.5 中引入

自动化测试中常见的一种情况是选择一个"多对多"元素. 在几个项目的列表中,如何区分选择的内容? 最常见的解决方法是通过文本匹配. 相反,更好的做法是通过唯一标识符而不是文本来匹配该特定元素.

我们通过添加`data-qa-*`可扩展选择机制来解决此问题.

#### Examples[](#examples "Permalink")

**例子 1**

给出以下 Rails 视图(以 GitLab Issues 为例):

```
%ul.issues-list
 - @issues.each do |issue|
   %li.issue{data: { qa_selector: 'issue', qa_issue_title: issue.title } }= link_to issue 
```

我们可以通过在 Rails 模型上进行匹配来选择特定的问题.

```
class Page::Project::Issues::Index < Page::Base
  def has_issue?(issue)
    has_element? :issue, issue_title: issue
  end
end 
```

在我们的测试中,我们可以验证此特定问题的存在.

```
describe 'Issue' do
  it 'has an issue titled "hello"' do
    Page::Project::Issues::Index.perform do |index|
      expect(index).to have_issue('hello')
    end
  end
end 
```

**例子 2**

*通过索引…*

```
%ol
  - @some_model.each_with_index do |model, idx|
    %li.model{ data: { qa_selector: 'model', qa_index: idx } } 
```

```
expect(the_page).to have_element(:model, index: 1) #=> select on the first model that appears in the list 
```

### Exceptions[](#exceptions "Permalink")

在某些情况下,可能无法或不值得添加选择器.

一些 UI 组件使用外部库,包括一些第三方维护的库. 即使 GitLab 维护了一个库,选择器的健全性测试也只能在 GitLab 项目中的代码上运行,因此无法为库中的代码指定视图的路径.

在这种罕见的情况下,在页面对象方法中使用 CSS 选择器是合理的,并带有注释说明为什么不能添加`element`原因.

### Define Page concerns[](#define-page-concerns "Permalink")

某些页面具有共同的行为,并且/或者在特定于 EE 的模块之前添加了特定于 EE 的方法.

These modules must:

1.`QA::Page::PageConcern`模块`extend QA::Page::PageConcern` ,并`extend QA::Page::PageConcern` .
2.  重写`self.prepended`方法,如果他们需要`include` / `prepend`其他模块本身,和/或定义`view``elements` .
3.`super`称为`self.prepended`的第一件事.
4.  包含/添加其他模块,并在`base.class_eval`块中定义其`view` / `elements` ,以确保在添加模块的类中定义了它们.

这些步骤确保健全性选择器检查将正确检测到问题.

例如, `qa/qa/ee/page/merge_request/show.rb`将 EE 特定方法添加到`qa/qa/page/merge_request/show.rb` (带有`QA::Page::MergeRequest::Show.prepend_if_ee('QA::EE::Page::MergeRequest::Show')` ),其实现方式如下(仅显示相关部分,并使用内嵌注释引用上述 4 个步骤):

```
module QA
  module EE
    module Page
      module MergeRequest
        module Show
          extend QA::Page::PageConcern # 1.

          def self.prepended(base) # 2.
            super # 3.

            base.class_eval do # 4.
              prepend Page::Component::LicenseManagement

              view 'app/assets/javascripts/vue_merge_request_widget/components/states/sha_mismatch.vue' do
                element :head_mismatch, "The source branch HEAD has recently changed."
              end

              [...]
            end
          end
        end
      end
    end
  end
end 
```

## Running the test locally[](#running-the-test-locally "Permalink")

在开发过程中,您可以运行以下命令来运行`qa:selectors`测试

```
bin/qa Test::Sanity::Selectors 
```

`qa`目录中.

## Where to ask for help?[](#where-to-ask-for-help "Permalink")

如果您需要更多信息,请在 Slack 上的`#quality`频道(仅限内部,GitLab 团队)上寻求帮助.

如果你不是一个团队成员,你仍然需要帮助的贡献,请打开 GitLab CE 问题追踪的一个问题`~QA`标签.