General GitHub Notes & GitHub Actions concepts and setup reference
Last Updated: October 15, 2020 by Pepe Sandoval
If you find the information in this page useful and want to show your support, you can make a donation
Use PayPal
This will help me create more stuff and fix the existent content...
Disclaimer


a runner is any machine with the GitHub actions runner application installed
GitHub Workflows are written in YAML
{}"" around keys or values, only needed if a value has spaces- every dash is a new element or with [] separated by commas> for long values to suppress new lines or | to keep them
name: Pepe
active: True
age: 20
array:
- item1
- item2
- key: value
- key2: value2
key3: value3arrayjsonformat: [item1, item2, key: value] address: country: Malta city: Valletta long_text: > this is the long text
your_repo/.github/workflows/your_file.yamlnameonjobs. jobs will be key-value pairs where the key will be the name of the job and the value will be an object that sets the different options for the job, the steps option is used to specify the steps of the job, it must be a list where each element is an object with the name and run keys
name: Shell Commands
on: [push]
jobs:
run-shell-commands:
runs-on: ubuntu-latest
steps:
- name: echo a string
run: echo "Hello World"
- name: multiline commands
run: |
node -v
npm -v
- name: python cmd
shell: python
run: |
import platform
print(platform.processor())

ACTIONS_STEP_DEBUG & ACTIONS_RUNNER_DEBUG to true as mentioned in the Enabling debug logging GitHub Documentation
shell option.It just need to be specified a the same indentation level of the other jobs and by default they will jobs run in parallel
If we want to specify a sequence for the jobs you can set the needs option in the job which can be a list of the jobs (names of jobs) the current job depends on and it will only run if the jobs in these dependencies list finish successfully
name: Shell Commands
on: [push]
jobs:
run-shell-commands:
runs-on: ubuntu-latest
steps:
- name: echo a string
run: echo "Hello World"
- name: multiline commands
run: |
node -v
npm -v
- name: python cmd
shell: python
run: |
import platform
print(platform.processor())
run-windows-commands:
runs-on: windows-latest
needs: [run-shell-commands]
steps:
- name: print dir PS
run: Get-Location
- name: print dir BASH
run: pwd
shell: bash
- name: python cmd
shell: python
run: |
import platform
print(platform.processor())
An Action is a script or code that can be written in JavaScript to perform some specific task, which then you execute in as a step in your workflow. This script is also called sometimes action file and can live in the same repo or in an external repo
user/repo@branch,version or commitIn a step we use the uses option to specify reference to that action, the with option to specify inputs and then we will need to access the .outputs field of the step to access the outputs an action produces
name: Actions Workflow
on: [push]
jobs:
run-github-actions:
runs-on: ubuntu-latest
steps:
- name: Simple JS action
id: greet
uses: actions/hello-world-javascript-action@v1
with:
who-to-greet: Pepe
- name: Log Greeting output
run: echo "Pepe ${{ steps.greet.outputs.time }}"
When a workflow is run on a GitHub Runner by default a folder is set up on the virtual machine the runner created, we refer to this folder as the Working Directory, for example /home/runner/work/github-actions-test/github-actions-test but by default the runner will NOT clone the repo
The most common way to clone our repo in a workflow is to use the GitHub checkout action which is an official GitHub action (this just means it is an action created by GitHub), this action can take as input a reference ref that can be branch, tag or SHA of the commit we want to use
If you want to clone the repo without using the checkout action you will need to use environment variables and data objects that are setup automatically by GitHub
$GITHUB_SHA this is the commit ID of the commit that triggered the workflow$GITHUB_REPOSITORY this is user name and repo name. e.x. sanpepe/github-actions-test$GITHUB_WORKSPACE this is the directory create by the runner for the workflow${{ github.token }} token to auth with your repo, this will be hidden in the logs if you print it for security reasons
name: Actions Workflow
on: [push]
jobs:
run-github-actions:
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v1
- name: List after checkout
run: |
pwd
ls -al
echo $GITHUB_SHA
echo $GITHUB_REPOSITORY
echo $GITHUB_WORKSPACE
echo "Token = ${{ github.token }}"
- name: Simple JS action
id: greet
uses: actions/hello-world-javascript-action@v1
with:
who-to-greet: Pepe
- name: Log Greeting output
run: echo "Pepe ${{ steps.greet.outputs.time }}"
We can use the on to define we want to run a workflow on events like push, pull_request, pull_request_review, fork, etc. (Webhook events that can trigger a workflow)
Some events can have Activity Types which are just a more specific type of event, to set these we need to use the types array
Events use env variables that are set differently depending on the events that triggered the workflow, for example on a push and on a pull_request the GITHUB_REF env variable is used to know what commit will be used, for push will be the latest of the branch we are pushing and for the pull request it will be the branch that requested the pull request (the one that wants to be merged)
name: Actions Workflow
on:
push:
pull_request:
types: [closed, assigned, opened, reopened]
...
To trigger on schedule we can use a cron schedule expression and the schedule option which is an array of objects with key cron and value must be a cron (expression but the minimum is every 5 minutes)
A cron expression consist of 5 things: minutes hours day_of_month month day_of_week each can be a number or symbol (* means any value). Examples:
* * * * * means run every minute of every hours of every day...1 * * * * means run on minute 1 of every hour so 12:01, 13:01, 14:01, etc0 20 * * * means run at 20:00 (8pm) every day
name: Actions Workflow
on:
schedule:
- cron: "10 * * * *"
...
repository_dispatchthe repository_dispatch is an event that can be triggered manually by sending a post request to the GitHub API URL in format https://api.github.com/repos/<owner>/<repo_name>/dispatches for example: https://api.github.com/repos/sanpepe/github-actions-test/dispatches
To be able to use the repository_dispatch feature you will need to send a token to auth the POST request, for that go to Settings -> Developer Settings -> Personal access tokens, click on Generate new token, give it a name on the Note section, select repo scope, finally click on the generate button and copy the token

