Support

How can we help you?

Get answers to all your Stark-related questions with
step-by-step guides from our Support center.

Integrating Stark into your CI/CD pipeline

Using Stark's CI/CD integrations to automate accessibility checks at every stage of your build


Why CI/CD is the Sweet Spot for Accessibility Monitoring

Most accessibility tools give you a snapshot. Stark's CI/CD integration gives you a signal that fires on every build, every PR, every deploy. If you're shipping a lot of code, this is the best way to find out about any issues while the code is still fresh.

CI/CD is the prime place for Stark to validate the actual, running experience: components rendered in a real browser, with real data, in real states. That's a big step beyond static code analysis. If contrast breaks because of newly introduced colors, CI/CD catches it. If aria attributes are incorrect for dynamic elements, CI/CD catches it.

Pairing Stark with your CI/CD pipeline means:

  • Accessibility becomes part of the definition of "done"
  • Regressions get caught before they reach production
  • Every PR gets a clear, automated accessibility signal
  • Reports flow into Stark for long-term trend tracking

What Stark's CI/CD Integration Unlocks

Beyond one-off scans, a CI/CD integration gives you repeatable, scriptable access to everything a browser can do. That means you can:

  • Automatically scan changes before merge
  • Block releases that introduce critical issues
  • Put components into specific states and scan each one
  • Simulate real user flows (sign in, checkout, settings changes)
  • Restrict scans to specific elements when you need focus
  • Enforce accessibility standards at scale, across dozens of contributors

And because every scan can be named and pushed up to Stark, you get a historical record of how your app's accessibility has changed over time, broken down per flow, per page, per component state.


Getting Started

Pick Your Framework

Stark supports the three most common E2E testing frameworks. If you're already running E2E tests, you're almost certainly using one of these — stick with what your team already knows.

  • Puppeteer — Great for simple scripted flows and Chromium-only setups
  • Playwright — Best for cross-browser testing and modern test ergonomics
  • Cypress — Best if your team is already writing Cypress tests and wants the familiar cy chainable API

The scan itself works the same way across all three — you'll call StarkScan (or cy.starkScan) on whatever you want to test, and a results summary comes back. Which framework you use only affects the surrounding glue code.


Set Up Your Stark Project

Before writing any test code, create a home for your results in Stark:

  1. Log into your Stark account
  2. Click Create a Project or select an existing project
  3. Scroll to Import results from your CI/CD workflow
  4. Copy the provided project token — you'll use it in the integration steps below
  5. Click Save CI/CD integration

Once that's done, any scans you run with that token will flow into the project automatically and live alongside your Figma files, URL scans, Storybook results, and any other assets attached to the project.

You'll also need an API key to install the Stark CI packages from our private NPM registry. Only team admins can generate API keys.

To generate an API key:

  1. Log into your Stark account
  2. Click Team Settings
  3. Scroll to API Keys and click Generate API Key
  4. Follow the instructions in the dialog for installing the CI packages

Install and Authenticate

