ReadyAPI and TestEngine with Zephyr Scale
In this post, we are going to talk about using some of the SmartBear API testing tools and writing the results in an automated fashion to Zephyr Scale. But first let's take a step back and discuss a general approach of using testing tools with test management tools. At a manual level, when creating simple Multi-Protocol API tests, we might use a lighter weight tool like SoapUI Open Source, a great tool for quick and basic functional and performance API tests. After we trigger the test manually, we as humans would parse the report for any details on the test run, and to track it would update a spreadsheet, testcase in Jira, but somewhere. This sequence is important to note for when we start to advance to an automated workflow. Now let's talk about automating this process. We execute our API tests in an ephemeral environment, publish the results to a relative location, and then pass those results from that relative location to a test management platform. It is quite simple. In this workflow it tends to be easier to use a tool that has native integrations into CI platforms like Jenkins and Azure Dev Ops, that make managing our executions in these ephemeral environments much easier. Here, ReadyAPI, or TestEngine is the clear choice as they both seamlessly integrate into any CI-CD system and have native integrations into Jenkins, Azure, and many more. Now that sequence I mentioned earlier is very important. We will execute our API tests, and then pass the results into Zephyr Scale as step 2 in the pipeline. Here is a look at a scripted pipeline approach, note that there is freestyle job options available in Jenkins too. ReadyAPI Jenkins Plugin: https://support.smartbear.com/readyapi/docs/integrations/jenkins.html TestEngine Jenkins Plugin: https://support.smartbear.com/testengine/docs/admin/jenkins.html node { stage('Run API Tests') { // Run the API Tests using ReadyAPI or TestEngine stage('Pass Results') { //Pass Results to Zephyr Scale } } Now we need to be a little more specific with the 'Run API Tests' Stage. Above I mention when we run our API Tests in an automated fashion, we need to write the results to a relative location so that we can then send those results to Zephyr Scale from that location. Both ReadyAPI and TestEngine allow us to write results to locations as part of the command-line or native integrations. I will show command- line options for ReadyAPI and UI native integration for TestEngine but both options are available for both tools. Starting with ReadyAPI, testrunner CLI, the -f and -F flags represent the directory we are writing to, and the report format, respectively. ReadyAPI offers reports in PDF, XLS, HTML, RTF, CSV, TXT and XML, but the automation recommendation would be to pass results in the Junit-XML option. At a basic level we need this: testrunner.bat [optional-arguments] <test-project> And we need to specify -f and -F testrunner.bat -f<directory> -F<args> <test-project> and with -f requiring a relative directory, that can change based on the CI system. I will use Azure Dev Ops for both my examples here. In Azure I pull my test cases from the $(System.DefaultWorkingDirectory), which contains my git repo. In Azure I publish results to the $(Common.TestResultsDirectory) An example full command would look like this: "C:\Program Files\SmartBear\ReadyAPI-3.40.1\bin\testrunner.bat" -r -a -j -f$(Common.TestResultsDirectory) "-RJUnit-Style HTML Report" -FXML "-EDefault environment" "$(System.DefaultWorkingDirectory)/" With TestEngine it's very similar, but I am highlighting it through the native integration, note the publish test results and save Junit report option enabled below: Now lastly, we need to send the results to Zephyr Scale from the pipeline, before our release is over. I think it's easiest with the Zephyr Scale API: https://support.smartbear.com/zephyr-squad-cloud/docs/api/index.html along with the Auto-Create Test Case option to true. The command below is a basic example, and replica of the one seen in the Azure Pipeline screenshot. curl -H "Authorization: Bearer Zephyr-Scale-Token-Here" -F file= Relative-Location-of-Report-Here\report.xml;type=application/xml "https://api.zephyrscale.smartbear.com/v2/automations/executions/junit?projectKey=Project-Key-Here&autoCreateTestCases=true" After you modify the API token, Relative location, and project key you are good to run the pipeline. When we jump to Jira, we can see inside Zephyr Scale that the results are populating. Even with transactional Data on the failed test steps1.7KViews0likes0CommentsA script to batch edit 'Locally Stored Value' boolean true/false
def Suite = 'TestSuitename' def tSuite = testRunner.testCase.testSuite.project.getTestSuiteByName(Suite) tSuite.testCaseList.each { Case -> for (prop in Case.getPropertyList()) { prop.setUserSpecific(true) } }DLeidelmeijer3 years agoSenior Member562Views0likes0CommentsSimple script to batch edit multiple groovy scripts
Question How to batch edit multiple groovy scripts? Here is my solution. Answer import com.eviware.soapui.impl.wsdl.teststeps.WsdlGroovyScriptTestStep def testSuite = 'testsuitename' String textToSearch = """text""" def replacementText = """text""" def tSuite = context.testCase.testSuite.project.testSuites[testSuite] def testcases = tSuite.testCaseList.toArray() testcases.each() {Case -> for( testSteps in Case.testStepList ) { if( testSteps instanceof WsdlGroovyScriptTestStep && testSteps.getScript().contains(textToSearch)) { log.info(Case.getName().toString() + " - " + testSteps.getScript().toString()) String newString = testSteps.getScript().toString().replace(textToSearch, replacementText) testSteps.setScript(newString) } } }DLeidelmeijer3 years agoSenior Member580Views3likes0CommentsUse of tags in command line via bamboo build - Run all tests while sending -T tags parameters
Question In our CI build pipeline (bamboo) I setup two parameters tagTestSuite and tagTestCase. This way, all our automation testers can specify, if needed, which test cases need to be ran based on certain tags. This works fine if there are indeed such tags passed on. But what if a project just wants to run all of its tests? These -T parameters cannot be left empty in the command line as that fails the build... Deleting them from the bamboo variables will then lose the standardized approach for all testers to do a tag-based execution. Seeing the documentation here (https://support.smartbear.com/readyapi/docs/functional/running/automating/cli.html)I noticed you can provide logical operators. So I thought I came up with a nice solution: I provide as default value for those parameters in bamboo a tag value that nobody would use and by setting the "!" operator in front of it I'd expected thatALL test suites/cases would run. So I've set parameters and resulted in a command line that has these : "-TTestSuite !itShouldRunAllTestSuites" "-TTestCase !itShouldRunAllTestCase". Since none of the testSuite/testCases has a tag "itShouldRunAllTestSuites" or "itShouldRunAllTestCases" I thought my project would be run in full.... But nope: error is shown:[errorlog] java.lang.Exception: The tag "itShouldRunAllTestSuites" was not found. Yes, I could go and add a tag to every single test suite and test case to work around this, but that could be tedious and not so future proof... Answer Solution: ReadyAPI seems to look first for a tag and then complain it wasn't found. So I tried this: I've added the tags "itShouldRunAllTestCases" and "itShouldRunAllTestSuites" to my project but did not tag any of my testsuites/testcases with this tag. Executed above again and now ALL of my tests get's executed nicely!JoostDG3 years agoFrequent Contributor682Views0likes0CommentsGmail OAuth 2.0 API Automation Example and example SubmitListener.beforeSubmit
Hi. Recently I had a use case where I had to verify that our application sends an email in a particular case. In my case, it was an email whenever a certain status is reached, but it could also be for instance the sending of an invitation (user-creation) email or whatnot. In essence: I want to check at a give moment when the status condition is reached that the email is delivered to the proper email address. In the past, there we some free email generators (like mailinator, 10minutemail,...) where you could get API calls to check the inbox, but I couldn't find any that offered that feature for free. So I decided to setup a dedicated gmail email address for my tests and talk to the gmail api to read the inbox (all messages), verify the subject and the actual content of a mail and delete the messages. More info on the gmail api here: https://developers.google.com/gmail/api/reference/rest. To get started, I created a gmail user and setup an OAuth2.0 google client Id (https://console.cloud.google.com/apis/credentials). These settings (clientId and secret) I used to setup an Authorization code grant as described here:https://support.smartbear.com/readyapi/docs/requests/auth/types/oauth2/grants/auth-code.html So far so good, the readyAPI internal browers showed me the google popup and I could manually insert the authentication that was needed to generate successfully an access token from google. BUT: When I tried to automate the flow in this popup using the example code provided in the documentation (=https://support.smartbear.com/readyapi/docs/requests/auth/types/oauth2/automate/sample.html) it did not work for me. Therefore I wanted to share the changes I made to it in order to get it working. Also the event handler SubmitListener.beforeSubmit(described on the same page) needed some rework for me to work properly. As an extra, I also have a test case setup script that deletes all emails in the gmail inbox so I have a proper starting situation for my tests. Hope this can help any other testers that would need this! Automation Scripts tab of the Auth Manager I have encrypted project properties that store my gmail username (gmailUser) and password (gmailPass). Page 1: // This function asks for permission to use OAuth. The user must be logged in to use it. Logging in is performed in the script below. function consent() { if (document.getElementById('submit_approve_access')){ document.getElementById('submit_approve_access').click(); } } // This function fills user password in when the user name is already known. It uses the project-level "pass" property. function fillpwd() { document.getElementsByName('password')[0].value = '${#Project#gmailPass}'; document.getElementById('passwordNext').click(); window.setInterval(consent, 1000); } // This script checks what page is displayed and provides the appropriate data. It uses the project-level "user" and "pass" properties. if (document.getElementById('profileIdentifier')) { document.getElementById('profileIdentifier').click(); window.setTimeout(fillpwd, 1000) }else if (document.getElementById('identifierId') && document.getElementById('identifierNext')) { document.getElementById('identifierId').value = '${#Project#gmailUser}'; document.getElementById('identifierNext').click(); window.setTimeout(fillpwd, 1000); } else if (document.getElementByType('password')) { fillpwd(); } else if(document.getElementById('submit_approve_access')){ window.setInterval(consent, 100); } Page 2: function consent() { if (document.getElementById('submit_approve_access')){ document.getElementById('submit_approve_access').click(); } } window.setInterval(consent, 100); Event handler SubmitListener.beforeSubmit: Note: There might be some redundant iteration of code in there, feel free to rewrite, main thing is: it works. I also expected that I could use the "Target" column to filter on the requests steps that start with "gmail*" but that didn't do it. So I fixed that in another way, together with providing some smart checking whether a new token generation is needed or not (gmail token is valid for 60 minutes). // Import the required classes import com.eviware.soapui.impl.rest.actions.oauth.OltuOAuth2ClientFacade; import com.eviware.soapui.support.editor.inspectors.auth.TokenType; import com.eviware.soapui.model.support.ModelSupport; import java.time.LocalDateTime import java.time.format.DateTimeFormatter def testStepName = context.getModelItem().getName() def expiresOn = context.expand('${#Project#expiresOn}') if (testStepName.toLowerCase().contains("gmail")) { // IF expiresOn == "" OR dateNow is > expiresOn then we need to get a new token. Otherwise the old should still do.... TimeZone.setDefault(TimeZone.getTimeZone('UTC')) TimeZone tz = TimeZone.getTimeZone("UTC") LocalDateTime dateNow = LocalDateTime.now() def patternUTC = "yyyy-MM-dd\'T\'HH:mm:ss.SSS\'Z\'" DateTimeFormatter dateFormatUTC = DateTimeFormatter.ofPattern(patternUTC).withLocale(Locale.US) String nowUtcFormat = dateNow.format(dateFormatUTC) LocalDateTime dateExpiresOn = dateNow.plusMinutes(55) String expiresOnUtcFormat = dateExpiresOn.format(dateFormatUTC) if (expiresOn == "") { /* log.info("Expires on is empty, so we need to run submitListener and get new token. We also write the new expiresOn to the project properties!") log.info("nowUtcFormat = " + nowUtcFormat) log.info("dateExpiresOn = " + expiresOnUtcFormat) log.info "let's run the submitListener.beforeSubmit to get a new accessToken. We know this one will be valid for 60 minutes, so we set a the expiresOn project property to now + 55 minutes" */ // Set up variables def project = ModelSupport.getModelItemProject(context.getModelItem()) project.setPropertyValue("expiresOn", expiresOnUtcFormat) def authProfile = project.getAuthRepository().getEntry("google") def oldToken = authProfile.getAccessToken() def tokenType = TokenType.ACCESS // Create a facade object def oAuthFacade = new OltuOAuth2ClientFacade(tokenType) // Request an access token in headless mode oAuthFacade.requestAccessToken(authProfile, true, true) // Wait until the access token gets updated int iteration = 0 while (oldToken == authProfile.getAccessToken() && iteration < 10) { sleep(500) iteration++ } // Post the info to the log log.info("Gmail authentication event handler: Project property \"expiresOn\" is empty! We get/set new token: " + authProfile.getAccessToken() + " with new expiresOn = " + expiresOnUtcFormat + ". The old token (with unknown expiresOn) was = " + oldToken) } else { def potentialNewExpiresOn = expiresOnUtcFormat //expiresOn retrieved from project properties dateExpiresOn = LocalDateTime.parse(expiresOn, dateFormatUTC) expiresOnUtcFormat = dateExpiresOn.format(dateFormatUTC) //log.info("dateExpiresOn = " + expiresOnUtcFormat) if (dateNow > dateExpiresOn) { // log.info "Expired! Let's get a new token..." // Set up variables def project = ModelSupport.getModelItemProject(context.getModelItem()) def authProfile = project.getAuthRepository().getEntry("google") def oldToken = authProfile.getAccessToken() def tokenType = TokenType.ACCESS // Create a facade object def oAuthFacade = new OltuOAuth2ClientFacade(tokenType) // Request an access token in headless mode oAuthFacade.requestAccessToken(authProfile, true, true) // Wait until the access token gets updated int iteration = 0 while (oldToken == authProfile.getAccessToken() && iteration < 10) { sleep(500) iteration++ } // Post the info to the log project.setPropertyValue("expiresOn", potentialNewExpiresOn) log.info("Token was expired! We get/set new token: " + authProfile.getAccessToken() + " with new expiresOn = " + potentialNewExpiresOn + ". The old token (expiresOn = $expiresOn vs now " + nowUtcFormat + ") was = " + oldToken) } else { //log.info "Not yet expired. Let's keep using the same old token (expiresOn = $expiresOn vs now " + nowUtcFormat + ")" } } } Setup script to delete all emails in the gmail inbox for proper start situation: I have a disabled test suite "WorkItem" with a test case named "GmailStartSituationCleanup" This test case has 4 steps: 1°GET gmail messagesList 2° Script "IterateOverAllGmailMessageIds" 3°DELETE gmail messageId 4°GET gmail messagesList-EmptyListCheck def testSuiteWorkItem = testRunner.testCase.testSuite.project.getTestSuiteByName("WorkItem") def testCaseGmailStartSituationCleanup = testSuiteWorkItem.getTestCaseByName("GmailStartSituationCleanup") testCaseGmailStartSituationCleanup.run(new com.eviware.soapui.support.types.StringToObjectMap(), false) The groovy test step 2°IterateOverAllGmailMessageIds = import com.eviware.soapui.support.JsonUtil def testStepAllMessages = testRunner.testCase.getTestStepAt(context.getCurrentStepIndex()-1) def teststepNameAllMessages = testStepAllMessages.getName() def testStepDeleteMessage = testRunner.testCase.getTestStepAt(context.getCurrentStepIndex()+1) testStepDeleteMessage.setDisabled(true) def testStepVerifAllDeleted = testRunner.testCase.getTestStepAt(context.getCurrentStepIndex()+2) testStepVerifAllDeleted.setDisabled(false) def responseStatus = testStepAllMessages.testRequest.response.responseHeaders["#status#"][0] if (responseStatus.contains("HTTP/1.1 2")){ def responseMessages = context.expand( '${'+teststepNameAllMessages+'#Response#$[\'messages\']}' ) if (responseMessages!= "" && responseMessages!= null){ def numberOfMessages = (JsonUtil.parseTrimmedText(responseMessages)).size() def id for (i=0;i<numberOfMessages;i++){ id = context.expand( '${'+teststepNameAllMessages+'#Response#$[\'messages\']['+i+'][\'id\']}' ) testStepDeleteMessage.setPropertyValue("messageId", id) log.info "Cleanup of gmail messages : Message "+(i+1).toString()+"/"+numberOfMessages.toString()+" with messageId $id will be deleted so we can continue with a proper starting situation..." testStepDeleteMessage.run(testRunner, context) } }else{ log.info "Cleanup of gmail messages : No messages found. We can continue with proper starting situation" testStepVerifAllDeleted.setDisabled(true) } }JoostDG3 years agoFrequent Contributor844Views3likes0CommentsHow to capture teststep status as pass/fail using groovy to write it in txt file
Question How to capture teststep status as pass/fail using groovy to write it in txt file Answer This can be done very simply by using the following TearDown Script: // Define variables for holding test suites, test cases and test steps def testSuites def testCases def testSteps // Get all the test suites from the project testSuites = project.testSuiteList File file = new File("C:\\Users\\luciana\\Desktop\\test.txt") /** * Iterate through each test suite, test case and test step */ testSuites.each() { // Log test suite name file << "-----------------------------------\n" file << "Running test suite: " + it.getName() + "\n" file << "-----------------------------------\n" // Get a list with the contained test cases testCases = it.getTestCaseList() testCases.each() { // Log test case name file << "-----------------------------------\n" file << "Running test case: " + it.getName() + "\n" file << "-----------------------------------\n" // Get a list with the contained test steps testSteps = it.getTestStepList() testSteps.each() { file << it.getName() + " - " + it.getAssertionStatus() + "\n" } } }Lucian4 years agoCommunity Hero576Views1like0CommentsGroovy Script to identify a file timestamp
Question How to identify a file timestamp? Answer Just sharing this groovy script I am using to disable a "DataCollection" step based on a filetime stamp. Use case: Identify when datasource file is modified If datasource file is already generated then skip/disable datacollection step which in the end writes data to datasource file why to disable - if you want to run the same API tests (but different versions say due to refactoring) against same dataset Below is the sample test case structure DataSource check script is shown below import com.eviware.soapui.support.GroovyUtils import java.text.DateFormat import java.text.SimpleDateFormat //## Get test step name // def currentStepInd = context.currentStepIndex def TestStepName = testRunner.testCase.getTestStepAt(currentStepInd).name log.info "------------------------------------------------" log.info "Running $TestStepName..." //## Get current Date ##// def CurrentDate = new Date() log.info "Current Date is $CurrentDate..." // Get datasource file // def DataSourceFile = context.expand('${projectDir}') + "\\DataSource.txt" File DataFile = new File(DataSourceFile) log.info "DataSource File is: $DataFile" def fileDate if (DataFile.exists()) { // Get the last modification information. Long lastModified = DataFile.lastModified() // Create a new date object and pass last modified fileDate = new Date(lastModified) //fileDate = sdf.format(fileDate) log.info "File modified time is: $fileDate" } //## To find Date Diff ##// def diff use(groovy.time.TimeCategory) { diff = (CurrentDate - fileDate).days } //## Skip DataCollection if DataSource is older than today ##// if(diff == 0) { //## disable teststep to skip data collection ##// log.info "Disabling testStep DataCollection..." testRunner.testCase.getTestStepByName( "DataCollection" ).setDisabled(true) }else{ //## enable teststep to run data collection ##// log.info "Enabling testStep DataCollection..." testRunner.testCase.getTestStepByName( "DataCollection" ).setDisabled(false) } log.info "Finished $TestStepName..." log.info "------------------------------------------------" thanks!New2API4 years agoFrequent Contributor461Views0likes0CommentsHow to get cookies in Groovy scripts?
Question How to get cookies in Groovy scripts? Answer If theSession optionis enabled, ReadyAPI maintains the HTTP session on the TestCase level automatically, which means that it storescookiesreceived from the server and sends them back in the subsequent requests to the same endpoint. If you need to get programmatic access to the stored cookies, you can use this Groovy script (it's applicable to ReadyAPI v.1.7.0 and later): import org.apache.http.protocol.HttpContext import com.eviware.soapui.model.iface.SubmitContext import org.apache.http.impl.client.BasicCookieStore import org.apache.http.client.protocol.HttpClientContext HttpContext httpContext = context.getProperty(SubmitContext.HTTP_STATE_PROPERTY) BasicCookieStore cookieStore = httpContext.getAttribute(HttpClientContext.COOKIE_STORE) //iterate through the cookies store and output the name-value pairs to the log cookieStore.getCookies().each{ log.info(it.name + "=" + it.value) } Example:NBorovykh4 years agoModerator479Views1like0CommentsHow to generate a random Number, String, AlphaNumeric string
Question How to generate a random Number, String, AlphaNumeric string Answer This below function will generate random Number, String, alphaNumeric string as what you pass in parameters. Refer below code and help yourself in generating random numbers. def num = generateRndString(10, "numeric"); log.info num def str = generateRndString(10, "string"); log.info str def alphaNum = generateRndString(10, "alphanumeric"); log.info alphaNum testRunner.testCase.getTestStepByName("Properties").setPropertyValue("RndNum", num) testRunner.testCase.getTestStepByName("Properties").setPropertyValue("RndString", str) testRunner.testCase.getTestStepByName("Properties").setPropertyValue("RndAlpha", alphaNum) def generateRndString(int num, String type){ def randValue = ""; if( type.equalsIgnoreCase("numeric") ){ def alphaNumeric = ('0'..'9').join() randValue = RandomStringUtils.random(num, alphaNumeric) while (randValue.size()!=num) { randValue = RandomStringUtils.random(num, alphaNumeric) } } else if( type.equalsIgnoreCase("string") ){ def alphaNumeric = (('a'..'z')+('A'..'Z')).join() randValue = RandomStringUtils.random(num, alphaNumeric) while (randValue.size()!=num) { randValue = RandomStringUtils.random(num, alphaNumeric) } } else if( type.equalsIgnoreCase("alphanumeric") ){ def alphaNumeric = (('0'..'9')+('a'..'z')+('A'..'Z')).join() randValue = RandomStringUtils.random(num, alphaNumeric) while (randValue.size()!=num) { randValue = RandomStringUtils.random(num, alphaNumeric) } } return randValue }HimanshuTayal4 years agoCommunity Hero566Views0likes0CommentsGroovy Automated DataSource Loopers
Symptoms You may need to create the loop steps through groovy manually.So without further ado this is how I script my groovy loopers NOTE: in this example I am connecting to an OracleDB to fuel my requests. Solution 1) Structure: The structure within my test case is quite simple:loopStarterto connect to the db/initialise the data - storing the current information into properties,responseto send these properties off to the API through a request,loopEnderto either propagate or exit the loop if the condition is met. 2) loopStarter Connection to/creation of your data source, be it a database, excel etc In this example I will be using the groovy.sqlclass (documentation) to connect to my db, and store the results into properties import groovy.sql.Sql sql = Sql.newInstance(<connection details>) def res = sql.rows(<sql query>) def loopProperties = testRunner.testCase.getTestStepByName("loopProperties") //will initialise count in the loopProperties step if count does not yet exist if(!loopProperties.hasProperty("count")){ loopProperties.setPropertyValue("count","0") } def count = Integer.parseInt(loopProperties.getPropertyValue("count")) //store the properties from the current result loopProperties.setPropertyValue("x",res[count].x) loopProperties.setPropertyValue("y",res[count].y) loopProperties.setPropertyValue("querySize",(String)res.size()) sql.close() sql.rows returns an array of ArrayLists, so to access the current result (more on this later) we will use res[count]. Just to reiterate this step ONLY sets the properties to be sent off in the request.querySize is set so we can continue looping over all the results from the query. 3) response To then call your properties into your request step we will do this: <soapenv:Envelope namespace:ns="namespace"> <soapenv:Header/> <soapenv:Body> <ns:x>${loopProperties#x}</ns:x> <ns:y>${loopProperties#y}</ns:y> </soapenv:Body> </soapenv:Envelope> if you choose not to store your properties in a property step and wanted to store them at the test case level you can fetch them like so: ... <ns:x>${#TestCase#x}</ns:x> ... 4) loopEnder This is the loop exit condition. It will tell the loop whether or not there are more results from your query to send through the request. This is also a pretty simple step: - check count vs querySize - increment count if true - go to loopStarter if true, to update the properties (count has been increased andbecause of this, so will the current result) def loopProperties = testRunner.testCase.getTestStepByName("loopProperties") def count = Integer.parseInt(loopProperties.getPropertyValue("count")) def querySize = Integer.parseInt(loopProperties.getPropertyValue("querySize")) if( count<querySize-1 ){ count = count+1 loopProperties.setPropertyValue("count", (String)count) testRunner.gotoStepByName("loopStarter") } 5) reset count Unnecessary step but I like getting into the habbit of clearing all properties after I'm done, and this groovy script will do it for you def loopProperties = testRunner.testCase.getTestStepByName("loopProperties") String[] removals = loopProperties.getPropertyNames() for(i=0;i<removals.size();i++){ loopProperties.removeProperty(removals[i]) } I've been thinking of doing something similar to this for the Excel DataSource scriptOlga_Tmentioned, well similar to the loopStarter step anyway - doing it completely through groovy so SoapUI free users can do it too, I just need to look into parsing exel files with groovy. Anyways I hope this helps you all, Mo502Views0likes0CommentsOperating with REST Interfaces and Requests
Question Create a script which posts REST Interfaces structures of a current project to a text file. Under each method, list names of functional REST Request Test Steps for this method. The format of the file is arbitrary (whatever looks more reasonable and readable for you). Example: https://www.screencast.com/t/WVmvALNW2Ds Answer def space = {num -> ' ' * num} def stringBuilder = new StringBuilder() project.getInterfaceList().each{p -> // interface name stringBuilder.append("${p.name}\n") p.getOperationList().each{m -> //operation name stringBuilder.append("${space(5)}${m.name}\n") m.getRequestList().each{r -> //http method name stringBuilder.append("${space(10)}${r.getMethod()}\n") //rest method name stringBuilder.append("${space(15)}${r.getRestMethod().name}\n") } } } def writeToFile(def directory, def filename, def extension, def content){ if(! content.trim().isEmpty()){ def folder = new File(directory) if( ! folder.exists()) folder.mkdirs() new File("$directory/$filename$extension").withWriter{out -> out << content } } } writeToFile("C:/", "project_structure", ".txt", stringBuilder.toString())aaronpliu4 years agoFrequent Contributor436Views0likes0CommentsOperating with TestCase properties
Question Create a script which updates values of the selected options in all TestCases within the current TestSuite. All the options from the Basic tab should be available for modification (https://www.screencast.com/t/CtKSe0H8) - it's up to the script user which ones to comment/uncomment before running the script. Answer Below scripts to update properties of test case in basic option. I post all of them as screenshot attached(https://www.screencast.com/t/CtKSe0H8). Maybe can better construct following scripts rather than writingsimilar method. FYI def searchProperties = {testcase, boolean flag -> testcase.setSearchProperties(flag)} def httpSession = {testcase, boolean flag -> testcase.setKeepSession(flag)} def abortFailOnError = {testcase, boolean flag -> testcase.setFailOnError(flag)} def failTestCaseOnErrors = {testcase, boolean flag -> testcase.setFailTestCaseOnErrors(flag)} def testCaseTimeout = {testcase, long num -> testcase.setTimeout(num)} def discardResult = {testcase, boolean flag -> testcase.setDiscardOkResults(flag)} def maxResults = {testcase, int num -> testcase.setMaxResults(num)} testSuite.getTestCaseList().each{testcase -> searchProperties.call(testcase, true) httpSession.call(testcase, true) abortFailOnError.call(testcase, false) // note its status failTestCaseOnErrors.call(testcase, true) // note its status testCaseTimeout.call(testcase, 10000) discardResult.call(testcase, true) maxResults.call(testcase, 10) }aaronpliu4 years agoFrequent Contributor458Views0likes0CommentsConvert a flat JSON object to x-www-form-urlencoded
Question How to create a Groovy script, which transforms a non-nested JSON object to thex-www-form-urlencoded format before sending it in the request body. Example: converting this object: { "name": "John", "age": 30, "city": "New York" } should result in this string: name=John&age=30&city=New%20York Answer def jsonData = ''' { "name":"John", "age":30, "city":"New York" } ''' def slurper = new groovy.json.JsonSlurper().parseText(jsonData) def str = new StringBuilder() def iter = slurper.keySet().iterator() while(iter.hasNext()){ def key = iter.next().toString() def value = slurper.get(key).toString().trim().replaceAll(" ","20%") str.append("$key=$value&") } log.info str[0..str.size()-2]aaronpliu4 years agoFrequent Contributor733Views1like0CommentsUnescape Json Strings
I wasn't 100% familiar with JSON strings, or working with events and attaching a groovy script, but after a bit of research I was able to put something together that should work. The challenge was as follows: Unescape JSON Strings Create a Groovy script, which unescapes JSON strings in the body of a REST API response before the response in shown in the response editor: Example of original response body: { "employee": "{\"name\":\"John\", \"age\":30, \"city\":\"New York\"}" } Example of the corresponding modified response body: { "employee": { "name": "John", "age": 30, "city": "New York" } } Here, for the request that has a response that needs to be escaped, we have to add an Event to the project. That event should be of type "RequestFilter.afterRequest" and should contain the following groovy script: def responseContent = context.httpResponse.responseContent; context.httpResponse.responseContent = responseContent.replaceAll("\\\\", ""); I believe this should solve the problem presented in the script challenge. I learned something new, too! I'd be eager to see how other people solve it, or if I am even on the right track. It appears to work for me, though.groovyguy4 years agoCommunity Hero449Views0likes0CommentsAutomate Environment creation for REST and SOAP services
This came up in a previous thread, and I've finally worked out how to populate environments into a project and how to set their endpoints accordingly. This is relatively simple and can be extended for adding multiple environments. We're currently using this to convert all of our various ReadyAPI / SoapUI NG Pro projects to support environments. It's working like a charm! // For REST Services import com.eviware.soapui.model.environment.* import com.eviware.soapui.config.* def project = context.testCase.testSuite.project; // Assuming binding is the first interface on the project. def binding = context.testCase.testSuite.project.getInterfaceAt(1).getName().toString(); // Getting current list of environment names def environNames = project.getEnvironmentNames().toString(); // Names of environments to add def name = "env1" // Environment URLs, number should match number in previous array for environment names project.addNewEnvironment(name); project.setActiveEnvironment(name); def environ = project.getActiveEnvironment(); def newService; // For REST service: newService = environ.addNewService(binding, com.eviware.soapui.config.ServiceConfig.Type.REST); def serviceConfig = newService.getConfig(); def endpointConfig; endpointConfig = serviceConfig.addNewEndpoint(); newService.buildEndpoint(endpointConfig); def isCopy1 = true; log.info(isCopy1); environ.populateService(newService, isCopy1); environ.release(); For SOAP services: import com.eviware.soapui.model.environment.* import com.eviware.soapui.config.* def project = context.testCase.testSuite.project; // Assuming binding is the first interface on the project. def binding = context.testCase.testSuite.project.getInterfaceAt(1).getName().toString(); // Getting current list of environment names def environNames = project.getEnvironmentNames().toString(); // Names of environments to add def name = "env1" // Environment URLs, number should match number in previous array for environment names project.addNewEnvironment(name); project.setActiveEnvironment(name); def environ = project.getActiveEnvironment(); def newService; // For SOAP service: newService = environ.addNewService(binding, com.eviware.soapui.config.ServiceConfig.Type.SOAP); // For REST service: // newService = environ.addNewService(binding, com.eviware.soapui.config.ServiceConfig.Type.REST); def serviceConfig = newService.getConfig(); def endpointConfig; endpointConfig = serviceConfig.addNewEndpoint(); endpointConfig.setStringValue(url) newService.buildEndpoint(endpointConfig); def isCopy1 = true; log.info(isCopy1); environ.populateService(newService, isCopy1); environ.release(); The two are almost identical except for a couple of lines. Other than that, these have both worked for me without any problems.444Views0likes0CommentsAutomated testing with Jenkins and bypassing Tests
I recently started working with my DevOps team to automate our ReadyAPI projects in Jenkins so we can automate some parts of our tests instead of having to manually re-check defects as they are fixed. The intent is to eventually end up automating a lot more but this was a great start. The problem I ran into was that, with the way our projects are stored in GitLab, there are some tests that may fail (rightfully so) depending on which environment was last tested against and where the automated test is running. My original plan was to use Tags to control what tests to run in Jenkins, but you can only specify which tags to run and not which ones to exclude. I did a bit more research and came up with a solution. I can likely improve on it with events now that I have started researching that, but I wanted to share. I set a Project level property called "Automated" and set it to false. This can be overridden with the testrunner.bat/testrunner.sh with a command line argument of "-PAutomated=true", so that the value is only ever true when it runs on Jenkins and the appropriate argument. With that, I put a groovy script test step at the beginning of the tests that would parse the value of Automated, and disable certain tests if the value was true, and re-enable them if the value was false. Example script here: def automated = context.expand( '${#Project#Automated}' ).toBoolean(); if (automated == false) { context.testCase.testSuite.testCases["TestCase1 to D isable"].setDisabled(false); context.testCase.testSuite.testCases["TestCase2 to D isable"].setDisabled(false); } else { context.testCase.testSuite.testCases["TestCase1 to Enable"].setDisabled(true); context.testCase.testSuite.testCases["TestCase2 to Enable"].setDisabled(true); }groovyguy4 years agoCommunity Hero482Views0likes0CommentsTesting for ascending sort
Symptoms One of the tests I often have to deal with is verifying that a service can sort (ascending or descending) on a particular element (or a number of them.) I'm sharing a groovy script I've written to help assert that the service is sorting as expected. It's a small step to change it to test for descending sort, and I wanted to see how people would do that. I know how I would, and can post my version later. Solution Here's my snippet: import com.eviware.soapui.support.XmlHolder def groovyUtils = new com.eviware.soapui.support.GroovyUtils( context ); def holder = groovyUtils.getXmlHolder( messageExchange.responseContentAsXml ) def nodeCount = holder["count(//*:Objects/*:Object)"] nodeCount = nodeCount.toInteger() bool ascending = true; if (nodeCount == 1) { // If only one object is returned, it is sorted. ascending = true; } else { // If there are more than one object in the response, use XPATH to get the values of the element that was sorted by. Using ObjectIdentifier for this example. currentValue = holder["//*:Objects/*:Object/*:ObjectIdentifier"]; // Test to see if the collection is equal to the sorted collection. Using false keeps the original collection the same and creates a temporary sorted copy. ascending = currentValue == currentValue.sort(false); }groovyguy4 years agoCommunity Hero460Views0likes0CommentsHow to modify xml files using ReadyAPI
Modifying xml filesor simply working with xml strings might look difficult for a beginner so for those who need itI will try to break the whole thing into pieces so that it is easier to understand. Referring to nodes and attributes Let's say this piece of xml is stored in the file 'mijnFruits.xml' somewhere on your computer: <fruits> <apples> <apple color="red"/> <apple color="green"/> </apples> <oranges> <clementine> <mandarine> </oranges> <bananas/> <jabu-ticaba/> </fruits> If you would want to use a groovy script to identify a certain element, you'll first have to define a variable in which to parse the content: def xml = new XmlSlurper().parse("D:\\someFolder\\mijnFruits.xml") In order to refer to the 'clementine' node for instance, you would use: xml.oranges.clementine // For instance, the following will replace the node 'clementine' with 'tangerine' // xml.oranges.clementine.replaceNode { // tangerine '' // } Please note that while refering to any node, the root node (i.e. "fruits") is replaced by the variable in which the file is parsed (in our case 'xml'). That is why in our example we used 'xml.oranges.clementine' and not 'fruits.oranges.clementine' In order to refer to a node that has a certain attribute ( for instance <apple color="red"/> ), the syntax is as follows: // This will return a list will all the nodes 'xml.apples.apple' that have an attribute 'color' and the value 'red' xml.apples.apple.findAll{it.@color == "red"} // Hence, it is possible to check if such a node exists. For instance: //if (xml.apples.apple.findAll{it.@color == "red"}.size() == 0) // log.info("Such a node doesn't exist.") //else // log.info("Such a node exists.") If you want to refer to a node whose name contains dashes (example: 'jabu-ticaba') then the name must be placed inside single quotes like xml.'jabu-ticaba' // Example of node removing // xml.'jabu-ticaba'.replaceNode { } Practical examples Change an attribute value of a property import groovy.xml.XmlUtil import groovy.util.Node // Parse the content of the document and store it in the 'xml' variable def xml = new XmlSlurper().parse("D:\\someFolder\\web.config") // Change the attribute value for 'xxs-protection' xml.webApplication.'xss-protection'.@enabled = "false" // Construct a FileWriter object and assigns it to the variable 'writer' def writer = new FileWriter("D:\\someFolder\\web.config") // Write the changes to the file XmlUtil.serialize(xml, writer) // Close file writer.close() Add a new node import groovy.xml.XmlUtil import groovy.util.Node // Parse the content of the document and store it in the 'xml' variable def xml = new XmlSlurper().parse("D:\\someFolder\\web.config") // Add a node called 'conf' inside the <confs></confs> tags. The node will have 3 attributes (name, project and version) along with their corresponding values ('myName', 'myProj' and 'v0.0') xml.shortcutSection.shortcuts.appendNode { shortcut(name: "myName", project: "myProject", version: "v0.0") } // Construct a FileWriter object and assigns it to the variable 'writer' def writer = new FileWriter("D:\\someFolder\\web.config") // Write the changes to the file XmlUtil.serialize(xml, writer) // Close file writer.close() // The output will look like // <confs> // <conf name="myName" project="myProject" version="v0.0"/> // </confs> Add a more complex node import groovy.xml.XmlUtil import groovy.util.Node // Parse the content of the document and store it in the 'xml' variable def xml = new XmlSlurper().parse("D:\\mijn\\Web.config") // Add a more complex node (which contain more nodes or attributes) xml.'hibernate-configuration'.'session-factories'.appendNode { 'session-factory'(name: "test") { properties { property(name: "one hell of a property", value: "great of course") property(name: "another hell of a property", value: "great again of course") property(name: "avcsacsadsadsa", value: "asdsadsadasda") property(name: "asdsadsadas", value: "asdsadsadsa") property(name: "another hell of a property", value: "great again of course") } } } // Construct a FileWriter object and assigns it to the variable 'writer' def writer = new FileWriter("D:\\mijn\\Web.config") // Write the changes to the file XmlUtil.serialize(xml, writer) // Close file writer.close() Remove a node import groovy.xml.XmlUtil import groovy.util.Node // Parse the content of the document and store it in the 'xml' variable def xml = new XmlSlurper().parse("D:\\mijn\\Web.config") // Remove a node xml.shortcutSection.replaceNode { } // this removes the shortcutSection node along with its containments // Construct a FileWriter object and assigns it to the variable 'writer' def writer = new FileWriter("D:\\mijn\\Web.config") // Write the changes to the file XmlUtil.serialize(xml, writer) // Close file writer.close() Remove a node that contains a certain attribute import groovy.xml.XmlUtil import groovy.util.Node // Parse the content of the document and store it in the 'xml' variable def xml = new XmlSlurper().parse("D:\\mijn\\Groovy projects\\Web.config") // Delete a node that has a certain given attribute xml.'hibernate-configuration'.'session-factories'.'session-factory'.findAll{it.@name == "myName"}.replaceNode { } // Construct a FileWriter object and assigns it to the variable 'writer' def writer = new FileWriter("D:\\mijn\\Groovy projects\\Web.config") // Write the changes to the file XmlUtil.serialize(xml, writer) // Close file writer.close() Replace a node import groovy.xml.XmlUtil import groovy.util.Node // Parse the content of the document and store it in the 'xml' variable def xml = new XmlSlurper().parse("D:\\mijn\\Groovy projects\\Web.config") xml.configSections.replaceNode { noMoreConfigSection '' } // Construct a FileWriter object and assigns it to the variable 'writer' def writer = new FileWriter("D:\\mijn\\Groovy projects\\Web.config") // Write the changes to the file XmlUtil.serialize(xml, writer) // Close file writer.close() // The result would be that the configSections node i.e. // <configSections> // <section ...> // <section ...> // </configSections> // would be replaced by <noMoreConfigSection/>Lucian4 years agoCommunity Hero697Views0likes0CommentsDispatching responses from a Virtual Service
Question Create a REST virtual service with the "GET /login"resource and add adispatching script, which returns different responses based on the values of query parameters (grant_type, username and password). A sample request: http://localhost:<VIRT_PORT>/login?grant_type=password&username=test&password=Test123! The dispatching condition: Ifgrant_typeis not equal to "password", the virt returns the "Unsupported Grant Type" response (HTTP status code is 401, body is {"error": "Unsupported Grant Type"}). Ifgrant_typeis equal to "password", but the username is not equal to "test" OR the password doesn't comply with the strength requirements (the minimal length is 8 characters, the password contains at least one uppercase letter, one lowercase letter, one number and one symbol) the virt returns the "Invalid Login Info" response (HTTP status code is 401, body is {"error": "Invalid Username or Password"}). If some parameters are missing, the virt returns the "Bad Request" response (HTTP state code is 400, body is {"error":"required parameters are missed"}) If all the parameters have valid values, the virt returns the "OK" response (HTTP status code is 200, body is {"result":"ok"}). Answer Here is a scriptthat dispatches responses The idea behind it is pretty simple namely creating one operation, 4 responses and setting the dispatch style to script.The code I used is attached below: // Get the full query string def queryString = mockRequest.getRequest().getQueryString() // Get the password index in the query string def passwordIndex = queryString.indexOf("password=") + 9 // Cut out the password starting with the previously obtained index def pass = queryString.substring(passwordIndex) // If there are more parameters after the password rule them out if (pass.contains("&")) pass = pass.substring(0, pass.indexOf("&")) /** * Start dispatching responses */ if( !queryString.contains("grant_type=") || !queryString.contains("username=") || !queryString.contains("password=") ) { // Return a bad request return "BadRequest" } else if ( !queryString.contains("grant_type=password")) { // Return an unsupported grant type response return "UnsupportedGrantType" } else if ( !queryString.contains("username=test") || !checkPasswordComplexity(pass)) { // Return invalid credentials return "InvalidCredentials" } else { return "Success" } // Methods definition def checkPasswordComplexity(String password) { if (password ==~ /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&])[A-Za-z\d$@$!%*?&]{8,}/) return true else return false }Lucian4 years agoCommunity Hero425Views0likes0CommentsCode for groovy datasource
Question generates some identification codes like X00001, X00002 and so on... Answer // Get current row def row = testRunner.testCase.testSteps["DataSource"].currentRow // Declare an array that will contain the id codes def codes = [] // Generate all id codes for (int i = 1; i < 100000; i++) { codes.add( String.format('X%05d', i) ) } // Make the values reachable from the datasource if (row < codes.size()) { // Return the name to data source's "File" property result["Code"] = codes[row] }Lucian4 years agoCommunity Hero473Views0likes0CommentsReadyAPI libraries - How to set them up
1.Reasoning Although not very well documented, ReadyAPI gives users the possibility to use libraries (-ish). Those are very handy when you have similar steps that are repeating over multiple test cases and they serve at least 3 basic purposes: less duplicate steps in your project more clear project structure given by the reduction of test steps in a test case ease in updating the project if something is to change at some point (because only the library has to be edited - and not every test steps in particular where they are used) 2.Libraries inReadyAPI Libraries in ReadyAPI are just test cases which are run from another test case or test suite. Take this example: In each step we want to first navigate to someDetails Sectionin a website and then check for name, occupation etc. You can see in the picture above that the Steps in blue quadrants are repeating themselves over and over again which is something usually undesirable. A cleaner way to do it would be to create aLibrariestest suite to hold any steps that are repeating in multiple places and/or are subject to future changes. In our case we would have a OpenDetailsSection test case: Then we would simply make use of the 'Run TestCase'test step to run theOpenDetailsSectiontest case:Lucian4 years agoCommunity Hero301Views0likes0CommentsProperties in SoapUI - case study
The possibility to use properties is a very important aspect in SoapUI automation. And although they are widely used they tend to be not very well understood. Indeed the name 'property' seems a little unfit for an element that (by all means) is presented by SmartBear as a variable to hold certain values for later use in the test execution cycle. For instance, one can have a 'sessionId' element to hold the current session string or one can have a 'cookie' element to hold some cookie value and so on. 1.Defining properties Properties can be defined at the project level, at the test suite level, at the test case level or in a special properties step. For instance, in order to define or edit a property at the test case level one must click on the desired test case in the left hand navigator and then just select the 'Custom Properties' tab. This is similar for any test suite or project. As said before there is also the possibility to use a special'properties step'. Thisstepmust be created inside a certain test case thus its properties will only be available inside that particular test case. 2. Reading properties 2.1 Referencing in SoapUI steps When wanting to reference a property, attention has to be paid to the level at which it was defined. There are different syntaxes that need to be written depending on it as presented in the table below: Level at which it is defined Syntax Example property name Example request Project level ${#Project#Property} Port http://myserver:${#Project#Port} Test suite level ${#TestSuite#Property} Port http://myserver:${#TestSuite#Port} Test case level ${#TestCase#Property} Port http://myserver:${#TestCase#Port} Special properties step ${#Property} Port http://myserver:${#Port} Following the above example, if one would have a project level property named Port with the value 8080, then that property could be used anywhere in the project by simply referencing it like${#Project#Port}. Example: When sending the request, the value of the ${#Project#Port} would be replaced by its value (in our example 8080). 2.2 Referencing in scripts In order to read a property in a script the following syntax is required: /** * Notes: * There are 4 properties defined at 4 levels: project, test suite, test case and special properties step level * The special properties step is simply called 'Properties' */ def stepProperty = testRunner.testCase.getTestStepByName( "Properties" ).getPropertyValue( "myCustomProperty" ) def testCaseProperty = testRunner.testCase.getPropertyValue( "myCustomProperty" ) def testSuiteProperty = testRunner.testCase.testSuite.getPropertyValue( "myCustomProperty" ) def projectProperty = testRunner.testCase.testSuite.project.getPropertyValue( "myCustomProperty" ) 3. Updating properties Updating tests can be done using a groovy script or a special transfer property step. Both of the methods are simple and straightforward but for the purpuse of this article only a groovy script example will be shown: /** * Notes: * This script changes 4 properties defined at 4 levels: project, test suite, test case and special properties step level * The special properties step is simply called 'Properties' */ testRunner.testCase.getTestStepByName( "Properties" ).setPropertyValue( "myCustomProperty", "newSpecialValue" ) testRunner.testCase.setPropertyValue( "myCustomProperty", "newTestCaseValue" ) testRunner.testCase.testSuite.setPropertyValue( "myCustomProperty", "newTestSuiteValue" ) testRunner.testCase.testSuite.project.setPropertyValue( "myCustomProperty", "newProjectValue" )Lucian4 years agoCommunity Hero468Views2likes0CommentsTest run groovy statistics
Solution Here is a script whichlogs in thestatus of the test suites / test cases. // Define variables for holding test suites, test cases and test steps def testSuites def testCases def passedTestCases = 0 def failedTestCases = 0 // Get the list of test suites testSuites = runner.getResults() // Iterate through each test suite testSuites.each() { log.info "----------------------------------------" log.info "The test suite " + "'" + it.getTestSuite().getName() +"'" + " has the status " + it.getStatus() + "." log.info "The following are the contained test cases..." // Get all the test cases and iterate through them testCases = it.getResults() testCases.each() { log.info "...the test case " + "'" + it.getTestCase().getName() +"'" + " with the status " + it.getStatus() + "." if ( it.getStatus().toString().equals("PASS") ) { passedTestCases++ } else { failedTestCases++ } } log.info "The number of passed test cases is " + passedTestCases.toString() + " while the number of failed test cases is " + failedTestCases.toString() + "." passedTestCases = 0 failedTestCases = 0 }Lucian4 years agoCommunity Hero212Views0likes0CommentsGroovy script to log the test execution duration for every test suite/test case
Solution The following is a TearDown script to log test execution duration for every test suite and test case. // Define variables for holding test suites, test cases and test steps def testSuites def testCases // Get the list of test suites testSuites = runner.getResults() // Iterate through each test suite testSuites.each() { log.info "----------------------------------------" log.info "The test suite " + "'" + it.getTestSuite().getName() +"'" + " took " + it.getTimeTaken() + "ms to finish." log.info "The following are the contained test cases along with their durations..." // Get all the test cases and iterate through them testCases = it.getResults() testCases.each() { log.info "...the test case " + "'" + it.getTestCase().getName() +"'" + " - " + it.getTimeTaken() + "ms." } }Lucian4 years agoCommunity Hero194Views0likes0CommentsLog all of the assertions across a test suite
Question How to log all of the assertions across a test suite? Answer Here is a script demonstrating how to do this: // Groovy import com.eviware.soapui.support.xml.XmlUtils; def p = context.testCase.testSuite.project; for(s in p.testSuiteList) { log.info("----------------------" + s.getLabel() + "------------------------"); for(c in s.testCaseList) { log.info("------" + c.getLabel() + "---------"); for(st in c.testStepList) { log.info("---" + st.getLabel() + "---"); if (st instanceof com.eviware.soapui.impl.wsdl.teststeps.WsdlTestRequestStep) { log.info("-------------------------------------------------"); def assertions = st.getAssertionCount(); for (int i = 0; i < assertions; i++) { def assertion = st.getAssertionAt(i); log.info(assertion.getLabel() + " " + assertion.getStatus() + " " + assertion.getErrors()); } } } } }TanyaYatskovska4 years agoSmartBear Alumni (Retired)189Views0likes0CommentsGroovy script to count JSON elements found by a JSONPath query
Solution The following script counts the number of elements found by a JSONPath query: // Groovy import static com.jayway.jsonpath.JsonPath.parse // Define a method to count the number of elements found as a result of a jsonPath query def countElement( String json, String jsonPath ) { return parse( json ).read( jsonPath ).size() } // Create an example jsonObject def jsonObject = '''{ "name" : "John", "age" : 30, "cars" : [ { "car" : "BMW", "color" : "red" }, { "car" : "Ford", "color" : "black" }, { "car" : "Fiat", "color" : "green" } ] }''' // Call the previously created method with the jsonObject as a paramenter log.info countElement( jsonObject, '$.cars.*' )TanyaYatskovska4 years agoSmartBear Alumni (Retired)525Views1like0CommentsParse a JSON Response Using Groovy
Symptoms Quite often we need to parse a JSON response to get some value or to compare the received value with the target one.This code can be copied directly into a Groovy Script test step and run to demonstrate a few ways to get data from a JSON REST response Solution // Some Examples of How to Parse a JSON Response Using Groovy // set the example json response string (for a REST Request step assertion, use "def json = messageExchange.response.contentAsString") def json = '[{"firstName":"Bob","lastName":"Smith","uniqueId":146732,"thisIsAlwaysNull":null,"jobInfo":{"title":"","type":"Peon","code":42},"reviews":[{"date":"2017-06-01","type":"Regular","rating":"Adequate"},{"date":"2017-09-15","type":"Special","rating":"Other"}]},{"firstName":"Jack","lastName":"Jones","uniqueId":746381,"thisIsAlwaysNull":null,"jobInfo":{"title":"Big Boss","type":"Management","code":1},"reviews":[{"date":"2007-11-05","type":"Initial","rating":"Spectacular"}]},{"firstName":"Will","lastName":"Tell","uniqueId":574831,"thisIsAlwaysNull":null,"jobInfo":{"title":"Sweeper","type":"Peon","code":452},"reviews":[]}]' // parse json string using JsonSlurper - basically converts it to a groovy list def parsedJson = new groovy.json.JsonSlurper().parseText(json) // get data log.info " Count of people returned: " + parsedJson.size() log.info " Was Will's info returned (exists)? " + ( parsedJson.find { it.firstName == "Will" } != null ) log.info " Was Alice's info NOT returned (not exists)? " + ( parsedJson.find { it.firstName == "Alice" } == null ) log.info " First person's first name: " + parsedJson[0].firstName log.info " Index of person with ID 746381: " + parsedJson.findIndexOf { it.uniqueId == 746381 } log.info " Info for person with last name Tell: " + parsedJson.find { it.lastName == 'Tell' } log.info " Jack's ID: " + ( parsedJson.find { it.firstName == 'Jack' } ).uniqueId log.info " Jack Jones's job title: " + ( parsedJson.find { it.firstName == 'Jack' && it.lastName == 'Jones' } ).jobInfo.title log.info " All peon job type people's first names: " + ( parsedJson.findAll { it.jobInfo.type == 'Peon' } ).firstName log.info " Is Will's thisIsAlwaysNull null? " + ( ( parsedJson.find { it.firstName == 'Will' } ).thisIsAlwaysNull == null ) log.info " Will Tell's had this many reviews: " + ( parsedJson.find { it.firstName == 'Will' && it.lastName == 'Tell' } ).reviews.size() log.info " Bob's 2017-06-01 review rating: " + ( ( parsedJson.find { it.firstName == 'Bob' } ).reviews.find { it.date == '2017-06-01' } ).ratingTanyaYatskovska4 years agoSmartBear Alumni (Retired)1.7KViews4likes0Comments