University course registration systems use reCAPTCHA v2 to protect enrollment forms, class search pages, and schedule planners. QA teams testing these systems need to handle CAPTCHAs across multiple test scenarios — enrollment workflows, waitlist logic, seat availability, and deadline enforcement. Here's how to integrate CAPTCHA solving into registration testing.
Where CAPTCHAs Appear in Registration Systems
| Registration step | CAPTCHA type | Purpose |
|---|---|---|
| Student login page | reCAPTCHA v2 | Prevent credential stuffing |
| Course search/browse | reCAPTCHA v2 | Limit automated lookups |
| Add/drop courses | reCAPTCHA v2 | Prevent automated enrollment |
| Waitlist registration | reCAPTCHA v2 | Block auto-registration bots |
| Schedule planner | Cloudflare Turnstile | Protect interactive tools |
| Transcript requests | reCAPTCHA v2 | Verify human user |
Registration Testing with CAPTCHA Solving
import requests
import time
import re
class RegistrationTester:
def __init__(self, api_key, portal_url):
self.api_key = api_key
self.portal_url = portal_url
self.session = requests.Session()
self.session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
})
def test_login(self, student_id, password):
"""Test login flow with CAPTCHA handling."""
login_page = self.session.get(f"{self.portal_url}/login")
site_key = self._extract_site_key(login_page.text)
token = self._solve_recaptcha(site_key, f"{self.portal_url}/login")
response = self.session.post(f"{self.portal_url}/login", data={
"studentId": student_id,
"password": password,
"g-recaptcha-response": token
})
return {
"status": response.status_code,
"redirected": response.url != f"{self.portal_url}/login",
"success": "dashboard" in response.url or response.status_code == 200
}
def test_course_search(self, term, department, level=None):
"""Test course search returns correct results."""
params = {"term": term, "dept": department}
if level:
params["level"] = level
response = self.session.get(
f"{self.portal_url}/courses/search", params=params
)
if self._has_captcha(response.text):
site_key = self._extract_site_key(response.text)
token = self._solve_recaptcha(
site_key, f"{self.portal_url}/courses/search"
)
response = self.session.post(
f"{self.portal_url}/courses/search",
data={**params, "g-recaptcha-response": token}
)
courses = self._parse_courses(response.text)
return {
"count": len(courses),
"courses": courses,
"all_match_dept": all(
c["department"] == department for c in courses
)
}
def test_enrollment(self, course_id, section):
"""Test enrolling in a course section."""
enroll_page = self.session.get(
f"{self.portal_url}/enroll/{course_id}/{section}"
)
if self._has_captcha(enroll_page.text):
site_key = self._extract_site_key(enroll_page.text)
token = self._solve_recaptcha(
site_key,
f"{self.portal_url}/enroll/{course_id}/{section}"
)
else:
token = None
form_data = {
"courseId": course_id,
"section": section,
"confirm": "true"
}
if token:
form_data["g-recaptcha-response"] = token
response = self.session.post(
f"{self.portal_url}/enroll/submit",
data=form_data
)
return {
"status": response.status_code,
"enrolled": "success" in response.text.lower(),
"waitlisted": "waitlist" in response.text.lower(),
"error": self._extract_error(response.text)
}
def test_seat_availability(self, course_ids):
"""Test seat availability display for multiple courses."""
results = []
for course_id in course_ids:
response = self.session.get(
f"{self.portal_url}/courses/{course_id}"
)
if self._has_captcha(response.text):
site_key = self._extract_site_key(response.text)
token = self._solve_recaptcha(
site_key, f"{self.portal_url}/courses/{course_id}"
)
response = self.session.post(
f"{self.portal_url}/courses/{course_id}",
data={"g-recaptcha-response": token}
)
availability = self._parse_availability(response.text)
results.append({"course_id": course_id, **availability})
return results
def _has_captcha(self, html):
return "g-recaptcha" in html or "recaptcha" in html.lower()
def _extract_site_key(self, html):
match = re.search(r'data-sitekey="([^"]+)"', html)
if match:
return match.group(1)
raise ValueError("reCAPTCHA site key not found")
def _solve_recaptcha(self, site_key, page_url):
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": self.api_key,
"method": "userrecaptcha",
"googlekey": site_key,
"pageurl": page_url,
"json": 1
})
task_id = resp.json()["request"]
for _ in range(60):
time.sleep(3)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": self.api_key,
"action": "get",
"id": task_id,
"json": 1
})
data = result.json()
if data["status"] == 1:
return data["request"]
raise TimeoutError("reCAPTCHA solve timed out")
def _parse_courses(self, html):
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, "html.parser")
courses = []
for row in soup.select(".course-row, tr.course"):
courses.append({
"id": row.get("data-course-id", ""),
"department": row.select_one(".dept")?.text?.strip() or "",
"number": row.select_one(".number")?.text?.strip() or "",
"title": row.select_one(".title")?.text?.strip() or "",
"seats": row.select_one(".seats")?.text?.strip() or ""
})
return courses
def _parse_availability(self, html):
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, "html.parser")
return {
"total_seats": soup.select_one(".total-seats")?.text?.strip(),
"enrolled": soup.select_one(".enrolled-count")?.text?.strip(),
"available": soup.select_one(".available-seats")?.text?.strip(),
"waitlist": soup.select_one(".waitlist-count")?.text?.strip()
}
def _extract_error(self, html):
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, "html.parser")
error = soup.select_one(".error-message, .alert-danger")
return error.text.strip() if error else None
Test Suite Runner (JavaScript)
class RegistrationTestSuite {
constructor(apiKey, portalUrl) {
this.apiKey = apiKey;
this.portalUrl = portalUrl;
this.results = [];
}
async runAll(testCredentials) {
const tests = [
() => this.testLogin(testCredentials),
() => this.testCourseSearch('Fall2025', 'CS'),
() => this.testEnrollment('CS101', '001'),
() => this.testDropCourse('CS101', '001'),
() => this.testWaitlistFlow('CS201', '001')
];
for (const test of tests) {
try {
const result = await test();
this.results.push({ ...result, passed: true });
} catch (error) {
this.results.push({ name: test.name, error: error.message, passed: false });
}
}
return this.generateReport();
}
async solveRecaptcha(pageUrl, html) {
const match = html.match(/data-sitekey="([^"]+)"/);
if (!match) return null;
const submitResp = await fetch('https://ocr.captchaai.com/in.php', {
method: 'POST',
body: new URLSearchParams({
key: this.apiKey,
method: 'userrecaptcha',
googlekey: match[1],
pageurl: pageUrl,
json: '1'
})
});
const { request: taskId } = await submitResp.json();
for (let i = 0; i < 60; i++) {
await new Promise(r => setTimeout(r, 3000));
const result = await fetch(
`https://ocr.captchaai.com/res.php?key=${this.apiKey}&action=get&id=${taskId}&json=1`
);
const data = await result.json();
if (data.status === 1) return data.request;
}
throw new Error('reCAPTCHA solve timed out');
}
generateReport() {
const passed = this.results.filter(r => r.passed).length;
const failed = this.results.filter(r => !r.passed).length;
return {
total: this.results.length,
passed,
failed,
passRate: `${((passed / this.results.length) * 100).toFixed(1)}%`,
details: this.results
};
}
}
// Usage
const suite = new RegistrationTestSuite('YOUR_API_KEY', 'https://register.university.edu');
const report = await suite.runAll({ studentId: 'test123', password: 'testpass' });
console.log(`Tests: ${report.passed}/${report.total} passed (${report.passRate})`);
Test Scenarios That Trigger CAPTCHAs
| Test scenario | CAPTCHA frequency | Impact |
|---|---|---|
| Login with multiple accounts | High | Each login attempt triggers CAPTCHA |
| Concurrent enrollment tests | High | Anti-bot protection |
| Rapid course searches | Moderate | Rate-limited queries |
| Schedule generation | Low | Single-session interaction |
| Prerequisite validation | Low | Part of enrollment flow |
Troubleshooting
| Issue | Cause | Fix |
|---|---|---|
| CAPTCHA on every test login | No session reuse | Authenticate once, run tests within session |
| Enrollment fails after CAPTCHA | CSRF token expired | Extract fresh CSRF token with CAPTCHA |
| Test data resets between runs | Portal clears test enrollments | Use dedicated test environment |
| reCAPTCHA site key not found | SPA renders CAPTCHA dynamically | Use browser automation to access rendered DOM |
FAQ
Should I use a test environment without CAPTCHAs?
Ideally, staging environments disable CAPTCHAs. When that's not possible (or when testing production behavior), CaptchaAI handles reCAPTCHA v2 solving so tests run without manual intervention.
How do I handle CAPTCHAs in CI/CD test pipelines?
Add CaptchaAI as a test dependency. When a test encounters a CAPTCHA, solve it via the API and continue. This keeps end-to-end tests reliable without disabling security features.
Can I test registration under load with CAPTCHAs?
Yes. CaptchaAI handles parallel reCAPTCHA solving for load testing. Submit multiple solve requests concurrently for each simulated student session.
Related Articles
- How To Solve Recaptcha V2 Callback Using Api
- Recaptcha V2 Turnstile Same Site Handling
- Recaptcha V2 Callback Mechanism
Next Steps
Test registration systems end-to-end — get your CaptchaAI API key and handle university portal CAPTCHAs in your test suite.
Discussions (0)
Join the conversation
Sign in to share your opinion.
Sign InNo comments yet.