Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Playwright Test Selector

CI

A GitHub Action that runs only the Playwright specs a pull request can actually affect — and escalates to the full suite whenever it can't justify narrowing.

- uses: sjzavala/playwright-test-selector@v1
  id: select
  with:
    test-dir: tests

- run: npx playwright test ${{ steps.select.outputs.specs }}

That's it. Two steps, no config file, no maintained mapping table.


The problem with "smart" test selection

Most diff-to-test tooling infers the mapping — from filename similarity, from import graphs, from coverage traces that go stale the moment someone refactors. The inference is invisible, so when it's wrong you find out by shipping a regression the suite could have caught.

This action doesn't infer. It reads a declaration the test itself carries:

/**
 * @qase-id  BOR-12
 * @covers   server/routes.js client/src/App.jsx
 * @guards   BUG-3 — lowercase query returned zero results (server/routes.js:37-39)
 */
test('BOR-12 — search is case-insensitive', { tag: ['@regression'] }, async ({ page }) => {

@covers is written by whoever wrote the test, at the moment they knew exactly what it exercised. The action parses it, maps the diff, and shows its work.

The guarantee

Narrowing never happens on a guess. Every rule either explains itself or escalates:

Rule Effect
spec-changed a changed spec always runs itself
covers-glob changed file matches a spec's @covers
guards-path changed file is cited in a spec's @guards prose
smoke anything tagged @smoke runs on every PR
infra changed lockfiles, playwright.config.*, workflows → full suite
unmapped file no spec claims the changed file → full suite

Those last two are the point. A repo with zero @covers annotations gets correct results on day one — it simply always runs everything — and gets faster as annotations accumulate. There is no adoption cliff, and no state where the action quietly skips the one test that mattered.

Every decision lands on the PR:

🧪 Playwright Test Selector

Selected 1 of 2 specs.

Spec Case Why it was selected
tests/smoke.spec.js BOR-1 tagged @smoke
declared in @covers → server/index.js
Skipped 1 spec
  • tests/search-case-insensitive.spec.js (BOR-5)

Usage

As a step

name: PR tests
on:
  pull_request:
    types: [opened, synchronize, reopened, labeled, unlabeled]

permissions:
  contents: read
  pull-requests: write

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
        with:
          fetch-depth: 0        # required — the selector diffs against the merge base

      - uses: actions/setup-node@v7
        with:
          node-version: '22'
      - run: npm ci
      - run: npx playwright install chromium

      - uses: sjzavala/playwright-test-selector@v1
        id: select
        with:
          test-dir: tests
          label: use-intelligent-runner   # optional: only narrow on labelled PRs
          comment: 'true'

      # Unquoted on purpose: an empty value correctly runs the whole suite.
      - run: npx playwright test ${{ steps.select.outputs.specs }}

As a whole job

If you don't already have a Playwright job:

jobs:
  tests:
    uses: sjzavala/playwright-test-selector/.github/workflows/intelligent-runner.yml@v1
    permissions:
      contents: read
      pull-requests: write
    with:
      test-dir: tests

Locally

The engine is a plain Node script with no dependencies:

node scripts/select-tests.mjs --changed "server/routes.js" --test-dir tests

Inputs

Input Default Description
test-dir tests Directory containing Playwright specs.
changed-files (derived) Newline/comma-separated list. Omit to diff against the PR's merge base.
label '' Only narrow when the PR carries this label. Empty means always narrow.
config .qa-tms/selection.json Optional escalation-glob overrides.
comment false Post the selection table as a PR comment. Needs pull-requests: write.
github-token ${{ github.token }} Token used for the comment.

Outputs

Output Description
mode subset when narrowing was safe, full when it escalated.
specs Space-separated spec paths. Empty in full mode — pass it unquoted and an empty value naturally runs everything.
selected-count / total-count For badges, summaries, or your own gating.
summary Path to the markdown explaining the selection.

Writing @covers

Name what the test actually exercised — the endpoints the run hit, the components that rendered. Prefer a file over a directory, and a directory over a wildcard:

 * @covers   server/routes.js client/src/**/*.jsx

Over-broad globs are not a safe hedge. server/** on every spec makes the map useless and quietly returns you to running everything.

Omitting it is safe but costly: an unclaimed file escalates that PR to the full suite. That's the intended fallback, not a failure.

Tuning escalation

Per-repo, in .qa-tms/selection.json:

{
  "infraGlobs": ["playwright.config.*", "package.json", "src/testing/**"],
  "ignoreGlobs": ["**/*.md", "docs/**"]
}

How this is tested

The logic that decides what not to run is the last place to accept "looks right":

  • 32 unit tests over glob matching, header parsing, and every selection rule — including the escalation paths, which are the ones that keep it safe.
  • A self-test job where the action runs itself against fixture specs in examples/ and asserts on its own outputs: that it narrows, that it escalates on an unclaimed file, that a path cited only in @guards prose still maps, and that the label gate holds.

Where this came from

The @covers / @guards header is the traceability format emitted by qa-tms, which generates Playwright specs from managed test cases. This action works with any spec carrying the header — the plugin is not required.

See it running on a real PR in borrower-search.

License

MIT

About

GitHub Action: run only the Playwright specs a PR can affect. Reads @Covers declarations from spec headers, and escalates to the full suite whenever narrowing can't be justified.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages