page_magic 1.2.6 → 2.0.0.alpha1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.rubocop.yml +19 -3
- data/.simplecov +5 -3
- data/.zsh_config +6 -0
- data/Dockerfile +10 -0
- data/Gemfile +12 -12
- data/Gemfile.lock +133 -126
- data/Makefile +13 -0
- data/README.md +78 -14
- data/Rakefile +12 -2
- data/VERSION +1 -1
- data/circle.yml +4 -2
- data/lib/active_support/core_ext/object/to_query.rb +84 -0
- data/lib/page_magic.rb +7 -4
- data/lib/page_magic/class_methods.rb +4 -1
- data/lib/page_magic/driver.rb +3 -0
- data/lib/page_magic/drivers.rb +6 -5
- data/lib/page_magic/drivers/poltergeist.rb +2 -0
- data/lib/page_magic/drivers/rack_test.rb +3 -1
- data/lib/page_magic/drivers/selenium.rb +4 -2
- data/lib/page_magic/element.rb +19 -10
- data/lib/page_magic/element/locators.rb +4 -1
- data/lib/page_magic/element/query.rb +33 -33
- data/lib/page_magic/element/query_builder.rb +61 -0
- data/lib/page_magic/element/selector.rb +3 -0
- data/lib/page_magic/element/selector_methods.rb +3 -0
- data/lib/page_magic/element_context.rb +9 -12
- data/lib/page_magic/element_definition_builder.rb +10 -4
- data/lib/page_magic/elements.rb +51 -8
- data/lib/page_magic/exceptions.rb +4 -33
- data/lib/page_magic/instance_methods.rb +6 -1
- data/lib/page_magic/matcher.rb +12 -3
- data/lib/page_magic/session.rb +7 -5
- data/lib/page_magic/session_methods.rb +2 -0
- data/lib/page_magic/utils/string.rb +14 -0
- data/lib/page_magic/wait_methods.rb +3 -0
- data/lib/page_magic/watcher.rb +2 -0
- data/lib/page_magic/watchers.rb +4 -1
- data/page_magic.gemspec +19 -13
- data/spec/element_spec.rb +2 -0
- data/spec/lib/active_support/core_ext/object/to_query_test.rb +78 -0
- data/spec/page_magic/class_methods_spec.rb +2 -0
- data/spec/page_magic/driver_spec.rb +2 -0
- data/spec/page_magic/drivers/poltergeist_spec.rb +2 -0
- data/spec/page_magic/drivers/rack_test_spec.rb +2 -0
- data/spec/page_magic/drivers/selenium_spec.rb +2 -0
- data/spec/page_magic/drivers_spec.rb +2 -0
- data/spec/page_magic/element/locators_spec.rb +2 -0
- data/spec/page_magic/element/query_builder_spec.rb +110 -0
- data/spec/page_magic/element/query_spec.rb +35 -77
- data/spec/page_magic/element/selector_spec.rb +14 -7
- data/spec/page_magic/element_context_spec.rb +5 -10
- data/spec/page_magic/element_definition_builder_spec.rb +3 -1
- data/spec/page_magic/elements_spec.rb +19 -5
- data/spec/page_magic/instance_methods_spec.rb +2 -0
- data/spec/page_magic/matcher_spec.rb +3 -1
- data/spec/page_magic/session_methods_spec.rb +2 -0
- data/spec/page_magic/session_spec.rb +3 -4
- data/spec/page_magic/utils/string_spec.rb +36 -0
- data/spec/page_magic/wait_methods_spec.rb +5 -3
- data/spec/page_magic/watchers_spec.rb +2 -0
- data/spec/page_magic_spec.rb +5 -7
- data/spec/spec_helper.rb +3 -0
- data/spec/support/shared_contexts.rb +3 -1
- data/spec/support/shared_contexts/files_context.rb +2 -0
- data/spec/support/shared_contexts/nested_elements_html_context.rb +2 -0
- data/spec/support/shared_contexts/rack_application_context.rb +2 -0
- data/spec/support/shared_contexts/webapp_fixture_context.rb +3 -1
- data/spec/support/shared_examples.rb +2 -0
- data/spec/watcher_spec.rb +3 -0
- metadata +62 -40
data/Rakefile
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
Bundler.require :test, :development
|
2
4
|
|
3
5
|
RuboCop::RakeTask.new
|
@@ -12,11 +14,19 @@ Jeweler::Tasks.new do |gem|
|
|
12
14
|
gem.license = 'ruby'
|
13
15
|
gem.summary = 'Framework for modeling and interacting with webpages'
|
14
16
|
gem.description = 'Framework for modeling and interacting with webpages which wraps capybara'
|
15
|
-
gem.email = 'info@
|
17
|
+
gem.email = 'info@lvl-up.uk'
|
16
18
|
gem.authors = ['Leon Davis']
|
17
19
|
gem.required_ruby_version = '>= 2.1'
|
18
20
|
end
|
19
21
|
|
20
22
|
Jeweler::RubygemsDotOrgTasks.new
|
21
23
|
|
22
|
-
|
24
|
+
require 'rake/testtask'
|
25
|
+
Rake::TestTask.new do |t|
|
26
|
+
t.libs << 'spec'
|
27
|
+
t.pattern = 'spec/**/*_test.rb'
|
28
|
+
t.warning = true
|
29
|
+
t.verbose = true
|
30
|
+
end
|
31
|
+
|
32
|
+
task default: [:spec, :test, 'rubocop:auto_correct']
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
2.0.0.alpha1
|
data/circle.yml
CHANGED
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
3
|
+
class Object
|
4
|
+
# Alias of <tt>to_s</tt>.
|
5
|
+
def to_param
|
6
|
+
to_s
|
7
|
+
end
|
8
|
+
|
9
|
+
# Converts an object into a string suitable for use as a URL query string,
|
10
|
+
# using the given <tt>key</tt> as the param name.
|
11
|
+
def to_query(key)
|
12
|
+
"#{CGI.escape(key.to_param)}=#{CGI.escape(to_param.to_s)}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class NilClass
|
17
|
+
# Returns +self+.
|
18
|
+
def to_param
|
19
|
+
self
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class TrueClass
|
24
|
+
# Returns +self+.
|
25
|
+
def to_param
|
26
|
+
self
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class FalseClass
|
31
|
+
# Returns +self+.
|
32
|
+
def to_param
|
33
|
+
self
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class Array
|
38
|
+
# Calls <tt>to_param</tt> on all its elements and joins the result with
|
39
|
+
# slashes. This is used by <tt>url_for</tt> in Action Pack.
|
40
|
+
def to_param
|
41
|
+
collect(&:to_param).join '/'
|
42
|
+
end
|
43
|
+
|
44
|
+
# Converts an array into a string suitable for use as a URL query string,
|
45
|
+
# using the given +key+ as the param name.
|
46
|
+
#
|
47
|
+
# ['Rails', 'coding'].to_query('hobbies') # => "hobbies%5B%5D=Rails&hobbies%5B%5D=coding"
|
48
|
+
def to_query(key)
|
49
|
+
prefix = "#{key}[]"
|
50
|
+
|
51
|
+
if empty?
|
52
|
+
nil.to_query(prefix)
|
53
|
+
else
|
54
|
+
collect { |value| value.to_query(prefix) }.join '&'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class Hash
|
60
|
+
# Returns a string representation of the receiver suitable for use as a URL
|
61
|
+
# query string:
|
62
|
+
#
|
63
|
+
# {name: 'David', nationality: 'Danish'}.to_query
|
64
|
+
# # => "name=David&nationality=Danish"
|
65
|
+
#
|
66
|
+
# An optional namespace can be passed to enclose key names:
|
67
|
+
#
|
68
|
+
# {name: 'David', nationality: 'Danish'}.to_query('user')
|
69
|
+
# # => "user%5Bname%5D=David&user%5Bnationality%5D=Danish"
|
70
|
+
#
|
71
|
+
# The string pairs "key=value" that conform the query string
|
72
|
+
# are sorted lexicographically in ascending order.
|
73
|
+
#
|
74
|
+
# This method is also aliased as +to_param+.
|
75
|
+
def to_query(namespace = nil)
|
76
|
+
collect do |key, value|
|
77
|
+
unless (value.is_a?(Hash) || value.is_a?(Array)) && value.empty?
|
78
|
+
value.to_query(namespace ? "#{namespace}[#{key}]" : key)
|
79
|
+
end
|
80
|
+
end.compact.sort! * '&'
|
81
|
+
end
|
82
|
+
|
83
|
+
alias to_param to_query
|
84
|
+
end
|
data/lib/page_magic.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
$LOAD_PATH.unshift(File.dirname(__FILE__).to_s)
|
2
4
|
require 'capybara'
|
3
5
|
require 'page_magic/exceptions'
|
@@ -31,7 +33,8 @@ module PageMagic
|
|
31
33
|
def included(clazz)
|
32
34
|
clazz.class_eval do
|
33
35
|
include(InstanceMethods)
|
34
|
-
extend
|
36
|
+
extend ClassMethods
|
37
|
+
extend Elements
|
35
38
|
end
|
36
39
|
end
|
37
40
|
|
@@ -48,10 +51,10 @@ module PageMagic
|
|
48
51
|
# @param [Symbol] browser name of browser
|
49
52
|
# @param [String] url url to start the session on
|
50
53
|
# @param [Hash] options browser driver specific options
|
51
|
-
# @return [Session] configured
|
52
|
-
def session(application: nil, browser: :rack_test,
|
54
|
+
# @return [Session] configured session
|
55
|
+
def session(url: nil, application: nil, browser: :rack_test, options: {})
|
53
56
|
driver = drivers.find(browser)
|
54
|
-
raise
|
57
|
+
raise UnsupportedBrowserException unless driver
|
55
58
|
|
56
59
|
Capybara.register_driver browser do |app|
|
57
60
|
driver.build(app, browser: browser, options: options)
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module PageMagic
|
2
4
|
# module ClassMethods - contains class level methods for PageObjects
|
3
5
|
module ClassMethods
|
@@ -15,6 +17,7 @@ module PageMagic
|
|
15
17
|
# if one has not been set on the page object class it will return a default block that does nothing
|
16
18
|
def on_load(&block)
|
17
19
|
return @on_load || DEFAULT_ON_LOAD unless block
|
20
|
+
|
18
21
|
@on_load = block
|
19
22
|
end
|
20
23
|
|
@@ -34,7 +37,7 @@ module PageMagic
|
|
34
37
|
session_options = { browser: browser, options: options, url: url }
|
35
38
|
session_options[:application] = application if application
|
36
39
|
|
37
|
-
PageMagic.session(session_options).tap do |session|
|
40
|
+
PageMagic.session(**session_options).tap do |session|
|
38
41
|
session.visit(self, url: url)
|
39
42
|
end
|
40
43
|
end
|
data/lib/page_magic/driver.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module PageMagic
|
2
4
|
# class Driver - instances are factories for drivers used by PageMagic
|
3
5
|
class Driver
|
4
6
|
attr_reader :supported_browsers, :handler
|
7
|
+
|
5
8
|
# Creates a driver definition
|
6
9
|
# @example
|
7
10
|
# Driver.new do |rack_application, options|
|
data/lib/page_magic/drivers.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'page_magic/driver'
|
4
|
+
require 'page_magic/utils/string'
|
2
5
|
module PageMagic
|
3
6
|
# class Drivers - creates an object that can be used to hold driver definitions
|
4
7
|
# These PageMagic gets the user's chosen driver from this object.
|
@@ -16,12 +19,10 @@ module PageMagic
|
|
16
19
|
# Loads drivers defined in files at the given path
|
17
20
|
# @param [String] path where the drivers are located
|
18
21
|
def load(path = "#{__dir__}/drivers")
|
19
|
-
|
20
|
-
|
21
|
-
Dir["#{path}/*.rb"].each do |driver_file|
|
22
|
+
Dir["#{path}/*.rb"].sort.each do |driver_file|
|
22
23
|
require driver_file
|
23
24
|
driver_name = File.basename(driver_file)[/(.*)\.rb$/, 1]
|
24
|
-
register self.class.const_get(
|
25
|
+
register self.class.const_get(Utils::String.classify(driver_name))
|
25
26
|
end
|
26
27
|
end
|
27
28
|
|
@@ -35,7 +36,7 @@ module PageMagic
|
|
35
36
|
# @param [Object] other subject of equality check
|
36
37
|
# @return [Boolean] true if the object is a match
|
37
38
|
def ==(other)
|
38
|
-
other.
|
39
|
+
other.respond_to?(:all) && all == other.all
|
39
40
|
end
|
40
41
|
end
|
41
42
|
end
|
@@ -1,4 +1,6 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
PageMagic::Drivers::Selenium = PageMagic::Driver.new(:chrome, :firefox) do |app, options, browser|
|
2
|
-
require 'watir
|
3
|
-
Capybara::Selenium::Driver.new(app, options.dup.merge(browser: browser))
|
4
|
+
require 'watir'
|
5
|
+
Capybara::Selenium::Driver.new(app, **options.dup.merge(browser: browser))
|
4
6
|
end
|
data/lib/page_magic/element.rb
CHANGED
@@ -1,17 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'forwardable'
|
2
4
|
require 'page_magic/element/selector_methods'
|
3
5
|
require 'page_magic/element/locators'
|
4
6
|
require 'page_magic/element/selector'
|
5
|
-
require 'page_magic/element/
|
7
|
+
require 'page_magic/element/query_builder'
|
6
8
|
module PageMagic
|
7
9
|
# class Element - represents an element in a html page.
|
8
10
|
class Element
|
9
|
-
EVENT_TYPES = [
|
11
|
+
EVENT_TYPES = %i[set select select_option unselect_option click].freeze
|
10
12
|
DEFAULT_HOOK = proc {}.freeze
|
11
|
-
EVENT_NOT_SUPPORTED_MSG = '%s event not supported'
|
13
|
+
EVENT_NOT_SUPPORTED_MSG = '%s event not supported'
|
12
14
|
|
13
|
-
include
|
14
|
-
|
15
|
+
include Locators
|
16
|
+
include WaitMethods
|
17
|
+
include SessionMethods
|
18
|
+
include Watchers
|
19
|
+
include SelectorMethods
|
20
|
+
extend Forwardable
|
21
|
+
extend SelectorMethods
|
22
|
+
extend Elements
|
15
23
|
|
16
24
|
attr_reader :type, :name, :parent_element, :browser_element, :before_events, :after_events
|
17
25
|
|
@@ -24,7 +32,7 @@ module PageMagic
|
|
24
32
|
# @!method before_events
|
25
33
|
# If a block is passed in, it adds it to be run before an event is triggered on an element.
|
26
34
|
# @see .after_events
|
27
|
-
%i
|
35
|
+
%i[after_events before_events].each do |method|
|
28
36
|
define_method method do |&block|
|
29
37
|
instance_variable_name = "@#{method}".to_sym
|
30
38
|
instance_variable_value = instance_variable_get(instance_variable_name) || [DEFAULT_HOOK]
|
@@ -38,6 +46,7 @@ module PageMagic
|
|
38
46
|
# @return [Element]
|
39
47
|
def parent_element(page_element = nil)
|
40
48
|
return @parent_page_element unless page_element
|
49
|
+
|
41
50
|
@parent_page_element = page_element
|
42
51
|
end
|
43
52
|
|
@@ -78,9 +87,7 @@ module PageMagic
|
|
78
87
|
# @raise [NotSupportedException] if the wrapped Capybara element does not support the method
|
79
88
|
EVENT_TYPES.each do |method|
|
80
89
|
define_method method do |*args|
|
81
|
-
unless browser_element.respond_to?(method)
|
82
|
-
raise NotSupportedException, EVENT_NOT_SUPPORTED_MSG % method
|
83
|
-
end
|
90
|
+
raise NotSupportedException, EVENT_NOT_SUPPORTED_MSG % method unless browser_element.respond_to?(method)
|
84
91
|
|
85
92
|
browser_element.send(method, *args)
|
86
93
|
end
|
@@ -91,7 +98,8 @@ module PageMagic
|
|
91
98
|
rescue ElementMissingException
|
92
99
|
begin
|
93
100
|
return browser_element.send(method, *args, &block) if browser_element.respond_to?(method)
|
94
|
-
|
101
|
+
|
102
|
+
parent_element.send(method, *args, &block)
|
95
103
|
rescue NoMethodError
|
96
104
|
super
|
97
105
|
end
|
@@ -131,6 +139,7 @@ module PageMagic
|
|
131
139
|
def wrap_events(raw_element)
|
132
140
|
EVENT_TYPES.each do |action_method|
|
133
141
|
next unless raw_element.respond_to?(action_method)
|
142
|
+
|
134
143
|
apply_hooks(raw_element: raw_element,
|
135
144
|
capybara_method: action_method,
|
136
145
|
before_events: before_events,
|
@@ -1,10 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module PageMagic
|
2
4
|
# for the benefit of pull review :@
|
3
5
|
class Element
|
4
6
|
# contains method for finding element definitions
|
5
7
|
module Locators
|
6
8
|
# message used when raising {ElementMissingException} from methods within this module
|
7
|
-
ELEMENT_NOT_DEFINED_MSG = 'Element not defined: %s'
|
9
|
+
ELEMENT_NOT_DEFINED_MSG = 'Element not defined: %s'
|
8
10
|
|
9
11
|
# find an element definition based on its name
|
10
12
|
# @param [Symbol] name name of the element
|
@@ -13,6 +15,7 @@ module PageMagic
|
|
13
15
|
def element_by_name(name, *args)
|
14
16
|
defintion = element_definitions[name]
|
15
17
|
raise ElementMissingException, (ELEMENT_NOT_DEFINED_MSG % name) unless defintion
|
18
|
+
|
16
19
|
defintion.call(self, *args)
|
17
20
|
end
|
18
21
|
|
@@ -1,45 +1,45 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module PageMagic
|
3
4
|
class Element
|
4
|
-
# class Query -
|
5
|
-
# - requirements on element type
|
6
|
-
# - selection criteria, modeled through the Selector class
|
7
|
-
# - options
|
5
|
+
# class Query - executes query on capybara driver
|
8
6
|
class Query
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
def find(type)
|
14
|
-
query = constants.find { |constant| constant.to_s.casecmp(type.to_s).zero? }
|
15
|
-
return ELEMENT unless query
|
16
|
-
const_get(query)
|
17
|
-
end
|
18
|
-
end
|
7
|
+
# Message template for execptions raised as a result of calling method_missing
|
8
|
+
ELEMENT_NOT_FOUND_MSG = 'Unable to find %s'
|
9
|
+
|
10
|
+
attr_reader :args, :multiple_results
|
19
11
|
|
20
|
-
|
12
|
+
alias multiple_results? multiple_results
|
21
13
|
|
22
|
-
|
23
|
-
|
24
|
-
@
|
14
|
+
def initialize(args, multiple_results: false)
|
15
|
+
@args = args
|
16
|
+
@multiple_results = multiple_results
|
25
17
|
end
|
26
18
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
19
|
+
def execute(capybara_element)
|
20
|
+
if multiple_results
|
21
|
+
capybara_element.all(*args).to_a.tap do |result|
|
22
|
+
raise Capybara::ElementNotFound if result.empty?
|
23
|
+
end
|
24
|
+
else
|
25
|
+
# TODO - make sure there is a test around this.
|
26
|
+
if args.last.is_a?(Hash)
|
27
|
+
capybara_element.find(*args[0...-1], **args.last)
|
28
|
+
else
|
29
|
+
capybara_element.find(*args)
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
end
|
34
|
+
rescue Capybara::Ambiguous => e
|
35
|
+
raise AmbiguousQueryException, e.message
|
36
|
+
rescue Capybara::ElementNotFound => e
|
37
|
+
raise ElementMissingException, e.message
|
37
38
|
end
|
38
39
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
BUTTON = Query.new(:button)
|
40
|
+
def ==(other)
|
41
|
+
other.respond_to?(:args) && args == other.args
|
42
|
+
end
|
43
43
|
end
|
44
44
|
end
|
45
45
|
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'page_magic/element/query'
|
4
|
+
module PageMagic
|
5
|
+
class Element
|
6
|
+
# class QueryBuilder - builds query to be executed on capybara driver, queries can include:
|
7
|
+
# - requirements on element type
|
8
|
+
# - selection criteria, modeled through the Selector class
|
9
|
+
# - options
|
10
|
+
class QueryBuilder
|
11
|
+
class << self
|
12
|
+
# Find a query using it's name
|
13
|
+
# @param [Symbol] type the name of the required query in snakecase format
|
14
|
+
# @return [QueryBuilder] returns the predefined query with the given name
|
15
|
+
def find(type)
|
16
|
+
query = constants.find { |constant| constant.to_s.casecmp(type.to_s).zero? }
|
17
|
+
return ELEMENT unless query
|
18
|
+
|
19
|
+
const_get(query)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :type
|
24
|
+
|
25
|
+
# @param type -
|
26
|
+
def initialize(type = nil)
|
27
|
+
@type = type
|
28
|
+
end
|
29
|
+
|
30
|
+
# Build query parameters for Capybara's find method
|
31
|
+
# @param [Hash] locator the location method e.g. text: 'button text'
|
32
|
+
# @param [Hash] capybara_options additional options to be provided to Capybara. e.g. count: 3
|
33
|
+
# @return [Array] list of compatible capybara query parameters.
|
34
|
+
def build(locator, capybara_options = {}, multiple_results: false)
|
35
|
+
args = [].tap do |array|
|
36
|
+
selector = Selector.find(locator.keys.first)
|
37
|
+
array << selector.build(type, locator.values.first)
|
38
|
+
array << capybara_options unless capybara_options.empty?
|
39
|
+
end.flatten
|
40
|
+
|
41
|
+
Query.new(args, multiple_results: multiple_results)
|
42
|
+
end
|
43
|
+
|
44
|
+
ELEMENT = QueryBuilder.new
|
45
|
+
TEXT_FIELD =
|
46
|
+
CHECKBOX =
|
47
|
+
SELECT_LIST =
|
48
|
+
RADIO =
|
49
|
+
TEXTAREA =
|
50
|
+
FIELD =
|
51
|
+
FILE_FIELD =
|
52
|
+
FILLABLE_FIELD =
|
53
|
+
RADIO_BUTTON =
|
54
|
+
SELECT = QueryBuilder.new(:field)
|
55
|
+
|
56
|
+
LINK = QueryBuilder.new(:link)
|
57
|
+
LABEL = QueryBuilder.new(:label)
|
58
|
+
BUTTON = QueryBuilder.new(:button)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|