Taurus allows me combine scenarios like this
scenarios:
create-bob-account:
requests:
- url: http://example.com/account/create
method: POST
body:
account_owner: bob
account_number: 123
create-lisa-account:
requests:
- url: http://example.com/account/create
method: POST
body:
account_owner: lisa
account_number: 321
account-transfer:
requests:
- include-scenario: create-bob-account
- include-scenario: create-bob-account
- url: http://example.com/transfer
method: POST
body:
account_number_from: 123
account_number_to: 321
...
In this example i have duplicate code that creates info about accout bob and lisa. Can i parametrize an create-account scenario? I.e. create script
scenarios:
create-account:
requests:
- url: http://example.com/account/create
method: POST
body:
account_owner: ${owner}
account_number: ${number}
and call it with different variables, something like this
scenarios:
account-transfer:
requests:
- include-scenario:
arugment: bob, 123
create-account
- include-scenario:
arugment: lisa, 321
create-account
- url: http://example.com/transfer
Maybe there is some way to implement this functionality?
The basic idea is to identify scripts that are independent of specific variables and reuse them in various scripts with the variables that are needed.
Thanks!
How about CSV Data Set Config? This way you can put your account names and IDs into a CSV file and include the request block to read these name/id combination from the CSV file, the relevant Taurus directives are data-sources and include-scenarios
Example YAML config:
execution:
- scenario: account-transfer
scenarios:
account-create:
data-sources:
- details.csv
requests:
- url: http://example.com/account/create
method: POST
body:
account_owner: ${account_owner}
account_number: ${account_number}
account-transfer:
requests:
- include-scenario: account-create
- include-scenario: account-create
- url: http://example.com/transfer
method: POST
body:
account_number_from: 123
account_number_to: 321
Related
When calling http endpoint in GCP workflow, only HttpStatus 200 is considered a success.
How to handle other Success Status codes? 201, 202, etc.
Example workflow from samples:
- readItem:
try:
call: http.get
args:
url: https://example.com/someapi
auth:
type: OIDC
result: APIResponse
except:
as: e
steps:
- knownErrors:
switch:
- condition: ${not("HttpError" in e.tags)}
next: connectionProblem
- condition: ${e.code == 404}
next: urlNotFound
- condition: ${e.code == 403}
next: authProblem
- UnhandledException:
raise: ${e}
- urlFound:
return: ${APIResponse.body}
- connectionProblem:
return: "Connection problem; check URL"
- urlNotFound:
return: "Sorry, URL wasn't found"
- authProblem:
return: "Authentication error"
If the api endpoint https://example.com/someapi returns anything other than a 200 status code the connectionProblem is invoked.
This is the same if its a GET or POST request.
What is the best way of handling this?
There is nothing referencing how to handle other 200s statuses in the documentation for Google Workflows so I assume this is not possible without treating them as errors.
This means that in order to do it you are going to need to add an extra step to deal with this status as an error handling strategy, like - condition: ${e.code == 201} for example.
Alternatively you could open a feature request in Google's Issue Tracker so that they can consider implementing different treatements of such status codes or at least so that this is touched on more details in the documentation.
At present response codes >= 400 and <= 599 are considered an error and will raise an exception. i.e. 200s are considered a success and will not.
Alternatively, if you want to trigger an exception handler for return codes in this range (or for any other reason), this can be done by adding an additional step to the try call, for example (illustration only):
main:
steps:
- getStuff:
try:
steps:
- callStep:
call: http.get
args:
url: <SOME URL>
result: r
- checkNotOK:
switch:
- condition: ${r.code == 202}
raise: ${r}
retry:
predicate: ${custom_predicate}
max_retries: 5
backoff:
initial_delay: 2
max_delay: 60
multiplier: 2
custom_predicate:
params: [e]
steps:
- what_to_repeat:
switch:
- condition: ${e.code == 202}
return: true
- otherwise:
return: false
I am testing google workflow and would like to call a workflow from another workflow but as a separate process (not a subworkflow)
I am able to start the execution but currently unable to retrieve the return value. I receive instead an instance of the execution:
{
"argument": "null",
"name": "projects/xxxxxxxxxxxx/locations/us-central1/workflows/child-workflow/executions/9fb4aa01-2585-42e7-a79f-cfb4b57b22d4",
"startTime": "2020-12-09T01:38:07.073406981Z",
"state": "ACTIVE",
"workflowRevisionId": "000003-cf3"
}
parent-workflow.yaml
main:
params: [args]
steps:
- callChild:
call: http.post
args:
url: 'https://workflowexecutions.googleapis.com/v1beta/projects/my-project/locations/us-central1/workflows/child-workflow/executions'
auth:
type: OAuth2
scope: 'https://www.googleapis.com/auth/cloud-platform'
result: callresult
- returnValue:
return: ${callresult.body}
child-workflow.yaml:
- getCurrentTime:
call: http.get
args:
url: https://us-central1-workflowsample.cloudfunctions.net/datetime
result: CurrentDateTime
- readWikipedia:
call: http.get
args:
url: https://en.wikipedia.org/w/api.php
query:
action: opensearch
search: ${CurrentDateTime.body.dayOfTheWeek}
result: WikiResult
- returnOutput:
return: ${WikiResult.body[1]}
also as an added question how can create a dynamic url from a variable. ${} doesn't seem to work there
As Executions are async API calls, you need to POLL for the workflow to see when finished.
You can have the following algorithm:
main:
steps:
- callChild:
call: http.post
args:
url: ${"https://workflowexecutions.googleapis.com/v1beta/projects/"+sys.get_env("GOOGLE_CLOUD_PROJECT_ID")+"/locations/us-central1/workflows/http_bitly_secrets/executions"}
auth:
type: OAuth2
scope: 'https://www.googleapis.com/auth/cloud-platform'
result: workflow
- waitExecution:
call: CloudWorkflowsWaitExecution
args:
execution: ${workflow.body.name}
result: workflow
- returnValue:
return: ${workflow}
CloudWorkflowsWaitExecution:
params: [execution]
steps:
- init:
assign:
- i: 0
- valid_states: ["ACTIVE","STATE_UNSPECIFIED"]
- result:
state: ACTIVE
- check_condition:
switch:
- condition: ${result.state in valid_states AND i<100}
next: iterate
next: exit_loop
- iterate:
steps:
- sleep:
call: sys.sleep
args:
seconds: 10
- process_item:
call: http.get
args:
url: ${"https://workflowexecutions.googleapis.com/v1beta/"+execution}
auth:
type: OAuth2
result: result
- assign_loop:
assign:
- i: ${i+1}
- result: ${result.body}
next: check_condition
- exit_loop:
return: ${result}
What you see here is that we have a CloudWorkflowsWaitExecution subworkflow which will loop 100 times at most, also has a 10 second delay, it will stop when the workflow has finished, and returns the result.
The output is:
argument: 'null'
endTime: '2020-12-09T13:00:11.099830035Z'
name: projects/985596417983/locations/us-central1/workflows/call_another_workflow/executions/05eeefb5-60bb-4b20-84bd-29f6338fa66b
result: '{"argument":"null","endTime":"2020-12-09T13:00:00.976951808Z","name":"projects/985596417983/locations/us-central1/workflows/http_bitly_secrets/executions/2f4b749c-4283-4c6b-b5c6-e04bbcd57230","result":"{\"archived\":false,\"created_at\":\"2020-10-17T11:12:31+0000\",\"custom_bitlinks\":[],\"deeplinks\":[],\"id\":\"j.mp/2SZaSQK\",\"link\":\"//<edited>/2SZaSQK\",\"long_url\":\"https://cloud.google.com/blog\",\"references\":{\"group\":\"https://api-ssl.bitly.com/v4/groups/Bg7eeADYBa9\"},\"tags\":[]}","startTime":"2020-12-09T13:00:00.577579042Z","state":"SUCCEEDED","workflowRevisionId":"000001-478"}'
startTime: '2020-12-09T13:00:00.353800247Z'
state: SUCCEEDED
workflowRevisionId: 000012-cb8
in the result there is a subkey that holds the results from the external Workflow execution.
The best method is now the workflows.executions.run helper method, which formats the request and blocks until the workflow execution has completed:
- run_execution:
try:
call: googleapis.workflowexecutions.v1.projects.locations.workflows.executions.run
args:
workflow_id: ${workflow}
location: ${location} # Defaults to current location
project_id: ${project} # Defaults to current project
argument: ${arguments} # Arguments could be specified inline as a map instead.
result: r1
except:
as: e
steps: ... # handle a failed execution
I've faced a problem using Taurus. Could someone help me, please? I'm trying to simulate 300 users but before sending those 300 users POST requests, I need to generate a token. The token is attached to the request in that way:
- url: http://url?user_token=${auth_token}
Now I have the following scenario:
load_api:
requests:
- once:
- url: https://endpoint/authenticateUser
method: POST
headers:
Content-Type: application/json
body:
username: username
password: pass
generateToken: true
extract-jsonpath:
auth_token:
jsonpath: $.token
label: get_token
- url: http://url/user_token=${auth_token}
method: POST
headers:
Content-Type: application/json
body-file: test_data/body.json
label: sending_300
As you can see, a token is generated for each thread. And I need that it is generated before the script and then the token is attached to the URL as a parameter. I've tried to separate those for two scenarios but in that way variables from one script can't be used in the other. I was also looking at global variables but it seems like that kind of variable can be created only before executing. So, if someone could help me, I'll appreciate your time spent.
EDIT (thank you so much Dmitri T):
Here is a workable script:
execution:
concurrency: 300
scenario: load_test
scenarios:
load_test:
requests:
- if: ${__groovy(ctx.getThreadNum() == 0 && vars.getIteration() == 1,)}
then:
- url: https://url/authenticateUser
method: POST
headers:
Content-Type: application/json
body:
username: username
password: pass
generateToken: true
extract-jsonpath:
auth_token:
jsonpath: $.token
label: get_token
jsr223: props.put('auth_token', vars.get('auth_token'))
else:
- url: http://endpoint?user_token=${__P(auth_token,)}
method: POST
headers:
Content-Type: application/json
body-file: test_data/body.json
label: sending_300_reqs
think-time: 10s # waiter for processing auth request
If you want to generate a token once and share it across 300 threads:
Generate token for 1st thread during the first iteration using If block and convert it to JMeter Property in JSR223 block. The condition for the If block would be:
${__groovy(ctx.getThreadNum() == 0 && vars.getIteration() == 1,)}
and the code for JSR223 block:
props.put('auth_token', vars.get('auth_token'))
check out Top 8 JMeter Java Classes You Should Be Using with Groovy article to learn what these ctx, props and vars words mean
In your 2nd request refer the property using __P() function
http://url/user_token=${__P(auth_token,)}
I'm attempting to make a GET call to a test management system that exposes an API. I want to provide Basic Auth in the header of the HTTPRequest provided as an action in OT like so.
includes: login.js
actors:
- actor: WEB
segments:
- segment: 1
actions:
- script: var xUsername = $env("X_USERNAME");
- script: var xPassword = $env("X_PASSWORD");
- script: $log("This is the username " + xUsername);
- script: $log("This is the password " + xPassword);
- description: Sample Reading testid
action: org.getopentest.actions.HttpRequest
args:
$checkpoint: true
$localData:
testRailCaseInfo: $output.body
url: https://sub.domain.io/api/v2/get_results/1234
headers:
Content-Type: application/json
Authorization: Basic xUsername xPassword
verb: GET
Is this correct?
Here are two ways to do it (please note I didn't test this code). You can either build the Authorization header value using a JavaScript expression,
- description: Read test ID
action: org.getopentest.actions.HttpRequest
args:
url: https://sub.domain.io/api/v2/get_results/1234
verb: GET
headers:
Content-Type: application/json
Authorization: |
$script
"Basic " + $base64($env("X_USERNAME") + ":" + $env("X_PASSWORD"))
or build the header value in a script action, ahead of time:
- script: |
// Build the authentication header
var authHeader =
"Basic " + $base64($env("X_USERNAME") + ":" + $env("X_PASSWORD"));
- description: Read test ID
action: org.getopentest.actions.HttpRequest
args:
url: https://sub.domain.io/api/v2/get_results/1234
verb: GET
headers:
Content-Type: application/json
Authorization: $script authHeader
I should probably explain what is the role or $script prefix in the two examples. When the value of an action argument starts with a dollar-prefixed symbol (like $json, $data, $format, etc.), the test actor understands that the expression is JavaScript code, evaluates the expression and uses the result as the value for the argument. When a JS expression doesn't start with a dollar-prefixed symbol (e.g. our expressions start with "Basic" and authHeader, respectively) we must prefix the expression with $script followed by one or more whitespace characters, to let the test actor know that the string that follows is JavaScript code and not just an ordinary string literal.
As for the format of the basic authentication scheme, you can find more information here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization.
I am pulling recent commits from github and trying to parse it using ruby. I know that I can parse it manually but I wanted to see if there was some package that could turn this into a hash or another data structure.
commits:
- parents:
- id: 202fb79e8686ee127fe49497c979cfc9c9d985d5
author:
name: This guy
login: tguy
email: tguy#tguy.com
url: a url
id: e466354edb31f243899051e2119f4ce72bafd5f3
committed_date: "2010-07-19T13:44:43-07:00"
authored_date: "2010-07-19T13:33:26-07:00"
message: |-
message
- parents:
- id: c3c349ec3e9a3990cac4d256c308b18fd35d9606
author:
name: Other Guy
login: oguy
email: oguy#gmail.com
url: another url
id: 202fb79e8686ee127fe49497c979cfc9c9d985d5
committed_date: "2010-07-19T13:44:11-07:00"
authored_date: "2010-07-19T13:44:11-07:00"
message: this is another message
This is YAML http://ruby-doc.org/core/classes/YAML.html. You can do something like obj = YAML::load yaml_string (and a require 'yaml' at the top of your file, its in the standard libs), and then access it like a nested hash.
YAML is basically used in the ruby world the way people use XML in the java/c# worlds.
Looks like YAML to me. There are parsers for a lot of languages. For example, with the YAML library included with Ruby:
data = <<HERE
commits:
- parents:
- id: 202fb79e8686ee127fe49497c979cfc9c9d985d5
author:
name: This guy
login: tguy
email: tguy#tguy.com
url: a url
id: e466354edb31f243899051e2119f4ce72bafd5f3
committed_date: "2010-07-19T13:44:43-07:00"
authored_date: "2010-07-19T13:33:26-07:00"
message: |-
message
- parents:
- id: c3c349ec3e9a3990cac4d256c308b18fd35d9606
author:
name: Other Guy
login: oguy
email: oguy#gmail.com
url: another url
id: 202fb79e8686ee127fe49497c979cfc9c9d985d5
committed_date: "2010-07-19T13:44:11-07:00"
authored_date: "2010-07-19T13:44:11-07:00"
message: this is another message
HERE
pp YAML.load data
It prints:
{"commits"=>
[{"author"=>{"name"=>"This guy", "login"=>"tguy", "email"=>"tguy#tguy.com"},
"parents"=>[{"id"=>"202fb79e8686ee127fe49497c979cfc9c9d985d5"}],
"url"=>"a url",
"id"=>"e466354edb31f243899051e2119f4ce72bafd5f3",
"committed_date"=>"2010-07-19T13:44:43-07:00",
"authored_date"=>"2010-07-19T13:33:26-07:00",
"message"=>"message"},
{"author"=>
{"name"=>"Other Guy", "login"=>"oguy", "email"=>"oguy#gmail.com"},
"parents"=>[{"id"=>"c3c349ec3e9a3990cac4d256c308b18fd35d9606"}],
"url"=>"another url",
"id"=>"202fb79e8686ee127fe49497c979cfc9c9d985d5",
"committed_date"=>"2010-07-19T13:44:11-07:00",
"authored_date"=>"2010-07-19T13:44:11-07:00",
"message"=>"this is another message"}]}
This format is YAML, but you can get the same information in XML or JSON, see General API Information. I'm sure there are libraries to parse those formats in Ruby.
Although this isn't exactly what you're looking for, here's some more info on pulling commits. http://develop.github.com/p/commits.html. Otherwise, I think you may just need to manually parse it.