@samsara-dev/appwright 0.5.1 → 0.6.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.
package/AGENTS.md ADDED
@@ -0,0 +1,53 @@
1
+ # Appwright Agent Guide
2
+
3
+ ## 1. Project Snapshot
4
+ - **Repo type:** Single-package TypeScript library (no workspaces)
5
+ - **Stack:** Node.js ≥20, TypeScript, Playwright fixtures, Vitest, ESLint (`@empiricalrun`)
6
+ - **Docs:** Each major directory ships its own `AGENTS.md`; nearest file wins
7
+
8
+ ## 2. Root Setup Commands
9
+ ```bash
10
+ npm ci # install dependencies (use npm install for incremental updates)
11
+ npm run lint # ESLint with @empiricalrun rules
12
+ npm run build # tsc --build (typecheck + emit to dist/)
13
+ npm test -- --run # Vitest single pass (avoids interactive watch mode)
14
+ npm run changeset # prepare release notes when changes ship
15
+ ```
16
+
17
+ ## 3. Universal Conventions
18
+ - Share a short plan with maintainers **before editing** (hard requirement)
19
+ - Author new code in `src/**`; never modify generated `dist/**`
20
+ - Rely on `src/logger.ts` instead of raw `console` (exceptions stream subprocess output only)
21
+ - Follow Conventional Commits (`feat:`, `fix:`, `chore:`) and branch from `main`
22
+ - Keep APIs backward compatible; prefer additive options over breaking changes
23
+ - Always generate a Changeset that captures the user-facing impact of the feature you worked on
24
+
25
+ ## 4. Security & Secrets
26
+ - Never commit API keys, BrowserStack creds, or AWS secrets; load via environment variables
27
+ - BrowserStack needs `BROWSERSTACK_USERNAME` / `BROWSERSTACK_ACCESS_KEY`
28
+ - Remote build downloads require AWS credentials (`AWS_REGION` plus standard SDK env vars)
29
+ - Avoid storing PII or test artifacts in the repo; use external storage for logs/videos
30
+
31
+ ## 5. JIT Index (what to open, not what to paste)
32
+
33
+ ### Package Structure
34
+ - Core library: `src/` → [see src/AGENTS.md](src/AGENTS.md)
35
+ - Device runtime: `src/device/` → [see src/device/AGENTS.md](src/device/AGENTS.md)
36
+ - Providers (BrowserStack, LambdaTest, emulator, local): `src/providers/` → [see src/providers/AGENTS.md](src/providers/AGENTS.md)
37
+ - Vision utilities: `src/vision/` → [see src/vision/AGENTS.md](src/vision/AGENTS.md)
38
+ - Visual trace capture: `src/visualTrace/` → [see src/visualTrace/AGENTS.md](src/visualTrace/AGENTS.md)
39
+ - Test suite: `src/tests/` → [see src/tests/AGENTS.md](src/tests/AGENTS.md)
40
+ - Example consumer app: `example/` → [see example/AGENTS.md](example/AGENTS.md)
41
+
42
+ ### Quick Find Commands
43
+ - Locate a public export: `rg -n "export .*" src/index.ts src/types` (fallback: `grep -rn "export .*" src/index.ts src/types`)
44
+ - Discover provider hooks: `rg -n "DeviceProvider" src/providers` (fallback: `grep -rn "DeviceProvider" src/providers`)
45
+ - Inspect Playwright fixtures: `rg -n "extend<TestLevelFixtures" src/fixture` (fallback: `grep -rn "extend<TestLevelFixtures" src/fixture`)
46
+ - Track vision helpers: `rg -n "AppwrightVision" src/vision` (fallback: `grep -rn "AppwrightVision" src/vision`)
47
+ - Find targeted tests: `rg -n "\\.spec\\.ts" src/tests` (fallback: `grep -rn "\.spec\.ts" src/tests`)
48
+
49
+ ## 6. Definition of Done
50
+ - `npm run lint && npm run build && npm test -- --run` must all pass locally
51
+ - Add a Changeset entry for user-facing changes
52
+ - Ensure documentation in relevant `AGENTS.md` files reflects the update
53
+ - Confirm the plan was shared and acknowledged before merging
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # appwright
2
2
 
