{"id":25776789,"url":"https://github.com/salesforce-misc/ReVoman","last_synced_at":"2025-02-27T06:06:57.444Z","repository":{"id":198509427,"uuid":"677000343","full_name":"salesforce-misc/ReVoman","owner":"salesforce-misc","description":null,"archived":false,"fork":false,"pushed_at":"2024-04-22T20:36:55.000Z","size":40068,"stargazers_count":11,"open_issues_count":10,"forks_count":5,"subscribers_count":0,"default_branch":"master","last_synced_at":"2024-04-23T02:15:06.123Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/salesforce-misc.png","metadata":{"files":{"readme":"README.adoc","changelog":null,"contributing":"CONTRIBUTING.adoc","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-08-10T13:59:34.000Z","updated_at":"2024-04-26T14:32:37.822Z","dependencies_parsed_at":null,"dependency_job_id":"f790db21-cf03-41a7-98df-6c5fd166a080","html_url":"https://github.com/salesforce-misc/ReVoman","commit_stats":null,"previous_names":["salesforce-misc/revoman"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salesforce-misc%2FReVoman","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salesforce-misc%2FReVoman/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salesforce-misc%2FReVoman/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salesforce-misc%2FReVoman/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/salesforce-misc","download_url":"https://codeload.github.com/salesforce-misc/ReVoman/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240987435,"owners_count":19889335,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-02-27T06:01:31.078Z","updated_at":"2025-02-27T06:06:57.433Z","avatar_url":"https://github.com/salesforce-misc.png","language":"Kotlin","funding_links":[],"categories":["测试"],"sub_categories":[],"readme":"= ReṼoman (Rev-Woman)\nGopal S Akshintala \u003cgopalakshintala@gmail.com\u003e\n:Revision: 1.0\nifdef::env-github[]\n:tip-caption: :bulb:\n:note-caption: :information_source:\n:important-caption: :heavy_exclamation_mark:\n:caution-caption: :fire:\n:warning-caption: :warning:\nendif::[]\n:hide-uri-scheme:\n:toc:\n:toc-placement!:\n:figure-caption!:\n:sourcedir: src/main/kotlin\n:testdir: src/test/java\n:integrationtestdir: src/integrationTest/java\n:pmtemplates: src/integrationTest/resources/pm-templates\n:imagesdir: docs/images\n:prewrap!:\n:revoman-version: 0.5.7\n\n'''\n\n*ReṼoman* is an API automation tool for JVM (Java/Kotlin) from the API-first SaaS company, *Salesforce*. It re-imagines API automation by letting you execute a Postman collection in a JVM program/test.\n\n'''\n\n[.lead]\nTo start with, think of it as Postman for JVM (Java/Kotlin);\nthat emulates the https://learning.postman.com/docs/collections/running-collections/intro-to-collection-runs/[**Postman Collection Runner**] through a Java program,\nessentially translating your manual testing into Automation, without any loss or resistance.\nBut it's even better!\n\nimage::postman-run.png[]\nimage::manual-to-automation.png[Manual to Automation, align=\"center\"]\n\n[.lead]\nIt strikes a balance between _flexibility_\nprovided by low-level tools like https://rest-assured.io/[**REST Assured**] or https://cucumber.io/[**Cucumber**] and _ease of use_\nprovided by UI tools like https://www.postman.com/[**Postman**]\n\nimage::hybrid-tool.png[]\n\n== Artifact\n\n[.lead]\nMaven\n[source,xml,subs=attributes+]\n----\n\u003cdependency\u003e\n\t\u003cgroupId\u003ecom.salesforce.revoman\u003c/groupId\u003e\n\t\u003cartifactId\u003erevoman\u003c/artifactId\u003e\n\t\u003cversion\u003e{revoman-version}\u003c/version\u003e\n\u003c/dependency\u003e\n----\n[.lead]\nBazel\n[source,bzl,subs=attributes+]\n----\n\"com.salesforce.revoman:revoman\"\n----\n[.lead]\nGradle Kts\n[source,kts,subs=attributes+]\n----\nimplementation(\"com.salesforce.revoman:revoman:{revoman-version}\")\n----\n\n[.lead]\nMinimum Java Version required = 17\n\ntoc::[]\n\n.Tech-Talk given at https://www.opensourceindia.in/osi-speakers-2023/gopala-sarma-akshintala/[Open Source Conf—2023], https://speakerdeck.com/gopalakshintala/revoman-a-template-driven-api-automation-tool-for-jvm[🎴Slide deck]\nimage::revoman-demo-thumbnail.png[link=\"https://www.youtube.com/watch?v=YxeRddSFkxc\u0026list=PLrJbJ9wDl9EC0bG6y9fyDylcfmB_lT_Or\u0026index=2\", align=\"center\"]\n\n== Why ReṼoman?\n\n=== The Problem\n\n* The majority of JVM SaaS applications are REST-based. But the API automation is done through a Mul-*T*-verse of Integration/Functional tests, E2E tests and Manual tests, each with its own frameworks, tools, and internal utilities, testing almost the same code flow.\n* These custom alien automation frameworks, often built using low-level tools like https://rest-assured.io/[**REST Assured**], are specific to a service or domain and are rigid to reuse, extend and difficult to maintain.\n* This automation competes on cognitive complexity and learning curve with the Prod code, and mostly, automation wins.\n* After a point, the API automation may deviate from its purpose of augmenting real end-user interaction and turns into a foot-chain for development.\n\nimage::cognitive-complexity.png[align=\"center\"]\n\n=== The Solution\n\nContrary to these custom frameworks,\nalmost every team uses https://www.postman.com/product/what-is-postman[*Postman*] for manual testing their APIs.\nPostman collections contain a lot of information about your APIs and the order\nin which they need to be executed for manual testing,\nin a https://www.postman.com/templates/[Structured Template].\nLeveraging it can mitigate writing a lot of code as we translate those manual steps into automation.\n\n____\n\n* How _productive_ would it be, if you could plug your exported Postman collection template,\nthat you anyway would have created for your manual testing and executed through your JVM tests?\n\n* How about a Universal API automation tool that promotes low code and low-cognitive-complexity and strikes a balance between flexibility and ease of use?\n\n____\n\n== API automation with _ReṼoman_\n\n=== Template-Driven Testing\n\n* The exported Postman collection JSON file is referred to as a Postman template, as it contains some placeholders/variables in the `+{{variable-key}}+` pattern. You can read more about it https://learning.postman.com/docs/sending-requests/variables/[here]\n* ReṼoman understands these templates and replaces these variables at the runtime, similar to Postman. It supports\n** Nested variables, e.g., `+{{variable-key{{nested-variable-key}}}}+`\n** link:{sourcedir}/com/salesforce/revoman/internal/postman/DynamicVariableGenerator.kt[Dynamic variables], e.g., `{{$randomUUID}}`, `{{$randomEmail}}`\n** \u003c\u003cCustom Dynamic variables\u003e\u003e\n\n[NOTE]\n====\nIn the case of collision between variable keys, the precedence order to derive a value to replace any key is:\n\n. Custom Dynamic variables\n. Generated Dynamic variables\n. Dynamic Environment supplied through config + Environment built during execution + Postman environment supplied as a file through config\n====\n\nTIP: For Dynamic variables, which gets populated from Java code, use $ sign within a variable to indicate it needs to be populated during manual testing. E.g.: `{{$unitPrice}}`\n\n[.lead]\nYou can _kick off_ this *Template-Driven Testing* by invoking `ReVoman.revUp()`,\nsupplying your Postman templates and environments, and all your customizations through a *Configuration*:\n\n[source,java,indent=0,tabsize=2,options=\"nowrap\"]\n----\nfinal var rundown =\n\tReVoman.revUp(\n\t\tKick.configure()\n\t\t...\n\t\t.off())\n----\n\n=== A Simple Example\n\nHere is a simple link:{pmtemplates}/restfulapidev/restful-api.dev.postman_collection.json[Exported Postman collection] and link:{pmtemplates}/restfulapidev/restful-api.dev.postman_environment.json[Environment],\nto hit a free public https://restful-api.dev/[RESTFUL-API].\nYou can import and manually test this collection through the `Run collection` button like this:\n\nimage::resfulapi-dev-pm.png[]\n\nYou can automate the same\nusing ReṼoman in a link:{integrationtestdir}/com/salesforce/revoman/integration/restfulapidev/RestfulAPIDevTest.java[Junit test]\nby supplying the template and environment path:\n\nifdef::env-github[]\n\n[source,java,indent=0,tabsize=2,options=\"nowrap\"]\n.link:{integrationtestdir}/com/salesforce/revoman/integration/restfulapidev/RestfulAPIDevTest.java[RestfulAPIDevTest.java, tag=revoman-simple-demo]\n----\n@Test\n@DisplayName(\"restful-api.dev\")\nvoid restfulApiDev() {\n\tfinal var rundown =\n\t\tReVoman.revUp( // \u003c1\u003e\n\t\t\tKick.configure()\n\t\t\t\t\t.templatePath(PM_COLLECTION_PATH) // \u003c2\u003e\n\t\t\t\t\t.environmentPath(PM_ENVIRONMENT_PATH) // \u003c3\u003e\n\t\t\t\t\t.off());\n\tassertThat(rundown.firstUnIgnoredUnsuccessfulStepReport()).isNull(); // \u003c4\u003e\n\tassertThat(rundown.stepReports).hasSize(3); // \u003c5\u003e\n}\n----\n\u003c1\u003e `revUp` is the method to call passing a configuration, built as below\n\u003c2\u003e Supply an exported Postman collection JSON file path\n\u003c3\u003e Supply an exported Postman environment JSON file path\n\u003c4\u003e Assert that the execution doesn't have any failures\n\u003c5\u003e Run more assertions on the \u003c\u003cRundown\u003e\u003e\n\nendif::[]\nifndef::env-github[]\n\n[source,java,indent=0,tabsize=2,options=\"nowrap\"]\n.link:{integrationtestdir}/com/salesforce/revoman/integration/restfulapidev/RestfulAPIDevTest.java[RestfulAPIDevTest.java,tag=revoman-simple-demo]\n----\ninclude::{integrationtestdir}/com/salesforce/revoman/integration/restfulapidev/RestfulAPIDevTest.java[tag=revoman-simple-demo]\n----\n\u003c1\u003e `revUp` is the method to call passing a configuration, built as below\n\u003c2\u003e Supply an exported Postman collection JSON file path\n\u003c3\u003e Supply an exported Postman environment JSON file path\n\u003c4\u003e Assert that the execution doesn't have any failures\n\u003c5\u003e Run more assertions on the \u003c\u003cRundown\u003e\u003e\n\nendif::[]\n\n=== Rundown\n\nAfter all this, you receive back a detailed *Rundown* in return.\nIt contains everything you need to know about what happened in an execution,\nsuch that you can seamlessly run more assertions on top of the run.\n\n[source,kotlin,indent=0,tabsize=2,options=\"nowrap\"]\n----\nRundown(\n\tval stepReports: List\u003cStepReport\u003e,\n\tval mutableEnv: PostmanEnvironment\u003cAny?\u003e)\n\nStepReport(\n\tstep: Step,\n\trequestInfo: Either\u003cRequestFailure, TxnInfo\u003cRequest\u003e\u003e? = null, // \u003c1\u003e\n\tpreStepHookFailure: PreStepHookFailure? = null,\n\tresponseInfo: Either\u003cResponseFailure, TxnInfo\u003cResponse\u003e\u003e? = null,\n\tpostStepHookFailure: PostStepHookFailure? = null,\n\tenvSnapshot: PostmanEnvironment\u003cAny?\u003e // \u003c2\u003e\n)\n----\n\u003c1\u003e https://docs.vavr.io/#_either[`Either` type from the VAVR] library represents either of the two states, used here to represent error or success\n\u003c2\u003e Snapshot of Environment at the end of each step execution. It can be compared with previous or next step environment snapshots to see what changed in this step\n\n[.lead]\n`Rundown` has many convenient methods to ease applying further assertions on top of it.\n\nTIP: Other simple examples to see in Action: link:{integrationtestdir}/com/salesforce/revoman/integration/pokemon/PokemonTest.java[PokemonTest.java]\n\n=== Advanced Example\n\n[.lead]\nReṼoman isn't just limited to executing Collection like Postman;\nyou can add more _bells and whistles_ 🔔:\n\nifdef::env-github[]\n\n[source,java,indent=0,tabsize=2,options=\"nowrap\"]\n.link:{integrationtestdir}/com/salesforce/revoman/integration/core/pq/PQE2EWithSMTest.java[PQE2EWithSMTest.java, tag=pq-e2e-with-revoman-config-demo]\n----\nfinal var pqRundown =\n\tReVoman.revUp( // \u003c1\u003e\n\t\t\tKick.configure()\n\t\t\t\t\t.templatePaths(PQ_TEMPLATE_PATHS) // \u003c2\u003e\n\t\t\t\t\t.environmentPath(PQ_ENV_PATH) // \u003c3\u003e\n\t\t\t\t\t.dynamicEnvironment( // \u003c4\u003e\n\t\t\t\t\t\t\tMap.of(\n\t\t\t\t\t\t\t\t\t\"$quoteFieldsToQuery\", \"LineItemCount, CalculationStatus\",\n\t\t\t\t\t\t\t\t\t\"$qliFieldsToQuery\", \"Id, Product2Id\",\n\t\t\t\t\t\t\t\t\t\"$qlrFieldsToQuery\", \"Id, QuoteId, MainQuoteLineId, AssociatedQuoteLineId\"))\n\t\t\t\t\t.customDynamicVariableGenerator( // \u003c5\u003e\n\t\t\t\t\t\t\t\"$unitPrice\",\n\t\t\t\t\t\t\t(ignore1, ignore2, ignore3) -\u003e String.valueOf(Random.Default.nextInt(999) + 1))\n\t\t\t\t\t.nodeModulesRelativePath(\"js\") // \u003c6\u003e\n\t\t\t\t\t.haltOnFailureOfTypeExcept(\n\t\t\t\t\t\t\tHTTP_STATUS,\n\t\t\t\t\t\t\tafterAllStepsContainingHeader(\"ignoreHTTPStatusUnsuccessful\")) // \u003c7\u003e\n\t\t\t\t\t.requestConfig( // \u003c8\u003e\n\t\t\t\t\t\t\tunmarshallRequest(\n\t\t\t\t\t\t\t\t\tbeforeStepContainingURIPathOfAny(PQ_URI_PATH),\n\t\t\t\t\t\t\t\t\tPlaceQuoteInputRepresentation.class,\n\t\t\t\t\t\t\t\t\tadapter(PlaceQuoteInputRepresentation.class)))\n\t\t\t\t\t.responseConfig( // \u003c9\u003e\n\t\t\t\t\t\t\tunmarshallResponse(\n\t\t\t\t\t\t\t\t\tafterStepContainingURIPathOfAny(PQ_URI_PATH),\n\t\t\t\t\t\t\t\t\tPlaceQuoteOutputRepresentation.class),\n\t\t\t\t\t\t\tunmarshallResponse(\n\t\t\t\t\t\t\t\t\tafterStepContainingURIPathOfAny(COMPOSITE_GRAPH_URI_PATH),\n\t\t\t\t\t\t\t\t\tCompositeGraphResponse.class,\n\t\t\t\t\t\t\t\t\tCompositeGraphResponse.ADAPTER))\n\t\t\t\t\t.hooks( // \u003c10\u003e\n\t\t\t\t\t\t\tpre(\n\t\t\t\t\t\t\t\t\tbeforeStepContainingURIPathOfAny(PQ_URI_PATH),\n\t\t\t\t\t\t\t\t\t(step, requestInfo, rundown) -\u003e {\n\t\t\t\t\t\t\t\t\t\tif (requestInfo.containsHeader(IS_SYNC_HEADER)) {\n\t\t\t\t\t\t\t\t\t\t\tLOGGER.info(\"This is a Sync step: {}\", step);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\tpost(\n\t\t\t\t\t\t\t\t\tafterStepContainingURIPathOfAny(PQ_URI_PATH),\n\t\t\t\t\t\t\t\t\t(stepReport, ignore) -\u003e {\n\t\t\t\t\t\t\t\t\t\tvalidatePQResponse(stepReport); // \u003c11\u003e\n\t\t\t\t\t\t\t\t\t\tfinal var isSyncStep =\n\t\t\t\t\t\t\t\t\t\t\t\tstepReport.responseInfo.get().containsHeader(IS_SYNC_HEADER);\n\t\t\t\t\t\t\t\t\t\tif (!isSyncStep) {\n\t\t\t\t\t\t\t\t\t\t\tLOGGER.info(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"Waiting in PostHook of the Async Step: {}, for the Quote's Asynchronous processing to finish\",\n\t\t\t\t\t\t\t\t\t\t\t\t\tstepReport.step);\n\t\t\t\t\t\t\t\t\t\t\t// ! CAUTION 10/09/23 gopala.akshintala: This can be flaky until\n\t\t\t\t\t\t\t\t\t\t\t// polling is implemented\n\t\t\t\t\t\t\t\t\t\t\tThread.sleep(5000);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\tpost(\n\t\t\t\t\t\t\t\t\tafterStepContainingURIPathOfAny(COMPOSITE_GRAPH_URI_PATH),\n\t\t\t\t\t\t\t\t\t(stepReport, ignore) -\u003e validateCompositeGraphResponse(stepReport)),\n\t\t\t\t\t\t\tpost(\n\t\t\t\t\t\t\t\t\tafterStepName(\"query-quote-and-related-records\"),\n\t\t\t\t\t\t\t\t\t(ignore, rundown) -\u003e assertAfterPQCreate(rundown.mutableEnv)))\n\t\t\t\t\t.globalCustomTypeAdapter(new IDAdapter()) // \u003c12\u003e\n\t\t\t\t\t.insecureHttp(true) // \u003c13\u003e\n\t\t\t\t\t.off()); // Kick-off\nassertThat(pqRundown.firstUnIgnoredUnsuccessfulStepReport()).isNull(); // \u003c14\u003e\nassertThat(pqRundown.mutableEnv)\n\t.containsAtLeastEntriesIn(\n\t\t\tMap.of(\n\t\t\t\t\t\"quoteCalculationStatusForSkipPricing\", PricingPref.Skip.completeStatus,\n\t\t\t\t\t\"quoteCalculationStatus\", PricingPref.System.completeStatus,\n\t\t\t\t\t\"quoteCalculationStatusAfterAllUpdates\", PricingPref.System.completeStatus));\n----\n\u003c1\u003e `revUp()` is the method to call passing a configuration, built as below\n\u003c2\u003e Supply the path (relative to resources) to the Template Collection JSON file/files\n\u003c3\u003e Supply the path (relative to resources) to the Environment JSON file/files\n\u003c4\u003e Supply any dynamic environment that is runtime-specific\n\u003c5\u003e \u003c\u003cCustom Dynamic variables\u003e\u003e\n\u003c6\u003e Node modules path (relative to resources) to be used inside \u003c\u003cPre-req and Post-res scripts\u003e\u003e\n\u003c7\u003e \u003c\u003cExecution Control\u003e\u003e\n\u003c8\u003e \u003c\u003c#_type_safety_with_flexible_json_pojo_marshallingserialization_and_unmarshallingdeserialization, Request Config\u003e\u003e\n\u003c9\u003e \u003c\u003c#_type_safety_with_flexible_json_pojo_marshallingserialization_and_unmarshallingdeserialization, Response Config\u003e\u003e\n\u003c10\u003e \u003c\u003c#_pre_step_and_post_step_hooks\u003e\u003e\n\u003c11\u003e \u003c\u003cResponse Validations\u003e\u003e\n\u003c12\u003e \u003c\u003c#_type_safety_with_flexible_json_pojo_marshallingserialization_and_unmarshallingdeserialization, Global Custom Type Adapters\u003e\u003e\n\u003c13\u003e Ignore Java cert issues when firing HTTP calls\n\u003c14\u003e Run more assertions on the \u003c\u003cRundown\u003e\u003e\n\nendif::[]\nifndef::env-github[]\n\n[source,java,indent=0,tabsize=2,options=\"nowrap\"]\n.link:{integrationtestdir}/com/salesforce/revoman/integration/core/pq/PQE2EWithSMTest.java[PQE2ETest.java, tag=pq-e2e-with-revoman-config-demo]\n----\ninclude::{integrationtestdir}/com/salesforce/revoman/integration/core/pq/PQE2EWithSMTest.java[tag=pq-e2e-with-revoman-config-demo]\n----\n\u003c1\u003e `revUp()` is the method to call passing a configuration, built as below\n\u003c2\u003e Supply the path (relative to resources) to the Template Collection JSON file/files\n\u003c3\u003e Supply the path (relative to resources) to the Environment JSON file/files\n\u003c4\u003e Supply any dynamic environment that is runtime-specific\n\u003c5\u003e \u003c\u003cCustom Dynamic variables\u003e\u003e\n\u003c6\u003e Node modules path (relative to resources) to be used inside \u003c\u003cPre-req and Post-res scripts\u003e\u003e\n\u003c7\u003e \u003c\u003cExecution Control\u003e\u003e\n\u003c8\u003e \u003c\u003c#_type_safety_with_flexible_json_pojo_marshallingserialization_and_unmarshallingdeserialization, Request Config\u003e\u003e\n\u003c9\u003e \u003c\u003c#_type_safety_with_flexible_json_pojo_marshallingserialization_and_unmarshallingdeserialization, Response Config\u003e\u003e\n\u003c10\u003e \u003c\u003c#_pre_step_and_post_step_hooks\u003e\u003e\n\u003c11\u003e \u003c\u003cResponse Validations\u003e\u003e\n\u003c12\u003e \u003c\u003c#_type_safety_with_flexible_json_pojo_marshallingserialization_and_unmarshallingdeserialization, Global Custom Type Adapters\u003e\u003e\n\u003c13\u003e Ignore Java cert issues when firing HTTP calls\n\u003c14\u003e Run more assertions on the \u003c\u003cRundown\u003e\u003e\nendif::[]\n\n== Reuse Config\n\nYou can define a base common config and reuse it by overriding certain properties using the `override...()` methods,\nwhich are present for each config attribute\n\n== Debugging UX\n\n[.lead]\nThis tool has a particular emphasis on the Debugging experience. Here is what a debugger view of a \u003c\u003cRundown\u003e\u003e looks like:\n\nimage:rundown.png[Rundown of all steps]\n\n[.lead]\n🔍 Let's zoom into a detailed view of one of those Step reports, which contains complete Request and Response info along with failure information if any:\n\nimage:step-report.png[Step Report]\n\n[.lead]\nHere are the environment *key-value* pairs accumulated along the entire execution and appended to the environment from the file and `dynamicEnvironment` supplied:\n\nimage:mutable-env.png[Mutable environment after the execution completion]\n\n[.lead]\nIf something goes wrong at any stage during the Step execution, ReṼoman *fails-fast* and captures the `Failure` in StepReport:\n\nimage:step-execution.png[Step Execution]\n\n[.lead]\nHere is the failure hierarchy of what can go wrong in this process\n\nimage::failure-hierarchy.png[Failure Hierarchy]\n\n[.lead]\nReṼoman logs all the key operations that happen inside its source-code,\nincluding how the environment variables are being mutated by a step in its \u003c\u003cPre-req and Post-res scripts\u003e\u003e.\nWatch your console to check what's going on in the execution or troubleshoot from CI/CD logs\n\nNOTE: link:docs/revoman.exe.log[📝Sample log] printed during execution\n\nimage:pq-exe-logging.gif[Monitor Execution]\n\n== Features\n\n[#_type_safety_with_flexible_json_pojo_marshallingserialization_and_unmarshallingdeserialization]\n=== Type Safety with flexible JSON \u003c- -\u003e POJO marshalling/serialization and unmarshalling/deserialization\n\nPostman operates purely with JSON.\nWhen interoperating Postman with JVM,\nunmarshalling/deserialization of JSON into a POJO and vice versa helps in writing Type-safe JVM code and enhances the debugging experience on JVM.\nReṼoman internally uses a modern JSON library called https://github.com/square/moshi[**Moshi**].\nSimple types whose JSON structure aligns with the POJO data structure can directly be converted.\nBut when the JSON structure may not align with the POJO,\nyou may need a _Custom Type Adapter_ for Marshalling to JSON or Unmarshalling from JSON.\nMoshi has it covered for you with its advanced adapter mechanism and ReṼoman accepts Moshi adapters.\nCheckout these methods that help us interop between Postman and JVM:\n\n==== `globalSkipTypes()`\n\nThere may be a POJO that inherits or contains legacy types that are hard or impossible to serialize.\nReṼoman lets you serialize only types that matter, through `globalSkipTypes`,\nwhere you can filter out these legacy types from Marshalling/Unmarshalling\n\n==== `requestConfig()`\n\n* Configure Moshi adapters to unmarshall/deserialize _Request_ JSON payload to a POJO on certain steps.\n* You may use the bundled static factory methods named `RequestConfig.unmarshallResponse()` for expressiveness.\n* You can pass a `PreTxnStepPick` which is a `Predicate` used\nto qualify a step whose Request JSON payload needs to be unmarshalled/deserialized.\n* If you have set up `requestConfig()` once, wherever you wish to read the request in your \u003c\u003c#_pre_step_and_post_step_hooks,Pre-Step Hooks\u003e\u003e, you can call `stepReport.requestInfo.get().\u003cTypeT\u003egetTypedTxnObj()` which returns your request JSON as a Strong type.\n* If you don't configure it for a step, Moshi unmarshalls the step request JSON into default data structures like `LinkedHashMap`\n\n==== `responseConfig()`\n\n* Configure Moshi adapters to unmarshall/deserialize _Response_ JSON payload to a POJO on certain steps.\n* You can configure separate adapters for success and error response. Success or Error response is determined by default with HTTP Status Code (SUCCESSFUL: `200 \u003c = statusCode \u003c = 299`).\n* Use bundled static factory methods like `ResponseConfig.unmarshallSuccessResponse()` and `ResponseConfig.unmarshallErrorResponse()` for expressiveness.\n* You can pass a `PostTxnStepPick` which is a `Predicate` used\nto qualify a step whose Response JSON payload needs to be unmarshalled/deserialized.\n* If you have set up `responseConfig()` once, wherever you wish to read or assert the response in your \u003c\u003c#_pre_step_and_post_step_hooks,Post-Step Hooks\u003e\u003e, you can call `stepReport.responseInfo.get().\u003cTypeT\u003egetTypedTxnObj()` which returns your response JSON as a Strong type to conveniently assert.\n* If you don't configure it for a step, Moshi unmarshalls the Step response JSON into default data structures like `LinkedHashMap`\n\n[TIP]\n====\n* There may be a scenario\nthat you cannot depend on HTTP Status Code to distinguish between Success or Error.\n* In such a case,\nyou can leverage a bundled Moshi factory link:{sourcedir}/com/salesforce/revoman/input/json/factories/DiMorphicAdapter.kt[DiMorphicAdapter]\nthat deals with it dynamically based on a `boolean` attribute value in the response JSON.\nSee `DiMorphicAdapter` in action from the test `compositeGraphResponseDiMorphicMarshallUnmarshall()` in link:{testdir}/com/salesforce/revoman/input/json/JsonPojoUtilsTest.java[JsonPojoUtilsTest].\n* You may also refer to link:https://square.github.io/moshi/1.x/moshi-adapters/adapters/com.squareup.moshi.adapters/-polymorphic-json-adapter-factory/index.html[PolymorphicJsonAdapterFactory] for Marshalling/Unmarshalling based on `String` type attribute.\n====\n\n==== `globalCustomTypeAdapters()`\n\n* These come handy when the same POJO/Data structure (e.g `ID`) is present across multiple Request or Response POJOs. These adapters compliment the custom adapters setup in `requestConfig()` or `responseConfig()` wherever these types are present.\n* But these adapters won't be used to marshall/unmarshall before or after each step execution. Which means you won't be able to Strong types in the debug view. Although, you can get them on-demand using the respective `getTypedObj()` method.\n\n==== JSON Reader/Writer Utils to build Moshi adapters\n\nReṼoman also comes\nbundled with link:{sourcedir}/com/salesforce/revoman/input/json/JsonReaderUtils.kt[JSON Reader utils] and link:{sourcedir}/com/salesforce/revoman/input/json/JsonWriterUtils.kt[JSON Writer utils]\nto help build Moshi adapters.\n\nTIP: Refer link:{integrationtestdir}/com/salesforce/revoman/integration/core/adapters/ConnectInputRepWithGraphAdapter.java[ConnectInputRepWithGraphAdapter] on how these utils come in handy in building an advanced Moshi adapter\n\n==== JSON POJO Utils\n\nThe bundled link:{sourcedir}/com/salesforce/revoman/input/json/JsonPojoUtils.kt[JSON POJO Utils] can be used to directly to convert JSON to POJO and vice versa.\n\n[TIP]\n====\nSee in Action:\n\n* link:{testdir}/com/salesforce/revoman/input/json/JsonPojoUtilsTest.java[JsonPojoUtilsTest]\n* link:{integrationtestdir}/com/salesforce/revoman/input/json/JsonPojoUtils2Test.java[JsonPojoUtils2Test]\n====\n\n=== Execution Control\n\nThe configuration offers methods through which the execution strategy can be controlled without making any changes to the template:\n\n* `haltOnAnyFailure` — This defaults to `false`. If set to `true`, the execution fails-fast when it encounters a failure.\n* `haltOnFailureOfTypeExcept` — This accepts pairs of `ExeType` and a `PostTxnStepPick` which are used to check if a Step can be ignored for failure for a specific failure type\n* `runOnlySteps`, `skipSteps` — All these accept a `predicate` of type `ExeStepPick`, which is invoked passing the current `Step` instance to decide whether to execute or skip a step.\n** There are some `ExeStepPick` predicates bundled with ReṼoman under `ExeStepPick.PickUtils` e.g `withName`, `inFolder` etc. You can write a custom predicate of your own too.\n\n[#_pre_step_and_post_step_hooks]\n=== Pre-Step and Post-Step Hooks\n\nA hook lets you fiddle with the execution by plugging in your custom JVM code before or after a Step execution.\n\n[#_step_picks]\nYou can pass a `PreTxnStepPick/PostTxnStepPick` which is a `Predicate` used\nto qualify a step for Pre-Step/Post-Step Hook respectively.\nReṼoman comes\nbundled with some predicates under the namespace `PreTxnStepPick.PickUtils`/`PostTxnStepPick.PickUtils` e.g `beforeStepContainingURIPathOfAny()`,\n`afterStepName()` etc. If those don't fit your needs, you can write your own custom predicates like below:\n\n[source,java,indent=0,tabsize=2,options=\"nowrap\"]\n----\nfinal var preTxnStepPick = (currentStep, requestInfo, rundown) -\u003e LOGGER.info(\"Picked `preLogHook` before stepName: {}\", currentStep)\nfinal var postTxnStepPick = (stepReport, rundown) -\u003e LOGGER.info(\"Picked `postLogHook` after stepName: {}\", stepReport.step.displayName)\n----\n\nAdd them to the config as below:\n\n[source,java,indent=0,tabsize=2,options=\"nowrap\"]\n----\n.hooks(\n\tpre(\n\t\t\tpreTxnStepPick,\n\t\t\t(currentStepName, requestInfo, rundown) -\u003e {\n\t\t\t\t//...code...\n\t\t\t}),\n\tpost(\n\t\t\tpostTxnStepPick,\n\t\t\t(currentStepName, rundown) -\u003e {\n\t\t\t\t//...code...\n\t\t\t})\n)\n----\n\n* You can do things like assertion on the rundown, \u003c\u003c#_response_validations,Response Validations\u003e\u003e,\nor even \u003c\u003c#_mutable_environment,mutate the environment\u003e\u003e with a value you programmatically derived,\nsuch that the execution of later steps picks up those changes.\n* Reserve hooks for plugging in your custom code or asserting and fail-fast in the middle of execution.\nIf your assertions can wait till the final rundown,\nit's cleaner to write them after the `revUp()` returns the rundown instead of adding hooks for each step\n\n[#_plug_in_your_java_code_in_between_postman_execution]\n==== Plug-in your Java code in-between Postman execution\n\nYou can plug in your java code\nto create/generate values for environment variables\nwhich can be populated and picked-up by subsequent steps.\nFor example, you may want some `xyzId` but you don't have a Postman collection to create it.\nInstead, you have a Java utility to generate/create it.\nYou can invoke the utility in a pre-hook of a step and set the value in `rundown.mutableEnv`,\nso the later steps can pick up value for `+{{xyzId}}+` variable from the environment.\n\n[#_response_validations]\n==== Response Validations\n\n* Post-Hooks are the best place to validate response right after the step.\n* If you have configured a strong type for your response through `responseConfig`, you can write type-safe validations by extracting your Strong type Object using `stepReport.responseInfo.get().\u003cTypeT\u003egetTypedTxnObj()` (if you have configured `responseConfig()` or `globalCustomTypeAdapters()`) or use `JsonPojoUtils.jsonToPojo(TypeT, stepReport.responseInfo.get().httpMsg.bodyString())` to convert it inline.\n* If your response data structure is non-trivial and has requirements to execute validations with different strategies like `fail-fast` or `error-accumulation`, consider using a library like https://github.com/salesforce-misc/Vador[*Vador*]\n\n=== Pre-req and Post-res scripts\n\n* Postman lets you write custom JavaScript in https://learning.postman.com/docs/writing-scripts/script-references/test-examples/[Pre-req and Post-res tabs] that get executed before and after a step respectively. When you export the collection as a template, these scripts also come bundled.\n* ReṼoman can execute this JavaScript on JVM. This support ensures that the Postman collection used for manual testing can be used *as-is* for the automation also, without any resistance to modify or overhead of maintaining separate versions for manual and automation.\n** Pre-req JS script is executed as the first step before Unmarshall request.\n** Post-res JS script is executed right after receiving an HTTP response.\n* ReṼoman supports using `npm` modules inside your Pre-req and Post-res JS scripts. You can install `npm` modules in any folder using traditional commands like `npm install \u003cmodule\u003e` and supply in the `Kick` config, the relative path of the parent folder that contains the `node_modules` folder using `nodeModulesRelativePath(...)`. Use those `npm` modules inside your scripts with `require(...)`, for example:\n\n.Install `moment` with npm\n[source,shellscript,indent=0,options=\"nowrap\"]\n----\nnpm install moment\n----\n\n.Use inside pre-req and post-res scripts\n[source,javascript,indent=0,tabsize=2,options=\"nowrap\"]\n----\nvar moment = require(\"moment\")\nvar _ = require('lodash')\n\npm.environment.set(\"$currentDate\", moment().format((\"YYYY-MM-DD\")))\nvar futureDateTime = moment().add(365, 'days')\npm.environment.set('$randomFutureDate', futureDateTime.format('YYYY-MM-DD'))\n\npm.environment.set(\"$quantity\", _.random(1, 10))\n----\n\n[TIP]\n====\n* `node_modules` Adds a lot of files to check in. You may replace them with a single distribution file\n\nimage::node_modules.png[]\n\n* If `node_modules` is ignored on your git repo, you can force-add to check in using the command `git add -all -f \u003cpath\u003e/node_modules`\n====\n\nCAUTION: The recommendation is not to add too much code in \u003c\u003cPre-req and Post-res scripts\u003e\u003e, as it's not intuitive to troubleshoot through debugging. Use it for simple operations that can be understood without debugging, and use \u003c\u003c#_pre_step_and_post_step_hooks,Pre-Step /Post-Step Hooks\u003e\u003e for any non-trivial operations, which are intuitive to debug.\n\n[#_mutable_environment]\n=== Mutable Environment\n\n* Environment is the only mutable-shared state across step executions, which can be used for data passing between the consumer and the library.\n* This can be mutated (set key-value pairs) through \u003c\u003cPre-req and Post-res scripts\u003e\u003e (using `pm.environment.set()`) and \u003c\u003c#_pre_step_and_post_step_hooks,Pre-Step /Post-Step Hooks\u003e\u003e (using the reference `rundown.mutableEnv`) during execution.\n\n==== Read Mutable Environment as Postman Environment JSON format\n\nYou may want to troubleshoot manually with Postman using the Mutable environment built during the ReṼoman execution.\n`rundown.mutableEnv.postmanEnvJSONFormat()` converts the mutable environment into a Postman JSON format,\nso you can copy and import that environment conveniently into Postman.\n\nTIP: You do NOT need to save the copied Postman environment JSON from the debugger into file.\nYou can paste (kbd:[Ctrl+v]) directly in the Postman environment window\n\n==== Read Environment value into Strong type\n\nYou can read any value from mutableEnv as a Strong type using `rundown.mutableEnv.\u003cTypeT\u003egetTypedObj()`\nSee it in action: `getTypedObj()`\ntest from link:{testdir}/com/salesforce/revoman/output/postman/PostmanEnvironmentTest.java[PostmanEnvironmentTest]\n\n==== `pmEnvSnapshot` in each StepReport\n\nEach StepReport also has a `pmEnvSnapshot` to assert if a step has executed as expected and compare snapshots from different steps to examine the execution progress.\n\n=== Compose Modular Executions\n\n* You don't have to squash all your steps into one mega collection. Instead, you can break them into easy-to-manage modular collections. `ReVoman.revUp()` accepts a list of collection paths through `templatePaths()`\n* But that doesn't mean you have to execute all these templates in one-go. You can make multiple `ReVoman.revUp()` calls for different collections.\n* If you wish to compose these executions in a specific order, you can use the `revUp()` overload which accepts a vararg `Kick` configs.\n** You can also achieve the same yourself, by adding the previous execution's `mutableEnv` to the current execution using the `dynamicEnvironment` parameter. This also comes in handy when you wish to execute a common step (e.g. `UserSetup`) inside a Test setup method and use that environment for all the tests.\n\n=== Custom Dynamic variables\n\nIf the in-built dynamic variables don't fit your needs, you can plug your own dynamic variable generator\nto be invoked to generate a value for your custom variable-key in the template at runtime.\n\n== USP\n\n=== Low-code\n\nTIP: Here is an example of a low-code link:{integrationtestdir}/com/salesforce/revoman/integration/core/pq/PQE2EWithSMTest.java[**E2E test**] that automates *~75 steps*\n\n[.lead]\nCompared to a traditional Integration/Functional or E2E test, approximately, the amount of code needed is *89%* less using ReṼoman.\nThe above test doesn't just have low code, but also low in Cognitive Complexity and Transparent in what it does.\n\n=== Low Learning Curve\n\nFamiliarity with Postman gets you a long way in understanding this tool.\nPlaying with the existing link:{integrationtestdir}[Integration Tests]\nand writing a couple of hands-on tests should get you started.\n\n=== CI/CD integrability\n\n* ReṼoman is like any JVM library that you can plug into any JVM program/test (e.g., JUnit tests or Integration tests).\n* Apart from adding a dependency in the build tool, there is *no extra setup needed* to execute these tests with ReṼoman in CI/CD.\n\n=== Up-to-date Postman collections that live along with Code in VCS\n\n* A nice side effect is, this lets the Postman collections always stay up to date and the entire Postman collection guards each *check-in* in the form of a Test suite augmenting manual testing.\n* Any day, you can find an up-to-date Postman collection for every feature you need to test, right in your VCS (Git) along with your code. Developers can import these templates directly from VCS into Postman for manual testing. This comes in very handy during a team blitz.\n\n=== Unified framework for Automating __Persona-based__ Manual testing\n\n* ReṼoman brings a *Unified \u0026amp; Simplified Test strategy* across the mul-**T**-verse (Integration Tests, E2E Tests, and Manual testing with Postman) for any API.\n* The automation stays as close as possible to Persona-based Manual testing, leading to Transparency and better Traceability of issues\n* This forces engineers to think like *API-first customers* while writing tests.\n* *Test Data setup:* You can use the ReṼoman for the Test data setup too. This eliminates the need for different teams to write their own internal utilities for data setup.\n* *E2E Test* can even reside outside the Service repo, as long as it can hit the service API\n\n== Perf\n\nThis entire execution of **~75 steps**, including **10 async steps**, took a mere *122 sec* on localhost.\nThis can be much better in auto-build environments.\n\nimage:pq-revoman-test-time.png[Localhost Test time on FTest console for ~75 steps]\n\nWARNING: ReṼoman internally is very light-weight, and the execution times are proportional\nto how your server responds or your network speed.\n\n== Future\n\n[.lead]\nThe future looks bright with multiple impactful features in the pipeline:\n\n* API metrics and Analytics\n* *It's built with extensibility* in mind. It can easily be extended to support other template formats, such as Kaiju templates used for availability testing.\n* In-built polling support for Async steps\n* Payload generation\n* Flow control through YAML config\n\n== FAQs\n\n=== How to Debug a step in the middle of an Execution?\n\n* You can add a \u003c\u003c#_pre_step_and_post_step_hooks,pre-hook\u003e\u003e to the Step you are interested and add a debug point inside that. This gets hit before ReṼoman fires the request in that Step\n* You can get more adventurous by attaching revoman jar sources and directly adding conditional debug points inside this library source-code. You can search for logs in the source-code that indicate key operations to add conditional debug points with conditions like StepName etc.\n\n=== Is there a way to add Metadata to a Postman collection Step?\n\n* You can add key-value pairs to a Step's HTTP Headers section (e.g., `ignoreHTTPStatusUnsuccessful=true`).\n* You can use this information in \u003c\u003c#_step_picks,Step Picks\u003e\u003e or \u003c\u003c#_pre_step_and_post_step_hooks\u003e\u003e to identify a particular step to execute any conditional logic\n\n=== Do I need to migrate all my existing TestUtils to Postman Collections?\n\nYou don't have to.\nThis is a JVM-first tool,\nand you can interlace your TestUtils through \u003c\u003c#_plug_in_your_java_code_in_between_postman_execution,Pre-Step/Post-Step Hooks\u003e\u003e\n\n=== Why not use https://learning.postman.com/docs/collections/using-newman-cli/command-line-integration-with-newman[Newman] or https://learning.postman.com/docs/postman-cli/postman-cli-overview/#comparing-the-postman-cli-and-newman[Postman CLI]?\n\n* ReṼoman may be similar to Newman or Postman CLI when it comes to executing a Postman collection, but the _similarities end there_.\n* Newman or Postman CLI are built for node and cannot be executed within a JVM. Even if you are able to run in some hacky way, there is no easy way to assert results.\n* ReṼoman is JVM first that lets you configure a lot more, and gives you back a detailed report to assert in a typesafe way\n\n== 🙌🏼 Consume-Collaborate-Contribute\n\n* This link:CONTRIBUTING.adoc[CONTRIBUTING] doc has all the information to set up this library on your local and get hands-on.\n* Any issues or PRs are welcome! ♥️\n* Join this https://sfdc.co/revoman-slack[Slack Community] to discuss issues or PRs related to Consumption-Collaboration-Contribution\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsalesforce-misc%2FReVoman","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsalesforce-misc%2FReVoman","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsalesforce-misc%2FReVoman/lists"}