name: Actions Workflow
on:
push:
repository_dispatch:
types: build
...
POST example using curl, you must get an 204 response:
curl -w "%{http_code}" --user sanpepe: --header "Content-Type: application/json" --request POST --data '{"event_type":"build"}' https://api.github.com/repos/sanpepe/github-actions-test/dispatches

We can filter by branches, tags or paths to be more specific about when a workflow should run, for example if you only want to run when there is a push to a certain branch. To do this you need to add filter options to the events on the on option
You can use <filter>-ignore (branches-ignore, tags-ignore, paths-ignore) on an event to signal you want to ignore those but you cannot have both <filter> (branches, tags, paths) and <filter>-ignore if you want to ignore a filter can also use the ! with the <filter> option. So for example you cannot use branches and branches-ignore at the same time but you can have a pattern branch with ! to signal it will be ignored
name: Actions Workflow
on:
push:
branches:
- master
- 'feature/*' # matches feature/feat1, feature/feat2, etc
- '!feature/featA' # ignores feature/featA
tags:
- v1
- v2.*
path:
- '**.py' # match push changes on python files
pull_request:
branches:
- master
...
To set environment variables you can use the env option which should be an object where key is the name of the env variable and the value will be the actual value of the env variable
If env option is set at the top level of the YAML file the variables will be available for all jobs, if you want variables specific to a job or step they need to be set at the env key but at the job or step level
name: Env Variables
on: push
env:
WF_ENV: Available to all jobs
jobs:
log-env:
runs-on: ubuntu-latest
env:
JOB_ENV: Available to all steps in log-env Job
steps:
- name: Log env Variables
env:
STEP_ENV: Available to only this step
run: |
echo "WF_ENV: ${WF_ENV}"
echo "JOB_ENV: ${JOB_ENV}"
echo "STEP_ENV: ${STEP_ENV}"
- name: Log env Variables Reloaded
run: |
echo "WF_ENV: ${WF_ENV}"
echo "JOB_ENV: ${JOB_ENV}"
echo "STEP_ENV: ${STEP_ENV}"
log-default-env:
runs-on: ubuntu-latest
steps:
- name: Log Default env Variables
run: |
echo "HOME: ${HOME}"
echo "GITHUB_WORKFLOW: ${GITHUB_WORKFLOW}"
echo "GITHUB_ACTION: ${GITHUB_ACTION}"
echo "GITHUB_ACTIONS: ${GITHUB_ACTIONS}"
echo "GITHUB_ACTOR: ${GITHUB_ACTOR}"
echo "GITHUB_REPOSITORY: ${GITHUB_REPOSITORY}"
echo "GITHUB_EVENT_NAME: ${GITHUB_EVENT_NAME}"
echo "GITHUB_WORKSPACE: ${GITHUB_WORKSPACE}"
echo "GITHUB_SHA: ${GITHUB_SHA}"
echo "GITHUB_REF: ${GITHUB_REF}"
echo "WF_ENV: ${WF_ENV}"
echo "JOB_ENV: ${JOB_ENV}"
echo "STEP_ENV: ${STEP_ENV}"
secrets object. Example:
name: Secret Env Variables
on: push
env:
MY_SECRET_ENV: ${{ secrets.MY_SECRET_ENV }}
${{ secrets.GITHUB_TOKEN }} if you want to auth with GitHub API or use it for other operations that require auth to GitHub. The most common usage of this token is to give access to third party apps, scripts or actions to your repo. Examples:actions/labeler which can automatically add labels to pull request but in order to do that it needs access to create labels in your repo
name: ENV Variables
on: push
env:
WF_ENV: Available to all jobs
jobs:
push_random_file:
runs-on: ubuntu-latest
steps:
- name: Push a random file
run: |
pwd
ls -a
git init
git remote add origin "https://$GITHUB_ACTOR:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY.git"
git config --global user.email "my-bot@bot.com"
git config --global user.name "my-bot"
git fetch
git checkout master
git branch --set-upstream-to=origin/master
git pull
ls -a
echo $RANDOM >> random.txt
ls -a
git add -A
git commit -m"Random file"
git push
...
You cannot trigger a workflow when you are using the
${{ secrets.GITHUB_TOKEN }}this is the reason pushing from a workflow won't cause an endless loop of workflows running forever
gpg --symmetric --cipher-algo AES256 my_secret.json), push the encrypted version, then create a GitHub secret with the passphrase and use it in your workflow to unencrypt the file (for example gpg --quiet --batch --yes --decrypt --passphrase="$PASSPHRASE" --output $HOME/my_secret.json my_secret.json.gpg
name: ENV Variables
on: push
jobs:
decrypt:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Decrypt
env:
PASSPHRASE: ${{ secrets.PASSPHRASE }}
run: gpg --quiet --batch --yes --decrypt --passphrase="$PASSPHRASE" --output $HOME/my_secret.json my_secret.json.gpg
- name: Print our file content
run: cat $HOME/my_secret.json
Context and expression syntax for GitHub Actions Documentation
The ${{}} syntax is used to write what is called an expression in a workflow, an expression is something that needs to be evaluated, so besides access to and object's values you can have things like comparisons ("a" == "a") or functions (toJson())
Objects in GitHub are also called context and provide information about your workflow
name: Context workflow
on: [push]
jobs:
dump-context:
runs-on: ubuntu-latest
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- name: Dump steps context
env:
STEPS_CONTEXT: ${{ toJson(steps) }}
run: echo "$STEPS_CONTEXT"
functions:
runs-on: ubuntu-latest
steps:
- name: functions examples
run: |
echo ${{ contains( 'hello', '11' ) }}
echo ${{ startsWith( 'hello', 'he' ) }}
echo ${{ endsWith( 'hello', '1o' ) }}
echo ${{ format( 'Hello {0} {1} {2}', 'World', '!', '!' ) }}
Job status functions are functions that return status of the job and steps of the job
Job status functions are used with the if key to determine if a job or step should run. Anything inside the if key is treated as an expression
By default if a step fails the following steps in a job won't run unless we change that behavior using a job status functions
failure(): returns true if previous step or job failssuccess(): returns true if previous step or job successcanceled(): returns true if previous step or job was canceledalwayts(): always returns true used to run step or job that needs to run even if anything failed in the previous step or job
name: Context workflow
on: [push]
jobs:
functions:
runs-on: ubuntu-latest
if: github.event_name == 'push'
steps:
- name: bad echo cmd
run: |
eccho "Bad echo command"
- name: functions examples
if: failure()
run: |
echo ${{ contains( 'hello', '11' ) }}
echo ${{ startsWith( 'hello', 'he' ) }}
echo ${{ endsWith( 'hello', '1o' ) }}
echo ${{ format( 'Hello {0} {1} {2}', 'World', '!', '!' ) }}

You can add the continue-on-error: true option to a step to signal all following steps should run even if the steps that has this set to true fails
You can also add the timeout-minutes to a job or step to set the max number of minutes before GitHub kills the process for this step or job
strategy is a special option we have have in GitHub workflows that can be specified in job to setup and environment matrix
A strategy can be used to run a job on different types of environments, for example if we want to a job with multiple versions of NodeJS and multiple Operating Systems we can setup a matrix of environments using the strategy option to do this
To set up a strategy we use the strategy option
fail-fast if set to false each of the jobs will run independent of the results of other jobsmax-parallel to limit the number of jobs that will run in parallel, by default github will maximize thismatrix key that must contain any keys and the values of those keys must be arrays with the values that will be used on the execution of the job, on the job we need to use the ${{matrix.key_name}} to reference the value that will change depending on which job is running
name: matrix
on: [push]
jobs:
node-version:
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
node_version: [6, 8, 10]
runs-on: ${{matrix.os}}
steps:
- name: Log Node version before
run: |
node -v
npm -v
- name: use setup-node
uses: actions/setup-node@v1
with:
node-version: ${{matrix.node_version}}
- name: Log Node version after
run: |
node -v
npm -v

we can exclude an specific combination of the matrix using the exclude inside the matrix, the exclude option must have an array of objects where each object must have the keys and values that we want to exclude or include
the include option is used to add extra values to a specific combination of the matrix
name: matrix
on: [push]
jobs:
node-version:
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
node_version: [6, 8, 10]
include:
- os: ubuntu-latest
node_version: 8
is_ubuntu_8: "true"
exclude:
- os: windows-latest
node_version: 6
- os: macos-latest
node_version: 8
runs-on: ${{matrix.os}}
...
container option and specifying a Docker image
name: Container
on: [push]
jobs:
node-docker:
runs-on: ubuntu-latest
container:
image: node:14.15.0-alpine3.10
steps:
- name: "Log Node Version"
run: |
node -v
cat /etc/os-release

Containers on Github Actions can only run on ubuntu
We can also do more complex stuff and specify multiple containers to run as services, for example an app that needs a DB to work can be run if we create and run a container that has the DB. For this we use the services option and inside we can follow same syntax as in docker-compose.yml file which let us specify multiple services that run on separate containers
we can use docker container as github action with the uses and with options in a step. E.x. uses: docker://node:14.15.0-alpine3.10
The power of being able to use docker images in Github actions lies in the fact that we can use the docker library of images. We can browse the library and if we find a docker image that does something we need we can use it as part of our workflow

CI (Continuous Integration) is a practice where one or more collaborators frequently commit code to a shared repository and if something breaks we want to detect the source of the error (the commit that caused the errors) as soon as possible
CI attempts to ensure that new code will not break any previously working tests or introduce any errors
CD (Continuous Deployment) is a practice where we want to be continuously deploying our application or in other words making releases of our application without impacting our clients/users

Two protected branches (Develop and Master), meaning developers cannot push directly to these they need to open a pull request to merge something to these branches
Master branch will contain deployable code, which means code that can be deployed to production. So we will never push anything into the master branch except if we want to deploy it to production. Our Develop branch on the other side will contain the latest development code that we would not like to deploy yet.
When a developer needs to work on something, he/she will or create a new branch from the Develop branch (e.x called Feature1)
When a developer work is done a pull request to merge into Develop branch will be opened, when this happens a workflow will run to execute tests (unit, integration & system), if test pass and no regression is detected this pull request will be in queue for a Reviewer to approve it
if reviewer approves this will be merged into the Develop this merge will run another workflow to run our tests again (unit, integration & system) and deploy to a staging server (beta)
When we want to make a release, we need to create pull request from the Develop branch to merge into master, this pull requests will trigger another workflow that will execute or tests again if our tests pass it will be in queue for a Reviewer to approve the release
if reviewer approves the release, this will be merged into Master this merge will run another workflow to run our tests again (unit, integration & system) and deploy to production
Github allow you to set a CODEOWNERS file to set who owns certain files (your_repo/.github/CODEOWNERS) you need to specify files, folders or patterns followed by Github username, this is used for pull-request to assign reviewers based on owners
*.js @sanpepe
*.yaml @sanpepe
To protect branches in Github 1) go to Settings -> Branches and Click on Add Rule 2) give the branch name and 3) Select the rules you want to enforce and click on Create (you may need to enter your password).
Protect branches in Github Example:

