> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cekura.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# GitLab CI/CD Tutorial

> Learn how to automatically test your agents in GitLab pipelines with the Cekura CI/CD component

export const CopyPageButton = () => {
  if (typeof window !== 'undefined') {
    setTimeout(function () {
      if (document.getElementById('ck-tools')) return;
      var anchor = document.getElementById('content-area') || document.querySelector('.mdx-content');
      if (!anchor) return;
      if (!document.getElementById('ck-style')) {
        var s = document.createElement('style');
        s.id = 'ck-style';
        s.textContent = '#ck-tools{position:absolute;top:6px;right:0;z-index:100;font-family:inherit;}' + '.ck-row{display:inline-flex;align-items:stretch;border:1px solid rgba(0,0,0,0.15);border-radius:8px;overflow:hidden;background:#fff;}' + ':root.dark .ck-row{background:rgba(255,255,255,0.06);border-color:rgba(255,255,255,0.12);}' + '.ck-btn{padding:5px 12px;border:none;background:none;cursor:pointer;font-size:13px;font-weight:500;font-family:inherit;color:#374151;}' + ':root.dark .ck-btn{color:#d1d5db;}' + '.ck-btn:hover{background:rgba(0,0,0,0.04);}' + ':root.dark .ck-btn:hover{background:rgba(255,255,255,0.06);}' + '.ck-chevron{padding:5px 8px;border:none;background:none;cursor:pointer;font-size:14px;font-family:inherit;color:#374151;}' + ':root.dark .ck-chevron{color:#d1d5db;}' + '.ck-chevron:hover{background:rgba(0,0,0,0.04);}' + ':root.dark .ck-chevron:hover{background:rgba(255,255,255,0.06);}' + '.ck-divider{width:1px;background:rgba(0,0,0,0.12);flex-shrink:0;}' + ':root.dark .ck-divider{background:rgba(255,255,255,0.12);}' + '.ck-dd{position:absolute;top:calc(100% + 4px);right:0;min-width:180px;background:#fff;border:1px solid rgba(0,0,0,0.12);border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,0.1);padding:4px;display:none;z-index:200;}' + ':root.dark .ck-dd{background:#1f2937;border-color:rgba(255,255,255,0.1);box-shadow:0 4px 16px rgba(0,0,0,0.35);}' + '.ck-item{display:block;width:100%;padding:7px 12px;border:none;background:none;border-radius:6px;cursor:pointer;font-size:13px;font-family:inherit;text-align:left;color:#374151;}' + ':root.dark .ck-item{color:#d1d5db;}' + '.ck-item:hover{background:rgba(0,0,0,0.05);}' + ':root.dark .ck-item:hover{background:rgba(255,255,255,0.07);}';
        document.head.appendChild(s);
      }
      var wrap = document.createElement('div');
      wrap.id = 'ck-tools';
      var row = document.createElement('div');
      row.className = 'ck-row';
      var mainBtn = document.createElement('button');
      mainBtn.className = 'ck-btn';
      mainBtn.textContent = 'Copy page';
      var divider = document.createElement('span');
      divider.className = 'ck-divider';
      var chevron = document.createElement('button');
      chevron.className = 'ck-chevron';
      chevron.textContent = '▾';
      var dd = document.createElement('div');
      dd.className = 'ck-dd';
      function closeDD() {
        dd.style.display = 'none';
      }
      function openDD() {
        dd.style.display = 'block';
      }
      chevron.onclick = function (e) {
        e.stopPropagation();
        if (dd.style.display === 'block') {
          closeDD();
        } else {
          openDD();
        }
      };
      document.addEventListener('click', function (e) {
        if (!e.target.closest('#ck-tools')) {
          closeDD();
        }
      });
      document.addEventListener('keydown', function (e) {
        if (e.key === 'Escape') {
          closeDD();
        }
      });
      function makeItem(label, fn) {
        var b = document.createElement('button');
        b.className = 'ck-item';
        b.textContent = label;
        b.onclick = function () {
          fn();
          closeDD();
        };
        return b;
      }
      function getMarkdown() {
        var walk = function (node) {
          if (!node) return '';
          if (node.nodeType === 3) return node.textContent || '';
          if (node.nodeType !== 1) return '';
          var tag = node.tagName.toLowerCase();
          var skip = ['script', 'style', 'svg', 'noscript', 'button', 'iframe'];
          if (skip.indexOf(tag) !== -1) return '';
          if (node.id === 'ck-tools') return '';
          var ch = Array.from(node.childNodes).map(walk).join('');
          if (tag === 'h1') return '\n# ' + ch.trim() + '\n\n';
          if (tag === 'h2') return '\n## ' + ch.trim() + '\n\n';
          if (tag === 'h3') return '\n### ' + ch.trim() + '\n\n';
          if (tag === 'p') return '\n' + ch.trim() + '\n\n';
          if (tag === 'pre') return '\n```\n' + node.textContent.trim() + '\n```\n\n';
          if (tag === 'li') return '- ' + ch.trim() + '\n';
          if (tag === 'code') return '`' + ch.trim() + '`';
          return ch;
        };
        var content = document.querySelector('.mdx-content') || document.getElementById('content-area') || document.body;
        return walk(content).replace(/\n\n\n+/g, '\n\n').trim();
      }
      function copyMd() {
        var md = getMarkdown();
        navigator.clipboard.writeText(md).then(function () {
          mainBtn.textContent = 'Copied!';
          setTimeout(function () {
            mainBtn.textContent = 'Copy page';
          }, 2000);
        });
      }
      function viewMd() {
        var md = getMarkdown();
        var safe = md.split('&').join('&amp;').split('<').join('&lt;').split('>').join('&gt;');
        var html = '<!DOCTYPE html><html><head><meta charset="utf-8"><style>body{font-family:monospace;max-width:860px;margin:40px auto;padding:0 24px;line-height:1.7;white-space:pre-wrap;word-wrap:break-word}</style></head><body>' + safe + '</body></html>';
        window.open(URL.createObjectURL(new Blob([html], {
          type: 'text/html'
        })), '_blank');
      }
      function openClaude() {
        var prompt = 'Can you read this Cekura docs page ' + window.location.href + ' so I can ask you questions?';
        window.open('https://claude.ai/new?q=' + encodeURIComponent(prompt), '_blank');
      }
      mainBtn.onclick = copyMd;
      dd.appendChild(makeItem('Copy page', copyMd));
      dd.appendChild(makeItem('View as Markdown', viewMd));
      dd.appendChild(makeItem('Open in Claude', openClaude));
      row.appendChild(mainBtn);
      row.appendChild(divider);
      row.appendChild(chevron);
      wrap.appendChild(row);
      wrap.appendChild(dd);
      anchor.style.position = 'relative';
      anchor.insertBefore(wrap, anchor.firstChild);
    }, 50);
  }
  return null;
};

<CopyPageButton />

Automate your agent testing with the [Cekura GitLab CI/CD component](https://gitlab.com/cekura/cicd). This guide shows you how to run Cekura scenarios on every code change, merge request, or scheduled pipeline.

The component starts a Cekura test, waits for it to finish, and reports the result through GitLab job logs, the **Tests** tab, job annotations, and downloadable artifacts.

## Before You Get Started

You need:

* A Cekura API key
* An agent and scenarios configured in the [Cekura dashboard](https://dashboard.cekura.ai)
* A project ID only when selecting scenarios by evaluator folder

The agent ID is optional. If you omit it, Cekura infers the agent from the selected scenarios.

Add the configuration to the pipeline file that GitLab actually loads. This is usually `.gitlab-ci.yml`, but it can be a [global pipeline](#global-pipelines-and-custom-stages). For GitLab Self-Managed, first follow [GitLab Self-Managed](#gitlab-self-managed).

## Choose Which Scenarios to Run

Choose a selector: scenario IDs, tags, or an evaluator folder. IDs and tags can be combined; a folder cannot be combined with either.

<Tabs>
  <Tab title="Scenario IDs">
    Run an exact set of scenarios using comma-separated IDs:

    ```yaml theme={null}
    include:
      - component: gitlab.com/cekura/cicd/run@1.0.0
        inputs:
          api_key: $CEKURA_API_KEY
          scenario_ids: "101,202"
    ```
  </Tab>

  <Tab title="Tags">
    Run scenarios selected by one or more supplied tags:

    ```yaml theme={null}
    include:
      - component: gitlab.com/cekura/cicd/run@1.0.0
        inputs:
          api_key: $CEKURA_API_KEY
          tags: "smoke,release"
    ```
  </Tab>

  <Tab title="Evaluator Folder">
    Run every scenario in a folder. `project_id` is required even when you provide `agent_id`:

    ```yaml theme={null}
    include:
      - component: gitlab.com/cekura/cicd/run@1.0.0
        inputs:
          api_key: $CEKURA_API_KEY
          folder_path: "Release checks"
          project_id: "1875"
    ```
  </Tab>
</Tabs>

These examples use GitLab.com. On GitLab Self-Managed, replace the `component` include with the mirrored or remote include described below; the selector inputs stay the same.

## Run the Pipeline

<Steps>
  <Step title="Add Your API Key">
    In your GitLab project, go to **Settings → CI/CD → Variables**, expand **Variables**, and select **Add variable**.

    Set the key to `CEKURA_API_KEY`, paste your Cekura API key as the value, and enable **Masked**.

    Enable **Protected** only when the Cekura job runs exclusively on protected branches or tags. Protected variables are not available to pipelines on unprotected refs.

    <Warning>
      Never put your API key directly in `.gitlab-ci.yml`.
    </Warning>
  </Step>

  <Step title="Add the Pipeline Configuration">
    Copy one configuration from [Choose Which Scenarios to Run](#choose-which-scenarios-to-run) into the active pipeline file.
  </Step>

  <Step title="Start a Test Run">
    Commit and push the pipeline configuration. By default, the generated `cekura-run` job runs whenever the pipeline is triggered.

    You can also go to **Build → Pipelines**, select **New pipeline**, choose a branch, and select **New pipeline** again to test it manually.
  </Step>

  <Step title="Review the Results">
    Open the `cekura-run` job to see the result summary, failed-run details, and a shareable link to the full result in Cekura.

    GitLab also displays each scenario run in the pipeline's **Tests** tab. The job publishes Markdown and JSON summaries as artifacts and adds a direct Cekura result link to the job page.
  </Step>
</Steps>

## GitLab Self-Managed

GitLab components can only be referenced from the same GitLab instance as the consuming project. A self-managed project therefore cannot use this GitLab.com reference directly:

```yaml theme={null}
component: gitlab.com/cekura/cicd/run@1.0.0
```

The configurations below require GitLab 17.0 or later because the Cekura template uses CI/CD inputs. Choose one of the following approaches.

### Production: Mirror the Component

GitLab recommends mirroring GitLab.com components into your self-managed instance:

1. Allow outbound access from the self-managed instance to `gitlab.com`.
2. Create a project on the self-managed instance and configure it as a pull mirror of [`gitlab.com/cekura/cicd`](https://gitlab.com/cekura/cicd). If pull mirroring is not available with your GitLab subscription, import the repository and repeat the import when upgrading the component.
3. Set the mirrored project as a CI/CD Catalog resource.
4. Publish a release for the mirrored `1.0.0` tag. Git repository mirroring copies the tag, but not the GitLab release or Catalog metadata.
5. Reference the mirrored component using the self-managed GitLab hostname and mirrored project path:

```yaml theme={null}
include:
  - component: gitlab.example.com/components/cekura-cicd/run@1.0.0
    inputs:
      api_key: $CEKURA_API_KEY
      scenario_ids: "101,202"
```

Replace `gitlab.example.com/components/cekura-cicd` with the actual hostname and project path of the mirror. See GitLab's guide to [using a GitLab.com component on GitLab Self-Managed](https://docs.gitlab.com/ci/components/#use-a-gitlabcom-component-on-gitlab-self-managed).

### Proof of Concept: Use a Remote Include

On GitLab 17.0 or later, you can include the public component template directly for a proof of concept:

```yaml theme={null}
include:
  - remote: "https://gitlab.com/cekura/cicd/-/raw/1.0.0/templates/run.yml"
    inputs:
      api_key: $CEKURA_API_KEY
      scenario_ids: "101,202"
```

The self-managed GitLab server must be able to make outbound HTTPS requests to `gitlab.com`. Remote includes are fetched without authentication, so this approach only works while the source file remains public.

<Warning>
  For production, mirror the component into your self-managed GitLab instance. This keeps the dependency under your instance's access controls and release-management process.
</Warning>

## Global Pipelines and Custom Stages

Global pipelines work with both GitLab.com and GitLab Self-Managed. Add the Cekura include to the active global configuration; changes to a repository-level `.gitlab-ci.yml` have no effect when GitLab is configured to load a different file.

The examples below use GitLab.com. On GitLab Self-Managed, use the mirrored component or remote include from the previous section.

The component creates a job named `cekura-run` and uses the `test` stage by default. The component cannot add a new stage to a global pipeline. Either pass the name of an existing stage:

```yaml theme={null}
include:
  - component: gitlab.com/cekura/cicd/run@1.0.0
    inputs:
      api_key: $CEKURA_API_KEY
      scenario_ids: "101,202"
      stage: validation
```

Or have the global pipeline owner add a dedicated stage and select it:

```yaml theme={null}
stages:
  - build
  - test
  - cekura
  - deploy

include:
  - component: gitlab.com/cekura/cicd/run@1.0.0
    inputs:
      api_key: $CEKURA_API_KEY
      scenario_ids: "101,202"
      stage: cekura
```

The value of `stage` must exactly match a stage defined by the active pipeline configuration.

## Configure Pipeline Triggers

The component creates a job named `cekura-run`. After the include, define only the job's GitLab [`rules`](https://docs.gitlab.com/ci/yaml/#rules); GitLab merges them with the component job.

<AccordionGroup>
  <Accordion title="Run on Merge Requests and the Default Branch">
    ```yaml theme={null}
    cekura-run:
      rules:
        - if: $CI_PIPELINE_SOURCE == "merge_request_event"
        - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
    ```
  </Accordion>

  <Accordion title="Run Only When Started Manually">
    ```yaml theme={null}
    cekura-run:
      rules:
        - when: manual
    ```
  </Accordion>

  <Accordion title="Run in Scheduled Pipelines">
    ```yaml theme={null}
    cekura-run:
      rules:
        - if: $CI_PIPELINE_SOURCE == "schedule"
    ```

    Create the schedule under **Build → Pipeline schedules** in your GitLab project.
  </Accordion>
</AccordionGroup>

## Component Inputs

Only `api_key` and a scenario selector are needed for a typical run. Empty optional inputs are not sent to Cekura, so the API applies its defaults.

| Input               | Description                                                                              | Required    | Default                 |
| ------------------- | ---------------------------------------------------------------------------------------- | ----------- | ----------------------- |
| `api_key`           | Cekura API key. Pass a masked CI/CD variable.                                            | Yes         | -                       |
| `scenario_ids`      | Comma-separated scenario IDs.                                                            | No\*        | -                       |
| `tags`              | Comma-separated scenario tags.                                                           | No\*        | -                       |
| `folder_path`       | Evaluator folder path. Requires `project_id`, even when `agent_id` is provided.          | No\*        | -                       |
| `project_id`        | Cekura project ID used with `folder_path`.                                               | With folder | -                       |
| `agent_id`          | Agent ID override. Otherwise inferred from the selected scenarios.                       | No          | -                       |
| `phone_number`      | Outbound phone number used for testing.                                                  | No          | -                       |
| `agent_number`      | Number on which the agent under test receives calls.                                     | No          | -                       |
| `name`              | Name for the Cekura test result.                                                         | No          | -                       |
| `api_url`           | Cekura API base URL.                                                                     | No          | `https://api.cekura.ai` |
| `frequency`         | Number of times to run each scenario.                                                    | No          | `1`                     |
| `personality_ids`   | Comma-separated personality ID overrides.                                                | No          | -                       |
| `test_profile_ids`  | Comma-separated test profile ID overrides.                                               | No          | -                       |
| `mode`              | `same_number` or `different_numbers`.                                                    | No          | API default             |
| `concurrency_limit` | Maximum number of parallel calls.                                                        | No          | API default             |
| `mock_tool_names`   | Comma-separated tools to mock. Use `[]` to mock none; omit to mock all configured tools. | No          | API default             |
| `timeout`           | Maximum time to wait for completion, in seconds.                                         | No          | `3600`                  |
| `stage`             | Pipeline stage for the generated job.                                                    | No          | `test`                  |

\*Provide at least one of `scenario_ids`, `tags`, or `folder_path`. A folder cannot be combined with IDs or tags.

## Use the Result in Another Job

The component publishes `RESULT_ID` and `RESULT_URL` in a GitLab dotenv report. A downstream job can use them by downloading the `cekura-run` artifacts:

```yaml theme={null}
publish-cekura-result:
  stage: deploy
  needs:
    - job: cekura-run
      artifacts: true
  script:
    - echo "Cekura result: ${RESULT_URL} (${RESULT_ID})"
```

## Troubleshooting

<AccordionGroup>
  <Accordion title="The cekura-run Job Does Not Appear">
    Open **Build → Pipeline editor → Full configuration** and search for `cekura-run`.

    If the job is absent:

    * Check **Settings → CI/CD → General pipelines → CI/CD configuration file** to confirm which pipeline configuration GitLab loads.
    * On GitLab Self-Managed, do not reference the component on `gitlab.com` with `include:component`. Mirror it locally or use the remote-include proof of concept above.
    * If a global pipeline is active, add the include to that configuration or use an extension mechanism provided by its owner.
  </Accordion>

  <Accordion title="The Pipeline Uses Custom Stages">
    The component uses `test` by default. If the active pipeline defines its own `stages:` list without `test`, pass an existing stage through the `stage` input or ask the global pipeline owner to add a dedicated stage.

    A component can select a stage, but it cannot add that stage to the consuming pipeline.
  </Accordion>

  <Accordion title="The API Key Is Not Available">
    Check that `CEKURA_API_KEY` is defined under **Settings → CI/CD → Variables** and is spelled exactly the same in `.gitlab-ci.yml`.

    If the variable is protected, the pipeline must run on a protected branch or tag. Otherwise, clear the **Protected** option or change where the job runs.
  </Accordion>

  <Accordion title="The Pipeline Configuration Is Invalid">
    On GitLab.com, confirm that the component reference is exactly `gitlab.com/cekura/cicd/run@1.0.0` and that `inputs` is nested under the component entry.

    On GitLab Self-Managed, confirm that the component reference uses the self-managed hostname and mirrored project path. For a remote include, confirm that the instance runs GitLab 17.0 or later and can access `gitlab.com`.

    Use **Build → Pipeline editor → Validate** in GitLab to validate the complete configuration.
  </Accordion>

  <Accordion title="No Scenarios Were Selected">
    Provide `scenario_ids`, `tags`, or `folder_path`. When using a folder, also provide `project_id`, and do not combine the folder with IDs or tags.
  </Accordion>

  <Accordion title="The Cekura Tests Fail">
    Open the `cekura-run` log for failure reasons and individual failed-run details. Follow the **View full results in Cekura** link for transcripts, evaluations, and the AI-generated analysis.
  </Accordion>
</AccordionGroup>
