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
autoload :View, 'qa/page/view'
autoload :Element, 'qa/page/element'
autoload :Validator, 'qa/page/validator'
autoload :Validatable, 'qa/page/validatable'
module Main
autoload :Login, 'qa/page/main/login'
......
......@@ -13,7 +13,6 @@ module QA
# 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.
QA::Runtime::Browser.visit(:gitlab, QA::Page::Main::Login)
QA::Page::Main::Login.perform(&:assert_page_loaded)
end
end
end
......
......@@ -8,6 +8,7 @@ module QA
prepend Support::Page::Logging if Runtime::Env.debug?
include Capybara::DSL
include Scenario::Actable
extend Validatable
extend SingleForwardable
ElementNotFound = Class.new(RuntimeError)
......@@ -93,8 +94,10 @@ module QA
find_element(name).set(false)
end
def click_element(name)
# replace with (..., page = self.class)
def click_element(name, page = nil)
find_element(name).click
page.validate_elements_present! if page
end
def fill_element(name, content)
......
# frozen_string_literal: true
require 'active_support/core_ext/array/extract_options'
module QA
module Page
class Element
attr_reader :name
attr_reader :name, :attributes
def initialize(name, pattern = nil)
def initialize(name, *options)
@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
def selector
"qa-#{@name.to_s.tr('_', '-')}"
end
def required?
!!@attributes[:required]
end
def selector_css
".#{selector}"
end
def expression
if @pattern.is_a?(String)
@_regexp ||= Regexp.new(Regexp.escape(@pattern))
if @attributes[:pattern].is_a?(String)
@_regexp ||= Regexp.new(Regexp.escape(@attributes[:pattern]))
else
@pattern
@attributes[:pattern]
end
end
......
......@@ -39,19 +39,7 @@ module QA
end
view 'app/views/layouts/devise.html.haml' do
element :login_page
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
element :login_page, required: true
end
def sign_in_using_credentials(user = nil)
......@@ -159,7 +147,7 @@ module QA
fill_element :login_field, user.username
fill_element :password_field, user.password
click_element :sign_in_button
click_element :sign_in_button, Page::Main::Menu
end
def set_initial_password_if_present
......
......@@ -10,15 +10,15 @@ module QA
end
view 'app/views/layouts/header/_default.html.haml' do
element :navbar
element :user_avatar
element :navbar, required: true
element :user_avatar, required: true
element :user_menu, '.dropdown-menu' # rubocop:disable QA/ElementWithPattern
end
view 'app/views/layouts/nav/_dashboard.html.haml' do
element :admin_area_link
element :projects_dropdown
element :groups_dropdown
element :projects_dropdown, required: true
element :groups_dropdown, required: true
element :snippets_link
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
@elements = []
end
def element(name, pattern = nil)
@elements.push(Page::Element.new(name, pattern))
def element(name, *args)
@elements.push(Page::Element.new(name, *args))
end
end
end
......
......@@ -33,6 +33,7 @@ module QA
def self.visit(address, page = nil, &block)
new.visit(address, page, &block)
page.validate_elements_present!
end
def self.configure!
......
......@@ -56,8 +56,11 @@ module QA
elements
end
def click_element(name)
log("clicking :#{name}")
def click_element(name, page = nil)
msg = ["clicking :#{name}"]
msg << ", expecting to be at #{page.class}" if page
log(msg.compact.join(' '))
super
end
......
......@@ -50,4 +50,60 @@ describe QA::Page::Element do
expect(subject.matches?('some_name selector')).to be false
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
# frozen_string_literal: true
require_relative '../../qa_helpers'
module RuboCop
module Cop
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
#
# # bad
# let(:user) { create(:user) }
# element :some_element, "link_to 'something'"
# element :some_element, /link_to 'something'/
#
# # good
# let(:users) { table(:users) }
# let(:user) { users.create!(name: 'User 1', username: 'user1') }
# element :some_element
# element :some_element, required: true
class ElementWithPattern < RuboCop::Cop::Cop
include QAHelpers
......@@ -22,10 +25,13 @@ module RuboCop
return unless in_qa_file?(node)
return unless method_name(node).to_s == 'element'
element_name, pattern = node.arguments
return unless pattern
element_name, *args = node.arguments
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
private
......
# frozen_string_literal: true
require 'spec_helper'
require 'rubocop'
......@@ -23,7 +25,7 @@ describe RuboCop::Cop::QA::ElementWithPattern do
element :groups_filter, 'search_field_tag :filter'
^^^^^^^^^^^^^^^^^^^^^^^^^^ Don't use a pattern for element, create a corresponding `qa-groups-filter` instead.
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
RUBY
end
......@@ -35,6 +37,13 @@ describe RuboCop::Cop::QA::ElementWithPattern do
element :groups_filter_placeholder
end
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
......
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