testdriverai 6.2.0 → 6.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/.github/workflows/acceptance-tests.yml +2 -0
  2. package/.github/workflows/acceptance-v6.yml +2 -0
  3. package/.github/workflows/lint.yml +4 -1
  4. package/.github/workflows/publish-canary.yml +2 -0
  5. package/.github/workflows/publish-latest.yml +1 -0
  6. package/.github/workflows/self-hosted.yml +102 -0
  7. package/.prettierignore +1 -0
  8. package/.vscode/settings.json +4 -1
  9. package/agent/events.js +1 -10
  10. package/agent/index.js +98 -55
  11. package/agent/interface.js +43 -6
  12. package/agent/lib/censorship.js +15 -10
  13. package/agent/lib/commander.js +31 -18
  14. package/agent/lib/commands.js +62 -17
  15. package/agent/lib/debugger-server.js +0 -5
  16. package/agent/lib/generator.js +2 -2
  17. package/agent/lib/sdk.js +2 -1
  18. package/agent/lib/source-mapper.js +1 -1
  19. package/debugger/index.html +1 -1
  20. package/docs/account/enterprise.mdx +8 -12
  21. package/docs/account/pricing.mdx +2 -2
  22. package/docs/account/projects.mdx +5 -0
  23. package/docs/apps/tauri-apps.mdx +361 -0
  24. package/docs/cli/overview.mdx +6 -6
  25. package/docs/commands/assert.mdx +1 -0
  26. package/docs/commands/hover-text.mdx +3 -1
  27. package/docs/commands/match-image.mdx +5 -4
  28. package/docs/commands/press-keys.mdx +6 -8
  29. package/docs/commands/scroll-until-image.mdx +8 -7
  30. package/docs/commands/scroll-until-text.mdx +7 -6
  31. package/docs/commands/wait-for-image.mdx +5 -4
  32. package/docs/commands/wait-for-text.mdx +6 -5
  33. package/docs/docs.json +42 -40
  34. package/docs/getting-started/playwright.mdx +342 -0
  35. package/docs/getting-started/self-hosting.mdx +370 -0
  36. package/docs/getting-started/vscode.mdx +67 -56
  37. package/docs/guide/dashcam.mdx +118 -0
  38. package/docs/guide/environment-variables.mdx +5 -5
  39. package/docs/images/content/self-hosted/launchtemplateid.png +0 -0
  40. package/docs/images/content/vscode/ide-full.png +0 -0
  41. package/docs/images/content/vscode/running.png +0 -0
  42. package/docs/overview/comparison.mdx +22 -39
  43. package/docs/overview/quickstart.mdx +84 -32
  44. package/docs/styles.css +10 -1
  45. package/interfaces/cli/commands/generate.js +3 -0
  46. package/interfaces/cli/lib/base.js +27 -5
  47. package/interfaces/cli/utils/factory.js +17 -4
  48. package/interfaces/logger.js +4 -4
  49. package/interfaces/readline.js +1 -1
  50. package/package.json +3 -3
  51. package/schema.json +21 -0
  52. package/setup/aws/cloudformation.yaml +463 -0
  53. package/setup/aws/spawn-runner.sh +190 -0
  54. package/testdriver/acceptance/hover-text.yaml +2 -1
  55. package/testdriver/acceptance/prompt.yaml +4 -1
  56. package/testdriver/acceptance/scroll-until-image.yaml +5 -0
  57. package/testdriver/edge-cases/js-exception.yaml +8 -0
  58. package/testdriver/edge-cases/js-promise.yaml +19 -0
  59. package/testdriver/edge-cases/lifecycle/postrun.yaml +10 -0
  60. package/testdriver/edge-cases/success-test.yaml +9 -0
  61. package/testdriver/examples/web/lifecycle/postrun.yaml +7 -0
  62. package/testdriver/examples/web/lifecycle/{provision.yaml → prerun.yaml} +6 -0
  63. package/testdriver/lifecycle/postrun.yaml +7 -0
  64. package/testdriver/lifecycle/prerun.yaml +17 -0
@@ -10,11 +10,10 @@ icon: boxing-glove
10
10
  TestDriver operates a full desktop environment, so it can run any application.
11
11
 
12
12
  <div className="comparison-table">
