Build a CI/CD Pipeline with Azure DevOps

Automate build, test, and package workflows with Azure DevOps Pipelines.

Introduction

30 Second Summary

Every time a developer pushes code, someone needs to verify it works before it reaches users. Without automation, that means manual testing and packaging for every single change.

In this project, you will build an automated CI/CD pipeline using Azure DevOps that builds, tests, and packages a Node.js application every time you push code to your repository.

What You'll Build

A YAML pipeline in Azure DevOps that automatically installs dependencies, runs Jest tests, and publishes a deployable artifact when code is pushed to your repository.

By the end of this project, you'll have:

  • An Azure DevOps project with a Git repository containing a Node.js calculator application.
  • A self-hosted pipeline agent running on your Windows machine, executing pipeline jobs locally so you can see exactly how pipelines work behind the scenes.
  • A multi-step YAML pipeline that installs dependencies, runs tests, and publishes build artifacts automatically on every code push.
  • Secret Mission: Convert your pipeline to a multi-stage pipeline with separate Build and Deploy stages, plus environment approval gates.

Are there any prerequisites?

No prior experience with CI/CD or Azure DevOps is needed. This project includes full setup instructions for every tool, including Git, Node.js, and the pipeline agent. You will need a Windows machine to run the self-hosted agent.

Set Up Azure DevOps and Your Self-Hosted Agent

Every CI/CD pipeline needs something to execute its jobs. That something is called an agent. An agent is a machine that listens for pipeline jobs, runs them, and reports the results back.

In this step, you will create an Azure DevOps account, set up a project, and configure a self-hosted agent on your Windows machine. By the end, your machine will be ready to run pipeline jobs automatically.

In this step, get ready to:
  • Create an Azure DevOps organization and project.
  • Set up a self-hosted agent pool and download the agent.
  • Configure and start the agent on your machine.
Create an Azure DevOps organization and project

Azure DevOps is a platform that provides developer tools for planning, building, and shipping software. You will use its Pipelines feature to automate builds and tests.

  • Open your web browser and navigate to dev.azure.com.
  • Click Start free and sign in with a Microsoft account (or create one if you do not have one).
  • When prompted, create a new organization. Choose any name you like for your organization.
  • After your organization is created, you will land on the new project screen. Enter calculator-pipeline as the project name.
  • Set the visibility to Private.
  • Under Advanced, confirm the version control is set to Git.
  • Click Create project.

You should now see your project dashboard with sections like Boards, Repos, and Pipelines in the left sidebar.

What is an organization in Azure DevOps?

An organization is the top-level container that holds all your projects, repos, and pipelines. Think of it as your team's workspace. A project sits inside an organization and groups related code, pipelines, and boards together.

Don't see the Create project screen?

If you were redirected to an existing organization or project, click the Azure DevOps logo in the top-left corner to return to your organization home. Then click New project in the top-right corner.

help me find the Create project screen in Azure DevOps

Create a self-hosted agent pool and download the agent

A pipeline agent pool is a group of machines available to run your pipeline jobs. You will create a pool called "MyLocalAgent" and register your own Windows machine in it.

Why use a self-hosted agent?

Microsoft-hosted agents require a free parallelism grant for new organizations, which takes 2-3 business days to process. Self-hosted agents work immediately with unlimited free minutes.

Using a self-hosted agent also teaches a deeper concept. You can see that the agent is literally your own machine executing the pipeline commands, which demystifies what happens behind the scenes in CI/CD.

  • In your Azure DevOps project, click the gear icon at the bottom of the left sidebar to open Organization settings.
  • In the left menu under Pipelines, click Agent pools.
  • Click Add pool in the top-right area.
  • Set the pool type to Self-hosted.
  • Enter MyLocalAgent as the pool name.
  • Click Create.

Now download the agent software to your machine.

  • Click into your new MyLocalAgent pool.
  • Click the Agents tab, then click New agent.
  • In the dialog that appears, select Windows and the x64 architecture.
  • Click Download to save the agent zip file.

Next, extract the agent to a folder on your machine. Open an elevated PowerShell window and create the agent directory.

  • Press the Windows key and type PowerShell.
  • Right-click Windows PowerShell and select Run as administrator.
  • Create the agent folder and extract the zip by running these commands:
mkdir C:\agents
cd C:\agents
Add-Type -AssemblyName System.IO.Compression.FileSystem
[System.IO.Compression.ZipFile]::ExtractToDirectory("$HOME\Downloads\vsts-agent-win-x64-4.248.0.zip", "$PWD")

What does this command do?

  • mkdir C:\agents creates the folder where the agent will live.
  • cd C:\agents moves into that folder.
  • The last two lines extract the downloaded zip file into the current directory. If your zip file has a different name, adjust the filename to match what you downloaded.

Seeing a file not found error?

The zip filename depends on the agent version you downloaded. Check your Downloads folder for the actual filename (it starts with vsts-agent-win-x64-) and update the command to match.

help me find and extract the Azure DevOps agent zip file

  • Confirm the extraction worked by running:
ls

You should see files like config.cmd, run.cmd, and folders like bin and externals in the output.

Configure and start the agent

Before you can run the agent, you need a Personal Access Token (PAT) to authenticate it with Azure DevOps. The PAT is only used during initial registration.

  • Go back to your browser with Azure DevOps open.
  • Click the User settings icon (the person icon with a gear) in the top-right corner.
  • Select Personal access tokens.
  • Click + New Token.
  • Give it a name like agent-setup.
  • Set the expiration to 30 days (or any duration you prefer).
  • Click Show all scopes at the bottom of the form.
  • Find Agent Pools in the scopes list and check Read & manage. Make sure all other scopes are unchecked.
  • Click Create.
  • Copy the token immediately and save it somewhere safe. It will not be shown again.

Copy your token now

Once you close the token creation dialog, you cannot view the token again. If you lose it, you will need to create a new one. Paste it into a text file or notes app temporarily.

Now configure the agent using the token you just created.

  • Return to your elevated PowerShell window (which should still be in C:\agents).
  • Start the agent configuration by running:
.\config.cmd

What happens during configuration?

The config command launches an interactive setup wizard that asks a series of questions. You will provide your server URL, authentication type, token, and pool name. Each prompt is covered in the bullets below.

  • When prompted for the server URL, enter your organization URL:
https://dev.azure.com/[[ORG_NAME="your organization name"]]
  • When prompted for the authentication type, enter PAT and press Enter.
  • When prompted for the token, paste the Personal Access Token you copied earlier and press Enter.
  • When prompted for the agent pool, enter MyLocalAgent and press Enter.
  • When prompted for the agent name, press Enter to accept the default (your machine name).
  • When prompted for the work folder, press Enter to accept the default (_work).

You should see a message confirming the agent was configured successfully.

  • Start the agent by running:
.\run.cmd

You should see output indicating the agent is listening for jobs. It will display something like "Listening for Jobs" in the console.

  • Verify the agent is online by going back to your browser.
  • Navigate to Organization settings > Agent pools > MyLocalAgent > Agents tab.
  • You should see your agent listed with a green Online status.

Agent showing as Offline?

Make sure the PowerShell window running .\run.cmd is still open. If you close it or press Ctrl+C, the agent stops. Keep this window open for the rest of the project.

If the agent never connected, double-check that you entered the correct organization URL and that your PAT has the Agent Pools (Read & manage) scope.

help me troubleshoot my Azure DevOps self-hosted agent not connecting

Keep this PowerShell window open

The agent runs interactively in this PowerShell window. Do not close it while working through this project. If you accidentally close it, navigate back to C:\agents in a new elevated PowerShell and run .\run.cmd again.

Your self-hosted agent is alive and listening for work. Next up, you will create the Node.js application that your pipeline will build and test.

Create the Node.js Project in Azure Repos

Your pipeline needs application code to build and test. Without source code in the repository, the pipeline has nothing to do.

In this step, you will clone the empty Azure Repos repository to your machine, write a simple Node.js calculator module with Jest tests, and push everything to the main branch.

In this step, get ready to:
  • Clone the Azure Repos repository to your local machine.
  • Write a calculator module and Jest tests.
  • Push the code to the remote repository.
Clone the repository to your local machine

Your Azure DevOps project already has an empty Git repository. You need to clone it so you have a local folder connected to the remote.

  • In your Azure DevOps project, click Repos in the left sidebar.
  • Click Files to open the Files view.
  • Click the Clone button in the upper right area to open the Clone Repository popup.
  • Copy the HTTPS clone URL. It follows the format https://dev.azure.com/{yourorganization}/calculator-pipeline/_git/calculator-pipeline.

What is this clone URL?

The clone URL uniquely identifies your remote repository. When you run git clone with this URL, Git creates a local copy of the repository and links it to the remote so you can push and pull changes.

  • Open a new PowerShell window. Your existing PowerShell window is running the agent, so you need a separate one for Git commands.
  • Press the Windows key to open your search bar.
  • Type PowerShell and press Enter to open it.
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Clone the repository by running this command:
git clone [[REPO_CLONE_URL="https://dev.azure.com/your-org/calculator-pipeline/_git/calculator-pipeline"]]

What about authentication?

Git may prompt you to sign in. If a browser window opens, sign in with the same Microsoft account you used for Azure DevOps. Windows Git Credential Manager handles this automatically on subsequent clones.

  • Move into the cloned repository folder by running:
cd calculator-pipeline
  • Create the src directory for your application code by running:
New-Item -ItemType Directory -Path src
  • Confirm your project structure by running:
dir

You should see the src folder listed in the output.

Getting a 'git is not recognized' error?

Git needs to be installed on your machine. Download and install Git for Windows from git-scm.com. After installing, close and reopen PowerShell so it picks up the new path.

Help me troubleshoot this

Write the calculator module and tests

You will create three files: a package.json for project configuration, a calculator module, and a test file. The pipeline will use these to run an automated build and test cycle.

Creating files in PowerShell

For each file below, you will run a PowerShell command that creates the file and writes all the code into it in one step. Just copy and paste each command into your PowerShell window.

  • Create package.json by running this command:
Set-Content -Path "package.json" -Value @'
{
  "name": "calculator-pipeline",
  "version": "1.0.0",
  "description": "A simple calculator app to demonstrate Azure DevOps CI/CD",
  "main": "src/calculator.js",
  "scripts": {
    "test": "jest"
  },
  "devDependencies": {
    "jest": "^30.4.2"
  }
}
'@

What does package.json do?

This file defines your Node.js project. The scripts.test field tells npm test to run Jest, your testing framework.

The devDependencies section lists Jest version ^30.4.2 as a development dependency that the pipeline will install.

  • Create src/calculator.js by running this command:
Set-Content -Path "src/calculator.js" -Value @'
function add(a, b) {
  return a + b;
}

function subtract(a, b) {
  return a - b;
}

function multiply(a, b) {
  return a * b;
}

module.exports = { add, subtract, multiply };
'@

What does this module do?

This is a simple calculator module with three functions: add, subtract, and multiply.

The module.exports line makes these functions available to other files that import this module.

  • Create src/calculator.test.js by running this command:
Set-Content -Path "src/calculator.test.js" -Value @'
const { add, subtract, multiply } = require("./calculator");

describe("Calculator", () => {
  test("adds two numbers", () => {
    expect(add(2, 3)).toBe(5);
  });

  test("subtracts two numbers", () => {
    expect(subtract(5, 3)).toBe(2);
  });

  test("multiplies two numbers", () => {
    expect(multiply(4, 3)).toBe(12);
  });
});
'@

What do these tests do?

Each test() block calls one calculator function with known inputs and checks the output using expect(...).toBe(...).

If any function returns the wrong result, the test fails and your pipeline will catch it.

  • Verify all files were created by running:
Get-ChildItem -Recurse -File

You should see package.json, src/calculator.js, and src/calculator.test.js listed in the output.

Missing files or wrong directory?

Make sure your PowerShell prompt shows you are inside the calculator-pipeline folder before creating files. Run pwd to check your current location. The path should end with calculator-pipeline.

Help me fix my file locations

✔️ Awesome, I've got everything!

Great. Make sure all three files are saved before moving on to the next substep.

ⓧ I'd like to double check the full code

Here is the exact content each file should contain.

{
  "name": "calculator-pipeline",
  "version": "1.0.0",
  "description": "A simple calculator app to demonstrate Azure DevOps CI/CD",
  "main": "src/calculator.js",
  "scripts": {
    "test": "jest"
  },
  "devDependencies": {
    "jest": "^30.4.2"
  }
}
function add(a, b) {
  return a + b;
}

function subtract(a, b) {
  return a - b;
}

function multiply(a, b) {
  return a * b;
}

module.exports = { add, subtract, multiply };
const { add, subtract, multiply } = require("./calculator");

describe("Calculator", () => {
  test("adds two numbers", () => {
    expect(add(2, 3)).toBe(5);
  });

  test("subtracts two numbers", () => {
    expect(subtract(5, 3)).toBe(2);
  });

  test("multiplies two numbers", () => {
    expect(multiply(4, 3)).toBe(12);
  });
});
Push the code to Azure Repos

Your files exist only on your local machine right now. You need to commit and push them so they appear in the Azure DevOps repository where your pipeline can access them.

  • Stage all files for commit by running:
git add .
  • Create a commit with a descriptive message by running:
git commit -m "Add calculator module with Jest tests"
  • Push the commit to the main branch on Azure Repos by running:
git push -u origin main

What does -u origin main mean?

The -u flag sets origin/main as the default upstream branch. After this first push, future pushes only need git push without specifying the branch.

  • Return to your browser and navigate to Repos in your Azure DevOps project.
  • Click Files in the left sidebar.
  • Refresh the page. You should see package.json and the src folder listed in the repository file browser.

Files not appearing in Azure DevOps?

Check your PowerShell output from the git push command. If it shows an error about authentication, Git Credential Manager may need you to sign in again. Re-run the push command and complete the sign-in prompt.

If you see a message about the branch not existing, make sure you used git push -u origin main (not master).

Help me fix my git push

Your repository now has application code ready to be built and tested. Next up, you will create the CI pipeline that automatically runs these tests every time you push code.

Create the CI Pipeline

Your calculator code is now living in Azure Repos. The next goal is to make something happen every time you push a change to that code.

In this step, you'll create a YAML pipeline that tells Azure Pipelines exactly what to do when new code lands on the main branch. The pipeline will install Node.js, grab your project's dependencies, and run your Jest tests automatically.

In this step, get ready to:
  • Create the pipeline YAML configuration file.
  • Define build steps that install dependencies and run tests.
  • Verify the pipeline runs successfully on your self-hosted agent.
Create the pipeline YAML file

Azure Pipelines reads a file called azure-pipelines.yml in the root of your repository. This file defines when the pipeline should run and where it should execute.

  • In your code editor, create a new file called azure-pipelines.yml in the root of your calculator-pipeline repository (the same level as package.json).
  • Add the following pipeline configuration to define when and where the pipeline runs:
trigger:
  - main

pool:
  name: "MyLocalAgent"

What does this code do?

trigger: - main tells Azure Pipelines to automatically run this pipeline every time code is pushed to the main branch.

pool: name: "MyLocalAgent" directs the pipeline to execute on your self-hosted agent instead of a Microsoft-hosted machine. This targets the agent pool you set up in Step 1.

Why YAML pipelines instead of Classic?

Azure DevOps offers two pipeline formats: YAML and Classic (a GUI editor). YAML pipelines live in your repository alongside your code, so they're version-controlled and traceable.

Microsoft recommends YAML pipelines for all new projects. The skills transfer directly to other CI/CD tools like GitHub Actions or GitLab CI.

Configure the build steps

The steps: section defines the actual work your agent performs. Each step is either a built-in task or a script command.

  • In azure-pipelines.yml, add the following steps section below the pool block:
steps:
  - task: NodeTool@0
    inputs:
      versionSpec: "22.x"
    displayName: "Install Node.js"

  - script: npm install
    displayName: "Install dependencies"

  - script: npm test
    displayName: "Run tests"

What does this code do?

NodeTool@0 is a built-in Azure Pipelines task that downloads and installs a specific version of Node.js on the agent. The 22.x version spec means it will install the latest Node.js 22 release.

The two script steps run terminal commands. npm install downloads the project's dependencies (Jest), and npm test runs your calculator test suite.

Each step's displayName controls what appears in the Azure DevOps pipeline run view, making it easy to see which step passed or failed.

  • Save azure-pipelines.yml.

YAML is whitespace-sensitive

YAML uses spaces for indentation, never tabs. Make sure your editor is set to insert spaces. Misaligned indentation will cause the pipeline to fail with a syntax error.

✔️ Awesome, I've got everything!

Great. Double check you've saved azure-pipelines.yml before moving on.

ⓧ I'd like to double check the full code

Here is the complete azure-pipelines.yml file:

trigger:
  - main

pool:
  name: "MyLocalAgent"

steps:
  - task: NodeTool@0
    inputs:
      versionSpec: "22.x"
    displayName: "Install Node.js"

  - script: npm install
    displayName: "Install dependencies"

  - script: npm test
    displayName: "Run tests"
  • Commit and push the new pipeline file by running these commands:
git add azure-pipelines.yml
git commit -m "Add CI pipeline configuration"
git push

Push rejected or authentication error?

Make sure you're on the main branch. Run git branch to check.

If you see a credentials prompt, use the same Git credentials or PAT you used when cloning the repository in Step 2.

Help me fix a git push error in Azure Repos.

Verify the pipeline runs successfully

Now that the YAML file is in your repository, you need to create the pipeline in Azure DevOps so it knows to use that file.

  • In your Azure DevOps project, click Pipelines in the left navigation menu.
  • Click Create Pipeline (or New pipeline if you already have pipelines).
  • On the "Where is your code?" screen, select Azure Repos Git.
  • Select the calculator-pipeline repository.
  • On the "Configure your pipeline" screen, select Existing Azure Pipelines YAML file.
  • Leave the branch as main and select /azure-pipelines.yml as the path. Click Continue.
  • Review the YAML preview. Click Run to save the pipeline and trigger the first run.

What happens when you click Run?

Azure DevOps registers the pipeline and immediately queues a job. Your self-hosted agent (the PowerShell window running run.cmd) picks up the job and executes each step defined in your YAML file.

Future pushes to main will trigger new runs automatically. You won't need to click Run again.

  • Watch the pipeline run page. You'll see each step execute in order: Install Node.js, Install dependencies, Run tests.
  • Verify all steps show green checkmarks, confirming your CI pipeline works.

What should I see?

The pipeline run page shows a list of steps with green checkmarks next to each one. In the Run tests step log, you should see Jest output showing 3 passing tests (add, subtract, multiply).

Pipeline stuck on "Queued" or not starting?

Your self-hosted agent must be running for the pipeline to execute. Switch to the PowerShell window at C:\agents and confirm run.cmd is still active. If it stopped, run .\run.cmd again.

Also check that the pool name in your YAML (MyLocalAgent) matches the agent pool you created in Step 1 exactly. Pool names are case-sensitive.

Help me fix my pipeline that won't start running.

Your CI pipeline is live. Every push to main now triggers an automatic build and test cycle. Next up, you'll add artifact publishing so the pipeline produces a deployable package.

Add Artifact Publishing

Your pipeline already builds and tests your code automatically on every push. Now it's time to complete the loop by producing a deployable package.

Right now, even though your tests pass, there's nothing tangible to deploy. A build artifact is a packaged version of your application that's ready to hand off to a deployment process.

In this step, get ready to:
  • Add archive and publish steps to the pipeline YAML.
  • Trigger the updated pipeline and verify it succeeds.
  • Download and inspect the published artifact.
Add archive and publish steps to the pipeline

You need two new tasks in your pipeline. The first zips your application code into a single archive. The second publishes that archive so it's accessible from the Azure DevOps portal.

  • Open azure-pipelines.yml in your code editor.
  • Find the last step in the file, which looks like this:
  - script: npm test
    displayName: "Run tests"
  • Add the following two tasks directly below the "Run tests" step:
  - task: ArchiveFiles@2
    inputs:
      rootFolderOrFile: "$(System.DefaultWorkingDirectory)"
      includeRootFolder: false
      archiveType: "zip"
      archiveFile: "$(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip"
      replaceExistingArchive: true
    displayName: "Archive application"

  - publish: $(Build.ArtifactStagingDirectory)
    artifact: drop
    displayName: "Publish artifact"

What does this code do?

These two tasks work together to package and store your application:

  • ArchiveFiles@2 zips everything in $(System.DefaultWorkingDirectory) (the folder where your repository is checked out) into a single .zip file.
  • The zip file is named using $(Build.BuildId), a unique numeric identifier for each pipeline run.
  • $(Build.ArtifactStagingDirectory) is a temporary folder designed for staging files before publishing.
  • The publish keyword uploads the contents of that staging folder as a pipeline artifact named "drop".
  • Save azure-pipelines.yml.

Seeing a YAML syntax error?

YAML is whitespace-sensitive. Make sure you use spaces (not tabs) for indentation, and that each new task lines up at the same level as the existing steps above it. The - task: line should start with exactly two spaces, matching the - script: lines above.

Help me fix my YAML indentation

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before moving on.

ⓧ I'd like to double check the full code

trigger:
  - main

pool:
  name: "MyLocalAgent"

steps:
  - task: NodeTool@0
    inputs:
      versionSpec: "22.x"
    displayName: "Install Node.js"

  - script: npm install
    displayName: "Install dependencies"

  - script: npm test
    displayName: "Run tests"

  - task: ArchiveFiles@2
    inputs:
      rootFolderOrFile: "$(System.DefaultWorkingDirectory)"
      includeRootFolder: false
      archiveType: "zip"
      archiveFile: "$(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip"
      replaceExistingArchive: true
    displayName: "Archive application"

  - publish: $(Build.ArtifactStagingDirectory)
    artifact: drop
    displayName: "Publish artifact"
Trigger the updated pipeline

Pushing this change to main will automatically trigger your pipeline, since you configured the trigger to fire on pushes to that branch.

  • Commit and push the updated YAML file by running these commands:
git add azure-pipelines.yml
git commit -m "Add artifact publishing to pipeline"
git push
  • Switch back to your Azure DevOps project in the browser.
  • Click Pipelines in the left sidebar.
  • You should see a new pipeline run triggered by your push. Click on it to watch the progress.

Watch the job execute on your self-hosted agent. You should see the two new steps ("Archive application" and "Publish artifact") run after the tests pass.

Pipeline stuck in queue?

Your self-hosted agent must be running for the pipeline to execute. Switch to the PowerShell window where you started the agent with .\run.cmd and confirm it shows "Listening for Jobs". If it's stopped, run .\run.cmd again from C:\agents.

Help me troubleshoot my queued pipeline

Download and inspect the artifact

Once the pipeline run completes with all green checkmarks, you can find the published artifact in the run summary.

  • On the pipeline run page, click the Summary tab.
  • In the Related section, you should see your published artifact listed. Click on it.
  • Expand the drop folder to see the zip file inside.
  • Click the download icon to download the artifact to your machine.
  • Extract the zip file and confirm it contains your application code (package.json, the src/ folder, and azure-pipelines.yml).

Why is this important?

This artifact is the deliverable output of your CI/CD pipeline. In a real-world workflow, a deployment stage would pick up this artifact and push it to a server, a container registry, or a cloud service.

You've just completed the full loop: code push triggers build, build triggers tests, tests pass, and a deployable package is produced.

Don't see an artifact in the summary?

Make sure all pipeline steps completed with green checkmarks. If the "Archive application" or "Publish artifact" steps show a red X, click on them to read the error log. The most common cause is incorrect indentation in the YAML file.

Help me find my missing artifact

Your pipeline now builds, tests, and packages your application into a deployable artifact. Next, you'll put the full loop to the test by adding new functionality and watching the pipeline handle it end to end.

Test the Full CI/CD Loop

Your pipeline already builds, tests, and packages your app automatically. But the real power of CI/CD is how it reacts to change.

In this step, you'll add a new feature to your calculator and push it to the repository. Without touching any pipeline settings, Azure DevOps will detect the change, run your updated tests, and publish a fresh artifact. This is the full CI/CD loop in action.

In this step, get ready to:
  • Add a divide function and its tests to the calculator app.
  • Push the changes and watch the pipeline trigger automatically.
  • Verify all tests pass and a fresh artifact is published.
Add a divide function to the calculator module

A real CI/CD pipeline proves its value when you ship new code. You'll add a divide function that includes error handling for division by zero.

  • Open src/calculator.js in your code editor.
  • Find the closing brace of the multiply function. Add the following divide function below it:
function divide(a, b) {
  if (b === 0) {
    throw new Error("Cannot divide by zero");
  }
  return a / b;
}

What does this code do?

The divide function checks whether the divisor is zero before performing the calculation. If it is, the function throws an error instead of returning Infinity. This is a common defensive coding pattern that your tests will verify.

  • Find the module.exports line at the bottom of the file:
module.exports = { add, subtract, multiply };
  • Replace it with this updated line that includes divide:
module.exports = { add, subtract, multiply, divide };
  • Save src/calculator.js.

✔️ Awesome, I've got everything!

Great. Make sure you've saved the file before moving on.

ⓧ I'd like to double check the full code

Here's the complete src/calculator.js file with the new divide function added:

function add(a, b) {
  return a + b;
}

function subtract(a, b) {
  return a - b;
}

function multiply(a, b) {
  return a * b;
}

function divide(a, b) {
  if (b === 0) {
    throw new Error("Cannot divide by zero");
  }
  return a / b;
}

module.exports = { add, subtract, multiply, divide };

Now you need tests that exercise the new function.

  • Open src/calculator.test.js.
  • Find the require line at the top of the file:
const { add, subtract, multiply } = require("./calculator");
  • Replace it with this updated import that includes divide:
const { add, subtract, multiply, divide } = require("./calculator");
  • Find the closing brackets of the last test (multiplies two numbers). Add the following two new tests below it, inside the describe block:
  test("divides two numbers", () => {
    expect(divide(10, 2)).toBe(5);
  });

  test("throws error when dividing by zero", () => {
    expect(() => divide(10, 0)).toThrow("Cannot divide by zero");
  });

What do these tests verify?

The first test confirms that divide(10, 2) returns 5. The second test wraps the call in an arrow function and uses .toThrow() to confirm that dividing by zero produces the expected error message.

  • Save src/calculator.test.js.

✔️ Awesome, I've got everything!

Great. Make sure you've saved the file before moving on to the push.

ⓧ I'd like to double check the full code

Here's the complete src/calculator.test.js file with the new divide tests added:

const { add, subtract, multiply, divide } = require("./calculator");

describe("Calculator", () => {
  test("adds two numbers", () => {
    expect(add(2, 3)).toBe(5);
  });

  test("subtracts two numbers", () => {
    expect(subtract(5, 3)).toBe(2);
  });

  test("multiplies two numbers", () => {
    expect(multiply(4, 3)).toBe(12);
  });

  test("divides two numbers", () => {
    expect(divide(10, 2)).toBe(5);
  });

  test("throws error when dividing by zero", () => {
    expect(() => divide(10, 0)).toThrow("Cannot divide by zero");
  });
});

Seeing a syntax error?

Make sure the new tests sit inside the describe("Calculator", () => { ... }); block, not after it. Each test must end with }); on its own line.

Help me debug my calculator test file

Push the changes and watch the pipeline react

Your pipeline is configured to trigger on every push to main. Committing and pushing these changes is all it takes to kick off a fresh build.

  • Stage, commit, and push your changes by running these commands:
git add .
git commit -m "Add divide function with tests"
git push

What does this trigger?

The trigger: - main line in your azure-pipelines.yml tells Azure DevOps to start a new pipeline run every time the main branch receives a push. You don't need to manually start anything.

Push rejected or authentication error?

If Git asks for credentials, use your Azure DevOps username and a Personal Access Token (the same one you created for the agent, or generate a new one with Code Read & Write scope).

Help me fix my Git push to Azure Repos

Verify the pipeline re-runs automatically

The moment your push lands, Azure DevOps queues a new pipeline run. Your self-hosted agent picks it up and executes every step, including your new tests.

  • In your Azure DevOps project, click Pipelines in the left sidebar.
  • Click into the running (or recently completed) pipeline run.
  • Watch the job execute on your self-hosted agent. Each step should show a green checkmark when it passes.
  • Click on the Run tests step to expand its log output. You should see all 5 tests passing, including divides two numbers and throws error when dividing by zero.

Why is this powerful?

You didn't click a "run" button or change any pipeline configuration. The push alone triggered a full build, test, and packaging cycle. This is Continuous Integration at work. Every code change is automatically validated.

  • After the run completes successfully, click the run summary to find the published artifact drop.
  • Download the artifact and verify it contains your updated application code, including the new divide function.
  • Navigate back to Pipelines to see your pipeline run history. You should see multiple successful runs listed.

Pipeline run not appearing?

Make sure your self-hosted agent is still running. Switch to the PowerShell window where you started .\run.cmd and confirm it shows a "Listening for jobs" message. If the agent stopped, run .\run.cmd again from C:\agents.

Also confirm your push went through by checking Repos in Azure DevOps. You should see your "Add divide function with tests" commit on the main branch.

Help me figure out why my pipeline didn't trigger

Secret mission

Multi-Stage Pipeline with Approvals

Your pipeline currently runs everything in a single stage. Real teams separate build from deploy, and require manual approval before code reaches production. Can you convert your pipeline into a multi-stage architecture with an environment approval gate?

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely on the Azure DevOps free tier and a self-hosted agent on your own machine, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • Azure DevOps organization and project (free, no ongoing cost).
  • Self-hosted agent running at C:\agents on your machine.
  • Local repository files on your machine.

Keep everything running

No action needed. Choose this if you're still actively building on the pipeline or plan to experiment further.

  • Leave the agent running in the PowerShell window with run.cmd active.
  • Your Azure DevOps project and pipeline will continue to trigger on every push to main.
  • The free tier gives you unlimited minutes on the self-hosted agent, so no charges apply.

Pause - I'll come back to this later

Shut down the agent to free up your PowerShell window. Your Azure DevOps project stays intact and you can restart anytime.

  • In the PowerShell window where the agent is running, press Ctrl+C to stop the agent.
  • When you're ready to resume, open an elevated PowerShell window, navigate to C:\agents, and run:
.\run.cmd
  • Your pipeline will pick up where it left off on the next push to main.

Delete - I don't want to use this again

Remove all project resources and start fresh. No paid resources to worry about.

  • Stop the agent by pressing Ctrl+C in the PowerShell window.
  • Remove the agent configuration by running this from C:\agents:
.\config remove
  • When prompted, authenticate with your PAT to confirm the removal.
  • Delete the agent folder by running:
Remove-Item -Recurse -Force C:\agents
  • Delete the Azure DevOps project by going to your organization at dev.azure.com.
  • Select Organization settings.
  • Select Projects.
  • Click the ellipsis (...) next to calculator-pipeline and choose Delete.
  • Confirm by typing the project name and clicking Delete.
  • Delete the local repository folder from your machine if you no longer need it.

Nice Work!

Nice Work!

You just built a fully automated CI/CD pipeline from scratch using Azure DevOps. Every time you push code, your pipeline builds, tests, and packages your application without any manual intervention.

You've learned how to:

  • Configure a self-hosted pipeline agent on your own machine, giving you hands-on understanding of how Azure DevOps executes pipeline jobs behind the scenes.
  • Write a YAML pipeline that automatically installs dependencies, runs Jest tests, and publishes a deployable artifact on every push to main.
  • Prove the full CI/CD loop works by making code changes and watching the pipeline react automatically, from commit to packaged artifact.
  • Convert to a multi-stage pipeline with separate Build and Deploy stages, plus environment approval gates for production deployments.

Ready to quiz yourself?