@samsara-dev/appwright 0.3.5 → 0.5.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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # appwright
2
2
 
3
+ ## 0.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - c40123e: Add per-test status syncing for persistentDevice so BrowserStack and LambdaTest sessions reflect test names and outcomes.
8
+
9
+ ## 0.4.0
10
+
11
+ ### Minor Changes
12
+
13
+ - ddb33f3: Add BrowserStack `updateAppSettings` support for iOS devices, including runtime helpers, provider capability forwarding, and documentation.
14
+
3
15
  ## 0.3.5
4
16
 
5
17
  ### Patch Changes
package/README.md CHANGED
@@ -116,6 +116,37 @@ the provider in your config.
116
116
  },
117
117
  ```
118
118
 
119
+ #### iOS App Settings (BrowserStack)
120
+
121
+ Configure iOS permissions by default in your config:
122
+
123
+ ```ts
124
+ {
125
+ name: "ios",
126
+ use: {
127
+ platform: Platform.IOS,
128
+ device: {
129
+ provider: "browserstack",
130
+ name: "iPhone 16 Pro",
131
+ osVersion: "18",
132
+ updateAppSettings: {
133
+ "Permission Settings": {
134
+ Location: {
135
+ "ALLOW LOCATION ACCESS": "Always",
136
+ "Precise Location": "ON"
137
+ }
138
+ }
139
+ }
140
+ },
141
+ buildPath: "bs://<app-id>",
142
+ },
143
+ }
144
+
145
+ // Tests run with permissions already granted - no setup needed!
146
+ ```
147
+
148
+ See the [iOS App Settings documentation](./docs/ios-app-settings-browserstack.md) for more details.
149
+
119
150
  #### Run tests on LambdaTest
120
151
 
121
152
  Appwright supports LambdaTest out of the box. To run tests on LambdaTest, configure
@@ -1,5 +1,5 @@
1
1
  import type { Client as WebDriverClient } from "webdriver";
2
- import { AppwrightLocator, ExtractType, Platform, TimeoutOptions, VisualTraceConfig } from "../types";
2
+ import { AppwrightLocator, DeviceProvider, ExtractType, IosAppSettings, IosPermissionSettings, Platform, TimeoutOptions, VisualTraceConfig } from "../types";
3
3
  import { z } from "zod";
4
4
  import { LLMModel } from "@empiricalrun/llm";
5
5
  import { TestInfo } from "@playwright/test";
@@ -9,7 +9,20 @@ export declare class Device {
9
9
  private timeoutOpts;
10
10
  private provider;
11
11
  private visualTraceService?;
12
- constructor(webDriverClient: WebDriverClient, bundleId: string | undefined, timeoutOpts: TimeoutOptions, provider: string);
12
+ private deviceProvider?;
13
+ private persistentSyncEnabled;
14
+ private activePersistentKey?;
15
+ constructor(webDriverClient: WebDriverClient, bundleId: string | undefined, timeoutOpts: TimeoutOptions, provider: string, deviceProvider?: DeviceProvider);
16
+ attachDeviceProvider(provider: DeviceProvider): void;
17
+ enablePersistentStatusSync(): void;
18
+ ensurePersistentLifecycle(testInfo: TestInfo): Promise<void>;
19
+ preparePersistentTest(testInfo: TestInfo): Promise<void>;
20
+ finalizePersistentTest(testInfo: TestInfo): Promise<void>;
21
+ private shouldSyncPersistent;
22
+ private persistentKey;
23
+ private mapPlaywrightStatus;
24
+ private failureReason;
25
+ private safeSync;
13
26
  /**
14
27
  * Initialize Visual Trace Service for screenshot capture during test execution
15
28
  */
@@ -196,5 +209,44 @@ export declare class Device {
196
209
  * To fill input fields using the selectors use `sendKeyStrokes` method from locator
197
210
  */
198
211
  sendKeyStrokes(value: string): Promise<void>;
212
+ /**
213
+ * Updates iOS app settings via BrowserStack executor (mid-session).
214
+ * For default settings, prefer configuring them in appwright.config.ts.
215
+ *
216
+ * @param args - Permission settings or Settings bundle entries
217
+ * @example
218
+ * // Update permissions mid-session
219
+ * await device.updateAppSettings({
220
+ * 'Permission Settings': { Camera: 'Allow' }
221
+ * });
222
+ *
223
+ * @example
224
+ * // Update custom settings
225
+ * await device.updateAppSettings({
226
+ * 'DarkMode': 1,
227
+ * 'Environment': 'production'
228
+ * });
229
+ */
230
+ updateAppSettings(args: IosAppSettings): Promise<void>;
231
+ /**
232
+ * Convenience method for updating iOS permission settings mid-session.
233
+ * For default permissions, prefer configuring them in appwright.config.ts.
234
+ *
235
+ * @param settings - iOS permission settings to update
236
+ * @example
237
+ * await device.updatePermissionSettings({
238
+ * Location: {
239
+ * 'ALLOW LOCATION ACCESS': 'Always',
240
+ * 'Precise Location': 'ON'
241
+ * },
242
+ * Camera: 'Allow'
243
+ * });
244
+ */
245
+ updatePermissionSettings(settings: IosPermissionSettings): Promise<void>;
246
+ /**
247
+ * Validates that the current platform is iOS and provider is BrowserStack.
248
+ * @private
249
+ */
250
+ private assertIOSBrowserStack;
199
251
  }
200
252
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/device/index.ts"],"names":[],"mappings":"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"}
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,qBAAa,MAAM;IAOf,OAAO,CAAC,eAAe;IACvB,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,QAAQ;IATlB,OAAO,CAAC,kBAAkB,CAAC,CAAqB;IAChD,OAAO,CAAC,cAAc,CAAC,CAAiB;IACxC,OAAO,CAAC,qBAAqB,CAAS;IACtC,OAAO,CAAC,mBAAmB,CAAC,CAAS;gBAG3B,eAAe,EAAE,eAAe,EAChC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,WAAW,EAAE,cAAc,EAC3B,QAAQ,EAAE,MAAM,EACxB,cAAc,CAAC,EAAE,cAAc;IAKjC,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;IAiBX;;;;;;;;;;;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;;;;;;;;;;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;IAmBL,cAAc,CAAC,OAAO,EAAE,MAAM;IAIpC;;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"}
@@ -56,6 +56,8 @@ let Device = (() => {
56
56
  let _screenshot_decorators;
57
57
  let _scroll_decorators;
58
58
  let _sendKeyStrokes_decorators;
59
+ let _updateAppSettings_decorators;
60
+ let _updatePermissionSettings_decorators;
59
61
  return class Device {
60
62
  static {
61
63
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
@@ -70,6 +72,8 @@ let Device = (() => {
70
72
  _screenshot_decorators = [utils_1.boxedStep];
71
73
  _scroll_decorators = [utils_1.boxedStep];
72
74
  _sendKeyStrokes_decorators = [utils_1.boxedStep];
75
+ _updateAppSettings_decorators = [utils_1.boxedStep];
76
+ _updatePermissionSettings_decorators = [utils_1.boxedStep];
73
77
  __esDecorate(this, null, _tap_decorators, { kind: "method", name: "tap", static: false, private: false, access: { has: obj => "tap" in obj, get: obj => obj.tap }, metadata: _metadata }, null, _instanceExtraInitializers);
74
78
  __esDecorate(this, null, _terminateApp_decorators, { kind: "method", name: "terminateApp", static: false, private: false, access: { has: obj => "terminateApp" in obj, get: obj => obj.terminateApp }, metadata: _metadata }, null, _instanceExtraInitializers);
75
79
  __esDecorate(this, null, _activateApp_decorators, { kind: "method", name: "activateApp", static: false, private: false, access: { has: obj => "activateApp" in obj, get: obj => obj.activateApp }, metadata: _metadata }, null, _instanceExtraInitializers);
@@ -81,6 +85,8 @@ let Device = (() => {
81
85
  __esDecorate(this, null, _screenshot_decorators, { kind: "method", name: "screenshot", static: false, private: false, access: { has: obj => "screenshot" in obj, get: obj => obj.screenshot }, metadata: _metadata }, null, _instanceExtraInitializers);
82
86
  __esDecorate(this, null, _scroll_decorators, { kind: "method", name: "scroll", static: false, private: false, access: { has: obj => "scroll" in obj, get: obj => obj.scroll }, metadata: _metadata }, null, _instanceExtraInitializers);
83
87
  __esDecorate(this, null, _sendKeyStrokes_decorators, { kind: "method", name: "sendKeyStrokes", static: false, private: false, access: { has: obj => "sendKeyStrokes" in obj, get: obj => obj.sendKeyStrokes }, metadata: _metadata }, null, _instanceExtraInitializers);
88
+ __esDecorate(this, null, _updateAppSettings_decorators, { kind: "method", name: "updateAppSettings", static: false, private: false, access: { has: obj => "updateAppSettings" in obj, get: obj => obj.updateAppSettings }, metadata: _metadata }, null, _instanceExtraInitializers);
89
+ __esDecorate(this, null, _updatePermissionSettings_decorators, { kind: "method", name: "updatePermissionSettings", static: false, private: false, access: { has: obj => "updatePermissionSettings" in obj, get: obj => obj.updatePermissionSettings }, metadata: _metadata }, null, _instanceExtraInitializers);
84
90
  if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
85
91
  }
86
92
  webDriverClient = __runInitializers(this, _instanceExtraInitializers);
@@ -88,11 +94,95 @@ let Device = (() => {
88
94
  timeoutOpts;
89
95
  provider;
90
96
  visualTraceService;
91
- constructor(webDriverClient, bundleId, timeoutOpts, provider) {
97
+ deviceProvider;
98
+ persistentSyncEnabled = false;
99
+ activePersistentKey;
100
+ constructor(webDriverClient, bundleId, timeoutOpts, provider, deviceProvider) {
92
101
  this.webDriverClient = webDriverClient;
93
102
  this.bundleId = bundleId;
94
103
  this.timeoutOpts = timeoutOpts;
95
104
  this.provider = provider;
105
+ this.deviceProvider = deviceProvider;
106
+ }
107
+ attachDeviceProvider(provider) {
108
+ this.deviceProvider = provider;
109
+ }
110
+ enablePersistentStatusSync() {
111
+ this.persistentSyncEnabled = true;
112
+ }
113
+ async ensurePersistentLifecycle(testInfo) {
114
+ if (!this.shouldSyncPersistent()) {
115
+ return;
116
+ }
117
+ await this.preparePersistentTest(testInfo);
118
+ }
119
+ async preparePersistentTest(testInfo) {
120
+ if (!this.shouldSyncPersistent()) {
121
+ return;
122
+ }
123
+ const key = this.persistentKey(testInfo);
124
+ if (this.activePersistentKey === key) {
125
+ return;
126
+ }
127
+ this.activePersistentKey = key;
128
+ await this.safeSync({ name: testInfo.title });
129
+ }
130
+ async finalizePersistentTest(testInfo) {
131
+ if (!this.shouldSyncPersistent()) {
132
+ return;
133
+ }
134
+ const key = this.persistentKey(testInfo);
135
+ if (!this.activePersistentKey) {
136
+ logger_1.logger.warn("finalizePersistentTest called before preparePersistentTest; syncing anyway.");
137
+ }
138
+ else if (this.activePersistentKey !== key) {
139
+ logger_1.logger.warn("finalizePersistentTest received unexpected test key; syncing anyway.");
140
+ }
141
+ const status = this.mapPlaywrightStatus(testInfo.status);
142
+ const reason = status === "failed" ? this.failureReason(testInfo) : undefined;
143
+ await this.safeSync({
144
+ name: testInfo.title,
145
+ status,
146
+ reason,
147
+ });
148
+ this.activePersistentKey = undefined;
149
+ }
150
+ shouldSyncPersistent() {
151
+ return (this.persistentSyncEnabled === true &&
152
+ typeof this.deviceProvider?.syncTestDetails === "function");
153
+ }
154
+ persistentKey(testInfo) {
155
+ return `${testInfo.testId}#${testInfo.retry}`;
156
+ }
157
+ mapPlaywrightStatus(status) {
158
+ switch (status) {
159
+ case "failed":
160
+ case "timedOut":
161
+ case "interrupted":
162
+ return "failed";
163
+ case "passed":
164
+ case "skipped":
165
+ default:
166
+ return "passed";
167
+ }
168
+ }
169
+ failureReason(testInfo) {
170
+ const error = testInfo.errors?.[0];
171
+ if (error?.message) {
172
+ return error.message;
173
+ }
174
+ return testInfo.error?.message;
175
+ }
176
+ async safeSync(details) {
177
+ if (!this.deviceProvider?.syncTestDetails) {
178
+ return;
179
+ }
180
+ try {
181
+ await this.deviceProvider.syncTestDetails(details);
182
+ }
183
+ catch (error) {
184
+ logger_1.logger.warn("Failed to sync test details", error);
185
+ }
96
186
  }
97
187
  /**
98
188
  * Initialize Visual Trace Service for screenshot capture during test execution
@@ -458,6 +548,65 @@ let Device = (() => {
458
548
  ]);
459
549
  await this.webDriverClient.releaseActions();
460
550
  }
551
+ /**
552
+ * Updates iOS app settings via BrowserStack executor (mid-session).
553
+ * For default settings, prefer configuring them in appwright.config.ts.
554
+ *
555
+ * @param args - Permission settings or Settings bundle entries
556
+ * @example
557
+ * // Update permissions mid-session
558
+ * await device.updateAppSettings({
559
+ * 'Permission Settings': { Camera: 'Allow' }
560
+ * });
561
+ *
562
+ * @example
563
+ * // Update custom settings
564
+ * await device.updateAppSettings({
565
+ * 'DarkMode': 1,
566
+ * 'Environment': 'production'
567
+ * });
568
+ */
569
+ async updateAppSettings(args) {
570
+ this.assertIOSBrowserStack("updateAppSettings");
571
+ const executor = {
572
+ action: "updateAppSettings",
573
+ arguments: args,
574
+ };
575
+ const script = `browserstack_executor: ${JSON.stringify(executor)}`;
576
+ await this.webDriverClient.executeScript(script, []);
577
+ }
578
+ /**
579
+ * Convenience method for updating iOS permission settings mid-session.
580
+ * For default permissions, prefer configuring them in appwright.config.ts.
581
+ *
582
+ * @param settings - iOS permission settings to update
583
+ * @example
584
+ * await device.updatePermissionSettings({
585
+ * Location: {
586
+ * 'ALLOW LOCATION ACCESS': 'Always',
587
+ * 'Precise Location': 'ON'
588
+ * },
589
+ * Camera: 'Allow'
590
+ * });
591
+ */
592
+ async updatePermissionSettings(settings) {
593
+ return this.updateAppSettings({ "Permission Settings": settings });
594
+ }
595
+ /**
596
+ * Validates that the current platform is iOS and provider is BrowserStack.
597
+ * @private
598
+ */
599
+ assertIOSBrowserStack(methodName) {
600
+ if (this.getPlatform() !== types_1.Platform.IOS) {
601
+ throw new Error(`${methodName} is only supported on iOS platform. ` +
602
+ `Current platform: ${this.getPlatform()}`);
603
+ }
604
+ if (this.provider !== "browserstack") {
605
+ throw new Error(`${methodName} is only supported with BrowserStack provider. ` +
606
+ `Current provider: ${this.provider}. ` +
607
+ `See https://www.browserstack.com/docs/app-automate/appium/advanced-features/ios-app-settings`);
608
+ }
609
+ }
461
610
  };