13
- | Application | TestDriver | Playwright | Selenium |
14
- |:-----------------:|:---------:|:-----------:|:--------:|
15
- | Web Apps | ✅ | ✅ | ✅ |
16
- | Desktop Apps | ✅ | | |
17
- | Chrome Extensions | ✅ | | |
13
+ | Application | TestDriver | Playwright | Selenium |
14
+ |:-----------------:|:---------:|:-----------:|:--------:| | Web Apps | ✅ |
15
+ | | | Mobile Apps | ✅ | ✅ | ✅ | | VS Code | ✅ | ✅ | ✅ | | Desktop
16
+ Apps | | | | | Chrome Extensions | | | |
18
17
  </div>
19
18
 
20
19
  ## Testing features
@@ -22,16 +21,11 @@ TestDriver operates a full desktop environment, so it can run any application.
22
21
  TestDriver is AI first.
23
22
 
24
23
  <div className="comparison-table">
25
- | Feature | TestDriver | Playwright | Selenium |
26
- |:--------------------:|:---------:|:----------:|:--------:|
27
- | Test Generation | ✅ | | |
28
- | Adaptive Testing | ✅ | | |
29
- | Visual Assertions | ✅ | | |
30
- | Self Healing | ✅ | | |
31
- | Application Switching | ✅ | | |
32
- | GitHub Actions | ✅ | ✅ | |
33
- | Team Dashboard | ✅ | | |
34
- | Team Collaboration | ✅ | | |
24
+ | Feature | TestDriver | Playwright | Selenium |
25
+ |:--------------------:|:---------:|:----------:|:--------:| | Test Generation
26
+ | | | | | Adaptive Testing | | | | | Visual Assertions | ✅ | | | | Self
27
+ Healing | | | | | Application Switching | | | | | GitHub Actions | ✅ |
28
+ | | | Team Dashboard | | | | | Team Collaboration | ✅ | | |
35
29
  </div>
36
30
 
37
31
  ## Test coverage
