Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
6c6dbf9
Restructure test suite and import the SDK by package name
dzmitry-duboyski Aug 6, 2026
14c1854
Add public API contract test
dzmitry-duboyski Aug 6, 2026
f7cdc5f
Add Jest config with coverage collection settings
dzmitry-duboyski Aug 6, 2026
a669676
Add GitHub Actions workflow for tests and coverage
dzmitry-duboyski Aug 6, 2026
00432dc
Document the test suite and the pending coverage badge work
dzmitry-duboyski Aug 6, 2026
6c246a4
Clarify that the install command pulls from GitHub
dzmitry-duboyski Aug 6, 2026
8ce5c53
Fix .npmignore
dzmitry-duboyski Aug 6, 2026
d878b0a
Ignore .claude/
dzmitry-duboyski Aug 6, 2026
f7daddc
Explain the package-name import rule in test headers
dzmitry-duboyski Aug 6, 2026
58def37
Do not fail the unit job when the coverage upload fails
dzmitry-duboyski Aug 7, 2026
0538aeb
Record why the coverage badge is blocked
dzmitry-duboyski Aug 7, 2026
8c4ffa8
Rename the example client variable to captchaSolver
dzmitry-duboyski Aug 7, 2026
c82b96b
Document the examples directory
dzmitry-duboyski Aug 7, 2026
0e9222b
Note the missing example images in the TODO
dzmitry-duboyski Aug 7, 2026
b8e6664
Split the integration tests into one file per check
dzmitry-duboyski Aug 7, 2026
106f7aa
Cover six more captcha types in the integration tests
dzmitry-duboyski Aug 7, 2026
a088f0d
Document how to run a single integration test
dzmitry-duboyski Aug 7, 2026
741eaa7
Ship sample captcha images and run the image flows on them
dzmitry-duboyski Aug 8, 2026
b38efed
Reset the package version to 0.0.1
dzmitry-duboyski Aug 8, 2026
24a7439
update gitignore
dzmitry-duboyski Aug 8, 2026
136aa95
Translate the test documentation to English and refresh its numbers
dzmitry-duboyski Aug 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 57 additions & 13 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,19 +1,63 @@
CAPTCHA_API_KEY=your_api_key_here
# Copy to .env and fill in. .env is gitignored.
#
# Used by two things:
# - the scripts in examples/, which call `import 'dotenv/config'`;
# - the integration tests, via tests/integration/helpers.js.
#
# Target pages and their widget identifiers are deliberately left empty. This
# repository does not carry links to real sites or their keys, so every target
# is yours to fill in. An integration suite whose variables are unset skips
# itself instead of failing -- see tests/README.md.
#
# What identifies the widget depends on the captcha: reCAPTCHA, Turnstile and
# Yandex use a sitekey, Tencent uses an appId, GeeTest v4 a captchaId. The
# variable names below follow whichever one the vendor actually uses.

RECAPTCHA_V2_SITE_KEY=6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI
RECAPTCHA_V2_URL=https://recaptcha-demo.appspot.com/recaptcha-v2-checkbox.php
# Required for everything below.
CAPTCHA_API_KEY=

RECAPTCHA_V3_SITE_KEY=6LfD3wAVAAAAAKQHYQqhNNYh3zSqLU0YPM-iu0Wc
RECAPTCHA_V3_URL=https://recaptcha-demo.appspot.com/recaptcha-v3-request-scores.php
# --- Targets for the integration tests ---
# Each pair enables one suite in tests/integration/. Fill in only the types you
# want to check; the rest will skip.

TURNSTILE_SITE_KEY=0x4AAAAAAAJmU3oC9CbBXuBT
TURNSTILE_URL=https://peet.ws/turnstile-test/non-interactive.html
# tests/integration/recaptcha_v2.test.js
RECAPTCHA_V2_URL=
RECAPTCHA_V2_SITE_KEY=

GEETEST_V3_GT=f2ae6cadcf7886856696c46d84d109d1
GEETEST_V3_CHALLENGE=12345678abc90123d45678e90123f45g6
GEETEST_V3_URL=https://www.geetest.com/en/demo
# tests/integration/recaptcha_v3.test.js
# RECAPTCHA_V3_PAGE_ACTION is optional: set it when the site passes an action
# to grecaptcha.execute().
RECAPTCHA_V3_URL=
RECAPTCHA_V3_SITE_KEY=
RECAPTCHA_V3_PAGE_ACTION=

GEETEST_V4_CAPTCHA_ID=e392e65f912c780f2c3ebac7702651de
GEETEST_V4_URL=https://www.geetest.com/en/demo
# tests/integration/turnstile.test.js
TURNSTILE_URL=
TURNSTILE_SITE_KEY=