3
+ ## 0.6.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 40979ac: - add BrowserStack S3 buildPath support with local download helper and coverage
8
+ - restructure hierarchical AGENTS.md guidance and reminders for contributors
9
+
10
+ ## 0.6.0
11
+
12
+ ### Minor Changes
13
+
14
+ - 98c9650: Expose `Device.getTimeouts()` along with a lightweight window helper for better session introspection.
15
+
3
16
  ## 0.5.1
4
17
 
5
18
  ### Patch Changes
@@ -3,6 +3,9 @@ import { AppwrightLocator, DeviceProvider, ExtractType, IosAppSettings, IosPermi
3
3
  import { z } from "zod";
4
4
  import { LLMModel } from "@empiricalrun/llm";
5
5
  import { TestInfo } from "@playwright/test";
6
+ type DeviceTimeouts = Partial<Record<string, number>> & {
7
+ command?: number;
8
+ };
6
9
  export declare class Device {
7
10
  private webDriverClient;
8
11
  private bundleId;
@@ -188,6 +191,19 @@ export declare class Device {
188
191
  setMockCameraView(imagePath: string): Promise<void>;
189
192
  pause(): Promise<void>;
190
193
  waitForTimeout(timeout: number): Promise<void>;
194
+ /**
195
+ * Get the current timeout settings for the WebDriver session.
196
+ */
197
+ getTimeouts(): Promise<DeviceTimeouts>;
198
+ /**
199
+ * Get the current window rectangle dimensions.
200
+ */
201
+ getWindowRect(): Promise<{
202
+ width: number;
203
+ height: number;
204
+ x: number;
205
+ y: number;
206
+ }>;
191
207
  /**
192
208
  * Get a screenshot of the current screen as a base64 encoded string.
193
209
  */
@@ -249,4 +265,5 @@ export declare class Device {
249
265
  */
250
266
  private assertIOSBrowserStack;
251
267
  }
268
+ export {};
252
269
  //# 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":"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"}
@@ -53,6 +53,8 @@ let Device = (() => {
53
53
  let _setMockCameraView_decorators;
54
54
  let _pause_decorators;
55
55
  let _waitForTimeout_decorators;
56
+ let _getTimeouts_decorators;
57
+ let _getWindowRect_decorators;
56
58
  let _screenshot_decorators;
57
59
  let _scroll_decorators;
58
60
  let _sendKeyStrokes_decorators;
@@ -69,6 +71,8 @@ let Device = (() => {
69
71
  _setMockCameraView_decorators = [utils_1.boxedStep];
70
72
  _pause_decorators = [utils_1.boxedStep];
71
73
  _waitForTimeout_decorators = [utils_1.boxedStep];
74
+ _getTimeouts_decorators = [utils_1.boxedStep];
75
+ _getWindowRect_decorators = [utils_1.boxedStep];
72
76
  _screenshot_decorators = [utils_1.boxedStep];
73
77
  _scroll_decorators = [utils_1.boxedStep];
74
78
  _sendKeyStrokes_decorators = [utils_1.boxedStep];
@@ -82,6 +86,8 @@ let Device = (() => {
82
86
  __esDecorate(this, null, _setMockCameraView_decorators, { kind: "method", name: "setMockCameraView", static: false, private: false, access: { has: obj => "setMockCameraView" in obj, get: obj => obj.setMockCameraView }, metadata: _metadata }, null, _instanceExtraInitializers);
83
87
  __esDecorate(this, null, _pause_decorators, { kind: "method", name: "pause", static: false, private: false, access: { has: obj => "pause" in obj, get: obj => obj.pause }, metadata: _metadata }, null, _instanceExtraInitializers);
84
88
  __esDecorate(this, null, _waitForTimeout_decorators, { kind: "method", name: "waitForTimeout", static: false, private: false, access: { has: obj => "waitForTimeout" in obj, get: obj => obj.waitForTimeout }, metadata: _metadata }, null, _instanceExtraInitializers);
89
+ __esDecorate(this, null, _getTimeouts_decorators, { kind: "method", name: "getTimeouts", static: false, private: false, access: { has: obj => "getTimeouts" in obj, get: obj => obj.getTimeouts }, metadata: _metadata }, null, _instanceExtraInitializers);
90
+ __esDecorate(this, null, _getWindowRect_decorators, { kind: "method", name: "getWindowRect", static: false, private: false, access: { has: obj => "getWindowRect" in obj, get: obj => obj.getWindowRect }, metadata: _metadata }, null, _instanceExtraInitializers);
85
91
  __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);
86
92
  __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);
87
93
  __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);
@@ -501,6 +507,18 @@ let Device = (() => {
501
507
  async waitForTimeout(timeout) {
502
508
  await new Promise((resolve) => setTimeout(resolve, timeout));
503
509
  }
510
+ /**
511
+ * Get the current timeout settings for the WebDriver session.
512
+ */
513
+ async getTimeouts() {
514
+ return (await this.webDriverClient.getTimeouts());
515
+ }
516
+ /**
517
+ * Get the current window rectangle dimensions.
518
+ */
519
+ async getWindowRect() {
520
+ return await this.webDriverClient.getWindowRect();
521
+ }
504
522
  /**
505
523
  * Get a screenshot of the current screen as a base64 encoded string.
506
524
  */
@@ -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;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;CAuFrB"}
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;AAsDtC,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;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;CAuFrB"}
@@ -11,6 +11,7 @@ const path_1 = __importDefault(require("path"));
11
11
  const types_1 = require("../../types");
12
12
  const device_1 = require("../../device");
13
13
  const logger_1 = require("../../logger");
14
+ const s3_1 = require("./s3");
14
15
  const API_BASE_URL = "https://api-cloud.browserstack.com/app-automate";
15
16
  const envVarKeyForBuild = (projectName) => `BROWSERSTACK_APP_URL_${projectName.toUpperCase()}`;
16
17
  function getAuthHeader() {
@@ -50,45 +51,59 @@ class BrowserStackDeviceProvider {
50
51
  throw new Error("BROWSERSTACK_USERNAME and BROWSERSTACK_ACCESS_KEY are required environment variables for this device provider.");
51
52
  }
52
53
  const buildPath = this.project.use.buildPath;
53
- const isHttpUrl = buildPath.startsWith("http");
54
+ const isS3Url = (0, s3_1.isS3Uri)(buildPath);
55
+ const isHttpUrl = !isS3Url && buildPath.startsWith("http");
54
56
  const isBrowserStackUrl = buildPath.startsWith("bs://");
55
57
  let appUrl = undefined;
56
- if (isBrowserStackUrl) {
57
- appUrl = buildPath;
58
- }
59
- else {
60
- // Upload the file to BrowserStack and get the appUrl
61
- let body;
62
- let headers = {
63
- Authorization: getAuthHeader(),
64
- };
65
- if (isHttpUrl) {
66
- body = new URLSearchParams({
67
- url: buildPath,
68
- });
58
+ let downloadedArtifact;
59
+ try {
60
+ if (isBrowserStackUrl) {
61
+ appUrl = buildPath;
69
62
  }
70
63
  else {
71
- if (!fs_1.default.existsSync(buildPath)) {
72
- throw new Error(`Build file not found: ${buildPath}`);
64
+ // Upload the file to BrowserStack and get the appUrl
65
+ let body;
66
+ let headers = {
67
+ Authorization: getAuthHeader(),
68
+ };
69
+ let uploadSource = buildPath;
70
+ if (isS3Url) {
71
+ logger_1.logger.log(`Downloading build from S3: ${buildPath}`);
72
+ downloadedArtifact = await (0, s3_1.downloadS3Artifact)(buildPath);
73
+ uploadSource = downloadedArtifact.filePath;
74
+ }
75
+ if (isHttpUrl) {
76
+ body = new URLSearchParams({
77
+ url: buildPath,
78
+ });
79
+ }
80
+ else {
81
+ if (!fs_1.default.existsSync(uploadSource)) {
82
+ throw new Error(`Build file not found: ${uploadSource}`);
83
+ }
84
+ const form = new form_data_1.default();
85
+ form.append("file", fs_1.default.createReadStream(uploadSource));
86
+ headers = { ...headers, ...form.getHeaders() };
87
+ body = form;
88
+ }
89
+ const fetch = (await import("node-fetch")).default;
90
+ logger_1.logger.log(`Uploading build to BrowserStack: ${uploadSource}`);
91
+ const response = await fetch(`${API_BASE_URL}/upload`, {
92
+ method: "POST",
93
+ headers,
94
+ body,
95
+ });
96
+ const data = await response.json();
97
+ appUrl = data.app_url;
98
+ if (!appUrl) {
99
+ logger_1.logger.error("Uploading the build failed:", data);
100
+ throw new Error(`Failed to upload build to BrowserStack: ${JSON.stringify(data)}`);
73
101
  }
74
- const form = new form_data_1.default();
75
- form.append("file", fs_1.default.createReadStream(buildPath));
76
- headers = { ...headers, ...form.getHeaders() };
77
- body = form;
78
- }
79
- const fetch = (await import("node-fetch")).default;
80
- logger_1.logger.log(`Uploading: ${buildPath}`);
81
- const response = await fetch(`${API_BASE_URL}/upload`, {
82
- method: "POST",
83
- headers,
84
- body,
85
- });
86
- const data = await response.json();
87
- appUrl = data.app_url;
88
- if (!appUrl) {
89
- logger_1.logger.error("Uploading the build failed:", data);
90
102
  }
91
103
  }
104
+ finally {
105
+ await downloadedArtifact?.cleanup();
106
+ }
92
107
  process.env[envVarKeyForBuild(this.project.name)] = appUrl;
93
108
  }
94
109
  async getDevice() {
@@ -0,0 +1,11 @@
1
+ export type DownloadedS3Artifact = {
2
+ filePath: string;
3
+ cleanup: () => Promise<void>;
4
+ };
5
+ export declare function isS3Uri(value: string): boolean;
6
+ export declare function parseS3Uri(uri: string): {
7
+ bucket: string;
8
+ key: string;
9
+ };
10
+ export declare function downloadS3Artifact(uri: string): Promise<DownloadedS3Artifact>;
11
+ //# sourceMappingURL=s3.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"s3.d.ts","sourceRoot":"","sources":["../../../src/providers/browserstack/s3.ts"],"names":[],"mappings":"AAeA,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B,CAAC;AAEF,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE9C;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAoBvE;AAgDD,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,oBAAoB,CAAC,CA8B/B"}
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.isS3Uri = isS3Uri;
7
+ exports.parseS3Uri = parseS3Uri;
8
+ exports.downloadS3Artifact = downloadS3Artifact;
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const promises_1 = __importDefault(require("fs/promises"));
11
+ const os_1 = __importDefault(require("os"));
12
+ const path_1 = __importDefault(require("path"));
13
+ const promises_2 = require("stream/promises");
14
+ const stream_1 = require("stream");
15
+ const client_s3_1 = require("@aws-sdk/client-s3");
16
+ const S3_SCHEME = "s3://";
17
+ const TEMP_PREFIX = "appwright-s3-";
18
+ function isS3Uri(value) {
19
+ return value.startsWith(S3_SCHEME);
20
+ }
21
+ function parseS3Uri(uri) {
22
+ if (!isS3Uri(uri)) {
23
+ throw new Error(`Invalid S3 URI: ${uri}`);
24
+ }
25
+ const remainder = uri.slice(S3_SCHEME.length);
26
+ const firstSlash = remainder.indexOf("/");
27
+ if (firstSlash === -1) {
28
+ throw new Error(`S3 URI must be in the format s3://bucket/key. Received: ${uri}`);
29
+ }
30
+ const bucket = remainder.slice(0, firstSlash);
31
+ const key = remainder.slice(firstSlash + 1);
32
+ if (!bucket || !key) {
33
+ throw new Error(`S3 URI must include both bucket and key: ${uri}`);
34
+ }
35
+ return {
36
+ bucket,
37
+ key: decodeURIComponent(key),
38
+ };
39
+ }
40
+ async function writeBodyToFile(body, destination) {
41
+ if (!body) {
42
+ throw new Error("Received empty S3 object body");
43
+ }
44
+ if (body instanceof stream_1.Readable) {
45
+ await (0, promises_2.pipeline)(body, fs_1.default.createWriteStream(destination));
46
+ return;
47
+ }
48
+ if (typeof body.transformToByteArray === "function") {
49
+ const bytes = await body.transformToByteArray();
50
+ await promises_1.default.writeFile(destination, Buffer.from(bytes));
51
+ return;
52
+ }
53
+ if (typeof body.arrayBuffer === "function") {
54
+ const buffer = Buffer.from(await body.arrayBuffer());
55
+ await promises_1.default.writeFile(destination, buffer);
56
+ return;
57
+ }
58
+ if (Symbol.asyncIterator in Object(body)) {
59
+ const chunks = [];
60
+ const iterable = body;
61
+ for await (const chunk of iterable) {
62
+ if (typeof chunk === "string") {
63
+ chunks.push(Buffer.from(chunk));
64
+ }
65
+ else if (chunk instanceof Uint8Array) {
66
+ chunks.push(Buffer.from(chunk));
67
+ }
68
+ else {
69
+ chunks.push(Buffer.from(String(chunk)));
70
+ }
71
+ }
72
+ await promises_1.default.writeFile(destination, Buffer.concat(chunks));
73
+ return;
74
+ }
75
+ throw new Error("Unsupported S3 response body type");
76
+ }
77
+ async function downloadS3Artifact(uri) {
78
+ const { bucket, key } = parseS3Uri(uri);
79
+ const region = process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION;
80
+ if (!region) {
81
+ throw new Error("Set AWS_REGION or AWS_DEFAULT_REGION to download builds from S3.");
82
+ }
83
+ const client = new client_s3_1.S3Client({ region });
84
+ const command = new client_s3_1.GetObjectCommand({ Bucket: bucket, Key: key });
85
+ const response = await client.send(command);
86
+ const tmpDir = await promises_1.default.mkdtemp(path_1.default.join(os_1.default.tmpdir(), TEMP_PREFIX));
87
+ const fileName = key.split("/").filter(Boolean).pop() ?? "artifact";
88
+ const destination = path_1.default.join(tmpDir, fileName);
89
+ try {
90
+ await writeBodyToFile(response.Body, destination);
91
+ }
92
+ catch (error) {
93
+ await promises_1.default.rm(tmpDir, { recursive: true, force: true });
94
+ throw error;
95
+ }
96
+ return {
97
+ filePath: destination,
98
+ cleanup: async () => {
99
+ await promises_1.default.rm(tmpDir, { recursive: true, force: true });
100
+ },
101
+ };
102
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=browserstack.s3.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browserstack.s3.spec.d.ts","sourceRoot":"","sources":["../../src/tests/browserstack.s3.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const vitest_1 = require("vitest");
7
+ const stream_1 = require("stream");
8
+ const promises_1 = __importDefault(require("fs/promises"));
9
+ const sendMock = vitest_1.vi.fn();
10
+ const capturedConfigs = [];
11
+ vitest_1.vi.mock("@aws-sdk/client-s3", () => {
12
+ class MockS3Client {
13
+ constructor(options) {
14
+ capturedConfigs.push(options);
15
+ }
16
+ send = sendMock;
17
+ }
18
+ class MockGetObjectCommand {
19
+ input;
20
+ constructor(input) {
21
+ this.input = input;
22
+ }
23
+ }
24
+ return {
25
+ S3Client: MockS3Client,
26
+ GetObjectCommand: MockGetObjectCommand,
27
+ __esModule: true,
28
+ };
29
+ });
30
+ let browserstackS3;
31
+ (0, vitest_1.beforeAll)(async () => {
32
+ browserstackS3 = await import("../providers/browserstack/s3.js");
33
+ });
34
+ (0, vitest_1.afterEach)(async () => {
35
+ sendMock.mockReset();
36
+ capturedConfigs.length = 0;
37
+ delete process.env.AWS_REGION;
38
+ delete process.env.AWS_DEFAULT_REGION;
39
+ });
40
+ (0, vitest_1.describe)("browserstack S3 helpers", () => {
41
+ (0, vitest_1.test)("parseS3Uri extracts bucket and decoded key", () => {
42
+ const result = browserstackS3.parseS3Uri("s3://my-bucket/builds/app%20v2.ipa");
43
+ (0, vitest_1.expect)(result).toEqual({
44
+ bucket: "my-bucket",
45
+ key: "builds/app v2.ipa",
46
+ });
47
+ });
48
+ (0, vitest_1.test)("downloadS3Artifact saves file locally and cleans up", async () => {
49
+ process.env.AWS_REGION = "us-west-2";
50
+ sendMock.mockResolvedValueOnce({
51
+ Body: stream_1.Readable.from(["test-binary"]),
52
+ });
53
+ const artifact = await browserstackS3.downloadS3Artifact("s3://test-bucket/apps/mobile.apk");
54
+ const fileContents = await promises_1.default.readFile(artifact.filePath, "utf-8");
55
+ (0, vitest_1.expect)(fileContents).toBe("test-binary");
56
+ (0, vitest_1.expect)(sendMock).toHaveBeenCalledTimes(1);
57
+ (0, vitest_1.expect)(sendMock.mock.calls[0]?.[0]).toMatchObject({
58
+ input: { Bucket: "test-bucket", Key: "apps/mobile.apk" },
59
+ });
60
+ (0, vitest_1.expect)(capturedConfigs[0]).toMatchObject({ region: "us-west-2" });
61
+ await artifact.cleanup();
62
+ await (0, vitest_1.expect)(promises_1.default.stat(artifact.filePath)).rejects.toMatchObject({
63
+ code: "ENOENT",
64
+ });
65
+ });
66
+ (0, vitest_1.test)("downloadS3Artifact throws when region is missing", async () => {
67
+ sendMock.mockResolvedValueOnce({
68
+ Body: stream_1.Readable.from(["unused"]),
69
+ });
70
+ await (0, vitest_1.expect)(browserstackS3.downloadS3Artifact("s3://bucket/key")).rejects.toThrow(/AWS_REGION/);
71
+ (0, vitest_1.expect)(sendMock).not.toHaveBeenCalled();
72
+ });
73
+ (0, vitest_1.test)("isS3Uri differentiates schemes", () => {
74
+ (0, vitest_1.expect)(browserstackS3.isS3Uri("s3://bucket/key")).toBe(true);
75
+ (0, vitest_1.expect)(browserstackS3.isS3Uri("https://example.com/app.apk")).toBe(false);
76
+ (0, vitest_1.expect)(browserstackS3.isS3Uri("bs://sample-app")).toBe(false);
77
+ });
78
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@samsara-dev/appwright",
3
- "version": "0.5.1",
3
+ "version": "0.6.1",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org/",
6
6
  "access": "public"
@@ -36,6 +36,7 @@
36
36
  "license": "Apache-2.0",
37
37
  "description": "E2E mobile app testing done right, with the Playwright test runner",
38
38
  "dependencies": {
39
+ "@aws-sdk/client-s3": "^3.932.0",
39
40
  "@empiricalrun/llm": "^0.9.25",
40
41
  "@ffmpeg-installer/ffmpeg": "^1.1.0",
41
42
  "@playwright/test": "^1.56.1",