name: CI
on:
push:
branches: [develop]
push:
branches: [develop]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Python 3.x
uses: actions/setup-python@v1
with:
python-version: "3.x"
- run: pip install --no-index -r $./requirements.txt
- name: run unit test
run: python -m unittest .../tests/unit/my_unit_test.py
PYTHONPATH: ./
- name: run integration tests
if: github.event_name == 'push'
run: python -m unittest .../tests/integration/my_int_test.py
- name: Deploy to Staging
if: github.event_name == 'push'
run: echo "Some step to publish code"
Consider a merge to a branch needs a push so it you trigger on push that will also be executed when a merge happens
If you want to cache the sate of your dependencies you can use the cache action for example to cache python dependencies
key option, this is save in your caches so every time it runs it first sees if that cache is found it will be retrieved and used in your workflow. And if not a new cache key will be created so that when you run your workflow again, you can use itTo Upload and artifact in Github we need to use the upload-artifact action and specify the path we want to upload, optionally give it an ID name to appear on Github
download-artifact action
...
- name: Upload artifact
uses: actions/upload-artifact@v1
with:
name: my_app_debug
path: ./logs/my_app_debug.log
...
semantic-release
Conventional commits: Have a format for the commits with special keywords to know what version to increment for the release
To Setup semantic release:
release.config.js file in your repo and set it up according with semantic release documentationmodule.exports = {
branches: "master",
repositoryUrl: "<github repo URL here>",
plugins: [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/github"
]
}
Add a step in your workflow to run npx semantic release and provide the env variable GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
If you want to upload extra assets to your release, you need to add them in the semantics-release config file (we can only add files)
module.exports = {
branches: "master",
repositoryUrl: "<github repo URL here>",
plugins: [
"@semantic-release/commit-analyzer", "@semantic-release/release-notes-generator",
["@semantic-release/github", {
assets: [
{ path: "build.zip", label: "Build" },
{ path: "coverage.zip", label: "Coverage" }
]
}]
]
}
To trigger an action after a workflow completion we usually use the release and other events in a separate workflow
Send a message
name: Notify on Release
on:
release:
types: [published]
jobs:
slack-message:
runs-on: ubuntu-latest
steps:
- name: Slack Message
run: |
curl -X POST -H 'content-type: application/json'
-- data '{"text":"New release ${{ github.event.release.tag_name }}
is out, <${{ github.event.release.html_url }}|check it out now.>"}'
${{ secrets.SLACK_WEBHOOK }}Remember you cannot trigger a workflow from inside another workflow using the Github token
secrets.GITHUB_TOKENthat is automatically generated by Github, if you want to do this you need to generate a separate token from your personal account (Go to Settings -> Developer Settings -> personal access tokens)
failure() condition which will only run if any of the steps in a job fails
...
- name: Open Issue
if: failure() && github.event_name == 'pull_request'
run: |
curl --request POST \
--url https://api.github.com/repos/${{ github.repository }}/issues \
--header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \
--header 'content-type: application/json' \
--data '{
"title": "Automated issue for commit: ${{ github.sha }}",
"body": "This issue was automatically created by the GitHub Action workflow **${{ github.workflow }}**. \n\n The commit hash was: _${{ github.sha }}_.",
"assignees": ["${{ github.event.pull_request.user.login }}"]
}'
...


