If you are looking for a way to automatically keep your project dependencies up to date, I have a solution for you.
You might be like me and have a one or two personal projects that you haven’t touched for a while. When you finally get back to them, you realize that there are a lot of dependencies that needs to be updated. It can be a tedious task to go through all of them and check if there are any new versions available. You might also find yourself in a situation where the dependencies are so out-dated, that it would be easier to start everything from scratch, rather than migrating to new versions.
The following guide is based on a Next.js project, but the same principles can be applied to other projects based on different programming languages and frameworks.
This is done easily by adding a dependabot.yml
file inside the .github
folder in your repository. Here is an example of how the file could look like:
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "npm"
open-pull-requests-limit: 10
directory: "/"
schedule:
interval: "daily"
time: "08:00"
timezone: "Europe/Helsinki"
To quickly go through the main parts of the file:
package-ecosystem
specifies the package manager that Dependabot should look for updates for. In this case, it’s npm. open-pull-requests-limit
specifies the maximum number of open pull requests that Dependabot can have at the same time. directory
specifies the directory where Dependabot should look for updates. In this case, it’s the root directory. schedule
specifies how often Dependabot should check for updates. In this case, it’s daily at 8:00 AM in the Europe/Helsinki timezone. To make sure that the updates don’t break your application , you can add Playwright tests to your CI pipeline.
You should follow the official installation guide to install Playwright.
// tests/example.spec.ts
import {test, expect} from '@playwright/test';
const BASE_URL = 'http://localhost:3000';
test('verify title', async ({page}) => {
await page.goto(BASE_URL);
await expect(page).toHaveTitle(/Page title/);
});
In the test we are simply visiting the local development server and checking that the title of the page matches the regex /Page title/
.
You can add visual regression tests to your Playwright tests by using the toHaveScreenshot
matcher. Here is an example of how you could add a visual regression test to the previous test file:
test("regression test", async ({page}) => {
await page.goto(BASE_URL)
await expect(page).toHaveScreenshot('frontpage.png', {fullPage: true});
})
You can read more about the visual regression testing with screenshots from the official documentation here .
You can create a GitHub Actions workflow file that runs the tests on every pull request. Here is an example of how the file could look like:
# .github/workflows/playwright.yml
name: Playwright tests
on:
pull_request:
jobs:
playwright-tests:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- name: Install dependencies
run: npm i
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Build app
run: npm run build
- name: Run Playwright tests
id: run-tests
run: npm run test
To automatically approve and merge the Dependabot pull requests , you can create a GitHub Actions workflow file that listens for new pull requests from Dependabot and automatically approves and merges them.
Here is an example of how the file could look like:
# .github/workflows/dependabot.yml
name: Dependabot auto-approve
on: pull_request
permissions:
contents: write
pull-requests: write
jobs:
dependabot:
runs-on: ubuntu-latest
if: github.actor == 'dependabot[bot]'
steps:
- name: Dependabot metadata
id: metadata
uses: dependabot/fetch-metadata@v2
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
- name: Approve a PR
run: gh pr review --approve "$PR_URL"
env:
PR_URL: ${{github.event.pull_request.html_url}}
GH_TOKEN: ${{secrets.GITHUB_TOKEN}}
- name: Enable auto-merge for Dependabot PRs
run: gh pr merge --auto --merge "$PR_URL"
env:
PR_URL: ${{github.event.pull_request.html_url}}
GH_TOKEN: ${{secrets.GITHUB_TOKEN}}
Here’s a quick recap what we have done so far:
There are still a few caveats to this setup that you should be aware of:
I was able to find a workaround for the above mentioned by creating a single workflow file, that listens for both the Dependabot and Playwright events. The workflow file then runs the Playwright tests and only approves and merges the Dependabot pull request if the tests pass.
It’s not dependant on branch protection rules nor any 3rd party actions. It will still work even if I decide to keep the repository private.
Here’s an example of a final workflow file that was able to achieve this for me personally and have been running successfully for a while now:
# .github/workflows/ci.yml
name: CI checks
on:
push:
permissions:
pull-requests: write
contents: write
statuses: read
checks: read
actions: read
jobs:
lint:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.tool-versions'
cache: 'npm'
- run: npm ci
- run: npm run lint
playwright-tests:
runs-on: ubuntu-latest
timeout-minutes: 5
needs:
- lint
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.tool-versions'
cache: 'npm'
- name: Cache Node Modules
id: cache-node-modules
uses: actions/cache@v4
with:
path: node_modules
key: node-modules-${{ hashFiles('package-lock.json') }}
- name: Install dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: npm i
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Next.js cache
uses: actions/cache@v4
with:
# See here for caching with `yarn` https://github.com/actions/cache/blob/main/examples.md#node---yarn or you can leverage caching with actions/setup-node https://github.com/actions/setup-node
path: |
~/.npm
${{ github.workspace }}/.next/cache
# Generate a new cache whenever packages or source files change.
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx') }}
# If source files changed but packages didn't, rebuild from a prior cache.
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
- name: Build app
run: npm run build
- name: Run Playwright tests
id: run-tests
run: npm run test:ci
- name: Upload Playwright report
uses: actions/upload-artifact@v3
if: failure() && steps.run-tests.conclusion == 'failure'
with:
name: playwright-report
path: playwright-report
retention-days: 3
- name: Update screenshots in visual tests
id: run-tests-update
if: failure() && steps.run-tests.conclusion == 'failure'
run: npm run test:update-snapshots
- name: Upload updated Playwright screenshots
uses: actions/upload-artifact@v3
if: failure() && steps.run-tests-update.conclusion == 'success'
with:
name: playwright-screenshots-updated
path: e2e/__screenshots__/**/*.png
retention-days: 3
dependabot-auto-approve-and-merge:
runs-on: ubuntu-latest
timeout-minutes: 5
if: github.actor == 'dependabot[bot]'
needs:
- playwright-tests
- lint
steps:
- uses: actions/checkout@v4
- uses: actions/github-script@v7
id: get_pr_data
with:
script: |
return (
await github.rest.repos.listPullRequestsAssociatedWithCommit({
commit_sha: context.sha,
owner: context.repo.owner,
repo: context.repo.repo,
})
).data[0];
- name: Pull Request data
id: pr_data
run: echo "{pull_request_number}=${{ fromJson(steps.get_pr_data.outputs.result).number }}" >> $GITHUB_OUTPUT
- name: Approve and Merge PR
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
bash .github/approve-and-merge.sh ${{ steps.pr_data.outputs.pull_request_number }}
And here’s a quick shell script that you can use to approve and merge the pull request based on the step results:
# approve-and-merge.sh
PR_NUMBER=$1
PR_CHECK_COMMAND=$(gh pr checks $PR_NUMBER)
# Check lines containing lint or playwright-tests and containing the word pass
lint_check=$(echo "$COMMAND" | grep -c -E '^lint.*pass' | wc -l | awk '{$1=$1};1')
playwright_tests_check=$(echo "$COMMAND" | grep -c -E '^playwright-tests.*pass' | wc -l | awk '{$1=$1};1')
# Exit code based on the result
if [ $lint_check -eq 1 ] && [ $playwright_tests_check -eq 1 ]; then
echo "Success: lint and playwright-tests both passed."
gh pr review $PR_NUMBER --approve
gh pr merge $PR_NUMBER --squash
exit 0
else
echo "Failure: lint and/or playwright-tests did not pass."
exit 1
fi
If you managed to get this far, I hope you found this guide helpful and that you were able to automate the process of keeping your project dependencies up to date. If you have any questions or feedback , feel free to reach out to me here !