462
611
  })();
463
612
  exports.Device = Device;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fixture/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,aAAa,EAGd,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAKnC,KAAK,iBAAiB,GAAG;IACvB;;;OAGG;IACH,cAAc,EAAE,cAAc,CAAC;IAE/B;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,KAAK,mBAAmB,GAAG;IACzB,gBAAgB,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,eAAO,MAAM,IAAI,uRAyEf,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,MAAM;2BACY,gBAAgB,YAAY,aAAa;;;;;;;EAUtE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fixture/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,aAAa,EAGd,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAqDnC,KAAK,iBAAiB,GAAG;IACvB;;;OAGG;IACH,cAAc,EAAE,cAAc,CAAC;IAE/B;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,KAAK,mBAAmB,GAAG;IACzB,gBAAgB,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,eAAO,MAAM,IAAI,uRAkFf,CAAC;AA8BH;;;;;;;GAOG;AACH,eAAO,MAAM,MAAM;2BACY,gBAAgB,YAAY,aAAa;;;;;;;EAUtE,CAAC"}
@@ -5,6 +5,39 @@ const test_1 = require("@playwright/test");
5
5
  const providers_1 = require("../providers");
6
6
  const workerInfo_1 = require("./workerInfo");
7
7
  const appium_1 = require("../providers/appium");