Github has public actions that live in the Github market place and we can reference in our workflow without actually including any code in our project
We can also write private actions that live in your project and reference them in the workflow
We can write actions using JavaScript or Docker containers
.github folder called actions and inside that create a folder for your actionaction.yml file to define basic information of your action: name, author, inputs & outputs and runs to specify entry point and type of action e.x. Javascript and index.jsinputs in a github action only accept strings
name: Hello World Pepe Action author: Pepe Sandoval description: Some description about action here inputs: who-to-say-hello: description: 'Who to Say Hello' required: true default: Pepe outputs: time: description: 'Time of Hello' runs: using: "node12" main: 'dist/index.js'
action.yml file (e.x. index.js), add here the code to run with your actions and use the GitHub Toolkit packages to get inputs and outputs.
// index.js
const core = require("@actions/core");
const github = require("@actions/github");
try {
const name = core.getInput("who-to-say-hello");
console.log(`Hello World ${name}`);
const time = new Date();
core.setOutput("time", time.toTimeString());
console.log(JSON.stringify(github, null, ' '));
} catch(error) {
// You can call this function to make action and workflow fail
core.setFailed(error.message)
}
nccnpm i -D @zeit/ncc or npm i -D @vercel/nccnpm install @actions/core and npm install @actions/githubnpx ncc build .github/actions/hello/index.js -o .github/actions/hello/distaction.yml to use the single compiled file e.x. main: dist/index.js./github/actions/...
name: Test Actions
on: [push]
jobs:
run-my-github-actions:
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v1
- name: Use my Action
id: hello
uses: ./.github/actions/hello
with:
who-to-say-hello: "Pepe Ch."
- name: Log Greeting output
run: echo "Pepe Action Time ${{ steps.hello.outputs.time }}"When you do changes in the entry point JS file remember to recompile it

