@samsara-dev/appwright 0.9.6 → 0.9.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # appwright
2
2
 
3
+ ## 0.9.8
4
+
5
+ ### Patch Changes
6
+
7
+ - eb8aea2: Cap video download retry backoff at 60s and add timestamps to all download logs
8
+
9
+ ## 0.9.7
10
+
11
+ ### Patch Changes
12
+
13
+ - dbd26ca: feat(gptDriver): integrate GPT Driver SDK for AI-powered test automation
14
+
15
+ Add GPT Driver SDK integration enabling natural language test automation via `device.gptDriver.*` methods:
16
+ - `aiExecute(instruction)` - execute AI-powered actions
17
+ - `assert(condition)` - verify screen state with natural language
18
+ - `assertBulk(conditions)` - verify multiple conditions at once
19
+ - `checkBulk(conditions)` - check conditions without failing test
20
+ - `extract(extractions)` - extract data from screen
21
+
22
+ Requires `GPT_DRIVER_API_KEY` environment variable. Tests gracefully skip when not configured.
23
+
3
24
  ## 0.9.6
4
25
 
5
26
  ### Patch Changes
package/README.md CHANGED
@@ -234,6 +234,40 @@ npm run extract:app
234
234
  npx appwright test --project ios
235
235
  ```
236
236
 
237
+ ## GPT Driver Integration
238
+
239
+ Appwright supports [GPT Driver](https://docs.mobileboost.io/gpt-driver-sdk) for AI-powered test automation. This enables natural language commands and AI assertions.
240
+
241
+ ### Setup
242
+
243
+ Set the `GPT_DRIVER_API_KEY` environment variable:
244
+
245
+ ```sh
246
+ export GPT_DRIVER_API_KEY=your-api-key
247
+ ```
248
+
249
+ ### Usage
250
+
251
+ ```ts
252
+ import { test } from "@samsara-dev/appwright";
253
+
254
+ test("User can add item to cart", async ({ device }) => {
255
+ // Natural language commands
256
+ await device.gptDriver.aiExecute("tap on the login button");
257
+ await device.gptDriver.aiExecute("enter 'admin' in the username field");
258
+
259
+ // AI-powered assertions
260
+ await device.gptDriver.assert("welcome message is visible");
261
+ await device.gptDriver.assert("cart shows 1 item");
262
+ });
263
+ ```
264
+
265
+ ### Data Privacy Notice
266
+
267
+ GPT Driver sends screenshots to Mobileboost's external API for AI processing. Do not use GPT Driver in tests that handle sensitive data displayed on screen.
268
+
269
+ Tests skip automatically when `GPT_DRIVER_API_KEY` is not set.
270
+
237
271
  ## Docs
238
272
 
239
273
  - [Basics](docs/basics.md)
@@ -16,6 +16,7 @@ export declare class Device {
16
16
  private persistentSyncEnabled;
17
17
  private activePersistentKey?;
18
18
  private cleanupCallback?;
19
+ private _gptDriverProvider;
19
20
  constructor(webDriverClient: WebDriverClient, bundleId: string | undefined, timeoutOpts: TimeoutOptions, provider: string, deviceProvider?: DeviceProvider, cleanupCallback?: () => Promise<void>);
20
21
  attachDeviceProvider(provider: DeviceProvider): void;
21
22
  enablePersistentStatusSync(): void;
@@ -60,6 +61,29 @@ export declare class Device {
60
61
  };
61
62
  }) => Promise<ExtractType<T>>;
62
63
  };
64
+ private gptDriverProvider;
65
+ /**
66
+ * GPT Driver AI-powered test automation.
67
+ * Requires GPT_DRIVER_API_KEY environment variable.
68
+ *
69
+ * Note: Screenshots are sent to GPT Driver's external API for AI processing.
70
+ *
71
+ * **Usage:**
72
+ * ```js
73
+ * await device.gptDriver.aiExecute("tap on the login button");
74
+ * await device.gptDriver.assert("welcome message is visible");
75
+ * await device.gptDriver.assertBulk(["button is visible", "text shows 'Hello'"]);
76
+ * const results = await device.gptDriver.checkBulk(["logged in", "menu open"]);
77
+ * const data = await device.gptDriver.extract(["total price", "item count"]);
78
+ * ```
79
+ */
80
+ gptDriver: {
81
+ aiExecute: (instruction: string) => Promise<void>;
82
+ assert: (condition: string) => Promise<void>;
83
+ assertBulk: (conditions: string[]) => Promise<void>;
84
+ checkBulk: (conditions: string[]) => Promise<Record<string, boolean>>;
85
+ extract: (extractions: string[]) => Promise<Record<string, any>>;
86
+ };
63
87
  /**
64
88
  * Closes the automation session. This is called automatically after each test.
65
89
  *
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/device/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,eAAe,EAAE,MAAM,WAAW,CAAC;AAE3D,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,WAAW,EACX,cAAc,EACd,qBAAqB,EACrB,QAAQ,EACR,cAAc,EACd,iBAAiB,EAClB,MAAM,UAAU,CAAC;AAKlB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAO7C,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,KAAK,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7E,qBAAa,MAAM;IAQf,OAAO,CAAC,eAAe;IACvB,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,QAAQ;IAVlB,OAAO,CAAC,kBAAkB,CAAC,CAAqB;IAChD,OAAO,CAAC,cAAc,CAAC,CAAiB;IACxC,OAAO,CAAC,qBAAqB,CAAS;IACtC,OAAO,CAAC,mBAAmB,CAAC,CAAS;IACrC,OAAO,CAAC,eAAe,CAAC,CAAsB;gBAGpC,eAAe,EAAE,eAAe,EAChC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,WAAW,EAAE,cAAc,EAC3B,QAAQ,EAAE,MAAM,EACxB,cAAc,CAAC,EAAE,cAAc,EAC/B,eAAe,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC;IAMvC,oBAAoB,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI;IAIpD,0BAA0B,IAAI,IAAI;IAI5B,yBAAyB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAO5D,qBAAqB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAYxD,sBAAsB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAyB/D,OAAO,CAAC,oBAAoB;IAO5B,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,mBAAmB;IAa3B,OAAO,CAAC,aAAa;YAQP,QAAQ;IAetB;;OAEG;IACH,qBAAqB,CACnB,QAAQ,EAAE,QAAQ,EAClB,UAAU,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,iBAAiB,GACzB,IAAI;IAQP;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC;IAKvC,OAAO,CAAC,EACN,QAAQ,EACR,YAAY,EACZ,WAAW,GACZ,EAAE;QACD,QAAQ,EAAE,MAAM,CAAC;QACjB,YAAY,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;KAC/B,GAAG,gBAAgB;IAWpB,OAAO,CAAC,MAAM;IAId,IAAI;sBAEQ,MAAM,YACJ;YACR,QAAQ,CAAC,EAAE,OAAO,CAAC;YACnB,SAAS,CAAC,EAAE;gBACV,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;aACjB,CAAC;SACH,KACA,OAAO,CAAC;YAAE,CAAC,EAAE,MAAM,CAAC;YAAC,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;gBAItB,CAAC,SAAS,CAAC,CAAC,OAAO,UACvB,MAAM,YACJ;YACR,cAAc,CAAC,EAAE,CAAC,CAAC;YACnB,KAAK,CAAC,EAAE,QAAQ,CAAC;YACjB,UAAU,CAAC,EAAE,MAAM,CAAC;YACpB,SAAS,CAAC,EAAE;gBACV,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;aACjB,CAAC;SACH,KACA,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;MAG1B;IAEF;;;;;;;OAOG;IACG,KAAK;IA0BX;;;;;;;;;;;OAWG;IAEG,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE;IAoB5C;;;;;;;;;;;;;;;;OAgBG;IACH,SAAS,CACP,IAAI,EAAE,MAAM,GAAG,MAAM,EACrB,EAAE,KAAa,EAAE,GAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAO,GAC1C,gBAAgB;IAsCnB;;;;;;;;;;;;OAYG;IACH,OAAO,CACL,IAAI,EAAE,MAAM,EACZ,EAAE,KAAa,EAAE,GAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAO,GAC1C,gBAAgB;IAiBnB;;;;;;;;;;;OAWG;IACH,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,gBAAgB;IAS1C,OAAO,CAAC,iBAAiB;IAczB;;;;;;;;;;OAUG;IACH,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,gBAAgB;IAI3C;;;;;;;;;OASG;IACH,WAAW,IAAI,QAAQ;IAMjB,YAAY,CAAC,QAAQ,CAAC,EAAE,MAAM;IAc9B,WAAW,CAAC,QAAQ,CAAC,EAAE,MAAM;IAanC;;;;;;;;;;;;;;;;;OAiBG;IAEG,aAAa,CAAC,OAAO,GAAE,MAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAQxD;;;;;;;;;;OAUG;IAEG,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC;IAqBzC;;;;;;;;;;;OAWG;IAEG,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBnD,KAAK;IAkBL,cAAc,CAAC,OAAO,EAAE,MAAM;IAIpC;;OAEG;IAEG,WAAW,IAAI,OAAO,CAAC,cAAc,CAAC;IAI5C;;OAEG;IAEG,aAAa,IAAI,OAAO,CAAC;QAC7B,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,CAAC,EAAE,MAAM,CAAC;QACV,CAAC,EAAE,MAAM,CAAC;KACX,CAAC;IAIF;;OAEG;IAEG,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;IAInC;;;;;;;;;;OAUG;IAEG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAU7B;;;OAGG;IAEG,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBlD;;;;;;;;;;;;;;;;;OAiBG;IAEU,iBAAiB,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAYnE;;;;;;;;;;;;;OAaG;IAEU,wBAAwB,CACnC,QAAQ,EAAE,qBAAqB,GAC9B,OAAO,CAAC,IAAI,CAAC;IAIhB;;;OAGG;IACH,OAAO,CAAC,qBAAqB;CAe9B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/device/index.ts"],"names":[],"mappings":"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"}
@@ -38,6 +38,7 @@ exports.Device = void 0;
38
38
  const locator_1 = require("../locator");
39
39
  const types_1 = require("../types");
40
40
  const vision_1 = require("../vision");
41
+ const gptDriver_1 = require("../gptDriver");
41
42
  const utils_1 = require("../utils");
42
43
  const utils_2 = require("../providers/browserstack/utils");
43
44
  const utils_3 = require("../providers/lambdatest/utils");
@@ -104,6 +105,7 @@ let Device = (() => {
104
105
  persistentSyncEnabled = false;
105
106
  activePersistentKey;
106
107
  cleanupCallback;
108
+ _gptDriverProvider = null;
107
109
  constructor(webDriverClient, bundleId, timeoutOpts, provider, deviceProvider, cleanupCallback) {
108
110
  this.webDriverClient = webDriverClient;
109
111
  this.bundleId = bundleId;
@@ -219,6 +221,44 @@ let Device = (() => {
219
221
  return await this.vision().query(prompt, options);
220
222
  },
221
223
  };
224
+ gptDriverProvider() {
225
+ if (!this._gptDriverProvider) {
226
+ this._gptDriverProvider = new gptDriver_1.GptDriverProvider(this.webDriverClient);
227
+ }
228
+ return this._gptDriverProvider;
229
+ }
230
+ /**
231
+ * GPT Driver AI-powered test automation.
232
+ * Requires GPT_DRIVER_API_KEY environment variable.
233
+ *
234
+ * Note: Screenshots are sent to GPT Driver's external API for AI processing.
235
+ *
236
+ * **Usage:**
237
+ * ```js
238
+ * await device.gptDriver.aiExecute("tap on the login button");
239
+ * await device.gptDriver.assert("welcome message is visible");
240
+ * await device.gptDriver.assertBulk(["button is visible", "text shows 'Hello'"]);
241
+ * const results = await device.gptDriver.checkBulk(["logged in", "menu open"]);
242
+ * const data = await device.gptDriver.extract(["total price", "item count"]);
243
+ * ```
244
+ */
245
+ gptDriver = {
246
+ aiExecute: async (instruction) => {
247
+ return await this.gptDriverProvider().aiExecute(instruction);
248
+ },
249
+ assert: async (condition) => {
250
+ return await this.gptDriverProvider().assert(condition);
251
+ },
252
+ assertBulk: async (conditions) => {
253
+ return await this.gptDriverProvider().assertBulk(conditions);
254
+ },
255
+ checkBulk: async (conditions) => {
256
+ return await this.gptDriverProvider().checkBulk(conditions);
257
+ },
258
+ extract: async (extractions) => {
259
+ return await this.gptDriverProvider().extract(extractions);
260
+ },
261
+ };
222
262
  /**
223
263
  * Closes the automation session. This is called automatically after each test.
224
264
  *
@@ -0,0 +1,38 @@
1
+ import type { Client as WebDriverClient } from "webdriver";
2
+ /**
3
+ * GptDriverApi defines the public interface for AI-powered test automation.
4
+ */
5
+ export interface GptDriverApi {
6
+ aiExecute(instruction: string): Promise<void>;
7
+ assert(condition: string): Promise<void>;
8
+ assertBulk(conditions: string[]): Promise<void>;
9
+ checkBulk(conditions: string[]): Promise<Record<string, boolean>>;
10
+ extract(extractions: string[]): Promise<Record<string, any>>;
11
+ }
12
+ /**
13
+ * GptDriverProvider wraps the gpt-driver-node SDK for AI-powered test automation.
14
+ *
15
+ * Pattern: Lazy-initialized singleton per Device instance.
16
+ * - Driver is created on first use (not in constructor)
17
+ * - Cached for subsequent calls
18
+ * - Gracefully skips tests when API key is not configured
19
+ *
20
+ * Similar to: VisionProvider
21
+ */
22
+ export declare class GptDriverProvider implements GptDriverApi {
23
+ private webDriverClient;
24
+ private driver;
25
+ constructor(webDriverClient: WebDriverClient);
26
+ private getAppiumUrl;
27
+ private getDriver;
28
+ private requireDriver;
29
+ private validateInstruction;
30
+ private validateConditions;
31
+ private sanitizeError;
32
+ aiExecute(instruction: string): Promise<void>;
33
+ assert(condition: string): Promise<void>;
34
+ assertBulk(conditions: string[]): Promise<void>;
35
+ checkBulk(conditions: string[]): Promise<Record<string, boolean>>;
36
+ extract(extractions: string[]): Promise<Record<string, any>>;
37
+ }
38
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/gptDriver/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,IAAI,eAAe,EAAE,MAAM,WAAW,CAAC;AAM3D;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAClE,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;CAC9D;AAED;;;;;;;;;GASG;AACH,qBAAa,iBAAkB,YAAW,YAAY;IAGxC,OAAO,CAAC,eAAe;IAFnC,OAAO,CAAC,MAAM,CAA0B;gBAEpB,eAAe,EAAE,eAAe;IAEpD,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,SAAS;IAuBjB,OAAO,CAAC,aAAa;IAYrB,OAAO,CAAC,mBAAmB;IAW3B,OAAO,CAAC,kBAAkB;IAS1B,OAAO,CAAC,aAAa;IAMf,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAa7C,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAaxC,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAa/C,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAajE,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAWnE"}
@@ -0,0 +1,187 @@
1
+ "use strict";
2
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
3
+ var useValue = arguments.length > 2;
4
+ for (var i = 0; i < initializers.length; i++) {
5
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
6
+ }
7
+ return useValue ? value : void 0;
8
+ };
9
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
10
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
11
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
12
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
13
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
14
+ var _, done = false;
15
+ for (var i = decorators.length - 1; i >= 0; i--) {
16
+ var context = {};
17
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
18
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
19
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
20
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
21
+ if (kind === "accessor") {
22
+ if (result === void 0) continue;
23
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
24
+ if (_ = accept(result.get)) descriptor.get = _;
25
+ if (_ = accept(result.set)) descriptor.set = _;
26
+ if (_ = accept(result.init)) initializers.unshift(_);
27
+ }
28
+ else if (_ = accept(result)) {
29
+ if (kind === "field") initializers.unshift(_);
30
+ else descriptor[key] = _;
31
+ }
32
+ }
33
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
34
+ done = true;
35
+ };
36
+ var __importDefault = (this && this.__importDefault) || function (mod) {
37
+ return (mod && mod.__esModule) ? mod : { "default": mod };
38
+ };
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ exports.GptDriverProvider = void 0;
41
+ const gpt_driver_node_1 = __importDefault(require("gpt-driver-node"));
42
+ const utils_1 = require("../utils");
43
+ const test_1 = __importDefault(require("@playwright/test"));
44
+ const MAX_INSTRUCTION_LENGTH = 10000;
45
+ /**
46
+ * GptDriverProvider wraps the gpt-driver-node SDK for AI-powered test automation.
47
+ *
48
+ * Pattern: Lazy-initialized singleton per Device instance.
49
+ * - Driver is created on first use (not in constructor)
50
+ * - Cached for subsequent calls
51
+ * - Gracefully skips tests when API key is not configured
52
+ *
53
+ * Similar to: VisionProvider
54
+ */
55
+ let GptDriverProvider = (() => {
56
+ let _instanceExtraInitializers = [];
57
+ let _aiExecute_decorators;
58
+ let _assert_decorators;
59
+ let _assertBulk_decorators;
60
+ let _checkBulk_decorators;
61
+ let _extract_decorators;
62
+ return class GptDriverProvider {
63
+ static {
64
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
65
+ _aiExecute_decorators = [utils_1.boxedStep];
66
+ _assert_decorators = [utils_1.boxedStep];
67
+ _assertBulk_decorators = [utils_1.boxedStep];
68
+ _checkBulk_decorators = [utils_1.boxedStep];
69
+ _extract_decorators = [utils_1.boxedStep];
70
+ __esDecorate(this, null, _aiExecute_decorators, { kind: "method", name: "aiExecute", static: false, private: false, access: { has: obj => "aiExecute" in obj, get: obj => obj.aiExecute }, metadata: _metadata }, null, _instanceExtraInitializers);
71
+ __esDecorate(this, null, _assert_decorators, { kind: "method", name: "assert", static: false, private: false, access: { has: obj => "assert" in obj, get: obj => obj.assert }, metadata: _metadata }, null, _instanceExtraInitializers);
72
+ __esDecorate(this, null, _assertBulk_decorators, { kind: "method", name: "assertBulk", static: false, private: false, access: { has: obj => "assertBulk" in obj, get: obj => obj.assertBulk }, metadata: _metadata }, null, _instanceExtraInitializers);
73
+ __esDecorate(this, null, _checkBulk_decorators, { kind: "method", name: "checkBulk", static: false, private: false, access: { has: obj => "checkBulk" in obj, get: obj => obj.checkBulk }, metadata: _metadata }, null, _instanceExtraInitializers);
74
+ __esDecorate(this, null, _extract_decorators, { kind: "method", name: "extract", static: false, private: false, access: { has: obj => "extract" in obj, get: obj => obj.extract }, metadata: _metadata }, null, _instanceExtraInitializers);
75
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
76
+ }
77
+ webDriverClient = __runInitializers(this, _instanceExtraInitializers);
78
+ driver = null;
79
+ constructor(webDriverClient) {
80
+ this.webDriverClient = webDriverClient;
81
+ }
82
+ getAppiumUrl() {
83
+ const { protocol, hostname, port, path, user, key } = this.webDriverClient.options;
84
+ // Include basic auth for cloud providers (BrowserStack, LambdaTest)
85
+ const auth = user && key ? `${user}:${key}@` : "";
86
+ return `${protocol}://${auth}${hostname}:${port}${path}`;
87
+ }
88
+ getDriver() {
89
+ if (this.driver)
90
+ return this.driver;
91
+ const apiKey = process.env.GPT_DRIVER_API_KEY;
92
+ if (!apiKey) {
93
+ console.debug("[GptDriver] GPT_DRIVER_API_KEY not set, GPT Driver features disabled");
94
+ return null;
95
+ }
96
+ this.driver = new gpt_driver_node_1.default({
97
+ apiKey,
98
+ // gpt-driver-node expects webdriverio Browser type but webdriver Client is API-compatible at runtime
99
+ driver: this.webDriverClient,
100
+ serverConfig: { url: this.getAppiumUrl() },
101
+ cachingMode: "FULL_SCREEN",
102
+ testId: test_1.default.info()?.testId ?? `test-${Date.now()}`,
103
+ });
104
+ console.debug("[GptDriver] Initialized successfully");
105
+ return this.driver;
106
+ }
107
+ requireDriver() {
108
+ const driver = this.getDriver();
109
+ if (!driver) {
110
+ test_1.default.skip(true, "GPT Driver not configured. Set GPT_DRIVER_API_KEY environment variable.");
111
+ throw new Error("GPT Driver not configured");
112
+ }
113
+ return driver;
114
+ }
115
+ validateInstruction(instruction, methodName) {
116
+ if (!instruction || typeof instruction !== "string") {
117
+ throw new Error(`${methodName} requires a non-empty instruction string`);
118
+ }
119
+ if (instruction.length > MAX_INSTRUCTION_LENGTH) {
120
+ throw new Error(`${methodName} instruction exceeds maximum length of ${MAX_INSTRUCTION_LENGTH}`);
121
+ }
122
+ }
123
+ validateConditions(conditions, methodName) {
124
+ if (!Array.isArray(conditions) || conditions.length === 0) {
125
+ throw new Error(`${methodName} requires a non-empty array of conditions`);
126
+ }
127
+ for (const condition of conditions) {
128
+ this.validateInstruction(condition, methodName);
129
+ }
130
+ }
131
+ sanitizeError(error) {
132
+ const message = error instanceof Error ? error.message : String(error);
133
+ return message.replace(/[a-zA-Z0-9]{32,}/g, "[REDACTED]");
134
+ }
135
+ async aiExecute(instruction) {
136
+ this.validateInstruction(instruction, "aiExecute");
137
+ const driver = this.requireDriver();
138
+ try {
139
+ await driver.aiExecute(instruction);
140
+ }
141
+ catch (error) {
142
+ throw new Error(`GPT Driver aiExecute failed: ${this.sanitizeError(error)}`);
143
+ }
144
+ }
145
+ async assert(condition) {
146
+ this.validateInstruction(condition, "assert");
147
+ const driver = this.requireDriver();
148
+ try {
149
+ await driver.assert(condition, {});
150
+ }
151
+ catch (error) {
152
+ throw new Error(`GPT Driver assertion failed: ${this.sanitizeError(error)}`);
153
+ }
154
+ }
155
+ async assertBulk(conditions) {
156
+ this.validateConditions(conditions, "assertBulk");
157
+ const driver = this.requireDriver();
158
+ try {
159
+ await driver.assertBulk(conditions, {});
160
+ }
161
+ catch (error) {
162
+ throw new Error(`GPT Driver assertBulk failed: ${this.sanitizeError(error)}`);
163
+ }
164
+ }
165
+ async checkBulk(conditions) {
166
+ this.validateConditions(conditions, "checkBulk");
167
+ const driver = this.requireDriver();
168
+ try {
169
+ return await driver.checkBulk(conditions);
170
+ }
171
+ catch (error) {
172
+ throw new Error(`GPT Driver checkBulk failed: ${this.sanitizeError(error)}`);
173
+ }
174
+ }
175
+ async extract(extractions) {
176
+ this.validateConditions(extractions, "extract");
177
+ const driver = this.requireDriver();
178
+ try {
179
+ return await driver.extract(extractions);
180
+ }
181
+ catch (error) {
182
+ throw new Error(`GPT Driver extract failed: ${this.sanitizeError(error)}`);
183
+ }
184
+ }
185
+ };
186
+ })();
187
+ exports.GptDriverProvider = GptDriverProvider;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/providers/browserstack/index.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,eAAe,EACf,cAAc,EAGf,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AA0DtC,qBAAa,0BAA2B,YAAW,cAAc;IAC/D,OAAO,CAAC,cAAc,CAAC,CAA6B;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,OAAO,CAA+B;gBAG5C,OAAO,EAAE,WAAW,CAAC,eAAe,CAAC,EACrC,WAAW,EAAE,MAAM,GAAG,SAAS;IAU3B,WAAW;IA0EX,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC;IAMlC,OAAO,CAAC,cAAc;YASR,YAAY;YAkCZ,iBAAiB;YAKjB,yBAAyB;WAK1B,aAAa,CACxB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IA+FlD,eAAe,CAAC,OAAO,EAAE;QAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf;IA2BD,OAAO,CAAC,YAAY;CA4KrB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/providers/browserstack/index.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,eAAe,EACf,cAAc,EAGf,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AA0DtC,qBAAa,0BAA2B,YAAW,cAAc;IAC/D,OAAO,CAAC,cAAc,CAAC,CAA6B;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,OAAO,CAA+B;gBAG5C,OAAO,EAAE,WAAW,CAAC,eAAe,CAAC,EACrC,WAAW,EAAE,MAAM,GAAG,SAAS;IAU3B,WAAW;IA0EX,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC;IAMlC,OAAO,CAAC,cAAc;YASR,YAAY;YAkCZ,iBAAiB;YAKjB,yBAAyB;WAK1B,aAAa,CACxB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAsGlD,eAAe,CAAC,OAAO,EAAE;QAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf;IA2BD,OAAO,CAAC,YAAY;CA4KrB"}
@@ -175,6 +175,7 @@ class BrowserStackDeviceProvider {
175
175
  //To catch the browserstack error in case all retries fails
176
176
  try {
177
177
  if (videoURL) {
178
+ logger_1.logger.log(`[${new Date().toISOString()}] Video download starting: ${videoURL}`);
178
179
  await (0, async_retry_1.default)(async () => {
179
180
  const response = await fetch(videoURL, {
180
181
  method: "GET",
@@ -200,11 +201,10 @@ class BrowserStackDeviceProvider {
200
201
  }, {
201
202
  retries: 10,
202
203
  minTimeout: 3_000,
204
+ maxTimeout: 60_000,
203
205
  onRetry: (err, i) => {
204
206
  const message = err instanceof Error ? err.message : String(err);
205
- if (i > 5) {
206
- logger_1.logger.warn(`Retry attempt ${i} failed: ${message}`);
207
- }
207
+ logger_1.logger.warn(`[${new Date().toISOString()}] Video download retry ${i}/10 failed: ${message}`);
208
208
  },
209
209
  });
210
210
  return new Promise((resolve, reject) => {
@@ -212,7 +212,7 @@ class BrowserStackDeviceProvider {
212
212
  fileStream.on("finish", () => {
213
213
  try {
214
214
  fs_1.default.renameSync(tempPathForWriting, pathToTestVideo);
215
- logger_1.logger.log(`Download finished and file closed: ${pathToTestVideo}`);
215
+ logger_1.logger.log(`[${new Date().toISOString()}] Video download completed: ${pathToTestVideo}`);
216
216
  resolve({ path: pathToTestVideo, contentType: "video/mp4" });
217
217
  }
218
218
  catch (err) {
@@ -233,7 +233,8 @@ class BrowserStackDeviceProvider {
233
233
  }
234
234
  }
235
235
  catch (e) {
236
- logger_1.logger.log(`Error Downloading video: `, e);
236
+ const message = e instanceof Error ? e.message : String(e);
237
+ logger_1.logger.warn(`[${new Date().toISOString()}] Video download failed after all retries: ${message}. Test will complete without video attachment.`);
237
238
  return null;
238
239
  }
239
240
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@samsara-dev/appwright",
3
- "version": "0.9.6",
3
+ "version": "0.9.8",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org/",
6
6
  "access": "public"
@@ -50,7 +50,8 @@
50
50
  "form-data": "4.0.5",
51
51
  "node-fetch": "^3.3.2",
52
52
  "picocolors": "^1.1.0",
53
- "webdriver": "^8.36.1"
53
+ "webdriver": "^8.36.1",
54
+ "gpt-driver-node": "^1.0.4"
54
55
  },
55
56
  "devDependencies": {
56
57
  "@changesets/cli": "^2.29.8",