@qawolf/cli 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -8514,7 +8514,7 @@ var require_BufferList = __commonJS((exports, module) => {
8514
8514
  this.head = this.tail = null;
8515
8515
  this.length = 0;
8516
8516
  };
8517
- BufferList.prototype.join = function join32(s3) {
8517
+ BufferList.prototype.join = function join33(s3) {
8518
8518
  if (this.length === 0)
8519
8519
  return "";
8520
8520
  var p3 = this.head;
@@ -23070,7 +23070,7 @@ var require_BufferList2 = __commonJS((exports, module) => {
23070
23070
  this.head = this.tail = null;
23071
23071
  this.length = 0;
23072
23072
  };
23073
- BufferList.prototype.join = function join32(s3) {
23073
+ BufferList.prototype.join = function join33(s3) {
23074
23074
  if (this.length === 0)
23075
23075
  return "";
23076
23076
  var p3 = this.head;
@@ -155181,7 +155181,7 @@ var authMessages = {
155181
155181
  rejected403: (noun) => `QA Wolf API rejected the${noun ? ` ${noun}` : ""} request (HTTP 403). Check that your API key has access to this environment.`,
155182
155182
  notFound404: (noun) => `QA Wolf API could not find ${noun ? `${noun} for that environment` : "that environment"} (HTTP 404). Check the --env value.`,
155183
155183
  failedWithStatus: (status, noun) => `QA Wolf API${noun ? ` ${noun}` : ""} request failed (HTTP ${status}).`,
155184
- networkUnreachable: (baseUrl, noun) => `Could not reach the QA Wolf API at ${baseUrl}${noun ? ` to fetch ${noun}` : ""}. Check your network connection and QAWOLF_API_URL.`,
155184
+ networkUnreachable: (baseUrl, noun) => `Could not reach the QA Wolf API at ${baseUrl}${noun ? ` to fetch ${noun}` : ""}. Check your network connection and QAWOLF_HOST_URL.`,
155185
155185
  unexpectedResponse: (noun) => `Unexpected${noun ? ` ${noun}` : ""} response from the QA Wolf API.`
155186
155186
  },
155187
155187
  bundle: {
@@ -155626,6 +155626,15 @@ function makeDefaultFs() {
155626
155626
  };
155627
155627
  }
155628
155628
 
155629
+ // src/shell/resolveHostUrl.ts
155630
+ var defaultHostUrl = "https://app.qawolf.com";
155631
+ function resolveHostUrl(env2) {
155632
+ const configuredHostUrl = env2["QAWOLF_HOST_URL"]?.trim();
155633
+ if (!configuredHostUrl)
155634
+ return defaultHostUrl;
155635
+ return configuredHostUrl.replace(/\/+$/, "");
155636
+ }
155637
+
155629
155638
  // src/domains/auth/resolve.ts
155630
155639
  import { Entry as Entry3 } from "@napi-rs/keyring";
155631
155640
 
@@ -173377,7 +173386,7 @@ function buildBaseContext(command, signals) {
173377
173386
  logPath: defaultLogPath(),
173378
173387
  ...verboseWrite ? { verboseWrite } : {}
173379
173388
  });
173380
- const apiBaseUrl = env2["QAWOLF_API_URL"]?.replace(/\/+$/, "") || "https://app.qawolf.com";
173389
+ const apiBaseUrl = resolveHostUrl(env2);
173381
173390
  return {
173382
173391
  ctx: {
173383
173392
  ui: createUI(outputMode, {
@@ -173589,7 +173598,7 @@ function registerAuthCommand(program2, signals) {
173589
173598
  // package.json
173590
173599
  var package_default = {
173591
173600
  name: "@qawolf/cli",
173592
- version: "1.3.0",
173601
+ version: "1.4.0",
173593
173602
  description: "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
173594
173603
  keywords: [
173595
173604
  "automation",
@@ -173649,16 +173658,17 @@ var package_default = {
173649
173658
  "@napi-rs/keyring": "1.3.0",
173650
173659
  "@oxc-node/core": "0.1.0",
173651
173660
  "@playwright/test": "1.60.0",
173652
- "@qawolf/api-contracts": "0.2.0",
173661
+ "@qawolf/api-contracts": "0.11.0",
173653
173662
  "@qawolf/emails": "1.1.1",
173654
173663
  "@qawolf/flow-targets": "1.0.0",
173655
- "@qawolf/flows": "0.1.1",
173664
+ "@qawolf/flows": "0.1.4",
173656
173665
  "@qawolf/testkit": "1.1.1",
173657
173666
  appium: "2.11.3",
173658
173667
  "appium-uiautomator2-driver": "3.7.0",
173659
173668
  "appium-xcuitest-driver": "9.10.5",
173660
173669
  commander: "14.0.3",
173661
173670
  "env-paths": "4.0.0",
173671
+ "expect-webdriverio": "5.6.5",
173662
173672
  picomatch: "4.0.4",
173663
173673
  pino: "10.3.1",
173664
173674
  playwright: "1.58.2",
@@ -175132,13 +175142,14 @@ import { createHash } from "node:crypto";
175132
175142
  import { join as join12, resolve as resolve3 } from "node:path";
175133
175143
 
175134
175144
  // src/generated/dependencyVersions.ts
175135
- var flowsVersion = "0.1.1";
175145
+ var flowsVersion = "0.1.4";
175136
175146
  var playwrightVersion = "1.58.2";
175137
175147
  var emailsVersion = "1.1.1";
175138
175148
  var testkitVersion = "1.1.1";
175139
175149
  var appiumVersion = "2.11.3";
175140
175150
  var appiumXcuitestDriverVersion = "9.10.5";
175141
175151
  var appiumUiautomator2DriverVersion = "3.7.0";
175152
+ var expectWebdriverioVersion = "5.6.5";
175142
175153
 
175143
175154
  // src/domains/runtimeEnv/pinnedPackages.ts
175144
175155
  var pinnedPackages = [
@@ -175151,7 +175162,8 @@ var pinnedPackages = [
175151
175162
  {
175152
175163
  name: "appium-uiautomator2-driver",
175153
175164
  version: appiumUiautomator2DriverVersion
175154
- }
175165
+ },
175166
+ { name: "expect-webdriverio", version: expectWebdriverioVersion }
175155
175167
  ];
175156
175168
 
175157
175169
  // src/domains/runtimeEnv/managedEnvDir.ts
@@ -175664,9 +175676,125 @@ var import_picomatch2 = __toESM(require_picomatch2(), 1);
175664
175676
 
175665
175677
  // node_modules/@qawolf/api-contracts/dist/v1/ids.js
175666
175678
  var defaultIdSchemas = {
175679
+ automation: exports_external.string().min(1).describe("The id of the automation."),
175667
175680
  environment: exports_external.string().min(1).describe("The id of the environment."),
175668
175681
  flow: exports_external.string().min(1).describe("The id of the flow."),
175669
- run: exports_external.string().min(1).describe("The id of the run.")
175682
+ issue: exports_external.string().min(1).describe("The id of the issue."),
175683
+ run: exports_external.string().min(1).describe("The id of the run."),
175684
+ workspace: exports_external.string().min(1).describe("The id of the workspace.")
175685
+ };
175686
+
175687
+ // node_modules/@qawolf/api-contracts/dist/v1/resource.js
175688
+ function resource(fields, { urlFieldDescription = "Absolute URL." } = {}) {
175689
+ return exports_external.object({
175690
+ ...fields,
175691
+ url: exports_external.url().describe(urlFieldDescription)
175692
+ });
175693
+ }
175694
+
175695
+ // node_modules/@qawolf/api-contracts/dist/v1/run/flowSelection.js
175696
+ var makeFlowSelectionSchema = (ids) => exports_external.object({
175697
+ flowIds: exports_external.array(ids.flow).default([]),
175698
+ tagNames: exports_external.array(exports_external.string().min(1)).default([])
175699
+ }).refine((selection) => selection.flowIds.length + selection.tagNames.length > 0, {
175700
+ message: "At least one flow or tag is required."
175701
+ });
175702
+
175703
+ // node_modules/@qawolf/api-contracts/dist/v1/run/index.js
175704
+ var makeCreateRunContract = (ids) => {
175705
+ const input = exports_external.object({
175706
+ environmentId: ids.environment,
175707
+ environmentVariables: exports_external.record(exports_external.string(), exports_external.string()).optional(),
175708
+ ignoreRules: exports_external.boolean().default(false),
175709
+ pullRequestNumber: exports_external.number().int().positive().describe("The number of the pull request to associate with the run.").optional(),
175710
+ repository: exports_external.string().min(1).describe('The owner/name of the repository, e.g. "acme/web".').optional()
175711
+ }).and(makeFlowSelectionSchema(ids));
175712
+ const output = resource({ runId: ids.run }, { urlFieldDescription: "Absolute URL of the run page." });
175713
+ return {
175714
+ description: "Create a run for the selected flows and/or tags in an environment.",
175715
+ input,
175716
+ kind: "write",
175717
+ name: "run.create",
175718
+ output
175719
+ };
175720
+ };
175721
+
175722
+ // node_modules/@qawolf/api-contracts/dist/v1/automate/index.js
175723
+ var makeAutomateContract = (ids) => {
175724
+ const input = exports_external.object({
175725
+ environmentId: ids.environment,
175726
+ flowIds: exports_external.array(ids.flow).min(1).max(300).refine((flowIds) => new Set(flowIds).size === flowIds.length, "flowIds must be unique").describe("Draft flows the automation builds."),
175727
+ patch: exports_external.string().min(1).max(1e6).optional().describe("Unified diff applied to the automation's working branch before it runs.")
175728
+ });
175729
+ const output = resource({ automationId: ids.automation }, { urlFieldDescription: "Absolute URL of the automation review page." });
175730
+ return {
175731
+ description: "Request an automation that builds the selected draft flows in an environment.",
175732
+ input,
175733
+ kind: "write",
175734
+ name: "automate",
175735
+ output
175736
+ };
175737
+ };
175738
+
175739
+ // node_modules/@qawolf/api-contracts/dist/v1/environment/index.js
175740
+ var makeEnvironmentResource = (environmentId) => resource({
175741
+ environmentId,
175742
+ flowCodeBranch: exports_external.discriminatedUnion("syncStatus", [
175743
+ exports_external.object({
175744
+ lastSyncedCommitHash: exports_external.string().describe("Hash of the latest reconciled commit."),
175745
+ name: exports_external.string().describe("The name of the environment's flow code branch."),
175746
+ syncStatus: exports_external.literal("reconciled").describe("The branch has reconciled at least one commit.")
175747
+ }),
175748
+ exports_external.object({
175749
+ name: exports_external.string().describe("The name of the environment's flow code branch."),
175750
+ syncStatus: exports_external.literal("not-reconciled").describe("The branch has not reconciled any commit yet.")
175751
+ })
175752
+ ]).optional().describe("The environment's flow code branch and its git reconciliation state, if it has one."),
175753
+ kind: exports_external.enum(["static", "preview"]).describe("Whether the environment is static or a preview."),
175754
+ name: exports_external.string(),
175755
+ status: exports_external.enum(["blocked", "needs-investigation", "ready", "running"]).describe("Standing health across the environment's runs: blocked (open blocking bugs), needs-investigation (failures awaiting triage), running (flows executing), ready (none of the above — the greenlit state)."),
175756
+ terminatedAt: exports_external.iso.datetime().optional().describe("When the environment was terminated, if it has been.")
175757
+ }, { urlFieldDescription: "Absolute URL of the environment page." });
175758
+ var makeGetEnvironmentContract = (ids) => {
175759
+ const input = exports_external.object({
175760
+ environmentId: ids.environment
175761
+ });
175762
+ const output = makeEnvironmentResource(ids.environment);
175763
+ return {
175764
+ description: "Read a single environment's name, kind, health status, and termination state.",
175765
+ input,
175766
+ kind: "read",
175767
+ name: "environment.get",
175768
+ output
175769
+ };
175770
+ };
175771
+ var makeListEnvironmentVariableNamesContract = (ids) => {
175772
+ const input = exports_external.object({
175773
+ environmentId: ids.environment
175774
+ });
175775
+ const output = exports_external.object({
175776
+ variableNames: exports_external.array(exports_external.string()).describe("Names of the environment's variables, sorted alphabetically. Values are never returned.")
175777
+ });
175778
+ return {
175779
+ description: "List the names of an environment's variables, sorted. Names only — values never leave the server.",
175780
+ input,
175781
+ kind: "read",
175782
+ name: "environment.listVariableNames",
175783
+ output
175784
+ };
175785
+ };
175786
+ var makeCreateEnvironmentContract = (ids) => {
175787
+ const input = exports_external.object({
175788
+ name: exports_external.string().min(1).max(255).regex(/\S/, "Environment name cannot be blank.").describe("Display name of the new environment.")
175789
+ });
175790
+ const output = makeEnvironmentResource(ids.environment);
175791
+ return {
175792
+ description: "Create an environment on the caller's team and return it in the environment.get shape.",
175793
+ input,
175794
+ kind: "write",
175795
+ name: "environment.create",
175796
+ output
175797
+ };
175670
175798
  };
175671
175799
 
175672
175800
  // node_modules/@qawolf/api-contracts/dist/v1/flow/index.js
@@ -175677,12 +175805,14 @@ var makeListFlowsContract = (ids) => {
175677
175805
  });
175678
175806
  const executionTarget = exports_external.union([exports_external.string().min(1), exports_external.record(exports_external.string(), exports_external.unknown())]).describe("The target the flow executes against, as a preset or object.");
175679
175807
  const output = exports_external.object({
175680
- flows: exports_external.array(exports_external.object({
175808
+ flows: exports_external.array(resource({
175681
175809
  executionTarget,
175682
175810
  flowId: ids.flow,
175683
175811
  name: exports_external.string(),
175684
175812
  path: exports_external.string().describe("The flow file path, relative to the repo."),
175685
175813
  tags: exports_external.array(exports_external.string())
175814
+ }, {
175815
+ urlFieldDescription: "Absolute URL of the flow page in its environment."
175686
175816
  }))
175687
175817
  });
175688
175818
  return {
@@ -175694,33 +175824,229 @@ var makeListFlowsContract = (ids) => {
175694
175824
  };
175695
175825
  };
175696
175826
 
175697
- // node_modules/@qawolf/api-contracts/dist/v1/run/index.js
175698
- var makeCreateRunContract = (ids) => {
175699
- const input = exports_external.object({
175827
+ // node_modules/@qawolf/api-contracts/dist/v1/pagination.js
175828
+ var makePaginationInputFields = (limits) => ({
175829
+ cursor: exports_external.string().min(1).optional().describe("Opaque cursor copied from the previous response's nextCursor. Omit to fetch the first page."),
175830
+ limit: exports_external.number().int().min(1).max(limits.maxLimit).default(limits.defaultLimit).describe(`Maximum rows per page. Defaults to ${limits.defaultLimit}, capped at ${limits.maxLimit}.`)
175831
+ });
175832
+ var nextCursorSchema = exports_external.string().min(1).optional().describe("Repeat the request with this value as cursor to fetch the next page. Absent when there are no further results.");
175833
+
175834
+ // node_modules/@qawolf/api-contracts/dist/v1/issue/index.js
175835
+ var issuePrioritySchema = exports_external.enum([
175836
+ "unprioritized",
175837
+ "low",
175838
+ "medium",
175839
+ "high",
175840
+ "urgent"
175841
+ ]);
175842
+ var issueStatusSchema = exports_external.enum([
175843
+ "pending",
175844
+ "inProgress",
175845
+ "paused",
175846
+ "resolved",
175847
+ "canceled",
175848
+ "archived"
175849
+ ]);
175850
+ var publicIssueTypeSchema = exports_external.enum(["bug", "coverageRequest", "maintenance"]);
175851
+ var openIssueStatuses = ["pending", "inProgress", "paused"];
175852
+ var makeIssueResourceSchema = (ids) => resource({
175853
+ createdAt: exports_external.coerce.date().describe("When the issue was created."),
175854
+ description: exports_external.string().describe("The issue description in Markdown."),
175855
+ issueId: ids.issue,
175856
+ name: exports_external.string(),
175857
+ number: exports_external.number().int(),
175858
+ priority: issuePrioritySchema,
175859
+ reproductions: exports_external.array(exports_external.object({
175700
175860
  environmentId: ids.environment,
175701
- environmentVariables: exports_external.record(exports_external.string(), exports_external.string()).optional(),
175702
- flowIds: exports_external.array(ids.flow).min(1),
175703
- ignoreRules: exports_external.boolean().default(false)
175861
+ firstReproducedInRunId: ids.run,
175862
+ flowId: ids.flow,
175863
+ isActive: exports_external.boolean(),
175864
+ lastReproducedInRunId: ids.run
175865
+ })).describe("The environments, flows, and runs where the issue was reproduced."),
175866
+ status: issueStatusSchema,
175867
+ type: publicIssueTypeSchema
175868
+ }, { urlFieldDescription: "Absolute URL of the issue page." });
175869
+ var makeFindIssuesContract = (ids) => {
175870
+ const input = exports_external.object({
175871
+ ...makePaginationInputFields({ defaultLimit: 20, maxLimit: 100 }),
175872
+ statuses: exports_external.array(issueStatusSchema).min(1).default([...openIssueStatuses]).describe("Issue statuses to include. Defaults to pending, in-progress, and paused issues."),
175873
+ type: publicIssueTypeSchema.describe("The issue type to include: bug report, maintenance report, or coverage request.")
175874
+ });
175875
+ const output = exports_external.object({
175876
+ issues: exports_external.array(makeIssueResourceSchema(ids)).describe("The team's bug reports, maintenance reports, or coverage requests, newest first."),
175877
+ nextCursor: nextCursorSchema
175878
+ });
175879
+ return {
175880
+ description: "List the team's bug reports, maintenance reports, or coverage requests, newest first.",
175881
+ input,
175882
+ kind: "read",
175883
+ name: "issue.find",
175884
+ output
175885
+ };
175886
+ };
175887
+ var makeGetIssueContract = (ids) => {
175888
+ const input = exports_external.object({
175889
+ issueId: ids.issue
175890
+ });
175891
+ const output = exports_external.object({
175892
+ issue: makeIssueResourceSchema(ids)
175704
175893
  });
175705
- const output = exports_external.object({ runId: ids.run });
175706
175894
  return {
175707
- description: "Create a run for selected flows in an environment.",
175895
+ description: "Get an issue by id.",
175896
+ input,
175897
+ kind: "read",
175898
+ name: "issue.get",
175899
+ output
175900
+ };
175901
+ };
175902
+
175903
+ // node_modules/@qawolf/api-contracts/dist/v1/issue/create.js
175904
+ var makeCreateIssueContract = (ids) => {
175905
+ const commonFields = {
175906
+ description: exports_external.string().optional().describe("The issue description as plain text; each line becomes a paragraph. Markdown is not parsed."),
175907
+ name: exports_external.string().trim().min(1).max(255),
175908
+ priority: issuePrioritySchema.optional().describe('Defaults to "unprioritized".')
175909
+ };
175910
+ const input = exports_external.discriminatedUnion("type", [
175911
+ exports_external.object({
175912
+ ...commonFields,
175913
+ type: exports_external.literal("bug")
175914
+ }),
175915
+ exports_external.object({
175916
+ ...commonFields,
175917
+ estimatedDueDate: exports_external.iso.date().optional().describe("The calendar date the coverage is estimated to be complete, as YYYY-MM-DD."),
175918
+ type: exports_external.literal("coverageRequest")
175919
+ })
175920
+ ]);
175921
+ const output = exports_external.object({
175922
+ issue: makeIssueResourceSchema(ids)
175923
+ });
175924
+ return {
175925
+ description: "Create a bug or coverage request issue for the caller's team. " + "Maintenance issues cannot be created through the public API.",
175708
175926
  input,
175709
175927
  kind: "write",
175710
- name: "run.create",
175928
+ name: "issue.create",
175711
175929
  output
175712
175930
  };
175713
175931
  };
175714
175932
 
175715
- // node_modules/@qawolf/api-contracts/dist/v1/index.js
175716
- var makeContractsV1 = (ids) => ({
175717
- flow: {
175718
- list: makeListFlowsContract(ids)
175719
- },
175720
- run: {
175721
- create: makeCreateRunContract(ids)
175722
- }
175933
+ // node_modules/@qawolf/api-contracts/dist/v1/run/summary.js
175934
+ var makeRunSummaryFields = (ids) => ({
175935
+ completedAt: exports_external.iso.datetime().optional().describe("When the run finished executing. Absent while queued or running, and also absent for a terminal run that never completed execution (e.g. every flow was canceled or skipped)."),
175936
+ createdAt: exports_external.iso.datetime(),
175937
+ git: exports_external.object({
175938
+ branch: exports_external.string().optional(),
175939
+ commitMessage: exports_external.string().optional(),
175940
+ commitSha: exports_external.string().optional(),
175941
+ commitUrl: exports_external.string().optional().describe("Link to the commit on the code host.")
175942
+ }).describe("The branch and commit under test. The fields are present when a deploy notification started the run, and absent for runs started another way, for example manually or with run.create."),
175943
+ runId: ids.run,
175944
+ status: exports_external.enum(["queued", "running", "passed", "failed", "canceled"]).describe("Whole-run status. Terminal statuses are passed, failed, and canceled; poll until one is reached.")
175945
+ });
175946
+
175947
+ // node_modules/@qawolf/api-contracts/dist/v1/run/find.js
175948
+ var makeFindRunsContract = (ids) => {
175949
+ const input = exports_external.object({
175950
+ createdAfter: exports_external.iso.datetime({ offset: true }).optional().describe("Only include runs created after this instant, e.g. the runs since a deploy."),
175951
+ ...makePaginationInputFields({ defaultLimit: 20, maxLimit: 100 }),
175952
+ environmentId: ids.environment
175953
+ });
175954
+ const output = exports_external.object({
175955
+ nextCursor: nextCursorSchema,
175956
+ runs: exports_external.array(resource(makeRunSummaryFields(ids), {
175957
+ urlFieldDescription: "Absolute URL of the run page."
175958
+ })).describe("The environment's runs, newest first. Per-flow results are available via run.get.")
175959
+ });
175960
+ return {
175961
+ description: "List an environment's recent runs, newest first.",
175962
+ input,
175963
+ kind: "read",
175964
+ name: "run.find",
175965
+ output
175966
+ };
175967
+ };
175968
+
175969
+ // node_modules/@qawolf/api-contracts/dist/v1/run/get.js
175970
+ var makeGetRunContract = (ids) => {
175971
+ const input = exports_external.object({
175972
+ runId: ids.run
175973
+ });
175974
+ const failedFlow = exports_external.object({
175975
+ failure: exports_external.object({
175976
+ error: exports_external.string()
175977
+ }),
175978
+ flowId: ids.flow,
175979
+ name: exports_external.string(),
175980
+ status: exports_external.literal("failed")
175981
+ });
175982
+ const nonFailedFlow = exports_external.object({
175983
+ flowId: ids.flow,
175984
+ name: exports_external.string(),
175985
+ status: exports_external.enum(["queued", "running", "passed", "canceled"])
175986
+ });
175987
+ const flows = exports_external.array(exports_external.discriminatedUnion("status", [failedFlow, nonFailedFlow])).describe("The run's flows, ordered alphabetically by name.");
175988
+ const output = resource({
175989
+ ...makeRunSummaryFields(ids),
175990
+ flows
175991
+ }, { urlFieldDescription: "Absolute URL of the run page." });
175992
+ return {
175993
+ description: "Get a run's status, per-flow results, and links.",
175994
+ input,
175995
+ kind: "read",
175996
+ name: "run.get",
175997
+ output
175998
+ };
175999
+ };
176000
+
176001
+ // node_modules/@qawolf/api-contracts/dist/v1/tag/index.js
176002
+ var makeTagResourceSchema = () => resource({
176003
+ color: exports_external.string().regex(/^#[0-9a-fA-F]{6}$/).describe("The tag's display color as a hex code, assigned by QA Wolf."),
176004
+ name: exports_external.string()
176005
+ }, {
176006
+ urlFieldDescription: "Absolute URL of the team's tag settings page."
175723
176007
  });
176008
+ var makeCreateTagContract = () => {
176009
+ const input = exports_external.object({
176010
+ name: exports_external.string().trim().min(1).max(255).refine((name) => !name.includes(","), "Tag names cannot include commas.")
176011
+ });
176012
+ const output = makeTagResourceSchema();
176013
+ return {
176014
+ description: "Create a tag on the caller's team. Tags select flows in run.create.",
176015
+ input,
176016
+ kind: "write",
176017
+ name: "tag.create",
176018
+ output
176019
+ };
176020
+ };
176021
+
176022
+ // node_modules/@qawolf/api-contracts/dist/v1/index.js
176023
+ var makeContractsV1 = (ids) => {
176024
+ const resolvedIds = { ...defaultIdSchemas, ...ids };
176025
+ return {
176026
+ automate: makeAutomateContract(resolvedIds),
176027
+ environment: {
176028
+ create: makeCreateEnvironmentContract(resolvedIds),
176029
+ get: makeGetEnvironmentContract(resolvedIds),
176030
+ listVariableNames: makeListEnvironmentVariableNamesContract(resolvedIds)
176031
+ },
176032
+ flow: {
176033
+ list: makeListFlowsContract(resolvedIds)
176034
+ },
176035
+ issue: {
176036
+ create: makeCreateIssueContract(resolvedIds),
176037
+ find: makeFindIssuesContract(resolvedIds),
176038
+ get: makeGetIssueContract(resolvedIds)
176039
+ },
176040
+ run: {
176041
+ create: makeCreateRunContract(resolvedIds),
176042
+ find: makeFindRunsContract(resolvedIds),
176043
+ get: makeGetRunContract(resolvedIds)
176044
+ },
176045
+ tag: {
176046
+ create: makeCreateTagContract()
176047
+ }
176048
+ };
176049
+ };
175724
176050
  var publicContractsV1 = makeContractsV1(defaultIdSchemas);
175725
176051
 
175726
176052
  // src/domains/flows/listRemote.ts
@@ -179728,8 +180054,7 @@ async function dispatchFlow({
179728
180054
  run = { ...run, manifest: stamp };
179729
180055
  const durationMs = deps.now() - flowStart;
179730
180056
  const outcome = run.passed ? "pass" : "fail";
179731
- const attempts = run.attempts;
179732
- const attempt = pluralize(attempts, "attempt");
180057
+ const attempt = pluralize(run.attempts, "attempt");
179733
180058
  deps.logger?.info(`${outcome}: ${flow.name} (${durationMs}ms, ${attempt})`);
179734
180059
  return { run, durationMs };
179735
180060
  }
@@ -180059,7 +180384,7 @@ async function defaultRunWebFlowDeps(cwd = process.cwd(), signals) {
180059
180384
 
180060
180385
  // src/domains/runtimeEnv/prepareRunDir.ts
180061
180386
  import { createHash as createHash3 } from "node:crypto";
180062
- import { basename as basename4, dirname as dirname10, join as join28, resolve as resolve8, sep as sep4 } from "node:path";
180387
+ import { basename as basename4, dirname as dirname10, join as join29, resolve as resolve8, sep as sep4 } from "node:path";
180063
180388
 
180064
180389
  // src/shell/copyDir.ts
180065
180390
  import { cp, readdir as readdir2 } from "node:fs/promises";
@@ -180100,6 +180425,9 @@ async function readPackageJson(pkgPath, fs3) {
180100
180425
  }
180101
180426
 
180102
180427
  // src/domains/runtimeEnv/innerHop.ts
180428
+ import { join as join27 } from "node:path";
180429
+
180430
+ // src/domains/runtimeEnv/linkPinnedPackages.ts
180103
180431
  import { dirname as dirname8, join as join26 } from "node:path";
180104
180432
 
180105
180433
  // src/domains/runtimeEnv/symlinkDir.ts
@@ -180109,25 +180437,35 @@ async function createDirSymlink(source, target) {
180109
180437
  await symlink(source, target, linkType);
180110
180438
  }
180111
180439
 
180112
- // src/domains/runtimeEnv/innerHop.ts
180113
- async function populateInnerHop(args) {
180114
- const { depsRoot, execDir, fs: fs3 } = args;
180440
+ // src/domains/runtimeEnv/linkPinnedPackages.ts
180441
+ async function linkPinnedPackages(args) {
180442
+ const { depsRoot, nodeModulesDir, fs: fs3 } = args;
180115
180443
  const managedModules = join26(depsRoot, "node_modules");
180116
- const innerModules = join26(execDir, "node_modules");
180117
- await fs3.mkdir(innerModules, { recursive: true });
180444
+ await fs3.mkdir(nodeModulesDir, { recursive: true });
180118
180445
  for (const { name } of pinnedPackages) {
180119
180446
  const segments = name.split("/");
180120
- const target = join26(innerModules, ...segments);
180447
+ const target = join26(nodeModulesDir, ...segments);
180121
180448
  if (segments.length > 1) {
180122
180449
  await fs3.mkdir(dirname8(target), { recursive: true });
180123
180450
  }
180451
+ await fs3.rm(target, { recursive: true, force: true });
180124
180452
  await createDirSymlink(join26(managedModules, ...segments), target);
180125
180453
  }
180126
180454
  }
180127
180455
 
180456
+ // src/domains/runtimeEnv/innerHop.ts
180457
+ async function populateInnerHop(args) {
180458
+ const { depsRoot, execDir, fs: fs3 } = args;
180459
+ await linkPinnedPackages({
180460
+ depsRoot,
180461
+ nodeModulesDir: join27(execDir, "node_modules"),
180462
+ fs: fs3
180463
+ });
180464
+ }
180465
+
180128
180466
  // src/domains/runtimeEnv/outerHop.ts
180129
180467
  import { lstat } from "node:fs/promises";
180130
- import { dirname as dirname9, join as join27 } from "node:path";
180468
+ import { dirname as dirname9, join as join28 } from "node:path";
180131
180469
  var executorPackageNames = new Set(pinnedPackages.map((p3) => p3.name));
180132
180470
  async function populateOuterHop(args) {
180133
180471
  const { projectDir, runDir, fs: fs3 } = args;
@@ -180137,23 +180475,28 @@ async function populateOuterHop(args) {
180137
180475
  const depNames = Object.keys(installableDeps);
180138
180476
  const { satisfying, rejected } = await findSatisfyingNodeModulesDir(projectDir, depNames, fs3);
180139
180477
  if (satisfying !== undefined) {
180140
- await createDirSymlink(satisfying, join27(runDir, "node_modules"));
180478
+ await createDirSymlink(satisfying, join28(runDir, "node_modules"));
180141
180479
  return { mode: "symlink", nodeModulesDir: satisfying };
180142
180480
  }
180143
180481
  if (depNames.length === 0)
180144
180482
  return { mode: "none" };
180145
180483
  args.onInstallStart?.(depNames.length);
180146
180484
  await installOuterDeps(runDir, installableDeps, fs3, args.install ?? spawnNpmInstall);
180485
+ await linkPinnedPackages({
180486
+ depsRoot: args.depsRoot,
180487
+ nodeModulesDir: join28(runDir, "node_modules"),
180488
+ fs: fs3
180489
+ });
180147
180490
  return { mode: "install", depCount: depNames.length, rejected };
180148
180491
  }
180149
180492
  async function findSatisfyingNodeModulesDir(startDir, depNames, fs3) {
180150
180493
  const rejected = [];
180151
180494
  let dir = startDir;
180152
180495
  while (true) {
180153
- const candidate = join27(dir, "node_modules");
180496
+ const candidate = join28(dir, "node_modules");
180154
180497
  const stats = await lstatSafe(candidate);
180155
180498
  if (stats !== undefined && stats.isDirectory() && !stats.isSymbolicLink()) {
180156
- const missing = depNames.filter((name) => !fs3.existsSync(join27(candidate, name, "package.json")));
180499
+ const missing = depNames.filter((name) => !fs3.existsSync(join28(candidate, name, "package.json")));
180157
180500
  if (missing.length === 0)
180158
180501
  return { satisfying: candidate, rejected };
180159
180502
  rejected.push({ dir: candidate, missing });
@@ -180174,7 +180517,7 @@ async function lstatSafe(path3) {
180174
180517
  async function readInstallableDeps(projectDir, fs3) {
180175
180518
  let content;
180176
180519
  try {
180177
- content = await fs3.readFile(join27(projectDir, "package.json"));
180520
+ content = await fs3.readFile(join28(projectDir, "package.json"));
180178
180521
  } catch {
180179
180522
  return {};
180180
180523
  }
@@ -180192,8 +180535,8 @@ async function readInstallableDeps(projectDir, fs3) {
180192
180535
  return Object.fromEntries(Object.entries(rawDeps).filter(([name]) => !executorPackageNames.has(name)).filter((entry) => typeof entry[1] === "string"));
180193
180536
  }
180194
180537
  async function installOuterDeps(runDir, deps, fs3, install) {
180195
- await fs3.writeFile(join27(runDir, "package.json"), JSON.stringify({ name: "qawolf-run", private: true, dependencies: deps }, undefined, 2));
180196
- await fs3.writeFile(join27(runDir, ".npmrc"), `@qawolf:registry=https://registry.npmjs.org/
180538
+ await fs3.writeFile(join28(runDir, "package.json"), JSON.stringify({ name: "qawolf-run", private: true, dependencies: deps }, undefined, 2));
180539
+ await fs3.writeFile(join28(runDir, ".npmrc"), `@qawolf:registry=https://registry.npmjs.org/
180197
180540
  `);
180198
180541
  const result = await install(runDir);
180199
180542
  if (result.exitCode !== 0) {
@@ -180207,10 +180550,10 @@ async function prepareRunDir(args) {
180207
180550
  const { files, projectDir, depsRoot, runRoot } = args;
180208
180551
  const fs3 = args.fs ?? makeDefaultFs();
180209
180552
  const runId = buildRunId(projectDir, files);
180210
- const runDir = join28(runRoot, runId);
180553
+ const runDir = join29(runRoot, runId);
180211
180554
  await fs3.rm(runDir, { recursive: true, force: true });
180212
180555
  await fs3.mkdir(runDir, { recursive: true });
180213
- const execDir = join28(runDir, "exec");
180556
+ const execDir = join29(runDir, "exec");
180214
180557
  await fs3.mkdir(execDir, { recursive: true });
180215
180558
  await populateInnerHop({ depsRoot, execDir, fs: fs3 });
180216
180559
  const stagedFiles = await stageFlowFiles({ files, projectDir, execDir, fs: fs3 });
@@ -180220,6 +180563,7 @@ async function prepareRunDir(args) {
180220
180563
  const outerHop = await populateOuterHop({
180221
180564
  projectDir,
180222
180565
  runDir,
180566
+ depsRoot,
180223
180567
  fs: fs3,
180224
180568
  ...args.onInstallStart !== undefined ? { onInstallStart: args.onInstallStart } : {}
180225
180569
  });
@@ -180247,37 +180591,37 @@ async function stageFlowFiles(args) {
180247
180591
  if (files.length > 1) {
180248
180592
  return Promise.all(files.map(async (f3) => {
180249
180593
  const dirHash = createHash3("sha256").update(dirname10(f3)).digest("hex").slice(0, 8);
180250
- const subDir = join28(execDir, dirHash);
180594
+ const subDir = join29(execDir, dirHash);
180251
180595
  await fs3.mkdir(subDir, { recursive: true });
180252
- const dest = join28(subDir, basename4(f3));
180596
+ const dest = join29(subDir, basename4(f3));
180253
180597
  await fs3.copyFile(f3, dest);
180254
180598
  return dest;
180255
180599
  }));
180256
180600
  }
180257
- await Promise.all(files.map((f3) => fs3.copyFile(f3, join28(execDir, basename4(f3)))));
180258
- return files.map((f3) => join28(execDir, basename4(f3)));
180601
+ await Promise.all(files.map((f3) => fs3.copyFile(f3, join29(execDir, basename4(f3)))));
180602
+ return files.map((f3) => join29(execDir, basename4(f3)));
180259
180603
  }
180260
180604
  function remapPath(file2, projectDir, execDir) {
180261
180605
  if (file2 === projectDir)
180262
180606
  return execDir;
180263
180607
  if (file2.startsWith(projectDir + sep4)) {
180264
- return join28(execDir, file2.slice(projectDir.length + 1));
180608
+ return join29(execDir, file2.slice(projectDir.length + 1));
180265
180609
  }
180266
180610
  return file2;
180267
180611
  }
180268
180612
 
180269
180613
  // src/shell/testkit.ts
180270
- import { join as join29 } from "node:path";
180614
+ import { join as join30 } from "node:path";
180271
180615
  function notAvailableLocally(name) {
180272
180616
  throw new Error(runnerMessages.notAvailableLocally(name));
180273
180617
  }
180274
180618
  async function loadSdkDeps(cwd) {
180275
- const testkitDir = join29(cwd, "node_modules", "@qawolf", "testkit");
180619
+ const testkitDir = join30(cwd, "node_modules", "@qawolf", "testkit");
180276
180620
  try {
180277
180621
  const clientMod = await import(resolveFromEnvDir(cwd, "@qawolf/testkit/client"));
180278
180622
  if (typeof clientMod.createTestkitClient !== "function")
180279
180623
  throw new Error("createTestkitClient is not a function");
180280
- const scopeMod = await import(join29(testkitDir, "dist", "clientScope.js"));
180624
+ const scopeMod = await import(join30(testkitDir, "dist", "clientScope.js"));
180281
180625
  if (typeof scopeMod.configureTestkitClient !== "function")
180282
180626
  throw new Error("configureTestkitClient is not a function");
180283
180627
  return {
@@ -180334,9 +180678,9 @@ async function registerLazyEmailsClient(getInbox, cwd, deps) {
180334
180678
  }
180335
180679
 
180336
180680
  // src/commands/flows/envVarDeps.ts
180337
- import { join as join30 } from "node:path";
180681
+ import { join as join31 } from "node:path";
180338
180682
  function envFilePath(envDir) {
180339
- return join30(envDir, ".env");
180683
+ return join31(envDir, ".env");
180340
180684
  }
180341
180685
  async function readEnv(envDir, fs3) {
180342
180686
  try {
@@ -180445,7 +180789,7 @@ async function createFlowRuntimeDeps({
180445
180789
  }
180446
180790
 
180447
180791
  // src/shell/appium/createAppiumServer.ts
180448
- import { join as join31 } from "node:path";
180792
+ import { join as join32 } from "node:path";
180449
180793
 
180450
180794
  // src/shell/appium/spawnAppium.ts
180451
180795
  import { spawn as spawn2 } from "node:child_process";
@@ -180520,7 +180864,7 @@ async function createAppiumServer(envDir, signals, params) {
180520
180864
  const spawnFn = params?.deps?.spawn ?? defaultSpawnAppium;
180521
180865
  const findFreePortFn = params?.deps?.findFreePort ?? findFreePort;
180522
180866
  const resolveAppiumBinFn = params?.deps?.resolveAppiumBin ?? resolveAppiumBin;
180523
- const appiumHome2 = params?.options?.appiumHome ?? join31(envPaths("qawolf").data, "appium");
180867
+ const appiumHome2 = params?.options?.appiumHome ?? join32(envPaths("qawolf").data, "appium");
180524
180868
  const timeoutMs3 = params?.options?.startTimeoutMs ?? defaultStartTimeoutMs;
180525
180869
  const bin = resolveAppiumBinFn(envDir);
180526
180870
  const port = await findFreePortFn();
@@ -180892,7 +181236,7 @@ async function installBrowserList(ctx, browsers, deps) {
180892
181236
  await ctx.ui.withProgress(browsers.map((browser) => ({
180893
181237
  message: installMessages.installingBrowser(browser),
180894
181238
  task: async () => {
180895
- const args = buildArgs(browser, deps.platform);
181239
+ const args = buildArgs(browser, deps.platform, deps.browserDeps);
180896
181240
  const result = await deps.spawn(deps.playwrightCliPath, args);
180897
181241
  if (result.exitCode !== 0) {
180898
181242
  throw new Error(formatError2(browser, result));
@@ -180912,6 +181256,7 @@ async function installBrowsers(ctx, pattern, deps) {
180912
181256
  await installBrowserList(ctx, browsers, {
180913
181257
  spawn: deps.spawn,
180914
181258
  platform: deps.platform,
181259
+ browserDeps: deps.browserDeps,
180915
181260
  playwrightCliPath
180916
181261
  });
180917
181262
  }
@@ -180926,8 +181271,8 @@ async function collectBrowsers(files, peekFlowMeta) {
180926
181271
  }
180927
181272
  return [...seen].sort();
180928
181273
  }
180929
- function buildArgs(browser, platform) {
180930
- return platform === "linux" ? ["install", "--with-deps", browser] : ["install", browser];
181274
+ function buildArgs(browser, platform, browserDeps) {
181275
+ return platform === "linux" && browserDeps ? ["install", "--with-deps", browser] : ["install", browser];
180931
181276
  }
180932
181277
  function formatError2(browser, result) {
180933
181278
  if (result.exitCode < 0) {
@@ -180939,7 +181284,7 @@ function formatError2(browser, result) {
180939
181284
  }
180940
181285
 
180941
181286
  // src/domains/runner/runAndroidFlow.ts
180942
- import path13 from "node:path";
181287
+ import path12 from "node:path";
180943
181288
 
180944
181289
  // src/shell/appium/createAndroidLaunchContext.ts
180945
181290
  import { writeFile } from "node:fs/promises";
@@ -181285,66 +181630,6 @@ function createRunner({
181285
181630
  }
181286
181631
 
181287
181632
  // src/domains/runner/runWebFlowUtils.ts
181288
- import crypto from "node:crypto";
181289
- import os6 from "node:os";
181290
- import path12 from "node:path";
181291
- function createLaunch({
181292
- browsers,
181293
- contextSetup,
181294
- launchBrowserOpts,
181295
- signals,
181296
- timeout,
181297
- traceMode = "off",
181298
- tracePath
181299
- }) {
181300
- const openBrowsers = [];
181301
- const openContexts = [];
181302
- const tracingEnabled = traceMode !== "off";
181303
- let closed = false;
181304
- const closeAll = async () => {
181305
- if (closed)
181306
- return;
181307
- closed = true;
181308
- if (tracingEnabled && tracePath !== undefined) {
181309
- await Promise.allSettled(openContexts.map((c2) => c2.tracing.stop({ path: tracePath })));
181310
- }
181311
- await Promise.allSettled(openContexts.map((c2) => c2.close()));
181312
- await Promise.allSettled(openBrowsers.map((b4) => b4.close()));
181313
- };
181314
- const unregister = signals.register(closeAll);
181315
- const trackContext = async (ctx) => {
181316
- openContexts.push(ctx);
181317
- if (tracingEnabled) {
181318
- await ctx.tracing.start({ screenshots: true, snapshots: true });
181319
- }
181320
- };
181321
- const launch = async (launchOpts) => {
181322
- const browserName = normalizeBrowserName(launchOpts?.browser);
181323
- const bt2 = browsers[browserName];
181324
- if (launchOpts?.persistentContext === true) {
181325
- const userDataDir = launchOpts.userDataDir ?? path12.join(os6.tmpdir(), `qawolf-${crypto.randomUUID()}`);
181326
- const context2 = await bt2.launchPersistentContext(userDataDir, {
181327
- ...launchBrowserOpts,
181328
- ...contextSetup
181329
- });
181330
- context2.setDefaultTimeout(timeout);
181331
- await trackContext(context2);
181332
- const page = await context2.newPage();
181333
- return { browserType: browserName, context: context2, page };
181334
- }
181335
- const browser = await bt2.launch(launchBrowserOpts);
181336
- openBrowsers.push(browser);
181337
- const context = await browser.newContext(contextSetup);
181338
- context.setDefaultTimeout(timeout);
181339
- await trackContext(context);
181340
- return { browser, browserType: browserName, context };
181341
- };
181342
- const cleanup = async () => {
181343
- unregister();
181344
- await closeAll();
181345
- };
181346
- return { launch, cleanup };
181347
- }
181348
181633
  function normalizeBrowserName(browser) {
181349
181634
  if (browser === "chrome" || browser === "msedge")
181350
181635
  return "chromium";
@@ -181384,7 +181669,7 @@ async function runAndroidFlow({
181384
181669
  throw new Error("runAndroidFlow: legacy flow functions are not supported; use flow() from @qawolf/flows/android");
181385
181670
  }
181386
181671
  } catch (err) {
181387
- const flowName2 = path13.basename(flowPath, path13.extname(flowPath));
181672
+ const flowName2 = path12.basename(flowPath, path12.extname(flowPath));
181388
181673
  return {
181389
181674
  passed: false,
181390
181675
  testCounts: { passed: 0, total: 0 },
@@ -181452,13 +181737,13 @@ async function runAndroidFlow({
181452
181737
  }
181453
181738
 
181454
181739
  // src/domains/runner/runWebFlow.ts
181455
- import path16 from "node:path";
181740
+ import path17 from "node:path";
181456
181741
 
181457
181742
  // src/domains/runner/initFlowRuntime.ts
181458
- import path14 from "node:path";
181743
+ import path13 from "node:path";
181459
181744
  import { pathToFileURL as pathToFileURL2 } from "node:url";
181460
181745
  async function runnerPathInDir(dir, fs9) {
181461
- const pkgPath = path14.join(dir, "node_modules", "@qawolf", "flows", "package.json");
181746
+ const pkgPath = path13.join(dir, "node_modules", "@qawolf", "flows", "package.json");
181462
181747
  try {
181463
181748
  const pkg = JSON.parse(await fs9.readFile(pkgPath));
181464
181749
  const entry = pkg.exports?.["./_runner"];
@@ -181466,7 +181751,7 @@ async function runnerPathInDir(dir, fs9) {
181466
181751
  if (typeof importPath !== "string") {
181467
181752
  throw new Error(`@qawolf/flows at ${pkgPath} does not export "./_runner" with an "import" condition`);
181468
181753
  }
181469
- return path14.resolve(path14.dirname(pkgPath), importPath);
181754
+ return path13.resolve(path13.dirname(pkgPath), importPath);
181470
181755
  } catch (err) {
181471
181756
  if (!isNoEntError(err))
181472
181757
  throw err;
@@ -181474,12 +181759,12 @@ async function runnerPathInDir(dir, fs9) {
181474
181759
  }
181475
181760
  }
181476
181761
  async function findFlowsRunnerPath(flowPath, fs9) {
181477
- let dir = path14.dirname(flowPath);
181762
+ let dir = path13.dirname(flowPath);
181478
181763
  while (true) {
181479
181764
  const result = await runnerPathInDir(dir, fs9);
181480
181765
  if (result !== undefined)
181481
181766
  return result;
181482
- const parent2 = path14.dirname(dir);
181767
+ const parent2 = path13.dirname(dir);
181483
181768
  if (parent2 === dir)
181484
181769
  throw new Error(`@qawolf/flows not found in node_modules above: ${flowPath}`);
181485
181770
  dir = parent2;
@@ -181511,7 +181796,7 @@ async function doInit(flowPath, timeout, fs9, depsRoot) {
181511
181796
  });
181512
181797
  }
181513
181798
  function initFlowRuntime(flowPath, options, fs9 = makeDefaultFs()) {
181514
- const startDir = path14.dirname(flowPath);
181799
+ const startDir = path13.dirname(flowPath);
181515
181800
  let p3 = initCache.get(startDir);
181516
181801
  if (!p3) {
181517
181802
  p3 = doInit(flowPath, options.timeout, fs9, options.depsRoot);
@@ -181520,13 +181805,137 @@ function initFlowRuntime(flowPath, options, fs9 = makeDefaultFs()) {
181520
181805
  return p3;
181521
181806
  }
181522
181807
 
181523
- // src/domains/runner/web/contextSetup.ts
181808
+ // src/domains/runner/createLaunch.ts
181809
+ import crypto from "node:crypto";
181810
+ import os6 from "node:os";
181524
181811
  import path15 from "node:path";
181812
+
181813
+ // src/domains/runner/web/artifactPaths.ts
181814
+ import path14 from "node:path";
181815
+ function indexedArtifactPath(basePath, index2) {
181816
+ if (index2 === 0)
181817
+ return basePath;
181818
+ const ext = path14.extname(basePath);
181819
+ const base = path14.basename(basePath, ext);
181820
+ return path14.join(path14.dirname(basePath), `${base}-${index2 + 1}${ext}`);
181821
+ }
181822
+ function createContextArtifacts(contextSetup, tracePath) {
181823
+ let contextIndex = 0;
181824
+ const harPaths = [];
181825
+ const tracePaths = [];
181826
+ const nextSetup = (overrides = {}) => {
181827
+ const index2 = contextIndex;
181828
+ contextIndex += 1;
181829
+ const setup = { ...contextSetup, ...overrides };
181830
+ if (contextSetup.recordHar !== undefined && overrides.recordHar === undefined) {
181831
+ setup.recordHar = {
181832
+ ...contextSetup.recordHar,
181833
+ path: indexedArtifactPath(contextSetup.recordHar.path, index2)
181834
+ };
181835
+ }
181836
+ if (setup.recordHar !== undefined)
181837
+ harPaths.push(setup.recordHar.path);
181838
+ return { setup, index: index2 };
181839
+ };
181840
+ const nextTracePath = (index2) => {
181841
+ if (tracePath === undefined)
181842
+ return;
181843
+ const contextTracePath = indexedArtifactPath(tracePath, index2);
181844
+ tracePaths.push(contextTracePath);
181845
+ return contextTracePath;
181846
+ };
181847
+ const artifactPaths = () => ({
181848
+ harPaths: [...harPaths],
181849
+ tracePaths: [...tracePaths]
181850
+ });
181851
+ return { nextSetup, nextTracePath, artifactPaths };
181852
+ }
181853
+
181854
+ // src/domains/runner/createLaunch.ts
181855
+ function createLaunch({
181856
+ browsers,
181857
+ contextSetup,
181858
+ launchBrowserOpts,
181859
+ signals,
181860
+ timeout,
181861
+ traceMode = "off",
181862
+ tracePath
181863
+ }) {
181864
+ const openBrowsers = [];
181865
+ const openContexts = [];
181866
+ const traceByContext = new Map;
181867
+ const tracingEnabled = traceMode !== "off";
181868
+ const { nextSetup, nextTracePath, artifactPaths } = createContextArtifacts(contextSetup, tracePath);
181869
+ let closePromise;
181870
+ const closeAll = () => {
181871
+ closePromise ??= (async () => {
181872
+ if (tracingEnabled) {
181873
+ await Promise.allSettled(openContexts.map((c2) => {
181874
+ const contextTracePath = traceByContext.get(c2);
181875
+ return contextTracePath === undefined ? Promise.resolve() : c2.tracing.stop({ path: contextTracePath });
181876
+ }));
181877
+ }
181878
+ await Promise.allSettled(openContexts.map((c2) => c2.close()));
181879
+ await Promise.allSettled(openBrowsers.map((b4) => b4.close()));
181880
+ })();
181881
+ return closePromise;
181882
+ };
181883
+ const unregister = signals.register(closeAll);
181884
+ const trackContext = async (ctx, index2) => {
181885
+ openContexts.push(ctx);
181886
+ if (tracingEnabled) {
181887
+ const contextTracePath = nextTracePath(index2);
181888
+ if (contextTracePath !== undefined) {
181889
+ traceByContext.set(ctx, contextTracePath);
181890
+ }
181891
+ await ctx.tracing.start({ screenshots: true, snapshots: true });
181892
+ }
181893
+ };
181894
+ const instrumentBrowser = (browser) => {
181895
+ const originalNewContext = browser.newContext.bind(browser);
181896
+ browser.newContext = async (opts) => {
181897
+ const { setup, index: index2 } = nextSetup(opts);
181898
+ const context = await originalNewContext(setup);
181899
+ context.setDefaultTimeout(timeout);
181900
+ await trackContext(context, index2);
181901
+ return context;
181902
+ };
181903
+ };
181904
+ const launch = async (launchOpts) => {
181905
+ const browserName = normalizeBrowserName(launchOpts?.browser);
181906
+ const bt2 = browsers[browserName];
181907
+ if (launchOpts?.persistentContext === true) {
181908
+ const userDataDir = launchOpts.userDataDir ?? path15.join(os6.tmpdir(), `qawolf-${crypto.randomUUID()}`);
181909
+ const { setup, index: index2 } = nextSetup();
181910
+ const context2 = await bt2.launchPersistentContext(userDataDir, {
181911
+ ...launchBrowserOpts,
181912
+ ...setup
181913
+ });
181914
+ context2.setDefaultTimeout(timeout);
181915
+ await trackContext(context2, index2);
181916
+ const page = await context2.newPage();
181917
+ return { browserType: browserName, context: context2, page };
181918
+ }
181919
+ const browser = await bt2.launch(launchBrowserOpts);
181920
+ openBrowsers.push(browser);
181921
+ instrumentBrowser(browser);
181922
+ const context = await browser.newContext({});
181923
+ return { browser, browserType: browserName, context };
181924
+ };
181925
+ const cleanup = async () => {
181926
+ unregister();
181927
+ await closeAll();
181928
+ };
181929
+ return { launch, cleanup, artifactPaths };
181930
+ }
181931
+
181932
+ // src/domains/runner/web/contextSetup.ts
181933
+ import path16 from "node:path";
181525
181934
  function harFlowPath(outputDir, flowName) {
181526
- return path15.join(outputDir, "har", `${flowName}.har`);
181935
+ return path16.join(outputDir, "har", `${flowName}.har`);
181527
181936
  }
181528
181937
  function traceFlowPath(outputDir, flowName) {
181529
- return path15.join(outputDir, "trace", `${flowName}.zip`);
181938
+ return path16.join(outputDir, "trace", `${flowName}.zip`);
181530
181939
  }
181531
181940
  function buildHarContextOpts(harPath, harContent) {
181532
181941
  return {
@@ -181542,7 +181951,7 @@ async function initHar(fs9, options, flowName) {
181542
181951
  if (harMode === "off")
181543
181952
  return { harMode, harPath: undefined };
181544
181953
  const harPath = harFlowPath(options.outputDir, flowName);
181545
- await fs9.mkdir(path15.dirname(harPath), { recursive: true });
181954
+ await fs9.mkdir(path16.dirname(harPath), { recursive: true });
181546
181955
  return { harMode, harPath };
181547
181956
  }
181548
181957
  async function initTrace(fs9, options, flowName) {
@@ -181550,7 +181959,7 @@ async function initTrace(fs9, options, flowName) {
181550
181959
  if (traceMode === "off")
181551
181960
  return { traceMode, tracePath: undefined };
181552
181961
  const tracePath = traceFlowPath(options.outputDir, flowName);
181553
- await fs9.mkdir(path15.dirname(tracePath), { recursive: true });
181962
+ await fs9.mkdir(path16.dirname(tracePath), { recursive: true });
181554
181963
  return { traceMode, tracePath };
181555
181964
  }
181556
181965
  async function maybeCleanupTrace(fs9, tracePath, passed, traceMode, warn) {
@@ -181563,7 +181972,7 @@ async function maybeCleanupTrace(fs9, tracePath, passed, traceMode, warn) {
181563
181972
  }
181564
181973
  }
181565
181974
  function buildContextSetup(videoSize, options, harPath) {
181566
- const videosDir = options.artifactDir ?? path15.join(options.outputDir, "videos");
181975
+ const videosDir = options.artifactDir ?? path16.join(options.outputDir, "videos");
181567
181976
  const harOpts = harPath !== undefined ? buildHarContextOpts(harPath, options.harContent ?? "omit") : {};
181568
181977
  return options.video !== "off" ? {
181569
181978
  viewport: videoSize,
@@ -181599,7 +182008,7 @@ async function runWebFlow({
181599
182008
  depsRoot: deps.depsRoot
181600
182009
  });
181601
182010
  } catch (err) {
181602
- const flowName2 = path16.basename(flowPath, path16.extname(flowPath));
182011
+ const flowName2 = path17.basename(flowPath, path17.extname(flowPath));
181603
182012
  return {
181604
182013
  passed: false,
181605
182014
  testCounts: { passed: 0, total: 0 },
@@ -181608,7 +182017,7 @@ async function runWebFlow({
181608
182017
  };
181609
182018
  }
181610
182019
  const isLegacy = typeof exported === "function";
181611
- const flowName = isLegacy ? path16.basename(flowPath, path16.extname(flowPath)) : exported.name;
182020
+ const flowName = isLegacy ? path17.basename(flowPath, path17.extname(flowPath)) : exported.name;
181612
182021
  const runFn = isLegacy ? exported : exported.run;
181613
182022
  const { harMode, harPath } = await initHar(deps.fs, options, flowName);
181614
182023
  const { traceMode, tracePath } = await initTrace(deps.fs, options, flowName);
@@ -181619,7 +182028,7 @@ async function runWebFlow({
181619
182028
  slowMo: options.slowMo,
181620
182029
  ...options.executablePath !== undefined ? { executablePath: options.executablePath } : {}
181621
182030
  };
181622
- const { launch, cleanup } = createLaunch({
182031
+ const { launch, cleanup, artifactPaths } = createLaunch({
181623
182032
  browsers: {
181624
182033
  chromium: deps.chromium,
181625
182034
  firefox: deps.firefox,
@@ -181661,25 +182070,26 @@ async function runWebFlow({
181661
182070
  } finally {
181662
182071
  await cleanup();
181663
182072
  const warn = (message) => deps.logger?.warn(message);
181664
- if (harPath !== undefined) {
181665
- await maybeCleanupHar(deps.fs, harPath, passed, harMode, warn);
182073
+ const assigned = artifactPaths();
182074
+ for (const contextHarPath of assigned.harPaths) {
182075
+ await maybeCleanupHar(deps.fs, contextHarPath, passed, harMode, warn);
181666
182076
  }
181667
- if (tracePath !== undefined) {
181668
- await maybeCleanupTrace(deps.fs, tracePath, passed, traceMode, warn);
182077
+ for (const contextTracePath of assigned.tracePaths) {
182078
+ await maybeCleanupTrace(deps.fs, contextTracePath, passed, traceMode, warn);
181669
182079
  }
181670
182080
  }
181671
182081
  }
181672
182082
 
181673
182083
  // src/shell/embeddedWorkerCli.ts
181674
182084
  import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
181675
- import { basename as basename6, join as join32 } from "node:path";
182085
+ import { basename as basename6, join as join33 } from "node:path";
181676
182086
  var embeddedCliPathEnv = "QAWOLF_EMBEDDED_CLI_PATH";
181677
182087
  function extractEmbeddedWorkerCli() {
181678
182088
  const assetPath = process.env[embeddedCliPathEnv];
181679
182089
  if (assetPath === undefined || assetPath === "")
181680
182090
  return;
181681
- const dir = join32(getDataDir(), "worker");
181682
- const dest = join32(dir, basename6(assetPath));
182091
+ const dir = join33(getDataDir(), "worker");
182092
+ const dest = join33(dir, basename6(assetPath));
181683
182093
  if (existsSync3(dest))
181684
182094
  return dest;
181685
182095
  mkdirSync3(dir, { recursive: true });
@@ -181982,7 +182392,7 @@ function createCompositeReporter(reporters) {
181982
182392
  };
181983
182393
  }
181984
182394
  // src/shell/reporter/createJUnitReporter.ts
181985
- import { join as join33 } from "node:path";
182395
+ import { join as join34 } from "node:path";
181986
182396
 
181987
182397
  // src/shell/reporter/junitXml.ts
181988
182398
  function escapeXml(str) {
@@ -182021,7 +182431,7 @@ ${suites}`;
182021
182431
 
182022
182432
  // src/shell/reporter/createJUnitReporter.ts
182023
182433
  function resolveJUnitOutputPath(junit, outputDir) {
182024
- return typeof junit === "string" && junit.trim() !== "" ? junit : join33(outputDir, "junit-report.xml");
182434
+ return typeof junit === "string" && junit.trim() !== "" ? junit : join34(outputDir, "junit-report.xml");
182025
182435
  }
182026
182436
  function createJUnitReporter(deps) {
182027
182437
  const flows = [];
@@ -182084,6 +182494,7 @@ function buildFlowsRunDeps(args) {
182084
182494
  installBrowsers: (innerCtx, browsers) => installBrowserList(innerCtx, browsers, {
182085
182495
  spawn: defaultSpawn,
182086
182496
  platform: process.platform,
182497
+ browserDeps: flags.browserDeps,
182087
182498
  playwrightCliPath: resolvePlaywrightCli(resolvedDir, process.platform)
182088
182499
  }),
182089
182500
  runWebFlow,
@@ -182108,11 +182519,11 @@ function buildFlowsRunDeps(args) {
182108
182519
 
182109
182520
  // src/commands/flows/loadEnvFile.ts
182110
182521
  import { readFile } from "node:fs/promises";
182111
- import { join as join34 } from "node:path";
182522
+ import { join as join35 } from "node:path";
182112
182523
  async function loadEnvFile(envDir) {
182113
182524
  let content;
182114
182525
  try {
182115
- content = await readFile(join34(envDir, ".env"), "utf8");
182526
+ content = await readFile(join35(envDir, ".env"), "utf8");
182116
182527
  } catch (err) {
182117
182528
  if (isNoEntError(err))
182118
182529
  return;
@@ -182212,7 +182623,7 @@ async function handleFlowsRun(ctx, pattern, flags, deps) {
182212
182623
  }
182213
182624
 
182214
182625
  // src/commands/flows/hybridRunDefaults.ts
182215
- import { join as join35, resolve as resolve9 } from "node:path";
182626
+ import { join as join36, resolve as resolve9 } from "node:path";
182216
182627
  function makeDefaultHybridDeps(fs9) {
182217
182628
  return {
182218
182629
  expandPatterns: (patterns, cwd, logger) => expandPatterns(patterns, cwd, logger, fs9),
@@ -182231,7 +182642,7 @@ async function handleHybridFlowsRun(ctx, pattern, flags, deps) {
182231
182642
  if (validation !== "ok") {
182232
182643
  return { error: validation.error, exitCode: 2 };
182233
182644
  }
182234
- const envDir = resolve9(join35(".qawolf", flags.env));
182645
+ const envDir = resolve9(join36(".qawolf", flags.env));
182235
182646
  const patternArgs = buildPatternArgs(pattern);
182236
182647
  const globFlows = () => resolvedDeps.expandPatterns(patternArgs, envDir, ctx.log("flows"));
182237
182648
  let files = await globFlows();
@@ -182268,7 +182679,7 @@ Examples:
182268
182679
  function registerFlowsRunCommand(flows, signals) {
182269
182680
  declareCommandKind(flows.command("run [pattern]"), "local", {
182270
182681
  kindNote: "read with --env"
182271
- }).description("Run flows matching [pattern], or every flow when omitted; with --env, pull missing flows from that QA Wolf environment").option("--retries <n>", "Retry each failing flow up to this many times", parseInteger("--retries", { min: 0 }), 0).option("--bail", "Stop the run after the first failure", false).option("--workers <n>", "Parallel worker count for web flows", parseInteger("--workers", { min: 1 }), 1).option("--timeout <ms>", "Default timeout for actions and assertions, in milliseconds", parseInteger("--timeout", { min: 0 }), 30000).option("--video <mode>", "Record video: on | off | retain-on-failure", parseEnum("--video", videoModes), videoDefault).option("--trace <mode>", "Record Playwright trace: on | off | retain-on-failure", parseEnum("--trace", traceModes), traceDefault).option("--har <mode>", "Record HAR network log: on | off | retain-on-failure", parseEnum("--har", harModes), harDefault).option("--har-content <mode>", "HAR response bodies: omit | full (full uses more memory)", parseEnum("--har-content", harContentModes), harContentDefault).option("--output-dir <path>", "Directory for run artifacts (videos, traces, HAR)", defaultOutputDir).option("--junit [path]", "Write a JUnit XML report (default: <output-dir>/junit-report.xml)").option("--headed", "Show the browser window instead of headless", false).option("--env <env>", "Pull and run a flow from this environment (UUID or slug) if not cached locally").option("--deps <dir>", "Use this prepared dependency directory instead of auto-installing the runtime; or set QAWOLF_RUNTIME_DIR to relocate the managed runtime").addHelpText("after", runExamples).action((pattern, opts, command) => {
182682
+ }).description("Run flows matching [pattern], or every flow when omitted; with --env, pull missing flows from that QA Wolf environment").option("--retries <n>", "Retry each failing flow up to this many times", parseInteger("--retries", { min: 0 }), 0).option("--bail", "Stop the run after the first failure", false).option("--workers <n>", "Parallel worker count for web flows", parseInteger("--workers", { min: 1 }), 1).option("--timeout <ms>", "Default timeout for actions and assertions, in milliseconds", parseInteger("--timeout", { min: 0 }), 30000).option("--video <mode>", "Record video: on | off | retain-on-failure", parseEnum("--video", videoModes), videoDefault).option("--trace <mode>", "Record Playwright trace: on | off | retain-on-failure", parseEnum("--trace", traceModes), traceDefault).option("--har <mode>", "Record HAR network log: on | off | retain-on-failure", parseEnum("--har", harModes), harDefault).option("--har-content <mode>", "HAR response bodies: omit | full (full uses more memory)", parseEnum("--har-content", harContentModes), harContentDefault).option("--output-dir <path>", "Directory for run artifacts (videos, traces, HAR)", defaultOutputDir).option("--junit [path]", "Write a JUnit XML report (default: <output-dir>/junit-report.xml)").option("--headed", "Show the browser window instead of headless", false).option("--env <env>", "Pull and run a flow from this environment (UUID or slug) if not cached locally").option("--deps <dir>", "Use this prepared dependency directory instead of auto-installing the runtime; or set QAWOLF_RUNTIME_DIR to relocate the managed runtime").option("--no-browser-deps", "Skip installing OS-level browser dependencies (Linux --with-deps, which needs root); requires the system libraries to already be present").addHelpText("after", runExamples).action((pattern, opts, command) => {
182272
182683
  if (opts.env !== undefined) {
182273
182684
  const hybridFlags = { ...opts, env: opts.env };
182274
182685
  return withAuthContext(signals, (ctx) => handleHybridFlowsRun(ctx, pattern, hybridFlags))(opts, command);
@@ -182305,7 +182716,7 @@ async function runWorker(signals) {
182305
182716
  throw new Error("worker subprocess currently supports web flows only");
182306
182717
  await configureTestkit(input.resolvedDir);
182307
182718
  const fs9 = makeDefaultFs();
182308
- const apiBaseUrl = process.env["QAWOLF_API_URL"]?.replace(/\/+$/, "") || "https://app.qawolf.com";
182719
+ const apiBaseUrl = resolveHostUrl(process.env);
182309
182720
  const flowRuntimeDeps = await createFlowRuntimeDeps({
182310
182721
  envDir: input.resolvedDir,
182311
182722
  ctx: { apiBaseUrl, configDir: getConfigDir(), fs: fs9 }
@@ -182385,7 +182796,7 @@ function registerFlowsCommand(program2, signals) {
182385
182796
  }
182386
182797
 
182387
182798
  // src/domains/init/init.ts
182388
- import { dirname as dirname13, join as join36, relative as relative7 } from "node:path";
182799
+ import { dirname as dirname13, join as join37, relative as relative7 } from "node:path";
182389
182800
 
182390
182801
  // src/domains/init/templates.ts
182391
182802
  var qawolfConfigTs = `export default {
@@ -182423,9 +182834,9 @@ function makeDefaultInitDeps() {
182423
182834
  async function handleInit(ctx, opts, deps = makeDefaultInitDeps()) {
182424
182835
  ctx.ui.gap();
182425
182836
  ctx.ui.intro(initMessages.title);
182426
- const configPath = join36(deps.cwd, "qawolf.config.ts");
182427
- const flowPath = join36(deps.cwd, "src", "flows", "example.flow.ts");
182428
- const gitignorePath = join36(deps.cwd, ".qawolf", ".gitignore");
182837
+ const configPath = join37(deps.cwd, "qawolf.config.ts");
182838
+ const flowPath = join37(deps.cwd, "src", "flows", "example.flow.ts");
182839
+ const gitignorePath = join37(deps.cwd, ".qawolf", ".gitignore");
182429
182840
  await writeWithPrompt(ctx, configPath, qawolfConfigTs, opts.yes, deps);
182430
182841
  await writeWithPrompt(ctx, flowPath, exampleFlowTs, opts.yes, deps);
182431
182842
  await writeWithPrompt(ctx, gitignorePath, qawolfGitignore, opts.yes, deps);
@@ -182449,7 +182860,7 @@ async function writeWithPrompt(ctx, filePath, content, yes, deps) {
182449
182860
  ctx.ui.step(initMessages.createdFile(relPath));
182450
182861
  }
182451
182862
  async function ensurePackageJson(ctx, yes, deps) {
182452
- const pkgPath = join36(deps.cwd, "package.json");
182863
+ const pkgPath = join37(deps.cwd, "package.json");
182453
182864
  if (!await deps.fs.pathExists(pkgPath)) {
182454
182865
  const confirmed2 = await ctx.ui.confirm(initMessages.createPackageJsonPrompt, { yes });
182455
182866
  if (!confirmed2.ok || !confirmed2.value) {
@@ -182508,19 +182919,19 @@ Examples:
182508
182919
  }
182509
182920
 
182510
182921
  // src/commands/install/android.ts
182511
- import { join as join39 } from "node:path";
182922
+ import { join as join40 } from "node:path";
182512
182923
 
182513
182924
  // src/domains/install/android/index.ts
182514
182925
  import { parseExecutionTarget as parseExecutionTarget4 } from "@qawolf/flow-targets";
182515
182926
 
182516
182927
  // src/domains/install/android/avd.ts
182517
- import { join as join37 } from "node:path";
182928
+ import { join as join38 } from "node:path";
182518
182929
  async function installAvds(ctx, specs, deps) {
182519
182930
  const versionResult = await deps.spawn(deps.sdkManagerPath, ["--version"]);
182520
182931
  if (versionResult.exitCode !== 0) {
182521
182932
  throw new Error(installMessages.android.sdkmanagerNotFound(deps.sdkManagerPath));
182522
182933
  }
182523
- const licenseFile = join37(deps.androidHome, "licenses", "android-sdk-license");
182934
+ const licenseFile = join38(deps.androidHome, "licenses", "android-sdk-license");
182524
182935
  if (deps.checkExists(licenseFile)) {
182525
182936
  ctx.ui.info(installMessages.android.licensesAlreadyAccepted);
182526
182937
  } else {
@@ -182534,7 +182945,7 @@ async function installAvds(ctx, specs, deps) {
182534
182945
  for (const spec of specs) {
182535
182946
  if (installedImages.has(spec.systemImage))
182536
182947
  continue;
182537
- const systemImg = join37(deps.androidHome, ...spec.systemImage.split(";"), "system.img");
182948
+ const systemImg = join38(deps.androidHome, ...spec.systemImage.split(";"), "system.img");
182538
182949
  if (deps.checkExists(systemImg)) {
182539
182950
  ctx.ui.info(installMessages.android.systemImageAlreadyInstalled(spec.systemImage));
182540
182951
  installedImages.add(spec.systemImage);
@@ -182582,9 +182993,9 @@ async function installAvds(ctx, specs, deps) {
182582
182993
  }
182583
182994
 
182584
182995
  // src/domains/install/android/driver.ts
182585
- import { join as join38 } from "node:path";
182996
+ import { join as join39 } from "node:path";
182586
182997
  var appiumEnv = {
182587
- APPIUM_HOME: join38(envPaths("qawolf").data, "appium")
182998
+ APPIUM_HOME: join39(envPaths("qawolf").data, "appium")
182588
182999
  };
182589
183000
  async function installUiautomator2Driver(ctx, deps) {
182590
183001
  const listResult = await deps.spawn(deps.appiumBinPath, ["driver", "list", "--installed"], { env: appiumEnv });
@@ -182671,8 +183082,8 @@ async function handleInstallAndroid(ctx, pattern, envDir) {
182671
183082
  arch: process.arch,
182672
183083
  androidHome: androidHome2,
182673
183084
  checkExists: (path9) => fs9.existsSync(path9),
182674
- sdkManagerPath: join39(androidHome2, "cmdline-tools", "latest", "bin", "sdkmanager"),
182675
- avdManagerPath: join39(androidHome2, "cmdline-tools", "latest", "bin", "avdmanager"),
183085
+ sdkManagerPath: join40(androidHome2, "cmdline-tools", "latest", "bin", "sdkmanager"),
183086
+ avdManagerPath: join40(androidHome2, "cmdline-tools", "latest", "bin", "avdmanager"),
182676
183087
  expandPatterns: (patterns, cwd) => expandPatterns(patterns, cwd ?? process.cwd(), undefined, fs9),
182677
183088
  peekFlowMeta: makePeekFlowMeta(fs9),
182678
183089
  resolveDepsRoot: async (files) => envDir ?? (await resolveDepsRoot({ files, fs: fs9 })).depsRoot,
@@ -182681,13 +183092,15 @@ async function handleInstallAndroid(ctx, pattern, envDir) {
182681
183092
  }
182682
183093
 
182683
183094
  // src/commands/install/browsers.ts
182684
- async function handleInstallBrowsers(ctx, pattern, envDir) {
183095
+ async function handleInstallBrowsers(ctx, pattern, options) {
182685
183096
  const cwd = process.cwd();
182686
183097
  const { fs: fs9 } = ctx;
183098
+ const { envDir, browserDeps } = options;
182687
183099
  return installBrowsers(ctx, pattern, {
182688
183100
  cwd,
182689
183101
  spawn: defaultSpawn,
182690
183102
  platform: process.platform,
183103
+ browserDeps,
182691
183104
  expandPatterns: (patterns, dir) => expandPatterns(patterns, dir ?? cwd, undefined, fs9),
182692
183105
  peekFlowMeta: makePeekFlowMeta(fs9),
182693
183106
  resolvePlaywrightCliPath: async (files) => {
@@ -182752,18 +183165,27 @@ async function installAll(ctx, pattern, deps) {
182752
183165
  }
182753
183166
  return firstError;
182754
183167
  }
182755
- async function handleInstall(ctx, pattern) {
183168
+ async function handleInstall(ctx, pattern, options) {
182756
183169
  const { fs: fs9 } = ctx;
182757
183170
  return installAll(ctx, pattern, {
182758
183171
  cwd: process.cwd(),
182759
183172
  expandPatterns: (patterns, cwd) => expandPatterns(patterns, cwd ?? process.cwd(), undefined, fs9),
182760
183173
  peekFlowMeta: makePeekFlowMeta(fs9),
182761
183174
  resolveDepsRoot: (files) => resolveDepsRoot({ files, fs: fs9 }),
182762
- installBrowsers: handleInstallBrowsers,
183175
+ installBrowsers: (innerCtx, innerPattern, envDir) => handleInstallBrowsers(innerCtx, innerPattern, {
183176
+ envDir,
183177
+ browserDeps: options.browserDeps
183178
+ }),
182763
183179
  installAndroid: handleInstallAndroid
182764
183180
  });
182765
183181
  }
182766
183182
 
183183
+ // src/commands/install/browserDepsFlag.ts
183184
+ function mergedBrowserDeps(local, command) {
183185
+ const parent2 = command.parent?.opts().browserDeps;
183186
+ return local && parent2 !== false;
183187
+ }
183188
+
182767
183189
  // src/commands/install/clear.ts
182768
183190
  async function handleInstallClear(ctx, opts) {
182769
183191
  const dir = managedEnvBaseDir();
@@ -182791,18 +183213,22 @@ async function handleInstallClear(ctx, opts) {
182791
183213
  }
182792
183214
 
182793
183215
  // src/commands/install/index.ts
183216
+ var noBrowserDepsDescription = "Skip installing OS-level browser dependencies (Linux --with-deps, which needs root); requires the system libraries to already be present";
182794
183217
  function registerInstallCommand(program2, signals) {
182795
- const install = declareCommandKind(program2.command("install"), "local").description("Install every runtime dependency the project's flows need").argument("[pattern]", "Glob limiting which flows determine required dependencies").addHelpText("after", `
183218
+ const install = declareCommandKind(program2.command("install"), "local").description("Install every runtime dependency the project's flows need").argument("[pattern]", "Glob limiting which flows determine required dependencies").option("--no-browser-deps", noBrowserDepsDescription).addHelpText("after", `
182796
183219
  Examples:
182797
183220
  $ qawolf install
182798
183221
  $ qawolf install "flows/checkout/**"`).action((pattern, opts, command) => {
182799
- return withContext(signals, (ctx) => handleInstall(ctx, pattern))(opts, command);
183222
+ return withContext(signals, (ctx) => handleInstall(ctx, pattern, { browserDeps: opts.browserDeps }))(opts, command);
182800
183223
  });
182801
- declareCommandKind(install.command("browsers [pattern]"), "local").description("Install Playwright browsers used by the project's web flows").addHelpText("after", `
183224
+ declareCommandKind(install.command("browsers [pattern]"), "local").description("Install Playwright browsers used by the project's web flows").option("--no-browser-deps", noBrowserDepsDescription).addHelpText("after", `
182802
183225
  Examples:
182803
183226
  $ qawolf install browsers
182804
183227
  $ qawolf install browsers "flows/web/**"`).action((pattern, opts, command) => {
182805
- return withContext(signals, (ctx) => handleInstallBrowsers(ctx, pattern))(opts, command);
183228
+ return withContext(signals, (ctx) => handleInstallBrowsers(ctx, pattern, {
183229
+ envDir: undefined,
183230
+ browserDeps: mergedBrowserDeps(opts.browserDeps, command)
183231
+ }))(opts, command);
182806
183232
  });
182807
183233
  declareCommandKind(install.command("android [pattern]"), "local").description("Install Android system images, AVDs, and the Appium driver used by the project's Android flows").addHelpText("after", `
182808
183234
  Examples:
@@ -182818,8 +183244,7 @@ Examples:
182818
183244
  });
182819
183245
  }
182820
183246
 
182821
- // src/core/publicApi/flagSpecs.ts
182822
- var kebabCase = (field) => field.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`);
183247
+ // src/core/publicApi/flagKind.ts
182823
183248
  function flagKind(schema) {
182824
183249
  if (schema.type === "string")
182825
183250
  return { ok: true, kind: "string" };
@@ -182854,6 +183279,110 @@ function flagKind(schema) {
182854
183279
  reason: `type ${schema.type ?? "unknown"} cannot be expressed as a flag`
182855
183280
  };
182856
183281
  }
183282
+
183283
+ // src/core/publicApi/objectShape.ts
183284
+ var asObjectShape = (schema) => schema.type === "object" && schema.properties ? { properties: schema.properties, required: new Set(schema.required) } : undefined;
183285
+ function mergeIntersection(members) {
183286
+ const shape = { properties: {}, required: new Set };
183287
+ for (const member of members) {
183288
+ const memberShape = asObjectShape(member);
183289
+ if (!memberShape) {
183290
+ return {
183291
+ ok: false,
183292
+ field: "",
183293
+ reason: "intersection members must be object schemas"
183294
+ };
183295
+ }
183296
+ for (const [field, fieldSchema] of Object.entries(memberShape.properties)) {
183297
+ if (field in shape.properties) {
183298
+ return {
183299
+ ok: false,
183300
+ field,
183301
+ reason: "field appears in multiple intersection members"
183302
+ };
183303
+ }
183304
+ shape.properties[field] = fieldSchema;
183305
+ if (memberShape.required.has(field))
183306
+ shape.required.add(field);
183307
+ }
183308
+ }
183309
+ return { ok: true, shape };
183310
+ }
183311
+ function mergeUnion(branches) {
183312
+ const shapes = [];
183313
+ for (const branch of branches) {
183314
+ const branchShape = asObjectShape(branch);
183315
+ if (!branchShape) {
183316
+ return {
183317
+ ok: false,
183318
+ field: "",
183319
+ reason: "union branches must be object schemas"
183320
+ };
183321
+ }
183322
+ shapes.push(branchShape);
183323
+ }
183324
+ const shape = { properties: {}, required: new Set };
183325
+ for (const branchShape of shapes) {
183326
+ for (const [field, fieldSchema] of Object.entries(branchShape.properties)) {
183327
+ const existing = shape.properties[field];
183328
+ if (!existing) {
183329
+ shape.properties[field] = { ...fieldSchema };
183330
+ continue;
183331
+ }
183332
+ const kinds2 = [flagKind(existing), flagKind(fieldSchema)];
183333
+ if (kinds2[0]?.ok && kinds2[1]?.ok && kinds2[0].kind !== kinds2[1].kind) {
183334
+ return {
183335
+ ok: false,
183336
+ field,
183337
+ reason: "field has conflicting types across union branches"
183338
+ };
183339
+ }
183340
+ if (!existing.description && fieldSchema.description) {
183341
+ existing.description = fieldSchema.description;
183342
+ }
183343
+ }
183344
+ }
183345
+ const discriminator = Object.keys(shape.properties).find((field) => {
183346
+ const values = shapes.map((branch) => branch.properties[field]?.const);
183347
+ return values.every((value) => typeof value === "string") && new Set(values).size === shapes.length && shapes.every((branch) => branch.required.has(field));
183348
+ });
183349
+ if (discriminator === undefined) {
183350
+ return {
183351
+ ok: false,
183352
+ field: "",
183353
+ reason: "union branches must share a literal discriminator field"
183354
+ };
183355
+ }
183356
+ for (const [field, fieldSchema] of Object.entries(shape.properties)) {
183357
+ const occurrences = shapes.filter((branch) => (field in branch.properties));
183358
+ if (occurrences.length === shapes.length && occurrences.every((branch) => branch.required.has(field))) {
183359
+ shape.required.add(field);
183360
+ }
183361
+ if (field === discriminator) {
183362
+ const values = shapes.map((branch) => branch.properties[field]?.const);
183363
+ fieldSchema.description = `One of: ${values.join(", ")}`;
183364
+ }
183365
+ }
183366
+ return { ok: true, shape };
183367
+ }
183368
+ function toObjectShape(schema) {
183369
+ if (schema.allOf)
183370
+ return mergeIntersection(schema.allOf);
183371
+ const branches = schema.oneOf ?? schema.anyOf;
183372
+ if (branches)
183373
+ return mergeUnion(branches);
183374
+ const shape = asObjectShape(schema);
183375
+ if (shape)
183376
+ return { ok: true, shape };
183377
+ return {
183378
+ ok: false,
183379
+ field: "",
183380
+ reason: "contract input must be an object schema"
183381
+ };
183382
+ }
183383
+
183384
+ // src/core/publicApi/flagSpecs.ts
183385
+ var kebabCase = (field) => field.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`);
182857
183386
  function flagUsage(field, kind) {
182858
183387
  const name = `--${kebabCase(field)}`;
182859
183388
  if (kind === "boolean")
@@ -182868,16 +183397,11 @@ function buildFlagSpecs(inputSchema) {
182868
183397
  const jsonSchema = exports_external.toJSONSchema(inputSchema, {
182869
183398
  io: "input"
182870
183399
  });
182871
- if (jsonSchema.type !== "object" || !jsonSchema.properties) {
182872
- return {
182873
- ok: false,
182874
- field: "",
182875
- reason: "contract input must be an object schema"
182876
- };
182877
- }
182878
- const required2 = new Set(jsonSchema.required ?? []);
183400
+ const result = toObjectShape(jsonSchema);
183401
+ if (!result.ok)
183402
+ return result;
182879
183403
  const flags = [];
182880
- for (const [field, fieldSchema] of Object.entries(jsonSchema.properties)) {
183404
+ for (const [field, fieldSchema] of Object.entries(result.shape.properties)) {
182881
183405
  const kind = flagKind(fieldSchema);
182882
183406
  if (!kind.ok)
182883
183407
  return { ok: false, field, reason: kind.reason };
@@ -182885,7 +183409,7 @@ function buildFlagSpecs(inputSchema) {
182885
183409
  field,
182886
183410
  flag: flagUsage(field, kind.kind),
182887
183411
  description: fieldSchema.description ?? "",
182888
- required: required2.has(field),
183412
+ required: result.shape.required.has(field),
182889
183413
  kind: kind.kind
182890
183414
  });
182891
183415
  }
@@ -183063,4 +183587,4 @@ createProgram({ signals }).parseAsync().catch(() => {
183063
183587
  process.exitCode = 1;
183064
183588
  }).finally(() => flushAndExit(typeof process.exitCode === "number" ? process.exitCode : 0));
183065
183589
 
183066
- //# debugId=64AB4032821CC8E864756E2164756E21
183590
+ //# debugId=F2AE77060354D60864756E2164756E21