@@ -59,15 +53,11 @@ TestDriver has more coverage than selector-based frameworks.
59
53
  Debugging features are powered by [Dashcam.io](https://dashcam.io).
60
54
 
61
55
  <div className="comparison-table">
62
- | Feature | TestDriver | Playwright | Selenium |
63
- |:------------------:|:----------:|:----------:|:--------:|
64
- | AI Summary | ✅ | | |
65
- | Video Replay | ✅ | ✅ | |
66
- | Browser Logs | | | |
67
- | Desktop Logs | ✅ | | |
68
- | Network Requests | ✅ | ✅ | |
69
- | Team Dashboard | ✅ | | |
70
- | Team Collaboration | ✅ | | |
56
+ | Feature | TestDriver | Playwright | Selenium |
57
+ |:------------------:|:----------:|:----------:|:--------:| | AI Summary | ✅
58
+ | | | | Video Replay | || | | Browser Logs | ✅ | ✅ | | | Desktop Logs
59
+ | | | | | Network Requests | | ✅ | | | Team Dashboard | ✅ | | | | Team
60
+ Collaboration | | | |
71
61
  </div>
72
62
 
73
63
  ## Web browser support
@@ -75,15 +65,10 @@ Debugging features are powered by [Dashcam.io](https://dashcam.io).
75
65
  TestDriver is browser agnostic and supports any version of any browser.
76
66
 
77
67
  <div className="comparison-table">
78
- | Feature | TestDriver | Playwright | Selenium |
79
- |:--------:|:----------:|:----------:|:--------:|
80
- | Chrome | ✅ | ✅ | ✅ |
81
- | Firefox | ✅ | ✅ | ✅ |
82
- | Webkit | ✅ | ✅ | ✅ |
83
- | IE | ✅ | | ✅ |
84
- | Edge | ✅ | ✅ | ✅ |
85
- | Opera | ✅ | | ✅ |
86
- | Safari | ✅ | | ✅ |
68
+ | Feature | TestDriver | Playwright | Selenium |
69
+ |:--------:|:----------:|:----------:|:--------:| | Chrome | ✅ | ✅ | ✅ | |
70
+ Firefox | | ✅ | ✅ | | Webkit | | ✅ | ✅ | | IE | ✅ | | ✅ | | Edge |
71
+ | | ✅ | | Opera | | | | | Safari | ✅ | | ✅ |
87
72
  </div>
88
73
 
89
74
  ## Operating system support
@@ -91,9 +76,7 @@ TestDriver is browser agnostic and supports any version of any browser.
91
76
  TestDriver currently supports Mac and Windows!
92
77
 
93
78
  <div className="comparison-table">
94
- | Feature | TestDriver | Playwright | Selenium |
95
- |:--------:|:----------:|:----------:|:--------:|
96
- | Windows | ✅ | ✅ | ✅ |
97
- | Mac | ✅ | ✅ | ✅ |
98
- | Linux | | ✅ | ✅ |
99
- </div>
79
+ | Feature | TestDriver | Playwright | Selenium |
80
+ |:--------:|:----------:|:----------:|:--------:| | Windows | ✅ | ✅ | ✅ | |
81
+ Mac | | ✅ | ✅ | | Linux | | | ✅ |
82
+ </div>
@@ -6,53 +6,105 @@ icon: "gauge-high"
6
6
  mode: "wide"
7
7
  ---
8
8
 
9
- <Tip>
10
- You will need a [TestDriver Pro](https://app.testdriver.ai/team) account
11
- ($20/m) to complete setup.
12
- </Tip>
13
-
14
9
  <Steps>
15
- <Step title="Install TestDriver">
16
-
17
- Click the button below to install the TestDriver extension for your preferred IDE. Then, follow the setup guide and chat with TestDriver to create your first test.
18
-
10
+ <Step title="Create a TestDriver Account">
11
+
12
+ You will need a [TestDriver Pro](https://app.testdriver.ai/team) account ($20/month) to get an API key.
13
+
19
14
  <Card
20
- horizontal
21
- title="VS Code"
15
+ title="Sign Up for TestDriver"
16
+ icon="user-plus"
17
+ href="https://app.testdriver.ai/team"
22
18
  arrow
23
- href="vscode:extension/testdriver.testdriver"
24
- icon="/images/content/extension/vscode.svg"
19
+ horizontal
25
20
  ></Card>
26
21
 
27
- <Card
28
- horizontal
29
- title="Cursor"
30
- arrow
31
- href="cursor:extension/testdriver.testdriver"
32
- icon="/images/content/extension/cursor.svg"
22
+ </Step>
23
+ <Step title="Set up your environment">
24
+
25
+ Copy your API key from [the TestDriver dashboard](https://app.testdriver.ai/team), and set it as an environment variable.
26
+
27
+ <Tabs>
28
+ <Tab title="macOS / Linux">
29
+ ```bash Export an environment variable on macOS or Linux systems
30
+ export TD_API_KEY="your_api_key_here"
31
+ ```
32
+ </Tab>
33
+ <Tab title="Windows">
34
+ ```powershell Export an environment variable in PowerShell
35
+ setx TD_API_KEY "your_api_key_here"
36
+ ```
37
+ </Tab>
38
+ </Tabs>
39
+
40
+ <Tip>Using VS Code, Cursor, or Windsurf? [Try our VS Code Extension (beta)](/getting-started/vscode).</Tip>
41
+
42
+ </Step>
43
+ <Step title="Check out an example test">
44
+
45
+ Download the TestDriver GitHub repository and run the example test.
33
46
 
34
- > </Card>
47
+ ```bash
48
+ git clone --depth 1 https://github.com/testdriverai/cli testdriverai
49
+ cd testdriverai/testdriver/acceptance
50
+ ```
35
51
 
36
- <Card
37
- horizontal
38
- title="Windsurf"
39
- arrow
40
- href="windsurf:extension/testdriver.testdriver"
41
- icon="/images/content/extension/windsurf.svg"
42
- ></Card>
52
+ TestDriver tests are written in YAML, a human-readable data format. The `prompt.yaml` file contains an example series of steps for the agent to execute.
53
+
54
+ ```yaml testdriver/acceptance/prompt.yaml
55
+ steps:
56
+ - prompt: log in
57
+ - prompt: add an item to the cart
58
+ - prompt: click on the cart icon
59
+ - prompt: complete checkout
60
+ ```
43
61
 
44
- The extension will generate most of your tests for you, but you'll probably want to customize them. [Learn more about editing tests](/getting-started/editing).
62
+ Each step has a `prompt` that describes what the agent should do. The agent will use the prompt to generate [commands](/commands/assert) that make the tests faster and more reliable the next time you run the test.
45
63
 
46
64
  </Step>
47
- <Step title="Run your tests in CI/CD">
65
+ <Step title="Generate regression test from prompts">
48
66
 
49
- Next, use `testdriverai` to run your tests in CI/CD pipelines.
67
+ Run the following command to run the test file. TestDriver will spawn a virtual machine, launch the sandbox test page, and execute the steps defined in the `prompt.yaml` file.
50
68
 
51
69
  ```bash
52
- TD_API_KEY=YOUR_KEY npx testdriverai@latest run testdriver/test.yaml
70
+ npx testdriverai@latest run prompt.yaml --write --heal
53
71
  ```
54
72
 
55
- Note that you'll want to store your API key within secret storeage. [Learn more about running tests in CI/CD pipelines](/action/setup).
73
+ The `--write` flag tells TestDriver to save any generated commands to the test file, and the `--heal` flag allows TestDriver to recover from unexpected issues during the test run.
74
+
75
+ <Tip>You can use an interactive CLI to generate test steps with the [explore command](/interactive/explore)</Tip>
76
+
77
+ </Step>
78
+ <Step title="Run the generated regression test">
79
+
80
+ After TestDriver has run the exploratory test, you'll see that the `prompt.yaml` file has been updated with commands generated by the agent to make the test faster and more reliable.
81
+
82
+ ```yaml
83
+ version: 6.0.0
84
+ steps:
85
+ - prompt: focus chrome
86
+ commands:
87
+ - command: focus-application
88
+ name: Google Chrome
89
+ - prompt: enter a username
90
+ commands:
91
+ - command: hover-text
92
+ text: Username
93
+ description: username input field
94
+ action: click
95
+ - command: type
96
+ text: standard_user
97
+ - prompt: enter a password
98
+ commands:
99
+ - command: hover-text
100
+ text: Password
101
+ description: password input field
102
+ action: click
103
+ - command: type
104
+ text: secret_password
105
+ ```
106
+
107
+ The `--write` command tells the agent to save the generated commands to the test file, and the `--heal` command gives the agent permission to recover if something goes wrong.
56
108
 
57
109
  </Step>
58
110
 
package/docs/styles.css CHANGED
@@ -53,4 +53,13 @@
53
53
  transform: translateX(-50%);
54
54
  justify-content: center;
55
55
  display: flex;
56
- }
56
+ }
57
+
58
+ img[src$=".svg"] {
59
+ background: transparent !important;
60
+ }
61
+
62
+ img[src$="https://tauri.app/favicon.svg"]
63
+ {
64
+ opacity: 0.3;
65
+ }
@@ -0,0 +1,3 @@
1
+ const { createOclifCommand } = require("../utils/factory.js");
2
+
3
+ module.exports = createOclifCommand("generate");
@@ -56,7 +56,8 @@ class BaseCommand extends Command {
56
56
  `testdriverai-cli-${process.pid}.log`,
57
57
  );
58
58
 
59
- console.log(`Log file created at: ${this.logFilePath}`);
59
+ console.log(`Log file: ${this.logFilePath}`);
60
+ console.log("");
60
61
  fs.writeFileSync(this.logFilePath, ""); // Initialize the log file
61
62
  }
62
63
 
@@ -69,11 +70,15 @@ class BaseCommand extends Command {
69
70
  );
70
71
  };
71
72
 
73
+ let isConnected = false;
74
+
72
75
  // Use pattern matching for log events, but skip log:Debug
73
76
  this.agent.emitter.on("log:*", (message) => {
74
77
  const event = this.agent.emitter.event;
75
78
 
76
79
  if (event === events.log.debug) return;
80
+
81
+ if (event === events.log.narration && isConnected) return;
77
82
  console.log(message);
78
83
  });
79
84
 
@@ -91,6 +96,7 @@ class BaseCommand extends Command {
91
96
 
92
97
  // Handle sandbox connection with pattern matching for subsequent events
93
98
  this.agent.emitter.on("sandbox:connected", () => {
99
+ isConnected = true;
94
100
  // Once sandbox is connected, send all log and error events to sandbox
95
101
  this.agent.emitter.on("log:*", (message) => {
96
102
  this.sendToSandbox(message);
@@ -125,8 +131,17 @@ class BaseCommand extends Command {
125
131
  process.exit(exitCode);
126
132
  });
127
133
 
134
+ // Handle unhandled promise rejections to prevent them from interfering with the exit flow
135
+ // This is particularly important when JavaScript execution in VM contexts leaves dangling promises
136
+ process.on("unhandledRejection", (reason) => {
137
+ // Log the rejection but don't let it crash the process
138
+ console.error("Unhandled Promise Rejection:", reason);
139
+ // The exit flow should continue normally
140
+ });
141
+
128
142
  // Handle show window events
129
143
  this.agent.emitter.on("show-window", async (url) => {
144
+ console.log("");
130
145
  console.log(`Live test execution: `);
131
146
  if (this.agent.config.CI) {
132
147
  let u = new URL(url);
@@ -158,20 +173,27 @@ class BaseCommand extends Command {
158
173
  return file;
159
174
  }
160
175
 
161
- async setupAgent(file, flags) {
176
+ async setupAgent(firstArg, flags) {
162
177
  // Load .env file into process.env for CLI usage
163
178
  require("dotenv").config();
164
179
 
165
180
  // Create the agent only when actually needed
166
181
  const TestDriverAgent = require("../../../agent/index.js");
167
182
 
168
- // Use --path flag if provided, otherwise use the file argument
169
- const filePath = this.id === "run" && flags.path ? flags.path : file;
183
+ let args;
184
+ if (this.id === "generate") {
185
+ // For generate command, the first parameter is a prompt, not a file
186
+ args = firstArg ? [firstArg] : [];
187
+ } else {
188
+ // For run and other commands, handle file path
189
+ const filePath = this.id === "run" && flags.path ? flags.path : firstArg;
190
+ args = filePath ? [filePath] : [];
191
+ }
170
192
 
171
193
  // Prepare CLI args for the agent with all derived options
172
194
  const cliArgs = {
173
195
  command: this.id,
174
- args: [filePath], // Pass the resolved file path as the first argument
196
+ args,
175
197
  options: {
176
198
  ...flags,
177
199
  resultFile:
@@ -28,9 +28,18 @@ function createOclifCommand(commandName) {
28
28
  this.agent.readlineInterface = readlineInterface;
29
29
  await readlineInterface.start();
30
30
  } else {
31
- // For run and sandbox commands, use the unified command system
32
- const fileArg = args.file || args.action || null;
33
- await this.setupAgent(fileArg, flags);
31
+ // For run and generate commands, use the unified command system
32
+ let commandArgs;
33
+ if (commandName === "generate") {
34
+ // Generate command: pass prompt as first argument
35
+ await this.setupAgent(args.prompt, flags);
36
+ commandArgs = [args.prompt];
37
+ } else {
38
+ // Run and other commands use file argument
39
+ const fileArg = args.file || args.action || null;
40
+ await this.setupAgent(fileArg, flags);
41
+ commandArgs = [fileArg];
42
+ }
34
43
 
35
44
  if (commandName === "run") {
36
45
  // Set error limit higher for run command
@@ -38,7 +47,11 @@ function createOclifCommand(commandName) {
38
47
  }
39
48
 
40
49
  // Execute through unified command system
41
- await this.agent.executeUnifiedCommand(commandName, [fileArg], flags);
50
+ await this.agent.executeUnifiedCommand(
51
+ commandName,
52
+ commandArgs,
53
+ flags,
54
+ );
42
55
  }
43
56
  } catch (error) {
44
57
  console.error(`Error executing ${commandName} command:`, error);
@@ -45,7 +45,7 @@ class CustomTransport extends Transport {
45
45
  // responsible for rendering ai markdown output
46
46
  const { marked } = require("marked");
47
47
  const { markedTerminal } = require("marked-terminal");
48
- const { censorSensitiveData } = require("../agent/lib/censorship");
48
+ const { censorSensitiveDataDeep } = require("../agent/lib/censorship");
49
49
 
50
50
  const { printf } = winston.format;
51
51
 
@@ -57,7 +57,7 @@ const logger = winston.createLogger({
57
57
  format: winston.format.combine(
58
58
  winston.format.splat(),
59
59
  winston.format((info) => {
60
- info.message = censorSensitiveData(info.message);
60
+ info.message = censorSensitiveDataDeep(info.message);
61
61
  return info;
62
62
  })(),
63
63
  logFormat,
@@ -341,7 +341,7 @@ const createMarkdownLogger = (emitter) => {
341
341
 
342
342
  let diff = consoleOutput.replace(previousConsoleOutput, "");
343
343
  if (diff) {
344
- diff = censorSensitiveData(diff);
344
+ diff = censorSensitiveDataDeep(diff);
345
345
  process.stdout.write(diff);
346
346
  }
347
347
  });
@@ -358,7 +358,7 @@ const createMarkdownLogger = (emitter) => {
358
358
  let diff = consoleOutput.replace(previousConsoleOutput, "");
359
359
 
360
360
  if (diff) {
361
- diff = censorSensitiveData(diff);
361
+ diff = censorSensitiveDataDeep(diff);
362
362
  process.stdout.write(diff);
363
363
  }
364
364
  process.stdout.write("\n\n");
@@ -110,7 +110,7 @@ class ReadlineInterface {
110
110
 
111
111
  try {
112
112
  // Parse interactive commands (starting with /)
113
- if (input.startsWith("/")) {
113
+ if (input.startsWith("/") && !input.startsWith("/explore")) {
114
114
  const parts = input.slice(1).split(" ");
115
115
  const commandName = parts[0];
116
116
  const args = parts.slice(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testdriverai",
3
- "version": "6.2.0",
3
+ "version": "6.2.1",
4
4
  "description": "Next generation autonomous AI agent for end-to-end testing of web & desktop",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -10,9 +10,9 @@
10
10
  "start": "node bin/testdriverai.js",
11
11
  "dev": "DEV=true node bin/testdriverai.js",
12
12
  "debug": "DEV=true VERBOSE=true node bin/testdriverai.js",
13
- "docs": "node docs/_scripts/link-replacer.js && cd docs && npx mint dev",
13
+ "docs": "node docs/_scripts/link-replacer.js && cd docs && npx mint@latest dev",
14
14
  "docs:dev": "cd docs && npx mint dev",
15
- "docs:build": "node docs/_scripts/link-replacer.js && cd docs && npx mint build",
15
+ "docs:build": "node docs/_scripts/link-replacer.js && cd docs && npx mint@latest build",
16
16
  "docs:links": "node docs/_scripts/link-replacer.js",
17
17
  "bundle": "node build.mjs",
18
18
  "test": "mocha test/*",
package/schema.json CHANGED
@@ -415,6 +415,9 @@
415
415
  "ai",
416
416
  "turbo"
417
417
  ]
418
+ },
419
+ "timeout": {
420
+ "type": "integer"
418
421
  }
419
422
  }
420
423
  },
@@ -471,6 +474,9 @@
471
474
  "drag-start",
472
475
  "drag-end"
473
476
  ]
477
+ },
478
+ "invert": {
479
+ "type": "boolean"
474
480
  }
475
481
  }
476
482
  },
@@ -490,6 +496,9 @@
490
496
  },
491
497
  "timeout": {
492
498
  "type": "integer"
499
+ },
500
+ "invert": {
501
+ "type": "boolean"
493
502
  }
494
503
  }
495
504
  },
@@ -516,6 +525,9 @@
516
525
  "ai",
517
526
  "turbo"
518
527
  ]
528
+ },
529
+ "invert": {
530
+ "type": "boolean"
519
531
  }
520
532
  }
521
533
  },
@@ -552,6 +564,9 @@
552
564
  "keyboard",
553
565
  "mouse"
554
566
  ]
567
+ },
568
+ "invert": {
569
+ "type": "boolean"
555
570
  }
556
571
  }
557
572
  },
@@ -590,6 +605,9 @@
590
605
  "keyboard",
591
606
  "mouse"
592
607
  ]
608
+ },
609
+ "invert": {
610
+ "type": "boolean"
593
611
  }
594
612
  },
595
613
  "anyOf": [
@@ -657,6 +675,9 @@
657
675
  },
658
676
  "async": {
659
677
  "type": "boolean"
678
+ },
679
+ "invert": {
680
+ "type": "boolean"
660
681
  }
661
682
  }
662
683
  },