I Automated Timesheets with Github Actions and Playwright

I don’t like silly tasks.

My legal employer has me fill in my hours every week, despite me being full-time with the subcontracted company. My hours don’t change. I am basically salaried, indirectly. It’s a long story. (Working remote from Hawai’i has its perks and its quirks.)

If I’m honest, it’s not that bad. The timesheet has an “autofill” feature so I usually just click a couple buttons and then I’m done.

A couple months ago, I forgot to click those buttons.

My only job (other than my job) is to tell my job that I did my job.

The money didn’t land. We were in the middle of moving money around for a family event and the money not showing up became a little stressful. It was my fault, but I was still irritated.

I decided to setup 2 reminders instead of 1 reminder to fill in my hours every week.

This worked well for a couple months. But as a software engineer I knew I could do better.

I went searching for some simple cron hosting sites. I was hoping that replit.com had free cron triggers but no luck. I didn’t care what I used, as long as it was easy to setup and free.

After a quick search, it turns out the easiest and cheapest thing to do what I wanted was to use Playwright and Github Actions. I also had email notifications as a requirement for success/failures and Github Actions has that built in!

I setup a new Playwright project, add a couple lines of code for an “end-to-end” test (despite it not being a test, but a cron task) and then created the Github Action and voilĂ  — timesheets automatically filled out every week. Github even tells me via email if the task completed or not. Perfect!

Here’s how I set it up. You can use this template for anything, really. The Playwright and Github Actions combo is awesome!

First, I created a playwright project (docs):

npm init playwright@latest

I followed the prompts to create a TypeScript project and had it setup Github Actions stuff for me.

Next, I installed dotenv to keep my secrets out of source code:

npm i dotenv@latest

I also deleted the test examples. Don’t need those.

Then I wrote my test. I found it easier to debug it by using the visual test runner that playwright provides. You can start it with:

npx playwright test --ui

Here’s our test:

import { test } from "@playwright/test";

test("fill timecard", async ({ page }) => {
  await page.goto(process.env.LOGIN_URL);
  await page.click("button:has-text('Accept Cookies')");
  await page.click("text=Contingent Workers");
  await page.click(".login-input-container input");
  await page.waitForTimeout(1000);
  await page.keyboard.type(process.env.USERNAME, { delay: 100 });
  await page.keyboard.press("Tab");
  await page.keyboard.type(process.env.PASSWORD, { delay: 100 });
  await page.keyboard.press("Tab");
  await page.click("[type=submit]");
  await page.waitForLoadState();
  await page.click("td a img");
  await page.click("text=Auto-Fill Timecard");
  page.on("dialog", (dialog) => dialog.accept());
  await page.click("text=Fill Timecard");
  await page.click("input:has-text('Submit To Manager')");
  await page.waitForSelector("text=Submitted", { state: "visible" });
});

You can see it’s pretty simple. It requires 3 environment variables to run properly, LOGIN_URL, USERNAME, and PASSWORD.

Next thing is to create a repository on Github.com. I did so and then uploaded everything.

Lastly, we told Github Actions we’d like to run this every week. I used the pre-generated .github/workflows/playwright.yml as a template and modified it to look as follows:

# cron.yml
name: Fill timecard
on:
  schedule:
    - cron: 0 16 * * 1
  workflow_dispatch:

jobs:
  fill-timecard:
    timeout-minutes: 15
    runs-on: ubuntu-latest

    env:
      LOGIN_URL: ${{ secrets.LOGIN_URL }}
      USERNAME: ${{ secrets.USERNAME }}
      PASSWORD: ${{ secrets.PASSWORD }}

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: lts/*

      - name: Install dependencies
        run: npm ci

      - name: Install Playwright Browsers
        run: npx playwright install --with-deps

      - name: Run Playwright tests
        run: npx playwright test

There’s a couple thing to note here.

First is the cron schedule string at the top. We told Github Actions to bootstrap a runner to execute those jobs every Monday at 16:00 UTC (if you’re as confused about cron format, head over to crontab.guru).

Second is this weird workflow_dispatch: line. We told Github Actions to allow “dispatching” from a cute green button, so that we can run this action whenever we feel like without having to wait until Monday or pull down the repository.

Cute green workflow_dispatch button

Next lines are boilerplate. We told actions we wanna create a job and then gave it a timeout and specified the runner that we wanted. (Note: linux always cheaper, MacOS is ridiculously expensive. I am cheap.)

In order to login to our site, we needed to setup those environment variables.

I added some secrets LOGIN_URL, USERNAME and PASSWORD to the Github repo. (Settings > Secrets and Variables > Actions > Repository Secrets > New Repository Secret) and created all three of those with the proper values.

And there it is!

Now my timecard is automatically filled every Monday, I get back 2 minutes of my day, and I have some peace of mind with email notifications when things fail.

Subscribe for more nerdy content.

Leave a comment

Your email address will not be published. Required fields are marked *