Playback via Gradle

abstract This section describes playing back Sahi Flowcharts via Gradle.

Playback on a single machine

One can now use the Run Settings dialog to generate the Gradle script automatically. Refer Run Settings for details on the UI. This will generate the contents of the Gradle script file.

If you wish for only the failed scripts to be retried after the run is complete, check the Add Retry Task checkbox on the Gradle Script dialog.

Once the Gradle script content is generated, copy the content to an empty file. If groovy was selected for gradle script, save the file as build.gradle, and for Kotlin, save the file as build.gradle.kts in your <SAHI_INSTALLATION_FOLDER>.

Use the gradle command to execute the gradle script.
gradle


or
./gradlew	 # if the script is saved as a gradle project


info
  • Gradle classpath is relative to where gradle is run from.
  • The main task is the runsahitests task which is also the default task, so you do not need to specify the task when running the gradle script.
info If you choose the Run sequentially in single browser session option, single session attribute would be set to true. Everything else remains the same.
info By default Gradle suppresses the info logs generated by Sahi. To view these logs execute the script using gradle --info or, create a file named gradle.properties alongside build.gradle and add this line:
org.gradle.logging.level=info
info When running large projects, you may face heap overflow issues gradle. This can be resolved by increasing the heap size of gradle. eg., to set heap size to 2048MB
  • run gradle with gradle -Dorg.gradle.jvmargs=-Xmx2048m
  • create a file named gradle.properties alongside build.gradle and add this line:
    org.gradle.jvmargs=-Xmx2048m

Distributed playback via Gradle

Distributed playback can be achieved through Gradle tasks as well.

These tasks can be used from Jenkins to distribute the scripts on multiple machines. For more details please refer Jenkins Integration.

The machine which distributes the scripts and manages the distributed run is referred to as the Master. All logs are visible from the Master. The other machines are called Slaves. Any machine in the distributed environment can serve as the Master. The machine which launches the distributed run is referred to as the Initiator.

Distributed run on the same Master

In the Run Settings dialog,

Distributed run on a different Master

In the Run Settings dialog, Refer Distributed Playback for details

info Depending on the scope of run, the fcProjectId, fcFlowchartId and fcPathId properties appear differently in the setproperties task of gradle script.
  • Project level Run: This occurs when the Run button on the Project page is used. All the flowcharts and all the paths within the specified project will be executed. Only fcProjectId property will be present in the gradle script.
  • Flowchart level Run: This occurs when the Run button on the Flowchart page is used. Only the specified flowchart among all the flowcharts of the selected project will be executed. Both fcProjectId and fcFlowchartId properties will be present in the gradle script.
  • Path level Run: This occurs when the Run button on the Automate dialog is used. Only the specified path among all the paths of the selected flowchart will be executed. All three properties, fcProjectId, fcFlowchartId and fcPathId will be present in the gradle script.

Sample Gradle Script

The above UIs for Parallel run/distributed run/different master would generate a Gradle script that looks like the following.
# Groovy Script
apply plugin: 'base'
apply plugin: 'java'

buildscript {
    repositories {
        flatDir name: 'localRepository', dirs: 'lib'
    }
    dependencies {
        classpath ':ant-sahi'
    }
}

import net.sf.sahi.util.BuildUtils
import java.text.DateFormat
import java.text.SimpleDateFormat

static def getFormattedDateTime() {
    DateFormat df = new SimpleDateFormat("yyyy_MM_dd__HH_mm_ss", new Locale("en", "IN"))
    return df.format(new Date())
}

tasks.register("set_timestamp") {
    group = "Sahi"
    description = "Set timestamp"

    extensions.add("timestamp", getFormattedDateTime())
}

