Last active
January 5, 2025 17:30
-
-
Save evagoras/b0dd9dadcb480aab2cba14bca7fb81b1 to your computer and use it in GitHub Desktop.
How to test reCaptcha forms in Playwright using TypeScript
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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') | |
}) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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']() | |
}) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://evagoras.com/2025/01/05/how-to-solve-recaptcha-forms-in-playwright-using-typescript/