IMAGE_TO_TEXT_BASE64=iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAACXBIWXMAAAsTAAALEwEAmpwYAAABaklEQVRIic2VPWtUQRjHfzPnnHv3bnb3ZneNuFkJKgkWVjY2NhZWFn4AEWwsLOwUbIVUtgERxCSFgiAGxEIiCOYDbPIVfBe5u3vuOTMHi7gkJP4Hnup5nv8z7/PMDP8ZMVIA3POcc0YIMUXkAVAVUcixpxpWCsgLWKu8AP4VJcSNMXUAHoJsh1DUlwLWao1i/DuI0CuUWwVa0sTGBB/+Q4qjQw5qZYp2B/UUESFPKK5ZAvSapFy5oAQnRw6CLPK95LmmAt0PDr9ZwnAI6t3nACdEtBniFJDPmHSm87mIdw4zVygIliYiTwH2K5IbhqINkDNjIl8adT3u5BIIHhO52B3Oc0FVT+4BEHuNWHfIDRFZX7vI/H9QIlIvB0R+AnvN6OtYRAVwZ80ICXTHaA+mIgtnmuNNzUGQtkgpYhA7z4goLskAJ0CaK2vIGa0FNv+L4hBZa4idDcAzz3aL8GmG5T8vsl/+zN6LFQAAAABJRU5ErkJggg==
# tests/integration/geetest_v4.test.js
# v4 only. v3 needs a fresh per-session `challenge` scraped from the target,
# so it cannot be driven from static configuration.
GEETEST_V4_URL=
GEETEST_V4_CAPTCHA_ID=

# tests/integration/yandex_smartcaptcha.test.js
YANDEX_SMARTCAPTCHA_URL=
YANDEX_SMARTCAPTCHA_SITE_KEY=

# tests/integration/tencent.test.js
# TENCENT_CAPTCHA_SCRIPT is optional: set it only when the site loads the
# widget from a non-default script URL.
TENCENT_URL=
TENCENT_APP_ID=
TENCENT_CAPTCHA_SCRIPT=

# tests/integration/image_to_text.test.js and coordinates.test.js need nothing
# here at all: no page, no widget identifier, just an image, and both images
# ship with the repository (examples/assets/). Those two suites run as-is with
# nothing but the key above.
#
# IMAGE_TO_TEXT_EXPECTED is the one optional knob: set it to the text on
# examples/assets/text-captcha.png to assert the answer itself instead of
# merely asserting that something came back.
IMAGE_TO_TEXT_EXPECTED=
82 changes: 82 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
name: Tests

on:
push:
branches: [main]
pull_request:
schedule:
# Ночной прогон integration-тестов против реального API
- cron: '0 3 * * *'
workflow_dispatch:

jobs:
unit:
name: Unit (Node ${{ matrix.node-version }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# Соответствует engines.node: ">=18" в package.json
node-version: [18, 20, 22]

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: npm

- run: npm ci

- name: Run unit tests with coverage
run: npm run test:unit -- --coverage

# Покрытие выгружается один раз, а не с каждой версии Node --
# цифра от версии не зависит, дубли только зашумят отчёт.
#
# fail-on-error: false -- выгрузка покрытия вспомогательная, и её сбой
# не должен красить джоб, в котором все тесты прошли. Иначе недоступность
# Coveralls или неподключённый репозиторий блокируют мёрж рабочего PR.
# Устаревший бейдж заметен и сам по себе.
- name: Upload coverage to Coveralls
if: matrix.node-version == 20
uses: coverallsapp/github-action@v2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
file: coverage/lcov.info
fail-on-error: false

integration:
name: Integration (real API)
# Только по расписанию и вручную. На pull_request не запускается намеренно:
# тесты тратят реальный баланс аккаунта, а в PR из форков секрет всё равно
# недоступен -- тесты молча пропустятся и дадут ложно-зелёный результат.
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm

- run: npm ci

- name: Fail early if the API key is missing
# Без ключа integration-тесты пропускают сами себя и джоб зеленеет,
# ничего не проверив. Лучше упасть явно.
env:
CAPTCHA_API_KEY: ${{ secrets.CAPTCHA_API_KEY }}
run: |
if [ -z "$CAPTCHA_API_KEY" ]; then
echo "::error::secrets.CAPTCHA_API_KEY is not set -- integration tests would silently skip"
exit 1
fi

- name: Run integration tests
env:
CAPTCHA_API_KEY: ${{ secrets.CAPTCHA_API_KEY }}
run: npm run test:integration
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ node_modules/
*.log
.DS_Store
dist/
coverage/
coverage/
.claude/
todo.md
37 changes: 37 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Что попадает в публикуемый пакет, в первую очередь определяет поле
# "files": ["src/"] в package.json -- это белый список, и он имеет приоритет
# над этим файлом. Здесь -- второй рубеж на случай, если "files" уберут или
# расширят: тогда лишнее не утечёт в тарбол молча.
#
# Важно: пока этот файл существует, npm НЕ использует .gitignore при упаковке.
# Поэтому всё, что не должно публиковаться, нужно перечислять именно здесь,
# даже если оно уже есть в .gitignore.
#
# Синтаксис -- как в .gitignore. Префикс "./" не работает: писать "tests",
# а не "./tests".

# Тесты и их конфигурация
tests/
jest.config.js
coverage/

# CI и локальные настройки инструментов
.github/
.claude/

# Примеры и ассеты репозитория: нужны на GitHub, но не потребителю пакета
examples/
assets/

# Переменные окружения
.env
.env.example

# Документы для контрибьюторов: место им на GitHub, а не в тарболе
CONTRIBUTING.md
CODE_OF_CONDUCT.md
todo.md

# npm и так всегда включает package.json, README, LICENSE и файл из "main",
# и всегда исключает .git, node_modules, .npmrc и npm-debug.log --
# перечислять их здесь не нужно.
Loading
Loading