Skip to content

Commit 5438246

Browse files
authored
Define screenshot methods in this lib (#98)
* tweak tests for screenshots * extract screenshots * fix rubocop * fix wrong tests * tweak docstring
1 parent 9bd9e11 commit 5438246

File tree

11 files changed

+190
-34
lines changed

11 files changed

+190
-34
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file.
44
## [Unreleased]
55
### Enhancements
66
- Add a `ImageElement` to handle images as elements by `matchTemplate`
7+
- [Internal] Define screenshot methods in appium_lib_core instead of Selenium's one
78

89
### Bug fixes
910

lib/appium_lib_core/common/base/bridge/mjsonwp.rb

+68
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,74 @@ class MJSONWP < ::Selenium::WebDriver::Remote::OSS::Bridge
66
def commands(command)
77
::Appium::Core::Commands::MJSONWP::COMMANDS[command]
88
end
9+
10+
#
11+
# @return [::Appium::Core::ImageElement|nil]
12+
# @raise [::Selenium::WebDriver::Error::TimeOutError|::Selenium::WebDriver::Error::WebDriverError]
13+
#
14+
def find_element_by_image(full_image:, partial_image:, match_threshold: nil, visualize: false)
15+
options = {}
16+
options[:threshold] = match_threshold unless match_threshold.nil?
17+
options[:visualize] = visualize
18+
19+
params = {}
20+
params[:mode] = :matchTemplate
21+
params[:firstImage] = full_image
22+
params[:secondImage] = partial_image
23+
params[:options] = options if options
24+
25+
result = execute(:compare_images, {}, params)
26+
rect = result['rect']
27+
28+
if rect
29+
return ::Appium::Core::ImageElement.new(self,
30+
rect['x'],
31+
rect['y'],
32+
rect['width'],
33+
rect['height'],
34+
result['visualization'])
35+
end
36+
nil
37+
end
38+
39+
#
40+
# @return [[]|[::Appium::Core::ImageElement]]
41+
# @raise [::Selenium::WebDriver::Error::TimeOutError|::Selenium::WebDriver::Error::WebDriverError]
42+
#
43+
def find_elements_by_image(full_image:, partial_images:, match_threshold: nil, visualize: false)
44+
options = {}
45+
options[:threshold] = match_threshold unless match_threshold.nil?
46+
options[:visualize] = visualize
47+
48+
params = {}
49+
params[:mode] = :matchTemplate
50+
params[:firstImage] = full_image
51+
params[:options] = options if options
52+
53+
partial_images.each_with_object([]) do |partial_image, acc|
54+
params[:secondImage] = partial_image
55+
56+
begin
57+
result = execute(:compare_images, {}, params)
58+
rect = result['rect']
59+
60+
if result['rect']
61+
acc.push ::Appium::Core::ImageElement.new(self,
62+
rect['x'],
63+
rect['y'],
64+
rect['width'],
65+
rect['height'],
66+
result['visualization'])
67+
end
68+
rescue ::Selenium::WebDriver::Error::WebDriverError => e
69+
acc if e.message.include?('Cannot find any occurrences')
70+
end
71+
end
72+
end
73+
74+
def take_element_screenshot(element)
75+
execute :take_element_screenshot, id: element.ref
76+
end
977
end # class MJSONWP
1078
end # class Bridge
1179
end # class Base

lib/appium_lib_core/common/base/bridge/w3c.rb

+4
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,10 @@ def log(type)
204204
end
205205
end
206206

207+
def take_element_screenshot(element)
208+
execute :take_element_screenshot, id: element.ref
209+
end
210+
207211
private
208212

209213
# Don't convert locators for Appium Client

lib/appium_lib_core/common/base/driver.rb

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
require 'base64'
22
require_relative 'search_context'
3+
require_relative 'screenshot'
34

45
module Appium
56
module Core
67
class Base
78
class Driver < ::Selenium::WebDriver::Driver
89
include ::Selenium::WebDriver::DriverExtensions::UploadsFiles
9-
include ::Selenium::WebDriver::DriverExtensions::TakesScreenshot
1010
include ::Selenium::WebDriver::DriverExtensions::HasSessionId
1111
include ::Selenium::WebDriver::DriverExtensions::Rotatable
1212
include ::Selenium::WebDriver::DriverExtensions::HasRemoteStatus
1313
include ::Selenium::WebDriver::DriverExtensions::HasWebStorage
1414

1515
include ::Appium::Core::Base::SearchContext
16+
include ::Appium::Core::Base::TakeScreenshot
1617

1718
def initialize(opts = {})
1819
listener = opts.delete(:listener)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
module Appium
2+
module Core
3+
class Base
4+
module TakeScreenshot
5+
#
6+
# Save a PNG screenshot to the given path
7+
#
8+
# @api public
9+
#
10+
def save_screenshot(png_path)
11+
extension = File.extname(png_path).downcase
12+
if extension != '.png'
13+
::Appium::Logger.warn 'name used for saved screenshot does not match file type. '\
14+
'It should end with .png extension'
15+
end
16+
File.open(png_path, 'wb') { |f| f << screenshot_as(:png) }
17+
end
18+
19+
#
20+
# Return a PNG screenshot in the given format as a string
21+
#
22+
# @param [:base64, :png] format
23+
# @return String screenshot
24+
#
25+
# @example
26+
#
27+
# @@driver.screenshot_as :base64 #=> "iVBORw0KGgoAAAANSUhEUgAABDgAAAB+CAIAAABOPDa6AAAAAX"
28+
#
29+
# @api public
30+
def screenshot_as(format)
31+
case format
32+
when :base64
33+
bridge.screenshot
34+
when :png
35+
bridge.screenshot.unpack('m')[0]
36+
else
37+
raise Core::Error::UnsupportedOperationError, "unsupported format: #{format.inspect}"
38+
end
39+
end
40+
41+
# @param [Selenium::WebDriver::Element] element A element you'd like to take screenshot.
42+
# @param [String] png_path A path to save the screenshot
43+
# @return [File] Path to the screenshot.
44+
#
45+
# @example
46+
#
47+
# @driver.save_element_screenshot(element, "fine_name.png")
48+
#
49+
def save_element_screenshot(element, png_path)
50+
extension = File.extname(png_path).downcase
51+
if extension != '.png'
52+
::Appium::Logger.warn 'name used for saved screenshot does not match file type. '\
53+
'It should end with .png extension'
54+
end
55+
File.open(png_path, 'wb') { |f| f << element_screenshot_as(element, :png) }
56+
end
57+
# backward compatibility
58+
alias take_element_screenshot save_element_screenshot
59+
60+
#
61+
# Return a PNG screenshot in the given format as a string
62+
#
63+
# @param [:base64, :png] format
64+
# @return String screenshot
65+
#
66+
# @example
67+
#
68+
# @@driver.element_screenshot_as element, :base64 #=> "iVBORw0KGgoAAAANSUhEUgAABDgAAAB+CAIAAABOPDa6AAAAAX"
69+
#
70+
def element_screenshot_as(element, format)
71+
case format
72+
when :base64
73+
bridge.take_element_screenshot(element)
74+
when :png
75+
bridge.take_element_screenshot(element).unpack('m')[0]
76+
else
77+
raise Core::Error::UnsupportedOperationError, "unsupported format: #{format.inspect}"
78+
end
79+
end
80+
end
81+
end
82+
end
83+
end

lib/appium_lib_core/common/error.rb

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ class CapabilityStructureError < CoreError; end
1111
class NotSupportedAppiumServer < CoreError; end
1212
class NoSuchElementError < CoreError; end
1313

14+
class UnsupportedOperationError < CoreError; end
15+
1416
# Server side error
1517
class ServerError; end
1618
end

lib/appium_lib_core/device.rb

+4-23
Original file line numberDiff line numberDiff line change
@@ -477,16 +477,6 @@ module Device
477477
# @driver.switch_to_default_context
478478
#
479479

480-
# @!method take_element_screenshot(element, png_path)
481-
# @param [Selenium::WebDriver::Element] element A element you'd like to take screenshot.
482-
# @param [String] png_path A path to save the screenshot
483-
# @return [File] Path to the screenshot.
484-
#
485-
# @example
486-
#
487-
# @driver.take_element_screenshot(element, "fine_name.png")
488-
#
489-
490480
# @!method stop_recording_screen(remote_path: nil, user: nil, pass: nil, method: 'PUT')
491481
# @param [String] remote_path: The path to the remote location, where the resulting video should be uploaded.
492482
# The following protocols are supported: http/https, ftp.
@@ -536,19 +526,6 @@ def device_time(format = nil)
536526
end
537527
end
538528

539-
add_endpoint_method(:take_element_screenshot) do
540-
def take_element_screenshot(element, png_path)
541-
result = execute :take_element_screenshot, id: element.ref
542-
543-
extension = File.extname(png_path).downcase
544-
if extension != '.png'
545-
::Appium::Logger.warn 'name used for saved screenshot does not match file type. '\
546-
'It should end with .png extension'
547-
end
548-
File.open(png_path, 'wb') { |f| f << result.unpack('m')[0] }
549-
end
550-
end
551-
552529
add_endpoint_method(:save_viewport_screenshot) do
553530
def save_viewport_screenshot(png_path)
554531
extension = File.extname(png_path).downcase
@@ -574,6 +551,10 @@ def save_viewport_screenshot(png_path)
574551
ScreenRecord.add_methods
575552
ImageComparison.add_methods
576553
AppState.add_methods
554+
555+
# Compatibility for appium_lib
556+
# TODO: Will remove
557+
delegate_from_appium_driver :take_element_screenshot
577558
end
578559

579560
# def extended

test/unit/android/device/mjsonwp/definition_test.rb

+6-3
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,15 @@ def test_delegate_driver_method
1919
assert @driver.respond_to? :launch_app
2020
end
2121

22-
def test_delegate_from_appium_driver
23-
assert @core.send(:delegated_target_for_test).respond_to? :launch_app
22+
def delegate_from_appium_driver(key)
23+
assert @core.send(:delegated_target_for_test).respond_to? key
2424
end
2525

2626
def parameterized_method_defined_check(array)
27-
array.each { |v| assert ::Appium::Core::Base::Bridge::MJSONWP.method_defined?(v) }
27+
array.each do |v|
28+
assert ::Appium::Core::Base::Bridge::MJSONWP.method_defined?(v)
29+
delegate_from_appium_driver(v)
30+
end
2831
end
2932

3033
def test_with_arg_definitions

test/unit/android/device/w3c/definition_test.rb

+6-3
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,15 @@ def test_delegate_driver_method
1818
assert @driver.respond_to? :launch_app
1919
end
2020

21-
def test_delegate_from_appium_driver
22-
assert @core.send(:delegated_target_for_test).respond_to? :launch_app
21+
def delegate_from_appium_driver(key)
22+
assert @core.send(:delegated_target_for_test).respond_to? key
2323
end
2424

2525
def parameterized_method_defined_check(array)
26-
array.each { |v| assert ::Appium::Core::Base::Bridge::W3C.method_defined?(v) }
26+
array.each do |v|
27+
assert ::Appium::Core::Base::Bridge::W3C.method_defined?(v)
28+
delegate_from_appium_driver(v)
29+
end
2730
end
2831

2932
def test_with_arg_definitions

test/unit/ios/device/mjsonwp/definition_test.rb

+6-3
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,15 @@ def test_delegate_driver_method
1818
assert @driver.respond_to? :launch_app
1919
end
2020

21-
def test_delegate_from_appium_driver
22-
assert @core.send(:delegated_target_for_test).respond_to? :launch_app
21+
def delegate_from_appium_driver(key)
22+
assert @core.send(:delegated_target_for_test).respond_to? key
2323
end
2424

2525
def parameterized_method_defined_check(array)
26-
array.each { |v| assert ::Appium::Core::Base::Bridge::MJSONWP.method_defined?(v) }
26+
array.each do |v|
27+
assert ::Appium::Core::Base::Bridge::MJSONWP.method_defined?(v)
28+
delegate_from_appium_driver(v)
29+
end
2730
end
2831

2932
def test_with_arg_definitions

test/unit/ios/device/w3c/definition_test.rb

+8-1
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,15 @@ def test_delegate_from_appium_driver
2222
assert @core.send(:delegated_target_for_test).respond_to? :launch_app
2323
end
2424

25+
def delegate_from_appium_driver(key)
26+
assert @core.send(:delegated_target_for_test).respond_to? key
27+
end
28+
2529
def parameterized_method_defined_check(array)
26-
array.each { |v| assert ::Appium::Core::Base::Bridge::W3C.method_defined?(v) }
30+
array.each do |v|
31+
assert ::Appium::Core::Base::Bridge::W3C.method_defined?(v)
32+
delegate_from_appium_driver(v)
33+
end
2734
end
2835

2936
def test_with_arg_definitions

0 commit comments

Comments
 (0)