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.