Module: TestaAppiumDriver::W3cScrollActions

Included in:
ScrollActions
Defined in:
lib/testa_appium_driver/common/scroll_actions/w3c_scroll_actions.rb

Instance Method Summary collapse

Instance Method Details

#apply_w3c_correction(x1, y1, direction) ⇒ Object



370
371
372
373
374
375
376
# File 'lib/testa_appium_driver/common/scroll_actions/w3c_scroll_actions.rb', line 370

def apply_w3c_correction(x1, y1, direction)
  y1 -= SCROLL_CORRECTION_W3C if direction == :down
  y1 += SCROLL_CORRECTION_W3C if direction == :up
  x1 -= SCROLL_CORRECTION_W3C if direction == :right
  x1 += SCROLL_CORRECTION_W3C if direction == :left
  [x1, y1]
end

#w3c_action(x0, y0, x1, y1, type, speed_coef: 1.0) ⇒ Object



341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
# File 'lib/testa_appium_driver/common/scroll_actions/w3c_scroll_actions.rb', line 341

def w3c_action(x0, y0, x1, y1, type, speed_coef: 1.0)
  speed_coef = 1 / speed_coef
  if type == SCROLL_ACTION_TYPE_SCROLL
    duration = 1.8 * speed_coef
  elsif type == SCROLL_ACTION_TYPE_FLING
    duration = 0.1 * speed_coef
  elsif type == SCROLL_ACTION_TYPE_DRAG
    duration = 3.5 * speed_coef
  else
    raise "Unknown scroll action type #{type}"
  end

  action_builder = @driver.action
  f1 = action_builder.add_pointer_input(:touch, "finger1")
  f1.create_pointer_move(duration: 0, x: x0, y: y0, origin: ::Selenium::WebDriver::Interactions::PointerMove::VIEWPORT)
  f1.create_pointer_down(:left)

  f1.create_pointer_move(duration: duration, x: x1, y: y1, origin: ::Selenium::WebDriver::Interactions::PointerMove::VIEWPORT)
  unless type == SCROLL_ACTION_TYPE_FLING
    # with this move we prevent flinging/overscroll
    overscroll_pause = @driver.ios? ? 1 : 0.5
    f1.create_pointer_move(duration: overscroll_pause, x: x1, y: y1, origin: ::Selenium::WebDriver::Interactions::PointerMove::VIEWPORT)
  end
  f1.create_pointer_up(:left)
  puts "Scroll execute[w3c_action]:  #{type}: {x0: #{x0}, y0: #{y0}} => (duration: #{duration}) => {x1: #{x1}, y1: #{y1}}"
  # $ctx.puts "Scroll execute[w3c_action]:  #{type}: {x0: #{x0}, y0: #{y0}} => (duration: #{duration}) => {x1: #{x1}, y1: #{y1}}"
  @driver.perform_actions [f1]
end

#w3c_align(with, scroll_to_find, max_attempts, speed_coef: 1.25) ⇒ Object



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
# File 'lib/testa_appium_driver/common/scroll_actions/w3c_scroll_actions.rb', line 172

def w3c_align(with, scroll_to_find, max_attempts, speed_coef: 1.25)
  default_deadzone!

  @locator.scroll_to if scroll_to_find

  if @locator.instance_of?(TestaAppiumDriver::Locator)
    element = @locator.execute
  else
    element = @locator
  end

  max_attempts = 3 if max_attempts.nil? || max_attempts <= 0
  # $ctx.puts("Is aligned: #{is_aligned?(with, element)}")
  timeout = 0
  stale_retries = 0
  begin
    until is_aligned?(with, element) || timeout == max_attempts
      # $ctx.puts("align roudn: #{timeout}")
      w3c_attempt_align(with, speed_coef)
      timeout += 1
    end
  rescue Selenium::WebDriver::Error::StaleElementReferenceError
    # if boundElementsByIndex is enabled, we can get stale element reference while doing scroll each
    stale_retries += 1
    sleep 0.2
    # $ctx.puts "Looking for #{@locator.xpath_selector}"
    element = @locator.execute if @locator.instance_of?(TestaAppiumDriver::Locator)
    retry if stale_retries < 4
    raise
  end
  # $ctx.puts("end align")
end

#w3c_attempt_align(with, speed_coef) ⇒ Object



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
# File 'lib/testa_appium_driver/common/scroll_actions/w3c_scroll_actions.rb', line 205

def w3c_attempt_align(with, speed_coef)
  case with
  when :top
    y0 = @bounds.bottom_right.y - @deadzone[:bottom]
    y1 = y0 - @align_offset
    x0 = @bounds.center.x
    x1 = x0
    scroll_direction = :down
  when :bottom
    y0 = @bounds.top_left.y + @deadzone[:top]
    y1 = y0 + @align_offset
    x0 = @bounds.center.x
    x1 = x0
    scroll_direction = :up
  when :left
    x0 = @bounds.bottom_right.x - @deadzone[:right]
    x1 = x0 - @align_offset
    y0 = @bounds.center.y
    y1 = y0
    scroll_direction = :right
  when :right
    x0 = @bounds.top_left.x + @deadzone[:top]
    x1 = x0 + @align_offset
    y0 = @bounds.center.y
    y1 = y0
    scroll_direction = :left
  else
    raise "Unsupported align with option: #{with}"
  end

  x1, y1 = apply_w3c_correction(x1, y1, scroll_direction) if @driver.device == :android
  w3c_action(x0, y0, x1, y1, SCROLL_ACTION_TYPE_SCROLL, speed_coef: speed_coef)
end

#w3c_drag_to(x0, y0, x1, y1) ⇒ Object



378
379
380
# File 'lib/testa_appium_driver/common/scroll_actions/w3c_scroll_actions.rb', line 378

def w3c_drag_to(x0, y0, x1, y1)
  w3c_action(x0, y0, x1, y1, SCROLL_ACTION_TYPE_DRAG)
end

#w3c_page_or_fling(type, direction) ⇒ Object



304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
# File 'lib/testa_appium_driver/common/scroll_actions/w3c_scroll_actions.rb', line 304

def w3c_page_or_fling(type, direction)
  default_deadzone!

  if direction == :down || direction == :up
    if direction == :down
      y0 = @bounds.bottom_right.y - @deadzone[:bottom].to_i
      y1 = @bounds.top_left.y + @deadzone[:top].to_i
    else
      y0 = @bounds.top_left.y + @deadzone[:top].to_i
      y1 = @bounds.bottom_right.y - @deadzone[:bottom].to_i
    end
    x0 = @bounds.top_left.x + (@bounds.width - @deadzone[:left].to_i - @deadzone[:right].to_i) / 2
    x0 = @bounds.top_left.x if x0 < @bounds.top_left.x
    x0 = @bounds.bottom_right.x if x0 > @bounds.bottom_right.x
    x1 = x0
  else
    if direction == :right
      x0 = @bounds.bottom_right.x - @deadzone[:right].to_i
      x1 = @bounds.top_left.x + @deadzone[:left].to_i
    else
      x0 = @bounds.top_left.x + @deadzone[:left].to_i
      x1 = @bounds.bottom_right.x - @deadzone[:right].to_i
    end

    y0 = @bounds.top_left.y + (@bounds.height - @deadzone[:top].to_i - @deadzone[:bottom].to_i) / 2
    y0 = @bounds.top_left.y if y0 < @bounds.top_left.y
    y0 = @bounds.bottom_right.y if y0 > @bounds.bottom_right.y
    y1 = y0
  end
  x1, y1 = apply_w3c_correction(x1, y1, direction) if @driver.device == :android

  speed_coef = 1
  speed_coef = 1.5 if type == SCROLL_ACTION_TYPE_SCROLL

  w3c_action(x0, y0, x1, y1, type, speed_coef: speed_coef)
end

#w3c_scroll_each(direction, &block) ⇒ Array

Returns:

  • (Array)


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
# File 'lib/testa_appium_driver/common/scroll_actions/w3c_scroll_actions.rb', line 4

def w3c_scroll_each(direction, &block)
  default_deadzone!
  if direction.nil?
    scroll_to_start
    if @scrollable.scroll_orientation == :vertical
      direction = :down
    else
      direction = :right
    end
  end

  case direction
  when :up
    align_with = :bottom
  when :down
    align_with = :top
  when :right
    align_with = :left
  when :left
    align_with = :right
  else
    align_with = :top
  end

  w3c_scroll_each_v2(direction, align_with, &block)
end

#w3c_scroll_each_v1(direction, align_with, &block) ⇒ Object

Deprecated.


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
# File 'lib/testa_appium_driver/common/scroll_actions/w3c_scroll_actions.rb', line 32

def w3c_scroll_each_v1(direction, align_with, &block)
  elements = []
  begin

    iterations = 0
    ignore_element_ids = []
    previous_element = nil

    until is_end_of_scroll?
      # $__.puts "-- new iteration"
      # $__.puts "-------------"
      aligned_items = 0
      new_ignore_element_ids = []
      matches = @locator.execute(skip_cache: true)
      matches.each_with_index do |m, index|
        # $__.puts "Matches: #{matches.count}"
        # $__.puts "M: #{m.id}"
        if ignore_element_ids.include?(m.id)
          previous_element = m
          next
        end

        sa = self.dup
        sa.locator = m
        sa.w3c_align(align_with, false, 1, speed_coef: 2.0)
        is_aligned = sa.is_aligned?(align_with, m)

        # add the previous to ignore if current is not aligned (probably means we cannot scroll any further)
        new_ignore_element_ids << previous_element.id if !is_aligned && !previous_element.nil?

        aligned_items += 1 if is_aligned

        # add last element to ignore
        new_ignore_element_ids << m.id if matches.count == index + 1

        elements << m
        if block_given? # block is given
          @locator.driver.invalidate_cache
          block.call(m) # use call to execute the block
        else
          # the value of block_argument becomes nil if you didn't give a block
          # block was not given
        end
        previous_element = m
      end

      iterations += 1
      break if !@max_scrolls.nil? && iterations == @max_scrolls

      if aligned_items.zero?
        self.send("page_#{direction}")
      else
        ignore_element_ids = new_ignore_element_ids.dup
      end
    end
  rescue StandardError => e
    raise e
  end

  elements