.github folder called actions and inside that create a folder for your actionaction.yml file to define basic information of your action: name, author, inputs & outputs and runs to specify image or dockerfile, set the args inside run to pass arguments to your dockerfile
name: Hello World Pepe Docker Action
author: Pepe Sandoval
description: Some description about action here
inputs:
who-to-say-hello:
description: 'Who to Say Hello'
required: true
default: Pepe
outputs:
time:
description: 'Time of Hello'
runs:
using: "docker"
image: 'Dockerfile'
args:
- ${{ inputs.who-to-say-hello }}
Dockerfile. A common practice is to use a shell script and use special echo strings to set outputsif you use an
.shscript make sure to give the executable permissions before pushing to repo. e.x.chmod +x .github/actions/hello-docker/entrypoint.shandgit add --chmod=+x -- ./.github/actions/hello-docker/entrypoint.sh
FROM alpine:3.11 COPY entrypoint.sh /entrypoint.sh ENTRYPOINT ["/entrypoint.sh"]
#!/bin/sh -l
echo "::debug ::Debug Message"
echo "Hello $1"
time=$(date)
echo "::set-output name=time::$time"
echo "::group::Some expandable logs"
echo 'Stuff 1'
echo 'Stuff 2'
echo 'Stuff 3'
echo "::endgroup::"
4. Use your action in your workflow, reference it using relative path ./github/actions/...
name: Test Actions Docker
on: [push]
jobs:
run-my-github-actions:
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v1
- name: Use my Action
id: hello
uses: ./.github/actions/hello-docker
with:
who-to-say-hello: "Pepe Ch."
- name: Log Greeting output
run: echo "Pepe Docker Action Time ${{ steps.hello.outputs.time }}"
if you want your action to fail you just need to exit the
.shscript with an exit code != 0

repo/.github/workflows/unit_test.yaml and does the minimal to run a unittest python script if this fails make sure your requirements.txt doesn't have issues and unit test python script works
name: Run Python Unit Test
on: push
jobs:
run-py-unit-test:
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v2
- name: Setup Python 3.8
uses: actions/setup-python@v2
with:
python-version: '3.8'
- name: Install Python requirements
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run Unit tests
run: |
python -m unittest tests/my_unit_test.py -v
If you find the information in this page useful and want to show your support, you can make a donation
Use PayPal
This will help me create more stuff and fix the existent content...