Skip to content

Instantly share code, notes, and snippets.

@evagoras
Last active January 5, 2025 17:30
Show Gist options
  • Save evagoras/b0dd9dadcb480aab2cba14bca7fb81b1 to your computer and use it in GitHub Desktop.
Save evagoras/b0dd9dadcb480aab2cba14bca7fb81b1 to your computer and use it in GitHub Desktop.
How to test reCaptcha forms in Playwright using TypeScript
import { expect, test } from '@playwright/test'
import { RecaptchaSection } from '../page-sections/recaptchaSection'
test('displays no errors when everything is correctly completed and form is submitted', async ({ page }, testInfo) => {
const recaptchaInstance = new RecaptchaSection(page, testInfo)
// go to the form page
await page.goto('/contact-us')
// fill out any form fields and click the submit button
await page.locator('#btnSubmit').click()
// this solves the reCAPTCHA if shown
await recaptchaInstance.solveIfPrompted()
// Wait for the thank-you page navigation or whatever comes next
await page.waitForURL('**/thank-you/contact-us*')
await expect(page).toHaveURL('/thank-you/contact-us')
})
import { Page, TestInfo } from '@playwright/test'
import * as dotenv from 'dotenv'
dotenv.config()
interface CreateTaskResponse {
errorId?: number
taskId?: number
errorCode?: string
errorDescription?: string
}
interface CaptchaSolution {
gRecaptchaResponse: string
}
interface GetTaskResponse {
errorId: number
status?: string
solution?: CaptchaSolution
cost?: number
ip?: string
createTime?: number
endTime?: number
solveCount?: number
errorCode?: string
errorDescription?: string
}
export class RecaptchaSection {
readonly page: Page
readonly testInfo: TestInfo
constructor(page: Page, testInfo: TestInfo) {
this.page = page
this.testInfo = testInfo
}
private async createTask(): Promise<CreateTaskResponse> {
const request = this.page.context().request
const response = await request.post('https://api.anti-captcha.com/createTask', {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
data: {
clientKey: process.env.ANTICAPTCHA_API_KEY,
task: {
type: "RecaptchaV2TaskProxyless",
websiteURL: process.env.BASE_URL,
websiteKey: process.env.ANTICAPTCHA_WEBSITE_KEY
},
softId: 0,
languagePool: "en"
}
})
if (response.ok()) {
return await response.json() as CreateTaskResponse
} else {
throw new Error('Failed to create anti-captcha task')
}
}
private async getTaskResult(taskId: number, maxRetries: number = 15, interval: number = 5000): Promise<GetTaskResponse> {
console.log('Returning the result of an anti-captcha task')
if (maxRetries === 0) {
throw new Error('Max retries reached, task result not ready.')
}
const request = this.page.context().request
const response = await request.post('https://api.anti-captcha.com/getTaskResult', {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
data: {
"clientKey": process.env.ANTICAPTCHA_API_KEY,
"taskId": taskId
}
})
const responseBody = await response.json()
if (response.ok() && responseBody.status === "ready") {
console.log('Task is ready')
return responseBody as GetTaskResponse
} else {
console.log(`Task not ready, retries left: ${maxRetries - 1}`)
// Wait for the specified interval before retrying
await new Promise(resolve => setTimeout(resolve, interval))
return this.getTaskResult(taskId, maxRetries - 1, interval)
}
}
async solveIfPrompted(maxRetries: number = 20, interval: number = 5000): Promise<void> {
try {
await this.page.waitForSelector('iframe[title="recaptcha challenge expires in two minutes"]', {
state: 'visible',
timeout: 10000
})
console.log('A captcha became visible and needs to be solved.')
} catch (error) {
console.log('The captcha did not become visible, proceeding without it.')
}
const recaptchaIframe = this.page.locator('iframe[title="recaptcha challenge expires in two minutes"]')
const recaptchaIframeExists = (await recaptchaIframe.count()) > 0
const isIframeVisible = await recaptchaIframe.isVisible()
if (!recaptchaIframeExists || !isIframeVisible) {
return
}
// the captcha needs to be solved
const grandParentElement = recaptchaIframe.locator('..').locator('..')
await grandParentElement.evaluate((element) => {
element.style.visibility = 'hidden'
element.style.top = '-10000px'
element.style.transition = 'visibility 0s linear 0.3s'
element.style.opacity = '0'
})
await this.page.click('body', { position: { x: 0, y: 0 } })
const taskResponse = await this.createTask()
let taskResult: GetTaskResponse
if (taskResponse.errorId === 0 && taskResponse.taskId) {
await this.page.waitForTimeout(5000)
taskResult = await this.getTaskResult(taskResponse.taskId, maxRetries, interval)
} else {
throw new Error('Failed to get taskId from the response')
}
await this.page.evaluate((captchaResponse) => {
const textarea = document.getElementById('g-recaptcha-response') as HTMLTextAreaElement
if (textarea) {
textarea.style.display = 'block'
textarea.value = captchaResponse || ''
}
}, taskResult.solution?.gRecaptchaResponse)
await this.page.evaluate(() => {
window['formSubmit']()
})
}
}
@evagoras
Copy link
Author

evagoras commented Jan 5, 2025

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment