@empiricalrun/playwright-utils 0.39.6 → 0.40.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/captcha/index.d.ts +1 -5
- package/dist/captcha/index.d.ts.map +1 -1
- package/dist/captcha/index.js +87 -19
- package/package.json +3 -3
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/captcha/vision.d.ts +0 -3
- package/dist/captcha/vision.d.ts.map +0 -1
- package/dist/captcha/vision.js +0 -82
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @empiricalrun/playwright-utils
|
|
2
2
|
|
|
3
|
+
## 0.40.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 644366a: fix: incorrect types
|
|
8
|
+
|
|
9
|
+
## 0.40.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- df795ce: feat: use external service dependencies via proxy
|
|
14
|
+
|
|
3
15
|
## 0.39.6
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/dist/captcha/index.d.ts
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
1
|
import type { Page } from "@playwright/test";
|
|
2
|
-
|
|
3
|
-
provider: "2captcha" | "llm-vision";
|
|
4
|
-
};
|
|
5
|
-
export declare function solveRecaptcha(page: Page, options?: CaptchaSolveOptions): Promise<void>;
|
|
6
|
-
export {};
|
|
2
|
+
export declare function solveRecaptcha(page: Page): Promise<void>;
|
|
7
3
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/captcha/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/captcha/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAmI7C,wBAAsB,cAAc,CAAC,IAAI,EAAE,IAAI,iBAU9C"}
|
package/dist/captcha/index.js
CHANGED
|
@@ -4,28 +4,96 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.solveRecaptcha = solveRecaptcha;
|
|
7
|
+
const dashboard_client_1 = require("@empiricalrun/dashboard-client");
|
|
7
8
|
const puppeteer_extra_plugin_recaptcha_1 = __importDefault(require("puppeteer-extra-plugin-recaptcha"));
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
9
|
+
const POLLING_INTERVAL = 10_000;
|
|
10
|
+
const MAX_POLLING_TIME = 300_000;
|
|
11
|
+
function buildTask(captcha) {
|
|
12
|
+
const taskType = captcha._vendor === "hcaptcha"
|
|
13
|
+
? "HCaptchaTaskProxyless"
|
|
14
|
+
: "RecaptchaV2TaskProxyless";
|
|
15
|
+
const task = {
|
|
16
|
+
type: taskType,
|
|
17
|
+
websiteURL: captcha.url,
|
|
18
|
+
websiteKey: captcha.sitekey,
|
|
19
|
+
};
|
|
20
|
+
if (captcha.isEnterprise) {
|
|
21
|
+
task.isEnterprise = true;
|
|
22
|
+
}
|
|
23
|
+
if (captcha.s) {
|
|
24
|
+
task.recaptchaDataSValue = captcha.s;
|
|
25
|
+
}
|
|
26
|
+
return task;
|
|
27
|
+
}
|
|
28
|
+
function assertNoError(result) {
|
|
29
|
+
if (result.errorId && result.errorId > 0) {
|
|
30
|
+
throw new Error(`2captcha error: ${result.errorCode || "unknown"}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function solveViaProxy(captchas) {
|
|
34
|
+
const apiClient = new dashboard_client_1.DashboardAPIClient({
|
|
35
|
+
authType: "project-api-key",
|
|
36
|
+
});
|
|
37
|
+
const solutions = await Promise.all(captchas.map((captcha) => solveSingleViaProxy(apiClient, captcha)));
|
|
38
|
+
return { solutions, error: solutions.find((s) => !!s.error) };
|
|
39
|
+
}
|
|
40
|
+
async function solveSingleViaProxy(apiClient, captcha) {
|
|
41
|
+
const solution = {
|
|
42
|
+
_vendor: captcha._vendor,
|
|
43
|
+
provider: "2captcha",
|
|
44
|
+
};
|
|
45
|
+
try {
|
|
46
|
+
if (!captcha.sitekey || !captcha.url || !captcha.id) {
|
|
47
|
+
throw new Error("Missing data in captcha");
|
|
18
48
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
},
|
|
24
|
-
visualFeedback: true,
|
|
49
|
+
solution.id = captcha.id;
|
|
50
|
+
solution.requestAt = new Date();
|
|
51
|
+
const createResult = await apiClient.request(`/api/twocaptcha/proxy`, {
|
|
52
|
+
method: "POST",
|
|
53
|
+
body: { endpoint: "/createTask", body: { task: buildTask(captcha) } },
|
|
25
54
|
});
|
|
26
|
-
|
|
55
|
+
assertNoError(createResult);
|
|
56
|
+
if (!createResult.taskId) {
|
|
57
|
+
throw new Error("2captcha error: no taskId returned");
|
|
58
|
+
}
|
|
59
|
+
const taskId = createResult.taskId;
|
|
60
|
+
const startTime = Date.now();
|
|
61
|
+
while (Date.now() - startTime < MAX_POLLING_TIME) {
|
|
62
|
+
await new Promise((resolve) => setTimeout(resolve, POLLING_INTERVAL));
|
|
63
|
+
const result = await apiClient.request(`/api/twocaptcha/proxy`, {
|
|
64
|
+
method: "POST",
|
|
65
|
+
body: { endpoint: "/getTaskResult", body: { taskId } },
|
|
66
|
+
});
|
|
67
|
+
assertNoError(result);
|
|
68
|
+
if (result.status === "ready" && result.solution) {
|
|
69
|
+
const token = result.solution.gRecaptchaResponse || result.solution.token;
|
|
70
|
+
if (token) {
|
|
71
|
+
solution.text = token;
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (!solution.text) {
|
|
77
|
+
throw new Error("2captcha error: polling timed out");
|
|
78
|
+
}
|
|
79
|
+
solution.responseAt = new Date();
|
|
80
|
+
solution.hasSolution = true;
|
|
81
|
+
solution.duration =
|
|
82
|
+
(solution.responseAt.getTime() - solution.requestAt.getTime()) / 1000;
|
|
27
83
|
}
|
|
28
|
-
|
|
29
|
-
|
|
84
|
+
catch (error) {
|
|
85
|
+
solution.error = String(error);
|
|
30
86
|
}
|
|
87
|
+
return solution;
|
|
88
|
+
}
|
|
89
|
+
async function solveRecaptcha(page) {
|
|
90
|
+
const rcPlugin = (0, puppeteer_extra_plugin_recaptcha_1.default)({
|
|
91
|
+
provider: {
|
|
92
|
+
id: "2captcha",
|
|
93
|
+
token: "proxy",
|
|
94
|
+
fn: solveViaProxy,
|
|
95
|
+
},
|
|
96
|
+
visualFeedback: true,
|
|
97
|
+
});
|
|
98
|
+
await rcPlugin.solveRecaptchas(page);
|
|
31
99
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@empiricalrun/playwright-utils",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.40.1",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"registry": "https://registry.npmjs.org/",
|
|
6
6
|
"access": "public"
|
|
@@ -44,9 +44,9 @@
|
|
|
44
44
|
"rimraf": "^6.0.1",
|
|
45
45
|
"ts-morph": "^23.0.0",
|
|
46
46
|
"@empiricalrun/cua": "^0.2.0",
|
|
47
|
-
"@empiricalrun/
|
|
47
|
+
"@empiricalrun/dashboard-client": "^0.2.0",
|
|
48
48
|
"@empiricalrun/llm": "^0.25.2",
|
|
49
|
-
"@empiricalrun/
|
|
49
|
+
"@empiricalrun/r2-uploader": "^0.9.1"
|
|
50
50
|
},
|
|
51
51
|
"scripts": {
|
|
52
52
|
"dev": "tsc --build --watch",
|
package/tsconfig.tsbuildinfo
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./src/email.ts","./src/index.ts","./src/kv.ts","./src/logger.ts","./src/mailosaur-client.ts","./src/playwright-extensions.ts","./src/postgres.ts","./src/telemetry.ts","./src/webhook.ts","./src/auth/google.ts","./src/auth/index.ts","./src/auth/types.ts","./src/captcha/index.ts","./src/
|
|
1
|
+
{"root":["./src/email.ts","./src/index.ts","./src/kv.ts","./src/logger.ts","./src/mailosaur-client.ts","./src/playwright-extensions.ts","./src/postgres.ts","./src/telemetry.ts","./src/webhook.ts","./src/auth/google.ts","./src/auth/index.ts","./src/auth/types.ts","./src/captcha/index.ts","./src/config/index.ts","./src/config/proxy.ts","./src/config/devices/types.ts","./src/overlay-tests/cache.spec.ts","./src/overlay-tests/click.spec.ts","./src/overlay-tests/fixtures.ts","./src/overlay-tests/patch.spec.ts","./src/reporter/blob-utils.ts","./src/reporter/empirical-reporter.ts","./src/reporter/failing-line.ts","./src/reporter/uploader.ts","./src/reporter/util.ts","./src/test/constants.ts","./src/test/index.ts","./src/test/types.ts","./src/test/video-labels.ts","./src/test/expect/index.ts","./src/test/expect/types.ts","./src/test/expect/visual.ts","./src/test/expect/webhook.ts","./src/test/scripts/agent-capabilities.ts","./src/test/scripts/index.ts","./src/test/scripts/locator-highlights.ts","./src/test/scripts/locator-vision.ts","./src/test/scripts/mouse-pointer.ts","./src/test/scripts/types.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/cache.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/index.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/prompt.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/types.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/utils.ts","./src/test/scripts/pw-locator-patch/highlight/click.ts","./src/test/scripts/pw-locator-patch/highlight/expect.ts","./src/test/scripts/pw-locator-patch/highlight/hover.ts","./src/test/scripts/pw-locator-patch/highlight/inner-text.ts","./src/test/scripts/pw-locator-patch/highlight/input-value.ts","./src/test/scripts/pw-locator-patch/highlight/is-checked.ts","./src/test/scripts/pw-locator-patch/highlight/is-disabled.ts","./src/test/scripts/pw-locator-patch/highlight/is-editable.ts","./src/test/scripts/pw-locator-patch/highlight/text-content.ts","./src/test/scripts/pw-locator-patch/utils/index.ts","./src/test/scripts/pw-locator-patch/vision/query.ts"],"version":"5.8.3"}
|
package/dist/captcha/vision.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"vision.d.ts","sourceRoot":"","sources":["../../src/captcha/vision.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAkB,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAyE5D,wBAAsB,cAAc,CAAC,IAAI,EAAE,IAAI,iBAiB9C"}
|
package/dist/captcha/vision.js
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.solveRecaptcha = solveRecaptcha;
|
|
4
|
-
const vision_1 = require("@empiricalrun/llm/vision");
|
|
5
|
-
const RECAPTCHA_FRAME_URL = "https://www.google.com/recaptcha/api2";
|
|
6
|
-
const MAX_ATTEMPTS = 5;
|
|
7
|
-
async function isVisible(locator) {
|
|
8
|
-
const isVisibleAccordingToPlaywright = await locator.isVisible();
|
|
9
|
-
if (isVisibleAccordingToPlaywright) {
|
|
10
|
-
const bbox = await locator.boundingBox();
|
|
11
|
-
const isValidBbox = !!bbox && bbox.width > 0 && bbox.height > 0 && bbox.x > 0 && bbox.y > 0;
|
|
12
|
-
return isValidBbox;
|
|
13
|
-
}
|
|
14
|
-
return false;
|
|
15
|
-
}
|
|
16
|
-
async function isVerifyCaptchaVisible(page) {
|
|
17
|
-
const frames = page.frames();
|
|
18
|
-
for (const frame of frames) {
|
|
19
|
-
if (frame.url().includes(RECAPTCHA_FRAME_URL)) {
|
|
20
|
-
if (await isVisible(frame.getByText("Select all images"))) {
|
|
21
|
-
return { frame, type: "images" };
|
|
22
|
-
}
|
|
23
|
-
else if (await isVisible(frame.getByText("Select all squares"))) {
|
|
24
|
-
return { frame, type: "squares" };
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
async function getObjectiveCaptcha(capFrame) {
|
|
30
|
-
const { frame, type } = capFrame;
|
|
31
|
-
const text = await frame.getByText(`Select all ${type}`).textContent();
|
|
32
|
-
return text
|
|
33
|
-
?.replace(`Select all ${type} with`, "")
|
|
34
|
-
.replace(`Click verify once there are none left`, "")
|
|
35
|
-
.replace(`If there are none, click skip`, "")
|
|
36
|
-
.trim();
|
|
37
|
-
}
|
|
38
|
-
async function clickImageTile(frame, nth) {
|
|
39
|
-
const image = frame.locator(".rc-imageselect-tile").nth(nth);
|
|
40
|
-
await image.dispatchEvent("click");
|
|
41
|
-
}
|
|
42
|
-
async function handleCaptcha(cf) {
|
|
43
|
-
const screenshot = await cf.frame
|
|
44
|
-
.locator(".rc-imageselect-challenge")
|
|
45
|
-
.screenshot();
|
|
46
|
-
const gridBoxesToSelect = await (0, vision_1.solveRecaptchaGrid)({
|
|
47
|
-
captchaType: cf.type,
|
|
48
|
-
captchaObject: (await getObjectiveCaptcha(cf)),
|
|
49
|
-
screenshotBase64: screenshot.toString("base64"),
|
|
50
|
-
});
|
|
51
|
-
for (const n of gridBoxesToSelect) {
|
|
52
|
-
await clickImageTile(cf.frame, n - 1);
|
|
53
|
-
}
|
|
54
|
-
const verifyButton = cf.frame.getByRole("button", { name: "Verify" });
|
|
55
|
-
const nextButton = cf.frame.getByRole("button", { name: "Next" });
|
|
56
|
-
if (await isVisible(verifyButton)) {
|
|
57
|
-
await verifyButton.dispatchEvent("click");
|
|
58
|
-
}
|
|
59
|
-
else if (await isVisible(nextButton)) {
|
|
60
|
-
await nextButton.dispatchEvent("click");
|
|
61
|
-
}
|
|
62
|
-
await cf.frame.waitForTimeout(3000);
|
|
63
|
-
}
|
|
64
|
-
async function solveRecaptcha(page) {
|
|
65
|
-
const frame = page.frameLocator('iframe[title="reCAPTCHA"]');
|
|
66
|
-
await frame.getByLabel("not a robot").click();
|
|
67
|
-
await page.waitForTimeout(2000);
|
|
68
|
-
for (let i = 0; i < MAX_ATTEMPTS; i++) {
|
|
69
|
-
const cf = await isVerifyCaptchaVisible(page);
|
|
70
|
-
if (cf) {
|
|
71
|
-
await handleCaptcha(cf);
|
|
72
|
-
}
|
|
73
|
-
else {
|
|
74
|
-
break;
|
|
75
|
-
}
|
|
76
|
-
if (i === MAX_ATTEMPTS - 1) {
|
|
77
|
-
if (await isVerifyCaptchaVisible(page)) {
|
|
78
|
-
throw new Error(`Failed to solve captcha in ${MAX_ATTEMPTS} attempts.`);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|