cy.task के साथ Cypress tests में कैप्चा कैसे हल करें

cypress captcha - How to Solve CAPTCHA in Cypress Tests With cy.task

Cypress का कैप्चा वाला मसला solving का मसला बाद में है, पहले runtime का मसला है। आपका spec code उसी browser के अंदर चलता है जिसकी जाँच हो रही है, इसलिए सॉल्वर से बात करने वाला Node client वहाँ नहीं रह सकता। इसके बजाय इसे cypress.config.js में एक task के रूप में register करें, उस task को spec से call करें, और token को खुद hidden field में लिख दें। तीन चीज़ें इसे काम करने लायक़ बनाती हैं: task, बढ़ा हुआ timeout, और Cypress click के बजाय सीधा DOM write। यह गाइड तीनों को कवर करती है।

आपको क्या चाहिए

  • Cypress 10 या उससे नया, क्योंकि cypress.config.js और setupNodeEvents यहीं आए थे। इससे पुराना कुछ भी पुरानी plugins file इस्तेमाल करता है और वही विचार वहाँ भी लागू होता है
  • Node.js 18 या उससे नया
  • CapSkip चालू और पहुँच में हो। Local mode उसी मशीन पर होने वाले test run के लिए 127.0.0.1 के port 8080 पर सुनता है, और Server mode आपके network या public IP पर सुनता है ताकि कोई CI runner या कोई दूसरी मशीन उसे call कर सके। दोनों यहाँ मिलेंगे: कनेक्शन सेटिंग्स
  • सॉल्वर client, dev dependency के रूप में इंस्टॉल किया हुआ
# The client only ever runs in the Node half of Cypress.
npm install --save-dev capskip

solve को spec में क्यों नहीं रखा जा सकता

Cypress दो processes में बँट जाता है और naive version के नाकाम होने की पूरी वजह यही है। आपकी spec file bundle होकर browser के अंदर, application के बग़ल में चलती है। cypress.config.js में जो कुछ है वह Node में, उसके बाहर चलता है।

इसलिए किसी spec के सबसे ऊपर solver client को require करना एक Node HTTP client को browser bundle में खींच लाता है। bundler अगर उसे जाने भी दे, तो browser call को रोक देता है: आपके app के origin से 127.0.0.1 के port 8080 पर जाने वाली request cross origin है, और सॉल्वर उसे जायज़ बनाने वाले CORS headers नहीं भेजता।

Cypress आपको Node तक जाने के दो रास्ते देता है, और दोनों ठीक हैं:

  • cy.task आपके द्वारा config में register किए गए किसी भी function को चलाता है। SDK की जगह यही है, क्योंकि तब उसका polling और backoff logic Node में चलता है, जहाँ के लिए उसे बनाया गया था।
  • cy.request browser के बजाय Cypress के Node process से HTTP call करता है, और इसीलिए Cypress का documentation कहता है कि यह CORS को पूरी तरह बायपास कर देता है। अच्छा है अगर आप raw API को सीधे हिट करना और dependency छोड़ना पसंद करें।

चरण 1: solve को एक task के रूप में register करें

एक function, एक बार register, और हर spec के लिए उपलब्ध।

// npm install --save-dev capskip
const { defineConfig } = require('cypress');
const { CapSkip } = require('capskip');

// Local mode. Point host at a server IP to share one solver.
const solver = new CapSkip({ host: '127.0.0.1', port: 8080 });

module.exports = defineConfig({
  // 60000 is the default, and a v2 solve can outlast it.
  taskTimeout: 180000,

  e2e: {
    setupNodeEvents(on) {
      on('task', {
        async solveRecaptcha({ sitekey, url }) {
          const result = await solver.recaptcha(sitekey, url);
          return result.code;   // the token
        },
      });
    },
  },
});

tasks के बारे में एक नियम जो पहली बार सबका एक घंटा खा जाता है: task को कोई value या null लौटाना ही चाहिए, कभी undefined नहीं। return statement भूल जाइए और Cypress command को fail कर देता है, यह कहते हुए कि task ने undefined लौटाया, जो पढ़ने में ऐसा लगता है जैसे सॉल्वर टूट गया हो, जबकि सॉल्वर को call तक नहीं किया गया था।

चरण 2: task को call करें और token inject करें

page से sitekey पढ़ें, उसे task को दें, और जवाब वहीं रख दें जहाँ widget ने रखा होता।

// cypress/e2e/login.cy.js
it('logs in through the reCAPTCHA', () => {
  cy.visit('/login');

  cy.get('[data-sitekey]')
    .invoke('attr', 'data-sitekey')
    .then((sitekey) => {
      const url = 'https://example.com/login';

      cy.task('solveRecaptcha', { sitekey, url }).then((token) => {
        // The widget writes into a hidden textarea. Do the same.
        cy.document().then((doc) => {
          doc.getElementById('g-recaptcha-response').value = token;
        });
      });
    });

  cy.get('button[type=submit]').click();
  cy.contains('Welcome back');
});

