Search code examples
azure-devopsyamlazure-pipelinesazure-pipelines-yaml

Azure DevOps YAML Pipeline - iterating over a list of strings and creating unique tasks per string


I currently have an Azure DevOps YAML pipeline that I use to run my WebdriverIO test suites in CI. If an Azure user selects 'all-suites', the pipeline should create an individual pipeline task for every suite. Currently, I have an individual pipeline script for every single suite I'm running, but I'm trying to figure out a way to use YAML syntax to iterate over a list of test suite names instead. This way, I don't have to manually add repetitive blocks of code every time I add a suite to my app. You can see my current pipeline file pattern here-

parameters:
    - name: testSuite
      type: string
      default: 'all-suites'
      values:
          - 'all-suites'
          - 'suite1'
          - 'suite2'
          - 'suite3'

trigger: none

jobs:
    - job: run_pipeline
      timeoutInMinutes: 120
      displayName: 'Run'
      pool:
          vmImage: 'ubuntu-latest'

      steps:
          # if the user doesn't select 'all-suites', it runs the specific suite selected as a single task
          - ${{ if ne(parameters.testSuite, 'all-suites') }}:
                - script: |
                      npm run example-command -- --suite ${{ parameters.testSuite }}
                  displayName: 'Run test suite: ${{ parameters.testSuite }}'
                  continueOnError: true
          # if the user does select 'all-suites', it runs specific tasks for each suite
          - ${{ if eq(parameters.testSuite, 'all-suites') }}:
                - script: |
                      npm run example-command -- --suite suite1
                  displayName: 'suite1'
                  continueOnError: true

                - script: |
                      npm run example-command -- --suite suite2
                  displayName: 'suite2'
                  continueOnError: true

                - script: |
                      npm run example-command -- --suite suite3
                  displayName: 'suite3'
                  continueOnError: true

Rather than having to manually add a new block every time I add a new suite, I was hoping I could iterate over something like this -

steps:
    - ${{ if ne(parameters.testSuite, 'all-suites') }}:
          - script: |
                npm run example-command -- --suite ${{ parameters.testSuite }}
            displayName: 'Run test suite: ${{ parameters.testSuite }}'
            continueOnError: true

    - ${{ if eq(parameters.testSuite, 'all-suites') }}:
          - ${{ each suite in <SUITE LIST HERE> }}:
                # skip 'all-suites' because it's not a real suite
                - ${{ if ne(suite, 'all-suites') }}:
                      - script: |
                            npm run example-command -- --suite ${{ suite }}
                        displayName: '${{ suite }}'
                        continueOnError: true

From what I've read, I can't access any sort of list in this way if it isn't available during compile, so I was wondering if anyone knew of any creative ways to create a list that is in fact available during compile.


Solution

  • This would be a workaround but it will work

    
    parameters:
        - name: testSuite
          type: string
          default: 'all-suites'
          values:
              - 'all-suites'
              - 'suite1'
              - 'suite2'
              - 'suite3'
        - name: suiteList
          type: object
          default:
          - 'suite1'
          - 'suite2'
          - 'suite3'
    
    steps:
        - ${{ if ne(parameters.testSuite, 'all-suites') }}:
              - script: |
                    npm run example-command -- --suite ${{ parameters.testSuite }}
                displayName: 'Run test suite: ${{ parameters.testSuite }}'
                continueOnError: true
    
        - ${{ if eq(parameters.testSuite, 'all-suites') }}:
              - ${{ each suite in parameters.suiteList }}:
                  - script: |
                        npm run example-command -- --suite ${{ suite }}
                    displayName: '${{ suite }}'
                    continueOnError: true
    

    The disadvantage of this that you have duplication of suite names in parameters.

    You could also try use if else here

    
    parameters:
        - name: testSuite
          type: string
          default: 'all-suites'
          values:
              - 'all-suites'
              - 'suite1'
              - 'suite2'
              - 'suite3'
        - name: suiteList
          type: object
          default:
          - 'suite1'
          - 'suite2'
          - 'suite3'
    
    steps:
        - ${{ if ne(parameters.testSuite, 'all-suites') }}:
              - script: |
                    npm run example-command -- --suite ${{ parameters.testSuite }}
                displayName: 'Run test suite: ${{ parameters.testSuite }}'
                continueOnError: true
    
        - ${{ else }}:
              - ${{ each suite in parameters.suiteList }}:
                  - script: |
                        npm run example-command -- --suite ${{ suite }}
                    displayName: '${{ suite }}'
                    continueOnError: true