tasks.register("setpropertieschrome") {
    group = "Sahi"
    description = "Initialize properties for run"
    dependsOn "set_timestamp"

    def ts = tasks.named("set_timestamp").get().timestamp
    def browser = "chrome"

    def props = [
		isFlowchartRun: "true",
		scriptDir: "D:/SahiPro/userdata/scripts5/",
		host: "localhost",
		port: "9999",
		threads: "5",
		autoThread: "false",
		failureRetryCount: "2",
		abortedRetryCount: "1",
		isAvoidSkipping: "false",
		startWith: "BROWSER",
		browser: "chrome",
		baseurl: "https://demoapps.testflowchart.com/opencart/",
		tags: "",
		suiteTimeOut: "0.0",
		fcIsNormalRun: "true",
		fcIsPositiveValidationRun: "true",
		fcIsNegativeValidationRun: "true",
		fcRetryFailuresEnabled: "true",
		failedSuiteName: "almost empty_failed_chrome.flowcharts.dd.csv",
		failedSuite: "almost empty_failed_chrome.flowcharts.dd.csv",
		fcProjectId: "almost empty",
		fcProjectName: "almost empty",
		fcFlowchartId: "opencart registration",
		fcFlowchartName: "opencart registration",
		fcPathId: "0",
		sendemail: "true",
		emailtrigger: "success,failure,user_aborted,aborted_timeout",
		emailattachment: "success,failure",
		emailproperties: "D:\\SahiPro\\userdata\\config\\email.properties",
		sendemailperiodically: "false",
		emailPasswordHidden: "true",
		haltonfailure: "false",
		showPeriodicSummary: "false",
		userDefinedId: "fc-user-id",
		isDRun: "true",
		ignorePattern: ".*(svn|copied).*",
		scriptsPathMaster: "temp/scripts/staging/${ts}_${browser}",
		scriptsPathInitiator: "D:/SahiPro/userdata/scripts5/",
		isSyncConfig: "true",
		isDifferentMaster: "true",
		sahihost: "192.168.1.94",
		sahiport: "9999",
		nodes: "192.168.1.92:9999,172.68.43.69:9999",
		reports: "junit,xml:D:/reports/xml,html:D:/reports/html",
		customfields: "extraParameters=filePath",
		scriptExtensions: "sah;sahi;js;ar.csv",
		scenarioExtensions: ".s.csv;xls;xlsx",
		csvSeparator: ",",
		javaDir: "java",
		exposedJavaClasses: "exposed_classes.txt"
    ]

    project.ext.propschrome = props
}

def buildUtils_chrome = new BuildUtils()
tasks.register("runtestschrome") {
    group = "Sahi"
    description = "Run tests on chrome browser"
    dependsOn "setpropertieschrome"

    def props_chrome_provider = project.provider {project.ext.propschrome}

    doLast {
        def ts = tasks.named("set_timestamp").get().timestamp
        def properties = props_chrome_provider.get() as HashMap

        def argsMap = [:]

        properties.each {key,value ->
            argsMap.put(key as String, value as String)
        }
        argsMap.put("reports", "junit:logs/temp/junit/${ts},xml:logs/temp/xml/${ts},html:logs/temp/html/${ts}".toString())

        def status = buildUtils_chrome.execute(argsMap , true)
        println "runtestschrome STATUS: ${status}"

        def deleteParams = [
            scriptDir: argsMap.scriptsPathInitiator,
            filePath: argsMap.scriptsPathMaster,
            isAutohealEnabled: argsMap.isAutohealEnabled,
            isSyncConfig: argsMap.isSyncConfig
        ]
        BuildUtils.deleteSuiteFromMasterStaging(argsMap.sahihost, argsMap.sahiport, deleteParams)
        def logs = [
            file("logs/junit").absolutePath, "D:/reports/xml", "D:/reports/html"
        ]
        BuildUtils.pullOfflineLogsFromMaster(argsMap.sahihost, argsMap.sahiport, argsMap.ignorePattern, logs, argsMap.reports)

        runtestschrome.ext.status = status
    }

    finalizedBy "retrytestschrome"
}


tasks.register("retrytestschrome") {
    group = "Sahi"
    description = "Retry failed scripts on chrome Browser"
    dependsOn "setpropertieschrome"

    onlyIf {
        runtestschrome.ext.status != "SUCCESS"
    }

    def props_chrome_provider = project.provider {project.ext.propschrome}

    doLast {
        def ts = tasks.named("set_timestamp").get().timestamp
        def properties = props_chrome_provider.get() as HashMap

        def argsMap = [:]

        properties.each {key,value ->
            argsMap.put(key as String, value as String)
        }

        argsMap["scriptsPathMaster"] = "${argsMap["scriptsPathMaster"]}_retry".toString()
        argsMap["suite"] = argsMap["failedSuite"]
        argsMap.put("reports", "junit:logs/temp/junit/${ts}/retry,xml:logs/temp/xml/${ts}/retry,html:logs/temp/html/${ts}/retry".toString())

        def status = buildUtils_chrome.retry(argsMap)
        println "retrytestschrome STATUS: ${status}"

        def deleteParams = [
            scriptDir: argsMap.scriptsPathInitiator,
            filePath: argsMap.scriptsPathMaster,
            isAutohealEnabled: argsMap.isAutohealEnabled,
            isSyncConfig: argsMap.isSyncConfig
        ]
        BuildUtils.deleteSuiteFromMasterStaging(argsMap.sahihost, argsMap.sahiport, deleteParams)
        def logs = [
            file("logs/junit/retry").absolutePath, "D:/reports/xml/retry", "D:/reports/html/retry"
        ]
        BuildUtils.pullOfflineLogsFromMaster(argsMap.sahihost, argsMap.sahiport, argsMap.ignorePattern, logs, argsMap.reports)

        if (!"SUCCESS".equalsIgnoreCase(status)) {
            throw new GradleException("sahi retry tests failed on browser ${properties.browser} with status ${status}")
        }
    }
}

