Kasus Penggunaan

Penanganan CAPTCHA untuk Pendaftaran Mata Kuliah Universitas

Sistem pendaftaran kursus universitas menggunakan reCAPTCHA v2 untuk melindungi formulir pendaftaran, halaman pencarian kelas, dan perencana jadwal. Tim QA yang menguji sistem ini perlu menangani CAPTCHA di beberapa skenario pengujian – alur kerja pendaftaran, logika daftar tunggu, ketersediaan kursi, dan penegakan tenggat waktu. Berikut cara mengintegrasikan penyelesaian CAPTCHA ke dalam pengujian pendaftaran.

Dimana CAPTCHA Muncul di Sistem Registrasi

Langkah pendaftaran jenis CAPTCHA Tujuan
Halaman login siswa reCAPTCHA v2 Cegah isian kredensial
Pencarian kursus/browse reCAPTCHA v2 Batasi pencarian otomatis
Tambahkan kursus /drop reCAPTCHA v2 Cegah pendaftaran otomatis
Pendaftaran daftar tunggu reCAPTCHA v2 Blokir bot pendaftaran otomatis
Perencana jadwal Cloudflare Turnstile Lindungi alat interaktif
Permintaan transkrip reCAPTCHA v2 Verifikasi pengguna manusia

Pengujian Registrasi dengan Pemecahan CAPTCHA

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")

        def text_or_empty(node):
            return node.text.strip() if node and node.text else ""

        courses = []
        for row in soup.select(".course-row, tr.course"):
            courses.append({
                "id": row.get("data-course-id", ""),
                "department": text_or_empty(row.select_one(".dept")),
                "number": text_or_empty(row.select_one(".number")),
                "title": text_or_empty(row.select_one(".title")),
                "seats": text_or_empty(row.select_one(".seats"))
            })
        return courses

    def _parse_availability(self, html):
        from bs4 import BeautifulSoup
        soup = BeautifulSoup(html, "html.parser")

        def text_or_none(node):
            return node.text.strip() if node and node.text else None

        return {
            "total_seats": text_or_none(soup.select_one(".total-seats")),
            "enrolled": text_or_none(soup.select_one(".enrolled-count")),
            "available": text_or_none(soup.select_one(".available-seats")),
            "waitlist": text_or_none(soup.select_one(".waitlist-count"))
        }

    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

Pelari Rangkaian Tes (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})`);

Skenario Uji yang Memicu CAPTCHA

Skenario pengujian frekuensi CAPTCHA Dampak
Masuk dengan banyak akun Tinggi Setiap upaya login memicu CAPTCHA
Tes pendaftaran bersamaan Tinggi Perlindungan anti-bot
Pencarian kursus cepat Sedang Kueri dengan tarif terbatas
Pembuatan jadwal Rendah Interaksi satu sesi
Validasi prasyarat Rendah Bagian dari alur pendaftaran

Pemecahan masalah

Masalah Sebab Perbaiki
CAPTCHA pada setiap login tes Tidak ada penggunaan kembali sesi Otentikasi sekali, jalankan tes dalam sesi
Pendaftaran gagal setelah CAPTCHA Token CSRF sudah habis masa berlakunya Ekstrak token CSRF baru dengan CAPTCHA
Data pengujian disetel ulang di antara proses yang berjalan Portal menghapus pendaftaran tes Gunakan lingkungan pengujian khusus
sitekey reCAPTCHA tidak ditemukan SPA merender CAPTCHA secara dinamis Gunakan otomatisasi browser untuk mengakses DOM yang dirender

Pertanyaan Umum

Haruskah saya menggunakan lingkungan pengujian tanpa CAPTCHA?

Idealnya, lingkungan staging menonaktifkan CAPTCHA. Jika hal tersebut tidak memungkinkan (atau saat menguji perilaku produksi), CaptchaAI menangani penyelesaian reCAPTCHA v2 sehingga pengujian berjalan tanpa intervensi manual.

Bagaimana cara menangani CAPTCHA di Pipeline pengujian CI/CD?

Tambahkan CaptchaAI sebagai ketergantungan pengujian. Saat pengujian menemukan CAPTCHA, selesaikan melalui API dan lanjutkan. Hal ini membuat pengujian end-to-end tetap dapat diandalkan tanpa menonaktifkan fitur keamanan.

Bisakah saya menguji registrasi yang sedang dimuat dengan CAPTCHA?

Ya. CaptchaAI menangani penyelesaian reCAPTCHA paralel untuk pengujian beban. Kirimkan beberapa permintaan penyelesaian secara bersamaan untuk setiap sesi siswa yang disimulasikan.

Artikel Terkait

  • Cara Mengatasi Callback Recaptcha V2 Menggunakan Api
  • Penanganan Situs yang Sama Turnstile Recaptcha V2
  • Mekanisme callback Recaptcha V2

Langkah Selanjutnya

Uji sistem registrasi secara end-to-end —dapatkan kunci API CaptchaAI Andadan menangani CAPTCHA portal universitas di ruang tes Anda.

Komentar dinonaktifkan untuk artikel ini.