diff --git a/documentation/docs/steps/karmaExecuteTests.md b/documentation/docs/steps/karmaExecuteTests.md new file mode 100644 index 00000000000..764b7ed3a2b --- /dev/null +++ b/documentation/docs/steps/karmaExecuteTests.md @@ -0,0 +1,98 @@ +# karmaExecuteTests + +## Description + +In this step the ([Karma test runner](http://karma-runner.github.io)) is executed. + +The step is using the `seleniumExecuteTest` step to spins up two containers in a Docker network: + +- a Selenium/Chrome container (`selenium/standalone-chrome`) +- a NodeJS container (`node:8-stretch`) + +In the Docker network, the containers can be referenced by the values provided in `dockerName` and `sidecarName`, the default values are `karma` and `selenium`. These values must be used in the `hostname` properties of the test configuration ([Karma](https://karma-runner.github.io/1.0/config/configuration-file.html) and [WebDriver](https://github.com/karma-runner/karma-webdriver-launcher#usage)). + +!!! note + In a Kubernetes environment, the containers both need to be referenced with `localhost`. + +## Prerequisites + +- **running Karma tests** - have a NPM module with running tests executed with Karma +- **configured WebDriver** - have the [`karma-webdriver-launcher`](https://github.com/karma-runner/karma-webdriver-launcher) package installed and a custom, WebDriver-based browser configured in Karma + +## Parameters + +| parameter | mandatory | default | possible values | +| ----------|-----------|---------|-----------------| +|script|yes||| +|containerPortMappings|no|`[node:8-stretch: [[containerPort: 9876, hostPort: 9876]]]`|| +|dockerEnvVars|no||| +|dockerImage|no|`node:8-stretch`|| +|dockerName|no|`karma`|| +|dockerWorkspace|no|`/home/node`|| +|failOnError|no||| +|installCommand|no|`npm install --quiet`|| +|modules|no|`['.']`|| +|runCommand|no|`npm run karma`|| +|sidecarEnvVars|no||| +|sidecarImage|no||| +|sidecarName|no||| +|sidecarVolumeBind|no||| +|stashContent|no||| + +- `script` - defines the global script environment of the Jenkinsfile run. Typically `this` is passed to this parameter. This allows the function to access the [`commonPipelineEnvironment`](commonPipelineEnvironment.md) for storing the measured duration. +- `containerPortMappings` - see step [dockerExecute](dockerExecute.md) +- `dockerEnvVars` - see step [dockerExecute](dockerExecute.md) +- `dockerImage` - see step [dockerExecute](dockerExecute.md) +- `dockerName` - see step [dockerExecute](dockerExecute.md) +- `dockerWorkspace` - see step [dockerExecute](dockerExecute.md) +- `failOnError` - see step [seleniumExecuteTests](seleniumExecuteTests.md) +- `installCommand` - the command that is executed to install dependencies +- `modules` - define the paths of the modules to execute tests on +- `runCommand` - the command that is executed to start the tests +- `sidecarEnvVars` - see step [dockerExecute](dockerExecute.md) +- `sidecarImage` - see step [dockerExecute](dockerExecute.md) +- `sidecarName` - see step [dockerExecute](dockerExecute.md) +- `sidecarVolumeBind` - see step [dockerExecute](dockerExecute.md) +- `stashContent` - pass specific stashed that should be considered for the tests + +## Step configuration + +We recommend to define values of step parameters via [config.yml file](../configuration.md). + +In following sections the configuration is possible: + +| parameter | general | step | stage | +| ----------|---------|------|-------| +|script|||| +|containerPortMappings|X|X|X| +|dockerEnvVars|X|X|X| +|dockerImage|X|X|X| +|dockerName|X|X|X| +|dockerWorkspace|X|X|X| +|failOnError|X|X|X| +|installCommand|X|X|X| +|modules|X|X|X| +|runCommand|X|X|X| +|sidecarEnvVars|X|X|X| +|sidecarImage|X|X|X| +|sidecarName|X|X|X| +|sidecarVolumeBind|X|X|X| +|stashContent|X|X|X| + +## Return value + +none + +## Side effects + +Step uses `seleniumExecuteTest` & `dockerExecute` inside. + +## Exceptions + +none + +## Example + +```groovy +karmaExecuteTests script: this, modules: ['./shoppinglist', './catalog'] +``` diff --git a/documentation/mkdocs.yml b/documentation/mkdocs.yml index cef9c12a4f7..8d081014c12 100644 --- a/documentation/mkdocs.yml +++ b/documentation/mkdocs.yml @@ -17,6 +17,7 @@ nav: - handlePipelineStepErrors: steps/handlePipelineStepErrors.md - healthExecuteCheck: steps/healthExecuteCheck.md - influxWriteData: steps/influxWriteData.md + - karmaExecuteTests: steps/karmaExecuteTests.md - mailSendNotification: steps/mailSendNotification.md - mavenExecute: steps/mavenExecute.md - mtaBuild: steps/mtaBuild.md diff --git a/resources/default_pipeline_environment.yml b/resources/default_pipeline_environment.yml index b4787259cb3..562d88537e0 100644 --- a/resources/default_pipeline_environment.yml +++ b/resources/default_pipeline_environment.yml @@ -182,6 +182,18 @@ steps: healthEndpoint: '' influxWriteData: influxServer: 'jenkins' + karmaExecuteTests: + containerPortMappings: + 'node:8-stretch': + - containerPort: 9876 + hostPort: 9876 + dockerImage: 'node:8-stretch' + dockerName: 'karma' + dockerWorkspace: '/home/node' + installCommand: 'npm install --quiet' + modules: + - '.' + runCommand: 'npm run karma' mailSendNotification: notificationAttachment: true notifyCulprits: true diff --git a/test/groovy/KarmaExecuteTestsTest.groovy b/test/groovy/KarmaExecuteTestsTest.groovy new file mode 100644 index 00000000000..d912c380f54 --- /dev/null +++ b/test/groovy/KarmaExecuteTestsTest.groovy @@ -0,0 +1,56 @@ +#!groovy +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.ExpectedException +import org.junit.rules.RuleChain +import util.* + +import static org.hamcrest.Matchers.* +import static org.junit.Assert.assertThat + +class KarmaExecuteTestsTest extends BasePiperTest { + private JenkinsStepRule jsr = new JenkinsStepRule(this) + private JenkinsLoggingRule jlr = new JenkinsLoggingRule(this) + private JenkinsShellCallRule jscr = new JenkinsShellCallRule(this) + private JenkinsEnvironmentRule jer = new JenkinsEnvironmentRule(this) + private ExpectedException thrown = ExpectedException.none() + + @Rule + public RuleChain rules = Rules + .getCommonRules(this) + .around(new JenkinsReadYamlRule(this)) + .around(jscr) + .around(jlr) + .around(jer) + .around(jsr) + .around(thrown) + + def seleniumParams = [:] + + @Before + void init() throws Exception { + helper.registerAllowedMethod("unstash", [String.class], { s -> return [s]}) + + helper.registerAllowedMethod('seleniumExecuteTests', [Map.class, Closure.class], {map, body -> + seleniumParams = map + return body() + }) + } + + @Test + void testDefaults() throws Exception { + jsr.step.karmaExecuteTests( + script: nullScript, + juStabUtils: utils + ) + assertThat(jscr.shell, hasItems( + containsString("cd '.' && npm install --quiet"), + containsString("cd '.' && npm run karma") + )) + assertThat(seleniumParams.dockerImage, is('node:8-stretch')) + assertThat(seleniumParams.dockerName, is('karma')) + assertThat(seleniumParams.dockerWorkspace, is('/home/node')) + assertJobStatusSuccess() + } +} diff --git a/vars/karmaExecuteTests.groovy b/vars/karmaExecuteTests.groovy new file mode 100644 index 00000000000..2743ea86619 --- /dev/null +++ b/vars/karmaExecuteTests.groovy @@ -0,0 +1,85 @@ +import static com.sap.piper.Prerequisites.checkScript + +import com.sap.piper.ConfigurationHelper +import com.sap.piper.GitUtils +import com.sap.piper.Utils + +import groovy.text.SimpleTemplateEngine +import groovy.transform.Field + +@Field String STEP_NAME = 'karmaExecuteTests' +@Field Set GENERAL_CONFIG_KEYS = [ + /** port mappings required for containers. This will only take effect inside a Kubernetes pod, format [[containerPort: 1111, hostPort: 1111]] */ + 'containerPortMappings', + /** envVars to be set in the execution container if required */ + 'dockerEnvVars', + /** Docker image for code execution */ + 'dockerImage', + /** name of the Docker container. If not on Kubernetes pod, this will define the network-alias to the NPM container and is thus required for accessing the server, example http://karma:9876 (default). */ + 'dockerName', + /** user home directory for Docker execution. This will only take effect inside a Kubernetes pod. */ + 'dockerWorkspace', + 'failOnError', + 'installCommand', + 'modules', + 'runCommand', + /** envVars to be set in Selenium container if required */ + 'sidecarEnvVars', + /** image for Selenium execution which runs as sidecar to dockerImage */ + 'sidecarImage', + /** name of the Selenium container. If not on Kubernetes pod, this will define the network-alias to the Selenium container and is thus required for accessing the server, example http://selenium:4444 (default) */ + 'sidecarName', + /** volume bind. This will not take effect in Kubernetes pod. */ + 'sidecarVolumeBind', + /** list of stash names which are required to be unstashed before test run */ + 'stashContent' +] +@Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS +@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS + +void call(Map parameters = [:]) { + handlePipelineStepErrors(stepName: STEP_NAME, stepParameters: parameters) { + final script = checkScript(this, parameters) ?: this + def utils = parameters?.juStabUtils ?: new Utils() + + // load default & individual configuration + Map config = ConfigurationHelper.newInstance(this) + .loadStepDefaults() + .mixinGeneralConfig(script.commonPipelineEnvironment, GENERAL_CONFIG_KEYS) + .mixinStepConfig(script.commonPipelineEnvironment, STEP_CONFIG_KEYS) + .mixinStageConfig(script.commonPipelineEnvironment, parameters.stageName?:env.STAGE_NAME, STEP_CONFIG_KEYS) + .mixin(parameters, PARAMETER_KEYS) + .use() + + utils.pushToSWA([step: STEP_NAME], config) + + def testJobs = [:] + def options = [ + containerPortMappings: config.containerPortMappings, + dockerEnvVars: config.dockerEnvVars, + dockerImage: config.dockerImage, + dockerName: config.dockerName, + dockerWorkspace: config.dockerWorkspace, + failOnError: config.failOnError, + sidecarEnvVars: config.sidecarEnvVars, + sidecarImage: config.sidecarImage, + sidecarName: config.sidecarName, + sidecarVolumeBind: config.sidecarVolumeBind, + stashContent: config.stashContent + ] + for(String path : config.modules){ + testJobs["Karma - ${path}"] = { + seleniumExecuteTests(options){ + sh "cd '${path}' && ${config.installCommand}" + sh "cd '${path}' && ${config.runCommand}" + } + } + } + + if(testJobs.size() == 1){ + testJobs.each({ key, value -> value() }) + }else{ + parallel testJobs.plus([failFast: false]) + } + } +}