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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +41 -0
  5. data/.travis.yml +11 -0
  6. data/Gemfile +8 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +210 -0
  9. data/Rakefile +20 -0
  10. data/bin/console +15 -0
  11. data/bin/setup +8 -0
  12. data/lib/libyui_client.rb +42 -0
  13. data/lib/libyui_client/actions.rb +12 -0
  14. data/lib/libyui_client/app.rb +230 -0
  15. data/lib/libyui_client/error.rb +13 -0
  16. data/lib/libyui_client/filter_extractor.rb +28 -0
  17. data/lib/libyui_client/http/http_client.rb +36 -0
  18. data/lib/libyui_client/http/response.rb +21 -0
  19. data/lib/libyui_client/http/version_controller.rb +26 -0
  20. data/lib/libyui_client/http/widget_controller.rb +54 -0
  21. data/lib/libyui_client/local_process.rb +73 -0
  22. data/lib/libyui_client/logger.rb +32 -0
  23. data/lib/libyui_client/timer.rb +20 -0
  24. data/lib/libyui_client/version.rb +6 -0
  25. data/lib/libyui_client/wait.rb +21 -0
  26. data/lib/libyui_client/waitable.rb +39 -0
  27. data/lib/libyui_client/widgets.rb +30 -0
  28. data/lib/libyui_client/widgets/bargraph.rb +62 -0
  29. data/lib/libyui_client/widgets/base.rb +114 -0
  30. data/lib/libyui_client/widgets/button.rb +33 -0
  31. data/lib/libyui_client/widgets/checkbox.rb +53 -0
  32. data/lib/libyui_client/widgets/combobox.rb +95 -0
  33. data/lib/libyui_client/widgets/datefield.rb +47 -0
  34. data/lib/libyui_client/widgets/label.rb +41 -0
  35. data/lib/libyui_client/widgets/menubutton.rb +48 -0
  36. data/lib/libyui_client/widgets/multilinebox.rb +84 -0
  37. data/lib/libyui_client/widgets/numberbox.rb +76 -0
  38. data/lib/libyui_client/widgets/progressbar.rb +45 -0
  39. data/lib/libyui_client/widgets/radiobutton.rb +35 -0
  40. data/lib/libyui_client/widgets/richtext.rb +36 -0
  41. data/lib/libyui_client/widgets/selectionbox.rb +87 -0
  42. data/lib/libyui_client/widgets/tab.rb +81 -0
  43. data/lib/libyui_client/widgets/table.rb +154 -0
  44. data/lib/libyui_client/widgets/textbox.rb +94 -0
  45. data/lib/libyui_client/widgets/timefield.rb +45 -0
  46. data/lib/libyui_client/widgets/tree.rb +149 -0
  47. data/libyui_client.gemspec +44 -0
  48. 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,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LibyuiClient
4
+ VERSION = '0.4.0'
5
+ API_VERSION = 'v1'
6
+ 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