[ Blog ] → Consulting

Our software testing checklist before every release

The checklist we run on Laravel and React releases, from the pull request to the hour after deploy, with the tests and commands behind each step.

Rainer Rodriguez

Co-founder, Nerdify

Aug 4, 2026 · 8 min read

A software testing checklist is the list of checks a change has to pass before it reaches users. Ours has five stages: the pull request (automated tests and review), the merge to staging (migrations and build), the check on staging (flows, roles, devices), the deploy to production, and the first hour after it.

We use this list on the Laravel, React and React Native products we build and maintain. It is written for a small team that ships often, so it leans on automation and keeps the manual part short enough that people do it every time.

Stage 1: The pull request

Every change starts as a pull request, and nothing merges until these pass.

  • The automated test suite passes in CI.
  • New behavior has tests: a feature test for each endpoint or page the change touches, a unit test for logic with branches.
  • Permission changes have a test per role, including the roles that should be refused.
  • Formatting and static analysis pass.
  • The author has used the feature in a browser or on a device, with the empty state, a long value and a slow network.
  • A second engineer has reviewed the code against the ticket's acceptance criteria.

Permission tests catch the bugs nobody reports, since a user who can see too much rarely complains. Pest datasets make a role matrix cheap to write:

php
it('restricts invoice exports by role', function (string $role, int $status): void {
    $user = User::factory()->withRole($role)->create();

    $this->actingAs($user)
        ->get('/invoices/export')
        ->assertStatus($status);
})->with([
    'admin' => ['admin', 200],
    'billing' => ['billing', 200],
    'staff' => ['staff', 403],
    'viewer' => ['viewer', 403],
]);

Adding a role later means adding one line, and the test tells you if the new role can do something it shouldn't.

Our CI runs on GitHub Actions for most projects. The core of the workflow is short:

yaml · .github/workflows/tests.yml
name: tests
on: [pull_request]

jobs:
  tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: shivammathur/setup-php@v2
        with:
          php-version: '8.5'
      - run: composer install --no-interaction --prefer-dist
      - run: npm ci && npm run build
      - run: cp .env.example .env && php artisan key:generate
      - run: php artisan test --parallel

We add the formatter and static analysis as separate steps so a failure says exactly what broke.

Stage 2: Merging to staging

When a change merges, it deploys to staging automatically. Before anyone tests it there, we check the parts that break deploys rather than features.

  • Migrations run cleanly against a copy of production-sized data, not only an empty database.
  • A migration that rewrites a large table has been timed, because a lock on a busy table during deploy takes the app down.
  • New environment variables and config values exist on staging and are listed for production.
  • Queued jobs from the old code can still be processed by the new code. A job serialized before the deploy and run after it is a common source of errors.
  • The front-end build has no new warnings, and the bundle size hasn't jumped.

php artisan migrate --pretend prints the SQL a migration will run without running it. We read that output for anything touching a large table.

Stage 3: Testing on staging

Staging is where a person checks what the automated tests can't: whether the feature makes sense, and whether it works with real-looking data.

  • Each acceptance criterion in the ticket is checked by someone other than the author.
  • The main flows of the product still work end to end, including the ones the change didn't touch. On most products we keep a short list of five to ten flows, such as sign-up, checkout or booking, that get clicked through before every release.
  • Each affected role has been tried with a real account for that role.
  • Emails and notifications arrive, with the right content and links that point to staging, not production.
  • The pages work at phone width and in Safari, which is where layout bugs often show up for us.
  • Forms show validation errors next to the right fields, and the submit button can't create duplicates on a double click.

For the flows that matter most, we automate the click-through with browser tests. Pest's browser plugin runs them on Playwright from the same test suite:

php
it('lets a player book a spot in a game', function (): void {
    $player = User::factory()->create();
    $game = Game::factory()->withOpenSpots(3)->create();

    $this->actingAs($player);

    visit("/games/{$game->id}")
        ->click('Book a spot')
        ->press('Confirm booking')
        ->assertSee('You are booked')
        ->assertNoJavaScriptErrors();
});

Browser tests are slower and more fragile than feature tests, so we keep them for the flows where a failure would stop the business, and we run them before release rather than on every push.

For mobile releases the device matrix replaces the browser check: the oldest iOS and Android versions we support, a small screen, and a test build installed from TestFlight or an internal track on a real device. 1Click's iOS and Android app, for example, goes through separate development, testing and production environments, with unit and end-to-end tests, before each store release.

We keep a short list of five to ten flows that get clicked through before every release.

Stage 4: The production deploy

The deploy itself has a short checklist, and it is the same every time.

  • The client has approved the release on staging.
  • Someone who can fix things is available for the next hour. We don't deploy on a Friday evening.
  • A database backup taken in the last 24 hours exists and has been restored at least once.
  • Larger features are behind a feature flag, so turning them off doesn't need a new deploy.
  • The plan for going back is written down. Our default is to roll forward with a fix, since rolling back database changes is risky, so we keep migrations additive where we can: add the new column, deploy code that writes to both, remove the old one in a later release.

If we could keep only one habit from this list, it would be additive migrations. A migration that renames a column breaks the old code the moment it runs, and during a deploy the old code is still serving requests for a few seconds.

Stage 5: The first hour after deploy

The release isn't done when the deploy finishes.

  • Error monitoring shows no new exception types.
  • The queue is processing and the failed jobs table isn't growing.
  • Response times on the main pages are in their usual range.
  • The engineer who shipped the change tries the main flow in production with a real account.
  • The client gets a short note: what changed, what to look at, and how to reach us if something looks wrong.

Problems that reach production often show up in this hour, and finding them here beats finding them through a support ticket the next morning.

What we leave off the list

There is no coverage percentage on the checklist. We have seen suites at high coverage that never assert the thing that matters, and suites with modest coverage that test every permission and every payment path. We would rather review which behaviors have tests than chase a number.

We also don't run load tests before every release. They go on the list for specific changes: a new endpoint the mobile app will call on every launch, a report that scans a large table, or a client expecting a traffic spike from a campaign. For an ordinary feature release, response times in the first hour after deploy tell us what we need to know.

Keep the checklist short

A checklist with 80 items gets skipped. Ours fits on one screen per stage, and we prune it. When an item hasn't caught anything in a long time and is covered by an automated test, we remove it. When a bug reaches production, we ask which item would have caught it, and if none would, we add one.

The list lives in the repository as a pull request template and a release template, so it is in front of the engineer at the moment it matters.

Read also · Product development

Quality assurance in a small product team

Aug 11, 2026 · 7 min read

[ Read → ]

If you want a team to set up this kind of release process on your product, or to review the one you have, see our consulting services.

Written by

Rainer Rodriguez

Co-founder, Nerdify

Rainer co-founded Nerdify in Managua, Nicaragua, in 2016 and works with the team on the client products it builds and maintains.

Service · Consulting

Need a team that works like this?

More from the blog

[ All articles → ]
  • Consulting

    How to choose a technology stack

    Choose the stack your team can build and maintain for years, that fits what the product does, and that you can hire for. How we decide, with examples.

    Carlos Saldaña

    Sep 8, 2026 · 7 min read

  • Consulting

    What we check in a web app security audit

    A website security audit reviews code, configuration, dependencies and infrastructure for weaknesses. This is the list we work through, in order.

    Rainer Rodriguez

    Aug 18, 2026 · 7 min read

  • Nearshore

    Nearshore vs. offshore: What changes for your team

    Nearshore teams work in or near your time zone. Offshore teams are far away, often half a day ahead. What that changes for cost, speed and how you work.

    Carlos Saldaña

    Oct 20, 2026 · 7 min read