testdriverai 7.3.5 → 7.3.7
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/.github/agents/testdriver.agent.md +641 -0
- package/.github/workflows/acceptance.yaml +1 -1
- package/.github/workflows/windows-self-hosted.yaml +1 -1
- package/CHANGELOG.md +8 -0
- package/docs/_data/examples-manifest.json +82 -50
- package/docs/v7/examples/ai.mdx +1 -1
- package/docs/v7/examples/assert.mdx +1 -1
- package/docs/v7/examples/captcha-api.mdx +1 -1
- package/docs/v7/examples/chrome-extension.mdx +1 -1
- package/docs/v7/examples/drag-and-drop.mdx +1 -1
- package/docs/v7/examples/element-not-found.mdx +1 -1
- package/docs/v7/examples/hover-image.mdx +1 -1
- package/docs/v7/examples/hover-text.mdx +1 -1
- package/docs/v7/examples/installer.mdx +1 -1
- package/docs/v7/examples/launch-vscode-linux.mdx +1 -1
- package/docs/v7/examples/match-image.mdx +1 -1
- package/docs/v7/examples/press-keys.mdx +1 -1
- package/docs/v7/examples/scroll-keyboard.mdx +1 -1
- package/docs/v7/examples/scroll-until-image.mdx +1 -1
- package/docs/v7/examples/scroll-until-text.mdx +1 -1
- package/docs/v7/examples/scroll.mdx +1 -1
- package/docs/v7/examples/type.mdx +1 -1
- package/docs/v7/examples/windows-installer.mdx +1 -1
- package/examples/ai.test.mjs +2 -1
- package/examples/assert.test.mjs +2 -2
- package/examples/captcha-api.test.mjs +3 -2
- package/examples/chrome-extension.test.mjs +3 -2
- package/examples/config.mjs +5 -0
- package/examples/drag-and-drop.test.mjs +2 -1
- package/examples/element-not-found.test.mjs +2 -1
- package/examples/exec-output.test.mjs +2 -1
- package/examples/exec-pwsh.test.mjs +2 -1
- package/examples/focus-window.test.mjs +2 -1
- package/examples/formatted-logging.test.mjs +2 -1
- package/examples/hover-image.test.mjs +2 -1
- package/examples/hover-text-with-description.test.mjs +2 -1
- package/examples/hover-text.test.mjs +2 -1
- package/examples/installer.test.mjs +3 -2
- package/examples/launch-vscode-linux.test.mjs +3 -2
- package/examples/match-image.test.mjs +2 -1
- package/examples/no-provision.test.mjs +2 -3
- package/examples/press-keys.test.mjs +7 -13
- package/examples/prompt.test.mjs +2 -1
- package/examples/scroll-keyboard.test.mjs +2 -1
- package/examples/scroll-until-image.test.mjs +2 -1
- package/examples/scroll-until-text.test.mjs +2 -1
- package/examples/scroll.test.mjs +2 -1
- package/examples/type.test.mjs +3 -2
- package/examples/windows-installer.test.mjs +2 -1
- package/examples/z_flake-diffthreshold-001.test.mjs +9 -0
- package/examples/z_flake-diffthreshold-01.test.mjs +9 -0
- package/examples/z_flake-diffthreshold-05.test.mjs +9 -0
- package/examples/z_flake-noredraw-cache.test.mjs +9 -0
- package/examples/z_flake-noredraw-nocache.test.mjs +9 -0
- package/examples/z_flake-redraw-cache.test.mjs +9 -0
- package/examples/z_flake-redraw-nocache.test.mjs +9 -0
- package/examples/z_flake-shared.mjs +50 -0
- package/package.json +2 -2
- package/sdk-log-formatter.js +6 -0
- package/sdk.js +66 -40
- package/vitest.config.mjs +1 -1
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { describe, expect, it } from "vitest";
|
|
7
7
|
import { TestDriver } from "../lib/vitest/hooks.mjs";
|
|
8
|
+
import { getDefaults } from "./config.mjs";
|
|
8
9
|
|
|
9
10
|
const isLinux = (process.env.TD_OS || "linux") === "linux";
|
|
10
11
|
|
|
@@ -12,7 +13,7 @@ describe("Provision Installer", () => {
|
|
|
12
13
|
it.skipIf(!isLinux)(
|
|
13
14
|
"should download and install a .deb package on Linux",
|
|
14
15
|
async (context) => {
|
|
15
|
-
const testdriver = TestDriver(context, {
|
|
16
|
+
const testdriver = TestDriver(context, { ...getDefaults(context) });
|
|
16
17
|
|
|
17
18
|
// Install bat (a cat clone with syntax highlighting) using provision.installer
|
|
18
19
|
const filePath = await testdriver.provision.installer({
|
|
@@ -30,7 +31,7 @@ describe("Provision Installer", () => {
|
|
|
30
31
|
it.skipIf(!isLinux)(
|
|
31
32
|
"should download a shell script and verify it exists",
|
|
32
33
|
async (context) => {
|
|
33
|
-
const testdriver = TestDriver(context, {
|
|
34
|
+
const testdriver = TestDriver(context, { ...getDefaults(context) });
|
|
34
35
|
|
|
35
36
|
// Download a shell script (nvm installer)
|
|
36
37
|
const filePath = await testdriver.provision.installer({
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
import { TestDriver } from "../lib/vitest/hooks.mjs";
|
|
3
|
+
import { getDefaults } from "./config.mjs";
|
|
3
4
|
|
|
4
5
|
const isLinux = (process.env.TD_OS || "linux") === "linux";
|
|
5
6
|
|
|
@@ -7,7 +8,7 @@ describe("Launch VS Code on Linux", () => {
|
|
|
7
8
|
it.skipIf(!isLinux)(
|
|
8
9
|
"should launch VS Code on Debian/Ubuntu",
|
|
9
10
|
async (context) => {
|
|
10
|
-
const testdriver = TestDriver(context, {
|
|
11
|
+
const testdriver = TestDriver(context, { ...getDefaults(context) });
|
|
11
12
|
|
|
12
13
|
// provision.vscode() automatically calls ready() and starts dashcam
|
|
13
14
|
await testdriver.provision.vscode();
|
|
@@ -24,7 +25,7 @@ describe("Launch VS Code on Linux", () => {
|
|
|
24
25
|
it.skipIf(!isLinux)(
|
|
25
26
|
"should install and use a VS Code extension",
|
|
26
27
|
async (context) => {
|
|
27
|
-
const testdriver = TestDriver(context, {
|
|
28
|
+
const testdriver = TestDriver(context, { ...getDefaults(context) });
|
|
28
29
|
|
|
29
30
|
// Launch VS Code with the Prettier extension installed
|
|
30
31
|
await testdriver.provision.vscode({
|
|
@@ -7,6 +7,7 @@ import path, { dirname } from "path";
|
|
|
7
7
|
import { fileURLToPath } from "url";
|
|
8
8
|
import { describe, expect, it } from "vitest";
|
|
9
9
|
import { TestDriver } from "../lib/vitest/hooks.mjs";
|
|
10
|
+
import { getDefaults } from "./config.mjs";
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Perform login flow for SauceLabs demo app
|
|
@@ -33,7 +34,7 @@ const __dirname = dirname(__filename);
|
|
|
33
34
|
|
|
34
35
|
describe("Match Image Test", () => {
|
|
35
36
|
it.skip("should match shopping cart image and verify empty cart", async (context) => {
|
|
36
|
-
const testdriver = TestDriver(context, {
|
|
37
|
+
const testdriver = TestDriver(context, { ...getDefaults(context), headless: true });
|
|
37
38
|
await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
|
|
38
39
|
|
|
39
40
|
//
|
|
@@ -5,12 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
import { describe, expect, it } from "vitest";
|
|
7
7
|
import { TestDriver } from "../lib/vitest/hooks.mjs";
|
|
8
|
+
import { getDefaults } from "./config.mjs";
|
|
8
9
|
|
|
9
10
|
describe("Assert Test", () => {
|
|
10
11
|
it("should assert the testdriver login page shows", async (context) => {
|
|
11
|
-
const testdriver = TestDriver(context, {
|
|
12
|
-
ip: context.ip || process.env.TD_IP,
|
|
13
|
-
});
|
|
12
|
+
const testdriver = TestDriver(context, { ...getDefaults(context) });
|
|
14
13
|
|
|
15
14
|
// Assert the TestDriver.ai Sandbox login page is displayed
|
|
16
15
|
const result = await testdriver.assert(
|
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
import { describe, expect, it } from "vitest";
|
|
7
7
|
import { TestDriver } from "../lib/vitest/hooks.mjs";
|
|
8
|
+
import { getDefaults } from "./config.mjs";
|
|
8
9
|
|
|
9
10
|
describe("Press Keys Test", () => {
|
|
10
11
|
it("should create tabs and navigate using keyboard shortcuts", async (context) => {
|
|
11
|
-
const testdriver = TestDriver(context, {
|
|
12
|
+
const testdriver = TestDriver(context, { ...getDefaults(context), headless: true });
|
|
12
13
|
await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
|
|
13
14
|
|
|
14
15
|
const signInButton = await testdriver.find(
|
|
@@ -20,23 +21,16 @@ describe("Press Keys Test", () => {
|
|
|
20
21
|
await testdriver.pressKeys(["ctrl", "t"]);
|
|
21
22
|
|
|
22
23
|
// Poll for "Learn more" to appear
|
|
23
|
-
let
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (learnMore.found()) break;
|
|
27
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
28
|
-
}
|
|
24
|
+
let imagesLink = await testdriver.find("Images", {timeout: 5000});
|
|
25
|
+
|
|
26
|
+
expect(imagesLink.found()).toBeTruthy();
|
|
29
27
|
|
|
30
28
|
// Open DevTools
|
|
31
29
|
await testdriver.pressKeys(["ctrl", "shift", "i"]);
|
|
32
30
|
|
|
33
31
|
// Poll for "Elements" to appear
|
|
34
|
-
let elements = await testdriver.find("Elements");
|
|
35
|
-
|
|
36
|
-
elements = await elements.find();
|
|
37
|
-
if (elements.found()) break;
|
|
38
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
39
|
-
}
|
|
32
|
+
let elements = await testdriver.find("Elements", {timeout: 5000});
|
|
33
|
+
expect(elements.found()).toBeTruthy();
|
|
40
34
|
|
|
41
35
|
// Open another tab and navigate
|
|
42
36
|
await testdriver.pressKeys(["ctrl", "t"]);
|
package/examples/prompt.test.mjs
CHANGED
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
import { describe, expect, it } from "vitest";
|
|
7
7
|
import { TestDriver } from "../lib/vitest/hooks.mjs";
|
|
8
|
+
import { getDefaults } from "./config.mjs";
|
|
8
9
|
|
|
9
10
|
describe.skip("Prompt Test", () => {
|
|
10
11
|
it("should execute AI-driven prompts", async (context) => {
|
|
11
|
-
const testdriver = TestDriver(context, {
|
|
12
|
+
const testdriver = TestDriver(context, { ...getDefaults(context), headless: true });
|
|
12
13
|
await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
|
|
13
14
|
|
|
14
15
|
//
|
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
import { describe, expect, it } from "vitest";
|
|
7
7
|
import { TestDriver } from "../lib/vitest/hooks.mjs";
|
|
8
|
+
import { getDefaults } from "./config.mjs";
|
|
8
9
|
|
|
9
10
|
describe("Scroll Keyboard Test", () => {
|
|
10
11
|
it("should navigate to webhamster.com and scroll with keyboard", async (context) => {
|
|
11
|
-
const testdriver = TestDriver(context, {
|
|
12
|
+
const testdriver = TestDriver(context, { ...getDefaults(context), headless: true });
|
|
12
13
|
await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
|
|
13
14
|
|
|
14
15
|
//
|
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
import { describe, expect, it } from "vitest";
|
|
7
7
|
import { TestDriver } from "../lib/vitest/hooks.mjs";
|
|
8
|
+
import { getDefaults } from "./config.mjs";
|
|
8
9
|
|
|
9
10
|
describe("Scroll Until Image Test", () => {
|
|
10
11
|
it.skip("should scroll until brown colored house image appears", async (context) => {
|
|
11
|
-
const testdriver = TestDriver(context, {
|
|
12
|
+
const testdriver = TestDriver(context, { ...getDefaults(context), headless: true });
|
|
12
13
|
await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
|
|
13
14
|
|
|
14
15
|
//
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { describe, expect, it } from "vitest";
|
|
7
7
|
import { TestDriver } from "../lib/vitest/hooks.mjs";
|
|
8
|
+
import { getDefaults } from "./config.mjs";
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Perform login flow for SauceLabs demo app
|
|
@@ -27,7 +28,7 @@ async function performLogin(client, username = "standard_user") {
|
|
|
27
28
|
|
|
28
29
|
describe("Scroll Until Text Test", () => {
|
|
29
30
|
it('should scroll until "testdriver socks" appears', async (context) => {
|
|
30
|
-
const testdriver = TestDriver(context, {
|
|
31
|
+
const testdriver = TestDriver(context, { ...getDefaults(context), headless: true });
|
|
31
32
|
await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
|
|
32
33
|
|
|
33
34
|
//
|
package/examples/scroll.test.mjs
CHANGED
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
|
|
8
8
|
import { describe, expect, it } from "vitest";
|
|
9
9
|
import { TestDriver } from "../lib/vitest/hooks.mjs";
|
|
10
|
+
import { getDefaults } from "./config.mjs";
|
|
10
11
|
|
|
11
12
|
describe("Scroll Test", () => {
|
|
12
13
|
it("should navigate and scroll down the page", async (context) => {
|
|
13
|
-
const testdriver = TestDriver(context, {
|
|
14
|
+
const testdriver = TestDriver(context, { ...getDefaults(context), headless: true });
|
|
14
15
|
await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
|
|
15
16
|
|
|
16
17
|
// Give Chrome a moment to fully render the UI
|
package/examples/type.test.mjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
import { TestDriver } from "../lib/vitest/hooks.mjs";
|
|
3
|
+
import { getDefaults } from "./config.mjs";
|
|
3
4
|
|
|
4
5
|
describe("Type Test", () => {
|
|
5
6
|
it("should enter standard_user in username field", async (context) => {
|
|
6
|
-
const testdriver = TestDriver(context, {
|
|
7
|
+
const testdriver = TestDriver(context, { ...getDefaults(context), headless: true });
|
|
7
8
|
await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
|
|
8
9
|
|
|
9
10
|
//
|
|
@@ -20,7 +21,7 @@ describe("Type Test", () => {
|
|
|
20
21
|
});
|
|
21
22
|
|
|
22
23
|
it("should show validation message when clicking Sign In without password", async (context) => {
|
|
23
|
-
const testdriver = TestDriver(context, {
|
|
24
|
+
const testdriver = TestDriver(context, { ...getDefaults(context), headless: true });
|
|
24
25
|
await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
|
|
25
26
|
|
|
26
27
|
// First fill in username
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import { describe, it } from "vitest";
|
|
13
13
|
import { TestDriver } from "../lib/vitest/hooks.mjs";
|
|
14
|
+
import { getDefaults } from "./config.mjs";
|
|
14
15
|
|
|
15
16
|
const isLinux = (process.env.TD_OS || "linux") === "linux";
|
|
16
17
|
|
|
@@ -19,7 +20,7 @@ describe("Windows App Installation", () => {
|
|
|
19
20
|
it.skipIf(isLinux)("should download, install, and launch GitButler on Windows", async (context) => {
|
|
20
21
|
// Alternative approach using provision.installer helper
|
|
21
22
|
const testdriver = TestDriver(context, {
|
|
22
|
-
|
|
23
|
+
...getDefaults(context),
|
|
23
24
|
os: 'windows'
|
|
24
25
|
});
|
|
25
26
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared test logic for popup-loading variants.
|
|
3
|
+
* Each variant file imports this and calls it with specific options.
|
|
4
|
+
*/
|
|
5
|
+
import { describe, expect, it } from "vitest";
|
|
6
|
+
import { TestDriver } from "../lib/vitest/hooks.mjs";
|
|
7
|
+
|
|
8
|
+
export function popupLoadingTest(label, options = {}) {
|
|
9
|
+
describe(`Popup with Loading (${label})`, () => {
|
|
10
|
+
it("should accept cookies and wait for completion", async (context) => {
|
|
11
|
+
const testdriver = TestDriver(context, {
|
|
12
|
+
ip: context.ip || process.env.TD_IP,
|
|
13
|
+
...options,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
await testdriver.provision.chrome({
|
|
17
|
+
url: "https://v0-popup-with-loading-bar.vercel.app/",
|
|
18
|
+
});
|
|
19
|
+
await testdriver.screenshot();
|
|
20
|
+
|
|
21
|
+
// Accept the cookie banner to trigger the loading process
|
|
22
|
+
let acceptButton = await testdriver.find("Accept All button on the cookie banner", {timeout: 60000});
|
|
23
|
+
|
|
24
|
+
if (acceptButton.found()) {
|
|
25
|
+
await acceptButton.click();
|
|
26
|
+
} else {
|
|
27
|
+
console.log('no cookie banner found, proceeding without accepting cookies');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
await testdriver.find('Start button').click();
|
|
31
|
+
|
|
32
|
+
// Wait for "All done!" to appear with 120s timeout
|
|
33
|
+
const allDone = await testdriver.find("All done! text or heading in a modal or popup", { timeout: 120000 });
|
|
34
|
+
await testdriver.screenshot();
|
|
35
|
+
|
|
36
|
+
const result = await testdriver.assert("The text 'All done!' is visible on the page");
|
|
37
|
+
expect(result).toBeTruthy();
|
|
38
|
+
|
|
39
|
+
// Click Continue to proceed to the image grid
|
|
40
|
+
await testdriver.find("Continue button in the modal").click();
|
|
41
|
+
|
|
42
|
+
// Wait for the 5x5 grid of images to fully load (up to 60s) and click the rocket
|
|
43
|
+
await testdriver.find("rocket image in the 5x5 grid", { timeout: 60000, cacheThreshold: -1 }).click();
|
|
44
|
+
|
|
45
|
+
// Assert the success message appears
|
|
46
|
+
const rocketResult = await testdriver.assert("The text 'You found the rocket!' is visible on the page");
|
|
47
|
+
expect(rocketResult).toBeTruthy();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "testdriverai",
|
|
3
|
-
"version": "7.3.
|
|
3
|
+
"version": "7.3.7",
|
|
4
4
|
"description": "Next generation autonomous AI agent for end-to-end testing of web & desktop",
|
|
5
5
|
"main": "sdk.js",
|
|
6
6
|
"types": "sdk.d.ts",
|
|
@@ -134,7 +134,7 @@
|
|
|
134
134
|
"mocha": "^10.8.2",
|
|
135
135
|
"node-addon-api": "^8.0.0",
|
|
136
136
|
"prettier": "3.3.3",
|
|
137
|
-
"testdriverai": "^7.
|
|
137
|
+
"testdriverai": "^7.3.6",
|
|
138
138
|
"vitest": "^4.0.18"
|
|
139
139
|
},
|
|
140
140
|
"optionalDependencies": {
|
package/sdk-log-formatter.js
CHANGED
|
@@ -377,6 +377,12 @@ class SDKLogFormatter {
|
|
|
377
377
|
if (meta.cacheHit) {
|
|
378
378
|
metaParts.push(chalk.bold.yellow("⚡ cached"));
|
|
379
379
|
}
|
|
380
|
+
if (meta.confidence !== undefined && meta.confidence !== null) {
|
|
381
|
+
metaParts.push(chalk.dim.gray(`confidence: ${meta.confidence}`));
|
|
382
|
+
}
|
|
383
|
+
if (meta.reasoning) {
|
|
384
|
+
metaParts.push(chalk.dim.gray(`reasoning: ${meta.reasoning}`));
|
|
385
|
+
}
|
|
380
386
|
// Duration always last
|
|
381
387
|
if (meta.duration) {
|
|
382
388
|
metaParts.push(this.formatDurationColored(meta.duration, thresholdKey));
|
package/sdk.js
CHANGED
|
@@ -414,6 +414,7 @@ class Element {
|
|
|
414
414
|
|
|
415
415
|
result.similarity = this._response.similarity;
|
|
416
416
|
result.confidence = this._response.confidence;
|
|
417
|
+
result.reasoning = this._response.reasoning;
|
|
417
418
|
result.selector = this._response.selector;
|
|
418
419
|
|
|
419
420
|
// Include AI response text if available
|
|
@@ -716,20 +717,26 @@ class Element {
|
|
|
716
717
|
cacheStrategy: response.cacheStrategy || null,
|
|
717
718
|
similarity: response.similarity ?? null,
|
|
718
719
|
confidence: response.confidence ?? null,
|
|
720
|
+
reasoning: response.reasoning ?? null,
|
|
719
721
|
};
|
|
720
722
|
|
|
721
723
|
// Emit element found as log:log event
|
|
722
724
|
const { events } = require("./agent/events.js");
|
|
723
725
|
const Dashcam = require("./lib/core/Dashcam");
|
|
724
726
|
const consoleUrl = Dashcam.getConsoleUrl(this.sdk.config?.TD_API_ROOT);
|
|
725
|
-
const
|
|
727
|
+
const meta = {
|
|
726
728
|
x: this.coordinates.x,
|
|
727
729
|
y: this.coordinates.y,
|
|
728
730
|
duration: debugInfo.duration,
|
|
729
731
|
cacheHit: debugInfo.cacheHit,
|
|
730
732
|
selectorId: this._response?.selector,
|
|
731
733
|
consoleUrl: consoleUrl,
|
|
732
|
-
}
|
|
734
|
+
};
|
|
735
|
+
if (!debugInfo.cacheHit) {
|
|
736
|
+
meta.confidence = debugInfo.confidence;
|
|
737
|
+
meta.reasoning = debugInfo.reasoning;
|
|
738
|
+
}
|
|
739
|
+
const formattedMessage = formatter.formatElementFound(this.description, meta);
|
|
733
740
|
this.sdk.emitter.emit(events.log.log, formattedMessage);
|
|
734
741
|
|
|
735
742
|
// Log cache information in debug mode
|
|
@@ -1116,6 +1123,14 @@ class Element {
|
|
|
1116
1123
|
return this._response?.confidence ?? null;
|
|
1117
1124
|
}
|
|
1118
1125
|
|
|
1126
|
+
/**
|
|
1127
|
+
* Get model reasoning for why this element was selected
|
|
1128
|
+
* @returns {string|null}
|
|
1129
|
+
*/
|
|
1130
|
+
get reasoning() {
|
|
1131
|
+
return this._response?.reasoning ?? null;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1119
1134
|
/**
|
|
1120
1135
|
* Get element width if available
|
|
1121
1136
|
* @returns {number|null}
|
|
@@ -1567,6 +1582,51 @@ class TestDriverSDK {
|
|
|
1567
1582
|
return "C:\\PROGRA~1\\nodejs\\node_modules\\dashcam-chrome\\build";
|
|
1568
1583
|
}
|
|
1569
1584
|
|
|
1585
|
+
/**
|
|
1586
|
+
* Wait for Chrome DevTools Protocol debugger to be ready on port 9222,
|
|
1587
|
+
* then wait for a page to report loaded.
|
|
1588
|
+
* Works on both Windows (PowerShell) and Linux (sh).
|
|
1589
|
+
* @param {number} [timeoutMs=60000] - Maximum time to wait in ms
|
|
1590
|
+
* @returns {Promise<void>}
|
|
1591
|
+
*/
|
|
1592
|
+
async _waitForChromeDebuggerReady(timeoutMs = 60000) {
|
|
1593
|
+
const shell = this.os === "windows" ? "pwsh" : "sh";
|
|
1594
|
+
|
|
1595
|
+
if (this.os === "windows") {
|
|
1596
|
+
// Wait for port 9222 to be listening
|
|
1597
|
+
await this.exec(
|
|
1598
|
+
shell,
|
|
1599
|
+
`$timeout = ${Math.floor(timeoutMs / 1000)}; $elapsed = 0; while ($elapsed -lt $timeout) { try { $tcp = New-Object System.Net.Sockets.TcpClient; $tcp.Connect('127.0.0.1', 9222); $tcp.Close(); break } catch { Start-Sleep -Milliseconds 200; $elapsed += 0.2 } }`,
|
|
1600
|
+
timeoutMs,
|
|
1601
|
+
true,
|
|
1602
|
+
);
|
|
1603
|
+
|
|
1604
|
+
// Wait for a page target to appear via CDP
|
|
1605
|
+
await this.exec(
|
|
1606
|
+
shell,
|
|
1607
|
+
`$timeout = ${Math.floor(timeoutMs / 1000)}; $elapsed = 0; while ($elapsed -lt $timeout) { try { $r = Invoke-RestMethod -Uri 'http://localhost:9222/json' -TimeoutSec 2; if ($r | Where-Object { $_.type -eq 'page' }) { break } } catch {} Start-Sleep -Milliseconds 500; $elapsed += 0.5 }`,
|
|
1608
|
+
timeoutMs,
|
|
1609
|
+
true,
|
|
1610
|
+
);
|
|
1611
|
+
} else {
|
|
1612
|
+
// Wait for port 9222 to be listening
|
|
1613
|
+
await this.exec(
|
|
1614
|
+
shell,
|
|
1615
|
+
`timeout=${Math.floor(timeoutMs / 1000)}; elapsed=0; while [ $elapsed -lt $timeout ]; do nc -z localhost 9222 && break; sleep 0.2; elapsed=$((elapsed + 1)); done`,
|
|
1616
|
+
timeoutMs,
|
|
1617
|
+
true,
|
|
1618
|
+
);
|
|
1619
|
+
|
|
1620
|
+
// Wait for a page target to appear via CDP
|
|
1621
|
+
await this.exec(
|
|
1622
|
+
shell,
|
|
1623
|
+
`timeout=${Math.floor(timeoutMs / 1000)}; elapsed=0; while [ $elapsed -lt $timeout ]; do curl -s http://localhost:9222/json 2>/dev/null | grep -q '"type": "page"' && break; sleep 0.5; elapsed=$((elapsed + 1)); done`,
|
|
1624
|
+
timeoutMs,
|
|
1625
|
+
true,
|
|
1626
|
+
);
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1570
1630
|
_createProvisionAPI() {
|
|
1571
1631
|
const self = this;
|
|
1572
1632
|
|
|
@@ -1691,31 +1751,9 @@ class TestDriverSDK {
|
|
|
1691
1751
|
);
|
|
1692
1752
|
}
|
|
1693
1753
|
|
|
1694
|
-
// Wait for Chrome to be ready
|
|
1754
|
+
// Wait for Chrome debugger port and page to be ready
|
|
1755
|
+
await this._waitForChromeDebuggerReady();
|
|
1695
1756
|
await this.focusApplication("Google Chrome");
|
|
1696
|
-
|
|
1697
|
-
// Wait for URL to load
|
|
1698
|
-
try {
|
|
1699
|
-
const urlObj = new URL(url);
|
|
1700
|
-
const domain = urlObj.hostname;
|
|
1701
|
-
|
|
1702
|
-
for (let attempt = 0; attempt < 30; attempt++) {
|
|
1703
|
-
const result = await this.find(`${domain}`);
|
|
1704
|
-
|
|
1705
|
-
if (result.found()) {
|
|
1706
|
-
break;
|
|
1707
|
-
} else {
|
|
1708
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1709
|
-
}
|
|
1710
|
-
}
|
|
1711
|
-
|
|
1712
|
-
await this.focusApplication("Google Chrome");
|
|
1713
|
-
} catch (e) {
|
|
1714
|
-
console.warn(
|
|
1715
|
-
`[provision.chrome] ⚠️ Could not parse URL "${url}":`,
|
|
1716
|
-
e.message,
|
|
1717
|
-
);
|
|
1718
|
-
}
|
|
1719
1757
|
},
|
|
1720
1758
|
|
|
1721
1759
|
/**
|
|
@@ -1977,20 +2015,8 @@ with zipfile.ZipFile(io.BytesIO(zip_data)) as zf:
|
|
|
1977
2015
|
);
|
|
1978
2016
|
}
|
|
1979
2017
|
|
|
1980
|
-
// Wait for Chrome to be ready
|
|
1981
|
-
await this.
|
|
1982
|
-
|
|
1983
|
-
// Wait for New Tab to appear
|
|
1984
|
-
for (let attempt = 0; attempt < 30; attempt++) {
|
|
1985
|
-
const result = await this.find("New Tab");
|
|
1986
|
-
|
|
1987
|
-
if (result.found()) {
|
|
1988
|
-
break;
|
|
1989
|
-
} else {
|
|
1990
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1991
|
-
}
|
|
1992
|
-
}
|
|
1993
|
-
|
|
2018
|
+
// Wait for Chrome debugger port and page to be ready
|
|
2019
|
+
await this._waitForChromeDebuggerReady();
|
|
1994
2020
|
await this.focusApplication("Google Chrome");
|
|
1995
2021
|
},
|
|
1996
2022
|
|