testdriverai 6.2.2 → 7.1.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/AGENTS.md +550 -0
- package/CODEOWNERS +0 -1
- package/README.md +126 -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 +300 -85
- 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 +910 -296
- package/agent/lib/redraw.js +129 -41
- package/agent/lib/sandbox.js +29 -6
- package/agent/lib/sdk.js +22 -0
- package/agent/lib/system.js +0 -3
- package/agent/lib/validation.js +1 -7
- package/debug-locate-response.js +82 -0
- package/debugger/index.html +15 -4
- package/docs/ARCHITECTURE.md +424 -0
- package/docs/AWESOME_LOGS_QUICK_REF.md +100 -0
- package/docs/MIGRATION.md +425 -0
- package/docs/PRESETS.md +210 -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 +286 -152
- package/docs/guide/best-practices-polling.mdx +154 -0
- 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/dashcam.mdx +497 -0
- package/docs/v7/api/doubleClick.mdx +102 -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/mouseDown.mdx +161 -0
- package/docs/v7/api/mouseUp.mdx +164 -0
- package/docs/v7/api/pressKeys.mdx +349 -0
- package/docs/v7/api/rightClick.mdx +123 -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/configuration.mdx +380 -0
- package/docs/v7/getting-started/quickstart.mdx +332 -0
- package/docs/v7/guides/best-practices.mdx +486 -0
- package/docs/v7/guides/caching-ai.mdx +215 -0
- package/docs/v7/guides/caching-selectors.mdx +292 -0
- package/docs/v7/guides/caching.mdx +366 -0
- package/docs/v7/guides/ci-cd/azure.mdx +587 -0
- package/docs/v7/guides/ci-cd/circleci.mdx +523 -0
- package/docs/v7/guides/ci-cd/github-actions.mdx +457 -0
- package/docs/v7/guides/ci-cd/gitlab.mdx +498 -0
- package/docs/v7/guides/ci-cd/jenkins.mdx +664 -0
- package/docs/v7/guides/ci-cd/travis.mdx +438 -0
- package/docs/v7/guides/debugging.mdx +349 -0
- package/docs/v7/guides/faq.mdx +393 -0
- package/docs/v7/guides/migration.mdx +562 -0
- package/docs/v7/guides/performance.mdx +517 -0
- package/docs/{getting-started → v7/guides}/self-hosting.mdx +11 -12
- package/docs/v7/guides/troubleshooting.mdx +526 -0
- package/docs/v7/guides/vitest-plugin.mdx +477 -0
- package/docs/v7/guides/vitest.mdx +535 -0
- package/docs/v7/platforms/linux.mdx +308 -0
- package/docs/v7/platforms/macos.mdx +433 -0
- package/docs/v7/platforms/windows.mdx +430 -0
- package/docs/v7/playwright.mdx +342 -0
- package/docs/v7/presets/chrome-extension.mdx +223 -0
- package/docs/v7/presets/chrome.mdx +287 -0
- package/docs/v7/presets/electron.mdx +435 -0
- package/docs/v7/presets/vscode.mdx +398 -0
- package/docs/v7/presets/webapp.mdx +396 -0
- package/docs/v7/progressive-apis/CORE.md +459 -0
- package/docs/v7/progressive-apis/HOOKS.md +360 -0
- package/docs/v7/progressive-apis/PROGRESSIVE_DISCLOSURE.md +230 -0
- package/docs/v7/progressive-apis/PROVISION.md +266 -0
- package/eslint.config.js +19 -1
- 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 +830 -0
- package/package.json +29 -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 +1028 -0
- package/sdk.js +2567 -0
- package/{.github/workflows/self-hosted.yml → self-hosted.yml} +13 -4
- package/setup/aws/cloudformation.yaml +9 -2
- package/src/core/Dashcam.js +469 -0
- package/src/core/index.d.ts +150 -0
- package/src/core/index.js +12 -0
- package/src/presets/index.mjs +331 -0
- package/src/vitest/extended.mjs +108 -0
- package/src/vitest/hooks.d.ts +119 -0
- package/src/vitest/hooks.mjs +298 -0
- package/src/vitest/index.mjs +64 -0
- package/src/vitest/lifecycle.mjs +277 -0
- package/src/vitest/utils.mjs +150 -0
- package/test/dashcam.test.js +137 -0
- package/test/mcp-example-test.yaml +27 -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 +26 -0
- package/testdriver/acceptance-sdk/auto-cache-key-demo.test.mjs +56 -0
- package/testdriver/acceptance-sdk/chrome-extension.test.mjs +89 -0
- package/testdriver/acceptance-sdk/drag-and-drop.test.mjs +58 -0
- package/testdriver/acceptance-sdk/element-not-found.test.mjs +25 -0
- package/testdriver/acceptance-sdk/exec-js.test.mjs +43 -0
- package/testdriver/acceptance-sdk/exec-output.test.mjs +59 -0
- package/testdriver/acceptance-sdk/exec-pwsh.test.mjs +57 -0
- package/testdriver/acceptance-sdk/focus-window.test.mjs +36 -0
- package/testdriver/acceptance-sdk/formatted-logging.test.mjs +26 -0
- package/testdriver/acceptance-sdk/hooks-example.test.mjs +38 -0
- package/testdriver/acceptance-sdk/hover-image.test.mjs +34 -0
- package/testdriver/acceptance-sdk/hover-text-with-description.test.mjs +38 -0
- package/testdriver/acceptance-sdk/hover-text.test.mjs +27 -0
- package/testdriver/acceptance-sdk/match-image.test.mjs +36 -0
- package/testdriver/acceptance-sdk/presets-example.test.mjs +87 -0
- package/testdriver/acceptance-sdk/press-keys.test.mjs +50 -0
- package/testdriver/acceptance-sdk/prompt.test.mjs +33 -0
- package/testdriver/acceptance-sdk/scroll-keyboard.test.mjs +38 -0
- package/testdriver/acceptance-sdk/scroll-until-image.test.mjs +39 -0
- package/testdriver/acceptance-sdk/scroll-until-text.test.mjs +28 -0
- package/testdriver/acceptance-sdk/scroll.test.mjs +41 -0
- package/testdriver/acceptance-sdk/setup/globalTeardown.mjs +11 -0
- package/testdriver/acceptance-sdk/setup/testHelpers.mjs +420 -0
- package/testdriver/acceptance-sdk/setup/vitestSetup.mjs +40 -0
- package/testdriver/acceptance-sdk/sully-ai.test.mjs +234 -0
- package/testdriver/acceptance-sdk/test-console-logs.test.mjs +42 -0
- package/testdriver/acceptance-sdk/type-checking-demo.js +49 -0
- package/testdriver/acceptance-sdk/type.test.mjs +45 -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 +66 -0
- package/vitest.config.mjs.bak +44 -0
- package/.github/workflows/acceptance-v6.yml +0 -169
- package/.vscode/mcp.json +0 -9
- 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
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Get Started with TestDriver & Playwright"
|
|
3
|
+
sidebarTitle: "Playwright"
|
|
4
|
+
tag: "NEW"
|
|
5
|
+
description: "In this guide, you'll setup your TestDriver account, create a new Playwright project, and leverage TestDriver's AI to convert tests to natural language."
|
|
6
|
+
icon: "masks-theater"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
`@testdriver.ai/playwright` is a backwards-compatible wrapper around `@playwright/test` that uses TestDriver's Vision AI to:
|
|
12
|
+
|
|
13
|
+
- Make [natural language assertions](#assertions-with-expect-tomatchprompt)
|
|
14
|
+
- [Replace brittle selectors](#locating-elements-with-testdriver-locate) with natural language
|
|
15
|
+
- [Perform actions](#performing-actions-with-testdriver-act) with a prompt
|
|
16
|
+
- Test with an [automated agent](#agentic-tests-with-test-agent)
|
|
17
|
+
|
|
18
|
+
We'll be incrementally converting Playwright's example test from the sample code:
|
|
19
|
+
|
|
20
|
+
```typescript tests/example.spec.ts icon=square-js
|
|
21
|
+
// Before
|
|
22
|
+
test("get started link", async ({ page }) => {
|
|
23
|
+
await page.goto("https://playwright.dev/");
|
|
24
|
+
await page.getByRole("link", { name: "Get started" }).click();
|
|
25
|
+
await expect(
|
|
26
|
+
page.getByRole("heading", { name: "Installation" }),
|
|
27
|
+
).toBeVisible();
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
To using natural language with TestDriver:
|
|
32
|
+
|
|
33
|
+
```typescript tests/example.spec.ts icon=square-js
|
|
34
|
+
// After
|
|
35
|
+
import { test } from "@testdriver.ai/playwright";
|
|
36
|
+
|
|
37
|
+
test.describe("get started link", () => {
|
|
38
|
+
test.beforeEach(async ({ page }) => page.goto("https://playwright.dev/"));
|
|
39
|
+
test.agent(`
|
|
40
|
+
- Click the 'Get started' link
|
|
41
|
+
- Verify the 'Installation' heading is visible
|
|
42
|
+
`);
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Prerequisites
|
|
47
|
+
|
|
48
|
+
<Steps>
|
|
49
|
+
<Step title="Create a TestDriver Account">
|
|
50
|
+
You will need a [Free TestDriver Account](https://app.testdriver.ai/team) to get an API key.
|
|
51
|
+
|
|
52
|
+
<Card title="Sign Up for TestDriver" icon="user-plus" horizontal href="https://app.testdriver.ai/team">
|
|
53
|
+
|
|
54
|
+
</Card>
|
|
55
|
+
|
|
56
|
+
</Step>
|
|
57
|
+
<Step title="Set up your environment">
|
|
58
|
+
Copy your API key from [the TestDriver dashboard](https://app.testdriver.ai/team), and set it as an environment variable.
|
|
59
|
+
|
|
60
|
+
<Tabs>
|
|
61
|
+
<Tab title="macOS / Linux">
|
|
62
|
+
```bash Export an environment variable on macOS or Linux systems
|
|
63
|
+
export TD_API_KEY="your_api_key_here"
|
|
64
|
+
```
|
|
65
|
+
</Tab>
|
|
66
|
+
<Tab title="Windows">
|
|
67
|
+
```powershell Export an environment variable in PowerShell
|
|
68
|
+
setx TD_API_KEY "your_api_key_here"
|
|
69
|
+
```
|
|
70
|
+
</Tab>
|
|
71
|
+
</Tabs>
|
|
72
|
+
|
|
73
|
+
</Step>
|
|
74
|
+
</Steps>
|
|
75
|
+
|
|
76
|
+
## Setup Playwright
|
|
77
|
+
|
|
78
|
+
<Steps>
|
|
79
|
+
<Step title="Initialize Playwright">
|
|
80
|
+
<Info>
|
|
81
|
+
This is a condensed version of [Playwright's Installation Instructions](https://playwright.dev/docs/intro).
|
|
82
|
+
|
|
83
|
+
**If you're new to Playwright, you should follow their guide first.**
|
|
84
|
+
</Info>
|
|
85
|
+
In a new folder or an existing, run:
|
|
86
|
+
|
|
87
|
+
<Tabs>
|
|
88
|
+
<Tab title="npm">
|
|
89
|
+
```bash
|
|
90
|
+
npm init playwright@latest
|
|
91
|
+
```
|
|
92
|
+
</Tab>
|
|
93
|
+
<Tab title="yarn">
|
|
94
|
+
```bash
|
|
95
|
+
yarn create playwright
|
|
96
|
+
```
|
|
97
|
+
</Tab>
|
|
98
|
+
<Tab title="pnpm">
|
|
99
|
+
```bash
|
|
100
|
+
pnpm create playwright
|
|
101
|
+
```
|
|
102
|
+
</Tab>
|
|
103
|
+
</Tabs>
|
|
104
|
+
Select the following options when prompted:
|
|
105
|
+
|
|
106
|
+
```console
|
|
107
|
+
✔ Do you want to use TypeScript or JavaScript?
|
|
108
|
+
> TypeScript
|
|
109
|
+
✔ Where to put your end-to-end tests?
|
|
110
|
+
> tests
|
|
111
|
+
✔ Add a GitHub Actions workflow? (y/N)
|
|
112
|
+
> N
|
|
113
|
+
✔ Install Playwright browsers (can be done manually via 'npx playwright install')? (Y/n)
|
|
114
|
+
> Y
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Then, confirm Playwright works by running:
|
|
118
|
+
|
|
119
|
+
<Tabs>
|
|
120
|
+
<Tab title="npm">
|
|
121
|
+
```bash
|
|
122
|
+
npx playwright test
|
|
123
|
+
```
|
|
124
|
+
</Tab>
|
|
125
|
+
<Tab title="yarn">
|
|
126
|
+
```bash
|
|
127
|
+
yarn playwright test
|
|
128
|
+
```
|
|
129
|
+
</Tab>
|
|
130
|
+
<Tab title="pnpm">
|
|
131
|
+
```bash
|
|
132
|
+
pnpm exec playwright test
|
|
133
|
+
```
|
|
134
|
+
</Tab>
|
|
135
|
+
</Tabs>
|
|
136
|
+
|
|
137
|
+
</Step>
|
|
138
|
+
</Steps>
|
|
139
|
+
|
|
140
|
+
## Setup `@testdriver.ai/playwright`
|
|
141
|
+
|
|
142
|
+
<Steps>
|
|
143
|
+
<Step title="Install TestDriver">
|
|
144
|
+
`@testdriver.ai/playwright` as a backwards-compatible wrapper around `@playwright/test`:
|
|
145
|
+
|
|
146
|
+
<Tabs>
|
|
147
|
+
<Tab title="npm">
|
|
148
|
+
```bash
|
|
149
|
+
npm install @testdriver.ai/playwright
|
|
150
|
+
```
|
|
151
|
+
</Tab>
|
|
152
|
+
<Tab title="yarn">
|
|
153
|
+
```bash
|
|
154
|
+
yarn add @testdriver.ai/playwright
|
|
155
|
+
```
|
|
156
|
+
</Tab>
|
|
157
|
+
<Tab title="pnpm">
|
|
158
|
+
```bash
|
|
159
|
+
pnpm add @testdriver.ai/playwright
|
|
160
|
+
```
|
|
161
|
+
</Tab>
|
|
162
|
+
</Tabs>
|
|
163
|
+
|
|
164
|
+
</Step>
|
|
165
|
+
<Step title="Run Playwright">
|
|
166
|
+
Before we start using TestDriver in our tests, run Playwright in [UI Mode](https://playwright.dev/docs/test-ui-mode):
|
|
167
|
+
|
|
168
|
+
<Tabs>
|
|
169
|
+
<Tab title="npm">
|
|
170
|
+
```bash
|
|
171
|
+
npx playwright test --ui
|
|
172
|
+
```
|
|
173
|
+
</Tab>
|
|
174
|
+
<Tab title="yarn">
|
|
175
|
+
```bash
|
|
176
|
+
yarn playwright test --ui
|
|
177
|
+
```
|
|
178
|
+
</Tab>
|
|
179
|
+
<Tab title="pnpm">
|
|
180
|
+
```bash
|
|
181
|
+
pnpm exec playwright test --ui
|
|
182
|
+
```
|
|
183
|
+
</Tab>
|
|
184
|
+
</Tabs>
|
|
185
|
+

|
|
186
|
+
|
|
187
|
+
Clicking the ▶️ button should successfully run the tests in the UI,
|
|
188
|
+
just as they did before with `playwright test` in the CLI.
|
|
189
|
+
|
|
190
|
+
</Step>
|
|
191
|
+
<Step title="Import TestDriver">
|
|
192
|
+
For the sake of simplicity, we'll be working with one test file for now.
|
|
193
|
+
|
|
194
|
+
Open `tests/example.spec.ts` in your editor & rename the `@playwright/test`
|
|
195
|
+
import to `@testdriver.ai/playwright`:
|
|
196
|
+
|
|
197
|
+
```typescript tests/example.spec.ts icon=square-js
|
|
198
|
+
import { test, expect } from '@playwright/test'; // [!code --]
|
|
199
|
+
import { test, expect } from '@testdriver.ai/playwright'; // [!code ++]
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Click the <Icon icon="play" /> button to run the test and verify everything still works.
|
|
203
|
+
|
|
204
|
+
<Tip>
|
|
205
|
+
Click the <Icon icon="eye" /> button to automatically re-run tests on save.
|
|
206
|
+
</Tip>
|
|
207
|
+
|
|
208
|
+
</Step>
|
|
209
|
+
</Steps>
|
|
210
|
+
|
|
211
|
+
## Usage
|
|
212
|
+
|
|
213
|
+
Because TestDriver uses AI vision instead of selectors, we can use natural language for
|
|
214
|
+
[assertions](#assertions-with-expect-tomatchprompt),
|
|
215
|
+
[locating](#locating-elements-with-testdriver-locate),
|
|
216
|
+
performing [actions](#performing-actions-with-testdriver-act),
|
|
217
|
+
or even having an [agent](#agentic-tests-with-test-agent) test for you!
|
|
218
|
+
|
|
219
|
+
### Assertions with `expect.toMatchPrompt`
|
|
220
|
+
|
|
221
|
+
Replace `toBeVisible` with `toMatchPrompt` to assert that the element is visible on the screen:
|
|
222
|
+
|
|
223
|
+
```typescript tests/example.spec.ts icon=square-js
|
|
224
|
+
// [!code --:2]
|
|
225
|
+
// Expects page to have a heading with the name of Installation.
|
|
226
|
+
await expect(page.getByRole("heading", { name: "Installation" })).toBeVisible();
|
|
227
|
+
// [!code ++:2]
|
|
228
|
+
await expect(page).toMatchPrompt("'Installation' heading is visible");
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Before, the test needed code comments to describe what the assertion is _actually checking_.
|
|
232
|
+
|
|
233
|
+
With `toMatchPrompt`, natural language acts as a description, selector, and assertion in one
|
|
234
|
+
|
|
235
|
+
<Tip>
|
|
236
|
+
TestDriver can reduce the amount of complexity in your tests, but you can
|
|
237
|
+
still "opt-in" to Playwright assertions and selectors if you need to (e.g.
|
|
238
|
+
validating accessibility with `page.getByRole`).
|
|
239
|
+
</Tip>
|
|
240
|
+
|
|
241
|
+
### Locating elements with `testdriver.locate`
|
|
242
|
+
|
|
243
|
+
TestDriver can replace `data-testid`s, `getByRole`, and CSS selectors with natural language.
|
|
244
|
+
|
|
245
|
+
First, update your test to get access to the `testdriver` fixture:
|
|
246
|
+
|
|
247
|
+
```typescript tests/example.spec.ts icon=square-js
|
|
248
|
+
// [!code --]
|
|
249
|
+
test('get started link', async ({ page, }) => {
|
|
250
|
+
// [!code ++]
|
|
251
|
+
test('get started link', async ({ page, testdriver }) => {
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Then, replace `getByRole` with `testdriver.locate`:
|
|
255
|
+
|
|
256
|
+
```typescript tests/example.spec.ts icon=square-js
|
|
257
|
+
test('get started link', async ({ page, testdriver }) => {
|
|
258
|
+
await page.goto('https://playwright.dev/');
|
|
259
|
+
|
|
260
|
+
// [!code --:2]
|
|
261
|
+
// Click the get started link.
|
|
262
|
+
await page.getByRole("link", { name: "Get started" }).click();
|
|
263
|
+
// [!code ++:2]
|
|
264
|
+
const link = await testdriver(page).locate("Get started link");
|
|
265
|
+
await link.click();
|
|
266
|
+
|
|
267
|
+
await expect(page).toMatchPrompt("'Installation' heading is visible");
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Now, our test uses natural language to both describe & locate the element.
|
|
271
|
+
|
|
272
|
+
<Tip>
|
|
273
|
+
In the example above, you can still use Playwright to assert that the element is indeed a link for accessibility:
|
|
274
|
+
|
|
275
|
+
```typescript tests/example.spec.ts icon=square-js
|
|
276
|
+
const link = await testdriver(page).locate("Get started link");
|
|
277
|
+
// [!code ++]
|
|
278
|
+
expect(link).toHaveRole("link");
|
|
279
|
+
await link.click();
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
This way you can write user-centric tests _and_ validate the implementation.
|
|
283
|
+
|
|
284
|
+
</Tip>
|
|
285
|
+
|
|
286
|
+
### Performing actions with `testdriver.act`
|
|
287
|
+
|
|
288
|
+
We can combine `locate` and `click` from the previous example into one line with `testdriver.act`:
|
|
289
|
+
|
|
290
|
+
```typescript tests/example.spec.ts icon=square-js
|
|
291
|
+
test("get started link", async ({ page, testdriver }) => {
|
|
292
|
+
await page.goto("https://playwright.dev/");
|
|
293
|
+
|
|
294
|
+
// [!code --:2]
|
|
295
|
+
const link = await testdriver(page).locate("Get started link");
|
|
296
|
+
await link.click();
|
|
297
|
+
// [!code ++]
|
|
298
|
+
await testdriver(page).act("Click the 'Get started' link");
|
|
299
|
+
|
|
300
|
+
await expect(page).toMatchPrompt("'Installation' heading is visible");
|
|
301
|
+
});
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Now the test uses the page the way a user would!
|
|
305
|
+
|
|
306
|
+
### Agentic tests with `test.agent`
|
|
307
|
+
|
|
308
|
+
TestDriver can automatically perform the entire test for you with an AI agent:
|
|
309
|
+
|
|
310
|
+
```typescript tests/example.spec.ts icon=square-js
|
|
311
|
+
// [!code --:6]
|
|
312
|
+
test("get started link", async ({ page, testdriver }) => {
|
|
313
|
+
await page.goto("https://playwright.dev/");
|
|
314
|
+
await testdriver(page).act("Click the 'Get started' link");
|
|
315
|
+
await expect(page).toMatchPrompt("'Installation' heading is visible");
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// [!code ++:7]
|
|
319
|
+
test.describe("get started link", () => {
|
|
320
|
+
test.beforeEach(async ({ page }) => page.goto("https://playwright.dev/"));
|
|
321
|
+
test.agent(`
|
|
322
|
+
- Click the 'Get started' link
|
|
323
|
+
- Verify the 'Installation' heading is visible
|
|
324
|
+
`);
|
|
325
|
+
});
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
Instead of writing the test implementation, we've used [`test.describe`](https://playwright.dev/docs/api/class-test#test-describe) to describe the test still,
|
|
329
|
+
but replaced the `test` itself with `test.agent`.
|
|
330
|
+
|
|
331
|
+
<Tip>
|
|
332
|
+
Use `test.beforeEach` to prepare the page for the agent (e.g.
|
|
333
|
+
[`page.goto`](https://playwright.dev/docs/api/class-page#page-goto), calling
|
|
334
|
+
an API to create a user). Use
|
|
335
|
+
[`test.afterEach`](https://playwright.dev/docs/api/class-test#test-after-each)
|
|
336
|
+
to clean up after the agent (e.g. `page.close`) or perform additional logic
|
|
337
|
+
(e.g. clearing the session).
|
|
338
|
+
</Tip>
|
|
339
|
+
|
|
340
|
+
## Conclusion
|
|
341
|
+
|
|
342
|
+
With `@testdriver.ai/playwright`, you can use as much or as little of Playwright's _or_ TestDriver's API as you need to validate correctness. It's up to you!
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Chrome Extension Testing"
|
|
3
|
+
sidebarTitle: "Chrome Extensions"
|
|
4
|
+
description: "Test Chrome extensions with Chrome for Testing"
|
|
5
|
+
icon: "puzzle-piece"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Test Chrome extensions by loading them into Chrome for Testing. This preset uses the `launchChromeExtension()` helper to launch Chrome with a specific extension loaded by its Chrome Web Store ID.
|
|
11
|
+
|
|
12
|
+
## Quick Start
|
|
13
|
+
|
|
14
|
+
```javascript
|
|
15
|
+
import { test } from 'vitest';
|
|
16
|
+
import TestDriver from 'testdriverai';
|
|
17
|
+
import {
|
|
18
|
+
runPrerunChromeExtension,
|
|
19
|
+
runPostrun
|
|
20
|
+
} from 'testdriverai/testdriver/acceptance-sdk/setup/lifecycleHelpers.mjs';
|
|
21
|
+
|
|
22
|
+
test('test chrome extension', async () => {
|
|
23
|
+
const client = await TestDriver.create({
|
|
24
|
+
apiKey: process.env.TD_API_KEY,
|
|
25
|
+
os: "linux",
|
|
26
|
+
verbosity: 1,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Launch Chrome with extension loaded
|
|
30
|
+
// Extension ID from Chrome Web Store
|
|
31
|
+
await runPrerunChromeExtension(client, "cjpalhdlnbpafiamejdnhcphjbkeiagm");
|
|
32
|
+
|
|
33
|
+
// Your test code here
|
|
34
|
+
await client.focusApplication("Google Chrome");
|
|
35
|
+
|
|
36
|
+
// ... test extension functionality
|
|
37
|
+
|
|
38
|
+
await runPostrun(client);
|
|
39
|
+
await client.cleanup();
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Finding Extension IDs
|
|
44
|
+
|
|
45
|
+
Extension IDs can be found in the Chrome Web Store URL:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
https://chrome.google.com/webstore/detail/ublock-origin/cjpalhdlnbpafiamejdnhcphjbkeiagm
|
|
49
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
50
|
+
This is the extension ID
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Popular Extensions
|
|
54
|
+
|
|
55
|
+
| Extension | ID |
|
|
56
|
+
|-----------|---|
|
|
57
|
+
| uBlock Origin | `cjpalhdlnbpafiamejdnhcphjbkeiagm` |
|
|
58
|
+
| React Developer Tools | `fmkadmapgofadopljbjfkapdkoienihi` |
|
|
59
|
+
| Redux DevTools | `lmhkpmbekcpmknklioeibfkpmmfibljd` |
|
|
60
|
+
| Bitwarden | `nngceckbapebfimnlniiiahkandclblb` |
|
|
61
|
+
|
|
62
|
+
## Loading Multiple Extensions
|
|
63
|
+
|
|
64
|
+
Load multiple extensions by separating IDs with commas:
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
67
|
+
await launchChromeExtension(
|
|
68
|
+
client,
|
|
69
|
+
"cjpalhdlnbpafiamejdnhcphjbkeiagm,nngceckbapebfimnlniiiahkandclblb"
|
|
70
|
+
);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Complete Example
|
|
74
|
+
|
|
75
|
+
```javascript
|
|
76
|
+
import { describe, it, beforeAll, afterAll } from 'vitest';
|
|
77
|
+
import TestDriver from 'testdriverai';
|
|
78
|
+
import {
|
|
79
|
+
runPrerunChromeExtension,
|
|
80
|
+
runPostrun
|
|
81
|
+
} from 'testdriverai/testdriver/acceptance-sdk/setup/lifecycleHelpers.mjs';
|
|
82
|
+
|
|
83
|
+
describe('Chrome Extension Testing', () => {
|
|
84
|
+
let client;
|
|
85
|
+
let dashcamUrl;
|
|
86
|
+
|
|
87
|
+
beforeAll(async () => {
|
|
88
|
+
client = await TestDriver.create({
|
|
89
|
+
apiKey: process.env.TD_API_KEY,
|
|
90
|
+
os: "linux",
|
|
91
|
+
verbosity: 1,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Load uBlock Origin extension
|
|
95
|
+
await runPrerunChromeExtension(client, "cjpalhdlnbpafiamejdnhcphjbkeiagm");
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
afterAll(async () => {
|
|
99
|
+
if (client) {
|
|
100
|
+
dashcamUrl = await runPostrun(client);
|
|
101
|
+
await client.cleanup();
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should verify extension is loaded', async () => {
|
|
106
|
+
await client.focusApplication("Google Chrome");
|
|
107
|
+
|
|
108
|
+
// Navigate to a page
|
|
109
|
+
const element = await client.find("TestDriver.ai Sandbox");
|
|
110
|
+
expect(element.found()).toBe(true);
|
|
111
|
+
|
|
112
|
+
// Test extension-specific functionality
|
|
113
|
+
// For example, checking if ads are blocked with uBlock
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should access extension popup', async () => {
|
|
117
|
+
await client.focusApplication("Google Chrome");
|
|
118
|
+
|
|
119
|
+
// Open extension management
|
|
120
|
+
await client.exec(
|
|
121
|
+
"sh",
|
|
122
|
+
`xdotool key --clearmodifiers ctrl+shift+e`,
|
|
123
|
+
5000,
|
|
124
|
+
true
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
// Wait for extensions page
|
|
128
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Environment Setup
|
|
134
|
+
|
|
135
|
+
Chrome for Testing is pre-installed in the E2B sandbox environment at:
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
/opt/chrome-for-testing/chrome
|
|
139
|
+
/usr/local/bin/chrome-for-testing (symlink)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Direct API Usage
|
|
143
|
+
|
|
144
|
+
Use the lower-level API for more control:
|
|
145
|
+
|
|
146
|
+
```javascript
|
|
147
|
+
import { launchChromeExtension } from 'testdriverai/testdriver/acceptance-sdk/setup/lifecycleHelpers.mjs';
|
|
148
|
+
|
|
149
|
+
// Launch with specific extension and URL
|
|
150
|
+
await launchChromeExtension(
|
|
151
|
+
client,
|
|
152
|
+
"cjpalhdlnbpafiamejdnhcphjbkeiagm",
|
|
153
|
+
"https://example.com"
|
|
154
|
+
);
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Testing Extension Features
|
|
158
|
+
|
|
159
|
+
### Test Extension Popup
|
|
160
|
+
|
|
161
|
+
```javascript
|
|
162
|
+
it('opens extension popup', async () => {
|
|
163
|
+
// Click extension icon (varies by extension)
|
|
164
|
+
await client.click('extension icon in toolbar');
|
|
165
|
+
|
|
166
|
+
// Interact with popup
|
|
167
|
+
const popup = await client.find('extension popup window');
|
|
168
|
+
expect(popup.found()).toBe(true);
|
|
169
|
+
});
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Test Extension Settings
|
|
173
|
+
|
|
174
|
+
```javascript
|
|
175
|
+
it('configures extension settings', async () => {
|
|
176
|
+
// Right-click extension icon
|
|
177
|
+
await client.rightClick('extension icon');
|
|
178
|
+
|
|
179
|
+
// Click options
|
|
180
|
+
await client.click('Options');
|
|
181
|
+
|
|
182
|
+
// Configure settings
|
|
183
|
+
await client.click('Enable feature X');
|
|
184
|
+
await client.click('Save');
|
|
185
|
+
});
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Test Content Scripts
|
|
189
|
+
|
|
190
|
+
```javascript
|
|
191
|
+
it('verifies content script injection', async () => {
|
|
192
|
+
// Navigate to a page
|
|
193
|
+
await client.exec('sh', 'xdotool key ctrl+l', 5000, true);
|
|
194
|
+
await client.type('https://example.com');
|
|
195
|
+
await client.pressKeys('Enter');
|
|
196
|
+
|
|
197
|
+
// Check for extension-injected elements
|
|
198
|
+
const injected = await client.find('element added by extension');
|
|
199
|
+
expect(injected.found()).toBe(true);
|
|
200
|
+
});
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Troubleshooting
|
|
204
|
+
|
|
205
|
+
### Extension Not Loading
|
|
206
|
+
|
|
207
|
+
1. Verify the extension ID is correct
|
|
208
|
+
2. Check Chrome for Testing is installed in the sandbox
|
|
209
|
+
3. Ensure extension is compatible with Chrome for Testing version
|
|
210
|
+
|
|
211
|
+
### Extension Permissions
|
|
212
|
+
|
|
213
|
+
Some extensions may require additional permissions or setup. You may need to:
|
|
214
|
+
|
|
215
|
+
1. Navigate to `chrome://extensions/`
|
|
216
|
+
2. Enable developer mode
|
|
217
|
+
3. Grant required permissions
|
|
218
|
+
|
|
219
|
+
## See Also
|
|
220
|
+
|
|
221
|
+
- [Web Apps (Chrome)](/v7/presets/chrome) - Regular Chrome browser testing
|
|
222
|
+
- [Desktop Apps (Electron)](/v7/presets/electron) - Electron app testing
|
|
223
|
+
- [Lifecycle Helpers](/v7/guides/lifecycle) - Prerun/postrun functions
|