end

#w3c_scroll_each_v2(direction, align_with, &block) ⇒ Object



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
# File 'lib/testa_appium_driver/common/scroll_actions/w3c_scroll_actions.rb', line 94

def w3c_scroll_each_v2(direction, align_with, &block)
  elements = []
  elements_in_current_iteration = []
  stale_retries = 0
  begin
    iterations = 0

    loop do
      elements_in_last_iteration = elements_in_current_iteration
      elements_in_current_iteration = []
      elements_count_before_iteration = elements.count

      new_element_in_this_iteration_found = false
      matches = @locator.execute(skip_cache: true)
      matches.each do |m|
        elements_in_current_iteration << m.id

        element_found_in_last_iteration = elements_in_last_iteration.include?(m.id)

        # ignore all elements from last iteration until first new element is found
        if !new_element_in_this_iteration_found && element_found_in_last_iteration
          next
        end

        # new element in this iteration is found,
        # process all remaining elements regardless if it is found in last iteration or not
        new_element_in_this_iteration_found = true

        sa = self.dup
        sa.locator = m
        sa.w3c_align(align_with, false, 1, speed_coef: 2.0)

        elements << m.id
        if block_given? # block is given
          @locator.driver.invalidate_cache
          block.call(m) # use call to execute the block
        else
          # the value of block_argument becomes nil if you didn't give a block
          # block was not given
        end
      end

      iterations += 1
      break if !@max_scrolls.nil? && iterations == @max_scrolls

      if matches.count.positive?
        # if there are elements on screen, but there are no new elements
        # then we have reached the end of scrolling
        if elements.count == elements_count_before_iteration
          return elements
        end
      else
        # there are no matching elements. to detect end of scroll,
        # first and last leaf is compared before with first and last leaf of previous iteration
        if is_end_of_scroll?
          return elements
        end
      end

      # if there are more than 1 match on screen,
      # the page is scrolled by aligning elements 1 by one,
      # but if there is only 1, or none elements.
      # then scroll the page manually
      self.send("page_#{direction}") if matches.count < 2
    end
  rescue Selenium::WebDriver::Error::StaleElementReferenceError
    # if boundElementsByIndex is enabled, we can get stale element reference while doing scroll each
    stale_retries += 1
    sleep 0.2
    retry if stale_retries < 4
    raise
  rescue StandardError => e
    raise e
  end

  elements
end

#w3c_scroll_to(direction) ⇒ Object

Raises:

  • (Selenium::WebDriver::Error::NoSuchElementError)


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
# File 'lib/testa_appium_driver/common/scroll_actions/w3c_scroll_actions.rb', line 239

def w3c_scroll_to(direction)
  rounds = 0
  max_scrolls_reached = false
  end_of_scroll_reached = false
  # $ctx.puts("starting scroll to")
  until (element_found = ((@driver.android? && @locator.exists?) || (@driver.ios? && @locator.exists? && @locator.in_viewport?))) || end_of_scroll_reached
    # $ctx.puts("Scroll to round: #{rounds}")
    end_of_scroll_reached = is_end_of_scroll?
    case direction
    when :down
      page_down
    when :right
      page_right
    when :left
      page_left
    when :up
      page_up
    else
      scroll_to_start
      @previous_elements = nil
      if @scrollable.scroll_orientation == :vertical
        direction = :down
      else
        direction = :right
      end
    end

    rounds += 1

    max_scrolls_reached = true if rounds == @max_scrolls
    break if rounds == @max_scrolls
  end
  # $ctx.puts("end scroll to")
  raise Selenium::WebDriver::Error::NoSuchElementError if (max_scrolls_reached || end_of_scroll_reached) && !element_found
end

#w3c_scroll_to_start_or_end(type) ⇒ Object



275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/testa_appium_driver/common/scroll_actions/w3c_scroll_actions.rb', line 275

def w3c_scroll_to_start_or_end(type)
  default_deadzone!

  @previous_elements = nil

  if type == :start
    if @scrollable.scroll_orientation == :vertical
      method = "fling_up"
    else
      method = "fling_left"
    end
  else
    if @scrollable.scroll_orientation == :vertical
      method = "fling_down"
    else
      method = "fling_right"
    end
  end

  iterations = 0
  until is_end_of_scroll? || iterations >= 3
    self.send(method)
    iterations += 1
  end

  # reset the flag for end of scroll elements
  @previous_elements = nil
end