tasks.register("runsahitests") {
    group = "Sahi"
    description = "Run all tests"
    dependsOn "runtestschrome"
}

defaultTasks 'runsahitests'


# Kotlin Script
import net.sf.sahi.util.BuildUtils
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.Date

plugins {
    id ("base")
    id("java")
}

buildscript {
    dependencies {
        classpath(files("lib/ant-sahi.jar"))
    }
}

fun getFormattedDateTime(): String {
    val df = SimpleDateFormat("yyyy_MM_dd__HH_mm_ss", Locale.of("en", "IN"))
    return df.format(Date())
}

tasks.register("settimestamp") {
    group = "Sahi"
    description = "Set timestamp"

    extensions.add("timestamp", getFormattedDateTime())
}

val propschrome: MapProperty<String, String> = project.objects.mapProperty(String::class.java, String::class.java)

tasks.register("setpropertieschrome") {
    group = "Sahi"
    description = "Initialize properties for run"
    dependsOn("settimestamp")

    val ts = tasks.named("settimestamp").get().extensions.findByName("timestamp")
    val browser = "chrome"

    val props = mutableMapOf(
		"isFlowchartRun" to "true",
		"scriptDir" to "D:/SahiPro/userdata/scripts5/",
		"host" to "localhost",
		"port" to "9999",
		"threads" to "5",
		"autoThread" to "false",
		"failureRetryCount" to "2",
		"abortedRetryCount" to "1",
		"isAvoidSkipping" to "false",
		"startWith" to "BROWSER",
		"browser" to "chrome",
		"baseurl" to "https://demoapps.testflowchart.com/opencart/",
		"tags" to "",
		"suiteTimeOut" to "0.0",
		"fcIsNormalRun" to "true",
		"fcIsPositiveValidationRun" to "true",
		"fcIsNegativeValidationRun" to "true",
		"fcRetryFailuresEnabled" to "true",
		"failedSuiteName" to "almost empty_failed_chrome.flowcharts.dd.csv",
		"failedSuite" to "almost empty_failed_chrome.flowcharts.dd.csv",
		"fcProjectId" to "almost empty",
		"fcProjectName" to "almost empty",
		"fcFlowchartId" to "opencart registration",
		"fcFlowchartName" to "opencart registration",
		"fcPathId" to "0",
		"sendemail" to "true",
		"emailtrigger" to "success,failure,user_aborted,aborted_timeout",
		"emailattachment" to "success,failure",
		"emailproperties" to "D:\\SahiPro\\userdata\\config\\email.properties",
		"sendemailperiodically" to "false",
		"emailPasswordHidden" to "true",
		"haltonfailure" to "false",
		"showPeriodicSummary" to "false",
		"userDefinedId" to "fc-user-id",
		"isDRun" to "true",
		"ignorePattern" to ".*(svn|copied).*",
		"scriptsPathMaster" to "temp/scripts/staging/${ts}_${browser}",
		"scriptsPathInitiator" to "D:/SahiPro/userdata/scripts5/",
		"isSyncConfig" to "true",
		"isDifferentMaster" to "true",
		"sahihost" to "192.168.1.94",
		"sahiport" to "9999",
		"nodes" to "192.168.1.92:9999,172.68.43.69:9999",
		"reports" to "junit,xml:D:/reports/xml,html:D:/reports/html",
		"customfields" to "extraParameters=filePath",
		"scriptExtensions" to "sah;sahi;js;ar.csv",
		"scenarioExtensions" to ".s.csv;xls;xlsx",
		"csvSeparator" to ",",
		"javaDir" to "java",
		"exposedJavaClasses" to "exposed_classes.txt"
    )

    propschrome.set(props)
}

