kameleon 0.0.9 → 0.2.0.alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENCE +1 -1
- data/README.textile +157 -0
- data/lib/kameleon.rb +3 -18
- data/lib/kameleon/dsl.rb +98 -0
- data/lib/kameleon/dsl/act/form.rb +106 -0
- data/lib/kameleon/dsl/act/mouse.rb +59 -0
- data/lib/kameleon/dsl/context/scope.rb +95 -0
- data/lib/kameleon/dsl/utils/debug.rb +10 -0
- data/lib/kameleon/dsl/verify/absence.rb +151 -0
- data/lib/kameleon/dsl/verify/presence.rb +327 -0
- data/lib/kameleon/ext/active_record/shared_single_connection.rb +42 -0
- data/lib/kameleon/ext/capybara/server.rb +21 -0
- data/lib/kameleon/ext/capybara/session_pool.rb +63 -0
- data/lib/kameleon/ext/rspec/all.rb +4 -0
- data/lib/kameleon/ext/rspec/dsl.rb +39 -0
- data/lib/kameleon/ext/rspec/garbage_collector.rb +11 -0
- data/lib/kameleon/ext/rspec/headless.rb +12 -0
- data/lib/kameleon/ext/rspec/pool.rb +7 -0
- data/lib/kameleon/ext/rspec/transactional_examples.rb +10 -0
- data/{spec/support/deferred_garbage_collection.rb → lib/kameleon/ext/ruby/deferred_garbage_collector.rb} +0 -0
- data/lib/kameleon/ext/session/devise.rb +28 -0
- data/lib/kameleon/session.rb +23 -0
- data/lib/kameleon/utils/configuration.rb +5 -5
- data/lib/kameleon/utils/debug_console.rb +31 -0
- metadata +73 -77
- data/lib/kameleon/dsl/act.rb +0 -107
- data/lib/kameleon/dsl/see.rb +0 -155
- data/lib/kameleon/session/capybara.rb +0 -33
- data/lib/kameleon/session/server.rb +0 -21
- data/lib/kameleon/session/session_pool.rb +0 -45
- data/lib/kameleon/user/abstract.rb +0 -142
- data/lib/kameleon/user/base.rb +0 -36
- data/lib/kameleon/user/guest.rb +0 -10
- data/lib/kameleon/utils/helpers.rb +0 -11
- data/lib/patch/capybara_selenium_driver.rb +0 -7
- data/spec/sample_rack_app/hey.rb +0 -24
- data/spec/spec_helper.rb +0 -41
- data/spec/unit/act/click_spec.rb +0 -52
- data/spec/unit/act/fill_in/attach_file_spec.rb +0 -36
- data/spec/unit/act/fill_in/checkbox_spec.rb +0 -87
- data/spec/unit/act/fill_in/date_ranges_spec.rb +0 -24
- data/spec/unit/act/fill_in/multiple_select_spec.rb +0 -42
- data/spec/unit/act/fill_in/radio_button_spec.rb +0 -35
- data/spec/unit/act/fill_in/select_spec.rb +0 -35
- data/spec/unit/act/fill_in/text_area_spec.rb +0 -52
- data/spec/unit/act/fill_in/text_input_spec.rb +0 -52
- data/spec/unit/act/on_hover_spec.rb +0 -34
- data/spec/unit/dsl/not_see/form_elements/fields/empty_spec.rb +0 -38
- data/spec/unit/dsl/not_see/form_elements/fields/readonly_spec.rb +0 -38
- data/spec/unit/dsl/not_see/form_elements/fields_spec.rb +0 -24
- data/spec/unit/dsl/not_see/form_elements/textareas_spec.rb +0 -25
- data/spec/unit/dsl/not_see/form_elements/texts_spec.rb +0 -26
- data/spec/unit/dsl/not_see/in_scopes_spec.rb +0 -63
- data/spec/unit/dsl/not_see/special_elements/buttons_spec.rb +0 -24
- data/spec/unit/dsl/not_see/special_elements/error_message_for_spec.rb +0 -24
- data/spec/unit/dsl/not_see/special_elements/images_spec.rb +0 -24
- data/spec/unit/dsl/not_see/special_elements/links_spec.rb +0 -46
- data/spec/unit/dsl/not_see/special_elements/ordered_texts_spec.rb +0 -21
- data/spec/unit/dsl/not_see/special_selectors_spec.rb +0 -39
- data/spec/unit/dsl/not_see/texts_spec.rb +0 -24
- data/spec/unit/dsl/see/counted_elements_spec.rb +0 -26
- data/spec/unit/dsl/see/form_elements/checkboxes_spec.rb +0 -45
- data/spec/unit/dsl/see/form_elements/fields/disabled_spec.rb +0 -30
- data/spec/unit/dsl/see/form_elements/fields/empty_spec.rb +0 -28
- data/spec/unit/dsl/see/form_elements/fields/readonly_spec.rb +0 -38
- data/spec/unit/dsl/see/form_elements/fields_spec.rb +0 -28
- data/spec/unit/dsl/see/form_elements/multiple_selects_spec.rb +0 -47
- data/spec/unit/dsl/see/form_elements/radio_buttons_spec.rb +0 -35
- data/spec/unit/dsl/see/form_elements/selects_spec.rb +0 -40
- data/spec/unit/dsl/see/form_elements/textareas_spec.rb +0 -29
- data/spec/unit/dsl/see/form_elements/texts_spec.rb +0 -29
- data/spec/unit/dsl/see/in_scopes_spec.rb +0 -85
- data/spec/unit/dsl/see/special_elements/buttons_spec.rb +0 -28
- data/spec/unit/dsl/see/special_elements/error_message_for_spec.rb +0 -24
- data/spec/unit/dsl/see/special_elements/images_spec.rb +0 -28
- data/spec/unit/dsl/see/special_elements/links_spec.rb +0 -55
- data/spec/unit/dsl/see/special_elements/ordered_texts_spec.rb +0 -21
- data/spec/unit/dsl/see/special_selectors_spec.rb +0 -57
- data/spec/unit/dsl/see/texts_spec.rb +0 -24
- data/spec/unit/guest_spec.rb +0 -46
- data/spec/unit/user_base_spec.rb +0 -13
data/LICENCE
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Copyright (c) 2011 Michał Czyż <http://linkd.in/michalczyz> @cs3b
|
1
|
+
Copyright (c) 2011..2012 Michał Czyż <http://linkd.in/michalczyz> @cs3b
|
2
2
|
|
3
3
|
Permission is hereby granted, free of charge, to any person obtaining
|
4
4
|
a copy of this software and associated documentation files (the
|
data/README.textile
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
h1. Kameleon [chameleon]
|
2
|
+
|
3
|
+
"!https://secure.travis-ci.org/cs3b/kameleon.png?branch=master!":http://travis-ci.org/cs3b/kameleon
|
4
|
+
|
5
|
+
Kameleon is a high abstraction dsl for better writing acceptance and integrationtests using Capybara.
|
6
|
+
And "better" means: more easily to mimic how user interact with browser.
|
7
|
+
|
8
|
+
h2. Setup:
|
9
|
+
|
10
|
+
Kameleon requires:
|
11
|
+
* rspec
|
12
|
+
* capybara
|
13
|
+
|
14
|
+
Optional is nice to use
|
15
|
+
* devise ( Kameleon uses paths suplied by devise. So if you're using some other authentication solution, you might want to overwrite it )
|
16
|
+
* selenium-wedriver && capybara-webkit for testing full site
|
17
|
+
* thin (if you want run your specs faster - add gem 'thin' to Gemfile in your app)
|
18
|
+
|
19
|
+
Gemfile
|
20
|
+
|
21
|
+
<pre>
|
22
|
+
gem 'kameleon', '>= 0.2.0'
|
23
|
+
</pre>
|
24
|
+
|
25
|
+
Before you start using Kameleon ensure that capybara is properly loaded (in your test helper file)
|
26
|
+
|
27
|
+
<pre>
|
28
|
+
require 'kameleon/ext/rspec/all'
|
29
|
+
</pre>
|
30
|
+
|
31
|
+
or just components you want, kemeleon/ext/rspec/all by default loads:
|
32
|
+
|
33
|
+
<pre>
|
34
|
+
require 'kameleon/ext/rspec/dsl'
|
35
|
+
require 'kameleon/ext/rspec/garbage_collector'
|
36
|
+
require 'kameleon/ext/rspec/headless'
|
37
|
+
</pre>
|
38
|
+
|
39
|
+
h2. Usage:
|
40
|
+
|
41
|
+
below few examples for more please check spec/integration tests suite
|
42
|
+
|
43
|
+
bc. background do
|
44
|
+
@admin = Factory.create(:user)
|
45
|
+
end
|
46
|
+
|
47
|
+
scenario "opening list of news", :status => 'done' do
|
48
|
+
click "homepage",
|
49
|
+
"community",
|
50
|
+
"news"
|
51
|
+
|
52
|
+
see "new medicine developed last weekend"
|
53
|
+
within(:row => 'some cell text') do
|
54
|
+
see 'see all values in row that contains that text'
|
55
|
+
end
|
56
|
+
see :ordered => 'Z', 'X', 'Y'
|
57
|
+
click :and_confirm => "Cancel", :and_dismiss => "Cancel"
|
58
|
+
end
|
59
|
+
|
60
|
+
Another example
|
61
|
+
|
62
|
+
bc. feature "Products", :driver => :selenium do
|
63
|
+
background do
|
64
|
+
@admin = Factory.create(:user)
|
65
|
+
create_session(:admin)
|
66
|
+
visit spree.admin_path
|
67
|
+
end
|
68
|
+
|
69
|
+
context "listing products" do
|
70
|
+
scenario "products sorting" do
|
71
|
+
Factory(:product, :name => 'apache baseball cap',
|
72
|
+
:available_on => '2011-01-06 18:21:13:',
|
73
|
+
:count_on_hand => '0')
|
74
|
+
Factory(:product, :name => 'zomg shirt',
|
75
|
+
:available_on => '2125-01-06 18:21:13',
|
76
|
+
:count_on_hand => '5')
|
77
|
+
|
78
|
+
act_as(:admin) do
|
79
|
+
click "Products"
|
80
|
+
see :ordered => ["apache baseball cap", "zomg shirt"]
|
81
|
+
|
82
|
+
click "admin_products_listing_name_title"
|
83
|
+
see :ordered => ["zomg shirt", "apache baseball cap"]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
And more complex example, with two user sessions:
|
89
|
+
|
90
|
+
bc. scenario "admin adds a product and user buys it", :status => "done", :driver => :selenium do
|
91
|
+
create_session(:admin)
|
92
|
+
@admin = Factory.create(:admin_user)
|
93
|
+
create_session(:user)
|
94
|
+
|
95
|
+
act_as(:admin) do
|
96
|
+
visit spree.admin_path
|
97
|
+
click "Products", "admin_new_product"
|
98
|
+
within('#new_product') do
|
99
|
+
see "SKU"
|
100
|
+
end
|
101
|
+
fill_in "product_name" => "Baseball Cap",
|
102
|
+
"product_sku" => "B100",
|
103
|
+
"product_price"=> "100",
|
104
|
+
"product_available_on"=> "2012/01/24"
|
105
|
+
|
106
|
+
click "Create"
|
107
|
+
see "successfully created!"
|
108
|
+
|
109
|
+
fill_in "product_on_hand" => "100"
|
110
|
+
click "Update"
|
111
|
+
see "successfully updated!"
|
112
|
+
end
|
113
|
+
|
114
|
+
act_as(:user) do
|
115
|
+
visit spree.root_path
|
116
|
+
click "Baseball Cap", "add-to-cart-button", "Checkout"
|
117
|
+
within("span.out-of-stock") do
|
118
|
+
see "Baseball Cap added to your cart"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
h2. Tips & Tricks:
|
125
|
+
* You have access to page variable. So if you think that something cannot be accomplished by the Kameleon DSL, you can just write using RSpec matchers and page variable. Like this: <pre> page.should have_css("li.banner_message", :count => 10) </pre> Of course, after you've submitted feature request to the owner of the original repository ;)
|
126
|
+
* It is handy to define a common set of areas, that user often follows navigating on the site. Here is an example:
|
127
|
+
<pre> Kameleon::Session.defined_areas.merge!({
|
128
|
+
:menu => [:xpath, "//nav/ul"],
|
129
|
+
:main => '.main_body',
|
130
|
+
:right_column => '.col_aside',
|
131
|
+
:ordered_list => '.ordered_list',
|
132
|
+
:favourites => '.favourites_list',
|
133
|
+
:gallery_tiny => '.gallery_tiny',
|
134
|
+
:gallery_list => '.gallery_list',
|
135
|
+
:content => '.col_content',
|
136
|
+
:col_aside => '.col_aside'
|
137
|
+
})
|
138
|
+
|
139
|
+
</pre>
|
140
|
+
|
141
|
+
soon we will merge with new capybara approach for that
|
142
|
+
|
143
|
+
h2. Tips & Tricks:
|
144
|
+
|
145
|
+
h3. Session pooling
|
146
|
+
|
147
|
+
Kameleon has useful technique of session pooling implemented, that can speed up test suite greatly.
|
148
|
+
In order to enable it, you need to pass this to the RSpec.configure block, in spec_helper.rb:
|
149
|
+
|
150
|
+
bc. config.after(:each) do
|
151
|
+
::SessionPool.release_all
|
152
|
+
end
|
153
|
+
|
154
|
+
h2. Credits:
|
155
|
+
* <a href="http://selleo.com/people/michal-czyz">Michał Czyż</a>
|
156
|
+
* <a href="http://selleo.com/people/radoslaw-jedryszczak">Radosław Jędryszczak</a>
|
157
|
+
* <a href="http://selleo.com/people">Szymon Kieloch</a>
|
data/lib/kameleon.rb
CHANGED
@@ -1,22 +1,7 @@
|
|
1
1
|
require 'kameleon/utils/configuration'
|
2
|
-
require 'kameleon/utils/helpers'
|
3
|
-
|
4
|
-
require 'kameleon/session/capybara'
|
5
|
-
require 'kameleon/session/server'
|
6
|
-
require 'kameleon/session/session_pool'
|
7
|
-
require 'kameleon/dsl/see'
|
8
|
-
require 'kameleon/dsl/act'
|
9
|
-
|
10
|
-
require 'kameleon/user/abstract'
|
11
|
-
require 'kameleon/user/base'
|
12
|
-
require 'kameleon/user/guest'
|
13
|
-
|
14
2
|
|
15
3
|
module Kameleon
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
# Set up default configuration for Kameleon
|
20
|
-
Kameleon.configure do |config|
|
21
|
-
config.default_file_path = 'spec/dummy'
|
4
|
+
def self.configure(&block)
|
5
|
+
yield(Kameleon::Utils::Configuration)
|
6
|
+
end
|
22
7
|
end
|
data/lib/kameleon/dsl.rb
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'kameleon/dsl/verify/presence'
|
2
|
+
require 'kameleon/dsl/verify/absence'
|
3
|
+
|
4
|
+
require 'kameleon/dsl/act/mouse'
|
5
|
+
require 'kameleon/dsl/act/form'
|
6
|
+
|
7
|
+
require 'kameleon/ext/capybara/session_pool'
|
8
|
+
require 'kameleon/session'
|
9
|
+
|
10
|
+
require 'kameleon/dsl/context/scope'
|
11
|
+
|
12
|
+
module Kameleon
|
13
|
+
module DSL
|
14
|
+
|
15
|
+
def see(*args)
|
16
|
+
Kameleon::DSL::Verify::Presence.new(*args).tap do |presence|
|
17
|
+
presence.conditions.each do |condition|
|
18
|
+
if block = condition.block
|
19
|
+
instance_exec(*condition.params, &block)
|
20
|
+
else
|
21
|
+
page.should send(condition.method, *condition.params)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
#! hmn - very similar to see
|
28
|
+
def not_see(*args)
|
29
|
+
Kameleon::DSL::Verify::Absence.new(*args).tap do |absence|
|
30
|
+
absence.conditions.each do |condition|
|
31
|
+
if condition.block
|
32
|
+
instance_eval(condition.block)
|
33
|
+
else
|
34
|
+
page.should send(condition.method, *condition.params)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def create_session(name = :default)
|
41
|
+
Kameleon::Session.new(name).tap { |ks| act_as(ks.name) }
|
42
|
+
end
|
43
|
+
|
44
|
+
def act_as(name)
|
45
|
+
if block_given?
|
46
|
+
using_session(name) do
|
47
|
+
yield
|
48
|
+
end
|
49
|
+
else
|
50
|
+
Capybara.session_name = name
|
51
|
+
end
|
52
|
+
self
|
53
|
+
end
|
54
|
+
|
55
|
+
def within(*scope, &block)
|
56
|
+
if block_given?
|
57
|
+
super(*parse_selector(scope).selector)
|
58
|
+
else
|
59
|
+
#! we need proxy object to make it working
|
60
|
+
raise 'not impelemented'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def click(*args)
|
65
|
+
Kameleon::DSL::Act::Mouse::Click.new(*args).tap do |click|
|
66
|
+
click.actions.each do |action|
|
67
|
+
if block = action.block
|
68
|
+
instance_exec(*action.params, &block)
|
69
|
+
else
|
70
|
+
page.send(action.method, *action.params)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def fill_in(args)
|
77
|
+
Kameleon::DSL::Act::Form.new(args).tap do |form|
|
78
|
+
form.actions.each do |action|
|
79
|
+
if block = action.block
|
80
|
+
instance_exec(*action.params, &block)
|
81
|
+
else
|
82
|
+
page.send(action.method, *action.params)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def refresh_page
|
89
|
+
visit current_url
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def parse_selector(scope)
|
95
|
+
Kameleon::DSL::Context::Scope.new(scope)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module Kameleon
|
2
|
+
module DSL
|
3
|
+
module Act
|
4
|
+
class Form
|
5
|
+
attr_reader :params
|
6
|
+
attr_reader :actions
|
7
|
+
|
8
|
+
def initialize(params)
|
9
|
+
raise "not supported" unless params.kind_of?(Hash)
|
10
|
+
@params = params
|
11
|
+
@actions = []
|
12
|
+
parse_actions
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def parse_actions
|
18
|
+
prepare_actions(params)
|
19
|
+
end
|
20
|
+
|
21
|
+
def prepare_actions(param)
|
22
|
+
param.each_pair do |value, identifier|
|
23
|
+
case identifier
|
24
|
+
when Array
|
25
|
+
identifier.each { |identifier| prepare_actions(value => identifier) }
|
26
|
+
else
|
27
|
+
case value
|
28
|
+
when Fixnum, String
|
29
|
+
actions << Action.new(:fill_in, identifier, :with => value)
|
30
|
+
when :check, :choose, :uncheck
|
31
|
+
actions << Action.new(value, identifier)
|
32
|
+
when :select, :unselect
|
33
|
+
actions.concat SelectTag.new(value, identifier).actions
|
34
|
+
when :attach
|
35
|
+
actions.concat AttachFileTag.new(identifier).actions
|
36
|
+
else
|
37
|
+
raise "not implemented"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class SelectTag
|
45
|
+
attr_reader :actions, :action
|
46
|
+
|
47
|
+
def initialize(action, params)
|
48
|
+
raise "not implemented" unless params.kind_of?(Hash)
|
49
|
+
@action = action
|
50
|
+
@actions = []
|
51
|
+
parse_params(params)
|
52
|
+
end
|
53
|
+
|
54
|
+
def parse_params(params)
|
55
|
+
params.each_pair do |option, id|
|
56
|
+
if option.kind_of?(Array)
|
57
|
+
option.each do |o|
|
58
|
+
parse_params(o => id)
|
59
|
+
end
|
60
|
+
else
|
61
|
+
actions << Action.new(action, option, :from => id)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class AttachFileTag
|
68
|
+
attr_reader :actions
|
69
|
+
|
70
|
+
def initialize(params)
|
71
|
+
raise "not implemented" unless params.kind_of?(Hash)
|
72
|
+
@actions = []
|
73
|
+
parse_params(params)
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def parse_params(params)
|
79
|
+
params.each_pair do |filename, identifier|
|
80
|
+
if identifier.kind_of?(Array)
|
81
|
+
identifier.each do |id|
|
82
|
+
parse_params(filename => id)
|
83
|
+
end
|
84
|
+
else
|
85
|
+
actions << Action.new(:attach_file, identifier, full_path(filename))
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def full_path(filename)
|
91
|
+
if File.file?(filename)
|
92
|
+
filename
|
93
|
+
else
|
94
|
+
prepare_full_path(filename)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def prepare_full_path(filename)
|
99
|
+
File.join(Kameleon::Utils::Configuration.assets_dir, filename)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
#! in future should we check in RackTest - does field is editable (detect readonly, disabled attributes) ?
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Kameleon
|
2
|
+
module DSL
|
3
|
+
module Act
|
4
|
+
module Mouse
|
5
|
+
class Click
|
6
|
+
attr_reader :params
|
7
|
+
attr_reader :actions
|
8
|
+
|
9
|
+
def initialize(*params)
|
10
|
+
@params = params
|
11
|
+
@actions = []
|
12
|
+
parse_actions
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def parse_actions
|
18
|
+
prepare_actions(params)
|
19
|
+
end
|
20
|
+
|
21
|
+
def prepare_actions(param)
|
22
|
+
case param
|
23
|
+
when String
|
24
|
+
actions << Action.new(:click_on, param)
|
25
|
+
when Hash
|
26
|
+
param.each_pair do |type, values|
|
27
|
+
case type
|
28
|
+
when :non_implemented
|
29
|
+
raise "not implemented"
|
30
|
+
else
|
31
|
+
raise "not implemented"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
when Array
|
35
|
+
params.each { |parameter| prepare_actions(parameter) }
|
36
|
+
else
|
37
|
+
raise "not implemented"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Action
|
44
|
+
attr_accessor :method, :params, :block
|
45
|
+
|
46
|
+
def initialize(method, *params, &block)
|
47
|
+
@method = method
|
48
|
+
@params = params
|
49
|
+
@block = block
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
#! click and dismiss
|
57
|
+
#! click and accept
|
58
|
+
#! click on image
|
59
|
+
#! click on element
|