8
+ const logger_1 = require("../logger");
9
+ const persistentDevicesByWorker = new Map();
10
+ function createPersistentContext(device) {
11
+ return {
12
+ device,
13
+ activeOperations: 0,
14
+ };
15
+ }
16
+ async function runWithLifecycle(context, task) {
17
+ context.activeOperations += 1;
18
+ try {
19
+ await task();
20
+ }
21
+ finally {
22
+ context.activeOperations -= 1;
23
+ if (context.activeOperations === 0 && context.resolveIdle) {
24
+ context.resolveIdle();
25
+ context.resolveIdle = undefined;
26
+ context.idlePromise = undefined;
27
+ }
28
+ }
29
+ }
30
+ async function waitForLifecycleToComplete(context) {
31
+ if (context.activeOperations === 0) {
32
+ return;
33
+ }
34
+ if (!context.idlePromise) {
35
+ context.idlePromise = new Promise((resolve) => {
36
+ context.resolveIdle = resolve;
37
+ });
38
+ }
39
+ await context.idlePromise;
40
+ }
8
41
  exports.test = test_1.test.extend({
9
42
  deviceProvider: async ({}, use, testInfo) => {
10
43
  const deviceProvider = (0, providers_1.createDeviceProvider)(testInfo.project);
@@ -54,13 +87,51 @@ exports.test = test_1.test.extend({
54
87
  const afterSession = new Date();
55
88
  const workerInfoStore = new workerInfo_1.WorkerInfoStore();
56
89
  await workerInfoStore.saveWorkerStartTime(workerIndex, sessionId, providerName, beforeSession, afterSession);
57
- await use(device);
58
- await workerInfoStore.saveWorkerEndTime(workerIndex, new Date());
59
- await device.close();
90
+ device.attachDeviceProvider(deviceProvider);
91
+ device.enablePersistentStatusSync();
92
+ const context = createPersistentContext(device);
93
+ persistentDevicesByWorker.set(workerIndex, context);
94
+ try {
95
+ await use(device);
96
+ }
97
+ finally {
98
+ await waitForLifecycleToComplete(context);
99
+ persistentDevicesByWorker.delete(workerIndex);
100
+ await workerInfoStore.saveWorkerEndTime(workerIndex, new Date());
101
+ await device.close();
102
+ }
60
103
  },
61
104
  { scope: "worker" },
62
105
  ],
63
106
  });
107
+ exports.test.beforeEach(async ({}, testInfo) => {
108
+ const context = persistentDevicesByWorker.get(testInfo.workerIndex);
109
+ if (!context) {
110
+ return;
111
+ }
112
+ await runWithLifecycle(context, async () => {
113
+ try {
114
+ await context.device.preparePersistentTest(testInfo);
115
+ }
116
+ catch (error) {
117
+ logger_1.logger.warn("Failed to prepare persistent test", error);
118
+ }
119
+ });
120
+ });
121
+ exports.test.afterEach(async ({}, testInfo) => {
122
+ const context = persistentDevicesByWorker.get(testInfo.workerIndex);
123
+ if (!context) {
124
+ return;
125
+ }
126
+ await runWithLifecycle(context, async () => {
127
+ try {
128
+ await context.device.finalizePersistentTest(testInfo);
129
+ }
130
+ catch (error) {
131
+ logger_1.logger.warn("Failed to finalize persistent test", error);
132
+ }
133
+ });
134
+ });
64
135
  /**
65
136
  * Function to extend Playwright’s expect assertion capabilities.
66
137
  * This adds a new method `toBeVisible` which checks if an element is visible on the screen.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/providers/browserstack/index.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,eAAe,EACf,cAAc,EAEf,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAqDtC,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;IAwDX,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC;IAMlC,OAAO,CAAC,cAAc;YASR,YAAY;YAgBZ,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;IAuFlD,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;CAuDrB"}
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;AAqDtC,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;IAwDX,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC;IAMlC,OAAO,CAAC,cAAc;YASR,YAAY;YAiBZ,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;IAuFlD,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;CA2FrB"}
@@ -8,6 +8,7 @@ const async_retry_1 = __importDefault(require("async-retry"));
8
8
  const fs_1 = __importDefault(require("fs"));
9
9
  const form_data_1 = __importDefault(require("form-data"));
10
10
  const path_1 = __importDefault(require("path"));
11
+ const types_1 = require("../../types");
11
12
  const device_1 = require("../../device");
12
13
  const logger_1 = require("../../logger");
13
14
  const API_BASE_URL = "https://api-cloud.browserstack.com/app-automate";
@@ -109,7 +110,7 @@ class BrowserStackDeviceProvider {
109
110
  const testOptions = {
110
111
  expectTimeout: this.project.use.expectTimeout,
111
112
  };
112
- return new device_1.Device(webDriverClient, bundleId, testOptions, this.project.use.device?.provider);
113
+ return new device_1.Device(webDriverClient, bundleId, testOptions, this.project.use.device?.provider, this);
113
114
  }
114
115
  async getSessionDetails() {
115
116
  const data = await getSessionDetails(this.sessionId);
@@ -254,6 +255,35 @@ class BrowserStackDeviceProvider {
254
255
  if (typeof deviceConfig?.appProfiling === "boolean") {
255
256
  bstackOptions.appProfiling = deviceConfig.appProfiling;
256
257
  }
258
+ // iOS App Settings support (capability-based for session start)
259
+ if (platformName === types_1.Platform.IOS) {
260
+ // Support environment variable override for CI/CD
261
+ const envSettingsJson = process.env.APPWRIGHT_BS_UPDATE_APP_SETTINGS_JSON;
262
+ let updateAppSettings;
263
+ if (envSettingsJson) {
264
+ try {
265
+ updateAppSettings = JSON.parse(envSettingsJson);
266
+ }
267
+ catch (e) {
268
+ throw new Error("APPWRIGHT_BS_UPDATE_APP_SETTINGS_JSON is not valid JSON. " +
269
+ "Provide a valid JSON string.");
270
+ }
271
+ }
272
+ else {
273
+ updateAppSettings = deviceConfig?.updateAppSettings;
274
+ }
275
+ if (updateAppSettings && typeof updateAppSettings === "object") {
276
+ // Add to bstack:options as per BrowserStack documentation
277
+ bstackOptions.updateAppSettings = updateAppSettings;
278
+ // Log for debugging (without exposing sensitive data)
279
+ const u = updateAppSettings;
280
+ const hasPermissions = !!u["Permission Settings"];
281
+ const customKeys = Object.keys(u).filter((k) => k !== "Permission Settings");
282
+ if (hasPermissions || customKeys.length > 0) {
283
+ logger_1.logger.log(`iOS app settings configured: permissions=${hasPermissions}, custom_keys=${customKeys.length}`);
284
+ }
285
+ }
286
+ }
257
287
  return {
258
288
  port: 443,
259
289
  path: "/wd/hub",
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/providers/emulator/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,cAAc,EAIf,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAOtC,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAI/C,qBAAa,gBAAiB,YAAW,cAAc;IAInD,OAAO,CAAC,OAAO;IAHjB,SAAS,CAAC,EAAE,MAAM,CAAC;gBAGT,OAAO,EAAE,WAAW,CAAC,eAAe,CAAC,EAC7C,WAAW,EAAE,MAAM,GAAG,SAAS;IAS3B,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC;IAI5B,WAAW;YA8BH,YAAY;YAwBZ,YAAY;CAmC3B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/providers/emulator/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,cAAc,EAIf,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAOtC,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAI/C,qBAAa,gBAAiB,YAAW,cAAc;IAInD,OAAO,CAAC,OAAO;IAHjB,SAAS,CAAC,EAAE,MAAM,CAAC;gBAGT,OAAO,EAAE,WAAW,CAAC,eAAe,CAAC,EAC7C,WAAW,EAAE,MAAM,GAAG,SAAS;IAS3B,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC;IAI5B,WAAW;YA8BH,YAAY;YAyBZ,YAAY;CAmC3B"}
@@ -49,7 +49,7 @@ Follow the steps mentioned in ${androidSimulatorConfigDocLink} to run test on An
49
49
  const testOptions = {
50
50
  expectTimeout,
51
51
  };
52
- return new device_1.Device(webDriverClient, undefined, testOptions, this.project.use.device?.provider);
52
+ return new device_1.Device(webDriverClient, undefined, testOptions, this.project.use.device?.provider, this);
53
53
  }
54
54
  async createConfig() {
55
55
  const platformName = this.project.use.platform;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/providers/lambdatest/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,eAAe,EAAE,cAAc,EAAoB,MAAM,aAAa,CAAC;AAChF,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAwDtC,qBAAa,wBAAyB,YAAW,cAAc;IAM3D,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,WAAW;IANrB,OAAO,CAAC,cAAc,CAAC,CAA2B;IAClD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,WAAW,CAAgC;gBAGzC,OAAO,EAAE,WAAW,CAAC,eAAe,CAAC,EACrC,WAAW,EAAE,MAAM,GAAG,SAAS;IASnC,WAAW;IA8DX,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC;IAMlC,OAAO,CAAC,cAAc;YASR,YAAY;WAeb,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;IA6ElD,eAAe,CAAC,OAAO,EAAE;QAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf;IA0BD,OAAO,CAAC,oBAAoB;IAuB5B,OAAO,CAAC,YAAY;CA8CrB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/providers/lambdatest/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,eAAe,EAAE,cAAc,EAAoB,MAAM,aAAa,CAAC;AAChF,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAwDtC,qBAAa,wBAAyB,YAAW,cAAc;IAM3D,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,WAAW;IANrB,OAAO,CAAC,cAAc,CAAC,CAA2B;IAClD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,WAAW,CAAgC;gBAGzC,OAAO,EAAE,WAAW,CAAC,eAAe,CAAC,EACrC,WAAW,EAAE,MAAM,GAAG,SAAS;IASnC,WAAW;IA8DX,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC;IAMlC,OAAO,CAAC,cAAc;YASR,YAAY;WAgBb,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;IA6ElD,eAAe,CAAC,OAAO,EAAE;QAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf;IA0BD,OAAO,CAAC,oBAAoB;IAuB5B,OAAO,CAAC,YAAY;CA8CrB"}
@@ -119,7 +119,7 @@ class LambdaTestDeviceProvider {
119
119
  const testOptions = {
120
120
  expectTimeout: this.project.use.expectTimeout,
121
121
  };
122
- return new device_1.Device(webDriverClient, this.appBundleId, testOptions, this.project.use.device?.provider);
122
+ return new device_1.Device(webDriverClient, this.appBundleId, testOptions, this.project.use.device?.provider, this);
123
123
  }
124
124
  static async downloadVideo(sessionId, outputDir, fileName) {
125
125
  const sessionData = await getSessionDetails(sessionId);
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/providers/local/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,cAAc,EAIf,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAY/C,qBAAa,mBAAoB,YAAW,cAAc;IAItD,OAAO,CAAC,OAAO;IAHjB,SAAS,CAAC,EAAE,MAAM,CAAC;gBAGT,OAAO,EAAE,WAAW,CAAC,eAAe,CAAC,EAC7C,WAAW,EAAE,MAAM,GAAG,SAAS;IAS3B,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC;IAI5B,WAAW;YAgBH,YAAY;YAyBZ,YAAY;CA6C3B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/providers/local/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,cAAc,EAIf,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAY/C,qBAAa,mBAAoB,YAAW,cAAc;IAItD,OAAO,CAAC,OAAO;IAHjB,SAAS,CAAC,EAAE,MAAM,CAAC;gBAGT,OAAO,EAAE,WAAW,CAAC,eAAe,CAAC,EAC7C,WAAW,EAAE,MAAM,GAAG,SAAS;IAS3B,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC;IAI5B,WAAW;YAgBH,YAAY;YA0BZ,YAAY;CA6C3B"}
@@ -40,7 +40,7 @@ class LocalDeviceProvider {
40
40
  const testOptions = {
41
41
  expectTimeout,
42
42
  };
43
- return new device_1.Device(webDriverClient, bundleId, testOptions, this.project.use.device?.provider);
43
+ return new device_1.Device(webDriverClient, bundleId, testOptions, this.project.use.device?.provider, this);
44
44
  }
45
45
  async createConfig() {
46
46
  const platformName = this.project.use.platform;
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const vitest_1 = require("vitest");
7
7
  const test_1 = __importDefault(require("@playwright/test"));
8
8
  const device_1 = require("../device");
9
+ const types_1 = require("../types");
9
10
  // Override Playwright's test.step/info to work in Vitest environment
10
11
  // so boxedStep decorator can execute without throwing.
11
12
  test_1.default.step = vitest_1.vi.fn(async (_name, body) => await body());
@@ -18,6 +19,34 @@ const createDevice = (executeScript = vitest_1.vi.fn()) => {
18
19
  const device = new device_1.Device(webDriverClient, "com.example.app", { expectTimeout: 1_000 }, "emulator");
19
20
  return { device, executeScript };
20
21
  };
22
+ const makeTestInfo = (overrides = {}) => {
23
+ return {
24
+ title: "example test",
25
+ status: "passed",
26
+ errors: [],
27
+ error: undefined,
28
+ testId: "test-id",
29
+ retry: 0,
30
+ workerIndex: 0,
31
+ project: { use: {} },
32
+ ...overrides,
33
+ };
34
+ };
35
+ const createPersistentDevice = () => {
36
+ const { device } = createDevice();
37
+ const syncTestDetails = vitest_1.vi.fn().mockResolvedValue(undefined);
38
+ const provider = {
39
+ getDevice: vitest_1.vi.fn(),
40
+ syncTestDetails,
41
+ };
42
+ device.attachDeviceProvider(provider);
43
+ device.enablePersistentStatusSync();
44
+ return {
45
+ device,
46
+ syncTestDetails,
47
+ provider,
48
+ };
49
+ };
21
50
  (0, vitest_1.describe)("Device", () => {
22
51
  (0, vitest_1.describe)("backgroundApp", () => {
23
52
  (0, vitest_1.test)("backgrounds indefinitely by default", async () => {
@@ -35,4 +64,134 @@ const createDevice = (executeScript = vitest_1.vi.fn()) => {
35
64
  ]);
36
65
  });
37
66
  });
67
+ (0, vitest_1.describe)("iOS App Settings (BrowserStack)", () => {
68
+ (0, vitest_1.describe)("updateAppSettings", () => {
69
+ (0, vitest_1.test)("formats browserstack_executor correctly for settings bundle", async () => {
70
+ const { device, executeScript } = createDevice();
71
+ vitest_1.vi.spyOn(device, "getPlatform").mockReturnValue(types_1.Platform.IOS);
72
+ device["provider"] = "browserstack";
73
+ const settings = {
74
+ DarkMode: 1,
75
+ Environment: "staging",
76
+ "Child Settings": { "Child Setting 1": "abc" },
77
+ };
78
+ await device.updateAppSettings(settings);
79
+ (0, vitest_1.expect)(executeScript).toHaveBeenCalledWith(vitest_1.expect.stringContaining("browserstack_executor:"), []);
80
+ const call = executeScript.mock.calls[0][0];
81
+ const [, payloadString] = call.split("browserstack_executor: ");
82
+ if (!payloadString) {
83
+ throw new Error("Expected browserstack_executor payload to be present");
84
+ }
85
+ const payload = JSON.parse(payloadString);
86
+ (0, vitest_1.expect)(payload).toEqual({
87
+ action: "updateAppSettings",
88
+ arguments: settings,
89
+ });
90
+ });
91
+ (0, vitest_1.test)("formats permission settings correctly", async () => {
92
+ const { device, executeScript } = createDevice();
93
+ vitest_1.vi.spyOn(device, "getPlatform").mockReturnValue(types_1.Platform.IOS);
94
+ device["provider"] = "browserstack";
95
+ const permissions = {
96
+ "Permission Settings": {
97
+ Location: {
98
+ "ALLOW LOCATION ACCESS": "Always",
99
+ "Precise Location": "ON",
100
+ },
101
+ Camera: "Allow",
102
+ },
103
+ };
104
+ await device.updateAppSettings(permissions);
105
+ const call = executeScript.mock.calls[0][0];
106
+ const [, payloadString] = call.split("browserstack_executor: ");
107
+ if (!payloadString) {
108
+ throw new Error("Expected browserstack_executor payload to be present");
109
+ }
110
+ const payload = JSON.parse(payloadString);
111
+ (0, vitest_1.expect)(payload.arguments).toEqual(permissions);
112
+ });
113
+ (0, vitest_1.test)("throws descriptive error for Android platform", async () => {
114
+ const { device } = createDevice();
115
+ vitest_1.vi.spyOn(device, "getPlatform").mockReturnValue(types_1.Platform.ANDROID);
116
+ device["provider"] = "browserstack";
117
+ await (0, vitest_1.expect)(device.updateAppSettings({})).rejects.toThrow("updateAppSettings is only supported on iOS platform. Current platform: android");
118
+ });
119
+ (0, vitest_1.test)("throws descriptive error for non-BrowserStack provider", async () => {
120
+ const { device } = createDevice();
121
+ vitest_1.vi.spyOn(device, "getPlatform").mockReturnValue(types_1.Platform.IOS);
122
+ device["provider"] = "emulator";
123
+ await (0, vitest_1.expect)(device.updateAppSettings({})).rejects.toThrow(/only supported with BrowserStack provider.*Current provider: emulator/);
124
+ });
125
+ });
126
+ (0, vitest_1.describe)("updatePermissionSettings", () => {
127
+ (0, vitest_1.test)("wraps settings in 'Permission Settings' key", async () => {
128
+ const { device } = createDevice();
129
+ vitest_1.vi.spyOn(device, "getPlatform").mockReturnValue(types_1.Platform.IOS);
130
+ device["provider"] = "browserstack";
131
+ const updateSpy = vitest_1.vi.spyOn(device, "updateAppSettings");
132
+ const permissions = {
133
+ Camera: "Allow",
134
+ Photos: "All Photos",
135
+ Notifications: { "Allow Notifications": "ON" },
136
+ };
137
+ await device.updatePermissionSettings(permissions);
138
+ (0, vitest_1.expect)(updateSpy).toHaveBeenCalledWith({
139
+ "Permission Settings": permissions,
140
+ });
141
+ });
142
+ });
143
+ });
144
+ (0, vitest_1.describe)("persistent sync", () => {
145
+ (0, vitest_1.test)("preparePersistentTest sends name once per test", async () => {
146
+ const { device, syncTestDetails } = createPersistentDevice();
147
+ const info = makeTestInfo({ title: "My test", testId: "t-1" });
148
+ await device.preparePersistentTest(info);
149
+ await device.preparePersistentTest(info);
150
+ (0, vitest_1.expect)(syncTestDetails).toHaveBeenCalledTimes(1);
151
+ (0, vitest_1.expect)(syncTestDetails).toHaveBeenCalledWith({ name: "My test" });
152
+ });
153
+ (0, vitest_1.test)("finalizePersistentTest maps failed status and reason", async () => {
154
+ const { device, syncTestDetails } = createPersistentDevice();
155
+ const info = makeTestInfo({
156
+ title: "fails",
157
+ status: "failed",
158
+ errors: [{ message: "boom" }],
159
+ testId: "t-2",
160
+ });
161
+ await device.finalizePersistentTest(info);
162
+ (0, vitest_1.expect)(syncTestDetails).toHaveBeenCalledWith(vitest_1.expect.objectContaining({
163
+ name: "fails",
164
+ status: "failed",
165
+ reason: "boom",
166
+ }));
167
+ });
168
+ (0, vitest_1.test)("ensurePersistentLifecycle triggers sync for new tests", async () => {
169
+ const { device, syncTestDetails } = createPersistentDevice();
170
+ const first = makeTestInfo({ title: "first", testId: "first", retry: 0 });
171
+ const second = makeTestInfo({
172
+ title: "second",
173
+ testId: "second",
174
+ retry: 0,
175
+ });
176
+ await device.ensurePersistentLifecycle(first);
177
+ await device.ensurePersistentLifecycle(second);
178
+ (0, vitest_1.expect)(syncTestDetails).toHaveBeenNthCalledWith(1, { name: "first" });
179
+ (0, vitest_1.expect)(syncTestDetails).toHaveBeenNthCalledWith(2, { name: "second" });
180
+ });
181
+ (0, vitest_1.test)("finalizePersistentTest defaults skipped to passed without reason", async () => {
182
+ const { device, syncTestDetails } = createPersistentDevice();
183
+ const info = makeTestInfo({
184
+ title: "skipped test",
185
+ status: "skipped",
186
+ testId: "t-3",
187
+ });
188
+ await device.finalizePersistentTest(info);
189
+ (0, vitest_1.expect)(syncTestDetails).toHaveBeenCalledWith(vitest_1.expect.objectContaining({
190
+ name: "skipped test",
191
+ status: "passed",
192
+ }));
193
+ const payload = syncTestDetails.mock.calls[0][0];
194
+ (0, vitest_1.expect)(payload.reason).toBeUndefined();
195
+ });
196
+ });
38
197
  });
@@ -1,6 +1,6 @@
1
1
  import { defineConfig } from "vitest/config";
2
2
  export default defineConfig({
3
3
  test: {
4
- exclude: ["example/**", "node_modules/**", "dist/**"],
4
+ exclude: ["**/example/**", "node_modules/**", "dist/**"],
5
5
  },
6
6
  });
@@ -110,6 +110,22 @@ export type BrowserStackConfig = {
110
110
  * Defaults to 180 seconds (3 minutes).
111
111
  */
112
112
  idleTimeout?: number;
113
+ /**
114
+ * iOS app settings to configure at session start.
115
+ * Can include Permission Settings and/or custom Settings Bundle entries.
116
+ * Only supported on iOS devices with BrowserStack provider.
117
+ *
118
+ * @example
119
+ * ```ts
120
+ * {
121
+ * 'Permission Settings': {
122
+ * Location: { 'ALLOW LOCATION ACCESS': 'Always' }
123
+ * },
124
+ * 'Environment': 'staging'
125
+ * }
126
+ * ```
127
+ */
128
+ updateAppSettings?: IosAppSettings;
113
129
  };