val buildUtils_chrome = BuildUtils()
tasks.register("runtestschrome") {
    group = "Sahi"
    description = "Run tests on chrome browser"
    dependsOn("setpropertieschrome")

    doLast {
        val ts = tasks.named("settimestamp").get().extensions.findByName("timestamp")
        val properties =  propschrome.get()

        val argsMap = mutableMapOf<String, String>()
        properties.forEach { (key: String, value: String) ->
            argsMap.put(key, value)
        }
        argsMap.put("reports", "junit:logs/temp/junit/${ts},xml:logs/temp/xml/${ts},html:logs/temp/html/${ts}".toString())

        val status = buildUtils_chrome.execute(argsMap , true)
        println("runtestschrome STATUS: ${status}")

        val deleteParams = mutableMapOf(
            "scriptDir" to "${argsMap["scriptsPathInitiator"]}",
            "filePath" to "${argsMap["scriptsPathMaster"]}",
            "isAutohealEnabled" to "${argsMap["isAutohealEnabled"]}",
            "isSyncConfig" to "${argsMap["isSyncConfig"]}"
        )
        BuildUtils.deleteSuiteFromMasterStaging(argsMap["sahihost"], argsMap["sahiport"], deleteParams)
        val logs = arrayOf(
            file("logs/junit").absolutePath, "D:/reports/xml", "D:/reports/html"
        ).toMutableList()
        BuildUtils.pullOfflineLogsFromMaster(argsMap["sahihost"], argsMap["sahiport"], argsMap["ignorePattern"], logs, argsMap["reports"])

        project.extra.set("runtestschromestatus", status)
    }

    finalizedBy("retrytestschrome")
}


tasks.register("retrytestschrome") {
    group="Sahi"
    description="Retry failed scripts on chrome browser"
    dependsOn("setpropertieschrome")

    onlyIf {
        !(project.extra.get("runtestschromestatus") as String).equals("SUCCESS", ignoreCase=true)
    }

    doLast {
        val ts = tasks.named("settimestamp").get().extensions.findByName("timestamp")
        val properties =  propschrome.get()

        val argsMap = mutableMapOf<String, String>()

        properties.forEach { (key: String, value: String) ->
            argsMap.put(key as String, value as String)
        }

        argsMap["scriptsPathMaster"] = "${argsMap["scriptsPathMaster"]}_retry".toString()
        argsMap["suite"] = argsMap["failedSuite"] ?: ""
        argsMap.put("reports", "junit:logs/temp/junit/${ts}/retry,xml:logs/temp/xml/${ts}/retry,html:logs/temp/html/${ts}/retry".toString())

        val status = buildUtils_chrome.retry(argsMap)
        println("retrytestschrome STATUS: ${status}")

        val deleteParams = mutableMapOf(
            "scriptDir" to "${argsMap["scriptsPathInitiator"]}",
            "filePath" to "${argsMap["scriptsPathMaster"]}",
            "isAutohealEnabled" to "${argsMap["isAutohealEnabled"]}",
            "isSyncConfig" to "${argsMap["isSyncConfig"]}"
        )
        BuildUtils.deleteSuiteFromMasterStaging(argsMap["sahihost"], argsMap["sahiport"], deleteParams)
        val logs = arrayOf(
            file("logs/junit/retry").absolutePath, "D:/reports/xml/retry", "D:/reports/html/retry"
        ).toMutableList()
        BuildUtils.pullOfflineLogsFromMaster(argsMap["sahihost"], argsMap["sahiport"], argsMap["ignorePattern"], logs, argsMap["reports"])

        if (!"SUCCESS".equals(status, ignoreCase = true)) {
            throw GradleException("sahi tests failed on browser ${properties.get("browser")} with status ${status}")
        }
    }
}

tasks.register("runsahitests") {
    group = "Sahi"
    description = "run all tests"
    dependsOn("runtestschrome")
}

defaultTasks("runsahitests")


danger NOTE: The above Gradle Script is for illustration purposes only. Use the Run Settings dialog to generate the gradle script content and save it to a file.

Attributes

Description of the Attributes in gradle scripts:

scriptDirSpecifies the path to the scripts folder. Can be the absolute path or a path relative to the userdata folder
fcProjectIdThis contains the name of the project intended to run.
fcFlowchartIdThis contains the name of the particular flowchart we intended to run.
fcPathIdThis contains specific path ID which we intend to run.
fcIsNormalRunThis contains state of the checkbox "Run Normal Path". If checked, the path will run without any validation i.e; with default or data generator's values.
fcIsNegativeValidationRunThis contains state of the checkbox "Run Negative Validations". If checked, the path will run with negative validation values.
fcIsPositiveValidationRunThis contains state of the checkbox "Run Positive Validations". If checked, the path will run with positive validation values.
fcIsRetryFailuresEnabledThis contains state of the checkbox "Add Retry Task". The value needs to be"true" if the user needs to retry the failed path during first run attempt.
If the checkbox for this property is checked, it adds an extra retry task, for example,retrytestschrome for running tests on chrome browser.
browserThe browser on which the file plays back
baseUrlSpecifies the starting URL for all the scripts.
hostHostname of the server where Sahi is running (Can be IP as well)
portPort on which Sahi is running
threadsNumber of simultaneous browser instances on which sahi tests will be run.
singlesessionSince this is a parallel run, it will be "false". If "true", runs all scripts in a single browser without closing it between scripts.
showPeriodicSummaryTakes "true" or "false". If "true", a periodic summary of script status is logged in the Gradle Console.
sendemailTakes "true" or "false". If "true", Sahi sends an email summarizing the playback.
emailtriggerTakes "success" or "failure" or "success,failure". Triggers email in case of SUCCESS, FAILURE and both cases respectively.
emailpropertiesPath to the properties file that contains details about the mail viz. information about the sender, receiver, host, port, subject, content etc. Path can be the absolute path or relative to where this gradle task is run from.
sendemailperiodicallyTakes "true" or "false". If "true", Sahi will send emails periodically till the execution is complete.
sendemailperiodicallytimeSpecifies the interval in minutes at which periodic emails are to be sent.
failedSuiteNameName of the failed suite. It is of the form <suitename>_failed_<browser>.suite.
failedsuiteRelative path to the failed suite. It is relative to scriptDir.
tagsTags should be specified only for dd.csv and .csv suites. Tags are specified so that it is easy to choose which scripts/testcases to run. eg. if tags are (user||admin)&&medium, all the scripts which have 'medium' tag and 'admin' or 'user' (or both) tag will be run.
userdefinedidThe unique id that the user can pass to differentiate a suite from others. This is an optional attribute.
reportsSahi can set offline logs to be generated in xml, html, pdf, junit, and excel types. The default type is html. Check the options required. This is an optional attribute.
customfieldsThis contains the values present in External Variables field. This is an optional attribute.

User can define multiple key-value pairs in the text-box provided(one pair each line). User then needs to copy the content of this text-box to a .propertiesfile and save it with a proper name. Now, the user needs to copy the path of this file into the value section of the .xml file's customfield property.
nodesnodes specify the machines on which the tests should run. Add as many node entries as there are machines to run. The nodes may or may not include the Master machine (localhost). If the Master machine is not included, scripts will not be run on the Master. There can be 1 or more nodes. This is an optional attribute.
sahihostHostname of the different Master server where Sahi is running (Can be IP as well)
sahiportPort on which Sahi is running
isSyncConfigShows status of Sync Configuration checkbox. If checked, it will sync the connfigurations from master machine to other nodes/slave machines.
configPathlocation of the configuration file

Killing an Ongoing Execution

warning WARNING: Killing single or all ongoing executions in the middle of it is not a routine task. You would kill an execution only if something went wrong and you wish to stop the execution.
Stop All from the Run button's dropdownn menu from the UI can be used to kill all ongoing executions.

This is possible through gradle task as well.

The Gradle task gives the ability to kill a specific execution or all ongoing executions.

You can run the Gradle task from the generated gradle script as
gradle kill -Phost=<masterhost> -Pport=<masterport> -PsuiteId=<suiteId> -PuserDefinedId=<userDefinedId>


Parameters and their description
sahihostHostname of the Master where Sahi is running (Can be IP as well).
sahiportPort on which Sahi is running
userDefinedIdUserDefinedId specified while running the original suite
suiteIdSuiteId of the running suite. Get this value from the Suite Info section of the Suite Report logs
info Alternatively, the following URL can be used to kill all the running scripts:
http://<master machine IP>:9999/_s_/dyn/pro/Master_killAll