Hackweek 16 (0x10)

I spent the previous Hackweek with Cucumber and YaST. It worked quite well but it turned out to be too big step forward and focused on one specific testing framework.

This time I decided to make a smaller step, but make the solution more generic. So instead of having direct Cucumber support in the UI I proposed implementing a generic REST API. The API can be then used by any framework (Cucumber, RSpec…) or it can be used directly by any script or tool.

See also the Hackweek project page.

Implementation

The current solution is based on these two key libraries:

Both libraries are already included in the openSUSE distributions so it is easy to use them.

Installation

After compiling and installing new libyui, libyui-qt and recompiling yast2-ycp-ui-bindings (because of ABI changes) you can run any YaST module with YUI_HTTP_SERVER=14155 environment variable set. Then you can access the REST API via the port 14115 using curl command. (You can use a different port number.)

Using the REST API

Run the YaST repository manager (as root):

YUI_HTTP_PORT=14155 yast2 repositories

Embedded help

This will start the YaST module as usually, there is no visible change. You can normally use the module, the only change is that the UI now listens on the port 14155 for incoming HTTP requests.

You can access the REST API (root not required) from command line like this:

> # dump the current dialog as a tree
> curl 'http://localhost:14155/dialog'
{
  "class" : "YDialog",
  "hstretch" : true,
  "type" : "wizard",
  "vstretch" : true,
  "widgets" : 
  [
    {
      "class" : "YWizard",
      "debug_label" : "Configured Software Repositories",
      "hstretch" : true,
      "id" : "wizard",
      "vstretch" : true,
      "widgets" : 
      [
...
> # dump the current dialog as a simple list (easier iteration, search)
> curl 'http://localhost:14155/widgets'
[
  {
    "class" : "YDialog",
    "hstretch" : true,
    "type" : "wizard",
    "vstretch" : true
  },
  {
    "class" : "YWizard",
    "debug_label" : "Configured Software Repositories",
    "hstretch" : true,
    "id" : "wizard",
    "vstretch" : true
  },
...
> # filter widgets by ID
> curl 'http://localhost:14155/widgets?id=enable'
[
  {
    "class" : "YCheckBox",
    "debug_label" : "Enabled",
    "id" : "enable",
    "label" : "E&nabled",
    "notify" : true,
    "value" : true
  }
]
> # filter widgets by label (as displayed, might be translated!)
> curl 'http://localhost:14155/widgets?label=Priority'
[
  {
    "class" : "YIntField",
    "debug_label" : "Priority",
    "hstretch" : true,
    "id" : "priority",
    "label" : "&Priority",
    "max_value" : 200,
    "min_value" : 0,
    "notify" : true,
    "value" : 99
  }
]
> # filter widgets by type (all check boxes in this case)
> curl 'http://localhost:14155/widgets?type=YCheckBox'
[
  {
    "class" : "YCheckBox",
    "debug_label" : "Enabled",
    "id" : "enable",
    "label" : "E&nabled",
    "notify" : true,
    "value" : true
  },
  {
    "class" : "YCheckBox",
    "debug_label" : "Automatically Refresh",
    "id" : "autorefresh",
    "label" : "Automatically &Refresh",
    "notify" : true,
    "value" : true
  },
  {
    "class" : "YCheckBox",
    "debug_label" : "Keep Downloaded Packages",
    "id" : "keeppackages",
    "label" : "&Keep Downloaded Packages",
    "notify" : true,
    "value" : false
  }
]
> # and finally do some action - press the [Abort] button and close the module,
> # you can use the same filters as in the queries above
> curl -X POST 'http://localhost:14155/widgets?id=abort&action=press'

To make it cool I have included some inline documentation or help, just open http://localhost:14155 URL in your browser:

Embedded help

Running a Cucumber Test

I have also written a simple set of step definitions for the Cucumber framework. This allows using the REST API in Cucumber integration tests.

Let’s have this example Cucumber test:

Feature: To install the 3rd party packages I must be able to add a new package
  repository to the package management.

  Background:

    # make sure the tested repository does not already exist
    When I run `zypper repos --uri`
    Then the output should not contain "https://download.opensuse.org/tumbleweed/repo/oss/"

  Scenario: Aborting the repository manager keeps the old settings

    Given I start the "/usr/sbin/yast2 repositories" application
    Then the dialog heading should be "Configured Software Repositories"

    When I click button "Add"
    Then the dialog heading should be "Add On Product"

    When I click button "Next"
    Then the dialog heading should be "Repository URL"

    When I enter "Tumbleweed OSS" into input field "Repository Name"
    And I enter "https://download.opensuse.org/tumbleweed/repo/oss/" into input field "URL"
    And I click button "Next"
    Then the dialog heading should be "Tumbleweed OSS License Agreement" in 60 seconds

    When I click button "Next"
    Then the dialog heading should be "Configured Software Repositories"

    When I click button "Cancel"
    Then a popup should be displayed
    And a label including "Abort the repository configuration?" should be displayed

    Then I click button "Yes"
    And I wait for the application to finish

    # verify that the tested repository was NOT added
    When I run `zypper repos --uri`
    Then the output should not contain "https://download.opensuse.org/tumbleweed/repo/oss/"

If you run it the result would look like this:

Adding a Repository in YaST

Pretty nice isn’t it?

Generic Usage

Because the REST API is implemented on the UI (libyui) level it means that any application using the library can be tested the same way, it is not limited only to YaST. For example it should be very easy to write tests also for the Mageia tools included in the Megeia distribution.

TODO

There are lots of missing features, mainly:

  • Return more details in the response, add missing attributes
  • Support more actions besides clicking a button or filling an InputField
  • Support more UI calls (e.g. UI.PollInput)
  • Support the other UIs (ncurses, maybe GTK)
  • Support for the package manager widget (implemented in the libyui-*-pkg subpackages)
  • Packaging - make the feature optional (if you do not want to include the web server for security reasons)
  • Add more step definitions in the Cucumber wrapper

Optional Features

  • User authentication (unauthenticated access is a security hole, but it might be OK when running tests in a sandboxed environment in a trusted network)
  • SSL support (securely transfer the current values and set the new values, e.g. the root password)
  • IPv6 support (it’s the future, isn’t it?)
  • Unix socket support (another kind of user authorization)

Conclusion

The feature is far from being production ready, it is still rather a proof of concept. But it shows that it works well and we can continue in this direction in the future.