114
130
  export type LambdaTestConfig = {
115
131
  provider: "lambdatest";
@@ -179,6 +195,36 @@ export type EmulatorConfig = {
179
195
  */
180
196
  orientation?: DeviceOrientation;
181
197
  };
198
+ /**
199
+ * iOS permission settings structure (based on BrowserStack API).
200
+ * Supports both simple string values and nested objects for permissions.
201
+ */
202
+ export type IosPermissionSettings = Partial<{
203
+ Location: Partial<{
204
+ "ALLOW LOCATION ACCESS": "Always" | "While Using the App" | "Never";
205
+ "Precise Location": "ON" | "OFF";
206
+ }>;
207
+ Camera: "Allow" | "Deny";
208
+ Contacts: "Allow" | "Deny";
209
+ Photos: "Add Photos Only" | "Selected Photos" | "All Photos" | "None";
210
+ Notifications: Partial<{
211
+ "Allow Notifications": "ON" | "OFF";
212
+ }>;
213
+ Language: string;
214
+ }>;
215
+ /**
216
+ * Settings bundle for custom app settings (iOS Settings Bundle).
217
+ */
218
+ export type IosSettingsBundle = Record<string, unknown>;
219
+ /**
220
+ * Combined type for all iOS settings (permissions + custom).
221
+ * Can include Permission Settings, custom Settings Bundle entries, or both.
222
+ */
223
+ export type IosAppSettings = {
224
+ "Permission Settings": IosPermissionSettings;
225
+ } | IosSettingsBundle | ({
226
+ "Permission Settings": IosPermissionSettings;
227
+ } & IosSettingsBundle);
182
228
  export declare enum Platform {
183
229
  ANDROID = "android",
184
230
  IOS = "ios"
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACnC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;AAEtE,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B;;OAEG;IACH,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,WAAW,cAAc;IAC7B;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,WAAW,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9B;;OAEG;IACH,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAE7B;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE;QAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACrB;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,EAAE,YAAY,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IAEpB,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,OAAO,GAAG,IAAI,GAAG,KAAK,GAAG,mBAAmB,CAAC;IAEjE;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,YAAY,GACpB,kBAAkB,GAClB,gBAAgB,GAChB,iBAAiB,GACjB,cAAc,CAAC;AAEnB;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,EAAE,cAAc,CAAC;IAEzB;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAEhC;;;OAGG;IACH,0BAA0B,CAAC,EAAE,OAAO,CAAC;IAErC;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;;;;;;;;;;;;OAcG;IACH,iBAAiB,CAAC,EAAE,cAAc,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,EAAE,YAAY,CAAC;IAEvB;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAEhC;;;OAGG;IACH,0BAA0B,CAAC,EAAE,OAAO,CAAC;IAErC;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,EAAE,cAAc,CAAC;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,WAAW,CAAC,EAAE,iBAAiB,CAAC;CACjC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,UAAU,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,WAAW,CAAC,EAAE,iBAAiB,CAAC;CACjC,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,qBAAqB,GAAG,OAAO,CAAC;IAC1C,QAAQ,EAAE,OAAO,CAAC;QAChB,uBAAuB,EAAE,QAAQ,GAAG,qBAAqB,GAAG,OAAO,CAAC;QACpE,kBAAkB,EAAE,IAAI,GAAG,KAAK,CAAC;KAClC,CAAC,CAAC;IACH,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC;IACzB,QAAQ,EAAE,OAAO,GAAG,MAAM,CAAC;IAC3B,MAAM,EAAE,iBAAiB,GAAG,iBAAiB,GAAG,YAAY,GAAG,MAAM,CAAC;IACtE,aAAa,EAAE,OAAO,CAAC;QACrB,qBAAqB,EAAE,IAAI,GAAG,KAAK,CAAC;KACrC,CAAC,CAAC;IACH,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC,CAAC;AAEH;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAExD;;;GAGG;AACH,MAAM,MAAM,cAAc,GACtB;IAAE,qBAAqB,EAAE,qBAAqB,CAAA;CAAE,GAChD,iBAAiB,GACjB,CAAC;IAAE,qBAAqB,EAAE,qBAAqB,CAAA;CAAE,GAAG,iBAAiB,CAAC,CAAC;AAE3E,oBAAY,QAAQ;IAClB,OAAO,YAAY;IACnB,GAAG,QAAQ;CACZ;AAED,oBAAY,iBAAiB;IAC3B,QAAQ,aAAa;IACrB,SAAS,cAAc;CACxB;AAED,oBAAY,eAAe;IACzB,EAAE,OAAO;IACT,IAAI,SAAS;IACb,IAAI,SAAS;IACb,KAAK,UAAU;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;;;OASG;IACH,GAAG,CAAC,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE5C;;;;;;;;;;OAUG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE5D;;;;;;;;;;OAUG;IACH,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEtE;;;;;;;;;;;OAWG;IACH,OAAO,CACL,KAAK,EAAE,UAAU,GAAG,SAAS,EAC7B,OAAO,CAAC,EAAE,aAAa,GACtB,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjB;;;;;;;;;;OAUG;IACH,SAAS,CAAC,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAErD;;;;;;;;;OASG;IACH,OAAO,CAAC,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAElD,MAAM,CAAC,SAAS,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnD;AAED,oBAAY,eAAe;IACzB,0BAA0B,4BAA4B;CACvD;AAED,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC;AAClE,MAAM,MAAM,kBAAkB,GAAG,qCAAqC,CAAC"}
package/dist/utils.d.ts CHANGED
@@ -4,9 +4,11 @@ export declare function boxedStep(target: Function, context: ClassMethodDecorato
4
4
  device?: {
5
5
  takeScreenshot: () => Promise<Buffer>;
6
6
  initializeVisualTrace?: (testInfo: any, retryIndex: number, config?: any) => void;
7
+ ensurePersistentLifecycle?: (testInfo: TestInfo) => Promise<void>;
7
8
  };
8
9
  takeScreenshot?: () => Promise<Buffer>;
9
10
  initializeVisualTrace?: (testInfo: any, retryIndex: number, config?: any) => void;
11
+ ensurePersistentLifecycle?: (testInfo: TestInfo) => Promise<void>;
10
12
  }, ...args: any) => Promise<any>;
11
13
  export declare function validateBuildPath(buildPath: string | undefined, expectedExtension: string): void;
12
14
  export declare function getLatestBuildToolsVersions(versions: string[]): string | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAa,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAQlD,wBAAgB,SAAS,CACvB,MAAM,EAAE,QAAQ,EAChB,OAAO,EAAE,2BAA2B,UAG5B;IACJ,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,MAAM,CAAC,EAAE;QACP,cAAc,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;QACtC,qBAAqB,CAAC,EAAE,CACtB,QAAQ,EAAE,GAAG,EACb,UAAU,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,GAAG,KACT,IAAI,CAAC;KACX,CAAC;IACF,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACvC,qBAAqB,CAAC,EAAE,CACtB,QAAQ,EAAE,GAAG,EACb,UAAU,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,GAAG,KACT,IAAI,CAAC;CACX,WACQ,GAAG,kBAwEf;AAED,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,iBAAiB,EAAE,MAAM,QAoB1B;AAED,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,MAAM,EAAE,GACjB,MAAM,GAAG,SAAS,CAEpB;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAuB7E;AAED,wBAAgB,QAAQ,WAEvB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,QAAQ,EAClB,UAAU,EAAE,MAAM,GACjB,OAAO,CA2BT;AAED;;GAEG;AACH,wBAAgB,iCAAiC,CAC/C,QAAQ,EAAE,QAAQ,EAClB,UAAU,EAAE,MAAM,GACjB,OAAO,CAST"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAa,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAQlD,wBAAgB,SAAS,CACvB,MAAM,EAAE,QAAQ,EAChB,OAAO,EAAE,2BAA2B,UAG5B;IACJ,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,MAAM,CAAC,EAAE;QACP,cAAc,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;QACtC,qBAAqB,CAAC,EAAE,CACtB,QAAQ,EAAE,GAAG,EACb,UAAU,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,GAAG,KACT,IAAI,CAAC;QACV,yBAAyB,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;KACnE,CAAC;IACF,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACvC,qBAAqB,CAAC,EAAE,CACtB,QAAQ,EAAE,GAAG,EACb,UAAU,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,GAAG,KACT,IAAI,CAAC;IACV,yBAAyB,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACnE,WACQ,GAAG,kBAgFf;AAED,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,iBAAiB,EAAE,MAAM,QAoB1B;AAED,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,MAAM,EAAE,GACjB,MAAM,GAAG,SAAS,CAEpB;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAuB7E;AAED,wBAAgB,QAAQ,WAEvB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,QAAQ,EAClB,UAAU,EAAE,MAAM,GACjB,OAAO,CA2BT;AAED;;GAEG;AACH,wBAAgB,iCAAiC,CAC/C,QAAQ,EAAE,QAAQ,EAClB,UAAU,EAAE,MAAM,GACjB,OAAO,CAST"}
package/dist/utils.js CHANGED
@@ -43,6 +43,11 @@ function boxedStep(target, context) {
43
43
  try {
44
44
  const testInfo = test_1.default.info();
45
45
  const device = this.device || this;
46
+ if (device &&
47
+ typeof device.ensurePersistentLifecycle === "function" &&
48
+ testInfo) {
49
+ await device.ensurePersistentLifecycle(testInfo);
50
+ }
46
51
  // Initialize or reinitialize if needed
47
52
  if (device?.initializeVisualTrace && testInfo) {
48
53
  const needsInit = (0, visualTrace_1.needsVisualTraceReinitialization)(testInfo.testId, testInfo.retry);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@samsara-dev/appwright",
3
- "version": "0.3.5",
3
+ "version": "0.5.0",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org/",
6
6
  "access": "public"