---
title: Kotlin reference
description: Reference documentation for the Allure Kotlin extensions | Metadata | Steps and stages | Parameters | Attachments | Global errors | Coroutines
---

# Allure Kotlin extensions reference

These are the functions provided by the [Allure Kotlin extensions](/docs/kotlin/). All of them are top-level functions in the `io.qameta.allure.kotlin.extensions` package, except for the coroutine functions in `io.qameta.allure.kotlin.coroutines`. You can import the functions one by one or all at once:

```kotlin
import io.qameta.allure.kotlin.extensions.*
```

The functions add information to the test, fixture or step that is currently running, in the same way as the Runtime API of your test framework's integration, for example, [Allure JUnit Jupiter](/docs/junit-jupiter-reference/). Calls made while no test or fixture is running have no effect.

## Metadata

Assign a test's [description, links and other metadata](/docs/v3/readability/#description-links-and-other-metadata).

### Description

- `description(description: String)`
- `descriptionHtml(descriptionHtml: String)`

Set the test's [description](/docs/v3/readability/#description). `description()` accepts Markdown, `descriptionHtml()` accepts HTML.

```kotlin
import io.qameta.allure.kotlin.extensions.description
import org.junit.jupiter.api.Test

class TestMyWebsite {

    @Test
    fun testAuthentication() {
        description("This test attempts to log into the website using a login and a password. Fails if any error happens.\n\nNote that this test does not test 2-Factor Authentication.")
        // ...
    }
}
```

### Label

- `label(name: String, value: String)`

Add a [label](/docs/v3/readability/#other-labels) with the given name and value to the test.

```kotlin
import io.qameta.allure.kotlin.extensions.label
import org.junit.jupiter.api.Test

class TestMyWebsite {

    @Test
    fun testAuthentication() {
        label(name = "component", value = "authentication")
        label(name = "owner", value = "John Doe")
        // ...
    }
}
```

### Link

- `link(url: String)`
- `link(name: String, url: String)`
- `link(name: String, type: String, url: String)`
- `issue(name: String, url: String)`
- `tms(name: String, url: String)`

Add a [link](/docs/v3/readability/#links) related to the test. `issue()` and `tms()` add links of the `issue` and `tms` types.

```kotlin
import io.qameta.allure.kotlin.extensions.issue
import io.qameta.allure.kotlin.extensions.link
import io.qameta.allure.kotlin.extensions.tms
import org.junit.jupiter.api.Test

class TestMyWebsite {

    @Test
    fun testAuthentication() {
        link(name = "Website", url = "https://dev.example.com/")
        issue(name = "AUTH-123", url = "https://issues.example.com/AUTH-123")
        tms(name = "TMS-456", url = "https://tms.example.com/TMS-456")
        // ...
    }
}
```

## Behavior-based hierarchy

- `epic(value: String)`
- `feature(value: String)`
- `story(value: String)`

Assign the names of the epic, feature and user story to the test, as part of Allure's [behavior-based hierarchy](/docs/v3/navigation/#behavior-based-hierarchy).

```kotlin
import io.qameta.allure.kotlin.extensions.epic
import io.qameta.allure.kotlin.extensions.feature
import io.qameta.allure.kotlin.extensions.story
import org.junit.jupiter.api.Test

class TestMyWebsite {

    @Test
    fun testAuthentication() {
        epic("Web interface")
        feature("Essential features")
        story("Authentication")
        // ...
    }
}
```

## Suite-based hierarchy

- `suite(value: String)`

Assign the name of the suite, as part of Allure's [suite-based hierarchy](/docs/v3/navigation/#suite-based-hierarchy). To set the other levels of the hierarchy, use `label()` with the `parentSuite` or `subSuite` label name.

```kotlin
import io.qameta.allure.kotlin.extensions.suite
import org.junit.jupiter.api.Test

class TestMyWebsite {

    @Test
    fun testAuthentication() {
        suite("Tests for essential features")
        // ...
    }
}
```

## Test steps

- `step(name: String = "step", body: StepScope.() -> T): T`
- `step(name: String, status: Status = Status.PASSED)`

Define a [test step](/docs/steps/).

The first function runs the code in `body` as a step with the given `name` and returns the value that the code returns. If the code throws an exception, the step is marked as failed (for assertion errors) or broken (for other exceptions), and the exception is passed on. Steps can contain other steps.

The second function adds a step that has already finished, with the given `name` and `status`.

Inside `body`, the following functions are available:

- `name(name: String)` - change the name of the step, for example, to include a value that becomes known during the step.
- `parameter(name: String, value: T, excluded: Boolean? = null, mode: Parameter.Mode? = null): T` - add a parameter to the step. The arguments are the same as for the [test parameters](#parametrized-tests).

```kotlin
import io.qameta.allure.kotlin.extensions.step
import io.qameta.allure.model.Status
import org.junit.jupiter.api.Test

class TestOrders {

    @Test
    fun testCreateOrder() {
        val orderId = step("Create order") {
            parameter(name = "region", value = "eu-west")
            val id = createOrder()
            name("Create order $id")
            id
        }

        step("Check the order") {
            step("Order exists")
            step("Delivery date is set", Status.SKIPPED)
            verifyOrder(orderId)
        }
    }
}
```

### Stages

- `stage(name: String)`

Start a stage: a named group of steps for a phase of the test, such as preparing data or checking results. A stage is shown as a step. It has no explicit end: it contains the steps and attachments that follow it, until the next stage starts or the step, test or fixture that contains it ends. A stage started inside a step becomes a part of that step.

```kotlin
import io.qameta.allure.kotlin.extensions.stage
import io.qameta.allure.kotlin.extensions.step
import org.junit.jupiter.api.Test

class TestOrders {

    @Test
    fun testCreateOrder() {
        stage("Prepare data")
        step("Create a customer")
        step("Add products to the cart")

        stage("Submit the order")
        step("Open the checkout page")
        step("Confirm the order")

        stage("Verify the result")
        step("Order status is created")
    }
}
```

## Parametrized tests

- `parameter(name: String, value: T, excluded: Boolean? = null, mode: Parameter.Mode? = null): T`

Add a [parameter](/docs/v3/readability/#parametrized-tests) with the given name and value to the test, and return the value. Allure Report shows the parameters on the test page. Different values of a parameter make different tests in the [history](/docs/history-and-retries/), unless the parameter is excluded.

- `excluded` - if `true`, the parameter is not used to tell tests apart in the history. Use it for values that change on every run, such as timestamps.
- `mode` - how the parameter is shown in the report: `Parameter.Mode.DEFAULT` shows the parameter and its value, `Parameter.Mode.MASKED` shows the parameter but hides its value (use it for passwords, tokens and other sensitive values), `Parameter.Mode.HIDDEN` doesn't show the parameter at all. Note that the values of masked and hidden parameters are still stored in the test results directory.

The parameters added this way are shown together with the ones that your test framework's integration reports, for example, the arguments of a parameterized test.

```kotlin
import io.qameta.allure.kotlin.extensions.parameter
import io.qameta.allure.model.Parameter
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource

class TestMyWebsite {

    @ParameterizedTest
    @ValueSource(strings = ["johndoe", "janedoe"])
    fun testAuthentication(login: String) {
        val browser = parameter("browser", "firefox")
        parameter(name = "password", value = "qwerty", mode = Parameter.Mode.MASKED)
        parameter(name = "startedAt", value = System.currentTimeMillis(), excluded = true)
        // ...
    }
}
```

## Attachments

- `attachment(name: String, content: String, type: String = "text/plain", fileExtension: String? = null)`
- `attachment(name: String, content: ByteArray, type: String = "application/octet-stream", fileExtension: String? = null)`
- `attachment(name: String, content: InputStream, type: String = "application/octet-stream", fileExtension: String? = null)`
- `attachmentAsync(name: String, content: CompletionStage<out InputStream>, type: String = "application/octet-stream", fileExtension: String? = null): CompletableFuture<Void>`
- `addHttpExchange(name: String, exchange: HttpExchange)`

Add an [attachment](/docs/attachments/) with the given name and content. Like in other Allure Java integrations, each attachment is shown as a step with the attachment's name.

The `type` is the [media type](https://en.wikipedia.org/wiki/Media_type) of the content. It defines how Allure Report shows the attachment. If `fileExtension` is not set, the file extension is chosen based on the type. Pass an empty string to save the file without an extension.

`attachmentAsync()` doesn't wait for the content. It returns a future that completes when the attachment is written. The test, fixture or step that the attachment belongs to waits for it before it ends.

`addHttpExchange()` adds an [HTTP exchange attachment](/docs/attachments/#http-exchanges) that you built yourself.

```kotlin
import io.qameta.allure.kotlin.extensions.attachment
import io.qameta.allure.kotlin.extensions.attachmentAsync
import org.junit.jupiter.api.Test
import java.util.concurrent.CompletableFuture

class TestMyWebsite {

    @Test
    fun testAuthentication() {
        attachment(name = "response", content = """{"status": "ok"}""", type = "application/json")
        attachment(name = "screenshot", content = takeScreenshot(), type = "image/png")

        attachmentAsync(
            name = "server log",
            content = CompletableFuture.supplyAsync { downloadServerLog() },
            type = "text/plain",
            fileExtension = "log",
        )
        // ...
    }
}
```

## Global errors

- `globalError(throwable: Throwable)`
- `globalError(statusDetails: StatusDetails)`

Report an error that doesn't belong to a single test, such as an environment that is not available. See [Global errors and attachments](/docs/global-errors-and-attachments/).

```kotlin
import io.qameta.allure.kotlin.extensions.globalError
import io.qameta.allure.model.StatusDetails

globalError(IllegalStateException("Test database snapshot is outdated"))
globalError(StatusDetails().setMessage("Payment sandbox is not available"))
```

## Coroutines

The functions in this section are provided by the `allure-kotlin-coroutines` module.

Allure remembers the running test and step separately for each thread. Coroutines can switch threads, for example, after `withContext(Dispatchers.IO)`, and the thread they continue on may not know which test is running. Steps and attachments added there may get lost, or end up in a different test if several tests run in parallel. The functions below make sure they end up in the right test and step.

### Suspending steps

- `step(name: String = "step", body: suspend StepScope.() -> T): T`

Same as [`step()`](#test-steps) from `allure-kotlin-extensions`, but the code in `body` can suspend. The step stays the current step when the code switches threads, so the steps and attachments added inside it are shown in it.

If the coroutine is cancelled, the step is marked as broken, and the cancellation exception is passed on.

```kotlin
import io.qameta.allure.kotlin.coroutines.step
import io.qameta.allure.kotlin.extensions.attachment
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.junit.jupiter.api.Test

class TestOrders {

    @Test
    fun testLoadOrder() {
        runBlocking {
            step("Load order") {
                val order = withContext(Dispatchers.IO) {
                    orderClient.load()
                }
                parameter("orderId", order.id)
                attachment(name = "order", content = order.toString())
            }
        }
    }
}
```

This function has the same name as `step()` from `io.qameta.allure.kotlin.extensions`. If a file needs both, import one of them under a different name, for example, `import io.qameta.allure.kotlin.extensions.step as syncStep`.

### Passing the current test to coroutines

- `withAllureContext(lifecycle: AllureLifecycle = Allure.getLifecycle(), body: suspend CoroutineScope.() -> T): T`
- `allureContext(): CoroutineContext`

When you start coroutines that run on other threads, such as `launch(Dispatchers.IO)`, run them inside `withAllureContext()`. It remembers the test, fixture or step that is running when it is called and passes it to all coroutines started inside it. Steps started in parallel coroutines are shown next to each other, not inside each other.

```kotlin
import io.qameta.allure.kotlin.coroutines.step
import io.qameta.allure.kotlin.coroutines.withAllureContext
import io.qameta.allure.kotlin.extensions.attachment
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Test

class TestReports {

    @Test
    fun testBuildReports() {
        runBlocking {
            withAllureContext {
                coroutineScope {
                    launch(Dispatchers.IO) {
                        attachment(name = "worker output", content = loadOutput())
                    }
                    launch(Dispatchers.Default) {
                        step("Calculate summary") {
                            calculateSummary()
                        }
                    }
                }
            }
        }
    }
}
```

Alternatively, add `allureContext()` to the context of a single coroutine:

```kotlin
launch(Dispatchers.IO + allureContext()) {
    // ...
}
```

Make sure that the coroutines finish before the test, fixture or step that started them ends, for example, by starting them in `coroutineScope { }` as above. Don't use `GlobalScope` or other ways to start coroutines that can outlive the test.

### Annotations and coroutines

The `@Step` and `@Attachment` annotations work in Kotlin code in the same way as in Java, and they need AspectJ, as described in the documentation of your test framework's integration. The functions on this page don't need AspectJ.

Don't use `@Step` on `suspend` functions. Such steps get an extra parameter with internal data, and the steps added after the function switches threads may not appear in the step. Use the suspending [`step()`](#suspending-steps) function instead.
