Resat is a script engine which allows grouping web requests into scenarios.
A scenario consists of serie of HTTP requests called steps.
Each step may be associated with guards and/or filters and/or handlers.
The syntax used to defined scenarios is simple and can be used by programmers and non-programmers alike. See the WRITING SCENARIOS section below for examples.
-
Guards keep making the same request until the response header and/or body satisfy(ies) certain conditions.
-
Filters validate the response and may save some of its elements in variables. Variables can be used to define requests, guards and filters.
-
Handlers allow writing custom code to handle a request and its response.
Scenarios are defined as YAML documents that must adhere to the Kwalify schemas defined in schemas/scenarios.yaml
. See the comments in this file for additional information.
Resat is configured through a YAML configuration file which defines default values that applies to all requests including the host name, base url, whether to use SSL, common headers and body parameters and optionally a username and password to be used with basic authentication. This configuration file is located in config/resat.yaml
by default.
There are two main use cases for resat:
-
Scripting: Resat can be used to chaing together a serie of REST API calls that can be used to perform repetitive tasks.
-
API testing: For REST API implementors, resat is the ideal automated regression tool. This is the tool we use at RightScale to test our APIs.
resat can be used as a ruby library or as an application. Using it as library involves instantiating the engine and calling the ‘run’ method:
require 'resat' options = OpenStruct.new options.verbose = false options.quiet = false options.norecursion = false options.loglevel = 'info' options.logfile = 'resat.log' options.configfile = 'config/resat.yaml' options.schemasdir = 'schemas' Resat::Log.init(options) engine = Resat::Engine.new(options) engine.run('my_scenario.yaml') if engine.succeeded? puts engine.summary.dark_blue else puts engine.summary.dark_red end puts "#{engine.requests_count} request(s)." puts "#{engine.ignored_count} scenario(s) ignored." puts "#{engine.skipped_count} YAML file(s) skipped."
See the examples and usage sections below for using resat as an application.
Run the scenario defined in scenario.yaml:
$ resat scenario.yaml
Execute scenarios defined in the ‘scenarios’ directory and its sub-directories:
$ resat scenarios
Only execute the scenarios defined in the current directory, do not execute scenarios found in sub-directories:
$ resat -n .
resat [options] target For help use: resat -h
-h, --help Display help message -v, --version Display version, then exit -q, --quiet Output as little as possible, override verbose -V, --verbose Verbose output -n, --norecursion Don't run scenarios defined in sub-directories -d, --define NAME:VAL Define global variable (can appear multiple times, escape ':' with '::') -f, --failonerror Stop resat from continuing to run if an error occurs -c, --config PATH Config file path (config/resat.yaml by default) -s, --schemasdir DIR Path to schemas directory (schemas/ by default) -l, --loglevel LVL Log level: debug, info, warn, error (info by default) -F, --logfile PATH Log file path (resat.log by default)
-
From source: run the following command from the root folder to be able to run resat from anywhere:
$ sudo ln -s `pwd`/bin/resat /usr/local/bin/resat
-
Using the gem:
$ sudo gem install resat
The source code of Resat is available via Git: github.com/raphael/resat.git Fork the project and send pull requests to contribute!
resat relies on Kwalify for validating YAML files:
$ sudo gem install kwalify
At the heart of your resat scripts are the scenarios. A scenario consists of one or more steps. A scenario may include other scenarios. A single execution of Resat can apply to multiple scenarios (all scenarios in a given folder).
A simple scenario containing a single step is defined below:
name: List all servers steps: - request: operation: index resource: servers
The first element of the scenario is its name. The name is used by the command line tool for update and error outputs.
The second element is the list of steps. A step must contain a request. A request can correspond to one of the REST CRUD operations and apply to a resource. CRUD operations are create, show, index, update, and destroy.
Operations that apply to a single resource rather than to all resources require the id element:
name: Show server 42 steps: - request: operation: show resource: servers id: 42
Resat also allows defining custom REST operations for making web requests that don’t map to a standard CRUD operation. A custom operation is defined by a type corresponding to the HTTP verb that the request should use (i.e. get
, post
, put
or delete
) and its name.
name: Twitter Timelines steps: - request: resource: statuses custom: # Use a custom operation name: public_timeline.xml # Operation name type: get # GET request
Alternatively, the path of a request can be defined manually:
name: Twitter Timeline steps: - request: path: statuses/public_timeline.xml type: get
Requests can then be followed by filters which can validate the response and/or extract elements from it.
name: Get Mephisto ServerTemplate steps: - request: operation: index resource: server_templates filters: - name: get server template href target: body validators: - field: server-templates/ec2-server-template[nickname='Mephisto all-in-one v8']/href is_empty: false extractors: - field: server-templates/ec2-server-template[nickname='Mephisto all-in-one v8']/href variable: server_template_href
Variables that are extracted from a request response can then be used for other requests, filters or guards. A variable is used using the $
sign followed by the variable name. A variable may be written to an output file if it has the save element and the configuration file defines an output file. A variable can also be exported to other scenarios that will get run in the same Resat execution (so a scenario can create resources and save their ids and a following scenario can reuse the ids to delete or update the resources).
The element to extract can be a response header or a response body field. If it is a response body field then an XPATH query is used to identity which part of the response body should be extracted.
The value to be extracted can be further defined using a regular expression with a capture block. The regular expression is applied to the field matching the XPATH associated with the extractor.
Note: Because XPATH is used to define fields in extractors and validators, only requests that return XML can be followed by filters.
name: Create Mephisto Server steps: - request: operation: create resource: servers valid_codes: - 201 params: - name: server[nickname] value: 'resat created server' - name: server[server_template_href] value: $server_template_href - name: server[deployment_href] value: $deployment_href filters: - name: validate server response target: body is_empty: true - name: extract server id target: header extractors: - field: location pattern: '.*\/(\d+)$' variable: server_id
A scenario request can also use guards. A guard identifies a response element similarly to an extractor (response header or body field identified by an XPATH and optionally a regular expression). A guard specifies a value that the element must match together with a period and a timeout that should be used to retry the request until the value matches the guard or the timeout is reached.
name: Wait until server 42 is operational steps: - request: resource: servers id: 42 operation: show guards: - target: body field: server/state pattern: 'operational' period: 10 timeout: 300 name: server operational
Finally a scenario request can include handlers. Handlers can only be included when resat is used as a library. The handler definition lists a unique name followed the corresponding ruby module name.
name: Store servers definitions steps: - request: resource: servers operation: index handlers: - name: save results module: ServersPersister
The ruby module must define a process
method which accepts two arguments:
def process(request, response)
-
request: an instance of Net::HTTPRequest corresponding to the request associated with this handler.
-
response: an instance of Net::HTTPResponse which contains the associated response.
It should also define a failures
method which can return a list of errors. The errors will get logged and optionally stop the execution of resat if the failonerror
option is set to true
.
-
Refer to the examples (github.com/raphael/resat/tree/master/examples) for fully functional and documented scenarios.
-
See the file
schemas/scenarios.yaml
(github.com/raphael/resat/blob/master/schemas/scenarios.yaml) for the complete reference on scenarios syntax.
Resat - Web scripting for the masses
- Author
-
Raphael Simon (<[email protected]>)
- Copyright
-
Copyright © 2009 RightScale, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ‘Software’), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.