shiplightai 0.1.65 → 0.1.67

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/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire as __cli_createRequire } from "module";
3
3
  const require = __cli_createRequire(import.meta.url);
4
- var cs=Object.defineProperty;var _=(e,t)=>()=>(e&&(t=e(e=0)),t);var ls=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),te=(e,t)=>{for(var n in t)cs(e,n,{get:t[n],enumerable:!0})};import*as O from"fs";import*as W from"path";import*as at from"os";import{execFileSync as ps}from"child_process";function ct(){return W.join(at.homedir(),".shiplight","version-check.json")}function fs(){return W.join(at.homedir(),".shiplight","npm-prefix.json")}function gs(e=fs()){try{let n=O.readFileSync(e,"utf-8"),r=JSON.parse(n);if(typeof r.prefix=="string"&&typeof r.fetchedAt=="number"&&Date.now()-r.fetchedAt<hs)return r.prefix}catch{}let t;try{t=ps("npm",["config","get","prefix"],{encoding:"utf-8",stdio:["ignore","pipe","ignore"]}).trim()}catch{return null}if(!t)return null;try{O.mkdirSync(W.dirname(e),{recursive:!0}),O.writeFileSync(e,JSON.stringify({prefix:t,fetchedAt:Date.now()}))}catch{}return t}function Vt(e={}){let t=e.scriptPath??process.argv[1],n=e.getPrefix??(()=>gs()),r=e.warn??(i=>console.warn(i)),s=e.error??(i=>console.error(i)),o=e.exit??(i=>process.exit(i));try{if(!t)return;let i=n();if(!i)return;let a,l;try{a=O.realpathSync(t),l=O.realpathSync(i)}catch{return}if(a.startsWith(l+W.sep)){s(`
4
+ var cn=Object.defineProperty;var _=(e,t)=>()=>(e&&(t=e(e=0)),t);var ln=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),te=(e,t)=>{for(var r in t)cn(e,r,{get:t[r],enumerable:!0})};import*as O from"fs";import*as W from"path";import*as at from"os";import{execFileSync as pn}from"child_process";function ct(){return W.join(at.homedir(),".shiplight","version-check.json")}function fn(){return W.join(at.homedir(),".shiplight","npm-prefix.json")}function gn(e=fn()){try{let r=O.readFileSync(e,"utf-8"),s=JSON.parse(r);if(typeof s.prefix=="string"&&typeof s.fetchedAt=="number"&&Date.now()-s.fetchedAt<hn)return s.prefix}catch{}let t;try{t=pn("npm",["config","get","prefix"],{encoding:"utf-8",stdio:["ignore","pipe","ignore"]}).trim()}catch{return null}if(!t)return null;try{O.mkdirSync(W.dirname(e),{recursive:!0}),O.writeFileSync(e,JSON.stringify({prefix:t,fetchedAt:Date.now()}))}catch{}return t}function Vt(e={}){let t=e.scriptPath??process.argv[1],r=e.getPrefix??(()=>gn()),s=e.warn??(i=>console.warn(i)),n=e.error??(i=>console.error(i)),o=e.exit??(i=>process.exit(i));try{if(!t)return;let i=r();if(!i)return;let a,l;try{a=O.realpathSync(t),l=O.realpathSync(i)}catch{return}if(a.startsWith(l+W.sep)){n(`
5
5
  shiplightai cannot be run from a global install.
6
6
  Global installs don't auto-update and cause version skew.
7
7
 
@@ -10,14 +10,14 @@ Install it as a project dependency instead:
10
10
  cd <your-project>
11
11
  npm i -D shiplightai
12
12
  npx shiplight <command>
13
- `),o(1);return}let c=W.join(l,"lib","node_modules","shiplightai"),u=W.join(l,"node_modules","shiplightai"),d=O.existsSync(c)?c:O.existsSync(u)?u:null;d&&r(`
13
+ `),o(1);return}let c=W.join(l,"lib","node_modules","shiplightai"),u=W.join(l,"node_modules","shiplightai"),d=O.existsSync(c)?c:O.existsSync(u)?u:null;d&&s(`
14
14
  \x1B[33m\u26A0 A global shiplightai install was detected at ${d}.
15
15
  Global installs don't auto-update and can shadow the project-local CLI on PATH.
16
16
  Please remove it: npm uninstall -g shiplightai\x1B[0m
17
- `)}catch{}}function ms(e=ct()){try{let t=O.readFileSync(e,"utf-8"),n=JSON.parse(t);if(typeof n.latest=="string"&&typeof n.fetchedAt=="number"&&Date.now()-n.fetchedAt<ds)return n}catch{}return null}function ys(e,t=ct()){try{O.mkdirSync(W.dirname(t),{recursive:!0}),O.writeFileSync(t,JSON.stringify(e))}catch{}}async function ws(){try{let e=await fetch(us,{headers:{Accept:"application/json"},signal:AbortSignal.timeout(3e3)});if(!e.ok)return null;let t=await e.json();return typeof t.version=="string"?t.version:null}catch{return null}}function bs(e,t){let n=d=>{let f=d.indexOf("-");return f===-1?[d,!1]:[d.slice(0,f),!0]},r=d=>d.split(".").map(f=>parseInt(f,10)||0),[s,o]=n(e),[i,a]=n(t),l=r(s),c=r(i),u=Math.max(l.length,c.length);for(let d=0;d<u;d++){let f=l[d]??0,g=c[d]??0;if(f<g)return!0;if(f>g)return!1}return!!(o&&!a)}async function Yt(e={}){let t=e.runningVersion??it,n=e.cwd??process.cwd(),r=e.cacheFile??ct(),s=e.fetchLatest??ws,o=e.env??process.env,i=e.warn??(c=>console.warn(c));if(o.CI||t==="dev"||!O.existsSync(W.join(n,"package-lock.json")))return;let a=null,l=ms(r);if(l)a=l.latest;else{try{a=await s()}catch{a=null}a&&ys({latest:a,fetchedAt:Date.now()},r)}a&&bs(t,a)&&i(`
17
+ `)}catch{}}function mn(e=ct()){try{let t=O.readFileSync(e,"utf-8"),r=JSON.parse(t);if(typeof r.latest=="string"&&typeof r.fetchedAt=="number"&&Date.now()-r.fetchedAt<dn)return r}catch{}return null}function yn(e,t=ct()){try{O.mkdirSync(W.dirname(t),{recursive:!0}),O.writeFileSync(t,JSON.stringify(e))}catch{}}async function wn(){try{let e=await fetch(un,{headers:{Accept:"application/json"},signal:AbortSignal.timeout(3e3)});if(!e.ok)return null;let t=await e.json();return typeof t.version=="string"?t.version:null}catch{return null}}function bn(e,t){let r=d=>{let f=d.indexOf("-");return f===-1?[d,!1]:[d.slice(0,f),!0]},s=d=>d.split(".").map(f=>parseInt(f,10)||0),[n,o]=r(e),[i,a]=r(t),l=s(n),c=s(i),u=Math.max(l.length,c.length);for(let d=0;d<u;d++){let f=l[d]??0,g=c[d]??0;if(f<g)return!0;if(f>g)return!1}return!!(o&&!a)}async function Yt(e={}){let t=e.runningVersion??it,r=e.cwd??process.cwd(),s=e.cacheFile??ct(),n=e.fetchLatest??wn,o=e.env??process.env,i=e.warn??(c=>console.warn(c));if(o.CI||t==="dev"||!O.existsSync(W.join(r,"package-lock.json")))return;let a=null,l=mn(s);if(l)a=l.latest;else{try{a=await n()}catch{a=null}a&&yn({latest:a,fetchedAt:Date.now()},s)}a&&bn(t,a)&&i(`
18
18
  \x1B[33m\u26A0 shiplightai ${a} is available (you have ${t}).
19
19
  Run: npm update shiplightai\x1B[0m
20
- `)}var it,_e,us,ds,hs,Le=_(()=>{"use strict";it="0.1.65",_e=it!=="dev"?it:void 0,us="https://registry.npmjs.org/shiplightai/latest",ds=3600*1e3,hs=10080*60*1e3});import*as ne from"fs";import*as ae from"path";function lt(e){let{projectPath:t}=e,n=e.projectName??ae.basename(ae.resolve(t));if(ne.existsSync(t)&&ne.readdirSync(t).length>0)throw new Error(`Cannot scaffold into non-empty directory: ${t}`);ne.mkdirSync(t,{recursive:!0});let r=(s,o)=>{let i=ae.join(t,s);ne.mkdirSync(ae.dirname(i),{recursive:!0}),ne.writeFileSync(i,o)};return r("package.json",vs.replace("{{name}}",n)),r("playwright.config.ts",Ss),r(".gitignore",_s),r(".env.example",xs),r(".mcp.json",Ts),r(".claude/CLAUDE.md",ks),r(".claude/auth.md",Ps),r(".claude/creating-tests.md",As),r(".claude/settings.local.json",Es),r(".claude/test-spec-template.md",$s),r(".claude/updating-tests.md",Ms),r("auth/example.login.ts",Is),r("environments/example.env.yaml",Os),r("tests/example.test.yaml",Ls),{projectPath:t,projectName:n,filesCreated:["package.json","playwright.config.ts",".gitignore",".env.example",".mcp.json",".claude/CLAUDE.md",".claude/auth.md",".claude/creating-tests.md",".claude/settings.local.json",".claude/test-spec-template.md",".claude/updating-tests.md","auth/example.login.ts","environments/example.env.yaml","tests/example.test.yaml"]}}var vs,Ss,_s,xs,Ts,ks,Ps,As,Es,$s,Ms,Is,Os,Ls,Jt=_(()=>{"use strict";vs=`{
20
+ `)}var it,_e,un,dn,hn,Le=_(()=>{"use strict";it="0.1.67",_e=it!=="dev"?it:void 0,un="https://registry.npmjs.org/shiplightai/latest",dn=3600*1e3,hn=10080*60*1e3});import*as re from"fs";import*as ae from"path";function lt(e){let{projectPath:t}=e,r=e.projectName??ae.basename(ae.resolve(t));if(re.existsSync(t)&&re.readdirSync(t).length>0)throw new Error(`Cannot scaffold into non-empty directory: ${t}`);re.mkdirSync(t,{recursive:!0});let s=(n,o)=>{let i=ae.join(t,n);re.mkdirSync(ae.dirname(i),{recursive:!0}),re.writeFileSync(i,o)};return s("package.json",vn.replace("{{name}}",r)),s("playwright.config.ts",Sn),s(".gitignore",_n),s(".env.example",xn),s(".mcp.json",Tn),s("auth/example.login.ts",Pn),s("environments/example.env.yaml",kn),s("tests/example.test.yaml",En),{projectPath:t,projectName:r,filesCreated:["package.json","playwright.config.ts",".gitignore",".env.example",".mcp.json","auth/example.login.ts","environments/example.env.yaml","tests/example.test.yaml"]}}var vn,Sn,_n,xn,Tn,Pn,kn,En,Jt=_(()=>{"use strict";vn=`{
21
21
  "name": "{{name}}",
22
22
  "type": "module",
23
23
  "scripts": {
@@ -29,7 +29,7 @@ Install it as a project dependency instead:
29
29
  "dotenv": "^16.4.7"
30
30
  }
31
31
  }
32
- `,Ss=`import { defineConfig, shiplightConfig } from 'shiplightai';
32
+ `,Sn=`import { defineConfig, shiplightConfig } from 'shiplightai';
33
33
 
34
34
  export default defineConfig({
35
35
  ...shiplightConfig(),
@@ -37,7 +37,7 @@ export default defineConfig({
37
37
  testMatch: ['**/*.test.ts', '**/*.yaml.spec.ts'],
38
38
  timeout: 120_000,
39
39
  expect: { timeout: 10_000 },
40
- retries: 0,
40
+ retries: 1,
41
41
  use: {
42
42
  headless: true,
43
43
  viewport: { width: 1280, height: 720 },
@@ -47,13 +47,13 @@ export default defineConfig({
47
47
  trace: 'on',
48
48
  },
49
49
  });
50
- `,_s=`node_modules/
50
+ `,_n=`node_modules/
51
51
  test-results/
52
52
  shiplight-report/
53
53
  .shiplight/
54
54
  .env
55
55
  *.yaml.spec.ts
56
- .auth`,xs=`# Shiplight API token (optional) \u2014 enables cloud sync tools
56
+ .auth`,xn=`# Shiplight API token (optional) \u2014 enables cloud sync tools
57
57
  # Get yours at https://app.shiplight.ai/settings/api-tokens
58
58
  # SHIPLIGHT_API_TOKEN=
59
59
 
@@ -77,7 +77,7 @@ shiplight-report/
77
77
 
78
78
  # Optional: override starting URL for all tests
79
79
  # PLAYWRIGHT_STARTING_URL=
80
- `,Ts=`{
80
+ `,Tn=`{
81
81
  "mcpServers": {
82
82
  "shiplight": {
83
83
  "command": "npx",
@@ -90,443 +90,11 @@ shiplight-report/
90
90
  }
91
91
  }
92
92
  }
93
- }`,ks=`# Shiplight Test Project
94
-
95
- This is a Shiplight E2E test project.
96
-
97
- ## Guides
98
-
99
- Read the relevant guide before starting any task:
100
-
101
- | Task | Guide |
102
- |------|-------|
103
- | Writing a new test | \`creating-tests.md\` |
104
- | Updating or fixing a test | \`updating-tests.md\` |
105
- | Auth fixtures and login | \`auth.md\` |
106
-
107
- ## Project Layout
108
-
109
- \`\`\`
110
- specs/tests/ one Markdown spec per executable test
111
- tests/ executable Shiplight YAML tests
112
- environments/ environment files (name \u2192 base URL)
113
- auth/ auth fixture TypeScript files, if any
114
- templates/ reusable YAML statement groups, if any
115
- functions/ TypeScript helper functions, if any
116
- files/ fixture files, if any
117
- test-results/ generated runtime artifacts (do not edit)
118
- shiplight-report/ generated reports (do not edit)
119
- .shiplight/ local Shiplight state (do not edit)
120
- \`\`\`
121
-
122
- ## Edit Contract
123
-
124
- Agents may edit:
125
-
126
- - \`specs/tests/**/*.md\`
127
- - \`tests/**/*.test.yaml\`
128
- - \`environments/**/*.env.yaml\`
129
- - \`auth/**/*.auth.ts\`
130
- - \`templates/**/*.tmpl.yaml\`
131
- - \`functions/**/*.func.ts\`
132
- - \`files/**\`
133
- - \`playwright.config.ts\` \u2014 only when changing project-level runtime behavior
134
- - \`package.json\` \u2014 only when changing commands or dependencies
135
-
136
- Agents must not edit:
137
-
138
- - \`**/*.yaml.spec.ts\`
139
- - \`test-results/**\`
140
- - \`shiplight-report/**\`
141
- - \`.shiplight/**\`
142
- - \`node_modules/**\`
143
- - \`.env\`
144
- - \`package-lock.json\` \u2014 unless a dependency change requires it
145
-
146
- ## Commands
147
-
148
- \`\`\`sh
149
- npm test # run all tests
150
- npm run test:headed # run all tests headed
151
- \`\`\`
152
-
153
- Use the narrowest relevant command when debugging a specific test.
154
-
155
- ## Ground Truth
156
-
157
- When sources disagree, this precedence applies:
158
-
159
- 1. Explicit user instruction
160
- 2. Test spec in \`specs/tests/\`
161
- 3. Existing test \`goal\`, step \`intent\`, and \`VERIFY\` assertions
162
- 4. Current app behavior
163
- 5. Agent docs in this project
164
- 6. Agent inference
165
-
166
- If current app behavior conflicts with the test spec or test goal, report the mismatch \u2014 do not silently rewrite the test.
167
- `,Ps=`# Auth
168
-
169
- This guide explains how to reason about authentication, how accounts are defined in environment files, and how to write auth login modules for tests that require login.
170
-
171
- ## Does a Test Need Auth?
172
-
173
- Before writing a test, determine whether it requires an authenticated session:
174
-
175
- - Does the starting page redirect anonymous visitors to login?
176
- - Do the user actions the test performs require an account?
177
-
178
- If unclear, infer from context or ask the user. Document the answer in the spec before writing any YAML.
179
-
180
- ## Accounts in Environment Files
181
-
182
- Accounts are defined inside the environment YAML file, not in separate files. Each account is a list entry with:
183
-
184
- - \`username\`: the actual account email or username (not sensitive, committed as-is)
185
- - \`password\`: the name of the env var holding the password
186
- - \`2fa_secret\`: (optional) the name of the env var holding the 2FA secret
187
-
188
- \`\`\`yaml
189
- name: prod
190
- url: https://prod.example.com
191
-
192
- accounts:
193
- - username: member@example.com
194
- password: PROD_MEMBER_PASSWORD
195
- - username: admin@example.com
196
- password: PROD_ADMIN_PASSWORD
197
- 2fa_secret: PROD_ADMIN_2FA_SECRET
198
- \`\`\`
199
-
200
- ### Adding a new account
201
-
202
- When a test needs an account that is not yet in the environment YAML:
203
-
204
- 1. Infer or clarify the account type needed (e.g., workspace member, admin). Do not ask the user for the password or 2FA secret.
205
- 2. Add the account entry to the environment YAML with the username and well-named env var references.
206
- 3. Tell the user which variables to add to \`.env\`.
207
-
208
- Example message to user:
209
-
210
- > Added an account to \`environments/prod.env.yaml\`. Please add the following to your \`.env\`:
211
- >
212
- > \`\`\`
213
- > PROD_MEMBER_PASSWORD=
214
- > \`\`\`
215
-
216
- ## Auth Login Modules
217
-
218
- An auth login module is a TypeScript file under \`auth/\` that exports a \`login(args)\` function. It performs the login flow, caches the resulting browser session state under \`.auth/\`, and returns the path to the state file. Session state is cached for 1 hour; subsequent calls within that window skip the login and return the cached path immediately.
219
-
220
- File naming: \`auth/<app-name>.login.ts\`
221
-
222
- Example: \`auth/staging.login.ts\`
223
-
224
- ### When to create a new login module
225
-
226
- **Always read \`auth/\` before creating anything.** If a login module already exists for the same environment, reuse it \u2014 pass different \`username\`/\`password\` args for different accounts.
227
-
228
- Create a new module only when no module exists for the target environment.
229
-
230
- ### What a login module does
231
-
232
- \`\`\`ts
233
- export async function login(args: Record<string, unknown> = {}): Promise<string> {
234
- const username = args.username as string;
235
- // args.password is the env var name; resolve it from process.env
236
- const rawPassword = args.password as string;
237
- const password = process.env[rawPassword] ?? rawPassword;
238
- // ...login flow...
239
- // returns path to cached .auth/<username>.json
240
- }
241
- \`\`\`
242
-
243
- Key points:
244
- - \`args.password\` is the **name** of the env var (e.g. \`STAGING_MEMBER_PASSWORD\`), resolved via \`process.env\` inside the module. Never hardcode passwords.
245
- - State files are written to \`.auth/\` (gitignored). The filename is derived from the username.
246
- - The module caches state for 1 hour. If the cached file is fresh, login is skipped.
247
-
248
- ### Referencing a login module in a test
249
-
250
- Use the \`use.account\` field in the YAML test:
251
-
252
- \`\`\`yaml
253
- # Spec: specs/tests/files-list.md
254
- goal: Workspace member can view files
255
- base_url: https://staging.example.com
256
- use:
257
- account:
258
- auth: auth/staging.login.ts
259
- args:
260
- username: member@example.com
261
- password: PROD_MEMBER_PASSWORD
262
-
263
- statements:
264
- - URL: /files
265
- ...
266
- \`\`\`
267
-
268
- \`args.password\` is the env var **name**, not the value. The login module resolves it from \`process.env\` at runtime.
269
-
270
- Different tests can use different accounts by passing different \`username\` and \`password\` args \u2014 all using the same login module.
271
-
272
- ## Secrets Policy
273
-
274
- Never commit real credentials to specs, tests, fixtures, environment files, or docs:
275
-
276
- - passwords
277
- - API keys
278
- - tokens
279
- - cookies
280
- - one-time codes
281
-
282
- Credentials belong in \`.env\`. Environment YAML files contain only env var names, not values. The \`.env\` file is gitignored and must not be edited unless the user explicitly asks.
283
- `,As=`# Creating Tests
284
-
285
- When asked to create a test, follow this workflow in order. Do not skip steps.
286
-
287
- ## 1. Determine the Environment
288
-
289
- Every test must target a specific named environment. Environments are defined in \`environments/*.env.yaml\`.
290
-
291
- ### Find or create the environment file
292
-
293
- Check \`environments/\` for an existing file that matches the target deployment (e.g., \`prod\`, \`staging\`, \`local\`). If one does not exist, create it:
294
-
295
- \`\`\`yaml
296
- name: staging
297
- url: https://staging.example.com
298
- \`\`\`
299
-
300
- The \`url\` field becomes the \`base_url\` in the YAML test.
301
-
302
- ### Clarify if ambiguous
303
-
304
- If the target environment is not obvious from context, ask the user before proceeding.
305
-
306
- Do not silently switch environments to make a test pass.
307
-
308
- ## 2. Determine Auth
309
-
310
- Decide whether the test requires authentication before writing any test steps.
311
-
312
- Ask yourself:
313
-
314
- - Does the starting page redirect anonymous visitors to login?
315
- - Do the actions in the test require an authenticated user?
316
-
317
- If unclear, infer from app behavior or ask the user.
318
-
319
- ### If auth is not required
320
-
321
- No account or auth fixture is needed. Note this in the spec when you write it:
322
-
323
- \`\`\`md
324
- - Auth: none, anonymous visitor
325
- \`\`\`
326
-
327
- ### If auth is required
328
-
329
- 1. List the available accounts from the environment YAML file (\`accounts\` key) and ask the user which account to use.
330
- 2. If a new account is needed, ask the user a direct question: "What is the username for this account?" Add the account to the environment YAML with the username stored directly and env var references only for the password and any other security-sensitive fields. Tell the user which variables to add to \`.env\`.
331
- 3. Check \`auth/\` for an existing fixture for that account. If none exists, create one.
332
-
333
- See \`auth.md\` for account definition format, env var naming convention, and how to write auth fixtures.
334
-
335
- ## 3. Write the Spec
336
-
337
- Now that environment and auth are resolved, create or locate a spec under \`specs/tests/\`. The spec is the source of truth for what the test should prove.
338
-
339
- If no spec exists for the requested behavior, create one using the template at \`.claude/test-spec-template.md\`.
340
-
341
- The spec must include:
342
-
343
- - **Goal**: the user-visible behavior the test protects
344
- - **Environment**: the environment name determined in step 1
345
- - **Auth**: the account reference determined in step 2
346
- - **Expected result**: what success looks like
347
-
348
- \`\`\`md
349
- ## Starting Point
350
-
351
- - Environment: prod
352
- - Auth: logged in as workspace member (prod/workspace-member)
353
- \`\`\`
354
-
355
- Use the environment name (e.g., \`prod\`), not the raw URL.
356
-
357
- Mark the spec \`Draft\` if any fields are still unresolved. Mark it \`Ready\` when the YAML test can be implemented without further questions. Do not proceed to step 4 until the spec is \`Ready\`.
358
-
359
- ## 4. Walk the App and Implement the YAML Test
360
-
361
- Once the spec is \`Ready\`:
362
-
363
- 1. Open a browser session and walk through the exact flow described in the spec. Capture accurate locators for every interactive element.
364
- 2. Create the YAML test under \`tests/\` using those locators.
365
-
366
- \`\`\`yaml
367
- # Spec: specs/tests/feature-name.md
368
- goal: User can complete the target workflow
369
- base_url: https://staging.example.com
370
-
371
- statements:
372
- - URL: /path
373
- ...
374
- \`\`\`
375
-
376
- - Use the \`url\` from the matching \`environments/*.env.yaml\` file as \`base_url\`.
377
- - If the test requires auth, see \`auth.md\`.
378
-
379
- Do not write statements from memory \u2014 always walk the app first.
380
-
381
- ## 5. Validate and Run
382
-
383
- 1. Validate the YAML using the \`validate_yaml_test\` tool. If it is rejected for too many draft statements, return to step 4 and capture more locators.
384
- 2. Run the test with: \`npx playwright test tests/<name>.test.yaml\`
385
- 3. If the test fails because the implementation violates the spec, fix the test.
386
- 4. If the test fails because the app behavior differs from the spec, report the mismatch \u2014 do not rewrite the spec to match broken behavior.
387
-
388
- ## 6. Update the Spec
389
-
390
- Do not close the task until this step is complete.
391
-
392
- - Update the spec status to \`Implemented\`.
393
- - Add the test file path to the \`Implementation\` section of the spec.
394
- `,Es=`{
395
- "enableAllProjectMcpServers": true,
396
- "enabledMcpjsonServers": [
397
- "shiplight"
398
- ]
399
- }
400
- `,$s=`# Test Spec: <Name>
401
-
402
- ## Status
403
-
404
- Draft
405
-
406
- Allowed values: Draft, Ready, Implemented.
407
-
408
- ## Goal
409
-
410
- Describe the user-visible behavior this test should protect.
411
-
412
- ## User Role
413
-
414
- Describe the user, account type, permission level, or auth state required.
415
-
416
- ## Starting Point
417
-
418
- - Environment: <environment name from environments/*.env.yaml, not a URL>
419
- - Auth: <none, anonymous visitor OR logged in as role/account fixture>
420
-
421
- ## Preconditions
422
-
423
- - List required setup, existing account state, feature flags, or app state before the test starts.
424
- - Keep concrete records, IDs, names, and generated values in Test Data.
425
-
426
- ## Test Data
427
-
428
- - List concrete records, IDs, names, routes, input values, fixture files, generated data, and uniqueness requirements the executable test depends on.
429
- - If data must already exist, include enough detail to locate it deterministically.
430
- - If data is created during the test, describe how it should be named.
431
-
432
- ## Steps
433
-
434
- 1. Start from the specified page or state.
435
- 2. Perform the main user action.
436
- 3. Continue through the workflow until the expected result is observable.
437
-
438
- ## Expected Result
439
-
440
- - State the final success condition in user-visible terms.
441
-
442
- ## Assertions
443
-
444
- - List the concrete user-visible checks the executable test should make.
445
-
446
- ## Cleanup
447
-
448
- - List any state the test must remove, reset, or restore after execution.
449
- - If cleanup is not needed, write: None.
450
-
451
- ## Out Of Scope
452
-
453
- - List related behavior this test should not cover.
454
-
455
- ## Notes
456
-
457
- - Add non-obvious assumptions, known product constraints, or open questions.
458
-
459
- ## Implementation
460
-
461
- - Test file:
462
- `,Ms=`# Updating Tests
463
-
464
- Use this workflow when changing, debugging, or repairing existing tests.
465
-
466
- ## Before Editing
467
-
468
- 1. Read the matching spec under \`specs/tests/\`, if one exists.
469
- 2. Read the YAML test \`goal\`, step \`intent\`s, and \`VERIFY\` assertions.
470
- 3. Scan other tests in \`tests/\` that cover the same page or feature \u2014 borrow working locators and patterns from them rather than guessing.
471
- 4. Identify the reason for the update:
472
- - **Locator drift**: the UI changed but the intended behavior did not.
473
- - **Product change**: the intended behavior changed intentionally.
474
- - **Test bug**: the implementation was wrong to begin with.
475
- - **Coverage gap**: new assertions need to be added.
476
-
477
- ## When the Intended Behavior Has Not Changed
478
-
479
- Update only the implementation \u2014 locators, waits, or setup \u2014 to restore the behavior described by the spec.
480
-
481
- Do not:
482
-
483
- - Delete assertions to make a test pass.
484
- - Skip steps the spec requires.
485
- - Reduce coverage to avoid a failure.
486
-
487
- If current app behavior conflicts with the spec or test goal, **report the mismatch** instead of rewriting the test around the current UI.
488
-
489
- ## When the Intended Behavior Has Changed
490
-
491
- If the product has changed intentionally:
492
-
493
- 1. Update the spec first.
494
- 2. Then update the YAML test to match the updated spec.
495
-
496
- Mark the spec \`Needs Update\` before starting. Mark it \`Implemented\` after completing and verifying.
497
-
498
- ## Files Not to Edit
499
-
500
- Do not edit generated or local-state files:
501
-
502
- - \`**/*.yaml.spec.ts\`
503
- - \`test-results/**\`
504
- - \`shiplight-report/**\`
505
- - \`.shiplight/**\`
506
- - \`node_modules/**\`
507
-
508
- These files may be useful for debugging but must not become the source of test intent.
509
-
510
- ## Test Data
511
-
512
- Prefer unique data per run when a test creates records. Do not depend on shared mutable state.
513
-
514
- If a test requires specific accounts, fixtures, or pre-existing records, document those dependencies in the spec.
515
-
516
- ## Reporting After Updates
517
-
518
- After completing update work, report:
519
-
520
- - Files created or changed.
521
- - Behavior covered.
522
- - Command run and pass/fail result.
523
- - Any product/spec mismatch or unresolved blocker.
524
- `,Is=`// This is an example auth fixture. Copy and adapt it for your actual login flow.
93
+ }`,Pn=`// This is an example auth fixture. Copy and adapt it for your actual login flow.
525
94
  import path from 'path';
526
95
  import fs from 'fs/promises';
527
96
  import { chromium } from '@playwright/test';
528
97
 
529
- const EXPIRY_MS = 60 * 60 * 1000; // 1 hour
530
98
  const AUTH_DIR = path.resolve(process.cwd(), '.auth');
531
99
 
532
100
  export async function login(args: Record<string, unknown> = {}): Promise<string> {
@@ -542,34 +110,36 @@ export async function login(args: Record<string, unknown> = {}): Promise<string>
542
110
  throw new Error('login() requires args.username and args.password');
543
111
  }
544
112
 
545
- try {
546
- const { mtimeMs } = await fs.stat(sessionPath);
547
- if (Date.now() - mtimeMs < EXPIRY_MS) return sessionPath;
548
- } catch { /* not cached yet */ }
549
-
550
- await fs.mkdir(path.dirname(sessionPath), { recursive: true });
551
-
113
+ const sessionExists = await fs.stat(sessionPath).then(() => true).catch(() => false);
552
114
  const browser = await chromium.launch();
553
- const page = await browser.newPage();
115
+ const context = await browser.newContext(sessionExists ? { storageState: sessionPath } : {});
116
+ const page = await context.newPage();
554
117
 
555
118
  await page.goto(baseUrl);
119
+
120
+ if (!page.url().includes('/login')) {
121
+ await context.storageState({ path: sessionPath });
122
+ await browser.close();
123
+ return sessionPath;
124
+ }
125
+
556
126
  await page.getByRole('textbox', { name: 'Email' }).fill(username);
557
127
  await page.getByRole('textbox', { name: 'Password' }).fill(password);
558
128
  await page.getByRole('button', { name: 'Log in' }).click();
559
- await page.waitForURL('**');
129
+ await page.waitForURL(\`\${baseUrl}/**\`);
560
130
 
561
131
  await page.context().storageState({ path: sessionPath });
562
132
  await browser.close();
563
133
 
564
134
  return sessionPath;
565
135
  }
566
- `,Os=`# This is an example of environment. Don't use it.
136
+ `,kn=`# This is an example of environment. Don't use it.
567
137
  name: example
568
138
  url: https://staging.example.com
569
139
  accounts:
570
140
  - username: test-user-1@example.com
571
141
  password: EXAMPLE_TEST_USER_1_PASSWORD
572
- 2fa_secret: EXAMPLE_TEST_USER_1_2FA_SECRET`,Ls=`goal: Verify the Shiplight homepage links to the quick-start docs
142
+ 2fa_secret: EXAMPLE_TEST_USER_1_2FA_SECRET`,En=`goal: Verify the Shiplight homepage links to the quick-start docs
573
143
  base_url: https://www.shiplight.ai
574
144
  statements:
575
145
  - URL: /
@@ -583,7 +153,7 @@ statements:
583
153
 
584
154
  - VERIFY: The browser lands on the docs site
585
155
  js: "await expect(page).toHaveURL(/docs\\\\.shiplight\\\\.ai/)"
586
- `});var qt=_(()=>{"use strict";Jt()});var Xt={};te(Xt,{runCreate:()=>Rs});import*as Ce from"path";import*as Ne from"fs";function Re(){console.log(`
156
+ `});var Xt=_(()=>{"use strict";Jt()});var qt={};te(qt,{runCreate:()=>An});import*as Ce from"path";import*as Ne from"fs";function Re(){console.log(`
587
157
  Usage: shiplight create <path> [options]
588
158
 
589
159
  Scaffold a new Shiplight test project at <path>. Creates package.json,
@@ -596,25 +166,25 @@ Options:
596
166
  Examples:
597
167
  shiplight create ./my-tests
598
168
  shiplight create ./my-tests --name acme-e2e
599
- `)}async function Rs(e){let t,n;for(let o=0;o<e.length;o++){let i=e[o];if(i==="--help"||i==="-h"){Re();return}else i==="--name"?(n=e[++o],(!n||!n.trim())&&(console.error("Error: --name requires a non-empty value"),process.exit(1)),/^[a-z0-9][a-z0-9._-]*$/.test(n)||(console.error(`Error: --name "${n}" is not a valid package name.
169
+ `)}async function An(e){let t,r;for(let o=0;o<e.length;o++){let i=e[o];if(i==="--help"||i==="-h"){Re();return}else i==="--name"?(r=e[++o],(!r||!r.trim())&&(console.error("Error: --name requires a non-empty value"),process.exit(1)),/^[a-z0-9][a-z0-9._-]*$/.test(r)||(console.error(`Error: --name "${r}" is not a valid package name.
600
170
  npm package names must be lowercase.
601
171
  Use only lowercase letters, digits, hyphens, underscores, and dots;
602
172
  must start with a lowercase letter or digit.`),process.exit(1))):i.startsWith("--")?(console.error(`Error: Unknown option: ${i}`),Re(),process.exit(1)):t?(console.error(`Error: Unexpected argument: ${i}`),Re(),process.exit(1)):t=i}t||(console.error(`Error: missing required <path> argument
603
- `),Re(),process.exit(1));let r=Ce.resolve(t);Ne.existsSync(r)&&Ne.readdirSync(r).length>0&&(console.error(`Error: target directory is not empty: ${r}
604
- Remove it or choose a different path.`),process.exit(1));let s=lt({projectPath:r,projectName:n});console.log(`
605
- Created Shiplight test project at ${s.projectPath}
606
- `),console.log(` Name: ${s.projectName}`),console.log(" Files:");for(let o of s.filesCreated)console.log(` - ${o}`);console.log(`
173
+ `),Re(),process.exit(1));let s=Ce.resolve(t);Ne.existsSync(s)&&Ne.readdirSync(s).length>0&&(console.error(`Error: target directory is not empty: ${s}
174
+ Remove it or choose a different path.`),process.exit(1));let n=lt({projectPath:s,projectName:r});console.log(`
175
+ Created Shiplight test project at ${n.projectPath}
176
+ `),console.log(` Name: ${n.projectName}`),console.log(" Files:");for(let o of n.filesCreated)console.log(` - ${o}`);console.log(`
607
177
  Next steps:
608
178
 
609
- cd ${Ce.relative(process.cwd(),s.projectPath)||"."}
179
+ cd ${Ce.relative(process.cwd(),n.projectPath)||"."}
610
180
  cp .env.example .env # then edit .env and set an AI provider API key
611
181
  npm install
612
182
  npx playwright install chromium
613
183
  npx shiplight test
614
- `)}var Zt=_(()=>{"use strict";qt()});import*as G from"fs";import*as re from"path";function Qt(e){let t=re.resolve(e);for(;;){if(G.existsSync(re.join(t,"package.json"))||G.existsSync(re.join(t,".shiplight")))return t;let n=re.dirname(t);if(n===t)break;t=n}return null}function en(e){return re.join(e,Cs)}function tn(e,t){return re.join(en(e),`${Ns}${t}.json`)}function nn(e,t){let n=Qt(t);if(!n)return;let r=en(n);G.mkdirSync(r,{recursive:!0});let s={port:e,yamlFile:t,pid:process.pid,startedAt:new Date().toISOString()};G.writeFileSync(tn(n,e),JSON.stringify(s),"utf-8")}function rn(e,t){let n=Qt(t);if(n)try{G.unlinkSync(tn(n,e))}catch{}}var Cs,Ns,sn=_(()=>{"use strict";Cs=".shiplight/run",Ns="debug-"});import*as an from"node:net";function on(e,t){return new Promise(n=>{let r=an.createServer();r.once("error",s=>{n(s.code!=="EADDRINUSE")}),r.once("listening",()=>{r.close(()=>n(!0))}),r.listen(e,t)})}async function Ds(e){return await on(e,"127.0.0.1")?on(e,"::1"):!1}async function cn(e,t){for(let n=e;n<e+t;n++)if(await Ds(n))return n;return null}var ln=_(()=>{"use strict"});var un={};te(un,{findPlaywrightConfig:()=>xe,makeIdempotentFileCleaner:()=>pn,spawnPlaywrightProcess:()=>pt});import*as se from"fs";import*as q from"path";import{spawn as js}from"child_process";import{parse as Fs}from"yaml";function pn(e){let t=!1;return()=>{if(!t){t=!0;try{se.unlinkSync(e)}catch{}}}}function xe(e){let t=["playwright.config.ts","playwright.config.js","playwright.config.mjs"],n=q.resolve(e);for(;;){for(let s of t){let o=q.join(n,s);if(se.existsSync(o))return o}let r=q.dirname(n);if(r===n)break;n=r}return null}function Us(e,t,n,r){let s={...n},o=s.launchOptions?.args??[];return s.launchOptions={...s.launchOptions??{},args:[...o,"--remote-debugging-port=0"]},`// @generated by shiplightai \u2014 temporary debug test
184
+ `)}var Zt=_(()=>{"use strict";Xt()});import*as G from"fs";import*as se from"path";function Qt(e){let t=se.resolve(e);for(;;){if(G.existsSync(se.join(t,"package.json"))||G.existsSync(se.join(t,".shiplight")))return t;let r=se.dirname(t);if(r===t)break;t=r}return null}function er(e){return se.join(e,$n)}function tr(e,t){return se.join(er(e),`${Mn}${t}.json`)}function rr(e,t){let r=Qt(t);if(!r)return;let s=er(r);G.mkdirSync(s,{recursive:!0});let n={port:e,yamlFile:t,pid:process.pid,startedAt:new Date().toISOString()};G.writeFileSync(tr(r,e),JSON.stringify(n),"utf-8")}function sr(e,t){let r=Qt(t);if(r)try{G.unlinkSync(tr(r,e))}catch{}}var $n,Mn,nr=_(()=>{"use strict";$n=".shiplight/run",Mn="debug-"});import*as ir from"node:net";function or(e,t){return new Promise(r=>{let s=ir.createServer();s.once("error",n=>{r(n.code!=="EADDRINUSE")}),s.once("listening",()=>{s.close(()=>r(!0))}),s.listen(e,t)})}async function In(e){return await or(e,"127.0.0.1")?or(e,"::1"):!1}async function ar(e,t){for(let r=e;r<e+t;r++)if(await In(r))return r;return null}var cr=_(()=>{"use strict"});var pr={};te(pr,{findPlaywrightConfig:()=>xe,makeIdempotentFileCleaner:()=>lr,spawnPlaywrightProcess:()=>pt});import*as ne from"fs";import*as X from"path";import{spawn as On}from"child_process";import{parse as Ln}from"yaml";function lr(e){let t=!1;return()=>{if(!t){t=!0;try{ne.unlinkSync(e)}catch{}}}}function xe(e){let t=["playwright.config.ts","playwright.config.js","playwright.config.mjs"],r=X.resolve(e);for(;;){for(let n of t){let o=X.join(r,n);if(ne.existsSync(o))return o}let s=X.dirname(r);if(s===r)break;r=s}return null}function Rn(e,t,r,s){let n={...r},o=n.launchOptions?.args??[];return n.launchOptions={...n.launchOptions??{},args:[...o,"--remote-debugging-port=0"]},`// @generated by shiplightai \u2014 temporary debug test
615
185
  import { test } from 'shiplightai/fixture';
616
186
  ${`
617
- test.use(${JSON.stringify(s)});
187
+ test.use(${JSON.stringify(n)});
618
188
  `}
619
189
  test('__shiplight_debug__', async ({ page, agent }) => {
620
190
  // Disable timeout \u2014 debugger runs until user stops it
@@ -627,7 +197,7 @@ test('__shiplight_debug__', async ({ page, agent }) => {
627
197
  port: ${t},
628
198
  page,
629
199
  agent,
630
- projectRoot: ${JSON.stringify(r??null)},
200
+ projectRoot: ${JSON.stringify(s??null)},
631
201
  });
632
202
 
633
203
  console.error('[debugger] Playwright sandbox ready on internal port ${t}');
@@ -635,30 +205,30 @@ test('__shiplight_debug__', async ({ page, agent }) => {
635
205
  // Keep alive until the server is closed externally (Ctrl+C kills the process)
636
206
  await new Promise(() => {});
637
207
  });
638
- `}async function Bs(e){let{createServer:t}=await import("net");for(let n=e;n<e+20;n++)if(await new Promise(s=>{let o=t();o.once("error",()=>s(!1)),o.once("listening",()=>{o.close(()=>s(!0))}),o.listen(n,"127.0.0.1")}))return n;throw new Error(`No available port found in range ${e}-${e+19}`)}async function pt(e){let{yamlFilePath:t,configPath:n,tempSuffix:r="",headed:s}=e,o=q.dirname(n),i=await Bs(16174),a;if(!se.existsSync(t))throw new Error(`Please select a test file before starting the debug session. File not found: ${t}`);try{let y=Fs(se.readFileSync(t,"utf-8"));y?.use&&typeof y.use=="object"&&!Array.isArray(y.use)&&(a=y.use),y?.base_url&&!a?.baseURL&&(a={...a,baseURL:y.base_url}),y?.settings?.auto_dismiss_modal!==void 0&&(a={...a,autoDismissModal:!!y.settings.auto_dismiss_modal}),y?.settings?.browser_timezone!=null&&(a={...a,timezoneId:String(y.settings.browser_timezone)}),y?.settings?.browser_language!=null&&(a={...a,locale:String(y.settings.browser_language)}),y?.settings?.extra_http_headers!=null&&typeof y.settings.extra_http_headers=="object"&&(a={...a,extraHTTPHeaders:y.settings.extra_http_headers})}catch(y){console.error("[debugger] Could not parse YAML for `use` block:",y)}let l=q.dirname(q.resolve(t)),c=r?`-${r}`:"",u=q.join(l,`.__shiplight_debug__${c}.yaml.spec.ts`),d=Us(t,i,a,o);se.writeFileSync(u,d);let f=pn(u),g=["playwright","test",u,...s?["--headed"]:[]],h=js("npx",g,{stdio:["ignore","pipe","pipe"],shell:!0,cwd:o,env:{...process.env,PWDEBUG:"console",SHIPLIGHT_REGISTRY_URL:""}});h.stdout?.on("data",y=>{process.stderr.write(y)}),h.stderr?.on("data",y=>{process.stderr.write(y)});let p=()=>{h.killed||h.kill("SIGTERM")};process.on("SIGTERM",p),process.on("SIGINT",p),process.on("exit",p),h.on("close",y=>{process.removeListener("SIGTERM",p),process.removeListener("SIGINT",p),process.removeListener("exit",p),f(),y!==0&&y!==null&&console.error(`[debugger] Playwright process exited with code ${y}`)}),console.error("[debugger] Waiting for Playwright sandbox to start...");let v=["127.0.0.1","::1"];async function w(y){try{let k=y.includes(":")?`[${y}]`:y,P=await fetch(`http://${k}:${i}/api/test-flow`);if(P.ok){try{await P.text()}catch{}return!0}}catch{}return!1}let m=null;for(let y=0;y<180;y++){if(h.exitCode!==null)throw f(),new Error(`Playwright process exited with code ${h.exitCode} before sandbox was ready`);for(let k of v)if(await w(k)){m=k;break}if(m){console.error(`[debugger] Playwright sandbox ready on ${m}:${i}`);break}if(y===179)throw p(),f(),new Error("Timed out waiting for Playwright sandbox to start (180s)");await new Promise(k=>setTimeout(k,1e3))}if(!m)throw p(),f(),new Error("Sandbox poll finished without a reachable host");return{port:i,host:m,pid:h.pid??0,cleanup:async()=>{p(),f()}}}var ut=_(()=>{"use strict"});var dn=_(()=>{"use strict"});var hn=_(()=>{"use strict"});var fn=_(()=>{"use strict"});import{v4 as pa}from"uuid";var gn=_(()=>{"use strict"});import{z as b}from"zod";var mn,dt,yn,fe,wn,bn,vn,j,Sn,De,ht,ft=_(()=>{"use strict";mn=b.enum(["JS_CODE","AI_MODE"]),dt=b.object({type:mn,expression:b.string()}),yn=b.enum(["DRAFT","STEP","ACTION","IF_ELSE","WHILE_LOOP"]),fe=b.object({uid:b.string(),type:yn,comment:b.string().optional()}),wn=b.object({action_data:b.object({action_name:b.string(),kwargs:b.record(b.any()).optional(),args:b.array(b.any()).optional()}),action_description:b.string().optional(),url:b.string().optional(),xpath:b.string().nullable().optional(),locator:b.string().nullable().optional(),css_selector:b.string().nullable().optional(),unique_selector:b.string().nullable().optional(),element_index:b.number().nullable().optional(),frame_path:b.array(b.any()).optional(),artifacts:b.record(b.any()).optional(),feedback:b.string().optional(),original_browser_use_action:b.any().optional()}).passthrough(),bn=fe.extend({type:b.literal("DRAFT"),description:b.string()}),vn=fe.extend({type:b.literal("ACTION"),description:b.string(),action_entity:wn.optional(),locator:b.string().optional(),use_pure_vision:b.boolean().optional()}),j=b.lazy(()=>b.union([bn,vn,fe.extend({type:b.literal("STEP"),description:b.string().optional().default(""),statements:b.array(j),reference_id:b.number().optional(),template_path:b.string().optional(),template_params:b.record(b.string()).optional()}),fe.extend({type:b.literal("IF_ELSE"),description:b.string().optional(),condition:dt,then:b.array(j),else:b.array(j).optional()}),fe.extend({type:b.literal("WHILE_LOOP"),description:b.string().optional(),condition:dt,body:b.array(j),timeout_ms:b.number().optional()})])),Sn=b.object({name:b.string(),statements:b.array(j),teardown:b.array(j).optional(),skip:b.union([b.boolean(),b.string()]).optional(),timeout:b.number().optional(),fail:b.union([b.boolean(),b.string()]).optional(),only:b.boolean().optional(),slow:b.boolean().optional()}),De=b.object({tests:b.array(Sn).min(1),beforeAll:b.array(j).optional(),afterAll:b.array(j).optional(),beforeEach:b.array(j).optional(),afterEach:b.array(j).optional()}),ht=b.object({comment:b.string().optional(),version:b.string().optional(),goal:b.string().optional(),url:b.string().optional(),baseURL:b.string().optional(),final_feedback:b.string().optional(),completed:b.boolean().optional(),success:b.boolean().optional(),statements:b.array(j).optional(),teardown:b.array(j).optional(),last_modified_at:b.string().optional(),testGroup:De.optional()}).refine(e=>e.testGroup!==void 0?e.goal===void 0&&(e.statements===void 0||e.statements.length===0):e.goal!==void 0,{message:"TestFlow must have either goal/statements (single test) or testGroup (suite), not both"})});import{stringify as Hs,parse as kn,parseAllDocuments as ma,parseDocument as Ws,Document as Gs,isMap as me,isSeq as z}from"yaml";import{v4 as K}from"uuid";function je(e,t){let n={...t?.test_case_id!==void 0?{test_case_id:t.test_case_id}:{},...t?.name?{name:t.name}:{},goal:e.goal??"",url:e.url,base_url:e.baseURL,...t?.timeout!==void 0?{timeout:t.timeout}:{},...t?.settings&&Object.keys(t.settings).length>0?{settings:t.settings}:{},statements:(e.statements??[]).map(B)};return e.final_feedback&&(n.final_feedback=e.final_feedback),e.teardown&&e.teardown.length>0&&(n.teardown=e.teardown.map(B)),n}function ke(e,t){if(e.testGroup)return An(e,t);let n=je(e,t),r=new Gs(n);return e.comment&&(r.commentBefore=e.comment),_n(r,e.statements??[]),e.teardown&&_n(r,e.teardown,"teardown"),r.toString(Pn)}function _n(e,t,n="statements"){let r=e.contents;if(!r||!me(r))return;let s=r.get(n,!0);z(s)&&Te(s,t)}function Te(e,t){for(let n=0;n<Math.min(e.items.length,t.length);n++){let r=t[n],s=e.items[n];if(n>0&&(s.spaceBefore=!0),r.comment&&(n===0?e.commentBefore=r.comment:s.commentBefore=r.comment),me(s)){let o=s;if(r.type==="STEP"){let i=o.get("statements",!0);z(i)&&Te(i,r.statements)}else if(r.type==="IF_ELSE"){let i=o.get("THEN",!0);z(i)&&Te(i,r.then);let a=o.get("ELSE",!0);z(a)&&r.else&&Te(a,r.else)}else if(r.type==="WHILE_LOOP"){let i=o.get("DO",!0);z(i)&&Te(i,r.body)}}}}function An(e,t){let n=e.testGroup;if(!n)throw new Error("suiteToYaml requires a TestFlow with testGroup");let r={};t?.test_case_id!==void 0&&(r.test_case_id=t.test_case_id),t?.name&&(r.name=t.name),t?.tags&&t.tags.length>0&&(r.tags=t.tags),t?.use&&Object.keys(t.use).length>0&&(r.use=t.use),t?.settings&&Object.keys(t.settings).length>0&&(r.settings=t.settings);let s={};return e.baseURL&&(s.base_url=e.baseURL),n.beforeAll&&n.beforeAll.length>0&&(s.beforeAll=n.beforeAll.map(B)),n.beforeEach&&n.beforeEach.length>0&&(s.beforeEach=n.beforeEach.map(B)),n.afterEach&&n.afterEach.length>0&&(s.afterEach=n.afterEach.map(B)),n.afterAll&&n.afterAll.length>0&&(s.afterAll=n.afterAll.map(B)),s.tests=n.tests.map(o=>{let i={name:o.name};return o.skip!==void 0&&(i.skip=o.skip),o.timeout!==void 0&&(i.timeout=o.timeout),o.fail!==void 0&&(i.fail=o.fail),o.only!==void 0&&(i.only=o.only),o.slow!==void 0&&(i.slow=o.slow),i.statements=o.statements.map(B),o.teardown&&o.teardown.length>0&&(i.teardown=o.teardown.map(B)),i}),r.suite=s,Hs(r,Pn)}function B(e){switch(e.type){case"DRAFT":return Ks(e);case"ACTION":return zs(e);case"STEP":return Vs(e);case"IF_ELSE":return Ys(e);case"WHILE_LOOP":return Js(e)}}function Ks(e){return{intent:e.description}}function zs(e){let t=e.action_entity?.action_data?.action_name??e.action_entity?.action?.action_name,n=e.action_entity?.action_data?.kwargs??e.action_entity?.action?.kwargs;if(t==="verify"){let a=n?.statement;if(typeof a=="string"&&!e.action_entity?.locator&&!e.action_entity?.xpath){let l=n?.code;return typeof l=="string"?{VERIFY:a,js:l}:{VERIFY:a}}}if(t==="go_to_url"){let a=n?.url;if(typeof a=="string"&&!e.action_entity?.locator&&!e.action_entity?.xpath){let l={URL:a};return n?.new_tab===!0&&(l.new_tab=!0),typeof n?.timeout_seconds=="number"&&(l.timeout_seconds=n.timeout_seconds),l}}if(t==="js_action"){let a=n?.code;if(typeof a=="string"&&e.description)return{intent:e.description,js:a}}if(t==="ai_wait_until"){let a=n?.condition;if(typeof a=="string"){let l={WAIT_UNTIL:a};return typeof n?.timeout_seconds=="number"&&n.timeout_seconds!==60&&(l.timeout_seconds=n.timeout_seconds),l}}if(t==="wait"){let a=n?.seconds,c={WAIT:e.description||`Wait ${a}s`};return typeof a=="number"&&(c.seconds=a),c}if(t==="js_code"){let a=n?.code;if(typeof a=="string"&&!e.action_entity?.locator&&!e.action_entity?.xpath)return{CODE:a}}if(!e.action_entity)return{intent:e.description};let r=e.action_entity.action_data??e.action_entity.action;if(!r)return{intent:e.description};let s={intent:e.description,action:r.action_name},o=e.locator??e.action_entity.locator;o&&(s.locator=o);let i=e.action_entity.xpath;if(i&&(s.xpath=i),e.use_pure_vision&&(s.use_pure_vision=!0),r.kwargs&&Object.keys(r.kwargs).length>0)for(let[a,l]of Object.entries(r.kwargs))s[a]=l;return r.args&&r.args.length>0&&(s.args=r.args),s}function Vs(e){if(e.template_path){let n={template:e.template_path};return e.template_params&&Object.keys(e.template_params).length>0&&(n.params=e.template_params),n}let t={STEP:e.description,statements:e.statements.map(B)};return e.reference_id!==void 0&&(t.reference_id=e.reference_id),t}function Ys(e){let t={IF:En(e.condition),THEN:e.then.map(B)};return e.else&&e.else.length>0&&(t.ELSE=e.else.map(B)),t}function Js(e){let t={WHILE:En(e.condition),DO:e.body.map(B)};return e.timeout_ms!==void 0&&(t.timeout_ms=e.timeout_ms),t}function En(e){return e.type==="JS_CODE"?`js:${e.expression}`:e.expression}function Pe(e){try{let t=kn(e);if(!t||typeof t!="object")return{};let n={};return typeof t.test_case_id=="number"&&Number.isFinite(t.test_case_id)&&(n.test_case_id=t.test_case_id),typeof t.template_id=="number"&&Number.isFinite(t.template_id)&&(n.template_id=t.template_id),typeof t.name=="string"&&t.name.trim()&&(n.name=t.name.trim()),typeof t.timeout=="number"&&Number.isFinite(t.timeout)&&(n.timeout=t.timeout),t.settings&&typeof t.settings=="object"&&!Array.isArray(t.settings)&&(n.settings=t.settings),n}catch{return{}}}function gt(e){if(e==null||typeof e!="object")return e;if(Array.isArray(e))return e.map(gt);let t=e,n=Object.keys(t);if(n.length===1){let s=n[0];if(s.startsWith("{ ")&&s.endsWith(" }")&&t[s]===null)return`{{${s.slice(2,-2)}}}`}let r={};for(let[s,o]of Object.entries(t))r[s]=gt(o);return r}function C(e){if(e.length>xn)throw new Error(`YAML input too large (${e.length} bytes, max ${xn})`);let t=gt(kn(e));if(!t||typeof t!="object")throw new Error("Invalid YAML: expected an object at root level");if(t.suite)return qs(t);let n={version:"1.3.0",goal:t.goal,url:t.url,baseURL:t.base_url,statements:H(t.statements??[])};t.final_feedback&&(n.final_feedback=t.final_feedback),t.teardown&&Array.isArray(t.teardown)&&(n.teardown=H(t.teardown));let r=ht.safeParse(n);if(!r.success)throw new Error(`Invalid TestFlow after YAML conversion: ${JSON.stringify(r.error.errors)}`);let s=r.data;return Fe(e,s),s}function qs(e){let t=e.suite;if(!t||typeof t!="object")throw new Error("Invalid suite: expected an object");let n=t.tests;if(!Array.isArray(n)||n.length===0)throw new Error('Suite must have a non-empty "tests" array');let s={tests:n.map(a=>{if(!a.name)throw new Error('Each test in a suite must have a "name" field');if(!Array.isArray(a.statements)||a.statements.length===0)throw new Error(`Suite test "${a.name}" must have a non-empty "statements" array`);let l={name:a.name,statements:H(a.statements)};return Array.isArray(a.teardown)&&a.teardown.length>0&&(l.teardown=H(a.teardown)),a.skip!==void 0&&(l.skip=a.skip),typeof a.timeout=="number"&&(l.timeout=a.timeout),a.fail!==void 0&&(l.fail=a.fail),a.only===!0&&(l.only=!0),a.slow===!0&&(l.slow=!0),l})};Array.isArray(t.beforeAll)&&t.beforeAll.length>0&&(s.beforeAll=H(t.beforeAll)),Array.isArray(t.afterAll)&&t.afterAll.length>0&&(s.afterAll=H(t.afterAll)),Array.isArray(t.beforeEach)&&t.beforeEach.length>0&&(s.beforeEach=H(t.beforeEach)),Array.isArray(t.afterEach)&&t.afterEach.length>0&&(s.afterEach=H(t.afterEach));let o=De.safeParse(s);if(!o.success)throw new Error(`Invalid TestGroup: ${JSON.stringify(o.error.errors)}`);return{version:"1.3.0",baseURL:t.base_url||void 0,testGroup:o.data}}function H(e){if(!Array.isArray(e))throw new Error("Expected an array of statements");return e.map(Xs)}function Xs(e){if(typeof e=="string")throw new Error(`Plain string statements are not supported. Use an object with a "desc" key instead. Example: { "desc": "${e}" }`);if(typeof e!="object"||e===null)throw new Error(`Invalid statement: expected object, got ${typeof e}`);let t=e;if("IF"in t)return Zs(t);if("WHILE"in t)return Qs(t);if("STEP"in t)return eo(t);if("VERIFY"in t){let n=t.VERIFY,r={statement:typeof n=="string"?n:String(n)};return typeof t.js=="string"&&(r.code=t.js),{uid:K(),type:"ACTION",description:String(n),action_entity:{action_description:String(n),action_data:{action_name:"verify",kwargs:r}}}}if("URL"in t){let n=t.URL,r=t.new_tab===!0?!0:void 0,s=typeof t.timeout_seconds=="number"?t.timeout_seconds:void 0,o={url:typeof n=="string"?n:String(n)};return r&&(o.new_tab=!0),s!==void 0&&(o.timeout_seconds=s),{uid:K(),type:"ACTION",description:`Navigate to ${n}`,action_entity:{action_description:`Navigate to ${n}`,action_data:{action_name:"go_to_url",kwargs:o}}}}if("WAIT_UNTIL"in t){let n=t.WAIT_UNTIL,r=typeof t.timeout_seconds=="number"?t.timeout_seconds:60;return{uid:K(),type:"ACTION",description:`Wait until: ${n}`,action_entity:{action_description:`Wait until: ${n}`,action_data:{action_name:"ai_wait_until",kwargs:{condition:typeof n=="string"?n:String(n),timeout_seconds:r}}}}}if("WAIT"in t){let n=t.WAIT,r=typeof t.seconds=="number"?t.seconds:3;return{uid:K(),type:"ACTION",description:typeof n=="string"?n:`Wait ${r}s`,action_entity:{action_description:typeof n=="string"?n:`Wait ${r}s`,action_data:{action_name:"wait",kwargs:{seconds:r}}}}}if("CODE"in t){let n=t.CODE;if(n==null)throw new Error('CODE statement has no code. Use "CODE: |" followed by indented code on the next line.');return{uid:K(),type:"ACTION",description:"Code block",action_entity:{action_description:"Code block",action_data:{action_name:"js_code",kwargs:{code:typeof n=="string"?n:String(n)}}}}}if("js"in t&&("intent"in t||"desc"in t)&&!("VERIFY"in t)&&t.action!=="verify"){let n=t.js,r=typeof t.intent=="string"?t.intent:typeof t.desc=="string"?t.desc:"";return{uid:K(),type:"ACTION",description:r,action_entity:{action_description:r,action_data:{action_name:"js_action",kwargs:{code:typeof n=="string"?n:String(n)}}}}}if("call"in t&&typeof t.call=="string"){let{call:n,...r}=t;return Tn({...r,action:"function",functionName:n})}if("action"in t)return Tn(t);if("intent"in t&&typeof t.intent=="string"||"desc"in t&&typeof t.desc=="string")return{uid:K(),type:"DRAFT",description:typeof t.intent=="string"?t.intent:t.desc};throw new Error(`Cannot infer statement type from object: ${JSON.stringify(t)}`)}function $n(e){if(typeof e!="string")throw new Error(`Condition must be a string, got ${typeof e}`);return e.startsWith("js:")?{type:"JS_CODE",expression:e.slice(3)}:{type:"AI_MODE",expression:e}}function Zs(e){let t=$n(e.IF),n=e.THEN;if(!Array.isArray(n))throw new Error("IF_ELSE requires a THEN array");let r={uid:K(),type:"IF_ELSE",condition:t,then:H(n)};return"ELSE"in e&&Array.isArray(e.ELSE)&&(r.else=H(e.ELSE)),r}function Qs(e){let t=$n(e.WHILE),n=e.DO;if(!Array.isArray(n))throw new Error("WHILE_LOOP requires a DO array");let r={uid:K(),type:"WHILE_LOOP",condition:t,body:H(n)};return typeof e.timeout_ms=="number"&&(r.timeout_ms=e.timeout_ms),r}function eo(e){let t=typeof e.STEP=="string"?e.STEP:"";if(!Array.isArray(e.statements))throw new Error("STEP requires a statements array");let n={uid:K(),type:"STEP",description:t,statements:H(e.statements)};if(typeof e.reference_id=="number"&&(n.reference_id=e.reference_id),typeof e.template_path=="string"&&(n.template_path=e.template_path),e.template_params&&typeof e.template_params=="object"&&!Array.isArray(e.template_params)){let r=e.template_params,s={};for(let[o,i]of Object.entries(r))s[o]=String(i);n.template_params=s}return n}function Tn(e){let t=typeof e.action=="string"?e.action:String(e.action),n=typeof e.intent=="string"?e.intent:typeof e.desc=="string"?e.desc:"",r=typeof e.locator=="string"?e.locator:void 0,s=typeof e.xpath=="string"?e.xpath:void 0,o=typeof e.use_pure_vision=="boolean"?e.use_pure_vision:void 0,i={};for(let[c,u]of Object.entries(e))to.has(c)||(i[c]=u);t==="verify"&&typeof i.js=="string"&&(i.code=i.js,delete i.js);let a={action_description:n,action_data:{action_name:t,kwargs:Object.keys(i).length>0?i:{}}};r&&(a.locator=r),s&&(a.xpath=s);let l={uid:K(),type:"ACTION",description:n,action_entity:a};return o&&(l.use_pure_vision=!0),l}function Fe(e,t){let n;try{n=Ws(e)}catch{return}let r=n.contents;if(!r||!me(r))return;if(n.commentBefore)t.comment=n.commentBefore;else{let l=r.items?.[0];l?.key&&l.key.commentBefore&&(t.comment=l.key.commentBefore)}let s=r,o=s.get("statements",!0);z(o)&&t.statements&&ge(o,t.statements);let i=s.get("teardown",!0);z(i)&&t.teardown&&ge(i,t.teardown)}function ge(e,t){e.commentBefore&&t.length>0&&(t[0].comment=e.commentBefore);for(let n=0;n<Math.min(e.items.length,t.length);n++){let r=e.items[n];r.commentBefore&&!(n===0&&e.commentBefore)&&(t[n].comment=r.commentBefore);let s=t[n];if(s.type==="STEP"&&me(r)){let o=r.get("statements",!0);z(o)&&ge(o,s.statements)}else if(s.type==="IF_ELSE"&&me(r)){let o=r.get("THEN",!0);z(o)&&ge(o,s.then);let i=r.get("ELSE",!0);z(i)&&s.else&&ge(i,s.else)}else if(s.type==="WHILE_LOOP"&&me(r)){let o=r.get("DO",!0);z(o)&&ge(o,s.body)}}}var Pn,xn,to,Ue=_(()=>{"use strict";ft();Pn={lineWidth:120,defaultKeyType:"PLAIN",defaultStringType:"PLAIN"};xn=1024*1024;to=new Set(["action","intent","desc","locator","xpath","use_pure_vision"])});import{parse as Sa,stringify as _a}from"yaml";var Mn=_(()=>{"use strict";Ue()});var mt,Be,He=_(()=>{"use strict";mt=e=>{let t=[];switch(e.type){case"STEP":e.statements&&t.push({key:"statements",statements:e.statements});break;case"IF_ELSE":e.then&&t.push({key:"then",statements:e.then}),e.else&&t.push({key:"else",statements:e.else});break;case"WHILE_LOOP":e.body&&t.push({key:"body",statements:e.body});break}return t},Be=e=>{let t=[],n=r=>{for(let s of r){t.push(s);let o=mt(s);for(let i of o)n(i.statements)}};return n(e),t}});function Cn(e){let t=0,n=0;for(let r of e)if(r.type==="DRAFT")n++;else if(r.type==="ACTION"){let s=r.action_entity?.action_data?.action_name??"";Rn.has(s)||t++}return{action:t,draft:n}}function ro(e){try{return new Function(`return async function() { ${e} }`),null}catch(t){return t.message}}function Ln(e){try{return new Function(`return async function() { return (${e}) }`),null}catch(t){return t.message}}function so(e){let t=e.split(/\r?\n/).map(o=>o.trim()).filter(o=>o.length>0&&!o.startsWith("//")),n=t.length,r=t.join(`
639
- `).match(/\bawait\b/g),s=r?r.length:0;return n<=In&&s<=On?null:`${n} non-blank line(s), ${s} await(s) \u2014 limits are ${In} lines and ${On} awaits. The js: field is a cache for one natural-language intent, not a place for multi-step logic. Break this into multiple statements \u2014 one VERIFY or intent+js per UI interaction or assertion \u2014 so each step is visible in the debugger and self-healable.`}function yt(e,t){let n=t?.coverageThreshold??no,r=[],s=[],o;try{o=C(e)}catch(f){return{valid:!1,errors:[`Invalid YAML: ${f.message}`],warnings:[],stats:{total:0,action:0,draft:0,coverage:0}}}o.goal||r.push('Missing required field: "goal"'),o.statements?.length||r.push('Missing required field: "statements"');let i=[...Be(o.statements??[]),...o.teardown?Be(o.teardown):[]],{action:a,draft:l}=Cn(i),c="Hint: in YAML double-quoted strings, backslashes are escape characters \u2014 use \\\\/ instead of \\/ for regex, or use single quotes.";for(let f of i){let g=f;if(g.reference_id!==void 0){let h=g.description||f.uid;r.push(`Unresolved cloud template reference on statement "${h}" (reference_id: ${g.reference_id}). Local YAML tests cannot use reference_id \u2014 inline the template statements or use the local "template:" key instead.`)}if(f.type==="ACTION"){let h=f,p=h.action_entity?.action_data?.action_name??"";if(p==="js_code"||p==="js_action"||p==="verify"||p==="ai_assert"){let v=h.action_entity?.action_data?.kwargs?.code;if(typeof v=="string"){let w=ro(v);if(w){let m=h.description||p;r.push(`Invalid JS in "${m}": ${w}. ${c}`)}else if(p==="verify"||p==="js_action"){let m=so(v);if(m){let y=h.description||p;r.push(`JS cache for "${y}" is too complex: ${m}`)}}}}}if(f.type==="IF_ELSE"){let h=f;if(h.condition.type==="JS_CODE"){let p=Ln(h.condition.expression);p&&r.push(`Invalid JS in IF condition "${h.condition.expression}": ${p}. ${c}`)}}if(f.type==="WHILE_LOOP"){let h=f;if(h.condition.type==="JS_CODE"){let p=Ln(h.condition.expression);p&&r.push(`Invalid JS in WHILE condition "${h.condition.expression}": ${p}. ${c}`)}}}let u=a+l,d=u>0?Math.round(a/u*100):0;return u>0&&d/100<n&&s.push(`Low action coverage: ${a}/${u} statements (${d}%) are enriched with action/js. ${l} draft statement(s) still need enrichment. Use MCP tools (act, get_locators) to convert drafts to actions.`),{valid:r.length===0,errors:r,warnings:s,stats:{total:u,action:a,draft:l,coverage:d}}}var no,In,On,Rn,Nn=_(()=>{"use strict";Ue();He();no=.5,In=5,On=3,Rn=new Set(["verify","ai_assert","done","go_to_url","ai_wait_until","wait","js_code"])});var We=_(()=>{"use strict"});var Dn=_(()=>{"use strict";We()});var jn,io,Fn,Un,Ae,ao,co,Bn=_(()=>{"use strict";jn=112,io=1080-jn,Fn={"Blackberry PlayBook":{name:"Blackberry PlayBook",userAgent:"Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/26.0 Safari/536.2+",screen:{width:600,height:1024},viewport:{width:600,height:1024},deviceScaleFactor:1,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"BlackBerry Z30":{name:"BlackBerry Z30",userAgent:"Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/26.0 Mobile Safari/537.10+",screen:{width:360,height:640},viewport:{width:360,height:640},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"Galaxy Note 3":{name:"Galaxy Note 3",userAgent:"Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/26.0 Mobile Safari/534.30",screen:{width:360,height:640},viewport:{width:360,height:640},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"Galaxy Note II":{name:"Galaxy Note II",userAgent:"Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/26.0 Mobile Safari/534.30",screen:{width:360,height:640},viewport:{width:360,height:640},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"Galaxy S III":{name:"Galaxy S III",userAgent:"Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/26.0 Mobile Safari/534.30",screen:{width:360,height:640},viewport:{width:360,height:640},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"Galaxy S5":{name:"Galaxy S5",userAgent:"Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:360,height:640},viewport:{width:360,height:640},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Galaxy S8":{name:"Galaxy S8",userAgent:"Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:360,height:740},viewport:{width:360,height:740},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Galaxy S9+":{name:"Galaxy S9+",userAgent:"Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:320,height:658},viewport:{width:320,height:658},deviceScaleFactor:4.5,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Galaxy S24":{name:"Galaxy S24",userAgent:"Mozilla/5.0 (Linux; Android 14; SM-S921U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:360,height:780},viewport:{width:360,height:780},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Galaxy A55":{name:"Galaxy A55",userAgent:"Mozilla/5.0 (Linux; Android 14; SM-A556B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:480,height:1040},viewport:{width:480,height:1040},deviceScaleFactor:2.25,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Galaxy Tab S4":{name:"Galaxy Tab S4",userAgent:"Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36",screen:{width:712,height:1138},viewport:{width:712,height:1138},deviceScaleFactor:2.25,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Galaxy Tab S9":{name:"Galaxy Tab S9",userAgent:"Mozilla/5.0 (Linux; Android 14; SM-X710) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36",screen:{width:640,height:1024},viewport:{width:640,height:1024},deviceScaleFactor:2.5,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"iPad (gen 5)":{name:"iPad (gen 5)",userAgent:"Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:768,height:1024},viewport:{width:768,height:1024},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPad (gen 6)":{name:"iPad (gen 6)",userAgent:"Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:768,height:1024},viewport:{width:768,height:1024},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPad (gen 7)":{name:"iPad (gen 7)",userAgent:"Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:810,height:1080},viewport:{width:810,height:1080},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPad (gen 11)":{name:"iPad (gen 11)",userAgent:"Mozilla/5.0 (iPad; CPU OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/19E241 Safari/604.1",screen:{width:656,height:944},viewport:{width:656,height:944},deviceScaleFactor:2.5,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPad Mini":{name:"iPad Mini",userAgent:"Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:768,height:1024},viewport:{width:768,height:1024},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPad Pro 11":{name:"iPad Pro 11",userAgent:"Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:834,height:1194},viewport:{width:834,height:1194},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 6":{name:"iPhone 6",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/26.0 Mobile/15A372 Safari/604.1",screen:{width:375,height:667},viewport:{width:375,height:667},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 6 Plus":{name:"iPhone 6 Plus",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/26.0 Mobile/15A372 Safari/604.1",screen:{width:414,height:736},viewport:{width:414,height:736},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 7":{name:"iPhone 7",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/26.0 Mobile/15A372 Safari/604.1",screen:{width:375,height:667},viewport:{width:375,height:667},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 7 Plus":{name:"iPhone 7 Plus",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/26.0 Mobile/15A372 Safari/604.1",screen:{width:414,height:736},viewport:{width:414,height:736},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 8":{name:"iPhone 8",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/26.0 Mobile/15A372 Safari/604.1",screen:{width:375,height:667},viewport:{width:375,height:667},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 8 Plus":{name:"iPhone 8 Plus",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/26.0 Mobile/15A372 Safari/604.1",screen:{width:414,height:736},viewport:{width:414,height:736},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone SE":{name:"iPhone SE",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/26.0 Mobile/14E304 Safari/602.1",screen:{width:320,height:568},viewport:{width:320,height:568},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone SE (3rd gen)":{name:"iPhone SE (3rd gen)",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/26.0 Mobile/19E241 Safari/602.1",screen:{width:375,height:667},viewport:{width:375,height:667},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone X":{name:"iPhone X",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/26.0 Mobile/15A372 Safari/604.1",screen:{width:375,height:812},viewport:{width:375,height:812},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone XR":{name:"iPhone XR",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:414,height:896},viewport:{width:414,height:896},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 11":{name:"iPhone 11",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:414,height:896},viewport:{width:414,height:715},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 11 Pro":{name:"iPhone 11 Pro",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:375,height:812},viewport:{width:375,height:635},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 11 Pro Max":{name:"iPhone 11 Pro Max",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:414,height:896},viewport:{width:414,height:715},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 12":{name:"iPhone 12",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:390,height:844},viewport:{width:390,height:664},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 12 Pro":{name:"iPhone 12 Pro",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:390,height:844},viewport:{width:390,height:664},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 12 Pro Max":{name:"iPhone 12 Pro Max",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:428,height:926},viewport:{width:428,height:746},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 12 Mini":{name:"iPhone 12 Mini",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:375,height:812},viewport:{width:375,height:629},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 13":{name:"iPhone 13",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:390,height:844},viewport:{width:390,height:664},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 13 Pro":{name:"iPhone 13 Pro",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:390,height:844},viewport:{width:390,height:664},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 13 Pro Max":{name:"iPhone 13 Pro Max",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:428,height:926},viewport:{width:428,height:746},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 13 Mini":{name:"iPhone 13 Mini",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:375,height:812},viewport:{width:375,height:629},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 14":{name:"iPhone 14",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:390,height:844},viewport:{width:390,height:664},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 14 Plus":{name:"iPhone 14 Plus",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:428,height:926},viewport:{width:428,height:746},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 14 Pro":{name:"iPhone 14 Pro",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:393,height:852},viewport:{width:393,height:660},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 14 Pro Max":{name:"iPhone 14 Pro Max",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:430,height:932},viewport:{width:430,height:740},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 15":{name:"iPhone 15",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:393,height:852},viewport:{width:393,height:659},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 15 Plus":{name:"iPhone 15 Plus",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:430,height:932},viewport:{width:430,height:739},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 15 Pro":{name:"iPhone 15 Pro",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:393,height:852},viewport:{width:393,height:659},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 15 Pro Max":{name:"iPhone 15 Pro Max",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:430,height:932},viewport:{width:430,height:739},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"Kindle Fire HDX":{name:"Kindle Fire HDX",userAgent:"Mozilla/5.0 (Linux; U; en-us; KFAPWI Build/JDQ39) AppleWebKit/535.19 (KHTML, like Gecko) Silk/3.13 Safari/535.19 Silk-Accelerated=true",screen:{width:800,height:1280},viewport:{width:800,height:1280},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"LG Optimus L70":{name:"LG Optimus L70",userAgent:"Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:384,height:640},viewport:{width:384,height:640},deviceScaleFactor:1.25,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Microsoft Lumia 550":{name:"Microsoft Lumia 550",userAgent:"Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36 Edge/14.14263",screen:{width:360,height:640},viewport:{width:360,height:640},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Microsoft Lumia 950":{name:"Microsoft Lumia 950",userAgent:"Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36 Edge/14.14263",screen:{width:360,height:640},viewport:{width:360,height:640},deviceScaleFactor:4,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Nexus 10":{name:"Nexus 10",userAgent:"Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36",screen:{width:800,height:1280},viewport:{width:800,height:1280},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Nexus 4":{name:"Nexus 4",userAgent:"Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:384,height:640},viewport:{width:384,height:640},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Nexus 5":{name:"Nexus 5",userAgent:"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:360,height:640},viewport:{width:360,height:640},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Nexus 5X":{name:"Nexus 5X",userAgent:"Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:412,height:732},viewport:{width:412,height:732},deviceScaleFactor:2.625,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Nexus 6":{name:"Nexus 6",userAgent:"Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:412,height:732},viewport:{width:412,height:732},deviceScaleFactor:3.5,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Nexus 6P":{name:"Nexus 6P",userAgent:"Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:412,height:732},viewport:{width:412,height:732},deviceScaleFactor:3.5,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Nexus 7":{name:"Nexus 7",userAgent:"Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36",screen:{width:600,height:960},viewport:{width:600,height:960},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Nokia Lumia 520":{name:"Nokia Lumia 520",userAgent:"Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 520)",screen:{width:320,height:533},viewport:{width:320,height:533},deviceScaleFactor:1.5,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Nokia N9":{name:"Nokia N9",userAgent:"Mozilla/5.0 (MeeGo; NokiaN9) AppleWebKit/534.13 (KHTML, like Gecko) NokiaBrowser/8.5.0 Mobile Safari/534.13",screen:{width:480,height:854},viewport:{width:480,height:854},deviceScaleFactor:1,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"Pixel 2":{name:"Pixel 2",userAgent:"Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:411,height:731},viewport:{width:411,height:731},deviceScaleFactor:2.625,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Pixel 2 XL":{name:"Pixel 2 XL",userAgent:"Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:411,height:823},viewport:{width:411,height:823},deviceScaleFactor:3.5,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Pixel 3":{name:"Pixel 3",userAgent:"Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:393,height:786},viewport:{width:393,height:786},deviceScaleFactor:2.75,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Pixel 4":{name:"Pixel 4",userAgent:"Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:353,height:745},viewport:{width:353,height:745},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Pixel 4a (5G)":{name:"Pixel 4a (5G)",userAgent:"Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:412,height:892},viewport:{width:412,height:765},deviceScaleFactor:2.63,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Pixel 5":{name:"Pixel 5",userAgent:"Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:393,height:851},viewport:{width:393,height:727},deviceScaleFactor:2.75,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Pixel 7":{name:"Pixel 7",userAgent:"Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:412,height:915},viewport:{width:412,height:839},deviceScaleFactor:2.625,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Moto G4":{name:"Moto G4",userAgent:"Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:360,height:640},viewport:{width:360,height:640},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Desktop Chrome HiDPI":{name:"Desktop Chrome HiDPI",userAgent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36",screen:{width:1792,height:1120},viewport:{width:1280,height:720},deviceScaleFactor:2,isMobile:!1,hasTouch:!1,defaultBrowserType:"chromium"},"Desktop Edge HiDPI":{name:"Desktop Edge HiDPI",userAgent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36 Edg/141.0.7390.16",screen:{width:1792,height:1120},viewport:{width:1280,height:720},deviceScaleFactor:2,isMobile:!1,hasTouch:!1,defaultBrowserType:"chromium"},"Desktop Firefox HiDPI":{name:"Desktop Firefox HiDPI",userAgent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:142.0.1) Gecko/20100101 Firefox/142.0.1",screen:{width:1792,height:1120},viewport:{width:1280,height:720},deviceScaleFactor:2,isMobile:!1,hasTouch:!1,defaultBrowserType:"firefox"},"Desktop Safari":{name:"Desktop Safari",userAgent:"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Safari/605.1.15",screen:{width:1792,height:1120},viewport:{width:1280,height:720},deviceScaleFactor:2,isMobile:!1,hasTouch:!1,defaultBrowserType:"webkit"},"Desktop Chrome":{name:"Desktop Chrome",displayName:"Playwright Chromium",userAgent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36",screen:{width:1920,height:1080},viewport:{width:1920,height:1080},deviceScaleFactor:1,isMobile:!1,hasTouch:!1,defaultBrowserType:"chromium"},"Desktop Chrome Medium Resolution":{name:"Desktop Chrome Medium Resolution",displayName:"Playwright Chromium",userAgent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36",screen:{width:1280,height:720},viewport:{width:1280,height:720},deviceScaleFactor:1,isMobile:!1,hasTouch:!1,defaultBrowserType:"chromium"},"Desktop Chrome (Branded)":{name:"Desktop Chrome (Branded)",displayName:"Google Chrome",userAgent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36",screen:{width:1920,height:1080},viewport:{width:1920,height:1080},deviceScaleFactor:1,isMobile:!1,hasTouch:!1,defaultBrowserType:"chromium",channel:"chrome"},"Desktop Chrome Medium Resolution (Branded)":{name:"Desktop Chrome Medium Resolution (Branded)",displayName:"Google Chrome",userAgent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36",screen:{width:1280,height:720},viewport:{width:1280,height:720},deviceScaleFactor:1,isMobile:!1,hasTouch:!1,defaultBrowserType:"chromium",channel:"chrome"},"Desktop Edge":{name:"Desktop Edge",userAgent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36 Edg/141.0.7390.16",screen:{width:1920,height:1080},viewport:{width:1280,height:720},deviceScaleFactor:1,isMobile:!1,hasTouch:!1,defaultBrowserType:"chromium"},"Desktop Edge (Branded)":{name:"Desktop Edge (Branded)",displayName:"Microsoft Edge",userAgent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36 Edg/141.0.7390.16",screen:{width:1920,height:1080},viewport:{width:1920,height:1080},deviceScaleFactor:1,isMobile:!1,hasTouch:!1,defaultBrowserType:"chromium",channel:"msedge"},"Desktop Edge Medium Resolution (Branded)":{name:"Desktop Edge Medium Resolution (Branded)",displayName:"Microsoft Edge",userAgent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36 Edg/141.0.7390.16",screen:{width:1280,height:720},viewport:{width:1280,height:720},deviceScaleFactor:1,isMobile:!1,hasTouch:!1,defaultBrowserType:"chromium",channel:"msedge"},"Desktop Firefox":{name:"Desktop Firefox",userAgent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:142.0.1) Gecko/20100101 Firefox/142.0.1",screen:{width:1920,height:1080},viewport:{width:1280,height:720},deviceScaleFactor:1,isMobile:!1,hasTouch:!1,defaultBrowserType:"firefox"}},Un={desktop:["Desktop Chrome","Desktop Chrome Medium Resolution","Desktop Chrome (Branded)","Desktop Chrome Medium Resolution (Branded)","Desktop Edge (Branded)","Desktop Edge Medium Resolution (Branded)","Desktop Safari"],mobile:["iPhone 15 Pro Max","iPhone 15 Pro","iPhone 15 Plus","iPhone 15","iPhone 14 Pro Max","iPhone 14 Pro","iPhone 14 Plus","iPhone 14","iPhone 13 Pro Max","iPhone 13 Pro","iPhone 13","iPhone 13 Mini","iPhone 12 Pro Max","iPhone 12 Pro","iPhone 12","iPhone 12 Mini","iPhone 11 Pro Max","iPhone 11 Pro","iPhone 11","iPhone XR","iPhone X","iPhone SE (3rd gen)","iPhone SE","iPhone 8 Plus","iPhone 8","iPhone 7 Plus","iPhone 7","iPhone 6 Plus","iPhone 6","Galaxy S24","Galaxy A55","Galaxy S9+","Galaxy S8","Galaxy S5","Galaxy Note 3","Galaxy Note II","Galaxy S III","Pixel 7","Pixel 5","Pixel 4a (5G)","Pixel 4","Pixel 3","Pixel 2 XL","Pixel 2","Nexus 6P","Nexus 6","Nexus 5X","Nexus 5","Nexus 4","Moto G4","LG Optimus L70","Microsoft Lumia 950","Microsoft Lumia 550","Nokia Lumia 520","Nokia N9","BlackBerry Z30"]},Ae=(e,t=!1)=>{let n=["chromium"];return t&&n.push("webkit"),Un[e].map(r=>Fn[r]).filter(r=>r.defaultBrowserType&&n.includes(r.defaultBrowserType))},ao={desktop:{label:"Desktop",type:"desktop",devices:Ae("desktop")},mobile:{label:"Mobile Web",type:"mobile",devices:Ae("mobile")}},co={desktop:{label:"Desktop",type:"desktop",devices:Ae("desktop",!0)},mobile:{label:"Mobile Web",type:"mobile",devices:Ae("mobile",!0)}}});var Hn=_(()=>{"use strict"});function wt(){return{version:"1.0",entries:{}}}var Wn=_(()=>{"use strict";He()});var V,bt,Gn=_(()=>{"use strict";V=(e=>(e.DRAFT="DRAFT",e.STEP="STEP",e.ACTION="ACTION",e.IF_ELSE="IF_ELSE",e.WHILE_LOOP="WHILE_LOOP",e))(V||{}),bt=18e4});var Kn=_(()=>{"use strict"});var zn=_(()=>{"use strict"});var Vn=_(()=>{"use strict"});var Yn=_(()=>{"use strict";We()});var ce=_(()=>{"use strict";dn();hn();fn();gn();Mn();Nn();Ue();ft();Dn();Bn();Hn();Wn();He();Gn();Kn();zn();Vn();Yn();We()});import{stringify as po}from"yaml";import{createHash as $o}from"crypto";import{parse as Mo,stringify as sr}from"yaml";import{readFileSync as Io,existsSync as Oo}from"fs";import{resolve as St,dirname as Lo}from"path";import{parse as Zn,stringify as Ro}from"yaml";import{readFileSync as Bo,writeFileSync as Ho,mkdirSync as Wo}from"fs";import{dirname as Go}from"path";function oe(e){return e.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(/\n/g,"\\n").replace(/\r/g,"\\r").replace(/\t/g,"\\t")}function I(e){return e.replace(/\r\n/g," ").replace(/\n/g," ").replace(/\r/g," ").trim()}function uo(e){let t=e.frame_path;return!t||t.length===0?"page":`page.frameLocator('${t[0]}')`}function ho(e){let t=e.xpath;return typeof t=="string"&&t.trim()?!t.startsWith("xpath=")&&!t.startsWith("/")&&!t.startsWith("//")?`xpath=//${t}`:t.startsWith("xpath=")?t:`xpath=${t}`:null}function Ke(e){let t=uo(e),n=e.locator;if(typeof n=="string"&&n.trim())return n=n.trim(),n.endsWith("first()")?`${t}.${n}`:`${t}.${n}.first()`;let r=ho(e);if(r){let s=JSON.stringify(r);return`${t}.locator(${s}).first()`}return null}function Jn(e){let t=e.action_data?.action_name;return!t||(t==="verify"||t==="ai_assert"||t==="assert")&&e.action_data?.kwargs?.code?!1:fo.includes(t)}function mo(e){let t=e.action_data?.action_name;return!t||(t==="verify"||t==="ai_assert"||t==="assert")&&e.action_data?.kwargs?.code?!1:!go.includes(t)}function S(e,t){R.set(e,t)}function yo(e){return R.get(e)}function ue(e,t,n=[]){let r=[...n];return t.locator?r.push(`locator: ${JSON.stringify(t.locator)}`):t.xpath&&r.push(`xpath: ${JSON.stringify(t.xpath)}`),t.frame_path&&t.frame_path.length>0&&r.push(`frame_path: ${JSON.stringify(t.frame_path)}`),r.length===0?[`await agent.execAction("${e}", page, {});`]:[`await agent.execAction("${e}", page, {`,...r.map(s=>` ${s},`),"});"]}function qn(e){let t=e.functionName;if(!t)return null;let n=Array.isArray(e.args)?e.args.map(String):[];if(n.length===0)return`await ${t}()`;let r=["page","testContext","request","agent"],s=["undefined","null","true","false"],o=n.map(i=>r.includes(i)||s.includes(i)||/^-?\d+(\.\d+)?$/.test(i)?i:i.startsWith("$")?`agent.agentServices.readVariable('${i.substring(1)}')`:`"${i}"`);return`await ${t}(${o.join(", ")})`}function X(e,t,n,r="main"){let s=[];for(let o=0;o<e.length;o++){let i=e[o],a=`${r}.${o}`,l=wo(i,t,a,n);l.length>0&&(s.push(...l),o<e.length-1&&s.push(""))}return s}function wo(e,t,n,r){let s=" ".repeat(t);switch(e.type){case"DRAFT":return bo(e,t,n,r);case"ACTION":return vo(e,t,n,r);case"STEP":return So(e,t,n,r);case"IF_ELSE":return _o(e,t,n,r);case"WHILE_LOOP":return xo(e,t,n,r);default:return[`${s}// Unknown statement type: ${e.type}`]}}function bo(e,t,n,r){let s=" ".repeat(t),o=e.description?.trim()||"";if(!o)return[`${s}// ${n}: Skipping - no description`];if(r.noAgent)return[`${s}// ${n}: ${I(o)}`,`${s}// DRAFT: ${I(o)} (requires agent - skipped in hook)`];let i=JSON.stringify(o);return[`${s}// ${n}: ${I(o)}`,`${s}// \u26A0 DRAFT: AI-resolved at runtime (~5-10s). Add a locator to make this <1s.`,`${s}page = agent.agentServices.validatePage(page);`,`${s}await agent.run(page, ${i}, '${n}');`]}function vo(e,t,n,r){let s=" ".repeat(t),o=e.description,i=e.uid,l=r.actionEntityStore?.entries[e.uid]?.action_entity??e.action_entity;if(!l){if(!o)return[`${s}// ${n}: Skipping - no description`];if(r.noAgent)return[`${s}// ${n}: ${I(o)}`,`${s}// DRAFT: ${I(o)} (requires agent - skipped in hook)`];let y=JSON.stringify(o),k=!!e.use_pure_vision;return[`${s}// ${n}: ${I(o)}`,`${s}// \u26A0 DRAFT: AI-resolved at runtime (~5-10s). Add a locator to make this <1s.`,`${s}page = agent.agentServices.validatePage(page);`,`${s}await agent.execute(page, ${y}, '${n}', ${k});`]}let c=e.locator?{...l,locator:e.locator}:l;o&&o!==c.action_description&&(c={...c,action_description:o});let u=c.action_data?.action_name||"",d=c.action_description||"",f=yo(u);if(!f)return[`${s}// ${n}: Unknown action: ${u}`];let g={imports:r.imports},h=f(c,n,g);if(r.noAgent){if(Jn(c))return[`${s}// ${n}: ${I(d)}`,`${s}// AI action: ${I(d)} (requires agent - skipped in hook)`];let y=To(c,u,s,n);return y||[`${s}// ${n}: ${I(d)}`,...h.map(k=>`${s}${k}`)]}if(Jn(c))return[`${s}// ${n}: ${I(d)}`,`${s}page = agent.agentServices.validatePage(page);`,...h.map(y=>`${s}${y}`)];let p=JSON.stringify(d),v=h.map(y=>`${s} ${y}`),w=mo(c),m=i?`'${i}'`:"undefined";return[`${s}// ${n}: ${I(d)}`,`${s}page = agent.agentServices.validatePage(page);`,`${s}await agent.step(page, async () => {`,...v,`${s}}, ${p}, '${n}', ${m}, ${w});`]}function So(e,t,n,r){let s=" ".repeat(t),o=[];e.description&&e.description.trim()&&o.push(`${s}// Step: ${I(e.description)}`);let i=X(e.statements,t,r,n);return o.push(...i),o}function _o(e,t,n,r){let s=" ".repeat(t),o=[];if(o.push(`${s}// ${n}: Conditional check`),e.condition.type==="JS_CODE")o.push(`${s}if (${e.condition.expression}) {`);else{o.push(`${s}// AI Condition: ${I(e.condition.expression)}`);let a=JSON.stringify(e.condition.expression);o.push(`${s}if (await agent.evaluate(page, ${a}, "${n}")) {`)}let i=X(e.then,t+1,r,`${n}.then`);if(o.push(...i),e.else&&e.else.length>0){o.push(`${s}} else {`);let a=X(e.else,t+1,r,`${n}.else`);o.push(...a)}return o.push(`${s}}`),o}function xo(e,t,n,r){let s=" ".repeat(t),o=[];o.push(`${s}// ${n}: Loop`);let i=e.timeout_ms??bt,a=i/1e3,l=e.timeout_ms?`While loop exceeded timeout of ${a}s`:`While loop exceeded default timeout of ${a}s`,c=`loop_${n.replace(/\./g,"_")}`;if(o.push(`${s}const ${c}_start = Date.now();`),o.push(`${s}const ${c}_timeout = ${i};`),o.push(`${s}const ${c}_check = () => {`),o.push(`${s} if (Date.now() - ${c}_start > ${c}_timeout) {`),o.push(`${s} throw new Error('${l}');`),o.push(`${s} }`),o.push(`${s} return true;`),o.push(`${s}};`),e.condition.type==="JS_CODE")o.push(`${s}while (${c}_check() && (${e.condition.expression})) {`);else{o.push(`${s}// AI Loop Condition: ${I(e.condition.expression)}`);let d=JSON.stringify(e.condition.expression);o.push(`${s}while (${c}_check() && await agent.evaluate(page, ${d}, "${n}")) {`)}let u=X(e.body,t+1,r,`${n}.body`);return o.push(...u),o.push(`${s}}`),o}function To(e,t,n,r){let s=e.action_description||"",o=e.action_data?.kwargs||{},i=o.timeout_ms??_t;switch(t){case"go_to_url":case"open_tab":{let a=o.url||"";return[`${n}// ${r}: ${I(s)}`,`${n}await page.goto(${JSON.stringify(a)}, { waitUntil: 'domcontentloaded' });`]}case"go_back":return[`${n}// ${r}: ${I(s)}`,`${n}await page.goBack();`];case"go_forward":return[`${n}// ${r}: ${I(s)}`,`${n}await page.goForward();`];case"input_text":{let a=o.text||"",l=Ke(e);return l?[`${n}// ${r}: ${I(s)}`,`${n}await ${l}.fill(${JSON.stringify(a)}, { timeout: ${i} });`]:null}case"select_dropdown_option":{let a=o.text||o.label||"",l=Ke(e);return l?[`${n}// ${r}: ${I(s)}`,`${n}await ${l}.selectOption({ label: ${JSON.stringify(a)} }, { timeout: ${i} });`]:null}default:return null}}function ko(e,t){let n=[],r=t?.version||"unknown";n.push(`// @generated by shiplightai v${r}`),n.push(...nr()),n.push(""),t?.use&&Object.keys(t.use).length>0&&(n.push(`test.use(${JSON.stringify(t.use,null,2)});`),n.push(""));let s=new Set,o={imports:s,actionEntityStore:t?.actionEntityStore};t?.beforeEach&&t.beforeEach.length>0&&(n.push(...Xn("beforeEach",t.beforeEach,o)),n.push(""));let i=t?.timeout||t?.skip!==void 0||t?.fail!==void 0||t?.only||t?.slow?{timeout:t.timeout,skip:t.skip,fail:t.fail,only:t.only,slow:t.slow}:void 0;if(t?.parameters&&t.parameters.length>0){let a=t?.testName||e.goal||"Generated test",l=vt(t?.tags);for(let c of t.parameters){let u=tr(e,c.values);n.push(...Ve(u,`${l}${oe(a)} [${oe(c.name)}]`,o,0,i)),n.push("")}}else{let a=t?.testName||e.goal||"Generated test",l=vt(t?.tags);n.push(...Ve(e,`${l}${oe(a)}`,o,0,i))}return t?.afterEach&&t.afterEach.length>0&&(n.push(""),n.push(...Xn("afterEach",t.afterEach,o))),rr(n,s),n.join(`
640
- `)}function Po(e,t){let n=[],r=t?.version||"unknown";n.push(`// @generated by shiplightai v${r}`),n.push(...nr()),n.push(""),t?.use&&Object.keys(t.use).length>0&&(n.push(`test.use(${JSON.stringify(t.use,null,2)});`),n.push(""));let s=new Set,o={imports:s,actionEntityStore:t?.actionEntityStore},i=t?.testName||"Test Suite",a=vt(t?.tags);n.push(`test.describe.serial('${a}${oe(i)}', () => {`),e.beforeAll&&e.beforeAll.length>0&&(n.push(...Ge("beforeAll",e.beforeAll,o,1)),n.push("")),e.beforeEach&&e.beforeEach.length>0&&(n.push(...Ge("beforeEach",e.beforeEach,o,1)),n.push(""));for(let c=0;c<e.tests.length;c++){let u=e.tests[c],d=u.timeout||u.skip!==void 0||u.fail!==void 0||u.only||u.slow?{timeout:u.timeout,skip:u.skip,fail:u.fail,only:u.only,slow:u.slow}:void 0;if(u.parameters&&u.parameters.length>0)for(let f of u.parameters){let g=tr(u.testFlow,f.values);n.push(...Ve(g,`${oe(u.name)} [${oe(f.name)}]`,o,1,d)),n.push("")}else n.push(...Ve(u.testFlow,oe(u.name),o,1,d)),(c<e.tests.length-1||e.afterEach||e.afterAll)&&n.push("")}return e.afterEach&&e.afterEach.length>0&&(n.push(...Ge("afterEach",e.afterEach,o,1)),n.push("")),e.afterAll&&e.afterAll.length>0&&n.push(...Ge("afterAll",e.afterAll,o,1)),n.push("});"),rr(n,s),n.join(`
641
- `)}function vt(e){return e&&e.length>0?e.map(t=>`@${t}`).join(" ")+" ":""}function ze(e){let t=new Set;function n(r){for(let s of r)switch(s.type){case V.ACTION:{let i=s.action_entity?.action_data?.kwargs;if(i?.args&&Array.isArray(i.args))for(let a of i.args)typeof a=="string"&&Ao.includes(a)&&t.add(a);break}case V.STEP:n(s.statements);break;case V.IF_ELSE:{let o=s;n(o.then),o.else&&n(o.else);break}case V.WHILE_LOOP:n(s.body);break}}return n(e),t}function Eo(e){let t=ze(e.statements??[]);if(e.teardown)for(let n of ze(e.teardown))t.add(n);return t}function xt(e){return`{ ${["page","agent",...Array.from(e).sort()].join(", ")} }`}function Ve(e,t,n,r=0,s){let o=" ".repeat(r),i=[],a=Eo(e),l=xt(a),c=s?.only?"test.only":"test";i.push(`${o}${c}('${t}', async (${l}) => {`),s?.skip===!0?i.push(`${o} test.skip();`):typeof s?.skip=="string"&&i.push(`${o} test.skip(true, '${oe(s.skip)}');`),s?.fail===!0?i.push(`${o} test.fail();`):typeof s?.fail=="string"&&i.push(`${o} test.fail(true, '${oe(s.fail)}');`),s?.slow&&i.push(`${o} test.slow();`),s?.timeout&&i.push(`${o} test.setTimeout(${s.timeout});`);let u=e.teardown&&e.teardown.length>0,d=r+1;if(u){if(i.push(`${o} try {`),e.statements&&e.statements.length>0){i.push(`${o} // Test steps`);let g=X(e.statements,d+1,n);i.push(...g)}i.push(`${o} } finally {`),i.push(`${o} // Teardown`);let f=X(e.teardown,d+1,n,"teardown");i.push(...f),i.push(`${o} }`)}else if(e.statements&&e.statements.length>0){i.push(`${o} // Test steps`);let f=X(e.statements,d,n);i.push(...f)}return i.push(`${o}});`),i}function Xn(e,t,n){let r=[],s=er(t),o=ze(s),i=xt(o);return r.push(`test.${e}(async (${i}) => {`),r.push(...X(s,1,n,e)),r.push("});"),r}function Ge(e,t,n,r){let s=" ".repeat(r),o=[],i=er(t);if(e==="beforeAll"||e==="afterAll"){let l={...n,noAgent:!0};o.push(`${s}test.${e}(async ({ browser }, workerInfo) => {`),o.push(`${s} const page = await browser.newPage({ baseURL: workerInfo.project.use.baseURL });`),o.push(...X(i,r+1,l,e)),o.push(`${s} await page.close();`),o.push(`${s}});`)}else{let l=ze(i),c=xt(l);o.push(`${s}test.${e}(async (${c}) => {`),o.push(...X(i,r+1,n,e)),o.push(`${s}});`)}return o}function er(e){let n=po({goal:"_hook",statements:e});return C(n).statements??[]}function tr(e,t){let n=ke(e);for(let[r,s]of Object.entries(t))n=n.split(`<<${r}>>`).join(String(s));return C(n)}function nr(){return["import { test, expect } from 'shiplightai/fixture';"]}function rr(e,t){if(t.size>0){let n=0;for(let s=0;s<e.length;s++)e[s].startsWith("import ")&&(n=s+1);let r=Array.from(t);e.splice(n,0,...r)}}function or(e,t,n){let r={expandingPaths:new Set([St(t)]),depth:0,referencedPaths:new Set,basePath:n},s={...e};Array.isArray(s.statements)&&(s.statements=le(s.statements,t,r)),Array.isArray(s.teardown)&&(s.teardown=le(s.teardown,t,r));for(let o of["beforeAll","afterAll","beforeEach","afterEach"])Array.isArray(s[o])&&(s[o]=le(s[o],t,r));return{doc:s,referencedTemplatePaths:Array.from(r.referencedPaths)}}function le(e,t,n){let r=[];for(let s of e)if(Co(s)){let o=No(s,t,n);r.push(o)}else r.push(Do(s,t,n));return r}function Co(e){return typeof e=="object"&&e!==null&&typeof e.template=="string"}function No(e,t,n){if(n.depth>=Qn)throw new Error(`Template expansion exceeded maximum depth of ${Qn}. Check for deeply nested or circular template references.`);let r=St(Lo(t),e.template),s=!Oo(r)&&n.basePath?St(n.basePath,e.template):r;if(n.expandingPaths.has(s))throw new Error(`Circular template reference detected: ${s} is already being expanded. Stack: ${Array.from(n.expandingPaths).join(" \u2192 ")} \u2192 ${s}`);n.referencedPaths.add(s);let o;try{o=Io(s,"utf-8")}catch(h){throw new Error(`Failed to read template file: ${s} (referenced from ${t}): ${h.message}`)}let i=Zn(o);if(!i||typeof i!="object")throw new Error(`Invalid template file: ${s} \u2014 expected a YAML object`);let a=i.params||[],l=e.params||{};for(let h of a)if(!(h in l))throw new Error(`Template ${e.template} requires param "${h}" but it was not provided. Required params: [${a.join(", ")}]`);let c=i.statements;if(!Array.isArray(c))throw new Error(`Template ${e.template} must have a "statements" array`);if(Object.keys(l).length>0){let p=Ro(c);for(let[v,w]of Object.entries(l))p=p.split(`<<${v}>>`).join(String(w));c=Zn(p)}let u={expandingPaths:new Set([...n.expandingPaths,s]),depth:n.depth+1,referencedPaths:n.referencedPaths},d=le(c,s,u),g={STEP:i.name||e.template.replace(/\.yaml$/,"").split("/").pop()||e.template,template_path:e.template,statements:d};return Object.keys(l).length>0&&(g.template_params=l),g}function Do(e,t,n){if(typeof e!="object"||e===null)return e;let r={...e};return Array.isArray(r.statements)&&(r.statements=le(r.statements,t,n)),Array.isArray(r.THEN)&&(r.THEN=le(r.THEN,t,n)),Array.isArray(r.ELSE)&&(r.ELSE=le(r.ELSE,t,n)),Array.isArray(r.DO)&&(r.DO=le(r.DO,t,n)),r}function kt(e,t,n){let r=Mo(e),s=r?.name,o=r?.tags,i=r?.use;if(r&&(r.name!==void 0||r.tags!==void 0||r.use!==void 0)&&(delete r.name,delete r.tags,delete r.use),r?.suite){if(r.goal||r.statements)throw new Tt('YAML file cannot have both "suite" and top-level "goal"/"statements". Use either suite format or single-test format.');return Fo(r,s,o,i,t,n)}return jo(r,s,o,i,t,n)}function jo(e,t,n,r,s,o){let i=e?.beforeEach,a=e?.afterEach,l=ir(e?.parameters),c=e?.timeout,u=e?.skip,d=e?.fail,f=e?.only,g=e?.slow,h=e?.settings,p=r;if(h){let y={};h.auto_dismiss_modal!==void 0&&(y.autoDismissModal=!!h.auto_dismiss_modal),h.browser_timezone!==void 0&&h.browser_timezone!==null&&(y.timezoneId=String(h.browser_timezone)),h.browser_language!==void 0&&h.browser_language!==null&&(y.locale=String(h.browser_language)),h.extra_http_headers!==void 0&&h.extra_http_headers!==null&&typeof h.extra_http_headers=="object"&&(y.extraHTTPHeaders=h.extra_http_headers),Object.keys(y).length>0&&(p={...p,...y})}if(e&&(delete e.beforeEach,delete e.afterEach,delete e.parameters,delete e.timeout,delete e.skip,delete e.fail,delete e.only,delete e.slow,delete e.settings),e?.url)throw new Tt(`The "url" field is not supported in local YAML tests. Use "base_url: ${e.url}" and add "- URL: /" as the first statement instead.`);e&&!e.goal&&t&&(e.goal=t);let v=[];if(s&&e&&typeof e=="object"){let y=or(e,s,o);e=y.doc,v=y.referencedTemplatePaths}let w=sr(e),m=C(w);return s&&(ye(m.statements??[],s,"main"),m.teardown&&ye(m.teardown,s,"teardown")),{testFlow:m,name:t,tags:n,use:p,beforeEach:i,afterEach:a,parameters:l,timeout:c,skip:u,fail:d,only:f,slow:g,referencedTemplatePaths:v}}function Fo(e,t,n,r,s,o){let i=e.suite;if(!Array.isArray(i.tests)||i.tests.length===0)throw new Error('Suite must have a non-empty "tests" array.');let a=i.beforeAll,l=i.afterAll,c=i.beforeEach,u=i.afterEach,d=[],f=i.tests.map(p=>{if(!p.name)throw new Error('Each test in a suite must have a "name" field.');if(!Array.isArray(p.statements)||p.statements.length===0)throw new Error(`Suite test "${p.name}" must have a non-empty "statements" array.`);let v={goal:p.name,statements:p.statements};p.teardown&&(v.teardown=p.teardown);let w=[],m=v;if(s&&typeof v=="object"){let E=or(v,s,o);m=E.doc,w=E.referencedTemplatePaths,d.push(...w)}let y=sr(m),k=C(y),P=ir(p.parameters);return{testFlow:k,name:p.name,tags:Array.isArray(p.tags)?p.tags:void 0,parameters:P,timeout:p.timeout,skip:p.skip,fail:p.fail,only:p.only,slow:p.slow}}),g=i.base_url,h=g?{...r,baseURL:g}:r;return{suite:{beforeAll:a,afterAll:l,beforeEach:c,afterEach:u,tests:f},name:t,tags:n,use:h,referencedTemplatePaths:d}}function ir(e){if(!(!Array.isArray(e)||e.length===0))return e.map((t,n)=>{if(!t.name)throw new Error(`Parameter set at index ${n} must have a "name" field.`);if(!t.values||typeof t.values!="object")throw new Error(`Parameter set "${t.name}" must have a "values" object.`);return{name:t.name,values:t.values}})}function ye(e,t,n){for(let r=0;r<e.length;r++){let s=e[r],o=`${n}.${r}`,i=s.description||"";if(s.uid=Uo(t,o,i),s.type===V.STEP)ye(s.statements,t,o);else if(s.type===V.IF_ELSE){let a=s;ye(a.then,t,`${o}.then`),a.else&&ye(a.else,t,`${o}.else`)}else s.type===V.WHILE_LOOP&&ye(s.body,t,`${o}.body`)}}function Uo(e,t,n){let r=$o("sha256").update(`${e}:${t}:${n}`).digest("hex");return`${r.slice(0,8)}-${r.slice(8,12)}-${r.slice(12,16)}-${r.slice(16,20)}-${r.slice(20,32)}`}function ar(e,t){let n;try{n=Bo(e,"utf-8")}catch(r){return{valid:!1,errors:[`Failed to read file: ${r.message}`],warnings:[]}}return Ko(n,e,t)}function Ko(e,t,n){let r=/\btemplate:\s/.test(e),s=/^suite:/m.test(e),o=r||s?null:yt(e);if(o&&!o.valid)return{valid:!1,errors:o.errors,warnings:[],stats:o.stats};let i,a,l=[];try{let c=n?.parsed??kt(e,t);l=c.referencedTemplatePaths;let u={version:n?.version,actionEntityStore:n?.actionEntityStore},d=c.testFlow?.baseURL?{...c.use,baseURL:c.testFlow.baseURL}:c.use;c.suite?i=Po(c.suite,{...u,testName:c.name,tags:c.tags,use:c.use}):i=ko(c.testFlow,{...u,testName:c.name,tags:c.tags,use:d,beforeEach:c.beforeEach,afterEach:c.afterEach,parameters:c.parameters,timeout:c.timeout,skip:c.skip,fail:c.fail,only:c.only,slow:c.slow});let f=i.split(`
208
+ `}async function Cn(e){let{createServer:t}=await import("net");for(let r=e;r<e+20;r++)if(await new Promise(n=>{let o=t();o.once("error",()=>n(!1)),o.once("listening",()=>{o.close(()=>n(!0))}),o.listen(r,"127.0.0.1")}))return r;throw new Error(`No available port found in range ${e}-${e+19}`)}async function pt(e){let{yamlFilePath:t,configPath:r,tempSuffix:s="",headed:n}=e,o=X.dirname(r),i=await Cn(16174),a;if(!ne.existsSync(t))throw new Error(`Please select a test file before starting the debug session. File not found: ${t}`);try{let y=Ln(ne.readFileSync(t,"utf-8"));y?.use&&typeof y.use=="object"&&!Array.isArray(y.use)&&(a=y.use),y?.base_url&&!a?.baseURL&&(a={...a,baseURL:y.base_url}),y?.settings?.auto_dismiss_modal!==void 0&&(a={...a,autoDismissModal:!!y.settings.auto_dismiss_modal}),y?.settings?.browser_timezone!=null&&(a={...a,timezoneId:String(y.settings.browser_timezone)}),y?.settings?.browser_language!=null&&(a={...a,locale:String(y.settings.browser_language)}),y?.settings?.extra_http_headers!=null&&typeof y.settings.extra_http_headers=="object"&&(a={...a,extraHTTPHeaders:y.settings.extra_http_headers})}catch(y){console.error("[debugger] Could not parse YAML for `use` block:",y)}let l=X.dirname(X.resolve(t)),c=s?`-${s}`:"",u=X.join(l,`.__shiplight_debug__${c}.yaml.spec.ts`),d=Rn(t,i,a,o);ne.writeFileSync(u,d);let f=lr(u),g=["playwright","test",u,...n?["--headed"]:[]],h=On("npx",g,{stdio:["ignore","pipe","pipe"],shell:!0,cwd:o,env:{...process.env,PWDEBUG:"console",SHIPLIGHT_REGISTRY_URL:""}});h.stdout?.on("data",y=>{process.stderr.write(y)}),h.stderr?.on("data",y=>{process.stderr.write(y)});let p=()=>{h.killed||h.kill("SIGTERM")};process.on("SIGTERM",p),process.on("SIGINT",p),process.on("exit",p),h.on("close",y=>{process.removeListener("SIGTERM",p),process.removeListener("SIGINT",p),process.removeListener("exit",p),f(),y!==0&&y!==null&&console.error(`[debugger] Playwright process exited with code ${y}`)}),console.error("[debugger] Waiting for Playwright sandbox to start...");let v=["127.0.0.1","::1"];async function w(y){try{let P=y.includes(":")?`[${y}]`:y,k=await fetch(`http://${P}:${i}/api/test-flow`);if(k.ok){try{await k.text()}catch{}return!0}}catch{}return!1}let m=null;for(let y=0;y<180;y++){if(h.exitCode!==null)throw f(),new Error(`Playwright process exited with code ${h.exitCode} before sandbox was ready`);for(let P of v)if(await w(P)){m=P;break}if(m){console.error(`[debugger] Playwright sandbox ready on ${m}:${i}`);break}if(y===179)throw p(),f(),new Error("Timed out waiting for Playwright sandbox to start (180s)");await new Promise(P=>setTimeout(P,1e3))}if(!m)throw p(),f(),new Error("Sandbox poll finished without a reachable host");return{port:i,host:m,pid:h.pid??0,cleanup:async()=>{p(),f()}}}var ut=_(()=>{"use strict"});var ur=_(()=>{"use strict"});var dr=_(()=>{"use strict"});var hr=_(()=>{"use strict"});import{v4 as na}from"uuid";var fr=_(()=>{"use strict"});import{z as b}from"zod";var gr,dt,mr,fe,yr,wr,br,j,vr,De,ht,ft=_(()=>{"use strict";gr=b.enum(["JS_CODE","AI_MODE"]),dt=b.object({type:gr,expression:b.string()}),mr=b.enum(["DRAFT","STEP","ACTION","IF_ELSE","WHILE_LOOP"]),fe=b.object({uid:b.string(),type:mr,comment:b.string().optional()}),yr=b.object({action_data:b.object({action_name:b.string(),kwargs:b.record(b.any()).optional(),args:b.array(b.any()).optional()}),action_description:b.string().optional(),url:b.string().optional(),xpath:b.string().nullable().optional(),locator:b.string().nullable().optional(),css_selector:b.string().nullable().optional(),unique_selector:b.string().nullable().optional(),element_index:b.number().nullable().optional(),frame_path:b.array(b.any()).optional(),artifacts:b.record(b.any()).optional(),feedback:b.string().optional(),original_browser_use_action:b.any().optional()}).passthrough(),wr=fe.extend({type:b.literal("DRAFT"),description:b.string()}),br=fe.extend({type:b.literal("ACTION"),description:b.string(),action_entity:yr.optional(),locator:b.string().optional(),use_pure_vision:b.boolean().optional()}),j=b.lazy(()=>b.union([wr,br,fe.extend({type:b.literal("STEP"),description:b.string().optional().default(""),statements:b.array(j),reference_id:b.number().optional(),template_path:b.string().optional(),template_params:b.record(b.string()).optional()}),fe.extend({type:b.literal("IF_ELSE"),description:b.string().optional(),condition:dt,then:b.array(j),else:b.array(j).optional()}),fe.extend({type:b.literal("WHILE_LOOP"),description:b.string().optional(),condition:dt,body:b.array(j),timeout_ms:b.number().optional()})])),vr=b.object({name:b.string(),statements:b.array(j),teardown:b.array(j).optional(),skip:b.union([b.boolean(),b.string()]).optional(),timeout:b.number().optional(),fail:b.union([b.boolean(),b.string()]).optional(),only:b.boolean().optional(),slow:b.boolean().optional()}),De=b.object({tests:b.array(vr).min(1),beforeAll:b.array(j).optional(),afterAll:b.array(j).optional(),beforeEach:b.array(j).optional(),afterEach:b.array(j).optional()}),ht=b.object({comment:b.string().optional(),version:b.string().optional(),goal:b.string().optional(),url:b.string().optional(),baseURL:b.string().optional(),final_feedback:b.string().optional(),completed:b.boolean().optional(),success:b.boolean().optional(),statements:b.array(j).optional(),teardown:b.array(j).optional(),last_modified_at:b.string().optional(),testGroup:De.optional()}).refine(e=>e.testGroup!==void 0?e.goal===void 0&&(e.statements===void 0||e.statements.length===0):e.goal!==void 0,{message:"TestFlow must have either goal/statements (single test) or testGroup (suite), not both"})});import{stringify as Nn,parse as Tr,parseAllDocuments as pa,parseDocument as Dn,Document as jn,isMap as me,isSeq as z}from"yaml";import{v4 as K}from"uuid";function je(e,t){let r={...t?.test_case_id!==void 0?{test_case_id:t.test_case_id}:{},...t?.name?{name:t.name}:{},goal:e.goal??"",url:e.url,base_url:e.baseURL,...t?.timeout!==void 0?{timeout:t.timeout}:{},...t?.settings&&Object.keys(t.settings).length>0?{settings:t.settings}:{},statements:(e.statements??[]).map(B)};return e.final_feedback&&(r.final_feedback=e.final_feedback),e.teardown&&e.teardown.length>0&&(r.teardown=e.teardown.map(B)),r}function Pe(e,t){if(e.testGroup)return kr(e,t);let r=je(e,t),s=new jn(r);return e.comment&&(s.commentBefore=e.comment),Sr(s,e.statements??[]),e.teardown&&Sr(s,e.teardown,"teardown"),s.toString(Pr)}function Sr(e,t,r="statements"){let s=e.contents;if(!s||!me(s))return;let n=s.get(r,!0);z(n)&&Te(n,t)}function Te(e,t){for(let r=0;r<Math.min(e.items.length,t.length);r++){let s=t[r],n=e.items[r];if(r>0&&(n.spaceBefore=!0),s.comment&&(r===0?e.commentBefore=s.comment:n.commentBefore=s.comment),me(n)){let o=n;if(s.type==="STEP"){let i=o.get("statements",!0);z(i)&&Te(i,s.statements)}else if(s.type==="IF_ELSE"){let i=o.get("THEN",!0);z(i)&&Te(i,s.then);let a=o.get("ELSE",!0);z(a)&&s.else&&Te(a,s.else)}else if(s.type==="WHILE_LOOP"){let i=o.get("DO",!0);z(i)&&Te(i,s.body)}}}}function kr(e,t){let r=e.testGroup;if(!r)throw new Error("suiteToYaml requires a TestFlow with testGroup");let s={};t?.test_case_id!==void 0&&(s.test_case_id=t.test_case_id),t?.name&&(s.name=t.name),t?.tags&&t.tags.length>0&&(s.tags=t.tags),t?.use&&Object.keys(t.use).length>0&&(s.use=t.use),t?.settings&&Object.keys(t.settings).length>0&&(s.settings=t.settings);let n={};return e.baseURL&&(n.base_url=e.baseURL),r.beforeAll&&r.beforeAll.length>0&&(n.beforeAll=r.beforeAll.map(B)),r.beforeEach&&r.beforeEach.length>0&&(n.beforeEach=r.beforeEach.map(B)),r.afterEach&&r.afterEach.length>0&&(n.afterEach=r.afterEach.map(B)),r.afterAll&&r.afterAll.length>0&&(n.afterAll=r.afterAll.map(B)),n.tests=r.tests.map(o=>{let i={name:o.name};return o.skip!==void 0&&(i.skip=o.skip),o.timeout!==void 0&&(i.timeout=o.timeout),o.fail!==void 0&&(i.fail=o.fail),o.only!==void 0&&(i.only=o.only),o.slow!==void 0&&(i.slow=o.slow),i.statements=o.statements.map(B),o.teardown&&o.teardown.length>0&&(i.teardown=o.teardown.map(B)),i}),s.suite=n,Nn(s,Pr)}function B(e){switch(e.type){case"DRAFT":return Fn(e);case"ACTION":return Un(e);case"STEP":return Bn(e);case"IF_ELSE":return Hn(e);case"WHILE_LOOP":return Wn(e)}}function Fn(e){return{intent:e.description}}function Un(e){let t=e.action_entity?.action_data?.action_name??e.action_entity?.action?.action_name,r=e.action_entity?.action_data?.kwargs??e.action_entity?.action?.kwargs;if(t==="verify"){let a=r?.statement;if(typeof a=="string"&&!e.action_entity?.locator&&!e.action_entity?.xpath){let l=r?.code;return typeof l=="string"?{VERIFY:a,js:l}:{VERIFY:a}}}if(t==="go_to_url"){let a=r?.url;if(typeof a=="string"&&!e.action_entity?.locator&&!e.action_entity?.xpath){let l={URL:a};return r?.new_tab===!0&&(l.new_tab=!0),typeof r?.timeout_seconds=="number"&&(l.timeout_seconds=r.timeout_seconds),l}}if(t==="js_action"){let a=r?.code;if(typeof a=="string"&&e.description)return{description:e.description,js:a}}if(t==="ai_wait_until"){let a=r?.condition;if(typeof a=="string"){let l={WAIT_UNTIL:a};return typeof r?.timeout_seconds=="number"&&r.timeout_seconds!==60&&(l.timeout_seconds=r.timeout_seconds),l}}if(t==="wait"){let a=r?.seconds,c={WAIT:e.description||`Wait ${a}s`};return typeof a=="number"&&(c.seconds=a),c}if(t==="js_code"){let a=r?.code;if(typeof a=="string"&&!e.action_entity?.locator&&!e.action_entity?.xpath)return{description:e.description||"Code block",js:a}}if(!e.action_entity)return{intent:e.description};let s=e.action_entity.action_data??e.action_entity.action;if(!s)return{intent:e.description};let n={intent:e.description,action:s.action_name},o=e.locator??e.action_entity.locator;o&&(n.locator=o);let i=e.action_entity.xpath;if(i&&(n.xpath=i),e.use_pure_vision&&(n.use_pure_vision=!0),s.kwargs&&Object.keys(s.kwargs).length>0)for(let[a,l]of Object.entries(s.kwargs))n[a]=l;return s.args&&s.args.length>0&&(n.args=s.args),n}function Bn(e){if(e.template_path){let r={template:e.template_path};return e.template_params&&Object.keys(e.template_params).length>0&&(r.params=e.template_params),r}let t={STEP:e.description,statements:e.statements.map(B)};return e.reference_id!==void 0&&(t.reference_id=e.reference_id),t}function Hn(e){let t={IF:Er(e.condition),THEN:e.then.map(B)};return e.else&&e.else.length>0&&(t.ELSE=e.else.map(B)),t}function Wn(e){let t={WHILE:Er(e.condition),DO:e.body.map(B)};return e.timeout_ms!==void 0&&(t.timeout_ms=e.timeout_ms),t}function Er(e){return e.type==="JS_CODE"?`js:${e.expression}`:e.expression}function ke(e){try{let t=Tr(e);if(!t||typeof t!="object")return{};let r={};return typeof t.test_case_id=="number"&&Number.isFinite(t.test_case_id)&&(r.test_case_id=t.test_case_id),typeof t.template_id=="number"&&Number.isFinite(t.template_id)&&(r.template_id=t.template_id),typeof t.name=="string"&&t.name.trim()&&(r.name=t.name.trim()),typeof t.timeout=="number"&&Number.isFinite(t.timeout)&&(r.timeout=t.timeout),t.settings&&typeof t.settings=="object"&&!Array.isArray(t.settings)&&(r.settings=t.settings),r}catch{return{}}}function gt(e){if(e==null||typeof e!="object")return e;if(Array.isArray(e))return e.map(gt);let t=e,r=Object.keys(t);if(r.length===1){let n=r[0];if(n.startsWith("{ ")&&n.endsWith(" }")&&t[n]===null)return`{{${n.slice(2,-2)}}}`}let s={};for(let[n,o]of Object.entries(t))s[n]=gt(o);return s}function C(e){if(e.length>_r)throw new Error(`YAML input too large (${e.length} bytes, max ${_r})`);let t=gt(Tr(e));if(!t||typeof t!="object")throw new Error("Invalid YAML: expected an object at root level");if(t.suite)return Gn(t);let r={version:"1.3.0",goal:t.goal,url:t.url,baseURL:t.base_url,statements:H(t.statements??[])};t.final_feedback&&(r.final_feedback=t.final_feedback),t.teardown&&Array.isArray(t.teardown)&&(r.teardown=H(t.teardown));let s=ht.safeParse(r);if(!s.success)throw new Error(`Invalid TestFlow after YAML conversion: ${JSON.stringify(s.error.errors)}`);let n=s.data;return Fe(e,n),n}function Gn(e){let t=e.suite;if(!t||typeof t!="object")throw new Error("Invalid suite: expected an object");let r=t.tests;if(!Array.isArray(r)||r.length===0)throw new Error('Suite must have a non-empty "tests" array');let n={tests:r.map(a=>{if(!a.name)throw new Error('Each test in a suite must have a "name" field');if(!Array.isArray(a.statements)||a.statements.length===0)throw new Error(`Suite test "${a.name}" must have a non-empty "statements" array`);let l={name:a.name,statements:H(a.statements)};return Array.isArray(a.teardown)&&a.teardown.length>0&&(l.teardown=H(a.teardown)),a.skip!==void 0&&(l.skip=a.skip),typeof a.timeout=="number"&&(l.timeout=a.timeout),a.fail!==void 0&&(l.fail=a.fail),a.only===!0&&(l.only=!0),a.slow===!0&&(l.slow=!0),l})};Array.isArray(t.beforeAll)&&t.beforeAll.length>0&&(n.beforeAll=H(t.beforeAll)),Array.isArray(t.afterAll)&&t.afterAll.length>0&&(n.afterAll=H(t.afterAll)),Array.isArray(t.beforeEach)&&t.beforeEach.length>0&&(n.beforeEach=H(t.beforeEach)),Array.isArray(t.afterEach)&&t.afterEach.length>0&&(n.afterEach=H(t.afterEach));let o=De.safeParse(n);if(!o.success)throw new Error(`Invalid TestGroup: ${JSON.stringify(o.error.errors)}`);return{version:"1.3.0",baseURL:t.base_url||void 0,testGroup:o.data}}function H(e){if(!Array.isArray(e))throw new Error("Expected an array of statements");return e.map(Kn)}function Kn(e){if(typeof e=="string")throw new Error(`Plain string statements are not supported. Use an object with a "desc" key instead. Example: { "desc": "${e}" }`);if(typeof e!="object"||e===null)throw new Error(`Invalid statement: expected object, got ${typeof e}`);let t=e;if("IF"in t)return zn(t);if("WHILE"in t)return Vn(t);if("STEP"in t)return Yn(t);if("VERIFY"in t){let r=t.VERIFY,s={statement:typeof r=="string"?r:String(r)};return typeof t.js=="string"&&(s.code=t.js),{uid:K(),type:"ACTION",description:String(r),action_entity:{action_description:String(r),action_data:{action_name:"verify",kwargs:s}}}}if("URL"in t){let r=t.URL,s=t.new_tab===!0?!0:void 0,n=typeof t.timeout_seconds=="number"?t.timeout_seconds:void 0,o={url:typeof r=="string"?r:String(r)};return s&&(o.new_tab=!0),n!==void 0&&(o.timeout_seconds=n),{uid:K(),type:"ACTION",description:`Navigate to ${r}`,action_entity:{action_description:`Navigate to ${r}`,action_data:{action_name:"go_to_url",kwargs:o}}}}if("WAIT_UNTIL"in t){let r=t.WAIT_UNTIL,s=typeof t.timeout_seconds=="number"?t.timeout_seconds:60;return{uid:K(),type:"ACTION",description:`Wait until: ${r}`,action_entity:{action_description:`Wait until: ${r}`,action_data:{action_name:"ai_wait_until",kwargs:{condition:typeof r=="string"?r:String(r),timeout_seconds:s}}}}}if("WAIT"in t){let r=t.WAIT,s=typeof t.seconds=="number"?t.seconds:3;return{uid:K(),type:"ACTION",description:typeof r=="string"?r:`Wait ${s}s`,action_entity:{action_description:typeof r=="string"?r:`Wait ${s}s`,action_data:{action_name:"wait",kwargs:{seconds:s}}}}}if("CODE"in t){let r=t.CODE;if(r==null)throw new Error('CODE statement has no code. Use "CODE: |" followed by indented code on the next line.');let s=typeof t.description=="string"?t.description:"Code block";return{uid:K(),type:"ACTION",description:s,action_entity:{action_description:s,action_data:{action_name:"js_code",kwargs:{code:typeof r=="string"?r:String(r)}}}}}if("js"in t&&!("VERIFY"in t)&&!("action"in t)){if("intent"in t||"desc"in t)throw new Error("A `js:` statement uses `description:`, not `intent:`. Raw JS does not self-heal \u2014 use `description: + js:` for code, or express it as a structured action (`intent:` + `action:`/`locator:`) to keep self-healing.");let r=typeof t.description=="string"&&t.description.trim()!==""?t.description:"Code block",s=t.js;return{uid:K(),type:"ACTION",description:r,action_entity:{action_description:r,action_data:{action_name:"js_code",kwargs:{code:typeof s=="string"?s:String(s)}}}}}if("call"in t&&typeof t.call=="string"){let{call:r,...s}=t;return xr({...s,action:"function",functionName:r})}if("action"in t)return xr(t);if("intent"in t&&typeof t.intent=="string"||"desc"in t&&typeof t.desc=="string")return{uid:K(),type:"DRAFT",description:typeof t.intent=="string"?t.intent:t.desc};throw new Error(`Cannot infer statement type from object: ${JSON.stringify(t)}`)}function Ar(e){if(typeof e!="string")throw new Error(`Condition must be a string, got ${typeof e}`);return e.startsWith("js:")?{type:"JS_CODE",expression:e.slice(3)}:{type:"AI_MODE",expression:e}}function zn(e){let t=Ar(e.IF),r=e.THEN;if(!Array.isArray(r))throw new Error("IF_ELSE requires a THEN array");let s={uid:K(),type:"IF_ELSE",condition:t,then:H(r)};return"ELSE"in e&&Array.isArray(e.ELSE)&&(s.else=H(e.ELSE)),s}function Vn(e){let t=Ar(e.WHILE),r=e.DO;if(!Array.isArray(r))throw new Error("WHILE_LOOP requires a DO array");let s={uid:K(),type:"WHILE_LOOP",condition:t,body:H(r)};return typeof e.timeout_ms=="number"&&(s.timeout_ms=e.timeout_ms),s}function Yn(e){let t=typeof e.STEP=="string"?e.STEP:"";if(!Array.isArray(e.statements))throw new Error("STEP requires a statements array");let r={uid:K(),type:"STEP",description:t,statements:H(e.statements)};if(typeof e.reference_id=="number"&&(r.reference_id=e.reference_id),typeof e.template_path=="string"&&(r.template_path=e.template_path),e.template_params&&typeof e.template_params=="object"&&!Array.isArray(e.template_params)){let s=e.template_params,n={};for(let[o,i]of Object.entries(s))n[o]=String(i);r.template_params=n}return r}function xr(e){let t=typeof e.action=="string"?e.action:String(e.action),r=typeof e.intent=="string"?e.intent:typeof e.desc=="string"?e.desc:"",s=typeof e.locator=="string"?e.locator:void 0,n=typeof e.xpath=="string"?e.xpath:void 0,o=typeof e.use_pure_vision=="boolean"?e.use_pure_vision:void 0,i={};for(let[c,u]of Object.entries(e))Jn.has(c)||(i[c]=u);t==="verify"&&typeof i.js=="string"&&(i.code=i.js,delete i.js);let a={action_description:r,action_data:{action_name:t,kwargs:Object.keys(i).length>0?i:{}}};s&&(a.locator=s),n&&(a.xpath=n);let l={uid:K(),type:"ACTION",description:r,action_entity:a};return o&&(l.use_pure_vision=!0),l}function Fe(e,t){let r;try{r=Dn(e)}catch{return}let s=r.contents;if(!s||!me(s))return;if(r.commentBefore)t.comment=r.commentBefore;else{let l=s.items?.[0];l?.key&&l.key.commentBefore&&(t.comment=l.key.commentBefore)}let n=s,o=n.get("statements",!0);z(o)&&t.statements&&ge(o,t.statements);let i=n.get("teardown",!0);z(i)&&t.teardown&&ge(i,t.teardown)}function ge(e,t){e.commentBefore&&t.length>0&&(t[0].comment=e.commentBefore);for(let r=0;r<Math.min(e.items.length,t.length);r++){let s=e.items[r];s.commentBefore&&!(r===0&&e.commentBefore)&&(t[r].comment=s.commentBefore);let n=t[r];if(n.type==="STEP"&&me(s)){let o=s.get("statements",!0);z(o)&&ge(o,n.statements)}else if(n.type==="IF_ELSE"&&me(s)){let o=s.get("THEN",!0);z(o)&&ge(o,n.then);let i=s.get("ELSE",!0);z(i)&&n.else&&ge(i,n.else)}else if(n.type==="WHILE_LOOP"&&me(s)){let o=s.get("DO",!0);z(o)&&ge(o,n.body)}}}var Pr,_r,Jn,Ue=_(()=>{"use strict";ft();Pr={lineWidth:120,defaultKeyType:"PLAIN",defaultStringType:"PLAIN"};_r=1024*1024;Jn=new Set(["action","intent","desc","locator","xpath","use_pure_vision"])});import{parse as ga,stringify as ma}from"yaml";var $r=_(()=>{"use strict";Ue()});var mt,Be,He=_(()=>{"use strict";mt=e=>{let t=[];switch(e.type){case"STEP":e.statements&&t.push({key:"statements",statements:e.statements});break;case"IF_ELSE":e.then&&t.push({key:"then",statements:e.then}),e.else&&t.push({key:"else",statements:e.else});break;case"WHILE_LOOP":e.body&&t.push({key:"body",statements:e.body});break}return t},Be=e=>{let t=[],r=s=>{for(let n of s){t.push(n);let o=mt(n);for(let i of o)r(i.statements)}};return r(e),t}});function Rr(e){let t=0,r=0;for(let s of e)if(s.type==="DRAFT")r++;else if(s.type==="ACTION"){let n=s.action_entity?.action_data?.action_name??"";Lr.has(n)||t++}return{action:t,draft:r}}function qn(e){try{return new Function(`return async function() { ${e} }`),null}catch(t){return t.message}}function Or(e){try{return new Function(`return async function() { return (${e}) }`),null}catch(t){return t.message}}function Zn(e){let t=e.split(/\r?\n/).map(o=>o.trim()).filter(o=>o.length>0&&!o.startsWith("//")),r=t.length,s=t.join(`
209
+ `).match(/\bawait\b/g),n=s?s.length:0;return r<=Mr&&n<=Ir?null:`${r} non-blank line(s), ${n} await(s) \u2014 limits are ${Mr} lines and ${Ir} awaits. The VERIFY js: field is a cache for one natural-language assertion, not a place for multi-step logic. Break this into multiple statements \u2014 one VERIFY per assertion \u2014 so each step is visible in the debugger and self-healable. For freeform setup code (mocking, storage), use the description: + js: escape hatch, which has no complexity cap.`}function yt(e,t){let r=t?.coverageThreshold??Xn,s=[],n=[],o;try{o=C(e)}catch(f){return{valid:!1,errors:[`Invalid YAML: ${f.message}`],warnings:[],stats:{total:0,action:0,draft:0,coverage:0}}}o.goal||s.push('Missing required field: "goal"'),o.statements?.length||s.push('Missing required field: "statements"');let i=[...Be(o.statements??[]),...o.teardown?Be(o.teardown):[]],{action:a,draft:l}=Rr(i),c="Hint: in YAML double-quoted strings, backslashes are escape characters \u2014 use \\\\/ instead of \\/ for regex, or use single quotes.";for(let f of i){let g=f;if(g.reference_id!==void 0){let h=g.description||f.uid;s.push(`Unresolved cloud template reference on statement "${h}" (reference_id: ${g.reference_id}). Local YAML tests cannot use reference_id \u2014 inline the template statements or use the local "template:" key instead.`)}if(f.type==="ACTION"){let h=f,p=h.action_entity?.action_data?.action_name??"";if(p==="js_code"||p==="js_action"||p==="verify"||p==="ai_assert"){let v=h.action_entity?.action_data?.kwargs?.code;if(typeof v=="string"){let w=qn(v);if(w){let m=h.description||p;s.push(`Invalid JS in "${m}": ${w}. ${c}`)}else if(p==="verify"){let m=Zn(v);if(m){let y=h.description||p;s.push(`JS cache for "${y}" is too complex: ${m}`)}}}}}if(f.type==="IF_ELSE"){let h=f;if(h.condition.type==="JS_CODE"){let p=Or(h.condition.expression);p&&s.push(`Invalid JS in IF condition "${h.condition.expression}": ${p}. ${c}`)}}if(f.type==="WHILE_LOOP"){let h=f;if(h.condition.type==="JS_CODE"){let p=Or(h.condition.expression);p&&s.push(`Invalid JS in WHILE condition "${h.condition.expression}": ${p}. ${c}`)}}}let u=a+l,d=u>0?Math.round(a/u*100):0;return u>0&&d/100<r&&n.push(`Low action coverage: ${a}/${u} statements (${d}%) are enriched with action/js. ${l} draft statement(s) still need enrichment. Use MCP tools (act, get_locators) to convert drafts to actions.`),{valid:s.length===0,errors:s,warnings:n,stats:{total:u,action:a,draft:l,coverage:d}}}var Xn,Mr,Ir,Lr,Cr=_(()=>{"use strict";Ue();He();Xn=.5,Mr=5,Ir=3,Lr=new Set(["verify","ai_assert","done","go_to_url","ai_wait_until","wait","js_code"])});var We=_(()=>{"use strict"});var Nr=_(()=>{"use strict";We()});var Dr,eo,jr,Fr,Ee,to,ro,Ur=_(()=>{"use strict";Dr=112,eo=1080-Dr,jr={"Blackberry PlayBook":{name:"Blackberry PlayBook",userAgent:"Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/26.0 Safari/536.2+",screen:{width:600,height:1024},viewport:{width:600,height:1024},deviceScaleFactor:1,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"BlackBerry Z30":{name:"BlackBerry Z30",userAgent:"Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/26.0 Mobile Safari/537.10+",screen:{width:360,height:640},viewport:{width:360,height:640},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"Galaxy Note 3":{name:"Galaxy Note 3",userAgent:"Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/26.0 Mobile Safari/534.30",screen:{width:360,height:640},viewport:{width:360,height:640},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"Galaxy Note II":{name:"Galaxy Note II",userAgent:"Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/26.0 Mobile Safari/534.30",screen:{width:360,height:640},viewport:{width:360,height:640},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"Galaxy S III":{name:"Galaxy S III",userAgent:"Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/26.0 Mobile Safari/534.30",screen:{width:360,height:640},viewport:{width:360,height:640},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"Galaxy S5":{name:"Galaxy S5",userAgent:"Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:360,height:640},viewport:{width:360,height:640},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Galaxy S8":{name:"Galaxy S8",userAgent:"Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:360,height:740},viewport:{width:360,height:740},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Galaxy S9+":{name:"Galaxy S9+",userAgent:"Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:320,height:658},viewport:{width:320,height:658},deviceScaleFactor:4.5,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Galaxy S24":{name:"Galaxy S24",userAgent:"Mozilla/5.0 (Linux; Android 14; SM-S921U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:360,height:780},viewport:{width:360,height:780},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Galaxy A55":{name:"Galaxy A55",userAgent:"Mozilla/5.0 (Linux; Android 14; SM-A556B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:480,height:1040},viewport:{width:480,height:1040},deviceScaleFactor:2.25,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Galaxy Tab S4":{name:"Galaxy Tab S4",userAgent:"Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36",screen:{width:712,height:1138},viewport:{width:712,height:1138},deviceScaleFactor:2.25,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Galaxy Tab S9":{name:"Galaxy Tab S9",userAgent:"Mozilla/5.0 (Linux; Android 14; SM-X710) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36",screen:{width:640,height:1024},viewport:{width:640,height:1024},deviceScaleFactor:2.5,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"iPad (gen 5)":{name:"iPad (gen 5)",userAgent:"Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:768,height:1024},viewport:{width:768,height:1024},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPad (gen 6)":{name:"iPad (gen 6)",userAgent:"Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:768,height:1024},viewport:{width:768,height:1024},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPad (gen 7)":{name:"iPad (gen 7)",userAgent:"Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:810,height:1080},viewport:{width:810,height:1080},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPad (gen 11)":{name:"iPad (gen 11)",userAgent:"Mozilla/5.0 (iPad; CPU OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/19E241 Safari/604.1",screen:{width:656,height:944},viewport:{width:656,height:944},deviceScaleFactor:2.5,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPad Mini":{name:"iPad Mini",userAgent:"Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:768,height:1024},viewport:{width:768,height:1024},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPad Pro 11":{name:"iPad Pro 11",userAgent:"Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:834,height:1194},viewport:{width:834,height:1194},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 6":{name:"iPhone 6",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/26.0 Mobile/15A372 Safari/604.1",screen:{width:375,height:667},viewport:{width:375,height:667},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 6 Plus":{name:"iPhone 6 Plus",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/26.0 Mobile/15A372 Safari/604.1",screen:{width:414,height:736},viewport:{width:414,height:736},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 7":{name:"iPhone 7",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/26.0 Mobile/15A372 Safari/604.1",screen:{width:375,height:667},viewport:{width:375,height:667},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 7 Plus":{name:"iPhone 7 Plus",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/26.0 Mobile/15A372 Safari/604.1",screen:{width:414,height:736},viewport:{width:414,height:736},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 8":{name:"iPhone 8",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/26.0 Mobile/15A372 Safari/604.1",screen:{width:375,height:667},viewport:{width:375,height:667},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 8 Plus":{name:"iPhone 8 Plus",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/26.0 Mobile/15A372 Safari/604.1",screen:{width:414,height:736},viewport:{width:414,height:736},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone SE":{name:"iPhone SE",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/26.0 Mobile/14E304 Safari/602.1",screen:{width:320,height:568},viewport:{width:320,height:568},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone SE (3rd gen)":{name:"iPhone SE (3rd gen)",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/26.0 Mobile/19E241 Safari/602.1",screen:{width:375,height:667},viewport:{width:375,height:667},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone X":{name:"iPhone X",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/26.0 Mobile/15A372 Safari/604.1",screen:{width:375,height:812},viewport:{width:375,height:812},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone XR":{name:"iPhone XR",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:414,height:896},viewport:{width:414,height:896},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 11":{name:"iPhone 11",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:414,height:896},viewport:{width:414,height:715},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 11 Pro":{name:"iPhone 11 Pro",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:375,height:812},viewport:{width:375,height:635},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 11 Pro Max":{name:"iPhone 11 Pro Max",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:414,height:896},viewport:{width:414,height:715},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 12":{name:"iPhone 12",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:390,height:844},viewport:{width:390,height:664},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 12 Pro":{name:"iPhone 12 Pro",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:390,height:844},viewport:{width:390,height:664},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 12 Pro Max":{name:"iPhone 12 Pro Max",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:428,height:926},viewport:{width:428,height:746},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 12 Mini":{name:"iPhone 12 Mini",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:375,height:812},viewport:{width:375,height:629},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 13":{name:"iPhone 13",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:390,height:844},viewport:{width:390,height:664},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 13 Pro":{name:"iPhone 13 Pro",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:390,height:844},viewport:{width:390,height:664},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 13 Pro Max":{name:"iPhone 13 Pro Max",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:428,height:926},viewport:{width:428,height:746},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 13 Mini":{name:"iPhone 13 Mini",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:375,height:812},viewport:{width:375,height:629},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 14":{name:"iPhone 14",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:390,height:844},viewport:{width:390,height:664},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 14 Plus":{name:"iPhone 14 Plus",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:428,height:926},viewport:{width:428,height:746},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 14 Pro":{name:"iPhone 14 Pro",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:393,height:852},viewport:{width:393,height:660},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 14 Pro Max":{name:"iPhone 14 Pro Max",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:430,height:932},viewport:{width:430,height:740},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 15":{name:"iPhone 15",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:393,height:852},viewport:{width:393,height:659},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 15 Plus":{name:"iPhone 15 Plus",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:430,height:932},viewport:{width:430,height:739},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 15 Pro":{name:"iPhone 15 Pro",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:393,height:852},viewport:{width:393,height:659},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"iPhone 15 Pro Max":{name:"iPhone 15 Pro Max",userAgent:"Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",screen:{width:430,height:932},viewport:{width:430,height:739},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"Kindle Fire HDX":{name:"Kindle Fire HDX",userAgent:"Mozilla/5.0 (Linux; U; en-us; KFAPWI Build/JDQ39) AppleWebKit/535.19 (KHTML, like Gecko) Silk/3.13 Safari/535.19 Silk-Accelerated=true",screen:{width:800,height:1280},viewport:{width:800,height:1280},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"LG Optimus L70":{name:"LG Optimus L70",userAgent:"Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:384,height:640},viewport:{width:384,height:640},deviceScaleFactor:1.25,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Microsoft Lumia 550":{name:"Microsoft Lumia 550",userAgent:"Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36 Edge/14.14263",screen:{width:360,height:640},viewport:{width:360,height:640},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Microsoft Lumia 950":{name:"Microsoft Lumia 950",userAgent:"Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36 Edge/14.14263",screen:{width:360,height:640},viewport:{width:360,height:640},deviceScaleFactor:4,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Nexus 10":{name:"Nexus 10",userAgent:"Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36",screen:{width:800,height:1280},viewport:{width:800,height:1280},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Nexus 4":{name:"Nexus 4",userAgent:"Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:384,height:640},viewport:{width:384,height:640},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Nexus 5":{name:"Nexus 5",userAgent:"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:360,height:640},viewport:{width:360,height:640},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Nexus 5X":{name:"Nexus 5X",userAgent:"Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:412,height:732},viewport:{width:412,height:732},deviceScaleFactor:2.625,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Nexus 6":{name:"Nexus 6",userAgent:"Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:412,height:732},viewport:{width:412,height:732},deviceScaleFactor:3.5,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Nexus 6P":{name:"Nexus 6P",userAgent:"Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:412,height:732},viewport:{width:412,height:732},deviceScaleFactor:3.5,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Nexus 7":{name:"Nexus 7",userAgent:"Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36",screen:{width:600,height:960},viewport:{width:600,height:960},deviceScaleFactor:2,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Nokia Lumia 520":{name:"Nokia Lumia 520",userAgent:"Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 520)",screen:{width:320,height:533},viewport:{width:320,height:533},deviceScaleFactor:1.5,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Nokia N9":{name:"Nokia N9",userAgent:"Mozilla/5.0 (MeeGo; NokiaN9) AppleWebKit/534.13 (KHTML, like Gecko) NokiaBrowser/8.5.0 Mobile Safari/534.13",screen:{width:480,height:854},viewport:{width:480,height:854},deviceScaleFactor:1,isMobile:!0,hasTouch:!0,defaultBrowserType:"webkit"},"Pixel 2":{name:"Pixel 2",userAgent:"Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:411,height:731},viewport:{width:411,height:731},deviceScaleFactor:2.625,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Pixel 2 XL":{name:"Pixel 2 XL",userAgent:"Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:411,height:823},viewport:{width:411,height:823},deviceScaleFactor:3.5,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Pixel 3":{name:"Pixel 3",userAgent:"Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:393,height:786},viewport:{width:393,height:786},deviceScaleFactor:2.75,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Pixel 4":{name:"Pixel 4",userAgent:"Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:353,height:745},viewport:{width:353,height:745},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Pixel 4a (5G)":{name:"Pixel 4a (5G)",userAgent:"Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:412,height:892},viewport:{width:412,height:765},deviceScaleFactor:2.63,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Pixel 5":{name:"Pixel 5",userAgent:"Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:393,height:851},viewport:{width:393,height:727},deviceScaleFactor:2.75,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Pixel 7":{name:"Pixel 7",userAgent:"Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:412,height:915},viewport:{width:412,height:839},deviceScaleFactor:2.625,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Moto G4":{name:"Moto G4",userAgent:"Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Mobile Safari/537.36",screen:{width:360,height:640},viewport:{width:360,height:640},deviceScaleFactor:3,isMobile:!0,hasTouch:!0,defaultBrowserType:"chromium"},"Desktop Chrome HiDPI":{name:"Desktop Chrome HiDPI",userAgent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36",screen:{width:1792,height:1120},viewport:{width:1280,height:720},deviceScaleFactor:2,isMobile:!1,hasTouch:!1,defaultBrowserType:"chromium"},"Desktop Edge HiDPI":{name:"Desktop Edge HiDPI",userAgent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36 Edg/141.0.7390.16",screen:{width:1792,height:1120},viewport:{width:1280,height:720},deviceScaleFactor:2,isMobile:!1,hasTouch:!1,defaultBrowserType:"chromium"},"Desktop Firefox HiDPI":{name:"Desktop Firefox HiDPI",userAgent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:142.0.1) Gecko/20100101 Firefox/142.0.1",screen:{width:1792,height:1120},viewport:{width:1280,height:720},deviceScaleFactor:2,isMobile:!1,hasTouch:!1,defaultBrowserType:"firefox"},"Desktop Safari":{name:"Desktop Safari",userAgent:"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Safari/605.1.15",screen:{width:1792,height:1120},viewport:{width:1280,height:720},deviceScaleFactor:2,isMobile:!1,hasTouch:!1,defaultBrowserType:"webkit"},"Desktop Chrome":{name:"Desktop Chrome",displayName:"Playwright Chromium",userAgent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36",screen:{width:1920,height:1080},viewport:{width:1920,height:1080},deviceScaleFactor:1,isMobile:!1,hasTouch:!1,defaultBrowserType:"chromium"},"Desktop Chrome Medium Resolution":{name:"Desktop Chrome Medium Resolution",displayName:"Playwright Chromium",userAgent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36",screen:{width:1280,height:720},viewport:{width:1280,height:720},deviceScaleFactor:1,isMobile:!1,hasTouch:!1,defaultBrowserType:"chromium"},"Desktop Chrome (Branded)":{name:"Desktop Chrome (Branded)",displayName:"Google Chrome",userAgent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36",screen:{width:1920,height:1080},viewport:{width:1920,height:1080},deviceScaleFactor:1,isMobile:!1,hasTouch:!1,defaultBrowserType:"chromium",channel:"chrome"},"Desktop Chrome Medium Resolution (Branded)":{name:"Desktop Chrome Medium Resolution (Branded)",displayName:"Google Chrome",userAgent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36",screen:{width:1280,height:720},viewport:{width:1280,height:720},deviceScaleFactor:1,isMobile:!1,hasTouch:!1,defaultBrowserType:"chromium",channel:"chrome"},"Desktop Edge":{name:"Desktop Edge",userAgent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36 Edg/141.0.7390.16",screen:{width:1920,height:1080},viewport:{width:1280,height:720},deviceScaleFactor:1,isMobile:!1,hasTouch:!1,defaultBrowserType:"chromium"},"Desktop Edge (Branded)":{name:"Desktop Edge (Branded)",displayName:"Microsoft Edge",userAgent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36 Edg/141.0.7390.16",screen:{width:1920,height:1080},viewport:{width:1920,height:1080},deviceScaleFactor:1,isMobile:!1,hasTouch:!1,defaultBrowserType:"chromium",channel:"msedge"},"Desktop Edge Medium Resolution (Branded)":{name:"Desktop Edge Medium Resolution (Branded)",displayName:"Microsoft Edge",userAgent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.16 Safari/537.36 Edg/141.0.7390.16",screen:{width:1280,height:720},viewport:{width:1280,height:720},deviceScaleFactor:1,isMobile:!1,hasTouch:!1,defaultBrowserType:"chromium",channel:"msedge"},"Desktop Firefox":{name:"Desktop Firefox",userAgent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:142.0.1) Gecko/20100101 Firefox/142.0.1",screen:{width:1920,height:1080},viewport:{width:1280,height:720},deviceScaleFactor:1,isMobile:!1,hasTouch:!1,defaultBrowserType:"firefox"}},Fr={desktop:["Desktop Chrome","Desktop Chrome Medium Resolution","Desktop Chrome (Branded)","Desktop Chrome Medium Resolution (Branded)","Desktop Edge (Branded)","Desktop Edge Medium Resolution (Branded)","Desktop Safari"],mobile:["iPhone 15 Pro Max","iPhone 15 Pro","iPhone 15 Plus","iPhone 15","iPhone 14 Pro Max","iPhone 14 Pro","iPhone 14 Plus","iPhone 14","iPhone 13 Pro Max","iPhone 13 Pro","iPhone 13","iPhone 13 Mini","iPhone 12 Pro Max","iPhone 12 Pro","iPhone 12","iPhone 12 Mini","iPhone 11 Pro Max","iPhone 11 Pro","iPhone 11","iPhone XR","iPhone X","iPhone SE (3rd gen)","iPhone SE","iPhone 8 Plus","iPhone 8","iPhone 7 Plus","iPhone 7","iPhone 6 Plus","iPhone 6","Galaxy S24","Galaxy A55","Galaxy S9+","Galaxy S8","Galaxy S5","Galaxy Note 3","Galaxy Note II","Galaxy S III","Pixel 7","Pixel 5","Pixel 4a (5G)","Pixel 4","Pixel 3","Pixel 2 XL","Pixel 2","Nexus 6P","Nexus 6","Nexus 5X","Nexus 5","Nexus 4","Moto G4","LG Optimus L70","Microsoft Lumia 950","Microsoft Lumia 550","Nokia Lumia 520","Nokia N9","BlackBerry Z30"]},Ee=(e,t=!1)=>{let r=["chromium"];return t&&r.push("webkit"),Fr[e].map(s=>jr[s]).filter(s=>s.defaultBrowserType&&r.includes(s.defaultBrowserType))},to={desktop:{label:"Desktop",type:"desktop",devices:Ee("desktop")},mobile:{label:"Mobile Web",type:"mobile",devices:Ee("mobile")}},ro={desktop:{label:"Desktop",type:"desktop",devices:Ee("desktop",!0)},mobile:{label:"Mobile Web",type:"mobile",devices:Ee("mobile",!0)}}});var Br=_(()=>{"use strict"});function wt(){return{version:"1.0",entries:{}}}var Hr=_(()=>{"use strict";He()});var V,bt,Wr=_(()=>{"use strict";V=(e=>(e.DRAFT="DRAFT",e.STEP="STEP",e.ACTION="ACTION",e.IF_ELSE="IF_ELSE",e.WHILE_LOOP="WHILE_LOOP",e))(V||{}),bt=18e4});var Gr=_(()=>{"use strict"});var Kr=_(()=>{"use strict"});var zr=_(()=>{"use strict"});var Vr=_(()=>{"use strict";We()});var ce=_(()=>{"use strict";ur();dr();hr();fr();$r();Cr();Ue();ft();Nr();Ur();Br();Hr();He();Wr();Gr();Kr();zr();Vr();We()});import{stringify as no}from"yaml";import{createHash as xo}from"crypto";import{parse as To,stringify as ss}from"yaml";import{readFileSync as Po,existsSync as ko}from"fs";import{resolve as St,dirname as Eo}from"path";import{parse as qr,stringify as Ao}from"yaml";import{readFileSync as Co,writeFileSync as No,mkdirSync as Do}from"fs";import{dirname as jo}from"path";function oe(e){return e.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(/\n/g,"\\n").replace(/\r/g,"\\r").replace(/\t/g,"\\t")}function I(e){return e.replace(/\r\n/g," ").replace(/\n/g," ").replace(/\r/g," ").trim()}function oo(e){let t=e.frame_path;return!t||t.length===0?"page":`page.frameLocator('${t[0]}')`}function io(e){let t=e.xpath;return typeof t=="string"&&t.trim()?!t.startsWith("xpath=")&&!t.startsWith("/")&&!t.startsWith("//")?`xpath=//${t}`:t.startsWith("xpath=")?t:`xpath=${t}`:null}function Ke(e){let t=oo(e),r=e.locator;if(typeof r=="string"&&r.trim())return r=r.trim(),r.endsWith("first()")?`${t}.${r}`:`${t}.${r}.first()`;let s=io(e);if(s){let n=JSON.stringify(s);return`${t}.locator(${n}).first()`}return null}function Yr(e){let t=e.action_data?.action_name;return!t||(t==="verify"||t==="ai_assert"||t==="assert")&&e.action_data?.kwargs?.code?!1:ao.includes(t)}function lo(e){let t=e.action_data?.action_name;return!t||(t==="verify"||t==="ai_assert"||t==="assert")&&e.action_data?.kwargs?.code?!1:!co.includes(t)}function S(e,t){R.set(e,t)}function po(e){return R.get(e)}function ue(e,t,r=[]){let s=[...r];return t.locator?s.push(`locator: ${JSON.stringify(t.locator)}`):t.xpath&&s.push(`xpath: ${JSON.stringify(t.xpath)}`),t.frame_path&&t.frame_path.length>0&&s.push(`frame_path: ${JSON.stringify(t.frame_path)}`),s.length===0?[`await agent.execAction("${e}", page, {});`]:[`await agent.execAction("${e}", page, {`,...s.map(n=>` ${n},`),"});"]}function Jr(e){let t=e.functionName;if(!t)return null;let r=Array.isArray(e.args)?e.args.map(String):[];if(r.length===0)return`await ${t}()`;let s=["page","testContext","request","agent"],n=["undefined","null","true","false"],o=r.map(i=>s.includes(i)||n.includes(i)||/^-?\d+(\.\d+)?$/.test(i)?i:i.startsWith("$")?`agent.agentServices.readVariable('${i.substring(1)}')`:`"${i}"`);return`await ${t}(${o.join(", ")})`}function q(e,t,r,s="main"){let n=[];for(let o=0;o<e.length;o++){let i=e[o],a=`${s}.${o}`,l=uo(i,t,a,r);l.length>0&&(n.push(...l),o<e.length-1&&n.push(""))}return n}function uo(e,t,r,s){let n=" ".repeat(t);switch(e.type){case"DRAFT":return ho(e,t,r,s);case"ACTION":return fo(e,t,r,s);case"STEP":return go(e,t,r,s);case"IF_ELSE":return mo(e,t,r,s);case"WHILE_LOOP":return yo(e,t,r,s);default:return[`${n}// Unknown statement type: ${e.type}`]}}function ho(e,t,r,s){let n=" ".repeat(t),o=e.description?.trim()||"";if(!o)return[`${n}// ${r}: Skipping - no description`];if(s.noAgent)return[`${n}// ${r}: ${I(o)}`,`${n}// DRAFT: ${I(o)} (requires agent - skipped in hook)`];let i=JSON.stringify(o);return[`${n}// ${r}: ${I(o)}`,`${n}// \u26A0 DRAFT: AI-resolved at runtime (~5-10s). Add a locator to make this <1s.`,`${n}page = agent.agentServices.validatePage(page);`,`${n}await agent.run(page, ${i}, '${r}');`]}function fo(e,t,r,s){let n=" ".repeat(t),o=e.description,i=e.uid,l=s.actionEntityStore?.entries[e.uid]?.action_entity??e.action_entity;if(!l){if(!o)return[`${n}// ${r}: Skipping - no description`];if(s.noAgent)return[`${n}// ${r}: ${I(o)}`,`${n}// DRAFT: ${I(o)} (requires agent - skipped in hook)`];let y=JSON.stringify(o),P=!!e.use_pure_vision;return[`${n}// ${r}: ${I(o)}`,`${n}// \u26A0 DRAFT: AI-resolved at runtime (~5-10s). Add a locator to make this <1s.`,`${n}page = agent.agentServices.validatePage(page);`,`${n}await agent.execute(page, ${y}, '${r}', ${P});`]}let c=e.locator?{...l,locator:e.locator}:l;o&&o!==c.action_description&&(c={...c,action_description:o});let u=c.action_data?.action_name||"",d=c.action_description||"",f=po(u);if(!f)return[`${n}// ${r}: Unknown action: ${u}`];let g={imports:s.imports},h=f(c,r,g);if(s.noAgent){if(Yr(c))return[`${n}// ${r}: ${I(d)}`,`${n}// AI action: ${I(d)} (requires agent - skipped in hook)`];let y=wo(c,u,n,r);return y||[`${n}// ${r}: ${I(d)}`,...h.map(P=>`${n}${P}`)]}if(Yr(c))return[`${n}// ${r}: ${I(d)}`,`${n}page = agent.agentServices.validatePage(page);`,...h.map(y=>`${n}${y}`)];let p=JSON.stringify(d),v=h.map(y=>`${n} ${y}`),w=lo(c),m=i?`'${i}'`:"undefined";return[`${n}// ${r}: ${I(d)}`,`${n}page = agent.agentServices.validatePage(page);`,`${n}await agent.step(page, async () => {`,...v,`${n}}, ${p}, '${r}', ${m}, ${w});`]}function go(e,t,r,s){let n=" ".repeat(t),o=[];e.description&&e.description.trim()&&o.push(`${n}// Step: ${I(e.description)}`);let i=q(e.statements,t,s,r);return o.push(...i),o}function mo(e,t,r,s){let n=" ".repeat(t),o=[];if(o.push(`${n}// ${r}: Conditional check`),e.condition.type==="JS_CODE")o.push(`${n}if (${e.condition.expression}) {`);else{o.push(`${n}// AI Condition: ${I(e.condition.expression)}`);let a=JSON.stringify(e.condition.expression);o.push(`${n}if (await agent.evaluate(page, ${a}, "${r}")) {`)}let i=q(e.then,t+1,s,`${r}.then`);if(o.push(...i),e.else&&e.else.length>0){o.push(`${n}} else {`);let a=q(e.else,t+1,s,`${r}.else`);o.push(...a)}return o.push(`${n}}`),o}function yo(e,t,r,s){let n=" ".repeat(t),o=[];o.push(`${n}// ${r}: Loop`);let i=e.timeout_ms??bt,a=i/1e3,l=e.timeout_ms?`While loop exceeded timeout of ${a}s`:`While loop exceeded default timeout of ${a}s`,c=`loop_${r.replace(/\./g,"_")}`;if(o.push(`${n}const ${c}_start = Date.now();`),o.push(`${n}const ${c}_timeout = ${i};`),o.push(`${n}const ${c}_check = () => {`),o.push(`${n} if (Date.now() - ${c}_start > ${c}_timeout) {`),o.push(`${n} throw new Error('${l}');`),o.push(`${n} }`),o.push(`${n} return true;`),o.push(`${n}};`),e.condition.type==="JS_CODE")o.push(`${n}while (${c}_check() && (${e.condition.expression})) {`);else{o.push(`${n}// AI Loop Condition: ${I(e.condition.expression)}`);let d=JSON.stringify(e.condition.expression);o.push(`${n}while (${c}_check() && await agent.evaluate(page, ${d}, "${r}")) {`)}let u=q(e.body,t+1,s,`${r}.body`);return o.push(...u),o.push(`${n}}`),o}function wo(e,t,r,s){let n=e.action_description||"",o=e.action_data?.kwargs||{},i=o.timeout_ms??_t;switch(t){case"go_to_url":case"open_tab":{let a=o.url||"";return[`${r}// ${s}: ${I(n)}`,`${r}await page.goto(${JSON.stringify(a)}, { waitUntil: 'domcontentloaded' });`]}case"go_back":return[`${r}// ${s}: ${I(n)}`,`${r}await page.goBack();`];case"go_forward":return[`${r}// ${s}: ${I(n)}`,`${r}await page.goForward();`];case"input_text":{let a=o.text||"",l=Ke(e);return l?[`${r}// ${s}: ${I(n)}`,`${r}await ${l}.fill(${JSON.stringify(a)}, { timeout: ${i} });`]:null}case"select_dropdown_option":{let a=o.text||o.label||"",l=Ke(e);return l?[`${r}// ${s}: ${I(n)}`,`${r}await ${l}.selectOption({ label: ${JSON.stringify(a)} }, { timeout: ${i} });`]:null}default:return null}}function bo(e,t){let r=[],s=t?.version||"unknown";r.push(`// @generated by shiplightai v${s}`),r.push(...ts()),r.push(""),t?.use&&Object.keys(t.use).length>0&&(r.push(`test.use(${JSON.stringify(t.use,null,2)});`),r.push(""));let n=new Set,o={imports:n,actionEntityStore:t?.actionEntityStore};t?.beforeEach&&t.beforeEach.length>0&&(r.push(...Xr("beforeEach",t.beforeEach,o)),r.push(""));let i=t?.timeout||t?.skip!==void 0||t?.fail!==void 0||t?.only||t?.slow?{timeout:t.timeout,skip:t.skip,fail:t.fail,only:t.only,slow:t.slow}:void 0;if(t?.parameters&&t.parameters.length>0){let a=t?.testName||e.goal||"Generated test",l=vt(t?.tags);for(let c of t.parameters){let u=es(e,c.values);r.push(...Ve(u,`${l}${oe(a)} [${oe(c.name)}]`,o,0,i)),r.push("")}}else{let a=t?.testName||e.goal||"Generated test",l=vt(t?.tags);r.push(...Ve(e,`${l}${oe(a)}`,o,0,i))}return t?.afterEach&&t.afterEach.length>0&&(r.push(""),r.push(...Xr("afterEach",t.afterEach,o))),rs(r,n),r.join(`
210
+ `)}function vo(e,t){let r=[],s=t?.version||"unknown";r.push(`// @generated by shiplightai v${s}`),r.push(...ts()),r.push(""),t?.use&&Object.keys(t.use).length>0&&(r.push(`test.use(${JSON.stringify(t.use,null,2)});`),r.push(""));let n=new Set,o={imports:n,actionEntityStore:t?.actionEntityStore},i=t?.testName||"Test Suite",a=vt(t?.tags);r.push(`test.describe.serial('${a}${oe(i)}', () => {`),e.beforeAll&&e.beforeAll.length>0&&(r.push(...Ge("beforeAll",e.beforeAll,o,1)),r.push("")),e.beforeEach&&e.beforeEach.length>0&&(r.push(...Ge("beforeEach",e.beforeEach,o,1)),r.push(""));for(let c=0;c<e.tests.length;c++){let u=e.tests[c],d=u.timeout||u.skip!==void 0||u.fail!==void 0||u.only||u.slow?{timeout:u.timeout,skip:u.skip,fail:u.fail,only:u.only,slow:u.slow}:void 0;if(u.parameters&&u.parameters.length>0)for(let f of u.parameters){let g=es(u.testFlow,f.values);r.push(...Ve(g,`${oe(u.name)} [${oe(f.name)}]`,o,1,d)),r.push("")}else r.push(...Ve(u.testFlow,oe(u.name),o,1,d)),(c<e.tests.length-1||e.afterEach||e.afterAll)&&r.push("")}return e.afterEach&&e.afterEach.length>0&&(r.push(...Ge("afterEach",e.afterEach,o,1)),r.push("")),e.afterAll&&e.afterAll.length>0&&r.push(...Ge("afterAll",e.afterAll,o,1)),r.push("});"),rs(r,n),r.join(`
211
+ `)}function vt(e){return e&&e.length>0?e.map(t=>`@${t}`).join(" ")+" ":""}function ze(e){let t=new Set;function r(s){for(let n of s)switch(n.type){case V.ACTION:{let i=n.action_entity?.action_data?.kwargs;if(i?.args&&Array.isArray(i.args))for(let a of i.args)typeof a=="string"&&So.includes(a)&&t.add(a);break}case V.STEP:r(n.statements);break;case V.IF_ELSE:{let o=n;r(o.then),o.else&&r(o.else);break}case V.WHILE_LOOP:r(n.body);break}}return r(e),t}function _o(e){let t=ze(e.statements??[]);if(e.teardown)for(let r of ze(e.teardown))t.add(r);return t}function xt(e){return`{ ${["page","agent",...Array.from(e).sort()].join(", ")} }`}function Ve(e,t,r,s=0,n){let o=" ".repeat(s),i=[],a=_o(e),l=xt(a),c=n?.only?"test.only":"test";i.push(`${o}${c}('${t}', async (${l}) => {`),n?.skip===!0?i.push(`${o} test.skip();`):typeof n?.skip=="string"&&i.push(`${o} test.skip(true, '${oe(n.skip)}');`),n?.fail===!0?i.push(`${o} test.fail();`):typeof n?.fail=="string"&&i.push(`${o} test.fail(true, '${oe(n.fail)}');`),n?.slow&&i.push(`${o} test.slow();`),n?.timeout&&i.push(`${o} test.setTimeout(${n.timeout});`);let u=e.teardown&&e.teardown.length>0,d=s+1;if(u){if(i.push(`${o} try {`),e.statements&&e.statements.length>0){i.push(`${o} // Test steps`);let g=q(e.statements,d+1,r);i.push(...g)}i.push(`${o} } finally {`),i.push(`${o} // Teardown`);let f=q(e.teardown,d+1,r,"teardown");i.push(...f),i.push(`${o} }`)}else if(e.statements&&e.statements.length>0){i.push(`${o} // Test steps`);let f=q(e.statements,d,r);i.push(...f)}return i.push(`${o}});`),i}function Xr(e,t,r){let s=[],n=Qr(t),o=ze(n),i=xt(o);return s.push(`test.${e}(async (${i}) => {`),s.push(...q(n,1,r,e)),s.push("});"),s}function Ge(e,t,r,s){let n=" ".repeat(s),o=[],i=Qr(t);if(e==="beforeAll"||e==="afterAll"){let l={...r,noAgent:!0};o.push(`${n}test.${e}(async ({ browser }, workerInfo) => {`),o.push(`${n} const page = await browser.newPage({ baseURL: workerInfo.project.use.baseURL });`),o.push(...q(i,s+1,l,e)),o.push(`${n} await page.close();`),o.push(`${n}});`)}else{let l=ze(i),c=xt(l);o.push(`${n}test.${e}(async (${c}) => {`),o.push(...q(i,s+1,r,e)),o.push(`${n}});`)}return o}function Qr(e){let r=no({goal:"_hook",statements:e});return C(r).statements??[]}function es(e,t){let r=Pe(e);for(let[s,n]of Object.entries(t))r=r.split(`<<${s}>>`).join(String(n));return C(r)}function ts(){return["import { test, expect } from 'shiplightai/fixture';"]}function rs(e,t){if(t.size>0){let r=0;for(let n=0;n<e.length;n++)e[n].startsWith("import ")&&(r=n+1);let s=Array.from(t);e.splice(r,0,...s)}}function ns(e,t,r){let s={expandingPaths:new Set([St(t)]),depth:0,referencedPaths:new Set,basePath:r},n={...e};Array.isArray(n.statements)&&(n.statements=le(n.statements,t,s)),Array.isArray(n.teardown)&&(n.teardown=le(n.teardown,t,s));for(let o of["beforeAll","afterAll","beforeEach","afterEach"])Array.isArray(n[o])&&(n[o]=le(n[o],t,s));return{doc:n,referencedTemplatePaths:Array.from(s.referencedPaths)}}function le(e,t,r){let s=[];for(let n of e)if($o(n)){let o=Mo(n,t,r);s.push(o)}else s.push(Io(n,t,r));return s}function $o(e){return typeof e=="object"&&e!==null&&typeof e.template=="string"}function Mo(e,t,r){if(r.depth>=Zr)throw new Error(`Template expansion exceeded maximum depth of ${Zr}. Check for deeply nested or circular template references.`);let s=St(Eo(t),e.template),n=!ko(s)&&r.basePath?St(r.basePath,e.template):s;if(r.expandingPaths.has(n))throw new Error(`Circular template reference detected: ${n} is already being expanded. Stack: ${Array.from(r.expandingPaths).join(" \u2192 ")} \u2192 ${n}`);r.referencedPaths.add(n);let o;try{o=Po(n,"utf-8")}catch(h){throw new Error(`Failed to read template file: ${n} (referenced from ${t}): ${h.message}`)}let i=qr(o);if(!i||typeof i!="object")throw new Error(`Invalid template file: ${n} \u2014 expected a YAML object`);let a=i.params||[],l=e.params||{};for(let h of a)if(!(h in l))throw new Error(`Template ${e.template} requires param "${h}" but it was not provided. Required params: [${a.join(", ")}]`);let c=i.statements;if(!Array.isArray(c))throw new Error(`Template ${e.template} must have a "statements" array`);if(Object.keys(l).length>0){let p=Ao(c);for(let[v,w]of Object.entries(l))p=p.split(`<<${v}>>`).join(String(w));c=qr(p)}let u={expandingPaths:new Set([...r.expandingPaths,n]),depth:r.depth+1,referencedPaths:r.referencedPaths},d=le(c,n,u),g={STEP:i.name||e.template.replace(/\.yaml$/,"").split("/").pop()||e.template,template_path:e.template,statements:d};return Object.keys(l).length>0&&(g.template_params=l),g}function Io(e,t,r){if(typeof e!="object"||e===null)return e;let s={...e};return Array.isArray(s.statements)&&(s.statements=le(s.statements,t,r)),Array.isArray(s.THEN)&&(s.THEN=le(s.THEN,t,r)),Array.isArray(s.ELSE)&&(s.ELSE=le(s.ELSE,t,r)),Array.isArray(s.DO)&&(s.DO=le(s.DO,t,r)),s}function Pt(e,t,r){let s=To(e),n=s?.name,o=s?.tags,i=s?.use;if(s&&(s.name!==void 0||s.tags!==void 0||s.use!==void 0)&&(delete s.name,delete s.tags,delete s.use),s?.suite){if(s.goal||s.statements)throw new Tt('YAML file cannot have both "suite" and top-level "goal"/"statements". Use either suite format or single-test format.');return Lo(s,n,o,i,t,r)}return Oo(s,n,o,i,t,r)}function Oo(e,t,r,s,n,o){let i=e?.beforeEach,a=e?.afterEach,l=os(e?.parameters),c=e?.timeout,u=e?.skip,d=e?.fail,f=e?.only,g=e?.slow,h=e?.settings,p=s;if(h){let y={};h.auto_dismiss_modal!==void 0&&(y.autoDismissModal=!!h.auto_dismiss_modal),h.browser_timezone!==void 0&&h.browser_timezone!==null&&(y.timezoneId=String(h.browser_timezone)),h.browser_language!==void 0&&h.browser_language!==null&&(y.locale=String(h.browser_language)),h.extra_http_headers!==void 0&&h.extra_http_headers!==null&&typeof h.extra_http_headers=="object"&&(y.extraHTTPHeaders=h.extra_http_headers),Object.keys(y).length>0&&(p={...p,...y})}if(e&&(delete e.beforeEach,delete e.afterEach,delete e.parameters,delete e.timeout,delete e.skip,delete e.fail,delete e.only,delete e.slow,delete e.settings),e?.url)throw new Tt(`The "url" field is not supported in local YAML tests. Use "base_url: ${e.url}" and add "- URL: /" as the first statement instead.`);e&&!e.goal&&t&&(e.goal=t);let v=[];if(n&&e&&typeof e=="object"){let y=ns(e,n,o);e=y.doc,v=y.referencedTemplatePaths}let w=ss(e),m=C(w);return n&&(ye(m.statements??[],n,"main"),m.teardown&&ye(m.teardown,n,"teardown")),{testFlow:m,name:t,tags:r,use:p,beforeEach:i,afterEach:a,parameters:l,timeout:c,skip:u,fail:d,only:f,slow:g,referencedTemplatePaths:v}}function Lo(e,t,r,s,n,o){let i=e.suite;if(!Array.isArray(i.tests)||i.tests.length===0)throw new Error('Suite must have a non-empty "tests" array.');let a=i.beforeAll,l=i.afterAll,c=i.beforeEach,u=i.afterEach,d=[],f=i.tests.map(p=>{if(!p.name)throw new Error('Each test in a suite must have a "name" field.');if(!Array.isArray(p.statements)||p.statements.length===0)throw new Error(`Suite test "${p.name}" must have a non-empty "statements" array.`);let v={goal:p.name,statements:p.statements};p.teardown&&(v.teardown=p.teardown);let w=[],m=v;if(n&&typeof v=="object"){let A=ns(v,n,o);m=A.doc,w=A.referencedTemplatePaths,d.push(...w)}let y=ss(m),P=C(y),k=os(p.parameters);return{testFlow:P,name:p.name,tags:Array.isArray(p.tags)?p.tags:void 0,parameters:k,timeout:p.timeout,skip:p.skip,fail:p.fail,only:p.only,slow:p.slow}}),g=i.base_url,h=g?{...s,baseURL:g}:s;return{suite:{beforeAll:a,afterAll:l,beforeEach:c,afterEach:u,tests:f},name:t,tags:r,use:h,referencedTemplatePaths:d}}function os(e){if(!(!Array.isArray(e)||e.length===0))return e.map((t,r)=>{if(!t.name)throw new Error(`Parameter set at index ${r} must have a "name" field.`);if(!t.values||typeof t.values!="object")throw new Error(`Parameter set "${t.name}" must have a "values" object.`);return{name:t.name,values:t.values}})}function ye(e,t,r){for(let s=0;s<e.length;s++){let n=e[s],o=`${r}.${s}`,i=n.description||"";if(n.uid=Ro(t,o,i),n.type===V.STEP)ye(n.statements,t,o);else if(n.type===V.IF_ELSE){let a=n;ye(a.then,t,`${o}.then`),a.else&&ye(a.else,t,`${o}.else`)}else n.type===V.WHILE_LOOP&&ye(n.body,t,`${o}.body`)}}function Ro(e,t,r){let s=xo("sha256").update(`${e}:${t}:${r}`).digest("hex");return`${s.slice(0,8)}-${s.slice(8,12)}-${s.slice(12,16)}-${s.slice(16,20)}-${s.slice(20,32)}`}function is(e,t){let r;try{r=Co(e,"utf-8")}catch(s){return{valid:!1,errors:[`Failed to read file: ${s.message}`],warnings:[]}}return Fo(r,e,t)}function Fo(e,t,r){let s=/\btemplate:\s/.test(e),n=/^suite:/m.test(e),o=s||n?null:yt(e);if(o&&!o.valid)return{valid:!1,errors:o.errors,warnings:[],stats:o.stats};let i,a,l=[];try{let c=r?.parsed??Pt(e,t);l=c.referencedTemplatePaths;let u={version:r?.version,actionEntityStore:r?.actionEntityStore},d=c.testFlow?.baseURL?{...c.use,baseURL:c.testFlow.baseURL}:c.use;c.suite?i=vo(c.suite,{...u,testName:c.name,tags:c.tags,use:c.use}):i=bo(c.testFlow,{...u,testName:c.name,tags:c.tags,use:d,beforeEach:c.beforeEach,afterEach:c.afterEach,parameters:c.parameters,timeout:c.timeout,skip:c.skip,fail:c.fail,only:c.only,slow:c.slow});let f=i.split(`
642
212
  `).filter(g=>!g.startsWith("import ")).join(`
643
- `);new Function(f),a=t.replace(/\.test\.yaml$/,".yaml.spec.ts"),Wo(Go(a),{recursive:!0}),Ho(a,i)}catch(c){let u=c instanceof Tt?"":c.message.includes("Unexpected token")?" This usually means a YAML escaping issue \u2014 in double-quoted strings, use \\\\/ instead of \\/ for regex patterns, or use single quotes / block scalars.":" This may indicate a transpiler bug \u2014 please report it.";return{valid:!1,errors:[`Transpilation failed: ${c.message}.${u}`],warnings:[],stats:o?.stats??{total:0,action:0,draft:0,coverage:0},referencedTemplatePaths:l}}return{valid:!0,errors:[],warnings:o?.warnings??[],stats:o?.stats??{total:0,action:0,draft:0,coverage:0},specFile:a,referencedTemplatePaths:l}}var _t,fo,go,R,Ao,Qn,Tt,Pt=_(()=>{"use strict";ce();ce();ce();ce();_t=5e3;fo=["ai_action","ai_step","ai_assert","ai_extract","ai_wait_until","verify","assert"],go=["js_code","function","wait","wait_for_download_complete","wait_for_page_ready","extract_email_content","extract_activation_code"];R=new Map;S("click",e=>{let t=Ke(e);if(!t)return['await agent.execAction("click", page, {});'];let n=e.action_data?.kwargs?.timeout_ms??_t;return[`await ${t}.click({ timeout: ${n} });`]});S("click_element",R.get("click"));S("click_element_by_index",R.get("click"));S("double_click",e=>ue("double_click",e));S("double_click_on_element",R.get("double_click"));S("right_click",e=>ue("right_click",e));S("right_click_on_element",R.get("right_click"));S("hover",e=>ue("hover",e));S("hover_element_by_index",R.get("hover"));S("input_text",e=>{let t=e.action_data?.kwargs?.text??e.action_data?.kwargs?.value??"";return ue("input_text",e,[`action_data: { kwargs: { text: ${JSON.stringify(t)} } }`])});S("fill",R.get("input_text"));S("clear_input",e=>ue("clear_input",e));S("press",e=>{let t=e.action_data?.kwargs?.keys;return[`await page.keyboard.press(${JSON.stringify(t)});`]});S("send_keys",R.get("press"));S("send_keys_on_element",e=>{let t=Ke(e),n=e.action_data?.kwargs?.keys||"";if(!t)return['await agent.execAction("send_keys_on_element", page, {',` action_data: { kwargs: { keys: ${JSON.stringify(n)} } },`,"});"];let r=e.action_data?.kwargs?.timeout_ms??_t;return[`await ${t}.press(${JSON.stringify(n)}, { timeout: ${r} });`]});S("select_dropdown_option",e=>{let t=e.action_data?.kwargs?.text||e.action_data?.kwargs?.option||"";return ue("select_dropdown_option",e,[`action_data: { kwargs: { text: ${JSON.stringify(t)} } }`])});S("scroll",e=>{let t=e.action_data?.kwargs?.down??!0;return[`await page.evaluate('window.scrollBy(0, window.innerHeight * ${(e.action_data?.kwargs?.num_pages??1)*(t?1:-1)})');`]});S("scroll_down",R.get("scroll"));S("scroll_up",R.get("scroll"));S("scroll_element",R.get("scroll"));S("scroll_to_text",e=>{let t=e.action_data?.kwargs?.text||"";return[`await page.getByText(${JSON.stringify(t)}, { exact: false }).first().scrollIntoViewIfNeeded();`]});S("scroll_on_element",e=>ue("scroll_on_element",e,[`action_data: { kwargs: ${JSON.stringify(e.action_data?.kwargs||{})} }`]));S("go_to_url",e=>{let t=e.action_data?.kwargs?.url||"";return e.action_data?.kwargs?.new_tab===!0?['await agent.execAction("go_to_url", page, {',` action_data: { kwargs: { url: ${JSON.stringify(t)}, new_tab: true } },`,"});"]:['await agent.execAction("go_to_url", page, {',` action_data: { kwargs: { url: ${JSON.stringify(t)} } },`,"});"]});S("open_tab",R.get("go_to_url"));S("go_back",()=>['await agent.execAction("go_back", page, {});']);S("reload_page",()=>['await agent.execAction("reload_page", page, {});']);S("wait",e=>[`await page.waitForTimeout(${(e.action_data?.kwargs?.seconds||1)*1e3});`]);S("wait_for_page_ready",()=>["await page.waitForLoadState('domcontentloaded');"]);S("verify",(e,t)=>{let n=e.action_data?.kwargs,r=typeof n?.code=="string",s=r?n?.statement||e.action_description:e.action_description||n?.statement;if(r&&s){let i=n.code.split(`
644
- `),a=JSON.stringify(s);return["{ const _t = Date.now(); try {",...i.map(l=>` ${l}`),` console.log(\`[VERIFY:JS] \u2713 \${((Date.now()-_t)/1000).toFixed(1)}s: ${a}\`);`,"} catch (_e) {",` console.log(\`[VERIFY:JS\u2192AI] JS failed \${((Date.now()-_t)/1000).toFixed(1)}s: (\${_e instanceof Error ? _e.message : String(_e)}), falling back to AI: ${a}\`);`,` await agent.assert(page, ${a}, ${JSON.stringify(t||"")});`,"} }"]}return r?n.code.split(`
645
- `):s?[`await agent.assert(page, ${JSON.stringify(s)}, ${JSON.stringify(t||"")});`]:["// Skipping verify: missing statement or code"]});S("ai_assert",R.get("verify"));S("assert",R.get("verify"));S("ai_action",(e,t)=>{let n=e.action_data?.kwargs?.statement;if(!n)return["// Skipping ai_action: missing statement"];let r=JSON.stringify(n),s=e.action_data?.kwargs?.use_pure_vision;return[`await agent.execute(page, ${r}, '${t||""}', ${s});`]});S("ai_step",(e,t)=>{let n=e.action_data?.kwargs?.statement;return n?[`await agent.run(page, ${JSON.stringify(n)}, '${t||""}');`]:["// Skipping ai_step: missing statement"]});S("ai_extract",(e,t)=>{let n=e.action_data?.kwargs?.element_description,r=e.action_data?.kwargs?.variable_name;if(!n||!r)return["// Skipping ai_extract: missing element_description or variable_name"];let s=JSON.stringify(n),o=JSON.stringify(r);return[`await agent.extract(page, ${s}, ${o}, '${t||""}');`]});S("ai_wait_until",(e,t)=>{let n=e.action_data?.kwargs?.condition,r=e.action_data?.kwargs?.timeout_seconds||60;return n?[`await agent.waitUntilCondition(page, ${JSON.stringify(n)}, ${r}, '${t||""}');`]:["// Skipping ai_wait_until: missing condition"]});S("save_variable",e=>{let t=e.action_data?.kwargs?.name||"",n=e.action_data?.kwargs?.value;return['await agent.execAction("save_variable", page, {',` action_data: { kwargs: { name: ${JSON.stringify(t)}, value: ${JSON.stringify(n)} } },`,"});"]});S("js_code",e=>{let t=e.action_data?.kwargs?.code;if(!t)return["// Skipping js_code: missing code"];let n=["{"],r=t.split(`
646
- `);for(let s of r)n.push(` ${s}`);return n.push("}"),n});S("function",(e,t,n)=>{let r=e.action_data?.kwargs||{},s=r.functionName;if(s&&s.includes("#")){let[i,a]=s.split("#");if(i&&a){let l=i.replace(/\.(ts|js|mjs)$/,""),c=`import { ${a} } from '${l}';`;n?.imports?.add(c);let u={...r,functionName:a},d=qn(u);return d?[d.endsWith(";")?d:`${d};`]:["// Skipping function: invalid export pattern"]}}let o=qn(r);return o?[o.endsWith(";")?o:`${o};`]:["// Skipping function: missing functionName"]});S("generate_2fa_code",e=>{let t=e.action_data?.kwargs?.otp_secret_key||"";return['await agent.execAction("generate_2fa_code", page, {',` action_data: { kwargs: { otp_secret_key: ${JSON.stringify(t)} } },`,"});"]});S("upload_file",e=>{let t=e.action_data?.kwargs||{},n=[],r={};return t.paths?r.paths=t.paths:t.path&&(r.path=t.path),t.use_file_input&&(r.use_file_input=!0),n.push(`action_data: { kwargs: ${JSON.stringify(r)} }`),e.locator?n.push(`locator: ${JSON.stringify(e.locator)}`):e.xpath&&n.push(`xpath: ${JSON.stringify(e.xpath)}`),e.frame_path&&e.frame_path.length>0&&n.push(`frame_path: ${JSON.stringify(e.frame_path)}`),['await agent.execAction("upload_file", page, {',...n.map(s=>` ${s},`),"});"]});S("wait_for_download_complete",e=>['await agent.execAction("wait_for_download_complete", page, {',` action_data: { kwargs: { timeout_seconds: ${e.action_data?.kwargs?.timeout_seconds||10} } },`,"});"]);S("switch_tab",e=>['await agent.execAction("switch_tab", page, {',` action_data: { kwargs: { page_id: ${e.action_data?.kwargs?.page_id??e.action_data?.kwargs?.tab_index??0} } },`,"});"]);S("close_tab",e=>{let t=e.action_data?.kwargs?.page_id;return t=t??e.action_data?.kwargs?.index,['await agent.execAction("close_tab", page, {',` action_data: { kwargs: { page_id: ${t} } },`,"});"]});S("set_date_for_native_date_picker",e=>{let t=e.action_data?.kwargs?.date??"",n=[];return n.push(`action_data: { kwargs: { date: ${JSON.stringify(t)} } }`),e.locator?n.push(`locator: ${JSON.stringify(e.locator)}`):e.xpath&&n.push(`xpath: ${JSON.stringify(e.xpath)}`),e.frame_path&&e.frame_path.length>0&&n.push(`frame_path: ${JSON.stringify(e.frame_path)}`),['await agent.execAction("set_date_for_native_date_picker", page, {',...n.map(r=>` ${r},`),"});"]});S("done",()=>["// Done - no action needed"]);S("js_action",e=>{let t=e.action_data?.kwargs?.code;return t?t.split(`
647
- `):["// Skipping js_action: missing code"]});Ao=["testContext","request"];Qn=5;Tt=class extends Error{constructor(e){super(e),this.name="YamlValidationError"}}});import{Router as zo}from"express";import*as T from"fs/promises";import*as x from"path";import{stringify as Ye,parse as Ee}from"yaml";function de(e){if(!e||e.length===0)return[];let n=Ye({goal:"_hook",statements:e});return C(n).statements??[]}async function cr(e){try{let t=await T.readdir(e,{withFileTypes:!0});for(let n of t)if(!(n.name==="node_modules"||n.name.startsWith("."))&&(n.isFile()&&n.name.endsWith(".test.yaml")||n.isDirectory()&&await cr(x.join(e,n.name))))return!0}catch{}return!1}function Je(e){let t=2166136261;for(let n=0;n<e.length;n++)t^=e.charCodeAt(n),t=Math.imul(t,16777619)>>>0;return t%2147483647+1}async function Vo(e){let t=new Map,n;try{n=await T.readdir(e)}catch(r){if(r.code==="ENOENT")return t;throw r}for(let r of n.filter(s=>s.endsWith(".yaml")))try{let s=x.join(e,r),i=Ee(await T.readFile(s,"utf-8"))?.name??x.basename(r,".yaml");t.set(Je(i),`templates/${r}`)}catch{}return t}function Et(e,t){if(Array.isArray(e))return e.map(o=>Et(o,t));if(!e||typeof e!="object")return e;let n=e,r={};for(let[o,i]of Object.entries(n))r[o]=Et(i,t);let s=typeof n.reference_id=="number"?n.reference_id:void 0;if(s!==void 0&&typeof r.template_path!="string"){let o=t.get(s);o&&(r.template_path=o,delete r.reference_id)}return r}function At(e){return je({statements:e}).statements}function lr(e){let{initialDir:t,initialFile:n,projectRoot:r,onFileSelected:s}=e,o=zo();function i(){return x.join(r??t,"fixtures")}o.get("/api/files",async(l,c)=>{try{let u=typeof l.query.dir=="string"?l.query.dir:t,d=x.resolve(u),f=await T.readdir(d,{withFileTypes:!0}),g=[];for(let p of f)if(p.name!=="node_modules"&&!p.name.startsWith("."))if(p.isDirectory()){let v=x.join(d,p.name);await cr(v)&&g.push({name:p.name,type:"directory",path:v})}else p.isFile()&&p.name.endsWith(".test.yaml")&&g.push({name:p.name,type:"file",path:x.join(d,p.name)});g.sort((p,v)=>p.type!==v.type?p.type==="directory"?-1:1:p.name.localeCompare(v.name));let h=x.dirname(d);c.json({dir:d,parent:h!==d?h:null,entries:g,initialFile:n??null,projectRoot:r??t})}catch(u){console.error("[debugger] Error listing files:",u),c.status(500).json({error:u.message})}});function a(l){if(typeof l=="string"&&l){let c=x.resolve(l);return c.endsWith(".test.yaml")?{filePath:c}:{error:"File must be a .test.yaml file"}}return n?{filePath:n}:{error:"No file specified. Pass ?file= parameter."}}return o.get("/api/test-flow",async(l,c)=>{let u=a(l.query.file);if("error"in u)return c.status(400).json({error:u.error});let d=u.filePath;try{s?.(d);let f=await T.readFile(d,"utf-8"),g=await T.stat(d),h=Pe(f),p=kt(f,d,r);if(p.suite){let v=p.suite,w={tests:v.tests.map(y=>({name:y.name,statements:y.testFlow.statements??[],teardown:y.testFlow.teardown,skip:y.skip,timeout:y.timeout,fail:y.fail,only:y.only,slow:y.slow})),beforeAll:de(v.beforeAll),afterAll:de(v.afterAll),beforeEach:de(v.beforeEach),afterEach:de(v.afterEach)},m={version:"1.3.0",baseURL:p.use?.baseURL,testGroup:w};c.json({isSuite:!0,testFlow:m,metadata:h,name:p.name,tags:p.tags,use:p.use,filePath:d,fileName:x.basename(d),lastModified:g.mtimeMs})}else{let v=p.testFlow;Fe(f,v),c.json({isSuite:!1,testFlow:v,metadata:h,name:p.name,tags:p.tags,use:p.use,filePath:d,fileName:x.basename(d),lastModified:g.mtimeMs})}}catch(f){if(f.code==="ENOENT")return c.status(404).json({error:`File not found: ${d}`});console.error("[debugger] Error loading test flow:",f),c.status(500).json({error:f.message})}}),o.put("/api/test-flow",async(l,c)=>{try{let u=a(l.query.file);if("error"in u)return c.status(400).json({error:u.error});let d=u.filePath,{testFlow:f,metadata:g}=l.body;if(!f)return c.status(400).json({error:"testFlow is required"});let h=x.join(r??t,"templates"),p=await Vo(h),v=Et(f,p),w=ke(v,g),m=d+".tmp";await T.writeFile(m,w,"utf-8"),await T.rename(m,d);let y=await T.stat(d);c.json({success:!0,lastModified:y.mtimeMs})}catch(u){console.error("[debugger] Error saving test flow:",u),c.status(500).json({error:u.message})}}),o.get("/api/fixtures",async(l,c)=>{try{let u=i();try{let f=(await T.readdir(u,{withFileTypes:!0})).filter(g=>g.isFile()).map(g=>g.name).sort();c.json({files:f,dir:u})}catch(d){if(d.code==="ENOENT")c.json({files:[],dir:u});else throw d}}catch(u){console.error("[debugger] Error listing fixtures:",u),c.status(500).json({error:u.message})}}),o.post("/api/fixtures",async(l,c)=>{try{let u=i();await T.mkdir(u,{recursive:!0});let{name:d,content:f}=l.body;if(!d||!f)return c.status(400).json({error:"name and content are required"});let g=x.basename(d);if(!g)return c.status(400).json({error:"Invalid file name"});let h=x.join(u,g);await T.writeFile(h,Buffer.from(f,"base64")),c.json({fileName:g})}catch(u){console.error("[debugger] Error saving fixture:",u),c.status(500).json({error:u.message})}}),o.get("/api/functions",async(l,c)=>{let u=x.join(r??t,"helpers"),d;try{d=await T.readdir(u)}catch(p){return p.code==="ENOENT"?c.json([]):(console.error("[debugger] Error reading helpers dir:",p),c.status(500).json({error:p.message}))}let f=d.filter(p=>/\.(ts|js|mjs)$/.test(p)),g=[],h=1;for(let p of f){let v=await T.readFile(x.join(u,p),"utf-8"),w=/export\s+async\s+function\s+(\w+)\s*(\([^)]*\))/g,m;for(;(m=w.exec(v))!==null;){let[,y,k]=m,P=k.replace(/\s*:\s*[^,)]+/g,"");g.push({id:h++,name:`helpers/${p}#${y}`,description:"",status:"Active",code:`async function ${y}${P} {}`})}}c.json(g)}),o.get("/api/reusable-steps",async(l,c)=>{let u=x.join(r??t,"templates"),d;try{d=await T.readdir(u)}catch(h){return h.code==="ENOENT"?c.json([]):(console.error("[debugger] Error reading templates dir:",h),c.status(500).json({error:h.message}))}let f=d.filter(h=>h.endsWith(".yaml")).sort(),g=[];for(let h of f)try{let p=await T.readFile(x.join(u,h),"utf-8"),v=Ee(p);if(!v||typeof v!="object")continue;let w=v.name??x.basename(h,".yaml"),m=v.description??"",y=de(v.statements);g.push({id:Je(w),organizationId:"local",name:w,description:m,statements:y})}catch(p){console.error(`[debugger] Error parsing template ${h}:`,p)}c.json(g)}),o.post("/api/reusable-steps/exists/:name",async(l,c)=>{let u=x.join(r??t,"templates"),d=decodeURIComponent(l.params.name).toLowerCase(),f;try{f=await T.readdir(u)}catch(g){return g.code==="ENOENT"?c.json(!1):c.status(500).json({error:g.message})}for(let g of f.filter(h=>h.endsWith(".yaml")))try{let h=await T.readFile(x.join(u,g),"utf-8");if((Ee(h)?.name??x.basename(g,".yaml")).toLowerCase()===d)return c.json(!0)}catch{}c.json(!1)}),o.post("/api/reusable-steps",async(l,c)=>{let u=x.join(r??t,"templates"),{reusableStep:d}=l.body??{};if(!d?.name||!Array.isArray(d.statements))return c.status(400).json({error:"reusableStep.name and reusableStep.statements are required"});await T.mkdir(u,{recursive:!0});let f=d.name.replace(/[/\\:*?"<>|]/g,"-").trim(),g=x.join(u,`${f}.yaml`),h={name:d.name,statements:At(d.statements)};d.description&&(h.description=d.description),await T.writeFile(g,Ye(h),"utf-8"),c.json({id:Je(d.name),organizationId:"local",name:d.name,description:d.description??"",statements:d.statements})}),o.put("/api/reusable-steps/:id/update",async(l,c)=>{let u=parseInt(l.params.id,10),d=x.join(r??t,"templates"),f;try{f=await T.readdir(d)}catch(g){return g.code==="ENOENT"?c.status(404).json({error:"Templates directory not found"}):c.status(500).json({error:g.message})}for(let g of f.filter(h=>h.endsWith(".yaml")))try{let h=x.join(d,g),p=Ee(await T.readFile(h,"utf-8"))??{},v=p.name??x.basename(g,".yaml");if(Je(v)!==u)continue;let w=l.body??{};return w.description!==void 0&&(p.description=w.description),w.statements!==void 0&&(p.statements=At(w.statements)),await T.writeFile(h,Ye(p),"utf-8"),c.json({id:u,organizationId:"local",...p,statements:w.statements!==void 0?w.statements:de(p.statements)})}catch{}c.status(404).json({error:`Template with id ${u} not found`})}),o.put("/api/templates/:name",async(l,c)=>{let u=x.join(r??t,"templates"),d=decodeURIComponent(l.params.name),f;try{f=await T.readdir(u)}catch(g){return g.code==="ENOENT"?c.status(404).json({error:"Templates directory not found"}):c.status(500).json({error:g.message})}for(let g of f.filter(h=>h.endsWith(".yaml")))try{let h=x.join(u,g),p=Ee(await T.readFile(h,"utf-8"))??{};if((p.name??x.basename(g,".yaml"))!==d)continue;let w=l.body??{};return w.name!==void 0&&(p.name=w.name),w.description!==void 0&&(p.description=w.description),w.statements!==void 0&&(p.statements=At(w.statements)),await T.writeFile(h,Ye(p),"utf-8"),c.json({organizationId:"local",...p,statements:w.statements!==void 0?w.statements:de(p.statements)})}catch{}c.status(404).json({error:`Template "${d}" not found`})}),o.get("/api/test-results",async(l,c)=>{let u=a(l.query.file);if("error"in u)return c.status(400).json({error:u.error});let d=r??t,f=x.basename(u.filePath,".test.yaml"),g=x.join(d,".shiplight","artifacts",f),h=[],p=null;try{let v=await T.readdir(g,{withFileTypes:!0});for(let w of v)if(w.isDirectory()){let m=x.join(g,w.name),y=await T.readdir(m);for(let k of y.filter(P=>/\.(png|jpe?g|webp)$/i.test(P))){let P=x.relative(g,x.join(m,k)),E=w.name.match(/^(.+?)_(before|after)$/),Se=E?E[1]:w.name,Oe=E?E[2]:"screenshot";h.push({url:`/api/report-assets/${f}/${P}`,label:Oe,stepId:Se})}}else/\.(webm|mp4)$/i.test(w.name)&&(p||(p=`/api/report-assets/${f}/${w.name}`));h.sort((w,m)=>w.stepId!==m.stepId?(w.stepId??"").localeCompare(m.stepId??""):w.label.localeCompare(m.label))}catch{}if(h.length===0&&!p)return c.status(404).json({error:"No test results found"});c.json({videoPath:p,screenshots:h})}),o}var pr=_(()=>{"use strict";ce();Pt()});import*as fr from"http";import*as gr from"net";import*as Z from"path";import*as mr from"fs";import{randomUUID as Yo}from"crypto";function dr(e){return new Promise((t,n)=>{if(e.body&&typeof e.body=="object")try{t(Buffer.from(JSON.stringify(e.body),"utf-8"));return}catch{}if(e.readableEnded){t(Buffer.alloc(0));return}let r=[];e.on("data",s=>r.push(s)),e.on("end",()=>t(Buffer.concat(r))),e.on("error",n)})}function hr(e,t,n,r,s,o){return new Promise(i=>{let a=Array.isArray(o)?o[0]:o,l=fr.request({hostname:n,port:r,path:e.originalUrl,method:e.method,headers:{"content-type":a||"application/json","content-length":String(s.length)},timeout:3e5},c=>{t.writeHead(c.statusCode??502,c.headers),c.on("data",u=>t.write(u)),c.on("end",()=>{t.end(),i()}),c.on("error",()=>{t.writableEnded||t.end(),i()})});l.on("error",c=>{t.headersSent?t.writableEnded||t.end():t.status(502).json({status:"error",message:"Inner server unavailable: "+c.message}),i()}),l.end(s)})}function $t(e,t){try{(!("destroyed"in e)||!e.destroyed)&&(e.write(t),e.destroy())}catch{}}var ur,qe,yr=_(()=>{"use strict";ut();ur=1e4,qe=class{sessions=new Map;byYamlPath=new Map;options;spawner;headed;constructor(t={}){this.options=t,this.spawner=t.spawner??pt,this.headed=t.headed??!1}log(t){this.options.onLog?this.options.onLog(t):console.error(t)}notifyStateChange(t){try{this.options.onSessionStateChange?.({...t})}catch(n){this.log(`[manager] onSessionStateChange listener threw: ${n.message}`)}}openSession(t){let n=Z.resolve(t),r=this.byYamlPath.get(n);if(r){let c=this.sessions.get(r);if(c&&c.session.status!=="ended")return{...c.session};this.byYamlPath.delete(n)}if(!mr.existsSync(n))throw new Error(`YAML file not found: ${n}`);if(!xe(Z.dirname(n)))throw new Error(`No Playwright config found for ${n} (searched parents for playwright.config.{ts,js,mjs}).`);let o=`dbg-${Yo().slice(0,8)}`,i=new Date().toISOString(),a={sessionId:o,yamlPath:n,innerPort:0,innerHost:"127.0.0.1",pid:0,startedAt:i,status:"idle",exitInfo:null},l={session:a,cleanup:async()=>{},readyPromise:Promise.resolve()};return this.sessions.set(o,l),this.byYamlPath.set(n,o),this.notifyStateChange(a),this.log(`[manager] session ${o} created (idle) for ${Z.basename(n)}`),{...a}}async startSandbox(t){let n=this.sessions.get(t);if(!n)throw new Error(`No session ${t} to start sandbox for`);if(n.session.status==="running"||n.session.status==="starting"){n.readyPromise&&await n.readyPromise;return}if(n.session.status==="ended")throw new Error(`Session ${t} has ended`);let r=n.session.yamlPath,s=xe(Z.dirname(r));if(!s)throw new Error(`No Playwright config found for ${r} (searched parents for playwright.config.{ts,js,mjs}).`);n.session.status="starting",this.notifyStateChange(n.session);let o=(async()=>{let i=ur+18e4,a,l=new Promise((d,f)=>{a=setTimeout(()=>f(new Error(`Timed out after ${i/1e3}s waiting for inner playwright to register on a port.`)),i)}),c;try{c=await Promise.race([this.spawner({yamlFilePath:r,configPath:s,tempSuffix:t,headed:this.headed}),l])}finally{a&&clearTimeout(a)}n.session.innerPort=c.port,n.session.innerHost=c.host,n.session.pid=c.pid,n.session.status="running",n.cleanup=c.cleanup;let u=d=>{n.session.status!=="ended"&&(n.session.status="ended",n.session.exitInfo=d,this.notifyStateChange(n.session))};n.livenessTimer=this.startLivenessProbe(t,u),this.notifyStateChange(n.session),this.log(`[manager] session ${t} running on ${n.session.innerHost}:${n.session.innerPort} (yaml=${Z.basename(r)})`)})();n.readyPromise=o;try{await o}catch(i){throw n.session.status="ended",n.session.exitInfo=i.message,this.notifyStateChange(n.session),i}}async stopSandbox(t){let n=this.sessions.get(t);if(n&&n.session.status!=="idle"&&n.session.status!=="ended"){this.log(`[manager] stopSandbox ${t}`),n.livenessTimer&&(clearInterval(n.livenessTimer),n.livenessTimer=void 0);try{await n.cleanup()}catch(r){this.log(`[manager] stopSandbox ${t} cleanup error: ${r.message}`)}n.session.innerPort=0,n.session.innerHost="127.0.0.1",n.session.pid=0,n.session.status="idle",n.session.exitInfo=null,n.cleanup=async()=>{},n.readyPromise=Promise.resolve(),this.notifyStateChange(n.session)}}startLivenessProbe(t,n){let o=0,i=setInterval(()=>{let a=this.sessions.get(t);if(!a||a.session.status==="ended"){clearInterval(i);return}let l=!1;try{process.kill(a.session.pid,0),l=!0}catch(c){c?.code!=="ESRCH"&&(l=!0)}l?o=0:(o+=1,o>=3&&(clearInterval(i),n("process-exited")))},3e3);return i.unref(),i}async closeSession(t){let n=this.sessions.get(t);if(n){this.log(`[manager] closeSession ${t} (status=${n.session.status})`),this.sessions.delete(t),this.byYamlPath.get(n.session.yamlPath)===t&&this.byYamlPath.delete(n.session.yamlPath),n.livenessTimer&&(clearInterval(n.livenessTimer),n.livenessTimer=void 0),n.session.status!=="ended"&&(n.session.status="ended",n.session.exitInfo="SIGTERM",this.notifyStateChange(n.session));try{await n.cleanup()}catch(r){this.log(`[manager] closeSession ${t} cleanup error: ${r.message}`)}}}async restartInner(t){let n=this.sessions.get(t);if(!n)throw new Error(`No session ${t} to restart`);if(n.restartInProgress)throw new Error(`Restart already in progress for session ${t}`);let r=n.session.yamlPath,s=xe(Z.dirname(r));if(!s)throw new Error(`No Playwright config found for ${r} (searched parents for playwright.config.{ts,js,mjs}).`);n.restartInProgress=!0;try{n.livenessTimer&&(clearInterval(n.livenessTimer),n.livenessTimer=void 0);try{await n.cleanup()}catch(c){this.log(`[manager] restartInner ${t} old cleanup error: ${c.message}`)}n.session.status="starting",n.session.innerPort=0,n.session.pid=0,this.notifyStateChange(n.session);let o=ur+18e4,i,a=new Promise((c,u)=>{i=setTimeout(()=>u(new Error(`Timed out after ${o/1e3}s waiting for inner playwright to respawn.`)),o)}),l;try{l=await Promise.race([this.spawner({yamlFilePath:r,configPath:s,tempSuffix:t,headed:this.headed}),a])}catch(c){throw n.session.status="ended",n.session.exitInfo=c.message,this.notifyStateChange(n.session),c}finally{i&&clearTimeout(i)}n.session.innerPort=l.port,n.session.innerHost=l.host,n.session.pid=l.pid,n.session.status="running",n.cleanup=l.cleanup,n.livenessTimer=this.startLivenessProbe(t,c=>{n.session.status!=="ended"&&(n.session.status="ended",n.session.exitInfo=c,this.notifyStateChange(n.session))}),this.notifyStateChange(n.session),this.log(`[manager] session ${t} restarted on ${n.session.innerHost}:${n.session.innerPort} (yaml=${Z.basename(r)})`)}finally{n.restartInProgress=!1}}listSessions(){return Array.from(this.sessions.values()).map(t=>({...t.session}))}getSession(t){let n=this.sessions.get(t);return n?{...n.session}:void 0}httpProxyFor(t,n={}){let{liveviewUrlBuilder:r}=n;return async(s,o,i)=>{let a=this.sessions.get(t);if(!a){o.status(404).json({status:"error",message:"Session not found"});return}if(a.session.status==="ended"){o.status(410).json({status:"error",message:"Session has ended",exitInfo:a.session.exitInfo});return}if(a.session.status==="idle"){o.status(503).json({status:"error",message:"Sandbox not started"});return}let l=`${s.method} ${s.path}`;if(l==="POST /api/int-runner/create-session"){let u=await dr(s),d={};if(u.length)try{d=JSON.parse(u.toString("utf-8"))}catch{o.status(400).json({status:"error",message:"Invalid JSON body"});return}d.testFilePath=a.session.yamlPath,await hr(s,o,a.session.innerHost,a.session.innerPort,Buffer.from(JSON.stringify(d),"utf-8"),"application/json");return}if(l==="POST /api/int-runner/terminate-session"){try{await this.stopSandbox(t),o.json({status:"success",details:"Sandbox stopped"})}catch(u){o.status(500).json({status:"error",message:u.message})}return}if(l==="POST /api/int-runner/liveview-url"){let u=r?.(s)??"";o.json({liveviewUrl:u,browserWsUrl:""});return}let c=await dr(s);await hr(s,o,a.session.innerHost,a.session.innerPort,c,s.headers["content-type"])}}wsUpgradeFor(t){return async(n,r,s,o)=>{let i=this.sessions.get(t);if(!i){$t(r,`HTTP/1.1 404 Not Found\r
213
+ `);new Function(f),a=t.replace(/\.test\.yaml$/,".yaml.spec.ts"),Do(jo(a),{recursive:!0}),No(a,i)}catch(c){let u=c instanceof Tt?"":c.message.includes("Unexpected token")?" This usually means a YAML escaping issue \u2014 in double-quoted strings, use \\\\/ instead of \\/ for regex patterns, or use single quotes / block scalars.":" This may indicate a transpiler bug \u2014 please report it.";return{valid:!1,errors:[`Transpilation failed: ${c.message}.${u}`],warnings:[],stats:o?.stats??{total:0,action:0,draft:0,coverage:0},referencedTemplatePaths:l}}return{valid:!0,errors:[],warnings:o?.warnings??[],stats:o?.stats??{total:0,action:0,draft:0,coverage:0},specFile:a,referencedTemplatePaths:l}}var _t,ao,co,R,So,Zr,Tt,kt=_(()=>{"use strict";ce();ce();ce();ce();_t=5e3;ao=["ai_action","ai_step","ai_assert","ai_extract","ai_wait_until","verify","assert"],co=["js_code","function","wait","wait_for_download_complete","wait_for_page_ready","extract_email_content","extract_activation_code"];R=new Map;S("click",e=>{let t=Ke(e);if(!t)return['await agent.execAction("click", page, {});'];let r=e.action_data?.kwargs?.timeout_ms??_t;return[`await ${t}.click({ timeout: ${r} });`]});S("click_element",R.get("click"));S("click_element_by_index",R.get("click"));S("double_click",e=>ue("double_click",e));S("double_click_on_element",R.get("double_click"));S("right_click",e=>ue("right_click",e));S("right_click_on_element",R.get("right_click"));S("hover",e=>ue("hover",e));S("hover_element_by_index",R.get("hover"));S("input_text",e=>{let t=e.action_data?.kwargs?.text??e.action_data?.kwargs?.value??"";return ue("input_text",e,[`action_data: { kwargs: { text: ${JSON.stringify(t)} } }`])});S("fill",R.get("input_text"));S("clear_input",e=>ue("clear_input",e));S("press",e=>{let t=e.action_data?.kwargs?.keys;return[`await page.keyboard.press(${JSON.stringify(t)});`]});S("send_keys",R.get("press"));S("send_keys_on_element",e=>{let t=Ke(e),r=e.action_data?.kwargs?.keys||"";if(!t)return['await agent.execAction("send_keys_on_element", page, {',` action_data: { kwargs: { keys: ${JSON.stringify(r)} } },`,"});"];let s=e.action_data?.kwargs?.timeout_ms??_t;return[`await ${t}.press(${JSON.stringify(r)}, { timeout: ${s} });`]});S("select_dropdown_option",e=>{let t=e.action_data?.kwargs?.text||e.action_data?.kwargs?.option||"";return ue("select_dropdown_option",e,[`action_data: { kwargs: { text: ${JSON.stringify(t)} } }`])});S("scroll",e=>{let t=e.action_data?.kwargs?.down??!0;return[`await page.evaluate('window.scrollBy(0, window.innerHeight * ${(e.action_data?.kwargs?.num_pages??1)*(t?1:-1)})');`]});S("scroll_down",R.get("scroll"));S("scroll_up",R.get("scroll"));S("scroll_element",R.get("scroll"));S("scroll_to_text",e=>{let t=e.action_data?.kwargs?.text||"";return[`await page.getByText(${JSON.stringify(t)}, { exact: false }).first().scrollIntoViewIfNeeded();`]});S("scroll_on_element",e=>ue("scroll_on_element",e,[`action_data: { kwargs: ${JSON.stringify(e.action_data?.kwargs||{})} }`]));S("go_to_url",e=>{let t=e.action_data?.kwargs?.url||"";return e.action_data?.kwargs?.new_tab===!0?['await agent.execAction("go_to_url", page, {',` action_data: { kwargs: { url: ${JSON.stringify(t)}, new_tab: true } },`,"});"]:['await agent.execAction("go_to_url", page, {',` action_data: { kwargs: { url: ${JSON.stringify(t)} } },`,"});"]});S("open_tab",R.get("go_to_url"));S("go_back",()=>['await agent.execAction("go_back", page, {});']);S("reload_page",()=>['await agent.execAction("reload_page", page, {});']);S("wait",e=>[`await page.waitForTimeout(${(e.action_data?.kwargs?.seconds||1)*1e3});`]);S("wait_for_page_ready",()=>["await page.waitForLoadState('domcontentloaded');"]);S("verify",(e,t)=>{let r=e.action_data?.kwargs,s=typeof r?.code=="string",n=s?r?.statement||e.action_description:e.action_description||r?.statement;if(s&&n){let i=r.code.split(`
214
+ `),a=JSON.stringify(n);return["{ const _t = Date.now(); try {",...i.map(l=>` ${l}`),` console.log(\`[VERIFY:JS] \u2713 \${((Date.now()-_t)/1000).toFixed(1)}s: ${a}\`);`,"} catch (_e) {",` console.log(\`[VERIFY:JS\u2192AI] JS failed \${((Date.now()-_t)/1000).toFixed(1)}s: (\${_e instanceof Error ? _e.message : String(_e)}), falling back to AI: ${a}\`);`,` await agent.assert(page, ${a}, ${JSON.stringify(t||"")});`,"} }"]}return s?r.code.split(`
215
+ `):n?[`await agent.assert(page, ${JSON.stringify(n)}, ${JSON.stringify(t||"")});`]:["// Skipping verify: missing statement or code"]});S("ai_assert",R.get("verify"));S("assert",R.get("verify"));S("ai_action",(e,t)=>{let r=e.action_data?.kwargs?.statement;if(!r)return["// Skipping ai_action: missing statement"];let s=JSON.stringify(r),n=e.action_data?.kwargs?.use_pure_vision;return[`await agent.execute(page, ${s}, '${t||""}', ${n});`]});S("ai_step",(e,t)=>{let r=e.action_data?.kwargs?.statement;return r?[`await agent.run(page, ${JSON.stringify(r)}, '${t||""}');`]:["// Skipping ai_step: missing statement"]});S("ai_extract",(e,t)=>{let r=e.action_data?.kwargs?.element_description,s=e.action_data?.kwargs?.variable_name;if(!r||!s)return["// Skipping ai_extract: missing element_description or variable_name"];let n=JSON.stringify(r),o=JSON.stringify(s);return[`await agent.extract(page, ${n}, ${o}, '${t||""}');`]});S("ai_wait_until",(e,t)=>{let r=e.action_data?.kwargs?.condition,s=e.action_data?.kwargs?.timeout_seconds||60;return r?[`await agent.waitUntilCondition(page, ${JSON.stringify(r)}, ${s}, '${t||""}');`]:["// Skipping ai_wait_until: missing condition"]});S("save_variable",e=>{let t=e.action_data?.kwargs?.name||"",r=e.action_data?.kwargs?.value;return['await agent.execAction("save_variable", page, {',` action_data: { kwargs: { name: ${JSON.stringify(t)}, value: ${JSON.stringify(r)} } },`,"});"]});S("js_code",e=>{let t=e.action_data?.kwargs?.code;if(!t)return["// Skipping js_code: missing code"];let r=["{"],s=t.split(`
216
+ `);for(let n of s)r.push(` ${n}`);return r.push("}"),r});S("function",(e,t,r)=>{let s=e.action_data?.kwargs||{},n=s.functionName;if(n&&n.includes("#")){let[i,a]=n.split("#");if(i&&a){let l=i.replace(/\.(ts|js|mjs)$/,""),c=`import { ${a} } from '${l}';`;r?.imports?.add(c);let u={...s,functionName:a},d=Jr(u);return d?[d.endsWith(";")?d:`${d};`]:["// Skipping function: invalid export pattern"]}}let o=Jr(s);return o?[o.endsWith(";")?o:`${o};`]:["// Skipping function: missing functionName"]});S("generate_2fa_code",e=>{let t=e.action_data?.kwargs?.otp_secret_key||"";return['await agent.execAction("generate_2fa_code", page, {',` action_data: { kwargs: { otp_secret_key: ${JSON.stringify(t)} } },`,"});"]});S("upload_file",e=>{let t=e.action_data?.kwargs||{},r=[],s={};return t.paths?s.paths=t.paths:t.path&&(s.path=t.path),t.use_file_input&&(s.use_file_input=!0),r.push(`action_data: { kwargs: ${JSON.stringify(s)} }`),e.locator?r.push(`locator: ${JSON.stringify(e.locator)}`):e.xpath&&r.push(`xpath: ${JSON.stringify(e.xpath)}`),e.frame_path&&e.frame_path.length>0&&r.push(`frame_path: ${JSON.stringify(e.frame_path)}`),['await agent.execAction("upload_file", page, {',...r.map(n=>` ${n},`),"});"]});S("wait_for_download_complete",e=>['await agent.execAction("wait_for_download_complete", page, {',` action_data: { kwargs: { timeout_seconds: ${e.action_data?.kwargs?.timeout_seconds||10} } },`,"});"]);S("switch_tab",e=>['await agent.execAction("switch_tab", page, {',` action_data: { kwargs: { page_id: ${e.action_data?.kwargs?.page_id??e.action_data?.kwargs?.tab_index??0} } },`,"});"]);S("close_tab",e=>{let t=e.action_data?.kwargs?.page_id;return t=t??e.action_data?.kwargs?.index,['await agent.execAction("close_tab", page, {',` action_data: { kwargs: { page_id: ${t} } },`,"});"]});S("set_date_for_native_date_picker",e=>{let t=e.action_data?.kwargs?.date??"",r=[];return r.push(`action_data: { kwargs: { date: ${JSON.stringify(t)} } }`),e.locator?r.push(`locator: ${JSON.stringify(e.locator)}`):e.xpath&&r.push(`xpath: ${JSON.stringify(e.xpath)}`),e.frame_path&&e.frame_path.length>0&&r.push(`frame_path: ${JSON.stringify(e.frame_path)}`),['await agent.execAction("set_date_for_native_date_picker", page, {',...r.map(s=>` ${s},`),"});"]});S("done",()=>["// Done - no action needed"]);S("js_action",e=>{let t=e.action_data?.kwargs?.code;return t?t.split(`
217
+ `):["// Skipping js_action: missing code"]});So=["testContext","request"];Zr=5;Tt=class extends Error{constructor(e){super(e),this.name="YamlValidationError"}}});import{Router as Uo}from"express";import*as T from"fs/promises";import*as x from"path";import{stringify as Ye,parse as Ae}from"yaml";function de(e){if(!e||e.length===0)return[];let r=Ye({goal:"_hook",statements:e});return C(r).statements??[]}async function as(e){try{let t=await T.readdir(e,{withFileTypes:!0});for(let r of t)if(!(r.name==="node_modules"||r.name.startsWith("."))&&(r.isFile()&&r.name.endsWith(".test.yaml")||r.isDirectory()&&await as(x.join(e,r.name))))return!0}catch{}return!1}function Je(e){let t=2166136261;for(let r=0;r<e.length;r++)t^=e.charCodeAt(r),t=Math.imul(t,16777619)>>>0;return t%2147483647+1}async function Bo(e){let t=new Map,r;try{r=await T.readdir(e)}catch(s){if(s.code==="ENOENT")return t;throw s}for(let s of r.filter(n=>n.endsWith(".yaml")))try{let n=x.join(e,s),i=Ae(await T.readFile(n,"utf-8"))?.name??x.basename(s,".yaml");t.set(Je(i),`templates/${s}`)}catch{}return t}function At(e,t){if(Array.isArray(e))return e.map(o=>At(o,t));if(!e||typeof e!="object")return e;let r=e,s={};for(let[o,i]of Object.entries(r))s[o]=At(i,t);let n=typeof r.reference_id=="number"?r.reference_id:void 0;if(n!==void 0&&typeof s.template_path!="string"){let o=t.get(n);o&&(s.template_path=o,delete s.reference_id)}return s}function Et(e){return je({statements:e}).statements}function cs(e){let{initialDir:t,initialFile:r,projectRoot:s,onFileSelected:n}=e,o=Uo();function i(){return x.join(s??t,"fixtures")}o.get("/api/files",async(l,c)=>{try{let u=typeof l.query.dir=="string"?l.query.dir:t,d=x.resolve(u),f=await T.readdir(d,{withFileTypes:!0}),g=[];for(let p of f)if(p.name!=="node_modules"&&!p.name.startsWith("."))if(p.isDirectory()){let v=x.join(d,p.name);await as(v)&&g.push({name:p.name,type:"directory",path:v})}else p.isFile()&&p.name.endsWith(".test.yaml")&&g.push({name:p.name,type:"file",path:x.join(d,p.name)});g.sort((p,v)=>p.type!==v.type?p.type==="directory"?-1:1:p.name.localeCompare(v.name));let h=x.dirname(d);c.json({dir:d,parent:h!==d?h:null,entries:g,initialFile:r??null,projectRoot:s??t})}catch(u){console.error("[debugger] Error listing files:",u),c.status(500).json({error:u.message})}});function a(l){if(typeof l=="string"&&l){let c=x.resolve(l);return c.endsWith(".test.yaml")?{filePath:c}:{error:"File must be a .test.yaml file"}}return r?{filePath:r}:{error:"No file specified. Pass ?file= parameter."}}return o.get("/api/test-flow",async(l,c)=>{let u=a(l.query.file);if("error"in u)return c.status(400).json({error:u.error});let d=u.filePath;try{n?.(d);let f=await T.readFile(d,"utf-8"),g=await T.stat(d),h=ke(f),p=Pt(f,d,s);if(p.suite){let v=p.suite,w={tests:v.tests.map(y=>({name:y.name,statements:y.testFlow.statements??[],teardown:y.testFlow.teardown,skip:y.skip,timeout:y.timeout,fail:y.fail,only:y.only,slow:y.slow})),beforeAll:de(v.beforeAll),afterAll:de(v.afterAll),beforeEach:de(v.beforeEach),afterEach:de(v.afterEach)},m={version:"1.3.0",baseURL:p.use?.baseURL,testGroup:w};c.json({isSuite:!0,testFlow:m,metadata:h,name:p.name,tags:p.tags,use:p.use,filePath:d,fileName:x.basename(d),lastModified:g.mtimeMs})}else{let v=p.testFlow;Fe(f,v),c.json({isSuite:!1,testFlow:v,metadata:h,name:p.name,tags:p.tags,use:p.use,filePath:d,fileName:x.basename(d),lastModified:g.mtimeMs})}}catch(f){if(f.code==="ENOENT")return c.status(404).json({error:`File not found: ${d}`});console.error("[debugger] Error loading test flow:",f),c.status(500).json({error:f.message})}}),o.put("/api/test-flow",async(l,c)=>{try{let u=a(l.query.file);if("error"in u)return c.status(400).json({error:u.error});let d=u.filePath,{testFlow:f,metadata:g}=l.body;if(!f)return c.status(400).json({error:"testFlow is required"});let h=x.join(s??t,"templates"),p=await Bo(h),v=At(f,p),w=Pe(v,g),m=d+".tmp";await T.writeFile(m,w,"utf-8"),await T.rename(m,d);let y=await T.stat(d);c.json({success:!0,lastModified:y.mtimeMs})}catch(u){console.error("[debugger] Error saving test flow:",u),c.status(500).json({error:u.message})}}),o.get("/api/fixtures",async(l,c)=>{try{let u=i();try{let f=(await T.readdir(u,{withFileTypes:!0})).filter(g=>g.isFile()).map(g=>g.name).sort();c.json({files:f,dir:u})}catch(d){if(d.code==="ENOENT")c.json({files:[],dir:u});else throw d}}catch(u){console.error("[debugger] Error listing fixtures:",u),c.status(500).json({error:u.message})}}),o.post("/api/fixtures",async(l,c)=>{try{let u=i();await T.mkdir(u,{recursive:!0});let{name:d,content:f}=l.body;if(!d||!f)return c.status(400).json({error:"name and content are required"});let g=x.basename(d);if(!g)return c.status(400).json({error:"Invalid file name"});let h=x.join(u,g);await T.writeFile(h,Buffer.from(f,"base64")),c.json({fileName:g})}catch(u){console.error("[debugger] Error saving fixture:",u),c.status(500).json({error:u.message})}}),o.get("/api/functions",async(l,c)=>{let u=x.join(s??t,"helpers"),d;try{d=await T.readdir(u)}catch(p){return p.code==="ENOENT"?c.json([]):(console.error("[debugger] Error reading helpers dir:",p),c.status(500).json({error:p.message}))}let f=d.filter(p=>/\.(ts|js|mjs)$/.test(p)),g=[],h=1;for(let p of f){let v=await T.readFile(x.join(u,p),"utf-8"),w=/export\s+async\s+function\s+(\w+)\s*(\([^)]*\))/g,m;for(;(m=w.exec(v))!==null;){let[,y,P]=m,k=P.replace(/\s*:\s*[^,)]+/g,"");g.push({id:h++,name:`helpers/${p}#${y}`,description:"",status:"Active",code:`async function ${y}${k} {}`})}}c.json(g)}),o.get("/api/reusable-steps",async(l,c)=>{let u=x.join(s??t,"templates"),d;try{d=await T.readdir(u)}catch(h){return h.code==="ENOENT"?c.json([]):(console.error("[debugger] Error reading templates dir:",h),c.status(500).json({error:h.message}))}let f=d.filter(h=>h.endsWith(".yaml")).sort(),g=[];for(let h of f)try{let p=await T.readFile(x.join(u,h),"utf-8"),v=Ae(p);if(!v||typeof v!="object")continue;let w=v.name??x.basename(h,".yaml"),m=v.description??"",y=de(v.statements);g.push({id:Je(w),organizationId:"local",name:w,description:m,statements:y})}catch(p){console.error(`[debugger] Error parsing template ${h}:`,p)}c.json(g)}),o.post("/api/reusable-steps/exists/:name",async(l,c)=>{let u=x.join(s??t,"templates"),d=decodeURIComponent(l.params.name).toLowerCase(),f;try{f=await T.readdir(u)}catch(g){return g.code==="ENOENT"?c.json(!1):c.status(500).json({error:g.message})}for(let g of f.filter(h=>h.endsWith(".yaml")))try{let h=await T.readFile(x.join(u,g),"utf-8");if((Ae(h)?.name??x.basename(g,".yaml")).toLowerCase()===d)return c.json(!0)}catch{}c.json(!1)}),o.post("/api/reusable-steps",async(l,c)=>{let u=x.join(s??t,"templates"),{reusableStep:d}=l.body??{};if(!d?.name||!Array.isArray(d.statements))return c.status(400).json({error:"reusableStep.name and reusableStep.statements are required"});await T.mkdir(u,{recursive:!0});let f=d.name.replace(/[/\\:*?"<>|]/g,"-").trim(),g=x.join(u,`${f}.yaml`),h={name:d.name,statements:Et(d.statements)};d.description&&(h.description=d.description),await T.writeFile(g,Ye(h),"utf-8"),c.json({id:Je(d.name),organizationId:"local",name:d.name,description:d.description??"",statements:d.statements})}),o.put("/api/reusable-steps/:id/update",async(l,c)=>{let u=parseInt(l.params.id,10),d=x.join(s??t,"templates"),f;try{f=await T.readdir(d)}catch(g){return g.code==="ENOENT"?c.status(404).json({error:"Templates directory not found"}):c.status(500).json({error:g.message})}for(let g of f.filter(h=>h.endsWith(".yaml")))try{let h=x.join(d,g),p=Ae(await T.readFile(h,"utf-8"))??{},v=p.name??x.basename(g,".yaml");if(Je(v)!==u)continue;let w=l.body??{};return w.description!==void 0&&(p.description=w.description),w.statements!==void 0&&(p.statements=Et(w.statements)),await T.writeFile(h,Ye(p),"utf-8"),c.json({id:u,organizationId:"local",...p,statements:w.statements!==void 0?w.statements:de(p.statements)})}catch{}c.status(404).json({error:`Template with id ${u} not found`})}),o.put("/api/templates/:name",async(l,c)=>{let u=x.join(s??t,"templates"),d=decodeURIComponent(l.params.name),f;try{f=await T.readdir(u)}catch(g){return g.code==="ENOENT"?c.status(404).json({error:"Templates directory not found"}):c.status(500).json({error:g.message})}for(let g of f.filter(h=>h.endsWith(".yaml")))try{let h=x.join(u,g),p=Ae(await T.readFile(h,"utf-8"))??{};if((p.name??x.basename(g,".yaml"))!==d)continue;let w=l.body??{};return w.name!==void 0&&(p.name=w.name),w.description!==void 0&&(p.description=w.description),w.statements!==void 0&&(p.statements=Et(w.statements)),await T.writeFile(h,Ye(p),"utf-8"),c.json({organizationId:"local",...p,statements:w.statements!==void 0?w.statements:de(p.statements)})}catch{}c.status(404).json({error:`Template "${d}" not found`})}),o.get("/api/test-results",async(l,c)=>{let u=a(l.query.file);if("error"in u)return c.status(400).json({error:u.error});let d=s??t,f=x.basename(u.filePath,".test.yaml"),g=x.join(d,".shiplight","artifacts",f),h=[],p=null;try{let v=await T.readdir(g,{withFileTypes:!0});for(let w of v)if(w.isDirectory()){let m=x.join(g,w.name),y=await T.readdir(m);for(let P of y.filter(k=>/\.(png|jpe?g|webp)$/i.test(k))){let k=x.relative(g,x.join(m,P)),A=w.name.match(/^(.+?)_(before|after)$/),Se=A?A[1]:w.name,Oe=A?A[2]:"screenshot";h.push({url:`/api/report-assets/${f}/${k}`,label:Oe,stepId:Se})}}else/\.(webm|mp4)$/i.test(w.name)&&(p||(p=`/api/report-assets/${f}/${w.name}`));h.sort((w,m)=>w.stepId!==m.stepId?(w.stepId??"").localeCompare(m.stepId??""):w.label.localeCompare(m.label))}catch{}if(h.length===0&&!p)return c.status(404).json({error:"No test results found"});c.json({videoPath:p,screenshots:h})}),o}var ls=_(()=>{"use strict";ce();kt()});import*as hs from"http";import*as fs from"net";import*as Z from"path";import*as gs from"fs";import{randomUUID as Ho}from"crypto";function us(e){return new Promise((t,r)=>{if(e.body&&typeof e.body=="object")try{t(Buffer.from(JSON.stringify(e.body),"utf-8"));return}catch{}if(e.readableEnded){t(Buffer.alloc(0));return}let s=[];e.on("data",n=>s.push(n)),e.on("end",()=>t(Buffer.concat(s))),e.on("error",r)})}function ds(e,t,r,s,n,o){return new Promise(i=>{let a=Array.isArray(o)?o[0]:o,l=hs.request({hostname:r,port:s,path:e.originalUrl,method:e.method,headers:{"content-type":a||"application/json","content-length":String(n.length)},timeout:3e5},c=>{t.writeHead(c.statusCode??502,c.headers),c.on("data",u=>t.write(u)),c.on("end",()=>{t.end(),i()}),c.on("error",()=>{t.writableEnded||t.end(),i()})});l.on("error",c=>{t.headersSent?t.writableEnded||t.end():t.status(502).json({status:"error",message:"Inner server unavailable: "+c.message}),i()}),l.end(n)})}function $t(e,t){try{(!("destroyed"in e)||!e.destroyed)&&(e.write(t),e.destroy())}catch{}}var ps,Xe,ms=_(()=>{"use strict";ut();ps=1e4,Xe=class{sessions=new Map;byYamlPath=new Map;options;spawner;headed;constructor(t={}){this.options=t,this.spawner=t.spawner??pt,this.headed=t.headed??!1}log(t){this.options.onLog?this.options.onLog(t):console.error(t)}notifyStateChange(t){try{this.options.onSessionStateChange?.({...t})}catch(r){this.log(`[manager] onSessionStateChange listener threw: ${r.message}`)}}openSession(t){let r=Z.resolve(t),s=this.byYamlPath.get(r);if(s){let c=this.sessions.get(s);if(c&&c.session.status!=="ended")return{...c.session};this.byYamlPath.delete(r)}if(!gs.existsSync(r))throw new Error(`YAML file not found: ${r}`);if(!xe(Z.dirname(r)))throw new Error(`No Playwright config found for ${r} (searched parents for playwright.config.{ts,js,mjs}).`);let o=`dbg-${Ho().slice(0,8)}`,i=new Date().toISOString(),a={sessionId:o,yamlPath:r,innerPort:0,innerHost:"127.0.0.1",pid:0,startedAt:i,status:"idle",exitInfo:null},l={session:a,cleanup:async()=>{},readyPromise:Promise.resolve()};return this.sessions.set(o,l),this.byYamlPath.set(r,o),this.notifyStateChange(a),this.log(`[manager] session ${o} created (idle) for ${Z.basename(r)}`),{...a}}async startSandbox(t){let r=this.sessions.get(t);if(!r)throw new Error(`No session ${t} to start sandbox for`);if(r.session.status==="running"||r.session.status==="starting"){r.readyPromise&&await r.readyPromise;return}if(r.session.status==="ended")throw new Error(`Session ${t} has ended`);let s=r.session.yamlPath,n=xe(Z.dirname(s));if(!n)throw new Error(`No Playwright config found for ${s} (searched parents for playwright.config.{ts,js,mjs}).`);r.session.status="starting",this.notifyStateChange(r.session);let o=(async()=>{let i=ps+18e4,a,l=new Promise((d,f)=>{a=setTimeout(()=>f(new Error(`Timed out after ${i/1e3}s waiting for inner playwright to register on a port.`)),i)}),c;try{c=await Promise.race([this.spawner({yamlFilePath:s,configPath:n,tempSuffix:t,headed:this.headed}),l])}finally{a&&clearTimeout(a)}r.session.innerPort=c.port,r.session.innerHost=c.host,r.session.pid=c.pid,r.session.status="running",r.cleanup=c.cleanup;let u=d=>{r.session.status!=="ended"&&(r.session.status="ended",r.session.exitInfo=d,this.notifyStateChange(r.session))};r.livenessTimer=this.startLivenessProbe(t,u),this.notifyStateChange(r.session),this.log(`[manager] session ${t} running on ${r.session.innerHost}:${r.session.innerPort} (yaml=${Z.basename(s)})`)})();r.readyPromise=o;try{await o}catch(i){throw r.session.status="ended",r.session.exitInfo=i.message,this.notifyStateChange(r.session),i}}async stopSandbox(t){let r=this.sessions.get(t);if(r&&r.session.status!=="idle"&&r.session.status!=="ended"){this.log(`[manager] stopSandbox ${t}`),r.livenessTimer&&(clearInterval(r.livenessTimer),r.livenessTimer=void 0);try{await r.cleanup()}catch(s){this.log(`[manager] stopSandbox ${t} cleanup error: ${s.message}`)}r.session.innerPort=0,r.session.innerHost="127.0.0.1",r.session.pid=0,r.session.status="idle",r.session.exitInfo=null,r.cleanup=async()=>{},r.readyPromise=Promise.resolve(),this.notifyStateChange(r.session)}}startLivenessProbe(t,r){let o=0,i=setInterval(()=>{let a=this.sessions.get(t);if(!a||a.session.status==="ended"){clearInterval(i);return}let l=!1;try{process.kill(a.session.pid,0),l=!0}catch(c){c?.code!=="ESRCH"&&(l=!0)}l?o=0:(o+=1,o>=3&&(clearInterval(i),r("process-exited")))},3e3);return i.unref(),i}async closeSession(t){let r=this.sessions.get(t);if(r){this.log(`[manager] closeSession ${t} (status=${r.session.status})`),this.sessions.delete(t),this.byYamlPath.get(r.session.yamlPath)===t&&this.byYamlPath.delete(r.session.yamlPath),r.livenessTimer&&(clearInterval(r.livenessTimer),r.livenessTimer=void 0),r.session.status!=="ended"&&(r.session.status="ended",r.session.exitInfo="SIGTERM",this.notifyStateChange(r.session));try{await r.cleanup()}catch(s){this.log(`[manager] closeSession ${t} cleanup error: ${s.message}`)}}}async restartInner(t){let r=this.sessions.get(t);if(!r)throw new Error(`No session ${t} to restart`);if(r.restartInProgress)throw new Error(`Restart already in progress for session ${t}`);let s=r.session.yamlPath,n=xe(Z.dirname(s));if(!n)throw new Error(`No Playwright config found for ${s} (searched parents for playwright.config.{ts,js,mjs}).`);r.restartInProgress=!0;try{r.livenessTimer&&(clearInterval(r.livenessTimer),r.livenessTimer=void 0);try{await r.cleanup()}catch(c){this.log(`[manager] restartInner ${t} old cleanup error: ${c.message}`)}r.session.status="starting",r.session.innerPort=0,r.session.pid=0,this.notifyStateChange(r.session);let o=ps+18e4,i,a=new Promise((c,u)=>{i=setTimeout(()=>u(new Error(`Timed out after ${o/1e3}s waiting for inner playwright to respawn.`)),o)}),l;try{l=await Promise.race([this.spawner({yamlFilePath:s,configPath:n,tempSuffix:t,headed:this.headed}),a])}catch(c){throw r.session.status="ended",r.session.exitInfo=c.message,this.notifyStateChange(r.session),c}finally{i&&clearTimeout(i)}r.session.innerPort=l.port,r.session.innerHost=l.host,r.session.pid=l.pid,r.session.status="running",r.cleanup=l.cleanup,r.livenessTimer=this.startLivenessProbe(t,c=>{r.session.status!=="ended"&&(r.session.status="ended",r.session.exitInfo=c,this.notifyStateChange(r.session))}),this.notifyStateChange(r.session),this.log(`[manager] session ${t} restarted on ${r.session.innerHost}:${r.session.innerPort} (yaml=${Z.basename(s)})`)}finally{r.restartInProgress=!1}}listSessions(){return Array.from(this.sessions.values()).map(t=>({...t.session}))}getSession(t){let r=this.sessions.get(t);return r?{...r.session}:void 0}httpProxyFor(t,r={}){let{liveviewUrlBuilder:s}=r;return async(n,o,i)=>{let a=this.sessions.get(t);if(!a){o.status(404).json({status:"error",message:"Session not found"});return}if(a.session.status==="ended"){o.status(410).json({status:"error",message:"Session has ended",exitInfo:a.session.exitInfo});return}if(a.session.status==="idle"){o.status(503).json({status:"error",message:"Sandbox not started"});return}let l=`${n.method} ${n.path}`;if(l==="POST /api/int-runner/create-session"){let u=await us(n),d={};if(u.length)try{d=JSON.parse(u.toString("utf-8"))}catch{o.status(400).json({status:"error",message:"Invalid JSON body"});return}d.testFilePath=a.session.yamlPath,await ds(n,o,a.session.innerHost,a.session.innerPort,Buffer.from(JSON.stringify(d),"utf-8"),"application/json");return}if(l==="POST /api/int-runner/terminate-session"){try{await this.stopSandbox(t),o.json({status:"success",details:"Sandbox stopped"})}catch(u){o.status(500).json({status:"error",message:u.message})}return}if(l==="POST /api/int-runner/liveview-url"){let u=s?.(n)??"";o.json({liveviewUrl:u,browserWsUrl:""});return}let c=await us(n);await ds(n,o,a.session.innerHost,a.session.innerPort,c,n.headers["content-type"])}}wsUpgradeFor(t){return async(r,s,n,o)=>{let i=this.sessions.get(t);if(!i){$t(s,`HTTP/1.1 404 Not Found\r
648
218
  \r
649
- `);return}if(i.session.status==="ended"){$t(r,`HTTP/1.1 410 Gone\r
219
+ `);return}if(i.session.status==="ended"){$t(s,`HTTP/1.1 410 Gone\r
650
220
  \r
651
- `);return}try{let a=i.session.innerHost.includes(":")?`[${i.session.innerHost}]`:i.session.innerHost,l=await fetch(`http://${a}:${i.session.innerPort}/api/browser-cdp`);if(!l.ok)throw new Error(`Inner /api/browser-cdp returned ${l.status}`);let{cdpUrl:c}=await l.json(),u=new URL(c.replace(/^ws/,"http")),d=parseInt(u.port||"80",10),f=u.hostname,g=u.pathname;o&&o.startsWith("/page/")&&(g=`/devtools${o}`);let h=gr.createConnection(d,f);h.on("connect",()=>{let p=`GET ${g} HTTP/1.1\r
221
+ `);return}try{let a=i.session.innerHost.includes(":")?`[${i.session.innerHost}]`:i.session.innerHost,l=await fetch(`http://${a}:${i.session.innerPort}/api/browser-cdp`);if(!l.ok)throw new Error(`Inner /api/browser-cdp returned ${l.status}`);let{cdpUrl:c}=await l.json(),u=new URL(c.replace(/^ws/,"http")),d=parseInt(u.port||"80",10),f=u.hostname,g=u.pathname;o&&o.startsWith("/page/")&&(g=`/devtools${o}`);let h=fs.createConnection(d,f);h.on("connect",()=>{let p=`GET ${g} HTTP/1.1\r
652
222
  Host: ${f}:${d}\r
653
- `;for(let[v,w]of Object.entries(n.headers)){let m=v.toLowerCase();m!=="host"&&m!=="origin"&&(p+=`${v}: ${Array.isArray(w)?w.join(", "):w}\r
223
+ `;for(let[v,w]of Object.entries(r.headers)){let m=v.toLowerCase();m!=="host"&&m!=="origin"&&(p+=`${v}: ${Array.isArray(w)?w.join(", "):w}\r
654
224
  `)}p+=`\r
655
- `,h.write(p),s.length&&h.write(s),h.pipe(r),r.pipe(h)}),h.on("error",()=>{(!("destroyed"in r)||!r.destroyed)&&r.destroy()}),r.on("error",()=>{h.destroyed||h.destroy()}),r.on("close",()=>{h.destroyed||h.destroy()})}catch(a){this.log(`[manager] WS upgrade for ${t} failed: ${a.message}`),$t(r,`HTTP/1.1 502 Bad Gateway\r
225
+ `,h.write(p),n.length&&h.write(n),h.pipe(s),s.pipe(h)}),h.on("error",()=>{(!("destroyed"in s)||!s.destroyed)&&s.destroy()}),s.on("error",()=>{h.destroyed||h.destroy()}),s.on("close",()=>{h.destroyed||h.destroy()})}catch(a){this.log(`[manager] WS upgrade for ${t} failed: ${a.message}`),$t(s,`HTTP/1.1 502 Bad Gateway\r
656
226
  \r
657
- `)}}}async shutdown(){let t=Array.from(this.sessions.keys());await Promise.allSettled(t.map(n=>this.closeSession(n)))}}});import{Router as Jo}from"express";import Mt from"express";import*as we from"fs";import*as Q from"path";function qo(e){return e?Q.isAbsolute(e)?Q.normalize(e):Q.resolve(e):null}function Xo(e,t){let n=t.headers.host??"127.0.0.1";return`${(t.headers["x-forwarded-proto"]??"ws")==="https"?"wss":"ws"}://${n}/ws/debugger/${e}/cdp-browser`}function Zo(e){let t=Q.basename(e);return t.endsWith(".test.yaml")||t.endsWith(".test.yml")}function wr(e){let{manager:t,staticDir:n,resolveYamlPath:r=qo,liveviewUrlBuilder:s=Xo,fallbackRouter:o,artifactsDir:i}=e,a=Jo(),l=i?Mt.static(i):null;a.post("/api/debugger/sessions",Mt.json(),async(u,d)=>{let f=u.body?.yamlPath;if(typeof f!="string"||!f){d.status(400).json({error:"yamlPath is required"});return}let g=r(f);if(!g){d.status(403).json({error:"Path outside allowed roots"});return}if(!Zo(g)){d.status(400).json({error:"Not a Shiplight test file (expected *.test.yaml or *.test.yml)"});return}if(!we.existsSync(g)){d.status(404).json({error:"File not found"});return}let h=t.listSessions().find(p=>p.yamlPath===g&&p.status!=="ended");try{let p=t.openSession(g);d.status(h?200:201).json({sessionId:p.sessionId,yamlPath:p.yamlPath,startedAt:p.startedAt,status:p.status})}catch(p){d.status(500).json({error:p.message})}}),a.get("/api/debugger/sessions",(u,d)=>{d.setHeader("Cache-Control","no-store"),d.json({sessions:t.listSessions().map(f=>({sessionId:f.sessionId,yamlPath:f.yamlPath,startedAt:f.startedAt,status:f.status}))})}),a.delete("/api/debugger/sessions/:sessionId",async(u,d)=>{let f=u.params.sessionId,g=!!t.getSession(f);await t.closeSession(f),d.json({deleted:!0,alreadyGone:!g})}),we.existsSync(n)?a.use("/debugger/static",Mt.static(n)):console.error(`[debugger] WARNING: debugger static dir missing at ${n} \u2014 iframe routes will 404`),a.get("/debugger/:sessionId/",(u,d)=>{let f=u.params.sessionId;if(!t.getSession(f)){d.status(404).send("Debugger session not found");return}let h=Q.join(n,"index.html");if(!we.existsSync(h)){d.status(500).send(`Debugger SPA bundle missing at ${h}`);return}let v=we.readFileSync(h,"utf-8").replace(/(src|href)="\/assets\//g,'$1="/debugger/static/assets/').replace(/(src|href)="\/index\.html/g,'$1="/debugger/static/index.html');d.type("html").send(v)});let c=async(u,d,f)=>{let g=u.params.sessionId;if(g==="static")return f();let h=t.getSession(g);if(!h){d.status(404).json({status:"error",message:"Session not found"});return}let p=u.originalUrl,v=`/debugger/${g}`;if(p.startsWith(v)){let m=p.slice(v.length)||"/";u.url=m,Object.defineProperty(u,"originalUrl",{value:m,configurable:!0})}if(l&&u.path.startsWith("/api/report-assets/")){u.url=u.url.replace(/^\/api\/report-assets/,""),l(u,d,f);return}if(h.status==="idle"||h.status==="starting"){let m=`${u.method} ${u.path}`;if(m==="POST /api/int-runner/create-session")try{await t.startSandbox(g)}catch(y){d.status(500).json({status:"error",message:y.message});return}else if(m==="POST /api/int-runner/liveview-url"){d.json({liveviewUrl:"",browserWsUrl:""});return}else if(u.path.startsWith("/api/int-runner/")){d.status(503).json({status:"error",message:"Sandbox not started"});return}else if(o){o(u,d,f);return}else{d.status(503).json({status:"error",message:"Sandbox not started"});return}}t.httpProxyFor(g,{liveviewUrlBuilder:()=>s(g,u)})(u,d,f)};return a.use("/debugger/:sessionId",c),a}function br(e,t,n,r){let o=(t.url??"").match(/^\/ws\/debugger\/([^/]+)(.*)$/);if(!o){n.write(`HTTP/1.1 404 Not Found\r
227
+ `)}}}async shutdown(){let t=Array.from(this.sessions.keys());await Promise.allSettled(t.map(r=>this.closeSession(r)))}}});import{Router as Wo}from"express";import Mt from"express";import*as we from"fs";import*as Q from"path";function Go(e){return e?Q.isAbsolute(e)?Q.normalize(e):Q.resolve(e):null}function Ko(e,t){let r=t.headers.host??"127.0.0.1";return`${(t.headers["x-forwarded-proto"]??"ws")==="https"?"wss":"ws"}://${r}/ws/debugger/${e}/cdp-browser`}function zo(e){let t=Q.basename(e);return t.endsWith(".test.yaml")||t.endsWith(".test.yml")}function ys(e){let{manager:t,staticDir:r,resolveYamlPath:s=Go,liveviewUrlBuilder:n=Ko,fallbackRouter:o,artifactsDir:i}=e,a=Wo(),l=i?Mt.static(i):null;a.post("/api/debugger/sessions",Mt.json(),async(u,d)=>{let f=u.body?.yamlPath;if(typeof f!="string"||!f){d.status(400).json({error:"yamlPath is required"});return}let g=s(f);if(!g){d.status(403).json({error:"Path outside allowed roots"});return}if(!zo(g)){d.status(400).json({error:"Not a Shiplight test file (expected *.test.yaml or *.test.yml)"});return}if(!we.existsSync(g)){d.status(404).json({error:"File not found"});return}let h=t.listSessions().find(p=>p.yamlPath===g&&p.status!=="ended");try{let p=t.openSession(g);d.status(h?200:201).json({sessionId:p.sessionId,yamlPath:p.yamlPath,startedAt:p.startedAt,status:p.status})}catch(p){d.status(500).json({error:p.message})}}),a.get("/api/debugger/sessions",(u,d)=>{d.setHeader("Cache-Control","no-store"),d.json({sessions:t.listSessions().map(f=>({sessionId:f.sessionId,yamlPath:f.yamlPath,startedAt:f.startedAt,status:f.status}))})}),a.delete("/api/debugger/sessions/:sessionId",async(u,d)=>{let f=u.params.sessionId,g=!!t.getSession(f);await t.closeSession(f),d.json({deleted:!0,alreadyGone:!g})}),we.existsSync(r)?a.use("/debugger/static",Mt.static(r)):console.error(`[debugger] WARNING: debugger static dir missing at ${r} \u2014 iframe routes will 404`),a.get("/debugger/:sessionId/",(u,d)=>{let f=u.params.sessionId;if(!t.getSession(f)){d.status(404).send("Debugger session not found");return}let h=Q.join(r,"index.html");if(!we.existsSync(h)){d.status(500).send(`Debugger SPA bundle missing at ${h}`);return}let v=we.readFileSync(h,"utf-8").replace(/(src|href)="\/assets\//g,'$1="/debugger/static/assets/').replace(/(src|href)="\/index\.html/g,'$1="/debugger/static/index.html');d.type("html").send(v)});let c=async(u,d,f)=>{let g=u.params.sessionId;if(g==="static")return f();let h=t.getSession(g);if(!h){d.status(404).json({status:"error",message:"Session not found"});return}let p=u.originalUrl,v=`/debugger/${g}`;if(p.startsWith(v)){let m=p.slice(v.length)||"/";u.url=m,Object.defineProperty(u,"originalUrl",{value:m,configurable:!0})}if(l&&u.path.startsWith("/api/report-assets/")){u.url=u.url.replace(/^\/api\/report-assets/,""),l(u,d,f);return}if(h.status==="idle"||h.status==="starting"){let m=`${u.method} ${u.path}`;if(m==="POST /api/int-runner/create-session")try{await t.startSandbox(g)}catch(y){d.status(500).json({status:"error",message:y.message});return}else if(m==="POST /api/int-runner/liveview-url"){d.json({liveviewUrl:"",browserWsUrl:""});return}else if(u.path.startsWith("/api/int-runner/")){d.status(503).json({status:"error",message:"Sandbox not started"});return}else if(o){o(u,d,f);return}else{d.status(503).json({status:"error",message:"Sandbox not started"});return}}t.httpProxyFor(g,{liveviewUrlBuilder:()=>n(g,u)})(u,d,f)};return a.use("/debugger/:sessionId",c),a}function ws(e,t,r,s){let o=(t.url??"").match(/^\/ws\/debugger\/([^/]+)(.*)$/);if(!o){r.write(`HTTP/1.1 404 Not Found\r
658
228
  \r
659
- `),n.destroy();return}let i=o[1],a=o[2]||"";a.startsWith("/cdp-browser/page/")?a=a.slice(12):(a==="/cdp-browser"||a==="/cdp-browser/")&&(a=""),e.wsUpgradeFor(i)(t,n,r,a||void 0)}var vr=_(()=>{"use strict"});var _r={};te(_r,{startDebuggerServer:()=>ti});import $e from"express";import*as N from"path";import{createRequire as Qo}from"node:module";async function ti(e){let{initialDir:t,initialFile:n,projectRoot:r,port:s,headed:o=!1}=e,i=new qe({headed:o}),a=$e();a.use((w,m,y)=>{if(m.setHeader("Access-Control-Allow-Origin","*"),m.setHeader("Access-Control-Allow-Methods","GET, POST, PUT, DELETE, OPTIONS"),m.setHeader("Access-Control-Allow-Headers","Content-Type, Accept, Cache-Control, Idempotency-Key"),w.method==="OPTIONS")return m.sendStatus(204);y()}),a.use($e.json({limit:"10mb"}));let l=lr({initialDir:t,initialFile:n,projectRoot:r});a.use(l);let c=N.join(r??t,".shiplight","artifacts");a.use("/api/report-assets",$e.static(c));let u=typeof import.meta.dirname=="string"?import.meta.dirname:__dirname,d=u.includes(N.sep+"src"+N.sep),f=d?N.resolve(u,"../../dist/static"):N.join(u,"static"),g=d?N.resolve(u,"../../dist/static-embedded"):N.join(u,"static-embedded");a.use("/devtools",$e.static(Sr)),a.use($e.static(f)),a.use(wr({manager:i,staticDir:g,fallbackRouter:l,artifactsDir:c})),a.get("*path",(w,m,y)=>{if(w.path.startsWith("/api/"))return y();m.sendFile(N.join(f,"index.html"),k=>{k&&m.send(ni(t,s))})});let h=await new Promise((w,m)=>{let y=a.listen(s,"localhost",()=>{w(y)});y.on("error",m)});h.on("upgrade",(w,m,y)=>{if((w.url??"").startsWith("/ws/debugger/")){br(i,w,m,y);return}m.write(`HTTP/1.1 404 Not Found\r
229
+ `),r.destroy();return}let i=o[1],a=o[2]||"";a.startsWith("/cdp-browser/page/")?a=a.slice(12):(a==="/cdp-browser"||a==="/cdp-browser/")&&(a=""),e.wsUpgradeFor(i)(t,r,s,a||void 0)}var bs=_(()=>{"use strict"});var Ss={};te(Ss,{startDebuggerServer:()=>Jo});import $e from"express";import*as N from"path";import{createRequire as Vo}from"node:module";async function Jo(e){let{initialDir:t,initialFile:r,projectRoot:s,port:n,headed:o=!1}=e,i=new Xe({headed:o}),a=$e();a.use((w,m,y)=>{if(m.setHeader("Access-Control-Allow-Origin","*"),m.setHeader("Access-Control-Allow-Methods","GET, POST, PUT, DELETE, OPTIONS"),m.setHeader("Access-Control-Allow-Headers","Content-Type, Accept, Cache-Control, Idempotency-Key"),w.method==="OPTIONS")return m.sendStatus(204);y()}),a.use($e.json({limit:"10mb"}));let l=cs({initialDir:t,initialFile:r,projectRoot:s});a.use(l);let c=N.join(s??t,".shiplight","artifacts");a.use("/api/report-assets",$e.static(c));let u=typeof import.meta.dirname=="string"?import.meta.dirname:__dirname,d=u.includes(N.sep+"src"+N.sep),f=d?N.resolve(u,"../../dist/static"):N.join(u,"static"),g=d?N.resolve(u,"../../dist/static-embedded"):N.join(u,"static-embedded");a.use("/devtools",$e.static(vs)),a.use($e.static(f)),a.use(ys({manager:i,staticDir:g,fallbackRouter:l,artifactsDir:c})),a.get("*path",(w,m,y)=>{if(w.path.startsWith("/api/"))return y();m.sendFile(N.join(f,"index.html"),P=>{P&&m.send(Xo(t,n))})});let h=await new Promise((w,m)=>{let y=a.listen(n,"localhost",()=>{w(y)});y.on("error",m)});h.on("upgrade",(w,m,y)=>{if((w.url??"").startsWith("/ws/debugger/")){ws(i,w,m,y);return}m.write(`HTTP/1.1 404 Not Found\r
660
230
  \r
661
- `),m.destroy()});let p=`http://localhost:${s}`,v=n?`${p}/?open=${encodeURIComponent(n)}`:p;return console.error(`[debugger] Server running at ${p}`),console.error(`[debugger] Directory: ${t}`),n&&console.error(`[debugger] File: ${n}`),{url:v,close:async()=>{await i.shutdown(),await new Promise((w,m)=>{h.close(y=>y?m(y):w())})}}}function ni(e,t){return`<!DOCTYPE html>
231
+ `),m.destroy()});let p=`http://localhost:${n}`,v=r?`${p}/?open=${encodeURIComponent(r)}`:p;return console.error(`[debugger] Server running at ${p}`),console.error(`[debugger] Directory: ${t}`),r&&console.error(`[debugger] File: ${r}`),{url:v,close:async()=>{await i.shutdown(),await new Promise((w,m)=>{h.close(y=>y?m(y):w())})}}}function Xo(e,t){return`<!DOCTYPE html>
662
232
  <html>
663
233
  <head>
664
234
  <meta charset="utf-8">
@@ -676,25 +246,25 @@ Host: ${f}:${d}\r
676
246
  <p>Directory: <code>${e}</code></p>
677
247
  <p>Server: localhost:${t}</p>
678
248
  </body>
679
- </html>`}var ei,Sr,xr=_(()=>{"use strict";pr();yr();vr();ei=Qo(import.meta.url);try{Sr=N.join(N.dirname(ei.resolve("@shiplightai/devtools-assets/package.json")),"dist")}catch{console.error("[debugger] Required peer package @shiplightai/devtools-assets is not installed. Reinstall shiplightai (it pulls the assets package as a dependency)."),process.exit(1)}});var kr={};te(kr,{startDebugger:()=>si});import*as Y from"fs";import*as pe from"path";function Xe(e){return process.stderr.isTTY?`\x1B[31m${e}\x1B[0m`:e}async function si(e){let t,n=be,r=!1,s,o=!1,i=!0,a=!1;for(let m=0;m<e.length;m++)e[m]==="--port"&&e[m+1]?(n=parseInt(e[m+1],10),r=!0,m++):e[m]==="--url"&&e[m+1]?(s=e[m+1],m++):e[m]==="--new"?o=!0:e[m]==="--open"?i=!1:e[m]==="--no-open"?i=!0:e[m]==="--headed"?a=!0:e[m]==="--help"||e[m]==="-h"?(oi(),process.exit(0)):e[m].startsWith("--")||(t=e[m]);let l,c;if(t){let m=pe.resolve(t);Y.existsSync(m)&&Y.statSync(m).isDirectory()?l=m:(l=pe.dirname(m),c=m)}else l=process.cwd();if(o&&c&&!Y.existsSync(c)){let m=pe.dirname(c);Y.existsSync(m)||Y.mkdirSync(m,{recursive:!0}),Y.writeFileSync(c,ri(s||"https://example.com"),"utf-8"),console.log(`Created new test file: ${c}`)}c&&!Y.existsSync(c)&&(console.error(`Error: File not found: ${c}`),console.error("Hint: Use --new to create a new test file."),process.exit(1));let{findPlaywrightConfig:u}=await Promise.resolve().then(()=>(ut(),un)),d=u(l),f=d?pe.dirname(d):l;(await import("dotenv")).config({path:pe.join(f,".env"),override:!0}),d||(console.error("Error: No Playwright config found in "+l),console.error("A Playwright config (playwright.config.ts) is required for the debugger."),process.exit(1));let{startDebuggerServer:h}=await Promise.resolve().then(()=>(xr(),_r));if(console.log(c?`Starting debugger for: ${c}`:`Starting debugger in: ${l}`),d&&console.log(`Using Playwright config: ${d}`),!r){let m=await cn(be,Tr);m===null&&(console.error(Xe(`Error: No available port found in range ${be}-${be+Tr-1}.`)),console.error(Xe("Close some debugger sessions, or pass --port <number> to pick a specific port.")),process.exit(1)),m!==be&&console.log(`Port ${be} is in use; using port ${m} instead.`),n=m}let p;try{p=await h({initialDir:l,initialFile:c,projectRoot:f,port:n,headed:a})}catch(m){throw m?.code==="EADDRINUSE"&&(console.error(Xe(`Error: Port ${n} is already in use.`)),console.error(Xe(r?"Try a different port number, or omit --port to let shiplight auto-pick one.":"All probed ports became busy after selection \u2014 re-run shiplight debug to retry.")),process.exit(1)),m}if(nn(n,l),console.log(`Debugger running at: ${p.url}`),console.log(""),console.log("Press Ctrl+C to stop."),!i)try{let{default:m}=await import("open");await m(p.url)}catch{}let v=!1,w=async()=>{v&&(console.log(`
249
+ </html>`}var Yo,vs,_s=_(()=>{"use strict";ls();ms();bs();Yo=Vo(import.meta.url);try{vs=N.join(N.dirname(Yo.resolve("@shiplightai/devtools-assets/package.json")),"dist")}catch{console.error("[debugger] Required peer package @shiplightai/devtools-assets is not installed. Reinstall shiplightai (it pulls the assets package as a dependency)."),process.exit(1)}});var Ts={};te(Ts,{startDebugger:()=>Zo});import*as Y from"fs";import*as pe from"path";function qe(e){return process.stderr.isTTY?`\x1B[31m${e}\x1B[0m`:e}async function Zo(e){let t,r=be,s=!1,n,o=!1,i=!0,a=!1;for(let m=0;m<e.length;m++)e[m]==="--port"&&e[m+1]?(r=parseInt(e[m+1],10),s=!0,m++):e[m]==="--url"&&e[m+1]?(n=e[m+1],m++):e[m]==="--new"?o=!0:e[m]==="--open"?i=!1:e[m]==="--no-open"?i=!0:e[m]==="--headed"?a=!0:e[m]==="--help"||e[m]==="-h"?(Qo(),process.exit(0)):e[m].startsWith("--")||(t=e[m]);let l,c;if(t){let m=pe.resolve(t);Y.existsSync(m)&&Y.statSync(m).isDirectory()?l=m:(l=pe.dirname(m),c=m)}else l=process.cwd();if(o&&c&&!Y.existsSync(c)){let m=pe.dirname(c);Y.existsSync(m)||Y.mkdirSync(m,{recursive:!0}),Y.writeFileSync(c,qo(n||"https://example.com"),"utf-8"),console.log(`Created new test file: ${c}`)}c&&!Y.existsSync(c)&&(console.error(`Error: File not found: ${c}`),console.error("Hint: Use --new to create a new test file."),process.exit(1));let{findPlaywrightConfig:u}=await Promise.resolve().then(()=>(ut(),pr)),d=u(l),f=d?pe.dirname(d):l;(await import("dotenv")).config({path:pe.join(f,".env"),override:!0}),d||(console.error("Error: No Playwright config found in "+l),console.error("A Playwright config (playwright.config.ts) is required for the debugger."),process.exit(1));let{startDebuggerServer:h}=await Promise.resolve().then(()=>(_s(),Ss));if(console.log(c?`Starting debugger for: ${c}`:`Starting debugger in: ${l}`),d&&console.log(`Using Playwright config: ${d}`),!s){let m=await ar(be,xs);m===null&&(console.error(qe(`Error: No available port found in range ${be}-${be+xs-1}.`)),console.error(qe("Close some debugger sessions, or pass --port <number> to pick a specific port.")),process.exit(1)),m!==be&&console.log(`Port ${be} is in use; using port ${m} instead.`),r=m}let p;try{p=await h({initialDir:l,initialFile:c,projectRoot:f,port:r,headed:a})}catch(m){throw m?.code==="EADDRINUSE"&&(console.error(qe(`Error: Port ${r} is already in use.`)),console.error(qe(s?"Try a different port number, or omit --port to let shiplight auto-pick one.":"All probed ports became busy after selection \u2014 re-run shiplight debug to retry.")),process.exit(1)),m}if(rr(r,l),console.log(`Debugger running at: ${p.url}`),console.log(""),console.log("Press Ctrl+C to stop."),!i)try{let{default:m}=await import("open");await m(p.url)}catch{}let v=!1,w=async()=>{v&&(console.log(`
680
250
  Force exiting...`),process.exit(1)),v=!0,console.log(`
681
- Shutting down...`);let m=setTimeout(()=>{console.error("Cleanup timed out, force exiting."),process.exit(1)},5e3);try{rn(n,l),await p.close()}catch{}clearTimeout(m),process.exit(0)};process.on("SIGINT",w),process.on("SIGTERM",w)}function oi(){console.log("Usage: shiplight debug [file-or-dir] [options]"),console.log(""),console.log("Arguments:"),console.log(" file-or-dir YAML test file or directory (default: cwd)"),console.log(""),console.log("Options:"),console.log(" --port <number> Server port (default: 6174)"),console.log(" --url <url> Override starting URL for the test"),console.log(" --new Create a new test file if it doesn't exist"),console.log(" --open Auto-open the debugger in your browser"),console.log(" --no-open Don't auto-open the browser (default)"),console.log(" --headed Force a visible Chromium window (overrides use.headless)"),console.log(" -h, --help Show this help message"),console.log(""),console.log("Examples:"),console.log(" shiplight debug # browse cwd"),console.log(" shiplight debug tests/ # browse tests/ directory"),console.log(" shiplight debug tests/login.test.yaml # open specific file"),console.log(" shiplight debug tests/login.test.yaml --port 8080"),console.log(" shiplight debug tests/checkout.test.yaml --new --url https://myapp.com/checkout")}var be,Tr,ri,Pr=_(()=>{"use strict";sn();ln();be=6174,Tr=10;ri=e=>`goal: New test
251
+ Shutting down...`);let m=setTimeout(()=>{console.error("Cleanup timed out, force exiting."),process.exit(1)},5e3);try{sr(r,l),await p.close()}catch{}clearTimeout(m),process.exit(0)};process.on("SIGINT",w),process.on("SIGTERM",w)}function Qo(){console.log("Usage: shiplight debug [file-or-dir] [options]"),console.log(""),console.log("Arguments:"),console.log(" file-or-dir YAML test file or directory (default: cwd)"),console.log(""),console.log("Options:"),console.log(" --port <number> Server port (default: 6174)"),console.log(" --url <url> Override starting URL for the test"),console.log(" --new Create a new test file if it doesn't exist"),console.log(" --open Auto-open the debugger in your browser"),console.log(" --no-open Don't auto-open the browser (default)"),console.log(" --headed Force a visible Chromium window (overrides use.headless)"),console.log(" -h, --help Show this help message"),console.log(""),console.log("Examples:"),console.log(" shiplight debug # browse cwd"),console.log(" shiplight debug tests/ # browse tests/ directory"),console.log(" shiplight debug tests/login.test.yaml # open specific file"),console.log(" shiplight debug tests/login.test.yaml --port 8080"),console.log(" shiplight debug tests/checkout.test.yaml --new --url https://myapp.com/checkout")}var be,xs,qo,Ps=_(()=>{"use strict";nr();cr();be=6174,xs=10;qo=e=>`goal: New test
682
252
  statements:
683
253
  - URL: ${e}
684
254
  - "TODO: Add your first step"
685
- `});import bp from"dotenv";function ai(){return globalThis[ii]}function Ze(){let e=ai();return e===void 0?process.env:e}var ii,It=_(()=>{"use strict";ii="__shiplightDotenvCache__"});function Qe(e,t){let n=t?.trim();return n?n.endsWith("/")?n.slice(0,-1):n:e.startsWith(ci)?li:pi}var ci,li,pi,Ot=_(()=>{"use strict";ci="shp_",li="https://nova-api.shiplight.ai",pi="https://api.shiplight.ai"});var Lt={};te(Lt,{lookupActionStores:()=>hi,updateActionStores:()=>fi});function Ar(){let e=Ze(),t=e.SHIPLIGHT_API_TOKEN;return t?{apiUrl:Qe(t,e.SHIPLIGHT_API_URL),apiToken:t}:null}async function hi(e){let t=Ar();if(!t||e.length===0)return new Map;try{let n=new AbortController,r=setTimeout(()=>n.abort(),ui),s=await fetch(`${t.apiUrl}/action-entity-cache/lookup`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${t.apiToken}`},body:JSON.stringify({test_paths:e}),signal:n.signal});if(clearTimeout(r),!s.ok)return console.warn(`[shiplight] Cache lookup failed: HTTP ${s.status}`),new Map;let o=await s.json(),i=new Map;for(let[a,l]of Object.entries(o.stores??{}))i.set(a,l);return i}catch(n){return n instanceof Error&&n.name!=="AbortError"&&console.warn("[shiplight] Cache lookup error:",n.message),new Map}}async function fi(e){let t=Ar();if(!t||e.size===0)return 0;try{let n=new AbortController,r=setTimeout(()=>n.abort(),di),s={};for(let[a,l]of e)s[a]=l;let o=await fetch(`${t.apiUrl}/action-entity-cache/update`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${t.apiToken}`},body:JSON.stringify({stores:s}),signal:n.signal});return clearTimeout(r),o.ok?(await o.json()).updated??0:(console.warn(`[shiplight] Cache update failed: HTTP ${o.status}`),0)}catch(n){return n instanceof Error&&n.name!=="AbortError"&&console.warn("[shiplight] Cache update error:",n.message),0}}var ui,di,Rt=_(()=>{"use strict";Ot();It();ui=2e3,di=5e3});import*as F from"fs";import*as Me from"path";import{globSync as gi}from"glob";function Er(e){let t=Ze().SHIPLIGHT_API_TOKEN;return process.env.CI&&t?new Nt:new Ct(e)}function Ie(e){return e.replace(/\//g,"__")+".json"}function yi(e){return e.replace(/\.json$/,"").replace(/__/g,"/")}var mi,Ct,Nt,$r=_(()=>{"use strict";ce();It();mi=".shiplight/action-cache";Ct=class{constructor(t){this.cwd=t;this.cacheDir=Me.join(t,mi)}isCloud=!1;cacheDir;async lookup(t){let n=new Map;if(t.length===0||!F.existsSync(this.cacheDir))return n;for(let r of t){let s=Me.join(this.cacheDir,Ie(r));try{if(F.existsSync(s)){let o=F.readFileSync(s,"utf-8");n.set(r,JSON.parse(o))}}catch{}}return n}async update(t){if(t.size===0)return 0;F.mkdirSync(this.cacheDir,{recursive:!0});let n=0;for(let[r,s]of t)try{let o=Me.join(this.cacheDir,Ie(r)),i=wt();if(F.existsSync(o))try{i=JSON.parse(F.readFileSync(o,"utf-8"))}catch{}let a={...i,entries:{...i.entries,...s.entries}};F.writeFileSync(o,JSON.stringify(a,null,2)),n++}catch{}return n}loadAll(){if(!F.existsSync(this.cacheDir))return;let t=gi("*.json",{cwd:this.cacheDir});if(t.length===0)return;let n=new Map,r=0;for(let s of t)try{let o=F.readFileSync(Me.join(this.cacheDir,s),"utf-8"),i=JSON.parse(o),a=yi(s);n.set(a,i),r+=Object.keys(i.entries??{}).length}catch{}if(n.size!==0)return console.log(`[shiplight] Cache: loaded ${r} cached action entit${r===1?"y":"ies"} for ${n.size} test file${n.size!==1?"s":""}`),n}},Nt=class{isCloud=!0;async lookup(t){let{lookupActionStores:n}=await Promise.resolve().then(()=>(Rt(),Lt));return n(t)}async update(t){let{updateActionStores:n}=await Promise.resolve().then(()=>(Rt(),Lt));return n(t)}loadAll(){}}});var Rr={};te(Rr,{buildPlaywrightSpawnOptions:()=>Ir,buildTestPathsFromGitInfo:()=>Lr,cloudKeyToCwdRelPath:()=>Ft,runTests:()=>bi});import{spawn as wi,execFileSync as Mr}from"child_process";import*as U from"fs";import*as D from"path";import{globSync as Dt}from"glob";async function bi(e){(e.includes("--help")||e.includes("-h"))&&(console.log("Usage: shiplight test [playwright-args...]"),console.log(""),console.log("Delegates to `npx playwright test` with all arguments forwarded."),console.log("Auto-detects playwright.config.ts in the current directory."),console.log(""),console.log("Examples:"),console.log(" shiplight test # run all tests"),console.log(" shiplight test --headed # run tests with browser visible"),console.log(" shiplight test tests/login.test.yaml # run a specific YAML test"),console.log(" shiplight test tests/login.test.ts # run a specific TS test"),console.log(" shiplight test --grep 'login' # filter tests by name"),process.exit(0));let t=process.cwd();["playwright.config.ts","playwright.config.js","playwright.config.mjs"].some(d=>U.existsSync(D.join(t,d)))||(console.warn("Warning: No playwright.config.ts found in current directory."),console.warn(`Make sure you're running from your project root.
686
- `));let s=e.includes("--magic"),i=e.filter(d=>d!=="--magic").map(d=>d.endsWith(".test.yaml")?d.replace(/\.test\.yaml$/,".yaml.spec.ts"):d),a=Er(t);await Si(t,a),_e&&process.stdout.write(`shiplightai v${_e}
687
- `);let l={...process.env};s&&(l.SHIPLIGHT_MAGIC="1");let c=wi("npx",["playwright","test",...i],Ir(t,l)),u=await new Promise(d=>{c.on("close",f=>d(f??1))});await _i(t,a),process.exit(u)}function Ir(e,t){return{stdio:"inherit",shell:process.platform==="win32",cwd:e,env:t}}function jt(){try{return Mr("git",["rev-parse","--show-toplevel"],{encoding:"utf-8",stdio:["pipe","pipe","pipe"]}).trim()}catch{return null}}function vi(){try{let e=Mr("git",["rev-parse","--abbrev-ref","HEAD"],{encoding:"utf-8",stdio:["pipe","pipe","pipe"]}).trim();if(e&&e!=="HEAD")return`${e}:`}catch{}return""}function Or(e,t,n){if(!n)return{testPaths:[...e],branchPrefix:""};let r=vi(),s=jt();return Lr(e,t,r,s)}function Lr(e,t,n,r){return{testPaths:e.map(o=>{let i=r?D.relative(r,D.resolve(t,o)):o;return`${n}${i}`}),branchPrefix:n}}function Ft(e,t,n,r){let s=t&&e.startsWith(t)?e.slice(t.length):e;return n?D.relative(r,D.resolve(n,s)):s}async function Si(e,t){try{let n=Dt("**/*.test.yaml",{cwd:e,ignore:["**/node_modules/**"]});if(n.length===0)return;let{testPaths:r,branchPrefix:s}=Or(n,e,t.isCloud),o=await t.lookup(r);if(o.size===0)return;let i=D.join(e,".shiplight","action-cache");U.mkdirSync(i,{recursive:!0});let a=jt(),l=0;for(let[c,u]of o){let d=t.isCloud?Ft(c,s,a,e):c,f=D.join(i,Ie(d));U.writeFileSync(f,JSON.stringify(u,null,2)),l++}console.log(`[shiplight] Cache: downloaded ${l} action store${l!==1?"s":""}`)}catch(n){console.warn("[shiplight] Cache download failed:",n.message)}}async function _i(e,t){try{let n=D.join(e,"test-results");if(!U.existsSync(n))return;let r=Dt("**/new-action-entities.json",{cwd:n});if(r.length===0)return;let s={};for(let d of r)try{let f=U.readFileSync(D.join(n,d),"utf-8"),g=JSON.parse(f);g?.entries&&Object.assign(s,g.entries)}catch{}if(Object.keys(s).length===0)return;let o=Dt("**/*.test.yaml",{cwd:e,ignore:["**/node_modules/**"]}),{testPaths:i,branchPrefix:a}=Or(o,e,t.isCloud),l=new Map;for(let d=0;d<o.length;d++){let f=o[d],g=i[d],h=D.join(e,f.replace(/\.test\.yaml$/,".yaml.spec.ts"));if(!U.existsSync(h))continue;let p=U.readFileSync(h,"utf-8"),v={};for(let[w,m]of Object.entries(s))p.includes(w)&&(v[w]=m);if(Object.keys(v).length>0){let w=t.isCloud?Ft(g,a,jt(),e):g,m=D.join(e,".shiplight","action-cache",Ie(w)),y={};if(U.existsSync(m))try{y=JSON.parse(U.readFileSync(m,"utf-8")).entries}catch{}l.set(g,{version:"1.0",entries:{...y,...v}})}}if(l.size===0)return;let c=await t.update(l),u=Array.from(l.values()).reduce((d,f)=>d+Object.keys(f.entries).length,0);console.log(`[shiplight] Cache: saved ${u} action entit${u!==1?"ies":"y"} for ${c} test${c!==1?"s":""}`)}catch(n){console.warn("[shiplight] Cache upload failed:",n.message)}}var Cr=_(()=>{"use strict";$r();Le()});function M(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#039;")}function tt(e){if(e<1e3)return`${e}ms`;if(e<6e4)return`${(e/1e3).toFixed(1)}s`;let t=Math.floor(e/6e4),n=(e%6e4/1e3).toFixed(0);return`${t}m ${n}s`}function Nr(e){return`passed after ${e||"?"} ${e===1?"retry":"retries"}`}function et(e){switch(e){case"passed":case"success":return'<span class="status-icon passed">&#x2714;</span>';case"flaky":return'<span class="status-icon flaky">&#x21BB;</span>';case"failed":case"failure":case"timedOut":return'<span class="status-icon failed">&#x2718;</span>';case"skipped":return'<span class="status-icon skipped">&#x2500;</span>';case"interrupted":return'<span class="status-icon failed">&#x26A0;</span>';default:return'<span class="status-icon pending">&#x25CB;</span>'}}function xi(e){return`<span class="badge badge-${e}">${e}</span>`}function Ti(e){if(!e.code)return"";let t=e.code.split(`
688
- `);return e.codeStartLine!=null&&e.codeLine!=null?`<div class="step-code"><pre class="code-block">${t.map((s,o)=>{let i=e.codeStartLine+o,a=i===e.codeLine,l=String(i).padStart(4);return`<span class="code-line${a?" code-line-active":""}">${l} \u2502 ${M(s)}</span>`}).join("")}</pre></div>`:`<div class="step-code"><pre class="code-block">${t.map(r=>`<span class="code-line code-line-body">${M(r)}</span>`).join("")}</pre></div>`}function ki(e){let t=e.duration!=null?`<span class="step-duration">${tt(e.duration)}</span>`:"",n=et(e.status);if(e.screenshot||e.message||e.error||e.code){let s="";e.screenshot&&(s=`<img src="${M(e.screenshot)}" alt="Step screenshot" class="step-screenshot" />`);let o=e.screenshot?"":Ti(e),i="";e.error&&(i=`<div class="step-error"><pre>${M(e.error)}</pre></div>`);let a="";e.message&&!e.error&&(a=`<div class="step-message">${M(e.message)}</div>`);let l=e.status==="failure"?" open":"";return`
255
+ `});import hp from"dotenv";function ti(){return globalThis[ei]}function Ze(){let e=ti();return e===void 0?process.env:e}var ei,It=_(()=>{"use strict";ei="__shiplightDotenvCache__"});function Qe(e,t){let r=t?.trim();return r?r.endsWith("/")?r.slice(0,-1):r:e.startsWith(ri)?si:ni}var ri,si,ni,Ot=_(()=>{"use strict";ri="shp_",si="https://nova-api.shiplight.ai",ni="https://api.shiplight.ai"});var Lt={};te(Lt,{lookupActionStores:()=>ai,updateActionStores:()=>ci});function ks(){let e=Ze(),t=e.SHIPLIGHT_API_TOKEN;return t?{apiUrl:Qe(t,e.SHIPLIGHT_API_URL),apiToken:t}:null}async function ai(e){let t=ks();if(!t||e.length===0)return new Map;try{let r=new AbortController,s=setTimeout(()=>r.abort(),oi),n=await fetch(`${t.apiUrl}/action-entity-cache/lookup`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${t.apiToken}`},body:JSON.stringify({test_paths:e}),signal:r.signal});if(clearTimeout(s),!n.ok)return console.warn(`[shiplight] Cache lookup failed: HTTP ${n.status}`),new Map;let o=await n.json(),i=new Map;for(let[a,l]of Object.entries(o.stores??{}))i.set(a,l);return i}catch(r){return r instanceof Error&&r.name!=="AbortError"&&console.warn("[shiplight] Cache lookup error:",r.message),new Map}}async function ci(e){let t=ks();if(!t||e.size===0)return 0;try{let r=new AbortController,s=setTimeout(()=>r.abort(),ii),n={};for(let[a,l]of e)n[a]=l;let o=await fetch(`${t.apiUrl}/action-entity-cache/update`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${t.apiToken}`},body:JSON.stringify({stores:n}),signal:r.signal});return clearTimeout(s),o.ok?(await o.json()).updated??0:(console.warn(`[shiplight] Cache update failed: HTTP ${o.status}`),0)}catch(r){return r instanceof Error&&r.name!=="AbortError"&&console.warn("[shiplight] Cache update error:",r.message),0}}var oi,ii,Rt=_(()=>{"use strict";Ot();It();oi=2e3,ii=5e3});import*as F from"fs";import*as Me from"path";import{globSync as li}from"glob";function Es(e){let t=Ze().SHIPLIGHT_API_TOKEN;return process.env.CI&&t?new Nt:new Ct(e)}function Ie(e){return e.replace(/\//g,"__")+".json"}function ui(e){return e.replace(/\.json$/,"").replace(/__/g,"/")}var pi,Ct,Nt,As=_(()=>{"use strict";ce();It();pi=".shiplight/action-cache";Ct=class{constructor(t){this.cwd=t;this.cacheDir=Me.join(t,pi)}isCloud=!1;cacheDir;async lookup(t){let r=new Map;if(t.length===0||!F.existsSync(this.cacheDir))return r;for(let s of t){let n=Me.join(this.cacheDir,Ie(s));try{if(F.existsSync(n)){let o=F.readFileSync(n,"utf-8");r.set(s,JSON.parse(o))}}catch{}}return r}async update(t){if(t.size===0)return 0;F.mkdirSync(this.cacheDir,{recursive:!0});let r=0;for(let[s,n]of t)try{let o=Me.join(this.cacheDir,Ie(s)),i=wt();if(F.existsSync(o))try{i=JSON.parse(F.readFileSync(o,"utf-8"))}catch{}let a={...i,entries:{...i.entries,...n.entries}};F.writeFileSync(o,JSON.stringify(a,null,2)),r++}catch{}return r}loadAll(){if(!F.existsSync(this.cacheDir))return;let t=li("*.json",{cwd:this.cacheDir});if(t.length===0)return;let r=new Map,s=0;for(let n of t)try{let o=F.readFileSync(Me.join(this.cacheDir,n),"utf-8"),i=JSON.parse(o),a=ui(n);r.set(a,i),s+=Object.keys(i.entries??{}).length}catch{}if(r.size!==0)return console.log(`[shiplight] Cache: loaded ${s} cached action entit${s===1?"y":"ies"} for ${r.size} test file${r.size!==1?"s":""}`),r}},Nt=class{isCloud=!0;async lookup(t){let{lookupActionStores:r}=await Promise.resolve().then(()=>(Rt(),Lt));return r(t)}async update(t){let{updateActionStores:r}=await Promise.resolve().then(()=>(Rt(),Lt));return r(t)}loadAll(){}}});var Ls={};te(Ls,{buildPlaywrightSpawnOptions:()=>Ms,buildTestPathsFromGitInfo:()=>Os,cloudKeyToCwdRelPath:()=>Ft,runTests:()=>hi});import{spawn as di,execFileSync as $s}from"child_process";import*as U from"fs";import*as D from"path";import{globSync as Dt}from"glob";async function hi(e){(e.includes("--help")||e.includes("-h"))&&(console.log("Usage: shiplight test [playwright-args...]"),console.log(""),console.log("Delegates to `npx playwright test` with all arguments forwarded."),console.log("Auto-detects playwright.config.ts in the current directory."),console.log(""),console.log("Examples:"),console.log(" shiplight test # run all tests"),console.log(" shiplight test --headed # run tests with browser visible"),console.log(" shiplight test tests/login.test.yaml # run a specific YAML test"),console.log(" shiplight test tests/login.test.ts # run a specific TS test"),console.log(" shiplight test --grep 'login' # filter tests by name"),process.exit(0));let t=process.cwd();["playwright.config.ts","playwright.config.js","playwright.config.mjs"].some(d=>U.existsSync(D.join(t,d)))||(console.warn("Warning: No playwright.config.ts found in current directory."),console.warn(`Make sure you're running from your project root.
256
+ `));let n=e.includes("--magic"),i=e.filter(d=>d!=="--magic").map(d=>d.endsWith(".test.yaml")?d.replace(/\.test\.yaml$/,".yaml.spec.ts"):d),a=Es(t);await gi(t,a),_e&&process.stdout.write(`shiplightai v${_e}
257
+ `);let l={...process.env};n&&(l.SHIPLIGHT_MAGIC="1");let c=di("npx",["playwright","test",...i],Ms(t,l)),u=await new Promise(d=>{c.on("close",f=>d(f??1))});await mi(t,a),process.exit(u)}function Ms(e,t){return{stdio:"inherit",shell:process.platform==="win32",cwd:e,env:t}}function jt(){try{return $s("git",["rev-parse","--show-toplevel"],{encoding:"utf-8",stdio:["pipe","pipe","pipe"]}).trim()}catch{return null}}function fi(){try{let e=$s("git",["rev-parse","--abbrev-ref","HEAD"],{encoding:"utf-8",stdio:["pipe","pipe","pipe"]}).trim();if(e&&e!=="HEAD")return`${e}:`}catch{}return""}function Is(e,t,r){if(!r)return{testPaths:[...e],branchPrefix:""};let s=fi(),n=jt();return Os(e,t,s,n)}function Os(e,t,r,s){return{testPaths:e.map(o=>{let i=s?D.relative(s,D.resolve(t,o)):o;return`${r}${i}`}),branchPrefix:r}}function Ft(e,t,r,s){let n=t&&e.startsWith(t)?e.slice(t.length):e;return r?D.relative(s,D.resolve(r,n)):n}async function gi(e,t){try{let r=Dt("**/*.test.yaml",{cwd:e,ignore:["**/node_modules/**"]});if(r.length===0)return;let{testPaths:s,branchPrefix:n}=Is(r,e,t.isCloud),o=await t.lookup(s);if(o.size===0)return;let i=D.join(e,".shiplight","action-cache");U.mkdirSync(i,{recursive:!0});let a=jt(),l=0;for(let[c,u]of o){let d=t.isCloud?Ft(c,n,a,e):c,f=D.join(i,Ie(d));U.writeFileSync(f,JSON.stringify(u,null,2)),l++}console.log(`[shiplight] Cache: downloaded ${l} action store${l!==1?"s":""}`)}catch(r){console.warn("[shiplight] Cache download failed:",r.message)}}async function mi(e,t){try{let r=D.join(e,"test-results");if(!U.existsSync(r))return;let s=Dt("**/new-action-entities.json",{cwd:r});if(s.length===0)return;let n={};for(let d of s)try{let f=U.readFileSync(D.join(r,d),"utf-8"),g=JSON.parse(f);g?.entries&&Object.assign(n,g.entries)}catch{}if(Object.keys(n).length===0)return;let o=Dt("**/*.test.yaml",{cwd:e,ignore:["**/node_modules/**"]}),{testPaths:i,branchPrefix:a}=Is(o,e,t.isCloud),l=new Map;for(let d=0;d<o.length;d++){let f=o[d],g=i[d],h=D.join(e,f.replace(/\.test\.yaml$/,".yaml.spec.ts"));if(!U.existsSync(h))continue;let p=U.readFileSync(h,"utf-8"),v={};for(let[w,m]of Object.entries(n))p.includes(w)&&(v[w]=m);if(Object.keys(v).length>0){let w=t.isCloud?Ft(g,a,jt(),e):g,m=D.join(e,".shiplight","action-cache",Ie(w)),y={};if(U.existsSync(m))try{y=JSON.parse(U.readFileSync(m,"utf-8")).entries}catch{}l.set(g,{version:"1.0",entries:{...y,...v}})}}if(l.size===0)return;let c=await t.update(l),u=Array.from(l.values()).reduce((d,f)=>d+Object.keys(f.entries).length,0);console.log(`[shiplight] Cache: saved ${u} action entit${u!==1?"ies":"y"} for ${c} test${c!==1?"s":""}`)}catch(r){console.warn("[shiplight] Cache upload failed:",r.message)}}var Rs=_(()=>{"use strict";As();Le()});function M(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#039;")}function tt(e){if(e<1e3)return`${e}ms`;if(e<6e4)return`${(e/1e3).toFixed(1)}s`;let t=Math.floor(e/6e4),r=(e%6e4/1e3).toFixed(0);return`${t}m ${r}s`}function Cs(e){return`passed after ${e||"?"} ${e===1?"retry":"retries"}`}function et(e){switch(e){case"passed":case"success":return'<span class="status-icon passed">&#x2714;</span>';case"flaky":return'<span class="status-icon flaky">&#x21BB;</span>';case"failed":case"failure":case"timedOut":return'<span class="status-icon failed">&#x2718;</span>';case"skipped":return'<span class="status-icon skipped">&#x2500;</span>';case"interrupted":return'<span class="status-icon failed">&#x26A0;</span>';default:return'<span class="status-icon pending">&#x25CB;</span>'}}function yi(e){return`<span class="badge badge-${e}">${e}</span>`}function wi(e){if(!e.code)return"";let t=e.code.split(`
258
+ `);return e.codeStartLine!=null&&e.codeLine!=null?`<div class="step-code"><pre class="code-block">${t.map((n,o)=>{let i=e.codeStartLine+o,a=i===e.codeLine,l=String(i).padStart(4);return`<span class="code-line${a?" code-line-active":""}">${l} \u2502 ${M(n)}</span>`}).join("")}</pre></div>`:`<div class="step-code"><pre class="code-block">${t.map(s=>`<span class="code-line code-line-body">${M(s)}</span>`).join("")}</pre></div>`}function bi(e){let t=e.duration!=null?`<span class="step-duration">${tt(e.duration)}</span>`:"",r=et(e.status);if(e.screenshot||e.message||e.error||e.code){let n="";e.screenshot&&(n=`<img src="${M(e.screenshot)}" alt="Step screenshot" class="step-screenshot" />`);let o=e.screenshot?"":wi(e),i="";e.error&&(i=`<div class="step-error"><pre>${M(e.error)}</pre></div>`);let a="";e.message&&!e.error&&(a=`<div class="step-message">${M(e.message)}</div>`);let l=e.status==="failure"?" open":"";return`
689
259
  <details class="step-details step step-${e.status}"${l}>
690
260
  <summary class="step-header">
691
- ${n}
261
+ ${r}
692
262
  <span class="step-id">${M(e.stepId)}</span>
693
263
  <span class="step-description-collapsed">${M(e.description)}</span>
694
264
  ${t}
695
265
  </summary>
696
266
  <div class="step-expanded">
697
- ${s}
267
+ ${n}
698
268
  ${o}
699
269
  <div class="step-description-full">${M(e.description)}</div>
700
270
  ${a}
@@ -703,12 +273,12 @@ statements:
703
273
  </details>`}return`
704
274
  <div class="step step-${e.status}">
705
275
  <div class="step-header">
706
- ${n}
276
+ ${r}
707
277
  <span class="step-id">${M(e.stepId)}</span>
708
278
  <span class="step-description">${M(e.description)}</span>
709
279
  ${t}
710
280
  </div>
711
- </div>`}function Pi(e,t,n,r){let s=[];e&&s.push(`
281
+ </div>`}function vi(e,t,r,s){let n=[];e&&n.push(`
712
282
  <details class="artifact-section">
713
283
  <summary class="artifact-summary">Video</summary>
714
284
  <div class="artifact-content">
@@ -719,61 +289,61 @@ statements:
719
289
  <button class="enlarge-btn" onclick="openVideoOverlay(this)" title="Enlarge">&#x26F6;</button>
720
290
  </div>
721
291
  </div>
722
- </details>`);let o=n.filter(i=>i.screenshot).map(i=>({src:i.screenshot,stepId:i.stepId,description:i.description,status:i.status,message:i.message||i.error||""}));if(o.length>0){let i=M(JSON.stringify(o)),a=o.map((l,c)=>`
292
+ </details>`);let o=r.filter(i=>i.screenshot).map(i=>({src:i.screenshot,stepId:i.stepId,description:i.description,status:i.status,message:i.message||i.error||""}));if(o.length>0){let i=M(JSON.stringify(o)),a=o.map((l,c)=>`
723
293
  <div class="screenshot-thumb" onclick="openGalleryAt(this, ${c})" data-gallery="${i}">
724
294
  <img src="${M(l.src)}" alt="${M(l.stepId)}" />
725
295
  <span class="thumb-label">${M(l.stepId)}</span>
726
- </div>`).join("");s.push(`
296
+ </div>`).join("");n.push(`
727
297
  <details class="artifact-section">
728
298
  <summary class="artifact-summary">Screenshots (${o.length})</summary>
729
299
  <div class="artifact-content">
730
300
  <div class="screenshot-grid">${a}</div>
731
301
  </div>
732
- </details>`)}if(t){let i=M(t);s.push(`
302
+ </details>`)}if(t){let i=M(t);n.push(`
733
303
  <details class="artifact-section">
734
304
  <summary class="artifact-summary">Trace</summary>
735
305
  <div class="artifact-content">
736
306
  <div class="trace-actions">
737
- <code class="trace-command" id="trace-cmd-${r}">npx playwright show-trace ${i}</code>
738
- <button class="copy-btn" onclick="copyTraceCmd('${r}')" title="Copy command">Copy</button>
307
+ <code class="trace-command" id="trace-cmd-${s}">npx playwright show-trace ${i}</code>
308
+ <button class="copy-btn" onclick="copyTraceCmd('${s}')" title="Copy command">Copy</button>
739
309
  </div>
740
310
  <p class="trace-hint">Run this command in your terminal to open the interactive Trace Viewer</p>
741
311
  <p class="trace-hint"><a href="${i}" class="attachment-link" download>Download trace.zip</a></p>
742
312
  </div>
743
- </details>`)}return s.length===0?"":`<div class="test-artifacts">${s.join("")}</div>`}function Dr(e,t,n,r,s){let o=e.map(ki).join(`
744
- `),i="";t&&!e.some(l=>l.error)&&(i=`<div class="test-error"><pre>${M(t)}</pre></div>`);let a=Pi(n,r,e,s);return`
313
+ </details>`)}return n.length===0?"":`<div class="test-artifacts">${n.join("")}</div>`}function Ns(e,t,r,s,n){let o=e.map(bi).join(`
314
+ `),i="";t&&!e.some(l=>l.error)&&(i=`<div class="test-error"><pre>${M(t)}</pre></div>`);let a=vi(r,s,e,n);return`
745
315
  ${i}
746
316
  <div class="steps-list">
747
317
  ${o||'<div class="no-steps">No YAML step details available</div>'}
748
318
  </div>
749
- ${a}`}function Ai(e,t){let n=e.flaky?"flaky":e.status,r=et(n),s;if(e.flaky&&e.attempts&&e.attempts.length>1){let o=`tabs-${t}`,i=e.attempts.length,a=e.attempts.map((c,u)=>{let d=u===i-1,f=c.status==="passed"?"passed":"failed",g=`Attempt ${c.attemptNumber}`;return`<button class="attempt-tab ${d?"active":""} attempt-tab-${f}"
319
+ ${a}`}function Si(e,t){let r=e.flaky?"flaky":e.status,s=et(r),n;if(e.flaky&&e.attempts&&e.attempts.length>1){let o=`tabs-${t}`,i=e.attempts.length,a=e.attempts.map((c,u)=>{let d=u===i-1,f=c.status==="passed"?"passed":"failed",g=`Attempt ${c.attemptNumber}`;return`<button class="attempt-tab ${d?"active":""} attempt-tab-${f}"
750
320
  onclick="switchAttemptTab('${o}', ${u})"
751
- data-tab-index="${u}">${et(f)} ${g} <span class="attempt-tab-badge badge-${f}">${c.status}</span></button>`}).join(""),l=e.attempts.map((c,u)=>{let d=u===i-1,f=Dr(c.steps,c.error,c.videoPath,c.tracePath,`${t}-attempt-${u}`);return`<div class="attempt-panel ${d?"active":""}" data-panel-index="${u}">
321
+ data-tab-index="${u}">${et(f)} ${g} <span class="attempt-tab-badge badge-${f}">${c.status}</span></button>`}).join(""),l=e.attempts.map((c,u)=>{let d=u===i-1,f=Ns(c.steps,c.error,c.videoPath,c.tracePath,`${t}-attempt-${u}`);return`<div class="attempt-panel ${d?"active":""}" data-panel-index="${u}">
752
322
  <div class="attempt-meta">
753
323
  ${et(c.status==="passed"?"passed":"failed")}
754
324
  <span class="attempt-meta-text">Attempt ${c.attemptNumber} &mdash; ${c.status} in ${tt(c.duration)}</span>
755
325
  </div>
756
326
  ${f}
757
- </div>`}).join("");s=`
758
- <div class="flaky-note">Flaky &mdash; ${Nr(e.retries)}</div>
327
+ </div>`}).join("");n=`
328
+ <div class="flaky-note">Flaky &mdash; ${Cs(e.retries)}</div>
759
329
  <div class="attempt-tabs" id="${o}">
760
330
  <div class="attempt-tab-bar">${a}</div>
761
331
  ${l}
762
- </div>`}else s=`
763
- ${e.flaky?`<div class="flaky-note">Flaky &mdash; ${Nr(e.retries)}</div>`:""}
764
- ${Dr(e.steps,e.error,e.videoPath,e.tracePath,String(t))}`;return`
332
+ </div>`}else n=`
333
+ ${e.flaky?`<div class="flaky-note">Flaky &mdash; ${Cs(e.retries)}</div>`:""}
334
+ ${Ns(e.steps,e.error,e.videoPath,e.tracePath,String(t))}`;return`
765
335
  <details class="test-details" ${e.status==="failed"||e.status==="timedOut"?"open":""}>
766
- <summary class="test-summary test-${n}">
767
- ${r}
336
+ <summary class="test-summary test-${r}">
337
+ ${s}
768
338
  <span class="test-title">${M(e.title)}</span>
769
339
  <span class="test-file">${M(e.file)}</span>
770
- ${xi(n)}
340
+ ${yi(r)}
771
341
  <span class="test-duration">${tt(e.duration)}</span>
772
342
  </summary>
773
343
  <div class="test-body">
774
- ${s}
344
+ ${n}
775
345
  </div>
776
- </details>`}function Ut(e){let t=e.tests.filter(a=>a.flaky).length,n=e.tests.filter(a=>a.status==="passed"&&!a.flaky).length,r=e.tests.filter(a=>a.status==="failed"||a.status==="timedOut").length,s=e.tests.filter(a=>a.status==="skipped").length,o=e.tests.length,i=e.tests.map((a,l)=>Ai(a,l)).join(`
346
+ </details>`}function Ut(e){let t=e.tests.filter(a=>a.flaky).length,r=e.tests.filter(a=>a.status==="passed"&&!a.flaky).length,s=e.tests.filter(a=>a.status==="failed"||a.status==="timedOut").length,n=e.tests.filter(a=>a.status==="skipped").length,o=e.tests.length,i=e.tests.map((a,l)=>Si(a,l)).join(`
777
347
  `);return`<!DOCTYPE html>
778
348
  <html lang="en">
779
349
  <head>
@@ -1407,10 +977,10 @@ statements:
1407
977
  <h1>Shiplight Test Report</h1>
1408
978
  <div class="summary">
1409
979
  <span class="summary-stat">${o} test${o!==1?"s":""}</span>
1410
- ${n>0?`<span class="summary-stat passed">${n} passed</span>`:""}
980
+ ${r>0?`<span class="summary-stat passed">${r} passed</span>`:""}
1411
981
  ${t>0?`<span class="summary-stat flaky">${t} flaky</span>`:""}
1412
- ${r>0?`<span class="summary-stat failed">${r} failed</span>`:""}
1413
- ${s>0?`<span class="summary-stat skipped">${s} skipped</span>`:""}
982
+ ${s>0?`<span class="summary-stat failed">${s} failed</span>`:""}
983
+ ${n>0?`<span class="summary-stat skipped">${n} skipped</span>`:""}
1414
984
  <span class="summary-stat">${tt(e.totalDuration)}</span>
1415
985
  ${e.timestamp?`<span class="summary-stat" style="margin-left:auto;color:var(--color-text-secondary)">${new Date(e.timestamp).toLocaleString()}</span>`:""}
1416
986
  </div>
@@ -1555,17 +1125,17 @@ statements:
1555
1125
  });
1556
1126
  </script>
1557
1127
  </body>
1558
- </html>`}var jr=_(()=>{"use strict"});import*as J from"fs";import*as ee from"path";import{execFileSync as Ei}from"child_process";import{createHash as $i}from"crypto";import ve from"axios";function Fr(e){let t=e.match(/^git@([^:]+):(.+?)(?:\.git)?$/);if(t)return`https://${t[1]}/${t[2]}`;let n=e.match(/^https?:\/\/([^/]+)\/(.+?)(?:\.git)?$/);if(n)return`https://${n[1]}/${n[2]}`}function ie(...e){try{return Ei("git",e,{stdio:["pipe","pipe","ignore"]}).toString().trim()||void 0}catch{return}}function Mi(){let e=process.env.GITHUB_EVENT_PATH;if(!e)return{};try{let t=J.readFileSync(e,"utf8");return JSON.parse(t)}catch{return{}}}function Ii(){let e={nodeVersion:process.version};if(process.env.GITHUB_ACTIONS){let t=process.env.GITHUB_SERVER_URL??"https://github.com",n=process.env.GITHUB_REPOSITORY??"",r=process.env.GITHUB_RUN_ID??"",s=process.env.GITHUB_EVENT_NAME??"",i=Mi().pull_request,a=i?.head?.sha,l=process.env.SHIPLIGHT_GIT_SHA??a??process.env.GITHUB_SHA??"",c=process.env.GITHUB_REF??"",u=process.env.SHIPLIGHT_PR_NUMBER??process.env.GITHUB_PR_NUMBER??c.match(/^refs\/pull\/(\d+)\//)?.[1],d=(process.env.SHIPLIGHT_GIT_BRANCH??process.env.GITHUB_HEAD_REF)||process.env.GITHUB_REF_NAME,f=process.env.SHIPLIGHT_PR_TITLE??i?.title,g=ie("log","-1","--pretty=%s"),h=ie("log","-1","--pretty=%ae");Object.assign(e,{ciProvider:"GitHub Action",gitCommit:l,gitBranch:d,gitRepo:n,commitMessage:g,authorEmail:h,prNumber:u,prTitle:f,prUrl:u&&n?`${t}/${n}/pull/${u}`:void 0,ciBuildId:r,ciBuildUrl:r&&n?`${t}/${n}/actions/runs/${r}`:void 0,commitUrl:l&&n?`${t}/${n}/commit/${l}`:void 0,triggeredBy:process.env.GITHUB_ACTOR,eventName:s,workflow:process.env.GITHUB_WORKFLOW})}else if(process.env.GITLAB_CI){let t=process.env.CI_PROJECT_URL??"",n=process.env.CI_COMMIT_SHA??"",r=process.env.CI_MERGE_REQUEST_IID,s=process.env.CI_COMMIT_AUTHOR_EMAIL??process.env.GITLAB_USER_EMAIL,o=process.env.CI_PROJECT_PATH;Object.assign(e,{ciProvider:"GitLab CI",gitCommit:n,gitBranch:process.env.CI_COMMIT_REF_NAME,gitRepo:o,commitMessage:process.env.CI_COMMIT_MESSAGE,authorEmail:s,prNumber:r,prTitle:process.env.CI_MERGE_REQUEST_TITLE,prUrl:r&&t?`${t}/-/merge_requests/${r}`:void 0,ciBuildId:process.env.CI_PIPELINE_ID,ciBuildUrl:process.env.CI_PIPELINE_URL,commitUrl:n&&t?`${t}/commit/${n}`:void 0,triggeredBy:process.env.GITLAB_USER_LOGIN})}else if(process.env.CIRCLECI){let t=process.env.CIRCLE_SHA1??"",n=process.env.CIRCLE_PROJECT_USERNAME??"",r=process.env.CIRCLE_PROJECT_REPONAME??"",s=n&&r?`${n}/${r}`:void 0,o=process.env.CIRCLE_PULL_REQUEST,i=process.env.CIRCLE_PR_NUMBER??o?.match(/\/pull\/(\d+)$/)?.[1],a=process.env.CIRCLE_REPOSITORY_URL,l=a?Fr(a):void 0,c=ie("log","-1","--pretty=%s"),u=ie("log","-1","--pretty=%ae");Object.assign(e,{ciProvider:"CircleCI",gitCommit:t,gitBranch:process.env.CIRCLE_BRANCH,gitRepo:s,commitMessage:c,authorEmail:u,prNumber:i,prUrl:o,ciBuildId:process.env.CIRCLE_BUILD_NUM,ciBuildUrl:process.env.CIRCLE_BUILD_URL,commitUrl:t&&l?`${l}/commit/${t}`:void 0,triggeredBy:process.env.CIRCLE_USERNAME})}else{e.ciProvider="Local";let t=ie("rev-parse","HEAD"),n=ie("rev-parse","--abbrev-ref","HEAD"),r=ie("log","-1","--pretty=%s"),s=ie("log","-1","--pretty=%ae"),o=ie("remote","get-url","origin"),i=o?Fr(o):void 0;Object.assign(e,{gitCommit:t,gitBranch:n,commitMessage:r,authorEmail:s,commitUrl:i&&t?`${i}/commit/${t}`:void 0})}return e}function Oi(e){switch(e){case"passed":return"Passed";case"failed":return"Failed";case"timedOut":return"TimedOut";case"skipped":return"Skipped";case"interrupted":return"Failed";default:return"Failed"}}function Ur(e){switch(e){case"passed":return"passed";case"skipped":return"skipped";default:return"failed"}}function Li(e){return e.length===0||e.every(t=>t.status==="skipped")?"Skipped":e.some(t=>t.status==="failed"||t.status==="timedOut"||t.status==="interrupted")?"Failed":"Passed"}function Ri(e,t){let n=new Map;for(let r of t){r.screenshotS3Uris={};let s=n.get(r.testCaseName);s?s.push(r):n.set(r.testCaseName,[r])}return e.map(r=>n.get(r.title)?.shift())}function nt(e,t){return ee.isAbsolute(t)?t:ee.join(e,t)}function Ci(e,t,n,r){let s={};for(let o of e.steps)s[o.stepId]={description:o.description,status:o.status,duration:o.duration,message:o.error??o.message,screenshotS3Uri:t[o.stepId]};return{schemaVersion:2,result:Ur(e.status),flaky:!1,segments:[{outcome:Ur(e.status),createdAt:e.endTime??new Date().toISOString(),fixId:null,resultJson:s,consoleLogs:[],stdout:e.stdout??"",stderr:e.stderr??"",videoS3Uri:n,traceS3Uri:r,actionStepsMap:e.actionStepsMap??{}}]}}function Wt(e){return $i("md5").update(e).digest("base64")}function Bt(e){return Wt(J.readFileSync(e))}async function Ht(e,t){let n=J.readFileSync(t),r=ee.extname(t).toLowerCase(),o={".png":"image/png",".webm":"video/webm",".zip":"application/zip",".json":"application/json"}[r]??"application/octet-stream";await ve.put(e,n,{headers:{"Content-Type":o,"Content-MD5":Wt(n)}})}async function Br(e,t,n,r){let s=Qe(r,process.env.SHIPLIGHT_API_URL),o={Authorization:`Bearer ${r}`,"Content-Type":"application/json"},i=Ii(),a=e.tests.map(p=>{let v={testCaseName:p.title,testCaseBaseName:p.baseTitle,suiteName:p.suiteName,file:p.file,tags:p.tags,suiteTags:p.suiteTags,baseUrl:p.baseUrl,skip:p.skip,slow:p.slow,timeout:p.timeout,parameterSetName:p.parameterSetName,flaky:p.flaky,retries:p.retries};if(p.videoPath){let w=nt(t,p.videoPath);J.existsSync(w)&&(v.videoMd5=Bt(w))}if(p.tracePath){let w=nt(t,p.tracePath);J.existsSync(w)&&(v.traceMd5=Bt(w))}return v}),l=e.tests.length;console.log(`[reporter] Uploading ${l} test result(s) to Shiplight cloud...`),console.log("[reporter] [1/4] Creating run record...");let u=(await ve.post(`${s}/v1/local-runs`,{trigger:i.ciProvider,startTime:n,metadata:i,tests:a},{headers:o})).data;console.log(`[reporter] [1/4] Run record created (testRunId=${u.testRunId})`);let d=Ri(e.tests,u.testCaseResults);console.log("[reporter] [2/4] Requesting screenshot upload URLs..."),await Promise.all(e.tests.map(async(p,v)=>{let w=d[v];if(!w)return;let m=p.steps.filter(P=>P.screenshot);if(!m.length)return;let y=m.map(P=>P.stepId),k={};for(let P of m){let E=ee.isAbsolute(P.screenshot)?P.screenshot:ee.join(t,P.screenshot);J.existsSync(E)&&(k[P.stepId]=Bt(E))}try{let P=await ve.post(`${s}/v1/local-runs/${u.testRunId}/results/${w.testCaseResultId}/screenshot-urls`,{stepIds:y,md5s:k},{headers:o});w.uploadUrls.screenshots=P.data.screenshots,w.screenshotS3Uris=P.data.screenshotS3Uris,console.log(`[reporter] [2/4] Got ${y.length} screenshot URL(s) for "${p.title}"`)}catch(P){console.warn(`[reporter] Failed to get screenshot URLs for "${p.title}":`,P)}})),console.log("[reporter] [3/4] Uploading assets...");let f=(await Promise.all(e.tests.map(async(p,v)=>{let w=d[v];if(!w){console.warn(`[reporter] No result slot found for test "${p.title}", skipping.`);return}let m=w.uploadUrls,y={},k=0;await Promise.all(p.steps.map(async L=>{if(L.screenshot&&m.screenshots?.[L.stepId]){let he=ee.isAbsolute(L.screenshot)?L.screenshot:ee.join(t,L.screenshot);if(J.existsSync(he))try{await Ht(m.screenshots[L.stepId],he),y[L.stepId]=w.screenshotS3Uris[L.stepId],k++}catch(as){console.warn(`[reporter] Screenshot upload failed for step ${L.stepId}:`,as)}}})),k>0&&console.log(`[reporter] [3/4] Uploaded ${k} screenshot(s) for "${p.title}"`);let P;if(p.videoPath&&m.video){let L=nt(t,p.videoPath);if(J.existsSync(L)){console.log(`[reporter] [3/4] Uploading video for "${p.title}"...`);try{await Ht(m.video,L),P=w.s3Uris.video,console.log(`[reporter] [3/4] Video uploaded for "${p.title}"`)}catch(he){console.warn("[reporter] Video upload failed:",he)}}}let E;if(p.tracePath&&m.trace){let L=nt(t,p.tracePath);if(J.existsSync(L)){console.log(`[reporter] [3/4] Uploading trace for "${p.title}"...`);try{await Ht(m.trace,L),E=w.s3Uris.trace,console.log(`[reporter] [3/4] Trace uploaded for "${p.title}"`)}catch(he){console.warn("[reporter] Trace upload failed:",he)}}}console.log(`[reporter] [3/4] Uploading report for "${p.title}"...`);let Se=Ci(p,y,P,E),Oe=Buffer.from(JSON.stringify(Se)),Kt=Wt(Oe),zt=await ve.post(`${s}/v1/local-runs/${u.testRunId}/results/${w.testCaseResultId}/report-url`,{md5:Kt},{headers:o}),os=zt.data.reportUrl,is=zt.data.reportS3Uri;return await ve.put(os,Oe,{headers:{"Content-Type":"application/json","Content-MD5":Kt}}),console.log(`[reporter] [3/4] Report uploaded for "${p.title}"`),{testCaseResultId:w.testCaseResultId,result:Oi(p.status),durationMs:p.duration,startTime:p.startTime,endTime:p.endTime,error:p.error,reportS3Uri:is,videoS3Uri:P,traceS3Uri:E,metadata:{suiteName:p.suiteName,file:p.file}}}))).filter(p=>!!p);console.log("[reporter] [4/4] Finalising run...");let g=Li(e.tests);console.log(`[reporter] [4/4] Overall status: ${g}`);let h=await ve.put(`${s}/v1/local-runs/${u.testRunId}/complete`,{status:g,endTime:new Date().toISOString(),totalDuration:e.totalDuration,results:f},{headers:o});console.log(`
1559
- Shiplight cloud report: ${Ni(h.data.reportUrl,s)}`)}function Ni(e,t){if(/^https?:\/\//.test(e))return e;let n=e.startsWith("/")?e:`/${e}`;return`${Di(t)}${n}`}function Di(e){let t=e.endsWith("/")?e.slice(0,-1):e;return t==="https://api.shiplight.ai"?"https://app.shiplight.ai":t==="https://nova-api.shiplight.ai"?"https://nova.shiplight.ai":t}var Hr=_(()=>{"use strict";Ot()});var Vr={};te(Vr,{buildGitHubSummary:()=>zr,runReport:()=>ji});import*as $ from"fs";import*as A from"path";async function ji(e){(e.includes("--help")||e.includes("-h"))&&(console.log("Usage: shiplight report [folder] [options]"),console.log(" shiplight report --merge <dirs...> [options]"),console.log(""),console.log("Regenerates index.html from report-data.json in the given folder."),console.log("With --merge, combines multiple shard report directories into one."),console.log(""),console.log("Options:"),console.log(" --open Open the report in the default browser after generating"),console.log(" --merge Merge multiple report directories into one"),console.log(" -o, --output <dir> Output directory for merged report (default: ./shiplight-report)"),console.log(" --github-summary Write test summary to $GITHUB_STEP_SUMMARY"),console.log(""),console.log("Examples:"),console.log(" shiplight report # regenerate ./shiplight-report/index.html"),console.log(" shiplight report my-report --open # regenerate and open"),console.log(" shiplight report --merge all-shards/*/shiplight-report/ # merge shard reports"),console.log(" shiplight report --merge shard-0/ shard-1/ -o combined-report # merge with custom output"),process.exit(0));let t=e.includes("--open"),n=e.includes("--merge"),r=e.includes("--github-summary");!n&&r&&console.warn("Warning: --github-summary is only supported with --merge, ignoring."),n?await Ui(e,t,r):await Fi(e,t)}async function Fi(e,t){let n=e.find(a=>!a.startsWith("--"))||"shiplight-report",r=A.isAbsolute(n)?n:A.join(process.cwd(),n),s=A.join(r,"report-data.json");$.existsSync(s)||(console.error(`Error: ${s} not found.`),console.error("Run a test first to generate report artifacts, then use this command to regenerate the HTML."),process.exit(1));let o;try{o=JSON.parse($.readFileSync(s,"utf-8"))}catch(a){console.error(`Error: Failed to parse ${s}`),console.error(a instanceof Error?a.message:String(a)),process.exit(1)}let i=A.join(r,"index.html");if($.writeFileSync(i,Ut(o),"utf-8"),console.log(`Shiplight report regenerated: ${i}`),await Kr(o,r),t)try{let a=(await import("open")).default;await a(i)}catch{}}async function Ui(e,t,n){let r=A.join(process.cwd(),"shiplight-report"),s=e.findIndex(g=>g==="-o"||g==="--output");if(s!==-1&&e[s+1]){let g=e[s+1];r=A.isAbsolute(g)?g:A.join(process.cwd(),g)}let o=new Set(["-o","--output"]),i=new Set(["--merge","--open","--github-summary","-o","--output"]),a=[];for(let g=0;g<e.length;g++){let h=e[g];if(o.has(h)){g++;continue}if(i.has(h))continue;let p=A.isAbsolute(h)?h:A.join(process.cwd(),h);a.push(p)}a.length===0&&(console.error("Error: --merge requires at least one input directory."),console.error("Usage: shiplight report --merge dir1/ dir2/ [-o output-dir]"),process.exit(1));let l=[],c=0,u=0;$.mkdirSync(A.join(r,"screenshots"),{recursive:!0});for(let g=0;g<a.length;g++){let h=a[g],p=`shard-${g}`,v=A.join(h,"report-data.json");if(!$.existsSync(v)){console.warn(`Warning: No report-data.json found in ${h}, skipping.`);continue}let w;try{w=JSON.parse($.readFileSync(v,"utf-8"))}catch{console.warn(`Warning: Failed to parse ${v}, skipping.`);continue}console.log(`Merging ${p}: ${w.tests.length} tests from ${h}`),c+=w.totalDuration||0;let m=A.join(h,"screenshots");$.existsSync(m)&&Gr(m,A.join(r,"screenshots",p));let y=A.join(r,p);for(let k of w.tests){let P=[k,...k.attempts||[]];for(let E of P)Bi(E.steps,p),E.videoPath&&Wr(h,E.videoPath,y)&&(E.videoPath=`${p}/${E.videoPath}`),E.tracePath&&Wr(h,E.tracePath,y)&&(E.tracePath=`${p}/${E.tracePath}`);l.push(k)}u++}l.length===0&&(console.error("Error: No tests found across any input directories."),process.exit(1));let d={tests:l,totalDuration:c,timestamp:new Date().toISOString(),shiplightVersion:_e};$.writeFileSync(A.join(r,"report-data.json"),JSON.stringify(d,null,2),"utf-8");let f=A.join(r,"index.html");if($.writeFileSync(f,Ut(d),"utf-8"),console.log(`
1560
- Merged ${l.length} tests from ${u} shards into: ${f}`),await Kr(d,r),n&&Hi(l),t)try{let g=(await import("open")).default;await g(f)}catch{}}function Wr(e,t,n){let r=A.resolve(e,t);return r.startsWith(A.resolve(e)+A.sep)?$.existsSync(r)?($.mkdirSync(n,{recursive:!0}),$.copyFileSync(r,A.join(n,t)),!0):!1:(console.warn(`Warning: Skipping artifact with path traversal: ${t}`),!1)}function Gr(e,t){$.mkdirSync(t,{recursive:!0});for(let n of $.readdirSync(e,{withFileTypes:!0})){let r=A.join(e,n.name),s=A.join(t,n.name);n.isDirectory()?Gr(r,s):$.copyFileSync(r,s)}}function Bi(e,t){for(let n of e)n.screenshot?.startsWith("screenshots/")&&(n.screenshot=n.screenshot.replace("screenshots/",`screenshots/${t}/`))}async function Kr(e,t){if(process.env.REPORT_TO_CLOUD!=="true")return;let n=process.env.SHIPLIGHT_API_TOKEN;if(!n){console.warn("[report] REPORT_TO_CLOUD is enabled but no SHIPLIGHT_API_TOKEN found, skipping cloud upload.");return}let r=e.tests.map(o=>o.startTime).filter(o=>!!o),s=r.length>0?r.sort()[0]:e.timestamp??new Date().toISOString();try{await Br(e,t,s,n)}catch(o){console.warn("[report] Cloud upload failed:",o)}}function Gt(e){let t=e.file.replace(".yaml.spec.ts",".test.yaml"),n=A.join("tests",A.basename(t));return{name:e.title||A.basename(t),yamlPath:n}}function zr(e){let t=e.filter(a=>!a.file.includes("auth.setup")),n=t.filter(a=>a.flaky),r=t.filter(a=>a.status==="passed"&&!a.flaky),s=t.filter(a=>a.status!=="passed"),o=t.length,i=`## Test Results
1128
+ </html>`}var Ds=_(()=>{"use strict"});import*as J from"fs";import*as ee from"path";import{execFileSync as _i}from"child_process";import{createHash as xi}from"crypto";import ve from"axios";function js(e){let t=e.match(/^git@([^:]+):(.+?)(?:\.git)?$/);if(t)return`https://${t[1]}/${t[2]}`;let r=e.match(/^https?:\/\/([^/]+)\/(.+?)(?:\.git)?$/);if(r)return`https://${r[1]}/${r[2]}`}function ie(...e){try{return _i("git",e,{stdio:["pipe","pipe","ignore"]}).toString().trim()||void 0}catch{return}}function Ti(){let e=process.env.GITHUB_EVENT_PATH;if(!e)return{};try{let t=J.readFileSync(e,"utf8");return JSON.parse(t)}catch{return{}}}function Pi(){let e={nodeVersion:process.version};if(process.env.GITHUB_ACTIONS){let t=process.env.GITHUB_SERVER_URL??"https://github.com",r=process.env.GITHUB_REPOSITORY??"",s=process.env.GITHUB_RUN_ID??"",n=process.env.GITHUB_EVENT_NAME??"",i=Ti().pull_request,a=i?.head?.sha,l=process.env.SHIPLIGHT_GIT_SHA??a??process.env.GITHUB_SHA??"",c=process.env.GITHUB_REF??"",u=process.env.SHIPLIGHT_PR_NUMBER??process.env.GITHUB_PR_NUMBER??c.match(/^refs\/pull\/(\d+)\//)?.[1],d=(process.env.SHIPLIGHT_GIT_BRANCH??process.env.GITHUB_HEAD_REF)||process.env.GITHUB_REF_NAME,f=process.env.SHIPLIGHT_PR_TITLE??i?.title,g=ie("log","-1","--pretty=%s"),h=ie("log","-1","--pretty=%ae");Object.assign(e,{ciProvider:"GitHub Action",gitCommit:l,gitBranch:d,gitRepo:r,commitMessage:g,authorEmail:h,prNumber:u,prTitle:f,prUrl:u&&r?`${t}/${r}/pull/${u}`:void 0,ciBuildId:s,ciBuildUrl:s&&r?`${t}/${r}/actions/runs/${s}`:void 0,commitUrl:l&&r?`${t}/${r}/commit/${l}`:void 0,triggeredBy:process.env.GITHUB_ACTOR,eventName:n,workflow:process.env.GITHUB_WORKFLOW})}else if(process.env.GITLAB_CI){let t=process.env.CI_PROJECT_URL??"",r=process.env.CI_COMMIT_SHA??"",s=process.env.CI_MERGE_REQUEST_IID,n=process.env.CI_COMMIT_AUTHOR_EMAIL??process.env.GITLAB_USER_EMAIL,o=process.env.CI_PROJECT_PATH;Object.assign(e,{ciProvider:"GitLab CI",gitCommit:r,gitBranch:process.env.CI_COMMIT_REF_NAME,gitRepo:o,commitMessage:process.env.CI_COMMIT_MESSAGE,authorEmail:n,prNumber:s,prTitle:process.env.CI_MERGE_REQUEST_TITLE,prUrl:s&&t?`${t}/-/merge_requests/${s}`:void 0,ciBuildId:process.env.CI_PIPELINE_ID,ciBuildUrl:process.env.CI_PIPELINE_URL,commitUrl:r&&t?`${t}/commit/${r}`:void 0,triggeredBy:process.env.GITLAB_USER_LOGIN})}else if(process.env.CIRCLECI){let t=process.env.CIRCLE_SHA1??"",r=process.env.CIRCLE_PROJECT_USERNAME??"",s=process.env.CIRCLE_PROJECT_REPONAME??"",n=r&&s?`${r}/${s}`:void 0,o=process.env.CIRCLE_PULL_REQUEST,i=process.env.CIRCLE_PR_NUMBER??o?.match(/\/pull\/(\d+)$/)?.[1],a=process.env.CIRCLE_REPOSITORY_URL,l=a?js(a):void 0,c=ie("log","-1","--pretty=%s"),u=ie("log","-1","--pretty=%ae");Object.assign(e,{ciProvider:"CircleCI",gitCommit:t,gitBranch:process.env.CIRCLE_BRANCH,gitRepo:n,commitMessage:c,authorEmail:u,prNumber:i,prUrl:o,ciBuildId:process.env.CIRCLE_BUILD_NUM,ciBuildUrl:process.env.CIRCLE_BUILD_URL,commitUrl:t&&l?`${l}/commit/${t}`:void 0,triggeredBy:process.env.CIRCLE_USERNAME})}else{e.ciProvider="Local";let t=ie("rev-parse","HEAD"),r=ie("rev-parse","--abbrev-ref","HEAD"),s=ie("log","-1","--pretty=%s"),n=ie("log","-1","--pretty=%ae"),o=ie("remote","get-url","origin"),i=o?js(o):void 0;Object.assign(e,{gitCommit:t,gitBranch:r,commitMessage:s,authorEmail:n,commitUrl:i&&t?`${i}/commit/${t}`:void 0})}return e}function ki(e){switch(e){case"passed":return"Passed";case"failed":return"Failed";case"timedOut":return"TimedOut";case"skipped":return"Skipped";case"interrupted":return"Failed";default:return"Failed"}}function Fs(e){switch(e){case"passed":return"passed";case"skipped":return"skipped";default:return"failed"}}function Ei(e){return e.length===0||e.every(t=>t.status==="skipped")?"Skipped":e.some(t=>t.status==="failed"||t.status==="timedOut"||t.status==="interrupted")?"Failed":"Passed"}function Ai(e,t){let r=new Map;for(let s of t){s.screenshotS3Uris={};let n=r.get(s.testCaseName);n?n.push(s):r.set(s.testCaseName,[s])}return e.map(s=>r.get(s.title)?.shift())}function rt(e,t){return ee.isAbsolute(t)?t:ee.join(e,t)}function $i(e,t,r,s){let n={};for(let o of e.steps)n[o.stepId]={description:o.description,status:o.status,duration:o.duration,message:o.error??o.message,screenshotS3Uri:t[o.stepId]};return{schemaVersion:2,result:Fs(e.status),flaky:!1,segments:[{outcome:Fs(e.status),createdAt:e.endTime??new Date().toISOString(),fixId:null,resultJson:n,consoleLogs:[],stdout:e.stdout??"",stderr:e.stderr??"",videoS3Uri:r,traceS3Uri:s,actionStepsMap:e.actionStepsMap??{}}]}}function Wt(e){return xi("md5").update(e).digest("base64")}function Bt(e){return Wt(J.readFileSync(e))}async function Ht(e,t){let r=J.readFileSync(t),s=ee.extname(t).toLowerCase(),o={".png":"image/png",".webm":"video/webm",".zip":"application/zip",".json":"application/json"}[s]??"application/octet-stream";await ve.put(e,r,{headers:{"Content-Type":o,"Content-MD5":Wt(r)}})}async function Us(e,t,r,s){let n=Qe(s,process.env.SHIPLIGHT_API_URL),o={Authorization:`Bearer ${s}`,"Content-Type":"application/json"},i=Pi(),a=e.tests.map(p=>{let v={testCaseName:p.title,testCaseBaseName:p.baseTitle,suiteName:p.suiteName,file:p.file,tags:p.tags,suiteTags:p.suiteTags,baseUrl:p.baseUrl,skip:p.skip,slow:p.slow,timeout:p.timeout,parameterSetName:p.parameterSetName,flaky:p.flaky,retries:p.retries};if(p.videoPath){let w=rt(t,p.videoPath);J.existsSync(w)&&(v.videoMd5=Bt(w))}if(p.tracePath){let w=rt(t,p.tracePath);J.existsSync(w)&&(v.traceMd5=Bt(w))}return v}),l=e.tests.length;console.log(`[reporter] Uploading ${l} test result(s) to Shiplight cloud...`),console.log("[reporter] [1/4] Creating run record...");let u=(await ve.post(`${n}/v1/local-runs`,{trigger:i.ciProvider,startTime:r,metadata:i,tests:a},{headers:o})).data;console.log(`[reporter] [1/4] Run record created (testRunId=${u.testRunId})`);let d=Ai(e.tests,u.testCaseResults);console.log("[reporter] [2/4] Requesting screenshot upload URLs..."),await Promise.all(e.tests.map(async(p,v)=>{let w=d[v];if(!w)return;let m=p.steps.filter(k=>k.screenshot);if(!m.length)return;let y=m.map(k=>k.stepId),P={};for(let k of m){let A=ee.isAbsolute(k.screenshot)?k.screenshot:ee.join(t,k.screenshot);J.existsSync(A)&&(P[k.stepId]=Bt(A))}try{let k=await ve.post(`${n}/v1/local-runs/${u.testRunId}/results/${w.testCaseResultId}/screenshot-urls`,{stepIds:y,md5s:P},{headers:o});w.uploadUrls.screenshots=k.data.screenshots,w.screenshotS3Uris=k.data.screenshotS3Uris,console.log(`[reporter] [2/4] Got ${y.length} screenshot URL(s) for "${p.title}"`)}catch(k){console.warn(`[reporter] Failed to get screenshot URLs for "${p.title}":`,k)}})),console.log("[reporter] [3/4] Uploading assets...");let f=(await Promise.all(e.tests.map(async(p,v)=>{let w=d[v];if(!w){console.warn(`[reporter] No result slot found for test "${p.title}", skipping.`);return}let m=w.uploadUrls,y={},P=0;await Promise.all(p.steps.map(async L=>{if(L.screenshot&&m.screenshots?.[L.stepId]){let he=ee.isAbsolute(L.screenshot)?L.screenshot:ee.join(t,L.screenshot);if(J.existsSync(he))try{await Ht(m.screenshots[L.stepId],he),y[L.stepId]=w.screenshotS3Uris[L.stepId],P++}catch(an){console.warn(`[reporter] Screenshot upload failed for step ${L.stepId}:`,an)}}})),P>0&&console.log(`[reporter] [3/4] Uploaded ${P} screenshot(s) for "${p.title}"`);let k;if(p.videoPath&&m.video){let L=rt(t,p.videoPath);if(J.existsSync(L)){console.log(`[reporter] [3/4] Uploading video for "${p.title}"...`);try{await Ht(m.video,L),k=w.s3Uris.video,console.log(`[reporter] [3/4] Video uploaded for "${p.title}"`)}catch(he){console.warn("[reporter] Video upload failed:",he)}}}let A;if(p.tracePath&&m.trace){let L=rt(t,p.tracePath);if(J.existsSync(L)){console.log(`[reporter] [3/4] Uploading trace for "${p.title}"...`);try{await Ht(m.trace,L),A=w.s3Uris.trace,console.log(`[reporter] [3/4] Trace uploaded for "${p.title}"`)}catch(he){console.warn("[reporter] Trace upload failed:",he)}}}console.log(`[reporter] [3/4] Uploading report for "${p.title}"...`);let Se=$i(p,y,k,A),Oe=Buffer.from(JSON.stringify(Se)),Kt=Wt(Oe),zt=await ve.post(`${n}/v1/local-runs/${u.testRunId}/results/${w.testCaseResultId}/report-url`,{md5:Kt},{headers:o}),nn=zt.data.reportUrl,on=zt.data.reportS3Uri;return await ve.put(nn,Oe,{headers:{"Content-Type":"application/json","Content-MD5":Kt}}),console.log(`[reporter] [3/4] Report uploaded for "${p.title}"`),{testCaseResultId:w.testCaseResultId,result:ki(p.status),durationMs:p.duration,startTime:p.startTime,endTime:p.endTime,error:p.error,reportS3Uri:on,videoS3Uri:k,traceS3Uri:A,metadata:{suiteName:p.suiteName,file:p.file}}}))).filter(p=>!!p);console.log("[reporter] [4/4] Finalising run...");let g=Ei(e.tests);console.log(`[reporter] [4/4] Overall status: ${g}`);let h=await ve.put(`${n}/v1/local-runs/${u.testRunId}/complete`,{status:g,endTime:new Date().toISOString(),totalDuration:e.totalDuration,results:f},{headers:o});console.log(`
1129
+ Shiplight cloud report: ${Mi(h.data.reportUrl,n)}`)}function Mi(e,t){if(/^https?:\/\//.test(e))return e;let r=e.startsWith("/")?e:`/${e}`;return`${Ii(t)}${r}`}function Ii(e){let t=e.endsWith("/")?e.slice(0,-1):e;return t==="https://api.shiplight.ai"?"https://app.shiplight.ai":t==="https://nova-api.shiplight.ai"?"https://nova.shiplight.ai":t}var Bs=_(()=>{"use strict";Ot()});var zs={};te(zs,{buildGitHubSummary:()=>Ks,runReport:()=>Oi});import*as $ from"fs";import*as E from"path";async function Oi(e){(e.includes("--help")||e.includes("-h"))&&(console.log("Usage: shiplight report [folder] [options]"),console.log(" shiplight report --merge <dirs...> [options]"),console.log(""),console.log("Regenerates index.html from report-data.json in the given folder."),console.log("With --merge, combines multiple shard report directories into one."),console.log(""),console.log("Options:"),console.log(" --open Open the report in the default browser after generating"),console.log(" --merge Merge multiple report directories into one"),console.log(" -o, --output <dir> Output directory for merged report (default: ./shiplight-report)"),console.log(" --github-summary Write test summary to $GITHUB_STEP_SUMMARY"),console.log(""),console.log("Examples:"),console.log(" shiplight report # regenerate ./shiplight-report/index.html"),console.log(" shiplight report my-report --open # regenerate and open"),console.log(" shiplight report --merge all-shards/*/shiplight-report/ # merge shard reports"),console.log(" shiplight report --merge shard-0/ shard-1/ -o combined-report # merge with custom output"),process.exit(0));let t=e.includes("--open"),r=e.includes("--merge"),s=e.includes("--github-summary");!r&&s&&console.warn("Warning: --github-summary is only supported with --merge, ignoring."),r?await Ri(e,t,s):await Li(e,t)}async function Li(e,t){let r=e.find(a=>!a.startsWith("--"))||"shiplight-report",s=E.isAbsolute(r)?r:E.join(process.cwd(),r),n=E.join(s,"report-data.json");$.existsSync(n)||(console.error(`Error: ${n} not found.`),console.error("Run a test first to generate report artifacts, then use this command to regenerate the HTML."),process.exit(1));let o;try{o=JSON.parse($.readFileSync(n,"utf-8"))}catch(a){console.error(`Error: Failed to parse ${n}`),console.error(a instanceof Error?a.message:String(a)),process.exit(1)}let i=E.join(s,"index.html");if($.writeFileSync(i,Ut(o),"utf-8"),console.log(`Shiplight report regenerated: ${i}`),await Gs(o,s),t)try{let a=(await import("open")).default;await a(i)}catch{}}async function Ri(e,t,r){let s=E.join(process.cwd(),"shiplight-report"),n=e.findIndex(g=>g==="-o"||g==="--output");if(n!==-1&&e[n+1]){let g=e[n+1];s=E.isAbsolute(g)?g:E.join(process.cwd(),g)}let o=new Set(["-o","--output"]),i=new Set(["--merge","--open","--github-summary","-o","--output"]),a=[];for(let g=0;g<e.length;g++){let h=e[g];if(o.has(h)){g++;continue}if(i.has(h))continue;let p=E.isAbsolute(h)?h:E.join(process.cwd(),h);a.push(p)}a.length===0&&(console.error("Error: --merge requires at least one input directory."),console.error("Usage: shiplight report --merge dir1/ dir2/ [-o output-dir]"),process.exit(1));let l=[],c=0,u=0;$.mkdirSync(E.join(s,"screenshots"),{recursive:!0});for(let g=0;g<a.length;g++){let h=a[g],p=`shard-${g}`,v=E.join(h,"report-data.json");if(!$.existsSync(v)){console.warn(`Warning: No report-data.json found in ${h}, skipping.`);continue}let w;try{w=JSON.parse($.readFileSync(v,"utf-8"))}catch{console.warn(`Warning: Failed to parse ${v}, skipping.`);continue}console.log(`Merging ${p}: ${w.tests.length} tests from ${h}`),c+=w.totalDuration||0;let m=E.join(h,"screenshots");$.existsSync(m)&&Ws(m,E.join(s,"screenshots",p));let y=E.join(s,p);for(let P of w.tests){let k=[P,...P.attempts||[]];for(let A of k)Ci(A.steps,p),A.videoPath&&Hs(h,A.videoPath,y)&&(A.videoPath=`${p}/${A.videoPath}`),A.tracePath&&Hs(h,A.tracePath,y)&&(A.tracePath=`${p}/${A.tracePath}`);l.push(P)}u++}l.length===0&&(console.error("Error: No tests found across any input directories."),process.exit(1));let d={tests:l,totalDuration:c,timestamp:new Date().toISOString(),shiplightVersion:_e};$.writeFileSync(E.join(s,"report-data.json"),JSON.stringify(d,null,2),"utf-8");let f=E.join(s,"index.html");if($.writeFileSync(f,Ut(d),"utf-8"),console.log(`
1130
+ Merged ${l.length} tests from ${u} shards into: ${f}`),await Gs(d,s),r&&Ni(l),t)try{let g=(await import("open")).default;await g(f)}catch{}}function Hs(e,t,r){let s=E.resolve(e,t);return s.startsWith(E.resolve(e)+E.sep)?$.existsSync(s)?($.mkdirSync(r,{recursive:!0}),$.copyFileSync(s,E.join(r,t)),!0):!1:(console.warn(`Warning: Skipping artifact with path traversal: ${t}`),!1)}function Ws(e,t){$.mkdirSync(t,{recursive:!0});for(let r of $.readdirSync(e,{withFileTypes:!0})){let s=E.join(e,r.name),n=E.join(t,r.name);r.isDirectory()?Ws(s,n):$.copyFileSync(s,n)}}function Ci(e,t){for(let r of e)r.screenshot?.startsWith("screenshots/")&&(r.screenshot=r.screenshot.replace("screenshots/",`screenshots/${t}/`))}async function Gs(e,t){if(process.env.REPORT_TO_CLOUD!=="true")return;let r=process.env.SHIPLIGHT_API_TOKEN;if(!r){console.warn("[report] REPORT_TO_CLOUD is enabled but no SHIPLIGHT_API_TOKEN found, skipping cloud upload.");return}let s=e.tests.map(o=>o.startTime).filter(o=>!!o),n=s.length>0?s.sort()[0]:e.timestamp??new Date().toISOString();try{await Us(e,t,n,r)}catch(o){console.warn("[report] Cloud upload failed:",o)}}function Gt(e){let t=e.file.replace(".yaml.spec.ts",".test.yaml"),r=E.join("tests",E.basename(t));return{name:e.title||E.basename(t),yamlPath:r}}function Ks(e){let t=e.filter(a=>!a.file.includes("auth.setup")),r=t.filter(a=>a.flaky),s=t.filter(a=>a.status==="passed"&&!a.flaky),n=t.filter(a=>a.status!=="passed"),o=t.length,i=`## Test Results
1561
1131
 
1562
- `;if(s.length===0&&n.length===0?i+=`\u2705 All ${o} tests passed
1132
+ `;if(n.length===0&&r.length===0?i+=`\u2705 All ${o} tests passed
1563
1133
 
1564
- `:s.length===0?i+=`\u2705 ${r.length} passed, \u26A0\uFE0F ${n.length} flaky / ${o} total
1134
+ `:n.length===0?i+=`\u2705 ${s.length} passed, \u26A0\uFE0F ${r.length} flaky / ${o} total
1565
1135
 
1566
- `:i+=`\u274C ${s.length} failed, \u26A0\uFE0F ${n.length} flaky, \u2705 ${r.length} passed / ${o} total
1136
+ `:i+=`\u274C ${n.length} failed, \u26A0\uFE0F ${r.length} flaky, \u2705 ${s.length} passed / ${o} total
1567
1137
 
1568
- `,s.length>0){let a=[...new Set(s.map(l=>Gt(l).yamlPath))];i+=`### Failed
1138
+ `,n.length>0){let a=[...new Set(n.map(l=>Gt(l).yamlPath))];i+=`### Failed
1569
1139
 
1570
1140
  `;for(let l of a)i+=`- \`npx shiplight test ${l}\`
1571
1141
  `;i+=`
@@ -1573,18 +1143,18 @@ Merged ${l.length} tests from ${u} shards into: ${f}`),await Kr(d,r),n&&Hi(l),t)
1573
1143
 
1574
1144
  `,i+="```sh\n",i+=`npx shiplight test ${a.map(l=>`"${l}"`).join(` \\
1575
1145
  `)}
1576
- `,i+="```\n\n"}if(n.length>0){let a=[...new Set(n.map(l=>Gt(l).yamlPath))];i+=`### Flaky (${a.length})
1146
+ `,i+="```\n\n"}if(r.length>0){let a=[...new Set(r.map(l=>Gt(l).yamlPath))];i+=`### Flaky (${a.length})
1577
1147
 
1578
1148
  `;for(let l of a)i+=`- \`${l}\`
1579
1149
  `;i+=`
1580
- `}if(r.length>0){let a=[...new Set(r.map(l=>Gt(l).yamlPath))];i+=`<details><summary>Passed (${a.length})</summary>
1150
+ `}if(s.length>0){let a=[...new Set(s.map(l=>Gt(l).yamlPath))];i+=`<details><summary>Passed (${a.length})</summary>
1581
1151
 
1582
1152
  `;for(let l of a)i+=`- ${l}
1583
1153
  `;i+=`
1584
1154
  </details>
1585
- `}return i}function Hi(e){let t=process.env.GITHUB_STEP_SUMMARY;if(!t){console.warn("Warning: $GITHUB_STEP_SUMMARY not set, skipping GitHub summary.");return}$.appendFileSync(t,zr(e)),console.log("GitHub step summary written.")}var Yr=_(()=>{"use strict";jr();Hr();Le()});var Jr,qr=_(()=>{"use strict";Jr="0.1.65"});var Xr={};te(Xr,{runTranspile:()=>Gi});import*as rt from"path";import{glob as Wi}from"glob";async function Gi(e){(e.includes("--help")||e.includes("-h"))&&(console.log("Usage: shiplight transpile [glob]"),console.log(""),console.log("Transpiles YAML test files to Playwright spec files (.yaml.spec.ts)."),console.log("Validates syntax and reports action coverage warnings."),console.log("Default glob: **/*.test.yaml"),console.log(""),console.log("Examples:"),console.log(" shiplight transpile # transpile all YAML tests"),console.log(' shiplight transpile "tests/**/*.test.yaml" # transpile specific directory'),console.log(" shiplight transpile tests/login.test.yaml # transpile a single file"),process.exit(0));let t=e[0]||"**/*.test.yaml",n=process.cwd(),r=await Wi(t,{cwd:n,ignore:["node_modules/**","*.yaml.spec.ts"]});r.length===0&&(console.log(`No files matched: ${t}`),process.exit(0));let s=0,o=0,i=0;for(let a of r.sort()){let l=rt.resolve(n,a),c=ar(l,{version:Jr});if(!c.valid){s++,console.log(`
1586
- \u2717 ${a}`);for(let d of c.errors)console.log(` ERROR: ${d}`);continue}i++;let u=rt.basename(c.specFile);if(c.warnings.length>0){o++,console.log(`\u26A0 ${a} \u2192 ${u}`);for(let d of c.warnings)console.log(` WARNING: ${d}`)}else console.log(`\u2713 ${a} \u2192 ${u}`)}console.log(`
1587
- ${r.length} file(s): ${i} transpiled, ${s} error(s), ${o} warning(s)`),process.exit(s>0?1:0)}var Zr=_(()=>{"use strict";Pt();qr()});var ts={};te(ts,{runInspect:()=>Ki});import*as st from"fs";import*as Qr from"path";async function Ki(e){(e.includes("--help")||e.includes("-h")||e.length===0)&&(console.log("Usage: shiplight inspect <file.test.yaml> [options]"),console.log(""),console.log("Parse a YAML test file and output the resulting TestFlow JSON."),console.log("Useful for verifying YAML \u2192 JSON conversion."),console.log(""),console.log("Options:"),console.log(" --pretty Pretty-print JSON (default)"),console.log(" --compact Compact JSON output"),console.log(" --stats Show statement statistics only"),console.log(""),console.log("Examples:"),console.log(" shiplight inspect tests/login.test.yaml"),console.log(" shiplight inspect tests/suite.test.yaml --stats"),console.log(" shiplight inspect tests/login.test.yaml --compact | jq ."),process.exit(e.length===0?1:0));let t=e.includes("--compact"),n=e.includes("--stats"),r=e.find(i=>!i.startsWith("--"));r||(console.error("Error: no file specified"),process.exit(1));let s=Qr.resolve(process.cwd(),r);st.existsSync(s)||(console.error(`Error: file not found: ${s}`),process.exit(1));let o=st.readFileSync(s,"utf-8");try{let i=Pe(o),a=C(o);if(n)zi(a,i);else{let l={...i.test_case_id!==void 0?{test_case_id:i.test_case_id}:{},...i.name?{name:i.name}:{},testFlow:a};console.log(JSON.stringify(l,null,t?0:2))}}catch(i){console.error(`Error parsing ${r}: ${i.message}`),process.exit(1)}}function zi(e,t){if(console.log(`File: ${t.name||"(unnamed)"}`),t.test_case_id!==void 0&&console.log(`Cloud ID: ${t.test_case_id}`),console.log(`Version: ${e.version||"unknown"}`),e.testGroup){let n=e.testGroup;console.log("Type: suite (testGroup)"),console.log(`Tests: ${n.tests.length}`);for(let r of n.tests){let s=r.skip?` [SKIP${typeof r.skip=="string"?`: ${r.skip}`:""}]`:"";console.log(` - ${r.name}: ${r.statements.length} statements${r.teardown?`, ${r.teardown.length} teardown`:""}${s}`)}n.beforeAll?.length&&console.log(`Hooks: beforeAll (${n.beforeAll.length})`),n.afterAll?.length&&console.log(`Hooks: afterAll (${n.afterAll.length})`),n.beforeEach?.length&&console.log(`Hooks: beforeEach (${n.beforeEach.length})`),n.afterEach?.length&&console.log(`Hooks: afterEach (${n.afterEach.length})`)}else{console.log("Type: single test"),console.log(`Goal: ${e.goal}`),e.url&&console.log(`URL: ${e.url}`),e.baseURL&&console.log(`Base URL: ${e.baseURL}`),console.log(`Statements: ${e.statements?.length??0}`),e.teardown?.length&&console.log(`Teardown: ${e.teardown.length}`);let n=es(e.statements??[]);console.log(` DRAFT: ${n.drafts}, ACTION: ${n.actions}, STEP: ${n.steps}`)}}function es(e){let t={drafts:0,actions:0,steps:0};for(let n of e)if(n.type==="DRAFT")t.drafts++;else if(n.type==="ACTION")t.actions++;else if(n.type==="STEP"){t.steps++;let r=es(n.statements??[]);t.drafts+=r.drafts,t.actions+=r.actions,t.steps+=r.steps}return t}var ns=_(()=>{"use strict";ce()});var rs=ls((Vp,Vi)=>{Vi.exports={name:"shiplightai",version:"0.1.65",type:"module",description:"Shiplight CLI for running and debugging .test.yaml files",main:"dist/index.js",types:"dist/index.d.ts",bin:{shiplight:"dist/cli.js"},exports:{".":{types:"./dist/index.d.ts",import:"./dist/index.js",require:"./dist/cjs/index.cjs",default:"./dist/index.js"},"./fixture":{types:"./dist/fixture.d.ts",import:"./dist/fixture.js",require:"./dist/cjs/fixture.cjs",default:"./dist/fixture.js"},"./debugger-pw":{types:"./dist/debugger-pw.d.ts",import:"./dist/debugger-pw.js",require:"./dist/cjs/debugger-pw.cjs",default:"./dist/debugger-pw.js"},"./debugger-manager":{types:"./dist/debugger-manager.d.ts",import:"./dist/debugger-manager.js",require:"./dist/cjs/debugger-manager.cjs",default:"./dist/debugger-manager.js"},"./debugger-server":{types:"./dist/debugger-server.d.ts",import:"./dist/debugger-server.js",require:"./dist/cjs/debugger-server.cjs",default:"./dist/debugger-server.js"},"./reporter":{types:"./dist/reporter.d.ts",import:"./dist/reporter.js",require:"./dist/cjs/reporter.cjs",default:"./dist/reporter.js"},"./package.json":"./package.json"},files:["dist","!dist/**/*.map","README.md"],publishConfig:{registry:"https://registry.npmjs.org",access:"public"},scripts:{prebuild:"pnpm typecheck",build:"tsup",pack:"pnpm build && pnpm pack",clean:"rm -rf dist",dev:"tsup --watch","dev:run":"node --import tsx/esm src/cli.ts",test:"playwright test","test:unit":"tsx --test 'src/**/*.test.ts'",typecheck:"tsc --noEmit"},dependencies:{"@ai-sdk/anthropic":"^3.0.1","@ai-sdk/google":"^3.0.1","@ai-sdk/google-vertex":"^4.0.1","@ai-sdk/openai":"^3.0.1","@ai-sdk/provider":"^3.0.1","@anthropic-ai/claude-agent-sdk":"^0.1.72","@babel/parser":"^7.28.5","@babel/plugin-transform-typescript":"^7.27.0","@google/genai":"^1.34.0","google-auth-library":"^10.0.0","@babel/preset-env":"^7.26.9","@babel/preset-typescript":"^7.27.0","@modelcontextprotocol/sdk":"^1.29.0","@shiplightai/devtools-assets":"workspace:*",ai:"^6.0.3",axios:"^1.15.0",chalk:"^4.1.2",commander:"^11.0.0",dotenv:"^16.0.3",express:"^5.2.1","fs-extra":"^11.2.0",glob:"^13.0.0","html-to-text":"^9.0.5",open:"^10.1.0",openai:"^6.25.0",ora:"^5.4.1",otplib:"^13.4.0","p-retry":"^6.2.1",sharp:"^0.34.5",uuid:"^11.1.0",yaml:"^2.8.3",zod:"^3.22.0","zod-to-json-schema":"^3.24.6"},devDependencies:{"@playwright/test":"1.60.0","@types/express":"^4.17.21","@types/node":"^24.0.0","mcp-tools":"workspace:*","sdk-core":"workspace:*","sdk-internal":"workspace:*","shiplight-tools":"workspace:*","shiplight-types":"workspace:*","@loggia/common":"workspace:*",tsup:"^8.3.5",typescript:"5.5.4"},peerDependencies:{"@playwright/test":"^1.60.0"},engines:{node:">=22.0.0"},keywords:["playwright","yaml","testing","automation","ai","shiplight","mcp"],author:"Shiplight",license:"MIT"}});Le();import Yi from"dotenv";Yi.config();Vt();Yt();function ss(){console.log(`
1155
+ `}return i}function Ni(e){let t=process.env.GITHUB_STEP_SUMMARY;if(!t){console.warn("Warning: $GITHUB_STEP_SUMMARY not set, skipping GitHub summary.");return}$.appendFileSync(t,Ks(e)),console.log("GitHub step summary written.")}var Vs=_(()=>{"use strict";Ds();Bs();Le()});var Ys,Js=_(()=>{"use strict";Ys="0.1.67"});var Xs={};te(Xs,{runTranspile:()=>ji});import*as st from"path";import{glob as Di}from"glob";async function ji(e){(e.includes("--help")||e.includes("-h"))&&(console.log("Usage: shiplight transpile [glob]"),console.log(""),console.log("Transpiles YAML test files to Playwright spec files (.yaml.spec.ts)."),console.log("Validates syntax and reports action coverage warnings."),console.log("Default glob: **/*.test.yaml"),console.log(""),console.log("Examples:"),console.log(" shiplight transpile # transpile all YAML tests"),console.log(' shiplight transpile "tests/**/*.test.yaml" # transpile specific directory'),console.log(" shiplight transpile tests/login.test.yaml # transpile a single file"),process.exit(0));let t=e[0]||"**/*.test.yaml",r=process.cwd(),s=await Di(t,{cwd:r,ignore:["node_modules/**","*.yaml.spec.ts"]});s.length===0&&(console.log(`No files matched: ${t}`),process.exit(0));let n=0,o=0,i=0;for(let a of s.sort()){let l=st.resolve(r,a),c=is(l,{version:Ys});if(!c.valid){n++,console.log(`
1156
+ \u2717 ${a}`);for(let d of c.errors)console.log(` ERROR: ${d}`);continue}i++;let u=st.basename(c.specFile);if(c.warnings.length>0){o++,console.log(`\u26A0 ${a} \u2192 ${u}`);for(let d of c.warnings)console.log(` WARNING: ${d}`)}else console.log(`\u2713 ${a} \u2192 ${u}`)}console.log(`
1157
+ ${s.length} file(s): ${i} transpiled, ${n} error(s), ${o} warning(s)`),process.exit(n>0?1:0)}var qs=_(()=>{"use strict";kt();Js()});var en={};te(en,{runInspect:()=>Fi});import*as nt from"fs";import*as Zs from"path";async function Fi(e){(e.includes("--help")||e.includes("-h")||e.length===0)&&(console.log("Usage: shiplight inspect <file.test.yaml> [options]"),console.log(""),console.log("Parse a YAML test file and output the resulting TestFlow JSON."),console.log("Useful for verifying YAML \u2192 JSON conversion."),console.log(""),console.log("Options:"),console.log(" --pretty Pretty-print JSON (default)"),console.log(" --compact Compact JSON output"),console.log(" --stats Show statement statistics only"),console.log(""),console.log("Examples:"),console.log(" shiplight inspect tests/login.test.yaml"),console.log(" shiplight inspect tests/suite.test.yaml --stats"),console.log(" shiplight inspect tests/login.test.yaml --compact | jq ."),process.exit(e.length===0?1:0));let t=e.includes("--compact"),r=e.includes("--stats"),s=e.find(i=>!i.startsWith("--"));s||(console.error("Error: no file specified"),process.exit(1));let n=Zs.resolve(process.cwd(),s);nt.existsSync(n)||(console.error(`Error: file not found: ${n}`),process.exit(1));let o=nt.readFileSync(n,"utf-8");try{let i=ke(o),a=C(o);if(r)Ui(a,i);else{let l={...i.test_case_id!==void 0?{test_case_id:i.test_case_id}:{},...i.name?{name:i.name}:{},testFlow:a};console.log(JSON.stringify(l,null,t?0:2))}}catch(i){console.error(`Error parsing ${s}: ${i.message}`),process.exit(1)}}function Ui(e,t){if(console.log(`File: ${t.name||"(unnamed)"}`),t.test_case_id!==void 0&&console.log(`Cloud ID: ${t.test_case_id}`),console.log(`Version: ${e.version||"unknown"}`),e.testGroup){let r=e.testGroup;console.log("Type: suite (testGroup)"),console.log(`Tests: ${r.tests.length}`);for(let s of r.tests){let n=s.skip?` [SKIP${typeof s.skip=="string"?`: ${s.skip}`:""}]`:"";console.log(` - ${s.name}: ${s.statements.length} statements${s.teardown?`, ${s.teardown.length} teardown`:""}${n}`)}r.beforeAll?.length&&console.log(`Hooks: beforeAll (${r.beforeAll.length})`),r.afterAll?.length&&console.log(`Hooks: afterAll (${r.afterAll.length})`),r.beforeEach?.length&&console.log(`Hooks: beforeEach (${r.beforeEach.length})`),r.afterEach?.length&&console.log(`Hooks: afterEach (${r.afterEach.length})`)}else{console.log("Type: single test"),console.log(`Goal: ${e.goal}`),e.url&&console.log(`URL: ${e.url}`),e.baseURL&&console.log(`Base URL: ${e.baseURL}`),console.log(`Statements: ${e.statements?.length??0}`),e.teardown?.length&&console.log(`Teardown: ${e.teardown.length}`);let r=Qs(e.statements??[]);console.log(` DRAFT: ${r.drafts}, ACTION: ${r.actions}, STEP: ${r.steps}`)}}function Qs(e){let t={drafts:0,actions:0,steps:0};for(let r of e)if(r.type==="DRAFT")t.drafts++;else if(r.type==="ACTION")t.actions++;else if(r.type==="STEP"){t.steps++;let s=Qs(r.statements??[]);t.drafts+=s.drafts,t.actions+=s.actions,t.steps+=s.steps}return t}var tn=_(()=>{"use strict";ce()});var rn=ln((Bp,Bi)=>{Bi.exports={name:"shiplightai",version:"0.1.67",type:"module",description:"Shiplight CLI for running and debugging .test.yaml files",main:"dist/index.js",types:"dist/index.d.ts",bin:{shiplight:"dist/cli.js"},exports:{".":{types:"./dist/index.d.ts",import:"./dist/index.js",require:"./dist/cjs/index.cjs",default:"./dist/index.js"},"./fixture":{types:"./dist/fixture.d.ts",import:"./dist/fixture.js",require:"./dist/cjs/fixture.cjs",default:"./dist/fixture.js"},"./debugger-pw":{types:"./dist/debugger-pw.d.ts",import:"./dist/debugger-pw.js",require:"./dist/cjs/debugger-pw.cjs",default:"./dist/debugger-pw.js"},"./debugger-manager":{types:"./dist/debugger-manager.d.ts",import:"./dist/debugger-manager.js",require:"./dist/cjs/debugger-manager.cjs",default:"./dist/debugger-manager.js"},"./debugger-server":{types:"./dist/debugger-server.d.ts",import:"./dist/debugger-server.js",require:"./dist/cjs/debugger-server.cjs",default:"./dist/debugger-server.js"},"./reporter":{types:"./dist/reporter.d.ts",import:"./dist/reporter.js",require:"./dist/cjs/reporter.cjs",default:"./dist/reporter.js"},"./package.json":"./package.json"},files:["dist","!dist/**/*.map","README.md"],publishConfig:{registry:"https://registry.npmjs.org",access:"public"},scripts:{prebuild:"pnpm typecheck",build:"tsup",pack:"pnpm build && pnpm pack",clean:"rm -rf dist",dev:"tsup --watch","dev:run":"node --import tsx/esm src/cli.ts",test:"pnpm test:unit && pnpm test:logic","test:unit":"tsx --test 'src/**/*.test.ts'","test:logic":"playwright test -c playwright.logic.config.ts","test:e2e":"playwright test -c playwright.config.ts",typecheck:"tsc --noEmit"},dependencies:{"@ai-sdk/anthropic":"^3.0.1","@ai-sdk/google":"^3.0.1","@ai-sdk/google-vertex":"^4.0.1","@ai-sdk/openai":"^3.0.1","@ai-sdk/provider":"^3.0.1","@anthropic-ai/claude-agent-sdk":"^0.1.72","@babel/parser":"^7.28.5","@babel/plugin-transform-typescript":"^7.27.0","@google/genai":"^1.34.0","google-auth-library":"^10.0.0","@babel/preset-env":"^7.26.9","@babel/preset-typescript":"^7.27.0","@modelcontextprotocol/sdk":"^1.29.0","@shiplightai/devtools-assets":"workspace:*",ai:"^6.0.3",axios:"^1.15.0",chalk:"^4.1.2",commander:"^11.0.0",dotenv:"^16.0.3",express:"^5.2.1","fs-extra":"^11.2.0",glob:"^13.0.0","html-to-text":"^9.0.5",open:"^10.1.0",openai:"^6.25.0",ora:"^5.4.1",otplib:"^13.4.0","p-retry":"^6.2.1",sharp:"^0.34.5",uuid:"^11.1.0",yaml:"^2.8.3",zod:"^3.22.0","zod-to-json-schema":"^3.24.6"},devDependencies:{"@playwright/test":"1.60.0","@types/express":"^4.17.21","@types/node":"^24.0.0","mcp-tools":"workspace:*","sdk-core":"workspace:*","sdk-internal":"workspace:*","shiplight-tools":"workspace:*","shiplight-types":"workspace:*","@loggia/common":"workspace:*",tsup:"^8.3.5",typescript:"5.5.4"},peerDependencies:{"@playwright/test":"^1.60.0"},engines:{node:">=22.0.0"},keywords:["playwright","yaml","testing","automation","ai","shiplight","mcp"],author:"Shiplight",license:"MIT"}});Le();import Hi from"dotenv";Hi.config();Vt();Yt();function sn(){console.log(`
1588
1158
  Usage: shiplight <command> [options]
1589
1159
 
1590
1160
  Commands:
@@ -1606,5 +1176,5 @@ Examples:
1606
1176
  shiplight transpile
1607
1177
  shiplight transpile "tests/**/*.test.yaml"
1608
1178
  shiplight debug tests/login.test.yaml
1609
- `)}var ot=process.argv[2];switch(ot){case"create":{let{runCreate:e}=await Promise.resolve().then(()=>(Zt(),Xt));await e(process.argv.slice(3));break}case"debug":{let{startDebugger:e}=await Promise.resolve().then(()=>(Pr(),kr));await e(process.argv.slice(3));break}case"test":{let{runTests:e}=await Promise.resolve().then(()=>(Cr(),Rr));await e(process.argv.slice(3));break}case"report":{let{runReport:e}=await Promise.resolve().then(()=>(Yr(),Vr));await e(process.argv.slice(3));break}case"transpile":{let{runTranspile:e}=await Promise.resolve().then(()=>(Zr(),Xr));await e(process.argv.slice(3));break}case"inspect":{let{runInspect:e}=await Promise.resolve().then(()=>(ns(),ts));await e(process.argv.slice(3));break}case"--version":case"-v":{let e=rs().version,t=process.env.SHIPLIGHT_BUILD_TAG?`-${process.env.SHIPLIGHT_BUILD_TAG}`:"";console.log(`${e}${t}`);break}case"--help":case"-h":ss();break;default:ot&&console.error(`Unknown command: ${ot}
1610
- `),ss(),process.exit(ot?1:0)}
1179
+ `)}var ot=process.argv[2];switch(ot){case"create":{let{runCreate:e}=await Promise.resolve().then(()=>(Zt(),qt));await e(process.argv.slice(3));break}case"debug":{let{startDebugger:e}=await Promise.resolve().then(()=>(Ps(),Ts));await e(process.argv.slice(3));break}case"test":{let{runTests:e}=await Promise.resolve().then(()=>(Rs(),Ls));await e(process.argv.slice(3));break}case"report":{let{runReport:e}=await Promise.resolve().then(()=>(Vs(),zs));await e(process.argv.slice(3));break}case"transpile":{let{runTranspile:e}=await Promise.resolve().then(()=>(qs(),Xs));await e(process.argv.slice(3));break}case"inspect":{let{runInspect:e}=await Promise.resolve().then(()=>(tn(),en));await e(process.argv.slice(3));break}case"--version":case"-v":{let e=rn().version,t=process.env.SHIPLIGHT_BUILD_TAG?`-${process.env.SHIPLIGHT_BUILD_TAG}`:"";console.log(`${e}${t}`);break}case"--help":case"-h":sn();break;default:ot&&console.error(`Unknown command: ${ot}
1180
+ `),sn(),process.exit(ot?1:0)}