Running specified Apex tests from a GitHub pull request body using Node.js

In the SalesforceBen article, How to build your own CI/CD pipeline (Using GitHub Actions), I showed that developers can specify which apex tests to run by using a special syntax in the pull request body.

Let's see how it's done

The syntax

First, the developer needs to use the following syntax to specify which tests to run when this pull request is merged into the target branch

Apex::[Test1,Test2,Test3]::Apex

Also, if the developer wants to run all tests, the following can be used as well

Apex::[All]::Apex

This syntax is arbitrary. In other words, it can be anything that you want. I decided to use Apex::[ as delimiters because it's very unlikely that a developer would write that in the pull request body without actually intending to specify the tests.

This means, that if the syntax was ApexTest or something more simple, the CI/CD job could incorrectly parse other text that has nothing to do with the tests. Hopefully, that made sense...  

Reading the pull request body

When the pull request is merged, a GitHub action workflow kicks in. And in this workflow, I want to read the pull request body and see if the special syntax is found, and if so, pass those tests over to the Salesforce CLI.

To read the pull request body, I'm using this inside my .yml workflow file. I've added comments so that you can see what's going on:

- name: 'Read PR Body'
              env:
                # The pull request body is available through the github context object
                # we put the body of the pull request in an env variable (only available to this step)
                PR_BODY: ${{github.event.pull_request.body}}

              # Here we print the content of the environment variable and
              # pipe to a a text file.

              # Then we call the local script parsePR.js, which will create
              # a new file called testsToRun.txt. This file will have the list
              # of tests to run separated by a comma

              # Finally, we add the list of tests to the $GITHUB_ENV variable
              # as this allows us to reference the list in a subsequent step.  If you
              # were using a normal env variable, its value would not be available outside this step.
              run: |
                  echo $PR_BODY > ./pr_body.txt
                  node ./parsePR.js              
                  TESTS=$(cat testsToRun.txt)       
                  echo "APEX_TESTS=$TESTS" >> $GITHUB_ENV

Ok, so as seen above, we create an env variable called PR_BODY  that contains the pull request body, and then we create a new file with that body

echo $PR_BODY > ./pr_body.txt

Then, we use Node.js to call the parsePR.js file

Using JavaScript to extract the apex tests

Let's define the function that will the text inside pr_body.txt

async function extractTests(){

    //by default we specify that all tests should run
    let testsFile = __dirname+'/testsToRun.txt';
    await fs.promises.writeFile(testsFile,'all');

Here, we create a new file called testsToRun.txt and set the value to all. This is because in case the developer didn't specify anything, we want to run all tests.

Then, we are going to read each line of the file, one by one

const lines = readline.createInterface({
    input: fs.createReadStream(__dirname+'/pr_body.txt'),
    crlfDelay: Infinity
});

Now, we loop through all the lines and see if we can find our special delimiter

for await (const line of lines) {
    //special delimeter for apex tests
    if(line.includes('Apex::[') && line.includes(']::Apex')){
 

Note here that I probably should used line.toLowerCase() to make sure that I find a match even if the developer didn't use the exact case, but I'll leave that to you as an exercise ;)

If we do find that delimiter, we then extract all the tests that were specified inside of it and we write them back to the testsToRun.txt file.

let tests = line.substring(8,line.length-7);
await fs.promises.writeFile(testsFile,tests);
await fs.promises.appendFile(testsFile,'\n');

Putting the specified tests in a GitHub env variable

Now, we can go back to our yml file and put those tests back into an env variable

TESTS=$(cat testsToRun.txt)       
echo "APEX_TESTS=$TESTS" >> $GITHUB_ENV

Passing the tests to the Salesforce CLI

Then, in our yml file we do the following

 - name: 'Check-only deploy delta changes - run specified tests'
              if: ${{ env.APEX_TESTS != 'all' }}
              run: |
                  echo ${{env.APEX_TESTS}}
                  sfdx force:source:deploy -p "changed-sources/force-app" --checkonly --testlevel RunSpecifiedTests --runtests ${{env.APEX_TESTS}} --json

What we did here is to check if the env.APEX_TESTS variable does not equal to all, meaning, there are some specified tests in it. If so, we then use the sfdx force:source:deploy command, passing the tests from the env variable

--testlevel RunSpecifiedTests --runtests ${{env.APEX_TESTS}}

And that's it! It works beautifully!

You can check the full javascript file here, and the GitHub action workflow file here.

💡
If you found this article useful, please consider subscribing! I have big plans for subscribers in the future :) 

Subscribe for exclusive Salesforce Engineering tips, expert DevOps content, and previews from my book 'Clean Apex Code' – by the creator of HappySoup.io!
fullstackdev@pro.com
Subscribe