testdriverai 6.2.1 → 7.0.0
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/workflows/acceptance-linux.yml +75 -0
- package/.github/workflows/acceptance-sdk-tests.yml +133 -0
- package/.vscode/settings.json +5 -1
- package/MIGRATION.md +389 -0
- package/PLUGIN_MIGRATION.md +222 -0
- package/PROMPT_CACHE.md +200 -0
- package/SDK_LOGGING.md +222 -0
- package/SDK_MIGRATION.md +474 -0
- package/SDK_README.md +1122 -0
- package/{testdriver → _testdriver}/acceptance/drag-and-drop.yaml +2 -2
- package/{testdriver → _testdriver}/acceptance/snippets/login.yaml +1 -1
- package/_testdriver/examples/desktop/lifecycle/prerun.yaml +0 -0
- package/{testdriver → _testdriver}/examples/web/lifecycle/prerun.yaml +6 -1
- package/{testdriver → _testdriver}/lifecycle/postrun.yaml +3 -2
- package/_testdriver/lifecycle/prerun.yaml +15 -0
- package/{testdriver → _testdriver}/lifecycle/provision.yaml +7 -2
- package/agent/index.js +258 -68
- package/agent/interface.js +15 -0
- package/agent/lib/cache.js +142 -0
- package/agent/lib/commander.js +1 -39
- package/agent/lib/commands.js +143 -188
- package/agent/lib/redraw.js +6 -3
- package/agent/lib/sandbox.js +19 -5
- package/agent/lib/sdk.js +1 -0
- package/agent/lib/system.js +0 -3
- package/agent/lib/validation.js +1 -7
- package/debug-locate-response.js +82 -0
- package/debug-screenshot-1763401388589.png +0 -0
- package/debugger/index.html +16 -5
- package/docs/ARCHITECTURE.md +424 -0
- package/docs/AWESOME_LOGS_QUICK_REF.md +100 -0
- package/docs/QUICK_START_TEST_RECORDING.md +215 -0
- package/docs/SDK_AWESOME_LOGS.md +468 -0
- package/docs/TEST_RECORDING.md +388 -0
- package/docs/docs.json +232 -152
- package/docs/sdk-browser-rendering.md +167 -0
- package/docs/v6/getting-started/self-hosting.mdx +407 -0
- package/docs/{guide → v6/guide}/dashcam.mdx +1 -1
- package/docs/{guide → v6/guide}/environment-variables.mdx +4 -5
- package/docs/{guide → v6/guide}/lifecycle.mdx +1 -1
- package/docs/v6/overview/comparison.mdx +101 -0
- package/docs/v7/README.md +135 -0
- package/docs/v7/api/ai.mdx +205 -0
- package/docs/v7/api/assert.mdx +285 -0
- package/docs/v7/api/assertions.mdx +403 -0
- package/docs/v7/api/click.mdx +287 -0
- package/docs/v7/api/client.mdx +322 -0
- package/docs/v7/api/elements.mdx +479 -0
- package/docs/v7/api/exec.mdx +346 -0
- package/docs/v7/api/find.mdx +316 -0
- package/docs/v7/api/focusApplication.mdx +294 -0
- package/docs/v7/api/hover.mdx +279 -0
- package/docs/v7/api/pressKeys.mdx +349 -0
- package/docs/v7/api/sandbox.mdx +404 -0
- package/docs/v7/api/scroll.mdx +300 -0
- package/docs/v7/api/type.mdx +314 -0
- package/docs/v7/commands/assert.mdx +45 -0
- package/docs/v7/commands/exec.mdx +282 -0
- package/docs/v7/commands/focus-application.mdx +44 -0
- package/docs/v7/commands/hover-image.mdx +69 -0
- package/docs/v7/commands/hover-text.mdx +47 -0
- package/docs/v7/commands/if.mdx +53 -0
- package/docs/v7/commands/match-image.mdx +67 -0
- package/docs/v7/commands/press-keys.mdx +87 -0
- package/docs/v7/commands/remember.mdx +49 -0
- package/docs/v7/commands/run.mdx +44 -0
- package/docs/v7/commands/scroll-until-image.mdx +66 -0
- package/docs/v7/commands/scroll-until-text.mdx +60 -0
- package/docs/v7/commands/scroll.mdx +69 -0
- package/docs/v7/commands/type.mdx +45 -0
- package/docs/v7/commands/wait-for-image.mdx +54 -0
- package/docs/v7/commands/wait-for-text.mdx +48 -0
- package/docs/v7/commands/wait.mdx +45 -0
- package/docs/v7/getting-started/quickstart.mdx +199 -0
- package/docs/v7/guides/migration.mdx +562 -0
- package/docs/{getting-started → v7/guides}/self-hosting.mdx +11 -12
- package/docs/v7/playwright.mdx +342 -0
- package/eslint.config.js +19 -1
- package/examples/run-tests-with-recording.sh +70 -0
- package/examples/screenshot-example.js +63 -0
- package/examples/sdk-awesome-logs-demo.js +177 -0
- package/examples/sdk-cache-thresholds.js +96 -0
- package/examples/sdk-element-properties.js +155 -0
- package/examples/sdk-simple-example.js +65 -0
- package/examples/test-recording-example.test.js +166 -0
- package/interfaces/cli/lib/base.js +10 -4
- package/interfaces/logger.js +2 -1
- package/interfaces/shared-test-state.mjs +69 -0
- package/interfaces/vitest-plugin.mjs +744 -0
- package/mcp-server/AI_GUIDELINES.md +57 -0
- package/package.json +18 -5
- package/schema.json +8 -29
- package/scripts/view-test-results.mjs +96 -0
- package/sdk-log-formatter.js +714 -0
- package/sdk.d.ts +735 -0
- package/sdk.js +1906 -0
- package/{.github/workflows/self-hosted.yml → self-hosted.yml} +13 -4
- package/setup/aws/cloudformation.yaml +9 -2
- package/test/mcp-example-test.yaml +27 -0
- package/test-find-api.js +73 -0
- package/test-prompt-cache.js +96 -0
- package/test-sandbox-render.js +28 -0
- package/test-sdk-methods.js +15 -0
- package/test-sdk-refactor.js +53 -0
- package/test-stack-trace.mjs +57 -0
- package/testdriver/acceptance-sdk/QUICK_REFERENCE.md +61 -0
- package/testdriver/acceptance-sdk/README.md +128 -0
- package/testdriver/acceptance-sdk/TEST_REPORTING.md +245 -0
- package/testdriver/acceptance-sdk/assert.test.mjs +44 -0
- package/testdriver/acceptance-sdk/drag-and-drop.test.mjs +70 -0
- package/testdriver/acceptance-sdk/element-not-found.test.mjs +38 -0
- package/testdriver/acceptance-sdk/exec-js.test.mjs +55 -0
- package/testdriver/acceptance-sdk/exec-output.test.mjs +71 -0
- package/testdriver/acceptance-sdk/exec-pwsh.test.mjs +69 -0
- package/testdriver/acceptance-sdk/focus-window.test.mjs +48 -0
- package/testdriver/acceptance-sdk/formatted-logging.test.mjs +41 -0
- package/testdriver/acceptance-sdk/hover-image.test.mjs +43 -0
- package/testdriver/acceptance-sdk/hover-text-with-description.test.mjs +50 -0
- package/testdriver/acceptance-sdk/hover-text.test.mjs +41 -0
- package/testdriver/acceptance-sdk/match-image.test.mjs +48 -0
- package/testdriver/acceptance-sdk/press-keys.test.mjs +64 -0
- package/testdriver/acceptance-sdk/prompt.test.mjs +45 -0
- package/testdriver/acceptance-sdk/scroll-keyboard.test.mjs +52 -0
- package/testdriver/acceptance-sdk/scroll-until-image.test.mjs +51 -0
- package/testdriver/acceptance-sdk/scroll-until-text.test.mjs +42 -0
- package/testdriver/acceptance-sdk/scroll.test.mjs +50 -0
- package/testdriver/acceptance-sdk/setup/globalTeardown.mjs +11 -0
- package/testdriver/acceptance-sdk/setup/lifecycleHelpers.mjs +239 -0
- package/testdriver/acceptance-sdk/setup/testHelpers.mjs +648 -0
- package/testdriver/acceptance-sdk/setup/vitestSetup.mjs +40 -0
- package/testdriver/acceptance-sdk/type-checking-demo.js +49 -0
- package/testdriver/acceptance-sdk/type.test.mjs +84 -0
- package/verify-element-api.js +89 -0
- package/verify-types.js +0 -0
- package/vitest.config.example.js +19 -0
- package/vitest.config.mjs +65 -0
- package/vitest.config.mjs.bak +44 -0
- package/.github/workflows/acceptance-v6.yml +0 -169
- package/docs/overview/comparison.mdx +0 -82
- package/testdriver/lifecycle/prerun.yaml +0 -17
- /package/{testdriver/examples/desktop/lifecycle/prerun.yaml → .env.example} +0 -0
- /package/{testdriver → _testdriver}/acceptance/assert.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/dashcam.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/embed.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/exec-js.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/exec-output.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/exec-shell.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/focus-window.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/hover-image.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/hover-text-with-description.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/hover-text.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/if-else.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/match-image.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/press-keys.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/prompt.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/remember.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/screenshots/cart.png +0 -0
- /package/{testdriver → _testdriver}/acceptance/scroll-keyboard.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/scroll-until-image.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/scroll-until-text.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/scroll.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/snippets/match-cart.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/type.yaml +0 -0
- /package/{testdriver → _testdriver}/behavior/failure.yaml +0 -0
- /package/{testdriver → _testdriver}/behavior/hover-text.yaml +0 -0
- /package/{testdriver → _testdriver}/behavior/lifecycle/postrun.yaml +0 -0
- /package/{testdriver → _testdriver}/behavior/lifecycle/prerun.yaml +0 -0
- /package/{testdriver → _testdriver}/behavior/lifecycle/provision.yaml +0 -0
- /package/{testdriver → _testdriver}/behavior/secrets.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/dashcam-chrome.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/exec-pwsh-multiline.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/js-exception.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/js-promise.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/lifecycle/postrun.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/prompt-in-middle.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/prompt-nested.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/success-test.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/android/example.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/android/lifecycle/postrun.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/android/lifecycle/provision.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/android/readme.md +0 -0
- /package/{testdriver → _testdriver}/examples/chrome-extension/lifecycle/provision.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/desktop/lifecycle/provision.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/vscode-extension/lifecycle/provision.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/web/lifecycle/postrun.yaml +0 -0
- /package/docs/{account → v6/account}/dashboard.mdx +0 -0
- /package/docs/{account → v6/account}/enterprise.mdx +0 -0
- /package/docs/{account → v6/account}/pricing.mdx +0 -0
- /package/docs/{account → v6/account}/projects.mdx +0 -0
- /package/docs/{account → v6/account}/team.mdx +0 -0
- /package/docs/{action → v6/action}/ami.mdx +0 -0
- /package/docs/{action → v6/action}/performance.mdx +0 -0
- /package/docs/{action → v6/action}/secrets.mdx +0 -0
- /package/docs/{apps → v6/apps}/chrome-extensions.mdx +0 -0
- /package/docs/{apps → v6/apps}/desktop-apps.mdx +0 -0
- /package/docs/{apps → v6/apps}/mobile-apps.mdx +0 -0
- /package/docs/{apps → v6/apps}/static-websites.mdx +0 -0
- /package/docs/{apps → v6/apps}/tauri-apps.mdx +0 -0
- /package/docs/{bugs → v6/bugs}/jira.mdx +0 -0
- /package/docs/{cli → v6/cli}/overview.mdx +0 -0
- /package/docs/{commands → v6/commands}/assert.mdx +0 -0
- /package/docs/{commands → v6/commands}/exec.mdx +0 -0
- /package/docs/{commands → v6/commands}/focus-application.mdx +0 -0
- /package/docs/{commands → v6/commands}/hover-image.mdx +0 -0
- /package/docs/{commands → v6/commands}/hover-text.mdx +0 -0
- /package/docs/{commands → v6/commands}/if.mdx +0 -0
- /package/docs/{commands → v6/commands}/match-image.mdx +0 -0
- /package/docs/{commands → v6/commands}/press-keys.mdx +0 -0
- /package/docs/{commands → v6/commands}/remember.mdx +0 -0
- /package/docs/{commands → v6/commands}/run.mdx +0 -0
- /package/docs/{commands → v6/commands}/scroll-until-image.mdx +0 -0
- /package/docs/{commands → v6/commands}/scroll-until-text.mdx +0 -0
- /package/docs/{commands → v6/commands}/scroll.mdx +0 -0
- /package/docs/{commands → v6/commands}/type.mdx +0 -0
- /package/docs/{commands → v6/commands}/wait-for-image.mdx +0 -0
- /package/docs/{commands → v6/commands}/wait-for-text.mdx +0 -0
- /package/docs/{commands → v6/commands}/wait.mdx +0 -0
- /package/docs/{exporting → v6/exporting}/junit.mdx +0 -0
- /package/docs/{exporting → v6/exporting}/playwright.mdx +0 -0
- /package/docs/{features → v6/features}/auto-healing.mdx +0 -0
- /package/docs/{features → v6/features}/generation.mdx +0 -0
- /package/docs/{features → v6/features}/parallel-testing.mdx +0 -0
- /package/docs/{features → v6/features}/reusable-snippets.mdx +0 -0
- /package/docs/{features → v6/features}/selectorless.mdx +0 -0
- /package/docs/{features → v6/features}/visual-assertions.mdx +0 -0
- /package/docs/{getting-started → v6/getting-started}/ci.mdx +0 -0
- /package/docs/{getting-started → v6/getting-started}/cli.mdx +0 -0
- /package/docs/{getting-started → v6/getting-started}/editing.mdx +0 -0
- /package/docs/{getting-started → v6/getting-started}/playwright.mdx +0 -0
- /package/docs/{getting-started → v6/getting-started}/running.mdx +0 -0
- /package/docs/{getting-started → v6/getting-started}/vscode.mdx +0 -0
- /package/docs/{guide → v6/guide}/assertions.mdx +0 -0
- /package/docs/{guide → v6/guide}/authentication.mdx +0 -0
- /package/docs/{guide → v6/guide}/code.mdx +0 -0
- /package/docs/{guide → v6/guide}/locating.mdx +0 -0
- /package/docs/{guide → v6/guide}/protips.mdx +0 -0
- /package/docs/{guide → v6/guide}/variables.mdx +0 -0
- /package/docs/{guide → v6/guide}/waiting.mdx +0 -0
- /package/docs/{importing → v6/importing}/csv.mdx +0 -0
- /package/docs/{importing → v6/importing}/gherkin.mdx +0 -0
- /package/docs/{importing → v6/importing}/jira.mdx +0 -0
- /package/docs/{importing → v6/importing}/testrail.mdx +0 -0
- /package/docs/{integrations → v6/integrations}/electron.mdx +0 -0
- /package/docs/{integrations → v6/integrations}/netlify.mdx +0 -0
- /package/docs/{integrations → v6/integrations}/vercel.mdx +0 -0
- /package/docs/{interactive → v6/interactive}/explore.mdx +0 -0
- /package/docs/{interactive → v6/interactive}/run.mdx +0 -0
- /package/docs/{interactive → v6/interactive}/save.mdx +0 -0
- /package/docs/{overview → v6/overview}/faq.mdx +0 -0
- /package/docs/{overview → v6/overview}/performance.mdx +0 -0
- /package/docs/{overview → v6/overview}/quickstart.mdx +0 -0
- /package/docs/{overview → v6/overview}/what-is-testdriver.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/ai-chatbot.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/cookie-banner.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/file-upload.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/form-filling.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/log-in.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/pdf-generation.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/spell-check.mdx +0 -0
- /package/docs/{security → v6/security}/action.mdx +0 -0
- /package/docs/{security → v6/security}/agent.mdx +0 -0
- /package/docs/{security → v6/security}/platform.mdx +0 -0
- /package/docs/{tutorials → v6/tutorials}/advanced-test.mdx +0 -0
- /package/docs/{tutorials → v6/tutorials}/basic-test.mdx +0 -0
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
name: AWS
|
|
2
|
-
|
|
3
2
|
on:
|
|
4
3
|
workflow_dispatch:
|
|
5
4
|
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
6
7
|
paths-ignore:
|
|
7
8
|
- "docs/**"
|
|
9
|
+
pull_request:
|
|
10
|
+
branches:
|
|
11
|
+
- main
|
|
12
|
+
types:
|
|
13
|
+
- ready_for_review
|
|
14
|
+
pull_request_review:
|
|
15
|
+
types:
|
|
16
|
+
- submitted
|
|
8
17
|
|
|
9
18
|
jobs:
|
|
10
19
|
gather:
|
|
@@ -54,6 +63,7 @@ jobs:
|
|
|
54
63
|
- name: Setup AWS Instance
|
|
55
64
|
id: aws-setup
|
|
56
65
|
run: |
|
|
66
|
+
chmod +x ./setup/aws/spawn-runner.sh
|
|
57
67
|
OUTPUT=$(./setup/aws/spawn-runner.sh | tee /dev/stderr) # Capture and display output
|
|
58
68
|
echo "$OUTPUT"
|
|
59
69
|
PUBLIC_IP=$(echo "$OUTPUT" | grep "PUBLIC_IP=" | cut -d'=' -f2)
|
|
@@ -68,9 +78,8 @@ jobs:
|
|
|
68
78
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
69
79
|
AWS_REGION: us-east-2
|
|
70
80
|
AWS_LAUNCH_TEMPLATE_ID: lt-00d02f31cfc602f27
|
|
71
|
-
AMI_ID: ami-
|
|
72
|
-
|
|
73
|
-
RESOLUTION_HEIGHT: 1080
|
|
81
|
+
AMI_ID: ami-055cd47506a2f39bb
|
|
82
|
+
RESOLUTION: 1920x1080
|
|
74
83
|
- name: Run TestDriver
|
|
75
84
|
run: node bin/testdriverai.js run testdriver/acceptance/${{ matrix.test }} --ip="${{ steps.aws-setup.outputs.public-ip }}" --junit=out.xml
|
|
76
85
|
env:
|
|
@@ -168,8 +168,15 @@ Resources:
|
|
|
168
168
|
IpProtocol: tcp,
|
|
169
169
|
FromPort: 8765,
|
|
170
170
|
ToPort: 8765,
|
|
171
|
-
CidrIp:
|
|
172
|
-
Description: "pyautogui-cli WebSockets",
|
|
171
|
+
CidrIp: 35.171.123.200/32,
|
|
172
|
+
Description: "pyautogui-cli WebSockets - Static IP 1",
|
|
173
|
+
}
|
|
174
|
+
- {
|
|
175
|
+
IpProtocol: tcp,
|
|
176
|
+
FromPort: 8765,
|
|
177
|
+
ToPort: 8765,
|
|
178
|
+
CidrIp: 52.201.199.222/32,
|
|
179
|
+
Description: "pyautogui-cli WebSockets - Static IP 2",
|
|
173
180
|
}
|
|
174
181
|
- {
|
|
175
182
|
IpProtocol: tcp,
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: MCP Example Test
|
|
2
|
+
description: Test created using TestDriver MCP to navigate example.com
|
|
3
|
+
|
|
4
|
+
steps:
|
|
5
|
+
# Open Chrome browser and navigate to example.com
|
|
6
|
+
- exec:
|
|
7
|
+
code: Start-Process chrome "https://example.com"
|
|
8
|
+
language: pwsh
|
|
9
|
+
timeout: 5000
|
|
10
|
+
|
|
11
|
+
# Wait for the page to load
|
|
12
|
+
- wait-for-text:
|
|
13
|
+
text: Example Domain
|
|
14
|
+
timeout: 10000
|
|
15
|
+
|
|
16
|
+
# Assert that the Example Domain heading is visible
|
|
17
|
+
- assert:
|
|
18
|
+
expect: The text "Example Domain" is visible on the page
|
|
19
|
+
|
|
20
|
+
# Click on the "Learn more" link
|
|
21
|
+
- hover-text:
|
|
22
|
+
text: Learn more
|
|
23
|
+
action: click
|
|
24
|
+
|
|
25
|
+
# Wait for navigation to complete
|
|
26
|
+
- wait:
|
|
27
|
+
timeout: 2000
|
package/test-find-api.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Quick test of the new find() API
|
|
5
|
+
* This is a simple smoke test to verify the Element class works
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const TestDriver = require("../sdk");
|
|
9
|
+
|
|
10
|
+
async function testFindAPI() {
|
|
11
|
+
console.log("Testing new find() API...\n");
|
|
12
|
+
|
|
13
|
+
const client = new TestDriver("test-key", {
|
|
14
|
+
logging: false,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// Test 1: Create element without connecting
|
|
18
|
+
console.log("✓ Test 1: Creating Element instance");
|
|
19
|
+
const element = client.find("test element");
|
|
20
|
+
console.log(" Element description:", element.description);
|
|
21
|
+
console.log(" Element found():", element.found());
|
|
22
|
+
console.log(" Element coordinates:", element.getCoordinates());
|
|
23
|
+
|
|
24
|
+
// Test 2: Verify element methods exist
|
|
25
|
+
console.log("\n✓ Test 2: Verifying Element methods");
|
|
26
|
+
console.log(" Has find():", typeof element.find === "function");
|
|
27
|
+
console.log(" Has click():", typeof element.click === "function");
|
|
28
|
+
console.log(" Has hover():", typeof element.hover === "function");
|
|
29
|
+
console.log(
|
|
30
|
+
" Has doubleClick():",
|
|
31
|
+
typeof element.doubleClick === "function",
|
|
32
|
+
);
|
|
33
|
+
console.log(" Has rightClick():", typeof element.rightClick === "function");
|
|
34
|
+
console.log(" Has mouseDown():", typeof element.mouseDown === "function");
|
|
35
|
+
console.log(" Has mouseUp():", typeof element.mouseUp === "function");
|
|
36
|
+
console.log(" Has found():", typeof element.found === "function");
|
|
37
|
+
console.log(
|
|
38
|
+
" Has getCoordinates():",
|
|
39
|
+
typeof element.getCoordinates === "function",
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
// Test 3: Verify error handling for clicking unfound element
|
|
43
|
+
console.log("\n✓ Test 3: Error handling for unfound element");
|
|
44
|
+
try {
|
|
45
|
+
await element.click();
|
|
46
|
+
console.log(" ❌ Should have thrown error");
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.log(" ✓ Correctly throws error:", error.message);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Test 4: Verify TypeScript types exist (if running from TypeScript)
|
|
52
|
+
console.log("\n✓ Test 4: SDK methods");
|
|
53
|
+
console.log(" Has find():", typeof client.find === "function");
|
|
54
|
+
console.log(
|
|
55
|
+
" Has deprecated hoverText():",
|
|
56
|
+
typeof client.hoverText === "undefined" ? "not yet connected" : "exists",
|
|
57
|
+
);
|
|
58
|
+
console.log(
|
|
59
|
+
" Has deprecated waitForText():",
|
|
60
|
+
typeof client.waitForText === "undefined" ? "not yet connected" : "exists",
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
console.log("\n✅ All basic tests passed!");
|
|
64
|
+
console.log(
|
|
65
|
+
"\nNote: Full integration tests require connection to TestDriver sandbox.",
|
|
66
|
+
);
|
|
67
|
+
console.log("See examples/sdk-find-example.js for complete usage examples.");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
testFindAPI().catch((error) => {
|
|
71
|
+
console.error("Test failed:", error);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Simple test to verify prompt caching functionality
|
|
5
|
+
*
|
|
6
|
+
* This test demonstrates that:
|
|
7
|
+
* 1. First .prompt() call makes an API request and caches the YAML response
|
|
8
|
+
* 2. Second .prompt() call with the same prompt uses the cached YAML
|
|
9
|
+
* 3. Cache can be disabled with TD_NO_PROMPT_CACHE=true
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const TestDriver = require("./sdk.js");
|
|
13
|
+
const promptCache = require("./agent/lib/cache.js");
|
|
14
|
+
|
|
15
|
+
async function testPromptCache() {
|
|
16
|
+
console.log("Testing prompt caching functionality...\n");
|
|
17
|
+
|
|
18
|
+
const client = new TestDriver(process.env.TD_API_KEY, {
|
|
19
|
+
os: "linux",
|
|
20
|
+
logging: true,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
// Connect to sandbox
|
|
25
|
+
console.log("Connecting to sandbox...");
|
|
26
|
+
await client.connect();
|
|
27
|
+
console.log("Connected!\n");
|
|
28
|
+
|
|
29
|
+
const testPrompt = "click the search button";
|
|
30
|
+
|
|
31
|
+
// Clear cache for this prompt to start fresh
|
|
32
|
+
const cachePath = promptCache.getCachePath(testPrompt);
|
|
33
|
+
console.log(`Cache path for "${testPrompt}": ${cachePath}\n`);
|
|
34
|
+
|
|
35
|
+
// Test 1: First call (should make API request and cache)
|
|
36
|
+
console.log("Test 1: First .ai() call (should cache the response)");
|
|
37
|
+
const stats1 = promptCache.getCacheStats();
|
|
38
|
+
console.log(`Cache before: ${stats1.count} files`);
|
|
39
|
+
|
|
40
|
+
await client.ai(testPrompt);
|
|
41
|
+
|
|
42
|
+
const stats2 = promptCache.getCacheStats();
|
|
43
|
+
console.log(`Cache after: ${stats2.count} files`);
|
|
44
|
+
console.log(
|
|
45
|
+
`Cache hit: ${promptCache.hasCache(testPrompt) ? "YES" : "NO"}\n`,
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
// Test 2: Second call (should use cache)
|
|
49
|
+
console.log(
|
|
50
|
+
"Test 2: Second .ai() call with same prompt (should use cache)",
|
|
51
|
+
);
|
|
52
|
+
console.log('Look for "(using cached response)" message above...\n');
|
|
53
|
+
await client.ai(testPrompt);
|
|
54
|
+
|
|
55
|
+
// Test 3: Third call with cache disabled (should make API call)
|
|
56
|
+
console.log(
|
|
57
|
+
"\nTest 3: Third .ai() call with cache=false (should bypass cache)",
|
|
58
|
+
);
|
|
59
|
+
await client.ai(testPrompt, false);
|
|
60
|
+
|
|
61
|
+
// Test 4: Show cache contents
|
|
62
|
+
console.log("\nTest 4: Cache contents");
|
|
63
|
+
const cachedYaml = promptCache.readCache(testPrompt);
|
|
64
|
+
if (cachedYaml) {
|
|
65
|
+
console.log("Cached YAML preview (first 500 chars):");
|
|
66
|
+
console.log(cachedYaml.substring(0, 500));
|
|
67
|
+
console.log("...\n");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Test 5: Cache statistics
|
|
71
|
+
console.log("Test 5: Cache statistics");
|
|
72
|
+
const finalStats = promptCache.getCacheStats();
|
|
73
|
+
console.log(`Total cached prompts: ${finalStats.count}`);
|
|
74
|
+
console.log(`Cache files:`, finalStats.files.slice(0, 5));
|
|
75
|
+
|
|
76
|
+
console.log("\n✅ Prompt caching test completed!");
|
|
77
|
+
console.log("\nTo disable caching, pass false: client.ai(prompt, false)");
|
|
78
|
+
console.log("To clear cache, delete .testdriver/.cache/*.yaml files");
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error("❌ Test failed:", error.message);
|
|
81
|
+
throw error;
|
|
82
|
+
} finally {
|
|
83
|
+
// Disconnect
|
|
84
|
+
await client.disconnect();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Run the test
|
|
89
|
+
if (require.main === module) {
|
|
90
|
+
testPromptCache().catch((error) => {
|
|
91
|
+
console.error("Test error:", error);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
module.exports = testPromptCache;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const TestDriver = require("./sdk.js");
|
|
2
|
+
|
|
3
|
+
async function test() {
|
|
4
|
+
console.log("Testing sandbox rendering...");
|
|
5
|
+
|
|
6
|
+
const client = new TestDriver(process.env.TD_API_KEY, {
|
|
7
|
+
os: process.env.TEST_PLATFORM || "linux",
|
|
8
|
+
headless: false, // Should open browser
|
|
9
|
+
logging: true,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
console.log("Connecting to sandbox...");
|
|
14
|
+
const instance = await client.connect();
|
|
15
|
+
console.log("Connected to instance:", instance);
|
|
16
|
+
|
|
17
|
+
// Wait a bit to see if browser opens
|
|
18
|
+
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
19
|
+
|
|
20
|
+
await client.disconnect();
|
|
21
|
+
console.log("Test completed successfully");
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.error("Test failed:", error);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
test();
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const SDK = require("./sdk.js");
|
|
2
|
+
|
|
3
|
+
const client = new SDK("test-key");
|
|
4
|
+
|
|
5
|
+
// Get all public methods (non-private, non-constructor)
|
|
6
|
+
const methods = Object.getOwnPropertyNames(Object.getPrototypeOf(client))
|
|
7
|
+
.filter((m) => !m.startsWith("_") && m !== "constructor")
|
|
8
|
+
.sort();
|
|
9
|
+
|
|
10
|
+
console.log("Public SDK Methods:");
|
|
11
|
+
console.log(methods.join(", "));
|
|
12
|
+
console.log("\nTotal:", methods.length, "methods");
|
|
13
|
+
|
|
14
|
+
// Check if commands will be set up after connect
|
|
15
|
+
console.log("\nCommands before connect:", client.commands);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Quick test to verify SDK refactoring works correctly
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const TestDriver = require("./sdk.js");
|
|
8
|
+
|
|
9
|
+
async function test() {
|
|
10
|
+
console.log("Testing SDK refactor...\n");
|
|
11
|
+
|
|
12
|
+
// Test 1: SDK construction
|
|
13
|
+
console.log("✓ Test 1: Creating SDK instance");
|
|
14
|
+
const client = new TestDriver(process.env.TD_API_KEY || "test-key", {
|
|
15
|
+
logging: false,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
console.log(" - agent exists:", !!client.agent);
|
|
19
|
+
console.log(" - emitter exists:", !!client.emitter);
|
|
20
|
+
console.log(" - config exists:", !!client.config);
|
|
21
|
+
console.log(" - session exists:", !!client.session);
|
|
22
|
+
console.log(" - apiClient exists:", !!client.apiClient);
|
|
23
|
+
console.log(" - analytics exists:", !!client.analytics);
|
|
24
|
+
console.log(" - sandbox exists:", !!client.sandbox);
|
|
25
|
+
console.log(" - system exists:", !!client.system);
|
|
26
|
+
|
|
27
|
+
// Test 2: Check agent methods are accessible
|
|
28
|
+
console.log("\n✓ Test 2: Checking agent methods");
|
|
29
|
+
console.log(
|
|
30
|
+
" - agent.exploratoryLoop exists:",
|
|
31
|
+
typeof client.agent.exploratoryLoop,
|
|
32
|
+
);
|
|
33
|
+
console.log(" - agent.buildEnv exists:", typeof client.agent.buildEnv);
|
|
34
|
+
console.log(
|
|
35
|
+
" - agent.getRecentSandboxId exists:",
|
|
36
|
+
typeof client.agent.getRecentSandboxId,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
// Test 3: Check SDK methods
|
|
40
|
+
console.log("\n✓ Test 3: Checking SDK methods");
|
|
41
|
+
console.log(" - ai() exists:", typeof client.ai);
|
|
42
|
+
console.log(" - auth() exists:", typeof client.auth);
|
|
43
|
+
console.log(" - connect() exists:", typeof client.connect);
|
|
44
|
+
console.log(" - disconnect() exists:", typeof client.disconnect);
|
|
45
|
+
|
|
46
|
+
console.log("\n✅ All basic tests passed!");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
test().catch((error) => {
|
|
50
|
+
console.error("\n❌ Test failed:", error.message);
|
|
51
|
+
console.error(error.stack);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quick test to verify stack trace filtering works
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Mock the MatchError similar to commands.js
|
|
6
|
+
class MatchError extends Error {
|
|
7
|
+
constructor(message, fatal = false) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.fatal = fatal;
|
|
10
|
+
this.attachScreenshot = true;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Simulate SDK wrapper with stack filtering (improved version)
|
|
15
|
+
class TestSDK {
|
|
16
|
+
constructor() {
|
|
17
|
+
const command = async (message) => {
|
|
18
|
+
// Simulate the command throwing an error
|
|
19
|
+
throw new MatchError(`AI Assertion failed: ${message}`, true);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Wrap the method with proper stack trace handling
|
|
23
|
+
this.assert = async function (...args) {
|
|
24
|
+
// Capture the call site for better error reporting
|
|
25
|
+
const callSite = {};
|
|
26
|
+
Error.captureStackTrace(callSite, this.assert);
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
return await command(...args);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
// Replace the stack trace to point to the actual caller
|
|
32
|
+
if (Error.captureStackTrace && callSite.stack) {
|
|
33
|
+
const errorMessage = error.stack?.split("\n")[0];
|
|
34
|
+
const callerStack = callSite.stack?.split("\n").slice(1);
|
|
35
|
+
error.stack = errorMessage + "\n" + callerStack.join("\n");
|
|
36
|
+
}
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
}.bind(this);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Test it
|
|
44
|
+
async function runTest() {
|
|
45
|
+
const client = new TestSDK();
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
console.log("Testing stack trace...\n");
|
|
49
|
+
await client.assert("home page appears"); // Line 42 - this should show in stack
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.log("Error caught!");
|
|
52
|
+
console.log("Stack trace:");
|
|
53
|
+
console.log(error.stack);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
runTest();
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# SDK Test Reporting - Quick Reference
|
|
2
|
+
|
|
3
|
+
## 🚀 Commands
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
# Run all tests
|
|
7
|
+
npm run test:sdk
|
|
8
|
+
|
|
9
|
+
# View terminal summary
|
|
10
|
+
npm run test:sdk:results
|
|
11
|
+
|
|
12
|
+
# Open HTML report
|
|
13
|
+
npm run test:sdk:report
|
|
14
|
+
|
|
15
|
+
# Watch mode (dev)
|
|
16
|
+
npm run test:sdk:watch
|
|
17
|
+
|
|
18
|
+
# Interactive UI
|
|
19
|
+
npm run test:sdk:ui
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## 📊 What You Get
|
|
23
|
+
|
|
24
|
+
### Locally
|
|
25
|
+
|
|
26
|
+
1. **Console**: Verbose logs with full test output
|
|
27
|
+
2. **Terminal Summary**: `npm run test:sdk:results` - Quick pass/fail counts
|
|
28
|
+
3. **HTML Report**: `npm run test:sdk:report` - Interactive browser viewer
|
|
29
|
+
|
|
30
|
+
### GitHub Actions
|
|
31
|
+
|
|
32
|
+
1. **Step Summary**: Markdown tables in workflow summary page
|
|
33
|
+
2. **Test Summary Action**: Badge counts and annotations
|
|
34
|
+
3. **Artifacts**: Download junit.xml, results.json, and index.html
|
|
35
|
+
|
|
36
|
+
## 📁 Output Files
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
test-results/
|
|
40
|
+
├── junit.xml # For CI/CD tools
|
|
41
|
+
├── results.json # Machine-readable
|
|
42
|
+
└── index.html # Interactive report
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## ⚡ Quick Tips
|
|
46
|
+
|
|
47
|
+
- **Debugging failures?** → `npm run test:sdk:report` (HTML has best error context)
|
|
48
|
+
- **Quick status check?** → `npm run test:sdk:results` (terminal summary)
|
|
49
|
+
- **PR review?** → Check GitHub Actions summary tab
|
|
50
|
+
- **Need history?** → Download artifacts from GitHub Actions runs
|
|
51
|
+
|
|
52
|
+
## 🔍 GitHub Summary Preview
|
|
53
|
+
|
|
54
|
+
Every test run creates a summary with:
|
|
55
|
+
|
|
56
|
+
- ✅ Pass/fail counts table
|
|
57
|
+
- ❌ Failed test details with errors
|
|
58
|
+
- ✅ List of all passing tests
|
|
59
|
+
- ⏱️ Duration metrics
|
|
60
|
+
|
|
61
|
+
Find it: Actions → Your workflow run → Summary tab
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# TestDriver SDK Acceptance Tests
|
|
2
|
+
|
|
3
|
+
This directory contains acceptance tests for the TestDriver SDK using Vitest.
|
|
4
|
+
|
|
5
|
+
## Running Tests
|
|
6
|
+
|
|
7
|
+
### Run All Tests (Cross-Platform)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm run test:sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Run Platform-Specific Tests
|
|
14
|
+
|
|
15
|
+
Use the `TEST_PLATFORM` environment variable to run tests for a specific platform:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Run Windows-only tests
|
|
19
|
+
npm run test:sdk:windows
|
|
20
|
+
|
|
21
|
+
# Run macOS-only tests
|
|
22
|
+
npm run test:sdk:mac
|
|
23
|
+
|
|
24
|
+
# Run Linux-only tests
|
|
25
|
+
npm run test:sdk:linux
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Or set the environment variable directly:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
TEST_PLATFORM=windows npm run test:sdk
|
|
32
|
+
TEST_PLATFORM=mac npm run test:sdk
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Test Organization
|
|
36
|
+
|
|
37
|
+
### Cross-Platform Tests
|
|
38
|
+
|
|
39
|
+
Tests without a platform suffix run on all platforms:
|
|
40
|
+
|
|
41
|
+
- `hover-text.test.mjs` - Runs everywhere
|
|
42
|
+
- `scroll.test.mjs` - Runs everywhere
|
|
43
|
+
- `screenshot.test.mjs` - Runs everywhere
|
|
44
|
+
|
|
45
|
+
### Platform-Specific Tests
|
|
46
|
+
|
|
47
|
+
Platform-specific tests use naming conventions:
|
|
48
|
+
|
|
49
|
+
- `*.windows.test.mjs` - Windows-only tests (e.g., `exec-pwsh.windows.test.mjs`)
|
|
50
|
+
- `*.mac.test.mjs` - macOS-only tests
|
|
51
|
+
- `*.linux.test.mjs` - Linux-only tests
|
|
52
|
+
|
|
53
|
+
### Conditional Test Skipping
|
|
54
|
+
|
|
55
|
+
Some tests use `skipIf` to conditionally skip based on the platform:
|
|
56
|
+
|
|
57
|
+
```javascript
|
|
58
|
+
it.skipIf(() => testdriver.os === "linux")(
|
|
59
|
+
"should run on Windows/Mac",
|
|
60
|
+
async () => {
|
|
61
|
+
// This test will be skipped on Linux
|
|
62
|
+
},
|
|
63
|
+
);
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Environment Variables
|
|
67
|
+
|
|
68
|
+
- `TEST_PLATFORM` - Filter tests by platform (`windows`, `mac`, `linux`)
|
|
69
|
+
- `TD_OS` - Override the sandbox OS (defaults to `linux`)
|
|
70
|
+
- `TD_API_KEY` - Your TestDriver API key (required)
|
|
71
|
+
- `TD_API_ROOT` - API endpoint (optional)
|
|
72
|
+
- `DEBUG_ENV` - Show environment variable loading (optional)
|
|
73
|
+
- `DEBUG_EVENTS` - Enable detailed event logging (optional)
|
|
74
|
+
|
|
75
|
+
## Examples
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Run only Windows tests on a Windows sandbox
|
|
79
|
+
TEST_PLATFORM=windows npm run test:sdk
|
|
80
|
+
|
|
81
|
+
# Run all tests but use a Windows sandbox
|
|
82
|
+
TD_OS=windows npm run test:sdk
|
|
83
|
+
|
|
84
|
+
# Run with debugging enabled
|
|
85
|
+
DEBUG_ENV=true DEBUG_EVENTS=true npm run test:sdk
|
|
86
|
+
|
|
87
|
+
# Watch mode for development
|
|
88
|
+
npm run test:sdk:watch
|
|
89
|
+
|
|
90
|
+
# Generate coverage report
|
|
91
|
+
npm run test:sdk:coverage
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Test Structure
|
|
95
|
+
|
|
96
|
+
Each test follows this pattern:
|
|
97
|
+
|
|
98
|
+
```javascript
|
|
99
|
+
import { afterEach, beforeEach, describe, it } from "vitest";
|
|
100
|
+
import {
|
|
101
|
+
createTestClient,
|
|
102
|
+
setupTest,
|
|
103
|
+
teardownTest,
|
|
104
|
+
} from "./setup/testHelpers.mjs";
|
|
105
|
+
|
|
106
|
+
describe("My Test", () => {
|
|
107
|
+
let testdriver;
|
|
108
|
+
|
|
109
|
+
beforeEach(async () => {
|
|
110
|
+
testdriver = createTestClient();
|
|
111
|
+
await setupTest(testdriver);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
afterEach(async () => {
|
|
115
|
+
await teardownTest(testdriver);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("should do something", async () => {
|
|
119
|
+
// Your test logic
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## See Also
|
|
125
|
+
|
|
126
|
+
- [SDK README](../../SDK_README.md) - Full SDK documentation
|
|
127
|
+
- [Quick Reference](./QUICK_REFERENCE.md) - SDK method quick reference
|
|
128
|
+
- [Test Reporting](./TEST_REPORTING.md) - Test recording and reporting docs
|