Add the Stark NPM registry to your .npmrc file (or create one at the root of your project if you don't have one)

@stark-ci:registry=https://npm.getstark.co
//npm.getstark.co/:_auth=<<encoded Stark API key>>

Then install the package for your framework

npm install @stark-ci/playwright
# or @stark-ci/puppeteer, or @stark-ci/cypress

Keep your project token out of source control — store it as an environment variable (STARK_PROJECT_TOKEN is a good default) and read it in from your test code. The Stark Playwright demo repo uses a .env.example file to show this pattern; copy that approach in your own repo.


Your First Scan

Here's the simplest possible Playwright test that scans a page and pushes results to Stark

const { test, expect } = require('@playwright/test');
const { StarkScan } = require('@stark-ci/playwright');

test('home page is accessible', async ({ page }) => {
  await page.goto('https://your-app.com/');

  const results = await StarkScan(page.mainFrame(), {
    wcagVersion: '2.2',
    sendResults: true,
    name: 'Home page',
    token: process.env.STARK_PROJECT_TOKEN,
  });

  expect(results.failed).toBe(0);
});

A few things are happening here:

  • sendResults: true pushes the scan up to Stark so it appears in your project's Reports & Insights
  • name is how this scan is identified in Stark — names must be unique per project, so give each scan something descriptive
  • The expect at the end is what turns accessibility results into a build pass/fail — tweak the threshold to match your team's tolerance

If you'd rather see a working end-to-end example, clone the Playwright demo repo and point it at your own project token. It's the fastest path from zero to a working setup.


Advanced: Scanning a Multi-Step Flow

This is where CI/CD really pulls ahead of every other kind of accessibility scanning. A URL scan can tell you whether a page is accessible. A CI/CD scan can tell you whether every state of every step of a flow is accessible.

Let's walk through a real example: scanning a sign in flow.

A typical sign in flow has at least four distinct states worth checking:

  1. The empty form on first load
  2. The form with a validation error (e.g., invalid email)
  3. The form in a submitting / loading state
  4. The authenticated landing page

A single URL scan would only see state #1. A CI/CD scan can walk through all four.


Structuring the Test

Here's what this looks like in Playwright:

const { test, expect } = require('@playwright/test');
const { StarkScan } = require('@stark-ci/playwright');

test('sign in flow is accessible end-to-end', async ({ page }) => {
  const token = process.env.STARK_PROJECT_TOKEN;

  // 1. Empty sign in form
  await page.goto('https://your-app.com/sign-in');
  await StarkScan(page.mainFrame(), {
    sendResults: true,
    name: 'Sign in — empty form',
    token,
  });

  // 2. Validation error state
  await page.fill('[name="email"]', 'not-an-email');
  await page.click('button[type="submit"]');
  await page.waitForSelector('[role="alert"]');
  await StarkScan(page.mainFrame(), {
    sendResults: true,
    name: 'Sign in — validation error',
    token,
  });

  // 3. Submitting state — scan just the form while it's loading
  await page.fill('[name="email"]', 'user@example.com');
  await page.fill('[name="password"]', process.env.TEST_USER_PASSWORD);
  await page.click('button[type="submit"]');
  await StarkScan(page.mainFrame(), {
    sendResults: true,
    name: 'Sign in — submitting',
    token,
    elementSelector: 'form',
    scanDepth: 'deep',
  });

  // 4. Authenticated landing page
  await page.waitForURL('**/dashboard');
  const results = await StarkScan(page.mainFrame(), {
    sendResults: true,
    name: 'Sign in — authenticated dashboard',
    token,
  });

  expect(results.failed).toBe(0);
});

Want a way to speed up writing these tests? Check out how you can use the Chrome Recorder feature to export steps in this video.


What's Happening Here

A few patterns worth pulling out:

  • Every scan has a unique name. This is what makes the results usable in Stark. If all four scans shared a name, they'd overwrite each other and you'd lose the state-by-state breakdown.
  • Use elementSelector + scanDepth when you want to focus. In state #3 we scan just the form because the loading state is really about the form — scanning the whole page would mix in unrelated elements and noise up the report.
  • Wait for the state to actually render before scanning. waitForSelector and waitForURL make sure the UI has settled. A scan against a mid-transition DOM is a scan against nothing useful.
  • Test credentials go in environment variables. Don't commit real users, and don't commit tokens. Use a dedicated test account with a known password stored as a secret in your CI provider.

The same pattern works for any multi-step flow — checkout, onboarding, account deletion, forgot password, a multi-step form. Anywhere a user sees more than one state, scan each one.


Going Further: Per-Criterion Assertions

Sometimes a single pass/fail number isn't the right threshold. Maybe you're willing to ship with a few potential issues but any contrast failure is an immediate block. Stark's results break down by WCAG criteria, so you can assert on the specific rules you care about:

const results = await StarkScan(page.mainFrame(), {
  wcagVersion: '2.2',
  sendResults: true,
  name: 'Dashboard',
  token: process.env.STARK_PROJECT_TOKEN,
});

// Block the build on any contrast failure specifically
expect(results.resultsByCriteria['1.4'].failed).toBe(0);

// But allow up to 5 "potentials" overall while the team triages
expect(results.potentials).toBeLessThan(5);

This lets you hold different parts of your app to different standards, or ratchet up the strictness over time as your team closes out existing issues.

Additionally, you can also focus just on specific categories and/or severities for further filtering. For both of those options, you pass them in as options in the StarkScan call like so

const results = await StarkScan(page.mainFrame(), {
  wcagVersion: '2.2',
  sendResults: true,
  name: 'Dashboard',
  categories: ["Color & Contrast", "Forms"],
  severity: ["high"],
  token: process.env.STARK_PROJECT_TOKEN,
});

Best Practices for a Robust CI/CD Setup

A few habits that separate a CI/CD accessibility setup that actually gets used from one that gets disabled three sprints in:

  • Name every scan clearly and consistently — Names should describe the state being scanned, not just the page. "Checkout — address step with errors" beats "Checkout page 2."
  • Use secrets for tokens and test credentials — Never commit a project token or test account password. Use your CI provider's secret management.
  • Set realistic thresholds to start — If you switch on a hard failed === 0 check on day one against an existing app, you'll break the build for everyone. Start with today's baseline as the ceiling and ratchet it down.
  • Use sendResults: "errorOnFailure" for the scans you care about most — This turns failed uploads into test failures, so you'll know if results stop flowing to Stark rather than silently losing them.
  • Scan states, not just pages — Empty states, error states, loading states, and success states all count as distinct scans. A page that passes in its default state can easily fail with a validation error visible.
  • Run scans on PRs, not just on main — Catching issues in review is cheaper than fixing them after merge. Add Stark scans to your PR checks, not just your nightly runs.
  • Keep scans fast and focused — Use elementSelector to narrow scans when you don't need a full-page report. Faster scans mean more scans.

Augmenting Your CI/CD Pipeline with Other Stark Integrations

CI/CD is powerful, but it's most powerful when it isn't working alone. The whole point of Stark is to catch accessibility issues as early and as often as possible. Every issue caught upstream is an issue your CI pipeline doesn't have to block a release over.

Here's how the other Stark integrations complement CI/CD scanning.


Catch Issues in Design with the Figma Plugin

The earlier you catch an issue, the cheaper it is to fix. The Stark Figma plugin is where the cheapest fixes happen — at the design stage, before a single line of code has been written. Encourage designers to keep it open while working, and your CI pipeline will have a lot less to complain about.


Catch Issues at Write-Time with Source Code Integration

Stark's Source Code Integration lints your code as you write it — red squiggly lines in your editor, just like TypeScript errors. It supports React, Vue, Angular, React Native, and Flutter.

Pair this with CI/CD and you get a two-stage safety net:

  • Source code linting catches structural issues (missing alt text, bad ARIA, invalid heading levels) before the code ever runs
  • CI/CD scans catch runtime and contextual issues (contrast in real states, focus order, dynamic content)

The CLI tool that ships with the source code integration can also be plugged directly into your pipeline for a broader static scan of your whole repo — a nice complement to the browser-based checks.


Validate Components in Isolation with the Storybook Addon

If you have a component library or design system with Storybook, the Stark Storybook Addon gives you WCAG Audits right next to each story. It can also automate building your Storybook, scanning each story, and sending the results up to Stark.

This is a great complement to CI/CD app scans because it lets you isolate component-level issues from integration-level issues. If a button fails contrast in your Storybook scan, it's a component problem. If it only fails in your CI scan of the live app, it's a composition or theming problem.


Verify in Real Browsers with the Stark Browser Extension

The Stark browser extension runs the same kinds of checks your CI pipeline runs, but on-demand, in any browser tab. It's the right tool for:

  • Quickly verifying a fix after CI flagged an issue
  • Running a manual WCAG Audit on a staging environment
  • Checking behavior that's hard to script (complex keyboard navigation, screen reader interactions)

Audits from the extension can be sent up to Stark too, so manual QA sessions contribute to the same project timeline your CI scans populate.


Monitor Live URLs with URL Scanning

CI/CD scans run on the code you're about to ship. URL scans run on what's actually live. Add your production and staging URLs as URL scan targets in Stark and you'll catch anything that slips past CI — content changes, third-party widget regressions, CDN issues, anything that can affect a page after it's deployed.

URL scans support authenticated pages as well, which makes them a nice safety net for internal tools and logged-in experiences.


Summary

CI/CD is where accessibility stops being a project and starts being a process. When Stark is wired into your pipeline:

  • Every PR gets an automated accessibility signal
  • Every state of every flow can be validated
  • Every regression gets caught before it ships
  • Every scan contributes to a long-term record in Stark

Pair that with Stark in design (Figma), at write-time (source code integration), in components (Storybook), in the browser (extension), and on live URLs (URL scanning), and accessibility becomes something your team maintains by default rather than rediscovers every quarter.

That's the goal: accessibility with the same rigor as privacy and security.

Have any questions about setting up Stark in your CI/CD pipeline? Don't hesitate to reach out to us at support@getstark.co.

Go back to articles