Angular 8 with Azure DevOps Build Pipeline

Angular 8 is a very useful JavaScript framework for building applications. Being able to build these projects on Azure DevOps Pipelines is very useful.

This is an update from Angular 7 blog post here..

Angular Project

The repository on GitHub is here.

Following the Angular Quickstart here I created a new folder and put Angular 8 project in there.

> mkdir src
> cd src
> npm install -g @angular/cli
> ng new angular8

Packages to add

> npm install puppeteer --save-dev
> npm install karma-junit-reporter --save-dev
> npm install jasmine-reporters --save-dev

Code Changes

Here’s the overview of code changes.

Added Build Pipeline YAML definition:

trigger:
  branches:
    include:
    - master
    - releases/*
    
resources:
- repo: self

pool:
  vmImage: 'Ubuntu 18.04'

variables:
  buildConfiguration: 'Release'
  ngBuildConfiguration: '--prod'

steps:

- task: DeleteFiles@1
  displayName: 'Delete JUnit files'
  inputs:
    SourceFolder: src/angular8/junit
    Contents: 'TEST*.xml'

- task: Npm@1
  displayName: 'npm install'
  inputs:
    command: install
    workingDir: src/angular8

- task: Npm@1
  displayName: 'Build Angular'
  inputs:
    command: custom
    customCommand: run build -- $(ngBuildConfiguration)
    workingDir: src/angular8

- task: PublishPipelineArtifact@0
  inputs:
    artifactName: angular8PipelineArtifactProd
    targetPath: 'src/angular8/dist'

- task: PublishBuildArtifacts@1
  inputs:
    PathtoPublish: 'src/angular8/dist'
    ArtifactName: angular8BuildArtifactProd

- task: DeleteFiles@1
  displayName: 'Delete Build files'
  inputs:
    Contents: 'src/angular8/dist'

- task: Npm@1
  displayName: 'Test Angular'
  inputs:
    command: custom
    customCommand: run test -- --watch=false --code-coverage
    workingDir: src/angular8

- task: PublishCodeCoverageResults@1
  displayName: 'Publish code coverage Angular results'
  condition: succeededOrFailed()
  inputs:
    codeCoverageTool: Cobertura
    summaryFileLocation: 'src/coverage/cobertura-coverage.xml'
    reportDirectory: src/coverage
    failIfCoverageEmpty: true

- task: PublishTestResults@2
  displayName: 'Publish Angular test results'
  condition: succeededOrFailed()
  inputs:
    searchFolder: $(System.DefaultWorkingDirectory)/src/junit
    testRunTitle: Angular
    testResultsFormat: JUnit
    testResultsFiles: "**/TESTS*.xml"

- task: Npm@1
  displayName: 'Lint Angular'
  inputs:
    command: custom
    customCommand: run lint --  --format=stylish
    workingDir: src/angular8

- task: Npm@1
  displayName: 'E2E Test Angular'
  inputs:
    command: custom
    customCommand: run e2e
    workingDir: src/angular8

- task: PublishTestResults@2
  displayName: 'Publish Angular E2E test results'
  condition: succeededOrFailed()
  inputs:
    searchFolder: $(System.DefaultWorkingDirectory)/src/angular8/e2e/junit
    testRunTitle: Angular_E2E
    testResultsFormat: JUnit
    testResultsFiles: "**/junit*.xml"

package.json scripts (in addition to whatever version of the above added packages):

  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "install-puppeteer": "cd node_modules/puppeteer && npm run install",
    "test": "npm run install-puppeteer && ng test",
    "lint": "ng lint",
    "e2e": "npm run install-puppeteer && ng e2e"
  },

Final karma.conf.js:

module.exports = function (config) {
  const puppeteer = require('puppeteer');
  process.env.CHROME_BIN = puppeteer.executablePath();
  console.log(process.env.CHROME_BIN)

  config.set({
    basePath: '',
    frameworks: ['jasmine', '@angular-devkit/build-angular'],
    plugins: [
      require('karma-jasmine'),
      require('karma-chrome-launcher'),
      require('karma-jasmine-html-reporter'),
      require('karma-coverage-istanbul-reporter'),
      require('@angular-devkit/build-angular/plugins/karma'),
      require('karma-junit-reporter')
    ],
    client: {
      clearContext: false // leave Jasmine Spec Runner output visible in browser
    },
    coverageIstanbulReporter: {
      dir: require('path').join(__dirname, '../coverage'),
      reports: ['html', 'lcovonly', 'text-summary', 'cobertura'],
      fixWebpackSourcePaths: true
    },
    reporters: ['progress', 'kjhtml', 'junit'],
    junitReporter: {
      outputDir: '../junit'
    },
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['ChromeHeadless'],
    singleRun: false
  });
};

Final protractor.conf.js:

const { SpecReporter } = require('jasmine-spec-reporter');
const { JUnitXmlReporter } = require('jasmine-reporters');
const puppeteer = require('puppeteer');

process.env.CHROME_BIN = puppeteer.executablePath();
console.log(process.env.CHROME_BIN)

exports.config = {
  allScriptsTimeout: 11000,
  specs: [
    './src/**/*.e2e-spec.ts'
  ],
  capabilities: {
    'browserName': 'chrome',

    chromeOptions: {
      args: ["--headless", "--disable-gpu", "--window-size=1200,900"],
      binary: process.env.CHROME_BIN
    }
  },
  directConnect: true,
  baseUrl: 'http://localhost:4200/',
  framework: 'jasmine',
  jasmineNodeOpts: {
    showColors: true,
    defaultTimeoutInterval: 30000,
    print: function () { }
  },
  onPrepare() {
    require('ts-node').register({
      project: require('path').join(__dirname, './tsconfig.json')
    });
    jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
    var junitReporter = new JUnitXmlReporter({
      savePath: require('path').join(__dirname, './junit'),
      consolidateAll: true
    });
    jasmine.getEnv().addReporter(junitReporter);
  }
};

Summary

My code is here. Azure DevOps makes builds easy.