Add a Stealth Score Check to Your CI Pipeline
You pin your Node.js version. You lock your dependencies. You run tests on every PR. But do you test whether your automated browser is still undetectable?
Browser updates, dependency bumps, and stealth plugin changes can silently break your fingerprint. A Chromium update might expose a new automation artifact. A Playwright upgrade might change how bindings are injected. By the time you notice an increased block rate, you've already lost data.
Here's how to add a stealth score check to your CI pipeline.
How it works
- Your CI pipeline launches a browser (the same way your production code does)
- The browser visits a StealthCheck monitoring URL
- StealthCheck analyzes the fingerprint and returns a score
- Your pipeline fails if the score drops below a threshold
This requires a StealthCheck monitoring profile, which provides a token for the lightweight check page. Monitoring profiles are available on paid plans.
Setup
1. Create a monitoring profile
Create an account and set up a browser profile. Set the target browser and OS to match your production setup. On the profile detail page, you'll see a monitoring URL (e.g., https://stealthcheck.io/check/a1b2c3d4-...). Copy the full URL and store it as a STEALTH_CHECK_URL secret in your CI provider.
2. Add the check to your test suite
The lightweight check page at /check/{token} runs the fingerprint analysis and exposes the result on window.__stealthcheck. The page adds a .done CSS class to the status element when analysis is complete.
Playwright
// stealth.spec.ts
import { test, expect } from '@playwright/test'
test('stealth score meets threshold', async ({ page }) => {
await page.goto(process.env.STEALTH_CHECK_URL)
await page.waitForSelector('.done', { timeout: 30000 })
const result = await page.evaluate(() => (window as any).__stealthcheck)
expect(result.score).toBeGreaterThanOrEqual(90)
if (result.score < 95) {
console.warn(`Stealth score: ${result.score}/100`)
console.warn(
'Issues:',
result.issues.map((i: { code: string }) => i.code).join(', '),
)
}
})
Puppeteer
// stealth.test.mjs
import puppeteer from 'puppeteer'
const browser = await puppeteer.launch({
headless: true,
// Use the same launch args as your production config
})
const page = await browser.newPage()
await page.goto(process.env.STEALTH_CHECK_URL)
await page.waitForSelector('.done', { timeout: 30000 })
const result = await page.evaluate(() => window.__stealthcheck)
if (result.score < 90) {
console.error(`Stealth score too low: ${result.score}/100`)
console.error(
'Issues:',
result.issues.map((i) => i.code).join(', '),
)
process.exit(1)
}
console.log(`Stealth score: ${result.score}/100`)
await browser.close()
3. Configure your CI
GitHub Actions (Playwright)
name: Stealth Check
on: [push, pull_request]
jobs:
stealth:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 22
- run: npm ci
- run: npx playwright install chromium
- name: Run stealth check
run: npx playwright test stealth.spec.ts
env:
STEALTH_CHECK_URL: ${{ secrets.STEALTH_CHECK_URL }}
GitHub Actions (Puppeteer)
name: Stealth Check
on: [push, pull_request]
jobs:
stealth:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 22
- run: npm ci
- name: Run stealth check
run: node stealth.test.mjs
env:
STEALTH_CHECK_URL: ${{ secrets.STEALTH_CHECK_URL }}
GitLab CI
stealth-check:
image: mcr.microsoft.com/playwright:v1.52.0
script:
- npm ci
- npx playwright test stealth.spec.ts
variables:
STEALTH_CHECK_URL: $STEALTH_CHECK_URL
Choosing a threshold
- 95+ is strict. Minor Chrome updates may cause temporary dips.
- 90 catches significant regressions while tolerating minor fluctuations.
- 85 is lenient. Acceptable if your use case tolerates some detection risk.
Reading the results
When the score drops, the issues array tells you what changed:
{
"score": 82,
"issues": [
{
"code": "webdriver_true",
"category": "automation",
"severity": "critical",
"title": "navigator.webdriver is true",
"description": "...",
"recommendation": "..."
},
{
"code": "plugins_missing",
"category": "consistency",
"severity": "warning",
"title": "No browser plugins detected",
"description": "...",
"recommendation": "..."
}
]
}
Each issue includes a category, severity (critical, warning, info), and a recommendation for how to fix it.
Common CI regressions
| Cause | Example | Fix |
|---|---|---|
| Chrome update | New version changes WebGL behavior or adds APIs | Pin Chrome version or update stealth patches |
| Playwright/Puppeteer update | Changed launch flags or binding injection | Pin version, test before upgrading |
| CI environment change | New runner image with different fonts or locale | Set explicit font and locale configuration |
| Dependency update | Stealth plugin changed or removed a patch | Pin plugin version, check changelog |
Further reading
- Run a free test to see your current score
- Is my Puppeteer bot detectable?
- Playwright stealth testing
Test your browser stealth for free
Run a free fingerprint test and see exactly what detection systems see.