diff --git a/README.md b/README.md
index c8bf373df..1b13d5a87 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@ The site is automatically deployed when commits are merged/pushed in `master`, h
### Preview documentation locally with Docker (Recommended)
-1. Install [docker-compose](https://docs.docker.com/compose/)
+1. Install [Docker Compose](https://docs.docker.com/compose/)
2. Run `docker-compose up`
3. Open `http://localhost:3131` in your browser, and voila!
diff --git a/_data/home-content.yml b/_data/home-content.yml
index 4fdd35926..99639d2e4 100644
--- a/_data/home-content.yml
+++ b/_data/home-content.yml
@@ -1,5 +1,6 @@
+
- title: Example catalog
icon: images/home-icons/tutorial.svg
url: ''
@@ -15,8 +16,6 @@
-
-
- title: Deployments
icon: images/home-icons/deployment.svg
url: ''
diff --git a/_data/nav.yml b/_data/nav.yml
index 681b2dce9..bb4ce2864 100644
--- a/_data/nav.yml
+++ b/_data/nav.yml
@@ -1,6 +1,4 @@
-
-
- title: Example catalog
url: "/example-catalog"
@@ -115,6 +113,7 @@
url: "/amazon-ecs"
- title: Elastic Beanstalk
url: "/elastic-beanstalk"
+
- title: Deployments
url: "/deployments"
pages:
@@ -257,7 +256,23 @@
- title: Sharing file systems
url: "/sharing-file-system"
-
+- title: CI/CD testing
+ url: "/testing"
+ pages:
+ - title: Unit tests
+ url: "/unit-tests"
+ - title: Integration tests
+ url: "/integration-tests"
+ - title: Creating test reports
+ url: "/test-reports"
+ - title: Creating compositions
+ url: "/create-composition"
+ - title: Dynamic preview environments
+ url: "/automatic-preview-environments"
+ - title: Security scanning
+ url: "/security-scanning"
+ - title: SonarQube scanning
+ url: "/sonarqube-integration"
- title: Clients
url: "/clients"
diff --git a/_docs/testing/automatic-preview-environments.md b/_docs/testing/automatic-preview-environments.md
new file mode 100644
index 000000000..163c0fb23
--- /dev/null
+++ b/_docs/testing/automatic-preview-environments.md
@@ -0,0 +1,65 @@
+---
+title: "Dynamic preview environments"
+description: "Preview test environments"
+group: testing
+toc: true
+---
+If your service is one of many microservices, after running automated tests on your service, you would probably want to check the new service with your whole system. In this case, you can launch the composition of your system as part of your build, and at the end of the build, open the composition.
+
+## Prerequisites
+
+Complete the tutorials for:
+* [Creating a basic pipeline]({{site.baseurl}}/docs/getting-started/create-a-basic-pipeline/)
+* [Creating temporary environments]({{site.baseurl}}/docs/getting-started/on-demand-environments/)
+
+## Launch the composition
+
+{:start="1"}
+1. Open your `codefresh.yml` file and add a new step:
+```yaml
+launch_composition_step:
+ title: "Launch full composition with latest images"
+ type: launch-composition
+ composition: your-composition-name
+ fail_fast: false
+```
+1. Commit and push the changes to Git repository.
+1. Build your service with Codefresh.
+1. In the Codefresh UI, from the Artifacts section on the sidebar, select **Compositions**, and then select the **Running Compositions** tab.
+ The new preview environment is displayed in the list of Running Compositions.
+
+## Launch an environment on single branch
+
+There is a limit to the number of environments you can run concurrently. That's why it's a good practice to launch the composition only on a certain condition. Usually the most relevant condition is the branch, since you probably want your environment to be updated on the main branch.
+
+The following instructions describe how to launch the environment for only the `master` branch:
+
+{:start="1"}
+1. Open your `codefresh.yml` file and add to the `launch_composition_step` the following:
+```yaml
+when:
+ branch:
+ only:
+ - master
+```
+1. Commit and push changes to your Git repository's `master` branch.
+1. Build your service with Codefresh on branch `master`.
+1. Create a new branch and push it to your Git repository under a new branch.
+1. Build your service with Codefresh on the new branch.
+1. When the build completes execution, open its log.
+ You should see something similar to the example below.
+
+{% include image.html
+lightbox="true"
+file="/images/testing/dynamic-preview-environment.png"
+url="/images/testing/dynamic-preview-environment.png"
+alt="Launch environment on single branch"
+caption="Launch environment on single branch"
+max-width="70%"
+%}
+
+## Related articles
+[Codefresh YAML]({{site.baseurl}}/docs/pipelines/what-is-the-codefresh-yaml/)
+[Creating compositions]({{site.baseurl}}/docs/testing/create-composition/)
+[Integration tests]({{site.baseurl}}/docs/testing/integration-tests/)
+[Service containers]({{site.baseurl}}/docs/pipelines/service-containers/)
diff --git a/_docs/testing/composition-service-discovery.md b/_docs/testing/composition-service-discovery.md
new file mode 100644
index 000000000..aa2cc537f
--- /dev/null
+++ b/_docs/testing/composition-service-discovery.md
@@ -0,0 +1,98 @@
+---
+title: "Composition Service Discovery (Experimental)"
+description: ""
+group: testing
+redirect_from:
+ - /docs/integration-tests/
+ - /docs/on-demand-test-environment/composition-service-discovery/
+toc: true
+---
+Codefresh enables you to launch multiple instances of the same composition utilizing Docker.
+This is possible, in part, because Docker can map ports from a container to a random port on a host. Following from this are some basic questions:
+ * If your container is mapped to a random port, how can you predict the URL of the application?
+ * How can you configure your web application's container with a "Base URL"?
+ * How can you reference other services without linking to them?
+
+{{site.data.callout.callout_info}}
+##### Availability
+
+This feature is available for users with a Pro subscription.
+Already a Pro subscriber? Contact us to opt into this feature.
+{{site.data.callout.end}}
+
+Codefresh utilizes `Dynamic Composition Service Routing` to deliver seamless service discovery within a composition.
+
+After you enable this feature for your account, Codefresh injects the URLs of every service in your composition as environment variables.
+
+## What do these environment variables look like?
+
+Every service URL environment variable is prefixed with `CF_URL_` and is uniquely identifiable using the service name:
+`CF_URL_SERVICENAME=http://foo.cf-cd.com/unique-url`.
+
+If your service exposes multiple ports, an environment variable will be injected for every port, and will be comprised of a combination of the service name and the service's port:
+`CF_URL_SERVICENAME_PORTNUMBER=http://foo.cf-cd.com/unique-url-1`.
+
+Also, every service would be having a domain that would direct to each service. The domains can be uniquely identifiable with environments variables:
+`CF_DOMAIN_SERVICENAME=unique-prefix.foo.cf-cd.com`.
+
+For services that exposes multiple ports the environment variable should have the port number as a suffix:
+`CF_DOMAIN_SERVICENAME_PORTNUMBER=unique-prefix-1.foo.cf-cd.com`.
+
+{{site.data.callout.callout_warning}}
+##### Note:
+
+The unique identifier is the service's name, not the name of the container that was produced for the service.
+{{site.data.callout.end}}
+
+## Example
+
+Consider the following composition:
+
+ `Composition.yml`
+{% highlight yaml %}
+version: '3'
+services:
+ db:
+ image: postgres
+ api:
+ image: myorg/api
+ ports:
+ - 9000
+ links
+ - db
+ web:
+ image: myorg/web
+ ports:
+ - 80
+ - 8080
+{% endhighlight %}
+
+The `db` service does not expose any ports. The `api` service exposes port `9000`, and the `web` service exposes port `80` and port `8080`.
+
+So, every container produced by this composition will be injected with:
+
+```
+CF_URL_API=http://foo.cf-cd.com/someurl
+CF_URL_WEB_80=http://foo.cf-cd.com/someurl2
+CF_URL_WEB_8080=http://foo.cf-cd.com/someurl3
+
+CF_DOMAIN_API=some-name.foo.cf-cd.com
+CF_DOMAIN_WEB_80=some-name2.foo.cf-cd.com
+CF_DOMAIN_WEB_8080=some-name3.foo.cf-cd.com
+```
+
+This means you can also discover these URLs within your application.
+For example, within a Node.js application you can run the following routine:
+
+ `Find all service URLs`
+{% highlight javascript %}
+const allVars = process.env;
+const allUrls = Object.keys(allVars)
+ .filter(envVarKey => envVarKey.startsWith('CF_URL_'))
+ .reduce((obj, envVarKey) => {
+ obj[envVarKey] = allVars[envVarKey];
+ return obj;
+ }, {});
+{% endhighlight %}
+
+The `allUrls` object will retain all the injected URL environment variables.
diff --git a/_docs/testing/create-composition.md b/_docs/testing/create-composition.md
new file mode 100644
index 000000000..cecbe179b
--- /dev/null
+++ b/_docs/testing/create-composition.md
@@ -0,0 +1,270 @@
+---
+title: "Creating compositions"
+description: "Create environment configurations in Codefresh"
+group: testing
+toc: true
+---
+
+Compositions can be launched as part of a unit test step, an integration test step, or to run an image for manual testing. You can create compositions from scratch or import an existing `docker-compose.yml` file.
+
+## Create composition
+1. In the Codefresh UI, from the Artifacts section in the sidebar, click [**Compositions**](https://g.codefresh.io/compositions){:target="\_blank"}.
+1. Click **Create Composition**.
+
+{% include
+image.html
+lightbox="true"
+file="/images/testing/compositions/add-composition-first.png"
+url="/images/testing/compositions/add-composition-first.png"
+alt="Add composition"
+captiom="Add composition"
+max-width="70%"
+%}
+
+{:start="3"}
+1.In the **Composition Name** text box, type a name for your composition, and click **Next**.
+
+{% include
+image.html
+lightbox="true"
+file="/images/testing/compositions/composition-name.png"
+url="/images/testing/compositions/composition-name.png"
+alt="Composition name"
+caption="Composition name"
+max-width="70%"
+%}
+
+{:start="4"}
+1. Select the type of composition to create:
+ * **From file in repo**: Start a new composition from a Docker Compose file in your repository.
+ * **From template**: Use a template as a starting point for the composition, if your repository doesn't include a `docker-compose.yml` file.
+ * **Empty composition**: Create a composition from scratch.
+
+{% include
+image.html
+lightbox="true"
+file="/images/testing/compositions/composition-method.png"
+url="/images/testing/compositions/composition-method.png"
+alt="Composition starting point"
+caption="Composition starting point"
+max-width="70%"
+%}
+
+{:start="5"}
+1. Click **Next**, and continue with one of the following:
+ * [From file in repo**](#from-file-in-repo)
+ * [From template](#from-template)
+ * [Empty composition](#empty-composition) (Advanced)
+
+
+### From file in repo
+Start a new composition from a Docker Compose file in your repository.
+
+{% include
+image.html
+lightbox="true"
+file="/images/testing/compositions/composition-file-in-repo.png"
+url="/images/testing/compositions/composition-file-in-repo.png"
+alt="Add composition from file in repo"
+caption="Add composition from file in repo"
+max-width="70%"
+%}
+
+1. To search for the repo, in the search box, type the name of the repository.
+ OR
+ Click **Add by URL**, and then enter the URL of the repo.
+1. From the **Branch** dropdown, select a branch for the first build.
+1. Click **Next**.
+1. Enter the path to `docker-compose.yml`.
+ By default, Codefresh searches for your `docker-compose.yml` at the root level of your repository, for the name _`docker-compose.yml`_. If your `docker-compose.yml` is in a subdirectory, provide the path as well, for example, `./foo/bar/docker-compose.yml`.
+
+{% include
+image.html
+lightbox="true"
+file="/images/testing/compositions/path-to-docker-compose.png"
+url="/images/testing/compositions/path-to-docker-compose.png"
+alt="Path to Compose file"
+caption="Path to Compose file"
+max-width="70%"
+%}
+
+{:start="5"}
+1. Click **Next**.
+ >We don’t support the `build` property of Docker Compose. We will replace it with images automatically using a pipeline that is automatically created.
+
+{% include
+image.html
+lightbox="true"
+file="/images/testing/compositions/replace-build.png"
+url="/images/testing/compositions/replace-build.png"
+alt="Replacing build with image"
+caption="Replacing build with image"
+max-width="70%"
+%}
+
+{:start="6"}
+1. Click **Create**.
+ Once ready, your composition is displayed in the Compositions list.
+
+{% include
+image.html
+lightbox="true"
+file="/images/testing/compositions/composition-list.png"
+url="/images/testing/compositions/composition-list.png"
+alt="Composition list"
+caption="Composition list"
+max-width="70%"
+%}
+
+
+
+### From template
+If your repository doesn't include a `docker-compose.yml` file, use one of our templates to see how it works.
+
+
+1. Choose the template.
+1. Click **Create**.
+
+{% include
+image.html
+lightbox="true"
+file="/images/testing/compositions/compose-from-template-select-template.png"
+url="/images/testing/compositions/compose-from-template-select-template.png"
+alt=" Select composition template"
+caption=" Select composition template"
+max-width="70%"
+%}
+
+ You will see the Composition editor, where you can tweak the template as needed, and then launch the composition to see results.
+
+{% include
+image.html
+lightbox="true"
+file="/images/testing/compositions/compose-from-template-edit.png"
+url="/images/testing/compositions/compose-from-template-edit.png"
+alt="Edit selected template"
+caption="Edit selected template"
+max-width="70%"
+%}
+
+> To launch this composition, click the rocket icon.
+
+
+### Empty composition
+Create a composition from scratch.
+
+
+1. To add a service, click the **Add Service** button.
+ You can add existing services, or provide the name for the Docker image to be pulled from the Docker registry.
+
+ {% include
+image.html
+lightbox="true"
+file="/images/testing/compositions/empty-composition.png"
+url="/images/testing/compositions/empty-composition.png"
+alt="Empty composition"
+caption="Empty composition"
+max-width="70%"
+%}
+
+{:start="2"}
+1. (Optional) Click **Edit**, and modify the content based on [Docker Compose YAML ](https://docs.docker.com/compose/compose-file/){:target="_blank"}.
+1. Click **Save** on the upper-right corner.
+
+
+
+## Working with existing compositions
+
+You can edit any composition you created, regardless of the method used to create it, by selecting it from the Compositions list.
+Edit as needed in the YAML editor.
+
+
+{% include
+image.html
+lightbox="true"
+file="/images/testing/compositions/existing-composition.png"
+url="/images/testing/compositions/existing-composition.png"
+alt="Edit existing composition"
+caption="Edit existing composition"
+max-width="70%"
+%}
+
+## Manually launching compositions
+
+When you are ready with the composition, launch it to inspect your application. Launching a composition creates a temporary
+test environment in your Codefresh account that you can use to inspect your application.
+
+1. In the Codefresh UI, from the Artifacts section in the sidebar, click [**Compositions**](https://g.codefresh.io/compositions){:target="\_blank"}.
+1. From the list, select the composition to launch.
+
+{% include
+image.html
+lightbox="true"
+file="/images/testing/compositions/composition-list.png"
+url="/images/testing/compositions/composition-list.png"
+alt="Composition list"
+caption="Composition list"
+max-width="70%"
+%}
+
+{:start="3"}
+1. Click the **Launch** icon.
+
+{% include
+image.html
+lightbox="true"
+file="/images/testing/compositions/composition-launch-button.png"
+url="/images/testing/compositions/composition-launch-button.png"
+alt="Launch composition"
+caption="Launch composition"
+max-width="70%"
+%}
+
+{:start="4"}
+1. To verify that the launch completed successfully, review the log.
+
+{% include
+image.html
+lightbox="true"
+file="/images/testing/compositions/composition-launch-log.png"
+url="/images/testing/compositions/composition-launch-log.png"
+alt="Composition log"
+caption="Composition log"
+max-width="70%"
+%}
+
+## Sharing the environment URL
+
+View a record for the running environment and all containers for the environment in the Running Compositions tab.
+
+1. In the Codefresh UI, from the Artifacts section in the sidebar, click [**Compositions**](https://g.codefresh.io/compositions){:target="\_blank"}.
+1. Click **Running Compositions**.
+
+{% include
+image.html
+lightbox="true"
+file="/images/testing/compositions/environment-running.png"
+url="/images/testing/compositions/environment-running.png"
+alt="environment-running.png"
+max-width="70%"
+caption="Active test environments"
+alt="Active test environments"
+%}
+
+{:start="3"}
+1. To share your environment with your team, click the **Hashtag** icon.
+
+{% include
+image.html
+lightbox="true"
+file="/images/testing/compositions/share-environment-link.png"
+url="/images/testing/compositions/share-environment-link.png"
+alt="Link for sharing environment"
+caption="Link for sharing environment"
+max-width="70%"
+%}
+
+## Related articles
+[Unit tests]({{site.baseurl}}/docs/testing/unit-tests/)
+[Integration tests]({{site.baseurl}}/docs/testing/integration-tests/)
+[Creating test reports]({{site.baseurl}}/docs/testing/test-reports/)
diff --git a/_docs/testing/integration-tests.md b/_docs/testing/integration-tests.md
new file mode 100644
index 000000000..c4a8bb0f7
--- /dev/null
+++ b/_docs/testing/integration-tests.md
@@ -0,0 +1,448 @@
+---
+title: "Integration tests"
+description: "Launch additional services in Codefresh pipelines"
+group: testing
+redirect_from:
+ - /docs/integration-tests/
+ - /docs/integration-test-script/
+ - /docs/testing/run-unit-test-with-db-composition/
+ - /docs/run-unit-test-with-db-composition/
+toc: true
+---
+
+Simple [unit tests]({{site.baseurl}}/docs/testing/unit-tests/) that rely only on the source code of the application, are very easy to execute in Codefresh, using only [freestyle steps]({{site.baseurl}}/docs/pipelines/steps/freestyle/). For integration tests however, you usually need to launch either the application itself, or one or more external services, such as a database.
+
+Codefresh offers two ways of launching sidecar containers (similar to `docker compose`) within the pipeline:
+
+1. [Compositions]({{site.baseurl}}/docs/pipelines/steps/composition/) is the old (but still supported) way
+1. [Service containers]({{site.baseurl}}/docs/pipelines/service-containers/) is the new and more flexible way
+
+For brand-new pipelines, we suggest you use service containers.
+They are much more flexible than compositions in these areas:
+1. Guaranteeing the order of service launch and their dependencies (a feature that is not even offered by vanilla `docker compose`).
+1. Using a special Docker image to preload data to a database, or otherwise initialize a service before running tests.
+1. Attaching service containers to the whole pipeline instead of individual steps .
+1. Auto-mounted Codefresh [shared volume]({{site.baseurl}}/docs/pipelines/introduction-to-codefresh-pipelines/#sharing-the-workspace-between-build-steps) for freestyle steps (unlike compositions), making file access very easy. You can execute your tests from the Git repository that was cloned.
+
+>This article explains how to run additional services that are automatically discarded once the pipeline has completed its run. If you are interested in temporary test environments, see the [preview environments]({{site.baseurl}}/docs/getting-started/on-demand-environments/).
+
+## How integration tests work in Codefresh
+
+* Service containers and `docker compose`
+ Service containers work similar to `docker compose`. A set of containers are launched on the same network with configurable hostnames and ports. Once they are up, you decide what to do, with a freestyle step that is part of the network as well. In the most typical pipeline, you can use your existing testing framework, regardless of the programming language, in the same manner as you would run your tests locally.
+
+* No hard-coded hostnames
+ A best practice is to make sure that the hostnames used by your integration tests to access external services are not hard-coded. Even though with Codefresh you can decide on the hostnames used in the pipeline, that is, the hostname of a MySQL or Redis instance, in the long run, it is better if you can choose that information freely without being limited to and by what is specified in the source code.
+
+* No `localhost` for an API endpoint
+ Make sure that your tests do **NOT** use `localhost` for an API endpoint. This technique does not work with `docker compose`, and will not work with Codefresh either.
+ Instead, use the hostname defined in the `docker-compose/codefresh.yml` file. For example, if you launch a MySQL service at hostname `my_db`, then your tests should use `my_db:3306` as a target.
+ Even better, make the hostname completely configurable with an environment variable so that you can change it within the Codefresh pipeline at will.
+ Basically, make sure that your integration tests work fine with `docker compose` locally on your workstation, before converting them to a Codefresh pipeline.
+
+>The services you launch in a Codefresh pipeline consume resources (memory/CPU) from the pipeline's runtime environment. The more services you launch, the less resources you have for the actual pipeline. We also suggest that you do **NOT** use service containers for load or performance testing.
+
+## Running integration tests directly from source code
+
+The simplest way to run integration tests is to check out the source code of your tests and launch the services that they need.
+
+{%
+ include image.html
+ lightbox="true"
+ file="/images/testing/integration-testing/from-source-code.png"
+ url="/images/testing/integration-testing/from-source-code.png"
+ alt="Testing directly from source code"
+ caption="Testing directly from source code"
+ max-width="70%"
+%}
+
+This is a very popular way of running integration tests but not the most flexible one. It works only when your tests have very simple requirements on their testing environment. It also doesn't make a clear distinction on source code that gets shipped to production with source code that is used only for testing. Make sure that you don't fall into the common trap of shipping testing tools with your Docker container in production.
+
+Here is the pipeline:
+
+`codefresh.yml`
+{% highlight yaml %}
+{% raw %}
+version: "1.0"
+steps:
+ main_clone:
+ type: "git-clone"
+ description: "Cloning main repository..."
+ repo: "kostis-codefresh/my-npm-example"
+ revision: "master"
+ git: github
+ my_deps:
+ image: 'node:11'
+ title: "Download Deps"
+ commands:
+ - 'npm install'
+ my_tests:
+ image: 'node:11'
+ title: "Integration tests"
+ commands:
+ - 'npm test'
+ services:
+ composition:
+ my_redis:
+ image: 'redis:latest'
+ ports:
+ - 6379
+{% endraw %}
+{% endhighlight %}
+
+We suggest using this technique only if your application is not Dockerized yet, that is, you don't deploy it with a Docker image to production.
+
+See more examples with [MySQL]({{site.baseurl}}/docs/example-catalog/ci-examples/integration-tests-with-mysql/), or [Postgres]({{site.baseurl}}/docs/example-catalog/ci-examples/integration-tests-with-postgres/).
+
+## Running tests after launching the application
+
+A better approach, that mimics what happens in reality, is to launch your application as a Docker image, and then run tests against it. This approach is only possible if you have adopted containers as deployment artifacts:
+
+{%
+ include image.html
+ lightbox="true"
+ file="/images/testing/integration-testing/to-app.png"
+ url="/images/testing/integration-testing/to-app.png"
+ alt="Launching the application to be tested"
+ caption="Launching the application to be tested"
+ max-width="70%"
+%}
+
+This technique is only limited by your pipeline resources.
+If you have not adopted microservices, it might be difficult to launch a huge monolith as part of a Codefresh pipeline (remember that service containers use the same resources as the pipeline).
+But for simple applications, this method ensures that your tests actually hit the running application.
+
+Here is the pipeline:
+
+`codefresh.yml`
+{% highlight yaml %}
+{% raw %}
+version: "1.0"
+steps:
+ main_clone:
+ type: "git-clone"
+ description: "Cloning main repository..."
+ repo: "kostis-codefresh/my-java-example"
+ revision: "master"
+ git: github
+ build_app_image:
+ title: "Building Docker Image"
+ type: "build"
+ image_name: "my-java-app"
+ tag: "master"
+ dockerfile: "Dockerfile"
+ my_tests:
+ image: 'maven:3.5.2-jdk-8-alpine'
+ title: "Integration tests"
+ commands:
+ - 'mvn -Dmaven.repo.local=/codefresh/volume/m2_repository integration-test'
+ services:
+ composition:
+ my_postgres:
+ image: 'postgres:11.5'
+ ports:
+ - 5432
+ app:
+ image: '${{build_app_image}}'
+ ports:
+ - 8080
+{% endraw %}
+{% endhighlight %}
+
+See more examples in [launching the application]({{site.baseurl}}/docs/example-catalog/ci-examples/run-integration-tests/), or [Postgres]({{site.baseurl}}/docs/example-catalog/ci-examples/integration-tests-with-postgres/).
+
+## Using a custom test image
+
+In all the previous examples, the integration tests ran in a public Dockerhub image that has the programming language/framework that your tests require. In more complex cases, you might need to create your own Docker image that contains exactly the tools that you wish.
+
+In this case, you can create a special Docker image that will be used just for testing and nothing else.
+
+{%
+ include image.html
+ lightbox="true"
+ file="/images/testing/integration-testing/special-image.png"
+ url="/images/testing/integration-testing/special-image.png"
+ alt="Using a dedicated testing image"
+ caption="Using a dedicated testing image"
+ max-width="70%"
+%}
+
+It is very easy to create a test image as part of a pipeline and then reference it for integration tests.
+Here is the pipeline:
+
+`codefresh.yml`
+{% highlight yaml %}
+{% raw %}
+version: "1.0"
+steps:
+ main_clone:
+ type: "git-clone"
+ description: "Cloning main repository..."
+ repo: "kostis-codefresh/my-app-example"
+ revision: "master"
+ git: github
+ build_app_image:
+ title: "Building App Docker Image"
+ type: "build"
+ image_name: "my-web-app"
+ tag: "master"
+ dockerfile: "Dockerfile"
+ build_test_image:
+ title: "Building Test Docker Image"
+ type: "build"
+ image_name: "my-test-image"
+ tag: "master"
+ dockerfile: "Dockerfile.testing"
+ my_tests:
+ image: '${{build_test_image}}'
+ title: "Integration tests"
+ commands:
+ - 'sh ./my-tests.sh'
+ services:
+ composition:
+ my_postgres:
+ image: 'postgres:11.5'
+ ports:
+ - 5432
+ app:
+ image: '${{build_app_image}}'
+ ports:
+ - 8080
+{% endraw %}
+{% endhighlight %}
+
+This is the recommended way to run integration tests in Codefresh. It creates a clear distinction between the source code that gets shipped to production and the source code that is needed only for tests. It also allows you to define what the test environment will look like (maybe you need multiple or exotic testing tools that are not available in Docker Hub).
+
+See more examples using a separate testing image [for the application]({{site.baseurl}}/docs/example-catalog/ci-examples/run-integration-tests/) or [a MySQL instance]({{site.baseurl}}/docs/example-catalog/ci-examples/integration-tests-with-mysql/).
+
+## Integration tests for microservices
+
+If you have enough pipeline resources, you can keep adding service containers that form a complex running environment. Service containers support [launch dependency order]({{site.baseurl}}/docs/pipelines/service-containers/#checking-readiness-of-a-service) as well as [post-launch phases]({{site.baseurl}}/docs/pipelines/service-containers/#preloading-data-to-databases), making them feasible for any complex infrastructure you have in mind.
+
+{%
+ include image.html
+ lightbox="true"
+ file="/images/testing/integration-testing/complex-tests.png"
+ url="/images/testing/integration-testing/complex-tests.png"
+ alt="Microservice testing"
+ caption="Microservice testing"
+ max-width="70%"
+%}
+
+Here is the pipeline:
+
+`codefresh.yml`
+{% highlight yaml %}
+{% raw %}
+version: "1.0"
+steps:
+ main_clone:
+ type: "git-clone"
+ description: "Cloning main repository..."
+ repo: "kostis-codefresh/my-app-example"
+ revision: "master"
+ git: github
+ build_frontend_image:
+ title: "Building Frontend Docker Image"
+ type: "build"
+ image_name: "my-web-app"
+ working_directory: './frontend'
+ tag: "master"
+ dockerfile: "Dockerfile"
+ build_backend_image:
+ title: "Building Backend Docker Image"
+ type: "build"
+ image_name: "my-backend-app"
+ working_directory: './backend'
+ tag: "master"
+ dockerfile: "Dockerfile"
+ build_test_image:
+ title: "Building Test Docker Image"
+ type: "build"
+ image_name: "my-test-image"
+ tag: "master"
+ dockerfile: "Dockerfile.testing"
+ my_tests:
+ image: '${{build_test_image}}'
+ title: "Integration tests"
+ commands:
+ - 'sh ./my-tests.sh'
+ services:
+ composition:
+ my_postgres:
+ image: 'postgres:11.5'
+ ports:
+ - 5432
+ redis_ds:
+ image: 'redis:latest'
+ ports:
+ - 6379
+ backend:
+ image: '${{build_backend_image}}'
+ ports:
+ - 9000
+ frontend:
+ image: '${{build_frontend_image}}'
+ ports:
+ - 8080
+{% endraw %}
+{% endhighlight %}
+
+Keep in mind that extra services use memory from the pipeline itself, so if you follow this route, make sure that the pipeline runs in the appropriate runtime environment.
+
+## Running service containers for the whole pipeline
+
+In most cases service containers should be only attached to the pipeline step that uses them.
+
+{%
+ include image.html
+ lightbox="true"
+ file="/images/testing/integration-testing/single-scope.png"
+ url="/images/testing/integration-testing/single-scope.png"
+ alt="Service containers for individual steps"
+ caption="Service containers for individual steps"
+ max-width="60%"
+%}
+
+Doing so not only helps with pipeline resources (as service containers are discarded when they are not needed), but also allows you to mix and match different service containers for different steps.
+
+Here is an example pipeline:
+
+`codefresh.yml`
+{% highlight yaml %}
+{% raw %}
+version: "1.0"
+steps:
+ main_clone:
+ type: "git-clone"
+ description: "Cloning main repository..."
+ repo: "kostis-codefresh/my-app-example"
+ revision: "master"
+ git: github
+ build_backend_image:
+ title: "Building Backend Docker Image"
+ type: "build"
+ image_name: "my-backend-app"
+ working_directory: './backend'
+ tag: "master"
+ dockerfile: "Dockerfile"
+ backend_tests:
+ image: 'maven:3.5.2-jdk-8-alpine'
+ title: "Running Backend tests"
+ commands:
+ - 'mvn -Dmaven.repo.local=/codefresh/volume/m2_repository integration-test'
+ services:
+ composition:
+ backend:
+ image: '${{build_backend_image}}'
+ ports:
+ - 9000
+ build_frontend_image:
+ title: "Building Frontend Docker Image"
+ type: "build"
+ image_name: "my-web-app"
+ working_directory: './frontend'
+ tag: "master"
+ dockerfile: "Dockerfile"
+ my_tests:
+ image: 'node:11'
+ title: "Running front-end tests"
+ commands:
+ - 'npm test'
+ services:
+ composition:
+ frontend:
+ image: '${{build_frontend_image}}'
+ ports:
+ - 8080
+{% endraw %}
+{% endhighlight %}
+
+In some cases however, you would like to execute multiple steps with integration tests that share the same environment. In this case
+you can also launch service containers at the beginning of the pipeline to make them available to all pipeline steps:
+
+{%
+ include image.html
+ lightbox="true"
+ file="/images/testing/integration-testing/multi-scope.png"
+ url="/images/testing/integration-testing/multi-scope.png"
+ alt="Service containers for the whole pipeline"
+ caption="Service containers for the whole pipeline"
+ max-width="60%"
+%}
+
+You can use this technique by putting the service container definition [at the root of the pipeline]({{site.baseurl}}/docs/pipelines/service-containers/#running-services-for-the-duration-of-the-pipeline) instead of within specific step.
+
+Here is an example that follows this technique:
+
+`codefresh.yml`
+{% highlight yaml %}
+{% raw %}
+version: "1.0"
+services:
+ name: my_database
+ composition:
+ my-redis-ds:
+ image: redis:latest
+ ports:
+ - 6379
+ my_postgres:
+ image: 'postgres:11.5'
+ ports:
+ - 5432
+steps:
+ main_clone:
+ type: "git-clone"
+ description: "Cloning main repository..."
+ repo: "kostis-codefresh/my-app-example"
+ revision: "master"
+ git: github
+ build_app_image:
+ title: "Building Docker Image"
+ type: "build"
+ image_name: "my-web-app"
+ tag: "master"
+ dockerfile: "Dockerfile"
+ my_api_tests:
+ image: '${{build_app_image}}'
+ title: "Running API tests"
+ commands:
+ - 'npm run test'
+ my_fuzzy_tests:
+ image: 'node:11'
+ title: "Fuzzy testing"
+ commands:
+ - 'npm run fuzzy-tests'
+ my_e2e_tests:
+ image: cypress/base
+ title: "Running E2E tests"
+ commands:
+ - 'cypress run'
+{% endraw %}
+{% endhighlight %}
+
+The Redis and PostgreSQL instances are now available to all pipeline steps. Read all about test results and graphs in [test reports]({{site.baseurl}}/docs/testing/test-reports/).
+
+
+## Creating test reports
+
+All the techniques shown above are also applicable to test reports.
+
+{% include
+image.html
+lightbox="true"
+file="/images/pipeline/test-reports/sample-test-report.png"
+url="/images/pipeline/test-reports/sample-test-report.png"
+alt="Sample Allure test report"
+caption="Sample Allure test report"
+max-width="70%"
+%}
+
+
+## Related articles
+[Service containers]({{site.baseurl}}/docs/testing/unit-tests/)
+[Run integration tests with MongoDB]({{site.baseurl}}/docs/example-catalog/ci-examples/integration-tests-with-mongo/)
+[Run integration tests with MySQL]({{site.baseurl}}/docs/example-catalog/ci-examples/integration-tests-with-mysql/)
+[Run integration tests with PostgreSQL]({{site.baseurl}}/docs/example-catalog/ci-examples/integration-tests-with-postgres/)
+[Run integration tests with Redis]({{site.baseurl}}/docs/example-catalog/ci-examples/integration-tests-with-redis/)
+[Run unit tests]({{site.baseurl}}/docs/example-catalog/ci-examples/run-unit-tests)
diff --git a/_docs/testing/security-scanning.md b/_docs/testing/security-scanning.md
new file mode 100644
index 000000000..469a44215
--- /dev/null
+++ b/_docs/testing/security-scanning.md
@@ -0,0 +1,199 @@
+---
+title: "Security scanning"
+description: "Scan for vulnerabilities with Codefresh pipelines"
+group: testing
+toc: true
+---
+
+Codefresh can integrate with any security scanning platform that scans source code or Docker images for vulnerabilities.
+
+The integration can happen via a [freestyle step]({{site.baseurl}}/docs/pipelines/steps/freestyle/) as long as the scanning solution offers any of the following:
+
+* A Docker image with the scanner
+* A CLI that can be packaged in a Docker image
+* An API
+
+Since all security solutions offer an API, Codefresh can essentially use any scanning solution via that interface.
+
+
+## Existing security integrations
+
+Codefresh already offers Docker images for the following security platforms:
+
+* [Anchore](https://codefresh.io/steps/step/anchore){:target="\_blank"}
+* [Aqua Security](https://codefresh.io/steps/step/aqua){:target="\_blank"}
+* [Clair](https://codefresh.io/steps/step/paclair){:target="\_blank"}
+* [Twistlock](https://codefresh.io/steps/step/twistlock){:target="\_blank"}
+* [WhiteSource](https://codefresh.io/steps/step/whitesource){:target="\_blank"}
+
+You can find more integrations as they are added in the [plugin directory](https://codefresh.io/steps/){:target="\_blank"}.
+
+
+## Security scanning strategies
+
+Because you can insert a scanning step anywhere in your pipeline, you have great flexibility on when to start a security scan.
+Common strategies are:
+1. Scanning the source code before being packaged in a container
+1. Scanning a container before being stored in a registry
+1. Scanning a container before being deployed to production
+1. A combination of the above
+
+
+Here is an example pipeline that scans a Docker image:
+* With [Aqua](https://www.aquasec.com/){:target="\_blank"} after being pushed to the [default Docker registry]({{site.baseurl}}/docs/docker-registries/external-docker-registries/#the-default-registry)
+* Before it is promoted to the [external Azure Registry]({{site.baseurl}}/docs/docker-registries/external-docker-registries/azure-docker-registry/){:target="\_blank"}.
+
+{% include image.html
+lightbox="true"
+file="/images/testing/security-scanning/aqua-scan.png"
+url="/images/testing/security-scanning/aqua-scan.png"
+alt="Scanning a Helm release with Aqua"
+caption="Scanning a Helm release with Aqua"
+max-width="100%"
+%}
+
+
+Here's the full pipeline definition:
+
+`codefresh.yml`
+{% highlight yaml %}
+{% raw %}
+
+version: '1.0'
+stages:
+ - prepare
+ - build
+ - test
+ - push
+ - deploy
+steps:
+ main_clone:
+ title: Cloning main repository...
+ type: git-clone
+ repo: '${{CF_REPO_OWNER}}/${{CF_REPO_NAME}}'
+ revision: '${{CF_REVISION}}'
+ stage: prepare
+ build:
+ title: "Building Docker Image"
+ type: "build"
+ image_name: "${{CF_ACCOUNT}}/${{CF_REPO_NAME}}"
+ tag: ${{CF_REVISION}}
+ dockerfile: "Dockerfile"
+ stage: "build"
+ AquaSecurityScan:
+ title: 'Aqua Private scan'
+ image: codefresh/cfstep-aqua
+ stage: test
+ environment:
+ - 'AQUA_HOST=${{AQUA_HOST}}'
+ - 'AQUA_PASSWORD=${{AQUA_PASSWORD}}'
+ - 'AQUA_USERNAME=${{AQUA_USERNAME}}'
+ - IMAGE=${{CF_ACCOUNT}}/${{CF_REPO_NAME}}
+ - TAG=${{CF_REVISION}}
+ - REGISTRY=codefresh
+ push:
+ title: "Pushing image to Azure registry"
+ type: "push"
+ stage: push
+ image_name: "${{CF_ACCOUNT}}/${{CF_REPO_NAME}}"
+ registry: "myazureregistry"
+ candidate: "${{build}}"
+ tags:
+ - "${{CF_BRANCH_TAG_NORMALIZED}}"
+ - "${{CF_REVISION}}"
+ - "${{CF_BRANCH_TAG_NORMALIZED}}-${{CF_SHORT_REVISION}}"
+
+ createpullsecret:
+ title: "Allowing cluster to pull Docker images"
+ image: codefresh/cli
+ stage: "deploy"
+ commands:
+ - codefresh generate image-pull-secret --cluster 'mydemok8scluster' --registry myazureregistry
+ deploy:
+ image: codefresh/cfstep-helm:2.12.0
+ stage: deploy
+ environment:
+ - CHART_REF=deploy/helm/colors
+ - RELEASE_NAME=color-coded
+ - KUBE_CONTEXT=mydemok8scluster
+ - CUSTOM_service.type=LoadBalancer
+ - CUSTOM_deployment[0].track=release
+ - CUSTOM_deployment[0].image.repository=registry3435454.azurecr.io/${{CF_REPO_OWNER}}/${{CF_REPO_NAME}}
+ - CUSTOM_deployment[0].image.tag="${{CF_BRANCH_TAG_NORMALIZED}}-${{CF_SHORT_REVISION}}"
+ - CUSTOM_deployment[0].image.version="${{CF_BRANCH_TAG_NORMALIZED}}-${{CF_SHORT_REVISION}}"
+ - CUSTOM_deployment[0].image.pullSecret=codefresh-generated-registry3435454.azurecr.io-myazureregistry-default
+{% endraw %}
+{% endhighlight %}
+
+The security scanning step is inserted after building the Docker image, but before promoting the image to the Azure Docker registry.
+
+
+## Viewing security reports
+
+The easiest way to view security reports is to visit the portal/dashboard of the security platform that you are using.
+
+{% include image.html
+lightbox="true"
+file="/images/testing/security-scanning/snyk-test-report.png"
+url="/images/testing/security-scanning/snyk-test-report.png"
+alt="Snyk Security Analysis"
+caption="Snyk Security Analysis"
+max-width="60%"
+%}
+
+You can also attach Analysis Reports to Codefresh builds using the [test reporting feature]({{site.baseurl}}/docs/testing/test-reports/).
+
+{% highlight yaml %}
+{% raw %}
+ ArchiveClairReport:
+ title: Upload Clair Report
+ image: codefresh/cf-docker-test-reporting
+ environment:
+ - REPORT_DIR=reports
+ - REPORT_INDEX_FILE=clair-scan.html
+{% endraw %}
+{% endhighlight %}
+
+In this example, we attach the Clair Scan report to the build created:
+
+{% include image.html
+lightbox="true"
+file="/images/testing/security-scanning/security-test-results.png"
+url="/images/testing/security-scanning/security-test-results.png"
+alt="Attaching scanning results to a build"
+caption="Attaching scanning results to a build"
+max-width="100%"
+%}
+
+To view the full report, click **Test Report**:
+
+{% include image.html
+lightbox="true"
+file="/images/testing/security-scanning/clair-scan.png"
+url="/images/testing/security-scanning/clair-scan.png"
+alt="Clair security scan"
+caption="Clair security scan"
+max-width="60%"
+%}
+
+
+## Security annotations
+
+Security scan results are also a perfect candidate for [extra metadata]({{site.baseurl}}/docs/docker-registries/metadata-annotations/) to add to your Docker images.
+
+{% include image.html
+lightbox="true"
+file="/images/testing/security-scanning/security-annotations.png"
+url="/images/testing/security-scanning/security-annotations.png"
+alt="Security annotations"
+caption="Security annotations"
+max-width="80%"
+%}
+
+You can add any metadata such as the number of issues for each category or even the URL the full report. This allows you to easily correlate docker images in Codefresh and security results of your scanning platform.
+
+## Related articles
+[Codefresh YAML]({{site.baseurl}}/docs/pipelines/what-is-the-codefresh-yaml/)
+[Pipeline steps]({{site.baseurl}}/docs/pipelines/steps/)
+[Creating test reports]({{site.baseurl}}/docs/testing/test-reports/)
+[Advanced workflows in pipelines]({{site.baseurl}}/docs/pipelines/advanced-workflows/)
diff --git a/_docs/testing/sonarqube-integration.md b/_docs/testing/sonarqube-integration.md
new file mode 100644
index 000000000..e33472042
--- /dev/null
+++ b/_docs/testing/sonarqube-integration.md
@@ -0,0 +1,140 @@
+---
+title: "SonarQube scanning"
+description: "Trigger a SonarQube Analysis from Codefresh"
+group: testing
+toc: true
+---
+
+[SonarQube](https://www.sonarqube.org/){:target="\_blank"} is a popular platform for Code Quality. It can be used for static and dynamic analysis of a codebase to detect common code issues such as bugs and vulnerabilities.
+
+
+{% include image.html
+lightbox="true"
+file="/images/testing/sonarqube/sonarqube-logo.png"
+url="/images/testing/sonarqube/sonarqube-logo.png"
+alt="SonarQube logo"
+max-width="40%"
+%}
+
+There are many ways to perform an [analysis with SonarQube](https://docs.sonarqube.org/latest/setup/overview/){:target="\_blank"}, but the easiest way is to use one that matches the build system of your application.
+
+This article shows how to use the [SonarQube plugin](https://codefresh.io/steps/step/sonar-scanner-cli){:target="\_blank"} on Codefresh from the plugin directory. Once it is set up, your code is automatically analysed everytime your pipeline runs.
+
+## Prerequisites for SonarQube scanning
+
+Before starting an analysis, you need to have a:
+
+ * Simple [Codefresh pipeline up and running]({{site.baseurl}}//docs/getting-started/create-a-basic-pipeline/)
+ * SonarQube account (Developer, Enterprise, or on the [SonarCloud](https://sonarcloud.io/){:target="\_blank"})
+
+## Get a security token from SonarQube
+
+To use the SonarQube plugin, you need to provide your login credentials in your Codefresh pipeline or generate a security token. We recommend the latter. You can either create a new token or reuse an existing one. Security-wise, it is best if each project has its own token.
+
+1. Log in into your account in SonarQube.
+1. Navigate to **USER > MY ACCOUNT**, which is on the top-right corner of your profile.
+1. Select the **Security** tab, and generate the security token.
+1. Save the token where you can access it again easily.
+
+{% include image.html
+lightbox="true"
+file="/images/testing/sonarqube/generate-token.png"
+url="/images/testing/sonarqube/generate-token.png"
+alt="SonarQube generate token"
+max-width="50%"
+%}
+
+## Set up sonar-project.properties file
+
+Set up a `sonar-project.properties` file in our root directry. This is needed as not all environment variables are currently [automatically defined](https://github.com/SonarSource/sonar-scanner-cli-docker/pull/50){:target="\_blank"} in the SonarScanner.
+
+1. Create a `sonar-project.properties` file.
+1. Add the following values:
+
+{% highlight yaml %}
+# must be unique in a given SonarQube instance
+sonar.projectKey=a unique project key
+
+# project name
+sonar.projectName=your project's name
+{% endhighlight %}
+
+The file is needed to run the SonarQube plugin.
+
+**Language-specific settings**
+Note that projects using specific languages may require additional configuration. For more information, see the appropriate language page in the [Sonarqube documentation](https://docs.sonarqube.org/latest/analysis/languages/overview/){:target="\_blank"}.
+
+
+## Run an analysis from the Codefresh Plugin
+
+If you are using the predefined Codefresh pipeline, you just need to look-up SonarQube under `STEPS` and you will find the custom plugin.
+
+{% include image.html
+lightbox="true"
+file="/images/testing/sonarqube/simplified-codefresh-pipeline.png"
+url="/images/testing/sonarqube/simplified-codefresh-pipeline.png"
+alt="SonarQube analysis for predefined Codefresh steps"
+max-width="80%"
+%}
+
+* Select the `sonar-scanner-cli`
+* Copy and past the step to your pipeline
+* Customise the values within the step as follows:
+ * `SONAR_HOST_URL: 'https://sonarcloud.io/'` # this is the URL to SonarCloud, if applicable, please replace it with the Server URL
+ * `SONAR_LOGIN: username` or access token (generated above)
+ * `SONAR_PASSWORD: password` if username is used
+ * `SONAR_PROJECT_BASE_DIR: set working directory for analysis`
+ * `SONAR_SCANNER_CLI_VERSION: latest`
+* Save and run your pipeline.
+
+
+Here is our example step:
+
+{% highlight yaml %}
+ sonarqube:
+ type: "sonar-scanner-cli"
+ stage: "push"
+ arguments:
+ SONAR_HOST_URL: 'https://sonarcloud.io/' # replace with your host url
+ SONAR_LOGIN: "insert access token" # replace with your access token
+ SONAR_PROJECT_BASE_DIR: "/codefresh/volume/sonarqube-example" #r eplace with your working directory
+ SONAR_SCANNER_CLI_VERSION: "latest"
+{% endhighlight %}
+
+## View the SonarQube analysis
+
+Once the Codefresh build starts, check the logs and monitor the progress of the analysis.
+
+{% include image.html
+lightbox="true"
+file="/images/testing/sonarqube/analysis-log.png"
+url="/images/testing/sonarqube/analysis-log.png"
+alt="SonarQube analysis"
+max-width="80%"
+%}
+
+Once the analysis is complete, go to the SonarQube dashboard and see the recent analysis of the project.
+
+{% include image.html
+lightbox="true"
+file="/images/testing/sonarqube/sonar-project.png"
+url="/images/testing/sonarqube/sonar-project.png"
+alt="SonarQube project"
+max-width="80%"
+%}
+
+Then you can drill down and view the various statistics.
+
+{% include image.html
+lightbox="true"
+file="/images/testing/sonarqube/sonar-analysis-details.png"
+url="/images/testing/sonarqube/sonar-analysis-details.png"
+alt="SonarQube Analysis details"
+max-width="80%"
+%}
+
+## Related articles
+[Codefresh YAML]({{site.baseurl}}/docs/pipelines/what-is-the-codefresh-yaml/)
+[Pipeline steps]({{site.baseurl}}/docs/pipelines/steps/)
+[Unit tests]({{site.baseurl}}/docs/testing/unit-tests/)
+[Integration tests]({{site.baseurl}}/docs/testing/integration-tests/)
diff --git a/_docs/testing/test-reports.md b/_docs/testing/test-reports.md
new file mode 100644
index 000000000..7511ae9ee
--- /dev/null
+++ b/_docs/testing/test-reports.md
@@ -0,0 +1,556 @@
+---
+title: "Creating test reports"
+description: "Create and view test reports in Codefresh"
+group: testing
+redirect_from:
+ - /docs/configure-ci-cd-pipeline/test-reports/
+toc: true
+---
+
+Codefresh offers the capability to store test results for every build and view them at any point in time.
+
+Currently, Codefresh supports storing test reports in:
+* [Google buckets](https://cloud.google.com/storage/docs/key-terms#buckets){:target="\_blank"}
+* [S3 buckets](https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingBucket.html){:target="\_blank"}
+* [Azure Storage](https://docs.microsoft.com/en-us/azure/storage/){:target="\_blank"}
+* [MinIO objects](https://min.io/){:target="\_blank"}
+
+## Test report modes
+
+There are two modes for processing test reports in Codefresh, built-in and custom test reporting
+
+1. Built-in test reporting based on the [Allure framework](http://allure.qatools.ru/){:target="\_blank"}
+ Allure is an open-source test framework that can produce HTML reports like the following:
+
+ {% include
+image.html
+lightbox="true"
+file="/images/pipeline/test-reports/sample-test-report.png"
+url="/images/pipeline/test-reports/sample-test-report.png"
+alt="Sample Allure test report"
+caption="Sample Allure test report"
+max-width="70%"
+%}
+
+ For more details, see the [official Allure documentation](https://docs.qameta.io/allure/){:target="\_blank"}.
+ Allure supports popular testing frameworks such as:
+ * Java/JUnit/TestNG/Cucumber
+ * Python/pytest
+ * JavaScript/Jasmine/Mocha
+ * Ruby/Rspec
+ * Groovy/Spock
+ * .NET/Nunit/mstest
+ * Scala/Scalatest
+ * PHP/PhpUnit
+
+{:start="2"}
+1. Custom reporting for any static website (HTML) content
+ If you use the custom reporting mode, you can select any kind of tool that you want, as long as it produces a static website in the end. You can also use the custom reporting mode for reports that are not test reports, such as security reports or quality reports.
+
+## Connecting your storage account
+
+As a first step, you need a cloud bucket to store your test results. You can use Google, AWS, Azure or MinIO for this purpose.
+Codefresh creates subfolders in the bucket with the names from every build ID. It will then upload the reports for that build to the respective folder. Multiple pipelines can use the same bucket.
+
+1. In the Codefresh UI, on the toolbar, click the Settings icon, and then from the sidebar select **Pipeline Integrations**.
+1. Scroll down to **Cloud Storage**, and click **Configure**.
+
+
+{% include
+image.html
+lightbox="true"
+file="/images/pipeline/test-reports/cloud-storage-integrations.png"
+url="/images/pipeline/test-reports/cloud-storage-integrations.png"
+alt="Cloud storage Integrations"
+caption="Cloud storage Integrations"
+max-width="80%"
+%}
+
+1. Click **Add Cloud Storage**, and select your cloud provider.
+1. Continue to define cloud settings according to your cloud provider, as described in the sections that follow.
+
+### Connecting a Google bucket
+
+1. Create a bucket either from the Google cloud console or the `gsutil` command line tool.
+ See the [official documentation](https://cloud.google.com/storage/docs/creating-buckets#storage-create-bucket-console){:target="\_blank"} for the exact details.
+1. [Connect your storage account](#connecting-your-storage-account) for **Google Cloud Storage**.
+
+{% include
+image.html
+lightbox="true"
+file="/images/pipeline/test-reports/cloud-storage-google.png"
+url="/images/pipeline/test-reports/cloud-storage-google.png"
+alt="Google cloud storage"
+caption="Google cloud storage"
+max-width="80%"
+%}
+
+{:start="3"}
+1. Define the settings:
+ * Select **OAuth2** as the connection method, which is the easiest way.
+ * Enter an arbitrary name for your integration.
+ * Select **Allow access to read and write into storage** as Codefresh needs to both write to and read from the bucket.
+1. Click **Save**.
+1. When Codefresh asks for extra permissions from your Google account, accept the permissions.
+
+The integration is ready. You will use the name of the integration as an environment variable in your Codefresh pipeline.
+
+> An alternative authentication method is to use **JSON Config** with a [Google service account key](https://console.cloud.google.com/apis/credentials/serviceaccountkey){:target="\_blank"}.
+ In that case, download the JSON file locally and paste its contents in the **JSON config** field.
+ For more information, see the [official documentation](https://cloud.google.com/iam/docs/creating-managing-service-account-keys){:target="\_blank"}.
+
+### Connecting an S3 bucket
+
+1. For AWS (Amazon Web Services), create an S3 bucket.
+ See the [official documentation](https://docs.aws.amazon.com/quickstarts/latest/s3backup/step-1-create-bucket.html){:target="\_blank"} or the [CLI](https://docs.aws.amazon.com/cli/latest/reference/s3api/create-bucket.html){:target="\_blank"}.
+
+1. Note down the **Access** and **Secret** keys.
+1. [Connect your storage account](#connecting-your-storage-account) for **Amazon Cloud Storage**.
+1. Define the settings:
+ * Enter an arbitrary name for your integration.
+ * Paste the **AWS Access Key ID** and **AWS Secret Access Key**.
+1. Click **Save**.
+
+{% include
+image.html
+lightbox="true"
+file="/images/pipeline/test-reports/cloud-storage-s3.png"
+url="/images/pipeline/test-reports/cloud-storage-s3.png"
+alt="S3 cloud storage"
+caption="S3 cloud storage"
+max-width="80%"
+%}
+
+You will use the name of the integration as an environment variable in your Codefresh pipeline.
+
+You can also use any [external secrets that you have defined]({{site.baseurl}}/docs/integrations/pipeline-integrations/secret-storage/) (such as Kubernetes secrets), as values, by clicking on the lock icon that appears next to field:
+* If you have already specified the resource field during secret definition, just enter the name of the secret directly in the text field, for example, `my-secret-key`.
+* If you didn't include a resource name during secret creation, enter the full name in the field, for example, `my-secret-resource@my-secret-key`.
+
+### Connecting Azure storage
+
+1. For Azure, create a storage account.
+ See the [official documentation](https://docs.microsoft.com/en-us/azure/storage/common/storage-account-create){:target="\_blank"}.
+1. Find one of the [two access keys](https://docs.microsoft.com/en-us/azure/storage/common/storage-account-keys-manage){:target="\_blank"} already created.
+1. Note down the **Account Name** and **Access key for the account**.
+1. [Connect your storage account](#connecting-your-storage-account) for **Azure File/Blob Storage**.
+1. Define the settings:
+ * Enter an arbitrary name for your integration.
+ * Paste the **Azure Account Name** and **Azure Account Key**.
+1. Click **Save**.
+
+
+{% include
+image.html
+lightbox="true"
+file="/images/pipeline/test-reports/cloud-storage-azure.png"
+url="/images/pipeline/test-reports/cloud-storage-azure.png"
+alt="Azure cloud storage"
+caption="Azure cloud storage"
+max-width="60%"
+%}
+
+You will use the name of the integration as an environment variable in your Codefresh pipeline.
+
+You can also use any [external secrets that you have defined]({{site.baseurl}}/docs/integrations/pipeline-integrations/secret-storage/) (such as Kubernetes secrets), as values, by clicking on the lock icon that appears next to field:
+* If you have already specified the resource field during secret definition, just enter the name of the secret directly in the text field, for example, `my-secret-key`.
+* If you didn't include a resource name during secret creation, enter the full name in the field, for example, `my-secret-resource@my-secret-key`.
+
+### Connecting MinIO storage
+
+1. Configure the MinIO server.
+ See the [official documentation](https://docs.min.io/docs/minio-quickstart-guide.html){:target="\_blank"}.
+1. Copy the Access and Secret keys. define the settings for MinIO cloud storage in your Codefresh account.
+1. [Connect your storage account](#connecting-your-storage-account) for ****MinIO Cloud Storage**.
+1. Define the settings:
+ * **NAME**: The name of the MinIO storage. Any name that is meaningful to you.
+ * **ENDPOINT**: The URL to the storage service object.
+ * **PORT**: Optional. The TCP/IP port number. If not defined, defaults to port `80` for HTTP, and `443` for HTTPS.
+ * **Minio Access Key**: The ID that uniquely identifies your account, similar to a user ID.
+ * **Secret Minio Key**: The password of your account.
+ * **Use SSL**: Select to enable secure HTTPS access. Not selected by default.
+1. Click **Save**.
+
+ {% include
+image.html
+lightbox="true"
+file="/images/pipeline/test-reports/cloud-storage-minio.png"
+url="/images/pipeline/test-reports/cloud-storage-minio.png"
+alt="MinIO cloud storage"
+caption="MinIO cloud storage"
+max-width="60%"
+%}
+
+### Integration example in Codefresh pipeline
+See an example of the integration in a pipeline:
+```yaml
+version: "1.0"
+stages:
+ - "clone"
+ - "test"
+
+steps:
+ clone:
+ title: "Cloning repository"
+ type: "git-clone"
+ repo: "https://github.com/vadim-kharin-codefresh/test/"
+ revision: "master"
+ stage: "clone"
+ unit_test_reporting_step:
+ title: Upload Mocha test reports
+ image: codefresh/cf-docker-test-reporting
+ working_directory: "${{clone}}"
+ stage: "test"
+ environment:
+ - REPORT_DIR=mochawesome-report
+ - REPORT_INDEX_FILE=mochawesome.html
+ - BUCKET_NAME=codefresh-test-reporting
+ - CF_STORAGE_INTEGRATION=minio
+ - CF_BRANCH_TAG_NORMALIZED=test
+```
+
+
+## Producing Allure test reports from Codefresh pipelines
+
+In order to obtain test reports with Allure, the general process of a pipeline is the following:
+
+1. Generate test results using Allure and store them in a folder named `allure-results` (which is the default name).
+1. Copy this folder to the [Codefresh volume]({{site.baseurl}}/docs/pipelines/introduction-to-codefresh-pipelines/#sharing-the-workspace-between-build-steps) to make it available to the next pipeline step.
+1. Use the special `cf-docker-test-reporting` pipeline step with a working directory for the folder that contains the `allure-results` subfolder.
+
+Let's see these requirements in order:
+
+### Collecting the Allure test results
+
+The first step is to run your unit/integration tests using Allure to gather the results.
+The process is different for every programming language. Follow the [official Allure documentation](https://docs.qameta.io/allure/){:target="\_blank"}. You can also take a look at any of the [examples](https://github.com/allure-examples){:target="\_blank"}.
+
+By default, Allure creates a folder named `allure-results` containing all the tests. The Codefresh reporting step looks for that folder to upload it to the cloud storage. If you change the default name, then you also need to add an extra parameter in the Codefresh reporting step.
+
+To pass the reports to the next step, you need to place them anywhere in the [Codefresh volume]({{site.baseurl}}/docs/pipelines/introduction-to-codefresh-pipelines/#sharing-the-workspace-between-build-steps) that is automatically shared between all Codefresh steps.
+
+>You can also leave the test results in the project folder that was checked out from Git, as this folder is already inside the shared Codefresh volume.
+
+Therefore, once you create the reports with your testing framework, make sure to copy them to `{% raw %}${{CF_VOLUME_PATH}}{% endraw %}` which is the [Codefresh variable]({{site.baseurl}}/docs/pipelines/variables/) that points to the shared volume.
+
+An example for Node tests would be the following:
+
+{% highlight yaml %}
+{% raw %}
+running_tests:
+ image: node
+ title: Running Unit tests
+ commands:
+ - npm test
+ - cp -r -f ./allure-results $CF_VOLUME_PATH/allure-results
+{% endraw %}
+{% endhighlight %}
+
+Here the tests are executed and then they are copied to `/codefresh/volume/allure-results`
+
+
+### Creating the Allure test reports
+
+
+Once the test results are collected, the next step is the same, regardless of your programming language and test framework.
+
+{% highlight yaml %}
+{% raw %}
+ unit_test_reporting_step:
+ title: Generate test reporting
+ image: codefresh/cf-docker-test-reporting
+ working_directory: '${{CF_VOLUME_PATH}}/'
+ environment:
+ - BUCKET_NAME=my-bucket-name
+ - CF_STORAGE_INTEGRATION=google
+{% endraw %}
+{% endhighlight %}
+
+Here, we execute the special `cf-docker-test-reporting` image as a [freestyle step]({{site.baseurl}}/docs/pipelines/steps/freestyle/). The important point is that this image searches for `allure-results` in its working directory. This is why we pass `/codefresh/volume/` as the working directory as this is the parent folder of the test results.
+
+The required environment variables are:
+ * `BUCKET_NAME`, the name of the bucket that you created in your cloud provider. Multiple pipelines can use the same bucket.
+ * `CF_STORAGE_INTEGRATION`, the name of the cloud integration you created in the Codefresh UI.
+
+If you used another directory name, you can configure the test reporting step to include `ALLURE_DIR`, as in the example below:
+
+{% highlight yaml %}
+{% raw %}
+ unit_test_reporting_step:
+ title: Generate test reporting
+ image: codefresh/cf-docker-test-reporting
+ working_directory: '${{CF_VOLUME_PATH}}/'
+ environment:
+ - ALLURE_DIR=my-own-allure-results-folder
+ - BUCKET_NAME=my-bucket-name
+ - CF_STORAGE_INTEGRATION=google
+{% endraw %}
+{% endhighlight %}
+
+
+Once that is done, the results are uploaded to Codefresh infrastructure. You can access
+the report for any build by clicking **Test Report** to the right of each build.
+
+
+{% include
+image.html
+lightbox="true"
+file="/images/pipeline/test-reports/test-report-button.png"
+url="/images/pipeline/test-reports/test-report-button.png"
+alt="Test report button"
+caption="Test report button"
+max-width="80%"
+%}
+
+Note that behind the scenes, Codefresh automatically handles Allure history for you. For each test run, Codefresh finds the historical results from previous runs, and recreates them. Codefresh handles all folders inside the storage bucket, so make sure not to tamper with them. Make sure also that the account/role you are using for the bucket has delete privileges.
+
+## Using the custom mode for generic reporting
+
+If you don't want to use Allure or wish to create some other kind of report, you can use the alternative mode for the Codefresh reporting step.
+
+Here is an example for a custom reporting step via [Mocha](https://mochajs.org/){:target="\_blank"}. The reports are placed in the folder `/codefresh/volume/demochat/mochawesome-report`.
+
+
+{% highlight yaml %}
+{% raw %}
+ unit_test_reporting_step:
+ title: Upload Mocha test reports
+ image: codefresh/cf-docker-test-reporting
+ working_directory: /codefresh/volume/demochat/
+ environment:
+ - REPORT_DIR=mochawesome-report
+ - REPORT_INDEX_FILE=mochawesome.html
+ - BUCKET_NAME=my-bucket-name
+ - CF_STORAGE_INTEGRATION=google
+{% endraw %}
+{% endhighlight %}
+
+The environment variables are:
+ * `BUCKET_NAME`, the name of the bucket that you created in your cloud provider.
+ * `CF_STORAGE_INTEGRATION`, the name of the cloud integration you created in the Codefresh UI.
+ * `REPORT_PATH`, the subfolder name in the bucket for each test report.
+ * Data is saved to the bucket in following path: `{bucketName}/{pipelineId}/{REPORT_PATH}/{branchName}/{buildId}/`
+ * `REPORT_DIR`, the name of the folder to be uploaded.
+ * `REPORT_INDEX_FILE`, the name of file to serve as the index file.
+
+In the above example, we define a non-Allure report directory and the file that serves as the index file.
+Here is the result:
+
+{% include
+image.html
+lightbox="true"
+file="/images/pipeline/test-reports/mocha-tests.png"
+url="/images/pipeline/test-reports/mocha-tests.png"
+alt="Custom reporting"
+caption="Custom reporting"
+max-width="70%"
+%}
+
+In a similar manner, you can upload reports from any other custom tool you have in your pipeline.
+
+If your report is only one file, simply use the `REPORT_INDEX_FILE` environment variable on its own, as below:
+
+{% highlight yaml %}
+{% raw %}
+ unit_test_reporting_step:
+ title: Upload single html file report
+ image: codefresh/cf-docker-test-reporting
+ working_directory: /codefresh/volume/my-app/
+ environment:
+ - REPORT_INDEX_FILE=my-test-report/my-result.html
+ - BUCKET_NAME=my-bucket-name
+ - CF_STORAGE_INTEGRATION=google
+{% endraw %}
+{% endhighlight %}
+
+
+
+### Cleaning the reports from the previous run
+
+In the typical scenario, the tests are run, the results are collected and saved in a folder, and then Codefresh creates the report.
+
+If something goes wrong with the actual tests, once the Codefresh reporting step runs, it actually picks the old reports from the previous build. Remember that everything that is placed in the Codefresh volume is not only shared between build steps, but also persists between different builds of the same pipeline for caching purposes.
+
+If that is a problem for you, pass the extra `CLEAR_TEST_REPORT` environment variable to the reporting step. This deletes the previous test results once uploaded, so are not available to the subsequent build.
+
+Here is an example:
+
+{% highlight yaml %}
+{% raw %}
+ unit_test_reporting_step:
+ title: Upload Mocha test reports
+ image: codefresh/cf-docker-test-reporting
+ working_directory: /codefresh/volume/demochat/
+ environment:
+ - REPORT_DIR=mochawesome-report
+ - REPORT_INDEX_FILE=mochawesome.html
+ - CLEAR_TEST_REPORT=true
+ - BUCKET_NAME=my-bucket-name
+ - CF_STORAGE_INTEGRATION=google
+{% endraw %}
+{% endhighlight %}
+
+>In the Allure reporting mode, the test results are automatically cleared by Codefresh. There is no need to manually define the `CLEAR_TEST_REPORT` variable.
+
+## Creating multiple reports
+
+You can create multiple reports from a single pipeline. As an example, you can create
+a single pipeline that creates two reports, one for code coverage, and the other one for security vulnerabilities.
+
+To achieve this, you only need to repeat the variables mentioned in this article with an index number that matches them to the report, `REPORT_DIR.0`, `REPORT_DIR.1`, `REPORT_DIR.2` and so on.
+
+The following variables can be indexed:
+ * `REPORT_DIR`
+ * `REPORT_INDEX_FILE`
+ * `ALLURE_DIR`
+ * `CLEAR_TEST_REPORT`
+ * `REPORT_TYPE` (explained later on)
+
+Here is an example of a pipeline that creates two reports, one for code coverage, and one for unit tests. Notice the index number (`.0` and `.1`) used in the variables.
+
+
+{% highlight yaml %}
+{% raw %}
+ unit_test_reporting_step:
+ title: Upload Mocha test reports
+ image: codefresh/cf-docker-test-reporting
+ working_directory: /codefresh/volume/demochat/
+ environment:
+ - BUCKET_NAME=codefresh-test-report
+ - CF_STORAGE_INTEGRATION=testReporting
+ - REPORT_DIR.0=coverage
+ - REPORT_INDEX_FILE.0=lcov-report/index.html
+ - REPORT_TYPE.0=coverage
+ - ALLURE_DIR.1=allure-results
+ - REPORT_TYPE.1=allure
+{% endraw %}
+{% endhighlight %}
+
+This is the top-level HTML file created by the reporting step:
+
+{% include
+image.html
+lightbox="true"
+file="/images/pipeline/test-reports/multiple-test-reports.png"
+url="/images/pipeline/test-reports/multiple-test-reports.png"
+alt="Multiple test reports"
+caption="Multiple test reports"
+max-width="60%"
+%}
+
+The icons shown are specified by the `REPORT_TYPE` variable. The following options are possible: `allure, mocha, spock, coverage, junit, testng, cucumber, pytest, rspec, phpunit, nunit, spectest`.
+
+If you don't provide a `REPORT_TYPE`, Codefresh uses a default icon.
+
+
+## Getting results from tests that fail
+
+By default, if unit tests fail, the pipeline stops execution. If you want the pipeline to keep running even if the tests fail,
+add the [fail_fast property]({{site.baseurl}}/docs/pipelines/what-is-the-codefresh-yaml/#execution-flow) to the pipeline and set it to `false`.
+
+Here is an example:
+
+{% highlight yaml %}
+{% raw %}
+ RunMyUnitTests:
+ image: node:latest
+ title: Running my UnitTests
+ fail_fast: false
+ commands:
+ - npm run test
+{% endraw %}
+{% endhighlight %}
+
+The pipeline continue its run, and any steps later in the pipeline that collect reports, also run as usual, with access to test results.
+
+## Marking the whole pipeline as failed if tests failed
+
+When you use the `fail_fast:false` property in your pipeline, the pipeline "succeeds" even if the tests fail, because test results are essentially ignored.
+
+To fail the pipeline when tests fail, use [conditional execution]({{site.baseurl}}/docs/pipelines/conditional-execution-of-steps/).
+
+As the last step in your pipeline, add the following step:
+
+{% highlight yaml %}
+{% raw %}
+ MarkMyPipelineStatus:
+ image: alpine:latest
+ title: Marking pipeline status
+ commands:
+ - echo "Unit tests failed"
+ - exit 1
+ when:
+ condition:
+ all:
+ myCondition: RunMyUnitTests.result == 'failure'
+{% endraw %}
+{% endhighlight %}
+
+This step checks the result of your unit tests, and stops the whole pipeline by exiting with an error, if the tests fail.
+Replace `RunMyUnitTests` with the name of your step that runs unit tests.
+
+Here is a full pipeline example:
+
+`codefresh.yml`
+{% highlight yaml %}
+{% raw %}
+version: '1.0'
+steps:
+ RunMyUnitTests:
+ image: alpine:latest
+ title: Running my UnitTests that will fail
+ fail_fast: false
+ commands:
+ - exit 1 #simulate test fail
+ CollectingMyTestresults:
+ image: alpine:latest
+ title: Collecting test results
+ commands:
+ - echo "collecting/copy test results"
+ MarkMyPipelineStatus:
+ image: alpine:latest
+ title: Checking Unit test result
+ commands:
+ - echo "Unit tests failed, marking the whole pipeline as failed"
+ - exit 1
+ when:
+ condition:
+ all:
+ myCondition: RunMyUnitTests.result == 'failure'
+{% endraw %}
+{% endhighlight %}
+
+If you run this pipeline, you will see:
+
+1. The `RunMyUnitTests` will fail but the pipeline will continue
+1. The `CollectingMyTestresults` step will always run even if tests fail
+1. The `MarkMyPipelineStatus` step will mark the whole pipeline as failed
+
+## Running the test reporting step in parallel mode
+
+Test reporting works well with the [parallel pipeline mode]({{site.baseurl}}/docs/pipelines/advanced-workflows/), where each step
+is evaluated any time there is a change in the workflow.
+
+Here is how you can define the test reporting step to run regardless of pipeline result:
+
+{% highlight yaml %}
+{% raw %}
+ unit_test_reporting_step:
+ [...]
+ when:
+ condition:
+ any:
+ mySuccessCondition: workflow.status == 'success'
+ myFailureCondition: workflow.status == 'failure'
+{% endraw %}
+{% endhighlight %}
+
+See [handling errors in a pipeline]({{site.baseurl}}/docs/pipelines/advanced-workflows/#handling-error-conditions-in-a-pipeline) for more details.
+
+
+
+## Related articles
+[Codefresh YAML]({{site.baseurl}}/docs/pipelines/what-is-the-codefresh-yaml/)
+[Steps in pipelines]({{site.baseurl}}/docs/pipelines/steps/)
+[Parallel workflows in pipelines]({{site.baseurl}}/docs/pipelines/advanced-workflows/)
+
diff --git a/_docs/testing/unit-tests.md b/_docs/testing/unit-tests.md
new file mode 100644
index 000000000..eb387f667
--- /dev/null
+++ b/_docs/testing/unit-tests.md
@@ -0,0 +1,295 @@
+---
+title: "Unit tests"
+description: "Run unit tests in Codefresh pipelines"
+group: testing
+redirect_from:
+ - /docs/unit-tests/
+toc: true
+---
+Easily run unit tests for every commit or pull request.
+
+>For the purposes of this article, "unit tests" are the tests that use only the source code of the application and nothing else. If you are interested in running tests with external services (such as databases), then see [integration tests]({{site.baseurl}}/docs/testing/integration-tests/).
+
+Different companies have different types of unit tests, and in several cases, the type of programming language also affects when/what tests are run. Codefresh supports all testing frameworks (including mocking frameworks) for all popular programming languages.
+
+Here we will see four ways of running unit tests in Codefresh:
+
+1. Running unit tests in a Dockerfile (recommended only for smoke tests)
+1. Running unit tests with an external image (best for traditional/simple applications)
+1. Running unit tests in the application image (not recommended, but very popular)
+1. Running unit tests using a special testing image (the recommended solution for complex applications)
+
+For an example application for 2 and 3, see [unit test examples]({{site.baseurl}}/docs/example-catelog/ci-examples/run-unit-tests/).
+
+## Running unit tests as part of a Docker build
+
+A handy way to quickly test a Docker image is by placing one or more smoke tests in the Dockerfile itself. The unit
+tests are executed when the image is built, and if they fail, the image is not created.
+
+Here is an example:
+
+ `Dockerfile`
+{% highlight docker %}
+{% raw %}
+FROM python:3.6.4-alpine3.6
+
+ENV FLASK_APP=minitwit
+COPY . /app
+WORKDIR /app
+
+RUN pip install --editable .
+RUN flask initdb
+
+# Unit tests
+RUN python setup.py test
+
+EXPOSE 5000
+CMD [ "flask", "run", "--host=0.0.0.0" ]
+{% endraw %}
+{% endhighlight %}
+
+This kind of unit test is transparent to Codefresh. The unit tests just execute in a [build step]({{site.baseurl}}/docs/pipelines/steps/build/) in the same manner as you would build the image on your workstation.
+
+{%
+ include image.html
+ lightbox="true"
+ file="/images/testing/unit-testing/unit-tests-in-dockerfile.png"
+ url="/images/testing/unit-testing/unit-tests-in-dockerfile.png"
+ alt="Unit tests inside a Dockerfile"
+ caption="Unit tests inside a Dockerfile"
+ max-width="80%"
+%}
+
+A big disadvantage of this unit testing method is that getting reports from the Docker image is not a straightforward process. On the other hand, such unit tests are very easy to integrate in any workflow. The Codefresh pipeline simply checks out the code and builds a Dockerfile.
+
+`codefresh.yml`
+{% highlight yaml %}
+{% raw %}
+version: '1.0'
+stages:
+ - prepare
+ - build
+steps:
+ main_clone:
+ title: Cloning main repository...
+ type: git-clone
+ repo: 'codefresh-contrib/python-flask-sample-app'
+ revision: 'with-tests-in-dockerfile'
+ stage: prepare
+ git: github
+ MyAppDockerImage:
+ title: Building Docker Image
+ type: build
+ stage: build
+ image_name: my-app-image
+ working_directory: ./
+ tag: with-tests-in-dockerfile
+ dockerfile: Dockerfile
+{% endraw %}
+{% endhighlight %}
+
+This technique is best used for a very small subset of unit tests that check the overall well-being of a Docker image. The bulk of the tests should be executed outside the Docker build process as we will see in the sections that follow.
+
+## Running unit tests using an external Docker image
+
+The recommended way to run unit tests in Codefresh pipelines is to select a Docker image that has all the test tools that you need, and define an explicit testing step in your pipeline, usually a [freestyle step]({{site.baseurl}}/docs/pipelines/steps/freestyle/).
+
+Here is an example where unit tests are run using a JDK/Maven image:
+
+`codefresh.yml`
+{% highlight yaml %}
+{% raw %}
+version: '1.0'
+stages:
+ - prepare
+ - test
+ - package
+steps:
+ main_clone:
+ title: Cloning main repository...
+ stage: prepare
+ type: git-clone
+ repo: 'codefresh-contrib/spring-boot-2-sample-app'
+ revision: master
+ git: github
+ MyUnitTests:
+ title: JUnit tests
+ stage: test
+ image: 'maven:3.5.2-jdk-8-alpine'
+ commands:
+ - mvn -Dmaven.repo.local=/codefresh/volume/m2_repository test
+ MyAppDockerImage:
+ title: Building Docker Image
+ type: build
+ stage: package
+ image_name: spring-boot-2-sample-app
+ working_directory: ./
+ tag: 'non-multi-stage'
+ dockerfile: Dockerfile
+{% endraw %}
+{% endhighlight %}
+
+The main advantage of this approach is that you can easily replicate your test environment in the Codefresh pipeline by selecting the appropriate image for your tests. You also get a clear overview on the test results. If they fail, the pipeline automatically stops execution. You can change this behavior with the [fail_fast]({{site.baseurl}}/docs/pipelines/what-is-the-codefresh-yaml/#execution-flow) property.
+
+{%
+ include image.html
+ lightbox="true"
+ file="/images/testing/unit-testing/unit-tests-with-external-image.png"
+ url="/images/testing/unit-testing/unit-tests-with-external-image.png"
+ alt="Unit tests with external Docker image"
+ caption="Unit tests with external Docker image"
+ max-width="80%"
+%}
+
+Notice that even if the example above eventually creates a Docker image, you can still use this way of running unit tests for traditional applications that are not dockerized yet, such as VM-based applications.
+
+## Running unit tests with the application Docker image
+
+In several cases, especially with dynamic languages, you can reuse the Docker image that holds the application also for unit tests. This is a very common technique for Node, Python, and Ruby applications.
+In this case, you can use [context variables]({{site.baseurl}}/docs/pipelines/variables/#context-related-variables) in Codefresh to run a unit test step in the image that was created in a previous step:
+
+`codefresh.yml`
+{% highlight yaml %}
+{% raw %}
+version: '1.0'
+stages:
+ - prepare
+ - build
+ - test
+steps:
+ main_clone:
+ title: Cloning main repository...
+ type: git-clone
+ repo: 'codefresh-contrib/python-flask-sample-app'
+ revision: 'master'
+ git: github
+ stage: prepare
+ MyAppDockerImage:
+ title: Building Docker Image
+ type: build
+ stage: build
+ image_name: my-app-image
+ working_directory: ./
+ tag: 'master'
+ dockerfile: Dockerfile
+ MyUnitTests:
+ title: Running Unit tests
+ stage: test
+ image: '${{MyAppDockerImage}}'
+ commands:
+ - python setup.py test
+{% endraw %}
+{% endhighlight %}
+
+We use a [Codefresh variable]({{site.baseurl}}/docs/pipelines/variables/) as the value of the `image` property in the last step. This will make the unit test execute in the same Docker container that was created in the second step of the pipeline.
+
+{%
+ include image.html
+ lightbox="true"
+ file="/images/testing/unit-testing/unit-tests-with-app-image.png"
+ url="/images/testing/unit-testing/unit-tests-with-app-image.png"
+ alt="Reusing the app image for unit tests"
+ caption="Reusing the app image for unit tests"
+ max-width="80%"
+%}
+
+This technique is certainly useful, but can be easily abused if you end up shipping testing tools in your production image (which is not recommended). If you find your production images filled with test tools and libraries, it is better to use the technique in the next section which uses a different image for tests.
+
+
+## Running unit tests with a dynamic Docker image
+
+The ultimate method of running unit tests in Codefresh is by creating a specific image, dedicated to unit tests. If Docker Hub doesn't already contain an image that suits you, you should create your own.
+
+This means that your application has *two Dockerfiles*. The main one that holds the application as a deployment artifact, and another one that holds all the unit test libraries and tools that you need.
+
+Here is an example:
+
+`codefresh.yml`
+{% highlight yaml %}
+{% raw %}
+version: '1.0'
+stages:
+ - prepare
+ - 'Test tools'
+ - test
+ - build
+steps:
+ main_clone:
+ title: Cloning main repository...
+ type: git-clone
+ repo: 'codefreshdemo/demochat'
+ revision: 'master'
+ git: github
+ stage: prepare
+ MyUnitTestDockerImage:
+ title: Building Test image
+ type: build
+ stage: 'Test tools'
+ image_name: my-test-image
+ working_directory: ./
+ tag: 'master'
+ dockerfile: Dockerfile.dev
+ MyUnitTests:
+ title: Running Unit tests
+ stage: test
+ image: '${{MyUnitTestDockerImage}}'
+ commands:
+ - npm run test
+ MyAppDockerImage:
+ title: Building Docker Image
+ type: build
+ stage: build
+ image_name: my-app-image
+ working_directory: ./
+ tag: 'master'
+ dockerfile: Dockerfile
+{% endraw %}
+{% endhighlight %}
+
+Here we create two Docker images:
+
+1. The first docker image is created from `Dockerfile.dev`.
+ Unit tests run in the context of that image (`MyUnitTestDockerImage`).
+1. The production application uses another Dockerfile.
+
+{%
+ include image.html
+ lightbox="true"
+ file="/images/testing/unit-testing/unit-tests-with-dedicated-image.png"
+ url="/images/testing/unit-testing/unit-tests-with-dedicated-image.png"
+ alt="Dedicated unit test image"
+ caption="Dedicated unit test image"
+ max-width="80%"
+%}
+
+This is one of the best ways to run unit tests (as well as integration tests), as it allows you to fine-tune the test environment while still shipping only what is needed to production.
+
+In the example above, we used two different Dockerfiles, but you could also use a single Dockerfile with multi-stage builds. Use the `target` directive to stop the image build process at a previous layer that has all the testing tools.
+
+## Creating test reports
+
+All the methods mentioned above for running unit tests, apart from the first method, can also be used for reporting. Read all about test results and graphs in [test reports]({{site.baseurl}}/docs/testing/test-reports/).
+
+{% include
+image.html
+lightbox="true"
+file="/images/pipeline/test-reports/sample-test-report.png"
+url="/images/pipeline/test-reports/sample-test-report.png"
+alt="Sample Allure test report"
+caption="Sample Allure test report"
+max-width="70%"
+%}
+
+
+
+## Related articles
+[Unit test example]({{site.baseurl}}/docs/example-catalog/ci-examples/run-unit-tests/)
+[Introduction to pipelines]({{site.baseurl}}/docs/pipelines/introduction-to-codefresh-pipelines/)
+[Codefresh YAML]({{site.baseurl}}/docs/pipelines/what-is-the-codefresh-yaml/)
+[On demand environments]({{site.baseurl}}/docs/getting-started/on-demand-environments/)
+[Integration tests]({{site.baseurl}}/docs/testing/integration-tests/)
+
+
+
+
+
diff --git a/images/testing/codacy/codacy-add-repo.png b/images/testing/codacy/codacy-add-repo.png
new file mode 100644
index 000000000..23a89a3df
Binary files /dev/null and b/images/testing/codacy/codacy-add-repo.png differ
diff --git a/images/testing/codacy/codacy-create-api-token.png b/images/testing/codacy/codacy-create-api-token.png
new file mode 100644
index 000000000..fa2eedaa3
Binary files /dev/null and b/images/testing/codacy/codacy-create-api-token.png differ
diff --git a/images/testing/codacy/codacy-pipeline.png b/images/testing/codacy/codacy-pipeline.png
new file mode 100644
index 000000000..0acf883bd
Binary files /dev/null and b/images/testing/codacy/codacy-pipeline.png differ
diff --git a/images/testing/codacy/codacy-report.png b/images/testing/codacy/codacy-report.png
new file mode 100644
index 000000000..5b185fe21
Binary files /dev/null and b/images/testing/codacy/codacy-report.png differ
diff --git a/images/testing/codacy/codacy-variable.png b/images/testing/codacy/codacy-variable.png
new file mode 100644
index 000000000..7dc5fc445
Binary files /dev/null and b/images/testing/codacy/codacy-variable.png differ
diff --git a/images/testing/codacy/create-api-token.png b/images/testing/codacy/create-api-token.png
new file mode 100644
index 000000000..0eca544df
Binary files /dev/null and b/images/testing/codacy/create-api-token.png differ
diff --git a/images/testing/codacy/create-codacy-pipeline.png b/images/testing/codacy/create-codacy-pipeline.png
new file mode 100644
index 000000000..1fe4c8c4f
Binary files /dev/null and b/images/testing/codacy/create-codacy-pipeline.png differ
diff --git a/images/testing/codacy/file-analysis.png b/images/testing/codacy/file-analysis.png
new file mode 100644
index 000000000..5324f4c27
Binary files /dev/null and b/images/testing/codacy/file-analysis.png differ
diff --git a/images/testing/codecov/analysis-report.png b/images/testing/codecov/analysis-report.png
new file mode 100644
index 000000000..4c2dfdc59
Binary files /dev/null and b/images/testing/codecov/analysis-report.png differ
diff --git a/images/testing/codecov/codecov-interface.png b/images/testing/codecov/codecov-interface.png
new file mode 100644
index 000000000..bb0044559
Binary files /dev/null and b/images/testing/codecov/codecov-interface.png differ
diff --git a/images/testing/codecov/codecov-pipeline.png b/images/testing/codecov/codecov-pipeline.png
new file mode 100644
index 000000000..72b0efd12
Binary files /dev/null and b/images/testing/codecov/codecov-pipeline.png differ
diff --git a/images/testing/codecov/codecov-report-details.png b/images/testing/codecov/codecov-report-details.png
new file mode 100644
index 000000000..e45890591
Binary files /dev/null and b/images/testing/codecov/codecov-report-details.png differ
diff --git a/images/testing/codecov/codecov-report.png b/images/testing/codecov/codecov-report.png
new file mode 100644
index 000000000..dbb9260de
Binary files /dev/null and b/images/testing/codecov/codecov-report.png differ
diff --git a/images/testing/compositions/972337d-codefresh_compose_by_url.png b/images/testing/compositions/972337d-codefresh_compose_by_url.png
new file mode 100644
index 000000000..770270099
Binary files /dev/null and b/images/testing/compositions/972337d-codefresh_compose_by_url.png differ
diff --git a/images/testing/compositions/add-composition-first.png b/images/testing/compositions/add-composition-first.png
new file mode 100644
index 000000000..fb6158221
Binary files /dev/null and b/images/testing/compositions/add-composition-first.png differ
diff --git a/images/testing/compositions/compose-from-template-edit.png b/images/testing/compositions/compose-from-template-edit.png
new file mode 100644
index 000000000..f56d592b1
Binary files /dev/null and b/images/testing/compositions/compose-from-template-edit.png differ
diff --git a/images/testing/compositions/compose-from-template-select-template.png b/images/testing/compositions/compose-from-template-select-template.png
new file mode 100644
index 000000000..d06faf3e4
Binary files /dev/null and b/images/testing/compositions/compose-from-template-select-template.png differ
diff --git a/images/testing/compositions/composition-file-in-repo.png b/images/testing/compositions/composition-file-in-repo.png
new file mode 100644
index 000000000..de975f2fc
Binary files /dev/null and b/images/testing/compositions/composition-file-in-repo.png differ
diff --git a/images/testing/compositions/composition-launch-button.png b/images/testing/compositions/composition-launch-button.png
new file mode 100644
index 000000000..7d7037655
Binary files /dev/null and b/images/testing/compositions/composition-launch-button.png differ
diff --git a/images/testing/compositions/composition-launch-log.png b/images/testing/compositions/composition-launch-log.png
new file mode 100644
index 000000000..eb510c5d3
Binary files /dev/null and b/images/testing/compositions/composition-launch-log.png differ
diff --git a/images/testing/compositions/composition-list.png b/images/testing/compositions/composition-list.png
new file mode 100644
index 000000000..e100f4369
Binary files /dev/null and b/images/testing/compositions/composition-list.png differ
diff --git a/images/testing/compositions/composition-method.png b/images/testing/compositions/composition-method.png
new file mode 100644
index 000000000..b17f91d37
Binary files /dev/null and b/images/testing/compositions/composition-method.png differ
diff --git a/images/testing/compositions/composition-name.png b/images/testing/compositions/composition-name.png
new file mode 100644
index 000000000..d313c9712
Binary files /dev/null and b/images/testing/compositions/composition-name.png differ
diff --git a/images/testing/compositions/empty-composition.png b/images/testing/compositions/empty-composition.png
new file mode 100644
index 000000000..72b8f1dce
Binary files /dev/null and b/images/testing/compositions/empty-composition.png differ
diff --git a/images/testing/compositions/environment-running.png b/images/testing/compositions/environment-running.png
new file mode 100644
index 000000000..32a81245b
Binary files /dev/null and b/images/testing/compositions/environment-running.png differ
diff --git a/images/testing/compositions/existing-composition.png b/images/testing/compositions/existing-composition.png
new file mode 100644
index 000000000..2afa2bdf5
Binary files /dev/null and b/images/testing/compositions/existing-composition.png differ
diff --git a/images/testing/compositions/path-to-docker-compose.png b/images/testing/compositions/path-to-docker-compose.png
new file mode 100644
index 000000000..f9414049a
Binary files /dev/null and b/images/testing/compositions/path-to-docker-compose.png differ
diff --git a/images/testing/compositions/replace-build.png b/images/testing/compositions/replace-build.png
new file mode 100644
index 000000000..3041fa53d
Binary files /dev/null and b/images/testing/compositions/replace-build.png differ
diff --git a/images/testing/compositions/share-environment-link.png b/images/testing/compositions/share-environment-link.png
new file mode 100644
index 000000000..c5998d85e
Binary files /dev/null and b/images/testing/compositions/share-environment-link.png differ
diff --git a/images/testing/coveralls/add-repository.png b/images/testing/coveralls/add-repository.png
new file mode 100644
index 000000000..a1534759f
Binary files /dev/null and b/images/testing/coveralls/add-repository.png differ
diff --git a/images/testing/coveralls/coveralls-coverage.png b/images/testing/coveralls/coveralls-coverage.png
new file mode 100644
index 000000000..85f8547ef
Binary files /dev/null and b/images/testing/coveralls/coveralls-coverage.png differ
diff --git a/images/testing/coveralls/coveralls-pipeline.png b/images/testing/coveralls/coveralls-pipeline.png
new file mode 100644
index 000000000..fd319891b
Binary files /dev/null and b/images/testing/coveralls/coveralls-pipeline.png differ
diff --git a/images/testing/coveralls/coveralls-sample-app.png b/images/testing/coveralls/coveralls-sample-app.png
new file mode 100644
index 000000000..a2ee18243
Binary files /dev/null and b/images/testing/coveralls/coveralls-sample-app.png differ
diff --git a/images/testing/coveralls/coveralls-specific-report.png b/images/testing/coveralls/coveralls-specific-report.png
new file mode 100644
index 000000000..1a71ffe22
Binary files /dev/null and b/images/testing/coveralls/coveralls-specific-report.png differ
diff --git a/images/testing/coveralls/create-coveralls-pipeline.png b/images/testing/coveralls/create-coveralls-pipeline.png
new file mode 100644
index 000000000..ca986dc8e
Binary files /dev/null and b/images/testing/coveralls/create-coveralls-pipeline.png differ
diff --git a/images/testing/dynamic-preview-environment.png b/images/testing/dynamic-preview-environment.png
new file mode 100644
index 000000000..8a2994f5f
Binary files /dev/null and b/images/testing/dynamic-preview-environment.png differ
diff --git a/images/testing/integration-testing/complex-tests.png b/images/testing/integration-testing/complex-tests.png
new file mode 100644
index 000000000..058340bf8
Binary files /dev/null and b/images/testing/integration-testing/complex-tests.png differ
diff --git a/images/testing/integration-testing/complex-tests.svg b/images/testing/integration-testing/complex-tests.svg
new file mode 100644
index 000000000..01a6f74d5
--- /dev/null
+++ b/images/testing/integration-testing/complex-tests.svg
@@ -0,0 +1,564 @@
+
+
+
+
diff --git a/images/testing/integration-testing/from-source-code.png b/images/testing/integration-testing/from-source-code.png
new file mode 100644
index 000000000..f62f82dee
Binary files /dev/null and b/images/testing/integration-testing/from-source-code.png differ
diff --git a/images/testing/integration-testing/from-source-code.svg b/images/testing/integration-testing/from-source-code.svg
new file mode 100644
index 000000000..36d859a92
--- /dev/null
+++ b/images/testing/integration-testing/from-source-code.svg
@@ -0,0 +1,359 @@
+
+
+
+
diff --git a/images/testing/integration-testing/multi-scope.png b/images/testing/integration-testing/multi-scope.png
new file mode 100644
index 000000000..476c779aa
Binary files /dev/null and b/images/testing/integration-testing/multi-scope.png differ
diff --git a/images/testing/integration-testing/multi-scope.svg b/images/testing/integration-testing/multi-scope.svg
new file mode 100644
index 000000000..4a336470d
--- /dev/null
+++ b/images/testing/integration-testing/multi-scope.svg
@@ -0,0 +1,428 @@
+
+
+
+
diff --git a/images/testing/integration-testing/scope.svg b/images/testing/integration-testing/scope.svg
new file mode 100644
index 000000000..740071a30
--- /dev/null
+++ b/images/testing/integration-testing/scope.svg
@@ -0,0 +1,426 @@
+
+
+
+
diff --git a/images/testing/integration-testing/single-scope.png b/images/testing/integration-testing/single-scope.png
new file mode 100644
index 000000000..7ed58805b
Binary files /dev/null and b/images/testing/integration-testing/single-scope.png differ
diff --git a/images/testing/integration-testing/single-scope.svg b/images/testing/integration-testing/single-scope.svg
new file mode 100644
index 000000000..7fce00e3e
--- /dev/null
+++ b/images/testing/integration-testing/single-scope.svg
@@ -0,0 +1,423 @@
+
+
+
+
diff --git a/images/testing/integration-testing/special-image.png b/images/testing/integration-testing/special-image.png
new file mode 100644
index 000000000..1294febfa
Binary files /dev/null and b/images/testing/integration-testing/special-image.png differ
diff --git a/images/testing/integration-testing/special-image.svg b/images/testing/integration-testing/special-image.svg
new file mode 100644
index 000000000..6cb5f8dd6
--- /dev/null
+++ b/images/testing/integration-testing/special-image.svg
@@ -0,0 +1,432 @@
+
+
+
+
diff --git a/images/testing/integration-testing/to-app.png b/images/testing/integration-testing/to-app.png
new file mode 100644
index 000000000..061256f0b
Binary files /dev/null and b/images/testing/integration-testing/to-app.png differ
diff --git a/images/testing/integration-testing/to-app.svg b/images/testing/integration-testing/to-app.svg
new file mode 100644
index 000000000..76a858536
--- /dev/null
+++ b/images/testing/integration-testing/to-app.svg
@@ -0,0 +1,416 @@
+
+
+
+
diff --git a/images/testing/security-scanning/.keep b/images/testing/security-scanning/.keep
new file mode 100644
index 000000000..e69de29bb
diff --git a/images/testing/security-scanning/aqua-scan.png b/images/testing/security-scanning/aqua-scan.png
new file mode 100644
index 000000000..a5562f18d
Binary files /dev/null and b/images/testing/security-scanning/aqua-scan.png differ
diff --git a/images/testing/security-scanning/clair-scan.png b/images/testing/security-scanning/clair-scan.png
new file mode 100644
index 000000000..8729f4c66
Binary files /dev/null and b/images/testing/security-scanning/clair-scan.png differ
diff --git a/images/testing/security-scanning/security-annotations.png b/images/testing/security-scanning/security-annotations.png
new file mode 100644
index 000000000..1c52099c6
Binary files /dev/null and b/images/testing/security-scanning/security-annotations.png differ
diff --git a/images/testing/security-scanning/security-test-results.png b/images/testing/security-scanning/security-test-results.png
new file mode 100644
index 000000000..5b1bd6916
Binary files /dev/null and b/images/testing/security-scanning/security-test-results.png differ
diff --git a/images/testing/security-scanning/snyk-test-report.png b/images/testing/security-scanning/snyk-test-report.png
new file mode 100644
index 000000000..8316e955f
Binary files /dev/null and b/images/testing/security-scanning/snyk-test-report.png differ
diff --git a/images/testing/sonarqube/analysis-log.png b/images/testing/sonarqube/analysis-log.png
new file mode 100644
index 000000000..328da43c0
Binary files /dev/null and b/images/testing/sonarqube/analysis-log.png differ
diff --git a/images/testing/sonarqube/codefresh-yaml-sonar.png b/images/testing/sonarqube/codefresh-yaml-sonar.png
new file mode 100644
index 000000000..863cf1013
Binary files /dev/null and b/images/testing/sonarqube/codefresh-yaml-sonar.png differ
diff --git a/images/testing/sonarqube/generate-token.png b/images/testing/sonarqube/generate-token.png
new file mode 100644
index 000000000..9b5b336e1
Binary files /dev/null and b/images/testing/sonarqube/generate-token.png differ
diff --git a/images/testing/sonarqube/simplified-codefresh-pipeline.png b/images/testing/sonarqube/simplified-codefresh-pipeline.png
new file mode 100644
index 000000000..83e6fcd60
Binary files /dev/null and b/images/testing/sonarqube/simplified-codefresh-pipeline.png differ
diff --git a/images/testing/sonarqube/sonar-analysis-details.png b/images/testing/sonarqube/sonar-analysis-details.png
new file mode 100644
index 000000000..8a913e0b5
Binary files /dev/null and b/images/testing/sonarqube/sonar-analysis-details.png differ
diff --git a/images/testing/sonarqube/sonar-instructions.png b/images/testing/sonarqube/sonar-instructions.png
new file mode 100644
index 000000000..9f33a8fa9
Binary files /dev/null and b/images/testing/sonarqube/sonar-instructions.png differ
diff --git a/images/testing/sonarqube/sonar-project.png b/images/testing/sonarqube/sonar-project.png
new file mode 100644
index 000000000..712783c02
Binary files /dev/null and b/images/testing/sonarqube/sonar-project.png differ
diff --git a/images/testing/sonarqube/sonarqube-logo.png b/images/testing/sonarqube/sonarqube-logo.png
new file mode 100644
index 000000000..568be52c5
Binary files /dev/null and b/images/testing/sonarqube/sonarqube-logo.png differ
diff --git a/images/testing/unit-testing/unit-tests-in-dockerfile.png b/images/testing/unit-testing/unit-tests-in-dockerfile.png
new file mode 100644
index 000000000..0704ce18d
Binary files /dev/null and b/images/testing/unit-testing/unit-tests-in-dockerfile.png differ
diff --git a/images/testing/unit-testing/unit-tests-with-app-image.png b/images/testing/unit-testing/unit-tests-with-app-image.png
new file mode 100644
index 000000000..46cd81fd1
Binary files /dev/null and b/images/testing/unit-testing/unit-tests-with-app-image.png differ
diff --git a/images/testing/unit-testing/unit-tests-with-dedicated-image.png b/images/testing/unit-testing/unit-tests-with-dedicated-image.png
new file mode 100644
index 000000000..2346cebe1
Binary files /dev/null and b/images/testing/unit-testing/unit-tests-with-dedicated-image.png differ
diff --git a/images/testing/unit-testing/unit-tests-with-external-image.png b/images/testing/unit-testing/unit-tests-with-external-image.png
new file mode 100644
index 000000000..6d7504a99
Binary files /dev/null and b/images/testing/unit-testing/unit-tests-with-external-image.png differ