Commit 7d5b68d8 authored by ddavison's avatar ddavison

Implement dynamic validation on QA Pages

Elements now have the ability to be required on pages or not
Currently using the default wait mechanism
Altered the ElementWithPattern Cop to fit new splat for init
parent 4063b7e8
...@@ -130,6 +130,7 @@ module QA ...@@ -130,6 +130,7 @@ module QA
autoload :View, 'qa/page/view' autoload :View, 'qa/page/view'
autoload :Element, 'qa/page/element' autoload :Element, 'qa/page/element'
autoload :Validator, 'qa/page/validator' autoload :Validator, 'qa/page/validator'
autoload :Validatable, 'qa/page/validatable'
module Main module Main
autoload :Login, 'qa/page/main/login' autoload :Login, 'qa/page/main/login'
......
...@@ -13,7 +13,6 @@ module QA ...@@ -13,7 +13,6 @@ module QA
# The login page could take some time to load the first time it is visited. # The login page could take some time to load the first time it is visited.
# We visit the login page and wait for it to properly load only once before the tests. # We visit the login page and wait for it to properly load only once before the tests.
QA::Runtime::Browser.visit(:gitlab, QA::Page::Main::Login) QA::Runtime::Browser.visit(:gitlab, QA::Page::Main::Login)
QA::Page::Main::Login.perform(&:assert_page_loaded)
end end
end end
end end
......
...@@ -8,6 +8,7 @@ module QA ...@@ -8,6 +8,7 @@ module QA
prepend Support::Page::Logging if Runtime::Env.debug? prepend Support::Page::Logging if Runtime::Env.debug?
include Capybara::DSL include Capybara::DSL
include Scenario::Actable include Scenario::Actable
extend Validatable
extend SingleForwardable extend SingleForwardable
ElementNotFound = Class.new(RuntimeError) ElementNotFound = Class.new(RuntimeError)
...@@ -93,8 +94,10 @@ module QA ...@@ -93,8 +94,10 @@ module QA
find_element(name).set(false) find_element(name).set(false)
end end
def click_element(name) # replace with (..., page = self.class)
def click_element(name, page = nil)
find_element(name).click find_element(name).click
page.validate_elements_present! if page
end end
def fill_element(name, content) def fill_element(name, content)
......
# frozen_string_literal: true # frozen_string_literal: true
require 'active_support/core_ext/array/extract_options'
module QA module QA
module Page module Page
class Element class Element
attr_reader :name attr_reader :name, :attributes
def initialize(name, pattern = nil) def initialize(name, *options)
@name = name @name = name
@pattern = pattern || selector @attributes = options.extract_options!
@attributes[:pattern] ||= selector
options.each do |option|
if option.is_a?(String) || option.is_a?(Regexp)
@attributes[:pattern] = option
end
end
end end
def selector def selector
"qa-#{@name.to_s.tr('_', '-')}" "qa-#{@name.to_s.tr('_', '-')}"
end end
def required?
!!@attributes[:required]
end
def selector_css def selector_css
".#{selector}" ".#{selector}"
end end
def expression def expression
if @pattern.is_a?(String) if @attributes[:pattern].is_a?(String)
@_regexp ||= Regexp.new(Regexp.escape(@pattern)) @_regexp ||= Regexp.new(Regexp.escape(@attributes[:pattern]))
else else
@pattern @attributes[:pattern]
end end
end end
......
...@@ -39,19 +39,7 @@ module QA ...@@ -39,19 +39,7 @@ module QA
end end
view 'app/views/layouts/devise.html.haml' do view 'app/views/layouts/devise.html.haml' do
element :login_page element :login_page, required: true
end
def assert_page_loaded
unless page_loaded?
raise QA::Runtime::Browser::NotRespondingError, "Login page did not load at #{QA::Page::Main::Login.perform(&:current_url)}"
end
end
def page_loaded?
wait(max: 60) do
has_element?(:login_page)
end
end end
def sign_in_using_credentials(user = nil) def sign_in_using_credentials(user = nil)
...@@ -159,7 +147,7 @@ module QA ...@@ -159,7 +147,7 @@ module QA
fill_element :login_field, user.username fill_element :login_field, user.username
fill_element :password_field, user.password fill_element :password_field, user.password
click_element :sign_in_button click_element :sign_in_button, Page::Main::Menu
end end
def set_initial_password_if_present def set_initial_password_if_present
......
...@@ -10,15 +10,15 @@ module QA ...@@ -10,15 +10,15 @@ module QA
end end
view 'app/views/layouts/header/_default.html.haml' do view 'app/views/layouts/header/_default.html.haml' do
element :navbar element :navbar, required: true
element :user_avatar element :user_avatar, required: true
element :user_menu, '.dropdown-menu' # rubocop:disable QA/ElementWithPattern element :user_menu, '.dropdown-menu' # rubocop:disable QA/ElementWithPattern
end end
view 'app/views/layouts/nav/_dashboard.html.haml' do view 'app/views/layouts/nav/_dashboard.html.haml' do
element :admin_area_link element :admin_area_link
element :projects_dropdown element :projects_dropdown, required: true
element :groups_dropdown element :groups_dropdown, required: true
element :snippets_link element :snippets_link
end end
......
# frozen_string_literal: true
module QA
module Page
module Validatable
PageValidationError = Class.new(StandardError)
def validate_elements_present!
base_page = self.new
elements.each do |element|
next unless element.required?
# TODO: this wait needs to be replaced by the wait class
unless base_page.has_element?(element.name, wait: 10)
raise Validatable::PageValidationError, "#{element.name} did not appear on #{self.name} as expected"
end
end
end
end
end
end
...@@ -50,8 +50,8 @@ module QA ...@@ -50,8 +50,8 @@ module QA
@elements = [] @elements = []
end end
def element(name, pattern = nil) def element(name, *args)
@elements.push(Page::Element.new(name, pattern)) @elements.push(Page::Element.new(name, *args))
end end
end end
end end
......
...@@ -33,6 +33,7 @@ module QA ...@@ -33,6 +33,7 @@ module QA
def self.visit(address, page = nil, &block) def self.visit(address, page = nil, &block)
new.visit(address, page, &block) new.visit(address, page, &block)
page.validate_elements_present!
end end
def self.configure! def self.configure!
......
...@@ -56,8 +56,11 @@ module QA ...@@ -56,8 +56,11 @@ module QA
elements elements
end end
def click_element(name) def click_element(name, page = nil)
log("clicking :#{name}") msg = ["clicking :#{name}"]
msg << ", expecting to be at #{page.class}" if page
log(msg.compact.join(' '))
super super
end end
......
...@@ -50,4 +50,60 @@ describe QA::Page::Element do ...@@ -50,4 +50,60 @@ describe QA::Page::Element do
expect(subject.matches?('some_name selector')).to be false expect(subject.matches?('some_name selector')).to be false
end end
end end
describe 'attributes' do
context 'element with no args' do
subject { described_class.new(:something) }
it 'defaults pattern to #selector' do
expect(subject.attributes[:pattern]).to eq 'qa-something'
expect(subject.attributes[:pattern]).to eq subject.selector
end
it 'is not required by default' do
expect(subject.required?).to be false
end
end
context 'element with a pattern' do
subject { described_class.new(:something, /link_to 'something'/) }
it 'has an attribute[pattern] of the pattern' do
expect(subject.attributes[:pattern]).to eq /link_to 'something'/
end
it 'is not required by default' do
expect(subject.required?).to be false
end
end
context 'element with requirement; no pattern' do
subject { described_class.new(:something, required: true) }
it 'has an attribute[pattern] of the selector' do
expect(subject.attributes[:pattern]).to eq 'qa-something'
expect(subject.attributes[:pattern]).to eq subject.selector
end
it 'is required' do
expect(subject.required?).to be true
end
end
context 'element with requirement and pattern' do
subject { described_class.new(:something, /link_to 'something_else_entirely'/, required: true) }
it 'has an attribute[pattern] of the passed pattern' do
expect(subject.attributes[:pattern]).to eq /link_to 'something_else_entirely'/
end
it 'is required' do
expect(subject.required?).to be true
end
it 'has a selector of the name' do
expect(subject.selector).to eq 'qa-something'
end
end
end
end end
# frozen_string_literal: true
require_relative '../../qa_helpers' require_relative '../../qa_helpers'
module RuboCop module RuboCop
module Cop module Cop
module QA module QA
# This cop checks for the usage of factories in migration specs # This cop checks for the usage of patterns in QA elements
# #
# @example # @example
# #
# # bad # # bad
# let(:user) { create(:user) } # element :some_element, "link_to 'something'"
# element :some_element, /link_to 'something'/
# #
# # good # # good
# let(:users) { table(:users) } # element :some_element
# let(:user) { users.create!(name: 'User 1', username: 'user1') } # element :some_element, required: true
class ElementWithPattern < RuboCop::Cop::Cop class ElementWithPattern < RuboCop::Cop::Cop
include QAHelpers include QAHelpers
...@@ -22,10 +25,13 @@ module RuboCop ...@@ -22,10 +25,13 @@ module RuboCop
return unless in_qa_file?(node) return unless in_qa_file?(node)
return unless method_name(node).to_s == 'element' return unless method_name(node).to_s == 'element'
element_name, pattern = node.arguments element_name, *args = node.arguments
return unless pattern
return if args.first.nil?
add_offense(node, location: pattern.source_range, message: MESSAGE % "qa-#{element_name.value.to_s.tr('_', '-')}") args.first.each_node(:str) do |arg|
add_offense(arg, message: MESSAGE % "qa-#{element_name.value.to_s.tr('_', '-')}")
end
end end
private private
......
# frozen_string_literal: true
require 'spec_helper' require 'spec_helper'
require 'rubocop' require 'rubocop'
...@@ -23,7 +25,7 @@ describe RuboCop::Cop::QA::ElementWithPattern do ...@@ -23,7 +25,7 @@ describe RuboCop::Cop::QA::ElementWithPattern do
element :groups_filter, 'search_field_tag :filter' element :groups_filter, 'search_field_tag :filter'
^^^^^^^^^^^^^^^^^^^^^^^^^^ Don't use a pattern for element, create a corresponding `qa-groups-filter` instead. ^^^^^^^^^^^^^^^^^^^^^^^^^^ Don't use a pattern for element, create a corresponding `qa-groups-filter` instead.
element :groups_filter_placeholder, /Search by name/ element :groups_filter_placeholder, /Search by name/
^^^^^^^^^^^^^^^^ Don't use a pattern for element, create a corresponding `qa-groups-filter-placeholder` instead. ^^^^^^^^^^^^^^ Don't use a pattern for element, create a corresponding `qa-groups-filter-placeholder` instead.
end end
RUBY RUBY
end end
...@@ -35,6 +37,13 @@ describe RuboCop::Cop::QA::ElementWithPattern do ...@@ -35,6 +37,13 @@ describe RuboCop::Cop::QA::ElementWithPattern do
element :groups_filter_placeholder element :groups_filter_placeholder
end end
RUBY RUBY
expect_no_offenses(<<-RUBY)
view 'app/views/shared/groups/_search_form.html.haml' do
element :groups_filter, required: true
element :groups_filter_placeholder, required: false
end
RUBY
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment