The rtv
(Runtime Variables) Library
/* @provengo summon rtv */
The Runtime Variable library allows capturing, processing, and testing values during test execution. For example, when testing a search engine, this library can be used to capture the result count of a broad search (say, "Pizza"), the result count of a narrower search (say, "pizza mentions in rock ballads"), and to validate that the broad search had more results than the narrow one.
Other usages include:
-
Using API tokens in tests - receiving one at the beginning of a test and then using it for the rest of the test.
-
Reducing test graph size, by storing data that should not affect behavior on its own. E.g. selecting a town on some initial stage, and then asserting that the system displays the same town at a later stage.
Values are typically stored by methods in actuation libraries such as the Selenium Library's store
method.
The RuntimeValues library is summoned by:
// @provengo summon rtv
Using Runtime Variables and the @{}
Syntax
Accessing runtime variables is done by enclosing the variable name in @{}
. For example, to have an event whose name is tha value of runtime variable rtv1
, use Event("@{rtv1}")
.
Any JavaScript expression can go inside @{}
. You can work with multiple variables, and even reference the top-level scope of your model, such as data stored in files at the data/
directory, or functions defined at the lib/
directory.
// ... inside a bthread, bws is a Selenium browser session
bws.store("firstName", XPATH_TO_FIRST_NAME_FIELD);
bws.store("lastName", XPATH_TO_LAST_NAME_FIELD);
bws.assertText(
"Sincerely, @{firstName[0]+'. '+lastName}",
XPATH_TO_PARTING_LINE); (1)
1 | Assert that the greeting is in the form of "Sincerely, L. Cohen". |
It is not possible to access local function variables. The reason is that these will be long gone when runtime arrives - the function runs during scenario generation, whereas test execution (runtime!) is done much later, typically after sampling the model and composing an optimal test suite. |
Example
/* @provengo summon rtv */
bthread("sample-and-check", function(){
// Generate two selenium sessions, s1 and s2.
s1.store("c1", "//*[@id='counter1']"); (1)
s2.store("c2", "//*[@id='counter2']"); (2)
rtv.assertLt("@{c1}", "@{c2}"); (3)
rtv.assertLe("@{c1}", "17"); (4)
rtv.assertEq("250", "@{c2}"); (5)
});
1 | Storing the text content of DOM element whose id is counter1 into runtime variable c1 . |
2 | Same as above, but with counter2 and c2 |
3 | Asserting that the numeric value of c1 is less then that of c2 |
4 | Asserting that the numeric value of c1 is less then or equal to 17 . |
5 | Asserting that the numeric value of c2 is equal to 250 . |
Events
rtv.anyStore(varName)
An event set containing all storage events that store values into varName
. Useful for triggering scenarios when a certain variable is updated, blocking a variable from being updated, and defining test suite goals based on which values where updated.
Does not cover indirect storage events such as methods from the Selenium package. |
varName
-
String. The name of the runtime variable we want to monitor.
Example
Below, whenever runtime variable customer_id
is updated, the on
handler prevents any updates to the variable until it completes a set of validations.
// @provengo summon rtv
on( rtv.anyStore("customer_num"), function(){
block( rtv.anyStore("customer_num"), function(){
request(Event("Validate Customer @{customer_num}"));
request(Event("Validate customer count"));
});
});
Methods
rtv.doStore(varName, value)
Stores value
into runtime variable varName
. In the typical use-case, runtime variables are updated by an automation layer, e.g. when a REST API requests a customer id from the system under test, and then uses that id during the rest of the test scenario. This command, which updates a runtime variable with a variable value from scenario generation time, serves a different use-case. It is typically used to reduce the model state space (see example) or to simulate sampled values from currently unavailable source. For example, if a certain value should be sampled from an API endpoint, but that endpoint is not implemented yet,
varName
-
String. The name of the runtime variable.
value
-
Any JavaScript value.
Example
The bthread below uses the runtime variable system to reduce states.
bthread("writer", function(){
let food = select("food").from( (1)
"Glazed Donuts",
"Jelly Donuts",
"Bavarian Cream-Filled Donuts",
"Bear Claws"
);
rtv.doStore("runtimeFood", food); (2)
food = null; (3)
request(Event("No, we're out of @{runtimeFood}")); (4)
});
-
A food type is selected using
select().from()
an is place intofood
, a regular variable. -
The value from
food
is stored into theruntimeFood
runtime variable. This is requests an event that will later appear in the test scenario, like any other event. -
The local variable is set to
null
, reducing the state space -
The runtime variable is used via the
@{}
syntax.
rtv.assertEq(expr1, expr2, errorMessage)
Asserts that the values of expr1
and expr2
are equal.
expr1
-
Number or String. May contain
@{}
expressions. expr2
-
Number or String. May contain
@{}
expressions. errorMessage
-
Optional string. The message to display if the assertion fails.
Example
Checking that both the API and the Web UI agree on an account balance. Assume browser
is a Selenium session, and api
is a REST API session.
bthread("account-balance-correct", function(){
browser.store("uiBalance", XPATH_TO_BALANCE);
api.get("/balance", {
callback: function(response, rtv){
rtv.set(apiBalance, response.body);
return true;
}
});
rtv.assertEq("@{uiBalance}", "@{apiBalance}", "Balances on API and UI do not match");
});
rtv.assertLt(expr1, expr2, errorMessage)
Asserts that the numeric value of expr1
is less than that of expr2
.
expr1
-
Number or String. May contain
@{}
expressions. expr2
-
Number or String. May contain
@{}
expressions. errorMessage
-
Optional string. The message to display if the assertion fails.
Example
Asserting that the user account balance is greater than 10.
bthread("account-balance-correct", function(){
browser.store("balance", XPATH_TO_BALANCE);
rtv.assertLt("10", "@{balance}", "User balance should be less than 10.");
});
rtv.assertLe(expr1, expr2, errorMessage)
Asserts that the numeric value of expr1
is less then or equal to that of expr2
.
expr1
-
String that may contain
@{}
expressions. expr2
-
String that may contain
@{}
expressions. errorMessage
-
Optional string. The message to display if the assertion fails.
Example
Asserting that the user account balance is greater or equals to 10.
bthread("account-balance-correct", function(){
browser.store("balance", XPATH_TO_BALANCE);
rtv.assertLt("10", "@{balance}", "User balance should be equal or grater than 10.");
});
rtv.assert(expr1, expr2, testFunction, errorMessage)
A generalized way of asserting over runtime values. Asserts that testFunction
return true
when called with expr1
and expr2
.
expr1
-
String that may contain
@{}
expressions. expr2
-
String that may contain
@{}
expressions. testFunction
-
Function. Takes two parameters (
expr1
andexpr2
), returns a boolean (true
/false
). If it returnsfalse
, the assertion fails. errorMessage
-
Optional string. The message to display if the assertion fails.
Example
Asserting that the sum of two runtime variables is even. Values were populated on different bthreads.
bthread("must be even", function(){
rtv.assert("@{varA}", "@{varB}", function(a, b){ return (a+b)%2==0; } )
});
rtv.log.LEVEL(expr)
Logs runtime values during test execution. Logging is available at four levels: fine
, info
, warn
, and error
. Messages logged to fine
are only printed when running in --verbose
mode. All messages are stored in the logs.
Useful for inspecting calculations performed and values sampled from tested system during test execution.
Evaluates the passed expression and prints it to the log at the specified logging level .
expr
-
Expression to print as log.
Example
Logging at the various levels during execution:
// Performs an API call and stores the data
bthread("performApiCall", function(){j;o
session.post("/get-quote", {
callback: function(r, rtvStore){
let body = JSON.parse(r.body);
rtvStore.set("apiKey", body.apiKey); (1)
}
});
rtv.log.info("Suggested quote is @{quote}"); (2)
rtv.log.warn("Tax on quote is @{quote*0.17}"); (3)
});
1 | Querying the system-under-test for a quote. |
2 | Logging the quote at the INFO level. |
3 | Logging an expression based on the quote at the WARN level. |
rtv.run(processFunction)
Perform general processing of runtime values. For example, can be used to extract numbers hidden in text paragraphs, to allow assertions later.
processFunction
-
Function. Receives an object that can read and write to the runtime values table, using methods
get(varName)
andset(varName, value)
.
Example
browser.store("userStats", XPATH_TO_USER_STATS) (1)
rtv.run(function(values) {
let userCount = extractUserCount(values.get("userStats") (2)
values.set("userCount", userCount); (3)
});
request(Event("User count is @{userCount}")); (4)
1 | Sample user statistics text from a web ui |
2 | Read that value, and call a function to extract user count |
3 | Store the clean number in a new runtime variable |
4 | Using the new variable value as usual. |