libyui_client 0.4.0
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.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +3 -0
- data/.rubocop.yml +41 -0
- data/.travis.yml +11 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +21 -0
- data/README.md +210 -0
- data/Rakefile +20 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/lib/libyui_client.rb +42 -0
- data/lib/libyui_client/actions.rb +12 -0
- data/lib/libyui_client/app.rb +230 -0
- data/lib/libyui_client/error.rb +13 -0
- data/lib/libyui_client/filter_extractor.rb +28 -0
- data/lib/libyui_client/http/http_client.rb +36 -0
- data/lib/libyui_client/http/response.rb +21 -0
- data/lib/libyui_client/http/version_controller.rb +26 -0
- data/lib/libyui_client/http/widget_controller.rb +54 -0
- data/lib/libyui_client/local_process.rb +73 -0
- data/lib/libyui_client/logger.rb +32 -0
- data/lib/libyui_client/timer.rb +20 -0
- data/lib/libyui_client/version.rb +6 -0
- data/lib/libyui_client/wait.rb +21 -0
- data/lib/libyui_client/waitable.rb +39 -0
- data/lib/libyui_client/widgets.rb +30 -0
- data/lib/libyui_client/widgets/bargraph.rb +62 -0
- data/lib/libyui_client/widgets/base.rb +114 -0
- data/lib/libyui_client/widgets/button.rb +33 -0
- data/lib/libyui_client/widgets/checkbox.rb +53 -0
- data/lib/libyui_client/widgets/combobox.rb +95 -0
- data/lib/libyui_client/widgets/datefield.rb +47 -0
- data/lib/libyui_client/widgets/label.rb +41 -0
- data/lib/libyui_client/widgets/menubutton.rb +48 -0
- data/lib/libyui_client/widgets/multilinebox.rb +84 -0
- data/lib/libyui_client/widgets/numberbox.rb +76 -0
- data/lib/libyui_client/widgets/progressbar.rb +45 -0
- data/lib/libyui_client/widgets/radiobutton.rb +35 -0
- data/lib/libyui_client/widgets/richtext.rb +36 -0
- data/lib/libyui_client/widgets/selectionbox.rb +87 -0
- data/lib/libyui_client/widgets/tab.rb +81 -0
- data/lib/libyui_client/widgets/table.rb +154 -0
- data/lib/libyui_client/widgets/textbox.rb +94 -0
- data/lib/libyui_client/widgets/timefield.rb +45 -0
- data/lib/libyui_client/widgets/tree.rb +149 -0
- data/libyui_client.gemspec +44 -0
- metadata +135 -0
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LibyuiClient
|
4
|
+
module Error
|
5
|
+
class LibyuiClientError < StandardError; end
|
6
|
+
|
7
|
+
class TimeoutError < LibyuiClientError; end
|
8
|
+
|
9
|
+
class WidgetNotFoundError < LibyuiClientError; end
|
10
|
+
|
11
|
+
class ItemNotFoundInWidgetError < LibyuiClientError; end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LibyuiClient
|
4
|
+
class FilterExtractor
|
5
|
+
attr_reader :full, :plain, :regex
|
6
|
+
|
7
|
+
def initialize(filter)
|
8
|
+
@full = build_filters(filter)
|
9
|
+
@plain = @full.reject { |_, v| v.is_a? Regexp }
|
10
|
+
@regex = @full.select { |_, v| v.is_a? Regexp }
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
full.to_s
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def build_filters(hash)
|
20
|
+
filter = {}
|
21
|
+
filter[:id] = hash[:id]
|
22
|
+
# Replace '&' in label filter as search is not possible when it contains the character
|
23
|
+
filter[:label] = hash[:label].is_a?(String) ? hash[:label].tr('&', '') : hash[:label]
|
24
|
+
filter[:type] = hash[:class] if hash.key?(:class)
|
25
|
+
filter.compact
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LibyuiClient
|
4
|
+
module Http
|
5
|
+
module HttpClient
|
6
|
+
module_function
|
7
|
+
|
8
|
+
def http_get(uri)
|
9
|
+
LibyuiClient.logger.debug("Request: [GET] #{uri}")
|
10
|
+
res = Net::HTTP.get_response(uri)
|
11
|
+
LibyuiClient.logger.debug("Response: [#{res.code}]\n#{res.body}")
|
12
|
+
res
|
13
|
+
end
|
14
|
+
|
15
|
+
def http_post(uri)
|
16
|
+
LibyuiClient.logger.debug("Request: [POST] #{uri}")
|
17
|
+
# a trick how to add query parameters to a POST request,
|
18
|
+
# the usual Net::HTTP.post(uri, data) does not allow using a query
|
19
|
+
req = Net::HTTP::Post.new("#{uri.path}?#{uri.query}")
|
20
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
21
|
+
res = http.request(req)
|
22
|
+
LibyuiClient.logger.debug("Response: [#{res.code}]\n#{res.body}")
|
23
|
+
res
|
24
|
+
end
|
25
|
+
|
26
|
+
def compose_uri(host, port, path, params = {})
|
27
|
+
URI::HTTP.build(
|
28
|
+
host: host,
|
29
|
+
port: port,
|
30
|
+
path: path,
|
31
|
+
query: URI.encode_www_form(params)
|
32
|
+
)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LibyuiClient
|
4
|
+
module Http
|
5
|
+
class Response
|
6
|
+
def initialize(res)
|
7
|
+
@res = res
|
8
|
+
end
|
9
|
+
|
10
|
+
def body(regex_filter: {})
|
11
|
+
result = JSON.parse(@res.body, symbolize_names: true)
|
12
|
+
result.select do |widget|
|
13
|
+
regex_filter.all? { |key, value| value.match(widget[key.to_sym]) }
|
14
|
+
end
|
15
|
+
rescue JSON::ParserError => e
|
16
|
+
LibyuiClient.logger.error("Error while parsing JSON from response:\n"\
|
17
|
+
"#{e.message}\n#{e.backtrace.inspect}")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LibyuiClient
|
4
|
+
module Http
|
5
|
+
class VersionController
|
6
|
+
def initialize(host:, port:)
|
7
|
+
@host = host
|
8
|
+
@port = port
|
9
|
+
@timeout = LibyuiClient.timeout
|
10
|
+
@interval = LibyuiClient.interval
|
11
|
+
end
|
12
|
+
|
13
|
+
# Gets server api version, so one could compare compatibility and detect
|
14
|
+
# if newer version was deployed
|
15
|
+
# @return server side REST API version
|
16
|
+
def api_version
|
17
|
+
Wait.until(timeout: @timeout, interval: @interval) do
|
18
|
+
res = HttpClient.http_get(HttpClient.compose_uri(@host, @port, '/version'))
|
19
|
+
raise Error::LibyuiClientError unless res.is_a?(Net::HTTPOK)
|
20
|
+
|
21
|
+
JSON.parse(res.body)['api_version']
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LibyuiClient
|
4
|
+
module Http
|
5
|
+
class WidgetController
|
6
|
+
def initialize(host:, port:)
|
7
|
+
@host = host
|
8
|
+
@port = port
|
9
|
+
@timeout = LibyuiClient.timeout
|
10
|
+
@interval = LibyuiClient.interval
|
11
|
+
end
|
12
|
+
|
13
|
+
# Find a widget using the filter.
|
14
|
+
# @param filter [Hash] identifiers to find a widget
|
15
|
+
# @return [Response]
|
16
|
+
def find(filter)
|
17
|
+
res = nil
|
18
|
+
Wait.until(timeout: @timeout, interval: @interval) do
|
19
|
+
uri = HttpClient.compose_uri(@host, @port, "/#{API_VERSION}/widgets", filter)
|
20
|
+
res = HttpClient.http_get(uri)
|
21
|
+
Response.new(res) if res.is_a?(Net::HTTPOK)
|
22
|
+
end
|
23
|
+
rescue Error::TimeoutError
|
24
|
+
rescue_errors(res)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Perform an action on the widget.
|
28
|
+
# @param filter [Hash] identifiers to find a widget
|
29
|
+
# @param action [Hash] what to do with the widget
|
30
|
+
# @return [Response]
|
31
|
+
def send_action(filter, action)
|
32
|
+
res = nil
|
33
|
+
Wait.until(timeout: @timeout, interval: @interval) do
|
34
|
+
uri = HttpClient.compose_uri(@host, @port, "/#{API_VERSION}/widgets",
|
35
|
+
filter.merge(action))
|
36
|
+
res = HttpClient.http_post(uri)
|
37
|
+
Response.new(res) if res.is_a?(Net::HTTPOK)
|
38
|
+
end
|
39
|
+
rescue Error::TimeoutError
|
40
|
+
rescue_errors(res)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def rescue_errors(response)
|
46
|
+
raise Error::WidgetNotFoundError if response.is_a?(Net::HTTPNotFound)
|
47
|
+
|
48
|
+
raise Error::ItemNotFoundInWidgetError if response.is_a?(Net::HTTPUnprocessableEntity)
|
49
|
+
|
50
|
+
raise Error::LibyuiClientError
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Client to interact with YAST UI rest api framework for integration testing
|
4
|
+
module LibyuiClient
|
5
|
+
class LocalProcess
|
6
|
+
# default timeout for process
|
7
|
+
DEFAULT_TIMEOUT_PROCESS = 2
|
8
|
+
|
9
|
+
# start the application in background
|
10
|
+
# @param application [String] the command to start
|
11
|
+
def start_app(application)
|
12
|
+
@app_host = 'localhost'
|
13
|
+
@app_port = port
|
14
|
+
|
15
|
+
# another app already running?
|
16
|
+
raise "The port #{@app_host}:#{@app_port} is already open!" if port_open?(@app_host, @app_port)
|
17
|
+
|
18
|
+
LibyuiClient.logger.debug("Starting #{application}...")
|
19
|
+
# create a new process group so easily we will be able
|
20
|
+
# to kill all its sub-processes
|
21
|
+
@app_pid = spawn(application, pgroup: true)
|
22
|
+
wait_for_port(@app_host, @app_port)
|
23
|
+
LibyuiClient.logger.debug("App started: '#{application}'")
|
24
|
+
end
|
25
|
+
|
26
|
+
# kill the process if it is still running after finishing a scenario
|
27
|
+
def kill_app
|
28
|
+
return unless @app_pid
|
29
|
+
|
30
|
+
Process.waitpid(@app_pid, Process::WNOHANG)
|
31
|
+
LibyuiClient.logger.debug("Sending KILL signal for PID #{@app_pid}")
|
32
|
+
Process.kill('-KILL', @app_pid)
|
33
|
+
rescue Errno::ECHILD, Errno::ESRCH
|
34
|
+
# the process has already exited
|
35
|
+
@app_pid = nil
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
# set the application introspection port for communication
|
41
|
+
def port
|
42
|
+
ENV['YUI_HTTP_PORT'] ||= '9999'
|
43
|
+
end
|
44
|
+
|
45
|
+
# is the target port open?
|
46
|
+
# @param host [String] the host to connect to
|
47
|
+
# @param port [Integer] the port number
|
48
|
+
# @return [Boolean] true if the port is open, false otherwise
|
49
|
+
def port_open?(host, port)
|
50
|
+
TCPSocket.new(host, port).close
|
51
|
+
true
|
52
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
|
53
|
+
false
|
54
|
+
end
|
55
|
+
|
56
|
+
# wait until the specified port is open or until the timeout is reached
|
57
|
+
# @param host [String] the host to connect to
|
58
|
+
# @param port [Integer] the port number
|
59
|
+
# @raise LibyuiClient::Error::TimeoutError if the port is not opened in time
|
60
|
+
def wait_for_port(host, port)
|
61
|
+
Wait.until(timeout: LibyuiClient.timeout, interval: LibyuiClient.interval) do
|
62
|
+
LibyuiClient.logger.debug("Waiting for #{host}:#{port}...")
|
63
|
+
port_open?(host, port)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# optionally allow a short delay between the steps to watch the UI changes
|
68
|
+
def add_step_delay
|
69
|
+
delay = ENV['STEP_DELAY'].to_f
|
70
|
+
sleep(delay) if delay.positive?
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
require 'logger'
|
5
|
+
|
6
|
+
module LibyuiClient
|
7
|
+
class Logger
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
def_delegators :@logger, :debug, :debug?,
|
11
|
+
:info, :info?,
|
12
|
+
:warn, :warn?,
|
13
|
+
:error, :error?,
|
14
|
+
:fatal, :fatal?,
|
15
|
+
:level
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@logger = create_logger($stdout)
|
19
|
+
end
|
20
|
+
|
21
|
+
def create_logger(output)
|
22
|
+
logger = ::Logger.new(output)
|
23
|
+
logger.progname = 'LibyuiClient'
|
24
|
+
logger.level = default_level
|
25
|
+
logger
|
26
|
+
end
|
27
|
+
|
28
|
+
def default_level
|
29
|
+
ENV['DEBUG'] ? :debug : :info
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LibyuiClient
|
4
|
+
module Timer
|
5
|
+
module_function
|
6
|
+
|
7
|
+
# Execute code block with the interval until timeout reached.
|
8
|
+
# @param timeout [Numeric] how many time in seconds to wait
|
9
|
+
# @param interval [Numeric] time in seconds between attempts
|
10
|
+
def wait(timeout, interval)
|
11
|
+
return yield if timeout.zero?
|
12
|
+
|
13
|
+
end_time = Time.now.to_f + timeout
|
14
|
+
while Time.now.to_f <= end_time
|
15
|
+
yield
|
16
|
+
sleep interval
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LibyuiClient
|
4
|
+
module Wait
|
5
|
+
def self.until(timeout:, interval:, message: nil, object: nil)
|
6
|
+
Timer.wait(timeout, interval) do
|
7
|
+
result = yield(object)
|
8
|
+
return result if result
|
9
|
+
end
|
10
|
+
raise Error::TimeoutError, "timed out after #{timeout} seconds. #{message}"
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.while(timeout:, interval:, message: nil, object: nil)
|
14
|
+
Timer.wait(timeout, interval) do
|
15
|
+
result = yield(object)
|
16
|
+
return unless result
|
17
|
+
end
|
18
|
+
raise Error::TimeoutError, "timed out after #{timeout} seconds. #{message}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LibyuiClient
|
4
|
+
module Waitable
|
5
|
+
# Waits until the block evaluation will return true, raises Error::TimeoutError on timeout.
|
6
|
+
# @param timeout [Numeric] how long to wait (in seconds). Default is LibyuiClient.timeout.
|
7
|
+
# @param interval [Numeric] time in seconds between attempts. Default is LibyuiClient.interval.
|
8
|
+
# @param message [String] message to be sent in case timeout is reached.
|
9
|
+
# @raise Error::TimeoutError
|
10
|
+
# @return [Waitable] waitable object
|
11
|
+
# @example Wait for checkbox to be checked
|
12
|
+
# app.checkbox(id: 'test').wait_until(&:checked?)
|
13
|
+
def wait_until(timeout: LibyuiClient.timeout, interval: LibyuiClient.interval, message: nil, &block)
|
14
|
+
@timeout = timeout
|
15
|
+
@interval = interval
|
16
|
+
Wait.until(timeout: timeout, interval: interval, message: message, object: self, &block)
|
17
|
+
@timeout = LibyuiClient.timeout
|
18
|
+
@interval = LibyuiClient.interval
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
# Waits while the block evaluation returns true, raises Error::TimeoutError on timeout.
|
23
|
+
# @param timeout [Numeric] how long to wait (in seconds). Default is LibyuiClient.timeout.
|
24
|
+
# @param interval [Numeric] time in seconds between attempts. Default is LibyuiClient.interval.
|
25
|
+
# @param message [String] message to be sent in case timeout is reached.
|
26
|
+
# @raise Error::TimeoutError
|
27
|
+
# @return [Waitable] waitable object
|
28
|
+
# @example Wait for checkbox to be unchecked
|
29
|
+
# app.checkbox(id: 'test').wait_while(&:checked?)
|
30
|
+
def wait_while(timeout: LibyuiClient.timeout, interval: LibyuiClient.interval, message: nil, &block)
|
31
|
+
@timeout = timeout
|
32
|
+
@interval = interval
|
33
|
+
Wait.while(timeout: timeout, interval: interval, message: message, object: self, &block)
|
34
|
+
@timeout = LibyuiClient.timeout
|
35
|
+
@interval = LibyuiClient.interval
|
36
|
+
self
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LibyuiClient
|
4
|
+
module Widgets
|
5
|
+
require 'libyui_client/waitable'
|
6
|
+
require 'libyui_client/widgets/base'
|
7
|
+
require 'libyui_client/widgets/bargraph'
|
8
|
+
require 'libyui_client/widgets/button'
|
9
|
+
require 'libyui_client/widgets/checkbox'
|
10
|
+
require 'libyui_client/widgets/combobox'
|
11
|
+
require 'libyui_client/widgets/datefield'
|
12
|
+
require 'libyui_client/widgets/label'
|
13
|
+
require 'libyui_client/widgets/menubutton'
|
14
|
+
require 'libyui_client/widgets/multilinebox'
|
15
|
+
require 'libyui_client/widgets/numberbox'
|
16
|
+
require 'libyui_client/widgets/progressbar'
|
17
|
+
require 'libyui_client/widgets/radiobutton'
|
18
|
+
require 'libyui_client/widgets/selectionbox'
|
19
|
+
require 'libyui_client/widgets/tab'
|
20
|
+
require 'libyui_client/widgets/table'
|
21
|
+
require 'libyui_client/widgets/textbox'
|
22
|
+
require 'libyui_client/widgets/timefield'
|
23
|
+
require 'libyui_client/widgets/label'
|
24
|
+
require 'libyui_client/widgets/richtext'
|
25
|
+
require 'libyui_client/widgets/tree'
|
26
|
+
# Class representing a Wizard UI. It can be YWizard
|
27
|
+
class Wizard < Widgets::Base
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LibyuiClient
|
4
|
+
module Widgets
|
5
|
+
# Class representing a bar graph in the UI, namely YBarGraph.
|
6
|
+
class Bargraph < Widgets::Base
|
7
|
+
# Returns the list of segments in the bar graph. Each segment is defined by
|
8
|
+
# label and value. Segment length is defined by proportional ratio of the
|
9
|
+
# segment value. For instance, if segment values are 100, 200, 700: it means
|
10
|
+
# first segment will take 100/1000=10%, second 20% and third one 70% accordingly.
|
11
|
+
# @return [Array] array of [Hash] objects with :label and :value keys which
|
12
|
+
# store label and segment value for given segment
|
13
|
+
# @example Get segments from the bargraph with id "test_id"
|
14
|
+
# {
|
15
|
+
# "class" : "YBarGraph",
|
16
|
+
# "id" : "test_id",
|
17
|
+
# "segments" :
|
18
|
+
# [
|
19
|
+
# {
|
20
|
+
# "label" : "Windows\nused\n%1 MB",
|
21
|
+
# "value" : 600
|
22
|
+
# },
|
23
|
+
# {
|
24
|
+
# "label" : "Windows\nfree\n%1 MB",
|
25
|
+
# "value" : 350
|
26
|
+
# },
|
27
|
+
# {
|
28
|
+
# "label" : "Linux\n%1 MB",
|
29
|
+
# "value" : 800
|
30
|
+
# }
|
31
|
+
# ],
|
32
|
+
# }
|
33
|
+
# @example
|
34
|
+
# app.bargraph(id: 'test_id').segments
|
35
|
+
# @example
|
36
|
+
# app.bargraph(id: 'test_id').segments[1][:value]
|
37
|
+
# @example
|
38
|
+
# app.bargraph(id: 'test_id').segments[1][:label]
|
39
|
+
def segments
|
40
|
+
property(:segments)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns the labels list of all segments in the bar graph.
|
44
|
+
# @return [Array] array of labels in order they are stored in segments hash.
|
45
|
+
# @example Get labels of the segments in the bargraph with id "test_id"
|
46
|
+
# @example
|
47
|
+
# app.bargraph(id: 'test_id').labels
|
48
|
+
def labels
|
49
|
+
segments.map { |segment| segment[:label] }
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns the values list of all segments in the bar graph.
|
53
|
+
# @return [Array] array of values in order they are stored in segments hash.
|
54
|
+
# @example Get values of the segments in the bargraph with id "test_id"
|
55
|
+
# @example
|
56
|
+
# app.bargraph(id: 'test_id').values
|
57
|
+
def values
|
58
|
+
segments.map { |segment| segment[:value] }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|