DOM write पर ध्यान दें। response field एक hidden textarea है, और Cypress जिस element को अदृश्य मानता है उसमें type करने से मना कर देता है, इसलिए get और type call वाला ज़ाहिर तरीक़ा token के पास पहुँचने से पहले ही visibility पर fail हो जाता है। cy.document के रास्ते जाना इससे बच निकलता है, ठीक वैसे ही जैसे widget का अपना JavaScript करता।

अगर widget div पर data-callback attribute है, तो submit button क्लिक करने के बजाय उस function को token के साथ invoke करें। इस तरह बने pages कभी सामान्य form submit जोड़ते ही नहीं, इसलिए क्लिक से कुछ नहीं होता।

जो timeout असल में काटता है वह taskTimeout है

यही वह नाकामी है जिसका इल्ज़ाम सबसे ज़्यादा सॉल्वर पर आता है, और इसका हल config में एक line है।

Cypress डिफ़ॉल्ट रूप से किसी task को 60 सेकंड देता है। reCAPTCHA v2 का job पहले 15 से 20 सेकंड तक तैयार नहीं होता, v3 में 10 से 15 लगते हैं, और व्यस्त मशीन दोनों को खींच सकती है। सीमा पूरी होते ही Cypress command को मार देता है और test एक ऐसे timeout के साथ fail होता है जो आपके task का नाम लेता है, कैप्चा का नहीं।

इसके बग़ल वाला जाल है ग़लत नंबर बढ़ा देना। Cypress के ज़्यादातर timeout मशवरे defaultCommandTimeout की ओर इशारा करते हैं, जो 4000 मिलीसेकंड है और DOM commands को संभालता है। task पर उसका कोई असर नहीं होता। यहाँ तीन values मायने रखती हैं और तीनों अलग-अलग हैं:

विकल्पडिफ़ॉल्टकिस पर लागू
taskTimeout60000 mscy.task, यानी solve
responseTimeout30000 mscy.request, यानी कोई raw API call
defaultCommandTimeout4000 msDOM commands, ऊपर बताए गए दोनों में से कोई नहीं

इसे config में ऊपर की तरह globally सेट करें, या जब सिर्फ़ एक ही test को छूट चाहिए तो हर call पर:

// Same task, a longer leash for this one call.
cy.task('solveRecaptcha', { sitekey, url }, { timeout: 180000 });

180 सेकंड एक समझदार सीमा है। यह सामान्य solve से क़रीब दस गुना है, और जान-बूझकर SDK की अपनी 300 सेकंड वाली reCAPTCHA polling सीमा से नीचे रखा गया है, ताकि Cypress सचमुच अटके हुए test को fail करे, न कि अब भी इंतज़ार कर रहे किसी client के पीछे लटका रहे। अगर आप चाहें कि client पहले हार माने, तो recaptchaTimeout को अपने task timeout से कम कर दें।

या SDK छोड़ दें और cy.request इस्तेमाल करें

API 2captcha compatible है, इसलिए दो calls पूरा काम कर देती हैं। चूँकि cy.request Node में चलता है, browser के origin नियम इसमें आते ही नहीं।

// No task registration needed. Both calls happen in Node.
function pollForToken(id, tries = 20) {
  return cy.request({
    method: 'POST',
    url: 'http://127.0.0.1:8080/res.php',
    form: true,
    body: { key: 'capskip', action: 'get', id },
  }).then((res) => {
    const text = res.body.trim();
    if (text !== 'CAPCHA_NOT_READY') return text.replace('OK|', '');
    if (tries === 0) throw new Error('gave up waiting for ' + id);
    return cy.wait(5000).then(() => pollForToken(id, tries - 1));
  });
}

ध्यान में रखने लायक़ दो बातें। res.php से आने वाली plain text reply सफल होने पर यह होती है: OK|TOKEN जबकि job के चलते रहने के दौरान सिर्फ़ CAPCHA_NOT_READY string आती है, जो एक status है, error नहीं। और कोई नतीजा सिर्फ़ एक ही बार पढ़ा जा सकता है, इसलिए दोबारा पूछने के बजाय आते ही उसे संभालकर रख लें। हर parameter और हर error string यहाँ सूचीबद्ध है: API डॉक्युमेंटेशन.

सॉल्वर को उसकी जगह रखते हुए CI में tests चलाना

यहीं वह suite, जो आपके laptop पर पास होती है, पहले ही push पर fail हो जाती है। किसी GitHub Actions runner, GitLab job या Jenkins agent का अपना loopback address होता है, और वहाँ port 8080 पर कोई नहीं सुन रहा होता। Local mode परिभाषा से ही मशीन तक सीमित है।

इसका जवाब Server mode है। CapSkip loopback के बजाय आपके network या public IP पर सुनता है, और config पते को environment से पढ़ता है।

// npm install --save-dev capskip
const { CapSkip } = require('capskip');

// Same client, different address. The spec never changes.
const solver = new CapSkip({
  host: process.env.CAPSKIP_HOST || '127.0.0.1',
  port: Number(process.env.CAPSKIP_PORT || 8080),
});

SDK खुद ही CAPSKIP_HOST और CAPSKIP_PORT पढ़ लेता है, इसलिए ऊपर वाला fallback उस runner के लिए एहतियात भर है जो इनके बिना शुरू होता है। सॉल्वर वाली मशीन के लिए स्टैटिक public IP की सलाह दी जाती है, और उसके steps यहाँ हैं: कनेक्शन सेटिंग्स। यह अब भी आपका अपना hardware है और अब भी unmetered है: बदला सिर्फ़ इतना है कि process कहाँ सुनता है।

आम errors और उनका मतलब

लक्षणकारणफिक्स
task solveRecaptcha register नहीं थाग़लत block में register किया, या config कभी export ही नहीं हुईe2e key के लिए setupNodeEvents के अंदर register करें
आपके task के लिए 60000ms इंतज़ार के बाद timeout हो गयाtaskTimeout अब भी अपने डिफ़ॉल्ट पर हैइसे 180000 कर दें, globally या हर call पर
task ने undefined लौटायाhandler में कोई return statement नहीं हैtoken लौटाएँ, या कुछ न हो तो null
Element दिख नहीं रहा, इसलिए Cypress type नहीं कर सकताresponse field एक hidden textarea हैvalue को इसके बजाय cy.document के ज़रिए लिखें
ERROR_GOOGLEKEYsitekey ख़ाली था, या किसी Turnstile widget का हैहल करने से पहले attribute को log करें; Turnstile का अपना method है
हर test पर NetworkExceptionउस host और port पर कोई नहीं सुन रहाLocal mode सिर्फ़ loopback है; CI से Server mode इस्तेमाल करें
लोकल पर हरा, CI में लालrunner आपकी मशीन के loopback तक नहीं पहुँच सकताCAPSKIP_HOST को किसी पहुँच-योग्य पते पर लगाएँ

अक्सर पूछे जाने वाले सवाल

क्या मुझे अपने test environment में कैप्चा बस बंद कर देना चाहिए?

अगर widget आपका अपना है, तो हाँ। staging build में कोई feature flag या test sitekey हल करने से सस्ता और तेज़ है, और suite को deterministic रखता है। हल करना तीन हालात में अपनी जगह बनाता है: कैप्चा किसी और का हो, staging को production की हूबहू नक़ल करनी हो, या जिसकी जाँच हो रही हो वह challenge path ही हो। तीसरे वाले के लिए कैप्चा डेमो पेज उपयोगी हैं, क्योंकि आप किसी spec को ऐसे widget पर लगा सकते हैं जो असली जैसा ही बर्ताव करता है।

क्या यह Cypress component testing में काम करता है?

काम लायक़ नहीं। component test किसी component को बिना असली page और बिना पीछे किसी server के mount करता है, इसलिए token को verify कराने के लिए कुछ होता ही नहीं। अगर आप इसे उपलब्ध रखना चाहें तो task को component key के नीचे register कर लें, लेकिन challenge वाला काम end to end specs में ही रखें, जहाँ submit करने के लिए कोई असली request होती है।

क्या Cypress Cloud पर record किया गया run सॉल्वर तक पहुँच सकता है?

Cypress Cloud नतीजे record करता है, आपके tests चलाता नहीं, इसलिए सवाल असल में उस मशीन का है जो browser चलाती है। आपके laptop पर वह Local mode है। किसी hosted runner पर उसे Server mode और सॉल्वर के पते तक एक रास्ता चाहिए, और recording दोनों ही सूरतों में एक जैसी काम करती है।

एक parallel Cypress run एक साथ कितने solves भेज सकता है?

जितनी spec files चल रही हों उतने। हर Cypress process अपना client रखता है और अपने task का इंतज़ार करता है, इसलिए client की तरफ़ कुछ भी configure करने को नहीं है। SDK 250 मिलीसेकंड पर polling शुरू करता है और pollingInterval की सीमा तक धीमा होता जाता है, जिससे कई solves एक साथ चलने पर भी तेज़ solve तेज़ ही रहता है। चूँकि काम आपके अपने hardware पर होता है, मशीनें जोड़ना क्षमता का फ़ैसला है, billing का नहीं।

संक्षेप में

client को cypress.config.js में रखें, उसे एक task के रूप में expose करें, taskTimeout को 180000 कर दें, और token को cy.document के ज़रिए hidden field में लिख दें। पूरा integration बस यही है, और जो हिस्से टूटते हैं वे इसके नीचे बैठे दो Cypress नियम हैं: spec code browser का code है, और task या तो लौटाता है या fail हो जाता है।

सॉल्वर को खुद चलाना ही किसी flaky challenge को बजट में गिनने के बजाय दोबारा आज़माना वाजिब बनाता है, और यह तर्क हर जगह टिकता है, आप कैप्चा सॉल्वरजहाँ भी रखें, किसी test suite में या production में। client options पर विस्तार से यहाँ बात है: Node.js इंटीग्रेशन गाइड, और browser की तरफ़ की वे बातें जो Cypress बाक़ी हर runner के साथ साझा करता है, यहाँ हैं: Playwright गाइड.